# PieChart

Parts of one whole.

- Group: Charts
- Import: `import { PieChart } from '@misoto22/folio/charts'`
- Page: https://ui.misoto22.com/components/pie-chart/
- Related: radial-chart, bar-chart

## When to reach for it

Roughly what share, and nothing more precise. Ranking or comparing wedges — especially across two pies — is a BarChart’s job.

## Anatomy

- **Figure frame** (required) — ChartFigure’s <figure>, with an empty state at zero rows. There are no axes here, so without one a pie at zero rows was a name over a blank box with the hidden table returning null — the picture and its text equivalent silent together. empty={false} restores that, for a chart whose emptiness is the reading.
- **Wedges** (required) — <PieChart.Pie>. Each sector is painted from a diagonal gradient keyed on the row’s nameKey value, so config keys must match those values exactly — a row whose name is absent from config points at a gradient that was never defined and comes out unpainted.
- **Legend** — <PieChart.Legend>, under the pie and centred by default. A pie has no category axis naming its sectors, so this is where the names live.
- **Wedge labels** — <PieChart.Label>, a LabelList reversed out in --chart-surface. Its dataKey defaults to the pie’s VALUE key, so composing it prints the numbers.
- **Tooltip** — <PieChart.Tooltip>, with the heading suppressed: the wedge’s own name is the row label, so a heading would print it twice.

## Best practices

### Do

- Keep it under about five wedges. Past that the reader is ranking angles, which is the comparison a pie is worst at — a BarChart puts the same shares on a length scale and the ranking falls out of the picture for free.
- Compose <PieChart.Label> whenever the exact share matters. It defaults to the value key, and a printed number removes the angle estimate entirely, which is this form’s only real weakness and its cheapest fix.
- Give it an innerRadius. A donut is read by arc LENGTH rather than by wedge area, which the eye does better, and the hole is somewhere to put the total.

### Don’t

- A negative value has no wedge. Parts of one whole cannot include a negative part, so a breakdown carrying a refund or churn against expansion is a WaterfallChart, which is built for signed contributions.
- Two pies side by side is not a comparison. Reading a wedge across two circles is harder than reading two wedges inside one, and the reader will try anyway — put the two periods in one grouped BarChart.
- Pass a POSITIVE paddingAngle if the chart has to survive forced colours. There is one fill variant here, so all eight --series-* tokens collapse to CanvasText and every wedge is the same solid shape; what separates them is geometry, and a gap of a degree or two is it. The stroke is not the mechanism — it is drawn only when paddingAngle is NEGATIVE, where the wedges overlap and the surface-coloured stroke re-separates them into stacked cards. 0 is the one value with neither, and a default pie in forced colours is one uniform disc with the legend and the labels carrying the whole reading.

## Accessibility

- title is required; the rows are also rendered as a visually hidden table.
- The legend sits under the pie by default, because a pie has no category axis naming its sectors.
- Compose a Label to print the numbers on the wedges: a pie’s weakness is that an angle is hard to read, and a printed number removes the guess.
- One fill variant, deliberately. A wedge is small and awkwardly shaped, and a texture inside one reads as noise.

## PieChart

Parts of one whole. Worth saying plainly: a pie answers "roughly what share" and nothing more precise. Comparing two adjacent wedges by eye is unreliable past about five of them, and comparing a wedge across two pies is worse. When the reader needs to rank or compare, a `<BarChart>` answers the same question better.

### Props

