import { Text } from '@/components/ui/text'; import { useColor } from '@/hooks/useColor'; import { useEffect, useState } from 'react'; import { LayoutChangeEvent, View, ViewStyle } from 'react-native'; import Animated, { useAnimatedProps, useSharedValue, withTiming, } from 'react-native-reanimated'; import Svg, { G, Path, Text as SvgText } from 'react-native-svg'; // Animated SVG Components const AnimatedPath = Animated.createAnimatedComponent(Path); interface ChartConfig { width?: number; height?: number; showLabels?: boolean; animated?: boolean; duration?: number; } interface ChartDataPoint { label: string; value: number; color?: string; } type Props = { data: ChartDataPoint[]; config?: ChartConfig; style?: ViewStyle; }; export const PieChart = ({ data, config = {}, style }: Props) => { const [containerWidth, setContainerWidth] = useState(300); const { height = 200, showLabels = true, animated = true, duration = 1000, } = config; // Use measured width or fallback to config width or default const chartWidth = containerWidth || config.width || 300; const primaryColor = useColor('primary'); const animationProgress = useSharedValue(0); const handleLayout = (event: LayoutChangeEvent) => { const { width: measuredWidth } = event.nativeEvent.layout; if (measuredWidth > 0) { setContainerWidth(measuredWidth); } }; useEffect(() => { if (animated) { animationProgress.value = withTiming(1, { duration }); } else { animationProgress.value = 1; } }, [data, animated, duration]); if (!data.length) return null; const total = data.reduce((sum, item) => sum + item.value, 0); const radius = Math.min(chartWidth, height) / 2 - 20; const centerX = chartWidth / 2; const centerY = height / 2; let currentAngle = -Math.PI / 2; // Start from top const colors = [ primaryColor, useColor('blue'), useColor('green'), useColor('orange'), useColor('purple'), useColor('pink'), ]; return ( {data.map((item, index) => { const sliceAngle = (item.value / total) * 2 * Math.PI; const startAngle = currentAngle; const endAngle = currentAngle + sliceAngle; const largeArcFlag = sliceAngle > Math.PI ? 1 : 0; const x1 = centerX + radius * Math.cos(startAngle); const y1 = centerY + radius * Math.sin(startAngle); const x2 = centerX + radius * Math.cos(endAngle); const y2 = centerY + radius * Math.sin(endAngle); const pathData = [ `M ${centerX} ${centerY}`, `L ${x1} ${y1}`, `A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2}`, 'Z', ].join(' '); // Label position const labelAngle = startAngle + sliceAngle / 2; const labelRadius = radius * 0.7; const labelX = centerX + labelRadius * Math.cos(labelAngle); const labelY = centerY + labelRadius * Math.sin(labelAngle); currentAngle = endAngle; const sliceAnimatedProps = useAnimatedProps(() => ({ opacity: animationProgress.value, })); return ( {showLabels && ( {Math.round((item.value / total) * 100)}% )} ); })} {/* Legend */} {data.map((item, index) => ( {item.label}: {item.value} ))} ); };