import { Colors } from '@/constants/Colors'; import React, { useMemo } from 'react'; import { StyleSheet, View } from 'react-native'; import Svg, * as SvgLib from 'react-native-svg'; export type RadarCategory = { key: string; label: string }; export type RadarChartSize = 'small' | 'medium' | 'large' | number; export type RadarChartProps = { categories: RadarCategory[]; values: number[]; // 与 categories 一一对应 maxValue?: number; // 默认 5 size?: RadarChartSize; // 组件宽高,默认 medium levels?: number; // 网格层数,默认 5 showGrid?: boolean; showLabels?: boolean; // 是否显示指标文字 }; // 预设尺寸配置 const SIZE_CONFIG = { small: { size: 116, fontSize: 8, labelOffset: -4, radiusRatio: 0.35 }, medium: { size: 200, fontSize: 11, labelOffset: 15, radiusRatio: 0.38 }, large: { size: 280, fontSize: 13, labelOffset: 18, radiusRatio: 0.4 }, }; export function RadarChart({ categories, values, maxValue = 5, size = 'medium', levels = 5, showGrid = true, showLabels = true }: RadarChartProps) { // 解析尺寸配置 const config = useMemo(() => { if (typeof size === 'number') { // 自定义尺寸 const baseSize = Math.max(80, Math.min(400, size)); let fontSize = 11; let labelOffset = 15; let radiusRatio = 0.38; // 根据尺寸自适应调整 if (baseSize < 140) { fontSize = 9; labelOffset = 12; radiusRatio = 0.35; } else if (baseSize > 240) { fontSize = 13; labelOffset = 18; radiusRatio = 0.4; } return { size: baseSize, fontSize, labelOffset, radiusRatio }; } // 预设尺寸 return SIZE_CONFIG[size] || SIZE_CONFIG.medium; }, [size]); const { size: actualSize, fontSize, labelOffset, radiusRatio } = config; const radius = actualSize * radiusRatio; const cx = actualSize / 2; const cy = actualSize / 2; const count = categories.length; const points = useMemo(() => { return categories.map((_, i) => { const angle = (Math.PI * 2 * i) / count - Math.PI / 2; // 顶部起点 return { angle, x: (v: number) => cx + Math.cos(angle) * v, y: (v: number) => cy + Math.sin(angle) * v }; }); }, [categories, count, cx, cy]); const valuePath = useMemo(() => { const rValues = values.map((v) => Math.max(0, Math.min(maxValue, v)) / maxValue * radius); const d = rValues .map((rv, i) => `${i === 0 ? 'M' : 'L'} ${points[i].x(rv)} ${points[i].y(rv)}`) .join(' '); return `${d} Z`; }, [values, maxValue, radius, points]); const gridPolygons = useMemo(() => { const polys: string[] = []; for (let l = 1; l <= levels; l++) { const r = (radius / levels) * l; const p = points.map((p) => `${p.x(r)},${p.y(r)}`).join(' '); polys.push(p); } return polys; }, [levels, radius, points]); // 检查是否有足够的空间显示文字 const hasEnoughSpace = actualSize >= 100; return ( {showGrid && ( {gridPolygons.map((pointsStr, idx) => ( ))} {points.map((p, i) => ( ))} )} {showLabels && hasEnoughSpace && categories.map((c, i) => { const r = radius + labelOffset; const x = points[i].x(r); const y = points[i].y(r); const anchor = Math.cos(points[i].angle) > 0.2 ? 'start' : Math.cos(points[i].angle) < -0.2 ? 'end' : 'middle'; const dy = Math.sin(points[i].angle) > 0.6 ? 10 : Math.sin(points[i].angle) < -0.6 ? -4 : 4; return ( {c.label} ); })} ); } const styles = StyleSheet.create({});