Use sparingly. A donut earns its space when 3–6 categories sum to a meaningful 100% and one segment dominates.
Patient origin by county, revenue by service line, traffic by source. 3–6 categories. With more than that, segments shrink into illegibility — switch to a horizontal bar. With only 2, use a single inline stat ("54% from Eagle County").
Each arc is a <circle> with stroke-dasharray set to the segment length. Initial dashoffset hides the stroke; transitioning to final = circ − segLen reveals it. Each segment's <g> wrapper is rotated by the cumulative angle so segments stack correctly. Stagger 200ms between arcs.
patientOrigin: {
categories: [
{ label: 'Eagle County', value: 54,
color: 'var(--primary)' },
{ label: 'Summit County', value: 28,
color: 'var(--tertiary)' },
// values may be counts OR %, chart normalizes
],
}
const r = 40, circ = 2 * Math.PI * r; let cumulative = 0; data.forEach((c, i) => { const segLen = (c.value / total) * circ; g.setAttribute('transform', `rotate(${(cumulative/circ)*360} 50 50)`); seg.setAttribute('stroke-dasharray', `${segLen} ${circ - segLen}`); cumulative += segLen; });