import { useMemo } from 'react'; import { Chart, ChartProps } from './Chart'; export interface PieChartData { label: string; value: number; color?: string; } export interface PieChartProps extends Omit { data: PieChartData[]; showLegend?: boolean; showLabels?: boolean; innerRadius?: number; colors?: string[]; } const DEFAULT_COLORS = [ '#7c9dd6', // indigo '#d4634a', // vermillion '#7a9e6c', // sage '#c9a84c', // gold '#a8a4a0', // text-secondary '#e0a0b8', // sakura '#3eaa5e', // terminal-green '#c840a0', // graffiti-magenta ]; /** * Composant PieChart pour visualisation de données en camembert. */ export function PieChart({ data, showLegend = true, showLabels = false, innerRadius = 0, colors = DEFAULT_COLORS, height = 300, className, ...chartProps }: PieChartProps) { const { segments } = useMemo(() => { if (data.length === 0) { return { segments: [], }; } const total = data.reduce((sum, item) => sum + Math.max(0, item.value), 0); const centerX = 50; const centerY = 50; const radius = 40; const innerR = (innerRadius / 100) * radius; let currentAngle = -90; const segments = data.map((item, index) => { const value = Math.max(0, item.value); const percentage = total > 0 ? value / total : 0; const angle = percentage * 360; const startAngle = currentAngle; const endAngle = currentAngle + angle; const startAngleRad = (startAngle * Math.PI) / 180; const endAngleRad = (endAngle * Math.PI) / 180; const x1 = centerX + radius * Math.cos(startAngleRad); const y1 = centerY + radius * Math.sin(startAngleRad); const x2 = centerX + radius * Math.cos(endAngleRad); const y2 = centerY + radius * Math.sin(endAngleRad); const x1Inner = centerX + innerR * Math.cos(startAngleRad); const y1Inner = centerY + innerR * Math.sin(startAngleRad); const x2Inner = centerX + innerR * Math.cos(endAngleRad); const y2Inner = centerY + innerR * Math.sin(endAngleRad); const largeArcFlag = angle > 180 ? 1 : 0; const path = innerR > 0 ? `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2} L ${x2Inner} ${y2Inner} A ${innerR} ${innerR} 0 ${largeArcFlag} 0 ${x1Inner} ${y1Inner} Z` : `M ${centerX} ${centerY} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2} Z`; const labelAngle = (startAngle + endAngle) / 2; const labelAngleRad = (labelAngle * Math.PI) / 180; const labelRadius = radius * 0.7; const labelX = centerX + labelRadius * Math.cos(labelAngleRad); const labelY = centerY + labelRadius * Math.sin(labelAngleRad); currentAngle += angle; return { ...item, path, color: item.color || colors[index % colors.length], percentage, labelX, labelY, labelAngle, }; }); return { segments }; }, [data, innerRadius, colors]); if (data.length === 0) { return (
Aucune donnée disponible
); } return (
{/* Segments */} {segments.map((segment, index) => ( {segment.label}: {segment.value} ( {Math.round(segment.percentage * 100)}%) {showLabels && segment.percentage > 0.05 && ( {Math.round(segment.percentage * 100)}% )} ))} {/* Legend */} {showLegend && (
{segments.map((segment, index) => (
{segment.label} ({segment.value})
))}
)}
); }