@gravity-ui/charts 1.14.0 → 1.15.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/ChartInner/useChartInnerProps.js +7 -2
- package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +1 -0
- package/dist/cjs/components/Tooltip/ChartTooltipContent.js +2 -2
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.d.ts +2 -1
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +57 -14
- package/dist/cjs/components/Tooltip/index.js +1 -1
- package/dist/cjs/hooks/useAxisScales/index.d.ts +1 -0
- package/dist/cjs/hooks/useAxisScales/index.js +53 -37
- package/dist/cjs/hooks/useChartOptions/x-axis.d.ts +3 -1
- package/dist/cjs/hooks/useChartOptions/x-axis.js +4 -3
- package/dist/cjs/hooks/utils/bar-x.d.ts +16 -0
- package/dist/cjs/hooks/utils/bar-x.js +41 -0
- package/dist/cjs/types/chart/tooltip.d.ts +14 -0
- package/dist/cjs/utils/chart/index.d.ts +1 -1
- package/dist/cjs/utils/chart/index.js +7 -2
- package/dist/esm/components/ChartInner/useChartInnerProps.js +7 -2
- package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +1 -0
- package/dist/esm/components/Tooltip/ChartTooltipContent.js +2 -2
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.d.ts +2 -1
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +57 -14
- package/dist/esm/components/Tooltip/index.js +1 -1
- package/dist/esm/hooks/useAxisScales/index.d.ts +1 -0
- package/dist/esm/hooks/useAxisScales/index.js +53 -37
- package/dist/esm/hooks/useChartOptions/x-axis.d.ts +3 -1
- package/dist/esm/hooks/useChartOptions/x-axis.js +4 -3
- package/dist/esm/hooks/utils/bar-x.d.ts +16 -0
- package/dist/esm/hooks/utils/bar-x.js +41 -0
- package/dist/esm/types/chart/tooltip.d.ts +14 -0
- package/dist/esm/utils/chart/index.d.ts +1 -1
- package/dist/esm/utils/chart/index.js +7 -2
- package/package.json +1 -1
|
@@ -39,8 +39,13 @@ export function useChartInnerProps(props) {
|
|
|
39
39
|
const [xAxis, setXAxis] = React.useState(null);
|
|
40
40
|
React.useEffect(() => {
|
|
41
41
|
setXAxis(null);
|
|
42
|
-
getPreparedXAxis({
|
|
43
|
-
|
|
42
|
+
getPreparedXAxis({
|
|
43
|
+
xAxis: data.xAxis,
|
|
44
|
+
width,
|
|
45
|
+
seriesData: zoomedSeriesData,
|
|
46
|
+
seriesOptions: preparedSeriesOptions,
|
|
47
|
+
}).then((val) => setXAxis(val));
|
|
48
|
+
}, [data.xAxis, preparedSeriesOptions, width, zoomedSeriesData]);
|
|
44
49
|
const [yAxis, setYAxis] = React.useState([]);
|
|
45
50
|
React.useEffect(() => {
|
|
46
51
|
setYAxis([]);
|
|
@@ -2,10 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import isNil from 'lodash/isNil';
|
|
3
3
|
import { DefaultTooltipContent } from './DefaultTooltipContent';
|
|
4
4
|
export const ChartTooltipContent = (props) => {
|
|
5
|
-
const { hovered, xAxis, yAxis, renderer, valueFormat, totals } = props;
|
|
5
|
+
const { hovered, xAxis, yAxis, renderer, rowRenderer, valueFormat, totals } = props;
|
|
6
6
|
if (!hovered) {
|
|
7
7
|
return null;
|
|
8
8
|
}
|
|
9
9
|
const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({ hovered, xAxis, yAxis });
|
|
10
|
-
return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, valueFormat: valueFormat, totals: totals })) : (customTooltip);
|
|
10
|
+
return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, valueFormat: valueFormat, totals: totals, rowRenderer: rowRenderer })) : (customTooltip);
|
|
11
11
|
};
|
|
@@ -6,6 +6,7 @@ type Props = {
|
|
|
6
6
|
valueFormat?: ValueFormat;
|
|
7
7
|
xAxis?: ChartXAxis | null;
|
|
8
8
|
yAxis?: ChartYAxis;
|
|
9
|
+
rowRenderer?: ChartTooltip['rowRenderer'];
|
|
9
10
|
};
|
|
10
|
-
export declare const DefaultTooltipContent: ({ hovered, xAxis, yAxis, valueFormat, totals }: Props) => React.JSX.Element;
|
|
11
|
+
export declare const DefaultTooltipContent: ({ hovered, xAxis, yAxis, valueFormat, totals, rowRenderer, }: Props) => React.JSX.Element;
|
|
11
12
|
export {};
|
|
@@ -7,9 +7,25 @@ import { Row } from './Row';
|
|
|
7
7
|
import { RowTotals } from './RowTotals';
|
|
8
8
|
import { getDefaultValueFormat, getHoveredValues, getMeasureValue, getPreparedAggregation, getXRowData, } from './utils';
|
|
9
9
|
const b = block('tooltip');
|
|
10
|
-
export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, totals }) => {
|
|
10
|
+
export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, totals, rowRenderer, }) => {
|
|
11
11
|
const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
|
|
12
12
|
const hoveredValues = getHoveredValues({ hovered, xAxis, yAxis });
|
|
13
|
+
const renderRow = ({ id, name, color, active, striped, value, formattedValue, }) => {
|
|
14
|
+
if (typeof rowRenderer === 'function') {
|
|
15
|
+
return rowRenderer({
|
|
16
|
+
id,
|
|
17
|
+
name,
|
|
18
|
+
color,
|
|
19
|
+
value,
|
|
20
|
+
formattedValue,
|
|
21
|
+
striped,
|
|
22
|
+
active,
|
|
23
|
+
className: b('content-row', { active, striped }),
|
|
24
|
+
hovered,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
|
|
28
|
+
};
|
|
13
29
|
return (React.createElement("div", { className: b('content') },
|
|
14
30
|
measureValue && (React.createElement("div", { className: b('series-name'), dangerouslySetInnerHTML: { __html: measureValue } })),
|
|
15
31
|
// eslint-disable-next-line complexity
|
|
@@ -30,7 +46,15 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
30
46
|
value: hoveredValues[i],
|
|
31
47
|
format,
|
|
32
48
|
});
|
|
33
|
-
return (
|
|
49
|
+
return renderRow({
|
|
50
|
+
id,
|
|
51
|
+
active,
|
|
52
|
+
color,
|
|
53
|
+
name: series.name,
|
|
54
|
+
striped,
|
|
55
|
+
value: hoveredValues[i],
|
|
56
|
+
formattedValue,
|
|
57
|
+
});
|
|
34
58
|
}
|
|
35
59
|
case 'waterfall': {
|
|
36
60
|
const isTotal = get(data, 'total', false);
|
|
@@ -56,7 +80,15 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
56
80
|
value: hoveredValues[i],
|
|
57
81
|
format,
|
|
58
82
|
});
|
|
59
|
-
return (
|
|
83
|
+
return renderRow({
|
|
84
|
+
id,
|
|
85
|
+
active,
|
|
86
|
+
color,
|
|
87
|
+
name: series.name,
|
|
88
|
+
striped,
|
|
89
|
+
value: hoveredValues[i],
|
|
90
|
+
formattedValue,
|
|
91
|
+
});
|
|
60
92
|
}
|
|
61
93
|
case 'pie':
|
|
62
94
|
case 'treemap': {
|
|
@@ -65,11 +97,13 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
65
97
|
value: hoveredValues[i],
|
|
66
98
|
format: valueFormat || { type: 'number' },
|
|
67
99
|
});
|
|
68
|
-
return (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
100
|
+
return renderRow({
|
|
101
|
+
id,
|
|
102
|
+
color,
|
|
103
|
+
name: [seriesData.name || seriesData.id].flat().join('\n'),
|
|
104
|
+
value: hoveredValues[i],
|
|
105
|
+
formattedValue,
|
|
106
|
+
});
|
|
73
107
|
}
|
|
74
108
|
case 'sankey': {
|
|
75
109
|
const { target, data: source } = seriesItem;
|
|
@@ -77,11 +111,13 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
77
111
|
value: hoveredValues[i],
|
|
78
112
|
format: valueFormat || { type: 'number' },
|
|
79
113
|
});
|
|
80
|
-
return (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
return renderRow({
|
|
115
|
+
id,
|
|
116
|
+
color,
|
|
117
|
+
name: `${source.name} → ${target === null || target === void 0 ? void 0 : target.name}`,
|
|
118
|
+
value: hoveredValues[i],
|
|
119
|
+
formattedValue,
|
|
120
|
+
});
|
|
85
121
|
}
|
|
86
122
|
case 'radar': {
|
|
87
123
|
const radarSeries = series;
|
|
@@ -89,7 +125,14 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
89
125
|
value: hoveredValues[i],
|
|
90
126
|
format: valueFormat || { type: 'number' },
|
|
91
127
|
});
|
|
92
|
-
return (
|
|
128
|
+
return renderRow({
|
|
129
|
+
id,
|
|
130
|
+
color,
|
|
131
|
+
active,
|
|
132
|
+
name: radarSeries.name || radarSeries.id,
|
|
133
|
+
value: hoveredValues[i],
|
|
134
|
+
formattedValue,
|
|
135
|
+
});
|
|
93
136
|
}
|
|
94
137
|
default: {
|
|
95
138
|
return null;
|
|
@@ -23,5 +23,5 @@ export const Tooltip = (props) => {
|
|
|
23
23
|
}, [left, top]);
|
|
24
24
|
return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { anchorElement: anchor, className: b({ pinned: tooltipPinned }), disableTransition: true, floatingStyles: tooltipPinned ? undefined : { pointerEvents: 'none' }, offset: { mainAxis: 20 }, onOpenChange: tooltipPinned ? handleOnOpenChange : undefined, open: true, placement: ['right', 'left', 'top', 'bottom'] },
|
|
25
25
|
React.createElement("div", { className: b('popup-content') },
|
|
26
|
-
React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer, valueFormat: tooltip.valueFormat, totals: tooltip.totals })))) : null;
|
|
26
|
+
React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer, rowRenderer: tooltip.rowRenderer, valueFormat: tooltip.valueFormat, totals: tooltip.totals })))) : null;
|
|
27
27
|
};
|
|
@@ -29,6 +29,7 @@ export declare function createXScale(args: {
|
|
|
29
29
|
axis: PreparedAxis | ChartAxis;
|
|
30
30
|
boundsWidth: number;
|
|
31
31
|
series: (PreparedSeries | ChartSeries)[];
|
|
32
|
+
seriesOptions: PreparedSeriesOptions;
|
|
32
33
|
hasZoomX?: boolean;
|
|
33
34
|
}): ScaleBand<string> | ScaleLinear<number, number, never> | ScaleTime<number, number, never>;
|
|
34
35
|
/**
|
|
@@ -4,6 +4,7 @@ import get from 'lodash/get';
|
|
|
4
4
|
import { DEFAULT_AXIS_TYPE, SeriesType } from '../../constants';
|
|
5
5
|
import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
|
|
6
6
|
import { getBarYLayoutForNumericScale, groupBarYDataByYValue } from '../utils';
|
|
7
|
+
import { getBarXLayoutForNumericScale, groupBarXDataByXValue } from '../utils/bar-x';
|
|
7
8
|
const X_AXIS_ZOOM_PADDING = 0.02;
|
|
8
9
|
function isNumericalArrayData(data) {
|
|
9
10
|
return data.every((d) => typeof d === 'number' || d === null);
|
|
@@ -130,32 +131,55 @@ function calculateXAxisPadding(series) {
|
|
|
130
131
|
});
|
|
131
132
|
return result;
|
|
132
133
|
}
|
|
134
|
+
function getXScaleRange({ boundsWidth, series, seriesOptions, hasZoomX, axis, maxPadding, }) {
|
|
135
|
+
const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
|
|
136
|
+
const xRange = [0, boundsWidth - maxPadding];
|
|
137
|
+
const xRangeZoom = [0 + xAxisZoomPadding, boundsWidth - xAxisZoomPadding];
|
|
138
|
+
const range = hasZoomX ? xRangeZoom : xRange;
|
|
139
|
+
const barXSeries = series.filter((s) => s.type === SeriesType.BarX);
|
|
140
|
+
if (barXSeries.length) {
|
|
141
|
+
const groupedData = groupBarXDataByXValue(barXSeries, axis);
|
|
142
|
+
if (Object.keys(groupedData).length > 1) {
|
|
143
|
+
const { bandSize } = getBarXLayoutForNumericScale({
|
|
144
|
+
plotWidth: boundsWidth - maxPadding,
|
|
145
|
+
groupedData,
|
|
146
|
+
seriesOptions,
|
|
147
|
+
});
|
|
148
|
+
const offset = bandSize / 2;
|
|
149
|
+
return [range[0] + offset, range[1] - offset];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return range;
|
|
153
|
+
}
|
|
133
154
|
// eslint-disable-next-line complexity
|
|
134
155
|
export function createXScale(args) {
|
|
135
|
-
const { axis, boundsWidth, series, hasZoomX } = args;
|
|
156
|
+
const { axis, boundsWidth, series, seriesOptions, hasZoomX } = args;
|
|
136
157
|
const xMinProps = get(axis, 'min');
|
|
137
158
|
const xMaxProps = get(axis, 'max');
|
|
138
159
|
const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
|
|
139
160
|
const xCategories = get(axis, 'categories');
|
|
140
|
-
const xTimestamps = get(axis, 'timestamps');
|
|
141
161
|
const maxPadding = get(axis, 'maxPadding', 0);
|
|
142
162
|
const xAxisMaxPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
163
|
+
const range = getXScaleRange({
|
|
164
|
+
boundsWidth,
|
|
165
|
+
series,
|
|
166
|
+
seriesOptions,
|
|
167
|
+
hasZoomX,
|
|
168
|
+
axis,
|
|
169
|
+
maxPadding: xAxisMaxPadding,
|
|
170
|
+
});
|
|
146
171
|
switch (axis.order) {
|
|
147
172
|
case 'sortDesc':
|
|
148
173
|
case 'reverse': {
|
|
149
|
-
|
|
150
|
-
xRangeZoom.reverse();
|
|
174
|
+
range.reverse();
|
|
151
175
|
}
|
|
152
176
|
}
|
|
153
177
|
switch (xType) {
|
|
154
178
|
case 'linear':
|
|
155
179
|
case 'logarithmic': {
|
|
156
|
-
const
|
|
157
|
-
if (isNumericalArrayData(
|
|
158
|
-
const [xMinDomain, xMaxDomain] = extent(
|
|
180
|
+
const domainData = getDomainDataXBySeries(series);
|
|
181
|
+
if (isNumericalArrayData(domainData)) {
|
|
182
|
+
const [xMinDomain, xMaxDomain] = extent(domainData);
|
|
159
183
|
let xMin;
|
|
160
184
|
let xMax;
|
|
161
185
|
if (typeof xMinProps === 'number') {
|
|
@@ -176,11 +200,10 @@ export function createXScale(args) {
|
|
|
176
200
|
: xMaxDomain;
|
|
177
201
|
}
|
|
178
202
|
const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
|
|
179
|
-
const scale = scaleFn()
|
|
180
|
-
.domain([xMin, xMax])
|
|
181
|
-
.range(hasZoomX ? xRangeZoom : xRange);
|
|
203
|
+
const scale = scaleFn().domain([xMin, xMax]).range(range);
|
|
182
204
|
if (!hasZoomX) {
|
|
183
|
-
|
|
205
|
+
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
206
|
+
scale.nice(Math.max(10, domainData.length));
|
|
184
207
|
}
|
|
185
208
|
return scale;
|
|
186
209
|
}
|
|
@@ -195,40 +218,27 @@ export function createXScale(args) {
|
|
|
195
218
|
});
|
|
196
219
|
const xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
|
|
197
220
|
if (xScale.step() / 2 < xAxisMaxPadding) {
|
|
198
|
-
xScale.range(
|
|
221
|
+
xScale.range(range);
|
|
199
222
|
}
|
|
200
223
|
return xScale;
|
|
201
224
|
}
|
|
202
225
|
break;
|
|
203
226
|
}
|
|
204
227
|
case 'datetime': {
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
let domain = null;
|
|
229
|
+
const domainData = get(axis, 'timestamps') || getDomainDataXBySeries(series);
|
|
230
|
+
if (isNumericalArrayData(domainData)) {
|
|
231
|
+
const [xMinTimestamp, xMaxTimestamp] = extent(domainData);
|
|
207
232
|
const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
|
|
208
233
|
const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
.range(hasZoomX ? xRangeZoom : xRange);
|
|
234
|
+
domain = [xMin, xMax];
|
|
235
|
+
const scale = scaleUtc().domain(domain).range(range);
|
|
212
236
|
if (!hasZoomX) {
|
|
213
|
-
|
|
237
|
+
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
238
|
+
scale.nice(Math.max(10, domainData.length));
|
|
214
239
|
}
|
|
215
240
|
return scale;
|
|
216
241
|
}
|
|
217
|
-
else {
|
|
218
|
-
const domain = getDomainDataXBySeries(series);
|
|
219
|
-
if (isNumericalArrayData(domain)) {
|
|
220
|
-
const [xMinTimestamp, xMaxTimestamp] = extent(domain);
|
|
221
|
-
const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
|
|
222
|
-
const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
|
|
223
|
-
const scale = scaleUtc()
|
|
224
|
-
.domain([xMin, xMax])
|
|
225
|
-
.range(hasZoomX ? xRangeZoom : xRange);
|
|
226
|
-
if (!hasZoomX) {
|
|
227
|
-
scale.nice();
|
|
228
|
-
}
|
|
229
|
-
return scale;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
242
|
break;
|
|
233
243
|
}
|
|
234
244
|
}
|
|
@@ -242,7 +252,13 @@ const createScales = (args) => {
|
|
|
242
252
|
visibleSeries = visibleSeries.length === 0 ? series : visibleSeries;
|
|
243
253
|
return {
|
|
244
254
|
xScale: xAxis
|
|
245
|
-
? createXScale({
|
|
255
|
+
? createXScale({
|
|
256
|
+
axis: xAxis,
|
|
257
|
+
boundsWidth,
|
|
258
|
+
series: visibleSeries,
|
|
259
|
+
seriesOptions,
|
|
260
|
+
hasZoomX,
|
|
261
|
+
})
|
|
246
262
|
: undefined,
|
|
247
263
|
yScale: yAxis.map((axis, index) => {
|
|
248
264
|
const axisSeries = series.filter((s) => {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ChartSeries, ChartXAxis } from '../../types';
|
|
2
|
+
import type { PreparedSeriesOptions } from '../useSeries/types';
|
|
2
3
|
import type { PreparedAxis } from './types';
|
|
3
|
-
export declare const getPreparedXAxis: ({ xAxis, seriesData, width, }: {
|
|
4
|
+
export declare const getPreparedXAxis: ({ xAxis, seriesData, seriesOptions, width, }: {
|
|
4
5
|
xAxis?: ChartXAxis;
|
|
5
6
|
seriesData: ChartSeries[];
|
|
7
|
+
seriesOptions: PreparedSeriesOptions;
|
|
6
8
|
width: number;
|
|
7
9
|
}) => Promise<PreparedAxis>;
|
|
@@ -3,8 +3,8 @@ import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, axisCrosshairDefaults, axisLa
|
|
|
3
3
|
import { calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, wrapText, } from '../../utils';
|
|
4
4
|
import { createXScale } from '../useAxisScales';
|
|
5
5
|
import { getAxisCategories, prepareAxisPlotLabel } from './utils';
|
|
6
|
-
async function getLabelSettings({ axis, seriesData, width, autoRotation = true, }) {
|
|
7
|
-
const scale = createXScale({ axis, series: seriesData, boundsWidth: width });
|
|
6
|
+
async function getLabelSettings({ axis, seriesData, seriesOptions, width, autoRotation = true, }) {
|
|
7
|
+
const scale = createXScale({ axis, series: seriesData, seriesOptions, boundsWidth: width });
|
|
8
8
|
const tickCount = getTicksCount({ axis, range: width });
|
|
9
9
|
const ticks = getXAxisItems({
|
|
10
10
|
scale: scale,
|
|
@@ -40,7 +40,7 @@ async function getLabelSettings({ axis, seriesData, width, autoRotation = true,
|
|
|
40
40
|
const maxHeight = rotation ? calculateCos(rotation) * axis.labels.maxWidth : labelsHeight;
|
|
41
41
|
return { height: Math.min(maxHeight, labelsHeight), rotation };
|
|
42
42
|
}
|
|
43
|
-
export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
|
|
43
|
+
export const getPreparedXAxis = async ({ xAxis, seriesData, seriesOptions, width, }) => {
|
|
44
44
|
var _a;
|
|
45
45
|
const titleText = get(xAxis, 'title.text', '');
|
|
46
46
|
const titleStyle = Object.assign(Object.assign({}, xAxisTitleDefaults.style), get(xAxis, 'title.style'));
|
|
@@ -132,6 +132,7 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
|
|
|
132
132
|
const { height, rotation } = await getLabelSettings({
|
|
133
133
|
axis: preparedXAxis,
|
|
134
134
|
seriesData,
|
|
135
|
+
seriesOptions,
|
|
135
136
|
width,
|
|
136
137
|
autoRotation: (_a = xAxis === null || xAxis === void 0 ? void 0 : xAxis.labels) === null || _a === void 0 ? void 0 : _a.autoRotation,
|
|
137
138
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BarXSeries, BarXSeriesData } from '../../types';
|
|
2
|
+
import type { PreparedAxis } from '../useChartOptions/types';
|
|
3
|
+
import type { PreparedBarXSeries, PreparedSeriesOptions } from '../useSeries/types';
|
|
4
|
+
export declare function groupBarXDataByXValue<T extends BarXSeries | PreparedBarXSeries>(series: T[], xAxis: PreparedAxis): Record<string | number, Record<string, {
|
|
5
|
+
data: BarXSeriesData;
|
|
6
|
+
series: T;
|
|
7
|
+
}[]>>;
|
|
8
|
+
export declare function getBarXLayoutForNumericScale(args: {
|
|
9
|
+
plotWidth: number;
|
|
10
|
+
seriesOptions: PreparedSeriesOptions;
|
|
11
|
+
groupedData: ReturnType<typeof groupBarXDataByXValue>;
|
|
12
|
+
}): {
|
|
13
|
+
bandSize: number;
|
|
14
|
+
barGap: number;
|
|
15
|
+
barSize: number;
|
|
16
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
import { getDataCategoryValue } from '../../utils';
|
|
3
|
+
import { MIN_BAR_GAP, MIN_BAR_GROUP_GAP, MIN_BAR_WIDTH } from '../constants';
|
|
4
|
+
import { getSeriesStackId } from '../useSeries/utils';
|
|
5
|
+
export function groupBarXDataByXValue(series, xAxis) {
|
|
6
|
+
const data = {};
|
|
7
|
+
series.forEach((s) => {
|
|
8
|
+
s.data.forEach((d) => {
|
|
9
|
+
var _a;
|
|
10
|
+
const categories = (_a = xAxis.categories) !== null && _a !== void 0 ? _a : [];
|
|
11
|
+
const key = xAxis.type === 'category'
|
|
12
|
+
? getDataCategoryValue({ axisDirection: 'x', categories, data: d })
|
|
13
|
+
: d.x;
|
|
14
|
+
if (key) {
|
|
15
|
+
if (!data[key]) {
|
|
16
|
+
data[key] = {};
|
|
17
|
+
}
|
|
18
|
+
const stackId = getSeriesStackId(s);
|
|
19
|
+
if (!data[key][stackId]) {
|
|
20
|
+
data[key][stackId] = [];
|
|
21
|
+
}
|
|
22
|
+
data[key][stackId].push({ data: d, series: s });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
export function getBarXLayoutForNumericScale(args) {
|
|
29
|
+
const { plotWidth, groupedData, seriesOptions } = args;
|
|
30
|
+
const barMaxWidth = get(seriesOptions, 'bar-x.barMaxWidth');
|
|
31
|
+
const barPadding = get(seriesOptions, 'bar-x.barPadding');
|
|
32
|
+
const groupPadding = get(seriesOptions, 'bar-x.groupPadding');
|
|
33
|
+
const groups = Object.values(groupedData);
|
|
34
|
+
const maxGroupItemCount = groups.reduce((acc, items) => Math.max(acc, Object.keys(items).length), 0);
|
|
35
|
+
const bandSize = plotWidth / groups.length;
|
|
36
|
+
const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
|
|
37
|
+
const groupSize = bandSize - groupGap;
|
|
38
|
+
const barGap = Math.max(bandSize * barPadding, MIN_BAR_GAP);
|
|
39
|
+
const barSize = Math.max(MIN_BAR_WIDTH, Math.min((groupSize - barGap) / maxGroupItemCount, barMaxWidth));
|
|
40
|
+
return { bandSize, barGap, barSize };
|
|
41
|
+
}
|
|
@@ -84,10 +84,24 @@ export interface ChartTooltipTotalsAggregationArgs<T = MeaningfulAny> extends Ch
|
|
|
84
84
|
}
|
|
85
85
|
export type ChartTooltipTotalsBuiltInAggregation = (typeof TOOLTIP_TOTALS_BUILT_IN_AGGREGATION)[keyof typeof TOOLTIP_TOTALS_BUILT_IN_AGGREGATION];
|
|
86
86
|
export type ChartTooltipTotalsAggregationValue = number | string | undefined;
|
|
87
|
+
export type ChartTooltipRowRendererArgs = {
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
active?: boolean;
|
|
91
|
+
color?: string;
|
|
92
|
+
striped?: boolean;
|
|
93
|
+
value: string | number | null | undefined;
|
|
94
|
+
formattedValue?: string;
|
|
95
|
+
hovered?: TooltipDataChunk<unknown>[];
|
|
96
|
+
className?: string;
|
|
97
|
+
};
|
|
87
98
|
export interface ChartTooltip<T = MeaningfulAny> {
|
|
88
99
|
enabled?: boolean;
|
|
89
100
|
/** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
|
|
90
101
|
renderer?: (args: ChartTooltipRendererArgs<T>) => React.ReactElement | null;
|
|
102
|
+
/** Defines the way a single data/series is displayed (corresponding to a separate selected point/ruler/shape on the chart).
|
|
103
|
+
* It is useful in cases where you need to display additional information, but keep the general format of the tooltip. */
|
|
104
|
+
rowRenderer?: (args: ChartTooltipRowRendererArgs) => React.ReactElement | null;
|
|
91
105
|
pin?: {
|
|
92
106
|
enabled?: boolean;
|
|
93
107
|
modifierKey?: 'altKey' | 'metaKey';
|
|
@@ -43,7 +43,7 @@ export declare function isSeriesWithCategoryValues(series: UnknownSeries): serie
|
|
|
43
43
|
category: string;
|
|
44
44
|
}[];
|
|
45
45
|
};
|
|
46
|
-
export declare const getDomainDataXBySeries: (series: UnknownSeries[]) =>
|
|
46
|
+
export declare const getDomainDataXBySeries: (series: UnknownSeries[]) => ({} | undefined)[];
|
|
47
47
|
export declare function getDefaultMaxXAxisValue(series: UnknownSeries[]): 0 | undefined;
|
|
48
48
|
export declare function getDefaultMinXAxisValue(series: UnknownSeries[]): number | undefined;
|
|
49
49
|
export declare function getDefaultMinYAxisValue(series?: UnknownSeries[]): number | undefined;
|
|
@@ -51,7 +51,11 @@ function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y
|
|
|
51
51
|
seriesStack.forEach((singleSeries) => {
|
|
52
52
|
const data = new Map();
|
|
53
53
|
singleSeries.data.forEach((point) => {
|
|
54
|
-
const
|
|
54
|
+
const keyValue = point[keyAttr];
|
|
55
|
+
if (keyValue === null) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const key = String(keyValue);
|
|
55
59
|
let value = 0;
|
|
56
60
|
if (valueAttr in point && typeof point[valueAttr] === 'number') {
|
|
57
61
|
value = point[valueAttr];
|
|
@@ -71,7 +75,7 @@ function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y
|
|
|
71
75
|
}
|
|
72
76
|
export const getDomainDataXBySeries = (series) => {
|
|
73
77
|
const groupedSeries = group(series, (item) => item.type);
|
|
74
|
-
|
|
78
|
+
const values = Array.from(groupedSeries).reduce((acc, [type, seriesList]) => {
|
|
75
79
|
switch (type) {
|
|
76
80
|
case 'bar-y': {
|
|
77
81
|
acc.push(...getDomainDataForStackedSeries(seriesList, 'y', 'x'));
|
|
@@ -85,6 +89,7 @@ export const getDomainDataXBySeries = (series) => {
|
|
|
85
89
|
}
|
|
86
90
|
return acc;
|
|
87
91
|
}, []);
|
|
92
|
+
return Array.from(new Set(values.filter((v) => v !== null)));
|
|
88
93
|
};
|
|
89
94
|
export function getDefaultMaxXAxisValue(series) {
|
|
90
95
|
if (series.some((s) => s.type === 'bar-y')) {
|
|
@@ -39,8 +39,13 @@ export function useChartInnerProps(props) {
|
|
|
39
39
|
const [xAxis, setXAxis] = React.useState(null);
|
|
40
40
|
React.useEffect(() => {
|
|
41
41
|
setXAxis(null);
|
|
42
|
-
getPreparedXAxis({
|
|
43
|
-
|
|
42
|
+
getPreparedXAxis({
|
|
43
|
+
xAxis: data.xAxis,
|
|
44
|
+
width,
|
|
45
|
+
seriesData: zoomedSeriesData,
|
|
46
|
+
seriesOptions: preparedSeriesOptions,
|
|
47
|
+
}).then((val) => setXAxis(val));
|
|
48
|
+
}, [data.xAxis, preparedSeriesOptions, width, zoomedSeriesData]);
|
|
44
49
|
const [yAxis, setYAxis] = React.useState([]);
|
|
45
50
|
React.useEffect(() => {
|
|
46
51
|
setYAxis([]);
|
|
@@ -2,10 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import isNil from 'lodash/isNil';
|
|
3
3
|
import { DefaultTooltipContent } from './DefaultTooltipContent';
|
|
4
4
|
export const ChartTooltipContent = (props) => {
|
|
5
|
-
const { hovered, xAxis, yAxis, renderer, valueFormat, totals } = props;
|
|
5
|
+
const { hovered, xAxis, yAxis, renderer, rowRenderer, valueFormat, totals } = props;
|
|
6
6
|
if (!hovered) {
|
|
7
7
|
return null;
|
|
8
8
|
}
|
|
9
9
|
const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({ hovered, xAxis, yAxis });
|
|
10
|
-
return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, valueFormat: valueFormat, totals: totals })) : (customTooltip);
|
|
10
|
+
return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, valueFormat: valueFormat, totals: totals, rowRenderer: rowRenderer })) : (customTooltip);
|
|
11
11
|
};
|
|
@@ -6,6 +6,7 @@ type Props = {
|
|
|
6
6
|
valueFormat?: ValueFormat;
|
|
7
7
|
xAxis?: ChartXAxis | null;
|
|
8
8
|
yAxis?: ChartYAxis;
|
|
9
|
+
rowRenderer?: ChartTooltip['rowRenderer'];
|
|
9
10
|
};
|
|
10
|
-
export declare const DefaultTooltipContent: ({ hovered, xAxis, yAxis, valueFormat, totals }: Props) => React.JSX.Element;
|
|
11
|
+
export declare const DefaultTooltipContent: ({ hovered, xAxis, yAxis, valueFormat, totals, rowRenderer, }: Props) => React.JSX.Element;
|
|
11
12
|
export {};
|
|
@@ -7,9 +7,25 @@ import { Row } from './Row';
|
|
|
7
7
|
import { RowTotals } from './RowTotals';
|
|
8
8
|
import { getDefaultValueFormat, getHoveredValues, getMeasureValue, getPreparedAggregation, getXRowData, } from './utils';
|
|
9
9
|
const b = block('tooltip');
|
|
10
|
-
export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, totals }) => {
|
|
10
|
+
export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, totals, rowRenderer, }) => {
|
|
11
11
|
const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
|
|
12
12
|
const hoveredValues = getHoveredValues({ hovered, xAxis, yAxis });
|
|
13
|
+
const renderRow = ({ id, name, color, active, striped, value, formattedValue, }) => {
|
|
14
|
+
if (typeof rowRenderer === 'function') {
|
|
15
|
+
return rowRenderer({
|
|
16
|
+
id,
|
|
17
|
+
name,
|
|
18
|
+
color,
|
|
19
|
+
value,
|
|
20
|
+
formattedValue,
|
|
21
|
+
striped,
|
|
22
|
+
active,
|
|
23
|
+
className: b('content-row', { active, striped }),
|
|
24
|
+
hovered,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
|
|
28
|
+
};
|
|
13
29
|
return (React.createElement("div", { className: b('content') },
|
|
14
30
|
measureValue && (React.createElement("div", { className: b('series-name'), dangerouslySetInnerHTML: { __html: measureValue } })),
|
|
15
31
|
// eslint-disable-next-line complexity
|
|
@@ -30,7 +46,15 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
30
46
|
value: hoveredValues[i],
|
|
31
47
|
format,
|
|
32
48
|
});
|
|
33
|
-
return (
|
|
49
|
+
return renderRow({
|
|
50
|
+
id,
|
|
51
|
+
active,
|
|
52
|
+
color,
|
|
53
|
+
name: series.name,
|
|
54
|
+
striped,
|
|
55
|
+
value: hoveredValues[i],
|
|
56
|
+
formattedValue,
|
|
57
|
+
});
|
|
34
58
|
}
|
|
35
59
|
case 'waterfall': {
|
|
36
60
|
const isTotal = get(data, 'total', false);
|
|
@@ -56,7 +80,15 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
56
80
|
value: hoveredValues[i],
|
|
57
81
|
format,
|
|
58
82
|
});
|
|
59
|
-
return (
|
|
83
|
+
return renderRow({
|
|
84
|
+
id,
|
|
85
|
+
active,
|
|
86
|
+
color,
|
|
87
|
+
name: series.name,
|
|
88
|
+
striped,
|
|
89
|
+
value: hoveredValues[i],
|
|
90
|
+
formattedValue,
|
|
91
|
+
});
|
|
60
92
|
}
|
|
61
93
|
case 'pie':
|
|
62
94
|
case 'treemap': {
|
|
@@ -65,11 +97,13 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
65
97
|
value: hoveredValues[i],
|
|
66
98
|
format: valueFormat || { type: 'number' },
|
|
67
99
|
});
|
|
68
|
-
return (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
100
|
+
return renderRow({
|
|
101
|
+
id,
|
|
102
|
+
color,
|
|
103
|
+
name: [seriesData.name || seriesData.id].flat().join('\n'),
|
|
104
|
+
value: hoveredValues[i],
|
|
105
|
+
formattedValue,
|
|
106
|
+
});
|
|
73
107
|
}
|
|
74
108
|
case 'sankey': {
|
|
75
109
|
const { target, data: source } = seriesItem;
|
|
@@ -77,11 +111,13 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
77
111
|
value: hoveredValues[i],
|
|
78
112
|
format: valueFormat || { type: 'number' },
|
|
79
113
|
});
|
|
80
|
-
return (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
return renderRow({
|
|
115
|
+
id,
|
|
116
|
+
color,
|
|
117
|
+
name: `${source.name} → ${target === null || target === void 0 ? void 0 : target.name}`,
|
|
118
|
+
value: hoveredValues[i],
|
|
119
|
+
formattedValue,
|
|
120
|
+
});
|
|
85
121
|
}
|
|
86
122
|
case 'radar': {
|
|
87
123
|
const radarSeries = series;
|
|
@@ -89,7 +125,14 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
89
125
|
value: hoveredValues[i],
|
|
90
126
|
format: valueFormat || { type: 'number' },
|
|
91
127
|
});
|
|
92
|
-
return (
|
|
128
|
+
return renderRow({
|
|
129
|
+
id,
|
|
130
|
+
color,
|
|
131
|
+
active,
|
|
132
|
+
name: radarSeries.name || radarSeries.id,
|
|
133
|
+
value: hoveredValues[i],
|
|
134
|
+
formattedValue,
|
|
135
|
+
});
|
|
93
136
|
}
|
|
94
137
|
default: {
|
|
95
138
|
return null;
|
|
@@ -23,5 +23,5 @@ export const Tooltip = (props) => {
|
|
|
23
23
|
}, [left, top]);
|
|
24
24
|
return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { anchorElement: anchor, className: b({ pinned: tooltipPinned }), disableTransition: true, floatingStyles: tooltipPinned ? undefined : { pointerEvents: 'none' }, offset: { mainAxis: 20 }, onOpenChange: tooltipPinned ? handleOnOpenChange : undefined, open: true, placement: ['right', 'left', 'top', 'bottom'] },
|
|
25
25
|
React.createElement("div", { className: b('popup-content') },
|
|
26
|
-
React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer, valueFormat: tooltip.valueFormat, totals: tooltip.totals })))) : null;
|
|
26
|
+
React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer, rowRenderer: tooltip.rowRenderer, valueFormat: tooltip.valueFormat, totals: tooltip.totals })))) : null;
|
|
27
27
|
};
|
|
@@ -29,6 +29,7 @@ export declare function createXScale(args: {
|
|
|
29
29
|
axis: PreparedAxis | ChartAxis;
|
|
30
30
|
boundsWidth: number;
|
|
31
31
|
series: (PreparedSeries | ChartSeries)[];
|
|
32
|
+
seriesOptions: PreparedSeriesOptions;
|
|
32
33
|
hasZoomX?: boolean;
|
|
33
34
|
}): ScaleBand<string> | ScaleLinear<number, number, never> | ScaleTime<number, number, never>;
|
|
34
35
|
/**
|
|
@@ -4,6 +4,7 @@ import get from 'lodash/get';
|
|
|
4
4
|
import { DEFAULT_AXIS_TYPE, SeriesType } from '../../constants';
|
|
5
5
|
import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
|
|
6
6
|
import { getBarYLayoutForNumericScale, groupBarYDataByYValue } from '../utils';
|
|
7
|
+
import { getBarXLayoutForNumericScale, groupBarXDataByXValue } from '../utils/bar-x';
|
|
7
8
|
const X_AXIS_ZOOM_PADDING = 0.02;
|
|
8
9
|
function isNumericalArrayData(data) {
|
|
9
10
|
return data.every((d) => typeof d === 'number' || d === null);
|
|
@@ -130,32 +131,55 @@ function calculateXAxisPadding(series) {
|
|
|
130
131
|
});
|
|
131
132
|
return result;
|
|
132
133
|
}
|
|
134
|
+
function getXScaleRange({ boundsWidth, series, seriesOptions, hasZoomX, axis, maxPadding, }) {
|
|
135
|
+
const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
|
|
136
|
+
const xRange = [0, boundsWidth - maxPadding];
|
|
137
|
+
const xRangeZoom = [0 + xAxisZoomPadding, boundsWidth - xAxisZoomPadding];
|
|
138
|
+
const range = hasZoomX ? xRangeZoom : xRange;
|
|
139
|
+
const barXSeries = series.filter((s) => s.type === SeriesType.BarX);
|
|
140
|
+
if (barXSeries.length) {
|
|
141
|
+
const groupedData = groupBarXDataByXValue(barXSeries, axis);
|
|
142
|
+
if (Object.keys(groupedData).length > 1) {
|
|
143
|
+
const { bandSize } = getBarXLayoutForNumericScale({
|
|
144
|
+
plotWidth: boundsWidth - maxPadding,
|
|
145
|
+
groupedData,
|
|
146
|
+
seriesOptions,
|
|
147
|
+
});
|
|
148
|
+
const offset = bandSize / 2;
|
|
149
|
+
return [range[0] + offset, range[1] - offset];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return range;
|
|
153
|
+
}
|
|
133
154
|
// eslint-disable-next-line complexity
|
|
134
155
|
export function createXScale(args) {
|
|
135
|
-
const { axis, boundsWidth, series, hasZoomX } = args;
|
|
156
|
+
const { axis, boundsWidth, series, seriesOptions, hasZoomX } = args;
|
|
136
157
|
const xMinProps = get(axis, 'min');
|
|
137
158
|
const xMaxProps = get(axis, 'max');
|
|
138
159
|
const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
|
|
139
160
|
const xCategories = get(axis, 'categories');
|
|
140
|
-
const xTimestamps = get(axis, 'timestamps');
|
|
141
161
|
const maxPadding = get(axis, 'maxPadding', 0);
|
|
142
162
|
const xAxisMaxPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
163
|
+
const range = getXScaleRange({
|
|
164
|
+
boundsWidth,
|
|
165
|
+
series,
|
|
166
|
+
seriesOptions,
|
|
167
|
+
hasZoomX,
|
|
168
|
+
axis,
|
|
169
|
+
maxPadding: xAxisMaxPadding,
|
|
170
|
+
});
|
|
146
171
|
switch (axis.order) {
|
|
147
172
|
case 'sortDesc':
|
|
148
173
|
case 'reverse': {
|
|
149
|
-
|
|
150
|
-
xRangeZoom.reverse();
|
|
174
|
+
range.reverse();
|
|
151
175
|
}
|
|
152
176
|
}
|
|
153
177
|
switch (xType) {
|
|
154
178
|
case 'linear':
|
|
155
179
|
case 'logarithmic': {
|
|
156
|
-
const
|
|
157
|
-
if (isNumericalArrayData(
|
|
158
|
-
const [xMinDomain, xMaxDomain] = extent(
|
|
180
|
+
const domainData = getDomainDataXBySeries(series);
|
|
181
|
+
if (isNumericalArrayData(domainData)) {
|
|
182
|
+
const [xMinDomain, xMaxDomain] = extent(domainData);
|
|
159
183
|
let xMin;
|
|
160
184
|
let xMax;
|
|
161
185
|
if (typeof xMinProps === 'number') {
|
|
@@ -176,11 +200,10 @@ export function createXScale(args) {
|
|
|
176
200
|
: xMaxDomain;
|
|
177
201
|
}
|
|
178
202
|
const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
|
|
179
|
-
const scale = scaleFn()
|
|
180
|
-
.domain([xMin, xMax])
|
|
181
|
-
.range(hasZoomX ? xRangeZoom : xRange);
|
|
203
|
+
const scale = scaleFn().domain([xMin, xMax]).range(range);
|
|
182
204
|
if (!hasZoomX) {
|
|
183
|
-
|
|
205
|
+
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
206
|
+
scale.nice(Math.max(10, domainData.length));
|
|
184
207
|
}
|
|
185
208
|
return scale;
|
|
186
209
|
}
|
|
@@ -195,40 +218,27 @@ export function createXScale(args) {
|
|
|
195
218
|
});
|
|
196
219
|
const xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
|
|
197
220
|
if (xScale.step() / 2 < xAxisMaxPadding) {
|
|
198
|
-
xScale.range(
|
|
221
|
+
xScale.range(range);
|
|
199
222
|
}
|
|
200
223
|
return xScale;
|
|
201
224
|
}
|
|
202
225
|
break;
|
|
203
226
|
}
|
|
204
227
|
case 'datetime': {
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
let domain = null;
|
|
229
|
+
const domainData = get(axis, 'timestamps') || getDomainDataXBySeries(series);
|
|
230
|
+
if (isNumericalArrayData(domainData)) {
|
|
231
|
+
const [xMinTimestamp, xMaxTimestamp] = extent(domainData);
|
|
207
232
|
const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
|
|
208
233
|
const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
.range(hasZoomX ? xRangeZoom : xRange);
|
|
234
|
+
domain = [xMin, xMax];
|
|
235
|
+
const scale = scaleUtc().domain(domain).range(range);
|
|
212
236
|
if (!hasZoomX) {
|
|
213
|
-
|
|
237
|
+
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
238
|
+
scale.nice(Math.max(10, domainData.length));
|
|
214
239
|
}
|
|
215
240
|
return scale;
|
|
216
241
|
}
|
|
217
|
-
else {
|
|
218
|
-
const domain = getDomainDataXBySeries(series);
|
|
219
|
-
if (isNumericalArrayData(domain)) {
|
|
220
|
-
const [xMinTimestamp, xMaxTimestamp] = extent(domain);
|
|
221
|
-
const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
|
|
222
|
-
const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
|
|
223
|
-
const scale = scaleUtc()
|
|
224
|
-
.domain([xMin, xMax])
|
|
225
|
-
.range(hasZoomX ? xRangeZoom : xRange);
|
|
226
|
-
if (!hasZoomX) {
|
|
227
|
-
scale.nice();
|
|
228
|
-
}
|
|
229
|
-
return scale;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
242
|
break;
|
|
233
243
|
}
|
|
234
244
|
}
|
|
@@ -242,7 +252,13 @@ const createScales = (args) => {
|
|
|
242
252
|
visibleSeries = visibleSeries.length === 0 ? series : visibleSeries;
|
|
243
253
|
return {
|
|
244
254
|
xScale: xAxis
|
|
245
|
-
? createXScale({
|
|
255
|
+
? createXScale({
|
|
256
|
+
axis: xAxis,
|
|
257
|
+
boundsWidth,
|
|
258
|
+
series: visibleSeries,
|
|
259
|
+
seriesOptions,
|
|
260
|
+
hasZoomX,
|
|
261
|
+
})
|
|
246
262
|
: undefined,
|
|
247
263
|
yScale: yAxis.map((axis, index) => {
|
|
248
264
|
const axisSeries = series.filter((s) => {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ChartSeries, ChartXAxis } from '../../types';
|
|
2
|
+
import type { PreparedSeriesOptions } from '../useSeries/types';
|
|
2
3
|
import type { PreparedAxis } from './types';
|
|
3
|
-
export declare const getPreparedXAxis: ({ xAxis, seriesData, width, }: {
|
|
4
|
+
export declare const getPreparedXAxis: ({ xAxis, seriesData, seriesOptions, width, }: {
|
|
4
5
|
xAxis?: ChartXAxis;
|
|
5
6
|
seriesData: ChartSeries[];
|
|
7
|
+
seriesOptions: PreparedSeriesOptions;
|
|
6
8
|
width: number;
|
|
7
9
|
}) => Promise<PreparedAxis>;
|
|
@@ -3,8 +3,8 @@ import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, axisCrosshairDefaults, axisLa
|
|
|
3
3
|
import { calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, wrapText, } from '../../utils';
|
|
4
4
|
import { createXScale } from '../useAxisScales';
|
|
5
5
|
import { getAxisCategories, prepareAxisPlotLabel } from './utils';
|
|
6
|
-
async function getLabelSettings({ axis, seriesData, width, autoRotation = true, }) {
|
|
7
|
-
const scale = createXScale({ axis, series: seriesData, boundsWidth: width });
|
|
6
|
+
async function getLabelSettings({ axis, seriesData, seriesOptions, width, autoRotation = true, }) {
|
|
7
|
+
const scale = createXScale({ axis, series: seriesData, seriesOptions, boundsWidth: width });
|
|
8
8
|
const tickCount = getTicksCount({ axis, range: width });
|
|
9
9
|
const ticks = getXAxisItems({
|
|
10
10
|
scale: scale,
|
|
@@ -40,7 +40,7 @@ async function getLabelSettings({ axis, seriesData, width, autoRotation = true,
|
|
|
40
40
|
const maxHeight = rotation ? calculateCos(rotation) * axis.labels.maxWidth : labelsHeight;
|
|
41
41
|
return { height: Math.min(maxHeight, labelsHeight), rotation };
|
|
42
42
|
}
|
|
43
|
-
export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
|
|
43
|
+
export const getPreparedXAxis = async ({ xAxis, seriesData, seriesOptions, width, }) => {
|
|
44
44
|
var _a;
|
|
45
45
|
const titleText = get(xAxis, 'title.text', '');
|
|
46
46
|
const titleStyle = Object.assign(Object.assign({}, xAxisTitleDefaults.style), get(xAxis, 'title.style'));
|
|
@@ -132,6 +132,7 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
|
|
|
132
132
|
const { height, rotation } = await getLabelSettings({
|
|
133
133
|
axis: preparedXAxis,
|
|
134
134
|
seriesData,
|
|
135
|
+
seriesOptions,
|
|
135
136
|
width,
|
|
136
137
|
autoRotation: (_a = xAxis === null || xAxis === void 0 ? void 0 : xAxis.labels) === null || _a === void 0 ? void 0 : _a.autoRotation,
|
|
137
138
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BarXSeries, BarXSeriesData } from '../../types';
|
|
2
|
+
import type { PreparedAxis } from '../useChartOptions/types';
|
|
3
|
+
import type { PreparedBarXSeries, PreparedSeriesOptions } from '../useSeries/types';
|
|
4
|
+
export declare function groupBarXDataByXValue<T extends BarXSeries | PreparedBarXSeries>(series: T[], xAxis: PreparedAxis): Record<string | number, Record<string, {
|
|
5
|
+
data: BarXSeriesData;
|
|
6
|
+
series: T;
|
|
7
|
+
}[]>>;
|
|
8
|
+
export declare function getBarXLayoutForNumericScale(args: {
|
|
9
|
+
plotWidth: number;
|
|
10
|
+
seriesOptions: PreparedSeriesOptions;
|
|
11
|
+
groupedData: ReturnType<typeof groupBarXDataByXValue>;
|
|
12
|
+
}): {
|
|
13
|
+
bandSize: number;
|
|
14
|
+
barGap: number;
|
|
15
|
+
barSize: number;
|
|
16
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
import { getDataCategoryValue } from '../../utils';
|
|
3
|
+
import { MIN_BAR_GAP, MIN_BAR_GROUP_GAP, MIN_BAR_WIDTH } from '../constants';
|
|
4
|
+
import { getSeriesStackId } from '../useSeries/utils';
|
|
5
|
+
export function groupBarXDataByXValue(series, xAxis) {
|
|
6
|
+
const data = {};
|
|
7
|
+
series.forEach((s) => {
|
|
8
|
+
s.data.forEach((d) => {
|
|
9
|
+
var _a;
|
|
10
|
+
const categories = (_a = xAxis.categories) !== null && _a !== void 0 ? _a : [];
|
|
11
|
+
const key = xAxis.type === 'category'
|
|
12
|
+
? getDataCategoryValue({ axisDirection: 'x', categories, data: d })
|
|
13
|
+
: d.x;
|
|
14
|
+
if (key) {
|
|
15
|
+
if (!data[key]) {
|
|
16
|
+
data[key] = {};
|
|
17
|
+
}
|
|
18
|
+
const stackId = getSeriesStackId(s);
|
|
19
|
+
if (!data[key][stackId]) {
|
|
20
|
+
data[key][stackId] = [];
|
|
21
|
+
}
|
|
22
|
+
data[key][stackId].push({ data: d, series: s });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
export function getBarXLayoutForNumericScale(args) {
|
|
29
|
+
const { plotWidth, groupedData, seriesOptions } = args;
|
|
30
|
+
const barMaxWidth = get(seriesOptions, 'bar-x.barMaxWidth');
|
|
31
|
+
const barPadding = get(seriesOptions, 'bar-x.barPadding');
|
|
32
|
+
const groupPadding = get(seriesOptions, 'bar-x.groupPadding');
|
|
33
|
+
const groups = Object.values(groupedData);
|
|
34
|
+
const maxGroupItemCount = groups.reduce((acc, items) => Math.max(acc, Object.keys(items).length), 0);
|
|
35
|
+
const bandSize = plotWidth / groups.length;
|
|
36
|
+
const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
|
|
37
|
+
const groupSize = bandSize - groupGap;
|
|
38
|
+
const barGap = Math.max(bandSize * barPadding, MIN_BAR_GAP);
|
|
39
|
+
const barSize = Math.max(MIN_BAR_WIDTH, Math.min((groupSize - barGap) / maxGroupItemCount, barMaxWidth));
|
|
40
|
+
return { bandSize, barGap, barSize };
|
|
41
|
+
}
|
|
@@ -84,10 +84,24 @@ export interface ChartTooltipTotalsAggregationArgs<T = MeaningfulAny> extends Ch
|
|
|
84
84
|
}
|
|
85
85
|
export type ChartTooltipTotalsBuiltInAggregation = (typeof TOOLTIP_TOTALS_BUILT_IN_AGGREGATION)[keyof typeof TOOLTIP_TOTALS_BUILT_IN_AGGREGATION];
|
|
86
86
|
export type ChartTooltipTotalsAggregationValue = number | string | undefined;
|
|
87
|
+
export type ChartTooltipRowRendererArgs = {
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
active?: boolean;
|
|
91
|
+
color?: string;
|
|
92
|
+
striped?: boolean;
|
|
93
|
+
value: string | number | null | undefined;
|
|
94
|
+
formattedValue?: string;
|
|
95
|
+
hovered?: TooltipDataChunk<unknown>[];
|
|
96
|
+
className?: string;
|
|
97
|
+
};
|
|
87
98
|
export interface ChartTooltip<T = MeaningfulAny> {
|
|
88
99
|
enabled?: boolean;
|
|
89
100
|
/** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
|
|
90
101
|
renderer?: (args: ChartTooltipRendererArgs<T>) => React.ReactElement | null;
|
|
102
|
+
/** Defines the way a single data/series is displayed (corresponding to a separate selected point/ruler/shape on the chart).
|
|
103
|
+
* It is useful in cases where you need to display additional information, but keep the general format of the tooltip. */
|
|
104
|
+
rowRenderer?: (args: ChartTooltipRowRendererArgs) => React.ReactElement | null;
|
|
91
105
|
pin?: {
|
|
92
106
|
enabled?: boolean;
|
|
93
107
|
modifierKey?: 'altKey' | 'metaKey';
|
|
@@ -43,7 +43,7 @@ export declare function isSeriesWithCategoryValues(series: UnknownSeries): serie
|
|
|
43
43
|
category: string;
|
|
44
44
|
}[];
|
|
45
45
|
};
|
|
46
|
-
export declare const getDomainDataXBySeries: (series: UnknownSeries[]) =>
|
|
46
|
+
export declare const getDomainDataXBySeries: (series: UnknownSeries[]) => ({} | undefined)[];
|
|
47
47
|
export declare function getDefaultMaxXAxisValue(series: UnknownSeries[]): 0 | undefined;
|
|
48
48
|
export declare function getDefaultMinXAxisValue(series: UnknownSeries[]): number | undefined;
|
|
49
49
|
export declare function getDefaultMinYAxisValue(series?: UnknownSeries[]): number | undefined;
|
|
@@ -51,7 +51,11 @@ function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y
|
|
|
51
51
|
seriesStack.forEach((singleSeries) => {
|
|
52
52
|
const data = new Map();
|
|
53
53
|
singleSeries.data.forEach((point) => {
|
|
54
|
-
const
|
|
54
|
+
const keyValue = point[keyAttr];
|
|
55
|
+
if (keyValue === null) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const key = String(keyValue);
|
|
55
59
|
let value = 0;
|
|
56
60
|
if (valueAttr in point && typeof point[valueAttr] === 'number') {
|
|
57
61
|
value = point[valueAttr];
|
|
@@ -71,7 +75,7 @@ function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y
|
|
|
71
75
|
}
|
|
72
76
|
export const getDomainDataXBySeries = (series) => {
|
|
73
77
|
const groupedSeries = group(series, (item) => item.type);
|
|
74
|
-
|
|
78
|
+
const values = Array.from(groupedSeries).reduce((acc, [type, seriesList]) => {
|
|
75
79
|
switch (type) {
|
|
76
80
|
case 'bar-y': {
|
|
77
81
|
acc.push(...getDomainDataForStackedSeries(seriesList, 'y', 'x'));
|
|
@@ -85,6 +89,7 @@ export const getDomainDataXBySeries = (series) => {
|
|
|
85
89
|
}
|
|
86
90
|
return acc;
|
|
87
91
|
}, []);
|
|
92
|
+
return Array.from(new Set(values.filter((v) => v !== null)));
|
|
88
93
|
};
|
|
89
94
|
export function getDefaultMaxXAxisValue(series) {
|
|
90
95
|
if (series.some((s) => s.type === 'bar-y')) {
|