Skip to content
helia-ui
React components
HELIA HUB

Charting candidates

Two choices are open on this page, and both are made here rather than argued. The first is the series palette: which six colors a chart takes, and in what order. The second is the library: four candidates, the same three charts, the same numbers, the same card.

The series are synthetic. Nothing on this page is a measurement of any part.

--helia-chart-1 through --helia-chart-6 used to alias the advisory tones, so the first two series were the green and the blue that a callout uses to mean success and information, and a chart wore the status vocabulary whether or not it meant any of it. The shipped palette below replaces all six: a light modern blue, the page ink, and a slate ramp. It is what the tokens resolve to today, and the gallery charts and the React chart follow it without being touched.

The three candidates after it are kept for the record. They are the other directions the palette could take, drawn the same way so the shipped one can be read against them. None of the four uses a green, and none uses a blue that could be read as a status color.

Every step is derived from the brand scale in the package sheets: the slate ramp is the OKLCH hue and chroma of --helia-accent-slate with the lightness stepped, and the chromatic steps take the hue and chroma of --helia-accent-cyan, --helia-accent-orange, --helia-accent-purple and --helia-accent-pink at whatever lightness the ground calls for. The two grounds stand side by side because the page is only ever in one of them and a palette has to survive both.

Under each palette are the same two charts: a two-series line and a grouped bar, drawn by the shipped Chart part with --helia-chart-1 through --helia-chart-6 set on a wrapper. Six declarations is the whole of a palette change, so nothing on this page is a mock-up of one. The shipped block sets the same values the sheet does, and the smoke suite reads them back against the document to keep the two from drifting apart.

Each palette also carries the smallest CIEDE2000 distance between any two of its six steps, which is the number that decides whether a reader can tell one series from another. Roughly: around 1 is the just-noticeable difference for two patches touching each other, and two marks separated across a chart need a good deal more than that. It is a floor, not an average, so a palette with five well-spread steps and one near-duplicate scores as the near-duplicate.

--helia-product-accent resolves to --helia-accent-slate on this site, so the product accent in A and B is a slate step, and C’s highlighted series is amber rather than the accent: a monochrome palette whose one highlight is another slate is not a highlight. On a product surface that sets the dial to a product hue, the accent steps move with it.

A light modern blue against the page ink, and a slate ramp at decreasing weight behind them. The first two series separate on lightness as well as hue, which is the widest separation six steps can offer, and nothing in it carries an advisory meaning. The four slate steps are a ramp and nothing else, so a chart that uses all six leans on order rather than on color.
StepRoleDarkLight
--helia-chart-1Blue#7fb0ff#4f8ff7
--helia-chart-2Page ink#ffffff#17181c
--helia-chart-3Slate 1#9ba5ba#4e586c
--helia-chart-4Slate 2#828ca2#656f84
--helia-chart-5Slate 3#6a758a#768096
--helia-chart-6Slate 4#596378#8892a8

Closest pair, CIEDE2000: 6.9 on dark, between steps 5 and 6, 6.4 on light, between steps 5 and 6.

Dark

Inference latency

Milliseconds per inference against input length, in tokens

  • heliaRT
  • heliaAOT
05101520100200300400500

Energy per inference

Microjoules per inference against deployment target

  • heliaRT
  • Reference
WearableHearableSensor050100150200250

Light

Inference latency

Milliseconds per inference against input length, in tokens

  • heliaRT
  • heliaAOT
05101520100200300400500

Energy per inference

Microjoules per inference against deployment target

  • heliaRT
  • Reference
WearableHearableSensor050100150200250
The page ink leads, the product accent answers it, and color arrives only at the fifth and sixth series. Two series read as type and figure rather than as two hues, which is the calmest of the three. Four of the six steps are neutral, so the palette is the hardest to tell apart once a chart uses all of it.
StepRoleDarkLight
--helia-chart-1Page ink#f0f1f2#111318
--helia-chart-2Product accent#a1abc0#687287
--helia-chart-3Slate mid#768096#394255
--helia-chart-4Slate far#4e586c#949fb4
--helia-chart-5Amber#fd9146#c46101
--helia-chart-6Violet#8e84ff#624bd6

Closest pair, CIEDE2000: 14.1 on dark, between steps 2 and 3, 16.1 on light, between steps 2 and 4.

Dark

Inference latency

Milliseconds per inference against input length, in tokens

  • heliaRT
  • heliaAOT
05101520100200300400500

Energy per inference

Microjoules per inference against deployment target

  • heliaRT
  • Reference
WearableHearableSensor050100150200250

Light

Inference latency

Milliseconds per inference against input length, in tokens

  • heliaRT
  • heliaAOT
05101520100200300400500

