Components / Charts
DonutChart
A donut whose arcs morph between datasets, with the active value rolling into the center.
Install
npx shadcn@latest add https://irisui.dev/r/donut-chart.jsonimport { DonutChart } from "@/components/iris/donut-chart/donut-chart";Preview
DonutChart
A donut for a few parts of one whole, with a synced legend, small parts grouped into Other, and the active value rolling into the centre.
When to use
- Two to six parts of one total where each part's share matters more than precise comparison: traffic sources, spend by model, plan mix.
- Highlighting one category from elsewhere on the page: drive
activeKeyfrom a table or filter. - Not for more than about six parts or for close comparisons; use
BarChart. For countable shares,WaffleChartreads more precisely.
Usage
import { DonutChart } from "@/components/iris/donut-chart/donut-chart";
h(DonutChart, {
label: "Storage by project, CapyDB",
unit: "GiB",
totalLabel: "Storage",
formatValue: (v) => v.toFixed(1),
data: [
{ key: "myroomie", label: "myroomie", value: 6.8 },
{ key: "entrolytics", label: "entrolytics", value: 5.5 },
{ key: "shop", label: "shop", value: 4.2 },
{ key: "notdocs", label: "notdocs", value: 3.1 },
{ key: "api", label: "api", value: 2.6 },
],
});
Props
| Prop | Type | Default | Description |
|---|---|---|---|
data |
{ key: string; label: string; value: number; color?: string }[] |
required | Parts of the whole. Zero or negative values are omitted. Omit color to use --series-1…4, then neutral steps. |
label |
string |
required | Names the whole for assistive technology. |
unit |
string |
"" |
Unit under the total at rest. |
formatValue |
(value: number) => string |
grouped number | Centre, legend and table values. |
totalLabel |
string |
"Total" |
Centre label above the total. |
size / thickness |
number |
208 / 24 |
Ring diameter and stroke in px; scales down in narrow containers. |
groupBelow |
number |
0.04 |
Parts below this share join Other (when at least two would). |
maxSegments |
number |
6 |
Most segments drawn, counting Other. |
otherLabel |
string |
"Other" |
Label of the grouped segment. |
activeKey / defaultActiveKey / onActiveChange |
string | null / callback |
— / null |
Pinned segment. Hover and focus preview without changing it. |
hiddenKeys / defaultHiddenKeys / onHiddenKeysChange |
string[] / callback |
— / [] |
Hidden segments; the ring redistributes. |
legendAction |
"toggle" | "select" |
"toggle" |
Legend click shows/hides or pins a segment. |
legend |
boolean |
true |
The synced legend. |
emptyLabel |
string |
"No data yet" |
Centre text when the total is zero. |
className |
string |
— | Class on the root figure. |
ref |
Ref<HTMLElement> |
— | Forwarded to the root figure. |
Notes
- Keyboard: the legend is the keyboard path. Arrow keys move between rows (looping), Home/End jump, Enter/Space toggles or pins, Escape clears the pin. Without a legend the ring takes focus and arrows walk the segments.
- Accessibility: legend rows are buttons with
aria-pressedand a label giving value, share and grouped members; show/hide changes are announced with the new total. The ring and centre readout are hidden; a hidden summary and table list every part. - Layout gotcha: the legend sits beside the ring only when an ancestor is an inline-size container at least 460px wide (the figure's own
@containerrule cannot query itself). Give the wrappercontainer-type: inline-size, as the preview does; otherwise the legend stacks below the ring. - Keep keys stable across datasets so arcs morph in place.