@gravity-ui/chartkit 3.4.3 → 3.6.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/D3Widget.js +3 -3
- package/build/plugins/d3/renderer/components/Chart.d.ts +3 -1
- package/build/plugins/d3/renderer/components/Chart.js +13 -12
- package/build/plugins/d3/renderer/components/Legend.d.ts +3 -2
- package/build/plugins/d3/renderer/components/Legend.js +101 -61
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +12 -0
- package/build/plugins/d3/renderer/components/styles.css +4 -13
- 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 +21 -15
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.d.ts +3 -1
- 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 +43 -14
- 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.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x.js +7 -3
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +7 -2
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +26 -2
- package/build/plugins/d3/renderer/hooks/useShapes/pie.d.ts +13 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie.js +126 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.d.ts +2 -1
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +6 -3
- package/build/plugins/d3/renderer/hooks/useShapes/styles.css +22 -0
- package/build/plugins/d3/renderer/utils/index.d.ts +49 -7
- package/build/plugins/d3/renderer/utils/index.js +50 -18
- package/build/plugins/d3/renderer/utils/math.d.ts +23 -0
- package/build/plugins/d3/renderer/utils/math.js +43 -0
- package/build/plugins/highcharts/renderer/components/HighchartsComponent.js +1 -1
- package/build/plugins/highcharts/renderer/components/HighchartsReact.d.ts +18 -0
- package/build/plugins/highcharts/renderer/components/HighchartsReact.js +46 -0
- package/build/plugins/index.d.ts +1 -0
- package/build/plugins/index.js +1 -0
- package/build/types/widget-data/bar-x.d.ts +6 -1
- package/build/types/widget-data/base.d.ts +12 -0
- package/build/types/widget-data/legend.d.ts +32 -0
- package/build/types/widget-data/pie.d.ts +37 -1
- package/build/types/widget-data/scatter.d.ts +6 -1
- package/package.json +1 -3
- 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 -28
|
@@ -1,23 +1,52 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import clone from 'lodash/clone';
|
|
3
2
|
import { scaleOrdinal } from 'd3';
|
|
4
3
|
import { DEFAULT_PALETTE } from '../../constants';
|
|
5
4
|
import { getSeriesNames } from '../../utils';
|
|
5
|
+
import { getActiveLegendItems, getAllLegendItems } from './utils';
|
|
6
|
+
import { prepareSeries } from './prepareSeries';
|
|
6
7
|
export const useSeries = (args) => {
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
const chartSeries = React.useMemo(() => {
|
|
8
|
+
const { series: { data: series }, legend, } = args;
|
|
9
|
+
const preparedSeries = React.useMemo(() => {
|
|
10
10
|
const seriesNames = getSeriesNames(series);
|
|
11
11
|
const colorScale = scaleOrdinal(seriesNames, DEFAULT_PALETTE);
|
|
12
|
-
return series.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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;
|
|
20
28
|
});
|
|
21
|
-
}, [
|
|
22
|
-
|
|
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 };
|
|
23
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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BarXSeries, BarXSeriesData, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData } from '../../../../../types/widget-data';
|
|
2
|
+
export type RectLegendSymbol = {
|
|
3
|
+
shape: 'rect';
|
|
4
|
+
} & Required<RectLegendSymbolOptions>;
|
|
5
|
+
export type PreparedLegendSymbol = RectLegendSymbol;
|
|
6
|
+
type BasePreparedSeries = {
|
|
7
|
+
color: string;
|
|
8
|
+
name: string;
|
|
9
|
+
visible: boolean;
|
|
10
|
+
legend: {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
symbol: PreparedLegendSymbol;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type PreparedScatterSeries = {
|
|
16
|
+
type: ScatterSeries['type'];
|
|
17
|
+
data: ScatterSeriesData[];
|
|
18
|
+
} & BasePreparedSeries;
|
|
19
|
+
export type PreparedBarXSeries = {
|
|
20
|
+
type: BarXSeries['type'];
|
|
21
|
+
data: BarXSeriesData[];
|
|
22
|
+
} & BasePreparedSeries;
|
|
23
|
+
export type PreparedPieSeries = BasePreparedSeries & Required<Omit<PieSeries, 'data'>> & {
|
|
24
|
+
data: PieSeriesData['value'];
|
|
25
|
+
stackId: string;
|
|
26
|
+
label?: PieSeriesData['label'];
|
|
27
|
+
};
|
|
28
|
+
export type PreparedSeries = PreparedScatterSeries | PreparedBarXSeries | PreparedPieSeries;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const getActiveLegendItems = (series) => {
|
|
2
|
+
return series.reduce((acc, s) => {
|
|
3
|
+
if (s.legend.enabled && s.visible) {
|
|
4
|
+
acc.push(s.name);
|
|
5
|
+
}
|
|
6
|
+
return acc;
|
|
7
|
+
}, []);
|
|
8
|
+
};
|
|
9
|
+
export const getAllLegendItems = (series) => {
|
|
10
|
+
return series.map((s) => s.name);
|
|
11
|
+
};
|
|
@@ -4,6 +4,8 @@ import { ChartScale } from '../useAxisScales';
|
|
|
4
4
|
import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
5
5
|
import { BarXSeries } from '../../../../../types/widget-data';
|
|
6
6
|
type Args = {
|
|
7
|
+
top: number;
|
|
8
|
+
left: number;
|
|
7
9
|
series: BarXSeries[];
|
|
8
10
|
xAxis: ChartOptions['xAxis'];
|
|
9
11
|
xScale: ChartScale;
|
|
@@ -11,6 +13,7 @@ type Args = {
|
|
|
11
13
|
yScale: ChartScale;
|
|
12
14
|
onSeriesMouseMove?: OnSeriesMouseMove;
|
|
13
15
|
onSeriesMouseLeave?: OnSeriesMouseLeave;
|
|
16
|
+
svgContainer: SVGSVGElement | null;
|
|
14
17
|
};
|
|
15
18
|
export declare function prepareBarXSeries(args: Args): React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
|
16
19
|
export {};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { block } from '../../../../../utils/cn';
|
|
3
|
+
import { pointer } from 'd3';
|
|
4
|
+
import { getRandomCKId } from '../../../../../utils';
|
|
3
5
|
const DEFAULT_BAR_RECT_WIDTH = 50;
|
|
4
6
|
const DEFAULT_LINEAR_BAR_RECT_WIDTH = 20;
|
|
5
7
|
const MIN_RECT_GAP = 1;
|
|
@@ -47,11 +49,11 @@ function minDiff(arr) {
|
|
|
47
49
|
return result;
|
|
48
50
|
}
|
|
49
51
|
export function prepareBarXSeries(args) {
|
|
50
|
-
const { series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave } = args;
|
|
52
|
+
const { top, left, series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave, svgContainer, } = args;
|
|
51
53
|
const seriesData = series.map(({ data }) => data).flat(2);
|
|
52
54
|
const minPointDistance = minDiff(seriesData.map((item) => Number(item.x)));
|
|
53
55
|
return series.reduce((result, item) => {
|
|
54
|
-
const randomKey =
|
|
56
|
+
const randomKey = getRandomCKId();
|
|
55
57
|
item.data.forEach((point, i) => {
|
|
56
58
|
const rectProps = getRectProperties({
|
|
57
59
|
point,
|
|
@@ -61,12 +63,14 @@ export function prepareBarXSeries(args) {
|
|
|
61
63
|
yScale,
|
|
62
64
|
minPointDistance,
|
|
63
65
|
});
|
|
64
|
-
result.push(React.createElement("rect", Object.assign({ key: `${i}-${randomKey}`, className: b('rect'), fill: item.color }, rectProps, { onMouseMove: function () {
|
|
66
|
+
result.push(React.createElement("rect", Object.assign({ key: `${i}-${randomKey}`, className: b('rect'), fill: item.color }, rectProps, { onMouseMove: function (e) {
|
|
67
|
+
const [x, y] = pointer(e, svgContainer);
|
|
65
68
|
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
66
69
|
hovered: {
|
|
67
70
|
data: point,
|
|
68
71
|
series: item,
|
|
69
72
|
},
|
|
73
|
+
pointerPosition: [x - left, y - top],
|
|
70
74
|
});
|
|
71
75
|
}, onMouseLeave: onSeriesMouseLeave })));
|
|
72
76
|
});
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ChartOptions } from '../useChartOptions/types';
|
|
3
3
|
import type { ChartScale } from '../useAxisScales';
|
|
4
|
-
import type {
|
|
4
|
+
import type { PreparedSeries } from '../';
|
|
5
5
|
import type { OnSeriesMouseMove, OnSeriesMouseLeave } from '../useTooltip/types';
|
|
6
|
+
import './styles.css';
|
|
6
7
|
type Args = {
|
|
7
|
-
|
|
8
|
+
top: number;
|
|
9
|
+
left: number;
|
|
10
|
+
boundsWidth: number;
|
|
11
|
+
boundsHeight: number;
|
|
12
|
+
series: PreparedSeries[];
|
|
8
13
|
xAxis: ChartOptions['xAxis'];
|
|
9
14
|
yAxis: ChartOptions['yAxis'];
|
|
10
15
|
svgContainer: SVGSVGElement | null;
|
|
@@ -3,8 +3,10 @@ import { group } from 'd3';
|
|
|
3
3
|
import { getOnlyVisibleSeries } from '../../utils';
|
|
4
4
|
import { prepareBarXSeries } from './bar-x';
|
|
5
5
|
import { prepareScatterSeries } from './scatter';
|
|
6
|
+
import { PieSeriesComponent } from './pie';
|
|
7
|
+
import './styles.css';
|
|
6
8
|
export const useShapes = (args) => {
|
|
7
|
-
const { series, xAxis, xScale, yAxis, yScale, svgContainer, onSeriesMouseMove, onSeriesMouseLeave, } = args;
|
|
9
|
+
const { top, left, boundsWidth, boundsHeight, series, xAxis, xScale, yAxis, yScale, svgContainer, onSeriesMouseMove, onSeriesMouseLeave, } = args;
|
|
8
10
|
const shapes = React.useMemo(() => {
|
|
9
11
|
const visibleSeries = getOnlyVisibleSeries(series);
|
|
10
12
|
const groupedSeries = group(visibleSeries, (item) => item.type);
|
|
@@ -14,6 +16,8 @@ export const useShapes = (args) => {
|
|
|
14
16
|
case 'bar-x': {
|
|
15
17
|
if (xScale && yScale) {
|
|
16
18
|
acc.push(...prepareBarXSeries({
|
|
19
|
+
top,
|
|
20
|
+
left,
|
|
17
21
|
series: chartSeries,
|
|
18
22
|
xAxis,
|
|
19
23
|
xScale,
|
|
@@ -21,6 +25,7 @@ export const useShapes = (args) => {
|
|
|
21
25
|
yScale,
|
|
22
26
|
onSeriesMouseMove,
|
|
23
27
|
onSeriesMouseLeave,
|
|
28
|
+
svgContainer,
|
|
24
29
|
}));
|
|
25
30
|
}
|
|
26
31
|
break;
|
|
@@ -28,6 +33,8 @@ export const useShapes = (args) => {
|
|
|
28
33
|
case 'scatter': {
|
|
29
34
|
if (xScale && yScale) {
|
|
30
35
|
acc.push(...prepareScatterSeries({
|
|
36
|
+
top,
|
|
37
|
+
left,
|
|
31
38
|
series: chartSeries,
|
|
32
39
|
xAxis,
|
|
33
40
|
xScale,
|
|
@@ -40,9 +47,26 @@ export const useShapes = (args) => {
|
|
|
40
47
|
}
|
|
41
48
|
break;
|
|
42
49
|
}
|
|
50
|
+
case 'pie': {
|
|
51
|
+
const groupedPieSeries = group(chartSeries, (item) => item.stackId);
|
|
52
|
+
acc.push(...Array.from(groupedPieSeries).map(([key, pieSeries]) => {
|
|
53
|
+
return (React.createElement(PieSeriesComponent, { key: `pie-${key}`, boundsWidth: boundsWidth, boundsHeight: boundsHeight, series: pieSeries, onSeriesMouseMove: onSeriesMouseMove, onSeriesMouseLeave: onSeriesMouseLeave, svgContainer: svgContainer }));
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
43
56
|
}
|
|
44
57
|
return acc;
|
|
45
58
|
}, []);
|
|
46
|
-
}, [
|
|
59
|
+
}, [
|
|
60
|
+
boundsWidth,
|
|
61
|
+
boundsHeight,
|
|
62
|
+
series,
|
|
63
|
+
xAxis,
|
|
64
|
+
xScale,
|
|
65
|
+
yAxis,
|
|
66
|
+
yScale,
|
|
67
|
+
svgContainer,
|
|
68
|
+
onSeriesMouseMove,
|
|
69
|
+
onSeriesMouseLeave,
|
|
70
|
+
]);
|
|
47
71
|
return { shapes };
|
|
48
72
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
3
|
+
import { PreparedPieSeries } from '../useSeries/types';
|
|
4
|
+
type PreparePieSeriesArgs = {
|
|
5
|
+
boundsWidth: number;
|
|
6
|
+
boundsHeight: number;
|
|
7
|
+
series: PreparedPieSeries[];
|
|
8
|
+
svgContainer: SVGSVGElement | null;
|
|
9
|
+
onSeriesMouseMove?: OnSeriesMouseMove;
|
|
10
|
+
onSeriesMouseLeave?: OnSeriesMouseLeave;
|
|
11
|
+
};
|
|
12
|
+
export declare function PieSeriesComponent(args: PreparePieSeriesArgs): React.JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { arc, pie, select } from 'd3';
|
|
3
|
+
import { block } from '../../../../../utils/cn';
|
|
4
|
+
import { calculateNumericProperty, getHorisontalSvgTextHeight } from '../../utils';
|
|
5
|
+
const b = block('d3-pie');
|
|
6
|
+
const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
7
|
+
var _a, _b;
|
|
8
|
+
const defaultX = boundsWidth * 0.5;
|
|
9
|
+
const defaultY = boundsHeight * 0.5;
|
|
10
|
+
if (!center) {
|
|
11
|
+
return [defaultX, defaultY];
|
|
12
|
+
}
|
|
13
|
+
const [x, y] = center;
|
|
14
|
+
const resultX = (_a = calculateNumericProperty({ value: x, base: boundsWidth })) !== null && _a !== void 0 ? _a : defaultX;
|
|
15
|
+
const resultY = (_b = calculateNumericProperty({ value: y, base: boundsHeight })) !== null && _b !== void 0 ? _b : defaultY;
|
|
16
|
+
return [resultX, resultY];
|
|
17
|
+
};
|
|
18
|
+
export function PieSeriesComponent(args) {
|
|
19
|
+
var _a;
|
|
20
|
+
const { boundsWidth, boundsHeight, series, onSeriesMouseMove, onSeriesMouseLeave, svgContainer } = args;
|
|
21
|
+
const ref = React.useRef(null);
|
|
22
|
+
const [x, y] = getCenter(boundsWidth, boundsHeight, (_a = series[0]) === null || _a === void 0 ? void 0 : _a.center);
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
25
|
+
if (!ref.current) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const svgElement = select(ref.current);
|
|
29
|
+
const isLabelsEnabled = (_b = (_a = series[0]) === null || _a === void 0 ? void 0 : _a.dataLabels) === null || _b === void 0 ? void 0 : _b.enabled;
|
|
30
|
+
let radiusRelatedToChart = Math.min(boundsWidth, boundsHeight) / 2;
|
|
31
|
+
if (isLabelsEnabled) {
|
|
32
|
+
// To have enough space for labels
|
|
33
|
+
radiusRelatedToChart *= 0.9;
|
|
34
|
+
}
|
|
35
|
+
let radius = (_d = calculateNumericProperty({ value: (_c = series[0]) === null || _c === void 0 ? void 0 : _c.radius, base: radiusRelatedToChart })) !== null && _d !== void 0 ? _d : radiusRelatedToChart;
|
|
36
|
+
const labelsArcRadius = ((_e = series[0]) === null || _e === void 0 ? void 0 : _e.radius) ? radius : radiusRelatedToChart;
|
|
37
|
+
if (isLabelsEnabled) {
|
|
38
|
+
// To have enough space for labels lines
|
|
39
|
+
radius *= 0.9;
|
|
40
|
+
}
|
|
41
|
+
const innerRadius = (_f = calculateNumericProperty({ value: series[0].innerRadius, base: radius })) !== null && _f !== void 0 ? _f : 0;
|
|
42
|
+
const pieGenerator = pie().value((d) => d.data);
|
|
43
|
+
const visibleData = series.filter((d) => d.visible);
|
|
44
|
+
const dataReady = pieGenerator(visibleData);
|
|
45
|
+
const arcGenerator = arc()
|
|
46
|
+
.innerRadius(innerRadius)
|
|
47
|
+
.outerRadius(radius)
|
|
48
|
+
.cornerRadius((d) => d.data.borderRadius);
|
|
49
|
+
svgElement.selectAll('*').remove();
|
|
50
|
+
svgElement
|
|
51
|
+
.selectAll('allSlices')
|
|
52
|
+
.data(dataReady)
|
|
53
|
+
.enter()
|
|
54
|
+
.append('path')
|
|
55
|
+
.attr('d', arcGenerator)
|
|
56
|
+
.attr('class', b('segment'))
|
|
57
|
+
.attr('fill', (d) => d.data.color || '')
|
|
58
|
+
.style('stroke', ((_g = series[0]) === null || _g === void 0 ? void 0 : _g.borderColor) || '')
|
|
59
|
+
.style('stroke-width', (_j = (_h = series[0]) === null || _h === void 0 ? void 0 : _h.borderWidth) !== null && _j !== void 0 ? _j : 1);
|
|
60
|
+
if ((_l = (_k = series[0]) === null || _k === void 0 ? void 0 : _k.dataLabels) === null || _l === void 0 ? void 0 : _l.enabled) {
|
|
61
|
+
const labelHeight = getHorisontalSvgTextHeight({ text: 'tmp' });
|
|
62
|
+
const outerArc = arc()
|
|
63
|
+
.innerRadius(labelsArcRadius)
|
|
64
|
+
.outerRadius(labelsArcRadius);
|
|
65
|
+
// Add the polylines between chart and labels
|
|
66
|
+
svgElement
|
|
67
|
+
.selectAll('allPolylines')
|
|
68
|
+
.data(dataReady)
|
|
69
|
+
.enter()
|
|
70
|
+
.append('polyline')
|
|
71
|
+
.attr('stroke', (d) => d.data.color || '')
|
|
72
|
+
.style('fill', 'none')
|
|
73
|
+
.attr('stroke-width', 1)
|
|
74
|
+
.attr('points', (d) => {
|
|
75
|
+
// Line insertion in the slice
|
|
76
|
+
const posA = arcGenerator.centroid(d);
|
|
77
|
+
// Line break: we use the other arc generator that has been built only for that
|
|
78
|
+
const posB = outerArc.centroid(d);
|
|
79
|
+
const posC = outerArc.centroid(d);
|
|
80
|
+
// We need the angle to see if the X position will be at the extreme right or extreme left
|
|
81
|
+
const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
|
|
82
|
+
const result = [posA, posB, posC];
|
|
83
|
+
if (midangle < Math.PI) {
|
|
84
|
+
// polylines located to the right
|
|
85
|
+
const nextCx = radiusRelatedToChart * 0.95;
|
|
86
|
+
if (nextCx > result[1][0]) {
|
|
87
|
+
result[2][0] = nextCx;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
result.splice(2, 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// polylines located to the left
|
|
95
|
+
const nextCx = radiusRelatedToChart * 0.95 * -1;
|
|
96
|
+
if (nextCx < result[1][0]) {
|
|
97
|
+
result[2][0] = nextCx;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
result.splice(2, 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return result.join(' ');
|
|
104
|
+
});
|
|
105
|
+
// Add the polylines between chart and labels
|
|
106
|
+
svgElement
|
|
107
|
+
.selectAll('allLabels')
|
|
108
|
+
.data(dataReady)
|
|
109
|
+
.join('text')
|
|
110
|
+
.text((d) => d.data.label || d.value)
|
|
111
|
+
.attr('class', b('label'))
|
|
112
|
+
.attr('transform', (d) => {
|
|
113
|
+
const pos = outerArc.centroid(d);
|
|
114
|
+
const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
|
|
115
|
+
pos[0] = radiusRelatedToChart * 0.99 * (midangle < Math.PI ? 1 : -1);
|
|
116
|
+
pos[1] += labelHeight / 4;
|
|
117
|
+
return `translate(${pos})`;
|
|
118
|
+
})
|
|
119
|
+
.style('text-anchor', (d) => {
|
|
120
|
+
const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
|
|
121
|
+
return midangle < Math.PI ? 'start' : 'end';
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}, [boundsWidth, boundsHeight, series, onSeriesMouseMove, onSeriesMouseLeave, svgContainer]);
|
|
125
|
+
return React.createElement("g", { ref: ref, className: b(), transform: `translate(${x}, ${y})` });
|
|
126
|
+
}
|
|
@@ -4,6 +4,8 @@ import { ChartScale } from '../useAxisScales';
|
|
|
4
4
|
import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
5
5
|
import { ScatterSeries } from '../../../../../types/widget-data';
|
|
6
6
|
type PrepareScatterSeriesArgs = {
|
|
7
|
+
top: number;
|
|
8
|
+
left: number;
|
|
7
9
|
series: ScatterSeries[];
|
|
8
10
|
xAxis: ChartOptions['xAxis'];
|
|
9
11
|
xScale: ChartScale;
|
|
@@ -12,7 +14,6 @@ type PrepareScatterSeriesArgs = {
|
|
|
12
14
|
svgContainer: SVGSVGElement | null;
|
|
13
15
|
onSeriesMouseMove?: OnSeriesMouseMove;
|
|
14
16
|
onSeriesMouseLeave?: OnSeriesMouseLeave;
|
|
15
|
-
key?: string;
|
|
16
17
|
};
|
|
17
18
|
export declare function prepareScatterSeries(args: PrepareScatterSeriesArgs): React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
|
18
19
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { pointer } from 'd3';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { block } from '../../../../../utils/cn';
|
|
4
|
+
import { getRandomCKId } from '../../../../../utils';
|
|
4
5
|
const b = block('d3-scatter');
|
|
5
6
|
const DEFAULT_SCATTER_POINT_RADIUS = 4;
|
|
6
7
|
const prepareCategoricalScatterData = (data) => {
|
|
@@ -33,9 +34,10 @@ const getPointProperties = (args) => {
|
|
|
33
34
|
return { r, cx, cy };
|
|
34
35
|
};
|
|
35
36
|
export function prepareScatterSeries(args) {
|
|
36
|
-
const { series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave,
|
|
37
|
+
const { top, left, series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave, svgContainer, } = args;
|
|
37
38
|
return series.reduce((result, s) => {
|
|
38
39
|
var _a;
|
|
40
|
+
const randomKey = getRandomCKId();
|
|
39
41
|
const preparedData = xAxis.type === 'category' || ((_a = yAxis[0]) === null || _a === void 0 ? void 0 : _a.type) === 'category'
|
|
40
42
|
? prepareCategoricalScatterData(s.data)
|
|
41
43
|
: prepareLinearScatterData(s.data);
|
|
@@ -47,13 +49,14 @@ export function prepareScatterSeries(args) {
|
|
|
47
49
|
yAxis,
|
|
48
50
|
yScale,
|
|
49
51
|
});
|
|
50
|
-
return (React.createElement("circle", Object.assign({ key: `${i}-${
|
|
52
|
+
return (React.createElement("circle", Object.assign({ key: `${i}-${randomKey}`, className: b('point'), fill: s.color }, pointProps, { onMouseMove: function (e) {
|
|
53
|
+
const [x, y] = pointer(e, svgContainer);
|
|
51
54
|
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
52
55
|
hovered: {
|
|
53
56
|
data: point,
|
|
54
57
|
series: s,
|
|
55
58
|
},
|
|
56
|
-
pointerPosition:
|
|
59
|
+
pointerPosition: [x - left, y - top],
|
|
57
60
|
});
|
|
58
61
|
}, onMouseLeave: onSeriesMouseLeave })));
|
|
59
62
|
}));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.chartkit-d3-scatter__point {
|
|
2
|
+
stroke-width: 1px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.chartkit-d3_hovered .chartkit-d3-scatter__point {
|
|
6
|
+
opacity: 0.5;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.chartkit-d3-scatter__point:hover {
|
|
10
|
+
stroke: #fff;
|
|
11
|
+
opacity: 1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.chartkit-d3-pie__segment {
|
|
15
|
+
stroke: var(--g-color-base-background);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.chartkit-d3-pie__label {
|
|
19
|
+
fill: var(--g-color-text-complementary);
|
|
20
|
+
font-size: 11px;
|
|
21
|
+
font-weight: bold;
|
|
22
|
+
}
|
|
@@ -1,11 +1,41 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { ChartKitWidgetSeries, ChartKitWidgetAxisType, ChartKitWidgetAxisLabels } from '../../../../types/widget-data';
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { AxisDomain } from 'd3';
|
|
2
|
+
import type { BaseTextStyle, ChartKitWidgetSeries, ChartKitWidgetAxisType, ChartKitWidgetAxisLabels } from '../../../../types/widget-data';
|
|
3
|
+
export * from './math';
|
|
4
|
+
type UnknownSeries = {
|
|
5
|
+
type: ChartKitWidgetSeries['type'];
|
|
6
|
+
data: unknown;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Checks whether the series should be drawn with axes.
|
|
10
|
+
*
|
|
11
|
+
* @param series - The series object to check.
|
|
12
|
+
* @returns `true` if the series should be drawn with axes, `false` otherwise.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isAxisRelatedSeries(series: UnknownSeries): boolean;
|
|
15
|
+
export declare function isSeriesWithNumericalXValues(series: UnknownSeries): series is {
|
|
16
|
+
type: ChartKitWidgetSeries['type'];
|
|
17
|
+
data: {
|
|
18
|
+
x: number;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
export declare function isSeriesWithNumericalYValues(series: UnknownSeries): series is {
|
|
22
|
+
type: ChartKitWidgetSeries['type'];
|
|
23
|
+
data: {
|
|
24
|
+
y: number;
|
|
25
|
+
}[];
|
|
26
|
+
};
|
|
27
|
+
export declare function isSeriesWithCategoryValues(series: UnknownSeries): series is {
|
|
28
|
+
type: ChartKitWidgetSeries['type'];
|
|
29
|
+
data: {
|
|
30
|
+
category: string;
|
|
31
|
+
}[];
|
|
32
|
+
};
|
|
33
|
+
export declare const getDomainDataXBySeries: (series: UnknownSeries[]) => number[];
|
|
34
|
+
export declare const getDomainDataYBySeries: (series: UnknownSeries[]) => unknown[];
|
|
6
35
|
export declare const getSeriesNames: (series: ChartKitWidgetSeries[]) => string[];
|
|
7
|
-
export declare const
|
|
8
|
-
|
|
36
|
+
export declare const getOnlyVisibleSeries: <T extends {
|
|
37
|
+
visible: boolean;
|
|
38
|
+
}>(series: T[]) => T[];
|
|
9
39
|
export declare const parseTransformStyle: (style: string | null) => {
|
|
10
40
|
x?: number | undefined;
|
|
11
41
|
y?: number | undefined;
|
|
@@ -16,3 +46,15 @@ export declare const formatAxisTickLabel: (args: {
|
|
|
16
46
|
dateFormat?: ChartKitWidgetAxisLabels['dateFormat'];
|
|
17
47
|
numberFormat?: ChartKitWidgetAxisLabels['numberFormat'];
|
|
18
48
|
}) => string;
|
|
49
|
+
/**
|
|
50
|
+
* Calculates the height of a text element in a horizontal SVG layout.
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} args - The arguments for the function.
|
|
53
|
+
* @param {string} args.text - The text to be measured.
|
|
54
|
+
* @param {Partial<BaseTextStyle>} args.style - Optional style properties for the text element.
|
|
55
|
+
* @return {number} The height of the text element.
|
|
56
|
+
*/
|
|
57
|
+
export declare const getHorisontalSvgTextHeight: (args: {
|
|
58
|
+
text: string;
|
|
59
|
+
style?: Partial<BaseTextStyle>;
|
|
60
|
+
}) => number;
|