@gravity-ui/chartkit 3.1.2 → 3.2.0-beta.1
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/__stories__/bar/category.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/bar/category.stories.js +75 -0
- package/build/plugins/d3/__stories__/bar/datetime.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/bar/datetime.stories.js +71 -0
- package/build/plugins/d3/__stories__/bar/linear.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/bar/linear.stories.js +74 -0
- package/build/plugins/d3/__stories__/penguins.json +3098 -0
- package/build/plugins/d3/__stories__/scatter/LinearCategories.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/scatter/LinearCategories.stories.js +103 -0
- package/build/plugins/d3/__stories__/scatter/Timestamp.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/scatter/Timestamp.stories.js +91 -0
- package/build/plugins/d3/index.d.ts +7 -0
- package/build/plugins/d3/index.js +10 -0
- package/build/plugins/d3/renderer/D3Widget.d.ts +15 -0
- package/build/plugins/d3/renderer/D3Widget.js +40 -0
- package/build/plugins/d3/renderer/components/AxisX.d.ts +10 -0
- package/build/plugins/d3/renderer/components/AxisX.js +68 -0
- package/build/plugins/d3/renderer/components/AxisY.d.ts +10 -0
- package/build/plugins/d3/renderer/components/AxisY.js +73 -0
- package/build/plugins/d3/renderer/components/Chart.d.ts +10 -0
- package/build/plugins/d3/renderer/components/Chart.js +64 -0
- package/build/plugins/d3/renderer/components/Legend.d.ts +12 -0
- package/build/plugins/d3/renderer/components/Legend.js +66 -0
- package/build/plugins/d3/renderer/components/Title.d.ts +7 -0
- package/build/plugins/d3/renderer/components/Title.js +8 -0
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.d.ts +10 -0
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +21 -0
- package/build/plugins/d3/renderer/components/Tooltip/index.d.ts +12 -0
- package/build/plugins/d3/renderer/components/Tooltip/index.js +53 -0
- package/build/plugins/d3/renderer/components/index.d.ts +1 -0
- package/build/plugins/d3/renderer/components/index.js +1 -0
- package/build/plugins/d3/renderer/components/styles.css +61 -0
- package/build/plugins/d3/renderer/constants.d.ts +1 -0
- package/build/plugins/d3/renderer/constants.js +22 -0
- package/build/plugins/d3/renderer/hooks/index.d.ts +10 -0
- package/build/plugins/d3/renderer/hooks/index.js +10 -0
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.d.ts +17 -0
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.js +13 -0
- package/build/plugins/d3/renderer/hooks/useChartEvents/index.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartEvents/index.js +15 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.d.ts +8 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.js +60 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/constants.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/constants.js +3 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.js +32 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/legend.d.ts +6 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/legend.js +7 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/title.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/title.js +17 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/tooltip.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/tooltip.js +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +33 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/types.js +1 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/utils.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/utils.js +18 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +29 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +35 -0
- package/build/plugins/d3/renderer/hooks/useLegend/index.d.ts +13 -0
- package/build/plugins/d3/renderer/hooks/useLegend/index.js +28 -0
- package/build/plugins/d3/renderer/hooks/useScales/index.d.ts +17 -0
- package/build/plugins/d3/renderer/hooks/useScales/index.js +106 -0
- package/build/plugins/d3/renderer/hooks/useSeries/index.d.ts +14 -0
- package/build/plugins/d3/renderer/hooks/useSeries/index.js +23 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar.d.ts +16 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar.js +75 -0
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +19 -0
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +44 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.d.ts +18 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +62 -0
- package/build/plugins/d3/renderer/hooks/useTooltip/index.d.ts +13 -0
- package/build/plugins/d3/renderer/hooks/useTooltip/index.js +19 -0
- package/build/plugins/d3/renderer/hooks/useTooltip/types.d.ts +7 -0
- package/build/plugins/d3/renderer/hooks/useTooltip/types.js +1 -0
- package/build/plugins/d3/renderer/utils/index.d.ts +18 -0
- package/build/plugins/d3/renderer/utils/index.js +71 -0
- package/build/plugins/d3/types.d.ts +4 -0
- package/build/plugins/d3/types.js +1 -0
- package/build/types/widget-data/axis.d.ts +24 -0
- package/build/types/widget-data/axis.js +1 -0
- package/build/types/widget-data/bar.d.ts +31 -0
- package/build/types/widget-data/bar.js +1 -0
- package/build/types/widget-data/base.d.ts +15 -0
- package/build/types/widget-data/base.js +1 -0
- package/build/types/widget-data/chart.d.ts +9 -0
- package/build/types/widget-data/chart.js +1 -0
- package/build/types/widget-data/index.d.ts +28 -0
- package/build/types/widget-data/index.js +10 -0
- package/build/types/widget-data/legend.d.ts +3 -0
- package/build/types/widget-data/legend.js +1 -0
- package/build/types/widget-data/pie.d.ts +10 -0
- package/build/types/widget-data/pie.js +1 -0
- package/build/types/widget-data/scatter.d.ts +20 -0
- package/build/types/widget-data/scatter.js +1 -0
- package/build/types/widget-data/series.d.ts +18 -0
- package/build/types/widget-data/series.js +1 -0
- package/build/types/widget-data/title.d.ts +5 -0
- package/build/types/widget-data/title.js +1 -0
- package/build/types/widget-data/tooltip.d.ts +12 -0
- package/build/types/widget-data/tooltip.js +1 -0
- package/build/types/widget.d.ts +5 -0
- package/package.json +7 -2
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { scaleBand, scaleLinear, scaleUtc, extent } from 'd3';
|
|
3
|
+
import get from 'lodash/get';
|
|
4
|
+
import { getOnlyVisibleSeries, getDomainDataXBySeries, getDomainDataYBySeries } from '../../utils';
|
|
5
|
+
const isNumericalArrayData = (data) => {
|
|
6
|
+
return data.every((d) => typeof d === 'number' || d === null);
|
|
7
|
+
};
|
|
8
|
+
const filterCategoriesByVisibleSeries = (categories, series) => {
|
|
9
|
+
return categories.filter((category) => {
|
|
10
|
+
return series.some((s) => {
|
|
11
|
+
return s.data.some((d) => 'category' in d && d.category === category);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
export const useScales = (args) => {
|
|
16
|
+
const { boundsWidth, boundsHeight, series, xAxis, yAxis } = args;
|
|
17
|
+
const scales = React.useMemo(() => {
|
|
18
|
+
const xType = get(xAxis, 'type', 'linear');
|
|
19
|
+
const xCategories = get(xAxis, 'categories');
|
|
20
|
+
const xTimestamps = get(xAxis, 'timestamps');
|
|
21
|
+
const yType = get(yAxis[0], 'type', 'linear');
|
|
22
|
+
const yMin = get(yAxis[0], 'min');
|
|
23
|
+
const yCategories = get(yAxis[0], 'categories');
|
|
24
|
+
const yTimestamps = get(xAxis, 'timestamps');
|
|
25
|
+
let visibleSeries = getOnlyVisibleSeries(series);
|
|
26
|
+
// Reassign to all series in case of all series unselected,
|
|
27
|
+
// otherwise we will get an empty space without grid
|
|
28
|
+
visibleSeries = visibleSeries.length === 0 ? series : visibleSeries;
|
|
29
|
+
let xScale;
|
|
30
|
+
let yScale;
|
|
31
|
+
switch (xType) {
|
|
32
|
+
case 'linear': {
|
|
33
|
+
const domain = getDomainDataXBySeries(visibleSeries);
|
|
34
|
+
if (isNumericalArrayData(domain)) {
|
|
35
|
+
const [xMin, xMax] = extent(domain);
|
|
36
|
+
xScale = scaleLinear().domain([xMin, xMax]).range([0, boundsWidth]).nice();
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case 'category': {
|
|
41
|
+
if (xCategories) {
|
|
42
|
+
const filteredCategories = filterCategoriesByVisibleSeries(xCategories, visibleSeries);
|
|
43
|
+
xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case 'datetime': {
|
|
48
|
+
if (xTimestamps) {
|
|
49
|
+
const [xMin, xMax] = extent(xTimestamps);
|
|
50
|
+
xScale = scaleUtc().domain([xMin, xMax]).range([0, boundsWidth]).nice();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const domain = getDomainDataXBySeries(visibleSeries);
|
|
54
|
+
if (isNumericalArrayData(domain)) {
|
|
55
|
+
const [xMin, xMax] = extent(domain);
|
|
56
|
+
xScale = scaleUtc().domain([xMin, xMax]).range([0, boundsWidth]).nice();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!xScale) {
|
|
63
|
+
throw new Error('Failed to create xScale');
|
|
64
|
+
}
|
|
65
|
+
switch (yType) {
|
|
66
|
+
case 'linear': {
|
|
67
|
+
const domain = getDomainDataYBySeries(visibleSeries);
|
|
68
|
+
if (isNumericalArrayData(domain)) {
|
|
69
|
+
const [domainYMin, yMax] = extent(domain);
|
|
70
|
+
const yMinValue = typeof yMin === 'number' ? yMin : domainYMin;
|
|
71
|
+
yScale = scaleLinear()
|
|
72
|
+
.domain([yMinValue, yMax])
|
|
73
|
+
.range([boundsHeight, 0])
|
|
74
|
+
.nice();
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'category': {
|
|
79
|
+
if (yCategories) {
|
|
80
|
+
const filteredCategories = filterCategoriesByVisibleSeries(yCategories, visibleSeries);
|
|
81
|
+
yScale = scaleBand().domain(filteredCategories).range([boundsHeight, 0]);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case 'datetime': {
|
|
86
|
+
if (yTimestamps) {
|
|
87
|
+
const [yMin, yMax] = extent(yTimestamps);
|
|
88
|
+
yScale = scaleUtc().domain([yMin, yMax]).range([boundsHeight, 0]).nice();
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const domain = getDomainDataYBySeries(visibleSeries);
|
|
92
|
+
if (isNumericalArrayData(domain)) {
|
|
93
|
+
const [yMin, yMax] = extent(domain);
|
|
94
|
+
yScale = scaleUtc().domain([yMin, yMax]).range([boundsHeight, 0]).nice();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!yScale) {
|
|
101
|
+
throw new Error('Failed to create yScale');
|
|
102
|
+
}
|
|
103
|
+
return { xScale, yScale };
|
|
104
|
+
}, [boundsWidth, boundsHeight, series, xAxis, yAxis]);
|
|
105
|
+
return scales;
|
|
106
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ChartKitWidgetSeries } from '../../../../../types/widget-data';
|
|
2
|
+
export type ChartSeries = ChartKitWidgetSeries & {
|
|
3
|
+
color: string;
|
|
4
|
+
name: string;
|
|
5
|
+
visible: boolean;
|
|
6
|
+
};
|
|
7
|
+
type Args = {
|
|
8
|
+
activeLegendItems: string[];
|
|
9
|
+
series: ChartKitWidgetSeries[];
|
|
10
|
+
};
|
|
11
|
+
export declare const useSeries: (args: Args) => {
|
|
12
|
+
chartSeries: ChartSeries[];
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import clone from 'lodash/clone';
|
|
3
|
+
import { scaleOrdinal } from 'd3';
|
|
4
|
+
import { DEFAULT_PALETTE } from '../../constants';
|
|
5
|
+
import { getSeriesNames } from '../../utils';
|
|
6
|
+
export const useSeries = (args) => {
|
|
7
|
+
const { activeLegendItems, series } = args;
|
|
8
|
+
// FIXME: handle case with one pie chart series
|
|
9
|
+
const chartSeries = React.useMemo(() => {
|
|
10
|
+
const seriesNames = getSeriesNames(series);
|
|
11
|
+
const colorScale = scaleOrdinal(seriesNames, DEFAULT_PALETTE);
|
|
12
|
+
return series.map((s) => {
|
|
13
|
+
const preparedSeries = clone(s);
|
|
14
|
+
const name = 'name' in s ? s.name : '';
|
|
15
|
+
const color = 'color' in s && s.color ? s.color : colorScale(name);
|
|
16
|
+
preparedSeries.color = color;
|
|
17
|
+
preparedSeries.name = name;
|
|
18
|
+
preparedSeries.visible = activeLegendItems.includes(name);
|
|
19
|
+
return preparedSeries;
|
|
20
|
+
});
|
|
21
|
+
}, [activeLegendItems, series]);
|
|
22
|
+
return { chartSeries };
|
|
23
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ChartOptions } from '../useChartOptions/types';
|
|
3
|
+
import { ChartScale } from '../useScales';
|
|
4
|
+
import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
5
|
+
import { BarSeries } from '../../../../../types/widget-data';
|
|
6
|
+
type Args = {
|
|
7
|
+
series: BarSeries[];
|
|
8
|
+
xAxis: ChartOptions['xAxis'];
|
|
9
|
+
xScale: ChartScale;
|
|
10
|
+
yAxis: ChartOptions['yAxis'];
|
|
11
|
+
yScale: ChartScale;
|
|
12
|
+
onSeriesMouseMove?: OnSeriesMouseMove;
|
|
13
|
+
onSeriesMouseLeave?: OnSeriesMouseLeave;
|
|
14
|
+
};
|
|
15
|
+
export declare function prepareBarSeries(args: Args): React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
const DEFAULT_BAR_RECT_WIDTH = 50;
|
|
4
|
+
const DEFAULT_LINEAR_BAR_RECT_WIDTH = 20;
|
|
5
|
+
const MIN_RECT_GAP = 1;
|
|
6
|
+
const b = block('chartkit-d3-bar');
|
|
7
|
+
const getRectProperties = (args) => {
|
|
8
|
+
const { point, xAxis, xScale, yAxis, yScale, minPointDistance } = args;
|
|
9
|
+
let cx;
|
|
10
|
+
let cy;
|
|
11
|
+
let width;
|
|
12
|
+
let height;
|
|
13
|
+
if (xAxis.type === 'category') {
|
|
14
|
+
const xBandScale = xScale;
|
|
15
|
+
const maxWidth = xBandScale.bandwidth() - MIN_RECT_GAP;
|
|
16
|
+
width = Math.min(maxWidth, DEFAULT_BAR_RECT_WIDTH);
|
|
17
|
+
cx = (xBandScale(point.category) || 0) + xBandScale.step() / 2 - width / 2;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const xLinearScale = xScale;
|
|
21
|
+
const [min, max] = xLinearScale.domain();
|
|
22
|
+
const range = xLinearScale.range();
|
|
23
|
+
const maxWidth = ((range[1] - range[0]) * minPointDistance) / (Number(max) - Number(min)) - MIN_RECT_GAP;
|
|
24
|
+
width = Math.min(Math.max(maxWidth, 1), DEFAULT_LINEAR_BAR_RECT_WIDTH);
|
|
25
|
+
cx = xLinearScale(point.x) - width / 2;
|
|
26
|
+
}
|
|
27
|
+
if (yAxis[0].type === 'linear') {
|
|
28
|
+
const yLinearScale = yScale;
|
|
29
|
+
cy = yLinearScale(point.y);
|
|
30
|
+
height = yLinearScale(yLinearScale.domain()[0]) - cy;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw Error(`The "${yAxis[0].type}" type for the Y axis is not supported`);
|
|
34
|
+
}
|
|
35
|
+
return { x: cx, y: cy, width, height };
|
|
36
|
+
};
|
|
37
|
+
function minDiff(arr) {
|
|
38
|
+
let result = Infinity;
|
|
39
|
+
for (let i = 0; i < arr.length - 1; i++) {
|
|
40
|
+
for (let j = i + 1; j < arr.length; j++) {
|
|
41
|
+
const diff = Math.abs(arr[i] - arr[j]);
|
|
42
|
+
if (diff < result) {
|
|
43
|
+
result = diff;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
export function prepareBarSeries(args) {
|
|
50
|
+
const { series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave } = args;
|
|
51
|
+
const seriesData = series.map(({ data }) => data).flat(2);
|
|
52
|
+
const minPointDistance = minDiff(seriesData.map((item) => Number(item.x)));
|
|
53
|
+
return series.reduce((result, item) => {
|
|
54
|
+
const randomKey = Math.random().toString();
|
|
55
|
+
item.data.forEach((point, i) => {
|
|
56
|
+
const rectProps = getRectProperties({
|
|
57
|
+
point,
|
|
58
|
+
xAxis,
|
|
59
|
+
xScale,
|
|
60
|
+
yAxis,
|
|
61
|
+
yScale,
|
|
62
|
+
minPointDistance,
|
|
63
|
+
});
|
|
64
|
+
result.push(React.createElement("rect", Object.assign({ key: `${i}-${randomKey}`, className: b('rect'), fill: item.color }, rectProps, { onMouseMove: function () {
|
|
65
|
+
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
66
|
+
hovered: {
|
|
67
|
+
data: point,
|
|
68
|
+
series: item,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}, onMouseLeave: onSeriesMouseLeave })));
|
|
72
|
+
});
|
|
73
|
+
return result;
|
|
74
|
+
}, []);
|
|
75
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ChartOptions } from '../useChartOptions/types';
|
|
3
|
+
import type { ChartScale } from '../useScales';
|
|
4
|
+
import type { ChartSeries } from '../useSeries';
|
|
5
|
+
import type { OnSeriesMouseMove, OnSeriesMouseLeave } from '../useTooltip/types';
|
|
6
|
+
type Args = {
|
|
7
|
+
series: ChartSeries[];
|
|
8
|
+
xAxis: ChartOptions['xAxis'];
|
|
9
|
+
xScale: ChartScale;
|
|
10
|
+
yAxis: ChartOptions['yAxis'];
|
|
11
|
+
yScale: ChartScale;
|
|
12
|
+
svgContainer: SVGSVGElement | null;
|
|
13
|
+
onSeriesMouseMove?: OnSeriesMouseMove;
|
|
14
|
+
onSeriesMouseLeave?: OnSeriesMouseLeave;
|
|
15
|
+
};
|
|
16
|
+
export declare const useShapes: (args: Args) => {
|
|
17
|
+
shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { group } from 'd3';
|
|
3
|
+
import { getOnlyVisibleSeries } from '../../utils';
|
|
4
|
+
import { prepareBarSeries } from './bar';
|
|
5
|
+
import { prepareScatterSeries } from './scatter';
|
|
6
|
+
export const useShapes = (args) => {
|
|
7
|
+
const { series, xAxis, xScale, yAxis, yScale, svgContainer, onSeriesMouseMove, onSeriesMouseLeave, } = args;
|
|
8
|
+
const shapes = React.useMemo(() => {
|
|
9
|
+
const visibleSeries = getOnlyVisibleSeries(series);
|
|
10
|
+
const groupedSeries = group(visibleSeries, (item) => item.type);
|
|
11
|
+
return Array.from(groupedSeries).reduce((acc, item) => {
|
|
12
|
+
const [seriesType, chartSeries] = item;
|
|
13
|
+
switch (seriesType) {
|
|
14
|
+
case 'bar': {
|
|
15
|
+
acc.push(...prepareBarSeries({
|
|
16
|
+
series: chartSeries,
|
|
17
|
+
xAxis,
|
|
18
|
+
xScale,
|
|
19
|
+
yAxis,
|
|
20
|
+
yScale,
|
|
21
|
+
onSeriesMouseMove,
|
|
22
|
+
onSeriesMouseLeave,
|
|
23
|
+
}));
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case 'scatter': {
|
|
27
|
+
acc.push(...prepareScatterSeries({
|
|
28
|
+
series: chartSeries,
|
|
29
|
+
xAxis,
|
|
30
|
+
xScale,
|
|
31
|
+
yAxis,
|
|
32
|
+
yScale,
|
|
33
|
+
onSeriesMouseMove,
|
|
34
|
+
onSeriesMouseLeave,
|
|
35
|
+
svgContainer,
|
|
36
|
+
}));
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return acc;
|
|
41
|
+
}, []);
|
|
42
|
+
}, [series, xAxis, xScale, yAxis, yScale, svgContainer, onSeriesMouseMove, onSeriesMouseLeave]);
|
|
43
|
+
return { shapes };
|
|
44
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ChartOptions } from '../useChartOptions/types';
|
|
3
|
+
import { ChartScale } from '../useScales';
|
|
4
|
+
import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
5
|
+
import { ScatterSeries } from '../../../../../types/widget-data';
|
|
6
|
+
type PrepareScatterSeriesArgs = {
|
|
7
|
+
series: ScatterSeries[];
|
|
8
|
+
xAxis: ChartOptions['xAxis'];
|
|
9
|
+
xScale: ChartScale;
|
|
10
|
+
yAxis: ChartOptions['yAxis'];
|
|
11
|
+
yScale: ChartScale;
|
|
12
|
+
svgContainer: SVGSVGElement | null;
|
|
13
|
+
onSeriesMouseMove?: OnSeriesMouseMove;
|
|
14
|
+
onSeriesMouseLeave?: OnSeriesMouseLeave;
|
|
15
|
+
key?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function prepareScatterSeries(args: PrepareScatterSeriesArgs): React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { pointer } from 'd3';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import block from 'bem-cn-lite';
|
|
4
|
+
const b = block('chartkit-d3-scatter');
|
|
5
|
+
const DEFAULT_SCATTER_POINT_RADIUS = 4;
|
|
6
|
+
const prepareCategoricalScatterData = (data) => {
|
|
7
|
+
return data.filter((d) => typeof d.category === 'string');
|
|
8
|
+
};
|
|
9
|
+
const prepareLinearScatterData = (data) => {
|
|
10
|
+
return data.filter((d) => typeof d.x === 'number' && typeof d.y === 'number');
|
|
11
|
+
};
|
|
12
|
+
const getPointProperties = (args) => {
|
|
13
|
+
const { point, xAxis, xScale, yAxis, yScale } = args;
|
|
14
|
+
const r = point.radius || DEFAULT_SCATTER_POINT_RADIUS;
|
|
15
|
+
let cx;
|
|
16
|
+
let cy;
|
|
17
|
+
if (xAxis.type === 'category') {
|
|
18
|
+
const xBandScale = xScale;
|
|
19
|
+
cx = (xBandScale(point.category) || 0) + xBandScale.step() / 2;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const xLinearScale = xScale;
|
|
23
|
+
cx = xLinearScale(point.x);
|
|
24
|
+
}
|
|
25
|
+
if (yAxis[0].type === 'category') {
|
|
26
|
+
const yBandScale = yScale;
|
|
27
|
+
cy = (yBandScale(point.category) || 0) + yBandScale.step() / 2;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const yLinearScale = yScale;
|
|
31
|
+
cy = yLinearScale(point.y);
|
|
32
|
+
}
|
|
33
|
+
return { r, cx, cy };
|
|
34
|
+
};
|
|
35
|
+
export function prepareScatterSeries(args) {
|
|
36
|
+
const { series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave, key, svgContainer, } = args;
|
|
37
|
+
return series.reduce((result, s) => {
|
|
38
|
+
var _a;
|
|
39
|
+
const preparedData = xAxis.type === 'category' || ((_a = yAxis[0]) === null || _a === void 0 ? void 0 : _a.type) === 'category'
|
|
40
|
+
? prepareCategoricalScatterData(s.data)
|
|
41
|
+
: prepareLinearScatterData(s.data);
|
|
42
|
+
result.push(...preparedData.map((point, i) => {
|
|
43
|
+
const pointProps = getPointProperties({
|
|
44
|
+
point,
|
|
45
|
+
xAxis,
|
|
46
|
+
xScale,
|
|
47
|
+
yAxis,
|
|
48
|
+
yScale,
|
|
49
|
+
});
|
|
50
|
+
return (React.createElement("circle", Object.assign({ key: `${i}-${key}`, className: b('point'), fill: s.color }, pointProps, { onMouseMove: function (e) {
|
|
51
|
+
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
52
|
+
hovered: {
|
|
53
|
+
data: point,
|
|
54
|
+
series: s,
|
|
55
|
+
},
|
|
56
|
+
pointerPosition: pointer(e, svgContainer),
|
|
57
|
+
});
|
|
58
|
+
}, onMouseLeave: onSeriesMouseLeave })));
|
|
59
|
+
}));
|
|
60
|
+
return result;
|
|
61
|
+
}, []);
|
|
62
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TooltipHoveredData } from '../../../../../types/widget-data';
|
|
2
|
+
import { PreparedTooltip } from '../useChartOptions/types';
|
|
3
|
+
import type { PointerPosition, OnSeriesMouseMove, OnSeriesMouseLeave } from './types';
|
|
4
|
+
type Args = {
|
|
5
|
+
tooltip: PreparedTooltip;
|
|
6
|
+
};
|
|
7
|
+
export declare const useTooltip: ({ tooltip }: Args) => {
|
|
8
|
+
hovered: TooltipHoveredData<any> | undefined;
|
|
9
|
+
pointerPosition: PointerPosition | undefined;
|
|
10
|
+
handleSeriesMouseMove: OnSeriesMouseMove | undefined;
|
|
11
|
+
handleSeriesMouseLeave: OnSeriesMouseLeave | undefined;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export const useTooltip = ({ tooltip }) => {
|
|
3
|
+
const [hovered, setTooltipHoveredData] = React.useState();
|
|
4
|
+
const [pointerPosition, setPointerPosition] = React.useState();
|
|
5
|
+
const handleSeriesMouseMove = React.useCallback(({ pointerPosition: nextPointerPosition, hovered: nextHovered }) => {
|
|
6
|
+
setTooltipHoveredData(nextHovered);
|
|
7
|
+
setPointerPosition(nextPointerPosition);
|
|
8
|
+
}, []);
|
|
9
|
+
const handleSeriesMouseLeave = React.useCallback(() => {
|
|
10
|
+
setTooltipHoveredData(undefined);
|
|
11
|
+
setPointerPosition(undefined);
|
|
12
|
+
}, []);
|
|
13
|
+
return {
|
|
14
|
+
hovered,
|
|
15
|
+
pointerPosition,
|
|
16
|
+
handleSeriesMouseMove: tooltip.enabled ? handleSeriesMouseMove : undefined,
|
|
17
|
+
handleSeriesMouseLeave: tooltip.enabled ? handleSeriesMouseLeave : undefined,
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TooltipHoveredData } from '../../../../../types/widget-data';
|
|
2
|
+
export type PointerPosition = [number, number];
|
|
3
|
+
export type OnSeriesMouseMove = (args: {
|
|
4
|
+
hovered: TooltipHoveredData;
|
|
5
|
+
pointerPosition?: PointerPosition;
|
|
6
|
+
}) => void;
|
|
7
|
+
export type OnSeriesMouseLeave = () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AxisDomain } from 'd3';
|
|
2
|
+
import type { ChartKitWidgetSeries, ChartKitWidgetAxisType, ChartKitWidgetAxisLabels } from '../../../../types/widget-data';
|
|
3
|
+
export declare const isAxisRelatedSeries: (series: ChartKitWidgetSeries) => boolean;
|
|
4
|
+
export declare const getDomainDataXBySeries: (series: ChartKitWidgetSeries[]) => unknown[];
|
|
5
|
+
export declare const getDomainDataYBySeries: (series: ChartKitWidgetSeries[]) => unknown[];
|
|
6
|
+
export declare const getSeriesNames: (series: ChartKitWidgetSeries[]) => string[];
|
|
7
|
+
export declare const getVisibleSeriesNames: (series: ChartKitWidgetSeries[]) => string[];
|
|
8
|
+
export declare const getOnlyVisibleSeries: <T extends ChartKitWidgetSeries<any>>(series: T[]) => T[];
|
|
9
|
+
export declare const parseTransformStyle: (style: string | null) => {
|
|
10
|
+
x?: number | undefined;
|
|
11
|
+
y?: number | undefined;
|
|
12
|
+
};
|
|
13
|
+
export declare const formatAxisTickLabel: (args: {
|
|
14
|
+
axisType: ChartKitWidgetAxisType;
|
|
15
|
+
value: AxisDomain;
|
|
16
|
+
dateFormat?: ChartKitWidgetAxisLabels['dateFormat'];
|
|
17
|
+
numberFormat?: ChartKitWidgetAxisLabels['numberFormat'];
|
|
18
|
+
}) => string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { dateTime } from '@gravity-ui/date-utils';
|
|
2
|
+
import { formatNumber } from '../../../shared';
|
|
3
|
+
const CHARTS_WITHOUT_AXIS = ['pie'];
|
|
4
|
+
// Сhecks whether the series should be drawn with axes
|
|
5
|
+
export const isAxisRelatedSeries = (series) => {
|
|
6
|
+
return !CHARTS_WITHOUT_AXIS.includes(series.type);
|
|
7
|
+
};
|
|
8
|
+
export const getDomainDataXBySeries = (series) => {
|
|
9
|
+
return series.filter(isAxisRelatedSeries).reduce((acc, s) => {
|
|
10
|
+
acc.push(...s.data.map((d) => 'x' in d && d.x));
|
|
11
|
+
return acc;
|
|
12
|
+
}, []);
|
|
13
|
+
};
|
|
14
|
+
export const getDomainDataYBySeries = (series) => {
|
|
15
|
+
return series.filter(isAxisRelatedSeries).reduce((acc, s) => {
|
|
16
|
+
acc.push(...s.data.map((d) => 'y' in d && d.y));
|
|
17
|
+
return acc;
|
|
18
|
+
}, []);
|
|
19
|
+
};
|
|
20
|
+
// Uses to get all series names array (except `pie` charts)
|
|
21
|
+
export const getSeriesNames = (series) => {
|
|
22
|
+
return series.reduce((acc, s) => {
|
|
23
|
+
if ('name' in s && typeof s.name === 'string') {
|
|
24
|
+
acc.push(s.name);
|
|
25
|
+
}
|
|
26
|
+
return acc;
|
|
27
|
+
}, []);
|
|
28
|
+
};
|
|
29
|
+
// Uses to get all visible series names array (except `pie` charts)
|
|
30
|
+
export const getVisibleSeriesNames = (series) => {
|
|
31
|
+
return series.reduce((acc, s) => {
|
|
32
|
+
var _a;
|
|
33
|
+
const visible = (_a = s.visible) !== null && _a !== void 0 ? _a : true;
|
|
34
|
+
if ('name' in s && typeof s.name === 'string' && visible) {
|
|
35
|
+
acc.push(s.name);
|
|
36
|
+
}
|
|
37
|
+
return acc;
|
|
38
|
+
}, []);
|
|
39
|
+
};
|
|
40
|
+
export const getOnlyVisibleSeries = (series) => {
|
|
41
|
+
return series.filter((s) => s.visible);
|
|
42
|
+
};
|
|
43
|
+
export const parseTransformStyle = (style) => {
|
|
44
|
+
var _a;
|
|
45
|
+
if (!style) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
const stringifiedValue = ((_a = style.match(/\((.*?)\)/)) === null || _a === void 0 ? void 0 : _a[1]) || '';
|
|
49
|
+
const [xString, yString] = stringifiedValue.split(',');
|
|
50
|
+
const x = Number.isNaN(Number(xString)) ? undefined : Number(xString);
|
|
51
|
+
const y = Number.isNaN(Number(yString)) ? undefined : Number(yString);
|
|
52
|
+
return { x, y };
|
|
53
|
+
};
|
|
54
|
+
const defaultFormatNumberOptions = {
|
|
55
|
+
precision: 0,
|
|
56
|
+
};
|
|
57
|
+
export const formatAxisTickLabel = (args) => {
|
|
58
|
+
const { axisType, value, dateFormat = 'DD.MM.YY', numberFormat = defaultFormatNumberOptions, } = args;
|
|
59
|
+
switch (axisType) {
|
|
60
|
+
case 'category': {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
case 'datetime': {
|
|
64
|
+
return dateTime({ input: value }).format(dateFormat);
|
|
65
|
+
}
|
|
66
|
+
case 'linear':
|
|
67
|
+
default: {
|
|
68
|
+
return formatNumber(value, numberFormat);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FormatNumberOptions } from '../../plugins/shared';
|
|
2
|
+
import type { BaseTextStyle } from './base';
|
|
3
|
+
export type ChartKitWidgetAxisType = 'category' | 'datetime' | 'linear';
|
|
4
|
+
export type ChartKitWidgetAxisLabels = {
|
|
5
|
+
/** Enable or disable the axis labels */
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
/** The pixel padding for axis labels */
|
|
8
|
+
padding?: number;
|
|
9
|
+
dateFormat?: string;
|
|
10
|
+
numberFormat?: FormatNumberOptions;
|
|
11
|
+
style?: Partial<BaseTextStyle>;
|
|
12
|
+
};
|
|
13
|
+
export type ChartKitWidgetAxis = {
|
|
14
|
+
categories?: string[];
|
|
15
|
+
timestamps?: number[];
|
|
16
|
+
type?: ChartKitWidgetAxisType;
|
|
17
|
+
/** The axis labels show the number or category for each tick */
|
|
18
|
+
labels?: ChartKitWidgetAxisLabels;
|
|
19
|
+
title?: {
|
|
20
|
+
text?: string;
|
|
21
|
+
};
|
|
22
|
+
/** The minimum value of the axis. If undefined the min value is automatically calculate */
|
|
23
|
+
min?: number;
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BaseSeries, BaseSeriesData } from './base';
|
|
2
|
+
import type { ChartKitWidgetSeriesOptions } from './series';
|
|
3
|
+
export type BarSeriesData<T = any> = BaseSeriesData<T> & {
|
|
4
|
+
/** The x value of the point */
|
|
5
|
+
x?: number;
|
|
6
|
+
/** The y value of the point */
|
|
7
|
+
y?: number;
|
|
8
|
+
/** Corresponding value of axis category */
|
|
9
|
+
category?: string;
|
|
10
|
+
};
|
|
11
|
+
export type BarSeries<T = any> = BaseSeries & {
|
|
12
|
+
type: 'bar';
|
|
13
|
+
data: BarSeriesData<T>[];
|
|
14
|
+
/** The name of the series (used in legend) */
|
|
15
|
+
name: string;
|
|
16
|
+
/** The main color of the series (hex, rgba) */
|
|
17
|
+
color?: string;
|
|
18
|
+
stacking?: 'normal' | 'percent';
|
|
19
|
+
/** This option allows grouping series in a stacked chart */
|
|
20
|
+
stackId?: string;
|
|
21
|
+
/** Whether to group non-stacked columns or to let them render independent of each other.
|
|
22
|
+
* When false columns will be laid out individually and overlap each other.
|
|
23
|
+
*
|
|
24
|
+
* @default true
|
|
25
|
+
* */
|
|
26
|
+
grouping?: boolean;
|
|
27
|
+
dataLabels?: ChartKitWidgetSeriesOptions['dataLabels'] & {
|
|
28
|
+
/** Whether to align the data label inside the box or to the actual value point */
|
|
29
|
+
inside?: boolean;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type BaseSeries = {
|
|
2
|
+
/** Initial visibility of the series */
|
|
3
|
+
visible?: boolean;
|
|
4
|
+
};
|
|
5
|
+
export type BaseSeriesData<T = any> = {
|
|
6
|
+
/**
|
|
7
|
+
* A reserved subspace to store options and values for customized functionality
|
|
8
|
+
*
|
|
9
|
+
* Here you can add additional data for your own event callbacks and formatter callbacks
|
|
10
|
+
*/
|
|
11
|
+
custom?: T;
|
|
12
|
+
};
|
|
13
|
+
export type BaseTextStyle = {
|
|
14
|
+
fontSize: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ChartKitWidgetAxis } from './axis';
|
|
2
|
+
import type { ChartKitWidgetChart } from './chart';
|
|
3
|
+
import type { ChartKitWidgetLegend } from './legend';
|
|
4
|
+
import type { ChartKitWidgetSeries, ChartKitWidgetSeriesOptions } from './series';
|
|
5
|
+
import type { ChartKitWidgetTitle } from './title';
|
|
6
|
+
import type { ChartKitWidgetTooltip } from './tooltip';
|
|
7
|
+
export * from './axis';
|
|
8
|
+
export * from './base';
|
|
9
|
+
export * from './chart';
|
|
10
|
+
export * from './legend';
|
|
11
|
+
export * from './pie';
|
|
12
|
+
export * from './scatter';
|
|
13
|
+
export * from './bar';
|
|
14
|
+
export * from './series';
|
|
15
|
+
export * from './title';
|
|
16
|
+
export * from './tooltip';
|
|
17
|
+
export type ChartKitWidgetData<T = any> = {
|
|
18
|
+
chart?: ChartKitWidgetChart;
|
|
19
|
+
legend?: ChartKitWidgetLegend;
|
|
20
|
+
series: {
|
|
21
|
+
data: ChartKitWidgetSeries<T>[];
|
|
22
|
+
options?: ChartKitWidgetSeriesOptions;
|
|
23
|
+
};
|
|
24
|
+
title?: ChartKitWidgetTitle;
|
|
25
|
+
tooltip?: ChartKitWidgetTooltip<T>;
|
|
26
|
+
xAxis?: ChartKitWidgetAxis;
|
|
27
|
+
yAxis?: ChartKitWidgetAxis[];
|
|
28
|
+
};
|