@tradingaction/core 2.0.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 (99) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +5 -0
  3. package/lib/CanvasContainer.d.ts +19 -0
  4. package/lib/CanvasContainer.js +28 -0
  5. package/lib/CanvasContainer.js.map +1 -0
  6. package/lib/Chart.d.ts +32 -0
  7. package/lib/Chart.js +57 -0
  8. package/lib/Chart.js.map +1 -0
  9. package/lib/ChartCanvas.d.ts +235 -0
  10. package/lib/ChartCanvas.js +810 -0
  11. package/lib/ChartCanvas.js.map +1 -0
  12. package/lib/EventCapture.d.ts +131 -0
  13. package/lib/EventCapture.js +489 -0
  14. package/lib/EventCapture.js.map +1 -0
  15. package/lib/GenericChartComponent.d.ts +21 -0
  16. package/lib/GenericChartComponent.js +75 -0
  17. package/lib/GenericChartComponent.js.map +1 -0
  18. package/lib/GenericComponent.d.ts +81 -0
  19. package/lib/GenericComponent.js +355 -0
  20. package/lib/GenericComponent.js.map +1 -0
  21. package/lib/MoreProps.d.ts +16 -0
  22. package/lib/MoreProps.js +2 -0
  23. package/lib/MoreProps.js.map +1 -0
  24. package/lib/index.d.ts +7 -0
  25. package/lib/index.js +8 -0
  26. package/lib/index.js.map +1 -0
  27. package/lib/useEvent.d.ts +1 -0
  28. package/lib/useEvent.js +13 -0
  29. package/lib/useEvent.js.map +1 -0
  30. package/lib/utils/ChartDataUtil.d.ts +49 -0
  31. package/lib/utils/ChartDataUtil.js +205 -0
  32. package/lib/utils/ChartDataUtil.js.map +1 -0
  33. package/lib/utils/PureComponent.d.ts +4 -0
  34. package/lib/utils/PureComponent.js +10 -0
  35. package/lib/utils/PureComponent.js.map +1 -0
  36. package/lib/utils/accumulatingWindow.d.ts +15 -0
  37. package/lib/utils/accumulatingWindow.js +98 -0
  38. package/lib/utils/accumulatingWindow.js.map +1 -0
  39. package/lib/utils/barWidth.d.ts +15 -0
  40. package/lib/utils/barWidth.js +27 -0
  41. package/lib/utils/barWidth.js.map +1 -0
  42. package/lib/utils/closestItem.d.ts +5 -0
  43. package/lib/utils/closestItem.js +45 -0
  44. package/lib/utils/closestItem.js.map +1 -0
  45. package/lib/utils/evaluator.d.ts +7 -0
  46. package/lib/utils/evaluator.js +94 -0
  47. package/lib/utils/evaluator.js.map +1 -0
  48. package/lib/utils/identity.d.ts +1 -0
  49. package/lib/utils/identity.js +2 -0
  50. package/lib/utils/identity.js.map +1 -0
  51. package/lib/utils/index.d.ts +46 -0
  52. package/lib/utils/index.js +126 -0
  53. package/lib/utils/index.js.map +1 -0
  54. package/lib/utils/noop.d.ts +1 -0
  55. package/lib/utils/noop.js +3 -0
  56. package/lib/utils/noop.js.map +1 -0
  57. package/lib/utils/shallowEqual.d.ts +1 -0
  58. package/lib/utils/shallowEqual.js +22 -0
  59. package/lib/utils/shallowEqual.js.map +1 -0
  60. package/lib/utils/slidingWindow.d.ts +19 -0
  61. package/lib/utils/slidingWindow.js +109 -0
  62. package/lib/utils/slidingWindow.js.map +1 -0
  63. package/lib/utils/strokeDasharray.d.ts +3 -0
  64. package/lib/utils/strokeDasharray.js +37 -0
  65. package/lib/utils/strokeDasharray.js.map +1 -0
  66. package/lib/utils/zipper.d.ts +7 -0
  67. package/lib/utils/zipper.js +36 -0
  68. package/lib/utils/zipper.js.map +1 -0
  69. package/lib/zoom/index.d.ts +1 -0
  70. package/lib/zoom/index.js +2 -0
  71. package/lib/zoom/index.js.map +1 -0
  72. package/lib/zoom/zoomBehavior.d.ts +10 -0
  73. package/lib/zoom/zoomBehavior.js +18 -0
  74. package/lib/zoom/zoomBehavior.js.map +1 -0
  75. package/package.json +52 -0
  76. package/src/CanvasContainer.tsx +44 -0
  77. package/src/Chart.tsx +114 -0
  78. package/src/ChartCanvas.tsx +1336 -0
  79. package/src/EventCapture.tsx +709 -0
  80. package/src/GenericChartComponent.tsx +98 -0
  81. package/src/GenericComponent.tsx +454 -0
  82. package/src/MoreProps.ts +17 -0
  83. package/src/index.ts +7 -0
  84. package/src/useEvent.ts +14 -0
  85. package/src/utils/ChartDataUtil.ts +297 -0
  86. package/src/utils/PureComponent.tsx +12 -0
  87. package/src/utils/accumulatingWindow.ts +118 -0
  88. package/src/utils/barWidth.ts +44 -0
  89. package/src/utils/closestItem.ts +60 -0
  90. package/src/utils/evaluator.ts +163 -0
  91. package/src/utils/identity.ts +1 -0
  92. package/src/utils/index.ts +153 -0
  93. package/src/utils/noop.ts +2 -0
  94. package/src/utils/shallowEqual.ts +25 -0
  95. package/src/utils/slidingWindow.ts +140 -0
  96. package/src/utils/strokeDasharray.ts +52 -0
  97. package/src/utils/zipper.ts +45 -0
  98. package/src/zoom/index.ts +1 -0
  99. package/src/zoom/zoomBehavior.ts +34 -0