- `config` (required) — `ChartConfig`. Sector names → their label and paint. Keys must match the `nameKey` values.
- `data` (required) — `TData[]`. The rows the chart draws. One entry per point, bar or category.
- `dataKey` (required) — `keyof TData & string`. The row field holding each sector's number.
- `nameKey` (required) — `keyof TData & string`. The row field holding each sector's name.
- `title` (required) — `string`. What the chart shows, in a sentence a reader could act on. Required, and announced to a screen reader even when it is not printed.
- `showTitle` — `boolean`. Prints the title above the plot instead of hiding it from sight.
- `description` — `ReactNode`. A line under the title — the unit, the window, the caveat.
- `children` (required) — `ReactNode`. The composed parts — axes, grid, tooltip, legend, and the marks themselves.
- `className` — `string`. Merged onto the figure, last, so a call site can size or space it.
- `chartProps` — `ComponentProps<typeof RechartsPieChart>`. Escape hatch onto the raw Recharts chart element.
- `defaultSelectedSector` — `string | null` default `null`. The sector lit on first render, when the chart keeps its own selection.
- `selectedSector` — `string | null`. The selected sector, driven from outside. Give this and the chart follows it; leave it undefined and the chart keeps its own, starting from `defaultSelectedSector`.
- `onSelectionChange` — `(selection: { name: string; value: number } | null) => void`. Fires when the selection changes, and with null when it is cleared.
- `isLoading` — `boolean` default `false`. Swaps the marks for an animated skeleton, keeping the measured height so the page does not jump when the data lands.
- `hideDataTable` — `boolean` default `false`. Drops the hidden table view. Only correct when the page prints the data itself.
- `empty` — `ChartEmptyProps | false`. What the chart shows when it has nothing to draw. `false` keeps the empty plot, for a chart whose emptiness is itself the reading.

## Example — default

```tsx
import { PieChart, type ChartConfig } from '@misoto22/folio/charts'

<PieChart
  title="Visitors by browser"
  config={config}
  data={data}
  dataKey="visitors"
  nameKey="browser"
>
  <PieChart.Pie />
  <PieChart.Tooltip />
  <PieChart.Legend />
</PieChart>
```

## Example — shape

```tsx
import { ToggleGroup, ToggleGroupItem } from '@misoto22/folio'
import { PieChart, type ChartConfig } from '@misoto22/folio/charts'

<div className="flex w-full flex-col gap-4">
  <ToggleGroup
    type="single"
    value={shape}
    onValueChange={(next) => next && setShape(next as Shape)}
    aria-label="Shape"
  >
    {(Object.keys(SHAPES) as Shape[]).map((option) => (
      <ToggleGroupItem key={option} value={option}>
        {option}
      </ToggleGroupItem>
    ))}
  </ToggleGroup>

  <PieChart
    title={`Visitors by browser — ${shape}`}
    config={config}
    data={data}
    dataKey="visitors"
    nameKey="browser"
  >
    <PieChart.Pie {...SHAPES[shape]} />
    <PieChart.Tooltip />
    <PieChart.Legend />
  </PieChart>
</div>
```

## Example — labels and emphasis

```tsx
import { PieChart, type ChartConfig } from '@misoto22/folio/charts'

<div className="grid w-full gap-8 lg:grid-cols-2">
  <PieChart
    title="Labelled"
    showTitle
    config={config}
    data={data}
    dataKey="visitors"
    nameKey="browser"
  >
    <PieChart.Pie innerRadius="45%">
      <PieChart.Label />
    </PieChart.Pie>
    <PieChart.Legend />
  </PieChart>

  <PieChart
    title="One wedge is the point"
    showTitle
    config={config}
    data={data}
    dataKey="visitors"
    nameKey="browser"
  >
    <PieChart.Background variant="overlapping-circles" />
    <PieChart.Pie innerRadius="50%" glowingSectors={['chrome']} isClickable />
    <PieChart.Tooltip />
    <PieChart.Legend isClickable />
  </PieChart>
</div>
```

## Example — loading

```tsx
import { PieChart, type ChartConfig } from '@misoto22/folio/charts'

<PieChart
  title="Visitors by browser"
  config={config}
  data={[]}
  dataKey="visitors"
  nameKey="browser"
  isLoading
>
  <PieChart.Pie innerRadius="50%" />
</PieChart>
```
