@gravity-ui/charts 1.34.7 → 1.34.9
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/AxisX/AxisX.js +0 -1
- package/dist/cjs/components/AxisX/prepare-axis-data.js +4 -23
- package/dist/cjs/components/AxisX/types.d.ts +1 -9
- package/dist/cjs/components/AxisY/prepare-axis-title.js +3 -34
- package/dist/cjs/components/AxisY/types.d.ts +1 -9
- package/dist/cjs/components/ChartInner/index.js +6 -6
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +0 -1
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +16 -6
- package/dist/cjs/components/ChartInner/useChartInnerState.d.ts +2 -3
- package/dist/cjs/components/ChartInner/useChartInnerState.js +14 -22
- package/dist/cjs/components/RangeSlider/index.d.ts +0 -1
- package/dist/cjs/components/RangeSlider/index.js +1 -8
- package/dist/cjs/components/types/index.d.ts +9 -0
- package/dist/cjs/components/types/index.js +1 -0
- package/dist/cjs/components/utils/axis-title.d.ts +6 -0
- package/dist/cjs/components/utils/axis-title.js +39 -0
- package/dist/cjs/components/{utils.d.ts → utils/index.d.ts} +3 -3
- package/dist/cjs/components/{utils.js → utils/index.js} +1 -1
- package/dist/cjs/hooks/useAxisScales/index.js +3 -1
- package/dist/cjs/hooks/useAxisScales/x-scale.d.ts +0 -6
- package/dist/cjs/hooks/useAxisScales/x-scale.js +12 -35
- package/dist/cjs/hooks/useAxisScales/y-scale.js +2 -7
- package/dist/cjs/hooks/useRangeSlider/types.d.ts +1 -1
- package/dist/cjs/hooks/useSeries/prepare-legend.js +2 -1
- package/dist/cjs/hooks/useShapes/area/index.js +13 -4
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +3 -2
- package/dist/cjs/hooks/useShapes/bar-x/index.js +13 -4
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +15 -6
- package/dist/cjs/hooks/useShapes/line/index.js +13 -4
- package/dist/cjs/hooks/useShapes/line/prepare-data.js +4 -2
- package/dist/cjs/hooks/useShapes/waterfall/index.js +13 -4
- package/dist/cjs/utils/chart/get-closest-data.js +39 -35
- package/dist/cjs/utils/chart/zoom.d.ts +2 -1
- package/dist/cjs/utils/chart/zoom.js +9 -0
- package/dist/esm/components/AxisX/AxisX.js +0 -1
- package/dist/esm/components/AxisX/prepare-axis-data.js +4 -23
- package/dist/esm/components/AxisX/types.d.ts +1 -9
- package/dist/esm/components/AxisY/prepare-axis-title.js +3 -34
- package/dist/esm/components/AxisY/types.d.ts +1 -9
- package/dist/esm/components/ChartInner/index.js +6 -6
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +0 -1
- package/dist/esm/components/ChartInner/useChartInnerProps.js +16 -6
- package/dist/esm/components/ChartInner/useChartInnerState.d.ts +2 -3
- package/dist/esm/components/ChartInner/useChartInnerState.js +14 -22
- package/dist/esm/components/RangeSlider/index.d.ts +0 -1
- package/dist/esm/components/RangeSlider/index.js +1 -8
- package/dist/esm/components/types/index.d.ts +9 -0
- package/dist/esm/components/types/index.js +1 -0
- package/dist/esm/components/utils/axis-title.d.ts +6 -0
- package/dist/esm/components/utils/axis-title.js +39 -0
- package/dist/esm/components/{utils.d.ts → utils/index.d.ts} +3 -3
- package/dist/esm/components/{utils.js → utils/index.js} +1 -1
- package/dist/esm/hooks/useAxisScales/index.js +3 -1
- package/dist/esm/hooks/useAxisScales/x-scale.d.ts +0 -6
- package/dist/esm/hooks/useAxisScales/x-scale.js +12 -35
- package/dist/esm/hooks/useAxisScales/y-scale.js +2 -7
- package/dist/esm/hooks/useRangeSlider/types.d.ts +1 -1
- package/dist/esm/hooks/useSeries/prepare-legend.js +2 -1
- package/dist/esm/hooks/useShapes/area/index.js +13 -4
- package/dist/esm/hooks/useShapes/area/prepare-data.js +3 -2
- package/dist/esm/hooks/useShapes/bar-x/index.js +13 -4
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +15 -6
- package/dist/esm/hooks/useShapes/line/index.js +13 -4
- package/dist/esm/hooks/useShapes/line/prepare-data.js +4 -2
- package/dist/esm/hooks/useShapes/waterfall/index.js +13 -4
- package/dist/esm/utils/chart/get-closest-data.js +39 -35
- package/dist/esm/utils/chart/zoom.d.ts +2 -1
- package/dist/esm/utils/chart/zoom.js +9 -0
- package/package.json +1 -1
|
@@ -11,8 +11,10 @@ export const AreaSeriesShapes = (args) => {
|
|
|
11
11
|
const hoveredDataRef = React.useRef(null);
|
|
12
12
|
const plotRef = React.useRef(null);
|
|
13
13
|
const markersRef = React.useRef(null);
|
|
14
|
+
const allowOverlapDataLabels = React.useMemo(() => {
|
|
15
|
+
return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
|
|
16
|
+
}, [preparedData]);
|
|
14
17
|
React.useEffect(() => {
|
|
15
|
-
var _a;
|
|
16
18
|
if (!plotRef.current || !markersRef.current) {
|
|
17
19
|
return () => { };
|
|
18
20
|
}
|
|
@@ -55,7 +57,7 @@ export const AreaSeriesShapes = (args) => {
|
|
|
55
57
|
let dataLabels = preparedData.reduce((acc, d) => {
|
|
56
58
|
return acc.concat(d.labels);
|
|
57
59
|
}, []);
|
|
58
|
-
if (!
|
|
60
|
+
if (!allowOverlapDataLabels) {
|
|
59
61
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
60
62
|
}
|
|
61
63
|
const labelsSelection = plotSvgElement
|
|
@@ -147,9 +149,16 @@ export const AreaSeriesShapes = (args) => {
|
|
|
147
149
|
return () => {
|
|
148
150
|
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.area', null);
|
|
149
151
|
};
|
|
150
|
-
}, [dispatcher, preparedData, seriesOptions]);
|
|
152
|
+
}, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
|
|
153
|
+
const htmlLayerData = React.useMemo(() => {
|
|
154
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
|
|
155
|
+
if (allowOverlapDataLabels) {
|
|
156
|
+
return { htmlElements: items };
|
|
157
|
+
}
|
|
158
|
+
return { htmlElements: filterOverlappingLabels(items) };
|
|
159
|
+
}, [allowOverlapDataLabels, preparedData]);
|
|
151
160
|
return (React.createElement(React.Fragment, null,
|
|
152
161
|
React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
153
162
|
React.createElement("g", { ref: markersRef }),
|
|
154
|
-
React.createElement(HtmlLayer, { preparedData:
|
|
163
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
155
164
|
};
|
|
@@ -27,14 +27,14 @@ function getXValues(series, xAxis, xScale) {
|
|
|
27
27
|
}
|
|
28
28
|
return Array.from(xValues);
|
|
29
29
|
}
|
|
30
|
-
async function prepareDataLabels({ series, points, xMax, yAxisTop, }) {
|
|
30
|
+
async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBounds, }) {
|
|
31
31
|
var _a;
|
|
32
32
|
const svgLabels = [];
|
|
33
33
|
const htmlLabels = [];
|
|
34
34
|
const getTextSize = getTextSizeFn({ style: series.dataLabels.style });
|
|
35
35
|
for (let pointsIndex = 0; pointsIndex < points.length; pointsIndex++) {
|
|
36
36
|
const point = points[pointsIndex];
|
|
37
|
-
if (point.y === null) {
|
|
37
|
+
if (point.y === null || isOutsideBounds(point.x, point.y)) {
|
|
38
38
|
continue;
|
|
39
39
|
}
|
|
40
40
|
const text = getFormattedValue(Object.assign({ value: (_a = point.data.label) !== null && _a !== void 0 ? _a : point.data.y }, series.dataLabels));
|
|
@@ -281,6 +281,7 @@ export const prepareAreaData = async (args) => {
|
|
|
281
281
|
points: item.points,
|
|
282
282
|
xMax,
|
|
283
283
|
yAxisTop: itemYAxisTop,
|
|
284
|
+
isOutsideBounds,
|
|
284
285
|
});
|
|
285
286
|
item.labels.push(...labelsData.svgLabels);
|
|
286
287
|
item.htmlElements.push(...labelsData.htmlLabels);
|
|
@@ -11,8 +11,10 @@ export const BarXSeriesShapes = (args) => {
|
|
|
11
11
|
const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
|
|
12
12
|
const hoveredDataRef = React.useRef(null);
|
|
13
13
|
const ref = React.useRef(null);
|
|
14
|
+
const allowOverlapDataLabels = React.useMemo(() => {
|
|
15
|
+
return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
|
|
16
|
+
}, [preparedData]);
|
|
14
17
|
React.useEffect(() => {
|
|
15
|
-
var _a;
|
|
16
18
|
if (!ref.current) {
|
|
17
19
|
return () => { };
|
|
18
20
|
}
|
|
@@ -46,7 +48,7 @@ export const BarXSeriesShapes = (args) => {
|
|
|
46
48
|
.attr('opacity', (d) => d.opacity)
|
|
47
49
|
.attr('cursor', (d) => d.series.cursor);
|
|
48
50
|
let dataLabels = preparedData.map((d) => d.label).filter(Boolean);
|
|
49
|
-
if (!
|
|
51
|
+
if (!allowOverlapDataLabels) {
|
|
50
52
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
51
53
|
}
|
|
52
54
|
const labelSelection = svgElement
|
|
@@ -108,8 +110,15 @@ export const BarXSeriesShapes = (args) => {
|
|
|
108
110
|
return () => {
|
|
109
111
|
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.bar-x', null);
|
|
110
112
|
};
|
|
111
|
-
}, [dispatcher, preparedData, seriesOptions]);
|
|
113
|
+
}, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
|
|
114
|
+
const htmlLayerData = React.useMemo(() => {
|
|
115
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
|
|
116
|
+
if (allowOverlapDataLabels) {
|
|
117
|
+
return { htmlElements: items };
|
|
118
|
+
}
|
|
119
|
+
return { htmlElements: filterOverlappingLabels(items) };
|
|
120
|
+
}, [allowOverlapDataLabels, preparedData]);
|
|
112
121
|
return (React.createElement(React.Fragment, null,
|
|
113
122
|
React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
114
|
-
React.createElement(HtmlLayer, { preparedData:
|
|
123
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
115
124
|
};
|
|
@@ -5,7 +5,7 @@ import { getFormattedValue } from '../../../utils/chart/format';
|
|
|
5
5
|
import { getSeriesStackId } from '../../useSeries/utils';
|
|
6
6
|
import { getBarXLayout } from '../../utils/bar-x';
|
|
7
7
|
const isSeriesDataValid = (d) => d.y !== null;
|
|
8
|
-
async function getLabelData(d) {
|
|
8
|
+
async function getLabelData(d, xMax) {
|
|
9
9
|
var _a;
|
|
10
10
|
if (!d.series.dataLabels.enabled) {
|
|
11
11
|
return undefined;
|
|
@@ -22,10 +22,10 @@ async function getLabelData(d) {
|
|
|
22
22
|
if (d.series.dataLabels.inside) {
|
|
23
23
|
y = d.y + d.height / 2;
|
|
24
24
|
}
|
|
25
|
-
const
|
|
25
|
+
const centerX = Math.min(xMax - width / 2, Math.max(width / 2, d.x + d.width / 2));
|
|
26
26
|
return {
|
|
27
27
|
text,
|
|
28
|
-
x: html ?
|
|
28
|
+
x: html ? centerX - width / 2 : centerX,
|
|
29
29
|
y: html ? y - height : y,
|
|
30
30
|
style,
|
|
31
31
|
size: { width, height },
|
|
@@ -35,7 +35,7 @@ async function getLabelData(d) {
|
|
|
35
35
|
}
|
|
36
36
|
// eslint-disable-next-line complexity
|
|
37
37
|
export const prepareBarXData = async (args) => {
|
|
38
|
-
var _a, _b, _c, _d;
|
|
38
|
+
var _a, _b, _c, _d, _e;
|
|
39
39
|
const { series, seriesOptions, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isRangeSlider, } = args;
|
|
40
40
|
const stackGap = seriesOptions['bar-x'].stackGap;
|
|
41
41
|
const categories = (_a = xAxis === null || xAxis === void 0 ? void 0 : xAxis.categories) !== null && _a !== void 0 ? _a : [];
|
|
@@ -176,10 +176,19 @@ export const prepareBarXData = async (args) => {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
+
const [_xMin, xRangeMax] = xScale.range();
|
|
180
|
+
const xMax = xRangeMax;
|
|
179
181
|
for (let i = 0; i < result.length; i++) {
|
|
180
182
|
const barData = result[i];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
const isBarOutsideBounds = barData.x + barData.width <= 0 ||
|
|
184
|
+
barData.x >= xMax ||
|
|
185
|
+
barData.y + barData.height <= 0 ||
|
|
186
|
+
barData.y >= plotHeight;
|
|
187
|
+
const isZeroValue = ((_e = barData.data.y) !== null && _e !== void 0 ? _e : 0) === 0;
|
|
188
|
+
if (barData.series.dataLabels.enabled &&
|
|
189
|
+
!isRangeSlider &&
|
|
190
|
+
(!isBarOutsideBounds || isZeroValue)) {
|
|
191
|
+
const label = await getLabelData(barData, xMax);
|
|
183
192
|
if (barData.series.dataLabels.html && label) {
|
|
184
193
|
barData.htmlElements.push({
|
|
185
194
|
x: label.x,
|
|
@@ -11,8 +11,10 @@ export const LineSeriesShapes = (args) => {
|
|
|
11
11
|
const hoveredDataRef = React.useRef(null);
|
|
12
12
|
const plotRef = React.useRef(null);
|
|
13
13
|
const markersRef = React.useRef(null);
|
|
14
|
+
const allowOverlapDataLabels = React.useMemo(() => {
|
|
15
|
+
return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
|
|
16
|
+
}, [preparedData]);
|
|
14
17
|
React.useEffect(() => {
|
|
15
|
-
var _a;
|
|
16
18
|
if (!plotRef.current || !markersRef.current) {
|
|
17
19
|
return () => { };
|
|
18
20
|
}
|
|
@@ -42,7 +44,7 @@ export const LineSeriesShapes = (args) => {
|
|
|
42
44
|
let dataLabels = preparedData.reduce((acc, d) => {
|
|
43
45
|
return acc.concat(d.labels);
|
|
44
46
|
}, []);
|
|
45
|
-
if (!
|
|
47
|
+
if (!allowOverlapDataLabels) {
|
|
46
48
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
47
49
|
}
|
|
48
50
|
const labelsSelection = plotSvgElement
|
|
@@ -133,9 +135,16 @@ export const LineSeriesShapes = (args) => {
|
|
|
133
135
|
return () => {
|
|
134
136
|
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.line', null);
|
|
135
137
|
};
|
|
136
|
-
}, [dispatcher, preparedData, seriesOptions]);
|
|
138
|
+
}, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
|
|
139
|
+
const htmlLayerData = React.useMemo(() => {
|
|
140
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
|
|
141
|
+
if (allowOverlapDataLabels) {
|
|
142
|
+
return { htmlElements: items };
|
|
143
|
+
}
|
|
144
|
+
return { htmlElements: filterOverlappingLabels(items) };
|
|
145
|
+
}, [allowOverlapDataLabels, preparedData]);
|
|
137
146
|
return (React.createElement(React.Fragment, null,
|
|
138
147
|
React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
139
148
|
React.createElement("g", { ref: markersRef }),
|
|
140
|
-
React.createElement(HtmlLayer, { preparedData:
|
|
149
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
141
150
|
};
|
|
@@ -49,7 +49,7 @@ export const prepareLineData = async (args) => {
|
|
|
49
49
|
if (s.dataLabels.enabled && !isRangeSlider) {
|
|
50
50
|
if (s.dataLabels.html) {
|
|
51
51
|
const list = await Promise.all(points.reduce((result, p) => {
|
|
52
|
-
if (p.y === null) {
|
|
52
|
+
if (p.y === null || p.x === null || isOutsideBounds(p.x, p.y)) {
|
|
53
53
|
return result;
|
|
54
54
|
}
|
|
55
55
|
result.push(getHtmlLabel(p, s, xMax));
|
|
@@ -61,7 +61,9 @@ export const prepareLineData = async (args) => {
|
|
|
61
61
|
const getTextSize = getTextSizeFn({ style: s.dataLabels.style });
|
|
62
62
|
for (let index = 0; index < points.length; index++) {
|
|
63
63
|
const point = points[index];
|
|
64
|
-
if (point.y !== null &&
|
|
64
|
+
if (point.y !== null &&
|
|
65
|
+
point.x !== null &&
|
|
66
|
+
!isOutsideBounds(point.x, point.y)) {
|
|
65
67
|
const labelValue = (_b = point.data.label) !== null && _b !== void 0 ? _b : point.data.y;
|
|
66
68
|
const text = getFormattedValue(Object.assign({ value: labelValue }, s.dataLabels));
|
|
67
69
|
const labelSize = await getTextSize(text);
|
|
@@ -12,8 +12,10 @@ export const WaterfallSeriesShapes = (args) => {
|
|
|
12
12
|
const hoveredDataRef = React.useRef(null);
|
|
13
13
|
const ref = React.useRef(null);
|
|
14
14
|
const connectorSelector = `.${b('connector')}`;
|
|
15
|
+
const allowOverlapDataLabels = React.useMemo(() => {
|
|
16
|
+
return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
|
|
17
|
+
}, [preparedData]);
|
|
15
18
|
React.useEffect(() => {
|
|
16
|
-
var _a;
|
|
17
19
|
if (!ref.current) {
|
|
18
20
|
return () => { };
|
|
19
21
|
}
|
|
@@ -34,7 +36,7 @@ export const WaterfallSeriesShapes = (args) => {
|
|
|
34
36
|
.attr('opacity', (d) => d.opacity)
|
|
35
37
|
.attr('cursor', (d) => d.series.cursor);
|
|
36
38
|
let dataLabels = preparedData.map((d) => d.label).filter(Boolean);
|
|
37
|
-
if (!
|
|
39
|
+
if (!allowOverlapDataLabels) {
|
|
38
40
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
39
41
|
}
|
|
40
42
|
const labelSelection = svgElement
|
|
@@ -125,8 +127,15 @@ export const WaterfallSeriesShapes = (args) => {
|
|
|
125
127
|
return () => {
|
|
126
128
|
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.waterfall', null);
|
|
127
129
|
};
|
|
128
|
-
}, [connectorSelector, dispatcher, preparedData, seriesOptions]);
|
|
130
|
+
}, [allowOverlapDataLabels, connectorSelector, dispatcher, preparedData, seriesOptions]);
|
|
131
|
+
const htmlLayerData = React.useMemo(() => {
|
|
132
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
|
|
133
|
+
if (allowOverlapDataLabels) {
|
|
134
|
+
return { htmlElements: items };
|
|
135
|
+
}
|
|
136
|
+
return { htmlElements: filterOverlappingLabels(items) };
|
|
137
|
+
}, [allowOverlapDataLabels, preparedData]);
|
|
129
138
|
return (React.createElement(React.Fragment, null,
|
|
130
139
|
React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
131
|
-
React.createElement(HtmlLayer, { preparedData:
|
|
140
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
132
141
|
};
|
|
@@ -47,19 +47,53 @@ export function getClosestPoints(args) {
|
|
|
47
47
|
const [pointerX, pointerY] = position;
|
|
48
48
|
const result = [];
|
|
49
49
|
const groups = groupBy(shapesData, getSeriesType);
|
|
50
|
+
const closestPointsByXValue = [];
|
|
50
51
|
// eslint-disable-next-line complexity
|
|
51
52
|
Object.entries(groups).forEach(([seriesType, list]) => {
|
|
52
53
|
var _a, _b, _c, _d, _e;
|
|
53
54
|
switch (seriesType) {
|
|
55
|
+
case 'line': {
|
|
56
|
+
const linePoints = list.reduce((acc, d) => {
|
|
57
|
+
acc.push(...d.points.reduce((accPoints, p) => {
|
|
58
|
+
if (p.y !== null && p.x !== null) {
|
|
59
|
+
accPoints.push({
|
|
60
|
+
data: p.data,
|
|
61
|
+
series: p.series,
|
|
62
|
+
x: p.x,
|
|
63
|
+
y0: p.y,
|
|
64
|
+
y1: p.y,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return accPoints;
|
|
68
|
+
}, []));
|
|
69
|
+
return acc;
|
|
70
|
+
}, []);
|
|
71
|
+
closestPointsByXValue.push(...linePoints);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'area': {
|
|
75
|
+
const areaPoints = list.reduce((acc, d) => {
|
|
76
|
+
Array.prototype.push.apply(acc, d.points.map((p) => ({
|
|
77
|
+
data: p.data,
|
|
78
|
+
series: p.series,
|
|
79
|
+
x: p.x,
|
|
80
|
+
y0: p.y0,
|
|
81
|
+
y1: p.y,
|
|
82
|
+
})));
|
|
83
|
+
return acc;
|
|
84
|
+
}, []);
|
|
85
|
+
closestPointsByXValue.push(...areaPoints);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
54
88
|
case 'bar-x': {
|
|
55
|
-
const
|
|
89
|
+
const barXPoints = list.map((d) => ({
|
|
56
90
|
data: d.data,
|
|
57
91
|
series: d.series,
|
|
58
92
|
x: d.x + d.width / 2,
|
|
59
93
|
y0: d.y,
|
|
60
94
|
y1: d.y + d.height,
|
|
61
95
|
}));
|
|
62
|
-
|
|
96
|
+
closestPointsByXValue.push(...barXPoints);
|
|
63
97
|
break;
|
|
64
98
|
}
|
|
65
99
|
case 'waterfall': {
|
|
@@ -74,39 +108,6 @@ export function getClosestPoints(args) {
|
|
|
74
108
|
result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
|
|
75
109
|
break;
|
|
76
110
|
}
|
|
77
|
-
case 'area': {
|
|
78
|
-
const points = list.reduce((acc, d) => {
|
|
79
|
-
Array.prototype.push.apply(acc, d.points.map((p) => ({
|
|
80
|
-
data: p.data,
|
|
81
|
-
series: p.series,
|
|
82
|
-
x: p.x,
|
|
83
|
-
y0: p.y0,
|
|
84
|
-
y1: p.y,
|
|
85
|
-
})));
|
|
86
|
-
return acc;
|
|
87
|
-
}, []);
|
|
88
|
-
result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
case 'line': {
|
|
92
|
-
const points = list.reduce((acc, d) => {
|
|
93
|
-
acc.push(...d.points.reduce((accPoints, p) => {
|
|
94
|
-
if (p.y !== null && p.x !== null) {
|
|
95
|
-
accPoints.push({
|
|
96
|
-
data: p.data,
|
|
97
|
-
series: p.series,
|
|
98
|
-
x: p.x,
|
|
99
|
-
y0: p.y,
|
|
100
|
-
y1: p.y,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return accPoints;
|
|
104
|
-
}, []));
|
|
105
|
-
return acc;
|
|
106
|
-
}, []);
|
|
107
|
-
result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
111
|
case 'bar-y': {
|
|
111
112
|
const points = list;
|
|
112
113
|
const sorted = sort(points, (p) => p.y);
|
|
@@ -267,6 +268,9 @@ export function getClosestPoints(args) {
|
|
|
267
268
|
}
|
|
268
269
|
}
|
|
269
270
|
});
|
|
271
|
+
if (closestPointsByXValue.length) {
|
|
272
|
+
result.push(...getClosestPointsByXValue(pointerX, pointerY, closestPointsByXValue));
|
|
273
|
+
}
|
|
270
274
|
return result;
|
|
271
275
|
}
|
|
272
276
|
function isInsidePath(args) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PreparedSeries, PreparedXAxis, PreparedYAxis } from '../../hooks';
|
|
1
|
+
import type { PreparedSeries, PreparedXAxis, PreparedYAxis, RangeSliderState } from '../../hooks';
|
|
2
2
|
import type { ZoomState } from '../../hooks/useZoom/types';
|
|
3
3
|
import type { ChartXAxis, ChartYAxis } from '../../types';
|
|
4
4
|
export declare function getZoomedSeriesData(args: {
|
|
@@ -10,3 +10,4 @@ export declare function getZoomedSeriesData(args: {
|
|
|
10
10
|
preparedSeries: PreparedSeries[];
|
|
11
11
|
preparedShapesSeries: PreparedSeries[];
|
|
12
12
|
};
|
|
13
|
+
export declare function getEffectiveXRange(zoomStateX: [number, number] | undefined, rangeSliderState: RangeSliderState | undefined): [number, number] | undefined;
|
|
@@ -123,3 +123,12 @@ export function getZoomedSeriesData(args) {
|
|
|
123
123
|
preparedShapesSeries: zoomedShapesSeriesData,
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
|
+
export function getEffectiveXRange(zoomStateX, rangeSliderState) {
|
|
127
|
+
if (zoomStateX && rangeSliderState) {
|
|
128
|
+
return [
|
|
129
|
+
Math.max(zoomStateX[0], rangeSliderState.min),
|
|
130
|
+
Math.min(zoomStateX[1], rangeSliderState.max),
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
return (zoomStateX !== null && zoomStateX !== void 0 ? zoomStateX : (rangeSliderState ? [rangeSliderState.min, rangeSliderState.max] : undefined));
|
|
134
|
+
}
|
|
@@ -32,7 +32,6 @@ export const AxisX = (props) => {
|
|
|
32
32
|
.attr('class', b('title'))
|
|
33
33
|
.append('text')
|
|
34
34
|
.attr('text-anchor', 'start')
|
|
35
|
-
.style('dominant-baseline', 'text-after-edge')
|
|
36
35
|
.style('transform', `translate(${preparedAxisData.title.x}px, ${preparedAxisData.title.y}px) rotate(${preparedAxisData.title.rotate}deg) translate(0px, ${preparedAxisData.title.offset}px)`)
|
|
37
36
|
.attr('font-size', preparedAxisData.title.style.fontSize)
|
|
38
37
|
.selectAll('tspan')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getUniqId } from '@gravity-ui/uikit';
|
|
2
|
-
import { calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis,
|
|
2
|
+
import { calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis, } from '../../utils';
|
|
3
3
|
import { getXAxisTickValues } from '../../utils/chart/axis/x-axis';
|
|
4
|
+
import { getMultilineTitleContentRows } from '../utils/axis-title';
|
|
4
5
|
async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWidth, axisWidth, boundsOffsetLeft, boundsOffsetRight, }) {
|
|
5
6
|
var _a;
|
|
6
7
|
const rotation = axis.labels.rotation;
|
|
@@ -189,23 +190,7 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
189
190
|
const titleContent = [];
|
|
190
191
|
const titleMaxWidth = axisWidth;
|
|
191
192
|
if (axis.title.maxRowCount > 1) {
|
|
192
|
-
|
|
193
|
-
text: axis.title.text,
|
|
194
|
-
style: axis.title.style,
|
|
195
|
-
width: titleMaxWidth,
|
|
196
|
-
getTextSize: getTitleTextSize,
|
|
197
|
-
});
|
|
198
|
-
for (let i = 0; i < axis.title.maxRowCount && i < titleTextRows.length; i++) {
|
|
199
|
-
const textRow = titleTextRows[i];
|
|
200
|
-
const textRowContent = textRow.text.trim();
|
|
201
|
-
const textRowSize = await getTitleTextSize(textRowContent);
|
|
202
|
-
titleContent.push({
|
|
203
|
-
text: textRowContent,
|
|
204
|
-
x: 0,
|
|
205
|
-
y: textRow.y,
|
|
206
|
-
size: textRowSize,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
193
|
+
titleContent.push(...(await getMultilineTitleContentRows({ axis, titleMaxWidth })));
|
|
209
194
|
}
|
|
210
195
|
else {
|
|
211
196
|
const text = await getTextWithElipsis({
|
|
@@ -245,11 +230,7 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
245
230
|
style: axis.title.style,
|
|
246
231
|
size: titleTextSize,
|
|
247
232
|
x,
|
|
248
|
-
y: height +
|
|
249
|
-
axis.labels.margin +
|
|
250
|
-
axis.labels.height +
|
|
251
|
-
axis.title.margin +
|
|
252
|
-
titleTextSize.height,
|
|
233
|
+
y: height + axis.labels.margin + axis.labels.height + axis.title.margin,
|
|
253
234
|
rotate: 0,
|
|
254
235
|
offset: 0,
|
|
255
236
|
};
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { DashStyle } from 'src/constants';
|
|
2
2
|
import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
|
|
3
|
-
|
|
4
|
-
text: string;
|
|
5
|
-
x: number;
|
|
6
|
-
y: number;
|
|
7
|
-
size: {
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
3
|
+
import type { TextRowData } from '../types';
|
|
12
4
|
export type AxisSvgLabelData = {
|
|
13
5
|
x: number;
|
|
14
6
|
y: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { calculateCos, calculateSin, getLabelsSize, getTextSizeFn, getTextWithElipsis,
|
|
1
|
+
import { calculateCos, calculateSin, getLabelsSize, getTextSizeFn, getTextWithElipsis, } from '../../utils';
|
|
2
|
+
import { getMultilineTitleContentRows } from '../utils/axis-title';
|
|
2
3
|
export async function prepareSvgYAxisTitle({ axis, axisTop, axisHeight, axisWidth, axisLabelsWidth, }) {
|
|
3
4
|
if (!axis.title.text || axis.title.html) {
|
|
4
5
|
return null;
|
|
@@ -10,39 +11,7 @@ export async function prepareSvgYAxisTitle({ axis, axisTop, axisHeight, axisWidt
|
|
|
10
11
|
const titleContent = [];
|
|
11
12
|
const titleMaxWidth = rotateAngle === 0 ? axis.title.maxWidth : sin * axisHeight;
|
|
12
13
|
if (axis.title.maxRowCount > 1) {
|
|
13
|
-
|
|
14
|
-
text: axis.title.text,
|
|
15
|
-
style: axis.title.style,
|
|
16
|
-
width: titleMaxWidth,
|
|
17
|
-
getTextSize: getTitleTextSize,
|
|
18
|
-
});
|
|
19
|
-
titleTextRows = titleTextRows.reduce((acc, row, index) => {
|
|
20
|
-
if (index < axis.title.maxRowCount) {
|
|
21
|
-
acc.push(row);
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
acc[axis.title.maxRowCount - 1].text += row.text;
|
|
25
|
-
}
|
|
26
|
-
return acc;
|
|
27
|
-
}, []);
|
|
28
|
-
for (let i = 0; i < titleTextRows.length; i++) {
|
|
29
|
-
const textRow = titleTextRows[i];
|
|
30
|
-
let textRowContent = textRow.text.trim();
|
|
31
|
-
if (i === titleTextRows.length - 1) {
|
|
32
|
-
textRowContent = await getTextWithElipsis({
|
|
33
|
-
text: textRowContent,
|
|
34
|
-
maxWidth: titleMaxWidth,
|
|
35
|
-
getTextWidth: async (s) => (await getTitleTextSize(s)).width,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
const textRowSize = await getTitleTextSize(textRowContent);
|
|
39
|
-
titleContent.push({
|
|
40
|
-
text: textRowContent,
|
|
41
|
-
x: 0,
|
|
42
|
-
y: textRow.y,
|
|
43
|
-
size: textRowSize,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
14
|
+
titleContent.push(...(await getMultilineTitleContentRows({ axis, titleMaxWidth })));
|
|
46
15
|
}
|
|
47
16
|
else {
|
|
48
17
|
const text = await getTextWithElipsis({
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { DashStyle } from 'src/constants';
|
|
2
2
|
import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
|
|
3
|
-
|
|
4
|
-
text: string;
|
|
5
|
-
x: number;
|
|
6
|
-
y: number;
|
|
7
|
-
size: {
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
3
|
+
import type { TextRowData } from '../types';
|
|
12
4
|
export type AxisSvgLabelData = {
|
|
13
5
|
x: number;
|
|
14
6
|
y: number;
|
|
@@ -26,7 +26,7 @@ import './styles.css';
|
|
|
26
26
|
const b = block('chart');
|
|
27
27
|
const DEBOUNCED_VALUE_DELAY = 10;
|
|
28
28
|
export const ChartInner = (props) => {
|
|
29
|
-
var _a, _b, _c, _d, _e
|
|
29
|
+
var _a, _b, _c, _d, _e;
|
|
30
30
|
const { width, height, data, onReady } = props;
|
|
31
31
|
const svgRef = React.useRef(null);
|
|
32
32
|
const resetZoomButtonRef = React.useRef(null);
|
|
@@ -60,13 +60,13 @@ export const ChartInner = (props) => {
|
|
|
60
60
|
}, [data.xAxis]);
|
|
61
61
|
const { initialized, setInitialized, tooltipPinned, togglePinTooltip, unpinTooltip, rangeSliderState, updateRangeSliderState, updateZoomState, zoomState, } = useChartInnerState({
|
|
62
62
|
dispatcher,
|
|
63
|
-
preparedChart,
|
|
64
63
|
preparedRangeSlider,
|
|
65
64
|
tooltip: preparedTooltip,
|
|
66
65
|
});
|
|
67
66
|
const { allPreparedSeries, boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedLegend, preparedSeries, preparedSeriesOptions, preparedSplit, prevHeight, prevWidth, shapes, shapesData, shapesReady, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
|
|
68
67
|
dispatcher,
|
|
69
|
-
htmlLayout, plotNode: plotRef.current, preparedChart,
|
|
68
|
+
htmlLayout, plotNode: plotRef.current, preparedChart,
|
|
69
|
+
rangeSliderState, svgContainer: svgRef.current, updateZoomState,
|
|
70
70
|
zoomState }));
|
|
71
71
|
const debouncedBoundsWidth = useDebouncedValue({
|
|
72
72
|
value: boundsWidth,
|
|
@@ -96,8 +96,8 @@ export const ChartInner = (props) => {
|
|
|
96
96
|
tooltipThrottle: preparedTooltip.throttle,
|
|
97
97
|
isOutsideBounds,
|
|
98
98
|
});
|
|
99
|
-
const clickHandler = (
|
|
100
|
-
const pointerMoveHandler = (
|
|
99
|
+
const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
|
|
100
|
+
const pointerMoveHandler = (_d = (_c = data.chart) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.pointermove;
|
|
101
101
|
const prevRangeSliderDefaultRange = usePrevious(preparedRangeSlider.defaultRange);
|
|
102
102
|
useCrosshair({
|
|
103
103
|
split: preparedSplit,
|
|
@@ -246,7 +246,7 @@ export const ChartInner = (props) => {
|
|
|
246
246
|
React.createElement("g", { ref: plotBeforeRef }),
|
|
247
247
|
shapes,
|
|
248
248
|
React.createElement("g", { ref: plotAfterRef })),
|
|
249
|
-
((
|
|
249
|
+
((_e = xAxis === null || xAxis === void 0 ? void 0 : xAxis.rangeSlider) === null || _e === void 0 ? void 0 : _e.enabled) && (React.createElement(RangeSlider, { boundsOffsetLeft: debouncedOffsetLeft, boundsWidth: debouncedBoundsWidth, height: height, htmlLayout: htmlLayout, onUpdate: updateRangeSliderState, preparedChart: preparedChart, preparedLegend: preparedLegend, preparedSeries: debouncedAllPreparedSeries, preparedSeriesOptions: preparedSeriesOptions, preparedRangeSlider: xAxis.rangeSlider, rangeSliderState: rangeSliderState, ref: rangeSliderRef, width: width, xAxis: data.xAxis, yAxis: data.yAxis })),
|
|
250
250
|
(preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && legendConfig && (React.createElement(Legend, { chartSeries: preparedSeries, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip, htmlLayout: htmlLayout }))));
|
|
251
251
|
return (React.createElement("div", { className: b() },
|
|
252
252
|
React.createElement("svg", { ref: svgRef, width: width, height: height,
|
|
@@ -11,7 +11,6 @@ type Props = ChartInnerProps & {
|
|
|
11
11
|
svgContainer: SVGGElement | null;
|
|
12
12
|
updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
|
|
13
13
|
zoomState: Partial<ZoomState>;
|
|
14
|
-
rangeSliderDomain?: [number, number];
|
|
15
14
|
rangeSliderState?: RangeSliderState;
|
|
16
15
|
};
|
|
17
16
|
export declare function useChartInnerProps(props: Props): {
|
|
@@ -4,7 +4,7 @@ import { useAxis, useAxisScales, useChartDimensions, useNormalizedOriginalData,
|
|
|
4
4
|
import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
|
|
5
5
|
import { getLegendComponents } from '../../hooks/useSeries/prepare-legend';
|
|
6
6
|
import { getPreparedOptions } from '../../hooks/useSeries/prepare-options';
|
|
7
|
-
import { getZoomedSeriesData } from '../../utils';
|
|
7
|
+
import { getEffectiveXRange, getZoomedSeriesData } from '../../utils';
|
|
8
8
|
import { hasAtLeastOneSeriesDataPerPlot } from './utils';
|
|
9
9
|
const CLIP_PATH_BY_SERIES_TYPE = {
|
|
10
10
|
[SERIES_TYPE.Scatter]: false,
|
|
@@ -35,7 +35,7 @@ function getBoundsOffsetLeft(args) {
|
|
|
35
35
|
}
|
|
36
36
|
export function useChartInnerProps(props) {
|
|
37
37
|
var _a;
|
|
38
|
-
const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, preparedChart,
|
|
38
|
+
const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, preparedChart, rangeSliderState, svgContainer, width, updateZoomState, zoomState, } = props;
|
|
39
39
|
const prevWidth = usePrevious(width);
|
|
40
40
|
const prevHeight = usePrevious(height);
|
|
41
41
|
const colors = React.useMemo(() => {
|
|
@@ -57,14 +57,25 @@ export function useChartInnerProps(props) {
|
|
|
57
57
|
seriesData: normalizedSeriesData,
|
|
58
58
|
seriesOptions: data.series.options,
|
|
59
59
|
});
|
|
60
|
+
const effectiveZoomState = React.useMemo(() => {
|
|
61
|
+
const result = {};
|
|
62
|
+
const effectiveX = getEffectiveXRange(zoomState.x, rangeSliderState);
|
|
63
|
+
if (effectiveX !== undefined) {
|
|
64
|
+
result.x = effectiveX;
|
|
65
|
+
}
|
|
66
|
+
if (zoomState.y !== undefined) {
|
|
67
|
+
result.y = zoomState.y;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}, [zoomState, rangeSliderState]);
|
|
60
71
|
const { preparedSeries, preparedShapesSeries } = React.useMemo(() => {
|
|
61
72
|
return getZoomedSeriesData({
|
|
62
73
|
seriesData: allPreparedSeries,
|
|
63
74
|
xAxis: normalizedXAxis,
|
|
64
75
|
yAxis: normalizedYAxis,
|
|
65
|
-
zoomState,
|
|
76
|
+
zoomState: effectiveZoomState,
|
|
66
77
|
});
|
|
67
|
-
}, [allPreparedSeries, normalizedXAxis, normalizedYAxis,
|
|
78
|
+
}, [allPreparedSeries, normalizedXAxis, normalizedYAxis, effectiveZoomState]);
|
|
68
79
|
const { legendConfig, legendItems } = React.useMemo(() => {
|
|
69
80
|
if (!preparedLegend) {
|
|
70
81
|
return { legendConfig: undefined, legendItems: [] };
|
|
@@ -126,7 +137,7 @@ export function useChartInnerProps(props) {
|
|
|
126
137
|
htmlLayout,
|
|
127
138
|
clipPathId,
|
|
128
139
|
isOutsideBounds,
|
|
129
|
-
zoomState,
|
|
140
|
+
zoomState: effectiveZoomState,
|
|
130
141
|
});
|
|
131
142
|
const handleAttemptToSetZoomState = React.useCallback((nextZoomState) => {
|
|
132
143
|
const { preparedSeries: nextZoomedSeriesData } = getZoomedSeriesData({
|
|
@@ -147,7 +158,6 @@ export function useChartInnerProps(props) {
|
|
|
147
158
|
plotContainerWidth: boundsWidth,
|
|
148
159
|
preparedSplit,
|
|
149
160
|
preparedZoom: preparedChart.zoom,
|
|
150
|
-
rangeSliderDomain,
|
|
151
161
|
xAxis,
|
|
152
162
|
xScale,
|
|
153
163
|
yAxis,
|