performance fixes
This commit is contained in:
parent
fc09da7053
commit
90f8a1b567
2 changed files with 89 additions and 45 deletions
|
|
@ -1112,6 +1112,17 @@ made `.price` undefined and axis/price drag stayed dead. Use
|
||||||
the scale is still auto; a mostly-vertical mouse drag now sets
|
the scale is still auto; a mostly-vertical mouse drag now sets
|
||||||
`manualPriceRange` the same way two-finger pan does.
|
`manualPriceRange` the same way two-finger pan does.
|
||||||
|
|
||||||
|
### 2026-08-16 — frontend CPU was overlay work on every mouse pixel
|
||||||
|
|
||||||
|
`subscribeCrosshairMove` rebuilt context-label DOM every move; those labels
|
||||||
|
do not depend on the cursor. `visibleLogicalRangeChange` and every tick
|
||||||
|
redrew handles, bridges, comments and labels without coalescing. Three
|
||||||
|
`window` pointermove listeners ran while idle.
|
||||||
|
|
||||||
|
Context labels now update only with the view. All overlay redraws share
|
||||||
|
one rAF and skip if the logical/price range did not change. Pointermoves
|
||||||
|
attach only for an armed tool or an in-progress drag.
|
||||||
|
|
||||||
### 2026-08-16 — the rest of the same session, none of it subtle
|
### 2026-08-16 — the rest of the same session, none of it subtle
|
||||||
|
|
||||||
Written because they were skipped the first time and then asked for.
|
Written because they were skipped the first time and then asked for.
|
||||||
|
|
|
||||||
123
static/chart.js
123
static/chart.js
|
|
@ -66,7 +66,10 @@ class ConfluenceChart {
|
||||||
this.commentNodes = new Map();
|
this.commentNodes = new Map();
|
||||||
this.commentLayer = null;
|
this.commentLayer = null;
|
||||||
this.contextLabelLayer = null;
|
this.contextLabelLayer = null;
|
||||||
this.contextLabelFrame = null;
|
this.overlayFrame = null;
|
||||||
|
this.overlayForce = false;
|
||||||
|
this.overlayRangeKey = '';
|
||||||
|
this.pointerAttached = { tool: false, anchor: false, line: false };
|
||||||
this.onCommentToggle = null;
|
this.onCommentToggle = null;
|
||||||
this.onCommentMove = null;
|
this.onCommentMove = null;
|
||||||
this.onCommentSelect = null;
|
this.onCommentSelect = null;
|
||||||
|
|
@ -254,9 +257,7 @@ class ConfluenceChart {
|
||||||
this.chart.applyOptions({ width: el.clientWidth, height: el.clientHeight });
|
this.chart.applyOptions({ width: el.clientWidth, height: el.clientHeight });
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.observePlotCanvas();
|
this.observePlotCanvas();
|
||||||
this.renderAnchorHandles();
|
this.scheduleOverlays(true);
|
||||||
this.renderComments();
|
|
||||||
this.renderContextLabels();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.resizeObserver.observe(el);
|
this.resizeObserver.observe(el);
|
||||||
|
|
@ -397,16 +398,10 @@ class ConfluenceChart {
|
||||||
this.toolMoveListener = event => this.moveToolGesture(event);
|
this.toolMoveListener = event => this.moveToolGesture(event);
|
||||||
this.toolUpListener = event => this.finishToolGesture(event);
|
this.toolUpListener = event => this.finishToolGesture(event);
|
||||||
el.addEventListener('pointerdown', this.toolDownListener);
|
el.addEventListener('pointerdown', this.toolDownListener);
|
||||||
window.addEventListener('pointermove', this.toolMoveListener);
|
|
||||||
window.addEventListener('pointerup', this.toolUpListener);
|
|
||||||
this.anchorMoveListener = event => this.moveAnchor(event);
|
this.anchorMoveListener = event => this.moveAnchor(event);
|
||||||
this.anchorUpListener = event => this.finishAnchorDrag(event);
|
this.anchorUpListener = event => this.finishAnchorDrag(event);
|
||||||
window.addEventListener('pointermove', this.anchorMoveListener);
|
|
||||||
window.addEventListener('pointerup', this.anchorUpListener);
|
|
||||||
this.lineMoveListener = event => this.moveLine(event);
|
this.lineMoveListener = event => this.moveLine(event);
|
||||||
this.lineUpListener = event => this.finishLineDrag(event);
|
this.lineUpListener = event => this.finishLineDrag(event);
|
||||||
window.addEventListener('pointermove', this.lineMoveListener);
|
|
||||||
window.addEventListener('pointerup', this.lineUpListener);
|
|
||||||
this.contextListener = event => this.showContextMenu(event);
|
this.contextListener = event => this.showContextMenu(event);
|
||||||
el.addEventListener('contextmenu', this.contextListener);
|
el.addEventListener('contextmenu', this.contextListener);
|
||||||
this.clickListener = event => {
|
this.clickListener = event => {
|
||||||
|
|
@ -423,7 +418,6 @@ class ConfluenceChart {
|
||||||
};
|
};
|
||||||
el.addEventListener('click', this.clickListener);
|
el.addEventListener('click', this.clickListener);
|
||||||
this.chart.subscribeCrosshairMove(param => {
|
this.chart.subscribeCrosshairMove(param => {
|
||||||
this.scheduleContextLabels();
|
|
||||||
if (!param.point || !param.time) {
|
if (!param.point || !param.time) {
|
||||||
this.tooltip.hidden = true;
|
this.tooltip.hidden = true;
|
||||||
this.hideOhlc();
|
this.hideOhlc();
|
||||||
|
|
@ -434,11 +428,7 @@ class ConfluenceChart {
|
||||||
});
|
});
|
||||||
requestAnimationFrame(() => this.observePlotCanvas());
|
requestAnimationFrame(() => this.observePlotCanvas());
|
||||||
this.chart.timeScale().subscribeVisibleLogicalRangeChange(() => {
|
this.chart.timeScale().subscribeVisibleLogicalRangeChange(() => {
|
||||||
this.renderAnchorHandles();
|
this.scheduleOverlays();
|
||||||
this.renderLineBridges();
|
|
||||||
this.renderProjectionDiagnostic();
|
|
||||||
this.renderComments();
|
|
||||||
this.renderContextLabels();
|
|
||||||
});
|
});
|
||||||
this.installPinchZoom();
|
this.installPinchZoom();
|
||||||
}
|
}
|
||||||
|
|
@ -469,12 +459,7 @@ class ConfluenceChart {
|
||||||
};
|
};
|
||||||
this.chart.timeScale().setVisibleRange(this.pendingView);
|
this.chart.timeScale().setVisibleRange(this.pendingView);
|
||||||
}
|
}
|
||||||
requestAnimationFrame(() => this.renderAnchorHandles());
|
this.scheduleOverlays(true);
|
||||||
// The bar grid just changed underneath every pinned comment.
|
|
||||||
this.renderComments();
|
|
||||||
this.renderContextLabels();
|
|
||||||
this.renderLineBridges();
|
|
||||||
this.renderProjectionDiagnostic();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTrendlineGeometry(geometry) {
|
setTrendlineGeometry(geometry) {
|
||||||
|
|
@ -591,10 +576,7 @@ class ConfluenceChart {
|
||||||
if (this.bars.length && this.bars[this.bars.length - 1].t === bar.t) this.bars[this.bars.length - 1] = bar;
|
if (this.bars.length && this.bars[this.bars.length - 1].t === bar.t) this.bars[this.bars.length - 1] = bar;
|
||||||
else this.bars.push(bar);
|
else this.bars.push(bar);
|
||||||
if (advanced) this.extendManualLineSeries(last, bar);
|
if (advanced) this.extendManualLineSeries(last, bar);
|
||||||
this.renderAnchorHandles();
|
this.scheduleOverlays(true);
|
||||||
if (advanced || this.lineBridgeLayer?.childElementCount) this.renderLineBridges();
|
|
||||||
this.renderContextLabels();
|
|
||||||
this.renderProjectionDiagnostic();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extendManualLineSeries(previous, bar) {
|
extendManualLineSeries(previous, bar) {
|
||||||
|
|
@ -623,7 +605,7 @@ class ConfluenceChart {
|
||||||
|
|
||||||
setComments(comments) {
|
setComments(comments) {
|
||||||
this.comments = comments;
|
this.comments = comments;
|
||||||
this.renderComments();
|
this.scheduleOverlays(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentPriceAnimation(enabled) {
|
setCurrentPriceAnimation(enabled) {
|
||||||
|
|
@ -804,9 +786,7 @@ class ConfluenceChart {
|
||||||
this.candles.applyOptions({ autoscaleInfoProvider: this.candleAutoscaleProvider });
|
this.candles.applyOptions({ autoscaleInfoProvider: this.candleAutoscaleProvider });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.renderAnchorHandles();
|
this.scheduleOverlays(true);
|
||||||
this.renderComments();
|
|
||||||
this.renderContextLabels();
|
|
||||||
this.updateCurrentPrice(this.lastCurrentPrice);
|
this.updateCurrentPrice(this.lastCurrentPrice);
|
||||||
};
|
};
|
||||||
this.touchEndListener = event => {
|
this.touchEndListener = event => {
|
||||||
|
|
@ -1174,10 +1154,7 @@ class ConfluenceChart {
|
||||||
this.chart.timeScale().setVisibleRange(this.pendingView);
|
this.chart.timeScale().setVisibleRange(this.pendingView);
|
||||||
this.pendingView = null;
|
this.pendingView = null;
|
||||||
}
|
}
|
||||||
this.renderAnchorHandles();
|
this.scheduleOverlays(true);
|
||||||
this.renderLineBridges();
|
|
||||||
this.renderContextLabels();
|
|
||||||
this.renderProjectionDiagnostic();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContextLabels() {
|
renderContextLabels() {
|
||||||
|
|
@ -1215,14 +1192,61 @@ class ConfluenceChart {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleContextLabels() {
|
overlayKey() {
|
||||||
if (this.contextLabelFrame != null) return;
|
const range = this.chart?.timeScale().getVisibleLogicalRange();
|
||||||
this.contextLabelFrame = requestAnimationFrame(() => {
|
if (!range) return '';
|
||||||
this.contextLabelFrame = null;
|
const price = this.manualPriceRange;
|
||||||
|
return `${range.from.toFixed(4)}:${range.to.toFixed(4)}:${price?.minValue ?? ''}:${price?.maxValue ?? ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleOverlays(force = false) {
|
||||||
|
this.overlayForce = this.overlayForce || force;
|
||||||
|
if (this.overlayFrame != null) return;
|
||||||
|
this.overlayFrame = requestAnimationFrame(() => {
|
||||||
|
this.overlayFrame = null;
|
||||||
|
const forceDraw = this.overlayForce;
|
||||||
|
this.overlayForce = false;
|
||||||
|
const key = this.overlayKey();
|
||||||
|
if (!forceDraw && key && key === this.overlayRangeKey) return;
|
||||||
|
this.overlayRangeKey = key;
|
||||||
|
this.renderAnchorHandles();
|
||||||
|
this.renderLineBridges();
|
||||||
|
this.renderProjectionDiagnostic();
|
||||||
|
this.renderComments();
|
||||||
this.renderContextLabels();
|
this.renderContextLabels();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachPointers(kind) {
|
||||||
|
if (this.pointerAttached[kind]) return;
|
||||||
|
this.pointerAttached[kind] = true;
|
||||||
|
if (kind === 'tool') {
|
||||||
|
window.addEventListener('pointermove', this.toolMoveListener);
|
||||||
|
window.addEventListener('pointerup', this.toolUpListener);
|
||||||
|
} else if (kind === 'anchor') {
|
||||||
|
window.addEventListener('pointermove', this.anchorMoveListener);
|
||||||
|
window.addEventListener('pointerup', this.anchorUpListener);
|
||||||
|
} else if (kind === 'line') {
|
||||||
|
window.addEventListener('pointermove', this.lineMoveListener);
|
||||||
|
window.addEventListener('pointerup', this.lineUpListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
detachPointers(kind) {
|
||||||
|
if (!this.pointerAttached[kind]) return;
|
||||||
|
this.pointerAttached[kind] = false;
|
||||||
|
if (kind === 'tool') {
|
||||||
|
window.removeEventListener('pointermove', this.toolMoveListener);
|
||||||
|
window.removeEventListener('pointerup', this.toolUpListener);
|
||||||
|
} else if (kind === 'anchor') {
|
||||||
|
window.removeEventListener('pointermove', this.anchorMoveListener);
|
||||||
|
window.removeEventListener('pointerup', this.anchorUpListener);
|
||||||
|
} else if (kind === 'line') {
|
||||||
|
window.removeEventListener('pointermove', this.lineMoveListener);
|
||||||
|
window.removeEventListener('pointerup', this.lineUpListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- tool arming and placement gestures ---------------------------------
|
// --- tool arming and placement gestures ---------------------------------
|
||||||
|
|
||||||
setToolCompleteHandler(handler) { this.onToolComplete = handler; }
|
setToolCompleteHandler(handler) { this.onToolComplete = handler; }
|
||||||
|
|
@ -1255,6 +1279,8 @@ class ConfluenceChart {
|
||||||
this.pendingAnchor = null;
|
this.pendingAnchor = null;
|
||||||
this.clearGesture();
|
this.clearGesture();
|
||||||
this.clearLinePreview();
|
this.clearLinePreview();
|
||||||
|
if (tool) this.attachPointers('tool');
|
||||||
|
else this.detachPointers('tool');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1292,11 +1318,7 @@ class ConfluenceChart {
|
||||||
this.observedPlotCanvas = canvas;
|
this.observedPlotCanvas = canvas;
|
||||||
this.plotResizeObserver = new ResizeObserver(() => {
|
this.plotResizeObserver = new ResizeObserver(() => {
|
||||||
this.syncOverlayLayer();
|
this.syncOverlayLayer();
|
||||||
this.renderAnchorHandles();
|
this.scheduleOverlays(true);
|
||||||
this.renderLineBridges();
|
|
||||||
this.renderProjectionDiagnostic();
|
|
||||||
this.renderComments();
|
|
||||||
this.renderContextLabels();
|
|
||||||
if (this.armedTool) this.renderGesture();
|
if (this.armedTool) this.renderGesture();
|
||||||
});
|
});
|
||||||
this.plotResizeObserver.observe(canvas);
|
this.plotResizeObserver.observe(canvas);
|
||||||
|
|
@ -1377,7 +1399,10 @@ class ConfluenceChart {
|
||||||
const point = this.eventPoint(event);
|
const point = this.eventPoint(event);
|
||||||
if (!point) return;
|
if (!point) return;
|
||||||
if (!this.armedTool) {
|
if (!this.armedTool) {
|
||||||
if (this.withinPlot(point)) this.mousePan = { start: point };
|
if (this.withinPlot(point)) {
|
||||||
|
this.mousePan = { start: point };
|
||||||
|
this.attachPointers('tool');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -1546,6 +1571,7 @@ class ConfluenceChart {
|
||||||
|
|
||||||
finishToolGesture(event) {
|
finishToolGesture(event) {
|
||||||
this.mousePan = null;
|
this.mousePan = null;
|
||||||
|
if (!this.armedTool) this.detachPointers('tool');
|
||||||
if (!this.armedTool || !this.gesture) return;
|
if (!this.armedTool || !this.gesture) return;
|
||||||
const tool = this.armedTool;
|
const tool = this.armedTool;
|
||||||
const { start, end } = this.gesture;
|
const { start, end } = this.gesture;
|
||||||
|
|
@ -2242,6 +2268,7 @@ class ConfluenceChart {
|
||||||
original: { ...level },
|
original: { ...level },
|
||||||
moved: false,
|
moved: false,
|
||||||
};
|
};
|
||||||
|
this.attachPointers('line');
|
||||||
event.currentTarget.setPointerCapture?.(event.pointerId);
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2291,6 +2318,7 @@ class ConfluenceChart {
|
||||||
const level = this.levels.find(value => value.id === this.draggingLine.id);
|
const level = this.levels.find(value => value.id === this.draggingLine.id);
|
||||||
const moved = this.draggingLine.moved;
|
const moved = this.draggingLine.moved;
|
||||||
this.draggingLine = null;
|
this.draggingLine = null;
|
||||||
|
this.detachPointers('line');
|
||||||
if (moved && level && this.onLineChange) this.onLineChange({ ...level });
|
if (moved && level && this.onLineChange) this.onLineChange({ ...level });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2303,6 +2331,7 @@ class ConfluenceChart {
|
||||||
anchor: level && ConfluenceChart.isFlat(level) ? 'price' : anchor,
|
anchor: level && ConfluenceChart.isFlat(level) ? 'price' : anchor,
|
||||||
};
|
};
|
||||||
event.currentTarget.setPointerCapture?.(event.pointerId);
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
||||||
|
this.attachPointers('anchor');
|
||||||
}
|
}
|
||||||
|
|
||||||
moveAnchor(event) {
|
moveAnchor(event) {
|
||||||
|
|
@ -2350,6 +2379,7 @@ class ConfluenceChart {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const level = this.levels.find(value => value.id === this.draggingAnchor.id);
|
const level = this.levels.find(value => value.id === this.draggingAnchor.id);
|
||||||
this.draggingAnchor = null;
|
this.draggingAnchor = null;
|
||||||
|
this.detachPointers('anchor');
|
||||||
this.hideSnapDot();
|
this.hideSnapDot();
|
||||||
if (level && this.onLineChange) this.onLineChange({ ...level });
|
if (level && this.onLineChange) this.onLineChange({ ...level });
|
||||||
}
|
}
|
||||||
|
|
@ -2425,7 +2455,10 @@ class ConfluenceChart {
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.resizeObserver) this.resizeObserver.disconnect();
|
if (this.resizeObserver) this.resizeObserver.disconnect();
|
||||||
if (this.plotResizeObserver) this.plotResizeObserver.disconnect();
|
if (this.plotResizeObserver) this.plotResizeObserver.disconnect();
|
||||||
if (this.contextLabelFrame != null) cancelAnimationFrame(this.contextLabelFrame);
|
if (this.overlayFrame != null) cancelAnimationFrame(this.overlayFrame);
|
||||||
|
this.detachPointers('tool');
|
||||||
|
this.detachPointers('anchor');
|
||||||
|
this.detachPointers('line');
|
||||||
clearTimeout(this.lineFocusTimer);
|
clearTimeout(this.lineFocusTimer);
|
||||||
for (const node of this.commentNodes.values()) clearTimeout(node.focusTimer);
|
for (const node of this.commentNodes.values()) clearTimeout(node.focusTimer);
|
||||||
if (this.chartEl && this.clickListener) this.chartEl.removeEventListener('click', this.clickListener);
|
if (this.chartEl && this.clickListener) this.chartEl.removeEventListener('click', this.clickListener);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue