@devexperts/dxcharts-lite 2.7.16 → 2.7.18

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.
Files changed (50) hide show
  1. package/dist/chart/animation/canvas-animation.d.ts +1 -0
  2. package/dist/chart/animation/canvas-animation.js +2 -1
  3. package/dist/chart/animation/viewport-model-animation.d.ts +4 -1
  4. package/dist/chart/animation/viewport-model-animation.js +21 -3
  5. package/dist/chart/bootstrap.js +17 -5
  6. package/dist/chart/chart.config.js +1 -1
  7. package/dist/chart/components/chart/chart-area-pan.handler.d.ts +16 -16
  8. package/dist/chart/components/chart/chart-area-pan.handler.js +8 -2
  9. package/dist/chart/components/chart/chart.component.js +2 -0
  10. package/dist/chart/components/chart/chart.model.d.ts +11 -0
  11. package/dist/chart/components/chart/chart.model.js +20 -0
  12. package/dist/chart/components/cross_tool/cross-tool.component.d.ts +3 -1
  13. package/dist/chart/components/cross_tool/cross-tool.component.js +3 -2
  14. package/dist/chart/components/cross_tool/cross-tool.model.d.ts +6 -3
  15. package/dist/chart/components/cross_tool/cross-tool.model.js +9 -5
  16. package/dist/chart/components/grid/grid.component.d.ts +1 -1
  17. package/dist/chart/components/grid/grid.component.js +2 -2
  18. package/dist/chart/components/grid/grid.drawer.d.ts +2 -1
  19. package/dist/chart/components/grid/grid.drawer.js +3 -2
  20. package/dist/chart/components/pan/chart-pan.component.js +17 -1
  21. package/dist/chart/components/pane/pane.component.js +3 -1
  22. package/dist/chart/drawers/data-series-drawers/trend-points.drawer.d.ts +12 -0
  23. package/dist/chart/drawers/data-series-drawers/trend-points.drawer.js +70 -0
  24. package/dist/chart/drawers/drawing-manager.js +0 -1
  25. package/dist/chart/inputhandlers/chart-resize.handler.d.ts +5 -0
  26. package/dist/chart/inputhandlers/chart-resize.handler.js +9 -1
  27. package/dist/chart/inputlisteners/canvas-input-listener.component.d.ts +28 -0
  28. package/dist/chart/inputlisteners/canvas-input-listener.component.js +104 -7
  29. package/dist/chart/model/data-series.config.d.ts +1 -1
  30. package/dist/chart/model/hit-test-canvas.model.d.ts +15 -13
  31. package/dist/chart/model/hit-test-canvas.model.js +2 -2
  32. package/dist/chart/model/scale.model.d.ts +5 -0
  33. package/dist/chart/model/scale.model.js +73 -7
  34. package/dist/chart/utils/device/touchpad.utils.d.ts +1 -0
  35. package/dist/chart/utils/device/touchpad.utils.js +1 -1
  36. package/dist/chart/utils/math.utils.js +1 -1
  37. package/dist/chart/utils/performance/queue-microtask.utils.d.ts +5 -0
  38. package/dist/chart/utils/performance/queue-microtask.utils.js +12 -0
  39. package/dist/chart/utils/performance/request-animation-frame-throttle.utils.d.ts +1 -0
  40. package/dist/chart/utils/performance/request-animation-frame-throttle.utils.js +3 -0
  41. package/dist/chart/utils/performance/safari/components/animations/safari-canvas-animation.d.ts +77 -0
  42. package/dist/chart/utils/performance/safari/components/animations/safari-canvas-animation.js +137 -0
  43. package/dist/chart/utils/performance/safari/components/chart-area-pan.handler/safari-chart-area-pan.handler.d.ts +54 -0
  44. package/dist/chart/utils/performance/safari/components/chart-area-pan.handler/safari-chart-area-pan.handler.js +180 -0
  45. package/dist/chart/utils/performance/safari/safari-performance.model.d.ts +32 -0
  46. package/dist/chart/utils/performance/safari/safari-performance.model.js +31 -0
  47. package/dist/chart/utils/performance/safari/safari-performance.utils.d.ts +18 -0
  48. package/dist/chart/utils/performance/safari/safari-performance.utils.js +34 -0
  49. package/dist/dxchart.min.js +4 -4
  50. 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.previousBCR !== undefined;
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
- this.documentDragListeners.forEach(unsub => unsub());
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.documentDragListeners.forEach(unsub => unsub());
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
- const bcr = this.element.getBoundingClientRect();
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
- // this is a bad solution because BCR causes reflow
331
- // it's ok while document is not changed, styles and layout
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
- private canvasInputListener;
24
- private canvasBoundsContainer;
25
- private hitTestSubscribers;
26
- private eventsSubscriptions;
27
- private hoverSubject;
28
- private touchStartSubject;
29
- private dblClickSubject;
30
- private rightClickSubject;
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
- private curImgData;
84
- private prevAnimationFrameId;
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
- private getPixel;
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
- private eventHandler;
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 { startViewportModelAnimation } from '../animation/viewport-model-animation';
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,15 +220,33 @@ export class ScaleModel extends ViewportModel {
196
220
  }
197
221
  else {
198
222
  (_a = this.currentAnimation) === null || _a === void 0 ? void 0 : _a.tick();
199
- startViewportModelAnimation(this.canvasAnimation, this, constrainedState);
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);
227
+ }
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);
200
240
  }
241
+ (_a = this.currentAnimation) === null || _a === void 0 ? void 0 : _a.tick();
242
+ startViewportModelAnimation(this.canvasAnimation, this, constrainedState);
201
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) {
205
247
  return;
206
248
  }
207
- if (this.state.lockPriceToBarRatio) {
249
+ if (this.state.lockPriceToBarRatio && this.initialViewportValidSubject.getValue()) {
208
250
  this.setLockedYScale(yStart, yEnd, fire, initialState);
209
251
  return;
210
252
  }
@@ -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 (this.state.auto) {
247
- this.autoScaleModel.doAutoYScale(constrainedState);
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, -5 * value) - 0.0009765625) * 1.0009775171065494;
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,