Framework binding

React components

A thin React wrapper around every chart class below — same config, same events, same imperative methods, exposed as a component instead of new XChart(el, config). Ships in the same package, under a separate entry point so plain-TS consumers pull in no React code.

Install

# same package as the vanilla charts — react / react-dom are peer deps
npm install @smalldat/sandycoast react react-dom

React 18 and 19 are both supported (peerDependencies). If your app doesn't already depend on React, the base @smalldat/sandycoast import stays React-free — only code that imports from @smalldat/sandycoast/react pulls it in.

Quick start

import { BarChart } from '@smalldat/sandycoast/react';

const data = {
  points: [
    { x: 'Q1', y: 120, z: 'EU' },
    { x: 'Q1', y: 90,  z: 'US' },
    { x: 'Q2', y: 140, z: 'EU' },
  ],
};

// hoisted, so its identity is stable across renders — see below
const options = { grain: { sizePx: 2.4 }, legend: { show: true } };

function Revenue() {
  return (
    <BarChart
      data={data}
      options={options}
      style={{ width: '100%', height: 360 }}
      onHover={({ bar }) => console.log(bar?.xValue, bar?.yValue)}
    />
  );
}
Container sizing. Same rule as the vanilla charts: the chart fills its container via a ResizeObserver, so give it a size — pass style (as above) or a className with a CSS rule.

Shared prop shape

Every component below takes the same five kinds of prop. Chart-specific pieces are the data type, the options type (that chart's config, minus data), and the event props — see Components.

PropTypeDescription
data requiredDataSet | MeshDataSet | OhlcDataSet | WindDataSet That chart's data — the same shape the vanilla constructor takes. See Data model.
optionsOmit<XChartConfig, 'data'> Every other config field that chart's doc page lists (style, animation, chrome, pan/zoom, …).
className / stylestring / CSSProperties Applied to the chart's container <div>.
onXxx(payload) => void One prop per event that chart's .on(event, fn) supports — see Components for the list per chart.
refRef<XChart> The underlying vanilla chart instance once mounted. See below.

data vs. options — what re-renders as what

The two props are deliberately handled differently, matching what the vanilla API can do efficiently:

Prop changeWhat happens
data changes (new reference) Calls the chart's update(data) (or setData for the wind rose) — grains morph to the new targets, no re-pour.
options changes (new reference) The chart is disposed and recreated — config like style, animation timing or axes isn't dynamically patchable on the vanilla class either, so this mirrors it exactly.
Memoize options. An inline object literal is a new reference every render, which would recreate the chart on every re-render. Hoist it to module scope (as in the quick start above) or wrap it in useMemo. The same goes for data, for a milder reason: a fresh literal every render restarts the morph tween rather than recreating the chart.

Components

Componentdata typeEvent propsConfig reference
BarChartDataSet onHover, onSeriesFocusBar chart →
LineChartDataSet onHover, onSeriesFocusLine chart →
PieChartDataSet onHover, onSeriesChange, onSeriesFocusPie chart →
ScatterChartMeshDataSet onHover, onSeriesFocusScatter chart →
WindRoseChart<Custom>WindDataSet<Custom> onHover, onSelect, onHighlightWind rose →
CandlestickChartOhlcDataSet onHover, onClick, onSeriesChange, onSeriesFocusCandlestick chart →

Each event prop's payload is exactly what the matching vanilla .on(event, fn) passes — see that chart's own Events section for the payload shape. WindRoseChart is generic over the same Custom per-observation payload type as the vanilla class; it defaults to unknown.

The ref — imperative access

A ref on any of these components resolves to the underlying chart instance — the exact same object new XChart(el, config) would give you — once it has mounted (null before that). Use it for anything that isn't a prop: focusSeries(), getView()/panBy()/zoomTo(), repour(), getData(), the wind rose's selectObservation() / selectSegment(), and so on — see each chart's Methods section.

import { useRef } from 'react';
import { BarChart } from '@smalldat/sandycoast/react';
import type { BarChart as BarChartInstance } from '@smalldat/sandycoast';

function Chart() {
  const ref = useRef<BarChartInstance>(null);

  return (
    <>
      <button onClick={() => ref.current?.focusSeries(null)}>Clear isolation</button>
      <BarChart ref={ref} data={data} style={{ height: 360 }} />
    </>
  );
}

More examples

Live data — the same morph the vanilla API gives you

function Live() {
  const [data, setData] = useState(initialData);

  // A new `data` reference morphs the grains in place — no re-pour.
  const refresh = () => setData(fetchLatest());

  return <BarChart data={data} style={{ height: 360 }} />;
}

Wind rose with a typed Custom payload

JSX doesn't take an explicit generic argument on the tag, so type windData itself — WindRoseChart infers Custom from it.

interface Reading { stationId: string; }

const windData: WindDataSet<Reading> = { points: [/* … */] };

<WindRoseChart
  data={windData}
  onSelect={({ segment, observationId }) => console.log(segment?.key, observationId)}
/>
Try it live. The playground demo site has a React bindings tab with a runnable example — and its exact source — for every chart above.

© 2026 · License · Data model →