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}