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
|
||||
`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
|
||||
|
||||
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.commentLayer = 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.onCommentMove = null;
|
||||
this.onCommentSelect = null;
|
||||
|
|
@ -254,9 +257,7 @@ class ConfluenceChart {
|
|||
this.chart.applyOptions({ width: el.clientWidth, height: el.clientHeight });
|
||||
requestAnimationFrame(() => {
|
||||
this.observePlotCanvas();
|
||||
this.renderAnchorHandles();
|
||||
this.renderComments();
|
||||
this.renderContextLabels();
|
||||
this.scheduleOverlays(true);
|
||||
});
|
||||
});
|
||||
this.resizeObserver.observe(el);
|
||||
|
|
@ -397,16 +398,10 @@ class ConfluenceChart {
|
|||
this.toolMoveListener = event => this.moveToolGesture(event);
|
||||
this.toolUpListener = event => this.finishToolGesture(event);
|
||||
el.addEventListener('pointerdown', this.toolDownListener);
|
||||
window.addEventListener('pointermove', this.toolMoveListener);
|
||||
window.addEventListener('pointerup', this.toolUpListener);
|
||||
this.anchorMoveListener = event => this.moveAnchor(event);
|
||||
this.anchorUpListener = event => this.finishAnchorDrag(event);
|
||||
window.addEventListener('pointermove', this.anchorMoveListener);
|
||||
window.addEventListener('pointerup', this.anchorUpListener);
|
||||
this.lineMoveListener = event => this.moveLine(event);
|
||||
this.lineUpListener = event => this.finishLineDrag(event);
|
||||
window.addEventListener('pointermove', this.lineMoveListener);
|
||||
window.addEventListener('pointerup', this.lineUpListener);
|
||||
this.contextListener = event => this.showContextMenu(event);
|
||||
el.addEventListener('contextmenu', this.contextListener);
|
||||
this.clickListener = event => {
|
||||
|
|
@ -423,7 +418,6 @@ class ConfluenceChart {
|
|||
};
|
||||
el.addEventListener('click', this.clickListener);
|
||||
this.chart.subscribeCrosshairMove(param => {
|
||||
this.scheduleContextLabels();
|
||||
if (!param.point || !param.time) {
|
||||
this.tooltip.hidden = true;
|
||||
this.hideOhlc();
|
||||
|
|
@ -434,11 +428,7 @@ class ConfluenceChart {
|
|||
});
|
||||
requestAnimationFrame(() => this.observePlotCanvas());
|
||||
this.chart.timeScale().subscribeVisibleLogicalRangeChange(() => {
|
||||
this.renderAnchorHandles();
|
||||
this.renderLineBridges();
|
||||
this.renderProjectionDiagnostic();
|
||||
this.renderComments();
|
||||
this.renderContextLabels();
|
||||
this.scheduleOverlays();
|
||||
});
|
||||
this.installPinchZoom();
|
||||
}
|
||||
|
|
@ -469,12 +459,7 @@ class ConfluenceChart {
|
|||
};
|
||||
this.chart.timeScale().setVisibleRange(this.pendingView);
|
||||
}
|
||||
requestAnimationFrame(() => this.renderAnchorHandles());
|
||||
// The bar grid just changed underneath every pinned comment.
|
||||
this.renderComments();
|
||||
this.renderContextLabels();
|
||||
this.renderLineBridges();
|
||||
this.renderProjectionDiagnostic();
|
||||
this.scheduleOverlays(true);
|
||||
}
|
||||
|
||||
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;
|
||||
else this.bars.push(bar);
|
||||
if (advanced) this.extendManualLineSeries(last, bar);
|
||||
this.renderAnchorHandles();
|
||||
if (advanced || this.lineBridgeLayer?.childElementCount) this.renderLineBridges();
|
||||
this.renderContextLabels();
|
||||
this.renderProjectionDiagnostic();
|
||||
this.scheduleOverlays(true);
|
||||
}
|
||||
|
||||
extendManualLineSeries(previous, bar) {
|
||||
|
|
@ -623,7 +605,7 @@ class ConfluenceChart {
|
|||
|
||||
setComments(comments) {
|
||||
this.comments = comments;
|
||||
this.renderComments();
|
||||
this.scheduleOverlays(true);
|
||||
}
|
||||
|
||||
setCurrentPriceAnimation(enabled) {
|
||||
|
|
@ -804,9 +786,7 @@ class ConfluenceChart {
|
|||
this.candles.applyOptions({ autoscaleInfoProvider: this.candleAutoscaleProvider });
|
||||
}
|
||||
}
|
||||
this.renderAnchorHandles();
|
||||
this.renderComments();
|
||||
this.renderContextLabels();
|
||||
this.scheduleOverlays(true);
|
||||
this.updateCurrentPrice(this.lastCurrentPrice);
|
||||
};
|
||||
this.touchEndListener = event => {
|
||||
|
|
@ -1174,10 +1154,7 @@ class ConfluenceChart {
|
|||
this.chart.timeScale().setVisibleRange(this.pendingView);
|
||||
this.pendingView = null;
|
||||
}
|
||||
this.renderAnchorHandles();
|
||||
this.renderLineBridges();
|
||||
this.renderContextLabels();
|
||||
this.renderProjectionDiagnostic();
|
||||
this.scheduleOverlays(true);
|
||||
}
|
||||
|
||||
renderContextLabels() {
|
||||
|
|
@ -1215,14 +1192,61 @@ class ConfluenceChart {
|
|||
}));
|
||||
}
|
||||
|
||||
scheduleContextLabels() {
|
||||
if (this.contextLabelFrame != null) return;
|
||||
this.contextLabelFrame = requestAnimationFrame(() => {
|
||||
this.contextLabelFrame = null;
|
||||
overlayKey() {
|
||||
const range = this.chart?.timeScale().getVisibleLogicalRange();
|
||||
if (!range) return '';
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
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 ---------------------------------
|
||||
|
||||
setToolCompleteHandler(handler) { this.onToolComplete = handler; }
|
||||
|
|
@ -1255,6 +1279,8 @@ class ConfluenceChart {
|
|||
this.pendingAnchor = null;
|
||||
this.clearGesture();
|
||||
this.clearLinePreview();
|
||||
if (tool) this.attachPointers('tool');
|
||||
else this.detachPointers('tool');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1292,11 +1318,7 @@ class ConfluenceChart {
|
|||
this.observedPlotCanvas = canvas;
|
||||
this.plotResizeObserver = new ResizeObserver(() => {
|
||||
this.syncOverlayLayer();
|
||||
this.renderAnchorHandles();
|
||||
this.renderLineBridges();
|
||||
this.renderProjectionDiagnostic();
|
||||
this.renderComments();
|
||||
this.renderContextLabels();
|
||||
this.scheduleOverlays(true);
|
||||
if (this.armedTool) this.renderGesture();
|
||||
});
|
||||
this.plotResizeObserver.observe(canvas);
|
||||
|
|
@ -1377,7 +1399,10 @@ class ConfluenceChart {
|
|||
const point = this.eventPoint(event);
|
||||
if (!point) return;
|
||||
if (!this.armedTool) {
|
||||
if (this.withinPlot(point)) this.mousePan = { start: point };
|
||||
if (this.withinPlot(point)) {
|
||||
this.mousePan = { start: point };
|
||||
this.attachPointers('tool');
|
||||
}
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
|
@ -1546,6 +1571,7 @@ class ConfluenceChart {
|
|||
|
||||
finishToolGesture(event) {
|
||||
this.mousePan = null;
|
||||
if (!this.armedTool) this.detachPointers('tool');
|
||||
if (!this.armedTool || !this.gesture) return;
|
||||
const tool = this.armedTool;
|
||||
const { start, end } = this.gesture;
|
||||
|
|
@ -2242,6 +2268,7 @@ class ConfluenceChart {
|
|||
original: { ...level },
|
||||
moved: false,
|
||||
};
|
||||
this.attachPointers('line');
|
||||
event.currentTarget.setPointerCapture?.(event.pointerId);
|
||||
}
|
||||
|
||||
|
|
@ -2291,6 +2318,7 @@ class ConfluenceChart {
|
|||
const level = this.levels.find(value => value.id === this.draggingLine.id);
|
||||
const moved = this.draggingLine.moved;
|
||||
this.draggingLine = null;
|
||||
this.detachPointers('line');
|
||||
if (moved && level && this.onLineChange) this.onLineChange({ ...level });
|
||||
}
|
||||
|
||||
|
|
@ -2303,6 +2331,7 @@ class ConfluenceChart {
|
|||
anchor: level && ConfluenceChart.isFlat(level) ? 'price' : anchor,
|
||||
};
|
||||
event.currentTarget.setPointerCapture?.(event.pointerId);
|
||||
this.attachPointers('anchor');
|
||||
}
|
||||
|
||||
moveAnchor(event) {
|
||||
|
|
@ -2350,6 +2379,7 @@ class ConfluenceChart {
|
|||
event.preventDefault();
|
||||
const level = this.levels.find(value => value.id === this.draggingAnchor.id);
|
||||
this.draggingAnchor = null;
|
||||
this.detachPointers('anchor');
|
||||
this.hideSnapDot();
|
||||
if (level && this.onLineChange) this.onLineChange({ ...level });
|
||||
}
|
||||
|
|
@ -2425,7 +2455,10 @@ class ConfluenceChart {
|
|||
destroy() {
|
||||
if (this.resizeObserver) this.resizeObserver.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);
|
||||
for (const node of this.commentNodes.values()) clearTimeout(node.focusTimer);
|
||||
if (this.chartEl && this.clickListener) this.chartEl.removeEventListener('click', this.clickListener);
|
||||
|
|
|
|||
Loading…
Reference in a new issue