Usage
label that names it, categories once, and one entry in series per line.
categories is the list of labels on the horizontal axis. Every series has one value per category, in the same order. A value that is not finite (NaN) leaves a gap in the line. Callbacks report datasetIndex (the series' position in series, hidden series included) and index (the category position). Put comparison series after the main ones and datasetIndex >= main.length tells them apart.
Labels, names and formatter results are copy or data: pass LocalizedMessage from your localization layer, or wrap data (a date you already formatted) with toDisplayValue.
curve is LineChartCurve.Monotone (default) or Linear, for every line in the chart. A monotone line never overshoots a point.fill on a series draws a gradient under the line, fading from the series color. It defaults to true for a one-series chart and false otherwise.dashed on a series draws a thinner, dashed line, for a comparison series. Pass the same color as the main series.color is a ChartColor palette solid token such as ChartColor.PaletteBlueSolid. With no color, a series takes Chroma's categorical palette in order: blue, orange, emerald, pink, teal, yellow, violet, lime, red, sky, fuchsia, cyan, then indigo, amber, purple, rose, green. After 17 series the order cycles.
palette changes where the default colors come from:
ChartPalette.Categorical (default).ChartPalette.HarmonyTriadic, read from --color-harmony-1 to --color-harmony-8, which a theme exported from Chroma with that preset defines. Export a preset with at least as many colors as series; later series fall back to --color-harmony-1.ChartColor tokens, used in order and then cycled.showValueAxis paints tick labels and beginAtZero (default true) keeps zero on the axis. The axis rounds its domain to the tick step, so ticks are evenly spaced. formatValue formats ticks, tooltip values and the data table. The horizontal axis draws every label until they would collide. It then keeps the last category and skips each earlier label that would touch the one drawn after it, with a 5px gap, using the measured text width. An end label wider than its band sits against the axis edge, so it never leaves the chart, and a label is never cut with an ellipsis.
showLegend paints a legend of buttons, one per series, at legendPosition (Top, Bottom, Start, End; Start and End mirror in right-to-left). Pressing one hides or shows its series and rescales the value axis. Control it with hiddenSeries and onHiddenSeriesChange(hidden, details), or start it with defaultHiddenSeries. details.cancel() keeps the current list.
The tooltip is on by default. It lists every visible series at the active category. The tooltip sits at the inline end of its anchor and stays inside the plot: it flips to the start side, or slides along the block axis, when it would cross an edge. Format it with formatTooltipTitle(index, category) and formatTooltipRow({ datasetIndex, index, value, formattedValue, dataset }), which returns the whole row text. showTooltip={false} turns it off. Escape dismisses it.
onSelect({ datasetIndex, index }) fires when a series' active marker is clicked, or when Enter or Space is pressed on the keyboard cursor. A click on the plot away from a marker selects nothing. With no onSelect the chart is read-only. The keyboard rule is in the accessibility notes.
The chart is 100% wide and tall and takes its size from its parent. There is no size prop. Give it a parent with a definite height, or a flex column (or grid) that sizes it: in a flex column the chart is a flex item that grows to fill the parent and shrinks to fit a max-block-size, so a column capped by max-block-size: 25dvh or held between a min-block-size and a max-block-size keeps the chart, its legend and its axes inside it. A wrapper between the column and the chart must itself be a flex column (display: flex; flex-direction: column; flex: 1 1 auto; min-block-size: 0): percentage heights do not resolve against a parent that only has a maximum, so a plain block wrapper lets the chart keep its aspect-ratio height and overflow the cap. In a parent with no height it takes an aspect ratio (2:1, or 1:1 for a pie) instead of collapsing. A parent with a height wins. It prints in its final state and theme with no code: the reveal animation is off in print and the layout is CSS. A parent that re-renders with equal series and categories rebuilds nothing, so you do not need to memoize them or the callbacks.
sparkline draws only the lines and their areas, in a small box: the MetricCard sparkline slot is 40px high, aria-hidden and clips its overflow. The chart fills the slot, has no axes, legend, tooltip or tab stop, and is one image named by label. Set beginAtZero={false} when the trend matters more than the size.
CONTRACT.md lists every prop, enum and export of the old package with its replacement and whether a codemod could do it. There is no adapter: the old config and plugins objects do not type-check, so the compiler points at every line.
What to do, in order:
@prepared911/ui/line-chart (and its styles.css next to tokens.css).labels from the datasets to categories, once, wrapped with toDisplayValue if they are already-formatted data.dataLabel to name, data to values, borderColor to color, and borderDash to dashed.backgroundColor, borderWidth, pointRadius, pointHitRadius, pointHoverRadius, tension and cubicInterpolationMode. Set fill: true where the gradient should show, and curve on the component if a chart should be Linear.config: showLegend, legendPosition (type ChartLegendPosition), showYAxis to showValueAxis, beginAtZero. Delete responsive, maintainAspectRatio, xAxisType, labelSuffix.plugins.tooltip.callbacks with formatTooltipTitle and formatTooltipRow, and onClick(event, elements) with onSelect.label, and delete any print code.comparison and onSelect; the rest is the same.
This fixture uses BarChart. See src/components/bar-chart/docs/usage.md, which migrates it. Nothing in it changes how a LineChart is migrated.
This fixture uses PieChart. See src/components/pie-chart/docs/usage.md.
Before: the chart was sized by its parent, but it needed responsive: true and maintainAspectRatio: false to fill it.
lazy(() => import("@prepared911/ui/line-chart")) pulls in no other chart, and importing it has no side effect.
Before: a 187-line hook listened for beforeprint and afterprint, called Chart.getChart(canvas) for each canvas, switched off responsive, resized the chart to the print aspect ratio, swapped each canvas for a PNG <img>, undid it all afterwards, and set plugins.printBackground: true.
After: delete the hook and printBackground. The chart is SVG and HTML, so it prints; its layout is CSS, so it fills the resized print container; the entrance animation is off in print, so a chart that was never scrolled into view prints in its final state; and it prints in its current theme.