Appearance
Data and streaming
Derived-series filters API
The calls
movingAverage(values: ArrayLike<number>, window: number): Float32ArrayThe mean of each point and the
window - 1points before it.The usual way to see the shape of a noisy signal. The first
window - 1points areNaN: a trailing average genuinely has no value before it has a window to average, and reporting the partial mean there would draw a curve that settles for reasons that are about the filter rather than the data.Computed as a running sum rather than a sum per point, so the cost is the length of the series and not the length times the window.
difference(values: ArrayLike<number>, lag?: number): Float32ArrayThe difference from
lagpoints earlier, in the series' own units.What
percentChangeis when the quantity has a meaningful zero or can cross it: a temperature that goes from -1 to 1 has changed by two degrees and by no sensible percentage.percentChange(values: ArrayLike<number>, lag?: number): Float32ArrayThe change from
lagpoints earlier, as a percentage of that earlier value.The first
lagpoints areNaN, and so is any point whose earlier value was zero: a change from nothing is not a percentage of anything, and reporting it as a very large number would put a spike on the chart that says more about the divisor than about the data.derive(values: ArrayLike<number>, f: (value: number, index: number) => number): Float32ArrayA filter of the caller's own, applied point by point.
Here so that writing one does not mean writing the typed-array plumbing around it.
fis handed each value and its index and returns the derived value, orNaNwhere it has nothing to say.
const raw = Float32Array.from({ length: 800 }, (_, i) => 12 + 3 * signal(i));
const derived = {
// The mean of each point and the 39 before it.
movingAverage: () => movingAverage(raw, 40),
// The change over ten points, in the series' own units.
difference: () => difference(raw, 10),
// The same change as a percentage of the earlier value.
percentChange: () => percentChange(raw, 10),
// A filter of the page's own: degrees Celsius to Fahrenheit.
derive: () => derive(raw, (celsius) => (celsius * 9) / 5 + 32),
}[filter]();
const x = plot.uploadBuffer(Float64Array.from(raw, (_, i) => i));
plot.setCharts({
charts: [
{ series: [{ x, y: plot.uploadBuffer(raw), label: "raw" }] },
{ series: [{ x, y: plot.uploadBuffer(derived), label: filter }] },
],
grid: { columns: 1 },
});Also called
2 in the code above, in the order it makes them
plot.uploadBuffer(values: Float32Array | Float64Array): BufferHandleUpload a block of scalars and return a handle naming it.
A buffer keeps the precision it arrived in. Use
Float64Arrayfor time and for anything wheref32's twenty-four bits of mantissa would lose something real: a millisecond timestamp narrowed tof32rounds every sample into a five-minute bucket, and it does that silently.plot.setCharts(document: ChartDocument): readonly ChartHandle[]Draw a document of charts, and get a handle for each one.
The handles come back in document order and are what names a chart afterwards:
chartView,setChartView,chartPicked, and thecharton a hover, a hit or any other event. A page linking two charts holds two handles rather than allocating ids.Replaces the charts and the series each one reads.
Throws rather than drawing a partial document, and a throw leaves the previous document in place. What it refuses: a misspelled key, a handle that names something other than a buffer, a range whose ends are the wrong way round, and a column on a chart kind that cannot read it. All four would otherwise be a chart that draws something subtly wrong, or nothing, with nothing said.
Setting a document again is the way to change one: there is no per-chart edit, and the compile is cheap because the data does not move. The buffers are named by handle, so a live chart writes to them with
writeBufferand never sets a document at all.