diff --git a/app/components/FundTrendChart.jsx b/app/components/FundTrendChart.jsx index aa96343..f8edc06 100644 --- a/app/components/FundTrendChart.jsx +++ b/app/components/FundTrendChart.jsx @@ -39,7 +39,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans useEffect(() => { // If collapsed, don't fetch data unless we have no data yet if (!isExpanded && data.length > 0) return; - + let active = true; setLoading(true); setError(null); @@ -229,41 +229,69 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans const ctx = chart.ctx; const datasets = chart.data.datasets; const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#22d3ee'; - - // Helper function to draw point label + + // 绘制圆角矩形(兼容无 roundRect 的环境) + const drawRoundRect = (left, top, w, h, r) => { + const rad = Math.min(r, w / 2, h / 2); + ctx.beginPath(); + ctx.moveTo(left + rad, top); + ctx.lineTo(left + w - rad, top); + ctx.quadraticCurveTo(left + w, top, left + w, top + rad); + ctx.lineTo(left + w, top + h - rad); + ctx.quadraticCurveTo(left + w, top + h, left + w - rad, top + h); + ctx.lineTo(left + rad, top + h); + ctx.quadraticCurveTo(left, top + h, left, top + h - rad); + ctx.lineTo(left, top + rad); + ctx.quadraticCurveTo(left, top, left + rad, top); + ctx.closePath(); + }; + const drawPointLabel = (datasetIndex, index, text, bgColor, textColor = '#ffffff', yOffset = 0) => { const meta = chart.getDatasetMeta(datasetIndex); if (!meta.data[index]) return; const element = meta.data[index]; - // Check if element is visible/not skipped if (element.skip) return; - + const x = element.x; const y = element.y + yOffset; - + const paddingH = 10; + const paddingV = 6; + const radius = 8; + ctx.save(); ctx.font = 'bold 11px sans-serif'; - const labelWidth = ctx.measureText(text).width + 12; - - // Draw label above the point + const textW = ctx.measureText(text).width; + const w = textW + paddingH * 2; + const h = 18; + const left = x - w / 2; + const top = y - 24; + + drawRoundRect(left, top, w, h, radius); ctx.globalAlpha = 0.7; ctx.fillStyle = bgColor; - ctx.fillRect(x - labelWidth/2, y - 24, labelWidth, 18); - + ctx.fill(); + ctx.globalAlpha = 0.7; ctx.fillStyle = textColor; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillText(text, x, y - 15); + ctx.fillText(text, x, top + h / 2); ctx.restore(); }; - // 1. Draw default labels for first buy and sell points + // Resolve active elements (hover/focus) first — used to decide whether to show default labels + let activeElements = []; + if (chart.tooltip?._active?.length) { + activeElements = chart.tooltip._active; + } else { + activeElements = chart.getActiveElements(); + } + + // 1. Draw default labels for first buy and sell points only when NOT focused/hovering // Index 1 is Buy, Index 2 is Sell - if (datasets[1] && datasets[1].data) { + if (!activeElements?.length && datasets[1] && datasets[1].data) { const firstBuyIndex = datasets[1].data.findIndex(v => v !== null && v !== undefined); if (firstBuyIndex !== -1) { - // Check collision with Sell let sellIndex = -1; if (datasets[2] && datasets[2].data) { sellIndex = datasets[2].data.findIndex(v => v !== null && v !== undefined); @@ -272,7 +300,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans drawPointLabel(1, firstBuyIndex, '买入', primaryColor, '#ffffff', isCollision ? -20 : 0); } } - if (datasets[2] && datasets[2].data) { + if (!activeElements?.length && datasets[2] && datasets[2].data) { const firstSellIndex = datasets[2].data.findIndex(v => v !== null && v !== undefined); if (firstSellIndex !== -1) { drawPointLabel(2, firstSellIndex, '卖出', '#f87171'); @@ -280,13 +308,6 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans } // 2. Handle active elements (hover crosshair) - let activeElements = []; - if (chart.tooltip?._active?.length) { - activeElements = chart.tooltip._active; - } else { - activeElements = chart.getActiveElements(); - } - if (activeElements && activeElements.length) { const activePoint = activeElements[0]; const x = activePoint.element.x; @@ -305,11 +326,11 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans // Draw vertical line ctx.moveTo(x, topY); ctx.lineTo(x, bottomY); - + // Draw horizontal line (based on first point - usually the main line) ctx.moveTo(leftX, y); ctx.lineTo(rightX, y); - + ctx.stroke(); // Draw labels @@ -320,7 +341,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans // Draw Axis Labels based on the first point (main line) const datasetIndex = activePoint.datasetIndex; const index = activePoint.index; - + const labels = chart.data.labels; if (labels && datasets && datasets[datasetIndex] && datasets[datasetIndex].data) { @@ -360,13 +381,13 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans // Determine background color based on dataset index // 1 = Buy (主题色), 2 = Sell (与折线图红色一致) const bgColor = dsIndex === 1 ? primaryColor : '#f87171'; - + // If collision, offset Buy label upwards let yOffset = 0; if (isCollision && dsIndex === 1) { yOffset = -20; } - + drawPointLabel(dsIndex, element.index, label, bgColor, '#ffffff', yOffset); } }); @@ -375,10 +396,10 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans } } }], []); // 移除 data 依赖,因为我们直接从 chart 实例读取数据 - + return (
e.stopPropagation()}> -
- + {isExpanded && (
{loading && ( -
加载中...
)} - + {!loading && data.length === 0 && ( -
diff --git a/package-lock.json b/package-lock.json index 9d79401..4db649f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "real-time-fund", - "version": "0.1.5", + "version": "0.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "real-time-fund", - "version": "0.1.5", + "version": "0.1.6", "dependencies": { "@dicebear/collection": "^9.3.1", "@dicebear/core": "^9.3.1",