vail

Internet morse code repeater
git clone https://git.woozle.org/neale/vail.git

vail / static / scripts
Neale Pickett  ·  2023-01-17

chart.mjs

  1import * as time from "./time.mjs"
  2
  3 /**
  4  * A chart of historical values.
  5  *
  6  * Curently this assumes all values are between 0 and 1,
  7  * since that's all I need.
  8  */
  9class HistoryChart {
 10    /**
 11     * @param {Element} canvas Canvas element to draw on
 12     * @param {string} style style to draw in; falls back to the `data-style` attribute
 13     * @param {Duration} duration Time to display history for
 14     */
 15    constructor(canvas, style=null, duration=20*time.Second) {
 16        this.canvas = canvas
 17        this.ctx = canvas.getContext("2d")
 18        this.duration = duration
 19
 20        this.data = []
 21
 22        // One canvas pixel = 20ms
 23        canvas.width = duration / (20 * time.Millisecond)
 24
 25        // Set origin to lower-left corner
 26        this.ctx.scale(1, -1)
 27        this.ctx.translate(0, -canvas.height)
 28
 29        this.ctx.fillStyle = style || canvas.dataset.color || "black"
 30        this.ctx.lineWdith = 2
 31
 32        this.running=true
 33        this.draw()
 34    }
 35
 36    /**
 37     * Set value to something at the current time.
 38     *
 39     * This also cleans up the event list,
 40     * purging anything that is too old to be displayed.
 41     *
 42     * @param {Number} value Value for the event
 43     */
 44     Set(value) {
 45        this.SetAt(value)
 46    }
 47
 48    /**
 49     * Set value to something at the provided time.
 50     *
 51     * This also cleans up the event list,
 52     * purging anything that is too old to be displayed.
 53     *
 54     * @param {Number} value Value for the event
 55     * @param {Number} when Time to set the value
 56     */
 57     SetAt(value, when=null) {
 58        let now = Date.now()
 59        if (!when) when=now
 60
 61        this.data.push([when, value])
 62        this.data.sort()
 63
 64        let earliest = now - this.duration
 65        // Leave one old datapoint so we know the value when the window opens
 66        while ((this.data.length > 1) && (this.data[1][0] < earliest)) {
 67            this.data.shift()
 68        }
 69    }
 70
 71    Stop() {
 72        this.running = false
 73    }
 74
 75    draw() {
 76        let now = Date.now()
 77        let earliest = now - this.duration
 78        let xScale = this.canvas.width / this.duration
 79        let yScale = this.canvas.height
 80        let y = 0
 81        let x = 0
 82
 83        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
 84
 85        for (let point of this.data) {
 86            let x2 = (point[0] - earliest) * xScale
 87            let y2 = point[1] * yScale
 88
 89            if (y > 0) {
 90                this.ctx.fillRect(x, 0, x2-x, y)
 91            }
 92
 93            x=x2
 94            y=y2
 95        }
 96        if (y > 0) {
 97            this.ctx.fillRect(x, 0, this.canvas.width, y)
 98        }
 99
100        if (this.running) {
101            requestAnimationFrame(() => this.draw())
102        }
103    }
104}
105
106/**
107 * Return a new chart based on an HTML selector
108 * 
109 * @param selector HTML selector
110 * @param style fill style
111 * @param duration duration of chart window
112 * @returns new chart, or null if it couldn't find a canvas
113 */
114function FromSelector(selector, style, duration=20*time.Second) {
115    let canvas = document.querySelector(selector)
116    if (canvas) {
117        return new HistoryChart(canvas, style, duration)
118    }
119    return null
120}
121export {HistoryChart, FromSelector}