snaplot
v0.12.0
Published
High-performance charts that actually look good
Downloads
664
Maintainers
Readme
snaplot
A canvas chart library built for streaming data and interactive dashboards.
Documentation · Live demos · npm
snaplot renders line, area, scatter, bar, histogram, and band charts from columnar Float64Arrays. It handles 200,000+ points at 60 fps through a layered canvas pipeline, binary-search viewport culling, and ring-buffer streaming updates. The library never mutates your input arrays.
The root package entry is framework-free. SolidJS components and reactive primitives ship from snaplot/solid.
Install
npm install snaplotSolid users also install solid-js ^1.9.0; framework-free ChartCore users do not need it at runtime.
Quick start
import { Chart } from 'snaplot/solid';
import type { ColumnarData } from 'snaplot';
const data: ColumnarData = [
new Float64Array(timestamps), // finite X values, sorted ascending
new Float64Array(cpu), // Y series 1
new Float64Array(memory), // Y series 2
];
<Chart
config={{
axes: { x: { type: 'time' }, y: { type: 'linear' } },
series: [
{ label: 'CPU', dataIndex: 1, type: 'line' },
{ label: 'Memory', dataIndex: 2, type: 'area' },
],
}}
data={data}
/>Column 0 is the default sorted X column. X values must be finite and non-decreasing, and every column must have equal length. Columns 1+ are value columns referenced by dataIndex; NaN in Y columns creates gaps and non-finite Y values are ignored by range calculations. Scatter series can use explicit xDataIndex and yDataIndex columns for visual position, but shared cursor/viewport operations still depend on the sorted default X column unless you opt into nearest-point scatter behavior.
Full API reference and runnable examples are in the documentation.
Features
Chart types
Line (linear, monotone, stepped interpolation), area with gradient fill, scatter with arbitrary X columns, colour/size encodings, box selection, and density rendering, grouped bar, histogram via the included histogram() utility (Freedman-Diaconis, Sturges, Scott), and band (fill between upper and lower series).
Scatter series can use xDataIndex/yDataIndex for tabular coordinates, colorBy for categorical/continuous/diverging colour encodings, sizeBy for radius encodings, tooltipFields for extra nearest-point tooltip rows, and renderMode: 'density' for aggregate heatmaps.
Interactions
Drag-to-zoom, wheel and pinch zoom at the cursor, pan on shift-drag, opt-in axis-gutter controls, double-click reset, proximity tooltips in three modes (index, nearest, x), keyboard pan and zoom, and touch gestures tuned for cursor-first mobile use.
Streaming
setData replaces the current window. appendData extends it, and config.streaming.maxLen enables fixed-window ring-buffer retention. Both update at 60 fps on realistic dashboard datasets.
Cursor-synced legend table
A table that shows the value of every visible series at the cursor position. Available as a DOM plugin (createLegendTablePlugin) or a SolidJS component (LegendTable) with typed column helpers (nameColumn, valueColumn, metricColumn, swatchColumn).
For compact card headers, Solid also exports <SeriesLegend chart={chart} />.
It inherits the resolved chart theme, toggles/highlights series accessibly, and
uses geometry-aware marks. Framework-free renderers can consume the same
resolved model through chart.getLegendItems().
Cross-chart sync
createChartGroup() keeps cursor and highlight state in sync across any number of charts. External controls can drive the highlight too, for UIs like sidebars or experiment lists. Index-based highlight sync is safe only when peer charts share identical series order; for experiment dashboards with subsets or reordered runs, configure highlight.getKey and drive focus with chart.setHighlightKey() or group.highlightKey().
Themes
Built-in themes include lightTheme, darkTheme, studioTheme, tokyoTheme, oceanTheme, forestTheme, violetTheme, fogTheme, ivoryTheme, mintTheme, sunsetTheme, midnightTheme, marsTheme, and refinedDarkTheme. Themes support role-aware palettes: palette remains the fallback series cycle, categoricalPalette drives unordered series, and sequentialPalette/heatmapGradient drive density heatmaps. CSS variable overrides and full custom ThemeConfig objects are supported. createTheme({ base, surface, text, muted, accent, categorical }) expands semantic application tokens into the detailed canvas roles, while backgroundColor: 'container' copies the nearest opaque container surface into the canvas without relying on unsupported alpha compositing.
Plugins
Simple lifecycle hooks: install, destroy, before/after each canvas layer, onCursorMove, onZoom, onClick, onSetData, onSetOptions. The built-in legend, legend-table, reference-line, and reference-region plugins use the same surface, and custom plugins can register at construction or via chart.use(plugin).
TypeScript
Strict types throughout, generic column helpers, no any in the public API.
Non-goals
snaplot stays a small library for streaming data and interactive dashboards. Out of scope, by policy: stacked series, animations and transitions beyond hover and highlight feedback, OHLC/candlestick chart types, ordinal/equi-spaced X distributions, a scale plugin system, and data munging beyond the exported utilities. Plugins and userland code can build these; keeping them out of core is what keeps the core fast and the API stable.
Performance notes
- 200K+ point charts render well under one frame on a modern laptop.
- Grid, data, and overlay are separate canvases. A cursor move repaints the overlay only. A data update skips the grid layer when the scales are unchanged.
- Viewport culling is a binary search on the X column for sorted series; scatter nearest-hit testing uses a cached screen-space grid for dense clouds and arbitrary X columns.
- While a pan or zoom gesture is in motion, very large series draw a reduced-fidelity pass (stride-sampled scatter, shape-preserving M4 for lines) and repaint at full fidelity on settle; see
performance.interactionSampling. - snaplot never mutates input arrays. Downsampling helpers (
lttb,m4) are exported as opt-in utilities. - Legend table updates use
textContentswaps in the cursor hot path, notinnerHTMLrebuilds. - The
bench/workspace reproduces all of this under a CPU-throttled mobile profile; see bench/README.md for reference numbers and the CI regression guard.
Development
npm install # installs all workspaces plus git hooks
npm run dev # docs site dev server with live HMR against the library
npm run build # library production build
npm run build:site # docs site production build
npm test # vitest
npm run check # biome lint/config checks
npm run typecheck # library + site tsc --noEmitSee CONTRIBUTING.md for the full workflow, conventions, and release process.
License
MIT
