Pie Chart
Parts of a whole, one slice per data row, with optional donut hole and token-driven colors.
Overview
PieChart is a recharts chart built on the shadcn chart primitive (ChartContainer) for parts of a whole. Pass tabular data, the nameKey (the slice label field), and the dataKey (the numeric value field). Slice colors come from each row's fill (e.g. fill: "var(--chart-1)"), and an optional config supplies the labels shown in the tooltip. Pass donut for a donut variant with a centered hole.
Reach for PieChart when proportions matter and you have two to six categories that sum to a meaningful total - traffic share by browser, revenue by tier, allocations by team. For seven or more categories, a BarChart reads faster; for time-series proportions, a stacked AreaChart carries more information.
The React edition (@gremorie/rx-data) renders with recharts inside the shadcn chart primitive; the Angular edition (@gremorie/ng-data) renders headless d3 geometry inside a styled SVG. The rendering engine differs; the public surface is identical input-for-input.
Anatomy
<pie-chart>
└─ figure (role="img") card surface, aspect-square
├─ svg
│ ├─ slices, one per row (donut hole when donut)
│ │ └─ row fill colored (palette / per-row fill)
│ └─ slice labels (optional via showLabels)
├─ tooltip overlay (optional via tooltip)
├─ legend
└─ sr-only data tableThe React edition composes this surface through the shadcn chart primitive:
PieChart recharts pie / donut on the shadcn chart primitive
└─ ChartContainer responsive square frame + tooltip label config
└─ RechartsPieChart recharts pie surface
├─ ChartTooltip hover card
│ └─ ChartTooltipContent themed tooltip body
└─ Pie one slice per row (nameKey + dataKey); innerRadius for donut
└─ LabelList optional slice labels (showLabels)Preview
- Chrome
- Safari
- Firefox
- Edge
- Other
| browser | visitors |
|---|---|
| Chrome | 275 |
| Safari | 200 |
| Firefox | 187 |
| Edge | 173 |
| Other | 90 |
'use client';import { PieChart, type ChartConfig, type ChartDatum } from '@gremorie/rx-data';// Categorical (pie / radial): one chart token per row via `fill`.const browserData: ChartDatum[] = [ { browser: 'Chrome', visitors: 275, fill: 'var(--chart-1)' }, { browser: 'Safari', visitors: 200, fill: 'var(--chart-2)' }, { browser: 'Firefox', visitors: 187, fill: 'var(--chart-3)' }, { browser: 'Edge', visitors: 173, fill: 'var(--chart-4)' }, { browser: 'Other', visitors: 90, fill: 'var(--chart-5)' },];const browserConfig: ChartConfig = { visitors: { label: 'Visitors' }, Chrome: { label: 'Chrome' }, Safari: { label: 'Safari' }, Firefox: { label: 'Firefox' }, Edge: { label: 'Edge' }, Other: { label: 'Other' },};export function PieChartPreview() { return ( <PieChart data={browserData} config={browserConfig} nameKey="browser" dataKey="visitors" donut /> );}Installation
bash npx gremorie@latest add rx-pie-chart bash pnpm dlx gremorie@latest add rx-pie-chart bash yarn dlx gremorie@latest add rx-pie-chart bash bunx --bun gremorie@latest add rx-pie-chart Usage
import { PieChart, type ChartConfig, type ChartDatum } from "@gremorie/rx-data";
const data: ChartDatum[] = [
{ browser: "Chrome", visitors: 275, fill: "var(--chart-1)" },
{ browser: "Safari", visitors: 200, fill: "var(--chart-2)" },
{ browser: "Firefox", visitors: 187, fill: "var(--chart-3)" },
{ browser: "Edge", visitors: 173, fill: "var(--chart-4)" },
{ browser: "Other", visitors: 90, fill: "var(--chart-5)" },
];
const config: ChartConfig = {
visitors: { label: "Visitors" },
Chrome: { label: "Chrome" },
Safari: { label: "Safari" },
Firefox: { label: "Firefox" },
Edge: { label: "Edge" },
Other: { label: "Other" },
};
export function BrowserShare() {
return (
<PieChart data={data} config={config} nameKey="browser" dataKey="visitors" />
);
}import { Component } from '@angular/core';
import { PieChart } from '@gremorie/ng-data';
import type { ChartConfig, ChartDatum } from '@gremorie/ng-data';
@Component({
selector: 'app-browser-share',
imports: [PieChart],
template: ` <pie-chart [data]="data" [config]="config" nameKey="browser" dataKey="visitors" />
`,
})
export class BrowserShareComponent {
readonly data: ChartDatum[] = [
{ browser: 'Chrome', visitors: 275, fill: 'var(--chart-1)' },
{ browser: 'Safari', visitors: 200, fill: 'var(--chart-2)' },
{ browser: 'Firefox', visitors: 187, fill: 'var(--chart-3)' },
{ browser: 'Edge', visitors: 173, fill: 'var(--chart-4)' },
{ browser: 'Other', visitors: 90, fill: 'var(--chart-5)' },
];
readonly config: ChartConfig = {
visitors: { label: 'Visitors' },
Chrome: { label: 'Chrome' },
Safari: { label: 'Safari' },
Firefox: { label: 'Firefox' },
Edge: { label: 'Edge' },
Other: { label: 'Other' },
};
}
API
<PieChart>
A recharts pie/donut composed inside the shadcn chart primitive (ChartContainer). Slice colors come from each row's fill; the optional config provides the labels shown in the tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
data | readonly ChartDatum[] | - | Tabular data. One row per slice. Each row needs a string field at nameKey, a numeric field at dataKey, and a fill for its slice color (e.g. var(--chart-1)). |
nameKey | string | - | Name of the slice label field. Used to resolve the tooltip label. |
dataKey | string | - | Name of the numeric value field. Slices are sized proportionally to its sum. |
config | ChartConfig | {} | Optional label/color map keyed by slice name (and value key). Powers the tooltip labels. |
donut | boolean | false | When true, carves out a centered hole (innerRadius 60) to produce a donut chart. |
showLabels | boolean | false | When true, draws each slice's name inside the slice via recharts LabelList. |
tooltip | boolean | true | Toggles the hover tooltip (ChartTooltip + ChartTooltipContent). |
className | string | - | Merged onto the ChartContainer. The bundled style supplies a centered, square aspect frame. |
Chart primitives for custom composition
For full control, compose raw recharts inside ChartContainer using the shadcn chart primitives, all re-exported from @gremorie/rx-data. This mirrors the official shadcn pie block (ui.shadcn.com/charts/pie).
| Export | Role |
|---|---|
ChartContainer | Responsive frame; injects per-key CSS color vars (--color-<key>) from config. |
ChartTooltip / ChartTooltipContent | Hover card and its themed content. |
ChartLegend / ChartLegendContent | Optional legend and its themed content. |
useChart | Hook returning the active config from context (use inside custom children). |
PieChart, Pie, Cell, Label | Raw recharts primitives composed inside ChartContainer. |
Types
| Type | Shape |
|---|---|
ChartDatum | Record<string, string | number> & { fill?: string } - one row of chart data; fill sets the per-slice color. |
ChartConfig | Map of key → { label?, icon?, color? }; provides tooltip/legend labels (and optional colors). |
Slice colors come from each row's fill (e.g. fill: "var(--chart-1)"), not
from config. The optional config maps each slice name to a {label} and
drives the labels shown in the tooltip.
Composition
- Pick
nameKeyanddataKey- the label column and the numeric column. Both must exist on every row. - Set a
fillper row - point each row at a--chart-*token (e.g.fill: "var(--chart-1)"). This is where slice colors come from. - Add a
configmapping each slice name (and the value key) to a{ label }so the tooltip reads cleanly. - Order rows by magnitude before passing them in. The biggest slice should sit at the 12-o'clock anchor so the eye reads it first.
- Toggle
donutwhen you want to embed a total or label at the center, or when the surrounding card is large enough that a solid pie feels too heavy.
The chart auto-sizes to its parent through ChartContainer (recharts ResponsiveContainer). The frame forces aspect-square so the slices stay circular.
Variations
Solid pie
- Chrome
- Safari
- Firefox
- Edge
- Other
| browser | visitors |
|---|---|
| Chrome | 275 |
| Safari | 200 |
| Firefox | 187 |
| Edge | 173 |
| Other | 90 |
'use client';import { PieChart, type ChartConfig, type ChartDatum } from '@gremorie/rx-data';// Categorical (pie / radial): one chart token per row via `fill`.const browserData: ChartDatum[] = [ { browser: 'Chrome', visitors: 275, fill: 'var(--chart-1)' }, { browser: 'Safari', visitors: 200, fill: 'var(--chart-2)' }, { browser: 'Firefox', visitors: 187, fill: 'var(--chart-3)' }, { browser: 'Edge', visitors: 173, fill: 'var(--chart-4)' }, { browser: 'Other', visitors: 90, fill: 'var(--chart-5)' },];const browserConfig: ChartConfig = { visitors: { label: 'Visitors' }, Chrome: { label: 'Chrome' }, Safari: { label: 'Safari' }, Firefox: { label: 'Firefox' }, Edge: { label: 'Edge' }, Other: { label: 'Other' },};export function PieChartPreview() { return ( <PieChart data={browserData} config={browserConfig} nameKey="browser" dataKey="visitors" donut /> );}<pie-chart [data]="data" [config]="config" nameKey="browser" dataKey="visitors" />The default. Use when the surrounding surface is small (card, side panel) or when no centered label is needed.
Donut variant
- Chrome
- Safari
- Firefox
- Edge
- Other
| browser | visitors |
|---|---|
| Chrome | 275 |
| Safari | 200 |
| Firefox | 187 |
| Edge | 173 |
| Other | 90 |
'use client';import { PieChart, type ChartConfig, type ChartDatum } from '@gremorie/rx-data';// Categorical (pie / radial): one chart token per row via `fill`.const browserData: ChartDatum[] = [ { browser: 'Chrome', visitors: 275, fill: 'var(--chart-1)' }, { browser: 'Safari', visitors: 200, fill: 'var(--chart-2)' }, { browser: 'Firefox', visitors: 187, fill: 'var(--chart-3)' }, { browser: 'Edge', visitors: 173, fill: 'var(--chart-4)' }, { browser: 'Other', visitors: 90, fill: 'var(--chart-5)' },];const browserConfig: ChartConfig = { visitors: { label: 'Visitors' }, Chrome: { label: 'Chrome' }, Safari: { label: 'Safari' }, Firefox: { label: 'Firefox' }, Edge: { label: 'Edge' }, Other: { label: 'Other' },};export function PieChartDonutPreview() { return ( <PieChart data={browserData} config={browserConfig} nameKey="browser" dataKey="visitors" donut /> );}<pie-chart
[data]="data"
[config]="config"
nameKey="browser"
dataKey="visitors"
[donut]="true"
/>Use when you want to embed a total or label at the center of the chart, or when a solid pie feels too heavy in a large surface. The hole uses an innerRadius of 60.
Labels
Set showLabels to draw each slice's name inside the slice, so the chart reads without a separate legend.
- Chrome
- Safari
- Firefox
- Edge
- Other
| browser | visitors |
|---|---|
| Chrome | 275 |
| Safari | 200 |
| Firefox | 187 |
| Edge | 173 |
| Other | 90 |
'use client';import { PieChart, type ChartConfig, type ChartDatum } from '@gremorie/rx-data';const browserData: ChartDatum[] = [ { browser: 'Chrome', visitors: 275, fill: 'var(--chart-1)' }, { browser: 'Safari', visitors: 200, fill: 'var(--chart-2)' }, { browser: 'Firefox', visitors: 187, fill: 'var(--chart-3)' }, { browser: 'Edge', visitors: 173, fill: 'var(--chart-4)' }, { browser: 'Other', visitors: 90, fill: 'var(--chart-5)' },];const browserConfig: ChartConfig = { visitors: { label: 'Visitors' }, Chrome: { label: 'Chrome' }, Safari: { label: 'Safari' }, Firefox: { label: 'Firefox' }, Edge: { label: 'Edge' }, Other: { label: 'Other' },};export function PieChartLabelsPreview() { return ( <PieChart data={browserData} config={browserConfig} nameKey="browser" dataKey="visitors" showLabels /> );}<PieChart
data={data}
config={config}
nameKey="browser"
dataKey="visitors"
showLabels
/><pie-chart
[data]="data"
[config]="config"
nameKey="browser"
dataKey="visitors"
[showLabels]="true"
/>Reach for labels when the surface is large enough to hold them and you want the slice names visible at rest. Combine with donut for a labeled donut.
Custom composition
When you need explicit per-slice colors, gradient fills, a center label, or a custom legend, compose raw recharts inside ChartContainer. This mirrors the official shadcn pie block.
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
type ChartDatum,
} from '@gremorie/rx-data';
import { Cell, Pie, PieChart } from 'recharts';
export function ExplicitColorPie({
data,
config,
nameKey,
dataKey,
colorByName,
}: {
data: ChartDatum[];
config: ChartConfig;
nameKey: string;
dataKey: string;
colorByName: Record<string, string>;
}) {
return (
<ChartContainer
config={config}
className="mx-auto aspect-square max-h-[260px]"
>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent nameKey={nameKey} hideLabel />}
/>
<Pie
data={data}
dataKey={dataKey}
nameKey={nameKey}
stroke="var(--background)"
strokeWidth={2}
>
{data.map((row) => (
<Cell
key={String(row[nameKey])}
fill={colorByName[String(row[nameKey])]}
/>
))}
</Pie>
</PieChart>
</ChartContainer>
);
}You can also skip <Cell> entirely and set fill directly on each row of data - the wrapper does exactly that.
Accessibility
- Accessibility layer: recharts'
accessibilityLayerprovides keyboard navigation and screen-reader announcements for the chart data. - Token contrast:
--chart-1through--chart-5ship with WCAG AA contrast against the card background in both light and dark themes. - Slice separation: stroke slices with the background color (see the custom composition example) so adjacent slices stay distinguishable for users with color-vision deficiencies even when the colors are close.
- Limit slice count: above six slices, a pie becomes hard to read. Reach for
BarChartinstead.
Related
- BarChart - reach for BarChart when you have more than six categories or want exact value comparison.
- RadialChart - sibling polar layout when categories should be shown as concentric arcs instead of slices.
- AreaChart - reach for a stacked AreaChart when proportions also need to vary over time.