@gravity-ui/chartkit 4.10.0 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +3 -3
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +1 -4
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +1 -4
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-pie.d.ts +8 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-pie.js +48 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +1 -38
- package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +20 -3
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +2 -1
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +8 -5
- package/build/plugins/d3/renderer/hooks/useShapes/pie/index.d.ts +12 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/index.js +155 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/prepare-data.d.ts +9 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/prepare-data.js +161 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/types.d.ts +34 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/types.js +1 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/utils.d.ts +4 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/utils.js +15 -0
- package/build/plugins/d3/renderer/hooks/useShapes/styles.css +1 -0
- package/build/plugins/d3/renderer/hooks/useShapes/utils.js +2 -6
- package/build/plugins/d3/renderer/types/index.d.ts +1 -1
- package/build/plugins/d3/renderer/utils/axis-generators/bottom.js +1 -1
- package/build/plugins/d3/renderer/utils/labels.d.ts +4 -1
- package/build/plugins/d3/renderer/utils/labels.js +14 -5
- package/build/plugins/d3/renderer/utils/text.d.ts +3 -3
- package/build/plugins/d3/renderer/utils/text.js +10 -7
- package/build/plugins/highcharts/renderer/components/withSplitPane/withSplitPane.js +4 -3
- package/build/plugins/yagr/renderer/YagrWidget.js +1 -1
- package/build/plugins/yagr/renderer/useWidgetData.d.ts +1 -1
- package/build/plugins/yagr/renderer/utils.js +6 -0
- package/build/plugins/yagr/types.d.ts +1 -1
- package/build/types/widget-data/pie.d.ts +29 -0
- package/build/types/widget-data/tooltip.d.ts +5 -1
- package/package.json +2 -2
- package/build/plugins/d3/renderer/hooks/useShapes/pie.d.ts +0 -13
- package/build/plugins/d3/renderer/hooks/useShapes/pie.js +0 -204
|
@@ -55,12 +55,12 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
|
|
|
55
55
|
xRow))));
|
|
56
56
|
}
|
|
57
57
|
case 'pie': {
|
|
58
|
-
const
|
|
58
|
+
const pieSeriesData = data;
|
|
59
59
|
return (React.createElement("div", { key: id },
|
|
60
60
|
React.createElement("span", null,
|
|
61
|
-
|
|
61
|
+
pieSeriesData.name || pieSeriesData.id,
|
|
62
62
|
"\u00A0"),
|
|
63
|
-
React.createElement("span", null,
|
|
63
|
+
React.createElement("span", null, pieSeriesData.value)));
|
|
64
64
|
}
|
|
65
65
|
default: {
|
|
66
66
|
return null;
|
|
@@ -29,10 +29,7 @@ function getLabelSettings({ axis, series, width, autoRotation = true, }) {
|
|
|
29
29
|
const labelsHeight = rotation
|
|
30
30
|
? getLabelsSize({
|
|
31
31
|
labels,
|
|
32
|
-
style:
|
|
33
|
-
'font-size': axis.labels.style.fontSize,
|
|
34
|
-
'font-weight': axis.labels.style.fontWeight || 'normal',
|
|
35
|
-
},
|
|
32
|
+
style: axis.labels.style,
|
|
36
33
|
rotation,
|
|
37
34
|
}).maxHeight
|
|
38
35
|
: axis.labels.lineHeight;
|
|
@@ -18,10 +18,7 @@ const getAxisLabelMaxWidth = (args) => {
|
|
|
18
18
|
}));
|
|
19
19
|
return getLabelsSize({
|
|
20
20
|
labels,
|
|
21
|
-
style:
|
|
22
|
-
'font-size': axis.labels.style.fontSize,
|
|
23
|
-
'font-weight': axis.labels.style.fontWeight || '',
|
|
24
|
-
},
|
|
21
|
+
style: axis.labels.style,
|
|
25
22
|
rotation: axis.labels.rotation,
|
|
26
23
|
}).maxWidth;
|
|
27
24
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PieSeries } from '../../../../../types';
|
|
2
|
+
import { PreparedLegend, PreparedSeries } from './types';
|
|
3
|
+
type PreparePieSeriesArgs = {
|
|
4
|
+
series: PieSeries;
|
|
5
|
+
legend: PreparedLegend;
|
|
6
|
+
};
|
|
7
|
+
export declare function preparePieSeries(args: PreparePieSeriesArgs): PreparedSeries[];
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { scaleOrdinal } from 'd3';
|
|
2
|
+
import { DEFAULT_PALETTE } from '../../constants';
|
|
3
|
+
import { getRandomCKId } from '../../../../../utils';
|
|
4
|
+
import get from 'lodash/get';
|
|
5
|
+
import { DEFAULT_DATALABELS_PADDING, DEFAULT_DATALABELS_STYLE } from './constants';
|
|
6
|
+
import { prepareLegendSymbol } from './utils';
|
|
7
|
+
export function preparePieSeries(args) {
|
|
8
|
+
const { series, legend } = args;
|
|
9
|
+
const dataNames = series.data.map((d) => d.name);
|
|
10
|
+
const colorScale = scaleOrdinal(dataNames, DEFAULT_PALETTE);
|
|
11
|
+
const stackId = getRandomCKId();
|
|
12
|
+
const preparedSeries = series.data.map((dataItem) => {
|
|
13
|
+
var _a, _b, _c;
|
|
14
|
+
const result = {
|
|
15
|
+
type: 'pie',
|
|
16
|
+
data: dataItem,
|
|
17
|
+
dataLabels: {
|
|
18
|
+
enabled: get(series, 'dataLabels.enabled', true),
|
|
19
|
+
style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.style),
|
|
20
|
+
padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
|
|
21
|
+
allowOverlap: get(series, 'dataLabels.allowOverlap', false),
|
|
22
|
+
connectorPadding: get(series, 'dataLabels.connectorPadding', 5),
|
|
23
|
+
connectorShape: get(series, 'dataLabels.connectorShape', 'polyline'),
|
|
24
|
+
distance: get(series, 'dataLabels.distance', 25),
|
|
25
|
+
connectorCurve: get(series, 'dataLabels.connectorCurve', 'basic'),
|
|
26
|
+
},
|
|
27
|
+
label: dataItem.label,
|
|
28
|
+
value: dataItem.value,
|
|
29
|
+
visible: typeof dataItem.visible === 'boolean' ? dataItem.visible : true,
|
|
30
|
+
name: dataItem.name,
|
|
31
|
+
id: '',
|
|
32
|
+
color: dataItem.color || colorScale(dataItem.name),
|
|
33
|
+
legend: {
|
|
34
|
+
enabled: get(series, 'legend.enabled', legend.enabled),
|
|
35
|
+
symbol: prepareLegendSymbol(series),
|
|
36
|
+
},
|
|
37
|
+
center: series.center || ['50%', '50%'],
|
|
38
|
+
borderColor: series.borderColor || '',
|
|
39
|
+
borderRadius: (_b = series.borderRadius) !== null && _b !== void 0 ? _b : 0,
|
|
40
|
+
borderWidth: (_c = series.borderWidth) !== null && _c !== void 0 ? _c : 1,
|
|
41
|
+
radius: series.radius || '100%',
|
|
42
|
+
innerRadius: series.innerRadius || 0,
|
|
43
|
+
stackId,
|
|
44
|
+
};
|
|
45
|
+
return result;
|
|
46
|
+
});
|
|
47
|
+
return preparedSeries;
|
|
48
|
+
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import cloneDeep from 'lodash/cloneDeep';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
|
-
import { scaleOrdinal } from 'd3';
|
|
4
|
-
import { getRandomCKId } from '../../../../../utils';
|
|
5
|
-
import { DEFAULT_PALETTE } from '../../constants';
|
|
6
3
|
import { prepareLineSeries } from './prepare-line-series';
|
|
7
4
|
import { prepareBarXSeries } from './prepare-bar-x';
|
|
8
5
|
import { prepareBarYSeries } from './prepare-bar-y';
|
|
9
6
|
import { prepareLegendSymbol } from './utils';
|
|
10
7
|
import { ChartKitError } from '../../../../../libs';
|
|
8
|
+
import { preparePieSeries } from './prepare-pie';
|
|
11
9
|
function prepareAxisRelatedSeries(args) {
|
|
12
10
|
const { colorScale, series, legend } = args;
|
|
13
11
|
const preparedSeries = cloneDeep(series);
|
|
@@ -21,41 +19,6 @@ function prepareAxisRelatedSeries(args) {
|
|
|
21
19
|
};
|
|
22
20
|
return [preparedSeries];
|
|
23
21
|
}
|
|
24
|
-
function preparePieSeries(args) {
|
|
25
|
-
const { series, legend } = args;
|
|
26
|
-
const dataNames = series.data.map((d) => d.name);
|
|
27
|
-
const colorScale = scaleOrdinal(dataNames, DEFAULT_PALETTE);
|
|
28
|
-
const stackId = getRandomCKId();
|
|
29
|
-
const preparedSeries = series.data.map((dataItem) => {
|
|
30
|
-
var _a, _b;
|
|
31
|
-
const result = {
|
|
32
|
-
type: 'pie',
|
|
33
|
-
data: dataItem,
|
|
34
|
-
dataLabels: {
|
|
35
|
-
enabled: get(series, 'dataLabels.enabled', true),
|
|
36
|
-
},
|
|
37
|
-
label: dataItem.label,
|
|
38
|
-
value: dataItem.value,
|
|
39
|
-
visible: typeof dataItem.visible === 'boolean' ? dataItem.visible : true,
|
|
40
|
-
name: dataItem.name,
|
|
41
|
-
id: '',
|
|
42
|
-
color: dataItem.color || colorScale(dataItem.name),
|
|
43
|
-
legend: {
|
|
44
|
-
enabled: get(series, 'legend.enabled', legend.enabled),
|
|
45
|
-
symbol: prepareLegendSymbol(series),
|
|
46
|
-
},
|
|
47
|
-
center: series.center || ['50%', '50%'],
|
|
48
|
-
borderColor: series.borderColor || '',
|
|
49
|
-
borderRadius: (_a = series.borderRadius) !== null && _a !== void 0 ? _a : 0,
|
|
50
|
-
borderWidth: (_b = series.borderWidth) !== null && _b !== void 0 ? _b : 1,
|
|
51
|
-
radius: series.radius || '100%',
|
|
52
|
-
innerRadius: series.innerRadius || 0,
|
|
53
|
-
stackId,
|
|
54
|
-
};
|
|
55
|
-
return result;
|
|
56
|
-
});
|
|
57
|
-
return preparedSeries;
|
|
58
|
-
}
|
|
59
22
|
export function prepareSeries(args) {
|
|
60
23
|
const { type, series, seriesOptions, legend, colorScale } = args;
|
|
61
24
|
switch (type) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BarXSeries, BarXSeriesData, BaseTextStyle, ChartKitWidgetLegend, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData, BarYSeries, BarYSeriesData, LineSeries, LineSeriesData } from '../../../../../types';
|
|
1
|
+
import { BarXSeries, BarXSeriesData, BaseTextStyle, ChartKitWidgetLegend, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData, BarYSeries, BarYSeriesData, LineSeries, LineSeriesData, ConnectorShape, ConnectorCurve } from '../../../../../types';
|
|
2
2
|
import type { SeriesOptionsDefaults } from '../../constants';
|
|
3
3
|
export type RectLegendSymbol = {
|
|
4
4
|
shape: 'rect';
|
|
@@ -67,12 +67,29 @@ export type PreparedBarYSeries = {
|
|
|
67
67
|
maxWidth: number;
|
|
68
68
|
};
|
|
69
69
|
} & BasePreparedSeries;
|
|
70
|
-
export type PreparedPieSeries =
|
|
70
|
+
export type PreparedPieSeries = {
|
|
71
|
+
type: PieSeries['type'];
|
|
71
72
|
data: PieSeriesData;
|
|
72
73
|
value: PieSeriesData['value'];
|
|
74
|
+
borderColor: string;
|
|
75
|
+
borderWidth: number;
|
|
76
|
+
borderRadius: number;
|
|
77
|
+
center?: [string | number | null, string | number | null];
|
|
78
|
+
radius?: string | number;
|
|
79
|
+
innerRadius?: string | number;
|
|
73
80
|
stackId: string;
|
|
74
81
|
label?: PieSeriesData['label'];
|
|
75
|
-
|
|
82
|
+
dataLabels: {
|
|
83
|
+
enabled: boolean;
|
|
84
|
+
padding: number;
|
|
85
|
+
style: BaseTextStyle;
|
|
86
|
+
allowOverlap: boolean;
|
|
87
|
+
connectorPadding: number;
|
|
88
|
+
connectorShape: ConnectorShape;
|
|
89
|
+
distance: number;
|
|
90
|
+
connectorCurve: ConnectorCurve;
|
|
91
|
+
};
|
|
92
|
+
} & BasePreparedSeries;
|
|
76
93
|
export type PreparedLineSeries = {
|
|
77
94
|
type: LineSeries['type'];
|
|
78
95
|
data: LineSeriesData[];
|
|
@@ -5,12 +5,13 @@ import type { ChartScale } from '../useAxisScales';
|
|
|
5
5
|
import type { PreparedSeries, PreparedSeriesOptions } from '../';
|
|
6
6
|
import type { PreparedBarXData } from './bar-x';
|
|
7
7
|
import type { PreparedScatterData } from './scatter';
|
|
8
|
+
import type { PreparedPieData } from './pie/types';
|
|
8
9
|
import type { PreparedLineData } from './line/types';
|
|
9
10
|
import type { PreparedBarYData } from './bar-y/types';
|
|
10
11
|
export type { PreparedBarXData } from './bar-x';
|
|
11
12
|
export type { PreparedScatterData } from './scatter';
|
|
12
13
|
import './styles.css';
|
|
13
|
-
export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData;
|
|
14
|
+
export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData;
|
|
14
15
|
type Args = {
|
|
15
16
|
boundsWidth: number;
|
|
16
17
|
boundsHeight: number;
|
|
@@ -3,7 +3,8 @@ import { group } from 'd3';
|
|
|
3
3
|
import { getOnlyVisibleSeries } from '../../utils';
|
|
4
4
|
import { BarXSeriesShapes, prepareBarXData } from './bar-x';
|
|
5
5
|
import { ScatterSeriesShape, prepareScatterData } from './scatter';
|
|
6
|
-
import {
|
|
6
|
+
import { PieSeriesShapes } from './pie';
|
|
7
|
+
import { preparePieData } from './pie/prepare-data';
|
|
7
8
|
import { prepareLineData } from './line/prepare-data';
|
|
8
9
|
import { LineSeriesShapes } from './line';
|
|
9
10
|
import { BarYSeriesShapes, prepareBarYData } from './bar-y';
|
|
@@ -75,10 +76,12 @@ export const useShapes = (args) => {
|
|
|
75
76
|
break;
|
|
76
77
|
}
|
|
77
78
|
case 'pie': {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const preparedData = preparePieData({
|
|
80
|
+
series: chartSeries,
|
|
81
|
+
boundsWidth,
|
|
82
|
+
boundsHeight,
|
|
83
|
+
});
|
|
84
|
+
acc.push(React.createElement(PieSeriesShapes, { key: "pie", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions, svgContainer: svgContainer }));
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
return acc;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Dispatch } from 'd3';
|
|
3
|
+
import { PreparedSeriesOptions } from '../../useSeries/types';
|
|
4
|
+
import { PreparedPieData } from './types';
|
|
5
|
+
type PreparePieSeriesArgs = {
|
|
6
|
+
dispatcher: Dispatch<object>;
|
|
7
|
+
preparedData: PreparedPieData[];
|
|
8
|
+
seriesOptions: PreparedSeriesOptions;
|
|
9
|
+
svgContainer: SVGSVGElement | null;
|
|
10
|
+
};
|
|
11
|
+
export declare function PieSeriesShapes(args: PreparePieSeriesArgs): React.JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import { arc, color, pointer, select } from 'd3';
|
|
4
|
+
import { block } from '../../../../../../utils/cn';
|
|
5
|
+
import { setActiveState } from '../utils';
|
|
6
|
+
import { line as lineGenerator } from 'd3-shape';
|
|
7
|
+
import { setEllipsisForOverflowTexts } from '../../../utils';
|
|
8
|
+
import { getCurveFactory } from './utils';
|
|
9
|
+
const b = block('d3-pie');
|
|
10
|
+
export function PieSeriesShapes(args) {
|
|
11
|
+
const { dispatcher, preparedData, seriesOptions, svgContainer } = args;
|
|
12
|
+
const ref = React.useRef(null);
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
if (!ref.current) {
|
|
15
|
+
return () => { };
|
|
16
|
+
}
|
|
17
|
+
const svgElement = select(ref.current);
|
|
18
|
+
svgElement.selectAll('*').remove();
|
|
19
|
+
const segmentSelector = `.${b('segment')}`;
|
|
20
|
+
const connectorSelector = `.${b('connector')}`;
|
|
21
|
+
const shapesSelection = svgElement
|
|
22
|
+
.selectAll('pie')
|
|
23
|
+
.data(preparedData)
|
|
24
|
+
.join('g')
|
|
25
|
+
.attr('id', (pieData) => pieData.id)
|
|
26
|
+
.attr('class', b('item'))
|
|
27
|
+
.attr('transform', (pieData) => {
|
|
28
|
+
const [x, y] = pieData.center;
|
|
29
|
+
return `translate(${x}, ${y})`;
|
|
30
|
+
})
|
|
31
|
+
.style('stroke', (pieData) => pieData.borderColor)
|
|
32
|
+
.style('stroke-width', (pieData) => pieData.borderWidth);
|
|
33
|
+
shapesSelection
|
|
34
|
+
.selectAll(segmentSelector)
|
|
35
|
+
.data((pieData) => pieData.segments)
|
|
36
|
+
.join('path')
|
|
37
|
+
.attr('d', (d) => {
|
|
38
|
+
const arcGenerator = arc()
|
|
39
|
+
.innerRadius(d.data.pie.innerRadius)
|
|
40
|
+
.outerRadius(d.data.pie.radius)
|
|
41
|
+
.cornerRadius(d.data.pie.borderRadius);
|
|
42
|
+
return arcGenerator(d);
|
|
43
|
+
})
|
|
44
|
+
.attr('class', b('segment'))
|
|
45
|
+
.attr('fill', (d) => d.data.color);
|
|
46
|
+
shapesSelection
|
|
47
|
+
.selectAll('text')
|
|
48
|
+
.data((pieData) => pieData.labels)
|
|
49
|
+
.join('text')
|
|
50
|
+
.text((d) => d.text)
|
|
51
|
+
.attr('class', b('label'))
|
|
52
|
+
.attr('x', (d) => d.x)
|
|
53
|
+
.attr('y', (d) => d.y)
|
|
54
|
+
.attr('text-anchor', (d) => d.textAnchor)
|
|
55
|
+
.style('font-size', (d) => d.style.fontSize)
|
|
56
|
+
.style('font-weight', (d) => d.style.fontWeight || null)
|
|
57
|
+
.style('fill', (d) => d.style.fontColor || null)
|
|
58
|
+
.call(setEllipsisForOverflowTexts, (d) => d.size.width > d.maxWidth ? d.maxWidth : Infinity);
|
|
59
|
+
// Add the polyline between chart and labels
|
|
60
|
+
shapesSelection
|
|
61
|
+
.selectAll(connectorSelector)
|
|
62
|
+
.data((pieData) => pieData.labels)
|
|
63
|
+
.enter()
|
|
64
|
+
.append('path')
|
|
65
|
+
.attr('class', b('connector'))
|
|
66
|
+
.attr('d', (d) => {
|
|
67
|
+
let line = lineGenerator();
|
|
68
|
+
const curveFactory = getCurveFactory(d.segment.pie);
|
|
69
|
+
if (curveFactory) {
|
|
70
|
+
line = line.curve(curveFactory);
|
|
71
|
+
}
|
|
72
|
+
return line(d.connector.points);
|
|
73
|
+
})
|
|
74
|
+
.attr('stroke', (d) => d.connector.color)
|
|
75
|
+
.attr('stroke-width', 1)
|
|
76
|
+
.attr('stroke-linejoin', 'round')
|
|
77
|
+
.attr('stroke-linecap', 'round')
|
|
78
|
+
.style('fill', 'none');
|
|
79
|
+
const eventName = `hover-shape.pie`;
|
|
80
|
+
const hoverOptions = get(seriesOptions, 'pie.states.hover');
|
|
81
|
+
const inactiveOptions = get(seriesOptions, 'pie.states.inactive');
|
|
82
|
+
svgElement
|
|
83
|
+
.on('mousemove', (e) => {
|
|
84
|
+
const datum = select(e.target).datum();
|
|
85
|
+
const seriesId = get(datum, 'data.series.id', get(datum, 'series.id'));
|
|
86
|
+
const currentSegment = preparedData.reduce((result, pie) => {
|
|
87
|
+
var _a;
|
|
88
|
+
return (result || ((_a = pie.segments.find((s) => s.data.series.id === seriesId)) === null || _a === void 0 ? void 0 : _a.data));
|
|
89
|
+
}, undefined);
|
|
90
|
+
if (currentSegment) {
|
|
91
|
+
const data = {
|
|
92
|
+
series: {
|
|
93
|
+
id: currentSegment.series.id,
|
|
94
|
+
type: 'pie',
|
|
95
|
+
name: currentSegment.series.name,
|
|
96
|
+
},
|
|
97
|
+
data: currentSegment.series,
|
|
98
|
+
};
|
|
99
|
+
dispatcher.call('hover-shape', {}, [data], pointer(e, svgContainer));
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
.on('mouseleave', () => {
|
|
103
|
+
dispatcher.call('hover-shape', {}, undefined);
|
|
104
|
+
});
|
|
105
|
+
dispatcher.on(eventName, (data) => {
|
|
106
|
+
const selectedSeriesId = data === null || data === void 0 ? void 0 : data[0].series.id;
|
|
107
|
+
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
108
|
+
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
109
|
+
shapesSelection.datum((_d, index, list) => {
|
|
110
|
+
const pieSelection = select(list[index]);
|
|
111
|
+
pieSelection
|
|
112
|
+
.selectAll(segmentSelector)
|
|
113
|
+
.datum((d, i, elements) => {
|
|
114
|
+
const hovered = Boolean(hoverEnabled && d.data.series.id === selectedSeriesId);
|
|
115
|
+
if (d.data.hovered !== hovered) {
|
|
116
|
+
d.data.hovered = hovered;
|
|
117
|
+
select(elements[i]).attr('fill', () => {
|
|
118
|
+
var _a;
|
|
119
|
+
const initialColor = d.data.color;
|
|
120
|
+
if (d.data.hovered) {
|
|
121
|
+
return (((_a = color(initialColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.brightness).toString()) || initialColor);
|
|
122
|
+
}
|
|
123
|
+
return initialColor;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
setActiveState({
|
|
127
|
+
element: elements[i],
|
|
128
|
+
state: inactiveOptions,
|
|
129
|
+
active: Boolean(!inactiveEnabled ||
|
|
130
|
+
!selectedSeriesId ||
|
|
131
|
+
selectedSeriesId === d.data.series.id),
|
|
132
|
+
datum: d.data,
|
|
133
|
+
});
|
|
134
|
+
return d;
|
|
135
|
+
});
|
|
136
|
+
const labelSelection = pieSelection.selectAll('tspan');
|
|
137
|
+
const connectorSelection = pieSelection.selectAll(connectorSelector);
|
|
138
|
+
labelSelection.merge(connectorSelection).datum((d, i, elements) => {
|
|
139
|
+
return setActiveState({
|
|
140
|
+
element: elements[i],
|
|
141
|
+
state: inactiveOptions,
|
|
142
|
+
active: Boolean(!inactiveEnabled ||
|
|
143
|
+
!selectedSeriesId ||
|
|
144
|
+
selectedSeriesId === d.series.id),
|
|
145
|
+
datum: d,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
return () => {
|
|
151
|
+
dispatcher.on(eventName, null);
|
|
152
|
+
};
|
|
153
|
+
}, [dispatcher, preparedData, seriesOptions, svgContainer]);
|
|
154
|
+
return React.createElement("g", { ref: ref, className: b(), style: { zIndex: 9 } });
|
|
155
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PreparedPieSeries } from '../../useSeries/types';
|
|
2
|
+
import { PreparedPieData } from './types';
|
|
3
|
+
type Args = {
|
|
4
|
+
series: PreparedPieSeries[];
|
|
5
|
+
boundsWidth: number;
|
|
6
|
+
boundsHeight: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function preparePieData(args: Args): PreparedPieData[];
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { arc, group } from 'd3';
|
|
2
|
+
import { calculateNumericProperty, getLabelsSize, getLeftPosition, isLabelsOverlapping, } from '../../../utils';
|
|
3
|
+
import { pieGenerator } from './utils';
|
|
4
|
+
const FULL_CIRCLE = Math.PI * 2;
|
|
5
|
+
const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
const defaultX = boundsWidth * 0.5;
|
|
8
|
+
const defaultY = boundsHeight * 0.5;
|
|
9
|
+
if (!center) {
|
|
10
|
+
return [defaultX, defaultY];
|
|
11
|
+
}
|
|
12
|
+
const [x, y] = center;
|
|
13
|
+
const resultX = (_a = calculateNumericProperty({ value: x, base: boundsWidth })) !== null && _a !== void 0 ? _a : defaultX;
|
|
14
|
+
const resultY = (_b = calculateNumericProperty({ value: y, base: boundsHeight })) !== null && _b !== void 0 ? _b : defaultY;
|
|
15
|
+
return [resultX, resultY];
|
|
16
|
+
};
|
|
17
|
+
export function preparePieData(args) {
|
|
18
|
+
const { series: prepapredSeries, boundsWidth, boundsHeight } = args;
|
|
19
|
+
const maxRadius = Math.min(boundsWidth, boundsHeight) / 2;
|
|
20
|
+
const groupedPieSeries = group(prepapredSeries, (pieSeries) => pieSeries.stackId);
|
|
21
|
+
return Array.from(groupedPieSeries).map(([stackId, items]) => {
|
|
22
|
+
var _a, _b, _c;
|
|
23
|
+
const { center, borderWidth, borderColor, borderRadius, radius: seriesRadius, innerRadius: seriesInnerRadius, dataLabels, } = items[0];
|
|
24
|
+
const radius = (_a = calculateNumericProperty({ value: seriesRadius, base: maxRadius })) !== null && _a !== void 0 ? _a : maxRadius;
|
|
25
|
+
const data = {
|
|
26
|
+
id: stackId,
|
|
27
|
+
center: getCenter(boundsWidth, boundsHeight, center),
|
|
28
|
+
innerRadius: (_b = calculateNumericProperty({ value: seriesInnerRadius, base: radius })) !== null && _b !== void 0 ? _b : 0,
|
|
29
|
+
radius,
|
|
30
|
+
segments: [],
|
|
31
|
+
labels: [],
|
|
32
|
+
borderColor,
|
|
33
|
+
borderWidth,
|
|
34
|
+
borderRadius,
|
|
35
|
+
series: items[0],
|
|
36
|
+
connectorCurve: dataLabels.connectorCurve,
|
|
37
|
+
};
|
|
38
|
+
const segments = items.map((item) => {
|
|
39
|
+
return {
|
|
40
|
+
value: item.value,
|
|
41
|
+
color: item.color,
|
|
42
|
+
series: item,
|
|
43
|
+
hovered: false,
|
|
44
|
+
active: true,
|
|
45
|
+
pie: data,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
data.segments = pieGenerator(segments);
|
|
49
|
+
if (dataLabels.enabled) {
|
|
50
|
+
const { style, connectorPadding, distance } = dataLabels;
|
|
51
|
+
const { maxHeight: labelHeight } = getLabelsSize({ labels: ['Some Label'], style });
|
|
52
|
+
const minSegmentRadius = maxRadius - connectorPadding - distance - labelHeight;
|
|
53
|
+
if (data.radius > minSegmentRadius) {
|
|
54
|
+
data.radius = minSegmentRadius;
|
|
55
|
+
data.innerRadius =
|
|
56
|
+
(_c = calculateNumericProperty({ value: seriesInnerRadius, base: data.radius })) !== null && _c !== void 0 ? _c : 0;
|
|
57
|
+
}
|
|
58
|
+
const connectorStartPointGenerator = arc()
|
|
59
|
+
.innerRadius(data.radius)
|
|
60
|
+
.outerRadius(data.radius);
|
|
61
|
+
const connectorMidPointRadius = data.radius + distance / 2;
|
|
62
|
+
const connectorMidPointGenerator = arc()
|
|
63
|
+
.innerRadius(connectorMidPointRadius)
|
|
64
|
+
.outerRadius(connectorMidPointRadius);
|
|
65
|
+
const connectorArcRadius = data.radius + distance;
|
|
66
|
+
const connectorEndPointGenerator = arc()
|
|
67
|
+
.innerRadius(connectorArcRadius)
|
|
68
|
+
.outerRadius(connectorArcRadius);
|
|
69
|
+
const labelArcRadius = connectorArcRadius + connectorPadding;
|
|
70
|
+
const labelArcGenerator = arc()
|
|
71
|
+
.innerRadius(labelArcRadius)
|
|
72
|
+
.outerRadius(labelArcRadius);
|
|
73
|
+
const labels = [];
|
|
74
|
+
items.forEach((d, index) => {
|
|
75
|
+
const prevLabel = labels[labels.length - 1];
|
|
76
|
+
const text = String(d.data.label || d.data.value);
|
|
77
|
+
const labelSize = getLabelsSize({ labels: [text], style });
|
|
78
|
+
const labelWidth = labelSize.maxWidth;
|
|
79
|
+
const relatedSegment = data.segments[index];
|
|
80
|
+
const getLabelPosition = (angle) => {
|
|
81
|
+
let [x, y] = labelArcGenerator.centroid(Object.assign(Object.assign({}, relatedSegment), { startAngle: angle, endAngle: angle }));
|
|
82
|
+
x = Math.max(-boundsWidth / 2, x);
|
|
83
|
+
if (y < 0) {
|
|
84
|
+
y -= labelHeight;
|
|
85
|
+
}
|
|
86
|
+
return [x, y];
|
|
87
|
+
};
|
|
88
|
+
const getConnectorPoints = (angle) => {
|
|
89
|
+
const connectorStartPoint = connectorStartPointGenerator.centroid(relatedSegment);
|
|
90
|
+
const connectorEndPoint = connectorEndPointGenerator.centroid(Object.assign(Object.assign({}, relatedSegment), { startAngle: angle, endAngle: angle }));
|
|
91
|
+
if (dataLabels.connectorShape === 'straight-line') {
|
|
92
|
+
return [connectorStartPoint, connectorEndPoint];
|
|
93
|
+
}
|
|
94
|
+
const connectorMidPoint = connectorMidPointGenerator.centroid(relatedSegment);
|
|
95
|
+
return [connectorStartPoint, connectorMidPoint, connectorEndPoint];
|
|
96
|
+
};
|
|
97
|
+
const midAngle = Math.max((prevLabel === null || prevLabel === void 0 ? void 0 : prevLabel.angle) || 0, relatedSegment.startAngle +
|
|
98
|
+
(relatedSegment.endAngle - relatedSegment.startAngle) / 2);
|
|
99
|
+
const [x, y] = getLabelPosition(midAngle);
|
|
100
|
+
const label = {
|
|
101
|
+
text,
|
|
102
|
+
x,
|
|
103
|
+
y,
|
|
104
|
+
style,
|
|
105
|
+
size: { width: labelWidth, height: labelHeight },
|
|
106
|
+
maxWidth: labelWidth,
|
|
107
|
+
textAnchor: midAngle < Math.PI ? 'start' : 'end',
|
|
108
|
+
series: { id: d.id },
|
|
109
|
+
active: true,
|
|
110
|
+
connector: {
|
|
111
|
+
points: getConnectorPoints(midAngle),
|
|
112
|
+
color: relatedSegment.data.color,
|
|
113
|
+
},
|
|
114
|
+
segment: relatedSegment.data,
|
|
115
|
+
angle: midAngle,
|
|
116
|
+
};
|
|
117
|
+
let overlap = false;
|
|
118
|
+
if (prevLabel) {
|
|
119
|
+
overlap = isLabelsOverlapping(prevLabel, label, dataLabels.padding);
|
|
120
|
+
if (overlap) {
|
|
121
|
+
let shouldAdjustAngle = true;
|
|
122
|
+
const step = Math.PI / 180;
|
|
123
|
+
while (shouldAdjustAngle) {
|
|
124
|
+
const newAngle = label.angle + step;
|
|
125
|
+
if (newAngle > FULL_CIRCLE &&
|
|
126
|
+
newAngle % FULL_CIRCLE > labels[0].angle) {
|
|
127
|
+
shouldAdjustAngle = false;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
label.angle = newAngle;
|
|
131
|
+
const [newX, newY] = getLabelPosition(newAngle);
|
|
132
|
+
label.x = newX;
|
|
133
|
+
label.y = newY;
|
|
134
|
+
label.connector.points = getConnectorPoints(newAngle);
|
|
135
|
+
if (!isLabelsOverlapping(prevLabel, label, dataLabels.padding)) {
|
|
136
|
+
shouldAdjustAngle = false;
|
|
137
|
+
overlap = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (dataLabels.allowOverlap || !overlap) {
|
|
144
|
+
const left = getLeftPosition(label);
|
|
145
|
+
if (Math.abs(left) > boundsWidth / 2) {
|
|
146
|
+
label.maxWidth = label.size.width - (Math.abs(left) - boundsWidth / 2);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const right = left + label.size.width;
|
|
150
|
+
if (right > boundsWidth / 2) {
|
|
151
|
+
label.maxWidth = label.size.width - (right - boundsWidth / 2);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
labels.push(label);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
data.labels = labels;
|
|
158
|
+
}
|
|
159
|
+
return data;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PieArcDatum } from 'd3';
|
|
2
|
+
import { PreparedPieSeries } from '../../useSeries/types';
|
|
3
|
+
import { LabelData } from '../../../types';
|
|
4
|
+
import { ConnectorCurve } from '../../../../../../types';
|
|
5
|
+
export type SegmentData = {
|
|
6
|
+
value: number;
|
|
7
|
+
color: string;
|
|
8
|
+
series: PreparedPieSeries;
|
|
9
|
+
hovered: boolean;
|
|
10
|
+
active: boolean;
|
|
11
|
+
pie: PreparedPieData;
|
|
12
|
+
};
|
|
13
|
+
export type PieLabelData = LabelData & {
|
|
14
|
+
connector: {
|
|
15
|
+
points: [number, number][];
|
|
16
|
+
color: string;
|
|
17
|
+
};
|
|
18
|
+
segment: SegmentData;
|
|
19
|
+
angle: number;
|
|
20
|
+
maxWidth: number;
|
|
21
|
+
};
|
|
22
|
+
export type PreparedPieData = {
|
|
23
|
+
id: string;
|
|
24
|
+
segments: PieArcDatum<SegmentData>[];
|
|
25
|
+
labels: PieLabelData[];
|
|
26
|
+
center: [number, number];
|
|
27
|
+
radius: number;
|
|
28
|
+
innerRadius: number;
|
|
29
|
+
borderRadius: number;
|
|
30
|
+
borderWidth: number;
|
|
31
|
+
borderColor: string;
|
|
32
|
+
series: PreparedPieSeries;
|
|
33
|
+
connectorCurve: ConnectorCurve;
|
|
34
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { pie, curveBasis, curveLinear } from 'd3';
|
|
2
|
+
export const pieGenerator = pie()
|
|
3
|
+
.value((d) => d.value)
|
|
4
|
+
.sort(null);
|
|
5
|
+
export function getCurveFactory(data) {
|
|
6
|
+
switch (data.connectorCurve) {
|
|
7
|
+
case 'basic': {
|
|
8
|
+
return curveBasis;
|
|
9
|
+
}
|
|
10
|
+
case 'linear': {
|
|
11
|
+
return curveLinear;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
@@ -29,12 +29,8 @@ export function setActiveState(args) {
|
|
|
29
29
|
const elementSelection = select(element);
|
|
30
30
|
if (datum.active !== active) {
|
|
31
31
|
datum.active = active;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return (state === null || state === void 0 ? void 0 : state.opacity) || null;
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
});
|
|
32
|
+
const opacity = datum.active ? null : state === null || state === void 0 ? void 0 : state.opacity;
|
|
33
|
+
elementSelection.attr('opacity', opacity || null);
|
|
38
34
|
}
|
|
39
35
|
return datum;
|
|
40
36
|
}
|
|
@@ -20,7 +20,7 @@ export function axisBottom(args) {
|
|
|
20
20
|
const values = getXAxisItems({ scale, count: ticksCount, maxCount: maxTickCount });
|
|
21
21
|
const labelHeight = getLabelsSize({
|
|
22
22
|
labels: values,
|
|
23
|
-
style:
|
|
23
|
+
style: labelsStyle,
|
|
24
24
|
}).maxHeight;
|
|
25
25
|
return function (selection) {
|
|
26
26
|
var _a, _b;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { LabelData } from '../types';
|
|
2
2
|
export declare function getLeftPosition(label: LabelData): number;
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function getOverlappingByX(rect1: LabelData, rect2: LabelData, gap?: number): number;
|
|
4
|
+
export declare function getOverlappingByY(rect1: LabelData, rect2: LabelData, gap?: number): number;
|
|
5
|
+
export declare function isLabelsOverlapping<T extends LabelData>(label1: T, label2: T, padding?: number): boolean;
|
|
6
|
+
export declare function filterOverlappingLabels<T extends LabelData>(labels: T[]): T[];
|
|
@@ -1,33 +1,42 @@
|
|
|
1
1
|
import sortBy from 'lodash/sortBy';
|
|
2
2
|
export function getLeftPosition(label) {
|
|
3
3
|
switch (label.textAnchor) {
|
|
4
|
+
case 'start': {
|
|
5
|
+
return label.x;
|
|
6
|
+
}
|
|
4
7
|
case 'middle': {
|
|
5
8
|
return label.x - label.size.width / 2;
|
|
6
9
|
}
|
|
10
|
+
case 'end': {
|
|
11
|
+
return label.x - label.size.width;
|
|
12
|
+
}
|
|
7
13
|
default: {
|
|
8
14
|
return label.x;
|
|
9
15
|
}
|
|
10
16
|
}
|
|
11
17
|
}
|
|
12
|
-
function
|
|
18
|
+
export function getOverlappingByX(rect1, rect2, gap = 0) {
|
|
13
19
|
const left1 = getLeftPosition(rect1);
|
|
14
20
|
const right1 = left1 + rect1.size.width;
|
|
15
21
|
const left2 = getLeftPosition(rect2);
|
|
16
22
|
const right2 = left2 + rect2.size.width;
|
|
17
|
-
return Math.max(0, Math.min(right1, right2) - Math.max(left1, left2)
|
|
23
|
+
return Math.max(0, Math.min(right1, right2) - Math.max(left1, left2) + gap);
|
|
18
24
|
}
|
|
19
|
-
function
|
|
25
|
+
export function getOverlappingByY(rect1, rect2, gap = 0) {
|
|
20
26
|
const top1 = rect1.y - rect1.size.height;
|
|
21
27
|
const bottom1 = rect1.y;
|
|
22
28
|
const top2 = rect2.y - rect2.size.height;
|
|
23
29
|
const bottom2 = rect2.y;
|
|
24
|
-
return Math.max(0, Math.min(bottom1, bottom2) - Math.max(top1, top2)
|
|
30
|
+
return Math.max(0, Math.min(bottom1, bottom2) - Math.max(top1, top2) + gap);
|
|
31
|
+
}
|
|
32
|
+
export function isLabelsOverlapping(label1, label2, padding = 0) {
|
|
33
|
+
return Boolean(getOverlappingByX(label1, label2, padding) && getOverlappingByY(label1, label2, padding));
|
|
25
34
|
}
|
|
26
35
|
export function filterOverlappingLabels(labels) {
|
|
27
36
|
const result = [];
|
|
28
37
|
const sorted = sortBy(labels, (d) => d.y, getLeftPosition);
|
|
29
38
|
sorted.forEach((label) => {
|
|
30
|
-
if (!result.some((l) =>
|
|
39
|
+
if (!result.some((l) => isLabelsOverlapping(label, l))) {
|
|
31
40
|
result.push(label);
|
|
32
41
|
}
|
|
33
42
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Selection } from 'd3';
|
|
2
2
|
import { BaseTextStyle } from '../../../../types';
|
|
3
|
-
export declare function setEllipsisForOverflowText(selection: Selection<SVGTextElement,
|
|
4
|
-
export declare function setEllipsisForOverflowTexts(selection: Selection<SVGTextElement,
|
|
3
|
+
export declare function setEllipsisForOverflowText<T>(selection: Selection<SVGTextElement, T, null, unknown>, maxWidth: number): void;
|
|
4
|
+
export declare function setEllipsisForOverflowTexts<T>(selection: Selection<SVGTextElement, T, any, unknown>, maxWidth: ((datum: T) => number) | number): void;
|
|
5
5
|
export declare function hasOverlappingLabels({ width, labels, padding, style, }: {
|
|
6
6
|
width: number;
|
|
7
7
|
labels: string[];
|
|
@@ -10,7 +10,7 @@ export declare function hasOverlappingLabels({ width, labels, padding, style, }:
|
|
|
10
10
|
}): boolean;
|
|
11
11
|
export declare function getLabelsSize({ labels, style, rotation, }: {
|
|
12
12
|
labels: string[];
|
|
13
|
-
style?:
|
|
13
|
+
style?: BaseTextStyle;
|
|
14
14
|
rotation?: number;
|
|
15
15
|
}): {
|
|
16
16
|
maxHeight: number;
|
|
@@ -12,8 +12,9 @@ export function setEllipsisForOverflowText(selection, maxWidth) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
export function setEllipsisForOverflowTexts(selection, maxWidth) {
|
|
15
|
-
selection.each(function () {
|
|
16
|
-
|
|
15
|
+
selection.each(function (datum) {
|
|
16
|
+
const textMaxWidth = typeof maxWidth === 'function' ? maxWidth(datum) : maxWidth;
|
|
17
|
+
setEllipsisForOverflowText(select(this), textMaxWidth);
|
|
17
18
|
});
|
|
18
19
|
}
|
|
19
20
|
export function hasOverlappingLabels({ width, labels, padding = 0, style, }) {
|
|
@@ -31,9 +32,8 @@ export function hasOverlappingLabels({ width, labels, padding = 0, style, }) {
|
|
|
31
32
|
}
|
|
32
33
|
function renderLabels(selection, { labels, style = {}, attrs = {}, }) {
|
|
33
34
|
const text = selection.append('g').append('text');
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
35
|
+
text.style('font-size', style.fontSize || '');
|
|
36
|
+
text.style('font-weight', style.fontWeight || '');
|
|
37
37
|
Object.entries(attrs).forEach(([name, value]) => {
|
|
38
38
|
text.attr(name, value);
|
|
39
39
|
});
|
|
@@ -48,7 +48,10 @@ function renderLabels(selection, { labels, style = {}, attrs = {}, }) {
|
|
|
48
48
|
}
|
|
49
49
|
export function getLabelsSize({ labels, style, rotation, }) {
|
|
50
50
|
var _a;
|
|
51
|
-
const
|
|
51
|
+
const container = select(document.body)
|
|
52
|
+
.append('div')
|
|
53
|
+
.attr('class', 'chartkit chartkit-theme_common');
|
|
54
|
+
const svg = container.append('svg');
|
|
52
55
|
const textSelection = renderLabels(svg, { labels, style });
|
|
53
56
|
if (rotation) {
|
|
54
57
|
textSelection
|
|
@@ -56,6 +59,6 @@ export function getLabelsSize({ labels, style, rotation, }) {
|
|
|
56
59
|
.style('transform', `rotate(${rotation}deg)`);
|
|
57
60
|
}
|
|
58
61
|
const { height = 0, width = 0 } = ((_a = svg.select('g').node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) || {};
|
|
59
|
-
|
|
62
|
+
container.remove();
|
|
60
63
|
return { maxHeight: height, maxWidth: width };
|
|
61
64
|
}
|
|
@@ -42,12 +42,13 @@ function getPointsForInitialRefresh(chart) {
|
|
|
42
42
|
return points;
|
|
43
43
|
}
|
|
44
44
|
function forceHoverState(chart, activePoints) {
|
|
45
|
+
var _a, _b, _c;
|
|
45
46
|
const chartType = get(chart, 'userOptions.chart.type') || '';
|
|
46
47
|
if (chartType === 'pie') {
|
|
47
48
|
chart.tooltip.refresh(activePoints);
|
|
48
49
|
chart.pointsForInitialRefresh = activePoints;
|
|
49
50
|
}
|
|
50
|
-
else if (chart.series.length === 1) {
|
|
51
|
+
else if (((_a = chart.series) === null || _a === void 0 ? void 0 : _a.length) === 1) {
|
|
51
52
|
const series = chart.series[0];
|
|
52
53
|
const seriesType = (series && series.type) || (chart.options.chart && chart.options.chart.type);
|
|
53
54
|
if (seriesType && seriesTypesNeedsOnlyHoverState.indexOf(seriesType)) {
|
|
@@ -61,8 +62,8 @@ function forceHoverState(chart, activePoints) {
|
|
|
61
62
|
}
|
|
62
63
|
if (chartTypesWithoutCrosshair.indexOf(chartType) === -1) {
|
|
63
64
|
const point = Array.isArray(activePoints) ? activePoints[0] : activePoints;
|
|
64
|
-
chart.xAxis[0].drawCrosshair(undefined, point);
|
|
65
|
-
chart.yAxis[0].drawCrosshair(undefined, point);
|
|
65
|
+
(_b = chart.xAxis) === null || _b === void 0 ? void 0 : _b[0].drawCrosshair(undefined, point);
|
|
66
|
+
(_c = chart.yAxis) === null || _c === void 0 ? void 0 : _c[0].drawCrosshair(undefined, point);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
var PaneSplits;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import isEmpty from 'lodash/isEmpty';
|
|
3
|
-
import YagrComponent from '@gravity-ui/yagr/
|
|
3
|
+
import YagrComponent from '@gravity-ui/yagr/react';
|
|
4
4
|
import { i18n } from '../../../i18n';
|
|
5
5
|
import { CHARTKIT_ERROR_CODE, ChartKitError } from '../../../libs';
|
|
6
6
|
import { useWidgetData } from './useWidgetData';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { YagrChartProps } from '@gravity-ui/yagr/
|
|
1
|
+
import type { YagrChartProps } from '@gravity-ui/yagr/react';
|
|
2
2
|
import type { MinimalValidConfig, YagrWidgetProps } from '../types';
|
|
3
3
|
export declare const useWidgetData: (props: YagrWidgetProps, id: string) => {
|
|
4
4
|
config: MinimalValidConfig;
|
|
@@ -104,6 +104,12 @@ export const shapeYagrConfig = (args) => {
|
|
|
104
104
|
if (args.customTooltip) {
|
|
105
105
|
config.tooltip.virtual = true;
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* @todo remove this on next chartkit major release
|
|
109
|
+
* This added to prevent breaking changes in chartkit, while updating yagr@4 which
|
|
110
|
+
* has fixed tooltip sorting (@see https://github.com/gravity-ui/yagr/issues/149)
|
|
111
|
+
*/
|
|
112
|
+
config.tooltip.sort = config.tooltip.sort || ((a, b) => b.rowIdx - a.rowIdx);
|
|
107
113
|
}
|
|
108
114
|
config.axes = config.axes || {};
|
|
109
115
|
const xAxis = config.axes[defaults.DEFAULT_X_SCALE];
|
|
@@ -2,7 +2,7 @@ import type { MinimalValidConfig, RawSerieData, YagrConfig } from '@gravity-ui/y
|
|
|
2
2
|
import type Yagr from '@gravity-ui/yagr';
|
|
3
3
|
import { ChartKitProps } from 'src/types';
|
|
4
4
|
export type { default as Yagr } from '@gravity-ui/yagr';
|
|
5
|
-
export type { YagrReactRef } from '@gravity-ui/yagr/
|
|
5
|
+
export type { YagrReactRef } from '@gravity-ui/yagr/react';
|
|
6
6
|
export * from '@gravity-ui/yagr/dist/types';
|
|
7
7
|
export interface CustomTooltipProps {
|
|
8
8
|
yagr: Yagr<MinimalValidConfig> | undefined;
|
|
@@ -10,6 +10,8 @@ export type PieSeriesData<T = any> = BaseSeriesData<T> & {
|
|
|
10
10
|
/** Initial data label of the pie segment. If not specified, the value is used. */
|
|
11
11
|
label?: string;
|
|
12
12
|
};
|
|
13
|
+
export type ConnectorShape = 'straight-line' | 'polyline';
|
|
14
|
+
export type ConnectorCurve = 'linear' | 'basic';
|
|
13
15
|
export type PieSeries<T = any> = BaseSeries & {
|
|
14
16
|
type: 'pie';
|
|
15
17
|
data: PieSeriesData<T>[];
|
|
@@ -41,4 +43,31 @@ export type PieSeries<T = any> = BaseSeries & {
|
|
|
41
43
|
legend?: ChartKitWidgetLegend & {
|
|
42
44
|
symbol?: RectLegendSymbolOptions;
|
|
43
45
|
};
|
|
46
|
+
dataLabels?: BaseSeries['dataLabels'] & {
|
|
47
|
+
/**
|
|
48
|
+
* The distance of the data label from the pie's edge.
|
|
49
|
+
*
|
|
50
|
+
* @default 30
|
|
51
|
+
* */
|
|
52
|
+
distance?: number;
|
|
53
|
+
/**
|
|
54
|
+
* The distance from the data label to the connector.
|
|
55
|
+
*
|
|
56
|
+
* @default 5
|
|
57
|
+
* */
|
|
58
|
+
connectorPadding?: number;
|
|
59
|
+
/**
|
|
60
|
+
* The method that is used to generate the connector path.
|
|
61
|
+
*
|
|
62
|
+
* @default 'polyline'
|
|
63
|
+
* */
|
|
64
|
+
connectorShape?: ConnectorShape;
|
|
65
|
+
/**
|
|
66
|
+
* How to interpolate between two-dimensional [x, y] points for a connector.
|
|
67
|
+
* Works only if connectorShape equals to 'polyline'
|
|
68
|
+
*
|
|
69
|
+
* @default 'basic'
|
|
70
|
+
* */
|
|
71
|
+
connectorCurve?: ConnectorCurve;
|
|
72
|
+
};
|
|
44
73
|
};
|
|
@@ -14,7 +14,11 @@ export type TooltipDataChunkBarY<T = any> = {
|
|
|
14
14
|
};
|
|
15
15
|
export type TooltipDataChunkPie<T = any> = {
|
|
16
16
|
data: PieSeriesData<T>;
|
|
17
|
-
series:
|
|
17
|
+
series: {
|
|
18
|
+
type: PieSeries['type'];
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
};
|
|
18
22
|
};
|
|
19
23
|
export type TooltipDataChunkScatter<T = any> = {
|
|
20
24
|
data: ScatterSeriesData<T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravity-ui/chartkit",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.0",
|
|
4
4
|
"description": "React component used to render charts based on any sources you need",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "git@github.com:gravity-ui/ChartKit.git",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@bem-react/classname": "^1.6.0",
|
|
50
50
|
"@gravity-ui/date-utils": "^1.4.1",
|
|
51
|
-
"@gravity-ui/yagr": "^
|
|
51
|
+
"@gravity-ui/yagr": "^4.0.0",
|
|
52
52
|
"afterframe": "^1.0.2",
|
|
53
53
|
"d3": "^7.8.5",
|
|
54
54
|
"lodash": "^4.17.21",
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { Dispatch } from 'd3';
|
|
3
|
-
import { PreparedPieSeries, PreparedSeriesOptions } from '../useSeries/types';
|
|
4
|
-
type PreparePieSeriesArgs = {
|
|
5
|
-
boundsWidth: number;
|
|
6
|
-
boundsHeight: number;
|
|
7
|
-
dispatcher: Dispatch<object>;
|
|
8
|
-
series: PreparedPieSeries[];
|
|
9
|
-
seriesOptions: PreparedSeriesOptions;
|
|
10
|
-
svgContainer: SVGSVGElement | null;
|
|
11
|
-
};
|
|
12
|
-
export declare function PieSeriesComponent(args: PreparePieSeriesArgs): React.JSX.Element;
|
|
13
|
-
export {};
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import get from 'lodash/get';
|
|
3
|
-
import kebabCase from 'lodash/kebabCase';
|
|
4
|
-
import { arc, color, pie, pointer, select } from 'd3';
|
|
5
|
-
import { block } from '../../../../../utils/cn';
|
|
6
|
-
import { calculateNumericProperty, extractD3DataFromNode, getHorisontalSvgTextHeight, isNodeContainsD3Data, } from '../../utils';
|
|
7
|
-
const b = block('d3-pie');
|
|
8
|
-
const preparePieData = (series) => {
|
|
9
|
-
return series.map((s) => ({ series: s, data: s.data }));
|
|
10
|
-
};
|
|
11
|
-
const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
12
|
-
var _a, _b;
|
|
13
|
-
const defaultX = boundsWidth * 0.5;
|
|
14
|
-
const defaultY = boundsHeight * 0.5;
|
|
15
|
-
if (!center) {
|
|
16
|
-
return [defaultX, defaultY];
|
|
17
|
-
}
|
|
18
|
-
const [x, y] = center;
|
|
19
|
-
const resultX = (_a = calculateNumericProperty({ value: x, base: boundsWidth })) !== null && _a !== void 0 ? _a : defaultX;
|
|
20
|
-
const resultY = (_b = calculateNumericProperty({ value: y, base: boundsHeight })) !== null && _b !== void 0 ? _b : defaultY;
|
|
21
|
-
return [resultX, resultY];
|
|
22
|
-
};
|
|
23
|
-
const getOpacity = (args) => {
|
|
24
|
-
const { data, hoveredData, opacity } = args;
|
|
25
|
-
if (data.series.id !== (hoveredData === null || hoveredData === void 0 ? void 0 : hoveredData.series.id)) {
|
|
26
|
-
return opacity || null;
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
};
|
|
30
|
-
const isNodeContainsPieData = (node) => {
|
|
31
|
-
return isNodeContainsD3Data(node);
|
|
32
|
-
};
|
|
33
|
-
export function PieSeriesComponent(args) {
|
|
34
|
-
var _a;
|
|
35
|
-
const { boundsWidth, boundsHeight, dispatcher, series, seriesOptions, svgContainer } = args;
|
|
36
|
-
const ref = React.useRef(null);
|
|
37
|
-
const [x, y] = getCenter(boundsWidth, boundsHeight, (_a = series[0]) === null || _a === void 0 ? void 0 : _a.center);
|
|
38
|
-
React.useEffect(() => {
|
|
39
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
40
|
-
if (!ref.current) {
|
|
41
|
-
return () => { };
|
|
42
|
-
}
|
|
43
|
-
const svgElement = select(ref.current);
|
|
44
|
-
const hoverOptions = get(seriesOptions, 'pie.states.hover');
|
|
45
|
-
const inactiveOptions = get(seriesOptions, 'pie.states.inactive');
|
|
46
|
-
const isLabelsEnabled = (_b = (_a = series[0]) === null || _a === void 0 ? void 0 : _a.dataLabels) === null || _b === void 0 ? void 0 : _b.enabled;
|
|
47
|
-
let radiusRelatedToChart = Math.min(boundsWidth, boundsHeight) / 2;
|
|
48
|
-
if (isLabelsEnabled) {
|
|
49
|
-
// To have enough space for labels
|
|
50
|
-
radiusRelatedToChart *= 0.9;
|
|
51
|
-
}
|
|
52
|
-
let radius = (_d = calculateNumericProperty({ value: (_c = series[0]) === null || _c === void 0 ? void 0 : _c.radius, base: radiusRelatedToChart })) !== null && _d !== void 0 ? _d : radiusRelatedToChart;
|
|
53
|
-
const labelsArcRadius = ((_e = series[0]) === null || _e === void 0 ? void 0 : _e.radius) ? radius : radiusRelatedToChart;
|
|
54
|
-
if (isLabelsEnabled) {
|
|
55
|
-
// To have enough space for labels lines
|
|
56
|
-
radius *= 0.9;
|
|
57
|
-
}
|
|
58
|
-
const innerRadius = (_f = calculateNumericProperty({ value: series[0].innerRadius, base: radius })) !== null && _f !== void 0 ? _f : 0;
|
|
59
|
-
const preparedData = preparePieData(series);
|
|
60
|
-
const pieGenerator = pie().value((d) => d.data.value);
|
|
61
|
-
const visibleData = preparedData.filter((d) => d.series.visible);
|
|
62
|
-
const dataReady = pieGenerator(visibleData);
|
|
63
|
-
const arcGenerator = arc()
|
|
64
|
-
.innerRadius(innerRadius)
|
|
65
|
-
.outerRadius(radius)
|
|
66
|
-
.cornerRadius((d) => d.data.series.borderRadius);
|
|
67
|
-
svgElement.selectAll('*').remove();
|
|
68
|
-
const segmentSelection = svgElement
|
|
69
|
-
.selectAll('segments')
|
|
70
|
-
.data(dataReady)
|
|
71
|
-
.enter()
|
|
72
|
-
.append('path')
|
|
73
|
-
.attr('d', arcGenerator)
|
|
74
|
-
.attr('class', b('segment'))
|
|
75
|
-
.attr('fill', (d) => d.data.series.color)
|
|
76
|
-
.style('stroke', ((_g = series[0]) === null || _g === void 0 ? void 0 : _g.borderColor) || '')
|
|
77
|
-
.style('stroke-width', (_j = (_h = series[0]) === null || _h === void 0 ? void 0 : _h.borderWidth) !== null && _j !== void 0 ? _j : 1);
|
|
78
|
-
let polylineSelection;
|
|
79
|
-
let labelSelection;
|
|
80
|
-
if ((_l = (_k = series[0]) === null || _k === void 0 ? void 0 : _k.dataLabels) === null || _l === void 0 ? void 0 : _l.enabled) {
|
|
81
|
-
const labelHeight = getHorisontalSvgTextHeight({ text: 'tmp' });
|
|
82
|
-
const outerArc = arc()
|
|
83
|
-
.innerRadius(labelsArcRadius)
|
|
84
|
-
.outerRadius(labelsArcRadius);
|
|
85
|
-
const polylineArc = arc()
|
|
86
|
-
.innerRadius(radius)
|
|
87
|
-
.outerRadius(radius);
|
|
88
|
-
// Add the polylines between chart and labels
|
|
89
|
-
polylineSelection = svgElement
|
|
90
|
-
.selectAll('polylines')
|
|
91
|
-
.data(dataReady)
|
|
92
|
-
.enter()
|
|
93
|
-
.append('polyline')
|
|
94
|
-
.attr('stroke', (d) => d.data.series.color || '')
|
|
95
|
-
.attr('stroke-width', 1)
|
|
96
|
-
.attr('points', (d) => {
|
|
97
|
-
// Line insertion in the slice
|
|
98
|
-
const posA = polylineArc.centroid(d);
|
|
99
|
-
// Line break: we use the other arc generator that has been built only for that
|
|
100
|
-
const posB = outerArc.centroid(d);
|
|
101
|
-
const posC = outerArc.centroid(d);
|
|
102
|
-
// We need the angle to see if the X position will be at the extreme right or extreme left
|
|
103
|
-
const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
|
|
104
|
-
const result = [posA, posB, posC];
|
|
105
|
-
if (midangle < Math.PI) {
|
|
106
|
-
// polylines located to the right
|
|
107
|
-
const nextCx = radiusRelatedToChart * 0.95;
|
|
108
|
-
if (nextCx > result[1][0]) {
|
|
109
|
-
result[2][0] = nextCx;
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
result.splice(2, 1);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
// polylines located to the left
|
|
117
|
-
const nextCx = radiusRelatedToChart * 0.95 * -1;
|
|
118
|
-
if (nextCx < result[1][0]) {
|
|
119
|
-
result[2][0] = nextCx;
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
result.splice(2, 1);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return result.join(' ');
|
|
126
|
-
})
|
|
127
|
-
.attr('pointer-events', 'none')
|
|
128
|
-
.style('fill', 'none');
|
|
129
|
-
// Add the polylines between chart and labels
|
|
130
|
-
labelSelection = svgElement
|
|
131
|
-
.selectAll('labels')
|
|
132
|
-
.data(dataReady)
|
|
133
|
-
.join('text')
|
|
134
|
-
.text((d) => d.data.series.label || d.value)
|
|
135
|
-
.attr('class', b('label'))
|
|
136
|
-
.attr('transform', (d) => {
|
|
137
|
-
const pos = outerArc.centroid(d);
|
|
138
|
-
const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
|
|
139
|
-
pos[0] = radiusRelatedToChart * 0.99 * (midangle < Math.PI ? 1 : -1);
|
|
140
|
-
pos[1] += labelHeight / 4;
|
|
141
|
-
return `translate(${pos})`;
|
|
142
|
-
})
|
|
143
|
-
.attr('pointer-events', 'none')
|
|
144
|
-
.style('text-anchor', (d) => {
|
|
145
|
-
const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
|
|
146
|
-
return midangle < Math.PI ? 'start' : 'end';
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
svgElement
|
|
150
|
-
.on('mousemove', (e) => {
|
|
151
|
-
const segment = e.target;
|
|
152
|
-
if (!isNodeContainsPieData(segment)) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const [pointerX, pointerY] = pointer(e, svgContainer);
|
|
156
|
-
const segmentData = extractD3DataFromNode(segment).data;
|
|
157
|
-
dispatcher.call('hover-shape', {}, [segmentData], [pointerX, pointerY]);
|
|
158
|
-
})
|
|
159
|
-
.on('mouseleave', () => {
|
|
160
|
-
dispatcher.call('hover-shape', {}, undefined);
|
|
161
|
-
});
|
|
162
|
-
const eventName = `hover-shape.pie-${kebabCase(preparedData[0].series.id)}`;
|
|
163
|
-
dispatcher.on(eventName, (datas) => {
|
|
164
|
-
const data = datas === null || datas === void 0 ? void 0 : datas[0];
|
|
165
|
-
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
166
|
-
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
167
|
-
if (hoverEnabled && data) {
|
|
168
|
-
segmentSelection.attr('fill', (d) => {
|
|
169
|
-
var _a;
|
|
170
|
-
const fillColor = d.data.series.color;
|
|
171
|
-
if (d.data.series.id === data.series.id) {
|
|
172
|
-
return (((_a = color(fillColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.brightness).toString()) ||
|
|
173
|
-
fillColor);
|
|
174
|
-
}
|
|
175
|
-
return fillColor;
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
else if (hoverEnabled) {
|
|
179
|
-
segmentSelection.attr('fill', (d) => d.data.series.color);
|
|
180
|
-
}
|
|
181
|
-
if (inactiveEnabled && data) {
|
|
182
|
-
const opacity = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.opacity;
|
|
183
|
-
segmentSelection.attr('opacity', (d) => {
|
|
184
|
-
return getOpacity({ data: d.data, hoveredData: data, opacity });
|
|
185
|
-
});
|
|
186
|
-
polylineSelection === null || polylineSelection === void 0 ? void 0 : polylineSelection.attr('opacity', (d) => {
|
|
187
|
-
return getOpacity({ data: d.data, hoveredData: data, opacity });
|
|
188
|
-
});
|
|
189
|
-
labelSelection === null || labelSelection === void 0 ? void 0 : labelSelection.attr('opacity', (d) => {
|
|
190
|
-
return getOpacity({ data: d.data, hoveredData: data, opacity });
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
else if (inactiveEnabled) {
|
|
194
|
-
segmentSelection.attr('opacity', null);
|
|
195
|
-
polylineSelection === null || polylineSelection === void 0 ? void 0 : polylineSelection.attr('opacity', null);
|
|
196
|
-
labelSelection === null || labelSelection === void 0 ? void 0 : labelSelection.attr('opacity', null);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
return () => {
|
|
200
|
-
dispatcher.on(eventName, null);
|
|
201
|
-
};
|
|
202
|
-
}, [boundsWidth, boundsHeight, dispatcher, series, seriesOptions, svgContainer]);
|
|
203
|
-
return React.createElement("g", { ref: ref, className: b(), transform: `translate(${x}, ${y})` });
|
|
204
|
-
}
|