@@ -0,0 +1,709 @@
1
+ import { ScaleContinuousNumeric, ScaleTime } from "d3-scale";
2
+ import { pointer, pointers, select } from "d3-selection";
3
+ import * as React from "react";
4
+
5
+ import {
6
+ d3Window,
7
+ getTouchProps,
8
+ MOUSEENTER,
9
+ MOUSELEAVE,
10
+ MOUSEMOVE,
11
+ mousePosition,
12
+ MOUSEUP,
13
+ TOUCHEND,
14
+ TOUCHMOVE,
15
+ touchPosition,
16
+ } from "./utils";
17
+ import { ChartConfig, getCurrentCharts } from "./utils/ChartDataUtil";
18
+
19
+ interface EventCaptureProps {
20
+ readonly mouseMove: boolean;
21
+ readonly zoom: boolean;
22
+ readonly pan: boolean;
23
+ readonly panSpeedMultiplier: number;
24
+ readonly focus: boolean;
25
+ readonly useCrossHairStyleCursor?: boolean;
26
+ readonly width: number;
27
+ readonly height: number;
28
+ readonly chartConfig: ChartConfig[];
29
+ readonly xAccessor: any; // func
30
+ readonly xScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;
31
+ readonly disableInteraction: boolean;
32
+ readonly getAllPanConditions: () => { panEnabled: boolean; draggable: boolean }[];
33
+ readonly onClick?: (mouseXY: number[], event: React.MouseEvent) => void;
34
+ readonly onContextMenu?: (mouseXY: number[], event: React.MouseEvent) => void;
35
+ readonly onDoubleClick?: (mouseXY: number[], event: React.MouseEvent) => void;
36
+ readonly onDragStart?: (details: { startPos: number[] }, event: React.MouseEvent) => void;
37
+ readonly onDrag?: (
38
+ details: { startPos: [number, number]; mouseXY: [number, number] },
39
+ event: React.MouseEvent,
40
+ ) => void;
41
+ readonly onDragComplete?: (details: { mouseXY: number[] }, event: React.MouseEvent) => void;
42
+ readonly onMouseDown?: (mouseXY: [number, number], currentCharts: string[], event: React.MouseEvent) => void;
43
+ readonly onMouseMove?: (
44
+ touchXY: [number, number],
45
+ eventType: string,
46
+ event: React.MouseEvent | React.TouchEvent,
47
+ ) => void;
48
+ readonly onMouseEnter?: (event: React.MouseEvent) => void;
49
+ readonly onMouseLeave?: (event: React.MouseEvent) => void;
50
+ readonly onPinchZoom?: (
51
+ initialPinch: {
52
+ readonly xScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;
53
+ readonly touch1Pos: [number, number];
54
+ readonly touch2Pos: [number, number];
55
+ readonly range: number[];
56
+ },
57
+ currentPinch: {
58
+ readonly xScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;
59
+ readonly touch1Pos: [number, number];
60
+ readonly touch2Pos: [number, number];
61
+ },
62
+ e: React.TouchEvent,
63
+ ) => void;
64
+ readonly onPinchZoomEnd?: (
65
+ initialPinch: {
66
+ readonly xScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;
67
+ readonly touch1Pos: [number, number];
68
+ readonly touch2Pos: [number, number];
69
+ readonly range: number[];
70
+ },
71
+ e: React.TouchEvent,
72
+ ) => void;
73
+ readonly onPan?: (
74
+ mouseXY: [number, number],
75
+ panStartXScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>,
76
+ panOrigin: { dx: number; dy: number },
77
+ chartsToPan: string[],
78
+ e: React.MouseEvent,
79
+ ) => void;
80
+ readonly onPanEnd?: (
81
+ mouseXY: [number, number],
82
+ panStartXScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>,
83
+ panOrigin: { dx: number; dy: number },
84
+ chartsToPan: string[],
85
+ e: React.MouseEvent | React.TouchEvent,
86
+ ) => void;
87
+ readonly onZoom?: (zoomDir: 1 | -1, mouseXY: number[], event: React.WheelEvent) => void;
88
+ }
89
+
90
+ interface EventCaptureState {
91
+ cursorOverrideClass?: string;
92
+ dragInProgress?: boolean;
93
+ dragStartPosition?: [number, number];
94
+ panInProgress: boolean;
95
+ panStart?: {
96
+ panStartXScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;
97
+ panOrigin: number[];
98
+ chartsToPan: string[];
99
+ };
100
+ pinchZoomStart?: {
101
+ xScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;
102
+ touch1Pos: [number, number];
103
+ touch2Pos: [number, number];
104
+ range: number[];
105
+ chartsToPan: string[];
106
+ };
107
+ }
108
+
109
+ export class EventCapture extends React.Component<EventCaptureProps, EventCaptureState> {
110
+ public static defaultProps = {
111
+ mouseMove: false,
112
+ zoom: false,
113
+ pan: false,
114
+ panSpeedMultiplier: 1,
115
+ focus: false,
116
+ disableInteraction: false,
117
+ };
118
+
119
+ private clicked?: boolean;
120
+ private dx?: number = 0;
121
+ private dy?: number = 0;
122
+ private dragHappened?: boolean;
123
+ private focus?: boolean;
124
+ private lastNewPos?: [number, number];
125
+ private mouseInside = false;
126
+ private mouseInteraction = true;
127
+ private panEndTimeout?: number;
128
+ private panHappened?: boolean;
129
+ private readonly ref = React.createRef<SVGRectElement>();
130
+
131
+ public constructor(props: EventCaptureProps) {
132
+ super(props);
133
+
134
+ this.focus = props.focus;
135
+ this.state = {
136
+ panInProgress: false,
137
+ };
138
+ }
139
+
140
+ public componentDidMount() {
141
+ const { disableInteraction } = this.props;
142
+
143
+ const { current } = this.ref;
144
+ if (current === null) {
145
+ return;
146
+ }
147
+
148
+ if (!disableInteraction) {
149
+ // @ts-ignore
150
+ select(current).on(MOUSEENTER, this.handleEnter).on(MOUSELEAVE, this.handleLeave);
151
+
152
+ // @ts-ignore
153
+ current.addEventListener("wheel", this.handleWheel, { passive: false });
154
+ }
155
+ }
156
+
157
+ public componentDidUpdate() {
158
+ this.componentDidMount();
159
+ }
160
+
161
+ public componentWillUnmount() {
162
+ const { disableInteraction } = this.props;
163
+
164
+ const { current } = this.ref;
165
+ if (current === null) {
166
+ return;
167
+ }
168
+
169
+ if (!disableInteraction) {
170
+ select(current).on(MOUSEENTER, null).on(MOUSELEAVE, null);
171
+ const win = d3Window(current);
172
+ select(win).on(MOUSEMOVE, null);
173
+
174
+ // @ts-ignore
175
+ current.removeEventListener("wheel", this.handleWheel, { passive: false });
176
+ }
177
+ }
178
+
179
+ public readonly handleEnter = (e: any) => {
180
+ const { onMouseEnter } = this.props;
181
+ if (onMouseEnter === undefined) {
182
+ return;
183
+ }
184
+
185
+ this.mouseInside = true;
186
+ if (!this.state.panInProgress && !this.state.dragInProgress) {
187
+ const win = d3Window(this.ref.current);
188
+ select(win).on(MOUSEMOVE, this.handleMouseMove);
189
+ }
190
+ onMouseEnter(e);
191
+ };
192
+
193
+ public handleLeave = (e: React.MouseEvent) => {
194
+ const { onMouseLeave } = this.props;
195
+ if (onMouseLeave === undefined) {
196
+ return;
197
+ }
198
+
199
+ this.mouseInside = false;
200
+ if (!this.state.panInProgress && !this.state.dragInProgress) {
201
+ const win = d3Window(this.ref.current);
202
+ select(win).on(MOUSEMOVE, null);
203
+ }
204
+ onMouseLeave(e);
205
+ };
206
+
207
+ public handleWheel = (e: React.WheelEvent) => {
208
+ const { pan, onPan, zoom, onZoom } = this.props;
209
+
210
+ if (!pan && !zoom) {
211
+ return;
212
+ }
213
+
214
+ const { panInProgress } = this.state;
215
+
216
+ const yZoom = Math.abs(e.deltaY) > Math.abs(e.deltaX) && Math.abs(e.deltaY) > 0;
217
+ const mouseXY = mousePosition(e);
218
+ e.preventDefault();
219
+
220
+ if (zoom && this.focus && yZoom && !panInProgress) {
221
+ const zoomDir = e.deltaY > 0 ? 1 : -1;
222
+
223
+ if (onZoom !== undefined) {
224
+ onZoom(zoomDir, mouseXY, e);
225
+ }
226
+ } else if (this.focus) {
227
+ if (this.shouldPan() && this.state.panStart !== undefined) {
228
+ // pan already in progress
229
+ const { panStartXScale, chartsToPan } = this.state.panStart;
230
+ this.lastNewPos = mouseXY;
231
+ this.panHappened = true;
232
+
233
+ if (this.dx === undefined) {
234
+ this.dx = 0;
235
+ }
236
+ if (this.dy === undefined) {
237
+ this.dy = 0;
238
+ }
239
+
240
+ this.dx -= e.deltaX;
241
+ this.dy += e.deltaY;
242
+ const dxdy = { dx: this.dx, dy: this.dy };
243
+
244
+ if (onPan !== undefined) {
245
+ onPan(mouseXY, panStartXScale, dxdy, chartsToPan, e);
246
+ }
247
+ } else {
248
+ const { xScale, chartConfig } = this.props;
249
+ const currentCharts = getCurrentCharts(chartConfig, mouseXY);
250
+
251
+ this.dx = 0;
252
+ this.dy = 0;
253
+ this.setState({
254
+ panInProgress: true,
255
+ panStart: {
256
+ panStartXScale: xScale,
257
+ panOrigin: mouseXY,
258
+ chartsToPan: currentCharts,
259
+ },
260
+ });
261
+ }
262
+ this.queuePanEnd(e);
263
+ }
264
+ };
265
+
266
+ public queuePanEnd(e: any) {
267
+ if (this.panEndTimeout !== undefined) {
268
+ window.clearTimeout(this.panEndTimeout);
269
+ }
270
+ this.panEndTimeout = window.setTimeout(() => {
271
+ this.handlePanEnd(e);
272
+ }, 100);
273
+ }
274
+
275
+ public handleMouseMove = (e: any) => {
276
+ const { onMouseMove, mouseMove } = this.props;
277
+ if (onMouseMove === undefined) {
278
+ return;
279
+ }
280
+
281
+ if (this.mouseInteraction && mouseMove && !this.state.panInProgress) {
282
+ const newPos = pointer(e, this.ref.current);
283
+
284
+ onMouseMove(newPos, "mouse", e);
285
+ }
286
+ };
287
+
288
+ public handleClick = (e: React.MouseEvent) => {
289
+ const mouseXY = mousePosition(e);
290
+ const { onClick, onDoubleClick } = this.props;
291
+
292
+ if (!this.panHappened && !this.dragHappened) {
293
+ if (this.clicked && onDoubleClick !== undefined) {
294
+ onDoubleClick(mouseXY, e);
295
+ this.clicked = false;
296
+ } else if (onClick !== undefined) {
297
+ onClick(mouseXY, e);
298
+ this.clicked = true;
299
+ setTimeout(() => {
300
+ if (this.clicked) {
301
+ this.clicked = false;
302
+ }
303
+ }, 400);
304
+ }
305
+ }
306
+ };
307
+
308
+ public handleRightClick = (e: React.MouseEvent) => {
309
+ e.stopPropagation();
310
+ e.preventDefault();
311
+ const { onContextMenu, onPanEnd } = this.props;
312
+
313
+ const mouseXY = mousePosition(e, this.ref.current!.getBoundingClientRect());
314
+
315
+ if (this.state.panStart !== undefined) {
316
+ const {
317
+ panStartXScale,
318
+ panOrigin: [dx, dy],
319
+ chartsToPan,
320
+ } = this.state.panStart;
321
+
322
+ if (this.panHappened && onPanEnd !== undefined) {
323
+ onPanEnd(mouseXY, panStartXScale, { dx, dy }, chartsToPan, e);
324
+ }
325
+ const win = d3Window(this.ref.current);
326
+ select(win).on(MOUSEMOVE, null).on(MOUSEUP, null);
327
+
328
+ this.setState({
329
+ panInProgress: false,
330
+ panStart: undefined,
331
+ });
332
+ }
333
+
334
+ if (onContextMenu !== undefined) {
335
+ onContextMenu(mouseXY, e);
336
+ }
337
+ };
338
+
339
+ public handleDrag = (e: any) => {
340
+ const { onDrag } = this.props;
341
+ if (onDrag === undefined) {
342
+ return;
343
+ }
344
+
345
+ this.dragHappened = true;
346
+
347
+ const { dragStartPosition } = this.state;
348
+ if (dragStartPosition === undefined) {
349
+ return;
350
+ }
351
+
352
+ const mouseXY = pointer(e, this.ref.current);
353
+
354
+ onDrag(
355
+ {
356
+ startPos: dragStartPosition,
357
+ mouseXY,
358
+ },
359
+ e,
360
+ );
361
+ };
362
+
363
+ public cancelDrag() {
364
+ const win = d3Window(this.ref.current);
365
+ select(win)
366
+ // @ts-ignore
367
+ .on(MOUSEMOVE, this.mouseInside ? this.handleMouseMove : null)
368
+ .on(MOUSEUP, null);
369
+
370
+ this.setState({
371
+ dragInProgress: false,
372
+ });
373
+ this.mouseInteraction = true;
374
+ }
375
+
376
+ public handleDragEnd = (e: any) => {
377
+ const mouseXY = pointer(e, this.ref.current);
378
+
379
+ const win = d3Window(this.ref.current);
380
+ select(win)
381
+ // @ts-ignore
382
+ .on(MOUSEMOVE, this.mouseInside ? this.handleMouseMove : null)
383
+ .on(MOUSEUP, null);
384
+
385
+ if (this.dragHappened) {
386
+ const { onDragComplete } = this.props;
387
+ if (onDragComplete !== undefined) {
388
+ onDragComplete({ mouseXY }, e);
389
+ }
390
+ }
391
+
392
+ this.setState({
393
+ dragInProgress: false,
394
+ });
395
+ this.mouseInteraction = true;
396
+ };
397
+
398
+ public canPan = () => {
399
+ const { getAllPanConditions } = this.props;
400
+ const { pan: initialPanEnabled } = this.props;
401
+
402
+ const { panEnabled, draggable: somethingSelected } = getAllPanConditions().reduce(
403
+ (returnObj, a) => {
404
+ return {
405
+ draggable: returnObj.draggable || a.draggable,
406
+ panEnabled: returnObj.panEnabled && a.panEnabled,
407
+ };
408
+ },
409
+ {
410
+ draggable: false,
411
+ panEnabled: initialPanEnabled,
412
+ },
413
+ );
414
+
415
+ return {
416
+ panEnabled,
417
+ somethingSelected,
418
+ };
419
+ };
420
+
421
+ public handleMouseDown = (e: React.MouseEvent) => {
422
+ if (e.button !== 0) {
423
+ return;
424
+ }
425
+ const { xScale, chartConfig, onMouseDown } = this.props;
426
+
427
+ this.panHappened = false;
428
+ this.dragHappened = false;
429
+ this.focus = true;
430
+
431
+ if (!this.state.panInProgress && this.mouseInteraction) {
432
+ const mouseXY = mousePosition(e);
433
+ const currentCharts = getCurrentCharts(chartConfig, mouseXY);
434
+ const { panEnabled, somethingSelected } = this.canPan();
435
+ const pan = panEnabled && !somethingSelected;
436
+
437
+ if (pan) {
438
+ this.setState({
439
+ panInProgress: pan,
440
+ panStart: {
441
+ panStartXScale: xScale,
442
+ panOrigin: mouseXY,
443
+ chartsToPan: currentCharts,
444
+ },
445
+ });
446
+
447
+ const win = d3Window(this.ref.current);
448
+ select(win).on(MOUSEMOVE, this.handlePan).on(MOUSEUP, this.handlePanEnd);
449
+ } else if (somethingSelected) {
450
+ this.setState({
451
+ panInProgress: false,
452
+ dragInProgress: true,
453
+ panStart: undefined,
454
+ dragStartPosition: mouseXY,
455
+ });
456
+
457
+ const { onDragStart } = this.props;
458
+
459
+ if (onDragStart !== undefined) {
460
+ onDragStart({ startPos: mouseXY }, e);
461
+ }
462
+
463
+ const win = d3Window(this.ref.current);
464
+ select(win).on(MOUSEMOVE, this.handleDrag).on(MOUSEUP, this.handleDragEnd);
465
+ }
466
+
467
+ if (onMouseDown !== undefined) {
468
+ onMouseDown(mouseXY, currentCharts, e);
469
+ }
470
+ }
471
+ e.preventDefault();
472
+ };
473
+
474
+ public shouldPan = () => {
475
+ const { pan: panEnabled, onPan } = this.props;
476
+ return panEnabled && onPan && this.state.panStart !== undefined;
477
+ };
478
+
479
+ public handlePan = (e: any) => {
480
+ if (this.shouldPan() && this.state.panStart !== undefined) {
481
+ this.panHappened = true;
482
+
483
+ const { panStartXScale, panOrigin, chartsToPan } = this.state.panStart;
484
+
485
+ let dx;
486
+ let dy;
487
+ let mouseXY;
488
+ if (this.mouseInteraction) {
489
+ mouseXY = pointer(e, this.ref.current);
490
+ this.lastNewPos = mouseXY;
491
+ dx = mouseXY[0] - panOrigin[0];
492
+ dy = mouseXY[1] - panOrigin[1];
493
+ } else {
494
+ mouseXY = pointers(e, this.ref.current)[0];
495
+ this.lastNewPos = mouseXY;
496
+ dx = panOrigin[0] - mouseXY[0];
497
+ dy = panOrigin[1] - mouseXY[1];
498
+ }
499
+
500
+ this.dx = dx;
501
+ this.dy = dy;
502
+
503
+ const { onPan } = this.props;
504
+ if (onPan !== undefined) {
505
+ onPan(mouseXY, panStartXScale, { dx, dy }, chartsToPan, e);
506
+ }
507
+ }
508
+ };
509
+
510
+ public handlePanEnd = (e: any) => {
511
+ const { pan: panEnabled, onPanEnd } = this.props;
512
+
513
+ if (this.state.panStart !== undefined) {
514
+ const { panStartXScale, chartsToPan } = this.state.panStart;
515
+
516
+ const win = d3Window(this.ref.current);
517
+ select(win)
518
+ // @ts-ignore
519
+ .on(MOUSEMOVE, this.mouseInside ? this.handleMouseMove : null)
520
+ .on(MOUSEUP, null)
521
+ .on(TOUCHMOVE, null)
522
+ .on(TOUCHEND, null);
523
+
524
+ if (this.panHappened && panEnabled && onPanEnd) {
525
+ const { dx = 0, dy = 0 } = this;
526
+
527
+ delete this.dx;
528
+ delete this.dy;
529
+ if (this.lastNewPos !== undefined) {
530
+ onPanEnd(this.lastNewPos, panStartXScale, { dx, dy }, chartsToPan, e);
531
+ }
532
+ }
533
+
534
+ this.setState({
535
+ panInProgress: false,
536
+ panStart: undefined,
537
+ });
538
+ }
539
+ };
540
+
541
+ public handleTouchMove = (e: React.TouchEvent) => {
542
+ const { onMouseMove } = this.props;
543
+ if (onMouseMove === undefined) {
544
+ return;
545
+ }
546
+
547
+ const touch = getTouchProps(e.touches[0]);
548
+ const touchXY = touchPosition(touch, e);
549
+ onMouseMove(touchXY, "touch", e);
550
+ };
551
+
552
+ public handleTouchStart = (e: React.TouchEvent) => {
553
+ this.mouseInteraction = false;
554
+
555
+ const { pan: panEnabled, chartConfig, onMouseMove, xScale, onPanEnd } = this.props;
556
+
557
+ if (e.touches.length === 1) {
558
+ this.panHappened = false;
559
+ const touchXY = touchPosition(getTouchProps(e.touches[0]), e);
560
+ if (onMouseMove !== undefined) {
561
+ onMouseMove(touchXY, "touch", e);
562
+ }
563
+
564
+ if (panEnabled) {
565
+ const currentCharts = getCurrentCharts(chartConfig, touchXY);
566
+
567
+ this.setState({
568
+ panInProgress: true,
569
+ panStart: {
570
+ panStartXScale: xScale,
571
+ panOrigin: touchXY,
572
+ chartsToPan: currentCharts,
573
+ },
574
+ });
575
+
576
+ const win = d3Window(this.ref.current);
577
+ select(win).on(TOUCHMOVE, this.handlePan, false).on(TOUCHEND, this.handlePanEnd, false);
578
+ }
579
+ } else if (e.touches.length === 2) {
580
+ // pinch zoom begin
581
+ // do nothing pinch zoom is handled in handleTouchMove
582
+ const { panInProgress, panStart } = this.state;
583
+
584
+ if (panInProgress && panEnabled && onPanEnd && panStart !== undefined) {
585
+ const {
586
+ panStartXScale,
587
+ panOrigin: [dx, dy],
588
+ chartsToPan,
589
+ } = panStart;
590
+
591
+ const win = d3Window(this.ref.current);
592
+ select(win)
593
+ // @ts-ignore
594
+ .on(MOUSEMOVE, this.mouseInside ? this.handleMouseMove : null)
595
+ .on(MOUSEUP, null)
596
+ .on(TOUCHMOVE, this.handlePinchZoom, false)
597
+ .on(TOUCHEND, this.handlePinchZoomEnd, false);
598
+
599
+ const touch1Pos = touchPosition(getTouchProps(e.touches[0]), e);
600
+ const touch2Pos = touchPosition(getTouchProps(e.touches[1]), e);
601
+
602
+ if (this.panHappened && panEnabled && onPanEnd && this.lastNewPos !== undefined) {
603
+ onPanEnd(this.lastNewPos, panStartXScale, { dx, dy }, chartsToPan, e);
604
+ }
605
+
606
+ this.setState({
607
+ panInProgress: false,
608
+ pinchZoomStart: {
609
+ xScale,
610
+ touch1Pos,
611
+ touch2Pos,
612
+ range: xScale.range(),
613
+ chartsToPan,
614
+ },
615
+ });
616
+ }
617
+ }
618
+ };
619
+
620
+ public handlePinchZoom = (e: any) => {
621
+ const { pinchZoomStart } = this.state;
622
+ if (pinchZoomStart === undefined) {
623
+ return;
624
+ }
625
+
626
+ const { xScale, zoom: zoomEnabled, onPinchZoom } = this.props;
627
+ if (!zoomEnabled || onPinchZoom === undefined) {
628
+ return;
629
+ }
630
+
631
+ const [touch1Pos, touch2Pos] = pointers(this.ref.current!);
632
+
633
+ const { chartsToPan, ...initialPinch } = pinchZoomStart;
634
+
635
+ onPinchZoom(
636
+ initialPinch,
637
+ {
638
+ touch1Pos,
639
+ touch2Pos,
640
+ xScale,
641
+ },
642
+ e,
643
+ );
644
+ };
645
+
646
+ public handlePinchZoomEnd = (e: any) => {
647
+ const win = d3Window(this.ref.current);
648
+ select(win).on(TOUCHMOVE, null).on(TOUCHEND, null);
649
+
650
+ const { pinchZoomStart } = this.state;
651
+ if (pinchZoomStart === undefined) {
652
+ return;
653
+ }
654
+
655
+ const { chartsToPan, ...initialPinch } = pinchZoomStart;
656
+
657
+ const { zoom: zoomEnabled, onPinchZoomEnd } = this.props;
658
+ if (zoomEnabled && onPinchZoomEnd) {
659
+ onPinchZoomEnd(initialPinch, e);
660
+ }
661
+
662
+ this.setState({
663
+ pinchZoomStart: undefined,
664
+ });
665
+ };
666
+
667
+ public setCursorClass = (cursorOverrideClass: string | undefined | null) => {
668
+ if (cursorOverrideClass !== this.state.cursorOverrideClass) {
669
+ this.setState({
670
+ cursorOverrideClass: cursorOverrideClass === null ? undefined : cursorOverrideClass,
671
+ });
672
+ }
673
+ };
674
+
675
+ public render() {
676
+ const { height, width, disableInteraction, useCrossHairStyleCursor } = this.props;
677
+
678
+ const className = disableInteraction
679
+ ? undefined
680
+ : this.state.cursorOverrideClass !== undefined
681
+ ? this.state.cursorOverrideClass
682
+ : !useCrossHairStyleCursor
683
+ ? undefined
684
+ : this.state.panInProgress
685
+ ? "react-financial-charts-grabbing-cursor"
686
+ : "react-financial-charts-crosshair-cursor";
687
+
688
+ const interactionProps = disableInteraction
689
+ ? undefined
690
+ : {
691
+ onMouseDown: this.handleMouseDown,
692
+ onClick: this.handleClick,
693
+ onContextMenu: this.handleRightClick,
694
+ onTouchStart: this.handleTouchStart,
695
+ onTouchMove: this.handleTouchMove,
696
+ };
697
+
698
+ return (
699
+ <rect
700
+ ref={this.ref}
701
+ className={className}
702
+ width={width}
703
+ height={height}
704
+ style={{ opacity: 0 }}
705
+ {...interactionProps}
706
+ />
707
+ );
708
+ }
709
+ }