@devexperts/dxcharts-lite 2.1.0 → 2.3.0

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 (31) hide show
  1. package/README.md +1 -1
  2. package/dist/chart/canvas/canvas-bounds-container.d.ts +2 -2
  3. package/dist/chart/canvas/canvas-bounds-container.js +2 -2
  4. package/dist/chart/chart.config.d.ts +17 -4
  5. package/dist/chart/chart.config.js +6 -2
  6. package/dist/chart/components/chart/chart-area-pan.handler.js +10 -5
  7. package/dist/chart/components/cross_tool/cross-tool.model.js +1 -1
  8. package/dist/chart/components/snapshot/snapshot.component.js +1 -0
  9. package/dist/chart/components/volumes/separate-volumes.component.js +2 -1
  10. package/dist/chart/components/volumes/volumes.formatter.d.ts +1 -1
  11. package/dist/chart/components/volumes/volumes.formatter.js +28 -25
  12. package/dist/chart/components/y_axis/y-axis-scale.handler.d.ts +5 -4
  13. package/dist/chart/components/y_axis/y-axis-scale.handler.js +9 -9
  14. package/dist/chart/components/y_axis/y-axis.component.js +1 -0
  15. package/dist/chart/drawers/data-series.drawer.js +1 -1
  16. package/dist/chart/inputhandlers/cross-event-producer.component.js +2 -1
  17. package/dist/chart/inputhandlers/hover-producer.component.js +1 -1
  18. package/dist/chart/inputlisteners/canvas-input-listener.component.d.ts +19 -3
  19. package/dist/chart/inputlisteners/canvas-input-listener.component.js +102 -44
  20. package/dist/chart/model/scale.model.d.ts +3 -2
  21. package/dist/chart/model/scale.model.js +23 -5
  22. package/dist/chart/model/scaling/constrait.functions.d.ts +1 -1
  23. package/dist/chart/model/scaling/constrait.functions.js +1 -2
  24. package/dist/chart/model/scaling/lock-ratio.model.d.ts +8 -2
  25. package/dist/chart/model/scaling/lock-ratio.model.js +18 -3
  26. package/dist/chart/utils/device/touchpad.utils.d.ts +3 -3
  27. package/dist/chart/utils/device/touchpad.utils.js +3 -5
  28. package/dist/chart/utils/math.utils.d.ts +0 -1
  29. package/dist/chart/utils/math.utils.js +1 -1
  30. package/dist/dxchart.min.js +4 -4
  31. package/package.json +4 -1
package/README.md CHANGED
@@ -23,7 +23,7 @@
23
23
  <img src="https://img.shields.io/static/v1?label=PRs&message=Welcome&color=blue" alt="PRs: Welcome" />
24
24
  </a>&nbsp;
25
25
  <a href="https://devexperts.com/dxcharts-demo/?lang=en">
26
- <img src="https://img.shields.io/static/v1?label=Latest%20version&message=2.1.0&color=blue" alt="Version" />
26
+ <img src="https://img.shields.io/static/v1?label=Latest%20version&message=2.2.0&color=blue" alt="Version" />
27
27
  </a>
28
28
  </p>
29
29
 
@@ -35,11 +35,11 @@ export declare class CanvasElement {
35
35
  static CHART_WITH_Y_AXIS: string;
36
36
  static EVENTS: string;
37
37
  /**
38
- * @deprecated - use CanvasElement.PANE_UUID(CHART_UUID) instead
38
+ * @returns pane bounds for the main chart
39
39
  */
40
40
  static CHART: string;
41
41
  /**
42
- * @deprecated - use CanvasElement.PANE_UUID_Y_AXIS(CHART_UUID) instead
42
+ * @returns y-axis bounds for the main chart
43
43
  */
44
44
  static Y_AXIS: string;
45
45
  }
@@ -31,11 +31,11 @@ CanvasElement.ALL_PANES = 'ALL_PANES';
31
31
  CanvasElement.CHART_WITH_Y_AXIS = 'CHART_WITH_Y_AXIS';
32
32
  CanvasElement.EVENTS = 'EVENTS';
33
33
  /**
34
- * @deprecated - use CanvasElement.PANE_UUID(CHART_UUID) instead
34
+ * @returns pane bounds for the main chart
35
35
  */
36
36
  CanvasElement.CHART = CanvasElement.PANE_UUID(CHART_UUID);
37
37
  /**
38
- * @deprecated - use CanvasElement.PANE_UUID_Y_AXIS(CHART_UUID) instead
38
+ * @returns y-axis bounds for the main chart
39
39
  */
40
40
  CanvasElement.Y_AXIS = CanvasElement.PANE_UUID_Y_AXIS(CHART_UUID);
