DonutChart
A `PieChart` with its middle opened up and filled with the number the chart is actually about — a total, a headline share — as ordinary DOM rather than SVG text.
<UiDonutChart :data="..." :categoryKey="..." :valueKey="..." :ariaLabel="..." />Needs the charts module
This component ships in @jgastager/bundy-ui/charts, a separate module so projects that never draw a chart don't carry the paint classes these need. Nothing extra to install — just add the module entry. See Charts.
Options
One block per prop — what it does, and what it looks like set.
Data
requireddata is one object per slice, all values non-negative. The hole in the middle is what a donut adds over a pie: room for the total the slices add up to.
- Chrome · 6,400
- Safari · 2,800
- Firefox · 1,200
- Edge · 780
A donut is a pie with the middle opened up, and the hole is the only reason to pick one — it's somewhere to put the number the chart is actually about. If you don't need that, PieChart is the same chart without the extra prop.
The hover fade is a CSS transition over the library's own 250ms duration, so it costs nothing and honours prefers-reduced-motion. There's no animate prop and nothing tweens on a data change — the marks are plain SVG elements Vue patches directly.
Category key
requiredcategoryKey sets which field on each row labels the slice.
- Chrome
- Safari
- Firefox
- Edge
Value key
requiredvalueKey sets which field holds its numeric size.
- Chrome
- Safari
- Firefox
- Edge
ARIA label
requiredariaLabel is required, and worth writing carefully — slice angles are the hardest thing on any chart to read back, so a screen reader gets nothing at all without it.
- Chrome
- Safari
- Firefox
- Edge
ARIA description
ariaDescription is the detail the label shouldn't carry.
- Chrome
- Safari
- Firefox
- Edge
Label
label names what the center number counts. Without it the number sits there unexplained.
- Chrome · 6,400
- Safari · 2,800
- Firefox · 1,200
- Edge · 780
- Chrome · 6,400
- Safari · 2,800
- Firefox · 1,200
- Edge · 780
Colors are the library's own semantic ones — primary, secondary, success, warning, error, info, pending, neutral — and any shade of them, primary-400 through primary-950, exactly as the bg-* utilities spell them. Set color per series (or per item) to pin one; leave it unset to take the next slot of the default sequence. neutral and secondary are never handed out automatically, so an unlabelled series never lands on them.
Value
value overrides the center number, which otherwise sums every slice. Use it when the headline isn't the total — a leading share, a budget remaining.
- Chrome
- Safari
- Firefox
- Edge
The default centre value is the sum of every slice. Pass value when the headline is something else — a share, a delta, a target.
Everything the PieChart notes say about sorting, non-negative values and slice counts applies here too; the two share their slice allocation and geometry.
Inner radius
innerRadius is the size of the hole, as a fraction of the radius — and the whole difference between a donut and a PieChart. Shrink it toward zero and you get a pie with a caption in it.
Radius
radius rounds the corners of each slice. A little softens the ring; too much starts eating into thin slices, which is how a small share disappears.
Gap
gap is the space between slices, in degrees. Zero gives an unbroken ring; a degree or two is enough to separate neighbouring slices that share a similar colour.
Sort
sort orders the slices largest-first by default, which is what makes a donut readable at a glance. Turn it off when the rows already have a meaningful order — a rating scale, a funnel.
- Medium
- Low
- High
- Critical
- Critical
- High
- Medium
- Low
Colors
colors pins a paint per slice, matched positionally to data before any sorting — so the colours follow your rows rather than the rendered order.
- Critical
- High
- Medium
- Low
Value format
valueFormat covers the tooltip, the legend, and the default center total together.
- Chrome · 6,400
- Safari · 2,800
- Firefox · 1,200
- Edge · 780
Tooltip
tooltip shows the tooltip on hover.
- Chrome
- Safari
- Firefox
- Edge
Animate
animate is on by default: the slices sweep open on the first draw and slide to their new angles. Roll new values to compare it with the chart on the right, which has it off. Only the marks move — axes, tooltips and labels always read the real numbers — and a reader who has asked their system for reduced motion gets no motion either way.
- Chrome
- Safari
- Firefox
- Edge
- Chrome
- Safari
- Firefox
- Edge
Legend
legend shows the legend.
The centre is real DOM positioned over the plot, not SVG text — so it can hold any Bundy component, and the number stays selectable and searchable. It's pinned to whichever edge the plot occupies so the legend doesn't push it off-centre.
Legend position
legendPosition sets where the legend sits relative to the plot.
- Chrome
- Safari
- Firefox
- Edge
Legend align
legendAlign places the key horizontally. Centre reads well under a ring, which is itself centred, where start would leave it visibly off-axis.
- Chrome
- Safari
- Other
- Chrome
- Safari
- Other
- Chrome
- Safari
- Other
Legend orientation
legendOrientation flows the entries in a row or a column. vertical reads well beside the ring — one slice per line, in the same order as the arcs.
- Chrome · 6,400
- Safari · 2,800
- Firefox · 1,200
- Edge · 780
Hovering dims every slice except the focused one to 0.7 rather than drawing a ring on it — a ring is a dot, which reads as a stray mark sitting on the arc. The hit test is polar: the angle and distance under the pointer, not a bounding box.
Legend values
legendValues prints each slice's number in the legend, muted, beside its label — value, share (a whole percent, in the active locale) or both. The chart owns the numbers, so the label stays bare: with valueFormat set you no longer get the value written into it as well. Leave it off and the legend is exactly what it was.
- Chrome6,400
- Safari2,800
- Firefox1,200
- Edge780
- Chrome57%
- Safari25%
- Firefox11%
- Edge7%
- Chrome6,400 · 57%
- Safari2,800 · 25%
- Firefox1,200 · 11%
- Edge780 · 7%
Only PieChart and DonutChart fill legend text for you; GaugeChart, RadialBarChart and VennChart don't yet. The values reach a standalone UiChartLegend on the same legendGroup too, so a legend placed beside the ring lists them as well — that's the way to get a legend down the side with its values in a column.
Legend group
legendGroup names a group that a standalone UiChartLegend with the same group belongs to, so the legend can sit anywhere — here above the plot — and still dim the other slices while you hover an entry. The chart hands the legend its entries, colors included, so it needs no items. Switch the chart's own legend off if you don't want two.
Height
height is the plot's height in pixels; the ring is sized to fit inside it. It's ignored once aspectRatio is set.
- Chrome
- Safari
- Other
- Chrome
- Safari
- Other
Aspect ratio
aspectRatio sizes the chart from its container's width instead of a fixed height.
- Chrome
- Safari
- Firefox
- Edge
Loading
loading is a skeleton at the chart's exact height; an empty data array gets the empty state instead.
Examples
The component in real use: compositions that answer "how do I build X with this", rather than one prop at a time.
Legend down the side
The chart's own legend sits under the plot. To put it beside the ring, switch that one off and place a UiChartLegend in the same group — it lists whatever the chart draws, legendValues texts included, so there's nothing to restate and the values line up as a column. Put it after the chart in the markup: a group-fed legend can only fill in once the chart has run.
- Positiv18 · 67%
- Neutral7 · 26%
- Negativ2 · 7%
Custom legend rows
#legend-item replaces what's inside each legend row, with the same scope UiChartLegend's #item gets: item (its text is the legendValues string), index, hovered and swatchClass. The row's hover handling stays outside it, so a custom row still dims the other slices.
- Positiv67%
- Neutral26%
- Negativ7%
API Reference
Generated from the component's source — props, slots and emits as the component actually declares them.
Props
number0.62(value: number) => stringbooleantruefalse | "value" | "share" | "both"falsestringSlots
legend-itemdefaultEmits
focusChange[label: string | null]Types
type ChartColorName = ChartColorFamily | `${ChartColorFamily}-${ChartColorShade}` | `[${string}]`;