Fix chart auto toggle to clearly split follow vs manual zoom.
On tail refresh, auto-on always snaps to latest candles while auto-off preserves the saved viewport with clamped range restore. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2026,12 +2026,6 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
function isViewingChartTail(range, candleCount) {
|
||||
if (!range || candleCount <= 0) return true;
|
||||
const maxTo = candleCount - 1 + RIGHT_OFFSET_BARS;
|
||||
return range.to >= maxTo - 24;
|
||||
}
|
||||
|
||||
function markChartRangeUserAdjusted() {
|
||||
chartRangeUserLocked = true;
|
||||
if (chartRangeLockTimer) clearTimeout(chartRangeLockTimer);
|
||||
@@ -2041,14 +2035,37 @@
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
function clampVisibleLogicalRange(range, candleCount) {
|
||||
if (!range || candleCount <= 0) return null;
|
||||
const maxTo = candleCount - 1 + RIGHT_OFFSET_BARS;
|
||||
const from = Math.max(-2, Math.min(range.from, candleCount - 1));
|
||||
const to = Math.max(0, Math.min(range.to, maxTo + 8));
|
||||
if (to <= from) return null;
|
||||
return { from: from, to: to };
|
||||
}
|
||||
|
||||
function restoreVisibleLogicalRange(range, candleCount) {
|
||||
if (!chart || !range || !isVisibleRangeValidForCandles(range, candleCount)) return false;
|
||||
const clamped = clampVisibleLogicalRange(range, candleCount);
|
||||
if (!chart || !clamped || !isVisibleRangeValidForCandles(clamped, candleCount)) return false;
|
||||
suppressRangeUserLock = true;
|
||||
chart.timeScale().setVisibleLogicalRange(range);
|
||||
chart.timeScale().setVisibleLogicalRange(clamped);
|
||||
suppressRangeUserLock = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyPreservedVisibleRange(range, candleCount) {
|
||||
if (!chart || !range || !candleCount) return;
|
||||
function applyOnce() {
|
||||
if (!chart || !lastCandles.length) return;
|
||||
applyChartRightGap();
|
||||
restoreVisibleLogicalRange(range, lastCandles.length);
|
||||
updateVisibleRangeMarkers();
|
||||
}
|
||||
applyOnce();
|
||||
requestAnimationFrame(applyOnce);
|
||||
setTimeout(applyOnce, 0);
|
||||
}
|
||||
|
||||
function shouldLoadOlderOnRange(range) {
|
||||
if (!range || !lastCandles.length) return false;
|
||||
const n = lastCandles.length;
|
||||
@@ -2229,11 +2246,9 @@
|
||||
if (!lastViewKey || vKey !== lastViewKey) return;
|
||||
const myToken = loadToken;
|
||||
const epochAtStart = chartViewEpoch;
|
||||
const candleCountBefore = lastCandles.length;
|
||||
const autoFollow = priceAutoScale;
|
||||
let savedRange = null;
|
||||
if (chart) savedRange = chart.timeScale().getVisibleLogicalRange();
|
||||
const wasViewingTail =
|
||||
!savedRange || isViewingChartTail(savedRange, candleCountBefore);
|
||||
try {
|
||||
const data = await fetchChartChunk({
|
||||
exchange_key: exKey,
|
||||
@@ -2255,37 +2270,20 @@
|
||||
applyChartPriceFormat();
|
||||
}
|
||||
}
|
||||
const autoFollow = priceAutoScale;
|
||||
const shouldPreserve =
|
||||
savedRange &&
|
||||
isVisibleRangeValidForCandles(savedRange, candleCountBefore) &&
|
||||
(!autoFollow || chartRangeUserLocked || !wasViewingTail);
|
||||
applyCandlesToChart(
|
||||
mergeCandles(lastCandles, alignCandlesToTick(data.candles), { prepend: false }),
|
||||
0,
|
||||
{
|
||||
preserveRange: !!shouldPreserve,
|
||||
preserveRange: false,
|
||||
skipAutoScale: !autoFollow,
|
||||
}
|
||||
);
|
||||
if (epochAtStart !== chartViewEpoch) return;
|
||||
const n = lastCandles.length;
|
||||
const minorTailUpdate = Math.abs(n - candleCountBefore) <= CHART_TAIL_REFRESH_LIMIT + 5;
|
||||
if (!autoFollow) {
|
||||
if (savedRange && isVisibleRangeValidForCandles(savedRange, n)) {
|
||||
restoreVisibleLogicalRange(savedRange, n);
|
||||
}
|
||||
} else if (wasViewingTail && !chartRangeUserLocked) {
|
||||
restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n);
|
||||
} else if (shouldPreserve && minorTailUpdate) {
|
||||
if (!restoreVisibleLogicalRange(savedRange, n) && !chartRangeUserLocked) {
|
||||
restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n);
|
||||
}
|
||||
} else if (!restoreVisibleLogicalRange(savedRange, n) && !chartRangeUserLocked) {
|
||||
const curRange = chart && chart.timeScale().getVisibleLogicalRange();
|
||||
if (!curRange || !isVisibleRangeValidForCandles(curRange, n)) {
|
||||
restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n);
|
||||
}
|
||||
if (autoFollow) {
|
||||
applyDefaultVisibleRange();
|
||||
} else if (savedRange) {
|
||||
applyPreservedVisibleRange(savedRange, n);
|
||||
}
|
||||
scheduleRangeUiUpdate();
|
||||
if (posContext) {
|
||||
|
||||
@@ -349,7 +349,7 @@
|
||||
|
||||
<div id="toast"></div>
|
||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<script src="/assets/chart.js?v=20260604-upnl-contracts"></script>
|
||||
<script src="/assets/chart.js?v=20260608-auto-viewport"></script>
|
||||
<script src="/assets/archive.js?v=20260607-hub-archive-v6"></script>
|
||||
<script src="/assets/ai_review_render.js?v=2"></script>
|
||||
<script src="/assets/app.js?v=20260607-hub-archive-v1"></script>
|
||||
|
||||
Reference in New Issue
Block a user