import React, { useEffect, useRef, useState } from 'react'; import { Box, VStack, HStack, Text, Button, ButtonGroup, Flex, Icon, Tooltip, } from '@chakra-ui/react'; import { init, dispose } from 'klinecharts'; import { FaExpand, FaCompress, FaCamera, FaRedo, } from 'react-icons/fa'; import { MetricDataPoint } from '@services/categoryService'; // 黑金主题配色 const themeColors = { bg: { primary: '#0a0a0a', secondary: '#1a1a1a', card: '#1e1e1e', }, text: { primary: '#ffffff', secondary: '#b8b8b8', muted: '#808080', gold: '#D4AF37', }, border: { default: 'rgba(255, 255, 255, 0.1)', gold: 'rgba(212, 175, 55, 0.3)', }, primary: { gold: '#D4AF37', goldLight: '#F4E3A7', }, }; interface KLineChartViewProps { data: MetricDataPoint[]; metricName: string; unit: string; frequency: string; } type TimeRange = '1M' | '3M' | '6M' | '1Y' | 'YTD' | 'ALL'; const KLineChartView: React.FC = ({ data, metricName, unit, frequency, }) => { const chartContainerRef = useRef(null); const chartRef = useRef(null); const [isFullscreen, setIsFullscreen] = useState(false); const [selectedRange, setSelectedRange] = useState('ALL'); // 初始化图表 useEffect(() => { if (!chartContainerRef.current || data.length === 0) return; // 创建图表实例 const chart = init(chartContainerRef.current, { styles: { grid: { horizontal: { color: 'rgba(255, 255, 255, 0.05)', }, vertical: { color: 'rgba(255, 255, 255, 0.05)', }, }, candle: { type: 'area' as const, // 使用面积图模式 area: { lineColor: themeColors.primary.gold, lineSize: 2, backgroundColor: [`${themeColors.primary.gold}30`, `${themeColors.primary.gold}05`], }, } as any, crosshair: { horizontal: { line: { color: themeColors.primary.gold, style: 'dashed', size: 1, }, text: { backgroundColor: themeColors.primary.gold, color: themeColors.bg.primary, }, }, vertical: { line: { color: themeColors.primary.gold, style: 'dashed', size: 1, }, text: { backgroundColor: themeColors.primary.gold, color: themeColors.bg.primary, }, }, }, xAxis: { axisLine: { color: themeColors.border.default, }, tickLine: { color: themeColors.border.default, }, tickText: { color: themeColors.text.secondary, }, }, yAxis: { axisLine: { color: themeColors.border.default, }, tickLine: { color: themeColors.border.default, }, tickText: { color: themeColors.text.secondary, }, }, }, }); // 转换数据格式 const chartData = data .filter((item) => item.value !== null) .map((item) => ({ timestamp: new Date(item.date).getTime(), open: item.value, high: item.value, low: item.value, close: item.value, })) .sort((a, b) => a.timestamp - b.timestamp); // 设置数据 (chart as any)?.applyNewData(chartData); chartRef.current = chart; // 清理 return () => { if (chartRef.current) { dispose(chartContainerRef.current!); chartRef.current = null; } }; }, [data]); // 时间范围筛选 const handleTimeRangeChange = (range: TimeRange) => { setSelectedRange(range); // TODO: 实现时间范围筛选逻辑 }; // 重置缩放 const handleReset = () => { chartRef.current?.zoomAtCoordinate({ x: 0.5 }, 1); setSelectedRange('ALL'); }; // 截图功能 const handleScreenshot = () => { if (!chartRef.current) return; const imageUrl = chartRef.current.getConvertPictureUrl(); const link = document.createElement('a'); link.href = imageUrl; link.download = `${metricName}_${new Date().toISOString().split('T')[0]}.png`; link.click(); }; // 全屏切换 const toggleFullscreen = () => { if (!chartContainerRef.current) return; if (!isFullscreen) { if (chartContainerRef.current.requestFullscreen) { chartContainerRef.current.requestFullscreen(); } } else { if (document.exitFullscreen) { document.exitFullscreen(); } } setIsFullscreen(!isFullscreen); }; // 计算统计数据 const stats = React.useMemo(() => { const values = data.filter((item) => item.value !== null).map((item) => item.value as number); if (values.length === 0) { return { min: 0, max: 0, avg: 0, latest: 0, change: 0, changePercent: 0 }; } const min = Math.min(...values); const max = Math.max(...values); const avg = values.reduce((sum, val) => sum + val, 0) / values.length; const latest = values[values.length - 1]; const first = values[0]; const change = latest - first; const changePercent = first !== 0 ? (change / first) * 100 : 0; return { min, max, avg, latest, change, changePercent }; }, [data]); // 格式化数字 const formatNumber = (num: number) => { if (Math.abs(num) >= 1e9) { return (num / 1e9).toFixed(2) + 'B'; } if (Math.abs(num) >= 1e6) { return (num / 1e6).toFixed(2) + 'M'; } if (Math.abs(num) >= 1e3) { return (num / 1e3).toFixed(2) + 'K'; } return num.toFixed(2); }; return ( {/* 工具栏 */} {/* 时间范围选择 */} {(['1M', '3M', '6M', '1Y', 'YTD', 'ALL'] as TimeRange[]).map((range) => ( ))} {/* 图表操作 */} {/* 统计数据 */} 最新值 {formatNumber(stats.latest)} {unit} = 0 ? '#00ff88' : '#ff4444'} fontSize="xs" fontWeight="bold" > {stats.change >= 0 ? '+' : ''} {formatNumber(stats.change)} ({stats.changePercent.toFixed(2)}%) 平均值 {formatNumber(stats.avg)} {unit} 最高值 {formatNumber(stats.max)} {unit} 最低值 {formatNumber(stats.min)} {unit} 数据点数 {data.filter((item) => item.value !== null).length} 频率 {frequency} {/* 图表容器 */} {/* 提示信息 */} 💡 提示:滚轮缩放,拖拽移动视图 数据来源: {metricName} ); }; export default KLineChartView;