Guides
Multi-chart dashboards
Multi-chart boards on a shared GPUDevice: appendData with maxPoints, thresholds and incident annotations, stream sampling, and a production checklist.
Shared GPU across charts
Create adapter, device, and pipeline cache once. Pass the same context into each ChartGPU.create. Charts do not destroy a shared device on dispose. Prefer this for three or more charts on one page.
import { ChartGPU, createPipelineCache, connectCharts } from '@chartgpu/chartgpu';
const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });
if (!adapter) throw new Error('No GPU adapter');
const device = await adapter.requestDevice();
const pipelineCache = createPipelineCache(device);
const shared = { adapter, device, pipelineCache };
const a = await ChartGPU.create(elA, optsA, shared);
const b = await ChartGPU.create(elB, optsB, shared);
const c = await ChartGPU.create(elC, optsC, shared);
connectCharts([a, b, c], { syncZoom: true });
appendData for the hot path
Prefer chart.appendData(seriesIndex, points, { maxPoints }) on every streaming series. Bound history with maxPoints so memory and GPU uploads stay capped. Do not rebuild the full options object every tick.
// One window per tick (column or tuple data both work)
chart.appendData(0, [[t, y0]], { maxPoints: 2_400 });
chart.appendData(1, [[t, y1]], { maxPoints: 2_400 });
Sampling on streams
For high-rate line series, set sampling: 'none' so appends stay on the incremental path. Product-level sampling (how often you score or poll upstream) is separate from chart sampling: sample upstream if needed; stream every retained window to the chart.
Thresholds as lineY
Static reference lines (SLO floors/ceilings) belong in annotations once at create time, not every tick.
annotations: [{
id: 'slo-max',
type: 'lineY',
y: 2, // domain units (e.g. percent)
layer: 'belowSeries',
style: { color: '#F0A202', lineDash: [6, 5], lineWidth: 2 },
label: { text: 'target <2%' },
}]
Incident annotations
Annotate discrete events only. Spread chart.options when calling setOption so series identity and append buffers stay intact (setOption replaces the full user option object).
if (incident) {
annotations = [...annotations, {
id: `${incident}-${t}`,
type: 'lineX',
x: t,
style: { color: '#E05A8C', lineWidth: 2 },
label: { text: incident, offset: [6, -10], background: { color: '#0a0b0e', opacity: 0.75 } },
}].slice(-12);
chart.setOption({ ...chart.options, annotations });
}
Example: multi-chart streaming board
Four charts on one shared device and pipeline cache. This demo uses online-eval-style metrics (quality, error rate, tool health, latency/cost). Controls stress the series and incident markers; follow-live pins the edge of each stream.
What this example’s series represent
- Generation quality — dual line (e.g. faithfulness / relevance) with a floor threshold
- Error / hallucination rate — percent series with a ceiling
lineY - Tool health — selection accuracy + execution success
- SLOs — p99 latency and cost/query with sparse
lineXincidents
The domain is optional; the patterns above apply to any multi-chart board.
Full hosted demo
A five-chart dashboard lives at the streaming dashboard demo.
Production checklist
- Dispose every chart on unmount; destroy the shared device when the board goes away
- Resize via
chart.resize()(coalesced ResizeObserver) - Disable decorative animation on high-frequency streams
- Annotate only discrete incidents; keep SLO thresholds as stable
lineY - Always spread
chart.optionswhen callingsetOptionso series identity is preserved - Feature-detect WebGPU and degrade gracefully
Related API
- Streaming API —
appendData,maxPoints - Chart API —
create, pipeline cache,connectCharts - Options —
autoScroll, data zoom, series sampling