@gravity-ui/charts 1.12.0 → 1.13.1
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/constants/defaults/series-options.d.ts +3 -1
- package/dist/cjs/constants/defaults/series-options.js +2 -0
- 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 +24 -15
- 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 +86 -61
- 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 +20 -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/constants/defaults/series-options.d.ts +3 -1
- package/dist/esm/constants/defaults/series-options.js +2 -0
- 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 +24 -15
- 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 +86 -61
- 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 +20 -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
|
@@ -32,6 +32,7 @@ async function getLabelData(d) {
|
|
|
32
32
|
}
|
|
33
33
|
export const prepareBarXData = async (args) => {
|
|
34
34
|
const { series, seriesOptions, xAxis, xScale, yScale, boundsHeight: plotHeight } = args;
|
|
35
|
+
const stackGap = seriesOptions['bar-x'].stackGap;
|
|
35
36
|
const categories = get(xAxis, 'categories', []);
|
|
36
37
|
const barMaxWidth = get(seriesOptions, 'bar-x.barMaxWidth');
|
|
37
38
|
const barPadding = get(seriesOptions, 'bar-x.barPadding');
|
|
@@ -95,46 +96,54 @@ export const prepareBarXData = async (args) => {
|
|
|
95
96
|
const rectGap = Math.max(bandWidth * barPadding, MIN_BAR_GAP);
|
|
96
97
|
const rectWidth = Math.max(MIN_BAR_WIDTH, Math.min(groupWidth / maxGroupSize - rectGap, barMaxWidth));
|
|
97
98
|
const result = [];
|
|
98
|
-
|
|
99
|
+
const groupedData = Object.entries(data);
|
|
100
|
+
for (let groupedDataIndex = 0; groupedDataIndex < groupedData.length; groupedDataIndex++) {
|
|
101
|
+
const [xValue, val] = groupedData[groupedDataIndex];
|
|
99
102
|
const stacks = Object.values(val);
|
|
100
103
|
const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
|
|
101
|
-
|
|
104
|
+
for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
|
|
105
|
+
const yValues = stacks[groupItemIndex];
|
|
102
106
|
let stackHeight = 0;
|
|
103
107
|
const stackItems = [];
|
|
104
108
|
const sortedData = sortKey
|
|
105
109
|
? sort(yValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
|
|
106
110
|
: yValues;
|
|
107
|
-
|
|
111
|
+
for (let yValueIndex = 0; yValueIndex < sortedData.length; yValueIndex++) {
|
|
112
|
+
const yValue = sortedData[yValueIndex];
|
|
108
113
|
const yAxisIndex = yValue.series.yAxis;
|
|
109
114
|
const seriesYScale = yScale[yAxisIndex];
|
|
110
115
|
let xCenter;
|
|
111
116
|
if (xAxis.type === 'category') {
|
|
112
117
|
const xBandScale = xScale;
|
|
113
|
-
xCenter =
|
|
114
|
-
(xBandScale(xValue) || 0) +
|
|
115
|
-
xBandScale.bandwidth() / 2;
|
|
118
|
+
xCenter = (xBandScale(xValue) || 0) + xBandScale.bandwidth() / 2;
|
|
116
119
|
}
|
|
117
120
|
else {
|
|
118
121
|
const xLinearScale = xScale;
|
|
119
122
|
xCenter = xLinearScale(Number(xValue));
|
|
120
123
|
}
|
|
121
|
-
const x = xCenter -
|
|
122
|
-
currentGroupWidth / 2 +
|
|
123
|
-
(rectWidth + rectGap) * groupItemIndex;
|
|
124
|
+
const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
|
|
124
125
|
const yDataValue = yValue.data.y;
|
|
125
126
|
const y = seriesYScale(yDataValue);
|
|
126
127
|
const base = seriesYScale(0);
|
|
128
|
+
const isLastStackItem = yValueIndex === sortedData.length - 1;
|
|
127
129
|
const height = yDataValue > 0 ? base - y : y - base;
|
|
130
|
+
let shapeHeight = height - (stackItems.length ? stackGap : 0);
|
|
131
|
+
if (shapeHeight < 0) {
|
|
132
|
+
shapeHeight = height;
|
|
133
|
+
}
|
|
134
|
+
if (height <= 0) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
128
137
|
const barData = {
|
|
129
138
|
x,
|
|
130
139
|
y: yDataValue > 0 ? y - stackHeight : seriesYScale(0),
|
|
131
140
|
width: rectWidth,
|
|
132
|
-
height,
|
|
141
|
+
height: shapeHeight,
|
|
133
142
|
opacity: get(yValue.data, 'opacity', null),
|
|
134
143
|
data: yValue.data,
|
|
135
144
|
series: yValue.series,
|
|
136
145
|
htmlElements: [],
|
|
137
|
-
isLastStackItem
|
|
146
|
+
isLastStackItem,
|
|
138
147
|
};
|
|
139
148
|
const label = await getLabelData(barData);
|
|
140
149
|
if (yValue.series.dataLabels.html && label) {
|
|
@@ -150,8 +159,8 @@ export const prepareBarXData = async (args) => {
|
|
|
150
159
|
barData.label = await getLabelData(barData);
|
|
151
160
|
}
|
|
152
161
|
stackItems.push(barData);
|
|
153
|
-
stackHeight += height
|
|
154
|
-
}
|
|
162
|
+
stackHeight += height;
|
|
163
|
+
}
|
|
155
164
|
if (series.some((s) => s.stacking === 'percent')) {
|
|
156
165
|
let acc = 0;
|
|
157
166
|
const ratio = plotHeight / (stackHeight - stackItems.length);
|
|
@@ -162,7 +171,7 @@ export const prepareBarXData = async (args) => {
|
|
|
162
171
|
});
|
|
163
172
|
}
|
|
164
173
|
result.push(...stackItems);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
167
176
|
return result;
|
|
168
177
|
};
|
|
@@ -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,52 +1,16 @@
|
|
|
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;
|
|
10
|
+
const stackGap = seriesOptions['bar-y'].stackGap;
|
|
46
11
|
const xLinearScale = xScale;
|
|
47
12
|
const yLinearScale = yScale;
|
|
48
13
|
const plotHeight = yLinearScale(yLinearScale.domain()[0]);
|
|
49
|
-
const plotWidth = xLinearScale(xLinearScale.domain()[1]);
|
|
50
14
|
const sortingOptions = get(seriesOptions, 'bar-y.dataSorting');
|
|
51
15
|
const comparator = (sortingOptions === null || sortingOptions === void 0 ? void 0 : sortingOptions.direction) === 'desc' ? descending : ascending;
|
|
52
16
|
const sortKey = (() => {
|
|
@@ -66,21 +30,32 @@ 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
|
|
82
47
|
? sort(measureValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
|
|
83
48
|
: measureValues;
|
|
49
|
+
let ratio = 1;
|
|
50
|
+
if (series.some((s) => s.stacking === 'percent')) {
|
|
51
|
+
const sum = sortedData.reduce((acc, item) => {
|
|
52
|
+
if (item.data.x) {
|
|
53
|
+
return acc + xLinearScale(Number(item.data.x));
|
|
54
|
+
}
|
|
55
|
+
return acc;
|
|
56
|
+
}, 0);
|
|
57
|
+
ratio = xLinearScale.range()[1] / sum;
|
|
58
|
+
}
|
|
84
59
|
sortedData.forEach(({ data, series: s }, xValueIndex) => {
|
|
85
60
|
let center;
|
|
86
61
|
if (yAxis[0].type === 'category') {
|
|
@@ -93,36 +68,86 @@ export const prepareBarYData = async (args) => {
|
|
|
93
68
|
}
|
|
94
69
|
const y = center - currentBarHeight / 2 + (barSize + barGap) * groupItemIndex;
|
|
95
70
|
const xValue = Number(data.x);
|
|
96
|
-
const
|
|
71
|
+
const isLastStackItem = xValueIndex === sortedData.length - 1;
|
|
72
|
+
const width = Math.abs(xLinearScale(xValue) * ratio - base);
|
|
73
|
+
let shapeWidth = width - (stackItems.length ? stackGap : 0);
|
|
74
|
+
if (shapeWidth < 0) {
|
|
75
|
+
shapeWidth = width;
|
|
76
|
+
}
|
|
77
|
+
if (shapeWidth <= 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const itemStackGap = width - shapeWidth;
|
|
97
81
|
const item = {
|
|
98
|
-
x: xValue >
|
|
99
|
-
y,
|
|
100
|
-
width,
|
|
82
|
+
x: (xValue > baseRangeValue ? stackSum : stackSum - width) + itemStackGap,
|
|
83
|
+
y: y,
|
|
84
|
+
width: shapeWidth,
|
|
101
85
|
height: barSize,
|
|
102
86
|
color: data.color || s.color,
|
|
87
|
+
borderColor: s.borderColor,
|
|
88
|
+
borderWidth: s.borderWidth,
|
|
103
89
|
opacity: get(data, 'opacity', null),
|
|
104
90
|
data,
|
|
105
91
|
series: s,
|
|
106
|
-
|
|
107
|
-
isLastStackItem: xValueIndex === sortedData.length - 1,
|
|
92
|
+
isLastStackItem,
|
|
108
93
|
};
|
|
109
94
|
stackItems.push(item);
|
|
110
|
-
stackSum += width
|
|
95
|
+
stackSum += width;
|
|
111
96
|
});
|
|
112
|
-
if (series.some((s) => s.stacking === 'percent')) {
|
|
113
|
-
let acc = 0;
|
|
114
|
-
const ratio = plotWidth / (stackSum - stackItems.length);
|
|
115
|
-
stackItems.forEach((item) => {
|
|
116
|
-
item.width = item.width * ratio;
|
|
117
|
-
item.x = acc;
|
|
118
|
-
acc += item.width;
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
97
|
result.push(...stackItems);
|
|
122
98
|
});
|
|
123
99
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
100
|
+
let labels = [];
|
|
101
|
+
const htmlElements = [];
|
|
102
|
+
const map = new Map();
|
|
103
|
+
for (let i = 0; i < result.length; i++) {
|
|
104
|
+
const prepared = result[i];
|
|
105
|
+
const dataLabels = prepared.series.dataLabels;
|
|
106
|
+
if (dataLabels.enabled) {
|
|
107
|
+
const data = prepared.data;
|
|
108
|
+
const content = getFormattedValue(Object.assign({ value: data.label || data.x }, dataLabels));
|
|
109
|
+
const x = dataLabels.inside
|
|
110
|
+
? prepared.x + prepared.width / 2
|
|
111
|
+
: prepared.x + prepared.width + DEFAULT_LABEL_PADDING;
|
|
112
|
+
const y = prepared.y + prepared.height / 2;
|
|
113
|
+
if (dataLabels.html) {
|
|
114
|
+
const { maxHeight: height, maxWidth: width } = await getLabelsSize({
|
|
115
|
+
labels: [content],
|
|
116
|
+
style: dataLabels.style,
|
|
117
|
+
html: dataLabels.html,
|
|
118
|
+
});
|
|
119
|
+
htmlElements.push({
|
|
120
|
+
x,
|
|
121
|
+
y: y - height / 2,
|
|
122
|
+
content,
|
|
123
|
+
size: { width, height },
|
|
124
|
+
style: dataLabels.style,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
if (!map.has(dataLabels.style)) {
|
|
129
|
+
map.set(dataLabels.style, getTextSizeFn({ style: dataLabels.style }));
|
|
130
|
+
}
|
|
131
|
+
const getTextSize = map.get(dataLabels.style);
|
|
132
|
+
const { width, height } = await getTextSize(content);
|
|
133
|
+
labels.push({
|
|
134
|
+
x,
|
|
135
|
+
y: y + height / 2,
|
|
136
|
+
text: content,
|
|
137
|
+
textAnchor: dataLabels.inside ? 'middle' : 'right',
|
|
138
|
+
style: dataLabels.style,
|
|
139
|
+
series: prepared.series,
|
|
140
|
+
size: { width, height },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!((_a = result[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
|
|
146
|
+
labels = filterOverlappingLabels(labels);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
shapes: result,
|
|
150
|
+
labels,
|
|
151
|
+
htmlElements,
|
|
152
|
+
};
|
|
128
153
|
};
|
|
@@ -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
|
|
@@ -79,6 +79,11 @@ export interface ChartSeriesOptions {
|
|
|
79
79
|
* @default 0
|
|
80
80
|
*/
|
|
81
81
|
borderRadius?: number;
|
|
82
|
+
/**
|
|
83
|
+
* The distance between the shapes of the stacked values, in pixels.
|
|
84
|
+
* @default 1
|
|
85
|
+
*/
|
|
86
|
+
stackGap?: number;
|
|
82
87
|
dataSorting?: {
|
|
83
88
|
/** Determines what data value should be used to sort by.
|
|
84
89
|
* Possible values are undefined to disable, "name" to sort by series name or "y"
|
|
@@ -115,11 +120,26 @@ export interface ChartSeriesOptions {
|
|
|
115
120
|
* @default 0.2
|
|
116
121
|
*/
|
|
117
122
|
groupPadding?: number;
|
|
123
|
+
/**
|
|
124
|
+
* The width of the border surrounding each bar.
|
|
125
|
+
*
|
|
126
|
+
* @default 0
|
|
127
|
+
*/
|
|
128
|
+
borderWidth?: number;
|
|
129
|
+
/**
|
|
130
|
+
* The color of the border surrounding each bar.
|
|
131
|
+
*/
|
|
132
|
+
borderColor?: string;
|
|
118
133
|
/**
|
|
119
134
|
* The corner radius of the border surrounding each bar.
|
|
120
135
|
* @default 0
|
|
121
136
|
*/
|
|
122
137
|
borderRadius?: number;
|
|
138
|
+
/**
|
|
139
|
+
* The distance between the shapes of the stacked values, in pixels.
|
|
140
|
+
* @default 1
|
|
141
|
+
*/
|
|
142
|
+
stackGap?: number;
|
|
123
143
|
dataSorting?: {
|
|
124
144
|
/** Determines what data value should be used to sort by.
|
|
125
145
|
* Possible values are undefined to disable, "name" to sort by series name or "x"
|
|
@@ -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;
|