import { useColor } from '@/hooks/useColor'; import { useEffect, useState } from 'react'; import { LayoutChangeEvent, View, ViewStyle } from 'react-native'; import Animated, { useAnimatedProps, useSharedValue, withDelay, withSpring, withTiming, } from 'react-native-reanimated'; import Svg, { Circle, Line, Path, Text as SvgText } from 'react-native-svg'; // Animated SVG Components const AnimatedPath = Animated.createAnimatedComponent(Path); const AnimatedCircle = Animated.createAnimatedComponent(Circle); interface ChartConfig { width?: number; height?: number; showLabels?: boolean; animated?: boolean; duration?: number; maxValue?: number; } interface RadarChartDataPoint { label: string; value: number; } type Props = { data: RadarChartDataPoint[]; config?: ChartConfig; style?: ViewStyle; }; export const RadarChart = ({ data, config = {}, style }: Props) => { const [containerWidth, setContainerWidth] = useState(300); const { height = 200, showLabels = true, animated = true, duration = 1000, maxValue, } = config; const chartWidth = containerWidth || config.width || 300; const primaryColor = useColor('primary'); const mutedColor = useColor('mutedForeground'); 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 centerX = chartWidth / 2; const centerY = height / 2; const radius = Math.min(chartWidth, height) / 2 - 40; const maxVal = maxValue || Math.max(...data.map((d) => d.value)); // Calculate points for each data point const angleStep = (2 * Math.PI) / data.length; const points = data.map((item, index) => { const angle = index * angleStep - Math.PI / 2; // Start from top const distance = (item.value / maxVal) * radius; return { x: centerX + distance * Math.cos(angle), y: centerY + distance * Math.sin(angle), labelX: centerX + (radius + 20) * Math.cos(angle), labelY: centerY + (radius + 20) * Math.sin(angle), label: item.label, }; }); // Create path for the radar area const radarPath = points.length > 0 ? `M${points[0].x},${points[0].y} ` + points .slice(1) .map((p) => `L${p.x},${p.y}`) .join(' ') + ' Z' : ''; const radarAnimatedProps = useAnimatedProps(() => ({ opacity: animationProgress.value * 0.3, })); return ( {/* Grid circles */} {[0.2, 0.4, 0.6, 0.8, 1].map((ratio, index) => ( ))} {/* Grid lines */} {data.map((_, index) => { const angle = index * angleStep - Math.PI / 2; const endX = centerX + radius * Math.cos(angle); const endY = centerY + radius * Math.sin(angle); return ( ); })} {/* Radar area */} {/* Data points */} {points.map((point, index) => { const pointAnimatedProps = useAnimatedProps(() => ({ opacity: animationProgress.value, r: withDelay(index * 100, withSpring(animationProgress.value * 4)), })); return ( ); })} {/* Labels */} {showLabels && points.map((point, index) => ( {point.label} ))} ); };