diff --git a/web/charts.js b/web/charts.js index d7d2edd..df763d7 100644 --- a/web/charts.js +++ b/web/charts.js @@ -42,6 +42,10 @@ let lwcVolumeSeries = null; let lwcResizeObserver = null; let lwcPriceLines = []; const symbolPriceMeta = new Map(); +let lwcModalCandles = []; +let lwcModalInterval = "1d"; +let lwcModalPriceMeta = { tick_size: "0.01", price_precision: 2 }; +let lwcOnVisibleRangeChange = null; function cacheKey(symbol, interval) { return `${symbol}:${interval}`; @@ -144,6 +148,38 @@ function findCandleExtremes(candles, interval) { return { maxHigh, minLow, highTime, lowTime }; } +function candlesInLogicalRange(candles, range) { + if (!range || !candles.length) return candles; + const from = Math.max(0, Math.floor(range.from)); + const to = Math.min(candles.length - 1, Math.ceil(range.to)); + if (from > to) return []; + return candles.slice(from, to + 1); +} + +function updateHighLowForVisibleWindow() { + if (!lwcChart || !lwcCandleSeries || !lwcModalCandles.length) return; + const range = lwcChart.timeScale().getVisibleLogicalRange(); + const visible = candlesInLogicalRange(lwcModalCandles, range); + const subset = visible.length ? visible : lwcModalCandles; + applyHighLowAnnotations(subset, lwcModalInterval, lwcModalPriceMeta); +} + +function bindVisibleRangeHighLow() { + if (!lwcChart) return; + unbindVisibleRangeHighLow(); + lwcOnVisibleRangeChange = () => { + updateHighLowForVisibleWindow(); + }; + lwcChart.timeScale().subscribeVisibleLogicalRangeChange(lwcOnVisibleRangeChange); +} + +function unbindVisibleRangeHighLow() { + if (lwcChart && lwcOnVisibleRangeChange) { + lwcChart.timeScale().unsubscribeVisibleLogicalRangeChange(lwcOnVisibleRangeChange); + } + lwcOnVisibleRangeChange = null; +} + function toLwcTime(ms, interval) { if (interval === "1d" || interval === "1w") { const d = new Date(ms); @@ -374,7 +410,9 @@ async function loadMiniChart(box) { } function destroyLwcChart() { + unbindVisibleRangeHighLow(); clearHighLowAnnotations(); + lwcModalCandles = []; if (lwcResizeObserver) { lwcResizeObserver.disconnect(); lwcResizeObserver = null; @@ -523,6 +561,8 @@ function ensureLwcChart(container) { }); lwcResizeObserver.observe(container); + bindVisibleRangeHighLow(); + return lwcChart; } @@ -536,11 +576,15 @@ function renderLwcChart(candles, interval, priceMeta) { const meta = getPriceMeta(chartModalSymbol, priceMeta); applySeriesPriceFormat(meta); + lwcModalCandles = candles; + lwcModalInterval = interval; + lwcModalPriceMeta = meta; + const { ohlc, vol } = candlesToLwc(candles, interval); lwcCandleSeries.setData(ohlc); lwcVolumeSeries.setData(vol); - applyHighLowAnnotations(candles, interval, meta); lwcChart.timeScale().fitContent(); + requestAnimationFrame(() => updateHighLowForVisibleWindow()); } function updateIntervalTabs() { @@ -556,7 +600,7 @@ function updateModalMeta(candles, source, interval) { title.textContent = `${chartModalSymbol} · ${interval.toUpperCase()} K线`; } if (hint) { - hint.textContent = `${candles.length} 根 · ${sourceLabel(source)} · 滚轮缩放 · 拖拽平移 · 十字线 · Esc 或点击遮罩关闭`; + hint.textContent = `${candles.length} 根 · ${sourceLabel(source)} · 最高/最低随可见窗口 · 滚轮缩放 · Esc 关闭`; } }