@gravity-ui/charts 1.20.0 → 1.21.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/dist/cjs/components/AxisY/AxisY.js +8 -1
- package/dist/cjs/components/AxisY/prepare-axis-data.js +39 -12
- package/dist/cjs/components/AxisY/types.d.ts +3 -0
- package/dist/cjs/components/ChartInner/index.js +18 -7
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +8 -11
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +39 -104
- package/dist/cjs/components/ChartInner/useChartInnerState.d.ts +4 -2
- package/dist/cjs/components/ChartInner/useChartInnerState.js +9 -0
- package/dist/cjs/components/ChartInner/utils.d.ts +2 -2
- package/dist/cjs/components/ChartInner/utils.js +1 -1
- package/dist/cjs/components/Title/index.d.ts +0 -1
- package/dist/cjs/components/Title/index.js +6 -4
- package/dist/cjs/hooks/index.d.ts +7 -3
- package/dist/cjs/hooks/index.js +7 -3
- package/dist/cjs/hooks/useAxis/index.d.ts +19 -0
- package/dist/cjs/hooks/useAxis/index.js +63 -0
- package/dist/cjs/hooks/useChartOptions/index.d.ts +1 -4
- package/dist/cjs/hooks/useChartOptions/index.js +2 -5
- package/dist/cjs/hooks/useChartOptions/title.js +4 -2
- package/dist/cjs/hooks/useChartOptions/types.d.ts +0 -1
- package/dist/cjs/hooks/useChartOptions/utils.d.ts +1 -4
- package/dist/cjs/hooks/useChartOptions/utils.js +29 -6
- package/dist/cjs/hooks/useChartOptions/x-axis.js +2 -2
- package/dist/cjs/hooks/useChartOptions/y-axis.js +10 -11
- package/dist/cjs/hooks/useNormalizedOriginalData/index.d.ts +40 -0
- package/dist/cjs/hooks/useNormalizedOriginalData/index.js +33 -0
- package/dist/cjs/hooks/useSeries/index.d.ts +0 -9
- package/dist/cjs/hooks/useSeries/index.js +0 -18
- package/dist/cjs/hooks/useSeries/types.d.ts +3 -0
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +4 -0
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +4 -0
- package/dist/cjs/hooks/useShapes/scatter/prepare-data.d.ts +2 -2
- package/dist/cjs/hooks/useShapes/scatter/prepare-data.js +40 -5
- package/dist/cjs/types/chart/axis.d.ts +20 -2
- package/dist/cjs/utils/chart/get-closest-data.js +1 -1
- package/dist/cjs/utils/chart/series/sorting.d.ts +2 -2
- package/dist/cjs/utils/chart/series/sorting.js +3 -3
- package/dist/cjs/utils/chart/zoom.d.ts +7 -6
- package/dist/cjs/utils/chart/zoom.js +14 -6
- package/dist/esm/components/AxisY/AxisY.js +8 -1
- package/dist/esm/components/AxisY/prepare-axis-data.js +39 -12
- package/dist/esm/components/AxisY/types.d.ts +3 -0
- package/dist/esm/components/ChartInner/index.js +18 -7
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +7 -10
- package/dist/esm/components/ChartInner/useChartInnerProps.js +39 -104
- package/dist/esm/components/ChartInner/useChartInnerState.d.ts +4 -2
- package/dist/esm/components/ChartInner/useChartInnerState.js +9 -0
- package/dist/esm/components/ChartInner/utils.d.ts +2 -2
- package/dist/esm/components/ChartInner/utils.js +1 -1
- package/dist/esm/components/Title/index.d.ts +0 -1
- package/dist/esm/components/Title/index.js +6 -4
- package/dist/esm/hooks/index.d.ts +7 -3
- package/dist/esm/hooks/index.js +7 -3
- package/dist/esm/hooks/useAxis/index.d.ts +19 -0
- package/dist/esm/hooks/useAxis/index.js +63 -0
- package/dist/esm/hooks/useChartOptions/index.d.ts +1 -4
- package/dist/esm/hooks/useChartOptions/index.js +2 -5
- package/dist/esm/hooks/useChartOptions/title.js +4 -2
- package/dist/esm/hooks/useChartOptions/types.d.ts +0 -1
- package/dist/esm/hooks/useChartOptions/utils.d.ts +1 -4
- package/dist/esm/hooks/useChartOptions/utils.js +29 -6
- package/dist/esm/hooks/useChartOptions/x-axis.js +2 -2
- package/dist/esm/hooks/useChartOptions/y-axis.js +10 -11
- package/dist/esm/hooks/useNormalizedOriginalData/index.d.ts +40 -0
- package/dist/esm/hooks/useNormalizedOriginalData/index.js +33 -0
- package/dist/esm/hooks/useSeries/index.d.ts +0 -9
- package/dist/esm/hooks/useSeries/index.js +0 -18
- package/dist/esm/hooks/useSeries/types.d.ts +3 -0
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +4 -0
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +4 -0
- package/dist/esm/hooks/useShapes/scatter/prepare-data.d.ts +2 -2
- package/dist/esm/hooks/useShapes/scatter/prepare-data.js +40 -5
- package/dist/esm/types/chart/axis.d.ts +20 -2
- package/dist/esm/utils/chart/get-closest-data.js +1 -1
- package/dist/esm/utils/chart/series/sorting.d.ts +2 -2
- package/dist/esm/utils/chart/series/sorting.js +3 -3
- package/dist/esm/utils/chart/zoom.d.ts +7 -6
- package/dist/esm/utils/chart/zoom.js +14 -6
- package/package.json +7 -16
- package/dist/cjs/components/Title/styles.css +0 -5
- package/dist/esm/components/Title/styles.css +0 -5
|
@@ -67,7 +67,14 @@ export const AxisY = (props) => {
|
|
|
67
67
|
}
|
|
68
68
|
if (tickData.svgLabel) {
|
|
69
69
|
const label = tickData.svgLabel;
|
|
70
|
-
const textSelection = tickSelection
|
|
70
|
+
const textSelection = tickSelection
|
|
71
|
+
.append('text')
|
|
72
|
+
.style('transform', [
|
|
73
|
+
`translate(${label.x}px, ${label.y}px)`,
|
|
74
|
+
label.angle ? `rotate(${label.angle}deg)` : '',
|
|
75
|
+
]
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.join(' '));
|
|
71
78
|
if (label.title) {
|
|
72
79
|
textSelection.append('title').html(label.title);
|
|
73
80
|
}
|
|
@@ -9,15 +9,19 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHei
|
|
|
9
9
|
const labelMaxWidth = axis.labels.width; //axis.labels.maxWidth;
|
|
10
10
|
const size = originalTextSize;
|
|
11
11
|
const content = [];
|
|
12
|
-
//
|
|
13
|
-
|
|
12
|
+
// line breaks in the text are supported only
|
|
13
|
+
// 1. for the category axis - it will look strange for numbers or dates
|
|
14
|
+
// 2. for labels without rotation - it is unclear how to handle long strings correctly
|
|
15
|
+
if (originalTextSize.width > labelMaxWidth &&
|
|
16
|
+
axis.type === 'category' &&
|
|
17
|
+
axis.labels.rotation === 0) {
|
|
14
18
|
const textRows = await wrapText({
|
|
15
19
|
text,
|
|
16
20
|
style: axis.labels.style,
|
|
17
21
|
width: labelMaxWidth,
|
|
18
22
|
getTextSize,
|
|
19
23
|
});
|
|
20
|
-
let labelTopOffset =
|
|
24
|
+
let labelTopOffset = 0;
|
|
21
25
|
let newLabelWidth = 0;
|
|
22
26
|
let newLabelHeight = 0;
|
|
23
27
|
for (let textRowIndex = 0; textRowIndex < textRows.length; textRowIndex++) {
|
|
@@ -43,9 +47,7 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHei
|
|
|
43
47
|
});
|
|
44
48
|
textSize = await getTextSize(rowText);
|
|
45
49
|
}
|
|
46
|
-
const x = axis.position === 'left'
|
|
47
|
-
? left - textSize.width - axis.labels.margin
|
|
48
|
-
: left + axis.labels.margin;
|
|
50
|
+
const x = axis.position === 'left' ? -textSize.width : 0;
|
|
49
51
|
content.push({
|
|
50
52
|
text: rowText,
|
|
51
53
|
x,
|
|
@@ -62,21 +64,46 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHei
|
|
|
62
64
|
size.height = newLabelHeight;
|
|
63
65
|
}
|
|
64
66
|
else {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
let rowText = text;
|
|
68
|
+
let textSize = await getTextSize(rowText);
|
|
69
|
+
const textMaxWidth = Math.min(labelMaxWidth / calculateCos(axis.labels.rotation) -
|
|
70
|
+
textSize.height * calculateCos(90 - axis.labels.rotation),
|
|
71
|
+
// for vertical labels, we need to take into account the available height, otherwise there may be intersections
|
|
72
|
+
axis.labels.rotation === 90 ? labelMaxHeight : Infinity,
|
|
73
|
+
// if there is no rotation, then the height of the label does not affect the width of the text
|
|
74
|
+
axis.labels.rotation === 0
|
|
75
|
+
? Infinity
|
|
76
|
+
: (top + topOffset - textSize.height / 2) / calculateSin(axis.labels.rotation));
|
|
77
|
+
if (textSize.width > textMaxWidth) {
|
|
78
|
+
rowText = await getTextWithElipsis({
|
|
79
|
+
text: rowText,
|
|
80
|
+
getTextWidth: async (str) => (await getTextSize(str)).width,
|
|
81
|
+
maxWidth: textMaxWidth,
|
|
82
|
+
});
|
|
83
|
+
textSize = await getTextSize(rowText);
|
|
84
|
+
}
|
|
85
|
+
const actualTextHeight = axis.labels.rotation
|
|
86
|
+
? textSize.height / calculateSin(axis.labels.rotation)
|
|
87
|
+
: textSize.height;
|
|
88
|
+
const x = axis.position === 'left' ? -textSize.width : 0;
|
|
89
|
+
const y = Math.max(-topOffset - top, -actualTextHeight / 2);
|
|
68
90
|
content.push({
|
|
69
|
-
text,
|
|
91
|
+
text: rowText,
|
|
70
92
|
x,
|
|
71
|
-
y
|
|
72
|
-
size,
|
|
93
|
+
y,
|
|
94
|
+
size: textSize,
|
|
73
95
|
});
|
|
74
96
|
}
|
|
97
|
+
const x = axis.position === 'left' ? left - axis.labels.margin : left + axis.labels.margin;
|
|
98
|
+
const y = top;
|
|
75
99
|
const svgLabel = {
|
|
76
100
|
title: content.length > 1 || ((_a = content[0]) === null || _a === void 0 ? void 0 : _a.text) !== text ? text : undefined,
|
|
77
101
|
content: content,
|
|
78
102
|
style: axis.labels.style,
|
|
79
103
|
size: size,
|
|
104
|
+
x,
|
|
105
|
+
y,
|
|
106
|
+
angle: axis.labels.rotation,
|
|
80
107
|
};
|
|
81
108
|
return svgLabel;
|
|
82
109
|
}
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { ArrowRotateLeft } from '@gravity-ui/icons';
|
|
3
3
|
import { Button, ButtonIcon, useUniqId } from '@gravity-ui/uikit';
|
|
4
4
|
import { useCrosshair } from '../../hooks';
|
|
5
|
+
import { getPreparedTooltip } from '../../hooks/useChartOptions/tooltip';
|
|
5
6
|
import { EventType, block, getDispatcher } from '../../utils';
|
|
6
7
|
import { AxisX } from '../AxisX/AxisX';
|
|
7
8
|
import { AxisY } from '../AxisY/AxisY';
|
|
@@ -26,12 +27,22 @@ export const ChartInner = (props) => {
|
|
|
26
27
|
const plotAfterRef = React.useRef(null);
|
|
27
28
|
const dispatcher = React.useMemo(() => getDispatcher(), []);
|
|
28
29
|
const clipPathId = useUniqId();
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const preparedTooltip = React.useMemo(() => {
|
|
31
|
+
return getPreparedTooltip({
|
|
32
|
+
tooltip: data.tooltip,
|
|
33
|
+
seriesData: data.series.data,
|
|
34
|
+
yAxes: data.yAxis,
|
|
35
|
+
xAxis: data.xAxis,
|
|
36
|
+
});
|
|
37
|
+
}, [data.series.data, data.tooltip, data.yAxis, data.xAxis]);
|
|
38
|
+
const { tooltipPinned, togglePinTooltip, unpinTooltip, updateZoomState, zoomState } = useChartInnerState({
|
|
32
39
|
dispatcher,
|
|
33
|
-
tooltip,
|
|
40
|
+
tooltip: preparedTooltip,
|
|
34
41
|
});
|
|
42
|
+
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, xAxis, xScale, yAxis, yScale, svgXPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
|
|
43
|
+
dispatcher,
|
|
44
|
+
htmlLayout, plotNode: plotRef.current, svgContainer: svgRef.current, updateZoomState,
|
|
45
|
+
zoomState }));
|
|
35
46
|
const { handleChartClick, handleMouseLeave, throttledHandleMouseMove, throttledHandleTouchMove } = useChartInnerHandlers({
|
|
36
47
|
boundsHeight,
|
|
37
48
|
boundsOffsetLeft,
|
|
@@ -45,7 +56,7 @@ export const ChartInner = (props) => {
|
|
|
45
56
|
unpinTooltip,
|
|
46
57
|
xAxis,
|
|
47
58
|
yAxis,
|
|
48
|
-
tooltipThrottle:
|
|
59
|
+
tooltipThrottle: preparedTooltip.throttle,
|
|
49
60
|
isOutsideBounds,
|
|
50
61
|
});
|
|
51
62
|
const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
|
|
@@ -132,8 +143,8 @@ export const ChartInner = (props) => {
|
|
|
132
143
|
React.createElement("div", { className: b('html-layer'), ref: setHtmlLayout, style: {
|
|
133
144
|
'--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
|
|
134
145
|
} }),
|
|
135
|
-
|
|
146
|
+
Object.keys(zoomState).length > 0 && (React.createElement(Button, { style: { position: 'absolute', top: 0, right: 0 }, onClick: () => updateZoomState({}) },
|
|
136
147
|
React.createElement(ButtonIcon, null,
|
|
137
148
|
React.createElement(ArrowRotateLeft, null)))),
|
|
138
|
-
React.createElement(Tooltip, { dispatcher: dispatcher, tooltip:
|
|
149
|
+
React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: preparedTooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
|
|
139
150
|
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Dispatch } from 'd3';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ZoomState } from '../../hooks/useZoom/types';
|
|
4
4
|
import type { ChartInnerProps } from './types';
|
|
5
5
|
type Props = ChartInnerProps & {
|
|
6
|
+
clipPathId: string;
|
|
6
7
|
dispatcher: Dispatch<object>;
|
|
7
8
|
htmlLayout: HTMLElement | null;
|
|
8
|
-
svgContainer: SVGGElement | null;
|
|
9
9
|
plotNode: SVGGElement | null;
|
|
10
|
-
|
|
10
|
+
svgContainer: SVGGElement | null;
|
|
11
|
+
updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
|
|
12
|
+
zoomState: Partial<ZoomState>;
|
|
11
13
|
};
|
|
12
14
|
export declare function useChartInnerProps(props: Props): {
|
|
13
15
|
svgBottomPos: number | undefined;
|
|
@@ -18,7 +20,6 @@ export declare function useChartInnerProps(props: Props): {
|
|
|
18
20
|
boundsOffsetTop: number;
|
|
19
21
|
boundsWidth: number;
|
|
20
22
|
handleLegendItemClick: import("../../hooks").OnLegendItemClick;
|
|
21
|
-
handleZoomReset: (() => void) | undefined;
|
|
22
23
|
isOutsideBounds: (x: number, y: number) => boolean;
|
|
23
24
|
legendConfig: {
|
|
24
25
|
offset: {
|
|
@@ -41,16 +42,12 @@ export declare function useChartInnerProps(props: Props): {
|
|
|
41
42
|
prevWidth: number | undefined;
|
|
42
43
|
shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
|
43
44
|
shapesData: import("../../hooks").ShapeData[];
|
|
44
|
-
title: (import("
|
|
45
|
+
title: (import("../..").ChartTitle & {
|
|
45
46
|
height: number;
|
|
46
47
|
}) | undefined;
|
|
47
|
-
|
|
48
|
-
enabled: boolean;
|
|
49
|
-
throttle: number;
|
|
50
|
-
};
|
|
51
|
-
xAxis: PreparedAxis | null;
|
|
48
|
+
xAxis: import("../../hooks").PreparedAxis | null;
|
|
52
49
|
xScale: import("../../hooks").ChartScale | undefined;
|
|
53
|
-
yAxis: PreparedAxis[];
|
|
50
|
+
yAxis: import("../../hooks").PreparedAxis[];
|
|
54
51
|
yScale: (import("../../hooks").ChartScale | undefined)[] | undefined;
|
|
55
52
|
};
|
|
56
53
|
export {};
|
|
@@ -1,115 +1,45 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
3
|
-
import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapeSeries, useShapes, useSplit, } from '../../hooks';
|
|
2
|
+
import { useAxis, useAxisScales, useChartDimensions, useChartOptions, useNormalizedOriginalData, usePrevious, useSeries, useShapes, useSplit, } from '../../hooks';
|
|
4
3
|
import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
|
|
5
|
-
import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
|
|
6
|
-
import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
|
|
7
4
|
import { getLegendComponents } from '../../hooks/useSeries/prepare-legend';
|
|
8
5
|
import { getPreparedOptions } from '../../hooks/useSeries/prepare-options';
|
|
9
|
-
import { getActiveLegendItems } from '../../hooks/useSeries/utils';
|
|
10
6
|
import { useZoom } from '../../hooks/useZoom';
|
|
11
|
-
import {
|
|
7
|
+
import { getZoomedSeriesData } from '../../utils';
|
|
12
8
|
import { hasAtLeastOneSeriesDataPerPlot } from './utils';
|
|
13
9
|
export function useChartInnerProps(props) {
|
|
14
10
|
var _a;
|
|
15
|
-
const {
|
|
11
|
+
const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, svgContainer, width, updateZoomState, zoomState, } = props;
|
|
16
12
|
const prevWidth = usePrevious(width);
|
|
17
13
|
const prevHeight = usePrevious(height);
|
|
18
|
-
const {
|
|
14
|
+
const { normalizedSeriesData, normalizedXAxis, normalizedYAxis } = useNormalizedOriginalData({
|
|
19
15
|
seriesData: data.series.data,
|
|
16
|
+
xAxis: data.xAxis,
|
|
17
|
+
yAxis: data.yAxis,
|
|
18
|
+
});
|
|
19
|
+
const { chart, title, colors } = useChartOptions({
|
|
20
20
|
chart: data.chart,
|
|
21
21
|
colors: data.colors,
|
|
22
|
+
seriesData: normalizedSeriesData,
|
|
22
23
|
title: data.title,
|
|
23
|
-
tooltip: data.tooltip,
|
|
24
|
-
xAxis: data.xAxis,
|
|
25
|
-
yAxes: data.yAxis,
|
|
26
24
|
});
|
|
27
25
|
const preparedSeriesOptions = React.useMemo(() => {
|
|
28
26
|
return getPreparedOptions(data.series.options);
|
|
29
27
|
}, [data.series.options]);
|
|
30
|
-
const
|
|
31
|
-
const sortedSeriesData = React.useMemo(() => {
|
|
32
|
-
return getSortedSeriesData({ seriesData: data.series.data, yAxes: data.yAxis });
|
|
33
|
-
}, [data.series.data, data.yAxis]);
|
|
34
|
-
const { zoomedSeriesData, zoomedShapesSeriesData } = React.useMemo(() => {
|
|
35
|
-
return getZoomedSeriesData({
|
|
36
|
-
seriesData: sortedSeriesData,
|
|
37
|
-
xAxis: data.xAxis,
|
|
38
|
-
yAxes: data.yAxis,
|
|
39
|
-
zoomState,
|
|
40
|
-
});
|
|
41
|
-
}, [data.xAxis, data.yAxis, sortedSeriesData, zoomState]);
|
|
42
|
-
const { preparedSeries, preparedLegend, handleLegendItemClick } = useSeries({
|
|
28
|
+
const { preparedSeries: basePreparedSeries, preparedLegend, handleLegendItemClick, } = useSeries({
|
|
43
29
|
colors,
|
|
44
30
|
legend: data.legend,
|
|
45
|
-
originalSeriesData:
|
|
46
|
-
seriesData:
|
|
47
|
-
seriesOptions: data.series.options,
|
|
48
|
-
});
|
|
49
|
-
// preparing the X and Y axes
|
|
50
|
-
const [axesState, setValue] = React.useState({ xAxis: null, yAxis: [] });
|
|
51
|
-
const axesStateRunRef = React.useRef(0);
|
|
52
|
-
const prevAxesStateValue = React.useRef(axesState);
|
|
53
|
-
const axesStateReady = React.useRef(false);
|
|
54
|
-
React.useEffect(() => {
|
|
55
|
-
axesStateRunRef.current++;
|
|
56
|
-
axesStateReady.current = false;
|
|
57
|
-
(async function () {
|
|
58
|
-
const currentRun = axesStateRunRef.current;
|
|
59
|
-
const seriesData = preparedSeries.filter((s) => s.visible);
|
|
60
|
-
const xAxis = await getPreparedXAxis({
|
|
61
|
-
xAxis: data.xAxis,
|
|
62
|
-
width,
|
|
63
|
-
seriesData,
|
|
64
|
-
seriesOptions: preparedSeriesOptions,
|
|
65
|
-
});
|
|
66
|
-
let estimatedBoundsHeight = height;
|
|
67
|
-
if (xAxis) {
|
|
68
|
-
estimatedBoundsHeight =
|
|
69
|
-
height -
|
|
70
|
-
(xAxis.title.height +
|
|
71
|
-
xAxis.title.margin +
|
|
72
|
-
xAxis.labels.margin +
|
|
73
|
-
xAxis.labels.height +
|
|
74
|
-
(preparedLegend ? preparedLegend.height + preparedLegend.margin : 0) +
|
|
75
|
-
chart.margin.top +
|
|
76
|
-
chart.margin.bottom);
|
|
77
|
-
}
|
|
78
|
-
const yAxis = await getPreparedYAxis({
|
|
79
|
-
height,
|
|
80
|
-
boundsHeight: estimatedBoundsHeight,
|
|
81
|
-
width,
|
|
82
|
-
seriesData,
|
|
83
|
-
yAxis: data.yAxis,
|
|
84
|
-
});
|
|
85
|
-
const newStateValue = { xAxis, yAxis };
|
|
86
|
-
if (axesStateRunRef.current === currentRun) {
|
|
87
|
-
if (!isEqual(prevAxesStateValue.current, newStateValue)) {
|
|
88
|
-
setValue(newStateValue);
|
|
89
|
-
prevAxesStateValue.current = newStateValue;
|
|
90
|
-
}
|
|
91
|
-
axesStateReady.current = true;
|
|
92
|
-
}
|
|
93
|
-
})();
|
|
94
|
-
}, [
|
|
95
|
-
chart.margin,
|
|
96
|
-
data.xAxis,
|
|
97
|
-
data.yAxis,
|
|
98
|
-
height,
|
|
99
|
-
preparedLegend,
|
|
100
|
-
preparedSeries,
|
|
101
|
-
preparedSeriesOptions,
|
|
102
|
-
width,
|
|
103
|
-
]);
|
|
104
|
-
const { xAxis, yAxis } = axesStateReady.current ? axesState : { xAxis: null, yAxis: [] };
|
|
105
|
-
const activeLegendItems = React.useMemo(() => getActiveLegendItems(preparedSeries), [preparedSeries]);
|
|
106
|
-
const { preparedSeries: preparedShapesSeries } = useShapeSeries({
|
|
107
|
-
colors,
|
|
108
|
-
seriesData: zoomedShapesSeriesData,
|
|
31
|
+
originalSeriesData: normalizedSeriesData,
|
|
32
|
+
seriesData: normalizedSeriesData,
|
|
109
33
|
seriesOptions: data.series.options,
|
|
110
|
-
activeLegendItems,
|
|
111
|
-
preparedLegend,
|
|
112
34
|
});
|
|
35
|
+
const { preparedSeries, preparedShapesSeries } = React.useMemo(() => {
|
|
36
|
+
return getZoomedSeriesData({
|
|
37
|
+
seriesData: basePreparedSeries,
|
|
38
|
+
xAxis: normalizedXAxis,
|
|
39
|
+
yAxis: normalizedYAxis,
|
|
40
|
+
zoomState,
|
|
41
|
+
});
|
|
42
|
+
}, [basePreparedSeries, normalizedXAxis, normalizedYAxis, zoomState]);
|
|
113
43
|
const { legendConfig, legendItems } = React.useMemo(() => {
|
|
114
44
|
if (!preparedLegend) {
|
|
115
45
|
return { legendConfig: undefined, legendItems: [] };
|
|
@@ -122,14 +52,24 @@ export function useChartInnerProps(props) {
|
|
|
122
52
|
preparedLegend,
|
|
123
53
|
});
|
|
124
54
|
}, [width, height, chart.margin, preparedSeries, preparedLegend]);
|
|
125
|
-
const {
|
|
55
|
+
const { xAxis, yAxis } = useAxis({
|
|
56
|
+
height,
|
|
57
|
+
preparedChart: chart,
|
|
58
|
+
preparedLegend,
|
|
59
|
+
preparedSeries,
|
|
60
|
+
preparedSeriesOptions,
|
|
126
61
|
width,
|
|
62
|
+
xAxis: normalizedXAxis,
|
|
63
|
+
yAxis: normalizedYAxis,
|
|
64
|
+
});
|
|
65
|
+
const { boundsWidth, boundsHeight } = useChartDimensions({
|
|
127
66
|
height,
|
|
128
67
|
margin: chart.margin,
|
|
129
68
|
preparedLegend,
|
|
130
|
-
preparedXAxis: xAxis,
|
|
131
|
-
preparedYAxis: yAxis,
|
|
132
69
|
preparedSeries: preparedSeries,
|
|
70
|
+
preparedYAxis: yAxis,
|
|
71
|
+
preparedXAxis: xAxis,
|
|
72
|
+
width,
|
|
133
73
|
});
|
|
134
74
|
const preparedSplit = useSplit({ split: data.split, boundsHeight, chartWidth: width });
|
|
135
75
|
const { xScale, yScale } = useAxisScales({
|
|
@@ -162,17 +102,17 @@ export function useChartInnerProps(props) {
|
|
|
162
102
|
isOutsideBounds,
|
|
163
103
|
});
|
|
164
104
|
const handleAttemptToSetZoomState = React.useCallback((nextZoomState) => {
|
|
165
|
-
const {
|
|
166
|
-
seriesData:
|
|
167
|
-
xAxis
|
|
168
|
-
|
|
105
|
+
const { preparedSeries: nextZoomedSeriesData } = getZoomedSeriesData({
|
|
106
|
+
seriesData: preparedSeries,
|
|
107
|
+
xAxis,
|
|
108
|
+
yAxis,
|
|
169
109
|
zoomState: nextZoomState,
|
|
170
110
|
});
|
|
171
111
|
const hasData = hasAtLeastOneSeriesDataPerPlot(nextZoomedSeriesData, yAxis);
|
|
172
112
|
if (hasData) {
|
|
173
|
-
|
|
113
|
+
updateZoomState(nextZoomState);
|
|
174
114
|
}
|
|
175
|
-
}, [
|
|
115
|
+
}, [xAxis, yAxis, preparedSeries, updateZoomState]);
|
|
176
116
|
useZoom({
|
|
177
117
|
node: plotNode,
|
|
178
118
|
onUpdate: handleAttemptToSetZoomState,
|
|
@@ -199,9 +139,6 @@ export function useChartInnerProps(props) {
|
|
|
199
139
|
return acc;
|
|
200
140
|
}, 0);
|
|
201
141
|
const { bottom, top, x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
|
|
202
|
-
const handleZoomReset = React.useCallback(() => {
|
|
203
|
-
setZoomState({});
|
|
204
|
-
}, []);
|
|
205
142
|
return {
|
|
206
143
|
svgBottomPos: bottom,
|
|
207
144
|
svgTopPos: top,
|
|
@@ -211,7 +148,6 @@ export function useChartInnerProps(props) {
|
|
|
211
148
|
boundsOffsetTop,
|
|
212
149
|
boundsWidth,
|
|
213
150
|
handleLegendItemClick,
|
|
214
|
-
handleZoomReset: Object.keys(zoomState).length > 0 ? handleZoomReset : undefined,
|
|
215
151
|
isOutsideBounds,
|
|
216
152
|
legendConfig,
|
|
217
153
|
legendItems,
|
|
@@ -223,7 +159,6 @@ export function useChartInnerProps(props) {
|
|
|
223
159
|
shapes,
|
|
224
160
|
shapesData,
|
|
225
161
|
title,
|
|
226
|
-
tooltip,
|
|
227
162
|
xAxis,
|
|
228
163
|
xScale,
|
|
229
164
|
yAxis,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Dispatch } from 'd3';
|
|
3
|
-
import type {
|
|
3
|
+
import type { PreparedTooltip, ZoomState } from '../../hooks';
|
|
4
4
|
type Props = {
|
|
5
5
|
dispatcher: Dispatch<object>;
|
|
6
|
-
tooltip?:
|
|
6
|
+
tooltip?: PreparedTooltip;
|
|
7
7
|
};
|
|
8
8
|
export declare function useChartInnerState(props: Props): {
|
|
9
9
|
tooltipPinned: boolean;
|
|
10
10
|
togglePinTooltip: ((value: boolean, event: React.MouseEvent) => void) | undefined;
|
|
11
11
|
unpinTooltip: (() => void) | undefined;
|
|
12
|
+
updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
|
|
13
|
+
zoomState: Partial<ZoomState>;
|
|
12
14
|
};
|
|
13
15
|
export {};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import isEqual from 'lodash/isEqual';
|
|
2
3
|
import { EventType, isMacintosh } from '../../utils';
|
|
3
4
|
export function useChartInnerState(props) {
|
|
4
5
|
var _a, _b;
|
|
5
6
|
const { dispatcher, tooltip } = props;
|
|
6
7
|
const [tooltipPinned, setTooltipPinned] = React.useState(false);
|
|
8
|
+
const [zoomState, setZoomState] = React.useState({});
|
|
7
9
|
const tooltipEnabled = tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled;
|
|
8
10
|
const tooltipPinEnabled = (_a = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _a === void 0 ? void 0 : _a.enabled;
|
|
9
11
|
const modifierKey = (_b = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _b === void 0 ? void 0 : _b.modifierKey;
|
|
@@ -26,9 +28,16 @@ export function useChartInnerState(props) {
|
|
|
26
28
|
setTooltipPinned(false);
|
|
27
29
|
dispatcher.call(EventType.HOVER_SHAPE, {}, undefined);
|
|
28
30
|
}, [dispatcher]);
|
|
31
|
+
const updateZoomState = React.useCallback((nextZoomState) => {
|
|
32
|
+
if (!isEqual(zoomState, nextZoomState)) {
|
|
33
|
+
setZoomState(nextZoomState);
|
|
34
|
+
}
|
|
35
|
+
}, [zoomState]);
|
|
29
36
|
return {
|
|
30
37
|
tooltipPinned,
|
|
31
38
|
togglePinTooltip: tooltipEnabled && tooltipPinEnabled ? togglePinTooltip : undefined,
|
|
32
39
|
unpinTooltip: tooltipEnabled && tooltipPinEnabled ? unpinTooltip : undefined,
|
|
40
|
+
updateZoomState,
|
|
41
|
+
zoomState,
|
|
33
42
|
};
|
|
34
43
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
import type { PreparedSeries } from '../../hooks';
|
|
1
2
|
import type { PreparedAxis } from '../../hooks/useChartOptions/types';
|
|
2
|
-
|
|
3
|
-
export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: ChartSeries[], yAxes?: PreparedAxis[]): boolean;
|
|
3
|
+
export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: PreparedSeries[], yAxes?: PreparedAxis[]): boolean;
|
|
4
4
|
export declare function useAsyncState<T>(value: T, setState: () => Promise<T>): T;
|
|
@@ -21,7 +21,7 @@ export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxes = []) {
|
|
|
21
21
|
const yAxis = yAxes[yAxisIndex];
|
|
22
22
|
const plotIndex = (_a = yAxis === null || yAxis === void 0 ? void 0 : yAxis.plotIndex) !== null && _a !== void 0 ? _a : 0;
|
|
23
23
|
if (!hasDataMap.get(plotIndex)) {
|
|
24
|
-
if (seriesDataChunk.data.length > 0) {
|
|
24
|
+
if (Array.isArray(seriesDataChunk.data) && seriesDataChunk.data.length > 0) {
|
|
25
25
|
hasDataMap.set(plotIndex, true);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { block } from '../../utils';
|
|
3
|
-
import './styles.css';
|
|
4
|
-
const b = block('title');
|
|
5
2
|
export const Title = (props) => {
|
|
6
3
|
const { chartWidth, text, height, style } = props;
|
|
7
|
-
return (React.createElement("text", {
|
|
4
|
+
return (React.createElement("text", { dx: chartWidth / 2, dy: height / 2, dominantBaseline: "middle", textAnchor: "middle", style: {
|
|
5
|
+
fill: style === null || style === void 0 ? void 0 : style.fontColor,
|
|
6
|
+
fontSize: style === null || style === void 0 ? void 0 : style.fontSize,
|
|
7
|
+
fontWeight: style === null || style === void 0 ? void 0 : style.fontWeight,
|
|
8
|
+
lineHeight: `${height}px`,
|
|
9
|
+
} },
|
|
8
10
|
React.createElement("tspan", { dangerouslySetInnerHTML: { __html: text } })));
|
|
9
11
|
};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
export * from './useAxis';
|
|
2
|
+
export * from './useAxisScales';
|
|
1
3
|
export * from './useChartDimensions';
|
|
2
4
|
export * from './useChartOptions';
|
|
3
5
|
export * from './useChartOptions/types';
|
|
4
|
-
export * from './
|
|
6
|
+
export * from './useCrosshair';
|
|
7
|
+
export * from './useNormalizedOriginalData';
|
|
5
8
|
export * from './usePrevious';
|
|
6
9
|
export * from './useSeries';
|
|
7
10
|
export * from './useSeries/types';
|
|
8
11
|
export * from './useShapes';
|
|
9
|
-
export * from './useTooltip';
|
|
10
12
|
export * from './useSplit';
|
|
11
13
|
export * from './useSplit/types';
|
|
12
|
-
export * from './
|
|
14
|
+
export * from './useTooltip';
|
|
15
|
+
export * from './useZoom';
|
|
16
|
+
export * from './useZoom/types';
|
package/dist/cjs/hooks/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
export * from './useAxis';
|
|
2
|
+
export * from './useAxisScales';
|
|
1
3
|
export * from './useChartDimensions';
|
|
2
4
|
export * from './useChartOptions';
|
|
3
5
|
export * from './useChartOptions/types';
|
|
4
|
-
export * from './
|
|
6
|
+
export * from './useCrosshair';
|
|
7
|
+
export * from './useNormalizedOriginalData';
|
|
5
8
|
export * from './usePrevious';
|
|
6
9
|
export * from './useSeries';
|
|
7
10
|
export * from './useSeries/types';
|
|
8
11
|
export * from './useShapes';
|
|
9
|
-
export * from './useTooltip';
|
|
10
12
|
export * from './useSplit';
|
|
11
13
|
export * from './useSplit/types';
|
|
12
|
-
export * from './
|
|
14
|
+
export * from './useTooltip';
|
|
15
|
+
export * from './useZoom';
|
|
16
|
+
export * from './useZoom/types';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ChartXAxis, ChartYAxis } from '../../types';
|
|
2
|
+
import type { PreparedAxis, PreparedChart } from '../useChartOptions/types';
|
|
3
|
+
import type { PreparedLegend, PreparedSeries, PreparedSeriesOptions } from '../useSeries/types';
|
|
4
|
+
interface UseAxesProps {
|
|
5
|
+
height: number;
|
|
6
|
+
preparedChart: PreparedChart;
|
|
7
|
+
preparedLegend: PreparedLegend | null;
|
|
8
|
+
preparedSeries: PreparedSeries[];
|
|
9
|
+
preparedSeriesOptions: PreparedSeriesOptions;
|
|
10
|
+
width: number;
|
|
11
|
+
boundsHeight?: number;
|
|
12
|
+
xAxis?: ChartXAxis;
|
|
13
|
+
yAxis?: ChartYAxis[];
|
|
14
|
+
}
|
|
15
|
+
export declare function useAxis(props: UseAxesProps): {
|
|
16
|
+
xAxis: PreparedAxis | null;
|
|
17
|
+
yAxis: PreparedAxis[];
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import isEqual from 'lodash/isEqual';
|
|
3
|
+
import { getPreparedXAxis } from '../useChartOptions/x-axis';
|
|
4
|
+
import { getPreparedYAxis } from '../useChartOptions/y-axis';
|
|
5
|
+
export function useAxis(props) {
|
|
6
|
+
const { boundsHeight, height, preparedChart, preparedLegend, preparedSeries, preparedSeriesOptions, width, xAxis, yAxis, } = props;
|
|
7
|
+
const [axesState, setValue] = React.useState({ xAxis: null, yAxis: [] });
|
|
8
|
+
const axesStateRunRef = React.useRef(0);
|
|
9
|
+
const prevAxesStateValue = React.useRef(axesState);
|
|
10
|
+
const axesStateReady = React.useRef(false);
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
axesStateRunRef.current++;
|
|
13
|
+
axesStateReady.current = false;
|
|
14
|
+
(async function () {
|
|
15
|
+
const currentRun = axesStateRunRef.current;
|
|
16
|
+
const seriesData = preparedSeries.filter((s) => s.visible);
|
|
17
|
+
const preparedXAxis = await getPreparedXAxis({
|
|
18
|
+
xAxis,
|
|
19
|
+
width,
|
|
20
|
+
seriesData,
|
|
21
|
+
seriesOptions: preparedSeriesOptions,
|
|
22
|
+
});
|
|
23
|
+
let estimatedBoundsHeight = boundsHeight !== null && boundsHeight !== void 0 ? boundsHeight : height;
|
|
24
|
+
if (preparedXAxis && typeof boundsHeight !== 'number') {
|
|
25
|
+
estimatedBoundsHeight =
|
|
26
|
+
height -
|
|
27
|
+
(preparedXAxis.title.height +
|
|
28
|
+
preparedXAxis.title.margin +
|
|
29
|
+
preparedXAxis.labels.margin +
|
|
30
|
+
preparedXAxis.labels.height +
|
|
31
|
+
(preparedLegend ? preparedLegend.height + preparedLegend.margin : 0) +
|
|
32
|
+
preparedChart.margin.top +
|
|
33
|
+
preparedChart.margin.bottom);
|
|
34
|
+
}
|
|
35
|
+
const preparedYAxis = await getPreparedYAxis({
|
|
36
|
+
height,
|
|
37
|
+
boundsHeight: estimatedBoundsHeight,
|
|
38
|
+
width,
|
|
39
|
+
seriesData,
|
|
40
|
+
yAxis,
|
|
41
|
+
});
|
|
42
|
+
const newStateValue = { xAxis: preparedXAxis, yAxis: preparedYAxis };
|
|
43
|
+
if (axesStateRunRef.current === currentRun) {
|
|
44
|
+
if (!isEqual(prevAxesStateValue.current, newStateValue)) {
|
|
45
|
+
setValue(newStateValue);
|
|
46
|
+
prevAxesStateValue.current = newStateValue;
|
|
47
|
+
}
|
|
48
|
+
axesStateReady.current = true;
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
}, [
|
|
52
|
+
boundsHeight,
|
|
53
|
+
height,
|
|
54
|
+
preparedChart.margin,
|
|
55
|
+
preparedLegend,
|
|
56
|
+
preparedSeries,
|
|
57
|
+
preparedSeriesOptions,
|
|
58
|
+
width,
|
|
59
|
+
xAxis,
|
|
60
|
+
yAxis,
|
|
61
|
+
]);
|
|
62
|
+
return axesStateReady.current ? axesState : { xAxis: null, yAxis: [] };
|
|
63
|
+
}
|