Energy per inference

Microjoules per inference against deployment target

  • heliaRT
  • Reference
WearableHearableSensor050100150200250
Five brand hues at one OKLCH lightness, so no series is louder than another, with the page ink as the sixth. The most distinguishable of the three and the only one that holds six series honestly. It is also the most color on a page that has very little, and five hues at one lightness is a palette a reader has to learn.
StepRoleDarkLight
--helia-chart-1Product accent#8892a8#687287
--helia-chart-2Teal#02a5b3#01818c
--helia-chart-3Amber#da721e#ae5500
--helia-chart-4Violet#887cff#6a55e1
--helia-chart-5Rose#e35b8e#bd376e
--helia-chart-6Page ink#f0f1f2#111318

Closest pair, CIEDE2000: 15.2 on dark, between steps 1 and 4, 16.5 on light, between steps 1 and 4.

Dark

Inference latency

Milliseconds per inference against input length, in tokens

  • heliaRT
  • heliaAOT
05101520100200300400500

Energy per inference

Microjoules per inference against deployment target

  • heliaRT
  • Reference
WearableHearableSensor050100150200250

Light

Inference latency

Milliseconds per inference against input length, in tokens

  • heliaRT
  • heliaAOT
05101520100200300400500

Energy per inference

Microjoules per inference against deployment target

  • heliaRT
  • Reference
WearableHearableSensor050100150200250
One highlighted series in amber and five slate steps behind it, so the chart says which series the sentence is about. Unmistakable when one series is the point. The five neutrals are a lightness ramp and nothing else, so they separate worst of the three and a six-series chart in this palette is a legend lookup.
StepRoleDarkLight
--helia-chart-1Highlight#fd9146#c46101
--helia-chart-2Slate 1#d5def0#131a2b
--helia-chart-3Slate 2#aeb8cc#2f384a
--helia-chart-4Slate 3#8892a8#4e586c
--helia-chart-5Slate 4#687287#707a90
--helia-chart-6Slate 5#4c5569#949fb4

Closest pair, CIEDE2000: 9.4 on dark, between steps 2 and 3, 9.5 on light, between steps 2 and 3.

Dark

Inference latency

Milliseconds per inference against input length, in tokens

  • heliaRT
  • heliaAOT
05101520100200300400500

Energy per inference

Microjoules per inference against deployment target

  • heliaRT
  • Reference
WearableHearableSensor050100150200250

Light

Inference latency

Milliseconds per inference against input length, in tokens

  • heliaRT
  • heliaAOT
05101520100200300400500

Energy per inference

Microjoules per inference against deployment target

  • heliaRT
  • Reference
WearableHearableSensor050100150200250

Both questions below are now settled: Observable Plot is the static layer and Apache ECharts is the interactive one, so Chart, ChartGroup and ChartPlot draw with Plot and ChartInteractive draws with ECharts wherever hovering, zooming, toggling or selecting is the point.

The comparison stays here because it is the evidence for that split rather than an open question: the rows below are what each library costs in island bytes, what it can render on the server, how it takes a theme, and which interactions it has without being written by hand.

Every chart carries a title and a subtitle, and the subtitle says what is plotted against what, so no axis is titled. Gridlines are horizontal only and drawn in the hairline token. All four rows take their series from --helia-chart-1 through --helia-chart-3, the same shipped palette the section above draws, so a difference between two rows is the library and never the colors.

LibraryLicenseVersionIsland JavaScriptServer renderTheming
MUI X Charts
@mui/x-charts
MIT9.14.0487 KBIn part. The surface and the legend are in the HTML; the marks wait to be measured.Colors are props, gridlines need an sx rule, and the rest is MUI theme defaults.
Recharts
recharts
MIT3.8.0429 KBNo. ResponsiveContainer has nothing to measure, so the HTML holds an empty container.Every color is a prop on the element that draws it, and there is no theme to override.
Observable Plot
@observablehq/plot
ISC0.6.17297 KBNo. Plot.plot builds against a document, so the island is client only.One color scale and one style object per figure.
Apache ECharts
echarts
Apache-2.06.1.0667 KBYes. echarts.init(null, ...) with ssr lays the figure out with no document, so the SVG is in the HTML; the island then replaces it with a live instance, and the interactions arrive with that.A JSON theme registered once. Every value in it is a var(), so the SVG follows the theme toggle.

License and version are the installed packages’, read from node_modules. The byte figures are minified JavaScript, not compressed, summed over the chunks this page loads for that island and with the shared React runtime taken out, because the page pays for that once whichever library wins. The four overlap: around 79 KB of d3 is common to the first three, so this page loads less than the column adds up to. Recharts is already a peer dependency of the package, so its bytes are a cost the React layer pays anyway; the other three would be new. ECharts is imported through its tree-shaking entry points rather than its barrel, so its figure is the one a site would actually ship.

