Appearance
Zoom and pan
Linked / synchronised charts
The calls
plot.setChartView(id: ChartHandle, x: readonly [number, number] | null, y: readonly [number, number] | null): voidShow a range on chart
id.nullhands that axis back to the document.Zoom and pan need none of this -- the plot drives them from the pointer. This is for what only a page can decide:
Throws on a range whose low end is not below its high end, rather than quietly swapping them: a reversed range usually means two variables the wrong way round, and silently fixing it hides that.
plot.chartView(id: ChartHandle): { x: [number, number] | null; y: [number, number] | null; }What chart
idis showing, as[lo, hi]per axis.nullon an axis the document still decides, which is what a chart nobody has zoomed reports for both. That is not "showing nothing": an auto axis goes on fitting its data every frame.idis the handlesetChartsreturned for the chart.plot.resetChartView(id: ChartHandle): voidHand chart
id's axes back to the document.The programmatic form of the gesture a viewer already has: a double click anywhere on a chart does this, and does it even when the data has been left off screen, which is the case it exists for. A page needs this only for a control of its own, or to reset a chart the pointer is not over.
Back to the document rather than to a remembered range. An axis the document leaves alone goes on fitting its data, so a live chart resumes following it instead of freezing at whatever it showed when the view was taken.
const count = 1500;
const x = plot.uploadBuffer(Float64Array.from({ length: count }, (_, i) => i / 10));
const column = (f) => plot.uploadBuffer(Float32Array.from({ length: count }, (_, i) => f(i / 10)));
const pressure = column((t) => 4 + Math.sin(t / 6) + 0.3 * Math.sin(t * 1.3));
const flow = column((t) => 2 + Math.cos(t / 6) * Math.exp(-t / 120));
// A handle per chart, in the order the document lists them.
const [upper, lower] = plot.setCharts({
charts: [
{ series: [{ x, y: pressure, label: "pressure" }], title: "Drag me" },
{ series: [{ x, y: flow, label: "flow" }], title: "I follow" },
],
grid: { columns: 1 },
});
return {
// What `plot.events()` returned for a frame.
events(list) {
for (const event of list) {
if (event.type !== "viewChanged" || event.chart !== upper) continue;
// Null means the upper chart went back to fitting its data, and
// then so does the lower one.
if (event.x === null) plot.resetChartView(lower);
else plot.setChartView(lower, event.x, null);
}
},
pointer() {
const view = plot.chartView(lower);
return view.x === null ? "lower chart: fitting its data" : `lower chart: x from ${fixed(view.x[0])} to ${fixed(view.x[1])}`;
},
};Also called
3 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.plot.events(): PlotEvent[]What the charts reported on the last frame.
Draining: a frame's events are handed over once. Calling this every frame reads what just happened; not calling it accumulates nothing, because each frame replaces the set.