Preserve chart zoom and pan across tail refresh updates.

Keep the user viewport when SSE updates candles so zooming out to see the full series is not reset to the recent 200-bar view.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-08 08:06:57 +08:00
parent 63472719ec
commit c2203abfa8
+52 -17
View File
@@ -175,6 +175,9 @@
let chartViewEpoch = 0;
let rangeUiTimer = null;
let loadOlderTimer = null;
let chartRangeUserLocked = false;
let chartRangeLockTimer = null;
let suppressRangeUserLock = false;
let priceTagTimer = null;
let tfDigitBuf = "";
let tfDigitTimer = null;
@@ -1950,6 +1953,9 @@
});
chart.timeScale().subscribeVisibleLogicalRangeChange(function (range) {
if (!chartDataLoading && range && !suppressRangeUserLock) {
markChartRangeUserAdjusted();
}
scheduleRangeUiUpdate();
if (
!range ||
@@ -2025,6 +2031,23 @@
return range.to >= maxTo - 24;
}
function markChartRangeUserAdjusted() {
chartRangeUserLocked = true;
if (chartRangeLockTimer) clearTimeout(chartRangeLockTimer);
chartRangeLockTimer = setTimeout(function () {
chartRangeLockTimer = null;
chartRangeUserLocked = false;
}, 30000);
}
function restoreVisibleLogicalRange(range, candleCount) {
if (!chart || !range || !isVisibleRangeValidForCandles(range, candleCount)) return false;
suppressRangeUserLock = true;
chart.timeScale().setVisibleLogicalRange(range);
suppressRangeUserLock = false;
return true;
}
function shouldLoadOlderOnRange(range) {
if (!range || !lastCandles.length) return false;
const n = lastCandles.length;
@@ -2100,7 +2123,12 @@
return merged;
}
function applyCandlesToChart(candles, rangeShift) {
function applyCandlesToChart(candles, rangeShift, opts) {
opts = opts || {};
let savedRange = null;
if (opts.preserveRange && chart) {
savedRange = chart.timeScale().getVisibleLogicalRange();
}
lastCandles = alignCandlesToTick(candles);
indexCandles(lastCandles);
candleSeries.setData(lastCandles);
@@ -2109,13 +2137,19 @@
if (rangeShift && chart) {
const range = chart.timeScale().getVisibleLogicalRange();
if (range) {
suppressRangeUserLock = true;
chart.timeScale().setVisibleLogicalRange({
from: range.from + rangeShift,
to: range.to + rangeShift,
});
suppressRangeUserLock = false;
}
} else if (savedRange) {
restoreVisibleLogicalRange(savedRange, lastCandles.length);
}
if (!opts.skipAutoScale) {
applyPriceAutoScale();
}
applyPriceAutoScale();
updateVisibleRangeMarkers();
try {
updateIndicators();
@@ -2194,8 +2228,6 @@
const candleCountBefore = lastCandles.length;
let savedRange = null;
if (chart) savedRange = chart.timeScale().getVisibleLogicalRange();
const wasViewingTail =
!savedRange || isViewingChartTail(savedRange, candleCountBefore);
try {
const data = await fetchChartChunk({
exchange_key: exKey,
@@ -2216,20 +2248,18 @@
applyChartPriceFormat();
}
}
applyCandlesToChart(mergeCandles(lastCandles, alignCandlesToTick(data.candles), { prepend: false }), 0);
applyCandlesToChart(
mergeCandles(lastCandles, alignCandlesToTick(data.candles), { prepend: false }),
0,
{ preserveRange: true, skipAutoScale: chartRangeUserLocked }
);
if (epochAtStart !== chartViewEpoch) return;
const n = lastCandles.length;
const curRange = chart && chart.timeScale().getVisibleLogicalRange();
const minorTailUpdate = Math.abs(n - candleCountBefore) <= 5;
if (
savedRange &&
isVisibleRangeValidForCandles(savedRange, n) &&
(minorTailUpdate || !wasViewingTail)
) {
chart.timeScale().setVisibleLogicalRange(savedRange);
} else if (!isVisibleRangeValidForCandles(curRange, n)) {
const tailRange = tailVisibleLogicalRange(n);
if (tailRange) chart.timeScale().setVisibleLogicalRange(tailRange);
if (!restoreVisibleLogicalRange(savedRange, n)) {
const curRange = chart && chart.timeScale().getVisibleLogicalRange();
if (!chartRangeUserLocked && curRange && !isVisibleRangeValidForCandles(curRange, n)) {
restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n);
}
}
scheduleRangeUiUpdate();
if (posContext) {
@@ -2258,7 +2288,7 @@
const r = tailVisibleLogicalRange(lastCandles.length);
if (!r) return;
applyChartRightGap();
chart.timeScale().setVisibleLogicalRange(r);
restoreVisibleLogicalRange(r, lastCandles.length);
updateVisibleRangeMarkers();
}
applyOnce();
@@ -2482,6 +2512,11 @@
chartDataLoading = true;
if (resetView) {
chartViewEpoch += 1;
chartRangeUserLocked = false;
if (chartRangeLockTimer) {
clearTimeout(chartRangeLockTimer);
chartRangeLockTimer = null;
}
resetChartHistoryState();
lastViewKey = "";
clearChartSeriesData();