@gravity-ui/charts 1.18.2 → 1.20.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/AxisY/AxisY.js +7 -5
- package/dist/cjs/components/AxisY/prepare-axis-data.js +8 -5
- package/dist/cjs/components/AxisY/types.d.ts +1 -1
- package/dist/cjs/components/AxisY/utils.js +1 -1
- package/dist/cjs/components/ChartInner/index.js +20 -26
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +2 -2
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +57 -31
- package/dist/cjs/components/ChartInner/utils.d.ts +1 -0
- package/dist/cjs/components/ChartInner/utils.js +21 -0
- package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +1 -1
- package/dist/cjs/components/Tooltip/ChartTooltipContent.js +3 -2
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +1 -0
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +2 -1
- package/dist/cjs/constants/chart-types.d.ts +1 -0
- package/dist/cjs/constants/chart-types.js +1 -0
- package/dist/cjs/constants/defaults/series-options.js +8 -0
- package/dist/cjs/hooks/useAxisScales/index.js +47 -8
- package/dist/cjs/hooks/useChartOptions/tooltip.js +1 -1
- package/dist/cjs/hooks/useChartOptions/x-axis.d.ts +1 -1
- package/dist/cjs/hooks/useChartOptions/x-axis.js +15 -4
- package/dist/cjs/hooks/useChartOptions/y-axis.js +15 -7
- package/dist/cjs/hooks/useSeries/prepare-heatmap.d.ts +11 -0
- package/dist/cjs/hooks/useSeries/prepare-heatmap.js +37 -0
- package/dist/cjs/hooks/useSeries/prepareSeries.js +9 -0
- package/dist/cjs/hooks/useSeries/types.d.ts +14 -2
- package/dist/cjs/hooks/useShapes/heatmap/index.d.ts +13 -0
- package/dist/cjs/hooks/useShapes/heatmap/index.js +74 -0
- package/dist/cjs/hooks/useShapes/heatmap/prepare-data.d.ts +13 -0
- package/dist/cjs/hooks/useShapes/heatmap/prepare-data.js +97 -0
- package/dist/cjs/hooks/useShapes/heatmap/types.d.ts +24 -0
- package/dist/cjs/hooks/useShapes/heatmap/types.js +1 -0
- package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
- package/dist/cjs/hooks/useShapes/index.js +15 -0
- package/dist/cjs/hooks/useShapes/styles.css +4 -0
- package/dist/cjs/hooks/useTooltip/index.js +11 -1
- package/dist/cjs/hooks/utils/bar-y.d.ts +0 -5
- package/dist/cjs/hooks/utils/bar-y.js +2 -29
- package/dist/cjs/hooks/utils/get-band-size.d.ts +5 -0
- package/dist/cjs/hooks/utils/get-band-size.js +29 -0
- package/dist/cjs/i18n/keysets/en.json +2 -1
- package/dist/cjs/i18n/keysets/ru.json +2 -1
- package/dist/cjs/types/chart/axis.d.ts +3 -1
- package/dist/cjs/types/chart/heatmap.d.ts +47 -0
- package/dist/cjs/types/chart/heatmap.js +1 -0
- package/dist/cjs/types/chart/series.d.ts +19 -2
- package/dist/cjs/types/chart/tooltip.d.ts +7 -1
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/index.js +1 -0
- package/dist/cjs/utils/chart/color.js +3 -2
- package/dist/cjs/utils/chart/get-closest-data.js +18 -1
- package/dist/cjs/utils/chart/index.js +10 -13
- package/dist/cjs/utils/chart/series/waterfall.d.ts +1 -1
- package/dist/cjs/utils/chart/series/waterfall.js +3 -3
- package/dist/cjs/validation/validate-axes.js +31 -1
- package/dist/esm/components/AxisY/AxisY.js +7 -5
- package/dist/esm/components/AxisY/prepare-axis-data.js +8 -5
- package/dist/esm/components/AxisY/types.d.ts +1 -1
- package/dist/esm/components/AxisY/utils.js +1 -1
- package/dist/esm/components/ChartInner/index.js +20 -26
- package/dist/esm/components/ChartInner/useChartInnerProps.js +57 -31
- package/dist/esm/components/ChartInner/utils.d.ts +1 -0
- package/dist/esm/components/ChartInner/utils.js +21 -0
- package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +1 -1
- package/dist/esm/components/Tooltip/ChartTooltipContent.js +3 -2
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +1 -0
- package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +2 -1
- package/dist/esm/constants/chart-types.d.ts +1 -0
- package/dist/esm/constants/chart-types.js +1 -0
- package/dist/esm/constants/defaults/series-options.js +8 -0
- package/dist/esm/hooks/useAxisScales/index.js +47 -8
- package/dist/esm/hooks/useChartOptions/tooltip.js +1 -1
- package/dist/esm/hooks/useChartOptions/x-axis.d.ts +1 -1
- package/dist/esm/hooks/useChartOptions/x-axis.js +15 -4
- package/dist/esm/hooks/useChartOptions/y-axis.js +15 -7
- package/dist/esm/hooks/useSeries/prepare-heatmap.d.ts +11 -0
- package/dist/esm/hooks/useSeries/prepare-heatmap.js +37 -0
- package/dist/esm/hooks/useSeries/prepareSeries.js +9 -0
- package/dist/esm/hooks/useSeries/types.d.ts +14 -2
- package/dist/esm/hooks/useShapes/heatmap/index.d.ts +13 -0
- package/dist/esm/hooks/useShapes/heatmap/index.js +74 -0
- package/dist/esm/hooks/useShapes/heatmap/prepare-data.d.ts +13 -0
- package/dist/esm/hooks/useShapes/heatmap/prepare-data.js +97 -0
- package/dist/esm/hooks/useShapes/heatmap/types.d.ts +24 -0
- package/dist/esm/hooks/useShapes/heatmap/types.js +1 -0
- package/dist/esm/hooks/useShapes/index.d.ts +2 -1
- package/dist/esm/hooks/useShapes/index.js +15 -0
- package/dist/esm/hooks/useShapes/styles.css +4 -0
- package/dist/esm/hooks/useTooltip/index.js +11 -1
- package/dist/esm/hooks/utils/bar-y.d.ts +0 -5
- package/dist/esm/hooks/utils/bar-y.js +2 -29
- package/dist/esm/hooks/utils/get-band-size.d.ts +5 -0
- package/dist/esm/hooks/utils/get-band-size.js +29 -0
- package/dist/esm/i18n/keysets/en.json +2 -1
- package/dist/esm/i18n/keysets/ru.json +2 -1
- package/dist/esm/types/chart/axis.d.ts +3 -1
- package/dist/esm/types/chart/heatmap.d.ts +47 -0
- package/dist/esm/types/chart/heatmap.js +1 -0
- package/dist/esm/types/chart/series.d.ts +19 -2
- package/dist/esm/types/chart/tooltip.d.ts +7 -1
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/utils/chart/color.js +3 -2
- package/dist/esm/utils/chart/get-closest-data.js +18 -1
- package/dist/esm/utils/chart/index.js +10 -13
- package/dist/esm/utils/chart/series/waterfall.d.ts +1 -1
- package/dist/esm/utils/chart/series/waterfall.js +3 -3
- package/dist/esm/validation/validate-axes.js +31 -1
- package/package.json +1 -1
|
@@ -49,7 +49,7 @@ export function getClosestPoints(args) {
|
|
|
49
49
|
const groups = groupBy(shapesData, getSeriesType);
|
|
50
50
|
// eslint-disable-next-line complexity
|
|
51
51
|
Object.entries(groups).forEach(([seriesType, list]) => {
|
|
52
|
-
var _a, _b, _c;
|
|
52
|
+
var _a, _b, _c, _d;
|
|
53
53
|
switch (seriesType) {
|
|
54
54
|
case 'bar-x': {
|
|
55
55
|
const points = list.map((d) => ({
|
|
@@ -177,6 +177,23 @@ export function getClosestPoints(args) {
|
|
|
177
177
|
}
|
|
178
178
|
break;
|
|
179
179
|
}
|
|
180
|
+
case 'heatmap': {
|
|
181
|
+
const data = list;
|
|
182
|
+
const closestPoint = (_d = data[0]) === null || _d === void 0 ? void 0 : _d.items.find((cell) => {
|
|
183
|
+
return (pointerX >= cell.x &&
|
|
184
|
+
pointerX <= cell.x + cell.width &&
|
|
185
|
+
pointerY >= cell.y &&
|
|
186
|
+
pointerY <= cell.y + cell.height);
|
|
187
|
+
});
|
|
188
|
+
if (closestPoint) {
|
|
189
|
+
result.push({
|
|
190
|
+
data: closestPoint.data,
|
|
191
|
+
series: data[0].series,
|
|
192
|
+
closest: true,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
180
197
|
case 'sankey': {
|
|
181
198
|
const [data] = list;
|
|
182
199
|
const closestLink = data.links.find((d) => {
|
|
@@ -2,7 +2,7 @@ import { group, select } from 'd3';
|
|
|
2
2
|
import get from 'lodash/get';
|
|
3
3
|
import isNil from 'lodash/isNil';
|
|
4
4
|
import sortBy from 'lodash/sortBy';
|
|
5
|
-
import { DEFAULT_AXIS_LABEL_FONT_SIZE } from '../../constants';
|
|
5
|
+
import { DEFAULT_AXIS_LABEL_FONT_SIZE, SeriesType } from '../../constants';
|
|
6
6
|
import { getSeriesStackId } from '../../hooks/useSeries/utils';
|
|
7
7
|
import { getWaterfallPointSubtotal } from './series/waterfall';
|
|
8
8
|
export * from './axis';
|
|
@@ -109,19 +109,16 @@ export function getDefaultMinXAxisValue(series) {
|
|
|
109
109
|
}
|
|
110
110
|
export function getDefaultMinYAxisValue(series) {
|
|
111
111
|
if (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type))) {
|
|
112
|
+
if (series.some((s) => s.type === SeriesType.Waterfall)) {
|
|
113
|
+
const seriesData = series.map((s) => s.data).flat();
|
|
114
|
+
const minSubTotal = seriesData.reduce((res, d) => Math.min(res, getWaterfallPointSubtotal(d, seriesData) || 0), 0);
|
|
115
|
+
return Math.min(0, minSubTotal);
|
|
116
|
+
}
|
|
112
117
|
return series.reduce((minValue, s) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
default: {
|
|
119
|
-
// https://github.com/gravity-ui/charts/issues/160
|
|
120
|
-
// @ts-expect-error
|
|
121
|
-
const minYValue = s.data.reduce((res, d) => Math.min(res, get(d, 'y', 0)), 0);
|
|
122
|
-
return Math.min(minValue, minYValue);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
118
|
+
// https://github.com/gravity-ui/charts/issues/160
|
|
119
|
+
// @ts-expect-error
|
|
120
|
+
const minYValue = s.data.reduce((res, d) => Math.min(res, get(d, 'y', 0)), 0);
|
|
121
|
+
return Math.min(minValue, minYValue);
|
|
125
122
|
}, 0);
|
|
126
123
|
}
|
|
127
124
|
return undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { PreparedWaterfallSeries, PreparedWaterfallSeriesData } from '../../../hooks';
|
|
2
2
|
import type { WaterfallSeriesData } from '../../../types';
|
|
3
3
|
export declare function getWaterfallPointColor(point: WaterfallSeriesData, series: PreparedWaterfallSeries): string;
|
|
4
|
-
export declare function getWaterfallPointSubtotal(point: PreparedWaterfallSeriesData,
|
|
4
|
+
export declare function getWaterfallPointSubtotal(point: PreparedWaterfallSeriesData, data: PreparedWaterfallSeriesData[]): number | null;
|
|
@@ -4,12 +4,12 @@ export function getWaterfallPointColor(point, series) {
|
|
|
4
4
|
}
|
|
5
5
|
return series.color;
|
|
6
6
|
}
|
|
7
|
-
export function getWaterfallPointSubtotal(point,
|
|
8
|
-
const pointIndex =
|
|
7
|
+
export function getWaterfallPointSubtotal(point, data) {
|
|
8
|
+
const pointIndex = data.indexOf(point);
|
|
9
9
|
if (pointIndex === -1) {
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
12
|
-
return
|
|
12
|
+
return data.reduce((sum, d, index) => {
|
|
13
13
|
if (index <= pointIndex) {
|
|
14
14
|
const value = d.total ? 0 : Number(d.y);
|
|
15
15
|
return sum + value;
|
|
@@ -2,6 +2,22 @@ import { AXIS_TYPE } from '../constants';
|
|
|
2
2
|
import { i18n } from '../i18n';
|
|
3
3
|
import { CHART_ERROR_CODE, ChartError } from '../libs';
|
|
4
4
|
const AVAILABLE_AXIS_TYPES = Object.values(AXIS_TYPE);
|
|
5
|
+
function validateDuplicateCategories({ categories, key, axisIndex, }) {
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
categories.forEach((category) => {
|
|
8
|
+
if (seen.has(category)) {
|
|
9
|
+
throw new ChartError({
|
|
10
|
+
code: CHART_ERROR_CODE.INVALID_DATA,
|
|
11
|
+
message: i18n('error', 'label_duplicate-axis-categories', {
|
|
12
|
+
key,
|
|
13
|
+
axisIndex,
|
|
14
|
+
duplicate: category,
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
seen.add(category);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
5
21
|
function validateAxisType({ axis, key }) {
|
|
6
22
|
if (axis.type && !AVAILABLE_AXIS_TYPES.includes(axis.type)) {
|
|
7
23
|
throw new ChartError({
|
|
@@ -38,9 +54,23 @@ export function validateAxes(args) {
|
|
|
38
54
|
if (xAxis) {
|
|
39
55
|
validateAxisType({ axis: xAxis, key: 'x' });
|
|
40
56
|
validateLabelsHtmlOptions({ axis: xAxis });
|
|
57
|
+
if ((xAxis === null || xAxis === void 0 ? void 0 : xAxis.type) === 'category' && xAxis.categories) {
|
|
58
|
+
validateDuplicateCategories({
|
|
59
|
+
categories: xAxis.categories,
|
|
60
|
+
key: 'x',
|
|
61
|
+
axisIndex: 0,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
41
64
|
}
|
|
42
|
-
yAxis.forEach((axis) => {
|
|
65
|
+
yAxis.forEach((axis, axisIndex) => {
|
|
43
66
|
validateAxisType({ axis, key: 'y' });
|
|
67
|
+
if (axis.type === 'category' && axis.categories) {
|
|
68
|
+
validateDuplicateCategories({
|
|
69
|
+
categories: axis.categories,
|
|
70
|
+
key: 'y',
|
|
71
|
+
axisIndex,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
44
74
|
validateLabelsHtmlOptions({ axis });
|
|
45
75
|
});
|
|
46
76
|
}
|
|
@@ -43,11 +43,13 @@ export const AxisY = (props) => {
|
|
|
43
43
|
.attr('y', (d) => d.y)
|
|
44
44
|
.attr('text-anchor', 'start');
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
if (preparedAxisData.domain) {
|
|
47
|
+
svgElement
|
|
48
|
+
.append('path')
|
|
49
|
+
.attr('class', b('domain'))
|
|
50
|
+
.attr('d', lineGenerator([preparedAxisData.domain.start, preparedAxisData.domain.end]))
|
|
51
|
+
.style('stroke', preparedAxisData.domain.lineColor);
|
|
52
|
+
}
|
|
51
53
|
const tickClassName = b('tick');
|
|
52
54
|
const ticks = svgElement
|
|
53
55
|
.selectAll(`.${tickClassName}`)
|
|
@@ -86,11 +86,14 @@ export async function prepareAxisData({ axis, split, scale, top: topOffset, widt
|
|
|
86
86
|
const axisPlotTopPosition = ((_a = split.plots[axis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
|
|
87
87
|
const axisHeight = ((_b = split.plots[axis.plotIndex]) === null || _b === void 0 ? void 0 : _b.height) || height;
|
|
88
88
|
const domainX = axis.position === 'left' ? 0 : width;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
let domain = null;
|
|
90
|
+
if (axis.visible) {
|
|
91
|
+
domain = {
|
|
92
|
+
start: [domainX, axisPlotTopPosition],
|
|
93
|
+
end: [domainX, axisPlotTopPosition + axisHeight],
|
|
94
|
+
lineColor: (_c = axis.lineColor) !== null && _c !== void 0 ? _c : '',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
94
97
|
const ticks = [];
|
|
95
98
|
const getTextSize = getTextSizeFn({ style: axis.labels.style });
|
|
96
99
|
const labelLineHeight = (await getTextSize('Tmp')).height;
|
|
@@ -74,7 +74,7 @@ export type AxisDomainData = {
|
|
|
74
74
|
export type AxisYData = {
|
|
75
75
|
id: string;
|
|
76
76
|
title: AxisTitleData | null;
|
|
77
|
-
domain: AxisDomainData;
|
|
77
|
+
domain: AxisDomainData | null;
|
|
78
78
|
ticks: AxisTickData[];
|
|
79
79
|
plotLines: AxisPlotLineData[];
|
|
80
80
|
plotBands: AxisPlotBandData[];
|
|
@@ -15,8 +15,8 @@ export function getTickValues({ scale, axis, labelLineHeight, series, }) {
|
|
|
15
15
|
}
|
|
16
16
|
const getScaleTicks = () => {
|
|
17
17
|
var _a;
|
|
18
|
+
const domainData = getDomainDataYBySeries(series);
|
|
18
19
|
if (series.some((s) => s.type === 'bar-y')) {
|
|
19
|
-
const domainData = getDomainDataYBySeries(series);
|
|
20
20
|
if (domainData.length < 3) {
|
|
21
21
|
return domainData;
|
|
22
22
|
}
|
|
@@ -13,6 +13,7 @@ import { Tooltip } from '../Tooltip';
|
|
|
13
13
|
import { useChartInnerHandlers } from './useChartInnerHandlers';
|
|
14
14
|
import { useChartInnerProps } from './useChartInnerProps';
|
|
15
15
|
import { useChartInnerState } from './useChartInnerState';
|
|
16
|
+
import { useAsyncState } from './utils';
|
|
16
17
|
import './styles.css';
|
|
17
18
|
const b = block('chart');
|
|
18
19
|
export const ChartInner = (props) => {
|
|
@@ -82,34 +83,27 @@ export const ChartInner = (props) => {
|
|
|
82
83
|
unpinTooltip === null || unpinTooltip === void 0 ? void 0 : unpinTooltip();
|
|
83
84
|
}
|
|
84
85
|
}, [prevWidth, width, prevHeight, height, tooltipPinned, unpinTooltip]);
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
height: boundsHeight,
|
|
102
|
-
split: preparedSplit,
|
|
103
|
-
series: preparedSeries,
|
|
104
|
-
});
|
|
105
|
-
items.push(axisData);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (countedRef.current === currentRun) {
|
|
109
|
-
setYAxisDataItems(items);
|
|
86
|
+
const setYAxisDataItems = React.useCallback(async () => {
|
|
87
|
+
const items = [];
|
|
88
|
+
for (let i = 0; i < yAxis.length; i++) {
|
|
89
|
+
const axis = yAxis[i];
|
|
90
|
+
const scale = yScale === null || yScale === void 0 ? void 0 : yScale[i];
|
|
91
|
+
if (scale) {
|
|
92
|
+
const axisData = await prepareAxisData({
|
|
93
|
+
axis,
|
|
94
|
+
scale,
|
|
95
|
+
top: boundsOffsetTop,
|
|
96
|
+
width: boundsWidth,
|
|
97
|
+
height: boundsHeight,
|
|
98
|
+
split: preparedSplit,
|
|
99
|
+
series: preparedSeries.filter((s) => s.visible),
|
|
100
|
+
});
|
|
101
|
+
items.push(axisData);
|
|
110
102
|
}
|
|
111
|
-
}
|
|
103
|
+
}
|
|
104
|
+
return items;
|
|
112
105
|
}, [boundsHeight, boundsOffsetTop, boundsWidth, preparedSeries, preparedSplit, yAxis, yScale]);
|
|
106
|
+
const yAxisDataItems = useAsyncState([], setYAxisDataItems);
|
|
113
107
|
return (React.createElement("div", { className: b() },
|
|
114
108
|
React.createElement("svg", { ref: svgRef, width: width, height: height,
|
|
115
109
|
// We use onPointerMove here because onMouseMove works incorrectly when the zoom setting is enabled:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import isEqual from 'lodash/isEqual';
|
|
2
3
|
import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapeSeries, useShapes, useSplit, } from '../../hooks';
|
|
3
4
|
import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
|
|
4
5
|
import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
|
|
@@ -38,37 +39,6 @@ export function useChartInnerProps(props) {
|
|
|
38
39
|
zoomState,
|
|
39
40
|
});
|
|
40
41
|
}, [data.xAxis, data.yAxis, sortedSeriesData, zoomState]);
|
|
41
|
-
const [xAxis, setXAxis] = React.useState(null);
|
|
42
|
-
React.useEffect(() => {
|
|
43
|
-
setXAxis(null);
|
|
44
|
-
getPreparedXAxis({
|
|
45
|
-
xAxis: data.xAxis,
|
|
46
|
-
width,
|
|
47
|
-
seriesData: zoomedSeriesData,
|
|
48
|
-
seriesOptions: preparedSeriesOptions,
|
|
49
|
-
}).then((val) => setXAxis(val));
|
|
50
|
-
}, [data.xAxis, preparedSeriesOptions, width, zoomedSeriesData]);
|
|
51
|
-
const estimatedBoundsHeight = React.useMemo(() => {
|
|
52
|
-
if (xAxis) {
|
|
53
|
-
return (height -
|
|
54
|
-
xAxis.title.height +
|
|
55
|
-
xAxis.title.margin +
|
|
56
|
-
xAxis.labels.margin +
|
|
57
|
-
parseInt(xAxis.labels.style.fontSize, 10));
|
|
58
|
-
}
|
|
59
|
-
return 0;
|
|
60
|
-
}, [height, xAxis]);
|
|
61
|
-
const [yAxis, setYAxis] = React.useState([]);
|
|
62
|
-
React.useEffect(() => {
|
|
63
|
-
setYAxis([]);
|
|
64
|
-
getPreparedYAxis({
|
|
65
|
-
height,
|
|
66
|
-
boundsHeight: estimatedBoundsHeight,
|
|
67
|
-
width,
|
|
68
|
-
seriesData: zoomedSeriesData,
|
|
69
|
-
yAxis: data.yAxis,
|
|
70
|
-
}).then((val) => setYAxis(val));
|
|
71
|
-
}, [data.yAxis, estimatedBoundsHeight, height, width, zoomedSeriesData]);
|
|
72
42
|
const { preparedSeries, preparedLegend, handleLegendItemClick } = useSeries({
|
|
73
43
|
colors,
|
|
74
44
|
legend: data.legend,
|
|
@@ -76,6 +46,62 @@ export function useChartInnerProps(props) {
|
|
|
76
46
|
seriesData: zoomedSeriesData,
|
|
77
47
|
seriesOptions: data.series.options,
|
|
78
48
|
});
|
|
49
|
+
// preparing the X and Y axes
|
|
50
|
+
const [axesState, setValue] = React.useState({ xAxis: null, yAxis: [] });
|
|
51
|
+
const axesStateRunRef = React.useRef(0);
|
|
52
|
+
const prevAxesStateValue = React.useRef(axesState);
|
|
53
|
+
const axesStateReady = React.useRef(false);
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
axesStateRunRef.current++;
|
|
56
|
+
axesStateReady.current = false;
|
|
57
|
+
(async function () {
|
|
58
|
+
const currentRun = axesStateRunRef.current;
|
|
59
|
+
const seriesData = preparedSeries.filter((s) => s.visible);
|
|
60
|
+
const xAxis = await getPreparedXAxis({
|
|
61
|
+
xAxis: data.xAxis,
|
|
62
|
+
width,
|
|
63
|
+
seriesData,
|
|
64
|
+
seriesOptions: preparedSeriesOptions,
|
|
65
|
+
});
|
|
66
|
+
let estimatedBoundsHeight = height;
|
|
67
|
+
if (xAxis) {
|
|
68
|
+
estimatedBoundsHeight =
|
|
69
|
+
height -
|
|
70
|
+
(xAxis.title.height +
|
|
71
|
+
xAxis.title.margin +
|
|
72
|
+
xAxis.labels.margin +
|
|
73
|
+
xAxis.labels.height +
|
|
74
|
+
(preparedLegend ? preparedLegend.height + preparedLegend.margin : 0) +
|
|
75
|
+
chart.margin.top +
|
|
76
|
+
chart.margin.bottom);
|
|
77
|
+
}
|
|
78
|
+
const yAxis = await getPreparedYAxis({
|
|
79
|
+
height,
|
|
80
|
+
boundsHeight: estimatedBoundsHeight,
|
|
81
|
+
width,
|
|
82
|
+
seriesData,
|
|
83
|
+
yAxis: data.yAxis,
|
|
84
|
+
});
|
|
85
|
+
const newStateValue = { xAxis, yAxis };
|
|
86
|
+
if (axesStateRunRef.current === currentRun) {
|
|
87
|
+
if (!isEqual(prevAxesStateValue.current, newStateValue)) {
|
|
88
|
+
setValue(newStateValue);
|
|
89
|
+
prevAxesStateValue.current = newStateValue;
|
|
90
|
+
}
|
|
91
|
+
axesStateReady.current = true;
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
94
|
+
}, [
|
|
95
|
+
chart.margin,
|
|
96
|
+
data.xAxis,
|
|
97
|
+
data.yAxis,
|
|
98
|
+
height,
|
|
99
|
+
preparedLegend,
|
|
100
|
+
preparedSeries,
|
|
101
|
+
preparedSeriesOptions,
|
|
102
|
+
width,
|
|
103
|
+
]);
|
|
104
|
+
const { xAxis, yAxis } = axesStateReady.current ? axesState : { xAxis: null, yAxis: [] };
|
|
79
105
|
const activeLegendItems = React.useMemo(() => getActiveLegendItems(preparedSeries), [preparedSeries]);
|
|
80
106
|
const { preparedSeries: preparedShapesSeries } = useShapeSeries({
|
|
81
107
|
colors,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { PreparedAxis } from '../../hooks/useChartOptions/types';
|
|
2
2
|
import type { ChartSeries } from '../../types';
|
|
3
3
|
export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: ChartSeries[], yAxes?: PreparedAxis[]): boolean;
|
|
4
|
+
export declare function useAsyncState<T>(value: T, setState: () => Promise<T>): T;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import isEqual from 'lodash/isEqual';
|
|
1
3
|
export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxes = []) {
|
|
2
4
|
const hasDataMap = new Map();
|
|
3
5
|
yAxes.forEach((yAxis) => {
|
|
@@ -26,3 +28,22 @@ export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxes = []) {
|
|
|
26
28
|
});
|
|
27
29
|
return [...hasDataMap.values()].every((hasData) => hasData);
|
|
28
30
|
}
|
|
31
|
+
export function useAsyncState(value, setState) {
|
|
32
|
+
const [stateValue, setValue] = React.useState(value);
|
|
33
|
+
const countedRef = React.useRef(0);
|
|
34
|
+
const prevValue = React.useRef(value);
|
|
35
|
+
const ready = React.useRef(false);
|
|
36
|
+
React.useEffect(() => {
|
|
37
|
+
countedRef.current++;
|
|
38
|
+
(async function () {
|
|
39
|
+
const currentRun = countedRef.current;
|
|
40
|
+
const newValue = await setState();
|
|
41
|
+
ready.current = true;
|
|
42
|
+
if (countedRef.current === currentRun && !isEqual(prevValue.current, newValue)) {
|
|
43
|
+
setValue(newValue);
|
|
44
|
+
prevValue.current = newValue;
|
|
45
|
+
}
|
|
46
|
+
})();
|
|
47
|
+
}, [setState]);
|
|
48
|
+
return stateValue;
|
|
49
|
+
}
|
|
@@ -12,4 +12,4 @@ export interface ChartTooltipContentProps {
|
|
|
12
12
|
yAxis?: ChartYAxis;
|
|
13
13
|
qa?: string;
|
|
14
14
|
}
|
|
15
|
-
export declare const ChartTooltipContent: (props: ChartTooltipContentProps) => React.JSX.Element | null
|
|
15
|
+
export declare const ChartTooltipContent: React.MemoExoticComponent<(props: ChartTooltipContentProps) => React.JSX.Element | null>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import isNil from 'lodash/isNil';
|
|
3
3
|
import { DefaultTooltipContent } from './DefaultTooltipContent';
|
|
4
|
-
export const ChartTooltipContent = (props) => {
|
|
4
|
+
export const ChartTooltipContent = React.memo((props) => {
|
|
5
5
|
const { hovered, xAxis, yAxis, renderer, rowRenderer, valueFormat, headerFormat, totals, pinned, qa, } = 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
10
|
return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, pinned: pinned, rowRenderer: rowRenderer, totals: totals, valueFormat: valueFormat, headerFormat: headerFormat, xAxis: xAxis, yAxis: yAxis, qa: qa })) : (customTooltip);
|
|
11
|
-
};
|
|
11
|
+
});
|
|
12
|
+
ChartTooltipContent.displayName = 'ChartTooltipContent';
|
|
@@ -39,7 +39,7 @@ export function getDefaultValueFormat({ axis, closestPointsRange, }) {
|
|
|
39
39
|
}
|
|
40
40
|
export const getMeasureValue = ({ data, xAxis, yAxis, headerFormat, }) => {
|
|
41
41
|
var _a, _b, _c, _d, _e, _f;
|
|
42
|
-
if (data.every((item) => ['pie', 'treemap', 'waterfall', 'sankey'].includes(item.series.type))) {
|
|
42
|
+
if (data.every((item) => ['pie', 'treemap', 'waterfall', 'sankey', 'heatmap'].includes(item.series.type))) {
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
45
45
|
if (data.some((item) => item.series.type === 'radar')) {
|
|
@@ -78,6 +78,7 @@ export function getHoveredValues(args) {
|
|
|
78
78
|
}
|
|
79
79
|
case 'pie':
|
|
80
80
|
case 'radar':
|
|
81
|
+
case 'heatmap':
|
|
81
82
|
case 'treemap': {
|
|
82
83
|
const seriesData = data;
|
|
83
84
|
return seriesData.value;
|
|
@@ -3,8 +3,8 @@ import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3';
|
|
|
3
3
|
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
|
-
import { getBandSize } from '../utils';
|
|
7
6
|
import { getBarXLayoutForNumericScale, groupBarXDataByXValue } from '../utils/bar-x';
|
|
7
|
+
import { getBandSize } from '../utils/get-band-size';
|
|
8
8
|
const X_AXIS_ZOOM_PADDING = 0.02;
|
|
9
9
|
function validateArrayData(data) {
|
|
10
10
|
let hasNumberAndNullValues;
|
|
@@ -59,6 +59,10 @@ function getYScaleRange(args) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
function isSeriesWithYAxisOffset(series) {
|
|
63
|
+
const types = [SeriesType.BarY, SeriesType.Heatmap];
|
|
64
|
+
return series.some((s) => types.includes(s.type));
|
|
65
|
+
}
|
|
62
66
|
// eslint-disable-next-line complexity
|
|
63
67
|
export function createYScale(args) {
|
|
64
68
|
const { axis, boundsHeight, series } = args;
|
|
@@ -89,9 +93,9 @@ export function createYScale(args) {
|
|
|
89
93
|
const scaleFn = axis.type === 'logarithmic' ? scaleLog : scaleLinear;
|
|
90
94
|
const scale = scaleFn().domain([yMin, yMax]).range(range);
|
|
91
95
|
let offsetMin = 0;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
96
|
+
// We should ignore padding if we are drawing only one point on the plot.
|
|
97
|
+
let offsetMax = yMin === yMax ? 0 : boundsHeight * axis.maxPadding;
|
|
98
|
+
if (isSeriesWithYAxisOffset(series)) {
|
|
95
99
|
if (domain.length > 1) {
|
|
96
100
|
const bandWidth = getBandSize({
|
|
97
101
|
scale: scale,
|
|
@@ -138,8 +142,7 @@ export function createYScale(args) {
|
|
|
138
142
|
const scale = scaleUtc().domain([yMin, yMax]).range(range);
|
|
139
143
|
let offsetMin = 0;
|
|
140
144
|
let offsetMax = boundsHeight * axis.maxPadding;
|
|
141
|
-
|
|
142
|
-
if (barYSeries.length) {
|
|
145
|
+
if (isSeriesWithYAxisOffset(series)) {
|
|
143
146
|
if (Object.keys(domain).length > 1) {
|
|
144
147
|
const bandWidth = getBandSize({
|
|
145
148
|
scale: scale,
|
|
@@ -175,6 +178,10 @@ function calculateXAxisPadding(series) {
|
|
|
175
178
|
});
|
|
176
179
|
return result;
|
|
177
180
|
}
|
|
181
|
+
function isSeriesWithXAxisOffset(series) {
|
|
182
|
+
const types = [SeriesType.Heatmap];
|
|
183
|
+
return series.some((s) => types.includes(s.type));
|
|
184
|
+
}
|
|
178
185
|
function getXScaleRange({ boundsWidth, series, seriesOptions, hasZoomX, axis, maxPadding, }) {
|
|
179
186
|
const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
|
|
180
187
|
const xRange = [0, boundsWidth - maxPadding];
|
|
@@ -252,7 +259,23 @@ export function createXScale(args) {
|
|
|
252
259
|
}
|
|
253
260
|
const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
|
|
254
261
|
const scale = scaleFn().domain([xMin, xMax]).range(range);
|
|
255
|
-
|
|
262
|
+
let offsetMin = 0;
|
|
263
|
+
let offsetMax = 0;
|
|
264
|
+
const hasOffset = isSeriesWithXAxisOffset(series);
|
|
265
|
+
if (hasOffset) {
|
|
266
|
+
if (domainData.length > 1) {
|
|
267
|
+
const bandWidth = getBandSize({
|
|
268
|
+
scale: scale,
|
|
269
|
+
domain: domainData,
|
|
270
|
+
});
|
|
271
|
+
offsetMin += bandWidth / 2;
|
|
272
|
+
offsetMax += bandWidth / 2;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const domainOffsetMin = Math.abs(scale.invert(offsetMin) - scale.invert(0));
|
|
276
|
+
const domainOffsetMax = Math.abs(scale.invert(offsetMax) - scale.invert(0));
|
|
277
|
+
scale.domain([xMin - domainOffsetMin, xMax + domainOffsetMax]);
|
|
278
|
+
if (!hasZoomX && !hasOffset) {
|
|
256
279
|
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
257
280
|
scale.nice(Math.max(10, domainData.length));
|
|
258
281
|
}
|
|
@@ -288,7 +311,23 @@ export function createXScale(args) {
|
|
|
288
311
|
const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
|
|
289
312
|
domain = [xMin, xMax];
|
|
290
313
|
const scale = scaleUtc().domain(domain).range(range);
|
|
291
|
-
|
|
314
|
+
let offsetMin = 0;
|
|
315
|
+
let offsetMax = 0;
|
|
316
|
+
const hasOffset = isSeriesWithXAxisOffset(series);
|
|
317
|
+
if (hasOffset) {
|
|
318
|
+
if (domainData.length > 1) {
|
|
319
|
+
const bandWidth = getBandSize({
|
|
320
|
+
scale: scale,
|
|
321
|
+
domain: domainData,
|
|
322
|
+
});
|
|
323
|
+
offsetMin += bandWidth / 2;
|
|
324
|
+
offsetMax += bandWidth / 2;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const domainOffsetMin = Math.abs(scale.invert(offsetMin).getTime() - scale.invert(0).getTime());
|
|
328
|
+
const domainOffsetMax = Math.abs(scale.invert(offsetMax).getTime() - scale.invert(0).getTime());
|
|
329
|
+
scale.domain([xMin - domainOffsetMin, xMax + domainOffsetMax]);
|
|
330
|
+
if (!hasZoomX && !hasOffset) {
|
|
292
331
|
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
293
332
|
scale.nice(Math.max(10, domainData.length));
|
|
294
333
|
}
|
|
@@ -2,7 +2,7 @@ import get from 'lodash/get';
|
|
|
2
2
|
import { getDefaultValueFormat } from '../../components/Tooltip/DefaultTooltipContent/utils';
|
|
3
3
|
import { getDomainDataXBySeries, getDomainDataYBySeries, getMinSpaceBetween } from '../../utils';
|
|
4
4
|
function getDefaultHeaderFormat({ seriesData, yAxes, xAxis, }) {
|
|
5
|
-
if (seriesData.every((item) => ['pie', 'treemap', 'waterfall', 'sankey', 'radar'].includes(item.type))) {
|
|
5
|
+
if (seriesData.every((item) => ['pie', 'treemap', 'waterfall', 'sankey', 'radar', 'heatmap'].includes(item.type))) {
|
|
6
6
|
return undefined;
|
|
7
7
|
}
|
|
8
8
|
if (seriesData.some((item) => item.type === 'bar-y')) {
|