/** * 图表辅助函数 * 用于处理异动标注等图表相关逻辑 */ /** * 获取异动标注的配色和符号 * @param {string} alertType - 异动类型 * @param {number} importanceScore - 重要性得分 * @returns {Object} { color, symbol, symbolSize } */ export const getAlertStyle = (alertType, importanceScore = 0.5) => { let color = '#ff6b6b'; let symbol = 'pin'; let symbolSize = 35; switch (alertType) { case 'surge_up': case 'surge': color = '#ff4757'; symbol = 'triangle'; symbolSize = 30 + Math.min(importanceScore * 20, 15); break; case 'surge_down': color = '#2ed573'; symbol = 'path://M0,0 L10,0 L5,10 Z'; // 向下三角形 symbolSize = 30 + Math.min(importanceScore * 20, 15); break; case 'limit_up': color = '#ff6348'; symbol = 'diamond'; symbolSize = 28; break; case 'rank_jump': color = '#3742fa'; symbol = 'circle'; symbolSize = 25; break; case 'volume_spike': color = '#ffa502'; symbol = 'rect'; symbolSize = 25; break; default: break; } return { color, symbol, symbolSize }; }; /** * 获取异动类型的显示标签 * @param {string} alertType - 异动类型 * @returns {string} 显示标签 */ export const getAlertTypeLabel = (alertType) => { const labels = { surge: '急涨', surge_up: '暴涨', surge_down: '暴跌', limit_up: '涨停增加', rank_jump: '排名跃升', volume_spike: '放量', unknown: '异动', }; return labels[alertType] || alertType; }; /** * 生成图表标注点数据 * @param {Array} alerts - 异动数据数组 * @param {Array} times - 时间数组 * @param {Array} prices - 价格数组 * @param {number} priceMax - 最高价格(用于无法匹配时间时的默认位置) * @param {number} maxCount - 最大显示数量 * @returns {Array} ECharts markPoint data */ export const getAlertMarkPoints = (alerts, times, prices, priceMax, maxCount = 15) => { if (!alerts || alerts.length === 0) return []; // 按重要性排序,限制显示数量 const sortedAlerts = [...alerts] .sort((a, b) => (b.final_score || b.importance_score || 0) - (a.final_score || a.importance_score || 0)) .slice(0, maxCount); return sortedAlerts.map((alert) => { // 找到对应时间的价格 const timeIndex = times.indexOf(alert.time); const price = timeIndex >= 0 ? prices[timeIndex] : (alert.index_price || priceMax); const { color, symbol, symbolSize } = getAlertStyle( alert.alert_type, alert.final_score / 100 || alert.importance_score || 0.5 ); // 格式化标签 let label = alert.concept_name || ''; if (label.length > 6) { label = label.substring(0, 5) + '...'; } // 添加涨停数量(如果有) if (alert.limit_up_count > 0) { label += `\n涨停: ${alert.limit_up_count}`; } const isDown = alert.alert_type === 'surge_down'; return { name: alert.concept_name, coord: [alert.time, price], value: label, symbol, symbolSize, itemStyle: { color, borderColor: '#fff', borderWidth: 1, shadowBlur: 3, shadowColor: 'rgba(0,0,0,0.2)', }, label: { show: true, position: isDown ? 'bottom' : 'top', formatter: '{b}', fontSize: 9, color: '#333', backgroundColor: isDown ? 'rgba(46, 213, 115, 0.9)' : 'rgba(255,255,255,0.9)', padding: [2, 4], borderRadius: 2, borderColor: color, borderWidth: 1, }, alertData: alert, // 存储原始数据 }; }); }; /** * 格式化分数显示 * @param {number} score - 分数 * @returns {string} 格式化后的分数 */ export const formatScore = (score) => { if (score === null || score === undefined) return '-'; return Math.round(score).toString(); }; /** * 获取分数对应的颜色 * @param {number} score - 分数 (0-100) * @returns {string} 颜色代码 */ export const getScoreColor = (score) => { if (score >= 80) return '#ff4757'; if (score >= 60) return '#ff6348'; if (score >= 40) return '#ffa502'; return '#747d8c'; };