import { z } from "zod"; import { CHART_COLORS } from "./chart-config"; export const PieChartProps = z.object({ title: z.string().describe("Chart title"), description: z.string().describe("Brief description or subtitle"), data: z.array( z.object({ label: z.string(), value: z.number(), }), ), }); type PieChartPropsType = z.infer; /** Custom SVG donut chart built with + stroke-dasharray. */ function DonutChart({ data, size = 240, strokeWidth = 40, }: { data: { label: string; value: number }[]; size?: number; strokeWidth?: number; }) { const radius = (size - strokeWidth) / 2; const circumference = 2 * Math.PI * radius; const center = size / 2; const total = data.reduce((sum, d) => sum + (Number(d.value) || 0), 0); // Calculate each slice's arc length and starting position let accumulated = 0; const slices = data.map((item, index) => { const val = Number(item.value) || 0; const ratio = total > 0 ? val / total : 0; const arc = ratio * circumference; const startAt = accumulated; accumulated += arc; return { ...item, arc, gap: circumference - arc, // Negative dashoffset shifts the dash forward (clockwise) to the correct position dashoffset: -startAt, color: CHART_COLORS[index % CHART_COLORS.length], }; }); return ( {/* Background ring */} {/* Data slices */} {slices.map((slice, i) => ( ))} ); } export function PieChart({ title, description, data }: PieChartPropsType) { if (!data || !Array.isArray(data) || data.length === 0) { return (

{title}

{description}

No data available

); } const total = data.reduce((sum, d) => sum + (Number(d.value) || 0), 0); return (

{title}

{description}

{/* Legend */}
{data.map((item, index) => { const val = Number(item.value) || 0; const pct = total > 0 ? ((val / total) * 100).toFixed(0) : 0; return (
{item.label} {val.toLocaleString()} {pct}%
); })}
); }