#lang racket
(require 2htdp/universe 2htdp/image (prefix-in p: plot) db)
(provide (all-from-out plot))
(provide (contract-out [live-graph (-> (or/c string? (listof string?)) (or/c (-> number?) (-> string? number?)) number? string? any/c)]
[live-dashboard (-> (listof (listof string?))
(listof (listof (-> number?)))
number?
string?
any/c)]
[struct db-struct ((connection connection?)
(table-name string?)
(field-label string?)
(field-date-time string?)
(field-value string?))]
[insert-into-db (-> db-struct? string? exact-integer? number? void?)]
[error-value parameter?]
[new-value-event parameter?]
[delta-x parameter?] [delta-y parameter?]
[y-min parameter?] [y-max parameter?])
live-dashboard-one-ft)
(define error-value (make-parameter 0))
(define new-value-event (make-parameter (lambda (label seconds value) '())))
(define delta-x (make-parameter 3600)) (define delta-y (make-parameter 0.02)) (define y-min (make-parameter 0))
(define y-max (make-parameter 100))
(struct world (labels fts values))
(struct db-struct (connection table-name field-label field-date-time field-value))
(define date-begin (current-seconds))
(define date-end (+ date-begin (delta-x)))
(define (insert-into-db db label seconds value)
(let ([t (seconds->date seconds)])
(query-exec (db-struct-connection db)
(string-append "insert into "
(db-struct-table-name db) "("
(db-struct-field-label db) ","
(db-struct-field-date-time db) ","
(db-struct-field-value db) ") values ('"
label "','"
(number->string (date-year t)) "-"
(number->string (date-month t)) "-"
(number->string (date-day t)) " "
(number->string (date-hour t)) ":"
(number->string (date-minute t)) ":"
(number->string (date-second t)) "',"
(number->string value) ")"))))
(define (new-data label ft data)
(local [(define d (ft))
(define evt (new-value-event))
(define t (current-seconds))]
(if (= d (error-value))
data
(begin
(evt label t d)
(cons (vector t d) data)))))
(define (new-data-h labels fts values)
(map new-data labels fts values))
(define (tick w)
(world (world-labels w)
(world-fts w)
(map new-data-h (world-labels w) (world-fts w) (world-values w))))
(define (graph data label)
(local ([define t (current-seconds)]
[define x0 (if (< t (+ date-begin (delta-x)))
date-begin
(+ (- t date-begin (delta-x)) date-begin))]
[define y0 (abs (vector-ref (first data) 1))])
(parameterize ([p:plot-x-ticks (p:time-ticks #:formats '("~H:~M"))])
(p:plot (p:lines data)
#:x-min x0 #:x-max (+ x0 (delta-x))
#:y-min (if (= y0 0)
-1
(- y0 (* y0 (delta-y))))
#:y-max (if (= y0 0)
1
(+ y0 (* y0 (delta-y))))
#:title label #:x-label "" #:y-label ""))))
(define (render-h labels values)
(if (empty? (rest labels))
(graph (first values) (first labels))
(apply beside (map graph values labels))))
(define (render w)
(if (empty? (rest (world-labels w)))
(render-h (first (world-labels w)) (first (world-values w)))
(apply above (map render-h (world-labels w) (world-values w)))))
(define (init-values-h fts-h)
(map (lambda (q) (list (vector (- (current-seconds) date-begin) (q))))
fts-h))
(define (live-graph label ft rate title)
(if (list? label)
(live-plot-series label ft rate title)
(live-dashboard (list (list label)) (list (list ft)) rate title)))
(define (live-dashboard labels fts rate title)
(big-bang (world labels
fts
(map init-values-h fts))
(on-tick tick rate)
(to-draw render)
(name title)))
(define-syntax-rule (live-dashboard-one-ft labels ft rate title)
(live-dashboard labels
(map (lambda (l)
(map (lambda (s)
(lambda () (ft s)))
l))
labels)
rate
title))
(define (new-data-serie label ft data)
(local [(define d (ft label))
(define evt (new-value-event))
(define t (current-seconds))]
(if (= d (error-value))
data
(begin
(evt label t d)
(cons (vector t d) data)))))
(define (tick-series w)
(world (world-labels w)
(world-fts w)
(map new-data-serie
(world-labels w) (world-fts w) (world-values w))))
(define (render-series w)
(local ([define t (current-seconds)]
[define x0 (if (< t (+ date-begin (delta-x)))
date-begin
(+ (- t date-begin (delta-x)) date-begin))]
[define last-values (map (lambda (value) (vector-ref (first value) 1)) (world-values w))]
[define y1-min (apply min last-values)]
[define y1-max (apply max last-values)]
[define y0-min (if (< y1-min (y-min))
(- y1-min (* (y-max) (delta-y)))
(y-min))]
[define y0-max (if (> y1-max (y-max))
(+ y1-max (* (y-max) (delta-y)))
(y-max))])
(parameterize ([p:plot-x-ticks (p:time-ticks #:formats '("~H:~M"))])
(p:plot (map (lambda (l x c) (p:lines x #:label l #:color c))
(world-labels w)
(world-values w)
(build-list (length (world-labels w)) values))
#:x-min x0 #:x-max (+ x0 (delta-x))
#:y-min y0-min #:y-max y0-max
#:x-label "" #:y-label ""))))
(define (init-values-series labels ft)
(map (lambda (label) (list (vector (- (current-seconds) date-begin) (ft label))))
labels))
(define (live-plot-series labels ft rate title)
(big-bang (world labels
(build-list (length labels) (lambda (i) ft))
(init-values-series labels ft))
(on-tick tick-series rate)
(to-draw render-series)
(name title)))