@gravity-ui/chartkit 4.0.0 → 4.2.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/README.md +2 -1
- package/build/plugins/d3/renderer/D3Widget.js +14 -5
- package/build/plugins/d3/renderer/components/Chart.js +1 -7
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.d.ts +1 -1
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +28 -6
- package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +18 -5
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.d.ts +1 -5
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.js +3 -5
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.d.ts +3 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.js +47 -10
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.js +2 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +19 -15
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x.d.ts +4 -4
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x.js +126 -111
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +11 -15
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.d.ts +9 -9
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +66 -39
- package/build/plugins/d3/renderer/utils/index.d.ts +7 -1
- package/build/plugins/d3/renderer/utils/index.js +28 -1
- package/build/types/widget-data/bar-x.d.ts +19 -5
- package/build/types/widget-data/base.d.ts +2 -0
- package/build/types/widget-data/pie.d.ts +0 -2
- package/build/types/widget-data/scatter.d.ts +19 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,7 +19,8 @@ import '@gravity-ui/uikit/styles/styles.scss';
|
|
|
19
19
|
```typescript
|
|
20
20
|
import {ThemeProvider} from '@gravity-ui/uikit';
|
|
21
21
|
import ChartKit, {settings} from '@gravity-ui/chartkit';
|
|
22
|
-
import {YagrPlugin
|
|
22
|
+
import {YagrPlugin} from '@gravity-ui/chartkit/yagr';
|
|
23
|
+
import type {YagrWidgetData} from '@gravity-ui/chartkit/yagr';
|
|
23
24
|
|
|
24
25
|
import '@gravity-ui/uikit/styles/styles.scss';
|
|
25
26
|
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { select } from 'd3';
|
|
3
3
|
import debounce from 'lodash/debounce';
|
|
4
|
+
import { getRandomCKId } from '../../../utils';
|
|
4
5
|
import { Chart } from './components';
|
|
5
6
|
const D3Widget = React.forwardRef(function D3Widget(props, forwardedRef) {
|
|
6
7
|
const ref = React.useRef(null);
|
|
7
8
|
const debounced = React.useRef();
|
|
8
9
|
const [dimensions, setDimensions] = React.useState();
|
|
9
10
|
const handleResize = React.useCallback(() => {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
var _a;
|
|
12
|
+
const parentElement = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.parentElement;
|
|
13
|
+
if (parentElement) {
|
|
14
|
+
const { top, left, width, height } = parentElement.getBoundingClientRect();
|
|
12
15
|
setDimensions({ top, left, width, height });
|
|
13
16
|
}
|
|
14
17
|
}, []);
|
|
@@ -25,16 +28,22 @@ const D3Widget = React.forwardRef(function D3Widget(props, forwardedRef) {
|
|
|
25
28
|
}), [handleResize]);
|
|
26
29
|
React.useEffect(() => {
|
|
27
30
|
const selection = select(window);
|
|
28
|
-
selection.
|
|
31
|
+
// https://github.com/d3/d3-selection/blob/main/README.md#handling-events
|
|
32
|
+
const eventName = `resize.${getRandomCKId()}`;
|
|
33
|
+
selection.on(eventName, debuncedHandleResize);
|
|
29
34
|
return () => {
|
|
30
35
|
// https://d3js.org/d3-selection/events#selection_on
|
|
31
|
-
selection.on(
|
|
36
|
+
selection.on(eventName, null);
|
|
32
37
|
};
|
|
33
38
|
}, [debuncedHandleResize]);
|
|
34
39
|
React.useEffect(() => {
|
|
35
40
|
// dimensions initialize
|
|
36
41
|
handleResize();
|
|
37
42
|
}, [handleResize]);
|
|
38
|
-
return (React.createElement("div", { ref: ref, style: {
|
|
43
|
+
return (React.createElement("div", { ref: ref, style: {
|
|
44
|
+
width: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) || '100%',
|
|
45
|
+
height: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) || '100%',
|
|
46
|
+
position: 'relative',
|
|
47
|
+
} }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (React.createElement(Chart, { top: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.top) || 0, left: dimensions.left || 0, width: dimensions.width, height: dimensions.height, data: props.data }))));
|
|
39
48
|
});
|
|
40
49
|
export default D3Widget;
|
|
@@ -18,9 +18,6 @@ export const Chart = (props) => {
|
|
|
18
18
|
width,
|
|
19
19
|
height,
|
|
20
20
|
margin: chart.margin,
|
|
21
|
-
legend,
|
|
22
|
-
title,
|
|
23
|
-
xAxis,
|
|
24
21
|
yAxis,
|
|
25
22
|
});
|
|
26
23
|
const { preparedSeries, handleLegendItemClick } = useSeries({ series: data.series, legend });
|
|
@@ -51,10 +48,7 @@ export const Chart = (props) => {
|
|
|
51
48
|
return (React.createElement(React.Fragment, null,
|
|
52
49
|
React.createElement("svg", { ref: svgRef, className: b({ hovered: chartHovered }), width: width, height: height, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
|
|
53
50
|
title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
|
|
54
|
-
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[
|
|
55
|
-
chart.margin.left,
|
|
56
|
-
chart.margin.top + ((title === null || title === void 0 ? void 0 : title.height) || 0),
|
|
57
|
-
].join(',')})` },
|
|
51
|
+
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[chart.margin.left, chart.margin.top].join(',')})` },
|
|
58
52
|
xScale && yScale && (React.createElement(React.Fragment, null,
|
|
59
53
|
React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
|
|
60
54
|
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import { dateTime } from '@gravity-ui/date-utils';
|
|
4
|
+
import { formatNumber } from '../../../../shared';
|
|
5
|
+
import { getDataCategoryValue } from '../../utils';
|
|
6
|
+
const DEFAULT_DATE_FORMAT = 'DD.MM.YY';
|
|
7
|
+
const getRowData = (fieldName, axis, data) => {
|
|
8
|
+
const categories = get(axis, 'categories', []);
|
|
9
|
+
switch (axis.type) {
|
|
10
|
+
case 'category': {
|
|
11
|
+
return getDataCategoryValue({ axisDirection: fieldName, categories, data });
|
|
12
|
+
}
|
|
13
|
+
case 'datetime': {
|
|
14
|
+
const value = get(data, fieldName);
|
|
15
|
+
return dateTime({ input: value }).format(DEFAULT_DATE_FORMAT);
|
|
16
|
+
}
|
|
17
|
+
case 'linear':
|
|
18
|
+
default: {
|
|
19
|
+
const value = get(data, fieldName);
|
|
20
|
+
return formatNumber(value);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const getXRowData = (xAxis, data) => getRowData('x', xAxis, data);
|
|
25
|
+
const getYRowData = (yAxis, data) => getRowData('y', yAxis, data);
|
|
2
26
|
export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
|
|
3
27
|
const { data, series } = hovered;
|
|
4
28
|
switch (series.type) {
|
|
5
29
|
case 'scatter': {
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const yRow = yAxis.type === 'category' ? scatterData.category : scatterData.y;
|
|
30
|
+
const xRow = getXRowData(xAxis, data);
|
|
31
|
+
const yRow = getYRowData(yAxis, data);
|
|
9
32
|
return (React.createElement("div", null,
|
|
10
33
|
React.createElement("div", null,
|
|
11
34
|
React.createElement("span", null, "X:\u00A0"),
|
|
@@ -15,9 +38,8 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
|
|
|
15
38
|
React.createElement("b", null, yRow))));
|
|
16
39
|
}
|
|
17
40
|
case 'bar-x': {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const yRow = yAxis.type === 'category' ? barXData.category : barXData.y;
|
|
41
|
+
const xRow = getXRowData(xAxis, data);
|
|
42
|
+
const yRow = getYRowData(yAxis, data);
|
|
21
43
|
return (React.createElement("div", null,
|
|
22
44
|
React.createElement("div", null, xRow),
|
|
23
45
|
React.createElement("div", null,
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { scaleBand, scaleLinear, scaleUtc, extent } from 'd3';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
|
-
import { getOnlyVisibleSeries,
|
|
4
|
+
import { getOnlyVisibleSeries, getDataCategoryValue, getDomainDataYBySeries, getDomainDataXBySeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
|
|
5
5
|
const isNumericalArrayData = (data) => {
|
|
6
6
|
return data.every((d) => typeof d === 'number' || d === null);
|
|
7
7
|
};
|
|
8
|
-
const filterCategoriesByVisibleSeries = (
|
|
8
|
+
const filterCategoriesByVisibleSeries = (args) => {
|
|
9
|
+
const { axisDirection, categories, series } = args;
|
|
9
10
|
return categories.filter((category) => {
|
|
10
11
|
return series.some((s) => {
|
|
11
|
-
return isSeriesWithCategoryValues(s) &&
|
|
12
|
+
return (isSeriesWithCategoryValues(s) &&
|
|
13
|
+
s.data.some((d) => {
|
|
14
|
+
const dataCategory = getDataCategoryValue({ axisDirection, categories, data: d });
|
|
15
|
+
return dataCategory === category;
|
|
16
|
+
}));
|
|
12
17
|
});
|
|
13
18
|
});
|
|
14
19
|
};
|
|
@@ -41,7 +46,11 @@ const createScales = (args) => {
|
|
|
41
46
|
}
|
|
42
47
|
case 'category': {
|
|
43
48
|
if (xCategories) {
|
|
44
|
-
const filteredCategories = filterCategoriesByVisibleSeries(
|
|
49
|
+
const filteredCategories = filterCategoriesByVisibleSeries({
|
|
50
|
+
axisDirection: 'x',
|
|
51
|
+
categories: xCategories,
|
|
52
|
+
series: visibleSeries,
|
|
53
|
+
});
|
|
45
54
|
xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
|
|
46
55
|
}
|
|
47
56
|
break;
|
|
@@ -78,7 +87,11 @@ const createScales = (args) => {
|
|
|
78
87
|
}
|
|
79
88
|
case 'category': {
|
|
80
89
|
if (yCategories) {
|
|
81
|
-
const filteredCategories = filterCategoriesByVisibleSeries(
|
|
90
|
+
const filteredCategories = filterCategoriesByVisibleSeries({
|
|
91
|
+
axisDirection: 'y',
|
|
92
|
+
categories: yCategories,
|
|
93
|
+
series: visibleSeries,
|
|
94
|
+
});
|
|
82
95
|
yScale = scaleBand().domain(filteredCategories).range([boundsHeight, 0]);
|
|
83
96
|
}
|
|
84
97
|
break;
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import type { ChartMargin } from '../../../../../types/widget-data';
|
|
2
|
-
import type { PreparedAxis
|
|
2
|
+
import type { PreparedAxis } from '../useChartOptions/types';
|
|
3
3
|
type Args = {
|
|
4
4
|
width: number;
|
|
5
5
|
height: number;
|
|
6
6
|
margin: ChartMargin;
|
|
7
|
-
legend: PreparedLegend;
|
|
8
|
-
title?: PreparedTitle;
|
|
9
|
-
xAxis?: PreparedAxis;
|
|
10
7
|
yAxis?: PreparedAxis[];
|
|
11
8
|
};
|
|
12
9
|
export declare const useChartDimensions: (args: Args) => {
|
|
13
10
|
boundsWidth: number;
|
|
14
11
|
boundsHeight: number;
|
|
15
|
-
legendHeight: number;
|
|
16
12
|
};
|
|
17
13
|
export {};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
export const useChartDimensions = (args) => {
|
|
2
|
-
const { margin,
|
|
3
|
-
const titleHeight = (title === null || title === void 0 ? void 0 : title.height) || 0;
|
|
4
|
-
const xAxisTitleHeight = (xAxis === null || xAxis === void 0 ? void 0 : xAxis.title.height) || 0;
|
|
2
|
+
const { margin, width, height, yAxis } = args;
|
|
5
3
|
const yAxisTitleHeight = (yAxis === null || yAxis === void 0 ? void 0 : yAxis.reduce((acc, axis) => {
|
|
6
4
|
return acc + (axis.title.height || 0);
|
|
7
5
|
}, 0)) || 0;
|
|
8
6
|
const boundsWidth = width - margin.right - margin.left - yAxisTitleHeight;
|
|
9
|
-
const boundsHeight = height - margin.top - margin.bottom
|
|
10
|
-
return { boundsWidth, boundsHeight
|
|
7
|
+
const boundsHeight = height - margin.top - margin.bottom;
|
|
8
|
+
return { boundsWidth, boundsHeight };
|
|
11
9
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ChartKitWidgetData } from '../../../../../types/widget-data';
|
|
2
|
-
import type { PreparedAxis, PreparedChart } from './types';
|
|
2
|
+
import type { PreparedAxis, PreparedChart, PreparedTitle, PreparedLegend } from './types';
|
|
3
3
|
export declare const getPreparedChart: (args: {
|
|
4
4
|
chart: ChartKitWidgetData['chart'];
|
|
5
5
|
series: ChartKitWidgetData['series'];
|
|
6
|
+
preparedLegend: PreparedLegend;
|
|
6
7
|
preparedXAxis: PreparedAxis;
|
|
7
8
|
preparedY1Axis: PreparedAxis;
|
|
9
|
+
preparedTitle?: PreparedTitle;
|
|
8
10
|
}) => PreparedChart;
|
|
@@ -42,26 +42,63 @@ const getAxisLabelMaxWidth = (args) => {
|
|
|
42
42
|
.remove();
|
|
43
43
|
return width;
|
|
44
44
|
};
|
|
45
|
-
|
|
46
|
-
const { chart,
|
|
47
|
-
const hasAxisRelatedSeries = series.data.some(isAxisRelatedSeries);
|
|
48
|
-
let marginBottom = get(chart, 'margin.bottom', 0);
|
|
49
|
-
let marginLeft = get(chart, 'margin.left', 0);
|
|
45
|
+
const getMarginTop = (args) => {
|
|
46
|
+
const { chart, hasAxisRelatedSeries, preparedY1Axis, preparedTitle } = args;
|
|
50
47
|
let marginTop = get(chart, 'margin.top', 0);
|
|
51
|
-
|
|
48
|
+
if (hasAxisRelatedSeries) {
|
|
49
|
+
marginTop +=
|
|
50
|
+
getHorisontalSvgTextHeight({ text: 'Tmp', style: preparedY1Axis.labels.style }) / 2;
|
|
51
|
+
}
|
|
52
|
+
if (preparedTitle === null || preparedTitle === void 0 ? void 0 : preparedTitle.height) {
|
|
53
|
+
marginTop += preparedTitle.height;
|
|
54
|
+
}
|
|
55
|
+
return marginTop;
|
|
56
|
+
};
|
|
57
|
+
const getMarginBottom = (args) => {
|
|
58
|
+
const { chart, hasAxisRelatedSeries, preparedLegend, preparedXAxis } = args;
|
|
59
|
+
let marginBottom = get(chart, 'margin.bottom', 0) + preparedLegend.height;
|
|
52
60
|
if (hasAxisRelatedSeries) {
|
|
53
61
|
marginBottom +=
|
|
54
|
-
preparedXAxis.
|
|
62
|
+
preparedXAxis.title.height +
|
|
55
63
|
getHorisontalSvgTextHeight({ text: 'Tmp', style: preparedXAxis.labels.style });
|
|
64
|
+
if (preparedXAxis.labels.enabled) {
|
|
65
|
+
marginBottom += preparedXAxis.labels.padding;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return marginBottom;
|
|
69
|
+
};
|
|
70
|
+
const getMarginLeft = (args) => {
|
|
71
|
+
const { chart, hasAxisRelatedSeries, series, preparedY1Axis } = args;
|
|
72
|
+
let marginLeft = get(chart, 'margin.left', 0);
|
|
73
|
+
if (hasAxisRelatedSeries) {
|
|
56
74
|
marginLeft +=
|
|
57
75
|
AXIS_WIDTH +
|
|
58
76
|
preparedY1Axis.labels.padding +
|
|
59
77
|
getAxisLabelMaxWidth({ axis: preparedY1Axis, series: series.data }) +
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
preparedY1Axis.title.height;
|
|
79
|
+
}
|
|
80
|
+
return marginLeft;
|
|
81
|
+
};
|
|
82
|
+
const getMarginRight = (args) => {
|
|
83
|
+
const { chart, hasAxisRelatedSeries, series, preparedXAxis } = args;
|
|
84
|
+
let marginRight = get(chart, 'margin.right', 0);
|
|
85
|
+
if (hasAxisRelatedSeries) {
|
|
63
86
|
marginRight += getAxisLabelMaxWidth({ axis: preparedXAxis, series: series.data }) / 2;
|
|
64
87
|
}
|
|
88
|
+
return marginRight;
|
|
89
|
+
};
|
|
90
|
+
export const getPreparedChart = (args) => {
|
|
91
|
+
const { chart, series, preparedLegend, preparedXAxis, preparedY1Axis, preparedTitle } = args;
|
|
92
|
+
const hasAxisRelatedSeries = series.data.some(isAxisRelatedSeries);
|
|
93
|
+
const marginTop = getMarginTop({ chart, hasAxisRelatedSeries, preparedY1Axis, preparedTitle });
|
|
94
|
+
const marginBottom = getMarginBottom({
|
|
95
|
+
chart,
|
|
96
|
+
hasAxisRelatedSeries,
|
|
97
|
+
preparedLegend,
|
|
98
|
+
preparedXAxis,
|
|
99
|
+
});
|
|
100
|
+
const marginLeft = getMarginLeft({ chart, hasAxisRelatedSeries, series, preparedY1Axis });
|
|
101
|
+
const marginRight = getMarginRight({ chart, hasAxisRelatedSeries, series, preparedXAxis });
|
|
65
102
|
return {
|
|
66
103
|
margin: {
|
|
67
104
|
top: marginTop,
|
|
@@ -39,30 +39,34 @@ function prepareAxisRelatedSeries(args) {
|
|
|
39
39
|
return [preparedSeries];
|
|
40
40
|
}
|
|
41
41
|
function prepareBarXSeries(args) {
|
|
42
|
-
const { colorScale, series, legend } = args;
|
|
42
|
+
const { colorScale, series: seriesList, legend } = args;
|
|
43
43
|
const commonStackId = getRandomCKId();
|
|
44
|
-
return
|
|
44
|
+
return seriesList.map((series) => {
|
|
45
45
|
var _a, _b, _c, _d;
|
|
46
|
-
const name =
|
|
47
|
-
const color =
|
|
46
|
+
const name = series.name || '';
|
|
47
|
+
const color = series.color || colorScale(name);
|
|
48
|
+
let stackId = series.stackId;
|
|
49
|
+
if (!stackId) {
|
|
50
|
+
stackId = series.stacking === 'normal' ? commonStackId : getRandomCKId();
|
|
51
|
+
}
|
|
48
52
|
return {
|
|
49
|
-
type:
|
|
53
|
+
type: series.type,
|
|
50
54
|
color: color,
|
|
51
55
|
name: name,
|
|
52
|
-
visible: get(
|
|
56
|
+
visible: get(series, 'visible', true),
|
|
53
57
|
legend: {
|
|
54
|
-
enabled: get(
|
|
55
|
-
symbol: prepareLegendSymbol(
|
|
58
|
+
enabled: get(series, 'legend.enabled', legend.enabled),
|
|
59
|
+
symbol: prepareLegendSymbol(series),
|
|
56
60
|
},
|
|
57
|
-
data:
|
|
58
|
-
stacking:
|
|
59
|
-
stackId
|
|
61
|
+
data: series.data,
|
|
62
|
+
stacking: series.stacking,
|
|
63
|
+
stackId,
|
|
60
64
|
dataLabels: {
|
|
61
|
-
enabled: ((_a =
|
|
62
|
-
inside: typeof ((_b =
|
|
63
|
-
? (_c =
|
|
65
|
+
enabled: ((_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) || false,
|
|
66
|
+
inside: typeof ((_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.inside) === 'boolean'
|
|
67
|
+
? (_c = series.dataLabels) === null || _c === void 0 ? void 0 : _c.inside
|
|
64
68
|
: false,
|
|
65
|
-
style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_d =
|
|
69
|
+
style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_d = series.dataLabels) === null || _d === void 0 ? void 0 : _d.style),
|
|
66
70
|
},
|
|
67
71
|
};
|
|
68
72
|
}, []);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
5
|
-
import { PreparedBarXSeries } from '../useSeries/types';
|
|
2
|
+
import type { ChartScale } from '../useAxisScales';
|
|
3
|
+
import type { ChartOptions } from '../useChartOptions/types';
|
|
4
|
+
import type { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
5
|
+
import type { PreparedBarXSeries } from '../useSeries/types';
|
|
6
6
|
type Args = {
|
|
7
7
|
top: number;
|
|
8
8
|
left: number;
|
|
@@ -1,51 +1,93 @@
|
|
|
1
|
+
import { max, pointer, select } from 'd3';
|
|
1
2
|
import React from 'react';
|
|
3
|
+
import get from 'lodash/get';
|
|
2
4
|
import { block } from '../../../../../utils/cn';
|
|
3
|
-
import {
|
|
4
|
-
const
|
|
5
|
-
const DEFAULT_LINEAR_BAR_RECT_WIDTH = 20;
|
|
5
|
+
import { getDataCategoryValue } from '../../utils';
|
|
6
|
+
const RECT_PADDING = 0.1;
|
|
6
7
|
const MIN_RECT_GAP = 1;
|
|
8
|
+
const MAX_RECT_WIDTH = 50;
|
|
9
|
+
const GROUP_PADDING = 0.1;
|
|
10
|
+
const MIN_GROUP_GAP = 1;
|
|
7
11
|
const DEFAULT_LABEL_PADDING = 7;
|
|
8
12
|
const b = block('d3-bar-x');
|
|
9
|
-
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
function prepareData(args) {
|
|
14
|
+
const { series, xAxis, xScale, yScale } = args;
|
|
15
|
+
const categories = get(xAxis, 'categories', []);
|
|
16
|
+
const data = {};
|
|
17
|
+
series.forEach((s) => {
|
|
18
|
+
s.data.forEach((d) => {
|
|
19
|
+
const xValue = xAxis.type === 'category'
|
|
20
|
+
? getDataCategoryValue({ axisDirection: 'x', categories, data: d })
|
|
21
|
+
: d.x;
|
|
22
|
+
if (xValue) {
|
|
23
|
+
if (!data[xValue]) {
|
|
24
|
+
data[xValue] = {};
|
|
25
|
+
}
|
|
26
|
+
const xGroup = data[xValue];
|
|
27
|
+
if (!xGroup[s.stackId]) {
|
|
28
|
+
xGroup[s.stackId] = [];
|
|
29
|
+
}
|
|
30
|
+
xGroup[s.stackId].push({ data: d, series: s });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
let bandWidth = Infinity;
|
|
15
35
|
if (xAxis.type === 'category') {
|
|
16
36
|
const xBandScale = xScale;
|
|
17
|
-
|
|
18
|
-
width = Math.min(maxWidth, DEFAULT_BAR_RECT_WIDTH);
|
|
19
|
-
cx = (xBandScale(point.category) || 0) + xBandScale.step() / 2 - width / 2;
|
|
37
|
+
bandWidth = xBandScale.bandwidth();
|
|
20
38
|
}
|
|
21
39
|
else {
|
|
22
40
|
const xLinearScale = xScale;
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
throw Error(`The "${yAxis[0].type}" type for the Y axis is not supported`);
|
|
36
|
-
}
|
|
37
|
-
return { x: cx, y: cy, width, height };
|
|
38
|
-
};
|
|
39
|
-
function minDiff(arr) {
|
|
40
|
-
let result = Infinity;
|
|
41
|
-
for (let i = 0; i < arr.length - 1; i++) {
|
|
42
|
-
for (let j = i + 1; j < arr.length; j++) {
|
|
43
|
-
const diff = Math.abs(arr[i] - arr[j]);
|
|
44
|
-
if (diff < result) {
|
|
45
|
-
result = diff;
|
|
41
|
+
const xValues = series.reduce((acc, s) => {
|
|
42
|
+
s.data.forEach((dataItem) => acc.push(Number(dataItem.x)));
|
|
43
|
+
return acc;
|
|
44
|
+
}, []);
|
|
45
|
+
xValues.sort().forEach((xValue, index) => {
|
|
46
|
+
if (index > 0 && xValue !== xValues[index - 1]) {
|
|
47
|
+
const dist = xLinearScale(xValue) - xLinearScale(xValues[index - 1]);
|
|
48
|
+
if (dist < bandWidth) {
|
|
49
|
+
bandWidth = dist;
|
|
50
|
+
}
|
|
46
51
|
}
|
|
47
|
-
}
|
|
52
|
+
});
|
|
48
53
|
}
|
|
54
|
+
const maxGroupSize = max(Object.values(data), (d) => Object.values(d).length) || 1;
|
|
55
|
+
const groupGap = Math.max(bandWidth * GROUP_PADDING, MIN_GROUP_GAP);
|
|
56
|
+
const maxGroupWidth = bandWidth - groupGap;
|
|
57
|
+
const rectGap = Math.max((maxGroupWidth / maxGroupSize) * RECT_PADDING, MIN_RECT_GAP);
|
|
58
|
+
const rectWidth = Math.min(maxGroupWidth / maxGroupSize - rectGap, MAX_RECT_WIDTH);
|
|
59
|
+
const result = [];
|
|
60
|
+
Object.entries(data).forEach(([xValue, val]) => {
|
|
61
|
+
const stacks = Object.values(val);
|
|
62
|
+
const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
|
|
63
|
+
stacks.forEach((yValues, groupItemIndex) => {
|
|
64
|
+
let stackHeight = 0;
|
|
65
|
+
yValues.forEach((yValue) => {
|
|
66
|
+
let xCenter;
|
|
67
|
+
if (xAxis.type === 'category') {
|
|
68
|
+
const xBandScale = xScale;
|
|
69
|
+
xCenter = (xBandScale(xValue) || 0) + xBandScale.bandwidth() / 2;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const xLinearScale = xScale;
|
|
73
|
+
xCenter = xLinearScale(Number(xValue));
|
|
74
|
+
}
|
|
75
|
+
const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
|
|
76
|
+
const yLinearScale = yScale;
|
|
77
|
+
const y = yLinearScale(yValue.data.y);
|
|
78
|
+
const height = yLinearScale(yLinearScale.domain()[0]) - y;
|
|
79
|
+
result.push({
|
|
80
|
+
x,
|
|
81
|
+
y: y - stackHeight,
|
|
82
|
+
width: rectWidth,
|
|
83
|
+
height,
|
|
84
|
+
data: yValue.data,
|
|
85
|
+
series: yValue.series,
|
|
86
|
+
});
|
|
87
|
+
stackHeight += height + 1;
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
49
91
|
return result;
|
|
50
92
|
}
|
|
51
93
|
export function BarXSeriesShapes(args) {
|
|
@@ -57,83 +99,56 @@ export function BarXSeriesShapes(args) {
|
|
|
57
99
|
}
|
|
58
100
|
const svgElement = select(ref.current);
|
|
59
101
|
svgElement.selectAll('*').remove();
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return Object.assign(Object.assign({}, rectProps), { y: rectY, data: dataItem });
|
|
86
|
-
});
|
|
87
|
-
svgElement
|
|
88
|
-
.selectAll('allRects')
|
|
89
|
-
.data(shapes)
|
|
90
|
-
.join('rect')
|
|
91
|
-
.attr('class', b('segment'))
|
|
92
|
-
.attr('x', (d) => d.x)
|
|
93
|
-
.attr('y', (d) => d.y)
|
|
94
|
-
.attr('height', (d) => d.height)
|
|
95
|
-
.attr('width', (d) => d.width)
|
|
96
|
-
.attr('fill', item.color)
|
|
97
|
-
.on('mousemove', (e, point) => {
|
|
98
|
-
const [x, y] = pointer(e, svgContainer);
|
|
99
|
-
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
100
|
-
hovered: {
|
|
101
|
-
data: point.data,
|
|
102
|
-
series: item,
|
|
103
|
-
},
|
|
104
|
-
pointerPosition: [x - left, y - top],
|
|
105
|
-
});
|
|
106
|
-
})
|
|
107
|
-
.on('mouseleave', () => {
|
|
108
|
-
if (onSeriesMouseLeave) {
|
|
109
|
-
onSeriesMouseLeave();
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
if (item.dataLabels.enabled) {
|
|
113
|
-
const selection = svgElement
|
|
114
|
-
.selectAll('allLabels')
|
|
115
|
-
.data(shapes)
|
|
116
|
-
.join('text')
|
|
117
|
-
.text((d) => String(d.data.label || d.data.y))
|
|
118
|
-
.attr('class', b('label'))
|
|
119
|
-
.attr('x', (d) => d.x + d.width / 2)
|
|
120
|
-
.attr('y', (d) => {
|
|
121
|
-
if (item.dataLabels.inside) {
|
|
122
|
-
return d.y + d.height / 2;
|
|
123
|
-
}
|
|
124
|
-
return d.y - DEFAULT_LABEL_PADDING;
|
|
125
|
-
})
|
|
126
|
-
.attr('text-anchor', 'middle')
|
|
127
|
-
.style('font-size', item.dataLabels.style.fontSize);
|
|
128
|
-
if (item.dataLabels.style.fontWeight) {
|
|
129
|
-
selection.style('font-weight', item.dataLabels.style.fontWeight);
|
|
130
|
-
}
|
|
131
|
-
if (item.dataLabels.style.fontColor) {
|
|
132
|
-
selection.style('fill', item.dataLabels.style.fontColor);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
102
|
+
const shapes = prepareData({
|
|
103
|
+
series,
|
|
104
|
+
xAxis,
|
|
105
|
+
xScale,
|
|
106
|
+
yAxis,
|
|
107
|
+
yScale,
|
|
108
|
+
});
|
|
109
|
+
svgElement
|
|
110
|
+
.selectAll('allRects')
|
|
111
|
+
.data(shapes)
|
|
112
|
+
.join('rect')
|
|
113
|
+
.attr('class', b('segment'))
|
|
114
|
+
.attr('x', (d) => d.x)
|
|
115
|
+
.attr('y', (d) => d.y)
|
|
116
|
+
.attr('height', (d) => d.height)
|
|
117
|
+
.attr('width', (d) => d.width)
|
|
118
|
+
.attr('fill', (d) => d.data.color || d.series.color)
|
|
119
|
+
.on('mousemove', (e, d) => {
|
|
120
|
+
const [x, y] = pointer(e, svgContainer);
|
|
121
|
+
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
122
|
+
hovered: {
|
|
123
|
+
data: d.data,
|
|
124
|
+
series: d.series,
|
|
125
|
+
},
|
|
126
|
+
pointerPosition: [x - left, y - top],
|
|
135
127
|
});
|
|
128
|
+
})
|
|
129
|
+
.on('mouseleave', () => {
|
|
130
|
+
if (onSeriesMouseLeave) {
|
|
131
|
+
onSeriesMouseLeave();
|
|
132
|
+
}
|
|
136
133
|
});
|
|
134
|
+
const dataLabels = shapes.filter((s) => s.series.dataLabels.enabled);
|
|
135
|
+
svgElement
|
|
136
|
+
.selectAll('allLabels')
|
|
137
|
+
.data(dataLabels)
|
|
138
|
+
.join('text')
|
|
139
|
+
.text((d) => String(d.data.label || d.data.y))
|
|
140
|
+
.attr('class', b('label'))
|
|
141
|
+
.attr('x', (d) => d.x + d.width / 2)
|
|
142
|
+
.attr('y', (d) => {
|
|
143
|
+
if (d.series.dataLabels.inside) {
|
|
144
|
+
return d.y + d.height / 2;
|
|
145
|
+
}
|
|
146
|
+
return d.y - DEFAULT_LABEL_PADDING;
|
|
147
|
+
})
|
|
148
|
+
.attr('text-anchor', 'middle')
|
|
149
|
+
.style('font-size', (d) => d.series.dataLabels.style.fontSize)
|
|
150
|
+
.style('font-weight', (d) => d.series.dataLabels.style.fontWeight || null)
|
|
151
|
+
.style('fill', (d) => d.series.dataLabels.style.fontColor || null);
|
|
137
152
|
}, [
|
|
138
153
|
onSeriesMouseMove,
|
|
139
154
|
onSeriesMouseLeave,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { group } from 'd3';
|
|
3
|
+
import { getRandomCKId } from '../../../../../utils';
|
|
3
4
|
import { getOnlyVisibleSeries } from '../../utils';
|
|
4
5
|
import { BarXSeriesShapes } from './bar-x';
|
|
5
|
-
import {
|
|
6
|
+
import { ScatterSeriesShape } from './scatter';
|
|
6
7
|
import { PieSeriesComponent } from './pie';
|
|
7
8
|
import './styles.css';
|
|
8
9
|
export const useShapes = (args) => {
|
|
@@ -15,29 +16,22 @@ export const useShapes = (args) => {
|
|
|
15
16
|
switch (seriesType) {
|
|
16
17
|
case 'bar-x': {
|
|
17
18
|
if (xScale && yScale) {
|
|
18
|
-
acc.push(React.createElement(BarXSeriesShapes,
|
|
19
|
+
acc.push(React.createElement(BarXSeriesShapes, { key: "bar-x", series: chartSeries, xAxis: xAxis, xScale: xScale, yAxis: yAxis, yScale: yScale, top: top, left: left, svgContainer: svgContainer, onSeriesMouseMove: onSeriesMouseMove, onSeriesMouseLeave: onSeriesMouseLeave }));
|
|
19
20
|
}
|
|
20
21
|
break;
|
|
21
22
|
}
|
|
22
23
|
case 'scatter': {
|
|
23
24
|
if (xScale && yScale) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
left,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
xScale,
|
|
30
|
-
yAxis,
|
|
31
|
-
yScale,
|
|
32
|
-
onSeriesMouseMove,
|
|
33
|
-
onSeriesMouseLeave,
|
|
34
|
-
svgContainer,
|
|
35
|
-
}));
|
|
25
|
+
const scatterShapes = chartSeries.map((scatterSeries, i) => {
|
|
26
|
+
const id = getRandomCKId();
|
|
27
|
+
return (React.createElement(ScatterSeriesShape, { key: `${i}-${id}`, top: top, left: left, series: scatterSeries, xAxis: xAxis, xScale: xScale, yAxis: yAxis, yScale: yScale, onSeriesMouseMove: onSeriesMouseMove, onSeriesMouseLeave: onSeriesMouseLeave, svgContainer: svgContainer }));
|
|
28
|
+
});
|
|
29
|
+
acc.push(...scatterShapes);
|
|
36
30
|
}
|
|
37
31
|
break;
|
|
38
32
|
}
|
|
39
33
|
case 'pie': {
|
|
40
|
-
const groupedPieSeries = group(chartSeries, (
|
|
34
|
+
const groupedPieSeries = group(chartSeries, (pieSeries) => pieSeries.stackId);
|
|
41
35
|
acc.push(...Array.from(groupedPieSeries).map(([key, pieSeries]) => {
|
|
42
36
|
return (React.createElement(PieSeriesComponent, { key: `pie-${key}`, boundsWidth: boundsWidth, boundsHeight: boundsHeight, series: pieSeries, onSeriesMouseMove: onSeriesMouseMove, onSeriesMouseLeave: onSeriesMouseLeave, svgContainer: svgContainer }));
|
|
43
37
|
}));
|
|
@@ -54,6 +48,8 @@ export const useShapes = (args) => {
|
|
|
54
48
|
yAxis,
|
|
55
49
|
yScale,
|
|
56
50
|
svgContainer,
|
|
51
|
+
left,
|
|
52
|
+
top,
|
|
57
53
|
onSeriesMouseMove,
|
|
58
54
|
onSeriesMouseLeave,
|
|
59
55
|
]);
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { ChartScale } from '../useAxisScales';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
type
|
|
2
|
+
import type { ScatterSeries } from '../../../../../types/widget-data';
|
|
3
|
+
import type { ChartScale } from '../useAxisScales';
|
|
4
|
+
import type { PreparedAxis } from '../useChartOptions/types';
|
|
5
|
+
import type { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
6
|
+
type ScatterSeriesShapeProps = {
|
|
7
7
|
top: number;
|
|
8
8
|
left: number;
|
|
9
|
-
series: ScatterSeries
|
|
10
|
-
xAxis:
|
|
9
|
+
series: ScatterSeries;
|
|
10
|
+
xAxis: PreparedAxis;
|
|
11
11
|
xScale: ChartScale;
|
|
12
|
-
yAxis:
|
|
12
|
+
yAxis: PreparedAxis[];
|
|
13
13
|
yScale: ChartScale;
|
|
14
14
|
svgContainer: SVGSVGElement | null;
|
|
15
15
|
onSeriesMouseMove?: OnSeriesMouseMove;
|
|
16
16
|
onSeriesMouseLeave?: OnSeriesMouseLeave;
|
|
17
17
|
};
|
|
18
|
-
export declare function
|
|
18
|
+
export declare function ScatterSeriesShape(props: ScatterSeriesShapeProps): React.JSX.Element;
|
|
19
19
|
export {};
|
|
@@ -1,65 +1,92 @@
|
|
|
1
|
-
import { pointer } from 'd3';
|
|
2
1
|
import React from 'react';
|
|
2
|
+
import { pointer, select } from 'd3';
|
|
3
|
+
import get from 'lodash/get';
|
|
3
4
|
import { block } from '../../../../../utils/cn';
|
|
4
|
-
import {
|
|
5
|
+
import { getDataCategoryValue } from '../../utils';
|
|
5
6
|
const b = block('d3-scatter');
|
|
6
7
|
const DEFAULT_SCATTER_POINT_RADIUS = 4;
|
|
7
|
-
const prepareCategoricalScatterData = (data) => {
|
|
8
|
-
return data.filter((d) => typeof d.category === 'string');
|
|
9
|
-
};
|
|
10
8
|
const prepareLinearScatterData = (data) => {
|
|
11
9
|
return data.filter((d) => typeof d.x === 'number' && typeof d.y === 'number');
|
|
12
10
|
};
|
|
13
|
-
const
|
|
14
|
-
const { point, xAxis, xScale
|
|
15
|
-
const r = point.radius || DEFAULT_SCATTER_POINT_RADIUS;
|
|
11
|
+
const getCxAttr = (args) => {
|
|
12
|
+
const { point, xAxis, xScale } = args;
|
|
16
13
|
let cx;
|
|
17
|
-
let cy;
|
|
18
14
|
if (xAxis.type === 'category') {
|
|
19
15
|
const xBandScale = xScale;
|
|
20
|
-
|
|
16
|
+
const categories = get(xAxis, 'categories', []);
|
|
17
|
+
const dataCategory = getDataCategoryValue({ axisDirection: 'x', categories, data: point });
|
|
18
|
+
cx = (xBandScale(dataCategory) || 0) + xBandScale.step() / 2;
|
|
21
19
|
}
|
|
22
20
|
else {
|
|
23
21
|
const xLinearScale = xScale;
|
|
24
22
|
cx = xLinearScale(point.x);
|
|
25
23
|
}
|
|
26
|
-
|
|
24
|
+
return cx;
|
|
25
|
+
};
|
|
26
|
+
const getCyAttr = (args) => {
|
|
27
|
+
const { point, yAxis, yScale } = args;
|
|
28
|
+
let cy;
|
|
29
|
+
if (yAxis.type === 'category') {
|
|
27
30
|
const yBandScale = yScale;
|
|
28
|
-
|
|
31
|
+
const categories = get(yAxis, 'categories', []);
|
|
32
|
+
const dataCategory = getDataCategoryValue({ axisDirection: 'y', categories, data: point });
|
|
33
|
+
cy = (yBandScale(dataCategory) || 0) + yBandScale.step() / 2;
|
|
29
34
|
}
|
|
30
35
|
else {
|
|
31
36
|
const yLinearScale = yScale;
|
|
32
37
|
cy = yLinearScale(point.y);
|
|
33
38
|
}
|
|
34
|
-
return
|
|
39
|
+
return cy;
|
|
35
40
|
};
|
|
36
|
-
export function
|
|
37
|
-
const {
|
|
38
|
-
|
|
41
|
+
export function ScatterSeriesShape(props) {
|
|
42
|
+
const { series, xAxis, xScale, yAxis, yScale, svgContainer, left, top, onSeriesMouseMove, onSeriesMouseLeave, } = props;
|
|
43
|
+
const ref = React.useRef(null);
|
|
44
|
+
React.useEffect(() => {
|
|
39
45
|
var _a;
|
|
40
|
-
|
|
46
|
+
if (!ref.current) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const svgElement = select(ref.current);
|
|
50
|
+
svgElement.selectAll('*').remove();
|
|
41
51
|
const preparedData = xAxis.type === 'category' || ((_a = yAxis[0]) === null || _a === void 0 ? void 0 : _a.type) === 'category'
|
|
42
|
-
?
|
|
43
|
-
: prepareLinearScatterData(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
? series.data
|
|
53
|
+
: prepareLinearScatterData(series.data);
|
|
54
|
+
svgElement
|
|
55
|
+
.selectAll('allPoints')
|
|
56
|
+
.data(preparedData)
|
|
57
|
+
.enter()
|
|
58
|
+
.append('circle')
|
|
59
|
+
.attr('class', b('point'))
|
|
60
|
+
.attr('fill', (d) => d.color || series.color || '')
|
|
61
|
+
.attr('r', (d) => d.radius || DEFAULT_SCATTER_POINT_RADIUS)
|
|
62
|
+
.attr('cx', (d) => getCxAttr({ point: d, xAxis, xScale }))
|
|
63
|
+
.attr('cy', (d) => getCyAttr({ point: d, yAxis: yAxis[0], yScale }))
|
|
64
|
+
.on('mousemove', (e, d) => {
|
|
65
|
+
const [x, y] = pointer(e, svgContainer);
|
|
66
|
+
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
67
|
+
hovered: {
|
|
68
|
+
data: d,
|
|
69
|
+
series,
|
|
70
|
+
},
|
|
71
|
+
pointerPosition: [x - left, y - top],
|
|
51
72
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
})
|
|
74
|
+
.on('mouseleave', () => {
|
|
75
|
+
if (onSeriesMouseLeave) {
|
|
76
|
+
onSeriesMouseLeave();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}, [
|
|
80
|
+
series,
|
|
81
|
+
xAxis,
|
|
82
|
+
xScale,
|
|
83
|
+
yAxis,
|
|
84
|
+
yScale,
|
|
85
|
+
svgContainer,
|
|
86
|
+
left,
|
|
87
|
+
top,
|
|
88
|
+
onSeriesMouseMove,
|
|
89
|
+
onSeriesMouseLeave,
|
|
90
|
+
]);
|
|
91
|
+
return React.createElement("g", { ref: ref, className: b() });
|
|
65
92
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AxisDomain } from 'd3';
|
|
2
|
-
import type { BaseTextStyle, ChartKitWidgetSeries, ChartKitWidgetAxisType, ChartKitWidgetAxisLabels } from '../../../../types/widget-data';
|
|
2
|
+
import type { BaseTextStyle, ChartKitWidgetSeries, ChartKitWidgetSeriesData, ChartKitWidgetAxisType, ChartKitWidgetAxisLabels } from '../../../../types/widget-data';
|
|
3
3
|
export * from './math';
|
|
4
|
+
export type AxisDirection = 'x' | 'y';
|
|
4
5
|
type UnknownSeries = {
|
|
5
6
|
type: ChartKitWidgetSeries['type'];
|
|
6
7
|
data: unknown;
|
|
@@ -58,3 +59,8 @@ export declare const getHorisontalSvgTextHeight: (args: {
|
|
|
58
59
|
text: string;
|
|
59
60
|
style?: Partial<BaseTextStyle>;
|
|
60
61
|
}) => number;
|
|
62
|
+
export declare const getDataCategoryValue: (args: {
|
|
63
|
+
axisDirection: AxisDirection;
|
|
64
|
+
categories: string[];
|
|
65
|
+
data: ChartKitWidgetSeriesData;
|
|
66
|
+
}) => string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { group, select } from 'd3';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
|
+
import isNil from 'lodash/isNil';
|
|
3
4
|
import { dateTime } from '@gravity-ui/date-utils';
|
|
4
5
|
import { formatNumber } from '../../../shared';
|
|
5
6
|
import { DEFAULT_AXIS_LABEL_FONT_SIZE } from '../constants';
|
|
@@ -46,7 +47,7 @@ export const getDomainDataYBySeries = (series) => {
|
|
|
46
47
|
if (typeof values[key] === 'undefined') {
|
|
47
48
|
values[key] = 0;
|
|
48
49
|
}
|
|
49
|
-
if (point.y) {
|
|
50
|
+
if (point.y && typeof point.y === 'number') {
|
|
50
51
|
values[key] += point.y;
|
|
51
52
|
}
|
|
52
53
|
});
|
|
@@ -128,3 +129,29 @@ export const getHorisontalSvgTextHeight = (args) => {
|
|
|
128
129
|
.remove();
|
|
129
130
|
return height;
|
|
130
131
|
};
|
|
132
|
+
const extractCategoryValue = (args) => {
|
|
133
|
+
const { axisDirection, categories, data } = args;
|
|
134
|
+
const dataCategory = get(data, axisDirection);
|
|
135
|
+
let categoryValue;
|
|
136
|
+
if ('category' in data && data.category) {
|
|
137
|
+
categoryValue = data.category;
|
|
138
|
+
}
|
|
139
|
+
if (typeof dataCategory === 'string') {
|
|
140
|
+
categoryValue = dataCategory;
|
|
141
|
+
}
|
|
142
|
+
if (typeof dataCategory === 'number') {
|
|
143
|
+
categoryValue = categories[dataCategory];
|
|
144
|
+
}
|
|
145
|
+
if (isNil(categoryValue)) {
|
|
146
|
+
throw new Error('It seems you are trying to get non-existing category value');
|
|
147
|
+
}
|
|
148
|
+
return categoryValue;
|
|
149
|
+
};
|
|
150
|
+
export const getDataCategoryValue = (args) => {
|
|
151
|
+
const { axisDirection, categories, data } = args;
|
|
152
|
+
const categoryValue = extractCategoryValue({ axisDirection, categories, data });
|
|
153
|
+
if (!categories.includes(categoryValue)) {
|
|
154
|
+
throw new Error('It seems you are trying to use category value that is not in categories array');
|
|
155
|
+
}
|
|
156
|
+
return categoryValue;
|
|
157
|
+
};
|
|
@@ -2,11 +2,25 @@ import type { BaseSeries, BaseSeriesData } from './base';
|
|
|
2
2
|
import type { ChartKitWidgetSeriesOptions } from './series';
|
|
3
3
|
import { ChartKitWidgetLegend, RectLegendSymbolOptions } from './legend';
|
|
4
4
|
export type BarXSeriesData<T = any> = BaseSeriesData<T> & {
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The `x` value of the bar. Depending on the context , it may represents:
|
|
7
|
+
* - numeric value (for `linear` x axis)
|
|
8
|
+
* - timestamp value (for `datetime` x axis)
|
|
9
|
+
* - x axis category value (for `category` x axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `xAxis.categories`
|
|
10
|
+
*/
|
|
11
|
+
x?: string | number;
|
|
12
|
+
/**
|
|
13
|
+
* The `y` value of the bar. Depending on the context , it may represents:
|
|
14
|
+
* - numeric value (for `linear` y axis)
|
|
15
|
+
* - timestamp value (for `datetime` y axis)
|
|
16
|
+
* - y axis category value (for `category` y axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `yAxis[0].categories`
|
|
17
|
+
*/
|
|
18
|
+
y?: string | number;
|
|
19
|
+
/**
|
|
20
|
+
* Corresponding value of axis category.
|
|
21
|
+
*
|
|
22
|
+
* @deprecated use `x` or `y` instead
|
|
23
|
+
*/
|
|
10
24
|
category?: string;
|
|
11
25
|
/** Data label value of the bar-x column. If not specified, the y value is used. */
|
|
12
26
|
label?: string | number;
|
|
@@ -22,6 +22,8 @@ export type BaseSeriesData<T = any> = {
|
|
|
22
22
|
* Here you can add additional data for your own event callbacks and formatter callbacks
|
|
23
23
|
*/
|
|
24
24
|
custom?: T;
|
|
25
|
+
/** Individual color for the data chunk (point in scatter, segment in pie, bar etc) */
|
|
26
|
+
color?: string;
|
|
25
27
|
};
|
|
26
28
|
export type BaseTextStyle = {
|
|
27
29
|
fontSize: string;
|
|
@@ -5,8 +5,6 @@ export type PieSeriesData<T = any> = BaseSeriesData<T> & {
|
|
|
5
5
|
value: number;
|
|
6
6
|
/** The name of the pie segment (used in legend, tooltip etc). */
|
|
7
7
|
name: string;
|
|
8
|
-
/** Individual color for the pie segment. */
|
|
9
|
-
color?: string;
|
|
10
8
|
/** Initial visibility of the pie segment. */
|
|
11
9
|
visible?: boolean;
|
|
12
10
|
/** Initial data label of the pie segment. If not specified, the value is used. */
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import type { BaseSeries, BaseSeriesData } from './base';
|
|
2
2
|
import type { ChartKitWidgetLegend, RectLegendSymbolOptions } from './legend';
|
|
3
3
|
export type ScatterSeriesData<T = any> = BaseSeriesData<T> & {
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The `x` value of the point. Depending on the context , it may represents:
|
|
6
|
+
* - numeric value (for `linear` x axis)
|
|
7
|
+
* - timestamp value (for `datetime` x axis)
|
|
8
|
+
* - x axis category value (for `category` x axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `xAxis.categories`
|
|
9
|
+
*/
|
|
10
|
+
x?: string | number;
|
|
11
|
+
/**
|
|
12
|
+
* The `y` value of the point. Depending on the context , it may represents:
|
|
13
|
+
* - numeric value (for `linear` y axis)
|
|
14
|
+
* - timestamp value (for `datetime` y axis)
|
|
15
|
+
* - y axis category value (for `category` y axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `yAxis[0].categories`
|
|
16
|
+
*/
|
|
17
|
+
y?: string | number;
|
|
18
|
+
/**
|
|
19
|
+
* Corresponding value of axis category.
|
|
20
|
+
*
|
|
21
|
+
* @deprecated use `x` or `y` instead
|
|
22
|
+
*/
|
|
9
23
|
category?: string;
|
|
10
24
|
radius?: number;
|
|
11
25
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravity-ui/chartkit",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "React component used to render charts based on any sources you need",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "git@github.com:gravity-ui/ChartKit.git",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@bem-react/classname": "^1.6.0",
|
|
50
50
|
"@gravity-ui/date-utils": "^1.4.1",
|
|
51
|
-
"@gravity-ui/yagr": "^3.7.
|
|
51
|
+
"@gravity-ui/yagr": "^3.7.13",
|
|
52
52
|
"d3": "^7.8.5",
|
|
53
53
|
"lodash": "^4.17.21",
|
|
54
54
|
"react-split-pane": "^0.1.92"
|