InteractionMUI X ChartsRechartsObservable PlotApache ECharts
Tooltip
A value readout that follows the pointer.
Native
ChartsTooltip, on by default.
Native
The Tooltip element.
Native
The tip mark under a pointer transform, and crosshair.
Native
tooltip, on the item or the axis.
Zoom
Narrowing the domain by wheel, drag or slider.
No
Not in the community package: the zoom plugin is x-charts-pro.
Native
The Brush element gives a range slider under the plot.
No
No zoom, and no interaction to build on.
Native
dataZoom, as a slider under the plot or on the wheel.
Legend toggle
Clicking a legend entry removes that series.
Native
hiddenItems and onHiddenItemsChange on the chart.
By hand
Legend onClick plus the series' own hide prop, held in state.
By hand
Plot legends are static swatches, so the island filters the data and rebuilds.
Native
The legend toggles the series it names.
Selection
Dragging over marks selects them and reports which.
No
The brush gestures and overlay are internals with no public props.
No
Brush narrows the domain; it does not report a set of marks.
No
The pointer transform picks one mark, never a set.
Native
brush returns the selected marks as an event.
Animation
Marks are drawn in rather than appearing.
Native
On unless skipAnimation is set.
Native
isAnimationActive, on by default.
No
A figure is built and appended; there is nothing to animate.
Native
On by default, and configurable.
Streaming
Appending points without rebuilding the chart.
By hand
Re-render with new series; there is no append.
By hand
New data prop; the animation replays each time.
By hand
Rebuild the whole figure and swap the node.
Native
appendData adds points without a rebuild.

Native means the library does it from options or props. By hand means the island writes it and the library only gets out of the way. No means neither. Each cell was checked against the installed package’s own type declarations, not its documentation.

The community charts package is MIT. It needs @mui/material as a peer, and although the @emotion peers are marked optional in this major, MUI’s styling engine loads @emotion/react at runtime and the server render fails without it, so @emotion/react and @emotion/styled came in as well. @mui/system arrives with @mui/material. Four new packages, then, and MUI’s styling layer alongside its charts.

Inference latency
Milliseconds per inference against input length, in tokens
  • heliaRT
  • heliaAOT
Energy per inference
Microjoules per inference against deployment target
  • heliaRT
  • Reference
Accuracy against model size
Top-1 accuracy, in percent, against model size in megabytes
  • int8
  • fp16

Already a peer dependency and already wrapped by the package’s shadcn chart component. Here it is used through its own primitives rather than the wrapper, so the comparison is between libraries and not between a library and a wrapper.

Inference latency
Milliseconds per inference against input length, in tokens
Energy per inference
Microjoules per inference against deployment target
Accuracy against model size
Top-1 accuracy, in percent, against model size in megabytes

Not a React library at all: Plot.plot builds a figure element against a DOM and hands it back, so an island owns the node and the width. That makes it the cheapest to drop into a page that is not React, and the one that needs an adapter in a page that is.

This row is hydrated, and it is Plot at its ceiling rather than at its default. The tooltips are the tip mark under a pointer transform and the crosshair interaction, both of which cost a line each. The legend is not Plot’s: Plot’s own legend is a static swatch strip, so what is above each figure is a set of buttons, and a click filters the data and rebuilds the whole figure. There is no zoom and no selection below, because there is nothing in Plot to build either one on.

Apache-2.0, and the only candidate here that treats a chart as something a reader operates. The row below has a tooltip on hover, a legend that toggles the series it names, a zoom on the line as both a slider and the wheel, a brush over the scatter that selects the marks inside it, and an entrance animation. None of that is written here; all of it is options.

The figures in the HTML are ECharts’ own server render. echarts.init(null, ...) with ssr lays a chart out with no document at all, so the build writes the SVG into the page and the island’s job on mount is to replace a picture with an instance. The theme is a JSON object registered once, and every color in it is a var() rather than a hue, which is what lets a figure rendered at build follow the theme toggle — the same trick the Astro Chart part uses, and the reason the SVG renderer is used on the client as well as on the server.

Inference latency
Milliseconds per inference against input length, in tokens
0 3 6 9 12 15 18 21 32 64 128 256 384 512 heliaRT heliaAOT
Energy per inference
Microjoules per inference against deployment target
0 50 100 150 200 250 Wearable Hearable Sensor heliaRT Reference
Accuracy against model size
Top-1 accuracy, in percent, against model size in megabytes
86 88 90 92 94 96 0 3 6 9 12 15 18 21 int8 fp16