@gravity-ui/charts 1.43.0 → 1.44.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/index.js +3 -3
- package/dist/cjs/components/ChartInner/useChartInnerHandlers.d.ts +3 -3
- package/dist/cjs/components/ChartInner/useChartInnerHandlers.js +10 -12
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +8 -4
- package/dist/cjs/components/ChartInner/utils/title.d.ts +3 -2
- package/dist/cjs/components/ChartInner/utils/title.js +19 -14
- package/dist/cjs/components/ChartInner/utils/zoom.js +3 -1
- package/dist/cjs/components/Title/index.d.ts +1 -3
- package/dist/cjs/components/Title/index.js +2 -2
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +31 -6
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +4 -5
- package/dist/cjs/core/constants/chart-types.d.ts +1 -0
- package/dist/cjs/core/constants/chart-types.js +1 -0
- package/dist/cjs/core/constants/defaults/series-options.d.ts +5 -1
- package/dist/cjs/core/constants/defaults/series-options.js +13 -0
- package/dist/cjs/core/constants/index.d.ts +0 -1
- package/dist/cjs/core/constants/index.js +0 -1
- package/dist/cjs/core/i18n/keysets/en.json +2 -1
- package/dist/cjs/core/i18n/keysets/ru.json +2 -1
- package/dist/cjs/core/series/prepare-legend.js +2 -2
- package/dist/cjs/core/series/prepare-x-range.d.ts +11 -0
- package/dist/cjs/core/series/prepare-x-range.js +41 -0
- package/dist/cjs/core/series/prepareSeries.js +9 -0
- package/dist/cjs/core/series/types.d.ts +18 -2
- package/dist/cjs/core/types/chart/area.d.ts +2 -1
- package/dist/cjs/core/types/chart/series.d.ts +29 -2
- package/dist/cjs/core/types/chart/tooltip.d.ts +6 -1
- package/dist/cjs/core/types/chart/x-range.d.ts +59 -0
- package/dist/cjs/core/types/chart/x-range.js +1 -0
- package/dist/cjs/core/types/chart/zoom.d.ts +1 -1
- package/dist/cjs/core/types/index.d.ts +1 -0
- package/dist/cjs/core/types/index.js +1 -0
- package/dist/cjs/core/utils/axis/x-axis.js +9 -1
- package/dist/cjs/core/utils/color.js +6 -0
- package/dist/cjs/core/utils/common.js +10 -0
- package/dist/cjs/core/utils/get-closest-data.js +19 -0
- package/dist/cjs/core/utils/labels.d.ts +1 -1
- package/dist/cjs/core/utils/labels.js +3 -2
- package/dist/cjs/core/validation/index.js +13 -0
- package/dist/cjs/core/zoom/zoom.js +24 -7
- package/dist/cjs/hooks/useSeries/index.js +8 -2
- package/dist/cjs/hooks/useShapes/area/index.js +2 -2
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +19 -18
- package/dist/cjs/hooks/useShapes/area/types.d.ts +2 -2
- package/dist/cjs/hooks/useShapes/bar-x/index.js +2 -2
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +37 -21
- package/dist/cjs/hooks/useShapes/bar-x/types.d.ts +2 -2
- package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
- package/dist/cjs/hooks/useShapes/index.js +35 -5
- package/dist/cjs/hooks/useShapes/line/index.js +7 -16
- package/dist/cjs/hooks/useShapes/line/prepare-data.d.ts +2 -0
- package/dist/cjs/hooks/useShapes/line/prepare-data.js +11 -7
- package/dist/cjs/hooks/useShapes/line/types.d.ts +2 -2
- package/dist/cjs/hooks/useShapes/x-range/index.d.ts +14 -0
- package/dist/cjs/hooks/useShapes/x-range/index.js +115 -0
- package/dist/cjs/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
- package/dist/cjs/hooks/useShapes/x-range/prepare-data.js +147 -0
- package/dist/cjs/hooks/useShapes/x-range/types.d.ts +12 -0
- package/dist/cjs/hooks/useShapes/x-range/types.js +1 -0
- package/dist/cjs/types/chart-ui.d.ts +4 -0
- package/dist/esm/components/ChartInner/index.js +3 -3
- package/dist/esm/components/ChartInner/useChartInnerHandlers.d.ts +3 -3
- package/dist/esm/components/ChartInner/useChartInnerHandlers.js +10 -12
- package/dist/esm/components/ChartInner/useChartInnerProps.js +8 -4
- package/dist/esm/components/ChartInner/utils/title.d.ts +3 -2
- package/dist/esm/components/ChartInner/utils/title.js +19 -14
- package/dist/esm/components/ChartInner/utils/zoom.js +3 -1
- package/dist/esm/components/Title/index.d.ts +1 -3
- package/dist/esm/components/Title/index.js +2 -2
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +31 -6
- package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +4 -5
- package/dist/esm/core/constants/chart-types.d.ts +1 -0
- package/dist/esm/core/constants/chart-types.js +1 -0
- package/dist/esm/core/constants/defaults/series-options.d.ts +5 -1
- package/dist/esm/core/constants/defaults/series-options.js +13 -0
- package/dist/esm/core/constants/index.d.ts +0 -1
- package/dist/esm/core/constants/index.js +0 -1
- package/dist/esm/core/i18n/keysets/en.json +2 -1
- package/dist/esm/core/i18n/keysets/ru.json +2 -1
- package/dist/esm/core/series/prepare-legend.js +2 -2
- package/dist/esm/core/series/prepare-x-range.d.ts +11 -0
- package/dist/esm/core/series/prepare-x-range.js +41 -0
- package/dist/esm/core/series/prepareSeries.js +9 -0
- package/dist/esm/core/series/types.d.ts +18 -2
- package/dist/esm/core/types/chart/area.d.ts +2 -1
- package/dist/esm/core/types/chart/series.d.ts +29 -2
- package/dist/esm/core/types/chart/tooltip.d.ts +6 -1
- package/dist/esm/core/types/chart/x-range.d.ts +59 -0
- package/dist/esm/core/types/chart/x-range.js +1 -0
- package/dist/esm/core/types/chart/zoom.d.ts +1 -1
- package/dist/esm/core/types/index.d.ts +1 -0
- package/dist/esm/core/types/index.js +1 -0
- package/dist/esm/core/utils/axis/x-axis.js +9 -1
- package/dist/esm/core/utils/color.js +6 -0
- package/dist/esm/core/utils/common.js +10 -0
- package/dist/esm/core/utils/get-closest-data.js +19 -0
- package/dist/esm/core/utils/labels.d.ts +1 -1
- package/dist/esm/core/utils/labels.js +3 -2
- package/dist/esm/core/validation/index.js +13 -0
- package/dist/esm/core/zoom/zoom.js +24 -7
- package/dist/esm/hooks/useSeries/index.js +8 -2
- package/dist/esm/hooks/useShapes/area/index.js +2 -2
- package/dist/esm/hooks/useShapes/area/prepare-data.js +19 -18
- package/dist/esm/hooks/useShapes/area/types.d.ts +2 -2
- package/dist/esm/hooks/useShapes/bar-x/index.js +2 -2
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +37 -21
- package/dist/esm/hooks/useShapes/bar-x/types.d.ts +2 -2
- package/dist/esm/hooks/useShapes/index.d.ts +2 -1
- package/dist/esm/hooks/useShapes/index.js +35 -5
- package/dist/esm/hooks/useShapes/line/index.js +7 -16
- package/dist/esm/hooks/useShapes/line/prepare-data.d.ts +2 -0
- package/dist/esm/hooks/useShapes/line/prepare-data.js +11 -7
- package/dist/esm/hooks/useShapes/line/types.d.ts +2 -2
- package/dist/esm/hooks/useShapes/x-range/index.d.ts +14 -0
- package/dist/esm/hooks/useShapes/x-range/index.js +115 -0
- package/dist/esm/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
- package/dist/esm/hooks/useShapes/x-range/prepare-data.js +147 -0
- package/dist/esm/hooks/useShapes/x-range/types.d.ts +12 -0
- package/dist/esm/hooks/useShapes/x-range/types.js +1 -0
- package/dist/esm/types/chart-ui.d.ts +4 -0
- package/package.json +1 -1
- package/dist/cjs/core/constants/misc.d.ts +0 -1
- package/dist/cjs/core/constants/misc.js +0 -7
- package/dist/esm/core/constants/misc.d.ts +0 -1
- package/dist/esm/core/constants/misc.js +0 -7
|
@@ -30,6 +30,7 @@ export * from './chart/radar';
|
|
|
30
30
|
export * from './chart/heatmap';
|
|
31
31
|
export * from './chart/funnel';
|
|
32
32
|
export * from './chart/brush';
|
|
33
|
+
export * from './chart/x-range';
|
|
33
34
|
export interface ChartData<T = MeaningfulAny> {
|
|
34
35
|
/**
|
|
35
36
|
* General options for the chart.
|
|
@@ -12,7 +12,15 @@ function getTicksCount(args) {
|
|
|
12
12
|
if (series) {
|
|
13
13
|
const xDataSet = new Set();
|
|
14
14
|
series === null || series === void 0 ? void 0 : series.forEach((item) => {
|
|
15
|
-
if (
|
|
15
|
+
if (item.type === 'x-range') {
|
|
16
|
+
item.data.forEach((d) => {
|
|
17
|
+
if (d.x0 !== null && d.x1 !== null) {
|
|
18
|
+
xDataSet.add(d.x0);
|
|
19
|
+
xDataSet.add(d.x1);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
else if (isSeriesWithNumericalXValues(item)) {
|
|
16
24
|
item.data.forEach((data) => {
|
|
17
25
|
xDataSet.add(data.x);
|
|
18
26
|
});
|
|
@@ -22,6 +22,12 @@ export function getDomainForContinuousColorScale(args) {
|
|
|
22
22
|
acc.push(...s.data.map((d) => Number(d.y)));
|
|
23
23
|
break;
|
|
24
24
|
}
|
|
25
|
+
case 'x-range': {
|
|
26
|
+
// Use bar duration (x1 - x0) as the color domain value so that
|
|
27
|
+
// longer bars can be visually distinguished by color intensity.
|
|
28
|
+
acc.push(...s.data.map((d) => Math.abs(Number(d.x1) - Number(d.x0))));
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
25
31
|
default: {
|
|
26
32
|
throw Error(`The method for calculation a domain for a continuous color scale for the "${s.type}" series is not defined`);
|
|
27
33
|
}
|
|
@@ -57,6 +57,16 @@ export const getDomainDataXBySeries = (series) => {
|
|
|
57
57
|
acc.push(...getDomainDataForStackedSeries(seriesList, 'y', 'x'));
|
|
58
58
|
break;
|
|
59
59
|
}
|
|
60
|
+
case 'x-range': {
|
|
61
|
+
seriesList.forEach((s) => {
|
|
62
|
+
s.data.forEach((d) => {
|
|
63
|
+
if (!isNil(d.x0) && !isNil(d.x1)) {
|
|
64
|
+
acc.push(d.x0, d.x1);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
60
70
|
default: {
|
|
61
71
|
seriesList.filter(isSeriesWithNumericalXValues).forEach((s) => {
|
|
62
72
|
acc.push(...s.data.map((d) => d.x));
|
|
@@ -288,6 +288,25 @@ export function getClosestPoints(args) {
|
|
|
288
288
|
}
|
|
289
289
|
break;
|
|
290
290
|
}
|
|
291
|
+
case 'x-range': {
|
|
292
|
+
const data = list;
|
|
293
|
+
const pointsInXRange = data.filter((d) => pointerX >= d.x &&
|
|
294
|
+
pointerX <= d.x + d.width &&
|
|
295
|
+
pointerY >= d.y &&
|
|
296
|
+
pointerY <= d.y + d.height);
|
|
297
|
+
if (pointsInXRange.length === 0) {
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
const closestByX = pointsInXRange.length === 1
|
|
301
|
+
? pointsInXRange[0]
|
|
302
|
+
: sort(pointsInXRange, (d) => Math.abs(d.x + d.width / 2 - pointerX))[0];
|
|
303
|
+
result.push(...pointsInXRange.map((d) => ({
|
|
304
|
+
data: d.data,
|
|
305
|
+
series: d.series,
|
|
306
|
+
closest: d === closestByX,
|
|
307
|
+
})));
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
291
310
|
}
|
|
292
311
|
});
|
|
293
312
|
if (closestPointsByXValue.length) {
|
|
@@ -3,7 +3,7 @@ export declare function getLeftPosition(label: LabelData): number;
|
|
|
3
3
|
export declare function getOverlappingByX(rect1: LabelData | HtmlItem, rect2: LabelData | HtmlItem, gap?: number): number;
|
|
4
4
|
export declare function getOverlappingByY(rect1: LabelData | HtmlItem, rect2: LabelData | HtmlItem, gap?: number): number;
|
|
5
5
|
export declare function isLabelsOverlapping<T extends LabelData | HtmlItem>(label1: T, label2: T, padding?: number): boolean;
|
|
6
|
-
export declare function filterOverlappingLabels<T extends LabelData | HtmlItem>(labels: T[]): T[];
|
|
6
|
+
export declare function filterOverlappingLabels<T extends LabelData | HtmlItem>(labels: T[], renderedSvgLabels?: T[]): T[];
|
|
7
7
|
export declare function getSvgLabelConstraintedPosition(args: {
|
|
8
8
|
boundsHeight: number;
|
|
9
9
|
boundsWidth: number;
|
|
@@ -32,11 +32,12 @@ export function getOverlappingByY(rect1, rect2, gap = 0) {
|
|
|
32
32
|
export function isLabelsOverlapping(label1, label2, padding = 0) {
|
|
33
33
|
return Boolean(getOverlappingByX(label1, label2, padding) && getOverlappingByY(label1, label2, padding));
|
|
34
34
|
}
|
|
35
|
-
export function filterOverlappingLabels(labels) {
|
|
35
|
+
export function filterOverlappingLabels(labels, renderedSvgLabels) {
|
|
36
36
|
const result = [];
|
|
37
37
|
const sorted = sortBy(labels, (d) => d.y, (d) => ('textAnchor' in d ? getLeftPosition(d) : d.x));
|
|
38
38
|
sorted.forEach((label) => {
|
|
39
|
-
if (!
|
|
39
|
+
if (!(renderedSvgLabels === null || renderedSvgLabels === void 0 ? void 0 : renderedSvgLabels.some((l) => isLabelsOverlapping(label, l))) &&
|
|
40
|
+
!result.some((l) => isLabelsOverlapping(label, l))) {
|
|
40
41
|
result.push(label);
|
|
41
42
|
}
|
|
42
43
|
});
|
|
@@ -265,6 +265,18 @@ function validateStacking({ series }) {
|
|
|
265
265
|
});
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
|
+
function validateStackingAreaNullMode({ series }) {
|
|
269
|
+
const availableStackingValues = ['normal', 'percent'];
|
|
270
|
+
const invalid = series.find((s) => s.type === 'area' &&
|
|
271
|
+
availableStackingValues.includes(s.stacking) &&
|
|
272
|
+
s.nullMode === 'connect');
|
|
273
|
+
if (invalid) {
|
|
274
|
+
throw new ChartError({
|
|
275
|
+
code: CHART_ERROR_CODE.INVALID_DATA,
|
|
276
|
+
message: i18n('error', 'label_stacking-area-connect-null-mode'),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
268
280
|
function validateTreemapSeries({ series }) {
|
|
269
281
|
const parentIds = {};
|
|
270
282
|
series.data.forEach((d) => {
|
|
@@ -379,6 +391,7 @@ export function validateData(data) {
|
|
|
379
391
|
}
|
|
380
392
|
validateAxes({ xAxis: data.xAxis, yAxis: data.yAxis });
|
|
381
393
|
validateTooltip({ tooltip: data.tooltip });
|
|
394
|
+
validateStackingAreaNullMode({ series: data.series.data });
|
|
382
395
|
if (data.series.data.some((s) => isEmpty(s.data))) {
|
|
383
396
|
throw new ChartError({
|
|
384
397
|
code: CHART_ERROR_CODE.INVALID_DATA,
|
|
@@ -69,13 +69,30 @@ export function getZoomedSeriesData(args) {
|
|
|
69
69
|
prevPointInRange = currentPointInRange;
|
|
70
70
|
if (zoomState.x) {
|
|
71
71
|
const [xMin, xMax] = zoomState.x;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
if ('x0' in point && 'x1' in point) {
|
|
73
|
+
const isStartInRange = isValueInRange({
|
|
74
|
+
axis: xAxis,
|
|
75
|
+
value: point.x0,
|
|
76
|
+
min: xMin,
|
|
77
|
+
max: xMax,
|
|
78
|
+
});
|
|
79
|
+
const isEndInRange = isValueInRange({
|
|
80
|
+
axis: xAxis,
|
|
81
|
+
value: point.x1,
|
|
82
|
+
min: xMin,
|
|
83
|
+
max: xMax,
|
|
84
|
+
});
|
|
85
|
+
inXRange = isStartInRange || isEndInRange;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const x = 'x' in point ? ((_a = point.x) !== null && _a !== void 0 ? _a : undefined) : undefined;
|
|
89
|
+
inXRange = isValueInRange({
|
|
90
|
+
axis: xAxis,
|
|
91
|
+
value: x,
|
|
92
|
+
min: xMin,
|
|
93
|
+
max: xMax,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
79
96
|
}
|
|
80
97
|
if (zoomState.y) {
|
|
81
98
|
const yAxisIndex = 'yAxis' in seriesItem && typeof seriesItem.yAxis === 'number'
|
|
@@ -13,14 +13,20 @@ export const getVisibleSeries = ({ preparedSeries, activeLegendItems, }) => {
|
|
|
13
13
|
export const getPreparedSeries = async ({ seriesData, seriesOptions, colors, preparedLegend, }) => {
|
|
14
14
|
const seriesNames = getSeriesNames(seriesData);
|
|
15
15
|
const colorScale = scaleOrdinal(seriesNames, colors);
|
|
16
|
-
const groupedSeries = group(seriesData, (item) =>
|
|
16
|
+
const groupedSeries = group(seriesData, (item, index) => {
|
|
17
|
+
if (item.type === 'line') {
|
|
18
|
+
return `${item.type}_${index}`;
|
|
19
|
+
}
|
|
20
|
+
return item.type;
|
|
21
|
+
});
|
|
17
22
|
const acc = [];
|
|
18
23
|
if (!preparedLegend) {
|
|
19
24
|
return acc;
|
|
20
25
|
}
|
|
21
26
|
const list = Array.from(groupedSeries);
|
|
22
27
|
for (let i = 0; i < list.length; i++) {
|
|
23
|
-
const [
|
|
28
|
+
const [_groupId, seriesList] = list[i];
|
|
29
|
+
const seriesType = seriesList[0].type;
|
|
24
30
|
acc.push(...(await prepareSeries({
|
|
25
31
|
type: seriesType,
|
|
26
32
|
series: seriesList,
|
|
@@ -60,7 +60,7 @@ export const AreaSeriesShapes = (args) => {
|
|
|
60
60
|
.attr('fill', (d) => d.color)
|
|
61
61
|
.attr('opacity', (d) => d.opacity);
|
|
62
62
|
let dataLabels = preparedData.reduce((acc, d) => {
|
|
63
|
-
return acc.concat(d.
|
|
63
|
+
return acc.concat(d.svgLabels);
|
|
64
64
|
}, []);
|
|
65
65
|
if (!allowOverlapDataLabels) {
|
|
66
66
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
@@ -191,7 +191,7 @@ export const AreaSeriesShapes = (args) => {
|
|
|
191
191
|
};
|
|
192
192
|
}, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
|
|
193
193
|
const htmlLayerData = React.useMemo(() => {
|
|
194
|
-
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.
|
|
194
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlLabels).flat();
|
|
195
195
|
if (allowOverlapDataLabels) {
|
|
196
196
|
return { htmlElements: items };
|
|
197
197
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { group, min } from 'd3-array';
|
|
1
|
+
import { group, min, sort } from 'd3-array';
|
|
2
2
|
import isNil from 'lodash/isNil';
|
|
3
3
|
import round from 'lodash/round';
|
|
4
4
|
import { getDataCategoryValue, getLabelsSize, getTextSizeFn } from '../../../core/utils';
|
|
@@ -27,7 +27,7 @@ function getXValues(series, xAxis, xScale) {
|
|
|
27
27
|
return acc;
|
|
28
28
|
}, []);
|
|
29
29
|
}
|
|
30
|
-
return Array.from(xValues);
|
|
30
|
+
return sort(Array.from(xValues), (d) => d[1]);
|
|
31
31
|
}
|
|
32
32
|
async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBounds, }) {
|
|
33
33
|
var _a;
|
|
@@ -166,13 +166,14 @@ export const prepareAreaData = async (args) => {
|
|
|
166
166
|
return m.set(key, d);
|
|
167
167
|
}, new Map());
|
|
168
168
|
const points = xValues.reduce((pointsAcc, [x, xValue], index) => {
|
|
169
|
-
var _a, _b, _c, _d, _e, _f, _g
|
|
170
|
-
const
|
|
169
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
170
|
+
const rawData = seriesData.get(x);
|
|
171
|
+
const d = rawData !== null && rawData !== void 0 ? rawData : {
|
|
171
172
|
x,
|
|
172
173
|
y: 0,
|
|
173
174
|
};
|
|
174
|
-
let yDataValue = (
|
|
175
|
-
if (s.nullMode === 'connect' && yDataValue === null) {
|
|
175
|
+
let yDataValue = (_a = d.y) !== null && _a !== void 0 ? _a : null;
|
|
176
|
+
if (s.nullMode === 'connect' && (yDataValue === null || !rawData)) {
|
|
176
177
|
return pointsAcc;
|
|
177
178
|
}
|
|
178
179
|
if (yDataValue && isPercentStacking) {
|
|
@@ -187,13 +188,13 @@ export const prepareAreaData = async (args) => {
|
|
|
187
188
|
});
|
|
188
189
|
if (typeof yDataValue === 'number' && yValue !== null) {
|
|
189
190
|
yValue = round(yValue, 2);
|
|
190
|
-
const prevPoint = seriesData.get((
|
|
191
|
-
const nextPoint = seriesData.get((
|
|
191
|
+
const prevPoint = seriesData.get((_b = xValues[index - 1]) === null || _b === void 0 ? void 0 : _b[0]);
|
|
192
|
+
const nextPoint = seriesData.get((_c = xValues[index + 1]) === null || _c === void 0 ? void 0 : _c[0]);
|
|
192
193
|
const currentPointStackHeight = Math.abs(yMin - yValue);
|
|
193
194
|
if (yDataValue >= 0) {
|
|
194
195
|
const positiveStackHeights = positiveStackValues.get(x);
|
|
195
|
-
let prevSectionStackHeight = (
|
|
196
|
-
let nextSectionStackHeight = (
|
|
196
|
+
let prevSectionStackHeight = (_d = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.prev) !== null && _d !== void 0 ? _d : 0;
|
|
197
|
+
let nextSectionStackHeight = (_e = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.next) !== null && _e !== void 0 ? _e : 0;
|
|
197
198
|
const point = {
|
|
198
199
|
y0: yAxisTop + yMin - prevSectionStackHeight,
|
|
199
200
|
x: xValue,
|
|
@@ -219,11 +220,11 @@ export const prepareAreaData = async (args) => {
|
|
|
219
220
|
point2.y = newYValue;
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
|
-
if ((prevPoint === null || prevPoint === void 0 ? void 0 : prevPoint.y) !== null) {
|
|
223
|
+
if ((prevPoint === null || prevPoint === void 0 ? void 0 : prevPoint.y) !== null || s.nullMode === 'zero') {
|
|
223
224
|
prevSectionStackHeight =
|
|
224
225
|
prevSectionStackHeight + currentPointStackHeight;
|
|
225
226
|
}
|
|
226
|
-
if ((nextPoint === null || nextPoint === void 0 ? void 0 : nextPoint.y) !== null) {
|
|
227
|
+
if ((nextPoint === null || nextPoint === void 0 ? void 0 : nextPoint.y) !== null || s.nullMode === 'zero') {
|
|
227
228
|
nextSectionStackHeight =
|
|
228
229
|
nextSectionStackHeight + currentPointStackHeight;
|
|
229
230
|
}
|
|
@@ -234,8 +235,8 @@ export const prepareAreaData = async (args) => {
|
|
|
234
235
|
}
|
|
235
236
|
else {
|
|
236
237
|
const negativeStackHeights = negativeStackValues.get(x);
|
|
237
|
-
let prevSectionStackHeight = (
|
|
238
|
-
let nextSectionStackHeight = (
|
|
238
|
+
let prevSectionStackHeight = (_f = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.prev) !== null && _f !== void 0 ? _f : 0;
|
|
239
|
+
let nextSectionStackHeight = (_g = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.next) !== null && _g !== void 0 ? _g : 0;
|
|
239
240
|
pointsAcc.push({
|
|
240
241
|
y0: yAxisTop + yMin + prevSectionStackHeight,
|
|
241
242
|
x: xValue,
|
|
@@ -300,7 +301,7 @@ export const prepareAreaData = async (args) => {
|
|
|
300
301
|
seriesStackData.push({
|
|
301
302
|
points,
|
|
302
303
|
markers,
|
|
303
|
-
|
|
304
|
+
svgLabels: [],
|
|
304
305
|
color: s.color,
|
|
305
306
|
opacity: s.opacity,
|
|
306
307
|
width: s.lineWidth,
|
|
@@ -308,7 +309,7 @@ export const prepareAreaData = async (args) => {
|
|
|
308
309
|
hovered: false,
|
|
309
310
|
active: true,
|
|
310
311
|
id: s.id,
|
|
311
|
-
|
|
312
|
+
htmlLabels: [],
|
|
312
313
|
});
|
|
313
314
|
}
|
|
314
315
|
for (let itemIndex = 0; itemIndex < seriesStackData.length; itemIndex++) {
|
|
@@ -323,8 +324,8 @@ export const prepareAreaData = async (args) => {
|
|
|
323
324
|
yAxisTop: itemYAxisTop,
|
|
324
325
|
isOutsideBounds,
|
|
325
326
|
});
|
|
326
|
-
item.
|
|
327
|
-
item.
|
|
327
|
+
item.svgLabels.push(...labelsData.svgLabels);
|
|
328
|
+
item.htmlLabels.push(...labelsData.htmlLabels);
|
|
328
329
|
}
|
|
329
330
|
}
|
|
330
331
|
result.push(...seriesStackData);
|
|
@@ -49,7 +49,7 @@ export const BarXSeriesShapes = (args) => {
|
|
|
49
49
|
.attr('fill', (d) => d.data.color || d.series.color)
|
|
50
50
|
.attr('opacity', (d) => d.opacity)
|
|
51
51
|
.attr('cursor', (d) => d.series.cursor);
|
|
52
|
-
let dataLabels = preparedData.map((d) => d.
|
|
52
|
+
let dataLabels = preparedData.map((d) => d.svgLabels).flat();
|
|
53
53
|
if (!allowOverlapDataLabels) {
|
|
54
54
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
55
55
|
}
|
|
@@ -114,7 +114,7 @@ export const BarXSeriesShapes = (args) => {
|
|
|
114
114
|
};
|
|
115
115
|
}, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
|
|
116
116
|
const htmlLayerData = React.useMemo(() => {
|
|
117
|
-
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.
|
|
117
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlLabels).flat();
|
|
118
118
|
if (allowOverlapDataLabels) {
|
|
119
119
|
return { htmlElements: items };
|
|
120
120
|
}
|
|
@@ -110,8 +110,8 @@ export const prepareBarXData = async (args) => {
|
|
|
110
110
|
const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
|
|
111
111
|
for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
|
|
112
112
|
const yValues = stacks[groupItemIndex];
|
|
113
|
-
let
|
|
114
|
-
let
|
|
113
|
+
let positiveStackSum = 0;
|
|
114
|
+
let negativeStackSum = 0;
|
|
115
115
|
const stackItems = [];
|
|
116
116
|
let sortedData = yValues;
|
|
117
117
|
if (sortKey) {
|
|
@@ -144,7 +144,6 @@ export const prepareBarXData = async (args) => {
|
|
|
144
144
|
}
|
|
145
145
|
const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
|
|
146
146
|
const yDataValue = ((_d = yValue.data.y) !== null && _d !== void 0 ? _d : 0);
|
|
147
|
-
const y = seriesYScale(yDataValue);
|
|
148
147
|
let base = 0;
|
|
149
148
|
if (seriesYAxis.type === 'logarithmic') {
|
|
150
149
|
const domainData = seriesYScale.domain();
|
|
@@ -155,7 +154,22 @@ export const prepareBarXData = async (args) => {
|
|
|
155
154
|
base = seriesYScale(0);
|
|
156
155
|
}
|
|
157
156
|
const isLastStackItem = yValueIndex === sortedData.length - 1;
|
|
158
|
-
|
|
157
|
+
let height;
|
|
158
|
+
let barPositionY;
|
|
159
|
+
if (yDataValue > 0) {
|
|
160
|
+
const newSum = positiveStackSum + yDataValue;
|
|
161
|
+
const topPixel = seriesYScale(newSum);
|
|
162
|
+
const bottomPixel = positiveStackSum === 0 ? base : seriesYScale(positiveStackSum);
|
|
163
|
+
height = Math.abs(bottomPixel - topPixel);
|
|
164
|
+
barPositionY = yAxisTop + topPixel;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const newSum = negativeStackSum + yDataValue;
|
|
168
|
+
const bottomPixel = negativeStackSum === 0 ? base : seriesYScale(negativeStackSum);
|
|
169
|
+
const topPixel = seriesYScale(newSum);
|
|
170
|
+
height = Math.abs(bottomPixel - topPixel);
|
|
171
|
+
barPositionY = yAxisTop + bottomPixel;
|
|
172
|
+
}
|
|
159
173
|
let shapeHeight = height - (stackItems.length ? stackGap : 0);
|
|
160
174
|
if (shapeHeight < 0) {
|
|
161
175
|
shapeHeight = height;
|
|
@@ -165,28 +179,28 @@ export const prepareBarXData = async (args) => {
|
|
|
165
179
|
}
|
|
166
180
|
const barData = {
|
|
167
181
|
x,
|
|
168
|
-
y:
|
|
169
|
-
? yAxisTop + y - positiveStackHeight
|
|
170
|
-
: yAxisTop + base + negativeStackHeight,
|
|
182
|
+
y: barPositionY,
|
|
171
183
|
width: rectWidth,
|
|
172
184
|
height: shapeHeight,
|
|
173
185
|
_height: height,
|
|
174
186
|
opacity: get(yValue.data, 'opacity', null),
|
|
175
187
|
data: yValue.data,
|
|
176
188
|
series: yValue.series,
|
|
177
|
-
|
|
189
|
+
htmlLabels: [],
|
|
190
|
+
svgLabels: [],
|
|
178
191
|
isLastStackItem,
|
|
179
192
|
};
|
|
180
193
|
stackItems.push(barData);
|
|
181
194
|
if (yDataValue > 0) {
|
|
182
|
-
|
|
195
|
+
positiveStackSum += yDataValue;
|
|
183
196
|
}
|
|
184
197
|
else {
|
|
185
|
-
|
|
198
|
+
negativeStackSum += yDataValue;
|
|
186
199
|
}
|
|
187
200
|
}
|
|
188
201
|
if (series.some((s) => s.stacking === 'percent')) {
|
|
189
202
|
let acc = 0;
|
|
203
|
+
const positiveStackHeight = stackItems.reduce((sum, item) => sum + item._height, 0);
|
|
190
204
|
const ratio = plotHeight / positiveStackHeight;
|
|
191
205
|
stackItems.forEach((item) => {
|
|
192
206
|
item.height = item._height * ratio;
|
|
@@ -211,17 +225,19 @@ export const prepareBarXData = async (args) => {
|
|
|
211
225
|
!isRangeSlider &&
|
|
212
226
|
(!isBarOutsideBounds || isZeroValue)) {
|
|
213
227
|
const label = await getLabelData(barData, xMax);
|
|
214
|
-
if (
|
|
215
|
-
barData.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
if (label) {
|
|
229
|
+
if (barData.series.dataLabels.html) {
|
|
230
|
+
barData.htmlLabels.push({
|
|
231
|
+
x: label.x,
|
|
232
|
+
y: label.y,
|
|
233
|
+
content: label.text,
|
|
234
|
+
size: label.size,
|
|
235
|
+
style: label.style,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
barData.svgLabels.push(label);
|
|
240
|
+
}
|
|
225
241
|
}
|
|
226
242
|
}
|
|
227
243
|
}
|
|
@@ -7,8 +7,8 @@ export type PreparedBarXData = Omit<TooltipDataChunkBarX, 'series'> & {
|
|
|
7
7
|
height: number;
|
|
8
8
|
opacity: number | null;
|
|
9
9
|
series: PreparedBarXSeries;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
svgLabels: LabelData[];
|
|
11
|
+
htmlLabels: HtmlItem[];
|
|
12
12
|
isLastStackItem: boolean;
|
|
13
13
|
/**
|
|
14
14
|
* the utility field for storing the original height (for recalculations, etc.)
|
|
@@ -19,8 +19,9 @@ import type { PreparedScatterData } from './scatter/types';
|
|
|
19
19
|
export type { PreparedBarXData } from './bar-x';
|
|
20
20
|
export type { PreparedScatterData } from './scatter/types';
|
|
21
21
|
import type { PreparedWaterfallData } from './waterfall';
|
|
22
|
+
import type { PreparedXRangeData } from './x-range';
|
|
22
23
|
import './styles.css';
|
|
23
|
-
export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData | PreparedRadarData | PreparedHeatmapData | PreparedFunnelData;
|
|
24
|
+
export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData | PreparedRadarData | PreparedHeatmapData | PreparedFunnelData | PreparedXRangeData;
|
|
24
25
|
export type ClipPathBySeriesType = Partial<Record<SeriesType, boolean>>;
|
|
25
26
|
type Args = {
|
|
26
27
|
boundsWidth: number;
|
|
@@ -22,6 +22,7 @@ import { TreemapSeriesShape } from './treemap';
|
|
|
22
22
|
import { prepareTreemapData } from './treemap/prepare-data';
|
|
23
23
|
import { getSeriesClipPathId } from './utils';
|
|
24
24
|
import { WaterfallSeriesShapes, prepareWaterfallData } from './waterfall';
|
|
25
|
+
import { XRangeSeriesShapes, prepareXRangeData } from './x-range';
|
|
25
26
|
import './styles.css';
|
|
26
27
|
function IS_OUTSIDE_BOUNDS() {
|
|
27
28
|
return false;
|
|
@@ -33,11 +34,20 @@ function shouldUseClipPathId(seriesType, clipPathBySeriesType) {
|
|
|
33
34
|
export async function getShapes(args) {
|
|
34
35
|
const { boundsWidth, boundsHeight, clipPathId, clipPathBySeriesType, dispatcher, htmlLayout, isOutsideBounds = IS_OUTSIDE_BOUNDS, isRangeSlider, series, seriesOptions, split, xAxis, xScale, yAxis, yScale, zoomState, } = args;
|
|
35
36
|
const visibleSeries = getOnlyVisibleSeries(series);
|
|
36
|
-
const groupedSeries = group(visibleSeries, (item) =>
|
|
37
|
+
const groupedSeries = group(visibleSeries, (item) => {
|
|
38
|
+
if (item.type === 'line') {
|
|
39
|
+
return item.id;
|
|
40
|
+
}
|
|
41
|
+
return item.type;
|
|
42
|
+
});
|
|
37
43
|
const shapesData = [];
|
|
38
44
|
const shapes = [];
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
const layers = [];
|
|
46
|
+
const groupedSeriesItems = Array.from(groupedSeries);
|
|
47
|
+
for (let index = groupedSeriesItems.length - 1; index >= 0; index--) {
|
|
48
|
+
const item = groupedSeriesItems[index];
|
|
49
|
+
const [groupId, chartSeries] = item;
|
|
50
|
+
const seriesType = chartSeries[0].type;
|
|
41
51
|
switch (seriesType) {
|
|
42
52
|
case SERIES_TYPE.BarX: {
|
|
43
53
|
if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
|
|
@@ -54,6 +64,7 @@ export async function getShapes(args) {
|
|
|
54
64
|
});
|
|
55
65
|
shapes[index] = (React.createElement(BarXSeriesShapes, { key: SERIES_TYPE.BarX, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
|
|
56
66
|
shapesData.splice(index, 0, ...preparedData);
|
|
67
|
+
layers.push(...preparedData);
|
|
57
68
|
}
|
|
58
69
|
break;
|
|
59
70
|
}
|
|
@@ -100,14 +111,16 @@ export async function getShapes(args) {
|
|
|
100
111
|
split,
|
|
101
112
|
isOutsideBounds,
|
|
102
113
|
isRangeSlider,
|
|
114
|
+
otherLayers: layers,
|
|
103
115
|
});
|
|
104
116
|
const resultClipPathId = getSeriesClipPathId({
|
|
105
117
|
clipPathId,
|
|
106
118
|
yAxis,
|
|
107
119
|
zoomState,
|
|
108
120
|
});
|
|
109
|
-
shapes[index] = (React.createElement(LineSeriesShapes, { key:
|
|
121
|
+
shapes[index] = (React.createElement(LineSeriesShapes, { key: groupId, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: resultClipPathId }));
|
|
110
122
|
shapesData.splice(index, 0, ...preparedData);
|
|
123
|
+
layers.push(...preparedData);
|
|
111
124
|
}
|
|
112
125
|
break;
|
|
113
126
|
}
|
|
@@ -125,6 +138,7 @@ export async function getShapes(args) {
|
|
|
125
138
|
});
|
|
126
139
|
shapes[index] = (React.createElement(AreaSeriesShapes, { key: SERIES_TYPE.Area, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
|
|
127
140
|
shapesData.splice(index, 0, ...preparedData);
|
|
141
|
+
layers.push(...preparedData);
|
|
128
142
|
}
|
|
129
143
|
break;
|
|
130
144
|
}
|
|
@@ -211,13 +225,29 @@ export async function getShapes(args) {
|
|
|
211
225
|
shapesData.splice(index, 0, preparedData);
|
|
212
226
|
break;
|
|
213
227
|
}
|
|
228
|
+
case SERIES_TYPE.XRange: {
|
|
229
|
+
if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
|
|
230
|
+
const preparedData = await prepareXRangeData({
|
|
231
|
+
series: chartSeries,
|
|
232
|
+
xAxis,
|
|
233
|
+
xScale,
|
|
234
|
+
yAxis,
|
|
235
|
+
yScale,
|
|
236
|
+
boundsWidth,
|
|
237
|
+
isRangeSlider,
|
|
238
|
+
});
|
|
239
|
+
shapes[index] = (React.createElement(XRangeSeriesShapes, { key: SERIES_TYPE.XRange, dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions, htmlLayout: htmlLayout, clipPathId: clipPathId }));
|
|
240
|
+
shapesData.splice(index, 0, ...preparedData);
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
214
244
|
default: {
|
|
215
245
|
throw new ChartError({
|
|
216
246
|
message: `The display method is not defined for a series with type "${seriesType}"`,
|
|
217
247
|
});
|
|
218
248
|
}
|
|
219
249
|
}
|
|
220
|
-
}
|
|
250
|
+
}
|
|
221
251
|
return { shapes, shapesData };
|
|
222
252
|
}
|
|
223
253
|
export const useShapes = (args) => {
|
|
@@ -3,7 +3,7 @@ import { color } from 'd3-color';
|
|
|
3
3
|
import { select } from 'd3-selection';
|
|
4
4
|
import { line as lineGenerator } from 'd3-shape';
|
|
5
5
|
import get from 'lodash/get';
|
|
6
|
-
import {
|
|
6
|
+
import { getLineDashArray } from '../../../core/utils';
|
|
7
7
|
import { block } from '../../../utils';
|
|
8
8
|
import { HtmlLayer } from '../HtmlLayer';
|
|
9
9
|
import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
|
|
@@ -15,9 +15,6 @@ export const LineSeriesShapes = (args) => {
|
|
|
15
15
|
const plotRef = React.useRef(null);
|
|
16
16
|
const markersRef = React.useRef(null);
|
|
17
17
|
const hoverMarkersRef = React.useRef(null);
|
|
18
|
-
const allowOverlapDataLabels = React.useMemo(() => {
|
|
19
|
-
return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
|
|
20
|
-
}, [preparedData]);
|
|
21
18
|
React.useEffect(() => {
|
|
22
19
|
if (!plotRef.current || !markersRef.current) {
|
|
23
20
|
return () => { };
|
|
@@ -46,12 +43,9 @@ export const LineSeriesShapes = (args) => {
|
|
|
46
43
|
.attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.lineWidth))
|
|
47
44
|
.attr('opacity', (d) => d.opacity)
|
|
48
45
|
.attr('cursor', (d) => d.series.cursor);
|
|
49
|
-
|
|
50
|
-
return acc.concat(d.
|
|
46
|
+
const dataLabels = preparedData.reduce((acc, d) => {
|
|
47
|
+
return acc.concat(d.svgLabels);
|
|
51
48
|
}, []);
|
|
52
|
-
if (!allowOverlapDataLabels) {
|
|
53
|
-
dataLabels = filterOverlappingLabels(dataLabels);
|
|
54
|
-
}
|
|
55
49
|
const labelsSelection = plotSvgElement
|
|
56
50
|
.selectAll('text')
|
|
57
51
|
.data(dataLabels)
|
|
@@ -175,14 +169,11 @@ export const LineSeriesShapes = (args) => {
|
|
|
175
169
|
return () => {
|
|
176
170
|
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.line', null);
|
|
177
171
|
};
|
|
178
|
-
}, [
|
|
172
|
+
}, [dispatcher, preparedData, seriesOptions]);
|
|
179
173
|
const htmlLayerData = React.useMemo(() => {
|
|
180
|
-
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
return { htmlElements: filterOverlappingLabels(items) };
|
|
185
|
-
}, [allowOverlapDataLabels, preparedData]);
|
|
174
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlLabels).flat();
|
|
175
|
+
return { htmlElements: items };
|
|
176
|
+
}, [preparedData]);
|
|
186
177
|
return (React.createElement(React.Fragment, null,
|
|
187
178
|
React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
188
179
|
React.createElement("g", { ref: markersRef }),
|