Understand the architecture behind Radiant Charts — a fully imperative HTML5 Canvas charting engine with a React wrapper. This page covers the rendering pipeline, scene graph, scale system, and the two API surfaces.
Every time you pass new options to RadiantChart, the engine runs a three-stage pipeline:
data array and series config. Computes scales (Linear, Band, Time, Log), populates axis domains, and instantiates or reuses series objects from an internal pool keyed by type and index. seriesRect after reserving space for axes, title, legend, and padding. Calls each series' update() method to position shapes within the available area.<div> and the lifecycle. All drawing is performed by ChartManager directly on the 2D canvas context.The canvas is organised as a tree of lightweight nodes. Each node type (Rect, Circle, Text, Line, Path) knows how to paint itself onto a CanvasRenderingContext2D. Nodes are grouped into layers:
// Layer order (bottom to top): titleGroup // Chart title subtitleGroup // Subtitle legendGroup // Legend items + pagination arrows axesGroup // X and Y axes with tick marks and labels annotationsGroup // User-defined annotations seriesGroup // Data series (bars, lines, markers, …) crosshairGroup // Interactive crosshair lines emptyGroup // "No data" placeholder watermarkGroup // Evaluation / watermark overlay
Because the scene graph is a plain object tree (not DOM nodes), hit-testing and animation operate on the same data structures with zero serialisation overhead.
Scales map data values to pixel coordinates. Radiant Charts ships four scale types:
LinearScale — Continuous numeric mapping. Used for Y axes and numeric X axes. Supports nice() for round tick values.BandScale — Categorical mapping. Maps discrete strings (e.g. month names) to evenly-spaced pixel bands. Configurable padding between bands.TimeScale — Date/time mapping. Internally uses epoch milliseconds with human-readable tick formatting.LogScale — Logarithmic mapping for data spanning multiple orders of magnitude.Each scale exposes convert(value) to map data → pixels and invert(pixel) to map pixels → data. The invert method powers focal-point zoom: the interaction manager reads the data value under the cursor, computes a new domain that keeps that value at the same pixel, then re-renders.
Each chart type is implemented as a series class (e.g. BarSeries, LineSeries). Series are instantiated by ChartManager based on the type field in the series config. Key lifecycle methods:
update(data, scales, rect, theme, animation) — Called during performLayout(). The series positions its shapes within the given rect using the provided scales.getTooltipData(x, y) — Returns the nearest data point for tooltip display at the given pixel coordinates.The <RadiantChart> component accepts a single options prop. Internally it creates a ChartManager on mount and calls manager.update(options) on every render. This is the most flexible API — any option can change at any time.
<RadiantChart
options={{
data,
series: [{ type: 'bar', xKey: 'category', yKey: 'value' }],
theme: 'dark',
}}
height={400}
/>The composable JSX API uses React Context internally. Each child component (<Bar>, <Line>, <XAxis>) renders null and registers its config with the parent <Chart>. The parent assembles a RadiantChartOptions object and renders a single <RadiantChart> under the hood.
<Chart data={data} theme="dark">
<XAxis dataKey="category" />
<YAxis />
<Legend />
<Bar xKey="category" yKey="value" fill="#6366f1" />
</Chart>