@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
|
@@ -2,6 +2,7 @@ import get from 'lodash/get';
|
|
|
2
2
|
import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, axisCrosshairDefaults, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
|
|
3
3
|
import { formatAxisTickLabel, getClosestPointsRange, getDefaultMinYAxisValue, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, isAxisRelatedSeries, wrapText, } from '../../utils';
|
|
4
4
|
import { createYScale } from '../useAxisScales';
|
|
5
|
+
import { getAxisCategories, prepareAxisPlotLabel } from './utils';
|
|
5
6
|
const getAxisLabelMaxWidth = async (args) => {
|
|
6
7
|
const { axis, seriesData, seriesOptions } = args;
|
|
7
8
|
if (!axis.labels.enabled) {
|
|
@@ -78,7 +79,7 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
|
|
|
78
79
|
maxWidth: get(axisItem, 'labels.maxWidth', axisLabelsDefaults.maxWidth),
|
|
79
80
|
},
|
|
80
81
|
lineColor: get(axisItem, 'lineColor'),
|
|
81
|
-
categories:
|
|
82
|
+
categories: getAxisCategories(axisItem),
|
|
82
83
|
timestamps: get(axisItem, 'timestamps'),
|
|
83
84
|
title: {
|
|
84
85
|
text: titleText,
|
|
@@ -108,6 +109,7 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
|
|
|
108
109
|
dashStyle: get(d, 'dashStyle', DASH_STYLE.Solid),
|
|
109
110
|
opacity: get(d, 'opacity', 1),
|
|
110
111
|
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
112
|
+
label: prepareAxisPlotLabel(d),
|
|
111
113
|
})),
|
|
112
114
|
plotBands: get(axisItem, 'plotBands', []).map((d) => ({
|
|
113
115
|
color: get(d, 'color', 'var(--g-color-base-brand)'),
|
|
@@ -115,6 +117,7 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
|
|
|
115
117
|
from: get(d, 'from', 0),
|
|
116
118
|
to: get(d, 'to', 0),
|
|
117
119
|
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
120
|
+
label: prepareAxisPlotLabel(d),
|
|
118
121
|
})),
|
|
119
122
|
crosshair: {
|
|
120
123
|
enabled: get(axisItem, 'crosshair.enabled', axisCrosshairDefaults.enabled),
|
|
@@ -126,6 +129,7 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
|
|
|
126
129
|
opacity: get(axisItem, 'crosshair.opacity', axisCrosshairDefaults.opacity),
|
|
127
130
|
},
|
|
128
131
|
visible: get(axisItem, 'visible', true),
|
|
132
|
+
order: axisItem.order,
|
|
129
133
|
};
|
|
130
134
|
if (labelsEnabled) {
|
|
131
135
|
preparedAxis.labels.width = await getAxisLabelMaxWidth({
|
|
@@ -6,8 +6,8 @@ export declare const DEFAULT_MARKER: {
|
|
|
6
6
|
enabled: boolean;
|
|
7
7
|
symbol: `${import("../../constants").SymbolType}`;
|
|
8
8
|
radius: number;
|
|
9
|
-
borderColor: string;
|
|
10
9
|
borderWidth: number;
|
|
10
|
+
borderColor: string;
|
|
11
11
|
};
|
|
12
12
|
type PrepareAreaSeriesArgs = {
|
|
13
13
|
colorScale: ScaleOrdinal<string, string>;
|
|
@@ -20,8 +20,11 @@ export declare function prepareBarYSeries(args: PrepareBarYSeriesArgs): Promise<
|
|
|
20
20
|
maxWidth: number;
|
|
21
21
|
html: boolean;
|
|
22
22
|
format?: import("../..").ValueFormat;
|
|
23
|
+
allowOverlap: boolean;
|
|
23
24
|
};
|
|
24
25
|
borderRadius: number;
|
|
26
|
+
borderWidth: number;
|
|
27
|
+
borderColor: string;
|
|
25
28
|
} & {
|
|
26
29
|
color: string;
|
|
27
30
|
name: string;
|
|
@@ -4,7 +4,7 @@ import { getLabelsSize, getUniqId } from '../../utils';
|
|
|
4
4
|
import { getFormattedValue } from '../../utils/chart/format';
|
|
5
5
|
import { getSeriesStackId, prepareLegendSymbol } from './utils';
|
|
6
6
|
async function prepareDataLabels(series) {
|
|
7
|
-
var _a, _b;
|
|
7
|
+
var _a, _b, _c, _d;
|
|
8
8
|
const enabled = get(series, 'dataLabels.enabled', false);
|
|
9
9
|
const style = Object.assign({}, DEFAULT_DATALABELS_STYLE, (_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.style);
|
|
10
10
|
const html = get(series, 'dataLabels.html', false);
|
|
@@ -25,12 +25,13 @@ async function prepareDataLabels(series) {
|
|
|
25
25
|
maxWidth,
|
|
26
26
|
html,
|
|
27
27
|
format: (_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.format,
|
|
28
|
+
allowOverlap: (_d = (_c = series.dataLabels) === null || _c === void 0 ? void 0 : _c.allowOverlap) !== null && _d !== void 0 ? _d : false,
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
export function prepareBarYSeries(args) {
|
|
31
32
|
const { colorScale, series: seriesList, seriesOptions, legend } = args;
|
|
32
33
|
return Promise.all(seriesList.map(async (series) => {
|
|
33
|
-
var _a, _b, _c;
|
|
34
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
34
35
|
const name = series.name || '';
|
|
35
36
|
const color = series.color || colorScale(name);
|
|
36
37
|
return {
|
|
@@ -49,6 +50,8 @@ export function prepareBarYSeries(args) {
|
|
|
49
50
|
dataLabels: await prepareDataLabels(series),
|
|
50
51
|
cursor: get(series, 'cursor', null),
|
|
51
52
|
borderRadius: (_c = (_a = series.borderRadius) !== null && _a !== void 0 ? _a : (_b = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _b === void 0 ? void 0 : _b.borderRadius) !== null && _c !== void 0 ? _c : 0,
|
|
53
|
+
borderWidth: (_f = (_d = series.borderWidth) !== null && _d !== void 0 ? _d : (_e = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _e === void 0 ? void 0 : _e.borderWidth) !== null && _f !== void 0 ? _f : 0,
|
|
54
|
+
borderColor: (_j = (_g = series.borderColor) !== null && _g !== void 0 ? _g : (_h = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _h === void 0 ? void 0 : _h.borderColor) !== null && _j !== void 0 ? _j : 'var(--gcharts-shape-border-color)',
|
|
52
55
|
};
|
|
53
56
|
}));
|
|
54
57
|
}
|
|
@@ -8,8 +8,8 @@ export declare const DEFAULT_MARKER: {
|
|
|
8
8
|
enabled: boolean;
|
|
9
9
|
symbol: `${import("../../constants").SymbolType}`;
|
|
10
10
|
radius: number;
|
|
11
|
-
borderColor: string;
|
|
12
11
|
borderWidth: number;
|
|
12
|
+
borderColor: string;
|
|
13
13
|
};
|
|
14
14
|
type PrepareLineSeriesArgs = {
|
|
15
15
|
colorScale: ScaleOrdinal<string, string>;
|
|
@@ -10,8 +10,8 @@ export declare const DEFAULT_MARKER: {
|
|
|
10
10
|
enabled: boolean;
|
|
11
11
|
radius: number;
|
|
12
12
|
symbol: `${import("../../constants").SymbolType}`;
|
|
13
|
-
borderColor: string;
|
|
14
13
|
borderWidth: number;
|
|
14
|
+
borderColor: string;
|
|
15
15
|
};
|
|
16
16
|
export declare function prepareRadarSeries(args: PrepareRadarSeriesArgs): PreparedRadarSeries[];
|
|
17
17
|
export {};
|
|
@@ -130,8 +130,11 @@ export type PreparedBarYSeries = {
|
|
|
130
130
|
maxWidth: number;
|
|
131
131
|
html: boolean;
|
|
132
132
|
format?: ValueFormat;
|
|
133
|
+
allowOverlap: boolean;
|
|
133
134
|
};
|
|
134
135
|
borderRadius: number;
|
|
136
|
+
borderWidth: number;
|
|
137
|
+
borderColor: string;
|
|
135
138
|
} & BasePreparedSeries;
|
|
136
139
|
export type PreparedPieSeries = {
|
|
137
140
|
type: PieSeries['type'];
|
|
@@ -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
|