@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,1336 @@
1
+ import { extent as d3Extent, max, min } from "d3-array";
2
+ import { ScaleContinuousNumeric, ScaleTime } from "d3-scale";
3
+ import * as React from "react";
4
+ import { clearCanvas, functor, head, identity, isDefined, isNotDefined, last, shallowEqual } from "./utils";
5
+ import { IZoomAnchorOptions, mouseBasedZoomAnchor } from "./zoom";
6
+ import {
7
+ ChartConfig,
8
+ getChartConfigWithUpdatedYScales,
9
+ getCurrentCharts,
10
+ getCurrentItem,
11
+ getNewChartConfig,
12
+ } from "./utils/ChartDataUtil";
13
+ import { EventCapture } from "./EventCapture";
14
+ import { CanvasContainer, ICanvasContexts } from "./CanvasContainer";
15
+ import evaluator from "./utils/evaluator";
16
+ import type { MoreProps } from "./MoreProps";
17
+
18
+ const CANDIDATES_FOR_RESET = ["seriesName"];
19
+
20
+ const shouldResetChart = (thisProps: any, nextProps: any) => {
21
+ return !CANDIDATES_FOR_RESET.every((key) => {
22
+ const result = shallowEqual(thisProps[key], nextProps[key]);
23
+ return result;
24
+ });
25
+ };
26
+
27
+ const getCursorStyle = () => {
28
+ const tooltipStyle = `
29
+ .react-financial-charts-grabbing-cursor {
30
+ pointer-events: all;
31
+ cursor: -moz-grabbing;
32
+ cursor: -webkit-grabbing;
33
+ cursor: grabbing;
34
+ }
35
+ .react-financial-charts-crosshair-cursor {
36
+ pointer-events: all;
37
+ cursor: crosshair;
38
+ }
39
+ .react-financial-charts-tooltip-hover {
40
+ pointer-events: all;
41
+ cursor: pointer;
42
+ }
43
+ .react-financial-charts-avoid-interaction {
44
+ pointer-events: none;
45
+ }
46
+ .react-financial-charts-enable-interaction {
47
+ pointer-events: all;
48
+ }
49
+ .react-financial-charts-tooltip {
50
+ pointer-events: all;
51
+ cursor: pointer;
52
+ }
53
+ .react-financial-charts-default-cursor {
54
+ cursor: default;
55
+ }
56
+ .react-financial-charts-move-cursor {
57
+ cursor: move;
58
+ }
59
+ .react-financial-charts-pointer-cursor {
60
+ cursor: pointer;
61
+ }
62
+ .react-financial-charts-ns-resize-cursor {
63
+ cursor: ns-resize;
64
+ }
65
+ .react-financial-charts-ew-resize-cursor {
66
+ cursor: ew-resize;
67
+ }`;
68
+ return <style type="text/css">{tooltipStyle}</style>;
69
+ };
70
+
71
+ export interface ChartCanvasContextType<TXAxis extends number | Date> {
72
+ width: number;
73
+ height: number;
74
+ margin: { top: number; right: number; bottom: number; left: number };
75
+ chartId: number | string;
76
+ getCanvasContexts?: () => ICanvasContexts | undefined;
77
+ xScale: Function;
78
+ ratio: number;
79
+ // Not sure if it should be optional
80
+ xAccessor: (data: any) => TXAxis;
81
+ displayXAccessor: (data: any) => TXAxis;
82
+ xAxisZoom?: (newDomain: any) => void;
83
+ yAxisZoom?: (chartId: string, newDomain: any) => void;
84
+ redraw: () => void;
85
+ plotData: any[];
86
+ fullData: any[];
87
+ chartConfigs: ChartConfig[];
88
+ morePropsDecorator?: () => void;
89
+ generateSubscriptionId?: () => number;
90
+ getMutableState: () => {};
91
+ amIOnTop: (id: string | number) => boolean;
92
+ subscribe: (id: string | number, rest: any) => void;
93
+ unsubscribe: (id: string | number) => void;
94
+ setCursorClass: (className: string | null | undefined) => void;
95
+ }
96
+
97
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
98
+ const noop = () => {};
99
+ export const chartCanvasContextDefaultValue: ChartCanvasContextType<number | Date> = {
100
+ amIOnTop: () => false,
101
+ chartConfigs: [],
102
+ chartId: 0,
103
+ ratio: 0,
104
+ displayXAccessor: () => 0,
105
+ fullData: [],
106
+ getMutableState: () => ({}),
107
+ height: 0,
108
+ margin: { top: 0, right: 0, bottom: 0, left: 0 },
109
+ plotData: [],
110
+ setCursorClass: noop,
111
+ subscribe: noop,
112
+ unsubscribe: noop,
113
+ redraw: noop,
114
+ width: 0,
115
+ xAccessor: () => 0,
116
+ xScale: noop,
117
+ };
118
+ export const ChartCanvasContext =
119
+ React.createContext<ChartCanvasContextType<number | Date>>(chartCanvasContextDefaultValue);
120
+
121
+ const getDimensions = <TXAxis extends number | Date>(props: ChartCanvasProps<TXAxis>) => {
122
+ const { margin, height, width } = props;
123
+ return {
124
+ height: height - margin.top - margin.bottom,
125
+ width: width - margin.left - margin.right,
126
+ };
127
+ };
128
+
129
+ const getXScaleDirection = (flipXScale?: boolean) => {
130
+ return flipXScale ? -1 : 1;
131
+ };
132
+
133
+ const calculateFullData = <TXAxis extends number | Date>(props: ChartCanvasProps<TXAxis>) => {
134
+ const {
135
+ data: fullData,
136
+ plotFull,
137
+ xScale,
138
+ clamp,
139
+ pointsPerPxThreshold,
140
+ flipXScale,
141
+ xAccessor,
142
+ displayXAccessor,
143
+ minPointsPerPxThreshold,
144
+ } = props;
145
+
146
+ const useWholeData = plotFull !== undefined ? plotFull : xAccessor === identity;
147
+
148
+ const { filterData } = evaluator({
149
+ xScale,
150
+ useWholeData,
151
+ clamp,
152
+ pointsPerPxThreshold,
153
+ minPointsPerPxThreshold,
154
+ flipXScale,
155
+ });
156
+
157
+ return {
158
+ xAccessor,
159
+ displayXAccessor: displayXAccessor ?? xAccessor,
160
+ xScale: xScale.copy(),
161
+ fullData,
162
+ filterData,
163
+ };
164
+ };
165
+
166
+ const resetChart = <TXAxis extends number | Date>(props: ChartCanvasProps<TXAxis>) => {
167
+ const state = calculateState(props);
168
+
169
+ const { xAccessor, displayXAccessor, fullData, plotData: initialPlotData, xScale } = state;
170
+
171
+ const { postCalculator, children } = props;
172
+
173
+ const plotData = postCalculator !== undefined ? postCalculator(initialPlotData) : initialPlotData;
174
+
175
+ const dimensions = getDimensions(props);
176
+
177
+ const chartConfigs = getChartConfigWithUpdatedYScales(
178
+ getNewChartConfig(dimensions, children),
179
+ { plotData, xAccessor, displayXAccessor, fullData },
180
+ xScale.domain(),
181
+ );
182
+
183
+ return {
184
+ ...state,
185
+ xScale,
186
+ plotData,
187
+ chartConfigs,
188
+ };
189
+ };
190
+
191
+ const updateChart = (
192
+ newState: any,
193
+ initialXScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>,
194
+ props: any,
195
+ lastItemWasVisible: boolean,
196
+ initialChartConfig: any,
197
+ ) => {
198
+ const { fullData, xScale, xAccessor, displayXAccessor, filterData } = newState;
199
+
200
+ const lastItem = last(fullData);
201
+ const lastXItem = xAccessor(lastItem);
202
+ const [start, end] = initialXScale.domain();
203
+
204
+ const { postCalculator, children, padding, flipXScale, maintainPointsPerPixelOnResize } = props;
205
+
206
+ const direction = getXScaleDirection(flipXScale);
207
+ const dimensions = getDimensions(props);
208
+ const updatedXScale = setXRange(xScale, dimensions, padding, direction);
209
+
210
+ let initialPlotData;
211
+ if (!lastItemWasVisible || end >= lastXItem) {
212
+ // resize comes here...
213
+ // get plotData between [start, end] and do not change the domain
214
+ const [rangeStart, rangeEnd] = initialXScale.range();
215
+ const [newRangeStart, newRangeEnd] = updatedXScale.range();
216
+ const newDomainExtent =
217
+ ((newRangeEnd - newRangeStart) / (rangeEnd - rangeStart)) * (end.valueOf() - start.valueOf());
218
+ const newStart = maintainPointsPerPixelOnResize ? end.valueOf() - newDomainExtent : start;
219
+
220
+ const lastItemX = initialXScale(lastXItem);
221
+
222
+ const response = filterData(fullData, [newStart, end], xAccessor, updatedXScale, {
223
+ fallbackStart: start,
224
+ fallbackEnd: { lastItem, lastItemX },
225
+ });
226
+ initialPlotData = response.plotData;
227
+ updatedXScale.domain(response.domain);
228
+ } else if (lastItemWasVisible && end < lastXItem) {
229
+ // this is when a new item is added and last item was visible
230
+ // so slide over and show the new item also
231
+
232
+ // get plotData between [xAccessor(l) - (end - start), xAccessor(l)] and DO change the domain
233
+ const dx = initialXScale(lastXItem) - initialXScale.range()[1];
234
+ const [newStart, newEnd] = initialXScale
235
+ .range()
236
+ .map((x) => x + dx)
237
+ .map((x) => initialXScale.invert(x));
238
+
239
+ const response = filterData(fullData, [newStart, newEnd], xAccessor, updatedXScale);
240
+ initialPlotData = response.plotData;
241
+ updatedXScale.domain(response.domain); // if last item was visible, then shift
242
+ }
243
+
244
+ const plotData = postCalculator(initialPlotData);
245
+
246
+ const chartConfigs = getChartConfigWithUpdatedYScales(
247
+ getNewChartConfig(dimensions, children, initialChartConfig),
248
+ { plotData, xAccessor, displayXAccessor, fullData },
249
+ updatedXScale.domain(),
250
+ );
251
+
252
+ return {
253
+ xScale: updatedXScale,
254
+ xAccessor,
255
+ chartConfigs,
256
+ plotData,
257
+ fullData,
258
+ filterData,
259
+ };
260
+ };
261
+
262
+ const calculateState = <TXAxis extends number | Date>(props: ChartCanvasProps<TXAxis>) => {
263
+ const { xAccessor: inputXAccessor, xExtents: xExtentsProp, data, padding, flipXScale } = props;
264
+
265
+ const direction = getXScaleDirection(flipXScale);
266
+
267
+ const dimensions = getDimensions(props);
268
+
269
+ const extent =
270
+ typeof xExtentsProp === "function"
271
+ ? xExtentsProp(data)
272
+ : (d3Extent<number | Date>(
273
+ xExtentsProp.map((d: any) => functor(d)).map((each: any) => each(data, inputXAccessor)),
274
+ ) as [TXAxis, TXAxis]);
275
+
276
+ const { xAccessor, displayXAccessor, xScale, fullData, filterData } = calculateFullData(props);
277
+
278
+ const updatedXScale = setXRange(xScale, dimensions, padding, direction);
279
+
280
+ const { plotData, domain } = filterData(fullData, extent, inputXAccessor, updatedXScale);
281
+
282
+ return {
283
+ plotData,
284
+ xScale: updatedXScale.domain(domain),
285
+ xAccessor,
286
+ displayXAccessor,
287
+ fullData,
288
+ filterData,
289
+ };
290
+ };
291
+
292
+ const setXRange = (xScale: any, dimensions: any, padding: any, direction = 1) => {
293
+ if (xScale.rangeRoundPoints) {
294
+ if (isNaN(padding)) {
295
+ throw new Error("padding has to be a number for ordinal scale");
296
+ }
297
+ xScale.rangeRoundPoints([0, dimensions.width], padding);
298
+ } else if (xScale.padding) {
299
+ if (isNaN(padding)) {
300
+ throw new Error("padding has to be a number for ordinal scale");
301
+ }
302
+ xScale.range([0, dimensions.width]);
303
+ xScale.padding(padding / 2);
304
+ } else {
305
+ const { left, right } = isNaN(padding) ? padding : { left: padding, right: padding };
306
+ if (direction > 0) {
307
+ xScale.range([left, dimensions.width - right]);
308
+ } else {
309
+ xScale.range([dimensions.width - right, left]);
310
+ }
311
+ }
312
+ return xScale;
313
+ };
314
+
315
+ const pinchCoordinates = (pinch: any) => {
316
+ const { touch1Pos, touch2Pos } = pinch;
317
+
318
+ return {
319
+ topLeft: [Math.min(touch1Pos[0], touch2Pos[0]), Math.min(touch1Pos[1], touch2Pos[1])],
320
+ bottomRight: [Math.max(touch1Pos[0], touch2Pos[0]), Math.max(touch1Pos[1], touch2Pos[1])],
321
+ };
322
+ };
323
+
324
+ const isInteractionEnabled = (
325
+ xScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>,
326
+ xAccessor: any,
327
+ data: any,
328
+ ) => {
329
+ const interaction = !isNaN(xScale(xAccessor(head(data)))) && isDefined(xScale.invert);
330
+ return interaction;
331
+ };
332
+
333
+ export interface ChartCanvasProps<TXAxis extends number | Date> {
334
+ readonly clamp?:
335
+ | boolean
336
+ | ("left" | "right" | "both")
337
+ | ((domain: [number, number], items: [number, number]) => [number, number]);
338
+ readonly className?: string;
339
+ readonly children?: React.ReactNode;
340
+ readonly data: any[];
341
+ readonly defaultFocus?: boolean;
342
+ readonly disableInteraction?: boolean;
343
+ readonly disablePan?: boolean;
344
+ readonly disableZoom?: boolean;
345
+ readonly displayXAccessor?: (data: any) => TXAxis;
346
+ readonly flipXScale?: boolean;
347
+ readonly height: number;
348
+ readonly margin: {
349
+ bottom: number;
350
+ left: number;
351
+ right: number;
352
+ top: number;
353
+ };
354
+ readonly maintainPointsPerPixelOnResize?: boolean;
355
+ readonly minPointsPerPxThreshold?: number;
356
+ readonly mouseMoveEvent?: boolean;
357
+ /**
358
+ * Called when panning left past the first data point.
359
+ */
360
+ readonly onLoadAfter?: (start: TXAxis, end: TXAxis) => void;
361
+ /**
362
+ * Called when panning right past the last data point.
363
+ */
364
+ readonly onLoadBefore?: (start: TXAxis, end: TXAxis) => void;
365
+ /**
366
+ * Click event handler.
367
+ */
368
+ readonly onClick?: React.MouseEventHandler<HTMLDivElement>;
369
+ /**
370
+ * Double click event handler.
371
+ */
372
+ readonly onDoubleClick?: React.MouseEventHandler<HTMLDivElement>;
373
+ readonly padding?:
374
+ | number
375
+ | {
376
+ bottom: number;
377
+ left: number;
378
+ right: number;
379
+ top: number;
380
+ };
381
+ readonly plotFull?: boolean;
382
+ readonly pointsPerPxThreshold?: number;
383
+ readonly postCalculator?: (plotData: any[]) => any[];
384
+ readonly ratio: number;
385
+ readonly seriesName: string;
386
+ readonly useCrossHairStyleCursor?: boolean;
387
+ readonly width: number;
388
+ readonly xAccessor: (data: any) => TXAxis;
389
+ readonly xExtents: ((data: any[]) => [TXAxis, TXAxis]) | (((data: any[]) => TXAxis) | TXAxis)[];
390
+ readonly xScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;
391
+ readonly zIndex?: number;
392
+ readonly zoomAnchor?: (options: IZoomAnchorOptions<any, TXAxis>) => TXAxis;
393
+ readonly zoomMultiplier?: number;
394
+ }
395
+
396
+ interface ChartCanvasState<TXAxis extends number | Date> {
397
+ lastProps?: ChartCanvasProps<TXAxis>;
398
+ propIteration?: number;
399
+ xAccessor: (data: any) => TXAxis;
400
+ displayXAccessor?: any;
401
+ filterData?: any;
402
+ chartConfigs: ChartConfig[];
403
+ plotData: any[];
404
+ xScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;
405
+ fullData: any[];
406
+ }
407
+
408
+ interface Subscription {
409
+ id: string;
410
+ getPanConditions: () => {
411
+ draggable: boolean;
412
+ panEnabled: boolean;
413
+ };
414
+ draw: (props: { trigger: string } | { force: boolean }) => void;
415
+ listener: (type: string, newMoreProps: MoreProps | undefined, state: any, e: any) => void;
416
+ }
417
+
418
+ interface MutableState {
419
+ mouseXY: [number, number];
420
+ currentItem: any;
421
+ currentCharts: string[];
422
+ }
423
+
424
+ export class ChartCanvas<TXAxis extends number | Date> extends React.Component<
425
+ ChartCanvasProps<TXAxis>,
426
+ ChartCanvasState<TXAxis>
427
+ > {
428
+ public static defaultProps = {
429
+ clamp: false,
430
+ className: "react-financial-charts",
431
+ defaultFocus: true,
432
+ disablePan: false,
433
+ disableInteraction: false,
434
+ disableZoom: false,
435
+ flipXScale: false,
436
+ maintainPointsPerPixelOnResize: true,
437
+ margin: { top: 0, right: 40, bottom: 40, left: 0 },
438
+ minPointsPerPxThreshold: 1 / 100,
439
+ mouseMoveEvent: true,
440
+ postCalculator: identity,
441
+ padding: 0,
442
+ pointsPerPxThreshold: 2,
443
+ useCrossHairStyleCursor: true,
444
+ xAccessor: identity as (data: any) => any,
445
+ xExtents: [min, max] as any[],
446
+ zIndex: 1,
447
+ zoomAnchor: mouseBasedZoomAnchor,
448
+ zoomMultiplier: 1.1,
449
+ };
450
+
451
+ private readonly canvasContainerRef = React.createRef<CanvasContainer>();
452
+ private readonly eventCaptureRef = React.createRef<EventCapture>();
453
+ private finalPinch?: boolean;
454
+ private lastSubscriptionId = 0;
455
+ private mutableState: MutableState = { mouseXY: [0, 0], currentCharts: [], currentItem: null };
456
+ private panInProgress = false;
457
+ private prevMouseXY?: number[];
458
+ private subscriptions: Subscription[] = [];
459
+ private waitingForPinchZoomAnimationFrame?: boolean;
460
+ private waitingForPanAnimationFrame?: boolean;
461
+ private waitingForMouseMoveAnimationFrame?: boolean;
462
+
463
+ // tslint:disable-next-line: variable-name
464
+ private hackyWayToStopPanBeyondBounds__plotData?: any[] | null;
465
+ // tslint:disable-next-line: variable-name
466
+ private hackyWayToStopPanBeyondBounds__domain?: any[] | null;
467
+
468
+ public constructor(props: ChartCanvasProps<TXAxis>) {
469
+ super(props);
470
+ this.state = resetChart(props);
471
+ }
472
+
473
+ public static getDerivedStateFromProps<TXAxis extends number | Date>(
474
+ props: ChartCanvasProps<TXAxis>,
475
+ state: ChartCanvasState<TXAxis>,
476
+ ): ChartCanvasState<TXAxis> {
477
+ const { chartConfigs: initialChartConfig, plotData, xAccessor, xScale } = state;
478
+ const interaction = isInteractionEnabled(xScale, xAccessor, plotData);
479
+ const shouldReset = shouldResetChart(state.lastProps || {}, props);
480
+ let newState: ChartCanvasState<TXAxis>;
481
+ if (!interaction || shouldReset || !shallowEqual(state.lastProps?.xExtents, props.xExtents)) {
482
+ // do reset
483
+ newState = resetChart(props);
484
+ } else {
485
+ const [start, end] = xScale.domain();
486
+ const prevLastItem = last(state.fullData);
487
+
488
+ const calculatedState = calculateFullData(props);
489
+ const { xAccessor } = calculatedState;
490
+ const previousX = xAccessor(prevLastItem);
491
+ const lastItemWasVisible = previousX <= end && previousX >= start;
492
+
493
+ newState = updateChart(calculatedState, xScale, props, lastItemWasVisible, initialChartConfig);
494
+ }
495
+ return {
496
+ ...newState,
497
+ lastProps: props,
498
+ propIteration: (state.propIteration || 0) + 1,
499
+ };
500
+ }
501
+
502
+ public getSnapshotBeforeUpdate(
503
+ prevProps: Readonly<ChartCanvasProps<TXAxis>>,
504
+ prevState: Readonly<ChartCanvasState<TXAxis>>,
505
+ ) {
506
+ // propIteration is incremented when the props change to differentiate between state updates
507
+ // and prop updates
508
+ if (prevState.propIteration !== this.state.propIteration && !this.panInProgress) {
509
+ this.clearThreeCanvas();
510
+ }
511
+ return null;
512
+ }
513
+
514
+ public componentDidUpdate(prevProps: ChartCanvasProps<TXAxis>) {
515
+ if (prevProps.data !== this.props.data) {
516
+ this.triggerEvent("dataupdated", {
517
+ chartConfigs: this.state.chartConfigs,
518
+ xScale: this.state.xScale,
519
+ plotData: this.state.plotData,
520
+ });
521
+ }
522
+ }
523
+
524
+ public getMutableState = () => {
525
+ return this.mutableState;
526
+ };
527
+
528
+ public getCanvasContexts = () => {
529
+ return this.canvasContainerRef.current?.getCanvasContexts();
530
+ };
531
+
532
+ public generateSubscriptionId = () => {
533
+ this.lastSubscriptionId++;
534
+
535
+ return this.lastSubscriptionId;
536
+ };
537
+
538
+ public clearBothCanvas() {
539
+ const canvases = this.getCanvasContexts();
540
+ if (canvases && canvases.axes && canvases.mouseCoord) {
541
+ clearCanvas([canvases.axes, canvases.mouseCoord], this.props.ratio);
542
+ }
543
+ }
544
+
545
+ public clearMouseCanvas() {
546
+ const canvases = this.getCanvasContexts();
547
+ if (canvases && canvases.mouseCoord) {
548
+ clearCanvas([canvases.mouseCoord], this.props.ratio);
549
+ }
550
+ }
551
+
552
+ public clearThreeCanvas() {
553
+ const canvases = this.getCanvasContexts();
554
+ if (canvases && canvases.axes && canvases.mouseCoord && canvases.bg) {
555
+ clearCanvas([canvases.axes, canvases.mouseCoord, canvases.bg], this.props.ratio);
556
+ }
557
+ }
558
+
559
+ public subscribe = (id: string | number, rest: any) => {
560
+ const {
561
+ getPanConditions = functor({
562
+ draggable: false,
563
+ panEnabled: true,
564
+ }),
565
+ } = rest;
566
+
567
+ this.subscriptions = this.subscriptions.concat({
568
+ id,
569
+ ...rest,
570
+ getPanConditions,
571
+ });
572
+ };
573
+
574
+ public unsubscribe = (id: string | number) => {
575
+ this.subscriptions = this.subscriptions.filter((each) => each.id !== id);
576
+ };
577
+
578
+ public getAllPanConditions = () => {
579
+ return this.subscriptions.map((each) => each.getPanConditions());
580
+ };
581
+
582
+ public setCursorClass = (className: string | null | undefined) => {
583
+ this.eventCaptureRef.current?.setCursorClass(className);
584
+ };
585
+
586
+ public amIOnTop = (id: string | number) => {
587
+ const dragableComponents = this.subscriptions.filter((each) => each.getPanConditions().draggable);
588
+
589
+ return dragableComponents.length > 0 && last(dragableComponents).id === id;
590
+ };
591
+
592
+ public handleContextMenu = (mouseXY: number[], e: React.MouseEvent) => {
593
+ const { xAccessor, chartConfigs, plotData, xScale } = this.state;
594
+
595
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
596
+
597
+ const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
598
+
599
+ this.triggerEvent(
600
+ "contextmenu",
601
+ {
602
+ mouseXY,
603
+ currentItem,
604
+ currentCharts,
605
+ },
606
+ e,
607
+ );
608
+ };
609
+
610
+ public calculateStateForDomain = (newDomain: any) => {
611
+ const {
612
+ xAccessor,
613
+ displayXAccessor,
614
+ xScale: initialXScale,
615
+ chartConfigs: initialChartConfig,
616
+ plotData: initialPlotData,
617
+ } = this.state;
618
+
619
+ const { filterData, fullData } = this.state;
620
+ const { postCalculator = ChartCanvas.defaultProps.postCalculator } = this.props;
621
+
622
+ const { plotData: beforePlotData, domain } = filterData(fullData, newDomain, xAccessor, initialXScale, {
623
+ currentPlotData: initialPlotData,
624
+ currentDomain: initialXScale!.domain(),
625
+ });
626
+
627
+ const plotData = postCalculator(beforePlotData);
628
+
629
+ const updatedScale = initialXScale.copy().domain(domain) as
630
+ | ScaleContinuousNumeric<number, number>
631
+ | ScaleTime<number, number>;
632
+
633
+ const chartConfigs = getChartConfigWithUpdatedYScales(
634
+ initialChartConfig,
635
+ { plotData, xAccessor, displayXAccessor, fullData },
636
+ updatedScale.domain(),
637
+ );
638
+
639
+ return {
640
+ xScale: updatedScale,
641
+ plotData,
642
+ chartConfigs,
643
+ };
644
+ };
645
+
646
+ public pinchZoomHelper = (initialPinch: any, finalPinch: any) => {
647
+ const { xScale: initialPinchXScale } = initialPinch;
648
+
649
+ const {
650
+ xScale: initialXScale,
651
+ chartConfigs: initialChartConfig,
652
+ plotData: initialPlotData,
653
+ xAccessor,
654
+ displayXAccessor,
655
+ filterData,
656
+ fullData,
657
+ } = this.state;
658
+ const { postCalculator = ChartCanvas.defaultProps.postCalculator } = this.props;
659
+
660
+ const { topLeft: iTL, bottomRight: iBR } = pinchCoordinates(initialPinch);
661
+ const { topLeft: fTL, bottomRight: fBR } = pinchCoordinates(finalPinch);
662
+
663
+ const e = initialPinchXScale.range()[1];
664
+
665
+ const xDash = Math.round(-(iBR[0] * fTL[0] - iTL[0] * fBR[0]) / (iTL[0] - iBR[0]));
666
+ const yDash = Math.round(
667
+ e + ((e - iBR[0]) * (e - fTL[0]) - (e - iTL[0]) * (e - fBR[0])) / (e - iTL[0] - (e - iBR[0])),
668
+ );
669
+
670
+ const x = Math.round((-xDash * iTL[0]) / (-xDash + fTL[0]));
671
+ const y = Math.round(e - ((yDash - e) * (e - iTL[0])) / (yDash + (e - fTL[0])));
672
+
673
+ const newDomain = [x, y].map(initialPinchXScale.invert);
674
+
675
+ const { plotData: beforePlotData, domain } = filterData(fullData, newDomain, xAccessor, initialPinchXScale, {
676
+ currentPlotData: initialPlotData,
677
+ currentDomain: initialXScale!.domain(),
678
+ });
679
+
680
+ const plotData = postCalculator(beforePlotData);
681
+
682
+ const updatedScale = initialXScale!.copy().domain(domain) as
683
+ | ScaleContinuousNumeric<number, number>
684
+ | ScaleTime<number, number>;
685
+
686
+ const mouseXY = finalPinch.touch1Pos;
687
+
688
+ const chartConfigs = getChartConfigWithUpdatedYScales(
689
+ initialChartConfig,
690
+ { plotData, xAccessor, displayXAccessor, fullData },
691
+ updatedScale.domain(),
692
+ );
693
+
694
+ const currentItem = getCurrentItem(updatedScale, xAccessor, mouseXY, plotData);
695
+
696
+ return {
697
+ chartConfigs,
698
+ xScale: updatedScale,
699
+ plotData,
700
+ mouseXY,
701
+ currentItem,
702
+ xAccessor,
703
+ fullData,
704
+ };
705
+ };
706
+
707
+ public cancelDrag() {
708
+ this.eventCaptureRef.current?.cancelDrag();
709
+ this.triggerEvent("dragcancel");
710
+ }
711
+
712
+ public handlePinchZoom = (initialPinch: any, finalPinch: any, e: any) => {
713
+ if (!this.waitingForPinchZoomAnimationFrame) {
714
+ this.waitingForPinchZoomAnimationFrame = true;
715
+ const state = this.pinchZoomHelper(initialPinch, finalPinch);
716
+
717
+ this.triggerEvent("pinchzoom", state, e);
718
+
719
+ this.finalPinch = finalPinch;
720
+
721
+ requestAnimationFrame(() => {
722
+ this.clearBothCanvas();
723
+ this.draw({ trigger: "pinchzoom" });
724
+ this.waitingForPinchZoomAnimationFrame = false;
725
+ });
726
+ }
727
+ };
728
+
729
+ public handlePinchZoomEnd = (initialPinch: any, e: any) => {
730
+ const { xAccessor = ChartCanvas.defaultProps.xAccessor } = this.state;
731
+
732
+ if (this.finalPinch) {
733
+ const state = this.pinchZoomHelper(initialPinch, this.finalPinch);
734
+ const { xScale, fullData } = state;
735
+ this.triggerEvent("pinchzoom", state, e);
736
+
737
+ this.finalPinch = undefined;
738
+
739
+ this.clearThreeCanvas();
740
+ const firstItem = head(fullData);
741
+ const scale_start = head(xScale.domain());
742
+ const data_start = xAccessor(firstItem);
743
+
744
+ const lastItem = last(fullData);
745
+ const scale_end = last(xScale.domain());
746
+ const data_end = xAccessor(lastItem);
747
+
748
+ const { onLoadAfter, onLoadBefore } = this.props;
749
+
750
+ this.setState(state, () => {
751
+ if (scale_start < data_start) {
752
+ if (onLoadBefore !== undefined) {
753
+ onLoadBefore(scale_start, data_start);
754
+ }
755
+ }
756
+ if (data_end < scale_end) {
757
+ if (onLoadAfter !== undefined) {
758
+ onLoadAfter(data_end, scale_end);
759
+ }
760
+ }
761
+ });
762
+ }
763
+ };
764
+
765
+ public handleZoom = (zoomDirection: any, mouseXY: any, e: any) => {
766
+ if (this.panInProgress) {
767
+ return;
768
+ }
769
+
770
+ const { xAccessor, xScale: initialXScale, plotData: initialPlotData, fullData } = this.state;
771
+ const {
772
+ zoomMultiplier = ChartCanvas.defaultProps.zoomMultiplier,
773
+ zoomAnchor = ChartCanvas.defaultProps.zoomAnchor,
774
+ } = this.props;
775
+
776
+ const item = zoomAnchor({
777
+ xScale: initialXScale!,
778
+ xAccessor: xAccessor!,
779
+ mouseXY,
780
+ plotData: initialPlotData,
781
+ });
782
+
783
+ const cx = initialXScale(item);
784
+ const c = zoomDirection > 0 ? 1 * zoomMultiplier : 1 / zoomMultiplier;
785
+ const newDomain = initialXScale!
786
+ .range()
787
+ .map((x) => cx + (x - cx) * c)
788
+ .map((x) => initialXScale.invert(x));
789
+
790
+ const { xScale, plotData, chartConfigs } = this.calculateStateForDomain(newDomain);
791
+
792
+ const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
793
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
794
+
795
+ this.clearThreeCanvas();
796
+
797
+ const firstItem = head(fullData);
798
+ const scale_start = head(xScale.domain());
799
+ const data_start = xAccessor!(firstItem);
800
+
801
+ const lastItem = last(fullData);
802
+ const scale_end = last(xScale.domain());
803
+ const data_end = xAccessor!(lastItem);
804
+
805
+ this.mutableState = {
806
+ mouseXY,
807
+ currentItem,
808
+ currentCharts,
809
+ };
810
+
811
+ this.triggerEvent(
812
+ "zoom",
813
+ {
814
+ xScale,
815
+ plotData,
816
+ chartConfigs,
817
+ mouseXY,
818
+ currentCharts,
819
+ currentItem,
820
+ show: true,
821
+ },
822
+ e,
823
+ );
824
+
825
+ const { onLoadAfter, onLoadBefore } = this.props;
826
+
827
+ this.setState(
828
+ {
829
+ xScale,
830
+ plotData,
831
+ chartConfigs,
832
+ },
833
+ () => {
834
+ if (scale_start < data_start) {
835
+ if (onLoadBefore !== undefined) {
836
+ onLoadBefore(scale_start, data_start);
837
+ }
838
+ }
839
+ if (data_end < scale_end) {
840
+ if (onLoadAfter !== undefined) {
841
+ onLoadAfter(data_end, scale_end);
842
+ }
843
+ }
844
+ },
845
+ );
846
+ };
847
+
848
+ public xAxisZoom = (newDomain: any) => {
849
+ const { xScale, plotData, chartConfigs } = this.calculateStateForDomain(newDomain);
850
+ this.clearThreeCanvas();
851
+
852
+ const { xAccessor, fullData } = this.state;
853
+ const firstItem = head(fullData);
854
+ const scale_start = head(xScale.domain());
855
+ const data_start = xAccessor!(firstItem);
856
+
857
+ const lastItem = last(fullData);
858
+ const scale_end = last(xScale.domain());
859
+ const data_end = xAccessor!(lastItem);
860
+
861
+ const { onLoadAfter, onLoadBefore } = this.props;
862
+
863
+ this.setState(
864
+ {
865
+ xScale,
866
+ plotData,
867
+ chartConfigs,
868
+ },
869
+ () => {
870
+ if (scale_start < data_start) {
871
+ if (onLoadBefore !== undefined) {
872
+ onLoadBefore(scale_start, data_start);
873
+ }
874
+ }
875
+ if (data_end < scale_end) {
876
+ if (onLoadAfter !== undefined) {
877
+ onLoadAfter(data_end, scale_end);
878
+ }
879
+ }
880
+ },
881
+ );
882
+ };
883
+
884
+ public yAxisZoom = (chartId: string, newDomain: any) => {
885
+ this.clearThreeCanvas();
886
+ const { chartConfigs: initialChartConfig } = this.state;
887
+ const chartConfigs = initialChartConfig.map((each: any) => {
888
+ if (each.id === chartId) {
889
+ const { yScale } = each;
890
+ return {
891
+ ...each,
892
+ yScale: yScale.copy().domain(newDomain),
893
+ yPanEnabled: true,
894
+ };
895
+ } else {
896
+ return each;
897
+ }
898
+ });
899
+
900
+ this.setState({
901
+ chartConfigs,
902
+ });
903
+ };
904
+
905
+ public triggerEvent(type: any, props?: any, e?: any) {
906
+ this.subscriptions.forEach((each) => {
907
+ const state = {
908
+ ...this.state,
909
+ subscriptions: this.subscriptions,
910
+ };
911
+ each.listener(type, props, state, e);
912
+ });
913
+ }
914
+
915
+ public draw = (props: { trigger: string } | { force: boolean }) => {
916
+ this.subscriptions.forEach((each) => {
917
+ if (isDefined(each.draw)) {
918
+ each.draw(props);
919
+ }
920
+ });
921
+ };
922
+
923
+ public redraw = () => {
924
+ this.clearThreeCanvas();
925
+ this.draw({ force: true });
926
+ };
927
+
928
+ public panHelper = (
929
+ mouseXY: [number, number],
930
+ initialXScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>,
931
+ { dx, dy }: { dx: number; dy: number },
932
+ chartsToPan: string[],
933
+ ) => {
934
+ const { xAccessor, displayXAccessor, chartConfigs: initialChartConfig, filterData, fullData } = this.state;
935
+ const { postCalculator = ChartCanvas.defaultProps.postCalculator } = this.props;
936
+
937
+ const newDomain = initialXScale
938
+ .range()
939
+ .map((x) => x - dx)
940
+ .map((x) => initialXScale.invert(x));
941
+
942
+ const { plotData: beforePlotData, domain } = filterData(fullData, newDomain, xAccessor, initialXScale, {
943
+ currentPlotData: this.hackyWayToStopPanBeyondBounds__plotData,
944
+ currentDomain: this.hackyWayToStopPanBeyondBounds__domain,
945
+ ignoreThresholds: true,
946
+ });
947
+
948
+ const updatedScale = initialXScale.copy().domain(domain) as
949
+ | ScaleContinuousNumeric<number, number>
950
+ | ScaleTime<number, number>;
951
+
952
+ const plotData = postCalculator(beforePlotData);
953
+
954
+ const currentItem = getCurrentItem(updatedScale, xAccessor, mouseXY, plotData);
955
+
956
+ const chartConfigs = getChartConfigWithUpdatedYScales(
957
+ initialChartConfig,
958
+ { plotData, xAccessor, displayXAccessor, fullData },
959
+ updatedScale.domain(),
960
+ dy,
961
+ chartsToPan,
962
+ );
963
+
964
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
965
+
966
+ return {
967
+ xScale: updatedScale,
968
+ plotData,
969
+ chartConfigs,
970
+ mouseXY,
971
+ currentCharts,
972
+ currentItem,
973
+ };
974
+ };
975
+
976
+ public handlePan = (
977
+ mousePosition: [number, number],
978
+ panStartXScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>,
979
+ dxdy: { dx: number; dy: number },
980
+ chartsToPan: string[],
981
+ e: React.MouseEvent,
982
+ ) => {
983
+ if (this.waitingForPanAnimationFrame) {
984
+ return;
985
+ }
986
+ this.waitingForPanAnimationFrame = true;
987
+
988
+ this.hackyWayToStopPanBeyondBounds__plotData =
989
+ this.hackyWayToStopPanBeyondBounds__plotData ?? this.state.plotData;
990
+ this.hackyWayToStopPanBeyondBounds__domain =
991
+ this.hackyWayToStopPanBeyondBounds__domain ?? this.state.xScale!.domain();
992
+
993
+ const newState = this.panHelper(mousePosition, panStartXScale, dxdy, chartsToPan);
994
+
995
+ this.hackyWayToStopPanBeyondBounds__plotData = newState.plotData;
996
+ this.hackyWayToStopPanBeyondBounds__domain = newState.xScale.domain();
997
+
998
+ this.panInProgress = true;
999
+
1000
+ this.triggerEvent("pan", newState, e);
1001
+
1002
+ this.mutableState = {
1003
+ mouseXY: newState.mouseXY,
1004
+ currentItem: newState.currentItem,
1005
+ currentCharts: newState.currentCharts,
1006
+ };
1007
+ requestAnimationFrame(() => {
1008
+ this.waitingForPanAnimationFrame = false;
1009
+ this.clearBothCanvas();
1010
+ this.draw({ trigger: "pan" });
1011
+ });
1012
+ };
1013
+
1014
+ public handlePanEnd = (
1015
+ mousePosition: [number, number],
1016
+ panStartXScale: ScaleContinuousNumeric<number, number> | ScaleTime<number, number>,
1017
+ dxdy: { dx: number; dy: number },
1018
+ chartsToPan: string[],
1019
+ e: React.MouseEvent | React.TouchEvent,
1020
+ ) => {
1021
+ const state = this.panHelper(mousePosition, panStartXScale, dxdy, chartsToPan);
1022
+ this.hackyWayToStopPanBeyondBounds__plotData = null;
1023
+ this.hackyWayToStopPanBeyondBounds__domain = null;
1024
+
1025
+ this.panInProgress = false;
1026
+
1027
+ const { xScale, plotData, chartConfigs } = state;
1028
+
1029
+ this.triggerEvent("panend", state, e);
1030
+
1031
+ requestAnimationFrame(() => {
1032
+ const { xAccessor, fullData } = this.state;
1033
+
1034
+ const firstItem = head(fullData);
1035
+ const scale_start = head(xScale.domain());
1036
+ const data_start = xAccessor!(firstItem);
1037
+
1038
+ const lastItem = last(fullData);
1039
+ const scale_end = last(xScale.domain());
1040
+ const data_end = xAccessor!(lastItem);
1041
+
1042
+ const { onLoadAfter, onLoadBefore } = this.props;
1043
+
1044
+ this.clearThreeCanvas();
1045
+
1046
+ this.setState(
1047
+ {
1048
+ xScale,
1049
+ plotData,
1050
+ chartConfigs,
1051
+ },
1052
+ () => {
1053
+ if (scale_start < data_start) {
1054
+ if (onLoadBefore !== undefined) {
1055
+ onLoadBefore(scale_start, data_start);
1056
+ }
1057
+ }
1058
+ if (data_end < scale_end) {
1059
+ if (onLoadAfter !== undefined) {
1060
+ onLoadAfter(data_end, scale_end);
1061
+ }
1062
+ }
1063
+ },
1064
+ );
1065
+ });
1066
+ };
1067
+
1068
+ public handleMouseDown = (_: number[], __: string[], e: React.MouseEvent) => {
1069
+ this.triggerEvent("mousedown", this.mutableState, e);
1070
+ };
1071
+
1072
+ public handleMouseEnter = (e: React.MouseEvent) => {
1073
+ this.triggerEvent(
1074
+ "mouseenter",
1075
+ {
1076
+ show: true,
1077
+ },
1078
+ e,
1079
+ );
1080
+ };
1081
+
1082
+ public handleMouseMove = (mouseXY: [number, number], _: string, e: any) => {
1083
+ if (this.waitingForMouseMoveAnimationFrame) {
1084
+ return;
1085
+ }
1086
+ this.waitingForMouseMoveAnimationFrame = true;
1087
+ const { chartConfigs, plotData, xScale, xAccessor } = this.state;
1088
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
1089
+ const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
1090
+ this.triggerEvent(
1091
+ "mousemove",
1092
+ {
1093
+ show: true,
1094
+ mouseXY,
1095
+ // prevMouseXY is used in interactive components
1096
+ prevMouseXY: this.prevMouseXY,
1097
+ currentItem,
1098
+ currentCharts,
1099
+ },
1100
+ e,
1101
+ );
1102
+ this.prevMouseXY = mouseXY;
1103
+ this.mutableState = {
1104
+ mouseXY,
1105
+ currentItem,
1106
+ currentCharts,
1107
+ };
1108
+ requestAnimationFrame(() => {
1109
+ this.clearMouseCanvas();
1110
+ this.draw({ trigger: "mousemove" });
1111
+ this.waitingForMouseMoveAnimationFrame = false;
1112
+ });
1113
+ };
1114
+
1115
+ public handleMouseLeave = (e: any) => {
1116
+ this.triggerEvent("mouseleave", { show: false }, e);
1117
+ this.clearMouseCanvas();
1118
+ this.draw({ trigger: "mouseleave" });
1119
+ };
1120
+
1121
+ public handleDragStart = ({ startPos }: any, e: any) => {
1122
+ this.triggerEvent("dragstart", { startPos }, e);
1123
+ };
1124
+
1125
+ public handleDrag = (
1126
+ { startPos, mouseXY }: { startPos: [number, number]; mouseXY: [number, number] },
1127
+ e: React.MouseEvent,
1128
+ ) => {
1129
+ const { chartConfigs, plotData, xScale, xAccessor } = this.state;
1130
+
1131
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
1132
+ const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
1133
+
1134
+ this.triggerEvent(
1135
+ "drag",
1136
+ {
1137
+ startPos,
1138
+ mouseXY,
1139
+ currentItem,
1140
+ currentCharts,
1141
+ },
1142
+ e,
1143
+ );
1144
+
1145
+ this.mutableState = {
1146
+ mouseXY,
1147
+ currentItem,
1148
+ currentCharts,
1149
+ };
1150
+
1151
+ requestAnimationFrame(() => {
1152
+ this.clearMouseCanvas();
1153
+ this.draw({ trigger: "drag" });
1154
+ });
1155
+ };
1156
+
1157
+ public handleDragEnd = ({ mouseXY }: { mouseXY: number[] }, e: React.MouseEvent) => {
1158
+ this.triggerEvent("dragend", { mouseXY }, e);
1159
+
1160
+ requestAnimationFrame(() => {
1161
+ this.clearMouseCanvas();
1162
+ this.draw({ trigger: "dragend" });
1163
+ });
1164
+ };
1165
+
1166
+ public handleClick = (_: number[], e: React.MouseEvent) => {
1167
+ this.triggerEvent("click", this.mutableState, e);
1168
+
1169
+ requestAnimationFrame(() => {
1170
+ this.clearMouseCanvas();
1171
+ this.draw({ trigger: "click" });
1172
+ });
1173
+ };
1174
+
1175
+ public handleDoubleClick = (_: number[], e: React.MouseEvent) => {
1176
+ this.triggerEvent("dblclick", {}, e);
1177
+ };
1178
+
1179
+ // TODO: Memoize this
1180
+ public getContextValues(): ChartCanvasContextType<TXAxis> {
1181
+ const dimensions = getDimensions(this.props);
1182
+ return {
1183
+ chartId: -1,
1184
+ fullData: this.state.fullData,
1185
+ plotData: this.state.plotData,
1186
+ width: dimensions.width,
1187
+ height: dimensions.height,
1188
+ chartConfigs: this.state.chartConfigs,
1189
+ xScale: this.state.xScale,
1190
+ xAccessor: this.state.xAccessor,
1191
+ displayXAccessor: this.state.displayXAccessor,
1192
+ margin: this.props.margin,
1193
+ ratio: this.props.ratio,
1194
+ xAxisZoom: this.xAxisZoom,
1195
+ yAxisZoom: this.yAxisZoom,
1196
+ getCanvasContexts: this.getCanvasContexts,
1197
+ redraw: this.redraw,
1198
+ subscribe: this.subscribe,
1199
+ unsubscribe: this.unsubscribe,
1200
+ generateSubscriptionId: this.generateSubscriptionId,
1201
+ getMutableState: this.getMutableState,
1202
+ amIOnTop: this.amIOnTop,
1203
+ setCursorClass: this.setCursorClass,
1204
+ };
1205
+ }
1206
+
1207
+ public resetYDomain = (chartId?: string) => {
1208
+ const { chartConfigs } = this.state;
1209
+ let changed = false;
1210
+ const newChartConfig = chartConfigs.map((each: any) => {
1211
+ if (
1212
+ (isNotDefined(chartId) || each.id === chartId) &&
1213
+ !shallowEqual(each.yScale.domain(), each.realYDomain)
1214
+ ) {
1215
+ changed = true;
1216
+ return {
1217
+ ...each,
1218
+ yScale: each.yScale.domain(each.realYDomain),
1219
+ yPanEnabled: false,
1220
+ };
1221
+ }
1222
+ return each;
1223
+ });
1224
+
1225
+ if (changed) {
1226
+ this.clearThreeCanvas();
1227
+ this.setState({
1228
+ chartConfigs: newChartConfig,
1229
+ });
1230
+ }
1231
+ };
1232
+
1233
+ public shouldComponentUpdate() {
1234
+ return !this.panInProgress;
1235
+ }
1236
+
1237
+ public render() {
1238
+ const {
1239
+ disableInteraction,
1240
+ disablePan,
1241
+ disableZoom,
1242
+ useCrossHairStyleCursor,
1243
+ onClick,
1244
+ onDoubleClick,
1245
+ height,
1246
+ width,
1247
+ margin = ChartCanvas.defaultProps.margin,
1248
+ className,
1249
+ zIndex = ChartCanvas.defaultProps.zIndex,
1250
+ defaultFocus,
1251
+ ratio,
1252
+ mouseMoveEvent,
1253
+ } = this.props;
1254
+
1255
+ const { plotData, xScale, xAccessor, chartConfigs } = this.state;
1256
+
1257
+ const dimensions = getDimensions(this.props);
1258
+
1259
+ const interaction = isInteractionEnabled(xScale, xAccessor, plotData);
1260
+
1261
+ const cursorStyle = useCrossHairStyleCursor && interaction;
1262
+
1263
+ const cursor = getCursorStyle();
1264
+
1265
+ return (
1266
+ <ChartCanvasContext.Provider value={this.getContextValues()}>
1267
+ <div
1268
+ style={{ position: "relative", width, height }}
1269
+ className={className}
1270
+ onClick={onClick}
1271
+ onDoubleClick={onDoubleClick}
1272
+ >
1273
+ <CanvasContainer
1274
+ ref={this.canvasContainerRef}
1275
+ ratio={ratio}
1276
+ width={width}
1277
+ height={height}
1278
+ style={{ height, zIndex, width }}
1279
+ />
1280
+ <svg
1281
+ className={className}
1282
+ width={width}
1283
+ height={height}
1284
+ style={{ position: "absolute", zIndex: zIndex + 5 }}
1285
+ >
1286
+ {cursor}
1287
+ <defs>
1288
+ <clipPath id="chart-area-clip">
1289
+ <rect x="0" y="0" width={dimensions.width} height={dimensions.height} />
1290
+ </clipPath>
1291
+ {chartConfigs.map((each: any, idx: number) => (
1292
+ <clipPath key={idx} id={`chart-area-clip-${each.id}`}>
1293
+ <rect x="0" y="0" width={each.width} height={each.height} />
1294
+ </clipPath>
1295
+ ))}
1296
+ </defs>
1297
+ <g transform={`translate(${margin.left + 0.5}, ${margin.top + 0.5})`}>
1298
+ <EventCapture
1299
+ ref={this.eventCaptureRef}
1300
+ useCrossHairStyleCursor={cursorStyle}
1301
+ mouseMove={mouseMoveEvent && interaction}
1302
+ zoom={!disableZoom && interaction}
1303
+ pan={!disablePan && interaction}
1304
+ width={dimensions.width}
1305
+ height={dimensions.height}
1306
+ chartConfig={chartConfigs}
1307
+ xScale={xScale!}
1308
+ xAccessor={xAccessor}
1309
+ focus={defaultFocus}
1310
+ disableInteraction={disableInteraction}
1311
+ getAllPanConditions={this.getAllPanConditions}
1312
+ onContextMenu={this.handleContextMenu}
1313
+ onClick={this.handleClick}
1314
+ onDoubleClick={this.handleDoubleClick}
1315
+ onMouseDown={this.handleMouseDown}
1316
+ onMouseMove={this.handleMouseMove}
1317
+ onMouseEnter={this.handleMouseEnter}
1318
+ onMouseLeave={this.handleMouseLeave}
1319
+ onDragStart={this.handleDragStart}
1320
+ onDrag={this.handleDrag}
1321
+ onDragComplete={this.handleDragEnd}
1322
+ onZoom={this.handleZoom}
1323
+ onPinchZoom={this.handlePinchZoom}
1324
+ onPinchZoomEnd={this.handlePinchZoomEnd}
1325
+ onPan={this.handlePan}
1326
+ onPanEnd={this.handlePanEnd}
1327
+ />
1328
+
1329
+ <g className="react-financial-charts-avoid-interaction">{this.props.children}</g>
1330
+ </g>
1331
+ </svg>
1332
+ </div>
1333
+ </ChartCanvasContext.Provider>
1334
+ );
1335
+ }
1336
+ }