Donut Chart | kamod chartsSkip to content

Charts

Donut Chart

Ratio-based rings and semicircles with center totals, outside labels, and focused subpath imports.

Charts / Donut Chart

innerRadiusRatiofull & halfCenter totals@kamod-ch/charts/donut

Overview

Revenue splitFull donut with default ring opening, rounded corners, slice gaps, outside labels, and automatic center total.1000
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 DonutChart when the hole size should scale with plot size via innerRadiusRatio. PieChart keeps pixel innerRadius for legacy callers and supports rose. For a permanent filled pie, prefer PieChart.
innerRadiusRatio
Ratio of the resolved outer radius, clamped to [0, 0.95]. Default 0.68 matches UIpkge thickness=0.32 because 1 - 0.32 = 0.68. Set 0 for a filled disc (see Filled example).
variant full / half
full renders a closed ring with default outside labels. half uses the upper semicircle, reserves space below the baseline for center totals, and pairs best with labels="none".
Center content priority
centerContent wins over formatTotal, which wins over automatic showTotal. Pass a render function to receive DonutCenterContext with total (visible slices) and grandTotal (all positive slices).
padAngle & cornerRadius
padAngle is in radians (d3 convention). Default Math.PI / 90 ≈ 2°. Convert degrees with (degrees * Math.PI) / 180. cornerRadius rounds outer arc corners; activeOffset expands the focused slice (default 3 px).
Legend & tooltip
Defaults are showLegend=false and tooltip=false — opt in explicitly. Legend toggles update total but not grandTotal. Tooltips reuse PieTooltipOptions.
Values & responsive SSR
Only finite values strictly greater than zero become slices. Use emptyContent when every value is non-positive. Pair responsive with initialWidth for 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/charts re-exports the same component and types.

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.

variant=fulltooltipshowLegendlabels=outside

Resize for mobile — outside labels reflow and the center total stays inside the hole. Long legend labels wrap in the HTML legend.

Revenue split by product lineShare of quarterly revenue. Tab to the chart, use arrow keys to move between slices, and Enter to activate. Legend toggles update visible totals.1000

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.

innerRadiusRatio=0filled
Filled revenue discNo center hole — innerRadiusRatio is explicitly zero.

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.

variant=halflabels=nonekeyboard
Half-year attainmentSemicircle progress with legend toggles, slice tooltips, and arrow-key navigation when the chart is focused.Visible total100k · 2/2

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.

centerContenttitledescription
Workspace storage utilization680 GB used of 1 TB provisioned capacity. This chart summarizes utilization; it is not a live progressbar control.

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

DonutChart props
PropTypeDefaultDescription
valueKeyDataAccessor<TDatum, number | null | undefined>—Numeric value for each slice. Only finite values strictly greater than zero render as slices.
labelKeyDataAccessor<TDatum, string | number | Date | null | undefined>—Label accessor for slices, legend items, and tooltips.
heightnumber—Chart height in pixels.
widthnumber—Fixed width. Prefer responsive + initialWidth for fluid layouts.
initialWidthnumber—SSR / first-paint width before ResizeObserver measures the container.
responsivebooleanfalseStretch to the parent container width.
titlestring—Accessible chart title rendered into SVG title.
descriptionstring—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.
innerRadiusRationumber0.68Inner 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.
outerRadiusnumber—Outer radius in pixels. Defaults to available plot size; outside labels auto-shrink radius for gutter space.
padAnglenumberMath.PI / 90Gap between adjacent slices in radians (~2° by default).
cornerRadiusnumber6Rounded outer corners for slice arcs.
sortboolean | PieSortComparator<TDatum>falseWhen true, sorts slices by descending value. Pass a comparator for custom ordering.
labelsPieLabelPosition | PieLabelOptions<TDatum>"outside"Default "outside" on DonutChart (PieChart defaults to "inside"). Use "none" on half variants.
colorsreadonly string[]—Explicit palette for slices. Colors stay stable by original data index.
showTotalbooleantrueRender automatic center total when centerContent is omitted. Uses visible slice sum.
formatTotalDonutFormatTotal<TDatum>—Format the visible total for center display. Ignored when centerContent is set.
centerContentDonutCenterContent<TDatum>—Static children, primitives, or render function receiving DonutCenterContext. Highest priority for center display.
emptyContentComponentChildren—Fallback when data exists but no slice has a finite value greater than zero, or when every slice is hidden via legend.
activeOffsetnumber3Pixel expansion applied to the active slice outer radius on pointer or keyboard focus.
tooltipboolean | PieTooltipOptions<TDatum>falsePointer and keyboard tooltips for the active slice. true uses default value and percentage formatting.
showLegendbooleanfalseRender an HTML legend with per-slice visibility toggles keyed by stable slice keys.
legendPieLegendOptions—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.
animateboolean | ChartAnimateOptions—Optional data animation. No-op on the static donut entry unless MotionConfig is present.

DonutCenterContext

Passed to centerContent render functions and formatTotal.

DonutCenterContext fields
PropTypeDefaultDescription
datareadonly TDatum[]—Original chart data array.
variantDonutVariant—Resolved variant: full or half.
totalnumber—Sum of visible slice values (respects legend toggles).
grandTotalnumber—Sum of all positive slices regardless of legend visibility.
visibleCountnumber—Number of slices currently visible.
segmentCountnumber—Number of slices with positive values in the source data.

PieTooltipOptions

DonutChart reuses pie tooltip options via tooltip.

PieTooltipOptions fields
PropTypeDefaultDescription
placementTooltipPlacement—Tooltip placement relative to the slice anchor.
offsetnumber—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.

PieLegendOptions fields
PropTypeDefaultDescription
interactivebooleantrueWhen 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

  1. centerContent — static children, primitives ("68%"), or (context) => … render function. Wins over all automatic totals.
  2. formatTotal — formats the visible total when showTotal is true and centerContent is omitted.
  3. showTotal — when true (default), renders String(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).