Usage
label that names it and one entry in slices per part. cutout: "60%" cuts the hole of a donut. The chart fills its parent, so size it with the parent; the circle is as wide as the shorter side and centered.
{ label, value, color? }, and slices run clockwise from the top in input order (mirrored in right-to-left). A value that is negative or not finite draws no slice and counts as zero. Callbacks report index, the slice's position in slices, hidden slices included. A pie has one dataset, so there is no datasetIndex.
Labels and formatter results are copy or data: pass LocalizedMessage, or wrap data with toDisplayValue.
cutout is a percentage string of the radius: "60%" is a donut, "0%" (the default) a full pie. sliceGap is the gap between slices in pixels, 4 by default and the same number of pixels at any chart size. sliceGap={0} removes it. The gap shows the surface behind the chart, so it has no color of its own.
A slice's color is a ChartColor palette solid token such as ChartColor.PaletteBlueSolid. With none, a slice takes Chroma's categorical palette by position: blue, orange, emerald, pink, teal, yellow, violet, lime, red, sky, fuchsia, cyan, then indigo, amber, purple, rose, green, and the order cycles after 17. palette changes where default colors come from: ChartPalette.Categorical (default), a harmony preset such as ChartPalette.HarmonyTriadic read from --color-harmony-1 to --color-harmony-8 (defined by a theme exported from Chroma with that preset), or an explicit list of ChartColor tokens. A palette color names a slice, never a meaning.
showLegend paints a legend of buttons, one per slice, at legendPosition (Top, Bottom, Start, End). An entry reads Label (NN%): a whole number when the percentage is whole, otherwise one decimal, in the locale's digits. The share is of all slices, so hiding a slice does not change another entry. formatLegendEntry({ index, value, formattedValue, share, formattedShare, slice }) replaces the text. Pressing an entry hides or shows its slice; the rest spread over the circle. Control it with hiddenSlices and onHiddenSlicesChange(hidden, details), or start it with defaultHiddenSlices. details.cancel() keeps the current list.
The tooltip is on by default. 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. It names the slice under the pointer or the keyboard: the heading is the label (formatTooltipTitle(index, label)) and the row reads value (share) (formatTooltipRow, which receives the same entry as the legend formatter). formatValue formats the value everywhere, and locale the percentage. showTooltip={false} turns it off. A pie 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. A parent that re-renders with equal slices rebuilds nothing, so you do not need to memoize them or the callbacks.
Moving a call site is mechanical: rename props, move the labels, values and colors into one list of slices, delete the props the type checker flags, and replace Chart.js callbacks with the typed formatters. The contract table in 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/pie-chart (and its styles.css next to tokens.css).labels, data and backgroundColor of datasets[0] into slices: labels[i] is label, data[i] is value and backgroundColor[i] is color. Wrap labels with toDisplayValue if they are already-formatted data.dataLabel, borderColor and borderWidth from the dataset. borderWidth: 0 becomes sliceGap={0}.config: cutout, showLegend, legendPosition (type ChartLegendPosition). Delete responsive, maintainAspectRatio, borderColor, legendColor, legendPadding, labels, values, colors and plugins.printBackground.plugins.tooltip.callbacks with formatTooltipTitle and formatTooltipRow.label, and delete any print code.src/components/line-chart/docs/usage.md, which migrates it.
This fixture uses BarChart. See src/components/bar-chart/docs/usage.md, which migrates it.
Before:
typeColors keeps working as it is: the entries may be bare custom property names or any CSS color. The legend reads Label (NN%), a whole percentage when it is whole and one decimal otherwise, as before. The legend color, legend padding, surface-colored border, sizing flags and print flag are gone because they are the defaults. Leave sliceGap out to keep the 4px gap.
Before: the chart was sized by its parent, but it needed responsive: true and maintainAspectRatio: false to fill it.
lazy(() => import("@prepared911/ui/pie-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, swapped each canvas for a PNG <img>, undid it afterwards, and set plugins.printBackground: true.
After: delete the hook and printBackground. The pie is SVG scaled by CSS, so it prints; the layout is CSS, so it fills the resized print container; the entrance animation is off in print; and the chart prints in its current theme.