@gravity-ui/charts 1.12.0 → 1.13.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/Axis/AxisX.js +62 -36
- package/dist/cjs/components/Axis/AxisY.js +67 -31
- package/dist/cjs/components/ChartInner/styles.css +1 -0
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +3 -3
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +1 -1
- package/dist/cjs/hooks/useAxisScales/index.js +19 -6
- package/dist/cjs/hooks/useChartOptions/types.d.ts +5 -0
- package/dist/cjs/hooks/useChartOptions/utils.d.ts +11 -0
- package/dist/cjs/hooks/useChartOptions/utils.js +27 -0
- package/dist/cjs/hooks/useChartOptions/x-axis.js +5 -1
- package/dist/cjs/hooks/useChartOptions/y-axis.js +5 -1
- package/dist/cjs/hooks/useSeries/prepare-area.d.ts +1 -1
- package/dist/cjs/hooks/useSeries/prepare-bar-y.d.ts +3 -0
- package/dist/cjs/hooks/useSeries/prepare-bar-y.js +5 -2
- package/dist/cjs/hooks/useSeries/prepare-line.d.ts +1 -1
- package/dist/cjs/hooks/useSeries/prepare-radar.d.ts +1 -1
- package/dist/cjs/hooks/useSeries/types.d.ts +3 -0
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +15 -12
- package/dist/cjs/hooks/useShapes/bar-y/index.d.ts +2 -2
- package/dist/cjs/hooks/useShapes/bar-y/index.js +5 -9
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.d.ts +2 -2
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +65 -47
- package/dist/cjs/hooks/useShapes/bar-y/types.d.ts +7 -2
- package/dist/cjs/hooks/useShapes/index.js +1 -1
- package/dist/cjs/hooks/utils/bar-y.d.ts +3 -3
- package/dist/cjs/hooks/utils/bar-y.js +7 -21
- package/dist/cjs/types/chart/axis.d.ts +13 -1
- package/dist/cjs/types/chart/bar-y.d.ts +10 -0
- package/dist/cjs/types/chart/series.d.ts +10 -0
- package/dist/cjs/utils/chart/axis-generators/bottom.js +26 -13
- package/dist/cjs/utils/chart/get-closest-data.js +13 -12
- package/dist/cjs/utils/chart/index.js +1 -1
- package/dist/cjs/utils/chart/series/sorting.d.ts +6 -2
- package/dist/cjs/utils/chart/series/sorting.js +29 -4
- package/dist/cjs/utils/chart/zoom.js +2 -1
- package/dist/esm/components/Axis/AxisX.js +62 -36
- package/dist/esm/components/Axis/AxisY.js +67 -31
- package/dist/esm/components/ChartInner/styles.css +1 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.js +3 -3
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +1 -1
- package/dist/esm/hooks/useAxisScales/index.js +19 -6
- package/dist/esm/hooks/useChartOptions/types.d.ts +5 -0
- package/dist/esm/hooks/useChartOptions/utils.d.ts +11 -0
- package/dist/esm/hooks/useChartOptions/utils.js +27 -0
- package/dist/esm/hooks/useChartOptions/x-axis.js +5 -1
- package/dist/esm/hooks/useChartOptions/y-axis.js +5 -1
- package/dist/esm/hooks/useSeries/prepare-area.d.ts +1 -1
- package/dist/esm/hooks/useSeries/prepare-bar-y.d.ts +3 -0
- package/dist/esm/hooks/useSeries/prepare-bar-y.js +5 -2
- package/dist/esm/hooks/useSeries/prepare-line.d.ts +1 -1
- package/dist/esm/hooks/useSeries/prepare-radar.d.ts +1 -1
- package/dist/esm/hooks/useSeries/types.d.ts +3 -0
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +15 -12
- package/dist/esm/hooks/useShapes/bar-y/index.d.ts +2 -2
- package/dist/esm/hooks/useShapes/bar-y/index.js +5 -9
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.d.ts +2 -2
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +65 -47
- package/dist/esm/hooks/useShapes/bar-y/types.d.ts +7 -2
- package/dist/esm/hooks/useShapes/index.js +1 -1
- package/dist/esm/hooks/utils/bar-y.d.ts +3 -3
- package/dist/esm/hooks/utils/bar-y.js +7 -21
- package/dist/esm/types/chart/axis.d.ts +13 -1
- package/dist/esm/types/chart/bar-y.d.ts +10 -0
- package/dist/esm/types/chart/series.d.ts +10 -0
- package/dist/esm/utils/chart/axis-generators/bottom.js +26 -13
- package/dist/esm/utils/chart/get-closest-data.js +13 -12
- package/dist/esm/utils/chart/index.js +1 -1
- package/dist/esm/utils/chart/series/sorting.d.ts +6 -2
- package/dist/esm/utils/chart/series/sorting.js +29 -4
- package/dist/esm/utils/chart/zoom.js +2 -1
- package/package.json +1 -1
|
@@ -95,36 +95,39 @@ export const prepareBarXData = async (args) => {
|
|
|
95
95
|
const rectGap = Math.max(bandWidth * barPadding, MIN_BAR_GAP);
|
|
96
96
|
const rectWidth = Math.max(MIN_BAR_WIDTH, Math.min(groupWidth / maxGroupSize - rectGap, barMaxWidth));
|
|
97
97
|
const result = [];
|
|
98
|
-
|
|
98
|
+
const groupedData = Object.entries(data);
|
|
99
|
+
for (let groupedDataIndex = 0; groupedDataIndex < groupedData.length; groupedDataIndex++) {
|
|
100
|
+
const [xValue, val] = groupedData[groupedDataIndex];
|
|
99
101
|
const stacks = Object.values(val);
|
|
100
102
|
const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
|
|
101
|
-
|
|
103
|
+
for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
|
|
104
|
+
const yValues = stacks[groupItemIndex];
|
|
102
105
|
let stackHeight = 0;
|
|
103
106
|
const stackItems = [];
|
|
104
107
|
const sortedData = sortKey
|
|
105
108
|
? sort(yValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
|
|
106
109
|
: yValues;
|
|
107
|
-
|
|
110
|
+
for (let yValueIndex = 0; yValueIndex < sortedData.length; yValueIndex++) {
|
|
111
|
+
const yValue = sortedData[yValueIndex];
|
|
108
112
|
const yAxisIndex = yValue.series.yAxis;
|
|
109
113
|
const seriesYScale = yScale[yAxisIndex];
|
|
110
114
|
let xCenter;
|
|
111
115
|
if (xAxis.type === 'category') {
|
|
112
116
|
const xBandScale = xScale;
|
|
113
|
-
xCenter =
|
|
114
|
-
(xBandScale(xValue) || 0) +
|
|
115
|
-
xBandScale.bandwidth() / 2;
|
|
117
|
+
xCenter = (xBandScale(xValue) || 0) + xBandScale.bandwidth() / 2;
|
|
116
118
|
}
|
|
117
119
|
else {
|
|
118
120
|
const xLinearScale = xScale;
|
|
119
121
|
xCenter = xLinearScale(Number(xValue));
|
|
120
122
|
}
|
|
121
|
-
const x = xCenter -
|
|
122
|
-
currentGroupWidth / 2 +
|
|
123
|
-
(rectWidth + rectGap) * groupItemIndex;
|
|
123
|
+
const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
|
|
124
124
|
const yDataValue = yValue.data.y;
|
|
125
125
|
const y = seriesYScale(yDataValue);
|
|
126
126
|
const base = seriesYScale(0);
|
|
127
127
|
const height = yDataValue > 0 ? base - y : y - base;
|
|
128
|
+
if (height <= 0) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
128
131
|
const barData = {
|
|
129
132
|
x,
|
|
130
133
|
y: yDataValue > 0 ? y - stackHeight : seriesYScale(0),
|
|
@@ -151,7 +154,7 @@ export const prepareBarXData = async (args) => {
|
|
|
151
154
|
}
|
|
152
155
|
stackItems.push(barData);
|
|
153
156
|
stackHeight += height + 1;
|
|
154
|
-
}
|
|
157
|
+
}
|
|
155
158
|
if (series.some((s) => s.stacking === 'percent')) {
|
|
156
159
|
let acc = 0;
|
|
157
160
|
const ratio = plotHeight / (stackHeight - stackItems.length);
|
|
@@ -162,7 +165,7 @@ export const prepareBarXData = async (args) => {
|
|
|
162
165
|
});
|
|
163
166
|
}
|
|
164
167
|
result.push(...stackItems);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
167
170
|
return result;
|
|
168
171
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Dispatch } from 'd3';
|
|
3
3
|
import type { PreparedSeriesOptions } from '../../useSeries/types';
|
|
4
|
-
import type {
|
|
4
|
+
import type { BarYShapesArgs } from './types';
|
|
5
5
|
export { prepareBarYData } from './prepare-data';
|
|
6
6
|
type Args = {
|
|
7
7
|
dispatcher: Dispatch<object>;
|
|
8
|
-
preparedData:
|
|
8
|
+
preparedData: BarYShapesArgs;
|
|
9
9
|
seriesOptions: PreparedSeriesOptions;
|
|
10
10
|
htmlLayout: HTMLElement | null;
|
|
11
11
|
clipPathId: string;
|
|
@@ -7,7 +7,7 @@ import { getRectPath } from '../utils';
|
|
|
7
7
|
export { prepareBarYData } from './prepare-data';
|
|
8
8
|
const b = block('bar-y');
|
|
9
9
|
export const BarYSeriesShapes = (args) => {
|
|
10
|
-
const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
|
|
10
|
+
const { dispatcher, preparedData: { shapes: preparedData, labels: dataLabels, htmlElements }, seriesOptions, htmlLayout, clipPathId, } = args;
|
|
11
11
|
const hoveredDataRef = React.useRef(null);
|
|
12
12
|
const ref = React.useRef(null);
|
|
13
13
|
React.useEffect(() => {
|
|
@@ -39,14 +39,10 @@ export const BarYSeriesShapes = (args) => {
|
|
|
39
39
|
.attr('height', (d) => d.height)
|
|
40
40
|
.attr('width', (d) => d.width)
|
|
41
41
|
.attr('fill', (d) => d.color)
|
|
42
|
+
.attr('stroke', (d) => d.borderColor)
|
|
43
|
+
.attr('stroke-width', (d) => d.borderWidth)
|
|
42
44
|
.attr('opacity', (d) => d.data.opacity || null)
|
|
43
45
|
.attr('cursor', (d) => d.series.cursor);
|
|
44
|
-
const dataLabels = preparedData.reduce((acc, d) => {
|
|
45
|
-
if (d.label) {
|
|
46
|
-
acc.push(d.label);
|
|
47
|
-
}
|
|
48
|
-
return acc;
|
|
49
|
-
}, []);
|
|
50
46
|
const labelSelection = svgElement
|
|
51
47
|
.selectAll('text')
|
|
52
48
|
.data(dataLabels)
|
|
@@ -97,8 +93,8 @@ export const BarYSeriesShapes = (args) => {
|
|
|
97
93
|
return () => {
|
|
98
94
|
dispatcher.on('hover-shape.bar-y', null);
|
|
99
95
|
};
|
|
100
|
-
}, [dispatcher, preparedData, seriesOptions]);
|
|
96
|
+
}, [dataLabels, dispatcher, preparedData, seriesOptions]);
|
|
101
97
|
return (React.createElement(React.Fragment, null,
|
|
102
98
|
React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
103
|
-
React.createElement(HtmlLayer, { preparedData:
|
|
99
|
+
React.createElement(HtmlLayer, { preparedData: { htmlElements }, htmlLayout: htmlLayout })));
|
|
104
100
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChartScale } from '../../useAxisScales';
|
|
2
2
|
import type { PreparedAxis } from '../../useChartOptions/types';
|
|
3
3
|
import type { PreparedBarYSeries, PreparedSeriesOptions } from '../../useSeries/types';
|
|
4
|
-
import type {
|
|
4
|
+
import type { BarYShapesArgs } from './types';
|
|
5
5
|
export declare const prepareBarYData: (args: {
|
|
6
6
|
series: PreparedBarYSeries[];
|
|
7
7
|
seriesOptions: PreparedSeriesOptions;
|
|
@@ -9,4 +9,4 @@ export declare const prepareBarYData: (args: {
|
|
|
9
9
|
xScale: ChartScale;
|
|
10
10
|
yAxis: PreparedAxis[];
|
|
11
11
|
yScale: ChartScale[];
|
|
12
|
-
}) => Promise<
|
|
12
|
+
}) => Promise<BarYShapesArgs>;
|
|
@@ -1,47 +1,11 @@
|
|
|
1
1
|
import { ascending, descending, sort } from 'd3';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
|
-
import { getLabelsSize } from '../../../utils';
|
|
3
|
+
import { filterOverlappingLabels, getLabelsSize, getTextSizeFn } from '../../../utils';
|
|
4
4
|
import { getFormattedValue } from '../../../utils/chart/format';
|
|
5
5
|
import { getBarYLayoutForCategoryScale, getBarYLayoutForNumericScale, groupBarYDataByYValue, } from '../../utils';
|
|
6
6
|
const DEFAULT_LABEL_PADDING = 7;
|
|
7
|
-
async function setLabel(prepared) {
|
|
8
|
-
const dataLabels = prepared.series.dataLabels;
|
|
9
|
-
if (!dataLabels.enabled) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
const data = prepared.data;
|
|
13
|
-
const content = getFormattedValue(Object.assign({ value: data.label || data.x }, dataLabels));
|
|
14
|
-
const { maxHeight: height, maxWidth: width } = await getLabelsSize({
|
|
15
|
-
labels: [content],
|
|
16
|
-
style: dataLabels.style,
|
|
17
|
-
html: dataLabels.html,
|
|
18
|
-
});
|
|
19
|
-
const x = dataLabels.inside
|
|
20
|
-
? prepared.x + prepared.width / 2
|
|
21
|
-
: prepared.x + prepared.width + DEFAULT_LABEL_PADDING;
|
|
22
|
-
const y = prepared.y + prepared.height / 2;
|
|
23
|
-
if (dataLabels.html) {
|
|
24
|
-
prepared.htmlElements.push({
|
|
25
|
-
x,
|
|
26
|
-
y: y - height / 2,
|
|
27
|
-
content,
|
|
28
|
-
size: { width, height },
|
|
29
|
-
style: dataLabels.style,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
prepared.label = {
|
|
34
|
-
x,
|
|
35
|
-
y: y + height / 2,
|
|
36
|
-
text: content,
|
|
37
|
-
textAnchor: dataLabels.inside ? 'middle' : 'right',
|
|
38
|
-
style: dataLabels.style,
|
|
39
|
-
series: prepared.series,
|
|
40
|
-
size: { width, height },
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
7
|
export const prepareBarYData = async (args) => {
|
|
8
|
+
var _a;
|
|
45
9
|
const { series, seriesOptions, yAxis, xScale, yScale: [yScale], } = args;
|
|
46
10
|
const xLinearScale = xScale;
|
|
47
11
|
const yLinearScale = yScale;
|
|
@@ -66,16 +30,17 @@ export const prepareBarYData = async (args) => {
|
|
|
66
30
|
const { bandSize, barGap, barSize } = yAxis[0].type === 'category'
|
|
67
31
|
? getBarYLayoutForCategoryScale({ groupedData, seriesOptions, yScale })
|
|
68
32
|
: getBarYLayoutForNumericScale({
|
|
69
|
-
|
|
33
|
+
groupedData,
|
|
70
34
|
seriesOptions,
|
|
71
35
|
plotHeight: plotHeight - plotHeight * yAxis[0].maxPadding,
|
|
72
36
|
});
|
|
73
37
|
const result = [];
|
|
38
|
+
const baseRangeValue = xLinearScale.range()[0];
|
|
74
39
|
Object.entries(groupedData).forEach(([yValue, val]) => {
|
|
75
40
|
const stacks = Object.values(val);
|
|
76
41
|
const currentBarHeight = barSize * stacks.length + barGap * (stacks.length - 1);
|
|
77
42
|
stacks.forEach((measureValues, groupItemIndex) => {
|
|
78
|
-
const base = xLinearScale(0);
|
|
43
|
+
const base = xLinearScale(0 - measureValues[0].series.borderWidth);
|
|
79
44
|
let stackSum = base;
|
|
80
45
|
const stackItems = [];
|
|
81
46
|
const sortedData = sortKey
|
|
@@ -93,17 +58,21 @@ export const prepareBarYData = async (args) => {
|
|
|
93
58
|
}
|
|
94
59
|
const y = center - currentBarHeight / 2 + (barSize + barGap) * groupItemIndex;
|
|
95
60
|
const xValue = Number(data.x);
|
|
96
|
-
const width =
|
|
61
|
+
const width = Math.abs(xLinearScale(xValue) - base);
|
|
62
|
+
if (width <= 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
97
65
|
const item = {
|
|
98
|
-
x: xValue >
|
|
66
|
+
x: xValue > baseRangeValue ? stackSum : stackSum - width,
|
|
99
67
|
y,
|
|
100
68
|
width,
|
|
101
69
|
height: barSize,
|
|
102
70
|
color: data.color || s.color,
|
|
71
|
+
borderColor: s.borderColor,
|
|
72
|
+
borderWidth: s.borderWidth,
|
|
103
73
|
opacity: get(data, 'opacity', null),
|
|
104
74
|
data,
|
|
105
75
|
series: s,
|
|
106
|
-
htmlElements: [],
|
|
107
76
|
isLastStackItem: xValueIndex === sortedData.length - 1,
|
|
108
77
|
};
|
|
109
78
|
stackItems.push(item);
|
|
@@ -121,8 +90,57 @@ export const prepareBarYData = async (args) => {
|
|
|
121
90
|
result.push(...stackItems);
|
|
122
91
|
});
|
|
123
92
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
93
|
+
let labels = [];
|
|
94
|
+
const htmlElements = [];
|
|
95
|
+
const map = new Map();
|
|
96
|
+
for (let i = 0; i < result.length; i++) {
|
|
97
|
+
const prepared = result[i];
|
|
98
|
+
const dataLabels = prepared.series.dataLabels;
|
|
99
|
+
if (dataLabels.enabled) {
|
|
100
|
+
const data = prepared.data;
|
|
101
|
+
const content = getFormattedValue(Object.assign({ value: data.label || data.x }, dataLabels));
|
|
102
|
+
const x = dataLabels.inside
|
|
103
|
+
? prepared.x + prepared.width / 2
|
|
104
|
+
: prepared.x + prepared.width + DEFAULT_LABEL_PADDING;
|
|
105
|
+
const y = prepared.y + prepared.height / 2;
|
|
106
|
+
if (dataLabels.html) {
|
|
107
|
+
const { maxHeight: height, maxWidth: width } = await getLabelsSize({
|
|
108
|
+
labels: [content],
|
|
109
|
+
style: dataLabels.style,
|
|
110
|
+
html: dataLabels.html,
|
|
111
|
+
});
|
|
112
|
+
htmlElements.push({
|
|
113
|
+
x,
|
|
114
|
+
y: y - height / 2,
|
|
115
|
+
content,
|
|
116
|
+
size: { width, height },
|
|
117
|
+
style: dataLabels.style,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
if (!map.has(dataLabels.style)) {
|
|
122
|
+
map.set(dataLabels.style, getTextSizeFn({ style: dataLabels.style }));
|
|
123
|
+
}
|
|
124
|
+
const getTextSize = map.get(dataLabels.style);
|
|
125
|
+
const { width, height } = await getTextSize(content);
|
|
126
|
+
labels.push({
|
|
127
|
+
x,
|
|
128
|
+
y: y + height / 2,
|
|
129
|
+
text: content,
|
|
130
|
+
textAnchor: dataLabels.inside ? 'middle' : 'right',
|
|
131
|
+
style: dataLabels.style,
|
|
132
|
+
series: prepared.series,
|
|
133
|
+
size: { width, height },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!((_a = result[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
|
|
139
|
+
labels = filterOverlappingLabels(labels);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
shapes: result,
|
|
143
|
+
labels,
|
|
144
|
+
htmlElements,
|
|
145
|
+
};
|
|
128
146
|
};
|
|
@@ -6,9 +6,14 @@ export type PreparedBarYData = Omit<TooltipDataChunkBarX, 'series'> & {
|
|
|
6
6
|
width: number;
|
|
7
7
|
height: number;
|
|
8
8
|
color: string;
|
|
9
|
+
borderWidth: number;
|
|
10
|
+
borderColor: string;
|
|
9
11
|
opacity: number | null;
|
|
10
12
|
series: PreparedBarYSeries;
|
|
11
|
-
label?: LabelData;
|
|
12
|
-
htmlElements: HtmlItem[];
|
|
13
13
|
isLastStackItem: boolean;
|
|
14
14
|
};
|
|
15
|
+
export type BarYShapesArgs = {
|
|
16
|
+
shapes: PreparedBarYData[];
|
|
17
|
+
labels: LabelData[];
|
|
18
|
+
htmlElements: HtmlItem[];
|
|
19
|
+
};
|
|
@@ -64,7 +64,7 @@ export const useShapes = (args) => {
|
|
|
64
64
|
yScale,
|
|
65
65
|
});
|
|
66
66
|
shapes.push(React.createElement(BarYSeriesShapes, { key: "bar-y", dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
|
|
67
|
-
shapesData.push(...preparedData);
|
|
67
|
+
shapesData.push(...preparedData.shapes);
|
|
68
68
|
}
|
|
69
69
|
break;
|
|
70
70
|
}
|
|
@@ -2,14 +2,14 @@ import type { BarYSeries, BarYSeriesData } from '../../types';
|
|
|
2
2
|
import type { ChartScale } from '../useAxisScales';
|
|
3
3
|
import type { PreparedAxis } from '../useChartOptions/types';
|
|
4
4
|
import type { PreparedBarYSeries, PreparedSeriesOptions } from '../useSeries/types';
|
|
5
|
-
export declare function groupBarYDataByYValue(series:
|
|
5
|
+
export declare function groupBarYDataByYValue<T extends BarYSeries | PreparedBarYSeries>(series: T[], yAxis: PreparedAxis[]): Record<string | number, Record<string, {
|
|
6
6
|
data: BarYSeriesData;
|
|
7
|
-
series:
|
|
7
|
+
series: T;
|
|
8
8
|
}[]>>;
|
|
9
9
|
export declare function getBarYLayoutForNumericScale(args: {
|
|
10
10
|
plotHeight: number;
|
|
11
|
-
series: (BarYSeries | PreparedBarYSeries)[];
|
|
12
11
|
seriesOptions: PreparedSeriesOptions;
|
|
12
|
+
groupedData: ReturnType<typeof groupBarYDataByYValue>;
|
|
13
13
|
}): {
|
|
14
14
|
bandSize: number;
|
|
15
15
|
barGap: number;
|
|
@@ -2,6 +2,7 @@ import { max } from 'd3';
|
|
|
2
2
|
import get from 'lodash/get';
|
|
3
3
|
import { getDataCategoryValue } from '../../utils';
|
|
4
4
|
import { MIN_BAR_GAP, MIN_BAR_GROUP_GAP, MIN_BAR_WIDTH } from '../constants';
|
|
5
|
+
import { getSeriesStackId } from '../useSeries/utils';
|
|
5
6
|
export function groupBarYDataByYValue(series, yAxis) {
|
|
6
7
|
const data = {};
|
|
7
8
|
series.forEach((s) => {
|
|
@@ -16,37 +17,22 @@ export function groupBarYDataByYValue(series, yAxis) {
|
|
|
16
17
|
if (!data[key]) {
|
|
17
18
|
data[key] = {};
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const stackId = getSeriesStackId(s);
|
|
21
|
+
if (!data[key][stackId]) {
|
|
22
|
+
data[key][stackId] = [];
|
|
21
23
|
}
|
|
22
|
-
data[key][
|
|
24
|
+
data[key][stackId].push({ data: d, series: s });
|
|
23
25
|
}
|
|
24
26
|
});
|
|
25
27
|
});
|
|
26
28
|
return data;
|
|
27
29
|
}
|
|
28
30
|
export function getBarYLayoutForNumericScale(args) {
|
|
29
|
-
const { plotHeight,
|
|
31
|
+
const { plotHeight, groupedData, seriesOptions } = args;
|
|
30
32
|
const barMaxWidth = get(seriesOptions, 'bar-y.barMaxWidth');
|
|
31
33
|
const barPadding = get(seriesOptions, 'bar-y.barPadding');
|
|
32
34
|
const groupPadding = get(seriesOptions, 'bar-y.groupPadding');
|
|
33
|
-
|
|
34
|
-
const yValuesByStackingIdMap = {};
|
|
35
|
-
series.forEach((s) => {
|
|
36
|
-
s.data.forEach((d) => {
|
|
37
|
-
if (s.stackId) {
|
|
38
|
-
if (!yValuesByStackingIdMap[s.stackId]) {
|
|
39
|
-
yValuesByStackingIdMap[s.stackId] = new Set();
|
|
40
|
-
}
|
|
41
|
-
yValuesByStackingIdMap[s.stackId].add(Number(d.y));
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
yValuesWithoutStacking += 1;
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
const stackedSeriesLength = Object.values(yValuesByStackingIdMap).reduce((acc, set) => acc + set.size, 0);
|
|
49
|
-
const dataLength = yValuesWithoutStacking + stackedSeriesLength;
|
|
35
|
+
const dataLength = Object.values(groupedData).reduce((sum, items) => sum + Object.keys(items).length, 0);
|
|
50
36
|
const bandSize = plotHeight / dataLength;
|
|
51
37
|
const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
|
|
52
38
|
const groupSize = bandSize - groupGap;
|
|
@@ -78,6 +78,10 @@ export interface ChartAxis {
|
|
|
78
78
|
plotBands?: AxisPlotBand[];
|
|
79
79
|
/** Whether axis, including axis title, line, ticks and labels, should be visible. */
|
|
80
80
|
visible?: boolean;
|
|
81
|
+
/** Setting the order of the axis values. It is not applied by default.
|
|
82
|
+
* the "reverse" value is needed to use the reverse order without sorting
|
|
83
|
+
*/
|
|
84
|
+
order?: 'sortAsc' | 'sortDesc' | 'reverse';
|
|
81
85
|
}
|
|
82
86
|
export interface ChartXAxis extends ChartAxis {
|
|
83
87
|
}
|
|
@@ -92,6 +96,14 @@ export interface AxisPlot {
|
|
|
92
96
|
* @default 1
|
|
93
97
|
* */
|
|
94
98
|
opacity?: number;
|
|
99
|
+
label?: {
|
|
100
|
+
text: string;
|
|
101
|
+
style?: Partial<BaseTextStyle>;
|
|
102
|
+
/** The pixel padding for label.
|
|
103
|
+
* @default 5
|
|
104
|
+
*/
|
|
105
|
+
padding?: number;
|
|
106
|
+
};
|
|
95
107
|
}
|
|
96
108
|
export interface AxisPlotLine extends AxisPlot {
|
|
97
109
|
/** The position of the line in axis units. */
|
|
@@ -121,7 +133,7 @@ export interface AxisPlotBand extends AxisPlot {
|
|
|
121
133
|
*/
|
|
122
134
|
to: number | string;
|
|
123
135
|
}
|
|
124
|
-
export interface AxisCrosshair extends Omit<AxisPlotLine, 'value'> {
|
|
136
|
+
export interface AxisCrosshair extends Omit<AxisPlotLine, 'value' | 'label'> {
|
|
125
137
|
/** Whether the crosshair should snap to the point or follow the pointer independent of points.
|
|
126
138
|
* @default true
|
|
127
139
|
*/
|
|
@@ -29,6 +29,16 @@ export interface BarYSeries<T = MeaningfulAny> extends BaseSeries {
|
|
|
29
29
|
name: string;
|
|
30
30
|
/** The main color of the series (hex, rgba) */
|
|
31
31
|
color?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The width of the border surrounding each bar.
|
|
34
|
+
*
|
|
35
|
+
* @default 0
|
|
36
|
+
*/
|
|
37
|
+
borderWidth?: number;
|
|
38
|
+
/**
|
|
39
|
+
* The color of the border surrounding each bar.
|
|
40
|
+
*/
|
|
41
|
+
borderColor?: string;
|
|
32
42
|
/**
|
|
33
43
|
* The corner radius of the border surrounding each bar.
|
|
34
44
|
* @default 0
|
|
@@ -115,6 +115,16 @@ export interface ChartSeriesOptions {
|
|
|
115
115
|
* @default 0.2
|
|
116
116
|
*/
|
|
117
117
|
groupPadding?: number;
|
|
118
|
+
/**
|
|
119
|
+
* The width of the border surrounding each bar.
|
|
120
|
+
*
|
|
121
|
+
* @default 0
|
|
122
|
+
*/
|
|
123
|
+
borderWidth?: number;
|
|
124
|
+
/**
|
|
125
|
+
* The color of the border surrounding each bar.
|
|
126
|
+
*/
|
|
127
|
+
borderColor?: string;
|
|
118
128
|
/**
|
|
119
129
|
* The corner radius of the border surrounding each bar.
|
|
120
130
|
* @default 0
|
|
@@ -93,18 +93,31 @@ export async function axisBottom(args) {
|
|
|
93
93
|
let elementX = 0;
|
|
94
94
|
// add an ellipsis to the labels that go beyond the boundaries of the chart
|
|
95
95
|
// and remove overlapping labels
|
|
96
|
-
labels
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
96
|
+
labels
|
|
97
|
+
.nodes()
|
|
98
|
+
.map((element) => {
|
|
99
|
+
const r = element.getBoundingClientRect();
|
|
100
|
+
return {
|
|
101
|
+
left: r.left,
|
|
102
|
+
right: r.right,
|
|
103
|
+
node: element,
|
|
104
|
+
};
|
|
105
|
+
}, {})
|
|
106
|
+
.sort((a, b) => {
|
|
107
|
+
return a.left - b.left;
|
|
108
|
+
})
|
|
109
|
+
.forEach(function (item, i, nodes) {
|
|
110
|
+
var _a, _b, _c, _d;
|
|
111
|
+
const { node, left, right: currentElementPositionRigth } = item;
|
|
112
|
+
const currentElement = node;
|
|
100
113
|
if (i === 0) {
|
|
101
114
|
const text = select(currentElement);
|
|
102
|
-
const nextElement = nodes[i + 1];
|
|
115
|
+
const nextElement = (_a = nodes[i + 1]) === null || _a === void 0 ? void 0 : _a.node;
|
|
103
116
|
const nextElementPosition = nextElement === null || nextElement === void 0 ? void 0 : nextElement.getBoundingClientRect();
|
|
104
|
-
if (
|
|
105
|
-
const rightmostPossiblePoint = (
|
|
117
|
+
if (left < leftmostLimit) {
|
|
118
|
+
const rightmostPossiblePoint = (_b = nextElementPosition === null || nextElementPosition === void 0 ? void 0 : nextElementPosition.left) !== null && _b !== void 0 ? _b : right;
|
|
106
119
|
const remainSpace = rightmostPossiblePoint -
|
|
107
|
-
|
|
120
|
+
currentElementPositionRigth +
|
|
108
121
|
x -
|
|
109
122
|
labelsMargin;
|
|
110
123
|
text.attr('text-anchor', 'start');
|
|
@@ -112,16 +125,16 @@ export async function axisBottom(args) {
|
|
|
112
125
|
}
|
|
113
126
|
}
|
|
114
127
|
else {
|
|
115
|
-
if (
|
|
116
|
-
(
|
|
128
|
+
if (left < elementX) {
|
|
129
|
+
(_c = currentElement.closest('.tick')) === null || _c === void 0 ? void 0 : _c.remove();
|
|
117
130
|
return;
|
|
118
131
|
}
|
|
119
|
-
elementX =
|
|
132
|
+
elementX = currentElementPositionRigth + labelsPaddings;
|
|
120
133
|
if (i === nodes.length - 1) {
|
|
121
|
-
const prevElement = nodes[i - 1];
|
|
134
|
+
const prevElement = (_d = nodes[i - 1]) === null || _d === void 0 ? void 0 : _d.node;
|
|
122
135
|
const text = select(currentElement);
|
|
123
136
|
const prevElementPosition = prevElement === null || prevElement === void 0 ? void 0 : prevElement.getBoundingClientRect();
|
|
124
|
-
const lackingSpace = Math.max(0,
|
|
137
|
+
const lackingSpace = Math.max(0, currentElementPositionRigth - right);
|
|
125
138
|
if (lackingSpace) {
|
|
126
139
|
const remainSpace = right - ((prevElementPosition === null || prevElementPosition === void 0 ? void 0 : prevElementPosition.right) || 0) - labelsPaddings;
|
|
127
140
|
const translateX = -lackingSpace;
|
|
@@ -49,7 +49,7 @@ export function getClosestPoints(args) {
|
|
|
49
49
|
const groups = groupBy(shapesData, getSeriesType);
|
|
50
50
|
// eslint-disable-next-line complexity
|
|
51
51
|
Object.entries(groups).forEach(([seriesType, list]) => {
|
|
52
|
-
var _a, _b;
|
|
52
|
+
var _a, _b, _c;
|
|
53
53
|
switch (seriesType) {
|
|
54
54
|
case 'bar-x': {
|
|
55
55
|
const points = list.map((d) => ({
|
|
@@ -106,26 +106,27 @@ export function getClosestPoints(args) {
|
|
|
106
106
|
const points = list;
|
|
107
107
|
const sorted = sort(points, (p) => p.y);
|
|
108
108
|
const closestYIndex = bisector((p) => p.y).center(sorted, pointerY);
|
|
109
|
-
|
|
110
|
-
let
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
const closestYPoint = sorted[closestYIndex];
|
|
110
|
+
let selectedPoints = [];
|
|
111
|
+
let closestPointXValue = -1;
|
|
112
|
+
if (closestYPoint) {
|
|
113
|
+
selectedPoints = points.filter((p) => p.data.y === closestYPoint.data.y);
|
|
114
|
+
const closestPoints = sort(selectedPoints.filter((p) => p.y === closestYPoint.y), (p) => p.x);
|
|
114
115
|
const lastPoint = closestPoints[closestPoints.length - 1];
|
|
115
116
|
if (pointerX < ((_a = closestPoints[0]) === null || _a === void 0 ? void 0 : _a.x)) {
|
|
116
|
-
|
|
117
|
+
closestPointXValue = closestPoints[0].x;
|
|
117
118
|
}
|
|
118
119
|
else if (lastPoint && pointerX > lastPoint.x + lastPoint.width) {
|
|
119
|
-
|
|
120
|
+
closestPointXValue = lastPoint.x;
|
|
120
121
|
}
|
|
121
122
|
else {
|
|
122
|
-
|
|
123
|
+
closestPointXValue = (_b = closestPoints.find((p) => pointerX > p.x && pointerX < p.x + p.width)) === null || _b === void 0 ? void 0 : _b.x;
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
|
-
result.push(...
|
|
126
|
+
result.push(...selectedPoints.map((p) => ({
|
|
126
127
|
data: p.data,
|
|
127
128
|
series: p.series,
|
|
128
|
-
closest:
|
|
129
|
+
closest: p.x === closestPointXValue && p.y === closestYPoint.y,
|
|
129
130
|
})));
|
|
130
131
|
break;
|
|
131
132
|
}
|
|
@@ -164,7 +165,7 @@ export function getClosestPoints(args) {
|
|
|
164
165
|
}
|
|
165
166
|
case 'treemap': {
|
|
166
167
|
const data = list;
|
|
167
|
-
const closestPoint = (
|
|
168
|
+
const closestPoint = (_c = data[0]) === null || _c === void 0 ? void 0 : _c.leaves.find((l) => {
|
|
168
169
|
return (pointerX >= l.x0 && pointerX <= l.x1 && pointerY >= l.y0 && pointerY <= l.y1);
|
|
169
170
|
});
|
|
170
171
|
if (closestPoint) {
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
import type { ChartSeries } from '../../../types';
|
|
2
|
-
export declare function getSortedSeriesData(seriesData
|
|
1
|
+
import type { ChartAxis, ChartSeries } from '../../../types';
|
|
2
|
+
export declare function getSortedSeriesData({ seriesData, yAxes, xAxis, }: {
|
|
3
|
+
seriesData: ChartSeries[];
|
|
4
|
+
yAxes?: ChartAxis[];
|
|
5
|
+
xAxis?: ChartAxis;
|
|
6
|
+
}): ChartSeries[];
|
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { sort } from 'd3';
|
|
2
|
+
import { isEmpty } from 'lodash';
|
|
3
|
+
import get from 'lodash/get';
|
|
2
4
|
import { SeriesType } from '../../../constants';
|
|
3
|
-
|
|
5
|
+
import { getAxisCategories } from '../../../hooks/useChartOptions/utils';
|
|
6
|
+
function applyAxisCategoriesOrder({ series, axis, key, }) {
|
|
7
|
+
var _a, _b;
|
|
8
|
+
const originalCategories = (_a = axis === null || axis === void 0 ? void 0 : axis.categories) !== null && _a !== void 0 ? _a : [];
|
|
9
|
+
if (isEmpty(originalCategories)) {
|
|
10
|
+
return series;
|
|
11
|
+
}
|
|
12
|
+
const axisCategories = (_b = getAxisCategories(axis)) !== null && _b !== void 0 ? _b : [];
|
|
13
|
+
const order = Object.fromEntries(axisCategories.map((value, index) => [value, index]));
|
|
14
|
+
const newSeriesData = series.data.map((d) => {
|
|
15
|
+
const value = get(d, key);
|
|
16
|
+
if (typeof value === 'number') {
|
|
17
|
+
return Object.assign(Object.assign({}, d), { [key]: order[originalCategories[value]] });
|
|
18
|
+
}
|
|
19
|
+
return d;
|
|
20
|
+
});
|
|
21
|
+
return Object.assign(Object.assign({}, series), { data: newSeriesData });
|
|
22
|
+
}
|
|
23
|
+
export function getSortedSeriesData({ seriesData, yAxes, xAxis, }) {
|
|
4
24
|
return seriesData.map((s) => {
|
|
5
|
-
|
|
25
|
+
const yAxis = yAxes === null || yAxes === void 0 ? void 0 : yAxes[0];
|
|
26
|
+
let sortedSeries = s;
|
|
27
|
+
sortedSeries = applyAxisCategoriesOrder({ series: sortedSeries, axis: yAxis, key: 'y' });
|
|
28
|
+
sortedSeries = applyAxisCategoriesOrder({ series: sortedSeries, axis: xAxis, key: 'x' });
|
|
29
|
+
switch (sortedSeries.type) {
|
|
6
30
|
case SeriesType.Area: {
|
|
7
|
-
|
|
31
|
+
sortedSeries = Object.assign(Object.assign({}, sortedSeries), { data: sort(sortedSeries.data, (d) => d.x) });
|
|
32
|
+
break;
|
|
8
33
|
}
|
|
9
34
|
}
|
|
10
|
-
return
|
|
35
|
+
return sortedSeries;
|
|
11
36
|
});
|
|
12
37
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SeriesType } from '../../constants';
|
|
2
|
+
import { getAxisCategories } from '../../hooks/useChartOptions/utils';
|
|
2
3
|
const SERIES_TYPE_WITH_HIDDEN_POINTS = [SeriesType.Area, SeriesType.Line];
|
|
3
4
|
// eslint-disable-next-line complexity
|
|
4
5
|
function isValueInRange(args) {
|
|
@@ -20,7 +21,7 @@ function isValueInRange(args) {
|
|
|
20
21
|
return numValue >= numMin && numValue <= numMax;
|
|
21
22
|
}
|
|
22
23
|
case 'category': {
|
|
23
|
-
const categories = (axis
|
|
24
|
+
const categories = getAxisCategories(axis) || [];
|
|
24
25
|
if (typeof value === 'string' && typeof min === 'number' && typeof max === 'number') {
|
|
25
26
|
const valueIndex = categories.indexOf(value);
|
|
26
27
|
if (min === -1 || max === -1 || valueIndex === -1) {
|