"use client"; import { useState, useMemo } from "react"; export interface PieSlice { label: string; value: number; color: string; } interface PieChartProps { data: PieSlice[]; } /** * Pure-SVG interactive pie chart. * Hover a slice or legend item to highlight it and show its percentage. */ export function PieChart({ data }: PieChartProps) { const [hovered, setHovered] = useState(null); const R = 80; const CX = 110; const CY = 110; const total = data.reduce((s, d) => s + d.value, 0) || 1; const slices = useMemo(() => { type Acc = { items: ReturnType[]; angle: number }; const makeSlice = (d: PieSlice, i: number, startAngle: number) => { const angle = (d.value / total) * 2 * Math.PI; const endAngle = startAngle + angle; const midAngle = startAngle + angle / 2; const x1 = CX + R * Math.cos(startAngle); const y1 = CY + R * Math.sin(startAngle); const x2 = CX + R * Math.cos(endAngle); const y2 = CY + R * Math.sin(endAngle); const largeArc = angle > Math.PI ? 1 : 0; const pathD = `M ${CX} ${CY} L ${x1.toFixed(2)} ${y1.toFixed(2)} A ${R} ${R} 0 ${largeArc} 1 ${x2.toFixed(2)} ${y2.toFixed(2)} Z`; return { ...d, pathD, labelX: CX + R * 0.65 * Math.cos(midAngle), labelY: CY + R * 0.65 * Math.sin(midAngle), percent: (d.value / total) * 100, index: i, endAngle, }; }; const { items } = data.reduce( (acc, d, i) => { const slice = makeSlice(d, i, acc.angle); return { items: [...acc.items, slice], angle: slice.endAngle }; }, { items: [], angle: -Math.PI / 2 }, ); return items; }, [data, total]); return (
setHovered(null)} > {slices.map((s) => ( setHovered(s.index)} opacity={hovered !== null && hovered !== s.index ? 0.65 : 1} /> ))} {hovered !== null && ( {slices[hovered].percent.toFixed(1)}% )} {/* Legend */}
{slices.map((s) => (
setHovered(s.index)} onMouseLeave={() => setHovered(null)} > {s.label} {s.percent.toFixed(1)}%
))}
); }