# 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.

## Palette candidates

`--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.

### Shipped. Blue and ink

### A. Ink-led

### B. Accent-led

### C. Monochrome plus one

## Library candidates

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.

### The facts

Library
License
Version
Island JavaScript
Server render
Theming

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.

### The interactions

Interaction

`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.

### MUI X Charts

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.

### Recharts

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.

### Observable Plot

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 ECharts

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.
