@gravity-ui/chartkit 3.5.0 → 4.0.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/renderer/components/Chart.js +7 -11
- package/build/plugins/d3/renderer/components/Legend.d.ts +3 -2
- package/build/plugins/d3/renderer/components/Legend.js +42 -21
- package/build/plugins/d3/renderer/constants.d.ts +3 -0
- package/build/plugins/d3/renderer/constants.js +3 -0
- package/build/plugins/d3/renderer/hooks/index.d.ts +1 -1
- package/build/plugins/d3/renderer/hooks/index.js +1 -1
- package/build/plugins/d3/renderer/hooks/useAxisScales/index.d.ts +2 -2
- package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +2 -2
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.d.ts +2 -2
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.js +2 -4
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.js +5 -6
- package/build/plugins/d3/renderer/hooks/useChartOptions/legend.js +7 -2
- package/build/plugins/d3/renderer/hooks/useChartOptions/title.js +2 -2
- package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +3 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +3 -3
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +3 -3
- package/build/plugins/d3/renderer/hooks/useSeries/constants.d.ts +1 -0
- package/build/plugins/d3/renderer/hooks/useSeries/constants.js +1 -0
- package/build/plugins/d3/renderer/hooks/useSeries/index.d.ts +10 -8
- package/build/plugins/d3/renderer/hooks/useSeries/index.js +44 -53
- package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.d.ts +9 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +85 -0
- package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +29 -0
- package/build/plugins/d3/renderer/hooks/useSeries/types.js +1 -0
- package/build/plugins/d3/renderer/hooks/useSeries/utils.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useSeries/utils.js +11 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x.js +2 -1
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +2 -2
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +4 -1
- package/build/plugins/d3/renderer/hooks/useShapes/pie.d.ts +2 -2
- package/build/plugins/d3/renderer/hooks/useShapes/pie.js +87 -12
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.d.ts +0 -1
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +4 -2
- package/build/plugins/d3/renderer/hooks/useShapes/styles.css +6 -0
- package/build/plugins/d3/renderer/utils/index.d.ts +42 -6
- package/build/plugins/d3/renderer/utils/index.js +43 -6
- package/build/types/widget-data/bar-x.d.ts +5 -0
- package/build/types/widget-data/base.d.ts +12 -3
- package/build/types/widget-data/legend.d.ts +32 -0
- package/build/types/widget-data/pie.d.ts +23 -4
- package/build/types/widget-data/scatter.d.ts +5 -0
- package/package.json +1 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/constants.d.ts +0 -3
- package/build/plugins/d3/renderer/hooks/useChartOptions/constants.js +0 -3
- package/build/plugins/d3/renderer/hooks/useChartOptions/utils.d.ts +0 -5
- package/build/plugins/d3/renderer/hooks/useChartOptions/utils.js +0 -18
- package/build/plugins/d3/renderer/hooks/useLegend/index.d.ts +0 -13
- package/build/plugins/d3/renderer/hooks/useLegend/index.js +0 -59
|
@@ -5,19 +5,16 @@ import { AxisX } from './AxisX';
|
|
|
5
5
|
import { Legend } from './Legend';
|
|
6
6
|
import { Title } from './Title';
|
|
7
7
|
import { Tooltip } from './Tooltip';
|
|
8
|
-
import { useChartDimensions, useChartEvents, useChartOptions,
|
|
9
|
-
import { isAxisRelatedSeries } from '../utils';
|
|
8
|
+
import { useChartDimensions, useChartEvents, useChartOptions, useAxisScales, useSeries, useShapes, useTooltip, } from '../hooks';
|
|
10
9
|
import './styles.css';
|
|
11
10
|
const b = block('d3');
|
|
12
11
|
export const Chart = (props) => {
|
|
13
12
|
const { top, left, width, height, data } = props;
|
|
14
13
|
// FIXME: add data validation
|
|
15
|
-
const { series } = data;
|
|
16
14
|
const svgRef = React.createRef();
|
|
17
|
-
const hasAxisRelatedSeries = series.data.some(isAxisRelatedSeries);
|
|
18
15
|
const { chartHovered, handleMouseEnter, handleMouseLeave } = useChartEvents();
|
|
19
16
|
const { chart, legend, title, tooltip, xAxis, yAxis } = useChartOptions(data);
|
|
20
|
-
const { boundsWidth, boundsHeight
|
|
17
|
+
const { boundsWidth, boundsHeight } = useChartDimensions({
|
|
21
18
|
width,
|
|
22
19
|
height,
|
|
23
20
|
margin: chart.margin,
|
|
@@ -26,12 +23,11 @@ export const Chart = (props) => {
|
|
|
26
23
|
xAxis,
|
|
27
24
|
yAxis,
|
|
28
25
|
});
|
|
29
|
-
const {
|
|
30
|
-
const { chartSeries } = useSeries({ activeLegendItems, series: series.data });
|
|
26
|
+
const { preparedSeries, handleLegendItemClick } = useSeries({ series: data.series, legend });
|
|
31
27
|
const { xScale, yScale } = useAxisScales({
|
|
32
28
|
boundsWidth,
|
|
33
29
|
boundsHeight,
|
|
34
|
-
series:
|
|
30
|
+
series: preparedSeries,
|
|
35
31
|
xAxis,
|
|
36
32
|
yAxis,
|
|
37
33
|
});
|
|
@@ -43,7 +39,7 @@ export const Chart = (props) => {
|
|
|
43
39
|
left,
|
|
44
40
|
boundsWidth,
|
|
45
41
|
boundsHeight,
|
|
46
|
-
series:
|
|
42
|
+
series: preparedSeries,
|
|
47
43
|
xAxis,
|
|
48
44
|
xScale,
|
|
49
45
|
yAxis,
|
|
@@ -59,11 +55,11 @@ export const Chart = (props) => {
|
|
|
59
55
|
chart.margin.left,
|
|
60
56
|
chart.margin.top + ((title === null || title === void 0 ? void 0 : title.height) || 0),
|
|
61
57
|
].join(',')})` },
|
|
62
|
-
|
|
58
|
+
xScale && yScale && (React.createElement(React.Fragment, null,
|
|
63
59
|
React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
|
|
64
60
|
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
65
61
|
React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale })))),
|
|
66
62
|
shapes),
|
|
67
|
-
legend.enabled && (React.createElement(Legend, { width: boundsWidth, offsetWidth: chart.margin.left, height:
|
|
63
|
+
legend.enabled && (React.createElement(Legend, { width: boundsWidth, offsetWidth: chart.margin.left, height: legend.height, legend: legend, offsetHeight: height - legend.height / 2, chartSeries: preparedSeries, onItemClick: handleLegendItemClick }))),
|
|
68
64
|
React.createElement(Tooltip, { hovered: hovered, pointerPosition: pointerPosition, tooltip: tooltip, xAxis: xAxis, yAxis: yAxis[0] })));
|
|
69
65
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { OnLegendItemClick, PreparedLegend, PreparedSeries } from '../hooks';
|
|
3
3
|
type Props = {
|
|
4
4
|
width: number;
|
|
5
5
|
height: number;
|
|
6
|
+
legend: PreparedLegend;
|
|
6
7
|
offsetWidth: number;
|
|
7
8
|
offsetHeight: number;
|
|
8
|
-
chartSeries:
|
|
9
|
+
chartSeries: PreparedSeries[];
|
|
9
10
|
onItemClick: OnLegendItemClick;
|
|
10
11
|
};
|
|
11
12
|
export declare const Legend: (props: Props) => React.JSX.Element;
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { select } from 'd3';
|
|
2
|
+
import { select, sum } from 'd3';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
4
|
import { block } from '../../../../utils/cn';
|
|
5
|
-
import { isAxisRelatedSeries } from '../utils';
|
|
6
5
|
const b = block('d3-legend');
|
|
7
6
|
const getLegendItems = (series) => {
|
|
8
7
|
return series.reduce((acc, s) => {
|
|
9
|
-
const isAxisRelated = isAxisRelatedSeries(s);
|
|
10
8
|
const legendEnabled = get(s, 'legend.enabled', true);
|
|
11
|
-
if (
|
|
12
|
-
acc.push(s);
|
|
13
|
-
}
|
|
14
|
-
else if (!isAxisRelated && legendEnabled) {
|
|
15
|
-
acc.push(...s.data);
|
|
9
|
+
if (legendEnabled) {
|
|
10
|
+
acc.push(Object.assign(Object.assign({}, s), { symbol: s.legend.symbol }));
|
|
16
11
|
}
|
|
17
12
|
return acc;
|
|
18
13
|
}, []);
|
|
19
14
|
};
|
|
15
|
+
function getLegendPosition(args) {
|
|
16
|
+
const { align, offsetWidth, width, contentWidth } = args;
|
|
17
|
+
const top = 0;
|
|
18
|
+
if (align === 'left') {
|
|
19
|
+
return { top, left: offsetWidth };
|
|
20
|
+
}
|
|
21
|
+
if (align === 'right') {
|
|
22
|
+
return { top, left: offsetWidth + width - contentWidth };
|
|
23
|
+
}
|
|
24
|
+
return { top, left: offsetWidth + width / 2 - contentWidth / 2 };
|
|
25
|
+
}
|
|
20
26
|
export const Legend = (props) => {
|
|
21
|
-
const { width, offsetWidth, height, offsetHeight, chartSeries, onItemClick } = props;
|
|
27
|
+
const { width, offsetWidth, height, offsetHeight, chartSeries, legend, onItemClick } = props;
|
|
22
28
|
const ref = React.useRef(null);
|
|
23
29
|
React.useEffect(() => {
|
|
24
30
|
if (!ref.current) {
|
|
25
31
|
return;
|
|
26
32
|
}
|
|
27
33
|
const legendItems = getLegendItems(chartSeries);
|
|
28
|
-
const size = 10;
|
|
29
34
|
const textWidths = [0];
|
|
30
35
|
const svgElement = select(ref.current);
|
|
31
36
|
svgElement.selectAll('*').remove();
|
|
@@ -51,24 +56,30 @@ export const Legend = (props) => {
|
|
|
51
56
|
.remove();
|
|
52
57
|
legendItemTemplate
|
|
53
58
|
.append('rect')
|
|
54
|
-
.attr('x', function (
|
|
55
|
-
return (
|
|
56
|
-
i *
|
|
59
|
+
.attr('x', function (legendItem, i) {
|
|
60
|
+
return (i * legendItem.symbol.width +
|
|
61
|
+
i * legend.itemDistance +
|
|
62
|
+
i * legendItem.symbol.padding +
|
|
57
63
|
textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
|
|
58
64
|
})
|
|
59
|
-
.attr('y', offsetHeight -
|
|
60
|
-
.attr('width',
|
|
61
|
-
.
|
|
65
|
+
.attr('y', (legendItem) => offsetHeight - legendItem.symbol.height / 2)
|
|
66
|
+
.attr('width', (legendItem) => {
|
|
67
|
+
return legendItem.symbol.width;
|
|
68
|
+
})
|
|
69
|
+
.attr('height', (legendItem) => legendItem.symbol.height)
|
|
70
|
+
.attr('rx', (legendItem) => legendItem.symbol.radius)
|
|
62
71
|
.attr('class', b('item-shape'))
|
|
63
72
|
.style('fill', function (d) {
|
|
64
73
|
return d.color;
|
|
65
74
|
});
|
|
66
75
|
legendItemTemplate
|
|
67
76
|
.append('text')
|
|
68
|
-
.attr('x', function (
|
|
69
|
-
return (
|
|
70
|
-
i *
|
|
71
|
-
|
|
77
|
+
.attr('x', function (legendItem, i) {
|
|
78
|
+
return (i * legendItem.symbol.width +
|
|
79
|
+
i * legend.itemDistance +
|
|
80
|
+
i * legendItem.symbol.padding +
|
|
81
|
+
legendItem.symbol.width +
|
|
82
|
+
legendItem.symbol.padding +
|
|
72
83
|
textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
|
|
73
84
|
})
|
|
74
85
|
.attr('y', offsetHeight)
|
|
@@ -80,6 +91,16 @@ export const Legend = (props) => {
|
|
|
80
91
|
return ('name' in d && d.name);
|
|
81
92
|
})
|
|
82
93
|
.style('alignment-baseline', 'middle');
|
|
83
|
-
|
|
94
|
+
const contentWidth = sum(textWidths) +
|
|
95
|
+
sum(legendItems, (item) => item.symbol.width + item.symbol.padding) +
|
|
96
|
+
legend.itemDistance * (legendItems.length - 1);
|
|
97
|
+
const { left } = getLegendPosition({
|
|
98
|
+
align: legend.align,
|
|
99
|
+
width,
|
|
100
|
+
offsetWidth,
|
|
101
|
+
contentWidth,
|
|
102
|
+
});
|
|
103
|
+
svgElement.attr('transform', `translate(${[left, 0].join(',')})`);
|
|
104
|
+
}, [width, offsetWidth, height, offsetHeight, chartSeries, onItemClick, legend]);
|
|
84
105
|
return React.createElement("g", { ref: ref, width: width, height: height });
|
|
85
106
|
};
|
|
@@ -2,9 +2,9 @@ export * from './useChartDimensions';
|
|
|
2
2
|
export * from './useChartEvents';
|
|
3
3
|
export * from './useChartOptions';
|
|
4
4
|
export * from './useChartOptions/types';
|
|
5
|
-
export * from './useLegend';
|
|
6
5
|
export * from './useAxisScales';
|
|
7
6
|
export * from './useSeries';
|
|
7
|
+
export * from './useSeries/types';
|
|
8
8
|
export * from './useShapes';
|
|
9
9
|
export * from './useTooltip';
|
|
10
10
|
export * from './useTooltip/types';
|
|
@@ -2,9 +2,9 @@ export * from './useChartDimensions';
|
|
|
2
2
|
export * from './useChartEvents';
|
|
3
3
|
export * from './useChartOptions';
|
|
4
4
|
export * from './useChartOptions/types';
|
|
5
|
-
export * from './useLegend';
|
|
6
5
|
export * from './useAxisScales';
|
|
7
6
|
export * from './useSeries';
|
|
7
|
+
export * from './useSeries/types';
|
|
8
8
|
export * from './useShapes';
|
|
9
9
|
export * from './useTooltip';
|
|
10
10
|
export * from './useTooltip/types';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ScaleBand, ScaleLinear, ScaleTime } from 'd3';
|
|
2
|
-
import type { ChartKitWidgetSeries } from '../../../../../types/widget-data';
|
|
3
2
|
import type { ChartOptions } from '../useChartOptions/types';
|
|
3
|
+
import { PreparedSeries } from '../useSeries/types';
|
|
4
4
|
export type ChartScale = ScaleLinear<number, number> | ScaleBand<string> | ScaleTime<number, number>;
|
|
5
5
|
type Args = {
|
|
6
6
|
boundsWidth: number;
|
|
7
7
|
boundsHeight: number;
|
|
8
|
-
series:
|
|
8
|
+
series: PreparedSeries[];
|
|
9
9
|
xAxis: ChartOptions['xAxis'];
|
|
10
10
|
yAxis: ChartOptions['yAxis'];
|
|
11
11
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
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, getDomainDataYBySeries, isAxisRelatedSeries, getDomainDataXBySeries, isSeriesWithCategoryValues, } from '../../utils';
|
|
5
5
|
const isNumericalArrayData = (data) => {
|
|
6
6
|
return data.every((d) => typeof d === 'number' || d === null);
|
|
7
7
|
};
|
|
8
8
|
const filterCategoriesByVisibleSeries = (categories, series) => {
|
|
9
9
|
return categories.filter((category) => {
|
|
10
10
|
return series.some((s) => {
|
|
11
|
-
return s.data.some((d) =>
|
|
11
|
+
return isSeriesWithCategoryValues(s) && s.data.some((d) => d.category === category);
|
|
12
12
|
});
|
|
13
13
|
});
|
|
14
14
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ChartMargin } from '../../../../../types/widget-data';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PreparedAxis, PreparedLegend, PreparedTitle } from '../useChartOptions/types';
|
|
3
3
|
type Args = {
|
|
4
4
|
width: number;
|
|
5
5
|
height: number;
|
|
6
6
|
margin: ChartMargin;
|
|
7
|
-
legend:
|
|
7
|
+
legend: PreparedLegend;
|
|
8
8
|
title?: PreparedTitle;
|
|
9
9
|
xAxis?: PreparedAxis;
|
|
10
10
|
yAxis?: PreparedAxis[];
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
const LEGEND_LINE_HEIGHT = 15;
|
|
2
1
|
export const useChartDimensions = (args) => {
|
|
3
2
|
const { margin, legend, title, width, height, xAxis, yAxis } = args;
|
|
4
|
-
const legendHeight = legend.enabled ? LEGEND_LINE_HEIGHT : 0;
|
|
5
3
|
const titleHeight = (title === null || title === void 0 ? void 0 : title.height) || 0;
|
|
6
4
|
const xAxisTitleHeight = (xAxis === null || xAxis === void 0 ? void 0 : xAxis.title.height) || 0;
|
|
7
5
|
const yAxisTitleHeight = (yAxis === null || yAxis === void 0 ? void 0 : yAxis.reduce((acc, axis) => {
|
|
8
6
|
return acc + (axis.title.height || 0);
|
|
9
7
|
}, 0)) || 0;
|
|
10
8
|
const boundsWidth = width - margin.right - margin.left - yAxisTitleHeight;
|
|
11
|
-
const boundsHeight = height - margin.top - margin.bottom -
|
|
12
|
-
return { boundsWidth, boundsHeight, legendHeight };
|
|
9
|
+
const boundsHeight = height - margin.top - margin.bottom - legend.height - titleHeight - xAxisTitleHeight;
|
|
10
|
+
return { boundsWidth, boundsHeight, legendHeight: legend.height };
|
|
13
11
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { select, max } from 'd3';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
|
-
import { formatAxisTickLabel, getDomainDataYBySeries, isAxisRelatedSeries } from '../../utils';
|
|
4
|
-
import { getHorisontalSvgTextDimensions } from './utils';
|
|
3
|
+
import { formatAxisTickLabel, getDomainDataYBySeries, getHorisontalSvgTextHeight, isAxisRelatedSeries, } from '../../utils';
|
|
5
4
|
const AXIS_WIDTH = 1;
|
|
6
5
|
const getAxisLabelMaxWidth = (args) => {
|
|
7
6
|
const { axis, series } = args;
|
|
@@ -9,8 +8,8 @@ const getAxisLabelMaxWidth = (args) => {
|
|
|
9
8
|
let width = 0;
|
|
10
9
|
switch (axis.type) {
|
|
11
10
|
case 'category': {
|
|
12
|
-
const
|
|
13
|
-
maxDomainValue = [...
|
|
11
|
+
const yCategories = get(axis, 'categories', []);
|
|
12
|
+
maxDomainValue = [...yCategories].sort((c1, c2) => c2.length - c1.length)[0];
|
|
14
13
|
break;
|
|
15
14
|
}
|
|
16
15
|
case 'datetime': {
|
|
@@ -53,14 +52,14 @@ export const getPreparedChart = (args) => {
|
|
|
53
52
|
if (hasAxisRelatedSeries) {
|
|
54
53
|
marginBottom +=
|
|
55
54
|
preparedXAxis.labels.padding +
|
|
56
|
-
|
|
55
|
+
getHorisontalSvgTextHeight({ text: 'Tmp', style: preparedXAxis.labels.style });
|
|
57
56
|
marginLeft +=
|
|
58
57
|
AXIS_WIDTH +
|
|
59
58
|
preparedY1Axis.labels.padding +
|
|
60
59
|
getAxisLabelMaxWidth({ axis: preparedY1Axis, series: series.data }) +
|
|
61
60
|
(preparedY1Axis.title.height || 0);
|
|
62
61
|
marginTop +=
|
|
63
|
-
|
|
62
|
+
getHorisontalSvgTextHeight({ text: 'Tmp', style: preparedY1Axis.labels.style }) / 2;
|
|
64
63
|
marginRight += getAxisLabelMaxWidth({ axis: preparedXAxis, series: series.data }) / 2;
|
|
65
64
|
}
|
|
66
65
|
return {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
const LEGEND_LINE_HEIGHT = 15;
|
|
1
2
|
export const getPreparedLegend = (args) => {
|
|
2
3
|
const { legend, series } = args;
|
|
3
|
-
const enabled = legend === null || legend === void 0 ? void 0 : legend.enabled;
|
|
4
|
+
const enabled = typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : series.data.length > 1;
|
|
5
|
+
const height = enabled ? LEGEND_LINE_HEIGHT : 0;
|
|
4
6
|
return {
|
|
5
|
-
|
|
7
|
+
align: (legend === null || legend === void 0 ? void 0 : legend.align) || 'center',
|
|
8
|
+
enabled,
|
|
9
|
+
itemDistance: (legend === null || legend === void 0 ? void 0 : legend.itemDistance) || 20,
|
|
10
|
+
height,
|
|
6
11
|
};
|
|
7
12
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import {
|
|
2
|
+
import { getHorisontalSvgTextHeight } from '../../utils';
|
|
3
3
|
const DEFAULT_TITLE_FONT_SIZE = '15px';
|
|
4
4
|
const TITLE_PADDINGS = 8 * 2;
|
|
5
5
|
export const getPreparedTitle = ({ title, }) => {
|
|
@@ -8,7 +8,7 @@ export const getPreparedTitle = ({ title, }) => {
|
|
|
8
8
|
fontSize: get(title, 'style.fontSize', DEFAULT_TITLE_FONT_SIZE),
|
|
9
9
|
};
|
|
10
10
|
const titleHeight = titleText
|
|
11
|
-
?
|
|
11
|
+
? getHorisontalSvgTextHeight({ text: titleText, style: titleStyle }) + TITLE_PADDINGS
|
|
12
12
|
: 0;
|
|
13
13
|
const preparedTitle = titleText
|
|
14
14
|
? { text: titleText, style: titleStyle, height: titleHeight }
|
|
@@ -5,7 +5,9 @@ type PreparedAxisLabels = Omit<ChartKitWidgetAxisLabels, 'enabled' | 'padding' |
|
|
|
5
5
|
export type PreparedChart = {
|
|
6
6
|
margin: ChartMargin;
|
|
7
7
|
};
|
|
8
|
-
export type PreparedLegend = Required<ChartKitWidgetLegend
|
|
8
|
+
export type PreparedLegend = Required<ChartKitWidgetLegend> & {
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
9
11
|
export type PreparedAxis = Omit<ChartKitWidgetAxis, 'type' | 'labels'> & {
|
|
10
12
|
type: ChartKitWidgetAxisType;
|
|
11
13
|
labels: PreparedAxisLabels;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from '
|
|
3
|
-
import {
|
|
2
|
+
import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from '../../constants';
|
|
3
|
+
import { getHorisontalSvgTextHeight } from '../../utils';
|
|
4
4
|
export const getPreparedXAxis = ({ xAxis }) => {
|
|
5
5
|
const titleText = get(xAxis, 'title.text', '');
|
|
6
6
|
const titleStyle = {
|
|
@@ -21,7 +21,7 @@ export const getPreparedXAxis = ({ xAxis }) => {
|
|
|
21
21
|
text: titleText,
|
|
22
22
|
style: titleStyle,
|
|
23
23
|
height: titleText
|
|
24
|
-
?
|
|
24
|
+
? getHorisontalSvgTextHeight({ text: titleText, style: titleStyle })
|
|
25
25
|
: 0,
|
|
26
26
|
},
|
|
27
27
|
min: get(xAxis, 'min'),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from '
|
|
3
|
-
import {
|
|
2
|
+
import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from '../../constants';
|
|
3
|
+
import { getHorisontalSvgTextHeight } from '../../utils';
|
|
4
4
|
export const getPreparedYAxis = ({ yAxis }) => {
|
|
5
5
|
// FIXME: add support for n axises
|
|
6
6
|
const yAxis1 = yAxis === null || yAxis === void 0 ? void 0 : yAxis[0];
|
|
@@ -26,7 +26,7 @@ export const getPreparedYAxis = ({ yAxis }) => {
|
|
|
26
26
|
text: y1TitleText,
|
|
27
27
|
style: y1TitleStyle,
|
|
28
28
|
height: y1TitleText
|
|
29
|
-
?
|
|
29
|
+
? getHorisontalSvgTextHeight({ text: y1TitleText, style: y1TitleStyle })
|
|
30
30
|
: 0,
|
|
31
31
|
},
|
|
32
32
|
min: get(yAxis1, 'min'),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DEFAULT_LEGEND_SYMBOL_SIZE = 10;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_LEGEND_SYMBOL_SIZE = 10;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { ChartKitWidgetData } from '../../../../../types/widget-data';
|
|
2
|
+
import { PreparedLegend } from '../useChartOptions/types';
|
|
3
|
+
import { PreparedSeries } from './types';
|
|
4
|
+
export type OnLegendItemClick = (data: {
|
|
4
5
|
name: string;
|
|
5
|
-
|
|
6
|
-
};
|
|
6
|
+
metaKey: boolean;
|
|
7
|
+
}) => void;
|
|
7
8
|
type Args = {
|
|
8
|
-
|
|
9
|
-
series:
|
|
9
|
+
legend: PreparedLegend;
|
|
10
|
+
series: ChartKitWidgetData['series'];
|
|
10
11
|
};
|
|
11
12
|
export declare const useSeries: (args: Args) => {
|
|
12
|
-
|
|
13
|
+
preparedSeries: PreparedSeries[];
|
|
14
|
+
handleLegendItemClick: OnLegendItemClick;
|
|
13
15
|
};
|
|
14
16
|
export {};
|
|
@@ -1,61 +1,52 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import cloneDeep from 'lodash/cloneDeep';
|
|
3
|
-
import get from 'lodash/get';
|
|
4
2
|
import { scaleOrdinal } from 'd3';
|
|
5
3
|
import { DEFAULT_PALETTE } from '../../constants';
|
|
6
|
-
import { getSeriesNames
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const preparedSeries = cloneDeep(series);
|
|
10
|
-
const legendEnabled = get(preparedSeries, 'legend.enabled', true);
|
|
11
|
-
const defaultVisible = get(preparedSeries, 'visible', true);
|
|
12
|
-
const name = 'name' in series && series.name ? series.name : '';
|
|
13
|
-
const color = 'color' in series && series.color ? series.color : colorScale(name);
|
|
14
|
-
preparedSeries.color = color;
|
|
15
|
-
preparedSeries.name = name;
|
|
16
|
-
preparedSeries.visible = legendEnabled ? activeLegendItems.includes(name) : defaultVisible;
|
|
17
|
-
return preparedSeries;
|
|
18
|
-
};
|
|
19
|
-
const preparePieSeries = (args) => {
|
|
20
|
-
const { activeLegendItems, series } = args;
|
|
21
|
-
const preparedSeries = cloneDeep(series);
|
|
22
|
-
const legendEnabled = get(preparedSeries, 'legend.enabled', true);
|
|
23
|
-
const dataNames = series.data.map((d) => d.name);
|
|
24
|
-
const colorScale = scaleOrdinal(dataNames, DEFAULT_PALETTE);
|
|
25
|
-
preparedSeries.data = preparedSeries.data.map((d) => {
|
|
26
|
-
const defaultVisible = get(d, 'visible', true);
|
|
27
|
-
d.color = d.color || colorScale(d.name);
|
|
28
|
-
d.visible = legendEnabled ? activeLegendItems.includes(d.name) : defaultVisible;
|
|
29
|
-
return d;
|
|
30
|
-
});
|
|
31
|
-
// Not axis related series manages their own data visibility inside their data
|
|
32
|
-
preparedSeries.visible = true;
|
|
33
|
-
return preparedSeries;
|
|
34
|
-
};
|
|
35
|
-
const prepareNotAxisRelatedSeries = (args) => {
|
|
36
|
-
const { activeLegendItems, series } = args;
|
|
37
|
-
switch (series.type) {
|
|
38
|
-
case 'pie': {
|
|
39
|
-
return preparePieSeries({ activeLegendItems, series });
|
|
40
|
-
}
|
|
41
|
-
default: {
|
|
42
|
-
throw new Error(`Series type ${series.type} does not support data preparation for series that do not support the presence of axes`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
};
|
|
4
|
+
import { getSeriesNames } from '../../utils';
|
|
5
|
+
import { getActiveLegendItems, getAllLegendItems } from './utils';
|
|
6
|
+
import { prepareSeries } from './prepareSeries';
|
|
46
7
|
export const useSeries = (args) => {
|
|
47
|
-
const {
|
|
48
|
-
const
|
|
8
|
+
const { series: { data: series }, legend, } = args;
|
|
9
|
+
const preparedSeries = React.useMemo(() => {
|
|
49
10
|
const seriesNames = getSeriesNames(series);
|
|
50
11
|
const colorScale = scaleOrdinal(seriesNames, DEFAULT_PALETTE);
|
|
51
|
-
return series.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
12
|
+
return series.reduce((acc, singleSeries) => {
|
|
13
|
+
acc.push(...prepareSeries({
|
|
14
|
+
series: singleSeries,
|
|
15
|
+
legend,
|
|
16
|
+
colorScale,
|
|
17
|
+
}));
|
|
18
|
+
return acc;
|
|
19
|
+
}, []);
|
|
20
|
+
}, [series, legend]);
|
|
21
|
+
const [activeLegendItems, setActiveLegendItems] = React.useState(getActiveLegendItems(preparedSeries));
|
|
22
|
+
const chartSeries = React.useMemo(() => {
|
|
23
|
+
return preparedSeries.map((singleSeries) => {
|
|
24
|
+
if (singleSeries.legend.enabled) {
|
|
25
|
+
return Object.assign(Object.assign({}, singleSeries), { visible: activeLegendItems.includes(singleSeries.name) });
|
|
26
|
+
}
|
|
27
|
+
return singleSeries;
|
|
58
28
|
});
|
|
59
|
-
}, [
|
|
60
|
-
|
|
29
|
+
}, [preparedSeries, activeLegendItems]);
|
|
30
|
+
// FIXME: remove effect. It initiates extra rerender
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
setActiveLegendItems(getActiveLegendItems(preparedSeries));
|
|
33
|
+
}, [preparedSeries]);
|
|
34
|
+
const handleLegendItemClick = React.useCallback(({ name, metaKey }) => {
|
|
35
|
+
const onlyItemSelected = activeLegendItems.length === 1 && activeLegendItems.includes(name);
|
|
36
|
+
let nextActiveLegendItems;
|
|
37
|
+
if (metaKey && activeLegendItems.includes(name)) {
|
|
38
|
+
nextActiveLegendItems = activeLegendItems.filter((item) => item !== name);
|
|
39
|
+
}
|
|
40
|
+
else if (metaKey && !activeLegendItems.includes(name)) {
|
|
41
|
+
nextActiveLegendItems = activeLegendItems.concat(name);
|
|
42
|
+
}
|
|
43
|
+
else if (onlyItemSelected) {
|
|
44
|
+
nextActiveLegendItems = getAllLegendItems(preparedSeries);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
nextActiveLegendItems = [name];
|
|
48
|
+
}
|
|
49
|
+
setActiveLegendItems(nextActiveLegendItems);
|
|
50
|
+
}, [preparedSeries, activeLegendItems]);
|
|
51
|
+
return { preparedSeries: chartSeries, handleLegendItemClick };
|
|
61
52
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScaleOrdinal } from 'd3';
|
|
2
|
+
import type { ChartKitWidgetSeries } from '../../../../../types/widget-data';
|
|
3
|
+
import type { PreparedLegend } from '../useChartOptions/types';
|
|
4
|
+
import type { PreparedSeries } from './types';
|
|
5
|
+
export declare function prepareSeries(args: {
|
|
6
|
+
series: ChartKitWidgetSeries;
|
|
7
|
+
legend: PreparedLegend;
|
|
8
|
+
colorScale: ScaleOrdinal<string, string>;
|
|
9
|
+
}): PreparedSeries[];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { scaleOrdinal } from 'd3';
|
|
2
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
3
|
+
import get from 'lodash/get';
|
|
4
|
+
import { DEFAULT_PALETTE } from '../../constants';
|
|
5
|
+
import { DEFAULT_LEGEND_SYMBOL_SIZE } from './constants';
|
|
6
|
+
import { getRandomCKId } from '../../../../../utils';
|
|
7
|
+
function prepareLegendSymbol(series) {
|
|
8
|
+
var _a;
|
|
9
|
+
switch (series.type) {
|
|
10
|
+
default: {
|
|
11
|
+
const symbolOptions = ((_a = series.legend) === null || _a === void 0 ? void 0 : _a.symbol) || {};
|
|
12
|
+
const symbolHeight = (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.height) || DEFAULT_LEGEND_SYMBOL_SIZE;
|
|
13
|
+
return {
|
|
14
|
+
shape: 'rect',
|
|
15
|
+
width: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.width) || DEFAULT_LEGEND_SYMBOL_SIZE,
|
|
16
|
+
height: symbolHeight,
|
|
17
|
+
radius: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.radius) || symbolHeight / 2,
|
|
18
|
+
padding: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.padding) || 5,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function prepareAxisRelatedSeries(args) {
|
|
24
|
+
const { colorScale, series, legend } = args;
|
|
25
|
+
const preparedSeries = cloneDeep(series);
|
|
26
|
+
const name = 'name' in series && series.name ? series.name : '';
|
|
27
|
+
const color = 'color' in series && series.color ? series.color : colorScale(name);
|
|
28
|
+
preparedSeries.color = color;
|
|
29
|
+
preparedSeries.name = name;
|
|
30
|
+
preparedSeries.visible = get(preparedSeries, 'visible', true);
|
|
31
|
+
preparedSeries.legend = {
|
|
32
|
+
enabled: get(preparedSeries, 'legend.enabled', legend.enabled),
|
|
33
|
+
symbol: prepareLegendSymbol(series),
|
|
34
|
+
};
|
|
35
|
+
return [preparedSeries];
|
|
36
|
+
}
|
|
37
|
+
function preparePieSeries(args) {
|
|
38
|
+
const { series, legend } = args;
|
|
39
|
+
const dataNames = series.data.map((d) => d.name);
|
|
40
|
+
const colorScale = scaleOrdinal(dataNames, DEFAULT_PALETTE);
|
|
41
|
+
const stackId = getRandomCKId();
|
|
42
|
+
const preparedSeries = series.data.map((dataItem) => {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
const result = {
|
|
45
|
+
type: 'pie',
|
|
46
|
+
data: dataItem.value,
|
|
47
|
+
dataLabels: {
|
|
48
|
+
enabled: get(series, 'dataLabels.enabled', true),
|
|
49
|
+
},
|
|
50
|
+
label: dataItem.label,
|
|
51
|
+
visible: typeof dataItem.visible === 'boolean' ? dataItem.visible : true,
|
|
52
|
+
name: dataItem.name,
|
|
53
|
+
color: dataItem.color || colorScale(dataItem.name),
|
|
54
|
+
legend: {
|
|
55
|
+
enabled: get(series, 'legend.enabled', legend.enabled),
|
|
56
|
+
symbol: prepareLegendSymbol(series),
|
|
57
|
+
},
|
|
58
|
+
center: series.center || ['50%', '50%'],
|
|
59
|
+
borderColor: series.borderColor || '',
|
|
60
|
+
borderRadius: (_a = series.borderRadius) !== null && _a !== void 0 ? _a : 0,
|
|
61
|
+
borderWidth: (_b = series.borderWidth) !== null && _b !== void 0 ? _b : 1,
|
|
62
|
+
radius: series.radius || '100%',
|
|
63
|
+
innerRadius: series.innerRadius || 0,
|
|
64
|
+
stackId,
|
|
65
|
+
};
|
|
66
|
+
return result;
|
|
67
|
+
});
|
|
68
|
+
return preparedSeries;
|
|
69
|
+
}
|
|
70
|
+
export function prepareSeries(args) {
|
|
71
|
+
const { series, legend, colorScale } = args;
|
|
72
|
+
switch (series.type) {
|
|
73
|
+
case 'pie': {
|
|
74
|
+
return preparePieSeries({ series, legend });
|
|
75
|
+
}
|
|
76
|
+
case 'scatter':
|
|
77
|
+
case 'bar-x': {
|
|
78
|
+
return prepareAxisRelatedSeries({ series, legend, colorScale });
|
|
79
|
+
}
|
|
80
|
+
default: {
|
|
81
|
+
const seriesType = get(series, 'type');
|
|
82
|
+
throw new Error(`Series type ${seriesType} does not support data preparation for series that do not support the presence of axes`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|