目录结构拆分: - types.ts: HeatmapDataItem, MarketHeatmapProps, TreeNodeData 等类型定义 - styles.ts: 颜色常量、ECharts 配置常量、涨跌幅阈值 - utils.ts: getMarketCapRange, getChangeColor, buildTreeData, tooltip 格式化函数 - components/HeatmapLegend.tsx: 图例原子组件 性能优化: - 使用 useMemo 缓存树图数据构建和 ECharts 配置 - HeatmapLegend 使用 memo 包装 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
106 lines
3.7 KiB
TypeScript
106 lines
3.7 KiB
TypeScript
/**
|
|
* MarketHeatmap 工具函数
|
|
*/
|
|
import type { HeatmapDataItem, TreeNodeData, MarketCapRange } from './types';
|
|
import { COLORS, CHANGE_THRESHOLDS } from './styles';
|
|
|
|
/**
|
|
* 获取市值区间标签
|
|
*/
|
|
export const getMarketCapRange = (cap: number): MarketCapRange => {
|
|
if (cap >= 1000) return '超大盘股(>1000亿)';
|
|
if (cap >= 500) return '大盘股(500-1000亿)';
|
|
if (cap >= 100) return '中盘股(100-500亿)';
|
|
if (cap >= 50) return '小盘股(50-100亿)';
|
|
return '微盘股(<50亿)';
|
|
};
|
|
|
|
/**
|
|
* 根据涨跌幅计算颜色
|
|
*/
|
|
export const getChangeColor = (change: number): string => {
|
|
if (change > 0) {
|
|
const intensity = Math.min(change / CHANGE_THRESHOLDS.maxChange, 1);
|
|
const opacity = CHANGE_THRESHOLDS.upBaseOpacity + intensity * CHANGE_THRESHOLDS.upMaxOpacity;
|
|
return `rgba(255, 77, 77, ${opacity})`;
|
|
}
|
|
if (change < 0) {
|
|
const intensity = Math.min(Math.abs(change) / CHANGE_THRESHOLDS.maxChange, 1);
|
|
const opacity = CHANGE_THRESHOLDS.downBaseOpacity + intensity * CHANGE_THRESHOLDS.downMaxOpacity;
|
|
return `rgba(34, 197, 94, ${opacity})`;
|
|
}
|
|
return COLORS.neutral;
|
|
};
|
|
|
|
/**
|
|
* 按市值分组数据
|
|
*/
|
|
export const groupByMarketCap = (data: HeatmapDataItem[]): Record<MarketCapRange, HeatmapDataItem[]> => {
|
|
const grouped: Record<string, HeatmapDataItem[]> = {};
|
|
|
|
data.forEach(item => {
|
|
const range = getMarketCapRange(item.market_cap);
|
|
if (!grouped[range]) {
|
|
grouped[range] = [];
|
|
}
|
|
grouped[range].push(item);
|
|
});
|
|
|
|
return grouped as Record<MarketCapRange, HeatmapDataItem[]>;
|
|
};
|
|
|
|
/**
|
|
* 构建树图数据
|
|
*/
|
|
export const buildTreeData = (data: HeatmapDataItem[]): TreeNodeData[] => {
|
|
const groupedData = groupByMarketCap(data);
|
|
|
|
return Object.entries(groupedData).map(([range, stocks]) => ({
|
|
name: range,
|
|
children: stocks.map(stock => ({
|
|
name: stock.stock_name,
|
|
value: Math.abs(stock.market_cap),
|
|
change: stock.change_percent || 0,
|
|
code: stock.stock_code,
|
|
amount: stock.amount,
|
|
industry: stock.industry,
|
|
province: stock.province,
|
|
itemStyle: { color: getChangeColor(stock.change_percent || 0) },
|
|
})),
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* 生成分组 tooltip HTML
|
|
*/
|
|
export const formatGroupTooltip = (data: TreeNodeData): string => {
|
|
const totalMarketCap = data.children?.reduce((sum, item) => sum + (item.value || 0), 0) || 0;
|
|
return `
|
|
<div style="padding: 10px; color: white;">
|
|
<div style="font-weight: bold; margin-bottom: 6px; font-size: 14px; color: ${COLORS.gold};">${data.name}</div>
|
|
<div style="color: #ccc;">包含 ${data.children?.length || 0} 只股票</div>
|
|
<div style="color: #ccc;">总市值: <span style="color: ${COLORS.gold}; font-weight: bold;">${totalMarketCap.toFixed(2)}</span> 亿元</div>
|
|
</div>
|
|
`;
|
|
};
|
|
|
|
/**
|
|
* 生成股票 tooltip HTML
|
|
*/
|
|
export const formatStockTooltip = (data: TreeNodeData): string => {
|
|
const changeColor = (data.change || 0) > 0 ? COLORS.up : COLORS.down;
|
|
const changeSign = (data.change || 0) > 0 ? '+' : '';
|
|
return `
|
|
<div style="padding: 10px; color: white;">
|
|
<div style="font-weight: bold; margin-bottom: 6px; font-size: 14px; color: ${COLORS.gold};">${data.name}</div>
|
|
<div style="color: #ccc;">代码: ${data.code || '-'}</div>
|
|
<div style="color: #ccc;">涨跌幅: <span style="color: ${changeColor}; font-weight: bold;">
|
|
${changeSign}${data.change?.toFixed(2) || 0}%
|
|
</span></div>
|
|
<div style="color: #ccc;">市值: <span style="font-weight: bold;">${data.value?.toFixed(2) || 0}</span> 亿元</div>
|
|
<div style="color: #ccc;">成交额: <span style="font-weight: bold;">${data.amount?.toFixed(2) || 0}</span> 亿元</div>
|
|
<div style="color: #ccc;">行业: ${data.industry || '未知'}</div>
|
|
</div>
|
|
`;
|
|
};
|