/** * A proportional ring. * * Earns its place only because the quantity it draws is genuinely a share of a * whole — the split of every graded field across the confidence bands. A ring * standing in for a single number would be decoration; this one is the only * shape on the board that shows three parts against their total at once. * * It is `aria-hidden`, and that is deliberate rather than lazy: the legend * beside it states every segment's name and count as text, so a screen reader * gets the data in full and is not read a list of arc lengths. The chart is the * fast path for someone looking; the legend is the actual record. */ export interface Slice { key: string label: string value: number color: string } export function Donut({ slices, centerValue, centerLabel, size = 150, thickness = 20, /** Arc-length notch between segments, so two adjacent colours stay countable. */ gap = 3, }: { slices: Slice[] centerValue: string centerLabel: string size?: number thickness?: number gap?: number }) { const r = (size - thickness) / 2 const c = 2 * Math.PI * r const total = slices.reduce((n, s) => n + s.value, 0) // Running start angle, in arc length. Accumulated rather than derived per // index so a zero-value slice consumes nothing at all instead of leaving a // gap where no data is. let at = 0 const drawn = slices .filter((s) => s.value > 0) .map((s) => { const len = (s.value / total) * c const seg = { ...s, len: Math.max(len - gap, 0.5), offset: at } at += len return seg }) return (