Fix market chart viewport jump when switching timeframes.
Reset visible range and block stale left-pan/tail refresh from applying the previous period logical range to new candles. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -177,6 +177,7 @@
|
||||
let currentTf = "1d";
|
||||
let exhaustedLeft = false;
|
||||
let loadingLeft = false;
|
||||
let chartDataLoading = false;
|
||||
let priceTagTimer = null;
|
||||
let tfDigitBuf = "";
|
||||
let tfDigitTimer = null;
|
||||
@@ -809,6 +810,7 @@
|
||||
if (elTf) elTf.value = tf;
|
||||
if (elFsTf) elFsTf.value = tf;
|
||||
currentTf = tf;
|
||||
lastViewKey = "";
|
||||
tickLiveClock();
|
||||
updateHeaderLabels(
|
||||
elSymbol && elSymbol.value.trim().toUpperCase(),
|
||||
@@ -1953,7 +1955,17 @@
|
||||
chart.timeScale().subscribeVisibleLogicalRangeChange(function (range) {
|
||||
updateVisibleRangeMarkers();
|
||||
updatePriceTag();
|
||||
if (!range || loadingLeft || exhaustedLeft || !lastCandles.length) return;
|
||||
if (
|
||||
!range ||
|
||||
chartDataLoading ||
|
||||
loadingLeft ||
|
||||
exhaustedLeft ||
|
||||
!lastCandles.length ||
|
||||
!lastViewKey
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (currentChartViewKey() !== lastViewKey) return;
|
||||
if (range.from < CHART_LOAD_LEFT_THRESHOLD) {
|
||||
void loadOlderCandles();
|
||||
}
|
||||
@@ -1996,6 +2008,30 @@
|
||||
loadingLeft = false;
|
||||
}
|
||||
|
||||
function currentChartViewKey() {
|
||||
const exKey = (elExchange && elExchange.value) || "";
|
||||
const sym = (elSymbol && elSymbol.value.trim().toUpperCase()) || "";
|
||||
const tf = (elTf && elTf.value) || currentTf || "1d";
|
||||
if (!exKey || !sym) return "";
|
||||
return viewKey(exKey, sym, tf);
|
||||
}
|
||||
|
||||
function isVisibleRangeValidForCandles(range, candleCount) {
|
||||
if (!range || candleCount <= 0) return false;
|
||||
const maxTo = candleCount - 1 + RIGHT_OFFSET_BARS;
|
||||
if (range.from < -2 || range.to < 0) return false;
|
||||
if (range.to > maxTo + 8) return false;
|
||||
if (range.from > candleCount - 1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function clearChartSeriesData() {
|
||||
lastCandles = [];
|
||||
candleByTime = {};
|
||||
if (candleSeries) candleSeries.setData([]);
|
||||
if (volumeSeries) volumeSeries.setData([]);
|
||||
}
|
||||
|
||||
function mergeCandles(existing, incoming, opts) {
|
||||
opts = opts || {};
|
||||
const prepend = !!opts.prepend;
|
||||
@@ -2064,11 +2100,13 @@
|
||||
}
|
||||
|
||||
async function loadOlderCandles() {
|
||||
if (loadingLeft || exhaustedLeft || !lastCandles.length) return;
|
||||
if (chartDataLoading || loadingLeft || exhaustedLeft || !lastCandles.length) return;
|
||||
const exKey = (elExchange && elExchange.value) || "";
|
||||
const sym = (elSymbol && elSymbol.value.trim().toUpperCase()) || "";
|
||||
const tf = (elTf && elTf.value) || "1d";
|
||||
if (!exKey || !sym) return;
|
||||
const vKey = viewKey(exKey, sym, tf);
|
||||
if (!lastViewKey || vKey !== lastViewKey) return;
|
||||
loadingLeft = true;
|
||||
const beforeMs = Number(lastCandles[0].time) * 1000;
|
||||
try {
|
||||
@@ -2107,8 +2145,11 @@
|
||||
const exKey = (elExchange && elExchange.value) || "";
|
||||
const sym = (elSymbol && elSymbol.value.trim().toUpperCase()) || "";
|
||||
const tf = (elTf && elTf.value) || "1d";
|
||||
if (!exKey || !sym || !lastCandles.length) return;
|
||||
const vKey = viewKey(exKey, sym, tf);
|
||||
if (!exKey || !sym || !lastCandles.length || chartDataLoading) return;
|
||||
if (!lastViewKey || vKey !== lastViewKey) return;
|
||||
const myToken = loadToken;
|
||||
const candleCountBefore = lastCandles.length;
|
||||
let savedRange = null;
|
||||
if (chart) savedRange = chart.timeScale().getVisibleLogicalRange();
|
||||
try {
|
||||
@@ -2119,6 +2160,7 @@
|
||||
limit: chartChunkLimit(tf),
|
||||
});
|
||||
if (myToken !== loadToken) return;
|
||||
if (vKey !== lastViewKey) return;
|
||||
if (!data.ok || !data.candles || !data.candles.length) return;
|
||||
if (data.price_tick != null) {
|
||||
priceTick = data.price_tick;
|
||||
@@ -2130,7 +2172,13 @@
|
||||
}
|
||||
}
|
||||
applyCandlesToChart(mergeCandles(lastCandles, alignCandlesToTick(data.candles), { prepend: false }), 0);
|
||||
if (savedRange) chart.timeScale().setVisibleLogicalRange(savedRange);
|
||||
if (
|
||||
savedRange &&
|
||||
isVisibleRangeValidForCandles(savedRange, lastCandles.length) &&
|
||||
Math.abs(lastCandles.length - candleCountBefore) < chartChunkLimit(tf)
|
||||
) {
|
||||
chart.timeScale().setVisibleLogicalRange(savedRange);
|
||||
}
|
||||
if (posContext) {
|
||||
updateLivePosPnl();
|
||||
refreshPosPnlFromBoard();
|
||||
@@ -2155,10 +2203,15 @@
|
||||
const n = lastCandles.length;
|
||||
const visible = Math.min(DEFAULT_VISIBLE_BARS, n);
|
||||
const from = Math.max(0, n - visible);
|
||||
// to 延伸到最后一根 K 线之后,留出 RIGHT_OFFSET_BARS 根空白(K 线与价格轴间距)
|
||||
const to = n - 1 + RIGHT_OFFSET_BARS;
|
||||
applyChartRightGap();
|
||||
chart.timeScale().setVisibleLogicalRange({ from: from, to: to });
|
||||
requestAnimationFrame(function () {
|
||||
requestAnimationFrame(function () {
|
||||
if (!chart || !lastCandles.length) return;
|
||||
chart.timeScale().setVisibleLogicalRange({ from: from, to: to });
|
||||
updateVisibleRangeMarkers();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateVisibleRangeMarkers() {
|
||||
@@ -2374,7 +2427,12 @@
|
||||
const myToken = ++loadToken;
|
||||
const vKey = viewKey(exKey, sym, tf);
|
||||
const resetView = !!force || vKey !== lastViewKey;
|
||||
if (resetView) resetChartHistoryState();
|
||||
chartDataLoading = true;
|
||||
if (resetView) {
|
||||
resetChartHistoryState();
|
||||
lastViewKey = "";
|
||||
clearChartSeriesData();
|
||||
}
|
||||
if (elStatus) {
|
||||
elStatus.className = "market-status";
|
||||
elStatus.textContent = "加载中…";
|
||||
@@ -2402,8 +2460,8 @@
|
||||
applyChartPriceFormat();
|
||||
}
|
||||
applyCandlesToChart(alignCandlesToTick(data.candles), 0);
|
||||
lastViewKey = vKey;
|
||||
if (resetView) {
|
||||
lastViewKey = vKey;
|
||||
applyDefaultVisibleRange();
|
||||
}
|
||||
syncPosContextForView(exKey, sym);
|
||||
@@ -2443,6 +2501,8 @@
|
||||
elStatus.className = "market-status err";
|
||||
elStatus.textContent = String(e.message || e);
|
||||
}
|
||||
} finally {
|
||||
if (myToken === loadToken) chartDataLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2461,6 +2521,7 @@
|
||||
tfDigitTimer = null;
|
||||
}
|
||||
currentTf = (elTf && elTf.value) || "1d";
|
||||
lastViewKey = "";
|
||||
tickLiveClock();
|
||||
syncFsToolbarFromMain();
|
||||
loadChart(false);
|
||||
@@ -2470,6 +2531,7 @@
|
||||
elExchange.addEventListener("change", function () {
|
||||
updateExchangeDisplay();
|
||||
syncFsToolbarFromMain();
|
||||
lastViewKey = "";
|
||||
loadChart(false);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user