41
41
  export { CanvasElement };
@@ -233,10 +233,23 @@ export interface ChartScale {
233
233
  * When dragging chart under specific angle - will automatically disable auto-scale.
234
234
  */
235
235
  autoScaleDisableOnDrag: AutoScaleDisableOnDrag;
236
- /**
237
- * 0..1 ratio of full viewport; 0.5 = middle, 0.75 = 3/4 of viewport
238
- */
239
- zoomSensitivity: number;
236
+ zoomSensitivity: {
237
+ /**
238
+ * Value is related to zoom event (zooming chart via mouse wheel)
239
+ * 0..1 ratio of full viewport; 0.5 = middle, 0.75 = 3/4 of viewport
240
+ */
241
+ wheel: number;
242
+ /**
243
+ * Value is related to pinch touchpad event (zooming chart via touchpad)
244
+ * 0..1 ratio of full viewport; 0.5 = middle, 0.75 = 3/4 of viewport
245
+ */
246
+ pinch: number;
247
+ /**
248
+ * Value is related to glide touchpad event (scrolling chart via touchpad)
249
+ * 0..1 ratio of full viewport; 0.5 = middle, 0.75 = 3/4 of viewport
250
+ */
251
+ glide: number;
252
+ };
240
253
  /**
241
254
  * Defines how much items (candles) will be in viewport when chart applies basic scale
242
255
  */
@@ -41,9 +41,13 @@ export const getDefaultConfig = () => ({
41
41
  yDiff: 80,
42
42
  },
43
43
  inverse: false,
44
- zoomSensitivity: 0.25,
44
+ zoomSensitivity: {
45
+ wheel: 0.25,
46
+ pinch: 0.05,
47
+ glide: 0.05,
48
+ },
45
49
  defaultViewportItems: 100,
46
- disableAnimations: false
50
+ disableAnimations: false,
47
51
  },
48
52
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
49
53
  components: {
@@ -60,10 +60,8 @@ export class ChartAreaPanHandler extends ChartBaseElement {
60
60
  * @param {WheelEvent} e - Wheel event
61
61
  * @returns {void}
62
62
  */
63
- this.zoomXHandler = (e) => {
64
- const isTouchpad = touchpadDetector(e);
63
+ this.zoomXHandler = (e, zoomSensitivity) => {
65
64
  const zoomIn = e.deltaY < 0;
66
- const zoomSensitivity = isTouchpad ? getTouchpadSensitivity(this.config) : this.config.scale.zoomSensitivity;
67
65
  if (this.config.scale.zoomToCursor) {
68
66
  const b = this.canvasBoundsContainer.getBounds(CanvasElement.CANVAS);
69
67
  const canvasW = b.width;
@@ -115,7 +113,13 @@ export class ChartAreaPanHandler extends ChartBaseElement {
115
113
  //#endregion
116
114
  this.addRxSubscription(merge(this.canvasInputListener.observeWheel(allPanesHitTest), this.canvasInputListener.observePinch(allPanesHitTest))
117
115
  .pipe(throttleTime(this.wheelTrottleTime, undefined, { trailing: true, leading: true }))
118
- .subscribe(this.zoomXHandler));
116
+ .subscribe(e => {
117
+ const isTouchpad = touchpadDetector(e);
118
+ const zoomSensitivity = isTouchpad
119
+ ? getTouchpadSensitivity(this.config.components.yAxis.type, this.config.scale.zoomSensitivity.pinch)
120
+ : this.config.scale.zoomSensitivity.wheel;
121
+ this.zoomXHandler(e, zoomSensitivity);
122
+ }));
119
123
  this.addRxSubscription(this.canvasInputListener
120
124
  .observeScrollGesture()
121
125
  .pipe(throttleTime(this.wheelTrottleTime, undefined, { trailing: true, leading: true }))
@@ -134,7 +138,8 @@ export class ChartAreaPanHandler extends ChartBaseElement {
134
138
  this.scale.moveXStart(this.scale.xStart - unitsDelta);
135
139
  }
136
140
  else if (deltaY !== 0 && Math.abs(deltaY) > Math.abs(deltaX)) {
137
- this.zoomXHandler(e);
141
+ const zoomSensitivity = getTouchpadSensitivity(this.config.components.yAxis.type, this.config.scale.zoomSensitivity.glide);
142
+ this.zoomXHandler(e, zoomSensitivity);
138
143
  }
139
144
  this.bus.fireDraw();
140
145
  }));
@@ -107,7 +107,7 @@ export class CrossToolModel extends ChartBaseElement {
107
107
  else {
108
108
  this.currentHover.y = hover.y;
109
109
  }
110
- this.currentHover.paneId = CHART_UUID;
110
+ this.currentHover.paneId = hover.paneId;
111
111
  this.currentHoverSubject.next(this.currentHover);
112
112
  }
113
113
  }
@@ -40,6 +40,7 @@ export class SnapshotComponent extends ChartBaseElement {
40
40
  ctx.drawImage(this.elements.mainCanvas, 0, 0, width, height);
41
41
  ctx.drawImage(this.elements.dynamicObjectsCanvas, 0, 0, width, height);
42
42
  ctx.drawImage(this.elements.crossToolCanvas, 0, 0, width, height);
43
+ ctx.drawImage(this.elements.yAxisLabelsCanvas, 0, 0, width, height);
43
44
  userDrawCallback && userDrawCallback(ctx);
44
45
  return new Promise((resolve, fail) => this.elements.snapshotCanvas.toBlob(blob => {
45
46
  return blob ? resolve(blob) : fail('Blob is null');
@@ -31,9 +31,10 @@ class SeparateVolumesComponent extends ChartBaseElement {
31
31
  */
32
32
  activateSeparateVolumes() {
33
33
  if (this.paneManager.panes[SeparateVolumesComponent.UUID] === undefined) {
34
+ const precision = 1;
34
35
  const volumePane = this.paneManager.createPane(SeparateVolumesComponent.UUID, {
35
36
  paneFormatters: {
36
- regular: volumeFormatter,
37
+ regular: (value) => volumeFormatter(value, precision),
37
38
  },
38
39
  useDefaultHighLow: false,
39
40
  increment: 1,
@@ -3,4 +3,4 @@
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
- export declare const volumeFormatter: (value: number) => string;
6
+ export declare const volumeFormatter: (value: number, precision?: number) => string;
@@ -3,34 +3,37 @@
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 DEFAULT_PRECISION = 5;
7
- const MIN_VALUE = 0.0000000001;
8
- const TRAILLING_ZEROES_REGEXP = /\.?0+$/;
9
- const TRAILLING_FRACTION_WITH_ZEROES_REGEXP = /(\.[1-9]*)0+$/;
10
- export const volumeFormatter = (value) => {
11
- const formatValue = (value, amountToCut) => {
12
- const rounder = Math.pow(10, DEFAULT_PRECISION);
13
- const roundedValue = Math.round(value * rounder) / rounder;
14
- if (roundedValue < 1 && roundedValue >= MIN_VALUE) {
15
- return roundedValue
16
- .toFixed(DEFAULT_PRECISION)
17
- .replace(TRAILLING_ZEROES_REGEXP, '')
18
- .replace(TRAILLING_FRACTION_WITH_ZEROES_REGEXP, '');
6
+ export const volumeFormatter = (value, precision = 1) => {
7
+ function formatNumber(value) {
8
+ let formattedResult;
9
+ const priceScale = Math.pow(10, precision);
10
+ value = Math.round(value * priceScale) / priceScale;
11
+ if (value >= 1e-15 && value < 1) {
12
+ formattedResult = value.toFixed(precision).replace(/\.?0+$/, ''); // regex removes trailing zeroes
19
13
  }
20
14
  else {
21
- return (roundedValue + '').replace(TRAILLING_FRACTION_WITH_ZEROES_REGEXP, '') + (amountToCut || '');
15
+ formattedResult = String(value);
22
16
  }
23
- };
24
- if (Math.abs(value) > 999999999) {
25
- value = Math.round(value / 1000000) * 1000000;
26
- return formatValue(value / 1000000000, 'B');
17
+ return formattedResult.replace(/(\.[1-9]*)0+$/, (e, p1) => p1);
27
18
  }
28
- if (Math.abs(value) > 999999) {
29
- value = 1000 * Math.round(value / 1000);
30
- return formatValue(value / 1000000, 'M');
31
- }
32
- if (Math.abs(value) > 9999) {
33
- return formatValue(value / 1000, 'K');
19
+ function format(value) {
20
+ let sign = '';
21
+ if (value < 0) {
22
+ sign = '-';
23
+ value = -value;
24
+ }
25
+ if (value < 995) {
26
+ return sign + formatNumber(value);
27
+ }
28
+ if (value < 999995) {
29
+ return sign + formatNumber(value / 1000) + 'K';
30
+ }
31
+ if (value < 999999995) {
32
+ value = 1000 * Math.round(value / 1000);
33
+ return sign + formatNumber(value / 1000000) + 'M';
34
+ }
35
+ value = 1000000 * Math.round(value / 1000000);
36
+ return sign + formatNumber(value / 1000000000) + 'B';
34
37
  }
35
- return formatValue(value);
38
+ return format(value);
36
39
  };
@@ -9,23 +9,24 @@ import { YAxisConfig } from '../../chart.config';
9
9
  import EventBus from '../../events/event-bus';
10
10
  import { ChartBaseElement } from '../../model/chart-base-element';
11
11
  import { CanvasInputListenerComponent } from '../../inputlisteners/canvas-input-listener.component';
12
- import { Pixel, Unit, ViewportModel } from '../../model/scaling/viewport.model';
12
+ import { Pixel, Unit } from '../../model/scaling/viewport.model';
13
13
  import { ChartPanComponent } from '../pan/chart-pan.component';
14
+ import { ScaleModel } from '../../model/scale.model';
14
15
  /**
15
16
  * Handles the mouse drag on Y axis - to zoom the viewport vertically.
16
17
  * @doc-tags scaling,zoom,viewport
17
18
  */
18
19
  export declare class YAxisScaleHandler extends ChartBaseElement {
19
20
  private bus;
20
- private viewportModel;
21
- private canvasBoundsContainer;
21
+ private scale;
22
+ private bounds;
22
23
  private autoScaleCallback;
23
24
  yAxisDragEndSubject: Subject<void>;
24
25
  lastYStart: Unit;
25
26
  lastYEnd: Unit;
26
27
  lastYHeight: Unit;
27
28
  lastYPxHeight: Pixel;
28
- constructor(bus: EventBus, config: YAxisConfig, chartPanComponent: ChartPanComponent, viewportModel: ViewportModel, canvasInputListener: CanvasInputListenerComponent, canvasBoundsContainer: CanvasBoundsContainer, hitTest: HitBoundsTest, autoScaleCallback: (auto: boolean) => void);
29
+ constructor(bus: EventBus, config: YAxisConfig, panning: ChartPanComponent, scale: ScaleModel, canvasInputListener: CanvasInputListenerComponent, bounds: CanvasBoundsContainer, hitTest: HitBoundsTest, autoScaleCallback: (auto: boolean) => void);
29
30
  private onYDragStart;
30
31
  private onYDragTick;
31
32
  private onYDragEnd;
@@ -14,11 +14,11 @@ const FULL_Y_HEIGHT_ZOOM_FACTOR = 10;
14
14
  * @doc-tags scaling,zoom,viewport
15
15
  */
16
16
  export class YAxisScaleHandler extends ChartBaseElement {
17
- constructor(bus, config, chartPanComponent, viewportModel, canvasInputListener, canvasBoundsContainer, hitTest, autoScaleCallback) {
17
+ constructor(bus, config, panning, scale, canvasInputListener, bounds, hitTest, autoScaleCallback) {
18
18
  super();
19
19
  this.bus = bus;
20
- this.viewportModel = viewportModel;
21
- this.canvasBoundsContainer = canvasBoundsContainer;
20
+ this.scale = scale;
21
+ this.bounds = bounds;
22
22
  this.autoScaleCallback = autoScaleCallback;
23
23
  this.yAxisDragEndSubject = new Subject();
24
24
  this.lastYStart = 0;
@@ -26,10 +26,10 @@ export class YAxisScaleHandler extends ChartBaseElement {
26
26
  this.lastYHeight = 0;
27
27
  this.lastYPxHeight = 0;
28
28
  this.onYDragStart = () => {
29
- this.lastYStart = this.viewportModel.yStart;
30
- this.lastYEnd = this.viewportModel.yEnd;
31
- this.lastYHeight = this.viewportModel.yEnd - this.viewportModel.yStart;
32
- this.lastYPxHeight = this.canvasBoundsContainer.getBounds(CanvasElement.Y_AXIS).height;
29
+ this.lastYStart = this.scale.yStart;
30
+ this.lastYEnd = this.scale.yEnd;
31
+ this.lastYHeight = this.scale.yEnd - this.scale.yStart;
32
+ this.lastYPxHeight = this.bounds.getBounds(CanvasElement.Y_AXIS).height;
33
33
  };
34
34
  this.onYDragTick = (dragInfo) => {
35
35
  const { delta: absoluteYDelta } = dragInfo;
@@ -48,7 +48,7 @@ export class YAxisScaleHandler extends ChartBaseElement {
48
48
  const newYStart = this.lastYStart - delta;
49
49
  const newYEnd = this.lastYEnd + delta;
50
50
  this.autoScaleCallback(false);
51
- this.viewportModel.setYScale(newYStart, newYEnd);
51
+ this.scale.setYScale(newYStart, newYEnd);
52
52
  this.bus.fireDraw();
53
53
  };
54
54
  this.onYDragEnd = () => {
@@ -61,7 +61,7 @@ export class YAxisScaleHandler extends ChartBaseElement {
61
61
  onDragTick: callIfPredicateTrue(this.onYDragTick, dragPredicate),
62
62
  onDragStart: callIfPredicateTrue(this.onYDragStart, dragPredicate),
63
63
  onDragEnd: callIfPredicateTrue(this.onYDragEnd, dragPredicate),
64
- }, canvasInputListener, chartPanComponent, {
64
+ }, canvasInputListener, panning, {
65
65
  disableChartPanning: false,
66
66
  });
67
67
  this.addChildEntity(dragNDropYComponent);
@@ -151,6 +151,7 @@ export class YAxisComponent extends ChartBaseElement {
151
151
  setAxisType(type) {
152
152
  if (type !== this.state.type) {
153
153
  this.state.type = type;
154
+ this.config.components.yAxis.type = type;
154
155
  this.axisTypeSetSubject.next(type);
155
156
  this.scale.autoScale(true);
156
157
  this.model.fancyLabelsModel.updateLabels(true);
@@ -53,7 +53,7 @@ export const setLineWidth = (ctx, lineWidth, dataSeries, drawerConfig, seriesSel
53
53
  ctx.lineWidth = drawerConfig.forceBold;
54
54
  }
55
55
  else if (dataSeries.hovered) {
56
- ctx.lineWidth = seriesSelectedWidth;
56
+ ctx.lineWidth = lineWidth !== seriesSelectedWidth ? lineWidth + 1 : seriesSelectedWidth;
57
57
  }
58
58
  else {
59
59
  ctx.lineWidth = lineWidth;
@@ -3,7 +3,8 @@
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 { BehaviorSubject, combineLatest, filter } from 'rxjs';
6
+ import { BehaviorSubject, combineLatest } from 'rxjs';
7
+ import { filter } from 'rxjs/operators';
7
8
  import { ChartBaseElement } from '../model/chart-base-element';
8
9
  export class CrossEventProducerComponent extends ChartBaseElement {
9
10
  constructor(canvasInputListener, canvasBoundsContainer) {
@@ -84,7 +84,7 @@ export class HoverProducerComponent extends ChartBaseElement {
84
84
  // special handling for mobile
85
85
  // on long touch - disable panning and show cross tool
86
86
  const hitTest = this.canvasBoundsContainer.getBoundsHitTest(CanvasElement.ALL_PANES);
87
- this.addRxSubscription(this.canvasInputListener.observeLongTouch(hitTest).subscribe(event => {
87
+ this.addRxSubscription(this.canvasInputListener.observeLongTouchStart(hitTest).subscribe(event => {
88
88
  this.paneManager.chartPanComponent.deactivatePanHandlers();
89
89
  this.longTouchActivatedSubject.next(true);
90
90
  const x = event.touches[0].clientX - this.canvasBoundsContainer.canvasOnPageLocation.x;
@@ -44,13 +44,16 @@ export declare class CanvasInputListenerComponent extends ChartBaseElement {
44
44
  private mouseUpDocumentSubject;
45
45
  private wheelSubject;
46
46
  private touchStartSubject;
47
+ private touchStartTimestamp;
47
48
  private touchMoveSubject;
48
49
  private touchEndSubject;
49
50
  private touchCancelSubject;
51
+ private longTouchStartSubject;
52
+ private longTouchEndSubject;
50
53
  private contextMenuSubject;
51
- private longTouchSubject;
52
54
  private pinchSubject;
53
55
  private scrollGestureSubject;
56
+ private fastTouchScroll;
54
57
  mouseLeavesCanvasSubject: Subject<boolean>;
55
58
  dragStartPoint: Point;
56
59
  prevDragPoint: Point;
@@ -300,11 +303,24 @@ export declare class CanvasInputListenerComponent extends ChartBaseElement {
300
303
  */
301
304
  observeContextMenu(hitBoundsTest?: HitBoundsTest): Observable<MouseEvent>;
302
305
  /**
303
- * Returns an Observable that emits a TouchEvent when a long touch is detected on the current element.
306
+ * Returns an Observable that emits a TouchEvent when a long touch start is detected on the current element.
304
307
  * @param {HitBoundsTest} [hitBoundsTest=() => true] - A function that returns a boolean indicating whether the touch event occurred within the bounds of the current element.
305
308
  * @returns {Observable<TouchEvent>} - An Observable that emits a TouchEvent when a long touch is detected on the current element.
306
309
  */
307
- observeLongTouch(hitBoundsTest?: HitBoundsTest): Observable<TouchEvent>;
310
+ observeLongTouchStart(hitBoundsTest?: HitBoundsTest): Observable<TouchEvent>;
311
+ /**
312
+ * Returns an Observable that emits a TouchEvent when a long touch end is detected on the current element.
313
+ * @param {HitBoundsTest} [hitBoundsTest=() => true] - A function that returns a boolean indicating whether the touch event occurred within the bounds of the current element.
314
+ * @returns {Observable<TouchEvent>} - An Observable that emits a TouchEvent when a long touch is detected on the current element.
315
+ */
316
+ observeLongTouchEnd(hitBoundsTest?: HitBoundsTest): Observable<TouchEvent>;
317
+ /**
318
+ * Returns an Observable that emits a Touch whenever a fast scroll is detected.
319
+ * Fast scroll happens whenever chart or any other pane were moved faster than usual
320
+ * The Observable is created from a Subject that is subscribed to by the component's template.
321
+ * @returns {Observable<TouchEvent>} An Observable that emits a TouchEvent whenever a fast scroll is detected.
322
+ */
323
+ observeFastTouchScroll(hitBoundsTest?: HitBoundsTest): Observable<TouchEvent>;
308
324
  /**
309
325
  * Returns the current point of the object.
310
326
  * @returns {Point} The current point of the object.
@@ -8,8 +8,8 @@ import { merge, Subject } from 'rxjs';
8
8
  import { ChartBaseElement } from '../model/chart-base-element';
9
9
  import { distinctUntilChanged, filter, map, tap } from 'rxjs/operators';
10
10
  import { EVENT_RESIZED } from '../events/events';
11
- import { deviceDetector } from '../utils/device/device-detector.utils';
12
11
  import { touchpadDetector } from '../utils/device/touchpad.utils';
12
+ import { deviceDetector } from '../utils/device/device-detector.utils';
13
13
  /**
14
14
  * Gathers user input on canvas element:
15
15
  * Chart update order should be following:
@@ -46,13 +46,16 @@ class CanvasInputListenerComponent extends ChartBaseElement {
46
46
  this.mouseUpDocumentSubject = new Subject();
47
47
  this.wheelSubject = new Subject();
48
48
  this.touchStartSubject = new Subject();
49
+ this.touchStartTimestamp = 0;
49
50
  this.touchMoveSubject = new Subject();
50
51
  this.touchEndSubject = new Subject();
51
52
  this.touchCancelSubject = new Subject();
53
+ this.longTouchStartSubject = new Subject();
54
+ this.longTouchEndSubject = new Subject();
52
55
  this.contextMenuSubject = new Subject();
53
- this.longTouchSubject = new Subject();
54
56
  this.pinchSubject = new Subject();
55
57
  this.scrollGestureSubject = new Subject();
58
+ this.fastTouchScroll = new Subject();
56
59
  this.mouseLeavesCanvasSubject = new Subject();
57
60
  // point at which start dragging
58
61
  this.dragStartPoint = { x: 0, y: 0 };
@@ -184,52 +187,84 @@ class CanvasInputListenerComponent extends ChartBaseElement {
184
187
  const device = deviceDetector();
185
188
  if (device === 'apple' || device === 'mobile') {
186
189
  // workaround to handle double taps for iOS
187
- const doubleTapListenerProducer = () => {
188
- let timeoutId = null;
189
- return () => {
190
- if (timeoutId) {
191
- this.dbTapSubject.next(this.currentPoint);
192
- clearTimeout(timeoutId);
193
- timeoutId = null;
190
+ let dbTapTimeout = null;
191
+ const doubleTapListenerProducer = (e) => {
192
+ e.preventDefault();
193
+ if (dbTapTimeout) {
194
+ this.dbTapSubject.next(this.currentPoint);
195
+ clearTimeout(dbTapTimeout);
196
+ dbTapTimeout = null;
197
+ }
198
+ else {
199
+ dbTapTimeout = window.setTimeout(() => {
200
+ dbTapTimeout = null;
201
+ }, 250);
202
+ }
203
+ };
204
+ this.addSubscription(subscribeListener(this.element, doubleTapListenerProducer, 'touchend'));
205
+ // workaround to handle long touch start/end for iOS
206
+ const longTouchListeners = (e, delay = 700, pixelsForMoveReset = 3) => {
207
+ e.preventDefault();
208
+ const initialCoords = { x: e.touches[0].clientX, y: e.touches[0].clientY };
209
+ let coords = { x: 0, y: 0 };
210
+ let longTouchStart = false;
211
+ let timerLongTouchStart = null;
212
+ // start timeout for long touch
213
+ timerLongTouchStart = setTimeout(() => {
214
+ longTouchStart = true;
215
+ this.longTouchStartSubject.next(e);
216
+ }, delay);
217
+ const touchMoveHandler = (e) => {
218
+ e.preventDefault();
219
+ coords = { x: e.touches[0].clientX, y: e.touches[0].clientY };
220
+ // clear long touch timeout if move area is bigger than pixelsForMove
221
+ if (Math.sqrt(Math.pow(coords.x - initialCoords.x, 2) + Math.pow(coords.y - initialCoords.y, 2)) >
222
+ pixelsForMoveReset ||
223
+ e.touches.length > 1) {
224
+ timerLongTouchStart && clearTimeout(timerLongTouchStart);
194
225
  }
195
- else {
196
- timeoutId = window.setTimeout(() => {
197
- timeoutId = null;
198
- }, 250);
226
+ };
227
+ const touchEndHandler = (e) => {
228
+ e.preventDefault();
229
+ timerLongTouchStart && clearTimeout(timerLongTouchStart);
230
+ if (longTouchStart) {
231
+ longTouchStart = false;
232
+ this.longTouchEndSubject.next(e);
199
233
  }
234
+ this.element.removeEventListener('touchend', touchEndHandler);
235
+ this.element.removeEventListener('touchmove', touchMoveHandler);
200
236
  };
237
+ this.element.addEventListener('touchmove', touchMoveHandler);
238
+ this.element.addEventListener('touchend', touchEndHandler);
201
239
  };
202
- const doubleTapListener = doubleTapListenerProducer();
203
- this.addSubscription(subscribeListener(this.element, doubleTapListener, 'touchend'));
204
- }
205
- const longTouchListener = (e, delay = 500, pixelsForMoveReset = 2) => {
206
- e.preventDefault();
207
- let timer = null;
208
- const initialCoords = { x: e.touches[0].clientX, y: e.touches[0].clientY };
209
- let coords = { x: 0, y: 0 };
210
- const touchMoveHandler = (e) => {
211
- coords = { x: e.touches[0].clientX, y: e.touches[0].clientY };
212
- if (Math.sqrt(Math.pow(coords.x - initialCoords.x, 2) + Math.pow(coords.y - initialCoords.y, 2)) >
213
- pixelsForMoveReset ||
214
- e.touches.length > 1) {
215
- touchEnd();
216
- }
217
- };
218
- const touchEnd = () => {
219
- if (timer) {
220
- clearTimeout(timer);
221
- timer = null;
240
+ this.addSubscription(subscribeListener(this.element, (e) => longTouchListeners(e), 'touchstart'));
241
+ const fastScrollListenerProducer = (e) => {
242
+ e.preventDefault();
243
+ // should work only if dragged to the left
244
+ if (this.prevDragPoint.x > this.dragStartPoint.x) {
245
+ // in percent, perhaps should be changed to just pixels,
246
+ // because landscape and portait orientations would give different % results
247
+ const minDistance = 35;
248
+ // in ms, should be lower to detect as "fast"
249
+ const maxTime = 250;
250
+ const touchStartTs = this.touchStartTimestamp;
251
+ const touchEndTs = Date.now();
252
+ const time = touchEndTs - touchStartTs;
253
+ const distance = ((this.prevDragPoint.x - this.dragStartPoint.x) / this.canvasBounds.width) * 100;
254
+ const isRightDistance = distance > minDistance;
255
+ const isRightTime = time <= maxTime;
256
+ if (isRightDistance && isRightTime) {
257
+ this.fastTouchScroll.next(e);
258
+ }
222
259
  }
223
- this.element.removeEventListener('touchend', touchEnd);
224
- this.element.removeEventListener('touchmove', touchMoveHandler);
225
260
  };
226
- timer = setTimeout(() => this.longTouchSubject.next(e), delay);
227
- this.element.addEventListener('touchmove', touchMoveHandler, { passive: true });
228
- this.element.addEventListener('touchend', touchEnd);
229
- };
230
- this.addSubscription(subscribeListener(this.element, (e) => longTouchListener(e), 'touchstart'));
261
+ this.addSubscription(subscribeListener(this.element, fastScrollListenerProducer, 'touchend'));
262
+ }
231
263
  this.addSubscription(subscribeListener(this.element, leftMouseButtonListener(() => this.dbClickSubject.next(this.currentPoint)), 'dblclick'));
232
- this.addSubscription(subscribeListener(this.element, (e) => this.touchStartSubject.next(e), 'touchstart'));
264
+ this.addSubscription(subscribeListener(this.element, (e) => {
265
+ this.touchStartSubject.next(e);
266
+ this.touchStartTimestamp = Date.now();
267
+ }, 'touchstart'));
233
268
  this.addSubscription(subscribeListener(this.element, (e) => this.touchMoveSubject.next(e), 'touchmove', true));
234
269
  this.addSubscription(subscribeListener(this.element, (e) => this.touchEndSubject.next(e), 'touchend'));
235
270
  this.addSubscription(subscribeListener(this.element, (e) => this.touchCancelSubject.next(e), 'touchcancel'));
@@ -615,12 +650,35 @@ class CanvasInputListenerComponent extends ChartBaseElement {
615
650
  return this.contextMenuSubject.asObservable().pipe(filter(() => hitBoundsTest(this.currentPoint.x, this.currentPoint.y)), tap(p => p.preventDefault()));
616
651
  }
617
652
  /**
618
- * Returns an Observable that emits a TouchEvent when a long touch is detected on the current element.
653
+ * Returns an Observable that emits a TouchEvent when a long touch start is detected on the current element.
619
654
  * @param {HitBoundsTest} [hitBoundsTest=() => true] - A function that returns a boolean indicating whether the touch event occurred within the bounds of the current element.
620
655
  * @returns {Observable<TouchEvent>} - An Observable that emits a TouchEvent when a long touch is detected on the current element.
621
656
  */
622
- observeLongTouch(hitBoundsTest = () => true) {
623
- return this.longTouchSubject.asObservable().pipe(filter(() => hitBoundsTest(this.currentPoint.x, this.currentPoint.y)), tap(p => p.preventDefault()));
657
+ observeLongTouchStart(hitBoundsTest = () => true) {
658
+ return this.longTouchStartSubject
659
+ .asObservable()
660
+ .pipe(filter(() => hitBoundsTest(this.currentPoint.x, this.currentPoint.y)));
661
+ }
662
+ /**
663
+ * Returns an Observable that emits a TouchEvent when a long touch end is detected on the current element.
664
+ * @param {HitBoundsTest} [hitBoundsTest=() => true] - A function that returns a boolean indicating whether the touch event occurred within the bounds of the current element.
665
+ * @returns {Observable<TouchEvent>} - An Observable that emits a TouchEvent when a long touch is detected on the current element.
666
+ */
667
+ observeLongTouchEnd(hitBoundsTest = () => true) {
668
+ return this.longTouchEndSubject
669
+ .asObservable()
670
+ .pipe(filter(() => hitBoundsTest(this.currentPoint.x, this.currentPoint.y)));
671
+ }
672
+ /**
673
+ * Returns an Observable that emits a Touch whenever a fast scroll is detected.
674
+ * Fast scroll happens whenever chart or any other pane were moved faster than usual
675
+ * The Observable is created from a Subject that is subscribed to by the component's template.
676
+ * @returns {Observable<TouchEvent>} An Observable that emits a TouchEvent whenever a fast scroll is detected.
677
+ */
678
+ observeFastTouchScroll(hitBoundsTest = () => true) {
679
+ return this.fastTouchScroll
680
+ .asObservable()
681
+ .pipe(filter(() => hitBoundsTest(this.currentPoint.x, this.currentPoint.y)));
624
682
  }
625
683
  /**
626
684
  * Returns the current point of the object.
@@ -71,13 +71,13 @@ export declare class ScaleModel extends ViewportModel {
71
71
  * @param forceNoAnimation Whether to skip animation.
72
72
  * @param zoomSensitivity The sensitivity of the zoom.
73
73
  */
74
- zoomXToPercent(viewportPercent: ViewportPercent, zoomIn: boolean, forceNoAnimation?: boolean, zoomSensitivity?: number): void;
74
+ zoomXToPercent(viewportPercent: ViewportPercent, zoomIn: boolean, forceNoAnimation: boolean | undefined, zoomSensitivity: number): void;
75
75
  /**
76
76
  * Zooms the X axis of the chart relativly to the end of the data range.
77
77
  * @param zoomIn - If true, the chart will be zoomed in. If false, the chart will be zoomed out.
78
78
  * @param zoomSensitivity - The sensitivity of the zoom. Default value is taken from the configuration object.
79
79
  */
80
- zoomXToEnd(zoomIn: boolean, zoomSensitivity?: number): void;
80
+ zoomXToEnd(zoomIn: boolean, zoomSensitivity: number): void;
81
81
  haltAnimation(): void;
82
82
  private zoomXTo;
83
83
  /**
@@ -89,6 +89,7 @@ export declare class ScaleModel extends ViewportModel {
89
89
  * @param forceNoAutoScale - force NOT apply auto-scaling (for lazy loading)
90
90
  */
91
91
  setXScale(xStart: Unit, xEnd: Unit): void;
92
+ setYScale(yStart: Unit, yEnd: Unit, fire?: boolean): void;
92
93
  /**
93
94
  * Moves both xStart and xEnd without changing the viewport width (zoom).
94
95
  * Works without animation.