@gravity-ui/chartkit 4.1.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/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/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/scatter.d.ts +6 -6
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +12 -9
- 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/scatter.d.ts +19 -5
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -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', (d) => d.data.color || 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,15 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { ChartScale } from '../useAxisScales';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
6
|
type ScatterSeriesShapeProps = {
|
|
7
7
|
top: number;
|
|
8
8
|
left: number;
|
|
9
9
|
series: ScatterSeries;
|
|
10
|
-
xAxis:
|
|
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;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { pointer, select } 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';
|
|
5
|
+
import { getDataCategoryValue } from '../../utils';
|
|
4
6
|
const b = block('d3-scatter');
|
|
5
7
|
const DEFAULT_SCATTER_POINT_RADIUS = 4;
|
|
6
|
-
const prepareCategoricalScatterData = (data) => {
|
|
7
|
-
return data.filter((d) => typeof d.category === 'string');
|
|
8
|
-
};
|
|
9
8
|
const prepareLinearScatterData = (data) => {
|
|
10
9
|
return data.filter((d) => typeof d.x === 'number' && typeof d.y === 'number');
|
|
11
10
|
};
|
|
@@ -14,7 +13,9 @@ const getCxAttr = (args) => {
|
|
|
14
13
|
let cx;
|
|
15
14
|
if (xAxis.type === 'category') {
|
|
16
15
|
const xBandScale = xScale;
|
|
17
|
-
|
|
16
|
+
const categories = get(xAxis, 'categories', []);
|
|
17
|
+
const dataCategory = getDataCategoryValue({ axisDirection: 'x', categories, data: point });
|
|
18
|
+
cx = (xBandScale(dataCategory) || 0) + xBandScale.step() / 2;
|
|
18
19
|
}
|
|
19
20
|
else {
|
|
20
21
|
const xLinearScale = xScale;
|
|
@@ -25,9 +26,11 @@ const getCxAttr = (args) => {
|
|
|
25
26
|
const getCyAttr = (args) => {
|
|
26
27
|
const { point, yAxis, yScale } = args;
|
|
27
28
|
let cy;
|
|
28
|
-
if (yAxis
|
|
29
|
+
if (yAxis.type === 'category') {
|
|
29
30
|
const yBandScale = yScale;
|
|
30
|
-
|
|
31
|
+
const categories = get(yAxis, 'categories', []);
|
|
32
|
+
const dataCategory = getDataCategoryValue({ axisDirection: 'y', categories, data: point });
|
|
33
|
+
cy = (yBandScale(dataCategory) || 0) + yBandScale.step() / 2;
|
|
31
34
|
}
|
|
32
35
|
else {
|
|
33
36
|
const yLinearScale = yScale;
|
|
@@ -46,7 +49,7 @@ export function ScatterSeriesShape(props) {
|
|
|
46
49
|
const svgElement = select(ref.current);
|
|
47
50
|
svgElement.selectAll('*').remove();
|
|
48
51
|
const preparedData = xAxis.type === 'category' || ((_a = yAxis[0]) === null || _a === void 0 ? void 0 : _a.type) === 'category'
|
|
49
|
-
?
|
|
52
|
+
? series.data
|
|
50
53
|
: prepareLinearScatterData(series.data);
|
|
51
54
|
svgElement
|
|
52
55
|
.selectAll('allPoints')
|
|
@@ -57,7 +60,7 @@ export function ScatterSeriesShape(props) {
|
|
|
57
60
|
.attr('fill', (d) => d.color || series.color || '')
|
|
58
61
|
.attr('r', (d) => d.radius || DEFAULT_SCATTER_POINT_RADIUS)
|
|
59
62
|
.attr('cx', (d) => getCxAttr({ point: d, xAxis, xScale }))
|
|
60
|
-
.attr('cy', (d) => getCyAttr({ point: d, yAxis, yScale }))
|
|
63
|
+
.attr('cy', (d) => getCyAttr({ point: d, yAxis: yAxis[0], yScale }))
|
|
61
64
|
.on('mousemove', (e, d) => {
|
|
62
65
|
const [x, y] = pointer(e, svgContainer);
|
|
63
66
|
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
@@ -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;
|
|
@@ -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