Skip to main content

@pond-ts/charts

Declarative, canvas-rendered time-series charts for React. Compose a chart from components the way you'd compose any other UI. It draws to a canvas rather than the DOM, so point count is a rendering cost and not a node count. And it reads a pond TimeSeries directly, so rolling, aggregation, alignment and gap handling all run right up to the plot — no adapter layer, and no second data model to keep in step.

Two days of household electricity demand at one-minute resolution — 2,880 samples, every one of them a point on the canvas. Quiet overnight, a sharp breakfast peak, a long cooking-hours evening. Move the pointer and a staff rises to a flag at each layer's value there; drag to pan, wheel to zoom, both stopped at the ends of the record. It's live, not a screenshot.

One column is doing all of that. kw is the only data. Demand at this resolution isn't a smooth signal with noise on it — it's a sum of rectangles, because a kettle is 2.6 kW for three minutes and nothing in between. So the raw minutes get two layers: a scatter for where the samples actually are, and a faint line joining them. The vertical strokes a per-minute polyline is mostly made of are the reading here — each one is something switching on, and the flats between them are the rectangles it draws. Over the top, the pale envelope is each hour's min–max and the line through it is the hour's mean. Neither is a second dataset prepared somewhere else; they're a single pond rolling() call, emitting three columns from the one source column and handing them to two more draw layers:

const rolled = demand.rolling(
'1h',
{
mean: { from: 'kw', using: 'avg' },
lo: { from: 'kw', using: 'min' },
hi: { from: 'kw', using: 'max' },
},
{ alignment: 'centered' },
);

That's the entire pitch: a chart consumes a pond TimeSeries directly, the transform pipeline runs right up to the plot, you compose a container, rows, and draw layers as React components, and the canvas renderer does the drawing. No chart-library data model to adapt to; no imperative escape hatch for interaction.

The break on the first afternoon is a ninety-minute recorder outage, carried as undefined rather than zero. The scatter has no points there and every line breaks — a connecting line is exactly the layer that would otherwise rule a confident straight edge across an hour and a half nobody measured, and zero would draw a switched-off house that never happened. That distinction is the whole of Missing data & gaps.

The demand is modelled, not measured. It's shaped after the UCI Individual household electric power consumption set (CC BY 4.0) — one house, one-minute active power in kW, 2,075,259 rows, ~1.25% of them missing — but none of its rows are redistributed here. The numbers are generated from how domestic load forms: a standby floor, a fridge duty-cycling against it, and discrete appliance runs on top. See the fixture.

Installnpm install @pond-ts/charts pond-ts @pond-ts/react react react-dom. Peer-depends on pond-ts, @pond-ts/react, and react; the pond packages release together, so keep their ranges in step. See Using @pond-ts/charts for the two integration gotchas (Storybook react-docgen, the repaint contract).

The layout model

Charts are composed, not configured — the same nesting react-timeseries-charts users will recognise:

  • <ChartContainer> — owns the shared x axis (time or value, inferred from the data), the view range, overall width, theme, and cursor mode.
  • <ChartRow> — one stacked plot band with its own y axis / height. A container holds one or more rows sharing the x axis.
  • <Layers> — the mandatory z-stack inside a row; children draw back-to-front in declaration order.
  • <YAxis> / <XAxis> / <TimeAxis> — placeable axes. id links a draw layer's axis to a scale; side places it; format (a d3 specifier or a function) formats ticks and the matching cursor readout.

The minimal API set — compact cut

The primitives you compose a chart from. The API reference has every prop, type, and default; this is the map.

Layout & axes

ExportWhat it is
ChartContainerRoot: shared x axis, range, width, theme, cursor.
ChartRowA stacked plot band with its own y axis and height.
LayersThe z-stack boundary inside a row.
YAxisA y axis: id, side, auto-fit or explicit domain, format.
XAxisA placeable x axis (time or value), custom ticks.
TimeAxisA thin <XAxis> preset for the time axis.
CategoryAxisA thin <XAxis> preset for the ordinal category axis.
CanvasThe low-level DPR-aware canvas primitive rows draw on.

Draw layers

