@gravity-ui/chartkit 5.14.0 → 5.15.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/Chart.js +5 -0
- package/build/plugins/d3/renderer/components/styles.css +8 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-pie.js +1 -0
- package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +1 -0
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +1 -0
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +2 -2
- package/build/plugins/d3/renderer/hooks/useShapes/pie/index.d.ts +1 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/index.js +18 -14
- package/build/plugins/d3/renderer/hooks/useShapes/pie/prepare-data.js +31 -12
- package/build/plugins/d3/renderer/hooks/useShapes/pie/types.d.ts +7 -5
- package/build/plugins/d3/renderer/types/index.d.ts +6 -0
- package/build/plugins/d3/renderer/utils/text.d.ts +2 -1
- package/build/plugins/d3/renderer/utils/text.js +25 -10
- package/build/types/widget-data/base.d.ts +7 -0
- package/package.json +2 -2
|
@@ -22,6 +22,7 @@ export const Chart = (props) => {
|
|
|
22
22
|
var _a, _b;
|
|
23
23
|
const { width, height, data } = props;
|
|
24
24
|
const svgRef = React.useRef(null);
|
|
25
|
+
const htmlLayerRef = React.useRef(null);
|
|
25
26
|
const dispatcher = React.useMemo(() => {
|
|
26
27
|
return getD3Dispatcher();
|
|
27
28
|
}, []);
|
|
@@ -71,6 +72,7 @@ export const Chart = (props) => {
|
|
|
71
72
|
yAxis,
|
|
72
73
|
yScale,
|
|
73
74
|
split: preparedSplit,
|
|
75
|
+
htmlLayout: htmlLayerRef.current,
|
|
74
76
|
});
|
|
75
77
|
const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
|
|
76
78
|
React.useEffect(() => {
|
|
@@ -136,5 +138,8 @@ export const Chart = (props) => {
|
|
|
136
138
|
React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit })))),
|
|
137
139
|
shapes),
|
|
138
140
|
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick }))),
|
|
141
|
+
React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef, style: {
|
|
142
|
+
transform: `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
|
|
143
|
+
} }),
|
|
139
144
|
React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0] })));
|
|
140
145
|
};
|
|
@@ -24,6 +24,7 @@ export function preparePieSeries(args) {
|
|
|
24
24
|
connectorShape: get(series, 'dataLabels.connectorShape', 'polyline'),
|
|
25
25
|
distance: get(series, 'dataLabels.distance', 25),
|
|
26
26
|
connectorCurve: get(series, 'dataLabels.connectorCurve', 'basic'),
|
|
27
|
+
html: get(series, 'dataLabels.html', false),
|
|
27
28
|
},
|
|
28
29
|
label: dataItem.label,
|
|
29
30
|
value: dataItem.value,
|
|
@@ -25,6 +25,7 @@ type Args = {
|
|
|
25
25
|
xScale?: ChartScale;
|
|
26
26
|
yScale?: ChartScale[];
|
|
27
27
|
split: PreparedSplit;
|
|
28
|
+
htmlLayout: HTMLElement | null;
|
|
28
29
|
};
|
|
29
30
|
export declare const useShapes: (args: Args) => {
|
|
30
31
|
shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
|
@@ -15,7 +15,7 @@ import { prepareTreemapData } from './treemap/prepare-data';
|
|
|
15
15
|
import { WaterfallSeriesShapes, prepareWaterfallData } from './waterfall';
|
|
16
16
|
import './styles.css';
|
|
17
17
|
export const useShapes = (args) => {
|
|
18
|
-
const { boundsWidth, boundsHeight, dispatcher, series, seriesOptions, xAxis, xScale, yAxis, yScale, split, } = args;
|
|
18
|
+
const { boundsWidth, boundsHeight, dispatcher, series, seriesOptions, xAxis, xScale, yAxis, yScale, split, htmlLayout, } = args;
|
|
19
19
|
const shapesComponents = React.useMemo(() => {
|
|
20
20
|
const visibleSeries = getOnlyVisibleSeries(series);
|
|
21
21
|
const groupedSeries = group(visibleSeries, (item) => item.type);
|
|
@@ -119,7 +119,7 @@ export const useShapes = (args) => {
|
|
|
119
119
|
boundsWidth,
|
|
120
120
|
boundsHeight,
|
|
121
121
|
});
|
|
122
|
-
acc.push(React.createElement(PieSeriesShapes, { key: "pie", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions }));
|
|
122
|
+
acc.push(React.createElement(PieSeriesShapes, { key: "pie", dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions, htmlLayout: htmlLayout }));
|
|
123
123
|
shapesData.push(...preparedData);
|
|
124
124
|
break;
|
|
125
125
|
}
|
|
@@ -6,6 +6,7 @@ type PreparePieSeriesArgs = {
|
|
|
6
6
|
dispatcher: Dispatch<object>;
|
|
7
7
|
preparedData: PreparedPieData[];
|
|
8
8
|
seriesOptions: PreparedSeriesOptions;
|
|
9
|
+
htmlLayout: HTMLElement | null;
|
|
9
10
|
};
|
|
10
11
|
export declare function getHaloVisibility(d: PieArcDatum<SegmentData>): "" | "hidden";
|
|
11
12
|
export declare function PieSeriesShapes(args: PreparePieSeriesArgs): React.JSX.Element;
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { Portal } from '@gravity-ui/uikit';
|
|
3
|
+
import { arc, color, select } from 'd3';
|
|
3
4
|
import get from 'lodash/get';
|
|
4
5
|
import { block } from '../../../../../../utils/cn';
|
|
5
6
|
import { setEllipsisForOverflowTexts } from '../../../utils';
|
|
6
7
|
import { setActiveState } from '../utils';
|
|
7
|
-
import { getCurveFactory } from './utils';
|
|
8
8
|
const b = block('d3-pie');
|
|
9
9
|
export function getHaloVisibility(d) {
|
|
10
10
|
const enabled = d.data.pie.halo.enabled && d.data.hovered;
|
|
11
11
|
return enabled ? '' : 'hidden';
|
|
12
12
|
}
|
|
13
13
|
export function PieSeriesShapes(args) {
|
|
14
|
-
const { dispatcher, preparedData, seriesOptions } = args;
|
|
14
|
+
const { dispatcher, preparedData, seriesOptions, htmlLayout } = args;
|
|
15
15
|
const ref = React.useRef(null);
|
|
16
|
+
const htmlItems = React.useMemo(() => {
|
|
17
|
+
return preparedData.reduce((result, d) => {
|
|
18
|
+
result.push(...d.htmlElements);
|
|
19
|
+
return result;
|
|
20
|
+
}, []);
|
|
21
|
+
}, [preparedData]);
|
|
16
22
|
React.useEffect(() => {
|
|
17
23
|
if (!ref.current) {
|
|
18
24
|
return () => { };
|
|
@@ -71,6 +77,7 @@ export function PieSeriesShapes(args) {
|
|
|
71
77
|
.attr('class', b('segment'))
|
|
72
78
|
.attr('fill', (d) => d.data.color)
|
|
73
79
|
.attr('opacity', (d) => d.data.opacity);
|
|
80
|
+
// render Labels
|
|
74
81
|
shapesSelection
|
|
75
82
|
.selectAll('text')
|
|
76
83
|
.data((pieData) => pieData.labels)
|
|
@@ -87,19 +94,12 @@ export function PieSeriesShapes(args) {
|
|
|
87
94
|
// Add the polyline between chart and labels
|
|
88
95
|
shapesSelection
|
|
89
96
|
.selectAll(connectorSelector)
|
|
90
|
-
.data((pieData) => pieData.
|
|
97
|
+
.data((pieData) => pieData.connectors)
|
|
91
98
|
.enter()
|
|
92
99
|
.append('path')
|
|
93
100
|
.attr('class', b('connector'))
|
|
94
|
-
.attr('d', (d) =>
|
|
95
|
-
|
|
96
|
-
const curveFactory = getCurveFactory(d.segment.pie);
|
|
97
|
-
if (curveFactory) {
|
|
98
|
-
line = line.curve(curveFactory);
|
|
99
|
-
}
|
|
100
|
-
return line(d.connector.points);
|
|
101
|
-
})
|
|
102
|
-
.attr('stroke', (d) => d.connector.color)
|
|
101
|
+
.attr('d', (d) => d.path)
|
|
102
|
+
.attr('stroke', (d) => d.color)
|
|
103
103
|
.attr('stroke-width', 1)
|
|
104
104
|
.attr('stroke-linejoin', 'round')
|
|
105
105
|
.attr('stroke-linecap', 'round')
|
|
@@ -172,5 +172,9 @@ export function PieSeriesShapes(args) {
|
|
|
172
172
|
dispatcher.on(eventName, null);
|
|
173
173
|
};
|
|
174
174
|
}, [dispatcher, preparedData, seriesOptions]);
|
|
175
|
-
return React.createElement(
|
|
175
|
+
return (React.createElement(React.Fragment, null,
|
|
176
|
+
React.createElement("g", { ref: ref, className: b(), style: { zIndex: 9 } }),
|
|
177
|
+
htmlLayout && (React.createElement(Portal, { container: htmlLayout }, htmlItems.map((item, index) => {
|
|
178
|
+
return (React.createElement("div", { key: index, dangerouslySetInnerHTML: { __html: item.content }, style: { position: 'absolute', left: item.x, top: item.y } }));
|
|
179
|
+
})))));
|
|
176
180
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { arc, group } from 'd3';
|
|
1
|
+
import { arc, group, line as lineGenerator } from 'd3';
|
|
2
2
|
import { calculateNumericProperty, getLabelsSize, getLeftPosition, isLabelsOverlapping, } from '../../../utils';
|
|
3
|
-
import { pieGenerator } from './utils';
|
|
3
|
+
import { getCurveFactory, pieGenerator } from './utils';
|
|
4
4
|
const FULL_CIRCLE = Math.PI * 2;
|
|
5
5
|
const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
6
6
|
var _a, _b;
|
|
@@ -30,6 +30,7 @@ export function preparePieData(args) {
|
|
|
30
30
|
radius,
|
|
31
31
|
segments: [],
|
|
32
32
|
labels: [],
|
|
33
|
+
connectors: [],
|
|
33
34
|
borderColor,
|
|
34
35
|
borderWidth,
|
|
35
36
|
borderRadius,
|
|
@@ -40,6 +41,7 @@ export function preparePieData(args) {
|
|
|
40
41
|
opacity: series.states.hover.halo.opacity,
|
|
41
42
|
size: series.states.hover.halo.size,
|
|
42
43
|
},
|
|
44
|
+
htmlElements: [],
|
|
43
45
|
};
|
|
44
46
|
const segments = items.map((item) => {
|
|
45
47
|
return {
|
|
@@ -53,6 +55,11 @@ export function preparePieData(args) {
|
|
|
53
55
|
};
|
|
54
56
|
});
|
|
55
57
|
data.segments = pieGenerator(segments);
|
|
58
|
+
let line = lineGenerator();
|
|
59
|
+
const curveFactory = getCurveFactory(data);
|
|
60
|
+
if (curveFactory) {
|
|
61
|
+
line = line.curve(curveFactory);
|
|
62
|
+
}
|
|
56
63
|
if (dataLabels.enabled) {
|
|
57
64
|
const { style, connectorPadding, distance } = dataLabels;
|
|
58
65
|
const { maxHeight: labelHeight } = getLabelsSize({ labels: ['Some Label'], style });
|
|
@@ -81,15 +88,17 @@ export function preparePieData(args) {
|
|
|
81
88
|
items.forEach((d, index) => {
|
|
82
89
|
const prevLabel = labels[labels.length - 1];
|
|
83
90
|
const text = String(d.data.label || d.data.value);
|
|
84
|
-
const
|
|
91
|
+
const shouldUseHtml = dataLabels.html;
|
|
92
|
+
const labelSize = getLabelsSize({ labels: [text], style, html: shouldUseHtml });
|
|
85
93
|
const labelWidth = labelSize.maxWidth;
|
|
86
94
|
const relatedSegment = data.segments[index];
|
|
87
95
|
const getLabelPosition = (angle) => {
|
|
88
96
|
let [x, y] = labelArcGenerator.centroid(Object.assign(Object.assign({}, relatedSegment), { startAngle: angle, endAngle: angle }));
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
97
|
+
y = y < 0 ? y - labelHeight : y;
|
|
98
|
+
if (shouldUseHtml) {
|
|
99
|
+
x = x < 0 ? x - labelWidth : x;
|
|
92
100
|
}
|
|
101
|
+
x = Math.max(-boundsWidth / 2, x);
|
|
93
102
|
return [x, y];
|
|
94
103
|
};
|
|
95
104
|
const getConnectorPoints = (angle) => {
|
|
@@ -114,12 +123,9 @@ export function preparePieData(args) {
|
|
|
114
123
|
textAnchor: midAngle < Math.PI ? 'start' : 'end',
|
|
115
124
|
series: { id: d.id },
|
|
116
125
|
active: true,
|
|
117
|
-
connector: {
|
|
118
|
-
points: getConnectorPoints(midAngle),
|
|
119
|
-
color: relatedSegment.data.color,
|
|
120
|
-
},
|
|
121
126
|
segment: relatedSegment.data,
|
|
122
127
|
angle: midAngle,
|
|
128
|
+
html: shouldUseHtml,
|
|
123
129
|
};
|
|
124
130
|
let overlap = false;
|
|
125
131
|
if (prevLabel) {
|
|
@@ -138,7 +144,6 @@ export function preparePieData(args) {
|
|
|
138
144
|
const [newX, newY] = getLabelPosition(newAngle);
|
|
139
145
|
label.x = newX;
|
|
140
146
|
label.y = newY;
|
|
141
|
-
label.connector.points = getConnectorPoints(newAngle);
|
|
142
147
|
if (!isLabelsOverlapping(prevLabel, label, dataLabels.padding)) {
|
|
143
148
|
shouldAdjustAngle = false;
|
|
144
149
|
overlap = false;
|
|
@@ -158,7 +163,21 @@ export function preparePieData(args) {
|
|
|
158
163
|
label.maxWidth = label.size.width - (right - boundsWidth / 2);
|
|
159
164
|
}
|
|
160
165
|
}
|
|
161
|
-
|
|
166
|
+
if (shouldUseHtml) {
|
|
167
|
+
data.htmlElements.push({
|
|
168
|
+
x: boundsWidth / 2 + label.x,
|
|
169
|
+
y: boundsHeight / 2 + label.y,
|
|
170
|
+
content: label.text,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
labels.push(label);
|
|
175
|
+
}
|
|
176
|
+
const connector = {
|
|
177
|
+
path: line(getConnectorPoints(midAngle)),
|
|
178
|
+
color: relatedSegment.data.color,
|
|
179
|
+
};
|
|
180
|
+
data.connectors.push(connector);
|
|
162
181
|
}
|
|
163
182
|
});
|
|
164
183
|
data.labels = labels;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PieArcDatum } from 'd3';
|
|
2
2
|
import { ConnectorCurve } from '../../../../../../types';
|
|
3
|
-
import { LabelData } from '../../../types';
|
|
3
|
+
import { HtmlItem, LabelData } from '../../../types';
|
|
4
4
|
import { PreparedPieSeries } from '../../useSeries/types';
|
|
5
5
|
export type SegmentData = {
|
|
6
6
|
value: number;
|
|
@@ -12,18 +12,19 @@ export type SegmentData = {
|
|
|
12
12
|
pie: PreparedPieData;
|
|
13
13
|
};
|
|
14
14
|
export type PieLabelData = LabelData & {
|
|
15
|
-
connector: {
|
|
16
|
-
points: [number, number][];
|
|
17
|
-
color: string;
|
|
18
|
-
};
|
|
19
15
|
segment: SegmentData;
|
|
20
16
|
angle: number;
|
|
21
17
|
maxWidth: number;
|
|
22
18
|
};
|
|
19
|
+
export type PieConnectorData = {
|
|
20
|
+
path: string | null;
|
|
21
|
+
color: string;
|
|
22
|
+
};
|
|
23
23
|
export type PreparedPieData = {
|
|
24
24
|
id: string;
|
|
25
25
|
segments: PieArcDatum<SegmentData>[];
|
|
26
26
|
labels: PieLabelData[];
|
|
27
|
+
connectors: PieConnectorData[];
|
|
27
28
|
center: [number, number];
|
|
28
29
|
radius: number;
|
|
29
30
|
innerRadius: number;
|
|
@@ -37,4 +38,5 @@ export type PreparedPieData = {
|
|
|
37
38
|
opacity: number;
|
|
38
39
|
size: number;
|
|
39
40
|
};
|
|
41
|
+
htmlElements: HtmlItem[];
|
|
40
42
|
};
|
|
@@ -9,10 +9,11 @@ export declare function hasOverlappingLabels({ width, labels, padding, style, }:
|
|
|
9
9
|
style?: BaseTextStyle;
|
|
10
10
|
padding?: number;
|
|
11
11
|
}): boolean;
|
|
12
|
-
export declare function getLabelsSize({ labels, style, rotation, }: {
|
|
12
|
+
export declare function getLabelsSize({ labels, style, rotation, html, }: {
|
|
13
13
|
labels: string[];
|
|
14
14
|
style?: BaseTextStyle;
|
|
15
15
|
rotation?: number;
|
|
16
|
+
html?: boolean;
|
|
16
17
|
}): {
|
|
17
18
|
maxHeight: number;
|
|
18
19
|
maxWidth: number;
|
|
@@ -63,24 +63,39 @@ function renderLabels(selection, { labels, style = {}, attrs = {}, }) {
|
|
|
63
63
|
.text((d) => d);
|
|
64
64
|
return text;
|
|
65
65
|
}
|
|
66
|
-
export function getLabelsSize({ labels, style, rotation, }) {
|
|
67
|
-
var _a;
|
|
66
|
+
export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
67
|
+
var _a, _b, _c, _d, _e;
|
|
68
68
|
if (!labels.filter(Boolean).length) {
|
|
69
69
|
return { maxHeight: 0, maxWidth: 0 };
|
|
70
70
|
}
|
|
71
71
|
const container = select(document.body)
|
|
72
72
|
.append('div')
|
|
73
73
|
.attr('class', 'chartkit chartkit-theme_common');
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
.
|
|
74
|
+
const result = { maxHeight: 0, maxWidth: 0 };
|
|
75
|
+
let labelWrapper;
|
|
76
|
+
if (html) {
|
|
77
|
+
labelWrapper = container.append('div').style('position', 'absolute').node();
|
|
78
|
+
labels.forEach((l) => {
|
|
79
|
+
labelWrapper === null || labelWrapper === void 0 ? void 0 : labelWrapper.insertAdjacentHTML('beforeend', l);
|
|
80
|
+
});
|
|
81
|
+
const rect = labelWrapper === null || labelWrapper === void 0 ? void 0 : labelWrapper.getBoundingClientRect();
|
|
82
|
+
result.maxWidth = (_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0;
|
|
83
|
+
result.maxHeight = (_b = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _b !== void 0 ? _b : 0;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const svg = container.append('svg');
|
|
87
|
+
const textSelection = renderLabels(svg, { labels, style });
|
|
88
|
+
if (rotation) {
|
|
89
|
+
textSelection
|
|
90
|
+
.attr('text-anchor', rotation > 0 ? 'start' : 'end')
|
|
91
|
+
.style('transform', `rotate(${rotation}deg)`);
|
|
92
|
+
}
|
|
93
|
+
const rect = (_c = svg.select('g').node()) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect();
|
|
94
|
+
result.maxWidth = (_d = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _d !== void 0 ? _d : 0;
|
|
95
|
+
result.maxHeight = (_e = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _e !== void 0 ? _e : 0;
|
|
80
96
|
}
|
|
81
|
-
const { height = 0, width = 0 } = ((_a = svg.select('g').node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) || {};
|
|
82
97
|
container.remove();
|
|
83
|
-
return
|
|
98
|
+
return result;
|
|
84
99
|
}
|
|
85
100
|
export function wrapText(args) {
|
|
86
101
|
const { text, style, width } = args;
|
|
@@ -21,6 +21,13 @@ export type BaseSeries = {
|
|
|
21
21
|
* @default false
|
|
22
22
|
* */
|
|
23
23
|
allowOverlap?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Allows to use any html-tags to display the content.
|
|
26
|
+
* The element will be displayed outside the box of the SVG element.
|
|
27
|
+
*
|
|
28
|
+
* @default false
|
|
29
|
+
* */
|
|
30
|
+
html?: boolean;
|
|
24
31
|
};
|
|
25
32
|
/** You can set the cursor to "pointer" if you have click events attached to the series, to signal to the user that the points and lines can be clicked. */
|
|
26
33
|
cursor?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravity-ui/chartkit",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.15.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",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@bem-react/classname": "^1.6.0",
|
|
50
50
|
"@gravity-ui/date-utils": "^2.1.0",
|
|
51
51
|
"@gravity-ui/i18n": "^1.0.0",
|
|
52
|
-
"@gravity-ui/yagr": "^4.3.
|
|
52
|
+
"@gravity-ui/yagr": "^4.3.4",
|
|
53
53
|
"afterframe": "^1.0.2",
|
|
54
54
|
"d3": "^7.8.5",
|
|
55
55
|
"lodash": "^4.17.21",
|