@pyreon/charts — API Reference
Generated from
charts'ssrc/manifest.ts— the same source that powersllms.txtand MCPget_api. Do not edit this page by hand; edit the manifest. For the conceptual guide, see charts.
Two independent charting engines behind two subpaths. @pyreon/charts/plot is Pyreon's OWN: pure-TypeScript geometry over a flat draw list, marks as imported bindings so tree-shaking is structural, a canvas backend, and a PURE SVG backend that renders on a server. @pyreon/charts is the ECharts bridge: zero ECharts bytes in your bundle until a chart actually renders — chart types and components are auto-detected from your options and dynamically imported on demand. Signal-driven options reactively update the chart when tracked signals change. useChart is the low-level hook with full control; <Chart /> is the declarative component with event binding. Both auto-resize via ResizeObserver and clean up on unmount.
Features
useChart<TOption>(optionsFn, config?) — low-level reactive hook with full lifecycle control
Chart component with declarative options, event binding, and auto-resize
onEvents map for ANY ECharts event (legendselectchanged, datazoom, brushselected, …), leak-safe binding
showLoading — reactive toggle of the ECharts loading overlay
Zero-byte lazy loading — chart types auto-detected and dynamically imported
Generic TOption for strict typed options via ComposeOption<SeriesUnion>
@pyreon/charts/manual entry for explicit tree-shaking control
All ECharts option and series types re-exported for single-import convenience
Complete example
A full, end-to-end usage of the package:
import { Chart, useChart, type EChartsOption, type ComposeOption, type BarSeriesOption, type LineSeriesOption } from '@pyreon/charts'
import { signal } from '@pyreon/reactivity'
const months = signal(['Jan', 'Feb', 'Mar', 'Apr'])
const revenue = signal([100, 200, 150, 300])
// Declarative component — simplest usage
<Chart
options={() => ({
xAxis: { type: 'category', data: months() },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: revenue() }],
tooltip: { trigger: 'axis' },
})}
style="height: 400px"
onClick={(params) => console.log('clicked:', params.name)}
/>
// useChart hook — full control over instance lifecycle
const MyChart = () => {
const chart = useChart(() => ({
xAxis: { type: 'category', data: months() },
yAxis: { type: 'value' },
series: [
{ type: 'bar', data: revenue() },
{ type: 'line', data: revenue().map((v) => v * 1.1) },
],
}))
return (
<div>
{chart.loading() ? 'Loading chart...' : null}
<div ref={chart.ref} style="height: 400px" />
<button onClick={() => chart.resize()}>Resize</button>
</div>
)
}
// Strict typed options — only bar + line allowed
type MyOption = ComposeOption<BarSeriesOption | LineSeriesOption>
const typedChart = useChart<MyOption>(() => ({
series: [{ type: 'bar', data: [1, 2, 3] }], // only 'bar' | 'line' autocomplete
}))
// Manual entry for tree-shaking control:
// import { useChart, Chart } from '@pyreon/charts/manual'
// — you register ECharts components yourselfExports
| Symbol | Kind | Summary |
|---|---|---|
useChart | hook | Create a reactive ECharts instance. |
Chart | component | Declarative chart component that wraps useChart internally. |
Plot | component | The grammar — <Plot data x> with MARK CHILDREN (Plot, because the package's default entry already exports the EChart |
PlotChart | component | Pyreon's OWN charting engine, from the @pyreon/charts/plot subpath — no ECharts, no third-party engine. |
ChartThemeProvider | component | Provides ONE theme to every /plot chart below it. |
BoxplotChart | component | A boxplot per category from RAW SAMPLES: values={(d) => d.samples} is reduced with fiveNumber (min, q1, median, q3, |
sma | function | Indicator MARKS over a value accessor, for the finance and telemetry charts that draw a signal beside its smoothing: `sm |
chartToSvg | function | Render a chart to a standalone <svg> STRING. |
PieChart | component | Pie and donut from the same engine (@pyreon/charts/plot); innerRadius is what makes it a donut. |
CandlestickChart | component | Candlestick chart from the plot engine (@pyreon/charts/plot) — open/high/low/close accessors per datum, direction enco |
HeatmapChart | component | Heatmap from the plot engine (@pyreon/charts/plot): two categorical axes, a value per cell, color as the third channel |
RadarChart | component | Radar (spider) chart from the plot engine (@pyreon/charts/plot) — one polygon per datum over shared spokes. |
TreemapChart | component | The hierarchy families of Pyreon's own engine share ONE data shape: TreeNode { name, value?, children?, color? }. |
MapChart | component | GeoJSON regions filled by value. |
optionToSvg | function | The ECharts option-compat facade: an ECharts-SHAPED option in, this engine out — cartesian series (line/bar/scatter/effe |
OptionChart | component | The ECharts-option-driven host: an ECharts-shaped option in (a value or an accessor), a live chart out. |
GanttChart | component | The Gantt family — one row per task on a calendar-aligned time axis. |
createChartHandle | function | The imperative handle (ECharts dispatchAction) for ONE <PlotChart handle>: a link (zoom, hover) plus selected |
createChartLink | function | Linked charts (ECharts connect): a shared { zoom, hover } pair of signals that every <PlotChart link> in a group u |
sonifyValues | function | A series as sound: each value maps linearly to a pitch between minHz and maxHz (valueToHz — a FINITE value outside |
API
useChart hook
<TOption extends EChartsOption = EChartsOption>(optionsFn: () => TOption, config?: UseChartConfig) => UseChartResultCreate a reactive ECharts instance. Options are passed as a function — signal reads inside are tracked and the chart updates automatically when any tracked signal changes. Lazy-loads the required ECharts modules on first render (zero bytes until mount). Returns ref (bind to a container div), instance (Signal<ECharts | null>), loading (Signal<boolean>), error (Signal<Error | null>), and resize(). Auto-resizes via ResizeObserver (autoresize: false | { throttle } to opt out/throttle) and disposes on unmount. theme accepts an accessor for reactive swaps; initOptions passes through to core.init; warm mounts (modules cached) are synchronous. getCore()/connect() are exported for registerMap/registerTheme/linked charts.
Example
const chart = useChart(() => ({
xAxis: { type: 'category', data: months() },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: revenue() }],
}))
<div ref={chart.ref} style="height: 400px" />
// chart.loading() — true until ECharts modules loaded + chart initialized
// chart.instance() — raw ECharts instance for imperative APICommon mistakes
Forgetting to set a height on the container div — ECharts requires explicit dimensions, it does not auto-size to content
Passing options as a plain object instead of a function — signal reads are not tracked and the chart never updates
Reading chart.instance() immediately after useChart — the instance is null until the async module load completes; check chart.loading() first
Calling chart.resize() during SSR — useChart is browser-only; the hook no-ops safely on the server but resize is meaningless
See also: Chart
Chart component
(props: ChartProps) => VNodeChildDeclarative chart component that wraps useChart internally. Accepts options (reactive function), style/class for the container, and event handlers. onEvents binds ANY ECharts event by name (legendselectchanged, datazoom, finished, …), with onClick/onMouseover/onMouseout as shorthands — binding is leak-safe (handler changes swap listeners, all removed on unmount). showLoading reactively toggles the ECharts loading overlay. Renders a div with the chart — auto-resizes and cleans up on unmount. Simpler than useChart for most use cases.
Example
<Chart
options={() => ({
legend: {},
series: [{ type: 'pie', data: [{ value: 60, name: 'A' }, { value: 40, name: 'B' }] }],
})}
style="height: 300px"
showLoading={isFetching()}
onEvents={{
legendselectchanged: (p) => console.log('toggled', p.name),
datazoom: (_p, instance) => syncOtherChart(instance.getOption()),
}}
/>Common mistakes
Missing style height on the Chart component — same as useChart, ECharts requires explicit container dimensions
Passing a static options object — wrap in
() => ({...})so signal reads inside are tracked reactivelyUsing onClick/onMouseover/onMouseout for a non-mouse event — those are only shorthands; reach for the general
onEventsmap (e.g.onEvents={{ legendselectchanged: fn }}) for any other ECharts eventPassing
themeas a plain VALUE and expecting runtime swaps — a value is applied once at init; pass an ACCESSOR (theme: () => (dark() ? 'dark' : null)) and a flip disposes + re-inits with the option, group, and events preservedRelying on the default merge when data shrinks — a signal change that removes a series/point leaves the old one; pass
notMergeorreplaceMerge="series"
See also: useChart
Plot component
<T>(props: PlotProps<T>) => VNodeThe grammar — <Plot data x> with MARK CHILDREN (Plot, because the package's default entry already exports the ECharts bridge as <Chart>). Channels are FIELD NAMES typed against the row (y="revenue") or accessors; marks are JSX children (<Bar y stack? group? waterfall?>, <Line y>, <Area y>, <Dot y r?> — r makes area-mapped bubbles; every cartesian mark takes errorLow / errorHigh channels for error bars) and draw in order; <Rule y | from to>, <Axis x|y|y2 format domain time hidden title labels scale>, <Scale y="log"|"time" x="time" normalize> (the log view, calendar labels, the 100% stack), <Histogram x bins> (bins the rows and draws one bar per bin — the whole plot, like the pivot), <Tip crosshair format>, <Legend toggle maxRows position>, <Zoom inside navigator presets link brush> and <Label text at series> (a datum-anchored point marker) declare annotations, axes, scales, the tooltip, the legend, every zoom surface and markers as data beside the marks. facet="region" renders small multiples — one titled panel per value in a facetColumns grid, every panel sharing the y domain; locale="de-DE" formats every number surface through Intl. The FAMILY marks cover the row-array hosts with the same grammar — <Arc value label color? innerRadius?> (pie / donut), <Stage value label color? sort? gap?> (funnel), <Cell x y value colors? gap?> (heatmap), <Candle open high low close upColor? downColor?> (candlestick, the plot's x labels the period) — one family per plot, and <Plot> renders that host instead of the cartesian plot (<Tip> / <Legend> / <Axis y format> still apply; a cartesian mark or <Zoom> beside one is reported and ignored). A <Show> around a mark adds/removes its series, and a <For each> (or a plain .map()) generates one per item — its render callback is resolved here, inside the resolving computed, so an accessor each tracks. A child that is not a mark renders nothing and says so in dev. color="region" switches to LONG format: one series per distinct value, categories from x, gaps where a (category, series) pair is absent, bars grouped unless stack. Marks are branded components <Plot> scans structurally (never invoked); it resolves them into the marks={[bars(…)]} props <PlotChart> takes, so the array form is the same spec — resolveGrammar is exported for that equivalence. Native: the compiler desugars <Plot> to <PlotChart marks> (byte-identical emit); the runtime color pivot warns by name and renders wide-format.
Example
import { Axis, Bar, Legend, Line, Plot, Tip, currency } from '@pyreon/charts/plot'
interface Row { month: string; revenue: number; target: number }
const rows: Row[] = [{ month: 'Jan', revenue: 3200, target: 3000 }, { month: 'Feb', revenue: 4100, target: 3400 }]
<Plot<Row> data={rows} x="month" title="Revenue vs target" showTitle>
<Bar y="revenue" label="Revenue" />
<Line y="target" label="Target" />
<Axis y format={currency('