@sgonzaloc/react-financial-charts-core 3.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 +26 -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 +799 -0
  11. package/lib/ChartCanvas.js.map +1 -0
  12. package/lib/EventCapture.d.ts +131 -0
  13. package/lib/EventCapture.js +490 -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 +356 -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,799 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { extent as d3Extent, max, min } from "d3-array";
3
+ import * as React from "react";
4
+ import { clearCanvas, functor, head, identity, isDefined, isNotDefined, last, shallowEqual } from "./utils";
5
+ import { mouseBasedZoomAnchor } from "./zoom";
6
+ import { getChartConfigWithUpdatedYScales, getCurrentCharts, getCurrentItem, getNewChartConfig, } from "./utils/ChartDataUtil";
7
+ import { EventCapture } from "./EventCapture";
8
+ import { CanvasContainer } from "./CanvasContainer";
9
+ import evaluator from "./utils/evaluator";
10
+ const CANDIDATES_FOR_RESET = ["seriesName"];
11
+ const shouldResetChart = (thisProps, nextProps) => {
12
+ return !CANDIDATES_FOR_RESET.every((key) => {
13
+ const result = shallowEqual(thisProps[key], nextProps[key]);
14
+ return result;
15
+ });
16
+ };
17
+ const getCursorStyle = () => {
18
+ const tooltipStyle = `
19
+ .react-financial-charts-grabbing-cursor {
20
+ pointer-events: all;
21
+ cursor: -moz-grabbing;
22
+ cursor: -webkit-grabbing;
23
+ cursor: grabbing;
24
+ }
25
+ .react-financial-charts-crosshair-cursor {
26
+ pointer-events: all;
27
+ cursor: crosshair;
28
+ }
29
+ .react-financial-charts-tooltip-hover {
30
+ pointer-events: all;
31
+ cursor: pointer;
32
+ }
33
+ .react-financial-charts-avoid-interaction {
34
+ pointer-events: none;
35
+ }
36
+ .react-financial-charts-enable-interaction {
37
+ pointer-events: all;
38
+ }
39
+ .react-financial-charts-tooltip {
40
+ pointer-events: all;
41
+ cursor: pointer;
42
+ }
43
+ .react-financial-charts-default-cursor {
44
+ cursor: default;
45
+ }
46
+ .react-financial-charts-move-cursor {
47
+ cursor: move;
48
+ }
49
+ .react-financial-charts-pointer-cursor {
50
+ cursor: pointer;
51
+ }
52
+ .react-financial-charts-ns-resize-cursor {
53
+ cursor: ns-resize;
54
+ }
55
+ .react-financial-charts-ew-resize-cursor {
56
+ cursor: ew-resize;
57
+ }`;
58
+ return _jsx("style", Object.assign({ type: "text/css" }, { children: tooltipStyle }));
59
+ };
60
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
61
+ const noop = () => { };
62
+ export const chartCanvasContextDefaultValue = {
63
+ amIOnTop: () => false,
64
+ chartConfigs: [],
65
+ chartId: 0,
66
+ ratio: 0,
67
+ displayXAccessor: () => 0,
68
+ fullData: [],
69
+ getMutableState: () => ({}),
70
+ height: 0,
71
+ margin: { top: 0, right: 0, bottom: 0, left: 0 },
72
+ plotData: [],
73
+ setCursorClass: noop,
74
+ subscribe: noop,
75
+ unsubscribe: noop,
76
+ redraw: noop,
77
+ width: 0,
78
+ xAccessor: () => 0,
79
+ xScale: noop,
80
+ };
81
+ export const ChartCanvasContext = React.createContext(chartCanvasContextDefaultValue);
82
+ const getDimensions = (props) => {
83
+ const { margin, height, width } = props;
84
+ return {
85
+ height: height - margin.top - margin.bottom,
86
+ width: width - margin.left - margin.right,
87
+ };
88
+ };
89
+ const getXScaleDirection = (flipXScale) => {
90
+ return flipXScale ? -1 : 1;
91
+ };
92
+ const calculateFullData = (props) => {
93
+ const { data: fullData, plotFull, xScale, clamp, pointsPerPxThreshold, flipXScale, xAccessor, displayXAccessor, minPointsPerPxThreshold, } = props;
94
+ const useWholeData = plotFull !== undefined ? plotFull : xAccessor === identity;
95
+ const { filterData } = evaluator({
96
+ xScale,
97
+ useWholeData,
98
+ clamp,
99
+ pointsPerPxThreshold,
100
+ minPointsPerPxThreshold,
101
+ flipXScale,
102
+ });
103
+ return {
104
+ xAccessor,
105
+ displayXAccessor: displayXAccessor !== null && displayXAccessor !== void 0 ? displayXAccessor : xAccessor,
106
+ xScale: xScale.copy(),
107
+ fullData,
108
+ filterData,
109
+ };
110
+ };
111
+ const resetChart = (props) => {
112
+ const state = calculateState(props);
113
+ const { xAccessor, displayXAccessor, fullData, plotData: initialPlotData, xScale } = state;
114
+ const { postCalculator, children } = props;
115
+ const plotData = postCalculator !== undefined ? postCalculator(initialPlotData) : initialPlotData;
116
+ const dimensions = getDimensions(props);
117
+ const chartConfigs = getChartConfigWithUpdatedYScales(getNewChartConfig(dimensions, children), { plotData, xAccessor, displayXAccessor, fullData }, xScale.domain());
118
+ return Object.assign(Object.assign({}, state), { xScale,
119
+ plotData,
120
+ chartConfigs });
121
+ };
122
+ const updateChart = (newState, initialXScale, props, lastItemWasVisible, initialChartConfig) => {
123
+ const { fullData, xScale, xAccessor, displayXAccessor, filterData } = newState;
124
+ const lastItem = last(fullData);
125
+ const lastXItem = xAccessor(lastItem);
126
+ const [start, end] = initialXScale.domain();
127
+ const { postCalculator, children, padding, flipXScale, maintainPointsPerPixelOnResize } = props;
128
+ const direction = getXScaleDirection(flipXScale);
129
+ const dimensions = getDimensions(props);
130
+ const updatedXScale = setXRange(xScale, dimensions, padding, direction);
131
+ let initialPlotData;
132
+ if (!lastItemWasVisible || end >= lastXItem) {
133
+ // resize comes here...
134
+ // get plotData between [start, end] and do not change the domain
135
+ const [rangeStart, rangeEnd] = initialXScale.range();
136
+ const [newRangeStart, newRangeEnd] = updatedXScale.range();
137
+ const newDomainExtent = ((newRangeEnd - newRangeStart) / (rangeEnd - rangeStart)) * (end.valueOf() - start.valueOf());
138
+ const newStart = maintainPointsPerPixelOnResize ? end.valueOf() - newDomainExtent : start;
139
+ const lastItemX = initialXScale(lastXItem);
140
+ const response = filterData(fullData, [newStart, end], xAccessor, updatedXScale, {
141
+ fallbackStart: start,
142
+ fallbackEnd: { lastItem, lastItemX },
143
+ });
144
+ initialPlotData = response.plotData;
145
+ updatedXScale.domain(response.domain);
146
+ }
147
+ else if (lastItemWasVisible && end < lastXItem) {
148
+ // this is when a new item is added and last item was visible
149
+ // so slide over and show the new item also
150
+ // get plotData between [xAccessor(l) - (end - start), xAccessor(l)] and DO change the domain
151
+ const dx = initialXScale(lastXItem) - initialXScale.range()[1];
152
+ const [newStart, newEnd] = initialXScale
153
+ .range()
154
+ .map((x) => x + dx)
155
+ .map((x) => initialXScale.invert(x));
156
+ const response = filterData(fullData, [newStart, newEnd], xAccessor, updatedXScale);
157
+ initialPlotData = response.plotData;
158
+ updatedXScale.domain(response.domain); // if last item was visible, then shift
159
+ }
160
+ const plotData = postCalculator(initialPlotData);
161
+ const chartConfigs = getChartConfigWithUpdatedYScales(getNewChartConfig(dimensions, children, initialChartConfig), { plotData, xAccessor, displayXAccessor, fullData }, updatedXScale.domain());
162
+ return {
163
+ xScale: updatedXScale,
164
+ xAccessor,
165
+ chartConfigs,
166
+ plotData,
167
+ fullData,
168
+ filterData,
169
+ };
170
+ };
171
+ const calculateState = (props) => {
172
+ const { xAccessor: inputXAccessor, xExtents: xExtentsProp, data, padding, flipXScale } = props;
173
+ const direction = getXScaleDirection(flipXScale);
174
+ const dimensions = getDimensions(props);
175
+ const extent = typeof xExtentsProp === "function"
176
+ ? xExtentsProp(data)
177
+ : d3Extent(xExtentsProp.map((d) => functor(d)).map((each) => each(data, inputXAccessor)));
178
+ const { xAccessor, displayXAccessor, xScale, fullData, filterData } = calculateFullData(props);
179
+ const updatedXScale = setXRange(xScale, dimensions, padding, direction);
180
+ const { plotData, domain } = filterData(fullData, extent, inputXAccessor, updatedXScale);
181
+ return {
182
+ plotData,
183
+ xScale: updatedXScale.domain(domain),
184
+ xAccessor,
185
+ displayXAccessor,
186
+ fullData,
187
+ filterData,
188
+ };
189
+ };
190
+ const setXRange = (xScale, dimensions, padding, direction = 1) => {
191
+ if (xScale.rangeRoundPoints) {
192
+ if (isNaN(padding)) {
193
+ throw new Error("padding has to be a number for ordinal scale");
194
+ }
195
+ xScale.rangeRoundPoints([0, dimensions.width], padding);
196
+ }
197
+ else if (xScale.padding) {
198
+ if (isNaN(padding)) {
199
+ throw new Error("padding has to be a number for ordinal scale");
200
+ }
201
+ xScale.range([0, dimensions.width]);
202
+ xScale.padding(padding / 2);
203
+ }
204
+ else {
205
+ const { left, right } = isNaN(padding) ? padding : { left: padding, right: padding };
206
+ if (direction > 0) {
207
+ xScale.range([left, dimensions.width - right]);
208
+ }
209
+ else {
210
+ xScale.range([dimensions.width - right, left]);
211
+ }
212
+ }
213
+ return xScale;
214
+ };
215
+ const pinchCoordinates = (pinch) => {
216
+ const { touch1Pos, touch2Pos } = pinch;
217
+ return {
218
+ topLeft: [Math.min(touch1Pos[0], touch2Pos[0]), Math.min(touch1Pos[1], touch2Pos[1])],
219
+ bottomRight: [Math.max(touch1Pos[0], touch2Pos[0]), Math.max(touch1Pos[1], touch2Pos[1])],
220
+ };
221
+ };
222
+ const isInteractionEnabled = (xScale, xAccessor, data) => {
223
+ const interaction = !isNaN(xScale(xAccessor(head(data)))) && isDefined(xScale.invert);
224
+ return interaction;
225
+ };
226
+ export class ChartCanvas extends React.Component {
227
+ constructor(props) {
228
+ super(props);
229
+ this.canvasContainerRef = React.createRef();
230
+ this.eventCaptureRef = React.createRef();
231
+ this.lastSubscriptionId = 0;
232
+ this.mutableState = { mouseXY: [0, 0], currentCharts: [], currentItem: null };
233
+ this.panInProgress = false;
234
+ this.subscriptions = [];
235
+ this.getMutableState = () => {
236
+ return this.mutableState;
237
+ };
238
+ this.getCanvasContexts = () => {
239
+ var _a;
240
+ return (_a = this.canvasContainerRef.current) === null || _a === void 0 ? void 0 : _a.getCanvasContexts();
241
+ };
242
+ this.generateSubscriptionId = () => {
243
+ this.lastSubscriptionId++;
244
+ return this.lastSubscriptionId;
245
+ };
246
+ this.subscribe = (id, rest) => {
247
+ const { getPanConditions = functor({
248
+ draggable: false,
249
+ panEnabled: true,
250
+ }), } = rest;
251
+ this.subscriptions = this.subscriptions.concat(Object.assign(Object.assign({ id }, rest), { getPanConditions }));
252
+ };
253
+ this.unsubscribe = (id) => {
254
+ this.subscriptions = this.subscriptions.filter((each) => each.id !== id);
255
+ };
256
+ this.getAllPanConditions = () => {
257
+ return this.subscriptions.map((each) => each.getPanConditions());
258
+ };
259
+ this.setCursorClass = (className) => {
260
+ var _a;
261
+ (_a = this.eventCaptureRef.current) === null || _a === void 0 ? void 0 : _a.setCursorClass(className);
262
+ };
263
+ this.amIOnTop = (id) => {
264
+ const dragableComponents = this.subscriptions.filter((each) => each.getPanConditions().draggable);
265
+ return dragableComponents.length > 0 && last(dragableComponents).id === id;
266
+ };
267
+ this.handleContextMenu = (mouseXY, e) => {
268
+ const { xAccessor, chartConfigs, plotData, xScale } = this.state;
269
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
270
+ const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
271
+ this.triggerEvent("contextmenu", {
272
+ mouseXY,
273
+ currentItem,
274
+ currentCharts,
275
+ }, e);
276
+ };
277
+ this.calculateStateForDomain = (newDomain) => {
278
+ const { xAccessor, displayXAccessor, xScale: initialXScale, chartConfigs: initialChartConfig, plotData: initialPlotData, } = this.state;
279
+ const { filterData, fullData } = this.state;
280
+ const { postCalculator = ChartCanvas.defaultProps.postCalculator } = this.props;
281
+ const { plotData: beforePlotData, domain } = filterData(fullData, newDomain, xAccessor, initialXScale, {
282
+ currentPlotData: initialPlotData,
283
+ currentDomain: initialXScale.domain(),
284
+ });
285
+ const plotData = postCalculator(beforePlotData);
286
+ const updatedScale = initialXScale.copy().domain(domain);
287
+ const chartConfigs = getChartConfigWithUpdatedYScales(initialChartConfig, { plotData, xAccessor, displayXAccessor, fullData }, updatedScale.domain());
288
+ return {
289
+ xScale: updatedScale,
290
+ plotData,
291
+ chartConfigs,
292
+ };
293
+ };
294
+ this.pinchZoomHelper = (initialPinch, finalPinch) => {
295
+ const { xScale: initialPinchXScale } = initialPinch;
296
+ const { xScale: initialXScale, chartConfigs: initialChartConfig, plotData: initialPlotData, xAccessor, displayXAccessor, filterData, fullData, } = this.state;
297
+ const { postCalculator = ChartCanvas.defaultProps.postCalculator } = this.props;
298
+ const { topLeft: iTL, bottomRight: iBR } = pinchCoordinates(initialPinch);
299
+ const { topLeft: fTL, bottomRight: fBR } = pinchCoordinates(finalPinch);
300
+ const e = initialPinchXScale.range()[1];
301
+ const xDash = Math.round(-(iBR[0] * fTL[0] - iTL[0] * fBR[0]) / (iTL[0] - iBR[0]));
302
+ const yDash = Math.round(e + ((e - iBR[0]) * (e - fTL[0]) - (e - iTL[0]) * (e - fBR[0])) / (e - iTL[0] - (e - iBR[0])));
303
+ const x = Math.round((-xDash * iTL[0]) / (-xDash + fTL[0]));
304
+ const y = Math.round(e - ((yDash - e) * (e - iTL[0])) / (yDash + (e - fTL[0])));
305
+ const newDomain = [x, y].map(initialPinchXScale.invert);
306
+ const { plotData: beforePlotData, domain } = filterData(fullData, newDomain, xAccessor, initialPinchXScale, {
307
+ currentPlotData: initialPlotData,
308
+ currentDomain: initialXScale.domain(),
309
+ });
310
+ const plotData = postCalculator(beforePlotData);
311
+ const updatedScale = initialXScale.copy().domain(domain);
312
+ const mouseXY = finalPinch.touch1Pos;
313
+ const chartConfigs = getChartConfigWithUpdatedYScales(initialChartConfig, { plotData, xAccessor, displayXAccessor, fullData }, updatedScale.domain());
314
+ const currentItem = getCurrentItem(updatedScale, xAccessor, mouseXY, plotData);
315
+ return {
316
+ chartConfigs,
317
+ xScale: updatedScale,
318
+ plotData,
319
+ mouseXY,
320
+ currentItem,
321
+ xAccessor,
322
+ fullData,
323
+ };
324
+ };
325
+ this.handlePinchZoom = (initialPinch, finalPinch, e) => {
326
+ if (!this.waitingForPinchZoomAnimationFrame) {
327
+ this.waitingForPinchZoomAnimationFrame = true;
328
+ const state = this.pinchZoomHelper(initialPinch, finalPinch);
329
+ this.triggerEvent("pinchzoom", state, e);
330
+ this.finalPinch = finalPinch;
331
+ requestAnimationFrame(() => {
332
+ this.clearBothCanvas();
333
+ this.draw({ trigger: "pinchzoom" });
334
+ this.waitingForPinchZoomAnimationFrame = false;
335
+ });
336
+ }
337
+ };
338
+ this.handlePinchZoomEnd = (initialPinch, e) => {
339
+ const { xAccessor = ChartCanvas.defaultProps.xAccessor } = this.state;
340
+ if (this.finalPinch) {
341
+ const state = this.pinchZoomHelper(initialPinch, this.finalPinch);
342
+ const { xScale, fullData } = state;
343
+ this.triggerEvent("pinchzoom", state, e);
344
+ this.finalPinch = undefined;
345
+ this.clearThreeCanvas();
346
+ const firstItem = head(fullData);
347
+ const scale_start = head(xScale.domain());
348
+ const data_start = xAccessor(firstItem);
349
+ const lastItem = last(fullData);
350
+ const scale_end = last(xScale.domain());
351
+ const data_end = xAccessor(lastItem);
352
+ const { onLoadAfter, onLoadBefore } = this.props;
353
+ this.setState(state, () => {
354
+ if (scale_start < data_start) {
355
+ if (onLoadBefore !== undefined) {
356
+ onLoadBefore(scale_start, data_start);
357
+ }
358
+ }
359
+ if (data_end < scale_end) {
360
+ if (onLoadAfter !== undefined) {
361
+ onLoadAfter(data_end, scale_end);
362
+ }
363
+ }
364
+ });
365
+ }
366
+ };
367
+ this.handleZoom = (zoomDirection, mouseXY, e) => {
368
+ if (this.panInProgress) {
369
+ return;
370
+ }
371
+ const { xAccessor, xScale: initialXScale, plotData: initialPlotData, fullData } = this.state;
372
+ const { zoomMultiplier = ChartCanvas.defaultProps.zoomMultiplier, zoomAnchor = ChartCanvas.defaultProps.zoomAnchor, } = this.props;
373
+ const item = zoomAnchor({
374
+ xScale: initialXScale,
375
+ xAccessor: xAccessor,
376
+ mouseXY,
377
+ plotData: initialPlotData,
378
+ });
379
+ const cx = initialXScale(item);
380
+ const c = zoomDirection > 0 ? 1 * zoomMultiplier : 1 / zoomMultiplier;
381
+ const newDomain = initialXScale
382
+ .range()
383
+ .map((x) => cx + (x - cx) * c)
384
+ .map((x) => initialXScale.invert(x));
385
+ const { xScale, plotData, chartConfigs } = this.calculateStateForDomain(newDomain);
386
+ const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
387
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
388
+ this.clearThreeCanvas();
389
+ const firstItem = head(fullData);
390
+ const scale_start = head(xScale.domain());
391
+ const data_start = xAccessor(firstItem);
392
+ const lastItem = last(fullData);
393
+ const scale_end = last(xScale.domain());
394
+ const data_end = xAccessor(lastItem);
395
+ this.mutableState = {
396
+ mouseXY,
397
+ currentItem,
398
+ currentCharts,
399
+ };
400
+ this.triggerEvent("zoom", {
401
+ xScale,
402
+ plotData,
403
+ chartConfigs,
404
+ mouseXY,
405
+ currentCharts,
406
+ currentItem,
407
+ show: true,
408
+ }, e);
409
+ const { onLoadAfter, onLoadBefore } = this.props;
410
+ this.setState({
411
+ xScale,
412
+ plotData,
413
+ chartConfigs,
414
+ }, () => {
415
+ if (scale_start < data_start) {
416
+ if (onLoadBefore !== undefined) {
417
+ onLoadBefore(scale_start, data_start);
418
+ }
419
+ }
420
+ if (data_end < scale_end) {
421
+ if (onLoadAfter !== undefined) {
422
+ onLoadAfter(data_end, scale_end);
423
+ }
424
+ }
425
+ });
426
+ };
427
+ this.xAxisZoom = (newDomain) => {
428
+ const { xScale, plotData, chartConfigs } = this.calculateStateForDomain(newDomain);
429
+ this.clearThreeCanvas();
430
+ const { xAccessor, fullData } = this.state;
431
+ const firstItem = head(fullData);
432
+ const scale_start = head(xScale.domain());
433
+ const data_start = xAccessor(firstItem);
434
+ const lastItem = last(fullData);
435
+ const scale_end = last(xScale.domain());
436
+ const data_end = xAccessor(lastItem);
437
+ const { onLoadAfter, onLoadBefore } = this.props;
438
+ this.setState({
439
+ xScale,
440
+ plotData,
441
+ chartConfigs,
442
+ }, () => {
443
+ if (scale_start < data_start) {
444
+ if (onLoadBefore !== undefined) {
445
+ onLoadBefore(scale_start, data_start);
446
+ }
447
+ }
448
+ if (data_end < scale_end) {
449
+ if (onLoadAfter !== undefined) {
450
+ onLoadAfter(data_end, scale_end);
451
+ }
452
+ }
453
+ });
454
+ };
455
+ this.yAxisZoom = (chartId, newDomain) => {
456
+ this.clearThreeCanvas();
457
+ const { chartConfigs: initialChartConfig } = this.state;
458
+ const chartConfigs = initialChartConfig.map((each) => {
459
+ if (each.id === chartId) {
460
+ const { yScale } = each;
461
+ return Object.assign(Object.assign({}, each), { yScale: yScale.copy().domain(newDomain), yPanEnabled: true });
462
+ }
463
+ else {
464
+ return each;
465
+ }
466
+ });
467
+ this.setState({
468
+ chartConfigs,
469
+ });
470
+ };
471
+ this.draw = (props) => {
472
+ this.subscriptions.forEach((each) => {
473
+ if (isDefined(each.draw)) {
474
+ each.draw(props);
475
+ }
476
+ });
477
+ };
478
+ this.redraw = () => {
479
+ this.clearThreeCanvas();
480
+ this.draw({ force: true });
481
+ };
482
+ this.panHelper = (mouseXY, initialXScale, { dx, dy }, chartsToPan) => {
483
+ const { xAccessor, displayXAccessor, chartConfigs: initialChartConfig, filterData, fullData } = this.state;
484
+ const { postCalculator = ChartCanvas.defaultProps.postCalculator } = this.props;
485
+ const newDomain = initialXScale
486
+ .range()
487
+ .map((x) => x - dx)
488
+ .map((x) => initialXScale.invert(x));
489
+ const { plotData: beforePlotData, domain } = filterData(fullData, newDomain, xAccessor, initialXScale, {
490
+ currentPlotData: this.hackyWayToStopPanBeyondBounds__plotData,
491
+ currentDomain: this.hackyWayToStopPanBeyondBounds__domain,
492
+ ignoreThresholds: true,
493
+ });
494
+ const updatedScale = initialXScale.copy().domain(domain);
495
+ const plotData = postCalculator(beforePlotData);
496
+ const currentItem = getCurrentItem(updatedScale, xAccessor, mouseXY, plotData);
497
+ const chartConfigs = getChartConfigWithUpdatedYScales(initialChartConfig, { plotData, xAccessor, displayXAccessor, fullData }, updatedScale.domain(), dy, chartsToPan);
498
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
499
+ return {
500
+ xScale: updatedScale,
501
+ plotData,
502
+ chartConfigs,
503
+ mouseXY,
504
+ currentCharts,
505
+ currentItem,
506
+ };
507
+ };
508
+ this.handlePan = (mousePosition, panStartXScale, dxdy, chartsToPan, e) => {
509
+ var _a, _b;
510
+ if (this.waitingForPanAnimationFrame) {
511
+ return;
512
+ }
513
+ this.waitingForPanAnimationFrame = true;
514
+ this.hackyWayToStopPanBeyondBounds__plotData =
515
+ (_a = this.hackyWayToStopPanBeyondBounds__plotData) !== null && _a !== void 0 ? _a : this.state.plotData;
516
+ this.hackyWayToStopPanBeyondBounds__domain =
517
+ (_b = this.hackyWayToStopPanBeyondBounds__domain) !== null && _b !== void 0 ? _b : this.state.xScale.domain();
518
+ const newState = this.panHelper(mousePosition, panStartXScale, dxdy, chartsToPan);
519
+ this.hackyWayToStopPanBeyondBounds__plotData = newState.plotData;
520
+ this.hackyWayToStopPanBeyondBounds__domain = newState.xScale.domain();
521
+ this.panInProgress = true;
522
+ this.triggerEvent("pan", newState, e);
523
+ this.mutableState = {
524
+ mouseXY: newState.mouseXY,
525
+ currentItem: newState.currentItem,
526
+ currentCharts: newState.currentCharts,
527
+ };
528
+ requestAnimationFrame(() => {
529
+ this.waitingForPanAnimationFrame = false;
530
+ this.clearBothCanvas();
531
+ this.draw({ trigger: "pan" });
532
+ });
533
+ };
534
+ this.handlePanEnd = (mousePosition, panStartXScale, dxdy, chartsToPan, e) => {
535
+ const state = this.panHelper(mousePosition, panStartXScale, dxdy, chartsToPan);
536
+ this.hackyWayToStopPanBeyondBounds__plotData = null;
537
+ this.hackyWayToStopPanBeyondBounds__domain = null;
538
+ this.panInProgress = false;
539
+ const { xScale, plotData, chartConfigs } = state;
540
+ this.triggerEvent("panend", state, e);
541
+ requestAnimationFrame(() => {
542
+ const { xAccessor, fullData } = this.state;
543
+ const firstItem = head(fullData);
544
+ const scale_start = head(xScale.domain());
545
+ const data_start = xAccessor(firstItem);
546
+ const lastItem = last(fullData);
547
+ const scale_end = last(xScale.domain());
548
+ const data_end = xAccessor(lastItem);
549
+ const { onLoadAfter, onLoadBefore } = this.props;
550
+ this.clearThreeCanvas();
551
+ this.setState({
552
+ xScale,
553
+ plotData,
554
+ chartConfigs,
555
+ }, () => {
556
+ if (scale_start < data_start) {
557
+ if (onLoadBefore !== undefined) {
558
+ onLoadBefore(scale_start, data_start);
559
+ }
560
+ }
561
+ if (data_end < scale_end) {
562
+ if (onLoadAfter !== undefined) {
563
+ onLoadAfter(data_end, scale_end);
564
+ }
565
+ }
566
+ });
567
+ });
568
+ };
569
+ this.handleMouseDown = (_, __, e) => {
570
+ this.triggerEvent("mousedown", this.mutableState, e);
571
+ };
572
+ this.handleMouseEnter = (e) => {
573
+ this.triggerEvent("mouseenter", {
574
+ show: true,
575
+ }, e);
576
+ };
577
+ this.handleMouseMove = (mouseXY, _, e) => {
578
+ if (this.waitingForMouseMoveAnimationFrame) {
579
+ return;
580
+ }
581
+ this.waitingForMouseMoveAnimationFrame = true;
582
+ const { chartConfigs, plotData, xScale, xAccessor } = this.state;
583
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
584
+ const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
585
+ this.triggerEvent("mousemove", {
586
+ show: true,
587
+ mouseXY,
588
+ // prevMouseXY is used in interactive components
589
+ prevMouseXY: this.prevMouseXY,
590
+ currentItem,
591
+ currentCharts,
592
+ }, e);
593
+ this.prevMouseXY = mouseXY;
594
+ this.mutableState = {
595
+ mouseXY,
596
+ currentItem,
597
+ currentCharts,
598
+ };
599
+ requestAnimationFrame(() => {
600
+ this.clearMouseCanvas();
601
+ this.draw({ trigger: "mousemove" });
602
+ this.waitingForMouseMoveAnimationFrame = false;
603
+ });
604
+ };
605
+ this.handleMouseLeave = (e) => {
606
+ this.triggerEvent("mouseleave", { show: false }, e);
607
+ this.clearMouseCanvas();
608
+ this.draw({ trigger: "mouseleave" });
609
+ };
610
+ this.handleDragStart = ({ startPos }, e) => {
611
+ this.triggerEvent("dragstart", { startPos }, e);
612
+ };
613
+ this.handleDrag = ({ startPos, mouseXY }, e) => {
614
+ const { chartConfigs, plotData, xScale, xAccessor } = this.state;
615
+ const currentCharts = getCurrentCharts(chartConfigs, mouseXY);
616
+ const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
617
+ this.triggerEvent("drag", {
618
+ startPos,
619
+ mouseXY,
620
+ currentItem,
621
+ currentCharts,
622
+ }, e);
623
+ this.mutableState = {
624
+ mouseXY,
625
+ currentItem,
626
+ currentCharts,
627
+ };
628
+ requestAnimationFrame(() => {
629
+ this.clearMouseCanvas();
630
+ this.draw({ trigger: "drag" });
631
+ });
632
+ };
633
+ this.handleDragEnd = ({ mouseXY }, e) => {
634
+ this.triggerEvent("dragend", { mouseXY }, e);
635
+ requestAnimationFrame(() => {
636
+ this.clearMouseCanvas();
637
+ this.draw({ trigger: "dragend" });
638
+ });
639
+ };
640
+ this.handleClick = (_, e) => {
641
+ this.triggerEvent("click", this.mutableState, e);
642
+ requestAnimationFrame(() => {
643
+ this.clearMouseCanvas();
644
+ this.draw({ trigger: "click" });
645
+ });
646
+ };
647
+ this.handleDoubleClick = (_, e) => {
648
+ this.triggerEvent("dblclick", {}, e);
649
+ };
650
+ this.resetYDomain = (chartId) => {
651
+ const { chartConfigs } = this.state;
652
+ let changed = false;
653
+ const newChartConfig = chartConfigs.map((each) => {
654
+ if ((isNotDefined(chartId) || each.id === chartId) &&
655
+ !shallowEqual(each.yScale.domain(), each.realYDomain)) {
656
+ changed = true;
657
+ return Object.assign(Object.assign({}, each), { yScale: each.yScale.domain(each.realYDomain), yPanEnabled: false });
658
+ }
659
+ return each;
660
+ });
661
+ if (changed) {
662
+ this.clearThreeCanvas();
663
+ this.setState({
664
+ chartConfigs: newChartConfig,
665
+ });
666
+ }
667
+ };
668
+ this.state = resetChart(props);
669
+ }
670
+ static getDerivedStateFromProps(props, state) {
671
+ var _a;
672
+ const { chartConfigs: initialChartConfig, plotData, xAccessor, xScale } = state;
673
+ const interaction = isInteractionEnabled(xScale, xAccessor, plotData);
674
+ const shouldReset = shouldResetChart(state.lastProps || {}, props);
675
+ let newState;
676
+ if (!interaction || shouldReset || !shallowEqual((_a = state.lastProps) === null || _a === void 0 ? void 0 : _a.xExtents, props.xExtents)) {
677
+ // do reset
678
+ newState = resetChart(props);
679
+ }
680
+ else {
681
+ const [start, end] = xScale.domain();
682
+ const prevLastItem = last(state.fullData);
683
+ const calculatedState = calculateFullData(props);
684
+ const { xAccessor } = calculatedState;
685
+ const previousX = xAccessor(prevLastItem);
686
+ const lastItemWasVisible = previousX <= end && previousX >= start;
687
+ newState = updateChart(calculatedState, xScale, props, lastItemWasVisible, initialChartConfig);
688
+ }
689
+ return Object.assign(Object.assign({}, newState), { lastProps: props, propIteration: (state.propIteration || 0) + 1 });
690
+ }
691
+ getSnapshotBeforeUpdate(prevProps, prevState) {
692
+ // propIteration is incremented when the props change to differentiate between state updates
693
+ // and prop updates
694
+ if (prevState.propIteration !== this.state.propIteration && !this.panInProgress) {
695
+ this.clearThreeCanvas();
696
+ }
697
+ return null;
698
+ }
699
+ componentDidUpdate(prevProps) {
700
+ if (prevProps.data !== this.props.data) {
701
+ this.triggerEvent("dataupdated", {
702
+ chartConfigs: this.state.chartConfigs,
703
+ xScale: this.state.xScale,
704
+ plotData: this.state.plotData,
705
+ });
706
+ }
707
+ }
708
+ clearBothCanvas() {
709
+ const canvases = this.getCanvasContexts();
710
+ if (canvases && canvases.axes && canvases.mouseCoord) {
711
+ clearCanvas([canvases.axes, canvases.mouseCoord], this.props.ratio);
712
+ }
713
+ }
714
+ clearMouseCanvas() {
715
+ const canvases = this.getCanvasContexts();
716
+ if (canvases && canvases.mouseCoord) {
717
+ clearCanvas([canvases.mouseCoord], this.props.ratio);
718
+ }
719
+ }
720
+ clearThreeCanvas() {
721
+ const canvases = this.getCanvasContexts();
722
+ if (canvases && canvases.axes && canvases.mouseCoord && canvases.bg) {
723
+ clearCanvas([canvases.axes, canvases.mouseCoord, canvases.bg], this.props.ratio);
724
+ }
725
+ }
726
+ cancelDrag() {
727
+ var _a;
728
+ (_a = this.eventCaptureRef.current) === null || _a === void 0 ? void 0 : _a.cancelDrag();
729
+ this.triggerEvent("dragcancel");
730
+ }
731
+ triggerEvent(type, props, e) {
732
+ this.subscriptions.forEach((each) => {
733
+ const state = Object.assign(Object.assign({}, this.state), { subscriptions: this.subscriptions });
734
+ each.listener(type, props, state, e);
735
+ });
736
+ }
737
+ // TODO: Memoize this
738
+ getContextValues() {
739
+ const dimensions = getDimensions(this.props);
740
+ return {
741
+ chartId: -1,
742
+ fullData: this.state.fullData,
743
+ plotData: this.state.plotData,
744
+ width: dimensions.width,
745
+ height: dimensions.height,
746
+ chartConfigs: this.state.chartConfigs,
747
+ xScale: this.state.xScale,
748
+ xAccessor: this.state.xAccessor,
749
+ displayXAccessor: this.state.displayXAccessor,
750
+ margin: this.props.margin,
751
+ ratio: this.props.ratio,
752
+ xAxisZoom: this.xAxisZoom,
753
+ yAxisZoom: this.yAxisZoom,
754
+ getCanvasContexts: this.getCanvasContexts,
755
+ redraw: this.redraw,
756
+ subscribe: this.subscribe,
757
+ unsubscribe: this.unsubscribe,
758
+ generateSubscriptionId: this.generateSubscriptionId,
759
+ getMutableState: this.getMutableState,
760
+ amIOnTop: this.amIOnTop,
761
+ setCursorClass: this.setCursorClass,
762
+ };
763
+ }
764
+ shouldComponentUpdate() {
765
+ return !this.panInProgress;
766
+ }
767
+ render() {
768
+ const { disableInteraction, disablePan, disableZoom, useCrossHairStyleCursor, onClick, onDoubleClick, height, width, margin = ChartCanvas.defaultProps.margin, className, zIndex = ChartCanvas.defaultProps.zIndex, defaultFocus, ratio, mouseMoveEvent, } = this.props;
769
+ const { plotData, xScale, xAccessor, chartConfigs } = this.state;
770
+ const dimensions = getDimensions(this.props);
771
+ const interaction = isInteractionEnabled(xScale, xAccessor, plotData);
772
+ const cursorStyle = useCrossHairStyleCursor && interaction;
773
+ const cursor = getCursorStyle();
774
+ return (_jsx(ChartCanvasContext.Provider, Object.assign({ value: this.getContextValues() }, { children: _jsxs("div", Object.assign({ style: { position: "relative", width, height }, className: className, onClick: onClick, onDoubleClick: onDoubleClick }, { children: [_jsx(CanvasContainer, { ref: this.canvasContainerRef, ratio: ratio, width: width, height: height, style: { height, zIndex, width } }), _jsxs("svg", Object.assign({ className: className, width: width, height: height, style: { position: "absolute", zIndex: zIndex + 5 } }, { children: [cursor, _jsxs("defs", { children: [_jsx("clipPath", Object.assign({ id: "chart-area-clip" }, { children: _jsx("rect", { x: "0", y: "0", width: dimensions.width, height: dimensions.height }) })), chartConfigs.map((each, idx) => (_jsx("clipPath", Object.assign({ id: `chart-area-clip-${each.id}` }, { children: _jsx("rect", { x: "0", y: "0", width: each.width, height: each.height }) }), idx)))] }), _jsxs("g", Object.assign({ transform: `translate(${margin.left + 0.5}, ${margin.top + 0.5})` }, { children: [_jsx(EventCapture, { ref: this.eventCaptureRef, useCrossHairStyleCursor: cursorStyle, mouseMove: mouseMoveEvent && interaction, zoom: !disableZoom && interaction, pan: !disablePan && interaction, width: dimensions.width, height: dimensions.height, chartConfig: chartConfigs, xScale: xScale, xAccessor: xAccessor, focus: defaultFocus, disableInteraction: disableInteraction, getAllPanConditions: this.getAllPanConditions, onContextMenu: this.handleContextMenu, onClick: this.handleClick, onDoubleClick: this.handleDoubleClick, onMouseDown: this.handleMouseDown, onMouseMove: this.handleMouseMove, onMouseEnter: this.handleMouseEnter, onMouseLeave: this.handleMouseLeave, onDragStart: this.handleDragStart, onDrag: this.handleDrag, onDragComplete: this.handleDragEnd, onZoom: this.handleZoom, onPinchZoom: this.handlePinchZoom, onPinchZoomEnd: this.handlePinchZoomEnd, onPan: this.handlePan, onPanEnd: this.handlePanEnd }), _jsx("g", Object.assign({ className: "react-financial-charts-avoid-interaction" }, { children: this.props.children }))] }))] }))] })) })));
775
+ }
776
+ }
777
+ ChartCanvas.defaultProps = {
778
+ clamp: false,
779
+ className: "react-financial-charts",
780
+ defaultFocus: true,
781
+ disablePan: false,
782
+ disableInteraction: false,
783
+ disableZoom: false,
784
+ flipXScale: false,
785
+ maintainPointsPerPixelOnResize: true,
786
+ margin: { top: 0, right: 40, bottom: 40, left: 0 },
787
+ minPointsPerPxThreshold: 1 / 100,
788
+ mouseMoveEvent: true,
789
+ postCalculator: identity,
790
+ padding: 0,
791
+ pointsPerPxThreshold: 2,
792
+ useCrossHairStyleCursor: true,
793
+ xAccessor: identity,
794
+ xExtents: [min, max],
795
+ zIndex: 1,
796
+ zoomAnchor: mouseBasedZoomAnchor,
797
+ zoomMultiplier: 1.1,
798
+ };
799
+ //# sourceMappingURL=ChartCanvas.js.map