Charts
Donut Chart
Ratio-based rings and semicircles with center totals, outside labels, and focused subpath imports.
Charts / Donut Chart
Overview
import { DonutChart } from "@kamod-ch/charts/donut";
DonutChart is the dedicated preset for rings and semicircles. It scales the hole with innerRadiusRatio, applies product defaults (rounded corners, slice gaps, outside labels on full charts), and can render an automatic center total via showTotal.
API notes
DonutChart is the ratio-first preset for rings and semicircles. Import from @kamod-ch/charts/donut for a tree-shakeable entry.
- DonutChart vs PieChart
- Use
DonutChartwhen the hole size should scale with plot size viainnerRadiusRatio. PieChart keeps pixelinnerRadiusfor legacy callers and supportsrose. For a permanent filled pie, prefer PieChart. - innerRadiusRatio
- Ratio of the resolved outer radius, clamped to
[0, 0.95]. Default0.68matches UIpkgethickness=0.32because1 - 0.32 = 0.68. Set0for a filled disc (see Filled example). - variant full / half
fullrenders a closed ring with default outside labels.halfuses the upper semicircle, reserves space below the baseline for center totals, and pairs best withlabels="none".- Center content priority
centerContentwins overformatTotal, which wins over automaticshowTotal. Pass a render function to receiveDonutCenterContextwithtotal(visible slices) andgrandTotal(all positive slices).- padAngle & cornerRadius
padAngleis in radians (d3 convention). DefaultMath.PI / 90≈ 2°. Convert degrees with(degrees * Math.PI) / 180.cornerRadiusrounds outer arc corners;activeOffsetexpands the focused slice (default3px).- Legend & tooltip
- Defaults are
showLegend=falseandtooltip=false— opt in explicitly. Legend toggles updatetotalbut notgrandTotal. Tooltips reusePieTooltipOptions. - Values & responsive SSR
- Only finite values strictly greater than zero become slices. Use
emptyContentwhen every value is non-positive. PairresponsivewithinitialWidthfor stable SSR markup before ResizeObserver measures the container. - Focused import
import { DonutChart } from "@kamod-ch/charts/donut"avoids cartesian chart entry modules. Root@kamod-ch/chartsre-exports the same component and types.
Gallery
Four reference variants — revenue split, filled disc, half progress, and KPI ring — each using the public package API with real data accessors.
Revenue split
Full variant with DonutChart defaults: innerRadiusRatio 0.68, cornerRadius 6, padAngle π/90 (~2°), outside labels with leader lines, and showTotal in the center. Tooltip and legend are enabled explicitly because both default to false.
Resize for mobile — outside labels reflow and the center total stays inside the hole. Long legend labels wrap in the HTML legend.
Filled donut (innerRadiusRatio=0)
Setting innerRadiusRatio to 0 removes the hole entirely. This is clearer than UIpkge thickness={0}, which still implies a ring component. For a permanent filled pie without ratio semantics, prefer PieChart.
Half donut progress
variant="half" renders the upper semicircle. Labels default to outside on full donuts but are set to "none" here so the baseline stays clean. showTotal renders below the arc in the reserved bottom zone. Tooltip, legend, and keyboard navigation mirror PieChart.
KPI ring
Used versus available capacity. centerContent="68%" works for static copy; the render function derives the percentage from DonutCenterContext without misusing progressbar semantics — use title and description for the metric meaning.
Accessibility
- Set title and description on every production chart. Pair critical KPIs with a DataTable from @kamod-ch/charts/accessibility.
- Tab to the SVG surface and use ArrowLeft/ArrowRight to move between slices; Enter or Space fires onSegmentClick.
- Focus rings follow the active slice expansion — verify in light and dark themes.
- Long legend labels wrap in the HTML legend without clipping the chart frame.
- Responsive + initialWidth keeps SSR markup stable; respect prefers-reduced-motion via Chart animate options or @kamod-ch/charts/motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| valueKey | DataAccessor<TDatum, number | null | undefined> | — | Numeric value for each slice. Only finite values strictly greater than zero render as slices. |
| labelKey | DataAccessor<TDatum, string | number | Date | null | undefined> | — | Label accessor for slices, legend items, and tooltips. |
| height | number | — | Chart height in pixels. |
| width | number | — | Fixed width. Prefer responsive + initialWidth for fluid layouts. |
| initialWidth | number | — | SSR / first-paint width before ResizeObserver measures the container. |
| responsive | boolean | false | Stretch to the parent container width. |
| title | string | — | Accessible chart title rendered into SVG title. |
| description | string | — | Accessible description rendered into SVG desc. |
| variant | "full" | "half" | "full" | full renders a closed ring; half renders the upper semicircle with space below the baseline for center totals. |
| innerRadiusRatio | number | 0.68 | Inner hole as a ratio of outer radius, clamped to [0, 0.95]. 0.68 matches UIpkge thickness 0.32. Set 0 for a filled disc. |
| outerRadius | number | — | Outer radius in pixels. Defaults to available plot size; outside labels auto-shrink radius for gutter space. |
| padAngle | number | Math.PI / 90 | Gap between adjacent slices in radians (~2° by default). |
| cornerRadius | number | 6 | Rounded outer corners for slice arcs. |
| sort | boolean | PieSortComparator<TDatum> | false | When true, sorts slices by descending value. Pass a comparator for custom ordering. |
| labels | PieLabelPosition | PieLabelOptions<TDatum> | "outside" | Default "outside" on DonutChart (PieChart defaults to "inside"). Use "none" on half variants. |
| colors | readonly string[] | — | Explicit palette for slices. Colors stay stable by original data index. |
| showTotal | boolean | true | Render automatic center total when centerContent is omitted. Uses visible slice sum. |
| formatTotal | DonutFormatTotal<TDatum> | — | Format the visible total for center display. Ignored when centerContent is set. |
| centerContent | DonutCenterContent<TDatum> | — | Static children, primitives, or render function receiving DonutCenterContext. Highest priority for center display. |
| emptyContent | ComponentChildren | — | Fallback when data exists but no slice has a finite value greater than zero, or when every slice is hidden via legend. |
| activeOffset | number | 3 | Pixel expansion applied to the active slice outer radius on pointer or keyboard focus. |
| tooltip | boolean | PieTooltipOptions<TDatum> | false | Pointer and keyboard tooltips for the active slice. true uses default value and percentage formatting. |
| showLegend | boolean | false | Render an HTML legend with per-slice visibility toggles keyed by stable slice keys. |
| legend | PieLegendOptions | — | Legend behavior. interactive defaults to true for segment toggling. |
| onSegmentClick | (datum, index, value) => void | — | Called when a slice is activated with pointer click or keyboard Enter/Space. |
| animate | boolean | ChartAnimateOptions | — | Optional data animation. No-op on the static donut entry unless MotionConfig is present. |
DonutCenterContext
Passed to centerContent render functions and formatTotal.
| Prop | Type | Default | Description |
|---|---|---|---|
| data | readonly TDatum[] | — | Original chart data array. |
| variant | DonutVariant | — | Resolved variant: full or half. |
| total | number | — | Sum of visible slice values (respects legend toggles). |
| grandTotal | number | — | Sum of all positive slices regardless of legend visibility. |
| visibleCount | number | — | Number of slices currently visible. |
| segmentCount | number | — | Number of slices with positive values in the source data. |
PieTooltipOptions
DonutChart reuses pie tooltip options via tooltip.
| Prop | Type | Default | Description |
|---|---|---|---|
| placement | TooltipPlacement | — | Tooltip placement relative to the slice anchor. |
| offset | number | — | Pixel gap between the anchor and tooltip content. |
| render | (context: PieSliceContext<TDatum>) => ComponentChildren | — | Fully custom tooltip body. Receives the active slice context. |
| formatValue | (value, context) => ComponentChildren | null | undefined | — | Format the numeric value. Return null to fall back to the default string. |
| formatPercentage | (percentage, context) => ComponentChildren | null | undefined | — | Format the percentage (0–100). Return null to fall back to one decimal place with a percent sign. |
PieLegendOptions
Nested under legend when showLegend is true.
| Prop | Type | Default | Description |
|---|---|---|---|
| interactive | boolean | true | When true, legend items toggle slice visibility. Hidden slices are excluded from totals and navigation. |
DonutChart vs PieChart
| Concern | DonutChart | PieChart |
| ------------------ | ---------------------------------------------------------- | ---------------------- |
| Hole sizing | innerRadiusRatio (0–0.95) scales with plot size | Pixel innerRadius |
| Layout presets | variant="full" \| "half", default outside labels on full | Full circle only |
| Center totals | showTotal, formatTotal, typed centerContent | centerContent only |
| Rose / Nightingale | Not supported — use PieChart | rose option |
| Import | @kamod-ch/charts/donut (tree-shakeable) | @kamod-ch/charts/pie |
When you need a permanent filled pie without ratio semantics, prefer PieChart. DonutChart with innerRadiusRatio={0} is explicit about removing the hole but still routes through the donut preset.
Variants, ratios, and UIpkge mapping
variant="full" (default) renders a closed ring centered in the plot. Default labels: "outside" with leader lines.
variant="half" renders the upper semicircle (-π/2 through π/2) with a lower center point. Reserve the baseline area for totals — use labels="none" so outside labels do not collide with the flat edge.
innerRadiusRatio maps to the inner hole as a fraction of the resolved outer radius, clamped to [0, 0.95]. Default 0.68 matches UIpkge thickness={0.32} because ring thickness 0.32 leaves 1 - 0.32 = 0.68 as inner ratio. Set innerRadiusRatio={0} for a filled disc.
padAngle is in radians (d3 convention). Default Math.PI / 90 ≈ 2°. Convert degrees: (degrees * Math.PI) / 180.
cornerRadius rounds outer arc corners (default 6). activeOffset expands the focused slice (default 3 px on DonutChart vs 6 on PieChart).
Center content priority
centerContent— static children, primitives ("68%"), or(context) => …render function. Wins over all automatic totals.formatTotal— formats the visibletotalwhenshowTotalis true andcenterContentis omitted.showTotal— whentrue(default), rendersString(context.total)if neither of the above overrides apply.
total sums visible slices (respects legend toggles). grandTotal always sums every positive slice regardless of legend visibility — useful for KPI denominators.
Tooltip, legend, and interaction
Both tooltip and showLegend default to false on DonutChart — enable them explicitly in dashboards that need exploration (see Revenue split). Tooltips reuse PieSliceContext and PieTooltipOptions. The legend toggles slice visibility by stable slice keys; percentages in tooltips track visible slices only.
Keyboard: tab to the chart surface, ArrowLeft/ArrowRight move between slices, Enter/Space activate onSegmentClick. Pointer hover applies activeOffset expansion with visible focus alignment.
Values, empty states, and responsive SSR
Only finite values strictly greater than zero become slices. Zero, negative, and non-finite values are omitted. When no slice qualifies, render emptyContent. When every slice is hidden via legend, emptyContent appears while the legend remains interactive.
Pair responsive with initialWidth so SSR and first client paint share the same width before ResizeObserver runs. See Responsive charts.
Motion and focused imports
Static @kamod-ch/charts/donut does not bundle motion. Wrap charts in MotionConfig from @kamod-ch/charts/motion when animation is required, and honor prefers-reduced-motion — see Motion.
The focused entry avoids cartesian chart modules:
import { DonutChart } from "@kamod-ch/charts/donut";
import type { DonutChartProps, DonutCenterContext } from "@kamod-ch/charts/donut";
Root @kamod-ch/charts re-exports the same symbols.
Accessibility
Always set title and description — they render into SVG <title> and <desc>. For critical financial or operational metrics, pair the chart with a DataTable so screen-reader users can read exact values without relying on slice color alone.
Do not use DonutChart as a live progressbar — describe utilization in title/description and keep centerContent informational (see KPI ring example).