PieChart

Parts of a single whole as slices, sorted largest-first. Honest for two or three shares and a rough read of the rest; past about five slices a `BarChart` compares them far better.

vue
<UiPieChart :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

required

data is one object per slice, and every value must be non-negative — a pie shows parts of one whole, and a negative part of a whole has no angle to draw.

  • Chrome · 6,400 visits
  • Safari · 2,800 visits
  • Firefox · 1,200 visits
  • Edge · 780 visits

Values must be non-negative — a pie shows parts of one whole, and a negative part isn't one. Negative and non-finite rows are dropped.

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.

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

required

categoryKey sets which field on each row labels the slice.

  • Chrome
  • Safari
  • Firefox
  • Edge

Value key

required

valueKey sets which field holds its numeric size.

  • Chrome
  • Safari
  • Firefox
  • Edge

ARIA label

required

ariaLabel is required, and worth writing carefully: slice angles are the hardest thing on any chart to read back, so the sentence should carry the finding rather than just naming the fields.

  • Chrome
  • Safari
  • Firefox
  • Edge

ARIA description

ariaDescription is the detail the label shouldn't carry.

Chrome dominates with 6,400 of 11,180 visits; Edge is the smallest share at 780.
  • Chrome
  • Safari
  • Firefox
  • Edge

Radius

radius rounds the corners of each slice. A little softens the wheel; too much starts eating into thin slices, which is how a small share disappears.

There's no innerRadius here on purpose. A pie with a hole in it is a donut, so that knob lives on DonutChart — one chart, one spelling.

Gap

gap is the space between slices, in degrees. Widen it to separate slices of similar colour; drop it to zero for an unbroken wheel.

Sort

sort orders the slices largest-first by default, which is what makes them comparable by eye. Turn it off when the rows already have a meaningful order — severity, plan tier, a funnel.

  • Medium
  • Low
  • High
  • Critical
  • Critical
  • High
  • Medium
  • Low

Slices are sorted largest-first by default. Comparing two similar angles is hard enough without them scattered around the circle.

Colors

colors pins a paint per slice, matched positionally to data — so it lines up with the rows as given, before any sorting.

  • Critical
  • High
  • Medium
  • Low

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 format

valueFormat formats the values in the tooltip and the legend.

  • Chrome · 6,400 visits
  • Safari · 2,800 visits
  • Firefox · 1,200 visits
  • Edge · 780 visits

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 is on by default, and worth keeping: without it the slices carry their identity in colour alone, which only works when the surrounding copy names them.

The legend is on by default and should usually stay on: without it, slices carry their identity in colour alone.

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 wheel, 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 is the natural fit for a pie — one slice per line, beside or beneath the circle.

  • Chrome · 6,400 visits
  • Safari · 2,800 visits
  • Firefox · 1,200 visits
  • Edge · 780 visits

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. To keep the values in a column, make the legend vertical.

  • Chrome6,400 visits
  • Safari2,800 visits
  • Firefox1,200 visits
  • Edge780 visits
  • Chrome57%
  • Safari25%
  • Firefox11%
  • Edge7%
  • Chrome6,400 visits · 57%
  • Safari2,800 visits · 25%
  • Firefox1,200 visits · 11%
  • Edge780 visits · 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 plot lists them as well.

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

No data to display

Examples

The component in real use: compositions that answer "how do I build X with this", rather than one prop at a time.

Fold the tail into “Other”

A pie stops being readable past five or six slices — the small ones become slivers no one can compare. Group the tail before it reaches the chart rather than shrinking the slices further.

  • Chrome · 6,400 visits
  • Safari · 2,800 visits
  • Other · 1,980 visits

Good for two or three shares and a rough read of the rest. Past about five slices the small ones stop being distinguishable and a horizontal BarChart compares them far better; fold the tail into an "Other" row rather than adding more colours.

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.

  • Chrome57%
  • Safari25%
  • Firefox11%
  • Edge7%

API Reference

Generated from the component's source — props, slots and emits as the component actually declares them.

Props

Prop
Type
Default
Description
datarequired
Record<string, unknown>[]
—
One object per slice. Every value must be non-negative — a pie shows parts of one whole, and a negative part isn't one. Want a hole in the middle? That's `UiDonutChart`, which adds one along with a slot for the number that goes in it.
categoryKeyrequired
string
—
Field holding each slice's label.
valueKeyrequired
string
—
Field holding each slice's numeric value.
ariaLabelrequired
string
—
What the chart shows, for screen readers. Required, and worth writing carefully: slice angles are the hardest encoding to read back, so name the headline share.
string
—
A longer accessible description, for detail the label shouldn't carry.
number
6
Corner radius on each slice, in pixels.
number
1
Gap between slices in degrees.
boolean
true
Sorts slices largest-first.
ChartColorName[]
—
Per-slice colors, positionally matched to `data`.
(value: number) => string
—
Formats values in the tooltip and legend.
boolean
true
Shows the tooltip on hover.
boolean
true
Sweeps the slices open on the first draw, and eases them to their new angles whenever the data changes. The tooltip and legend always read the real numbers. Readers who ask their system for reduced motion get no animation either way.
boolean
true
Shows the legend. Slices carry their identity in color alone otherwise.
"top" | "bottom"
'bottom'
Where the legend sits relative to the plot.
"start" | "center" | "end"
'center'
Horizontal placement of the legend.
"horizontal" | "vertical"
'horizontal'
How the legend entries flow.
false | "value" | "share" | "both"
false
What each legend entry prints beside its label, as the muted `text` of a `UiChartLegend` entry — the count, the share of the whole, or both — so a vertical legend reads as a column. - `false` — nothing extra. With a `valueFormat` the value is still appended to the label itself, as it always was. - `value` — the slice's value, through `valueFormat` when set. - `share` — its share of the total, as a whole percent in the active locale. - `both` — `value · share`. Set, the label is the bare one: the chart owns the numbers, so `valueFormat` no longer writes them into it a second time.
string
—
Names the group a `<UiChartLegend group="…">` elsewhere on the page belongs to, so hovering its entries dims this chart's other marks. Set it — usually with `:legend="false"` — to place the legend anywhere you like instead of under the plot.
number
320
Height of the plot in pixels. Ignored when `aspectRatio` is set.
number
—
Width-to-height ratio, sizing the chart from its container's width.
boolean
false
Replaces the plot with a skeleton of the same height.

Slots

Slot
legend-item
Receives `{ item, index, hovered, swatchClass }`.

Emits

Event
Payload
Description
focusChange
[label: string | null]
The slice under the pointer, or `null` once it leaves.

Types

ts
interface ChartPlot {
    x: number;
    y: number;
    width: number;
    height: number;
    /** Right edge, since marks need it more often than the width. */
    right: number;
    /** Bottom edge, which is also the x axis' baseline. */
    bottom: number;
}
ts
type ChartColorName = ChartColorFamily | `${ChartColorFamily}-${ChartColorShade}` | `[${string}]`;