mirror of https://github.com/nealey/vail.git
124 lines
3.2 KiB
JavaScript
124 lines
3.2 KiB
JavaScript
/** @typedef {Number} Duration */
|
|
const Millisecond = 1
|
|
const Second = 1000 * Millisecond
|
|
|
|
/**
|
|
* A chart of historical values.
|
|
*
|
|
* Curently this assumes all values are between 0 and 1,
|
|
* since that's all I need.
|
|
*/
|
|
class HistoryChart {
|
|
/**
|
|
* @param {Element} canvas Canvas element to draw on
|
|
* @param {string} style style to draw in; falls back to the `data-style` attribute
|
|
* @param {Duration} duration Time to display history for
|
|
*/
|
|
constructor(canvas, style=null, duration=20*Second) {
|
|
this.canvas = canvas
|
|
this.ctx = canvas.getContext("2d")
|
|
this.duration = duration
|
|
|
|
this.data = []
|
|
|
|
// One canvas pixel = 20ms
|
|
canvas.width = duration / (20 * Millisecond)
|
|
|
|
// Set origin to lower-left corner
|
|
this.ctx.scale(1, -1)
|
|
this.ctx.translate(0, -canvas.height)
|
|
|
|
this.ctx.fillStyle = style || canvas.dataset.color || "black"
|
|
this.ctx.lineWdith = 2
|
|
|
|
this.running=true
|
|
this.draw()
|
|
}
|
|
|
|
/**
|
|
* Set value to something at the current time.
|
|
*
|
|
* This also cleans up the event list,
|
|
* purging anything that is too old to be displayed.
|
|
*
|
|
* @param {Number} value Value for the event
|
|
*/
|
|
Set(value) {
|
|
this.SetAt(value)
|
|
}
|
|
|
|
/**
|
|
* Set value to something at the provided time.
|
|
*
|
|
* This also cleans up the event list,
|
|
* purging anything that is too old to be displayed.
|
|
*
|
|
* @param {Number} value Value for the event
|
|
* @param {Number} when Time to set the value
|
|
*/
|
|
SetAt(value, when=null) {
|
|
let now = Date.now()
|
|
if (!when) when=now
|
|
|
|
this.data.push([now, value])
|
|
this.data.sort()
|
|
|
|
let earliest = now - this.duration
|
|
// Leave one old datapoint so we know the value when the window opens
|
|
while ((this.data.length > 1) && (this.data[1][0] < earliest)) {
|
|
this.data.shift()
|
|
}
|
|
}
|
|
|
|
Stop() {
|
|
this.running = false
|
|
}
|
|
|
|
draw() {
|
|
let now = Date.now()
|
|
let earliest = now - this.duration
|
|
let xScale = this.canvas.width / this.duration
|
|
let yScale = this.canvas.height * 0.95
|
|
let y = 0
|
|
let x = 0
|
|
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
|
|
|
for (let point of this.data) {
|
|
let x2 = (point[0] - earliest) * xScale
|
|
let y2 = point[1] * yScale
|
|
|
|
if (y > 0) {
|
|
this.ctx.fillRect(x, 0, x2-x, y)
|
|
}
|
|
|
|
x=x2
|
|
y=y2
|
|
}
|
|
if (y > 0) {
|
|
this.ctx.fillRect(x, 0, this.canvas.width, y)
|
|
}
|
|
|
|
if (this.running) {
|
|
requestAnimationFrame(() => this.draw())
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a new chart based on an HTML selector
|
|
*
|
|
* @param selector HTML selector
|
|
* @param style fill style
|
|
* @param duration duration of chart window
|
|
* @returns new chart, or null if it couldn't find a canvas
|
|
*/
|
|
function FromSelector(selector, style, duration=20*Second) {
|
|
let canvas = document.querySelector(selector)
|
|
if (canvas) {
|
|
return new HistoryChart(canvas, style, duration)
|
|
}
|
|
return null
|
|
}
|
|
export {HistoryChart, FromSelector}
|