@devexperts/dxcharts-lite 2.7.16 → 2.7.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chart/animation/canvas-animation.d.ts +1 -0
- package/dist/chart/animation/canvas-animation.js +2 -1
- package/dist/chart/animation/viewport-model-animation.d.ts +4 -1
- package/dist/chart/animation/viewport-model-animation.js +21 -3
- package/dist/chart/bootstrap.js +17 -5
- package/dist/chart/chart.config.js +1 -1
- package/dist/chart/components/chart/chart-area-pan.handler.d.ts +16 -16
- package/dist/chart/components/chart/chart-area-pan.handler.js +8 -2
- package/dist/chart/components/chart/chart.component.js +2 -0
- package/dist/chart/components/chart/chart.model.d.ts +11 -0
- package/dist/chart/components/chart/chart.model.js +20 -0
- package/dist/chart/components/cross_tool/cross-tool.component.d.ts +3 -1
- package/dist/chart/components/cross_tool/cross-tool.component.js +3 -2
- package/dist/chart/components/cross_tool/cross-tool.model.d.ts +6 -3
- package/dist/chart/components/cross_tool/cross-tool.model.js +9 -5
- package/dist/chart/components/grid/grid.component.d.ts +1 -1
- package/dist/chart/components/grid/grid.component.js +2 -2
- package/dist/chart/components/grid/grid.drawer.d.ts +2 -1
- package/dist/chart/components/grid/grid.drawer.js +3 -2
- package/dist/chart/components/pan/chart-pan.component.js +17 -1
- package/dist/chart/components/pane/pane.component.js +3 -1
- package/dist/chart/drawers/data-series-drawers/trend-points.drawer.d.ts +12 -0
- package/dist/chart/drawers/data-series-drawers/trend-points.drawer.js +70 -0
- package/dist/chart/drawers/drawing-manager.js +0 -1
- package/dist/chart/inputhandlers/chart-resize.handler.d.ts +5 -0
- package/dist/chart/inputhandlers/chart-resize.handler.js +9 -1
- package/dist/chart/inputlisteners/canvas-input-listener.component.d.ts +28 -0
- package/dist/chart/inputlisteners/canvas-input-listener.component.js +104 -7
- package/dist/chart/model/data-series.config.d.ts +1 -1
- package/dist/chart/model/hit-test-canvas.model.d.ts +15 -13
- package/dist/chart/model/hit-test-canvas.model.js +2 -2
- package/dist/chart/model/scale.model.d.ts +5 -0
- package/dist/chart/model/scale.model.js +72 -6
- package/dist/chart/utils/device/touchpad.utils.d.ts +1 -0
- package/dist/chart/utils/device/touchpad.utils.js +1 -1
- package/dist/chart/utils/math.utils.js +1 -1
- package/dist/chart/utils/performance/queue-microtask.utils.d.ts +5 -0
- package/dist/chart/utils/performance/queue-microtask.utils.js +12 -0
- package/dist/chart/utils/performance/request-animation-frame-throttle.utils.d.ts +1 -0
- package/dist/chart/utils/performance/request-animation-frame-throttle.utils.js +3 -0
- package/dist/chart/utils/performance/safari/components/animations/safari-canvas-animation.d.ts +77 -0
- package/dist/chart/utils/performance/safari/components/animations/safari-canvas-animation.js +137 -0
- package/dist/chart/utils/performance/safari/components/chart-area-pan.handler/safari-chart-area-pan.handler.d.ts +54 -0
- package/dist/chart/utils/performance/safari/components/chart-area-pan.handler/safari-chart-area-pan.handler.js +180 -0
- package/dist/chart/utils/performance/safari/safari-performance.model.d.ts +32 -0
- package/dist/chart/utils/performance/safari/safari-performance.model.js +31 -0
- package/dist/chart/utils/performance/safari/safari-performance.utils.d.ts +18 -0
- package/dist/chart/utils/performance/safari/safari-performance.utils.js +34 -0
- package/dist/dxchart.min.js +4 -4
- package/package.json +2 -2
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2019 - 2025 Devexperts Solutions IE Limited
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
|
+
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
import { flat } from '../../utils/array.utils';
|
|
7
|
+
const DEFAULT_FILL_COLOR = '#FF00FF';
|
|
8
|
+
export class TrendPointsDrawer {
|
|
9
|
+
constructor() { }
|
|
10
|
+
draw(ctx, allPoints, model, hitTestDrawerConfig) {
|
|
11
|
+
const allPointsFlat = flat(allPoints);
|
|
12
|
+
const config = model.getPaintConfig(0);
|
|
13
|
+
const radius = hitTestDrawerConfig.hoverWidth ? hitTestDrawerConfig.hoverWidth / 2 : config.lineWidth;
|
|
14
|
+
allPointsFlat.forEach((point, idx) => {
|
|
15
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
16
|
+
// 2 colors: Negative and Positive
|
|
17
|
+
const previousClose = previousValue(allPointsFlat, idx);
|
|
18
|
+
const isNegativeTrend = previousClose && point.close < previousClose;
|
|
19
|
+
if (((_a = config.multiplyColors) === null || _a === void 0 ? void 0 : _a.length) === 2) {
|
|
20
|
+
ctx.fillStyle =
|
|
21
|
+
(_b = hitTestDrawerConfig.color) !== null && _b !== void 0 ? _b : ((isNegativeTrend ? config.multiplyColors[1] : config.multiplyColors[0]) ||
|
|
22
|
+
config.color ||
|
|
23
|
+
DEFAULT_FILL_COLOR);
|
|
24
|
+
const x = model.view.toX(point.centerUnit);
|
|
25
|
+
const y = model.view.toY(point.close);
|
|
26
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
27
|
+
ctx.fill();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// 4 colors: Negative and Down, Negative and Up, Positive and Down, Positive and Up
|
|
31
|
+
if (((_c = config.multiplyColors) === null || _c === void 0 ? void 0 : _c.length) === 4) {
|
|
32
|
+
if (isNegativeTrend && point.close < 0) {
|
|
33
|
+
ctx.fillStyle =
|
|
34
|
+
(_d = hitTestDrawerConfig.color) !== null && _d !== void 0 ? _d : (config.multiplyColors[0] || config.color || DEFAULT_FILL_COLOR);
|
|
35
|
+
}
|
|
36
|
+
if (!isNegativeTrend && point.close < 0) {
|
|
37
|
+
ctx.fillStyle =
|
|
38
|
+
(_e = hitTestDrawerConfig.color) !== null && _e !== void 0 ? _e : (config.multiplyColors[1] || config.color || DEFAULT_FILL_COLOR);
|
|
39
|
+
}
|
|
40
|
+
if (isNegativeTrend && point.close > 0) {
|
|
41
|
+
ctx.fillStyle =
|
|
42
|
+
(_f = hitTestDrawerConfig.color) !== null && _f !== void 0 ? _f : (config.multiplyColors[2] || config.color || DEFAULT_FILL_COLOR);
|
|
43
|
+
}
|
|
44
|
+
if (!isNegativeTrend && point.close > 0) {
|
|
45
|
+
ctx.fillStyle =
|
|
46
|
+
(_g = hitTestDrawerConfig.color) !== null && _g !== void 0 ? _g : (config.multiplyColors[3] || config.color || DEFAULT_FILL_COLOR);
|
|
47
|
+
}
|
|
48
|
+
ctx.beginPath();
|
|
49
|
+
const x = model.view.toX(point.centerUnit);
|
|
50
|
+
const y = model.view.toY(point.close);
|
|
51
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
52
|
+
ctx.fill();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// 1 color
|
|
56
|
+
ctx.fillStyle = (_h = hitTestDrawerConfig.color) !== null && _h !== void 0 ? _h : (config.color || DEFAULT_FILL_COLOR);
|
|
57
|
+
ctx.beginPath();
|
|
58
|
+
const x = model.view.toX(point.centerUnit);
|
|
59
|
+
const y = model.view.toY(point.close);
|
|
60
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
61
|
+
ctx.fill();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export const previousValue = (arr, idx) => {
|
|
66
|
+
do {
|
|
67
|
+
idx--;
|
|
68
|
+
} while (idx >= 0 && !isFinite(arr[idx] && arr[idx].close));
|
|
69
|
+
return arr[idx] ? arr[idx].close : undefined;
|
|
70
|
+
};
|
|
@@ -45,7 +45,6 @@ export class DrawingManager {
|
|
|
45
45
|
this.drawersMap = {};
|
|
46
46
|
this.canvasIdsList = [];
|
|
47
47
|
this.animFrameId = `draw_${uuid()}`;
|
|
48
|
-
// eventBus.on(EVENT_DRAW_LAST_CANDLE, () => animationFrameThrottled(this.animFrameId + 'last', () => this.drawLastBar()));
|
|
49
48
|
this.drawHitTestCanvas = () => {
|
|
50
49
|
this.drawingOrder.forEach(drawer => {
|
|
51
50
|
if (drawer.indexOf(HIT_TEST_PREFIX) !== -1) {
|
|
@@ -21,6 +21,7 @@ export declare class ChartResizeHandler {
|
|
|
21
21
|
previousBCR: PickedDOMRect | undefined;
|
|
22
22
|
private animFrameId;
|
|
23
23
|
canvasResized: Subject<PickedDOMRect>;
|
|
24
|
+
private recentlyResized;
|
|
24
25
|
constructor(frameElement: HTMLElement, resizerElement: HTMLElement, bus: EventBus, canvasModels: CanvasModel[], config?: FullChartConfig | undefined);
|
|
25
26
|
/**
|
|
26
27
|
* Subscribe to resize events
|
|
@@ -43,6 +44,10 @@ export declare class ChartResizeHandler {
|
|
|
43
44
|
* @returns {void}
|
|
44
45
|
*/
|
|
45
46
|
fireUpdates(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Resets the resize flag after it has been processed
|
|
49
|
+
*/
|
|
50
|
+
clearResizedFlag(): void;
|
|
46
51
|
/**
|
|
47
52
|
* Checks if the dimensions of two PickedDOMRect objects are different.
|
|
48
53
|
* @param {PickedDOMRect} previousBCR - The previous bounding client rectangle.
|
|
@@ -20,6 +20,7 @@ export class ChartResizeHandler {
|
|
|
20
20
|
this.previousBCR = undefined;
|
|
21
21
|
this.animFrameId = `resize_${uuid()}`;
|
|
22
22
|
this.canvasResized = new Subject();
|
|
23
|
+
this.recentlyResized = false; // Flag to track if resize happened recently
|
|
23
24
|
this.elementResizeDetector = new ResizeObserver(() => this.handleResize());
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
@@ -59,12 +60,19 @@ export class ChartResizeHandler {
|
|
|
59
60
|
}
|
|
60
61
|
if (this.previousBCR === undefined || this.isBCRDimensionsDiffer(this.previousBCR, newBCR)) {
|
|
61
62
|
this.previousBCR = newBCR;
|
|
63
|
+
this.recentlyResized = true; // Set flag when actual resize happens
|
|
62
64
|
this.canvasModels.forEach(model => this.previousBCR && model.updateDPR(this.previousBCR));
|
|
63
65
|
this.canvasResized.next(newBCR);
|
|
64
66
|
this.bus.fire(EVENT_RESIZED, newBCR);
|
|
65
67
|
this.bus.fire(EVENT_DRAW);
|
|
66
68
|
}
|
|
67
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Resets the resize flag after it has been processed
|
|
72
|
+
*/
|
|
73
|
+
clearResizedFlag() {
|
|
74
|
+
this.recentlyResized = false;
|
|
75
|
+
}
|
|
68
76
|
/**
|
|
69
77
|
* Checks if the dimensions of two PickedDOMRect objects are different.
|
|
70
78
|
* @param {PickedDOMRect} previousBCR - The previous bounding client rectangle.
|
|
@@ -98,6 +106,6 @@ export class ChartResizeHandler {
|
|
|
98
106
|
* @returns {boolean} - Returns true if the canvas has been resized, false otherwise.
|
|
99
107
|
*/
|
|
100
108
|
wasResized() {
|
|
101
|
-
return this.
|
|
109
|
+
return this.recentlyResized;
|
|
102
110
|
}
|
|
103
111
|
}
|
|
@@ -60,8 +60,35 @@ export declare class CanvasInputListenerComponent extends ChartBaseElement {
|
|
|
60
60
|
dragging: boolean;
|
|
61
61
|
dragStartEvent?: CustomMouseEvent;
|
|
62
62
|
canvasBounds: Bounds;
|
|
63
|
+
private cachedElementRect;
|
|
64
|
+
private rectCacheValid;
|
|
63
65
|
constructor(eventBus: EventBus, element: HTMLElement);
|
|
64
66
|
private documentDragListeners;
|
|
67
|
+
/**
|
|
68
|
+
* Gets cached element bounding rect or calculates new one if cache is invalid.
|
|
69
|
+
* Caching prevents expensive getBoundingClientRect() calls during frequent operations.
|
|
70
|
+
*/
|
|
71
|
+
private getElementRect;
|
|
72
|
+
/**
|
|
73
|
+
* Creates fallback DOMRect for error cases or when element is unavailable
|
|
74
|
+
*/
|
|
75
|
+
private createFallbackRect;
|
|
76
|
+
/**
|
|
77
|
+
* Invalidates the cached bounding client rect, forcing recalculation on next access
|
|
78
|
+
*/
|
|
79
|
+
private invalidateRectCache;
|
|
80
|
+
/**
|
|
81
|
+
* Forces immediate initialization of the rect cache and canvas bounds
|
|
82
|
+
*/
|
|
83
|
+
initializeRectCache(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Safely cleans up document drag listeners to prevent memory leaks
|
|
86
|
+
*/
|
|
87
|
+
private cleanupDocumentDragListeners;
|
|
88
|
+
/**
|
|
89
|
+
* Override doDeactivate to ensure proper cleanup of cached data
|
|
90
|
+
*/
|
|
91
|
+
protected doDeactivate(): void;
|
|
65
92
|
private dragProcessListener;
|
|
66
93
|
private dragStartListener;
|
|
67
94
|
/**
|
|
@@ -113,6 +140,7 @@ export declare class CanvasInputListenerComponent extends ChartBaseElement {
|
|
|
113
140
|
private updateCurrentDocumentPoint;
|
|
114
141
|
/**
|
|
115
142
|
* Updates the current mouse point based on the provided CustomMouseEvent.
|
|
143
|
+
* Uses cached element rect to avoid frequent reflow in Safari.
|
|
116
144
|
* @param {CustomMouseEvent} e - The CustomMouseEvent object containing the mouse/touch coordinates.
|
|
117
145
|
* @returns {void}
|
|
118
146
|
* @private
|
|
@@ -71,6 +71,9 @@ export class CanvasInputListenerComponent extends ChartBaseElement {
|
|
|
71
71
|
width: 0,
|
|
72
72
|
height: 0,
|
|
73
73
|
};
|
|
74
|
+
// Cached bounding client rect to avoid frequent reflow in Safari
|
|
75
|
+
this.cachedElementRect = null;
|
|
76
|
+
this.rectCacheValid = false;
|
|
74
77
|
this.documentDragListeners = [];
|
|
75
78
|
this.dragProcessListener = () => {
|
|
76
79
|
this.xDragSubject.next(this.currentPoint.x - this.dragStartPoint.x);
|
|
@@ -85,7 +88,8 @@ export class CanvasInputListenerComponent extends ChartBaseElement {
|
|
|
85
88
|
this.updateCurrentPoints(e);
|
|
86
89
|
this.dragging = true;
|
|
87
90
|
this.dragStartEvent = e;
|
|
88
|
-
|
|
91
|
+
// Clean up any existing drag listeners before adding new ones
|
|
92
|
+
this.cleanupDocumentDragListeners();
|
|
89
93
|
this.dragStartPoint = CanvasInputListenerComponent.copyPoint(this.currentPoint);
|
|
90
94
|
this.xDragStartSubject.next(this.dragStartPoint);
|
|
91
95
|
this.yDragStartSubject.next(this.dragStartPoint);
|
|
@@ -104,7 +108,7 @@ export class CanvasInputListenerComponent extends ChartBaseElement {
|
|
|
104
108
|
this.updateElementOffsetListener = () => this.mouseMoveSubject.next(this.currentPoint);
|
|
105
109
|
this.dragEndListener = () => {
|
|
106
110
|
this.dragging = false;
|
|
107
|
-
this.
|
|
111
|
+
this.cleanupDocumentDragListeners();
|
|
108
112
|
this.xDragEndSubject.next();
|
|
109
113
|
this.yDragEndSubject.next();
|
|
110
114
|
};
|
|
@@ -113,6 +117,97 @@ export class CanvasInputListenerComponent extends ChartBaseElement {
|
|
|
113
117
|
this.updateCurrentMousePoint(e);
|
|
114
118
|
};
|
|
115
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Gets cached element bounding rect or calculates new one if cache is invalid.
|
|
122
|
+
* Caching prevents expensive getBoundingClientRect() calls during frequent operations.
|
|
123
|
+
*/
|
|
124
|
+
getElementRect() {
|
|
125
|
+
if (this.rectCacheValid && this.cachedElementRect) {
|
|
126
|
+
return this.cachedElementRect;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
if (!this.element || !this.element.isConnected) {
|
|
130
|
+
return this.createFallbackRect();
|
|
131
|
+
}
|
|
132
|
+
const rect = this.element.getBoundingClientRect();
|
|
133
|
+
this.cachedElementRect = rect;
|
|
134
|
+
this.rectCacheValid = true;
|
|
135
|
+
return rect;
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return this.createFallbackRect();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Creates fallback DOMRect for error cases or when element is unavailable
|
|
143
|
+
*/
|
|
144
|
+
createFallbackRect() {
|
|
145
|
+
// DOMRect might not be available in older browsers
|
|
146
|
+
if (typeof DOMRect !== 'undefined') {
|
|
147
|
+
return new DOMRect(0, 0, 0, 0);
|
|
148
|
+
}
|
|
149
|
+
// Fallback for older browsers - create object with same interface
|
|
150
|
+
const rect = {
|
|
151
|
+
x: 0,
|
|
152
|
+
y: 0,
|
|
153
|
+
width: 0,
|
|
154
|
+
height: 0,
|
|
155
|
+
top: 0,
|
|
156
|
+
right: 0,
|
|
157
|
+
bottom: 0,
|
|
158
|
+
left: 0,
|
|
159
|
+
toJSON: () => ({}),
|
|
160
|
+
};
|
|
161
|
+
return rect;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Invalidates the cached bounding client rect, forcing recalculation on next access
|
|
165
|
+
*/
|
|
166
|
+
invalidateRectCache() {
|
|
167
|
+
this.rectCacheValid = false;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Forces immediate initialization of the rect cache and canvas bounds
|
|
171
|
+
*/
|
|
172
|
+
initializeRectCache() {
|
|
173
|
+
this.invalidateRectCache();
|
|
174
|
+
const bcr = this.getElementRect();
|
|
175
|
+
this.canvasBounds.x = bcr.left;
|
|
176
|
+
this.canvasBounds.y = bcr.top;
|
|
177
|
+
this.canvasBounds.width = bcr.width;
|
|
178
|
+
this.canvasBounds.height = bcr.height;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Safely cleans up document drag listeners to prevent memory leaks
|
|
182
|
+
*/
|
|
183
|
+
cleanupDocumentDragListeners() {
|
|
184
|
+
if (!this.documentDragListeners || !Array.isArray(this.documentDragListeners)) {
|
|
185
|
+
this.documentDragListeners = [];
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.documentDragListeners.forEach(unsub => {
|
|
189
|
+
try {
|
|
190
|
+
unsub();
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
// Ignore errors during cleanup to prevent crashes
|
|
194
|
+
console.warn('Error cleaning up drag listener:', error);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
this.documentDragListeners = [];
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Override doDeactivate to ensure proper cleanup of cached data
|
|
201
|
+
*/
|
|
202
|
+
doDeactivate() {
|
|
203
|
+
// Clean up rect cache to prevent stale data
|
|
204
|
+
this.invalidateRectCache();
|
|
205
|
+
this.cachedElementRect = null;
|
|
206
|
+
// Clean up drag listeners safely
|
|
207
|
+
this.cleanupDocumentDragListeners();
|
|
208
|
+
// Call parent cleanup
|
|
209
|
+
super.doDeactivate();
|
|
210
|
+
}
|
|
116
211
|
/**
|
|
117
212
|
* This methods allows change scale, only if dragging using primary mouse button or touch.
|
|
118
213
|
* @param e
|
|
@@ -261,6 +356,7 @@ export class CanvasInputListenerComponent extends ChartBaseElement {
|
|
|
261
356
|
this.addSubscription(subscribeListener(this.element, (e) => this.touchEndSubject.next(e), 'touchend'));
|
|
262
357
|
this.addSubscription(subscribeListener(this.element, (e) => this.touchCancelSubject.next(e), 'touchcancel'));
|
|
263
358
|
this.addSubscription(subscribeListener(this.element, (e) => {
|
|
359
|
+
this.updateCurrentPoints(e);
|
|
264
360
|
this.wheelSubject.next(e);
|
|
265
361
|
e.preventDefault(); // to disable the scroll over the document, if for example chart is used as widget
|
|
266
362
|
}, 'wheel'));
|
|
@@ -269,7 +365,9 @@ export class CanvasInputListenerComponent extends ChartBaseElement {
|
|
|
269
365
|
this.addSubscription(subscribeListener(this.element, leftMouseButtonListener(() => this.mouseUpSubject.next(this.currentPoint)), 'mouseup'));
|
|
270
366
|
this.addSubscription(subscribeListener(document, leftMouseButtonListener(() => this.mouseUpDocumentSubject.next(this.currentPoint)), 'mouseup'));
|
|
271
367
|
this.addRxSubscription(this.eventBus.observe(EVENT_RESIZED).subscribe(() => {
|
|
272
|
-
|
|
368
|
+
// Force invalidate cache on resize to ensure fresh getBoundingClientRect data
|
|
369
|
+
this.invalidateRectCache();
|
|
370
|
+
const bcr = this.getElementRect();
|
|
273
371
|
this.canvasBounds.x = bcr.left;
|
|
274
372
|
this.canvasBounds.y = bcr.top;
|
|
275
373
|
this.canvasBounds.width = bcr.width;
|
|
@@ -322,15 +420,14 @@ export class CanvasInputListenerComponent extends ChartBaseElement {
|
|
|
322
420
|
}
|
|
323
421
|
/**
|
|
324
422
|
* Updates the current mouse point based on the provided CustomMouseEvent.
|
|
423
|
+
* Uses cached element rect to avoid frequent reflow in Safari.
|
|
325
424
|
* @param {CustomMouseEvent} e - The CustomMouseEvent object containing the mouse/touch coordinates.
|
|
326
425
|
* @returns {void}
|
|
327
426
|
* @private
|
|
328
427
|
*/
|
|
329
428
|
updateCurrentMousePoint(e) {
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
// but it's faster to cache BCR and recalculate when it's needed
|
|
333
|
-
const rect = this.element.getBoundingClientRect();
|
|
429
|
+
// Use cached rect to prevent frequent reflow/repaint in Safari
|
|
430
|
+
const rect = this.getElementRect();
|
|
334
431
|
if ('clientX' in e) {
|
|
335
432
|
this.currentPoint.x = e.clientX - rect.left;
|
|
336
433
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
6
|
import { BarTypes, YAxisLabelAppearanceType, YAxisLabelMode } from '../chart.config';
|
|
7
|
-
export type DataSeriesType = 'POINTS' | 'LINEAR' | 'HISTOGRAM' | 'TREND_HISTOGRAM' | 'DIFFERENCE' | 'TEXT' | 'ABOVE_CANDLE_TEXT' | 'BELOW_CANDLE_TEXT' | 'ABOVE_CANDLE_TRIANGLE' | 'TRIANGLE' | 'COLOR_CANDLE' | 'RECTANGULAR' | 'EMA_CLOUD_LINE' | 'LINEAR_TREND' | keyof BarTypes | string;
|
|
7
|
+
export type DataSeriesType = 'POINTS' | 'TREND_POINTS' | 'LINEAR' | 'HISTOGRAM' | 'TREND_HISTOGRAM' | 'DIFFERENCE' | 'TEXT' | 'ABOVE_CANDLE_TEXT' | 'BELOW_CANDLE_TEXT' | 'ABOVE_CANDLE_TRIANGLE' | 'TRIANGLE' | 'COLOR_CANDLE' | 'RECTANGULAR' | 'EMA_CLOUD_LINE' | 'LINEAR_TREND' | 'SQUEEZE_POINTS' | keyof BarTypes | string;
|
|
8
8
|
export interface DataSeriesConfig {
|
|
9
9
|
paintConfig: Array<DataSeriesPaintConfig>;
|
|
10
10
|
visible: boolean;
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
4
|
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
|
-
import { Observable, BehaviorSubject } from 'rxjs';
|
|
6
|
+
import { Observable, Subject, Subscription, BehaviorSubject } from 'rxjs';
|
|
7
7
|
import { CanvasBoundsContainer } from '../canvas/canvas-bounds-container';
|
|
8
8
|
import { CursorType, FullChartConfig } from '../chart.config';
|
|
9
9
|
import { CanvasModel } from './canvas.model';
|
|
10
10
|
import { DrawingManager } from '../drawers/drawing-manager';
|
|
11
11
|
import EventBus from '../events/event-bus';
|
|
12
12
|
import { CanvasInputListenerComponent, Point } from '../inputlisteners/canvas-input-listener.component';
|
|
13
|
+
export declare const bigPrimeNumber = 317;
|
|
13
14
|
export type HitTestEvents = 'mousedown' | 'hover' | 'touchstart' | 'dblclick' | 'contextmenu' | 'zoom' | 'mouseup';
|
|
14
15
|
type HitTestType = 'DRAWINGS' | 'DATA_SERIES' | 'EVENTS' | 'NEWS' | 'EXECUTED_ORDERS';
|
|
15
16
|
export declare const HIT_TEST_ID_RANGE: Record<HitTestType, [number, number]>;
|
|
@@ -20,14 +21,14 @@ export declare const HIT_TEST_ID_RANGE: Record<HitTestType, [number, number]>;
|
|
|
20
21
|
* @doc-tags chart-core,hit-test
|
|
21
22
|
*/
|
|
22
23
|
export declare class HitTestCanvasModel extends CanvasModel {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
protected canvasInputListener: CanvasInputListenerComponent;
|
|
25
|
+
protected canvasBoundsContainer: CanvasBoundsContainer;
|
|
26
|
+
protected hitTestSubscribers: HitTestSubscriber[];
|
|
27
|
+
protected eventsSubscriptions: Subscription[];
|
|
28
|
+
protected hoverSubject: Subject<HitTestEvent>;
|
|
29
|
+
protected touchStartSubject: Subject<HitTestEvent>;
|
|
30
|
+
protected dblClickSubject: Subject<HitTestEvent>;
|
|
31
|
+
protected rightClickSubject: Subject<HitTestEvent>;
|
|
31
32
|
hitTestDrawersPredicateSubject: BehaviorSubject<boolean>;
|
|
32
33
|
constructor(eventBus: EventBus, canvas: HTMLCanvasElement, canvasInputListener: CanvasInputListenerComponent, canvasBoundsContainer: CanvasBoundsContainer, drawingManager: DrawingManager, chartConfig: FullChartConfig, canvasModels: CanvasModel[], resizer?: HTMLElement);
|
|
33
34
|
/**
|
|
@@ -80,8 +81,8 @@ export declare class HitTestCanvasModel extends CanvasModel {
|
|
|
80
81
|
* Observes rightclicked on element event, provides rightclicked element model.
|
|
81
82
|
*/
|
|
82
83
|
observeRightClickOnElement(): Observable<HitTestEvent>;
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
protected curImgData: Uint8ClampedArray;
|
|
85
|
+
protected prevAnimationFrameId: number;
|
|
85
86
|
/**
|
|
86
87
|
* Retrieves the pixel data at the specified coordinates.
|
|
87
88
|
*
|
|
@@ -90,7 +91,7 @@ export declare class HitTestCanvasModel extends CanvasModel {
|
|
|
90
91
|
* @param {number} y - The y-coordinate of the pixel.
|
|
91
92
|
* @returns {Uint8ClampedArray} - The pixel data at the specified coordinates.
|
|
92
93
|
*/
|
|
93
|
-
|
|
94
|
+
protected getPixel(x: number, y: number): Uint8ClampedArray;
|
|
94
95
|
/**
|
|
95
96
|
* Resolves ht model based on the provided point
|
|
96
97
|
* @param point - The point for which to resolve model
|
|
@@ -108,7 +109,7 @@ export declare class HitTestCanvasModel extends CanvasModel {
|
|
|
108
109
|
* @param {HitTestEvents} event - The type of event that occurred.
|
|
109
110
|
* @returns {void}
|
|
110
111
|
*/
|
|
111
|
-
|
|
112
|
+
protected eventHandler(point: Point, event: HitTestEvents): void;
|
|
112
113
|
}
|
|
113
114
|
export interface HitTestSubscriber<T extends unknown = unknown> {
|
|
114
115
|
getIdRange(): [number, number];
|
|
@@ -126,4 +127,5 @@ export interface HitTestEvent<T = unknown> {
|
|
|
126
127
|
readonly point: Point;
|
|
127
128
|
readonly model: T;
|
|
128
129
|
}
|
|
130
|
+
export declare const sortSubscribers: (subs: HitTestSubscriber[], id: number) => [HitTestSubscriber | undefined, HitTestSubscriber[]];
|
|
129
131
|
export {};
|
|
@@ -8,7 +8,7 @@ import { map, throttleTime } from 'rxjs/operators';
|
|
|
8
8
|
import { CanvasElement } from '../canvas/canvas-bounds-container';
|
|
9
9
|
import { CanvasModel, initCanvasWithConfig } from './canvas.model';
|
|
10
10
|
import { animationFrameId } from '../utils/performance/request-animation-frame-throttle.utils';
|
|
11
|
-
const bigPrimeNumber = 317;
|
|
11
|
+
export const bigPrimeNumber = 317;
|
|
12
12
|
export const HIT_TEST_ID_RANGE = {
|
|
13
13
|
DRAWINGS: [1, 4999],
|
|
14
14
|
NEWS: [5000, 5999],
|
|
@@ -248,7 +248,7 @@ export class HitTestCanvasModel extends CanvasModel {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
-
const sortSubscribers = (subs, id) => {
|
|
251
|
+
export const sortSubscribers = (subs, id) => {
|
|
252
252
|
let mainSubscriber = undefined;
|
|
253
253
|
const restSubs = [];
|
|
254
254
|
subs.forEach(sub => {
|
|
@@ -9,6 +9,7 @@ import { CanvasAnimation } from '../animation/canvas-animation';
|
|
|
9
9
|
import { AutoScaleViewportSubModel } from './scaling/auto-scale.model';
|
|
10
10
|
import { Price, Unit, ViewportModel, ViewportModelState, Zoom } from './scaling/viewport.model';
|
|
11
11
|
import { BoundsProvider } from './bounds.model';
|
|
12
|
+
import VisualCandle from './visual-candle';
|
|
12
13
|
export interface HighLowWithIndex {
|
|
13
14
|
high: Price;
|
|
14
15
|
low: Price;
|
|
@@ -50,6 +51,8 @@ export declare class ScaleModel extends ViewportModel {
|
|
|
50
51
|
history: ScaleHistoryItem[];
|
|
51
52
|
offsets: ChartConfigComponentsOffsets;
|
|
52
53
|
private xConstraints;
|
|
54
|
+
private autoScaleZoomSubscription;
|
|
55
|
+
private autoScalePanSubscription;
|
|
53
56
|
constructor(config: FullChartConfig, getBounds: BoundsProvider, canvasAnimation: CanvasAnimation);
|
|
54
57
|
protected doActivate(): void;
|
|
55
58
|
protected doDeactivate(): void;
|
|
@@ -98,6 +101,7 @@ export declare class ScaleModel extends ViewportModel {
|
|
|
98
101
|
* @param forceNoAutoScale - force NOT apply auto-scaling (for lazy loading)
|
|
99
102
|
*/
|
|
100
103
|
setXScale(xStart: Unit, xEnd: Unit, forceNoAnimation?: boolean): void;
|
|
104
|
+
setXScaleWithoutYScale(visualCandleSource: VisualCandle[]): void;
|
|
101
105
|
setYScale(yStart: Unit, yEnd: Unit, fire?: boolean): void;
|
|
102
106
|
private setLockedYScale;
|
|
103
107
|
/**
|
|
@@ -164,6 +168,7 @@ export declare class ScaleModel extends ViewportModel {
|
|
|
164
168
|
* @param value - If true, the price-to-bar ratio will be locked. If false, it will not be locked.
|
|
165
169
|
*/
|
|
166
170
|
setLockPriceToBarRatio(value?: boolean): void;
|
|
171
|
+
private initAutoScaleThrottling;
|
|
167
172
|
}
|
|
168
173
|
/**
|
|
169
174
|
* The SyncedByXScaleModel class extends the ScaleModel class and adds support for synchronization with other ScaleModel instance, so both instances maintain the same X-axis bounds.
|
|
@@ -4,13 +4,20 @@
|
|
|
4
4
|
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
6
|
import { Subject } from 'rxjs';
|
|
7
|
-
import {
|
|
7
|
+
import { throttleTime } from 'rxjs/operators';
|
|
8
|
+
import { animationFrameScheduler } from 'rxjs';
|
|
8
9
|
import { cloneUnsafe } from '../utils/object.utils';
|
|
9
10
|
import { AutoScaleViewportSubModel } from './scaling/auto-scale.model';
|
|
10
11
|
import { changeXToKeepRatio, changeYToKeepRatio } from './scaling/lock-ratio.model';
|
|
11
12
|
import { moveXStart, moveYStart } from './scaling/move-chart.functions';
|
|
12
13
|
import { ViewportModel, calculateZoom, compareStates, } from './scaling/viewport.model';
|
|
13
14
|
import { zoomXToEndViewportCalculator, zoomXToPercentViewportCalculator } from './scaling/x-zooming.functions';
|
|
15
|
+
import { VIEWPORT_ANIMATION_ID } from '../animation/canvas-animation';
|
|
16
|
+
import { startViewportModelAnimation, startViewportModelAnimationSafari } from '../animation/viewport-model-animation';
|
|
17
|
+
import { ONE_FRAME_MS } from '../utils/numeric-constants.utils';
|
|
18
|
+
import { isSafari } from '../utils/device/touchpad.utils';
|
|
19
|
+
const autoScaleZoomSubject = new Subject();
|
|
20
|
+
const autoScalePanSubject = new Subject();
|
|
14
21
|
export const getDefaultHighLowWithIndex = () => ({
|
|
15
22
|
high: Number.NEGATIVE_INFINITY,
|
|
16
23
|
low: Number.POSITIVE_INFINITY,
|
|
@@ -40,6 +47,8 @@ export class ScaleModel extends ViewportModel {
|
|
|
40
47
|
// TODO rework, make a new history based on units
|
|
41
48
|
this.history = [];
|
|
42
49
|
this.xConstraints = [];
|
|
50
|
+
this.autoScaleZoomSubscription = null;
|
|
51
|
+
this.autoScalePanSubscription = null;
|
|
43
52
|
this.scalePostProcessor = (initialState, state) => {
|
|
44
53
|
// for now <s>reduceRight<s/> reduce bcs ChartModel#getZoomConstrait should be invoked first
|
|
45
54
|
// if we will need more complex order handling -> add some managing
|
|
@@ -48,6 +57,7 @@ export class ScaleModel extends ViewportModel {
|
|
|
48
57
|
this.state = cloneUnsafe(config.scale);
|
|
49
58
|
this.autoScaleModel = new AutoScaleViewportSubModel(this);
|
|
50
59
|
this.offsets = this.config.components.offsets;
|
|
60
|
+
this.initAutoScaleThrottling();
|
|
51
61
|
}
|
|
52
62
|
doActivate() {
|
|
53
63
|
super.doActivate();
|
|
@@ -59,6 +69,15 @@ export class ScaleModel extends ViewportModel {
|
|
|
59
69
|
}));
|
|
60
70
|
}
|
|
61
71
|
doDeactivate() {
|
|
72
|
+
// Clean up auto-scale throttling subscriptions to prevent memory leaks
|
|
73
|
+
if (this.autoScaleZoomSubscription) {
|
|
74
|
+
this.autoScaleZoomSubscription.unsubscribe();
|
|
75
|
+
this.autoScaleZoomSubscription = null;
|
|
76
|
+
}
|
|
77
|
+
if (this.autoScalePanSubscription) {
|
|
78
|
+
this.autoScalePanSubscription.unsubscribe();
|
|
79
|
+
this.autoScalePanSubscription = null;
|
|
80
|
+
}
|
|
62
81
|
super.doDeactivate();
|
|
63
82
|
this.scaleInversedSubject.complete();
|
|
64
83
|
this.beforeStartAnimationSubject.complete();
|
|
@@ -119,8 +138,13 @@ export class ScaleModel extends ViewportModel {
|
|
|
119
138
|
}
|
|
120
139
|
haltAnimation() {
|
|
121
140
|
var _a;
|
|
141
|
+
// Stop current animation if it exists
|
|
122
142
|
if ((_a = this.currentAnimation) === null || _a === void 0 ? void 0 : _a.animationInProgress) {
|
|
123
143
|
this.currentAnimation.finishAnimation();
|
|
144
|
+
// Clear the current animation reference
|
|
145
|
+
this.currentAnimation = undefined;
|
|
146
|
+
// CRITICAL: Force stop all viewport animations in the animation system to prevent accumulation
|
|
147
|
+
this.canvasAnimation.forceStopAnimation(VIEWPORT_ANIMATION_ID);
|
|
124
148
|
this.doAutoScale();
|
|
125
149
|
}
|
|
126
150
|
}
|
|
@@ -196,9 +220,27 @@ export class ScaleModel extends ViewportModel {
|
|
|
196
220
|
}
|
|
197
221
|
else {
|
|
198
222
|
(_a = this.currentAnimation) === null || _a === void 0 ? void 0 : _a.tick();
|
|
199
|
-
|
|
223
|
+
// Big profit in performance for safari
|
|
224
|
+
isSafari
|
|
225
|
+
? startViewportModelAnimationSafari(this.canvasAnimation, this, constrainedState, this.state.auto ? this.autoScaleModel : undefined)
|
|
226
|
+
: startViewportModelAnimation(this.canvasAnimation, this, constrainedState);
|
|
200
227
|
}
|
|
201
228
|
}
|
|
229
|
+
setXScaleWithoutYScale(visualCandleSource) {
|
|
230
|
+
var _a;
|
|
231
|
+
const initialStateCopy = this.export();
|
|
232
|
+
const vCandles = visualCandleSource.slice(Math.max(visualCandleSource.length - this.state.defaultViewportItems, 0));
|
|
233
|
+
const endCandle = vCandles[vCandles.length - 1];
|
|
234
|
+
const xEnd = endCandle.startUnit + endCandle.width + this.offsets.right;
|
|
235
|
+
const xStart = xEnd - (this.getBounds().width * this.zoomX);
|
|
236
|
+
const state = Object.assign(Object.assign({}, initialStateCopy), { xStart, xEnd });
|
|
237
|
+
const constrainedState = this.scalePostProcessor(initialStateCopy, state);
|
|
238
|
+
if (this.state.auto) {
|
|
239
|
+
this.autoScaleModel.doAutoYScale(constrainedState);
|
|
240
|
+
}
|
|
241
|
+
(_a = this.currentAnimation) === null || _a === void 0 ? void 0 : _a.tick();
|
|
242
|
+
startViewportModelAnimation(this.canvasAnimation, this, constrainedState);
|
|
243
|
+
}
|
|
202
244
|
setYScale(yStart, yEnd, fire = false) {
|
|
203
245
|
const initialState = this.export();
|
|
204
246
|
if (initialState.yStart === yStart && initialState.yEnd === yEnd && initialState.zoomY > 0) {
|
|
@@ -238,15 +280,19 @@ export class ScaleModel extends ViewportModel {
|
|
|
238
280
|
moveXStart(xStart) {
|
|
239
281
|
const state = this.export();
|
|
240
282
|
const initialStateCopy = Object.assign({}, state);
|
|
241
|
-
// always stop the animations
|
|
242
283
|
this.haltAnimation();
|
|
243
284
|
moveXStart(state, xStart);
|
|
244
285
|
// there we need only candles constraint
|
|
245
286
|
const constrainedState = this.scalePostProcessor(initialStateCopy, state);
|
|
246
|
-
if (
|
|
247
|
-
this.autoScaleModel
|
|
287
|
+
if (isSafari) {
|
|
288
|
+
startViewportModelAnimationSafari(this.canvasAnimation, this, constrainedState, this.state.auto ? this.autoScaleModel : undefined);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
if (this.state.auto) {
|
|
292
|
+
this.autoScaleModel.doAutoYScale(constrainedState);
|
|
293
|
+
}
|
|
294
|
+
this.apply(constrainedState);
|
|
248
295
|
}
|
|
249
|
-
this.apply(constrainedState);
|
|
250
296
|
}
|
|
251
297
|
/**
|
|
252
298
|
* Moves both yStart and yEnd without changing the viewport height (zoom).
|
|
@@ -358,6 +404,26 @@ export class ScaleModel extends ViewportModel {
|
|
|
358
404
|
}
|
|
359
405
|
this.state.lockPriceToBarRatio = value;
|
|
360
406
|
}
|
|
407
|
+
initAutoScaleThrottling() {
|
|
408
|
+
// Throttle zoom auto-scale to ONE_FRAME_MS
|
|
409
|
+
this.autoScaleZoomSubscription = autoScaleZoomSubject
|
|
410
|
+
.pipe(throttleTime(ONE_FRAME_MS, animationFrameScheduler, { trailing: true, leading: false }))
|
|
411
|
+
.subscribe(() => {
|
|
412
|
+
const state = this.export();
|
|
413
|
+
const initialStateCopy = Object.assign({}, state);
|
|
414
|
+
const constrainedState = this.scalePostProcessor(initialStateCopy, state);
|
|
415
|
+
this.autoScaleModel.doAutoYScale(constrainedState);
|
|
416
|
+
});
|
|
417
|
+
// Throttle pan auto-scale to 250ms
|
|
418
|
+
this.autoScalePanSubscription = autoScalePanSubject
|
|
419
|
+
.pipe(throttleTime(ONE_FRAME_MS, animationFrameScheduler, { trailing: true, leading: false }))
|
|
420
|
+
.subscribe(() => {
|
|
421
|
+
const state = this.export();
|
|
422
|
+
const initialStateCopy = Object.assign({}, state);
|
|
423
|
+
const constrainedState = this.scalePostProcessor(initialStateCopy, state);
|
|
424
|
+
this.autoScaleModel.doAutoYScale(constrainedState);
|
|
425
|
+
});
|
|
426
|
+
}
|
|
361
427
|
}
|
|
362
428
|
/**
|
|
363
429
|
* The SyncedByXScaleModel class extends the ScaleModel class and adds support for synchronization with other ScaleModel instance, so both instances maintain the same X-axis bounds.
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
6
|
import { PriceAxisType } from '../../components/labels_generator/numeric-axis-labels.generator';
|
|
7
|
+
export declare const isSafari: boolean;
|
|
7
8
|
export declare const isFirefox: boolean;
|
|
8
9
|
/**
|
|
9
10
|
* this function determines whether the event was triggered with the mouse or the touchpad
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
4
|
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
|
-
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
6
|
+
export const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
7
7
|
//@ts-ignore
|
|
8
8
|
const isChrome = !!window.chrome;
|
|
9
9
|
//@ts-ignore
|
|
@@ -113,7 +113,7 @@ export function clamp(value, min, max) {
|
|
|
113
113
|
return Math.max(min, Math.min(value, max));
|
|
114
114
|
}
|
|
115
115
|
export function easeExpOut(value) {
|
|
116
|
-
return 1 - (Math.pow(2, -
|
|
116
|
+
return 1 - (Math.pow(2, -10 * value) - 0.0009765625) * 1.0009775171065494;
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
119
|
* Returns the first finite number in a list of numbers. If no finite number is found,
|