@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.
- package/LICENSE +24 -0
- package/README.md +5 -0
- package/lib/CanvasContainer.d.ts +19 -0
- package/lib/CanvasContainer.js +28 -0
- package/lib/CanvasContainer.js.map +1 -0
- package/lib/Chart.d.ts +32 -0
- package/lib/Chart.js +57 -0
- package/lib/Chart.js.map +1 -0
- package/lib/ChartCanvas.d.ts +235 -0
- package/lib/ChartCanvas.js +810 -0
- package/lib/ChartCanvas.js.map +1 -0
- package/lib/EventCapture.d.ts +131 -0
- package/lib/EventCapture.js +489 -0
- package/lib/EventCapture.js.map +1 -0
- package/lib/GenericChartComponent.d.ts +21 -0
- package/lib/GenericChartComponent.js +75 -0
- package/lib/GenericChartComponent.js.map +1 -0
- package/lib/GenericComponent.d.ts +81 -0
- package/lib/GenericComponent.js +355 -0
- package/lib/GenericComponent.js.map +1 -0
- package/lib/MoreProps.d.ts +16 -0
- package/lib/MoreProps.js +2 -0
- package/lib/MoreProps.js.map +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +8 -0
- package/lib/index.js.map +1 -0
- package/lib/useEvent.d.ts +1 -0
- package/lib/useEvent.js +13 -0
- package/lib/useEvent.js.map +1 -0
- package/lib/utils/ChartDataUtil.d.ts +49 -0
- package/lib/utils/ChartDataUtil.js +205 -0
- package/lib/utils/ChartDataUtil.js.map +1 -0
- package/lib/utils/PureComponent.d.ts +4 -0
- package/lib/utils/PureComponent.js +10 -0
- package/lib/utils/PureComponent.js.map +1 -0
- package/lib/utils/accumulatingWindow.d.ts +15 -0
- package/lib/utils/accumulatingWindow.js +98 -0
- package/lib/utils/accumulatingWindow.js.map +1 -0
- package/lib/utils/barWidth.d.ts +15 -0
- package/lib/utils/barWidth.js +27 -0
- package/lib/utils/barWidth.js.map +1 -0
- package/lib/utils/closestItem.d.ts +5 -0
- package/lib/utils/closestItem.js +45 -0
- package/lib/utils/closestItem.js.map +1 -0
- package/lib/utils/evaluator.d.ts +7 -0
- package/lib/utils/evaluator.js +94 -0
- package/lib/utils/evaluator.js.map +1 -0
- package/lib/utils/identity.d.ts +1 -0
- package/lib/utils/identity.js +2 -0
- package/lib/utils/identity.js.map +1 -0
- package/lib/utils/index.d.ts +46 -0
- package/lib/utils/index.js +126 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/noop.d.ts +1 -0
- package/lib/utils/noop.js +3 -0
- package/lib/utils/noop.js.map +1 -0
- package/lib/utils/shallowEqual.d.ts +1 -0
- package/lib/utils/shallowEqual.js +22 -0
- package/lib/utils/shallowEqual.js.map +1 -0
- package/lib/utils/slidingWindow.d.ts +19 -0
- package/lib/utils/slidingWindow.js +109 -0
- package/lib/utils/slidingWindow.js.map +1 -0
- package/lib/utils/strokeDasharray.d.ts +3 -0
- package/lib/utils/strokeDasharray.js +37 -0
- package/lib/utils/strokeDasharray.js.map +1 -0
- package/lib/utils/zipper.d.ts +7 -0
- package/lib/utils/zipper.js +36 -0
- package/lib/utils/zipper.js.map +1 -0
- package/lib/zoom/index.d.ts +1 -0
- package/lib/zoom/index.js +2 -0
- package/lib/zoom/index.js.map +1 -0
- package/lib/zoom/zoomBehavior.d.ts +10 -0
- package/lib/zoom/zoomBehavior.js +18 -0
- package/lib/zoom/zoomBehavior.js.map +1 -0
- package/package.json +52 -0
- package/src/CanvasContainer.tsx +44 -0
- package/src/Chart.tsx +114 -0
- package/src/ChartCanvas.tsx +1336 -0
- package/src/EventCapture.tsx +709 -0
- package/src/GenericChartComponent.tsx +98 -0
- package/src/GenericComponent.tsx +454 -0
- package/src/MoreProps.ts +17 -0
- package/src/index.ts +7 -0
- package/src/useEvent.ts +14 -0
- package/src/utils/ChartDataUtil.ts +297 -0
- package/src/utils/PureComponent.tsx +12 -0
- package/src/utils/accumulatingWindow.ts +118 -0
- package/src/utils/barWidth.ts +44 -0
- package/src/utils/closestItem.ts +60 -0
- package/src/utils/evaluator.ts +163 -0
- package/src/utils/identity.ts +1 -0
- package/src/utils/index.ts +153 -0
- package/src/utils/noop.ts +2 -0
- package/src/utils/shallowEqual.ts +25 -0
- package/src/utils/slidingWindow.ts +140 -0
- package/src/utils/strokeDasharray.ts +52 -0
- package/src/utils/zipper.ts +45 -0
- package/src/zoom/index.ts +1 -0
- 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
|
+
}
|