ExportWhat it draws
LineChartA gap-aware line.
AreaChartA filled area.
BandChartA filled lower/upper envelope (variance band).
ScatterChartPoints, with data-driven radius / colour encoding.
BarChartBars, histograms — stacked (columns / a Map / bins) and horizontal (guide) — and categorical bars (categories, a bar per category on an ordinal axis; guide).
BoxPlotBox-and-whisker per bucket (shape: whisker / solid / none) from five pre-computed quantile columns.
CandlestickFirst-class OHLC candles (variant: candle / bar / hollow; colorBy; optional showOHLC axis pills). Pairs with the trading-time axis (calendar on ChartContainer).

Every draw layer takes a pond series + a column, an as style identifier (theme lookup), and an axis scale id — style and scale are separate channels by design (no per-component colour/width props).

Row lists (standalone — no ChartContainer)

ExportWhat it is
BarListA ranked row table — one proportional bar line per column per entity, data cells, sort, expander (guide).
BoxListIts distribution sister — a five-number box per line plus a current-value tick (the traffic-by-interface table; guide).

One row per entity rather than per bucket. A per-event series feeds directly (<BarList series={splits} label={…}> — one row per event, no shaping step), and partition facts (partitionBy + reduce) spread straight into a row's values — see the acquisition recipes.

Annotations & indicators

ExportWhat it is
RegionA shaded x-range (annotation register — never a data hue).
BaselineA horizontal value line, optional axis-pill indicator.
MarkerA vertical x line, optional axis-pill indicator.
YAxisIndicatorA live value pill pinned to a y-axis edge.
createLiveValueHigh-frequency pill updates with an isolated repaint.

View builders & theming

With a pond series, the components above are the whole data contract — the from* exports below expose the chart-ready view shapes for consumers writing custom draw code; no shipped layer needs them.

ExportWhat it is
fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeries, ohlcFromTimeSeriesBuild the chart-ready view shapes for custom draw code (no shipped layer needs them).
stacksFromGroups, stacksFromColumns, stacksFromBins, categoryStack, transposeRowStacked / histogram / categorical view builders (bins and categories inputs also accept their sources directly).
defaultThemeThe neutral built-in ChartTheme.
cssVarTheme, useChartThemeBuild a theme from CSS custom properties and follow a dark/light toggle.

Why @pond-ts/charts — and when not to use it

The pitch is narrow on purpose: canvas rendering, streaming-first (a LiveSeries re-render is the same code path as a batch one — no separate "live mode" API), and the pond transform pipeline right up to the plot (rolling, aggregation, gap-fill all feed the same layer props a batch chart uses). If your charts are static SVG dashboards over plain arrays, that's real machinery you don't need — the bridge page covers exporting pond data to Recharts instead.

Some honest constraints, before you commit: you adopt the pond TimeSeries data model end to end (not a drop-in over arbitrary JSON); width is an explicit pixel number, not "100%" — the responsive-width recipe is one ResizeObserver away, and it's what the Gallery's own cards run on; interaction is mouse-only (no keyboard/touch cursor or selection yet); and the package is pre-1.0 — the API is stabilizing, not frozen. None of that is hidden: the Storybook and the Gallery show exactly what ships today, working, not a mockup.

Used in production for activity dashboards and ops telemetry today, with a financial-charts build actively underway. See the CHANGELOG for what's shipped in each release — the whole monorepo tags and publishes together.

Where to go next

  • Learn charts — new to the library? Start here: a nine-chapter tutorial track, one running example, a live chart in every chapter.
  • Gallery — eight live charts spanning every draw layer, each linking its full Storybook coverage. Touch one before you read another word of API reference.
  • Value axis — the chart-level reference for plotting against something other than time: axis-kind inference, byColumn histograms, category axes, and dual x-axes.
  • Storybook — every component and interaction mode, one story per prop/state: the systematic, API-adjacent knob walk.
  • API reference — the in-site, full-width reference: every component, prop, and type.
  • Using @pond-ts/charts — install notes and integration gotchas.
  • Theming — the ChartTheme model and the CSS-token bridge.
  • Resizable multi-panel layout — a full chart layout worked end to end.
note

The docs here are an introduction plus the generated type reference. A per-chart-type reference section, a dedicated interaction/annotations reference (deeper than the Learn track's tour), and the financial-charts flagship guide are on the roadmap — see PLAN.md's "Docs site wave" for what's shipped and what's next.