import React, { useMemo, useState, useEffect } from 'react';
import {
Box,
Card,
CardHeader,
CardBody,
Center,
VStack,
HStack,
Heading,
Text,
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
Stat,
StatLabel,
StatNumber,
StatHelpText,
SimpleGrid,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
Button,
Badge,
Divider,
Avatar,
Tag,
TagLabel,
Wrap,
WrapItem,
useColorModeValue,
} from '@chakra-ui/react';
import { getFormattedTextProps } from '../../../utils/textUtils';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import RiskDisclaimer from '../../../components/RiskDisclaimer';
import './WordCloud.css';
import {
BarChart, Bar,
PieChart, Pie, Cell,
XAxis, YAxis,
CartesianGrid,
Tooltip as RechartsTooltip,
Legend,
ResponsiveContainer,
Treemap,
Area, AreaChart,
} from 'recharts';
import ReactWordcloud from 'react-wordcloud';
// 颜色配置
const CHART_COLORS = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEEAD',
'#D4A5A5', '#9B6B6B', '#E9967A', '#B19CD9', '#87CEEB'
];
// 词云图组件
const WordCloud = ({ data }) => {
if (!data || data.length === 0) {
return (
暂无词云数据
);
}
const words = data.slice(0, 100).map(item => ({
text: item.name || item.text,
value: item.value || item.count || 1
}));
const options = {
rotations: 2,
rotationAngles: [-90, 0],
fontFamily: 'Microsoft YaHei, sans-serif',
fontSizes: [16, 80],
fontWeight: 'bold',
padding: 3,
scale: 'sqrt',
};
const callbacks = {
getWordColor: () => {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEEAD'];
return colors[Math.floor(Math.random() * colors.length)];
}
};
return (
);
};
// 板块热力图组件
const SectorHeatMap = ({ data }) => {
if (!data) return null;
const sectors = Object.entries(data)
.filter(([name]) => name !== '其他')
.map(([name, info]) => ({
name,
count: info.count,
stocks: info.stocks || []
}))
.sort((a, b) => b.count - a.count)
.slice(0, 20);
const maxCount = Math.max(...sectors.map(s => s.count), 1);
const getColor = (count) => {
const intensity = count / maxCount;
if (intensity < 0.1) return '#E6F3FF';
if (intensity < 0.2) return '#CCE7FF';
if (intensity < 0.3) return '#99CFFF';
if (intensity < 0.4) return '#66B7FF';
if (intensity < 0.5) return '#339FFF';
if (intensity < 0.6) return '#0087FF';
if (intensity < 0.7) return '#0069CC';
if (intensity < 0.8) return '#004C99';
if (intensity < 0.9) return '#003066';
return '#001433';
};
const cols = 5;
const rows = Math.ceil(sectors.length / cols);
const cellWidth = 150;
const cellHeight = 100;
const padding = 10;
return (
少
{[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1].map((intensity) => (
))}
多
);
};
// 板块关联图组件
const SectorRelationMap = ({ data }) => {
if (!data || !data.length) return null;
const sectors = [...new Set(data.flatMap(item => item.core_sectors || []))];
const sectorPairs = {};
data.forEach(stock => {
const stockSectors = stock.core_sectors || [];
for (let i = 0; i < stockSectors.length; i++) {
for (let j = i + 1; j < stockSectors.length; j++) {
const key = [stockSectors[i], stockSectors[j]].sort().join('-');
sectorPairs[key] = (sectorPairs[key] || 0) + 1;
}
}
});
const topSectors = sectors
.map(s => ({
name: s,
count: data.filter(d => (d.core_sectors || []).includes(s)).length
}))
.sort((a, b) => b.count - a.count)
.slice(0, 15)
.map(s => s.name);
const matrix = [];
const maxValue = Math.max(...Object.values(sectorPairs), 1);
topSectors.forEach((sector1, i) => {
topSectors.forEach((sector2, j) => {
if (i <= j) {
const key = [sector1, sector2].sort().join('-');
const value = i === j ?
data.filter(d => (d.core_sectors || []).includes(sector1)).length :
sectorPairs[key] || 0;
matrix.push({
x: j,
y: i,
value,
sector1,
sector2,
intensity: value / maxValue
});
}
});
});
const getColor = (intensity) => {
if (intensity === 0) return '#f7fafc';
if (intensity < 0.2) return '#bee3f8';
if (intensity < 0.4) return '#90cdf4';
if (intensity < 0.6) return '#63b3ed';
if (intensity < 0.8) return '#4299e1';
return '#3182ce';
};
const cellSize = 40;
const fontSize = 10;
return (
);
};
// 数据分析主组件
export const DataAnalysis = ({ dailyData, wordCloudData }) => {
const cardBg = useColorModeValue('white', 'gray.800');
const pieData = useMemo(() => {
if (!dailyData?.chart_data) return [];
return dailyData.chart_data.labels.slice(0, 10).map((label, index) => ({
name: label,
value: dailyData.chart_data.counts[index],
}));
}, [dailyData]);
const timeDistributionData = useMemo(() => {
if (!dailyData?.summary?.zt_time_distribution) return [];
const dist = dailyData.summary.zt_time_distribution;
return [
{ name: '早盘(9:30-11:30)', value: dist.morning || 0, color: '#48BB78' },
{ name: '午盘(11:30-13:00)', value: dist.midday || 0, color: '#ED8936' },
{ name: '尾盘(13:00-15:00)', value: dist.afternoon || 0, color: '#4299E1' }
];
}, [dailyData]);
const allStocks = useMemo(() => {
if (!dailyData?.sector_data) return [];
return Object.values(dailyData.sector_data).flatMap(sector => sector.stocks || []);
}, [dailyData]);
return (
数据分析
词云图
板块分布
板块热力图
板块关联
时间分布
{wordCloudData && wordCloudData.length > 0 ? (
) : dailyData?.word_freq_data && dailyData.word_freq_data.length > 0 ? (
) : (
暂无词云数据
)}
`${name} ${(percent * 100).toFixed(0)}%`}
outerRadius={120}
fill="#8884d8"
dataKey="value"
animationBegin={0}
animationDuration={800}
>
{pieData.map((entry, index) => (
|
))}
{dailyData?.sector_data ? (
) : (
暂无数据
)}
{allStocks.length > 0 ? (
) : (
暂无数据
)}
{timeDistributionData.map((entry, index) => (
|
))}
{timeDistributionData.map((item, index) => (
{item.name}
{item.value}
{((item.value / (dailyData?.total_stocks || 1)) * 100).toFixed(1)}%
))}
);
};
// 股票详情弹窗组件
export const StockDetailModal = ({ isOpen, onClose, selectedStock }) => {
const accentColor = useColorModeValue('blue.500', 'blue.300');
if (!selectedStock) return null;
return (
{selectedStock.sname}
{selectedStock.scode}
涨停信息
涨停时间: {selectedStock.formatted_time || selectedStock.zt_time}
{selectedStock.continuous_days && (
{selectedStock.continuous_days}
)}
涨停原因
{selectedStock.brief || '暂无涨停原因'}
{selectedStock.summary && (
<>
详细分析
{getFormattedTextProps(selectedStock.summary).children}
>
)}
所属板块
{selectedStock.core_sectors?.map((sector, i) => (
{sector}
))}
{/* 风险提示 */}
}>
查看K线图
);
};
export default { DataAnalysis, StockDetailModal };