@gravity-ui/charts 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/ChartInner/index.js +3 -5
- package/dist/cjs/constants/defaults/data-labels.d.ts +2 -0
- package/dist/cjs/constants/defaults/data-labels.js +5 -0
- package/dist/cjs/constants/defaults/index.d.ts +1 -0
- package/dist/cjs/constants/defaults/index.js +1 -0
- package/dist/cjs/hooks/useSeries/constants.d.ts +1 -2
- package/dist/cjs/hooks/useSeries/constants.js +0 -5
- package/dist/cjs/hooks/useSeries/prepare-area.js +2 -1
- package/dist/cjs/hooks/useSeries/prepare-bar-x.js +2 -1
- package/dist/cjs/hooks/useSeries/prepare-bar-y.js +1 -1
- package/dist/cjs/hooks/useSeries/prepare-legend.js +1 -1
- package/dist/cjs/hooks/useSeries/prepare-line.js +2 -2
- package/dist/cjs/hooks/useSeries/prepare-pie.js +3 -2
- package/dist/cjs/hooks/useSeries/prepare-radar.js +2 -2
- package/dist/cjs/hooks/useSeries/prepare-sankey.js +1 -1
- package/dist/cjs/hooks/useSeries/prepare-treemap.js +2 -2
- package/dist/cjs/hooks/useSeries/prepare-waterfall.js +2 -2
- package/dist/cjs/hooks/useSeries/types.d.ts +1 -0
- package/dist/cjs/hooks/useShapes/HtmlLayer.js +2 -1
- package/dist/cjs/hooks/useShapes/pie/prepare-data.js +84 -30
- package/dist/cjs/hooks/useShapes/pie/utils.js +2 -1
- package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +19 -11
- package/dist/cjs/types/chart/pie.d.ts +8 -0
- package/dist/cjs/types/chart-ui.d.ts +1 -1
- package/dist/cjs/utils/chart/text.d.ts +1 -1
- package/dist/cjs/utils/chart/text.js +13 -7
- package/dist/cjs/utils/chart-ui/pie-center-text.js +5 -1
- package/dist/esm/components/ChartInner/index.js +3 -5
- package/dist/esm/constants/defaults/data-labels.d.ts +2 -0
- package/dist/esm/constants/defaults/data-labels.js +5 -0
- package/dist/esm/constants/defaults/index.d.ts +1 -0
- package/dist/esm/constants/defaults/index.js +1 -0
- package/dist/esm/hooks/useSeries/constants.d.ts +1 -2
- package/dist/esm/hooks/useSeries/constants.js +0 -5
- package/dist/esm/hooks/useSeries/prepare-area.js +2 -1
- package/dist/esm/hooks/useSeries/prepare-bar-x.js +2 -1
- package/dist/esm/hooks/useSeries/prepare-bar-y.js +1 -1
- package/dist/esm/hooks/useSeries/prepare-legend.js +1 -1
- package/dist/esm/hooks/useSeries/prepare-line.js +2 -2
- package/dist/esm/hooks/useSeries/prepare-pie.js +3 -2
- package/dist/esm/hooks/useSeries/prepare-radar.js +2 -2
- package/dist/esm/hooks/useSeries/prepare-sankey.js +1 -1
- package/dist/esm/hooks/useSeries/prepare-treemap.js +2 -2
- package/dist/esm/hooks/useSeries/prepare-waterfall.js +2 -2
- package/dist/esm/hooks/useSeries/types.d.ts +1 -0
- package/dist/esm/hooks/useShapes/HtmlLayer.js +2 -1
- package/dist/esm/hooks/useShapes/pie/prepare-data.js +84 -30
- package/dist/esm/hooks/useShapes/pie/utils.js +2 -1
- package/dist/esm/hooks/useShapes/treemap/prepare-data.js +19 -11
- package/dist/esm/types/chart/pie.d.ts +8 -0
- package/dist/esm/types/chart-ui.d.ts +1 -1
- package/dist/esm/utils/chart/text.d.ts +1 -1
- package/dist/esm/utils/chart/text.js +13 -7
- package/dist/esm/utils/chart-ui/pie-center-text.js +5 -1
- package/package.json +1 -1
|
@@ -59,8 +59,8 @@ export const ChartInner = (props) => {
|
|
|
59
59
|
unpinTooltip === null || unpinTooltip === void 0 ? void 0 : unpinTooltip();
|
|
60
60
|
}
|
|
61
61
|
}, [prevWidth, width, prevHeight, height, tooltipPinned, unpinTooltip]);
|
|
62
|
-
return (React.createElement(
|
|
63
|
-
React.createElement("svg", { ref: svgRef,
|
|
62
|
+
return (React.createElement("div", { className: b() },
|
|
63
|
+
React.createElement("svg", { ref: svgRef, width: width, height: height, onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave, onTouchStart: throttledHandleTouchMove, onTouchMove: throttledHandleTouchMove, onClick: handleChartClick },
|
|
64
64
|
title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
|
|
65
65
|
React.createElement("g", { transform: `translate(0, ${boundsOffsetTop})` }, preparedSplit.plots.map((plot, index) => {
|
|
66
66
|
return React.createElement(PlotTitle, { key: `plot-${index}`, title: plot.title });
|
|
@@ -72,8 +72,6 @@ export const ChartInner = (props) => {
|
|
|
72
72
|
React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
|
|
73
73
|
shapes),
|
|
74
74
|
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip }))),
|
|
75
|
-
React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef,
|
|
76
|
-
transform: `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
|
|
77
|
-
} }),
|
|
75
|
+
React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef }),
|
|
78
76
|
React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
|
|
79
77
|
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Halo } from '../../types';
|
|
2
2
|
import type { PointMarkerOptions } from '../../types/chart/marker';
|
|
3
3
|
export declare const DEFAULT_LEGEND_SYMBOL_SIZE = 8;
|
|
4
4
|
export declare const DEFAULT_LEGEND_SYMBOL_PADDING = 5;
|
|
5
5
|
export declare const DEFAULT_DATALABELS_PADDING = 5;
|
|
6
|
-
export declare const DEFAULT_DATALABELS_STYLE: BaseTextStyle;
|
|
7
6
|
export declare const DEFAULT_HALO_OPTIONS: Required<Halo>;
|
|
8
7
|
export declare const DEFAULT_POINT_MARKER_OPTIONS: Omit<Required<PointMarkerOptions>, 'enabled'>;
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
export const DEFAULT_LEGEND_SYMBOL_SIZE = 8;
|
|
2
2
|
export const DEFAULT_LEGEND_SYMBOL_PADDING = 5;
|
|
3
3
|
export const DEFAULT_DATALABELS_PADDING = 5;
|
|
4
|
-
export const DEFAULT_DATALABELS_STYLE = {
|
|
5
|
-
fontSize: '11px',
|
|
6
|
-
fontWeight: 'bold',
|
|
7
|
-
fontColor: 'var(--gcharts-data-labels)',
|
|
8
|
-
};
|
|
9
4
|
export const DEFAULT_HALO_OPTIONS = {
|
|
10
5
|
enabled: true,
|
|
11
6
|
opacity: 0.25,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import merge from 'lodash/merge';
|
|
3
|
+
import { DEFAULT_DATALABELS_STYLE } from '../../constants';
|
|
3
4
|
import { getUniqId } from '../../utils';
|
|
4
|
-
import { DEFAULT_DATALABELS_PADDING,
|
|
5
|
+
import { DEFAULT_DATALABELS_PADDING, DEFAULT_HALO_OPTIONS, DEFAULT_POINT_MARKER_OPTIONS, } from './constants';
|
|
5
6
|
import { getSeriesStackId, prepareLegendSymbol } from './utils';
|
|
6
7
|
export const DEFAULT_LINE_WIDTH = 1;
|
|
7
8
|
export const DEFAULT_MARKER = Object.assign(Object.assign({}, DEFAULT_POINT_MARKER_OPTIONS), { enabled: false });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
+
import { DEFAULT_DATALABELS_STYLE } from '../../constants';
|
|
2
3
|
import { getUniqId } from '../../utils';
|
|
3
|
-
import { DEFAULT_DATALABELS_PADDING
|
|
4
|
+
import { DEFAULT_DATALABELS_PADDING } from './constants';
|
|
4
5
|
import { getSeriesStackId, prepareLegendSymbol } from './utils';
|
|
5
6
|
export function prepareBarXSeries(args) {
|
|
6
7
|
const { colorScale, series: seriesList, seriesOptions, legend } = args;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
+
import { DEFAULT_DATALABELS_STYLE } from '../../constants';
|
|
2
3
|
import { getLabelsSize, getUniqId } from '../../utils';
|
|
3
4
|
import { getFormattedValue } from '../../utils/chart/format';
|
|
4
|
-
import { DEFAULT_DATALABELS_STYLE } from './constants';
|
|
5
5
|
import { getSeriesStackId, prepareLegendSymbol } from './utils';
|
|
6
6
|
function prepareDataLabels(series) {
|
|
7
7
|
var _a, _b;
|
|
@@ -133,7 +133,7 @@ export const getLegendComponents = (args) => {
|
|
|
133
133
|
const limit = Math.floor(maxLegendHeight / preparedLegend.lineHeight) - 1;
|
|
134
134
|
const maxPage = Math.ceil(items.length / limit);
|
|
135
135
|
pagination = { limit, maxPage };
|
|
136
|
-
legendHeight =
|
|
136
|
+
legendHeight = preparedLegend.lineHeight * (limit + 1);
|
|
137
137
|
}
|
|
138
138
|
preparedLegend.height = legendHeight;
|
|
139
139
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import merge from 'lodash/merge';
|
|
3
|
-
import { DashStyle, LineCap } from '../../constants';
|
|
3
|
+
import { DEFAULT_DATALABELS_STYLE, DashStyle, LineCap } from '../../constants';
|
|
4
4
|
import { getUniqId } from '../../utils';
|
|
5
|
-
import { DEFAULT_DATALABELS_PADDING,
|
|
5
|
+
import { DEFAULT_DATALABELS_PADDING, DEFAULT_HALO_OPTIONS, DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_POINT_MARKER_OPTIONS, } from './constants';
|
|
6
6
|
export const DEFAULT_LEGEND_SYMBOL_SIZE = 16;
|
|
7
7
|
export const DEFAULT_LINE_WIDTH = 1;
|
|
8
8
|
export const DEFAULT_DASH_STYLE = DashStyle.Solid;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { scaleOrdinal } from 'd3';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
|
-
import { DEFAULT_PALETTE } from '../../constants';
|
|
3
|
+
import { DEFAULT_DATALABELS_STYLE, DEFAULT_PALETTE } from '../../constants';
|
|
4
4
|
import { getUniqId } from '../../utils';
|
|
5
|
-
import { DEFAULT_DATALABELS_PADDING
|
|
5
|
+
import { DEFAULT_DATALABELS_PADDING } from './constants';
|
|
6
6
|
import { prepareLegendSymbol } from './utils';
|
|
7
7
|
export function preparePieSeries(args) {
|
|
8
8
|
const { series, seriesOptions, legend } = args;
|
|
@@ -43,6 +43,7 @@ export function preparePieSeries(args) {
|
|
|
43
43
|
borderWidth: (_d = series.borderWidth) !== null && _d !== void 0 ? _d : 1,
|
|
44
44
|
radius: (_f = (_e = dataItem.radius) !== null && _e !== void 0 ? _e : series.radius) !== null && _f !== void 0 ? _f : '100%',
|
|
45
45
|
innerRadius: series.innerRadius || 0,
|
|
46
|
+
minRadius: series.minRadius,
|
|
46
47
|
stackId,
|
|
47
48
|
states: {
|
|
48
49
|
hover: {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { scaleOrdinal } from 'd3';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
3
|
import merge from 'lodash/merge';
|
|
4
|
-
import { DEFAULT_PALETTE } from '../../constants';
|
|
4
|
+
import { DEFAULT_DATALABELS_STYLE, DEFAULT_PALETTE } from '../../constants';
|
|
5
5
|
import { getUniqId } from '../../utils';
|
|
6
|
-
import { DEFAULT_DATALABELS_PADDING,
|
|
6
|
+
import { DEFAULT_DATALABELS_PADDING, DEFAULT_HALO_OPTIONS, DEFAULT_POINT_MARKER_OPTIONS, } from './constants';
|
|
7
7
|
import { prepareLegendSymbol } from './utils';
|
|
8
8
|
export const DEFAULT_MARKER = Object.assign(Object.assign({}, DEFAULT_POINT_MARKER_OPTIONS), { enabled: true, radius: 2 });
|
|
9
9
|
function prepareMarker(series, seriesOptions) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
+
import { DEFAULT_DATALABELS_STYLE } from '../../constants';
|
|
2
3
|
import { getUniqId } from '../../utils';
|
|
3
|
-
import { DEFAULT_DATALABELS_STYLE } from './constants';
|
|
4
4
|
import { prepareLegendSymbol } from './utils';
|
|
5
5
|
export function prepareSankeySeries(args) {
|
|
6
6
|
const { colorScale, legend, series } = args;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import { LayoutAlgorithm } from '../../constants';
|
|
2
|
+
import { DEFAULT_DATALABELS_STYLE, LayoutAlgorithm } from '../../constants';
|
|
3
3
|
import { getUniqId } from '../../utils';
|
|
4
|
-
import { DEFAULT_DATALABELS_PADDING
|
|
4
|
+
import { DEFAULT_DATALABELS_PADDING } from './constants';
|
|
5
5
|
import { prepareLegendSymbol } from './utils';
|
|
6
6
|
export function prepareTreemap(args) {
|
|
7
7
|
const { colorScale, legend, series } = args;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import { DEFAULT_PALETTE } from '../../constants';
|
|
2
|
+
import { DEFAULT_DATALABELS_STYLE, DEFAULT_PALETTE } from '../../constants';
|
|
3
3
|
import { getUniqId } from '../../utils';
|
|
4
|
-
import { DEFAULT_DATALABELS_PADDING
|
|
4
|
+
import { DEFAULT_DATALABELS_PADDING } from './constants';
|
|
5
5
|
import { prepareLegendSymbol } from './utils';
|
|
6
6
|
export function prepareWaterfallSeries(args) {
|
|
7
7
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
@@ -137,6 +137,7 @@ export type PreparedPieSeries = {
|
|
|
137
137
|
center?: [string | number | null, string | number | null];
|
|
138
138
|
radius?: string | number;
|
|
139
139
|
innerRadius?: string | number;
|
|
140
|
+
minRadius?: string | number;
|
|
140
141
|
stackId: string;
|
|
141
142
|
label?: PieSeriesData['label'];
|
|
142
143
|
dataLabels: {
|
|
@@ -17,7 +17,8 @@ export const HtmlLayer = (props) => {
|
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
19
|
return (React.createElement(Portal, { container: htmlLayout }, items.map((item, index) => {
|
|
20
|
-
|
|
20
|
+
var _a, _b, _c;
|
|
21
|
+
const style = Object.assign(Object.assign({}, item.style), { color: (_b = (_a = item.style) === null || _a === void 0 ? void 0 : _a.color) !== null && _b !== void 0 ? _b : (_c = item.style) === null || _c === void 0 ? void 0 : _c.fontColor, position: 'absolute', left: item.x, top: item.y });
|
|
21
22
|
return (React.createElement("div", { key: index, dangerouslySetInnerHTML: { __html: item.content }, style: style }));
|
|
22
23
|
})));
|
|
23
24
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { arc, group, line as lineGenerator } from 'd3';
|
|
2
|
+
import merge from 'lodash/merge';
|
|
3
|
+
import { DEFAULT_DATALABELS_STYLE } from '../../../constants';
|
|
2
4
|
import { calculateNumericProperty, getLabelsSize, getLeftPosition, isLabelsOverlapping, } from '../../../utils';
|
|
3
5
|
import { getFormattedValue } from '../../../utils/chart/format';
|
|
4
6
|
import { getCurveFactory, getInscribedAngle, pieGenerator } from './utils';
|
|
@@ -16,17 +18,22 @@ const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
|
16
18
|
return [resultX, resultY];
|
|
17
19
|
};
|
|
18
20
|
export function preparePieData(args) {
|
|
21
|
+
var _a, _b;
|
|
19
22
|
const { series: preparedSeries, boundsWidth, boundsHeight } = args;
|
|
20
23
|
const haloSize = preparedSeries[0].states.hover.halo.enabled
|
|
21
24
|
? preparedSeries[0].states.hover.halo.size
|
|
22
25
|
: 0;
|
|
23
26
|
const maxRadius = Math.min(boundsWidth, boundsHeight) / 2 - haloSize;
|
|
24
|
-
const
|
|
27
|
+
const propsMinRadius = calculateNumericProperty({
|
|
28
|
+
value: preparedSeries[0].minRadius,
|
|
29
|
+
base: maxRadius,
|
|
30
|
+
});
|
|
31
|
+
const minRadius = typeof propsMinRadius === 'number' ? propsMinRadius : maxRadius * 0.3;
|
|
25
32
|
const groupedPieSeries = group(preparedSeries, (pieSeries) => pieSeries.stackId);
|
|
33
|
+
const dataLabelsStyle = merge({}, DEFAULT_DATALABELS_STYLE, (_b = (_a = preparedSeries[0]) === null || _a === void 0 ? void 0 : _a.dataLabels) === null || _b === void 0 ? void 0 : _b.style);
|
|
26
34
|
const prepareItem = (stackId, items) => {
|
|
27
|
-
var _a;
|
|
28
35
|
const series = items[0];
|
|
29
|
-
const { center, borderWidth, borderColor, borderRadius,
|
|
36
|
+
const { center, borderWidth, borderColor, borderRadius, dataLabels } = series;
|
|
30
37
|
const data = {
|
|
31
38
|
id: stackId,
|
|
32
39
|
center: getCenter(boundsWidth, boundsHeight, center),
|
|
@@ -48,9 +55,8 @@ export function preparePieData(args) {
|
|
|
48
55
|
};
|
|
49
56
|
const { maxHeight: labelHeight } = getLabelsSize({
|
|
50
57
|
labels: ['Some Label'],
|
|
51
|
-
style:
|
|
58
|
+
style: dataLabelsStyle,
|
|
52
59
|
});
|
|
53
|
-
let segmentMaxRadius = 0;
|
|
54
60
|
const segments = items.map((item) => {
|
|
55
61
|
var _a;
|
|
56
62
|
let maxSegmentRadius = maxRadius;
|
|
@@ -58,7 +64,6 @@ export function preparePieData(args) {
|
|
|
58
64
|
maxSegmentRadius -= dataLabels.distance + dataLabels.connectorPadding + labelHeight;
|
|
59
65
|
}
|
|
60
66
|
const segmentRadius = (_a = calculateNumericProperty({ value: item.radius, base: maxSegmentRadius })) !== null && _a !== void 0 ? _a : maxSegmentRadius;
|
|
61
|
-
segmentMaxRadius = Math.max(segmentMaxRadius, segmentRadius);
|
|
62
67
|
return {
|
|
63
68
|
value: item.value,
|
|
64
69
|
color: item.color,
|
|
@@ -71,8 +76,6 @@ export function preparePieData(args) {
|
|
|
71
76
|
};
|
|
72
77
|
});
|
|
73
78
|
data.segments = pieGenerator(segments);
|
|
74
|
-
data.innerRadius =
|
|
75
|
-
(_a = calculateNumericProperty({ value: seriesInnerRadius, base: segmentMaxRadius })) !== null && _a !== void 0 ? _a : 0;
|
|
76
79
|
return data;
|
|
77
80
|
};
|
|
78
81
|
const prepareLabels = (prepareLabelsArgs) => {
|
|
@@ -84,13 +87,18 @@ export function preparePieData(args) {
|
|
|
84
87
|
if (!dataLabels.enabled) {
|
|
85
88
|
return { labels, htmlLabels, connectors };
|
|
86
89
|
}
|
|
90
|
+
const shouldUseHtml = dataLabels.html;
|
|
87
91
|
let line = lineGenerator();
|
|
88
92
|
const curveFactory = getCurveFactory(data);
|
|
89
93
|
if (curveFactory) {
|
|
90
94
|
line = line.curve(curveFactory);
|
|
91
95
|
}
|
|
92
96
|
const { style, connectorPadding, distance } = dataLabels;
|
|
93
|
-
const { maxHeight: labelHeight } = getLabelsSize({
|
|
97
|
+
const { maxHeight: labelHeight } = getLabelsSize({
|
|
98
|
+
labels: ['Some Label'],
|
|
99
|
+
style: dataLabelsStyle,
|
|
100
|
+
html: shouldUseHtml,
|
|
101
|
+
});
|
|
94
102
|
const connectorStartPointGenerator = arc()
|
|
95
103
|
.innerRadius((d) => d.data.radius)
|
|
96
104
|
.outerRadius((d) => d.data.radius);
|
|
@@ -104,22 +112,35 @@ export function preparePieData(args) {
|
|
|
104
112
|
.innerRadius((d) => d.data.radius + distance + connectorPadding)
|
|
105
113
|
.outerRadius((d) => d.data.radius + distance + connectorPadding);
|
|
106
114
|
let shouldStopLabelPlacement = false;
|
|
115
|
+
// eslint-disable-next-line complexity
|
|
107
116
|
series.forEach((d, index) => {
|
|
108
117
|
const prevLabel = labels[labels.length - 1];
|
|
109
118
|
const text = getFormattedValue(Object.assign({ value: d.data.label || d.data.value }, d.dataLabels));
|
|
110
|
-
const
|
|
111
|
-
|
|
119
|
+
const labelSize = getLabelsSize({
|
|
120
|
+
labels: [text],
|
|
121
|
+
style: dataLabelsStyle,
|
|
122
|
+
html: shouldUseHtml,
|
|
123
|
+
});
|
|
112
124
|
const labelWidth = labelSize.maxWidth;
|
|
113
125
|
const relatedSegment = data.segments[index];
|
|
126
|
+
/**
|
|
127
|
+
* Compute the label coordinates on the label arc for a given angle.
|
|
128
|
+
*
|
|
129
|
+
* For HTML labels, the function returns the top-left corner to account for
|
|
130
|
+
* element box positioning. It shifts left by the label width when the point is
|
|
131
|
+
* on the left side (x < 0) and shifts up by the label height when above the
|
|
132
|
+
* horizontal center (y < 0). For SVG text, only the vertical shift is applied
|
|
133
|
+
* to compensate for text baseline.
|
|
134
|
+
*
|
|
135
|
+
* @param {number} angle - Angle in radians at which the label should be placed.
|
|
136
|
+
* @returns {[number, number]} A tuple [x, y] relative to the pie center.
|
|
137
|
+
*/
|
|
114
138
|
const getLabelPosition = (angle) => {
|
|
115
139
|
let [x, y] = labelArcGenerator.centroid(Object.assign(Object.assign({}, relatedSegment), { startAngle: angle, endAngle: angle }));
|
|
116
140
|
if (shouldUseHtml) {
|
|
117
141
|
x = x < 0 ? x - labelWidth : x;
|
|
118
|
-
y = y - labelSize.maxHeight;
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
y = y < 0 ? y - labelHeight : y;
|
|
122
142
|
}
|
|
143
|
+
y = y < 0 ? y - labelHeight : y;
|
|
123
144
|
return [x, y];
|
|
124
145
|
};
|
|
125
146
|
const getConnectorPoints = (angle) => {
|
|
@@ -148,10 +169,14 @@ export function preparePieData(args) {
|
|
|
148
169
|
angle: midAngle,
|
|
149
170
|
};
|
|
150
171
|
if (!allowOverlow) {
|
|
151
|
-
const labelLeftPosition = getLeftPosition(label);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
const labelLeftPosition = shouldUseHtml ? label.x : getLeftPosition(label);
|
|
173
|
+
let newMaxWidth;
|
|
174
|
+
if (label.x > 0) {
|
|
175
|
+
newMaxWidth = Math.min(boundsWidth - data.center[0] - labelLeftPosition, labelWidth);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
newMaxWidth = Math.min(data.center[0] + labelLeftPosition + label.size.width, labelWidth);
|
|
179
|
+
}
|
|
155
180
|
if (newMaxWidth !== label.maxWidth) {
|
|
156
181
|
label.maxWidth = Math.max(0, newMaxWidth);
|
|
157
182
|
}
|
|
@@ -173,11 +198,17 @@ export function preparePieData(args) {
|
|
|
173
198
|
shouldAdjustAngle = false;
|
|
174
199
|
}
|
|
175
200
|
else {
|
|
176
|
-
label.angle = newAngle;
|
|
177
201
|
const [newX, newY] = getLabelPosition(newAngle);
|
|
202
|
+
label.angle = newAngle;
|
|
203
|
+
label.textAnchor = newAngle < Math.PI ? 'start' : 'end';
|
|
178
204
|
label.x = newX;
|
|
179
205
|
label.y = newY;
|
|
180
|
-
|
|
206
|
+
// See `getLabelPosition`: for HTML labels we return top-left,
|
|
207
|
+
// so shift x by labelWidth when textAnchor is 'end'.
|
|
208
|
+
const pointC = shouldUseHtml && label.textAnchor === 'end'
|
|
209
|
+
? [newX + labelWidth, newY]
|
|
210
|
+
: [newX, newY];
|
|
211
|
+
const inscribedAngle = getInscribedAngle(pointA, pointB, pointC);
|
|
181
212
|
if (inscribedAngle > 90) {
|
|
182
213
|
shouldAdjustAngle = false;
|
|
183
214
|
shouldStopLabelPlacement = true;
|
|
@@ -192,18 +223,19 @@ export function preparePieData(args) {
|
|
|
192
223
|
}
|
|
193
224
|
const isLabelOverlapped = !dataLabels.allowOverlap && overlap;
|
|
194
225
|
if (!isLabelOverlapped && label.maxWidth > 0 && !shouldStopLabelPlacement) {
|
|
226
|
+
labels.push(label);
|
|
195
227
|
if (shouldUseHtml) {
|
|
228
|
+
const htmlLabelX = data.center[0] + label.x;
|
|
196
229
|
htmlLabels.push({
|
|
197
|
-
x:
|
|
230
|
+
x: Math.max(0, htmlLabelX),
|
|
198
231
|
y: Math.max(0, data.center[1] + label.y),
|
|
199
232
|
content: label.text,
|
|
200
233
|
size: label.size,
|
|
201
|
-
style: label.style,
|
|
234
|
+
style: Object.assign(Object.assign({}, label.style), { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', maxWidth: Math.min(label.x > 0
|
|
235
|
+
? boundsWidth - htmlLabelX
|
|
236
|
+
: htmlLabelX + label.size.width, label.size.width) }),
|
|
202
237
|
});
|
|
203
238
|
}
|
|
204
|
-
else {
|
|
205
|
-
labels.push(label);
|
|
206
|
-
}
|
|
207
239
|
const connector = {
|
|
208
240
|
path: line(getConnectorPoints(label.angle)),
|
|
209
241
|
color: relatedSegment.data.color,
|
|
@@ -212,12 +244,13 @@ export function preparePieData(args) {
|
|
|
212
244
|
}
|
|
213
245
|
});
|
|
214
246
|
return {
|
|
215
|
-
labels,
|
|
247
|
+
labels: shouldUseHtml ? [] : labels,
|
|
216
248
|
htmlLabels,
|
|
217
249
|
connectors,
|
|
218
250
|
};
|
|
219
251
|
};
|
|
220
252
|
return Array.from(groupedPieSeries).map(([stackId, items]) => {
|
|
253
|
+
var _a;
|
|
221
254
|
const data = prepareItem(stackId, items);
|
|
222
255
|
const preparedLabels = prepareLabels({
|
|
223
256
|
data,
|
|
@@ -237,6 +270,17 @@ export function preparePieData(args) {
|
|
|
237
270
|
maxLeftRightFreeSpace = Math.max(0, Math.min(maxLeftRightFreeSpace, freeSpace));
|
|
238
271
|
labelsOverflow = freeSpace < 0 ? Math.max(labelsOverflow, -freeSpace) : labelsOverflow;
|
|
239
272
|
});
|
|
273
|
+
preparedLabels.htmlLabels.forEach((label) => {
|
|
274
|
+
let freeSpace = 0;
|
|
275
|
+
if (label.x < data.center[0]) {
|
|
276
|
+
freeSpace = Math.max(label.x, 0);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
freeSpace = boundsWidth - label.x - label.size.width;
|
|
280
|
+
}
|
|
281
|
+
maxLeftRightFreeSpace = Math.max(0, Math.min(maxLeftRightFreeSpace, freeSpace));
|
|
282
|
+
labelsOverflow = freeSpace < 0 ? Math.max(labelsOverflow, -freeSpace) : labelsOverflow;
|
|
283
|
+
});
|
|
240
284
|
const segmentMaxRadius = Math.max(...data.segments.map((s) => s.data.radius));
|
|
241
285
|
if (labelsOverflow) {
|
|
242
286
|
data.segments.forEach((s) => {
|
|
@@ -251,7 +295,7 @@ export function preparePieData(args) {
|
|
|
251
295
|
topFreeSpace = Math.min(topFreeSpace, data.center[1] - topSvgLabel);
|
|
252
296
|
}
|
|
253
297
|
if (preparedLabels.htmlLabels.length) {
|
|
254
|
-
const topHtmlLabel = Math.
|
|
298
|
+
const topHtmlLabel = Math.min(...preparedLabels.htmlLabels.map((l) => l.y));
|
|
255
299
|
topFreeSpace = Math.min(topFreeSpace, topHtmlLabel);
|
|
256
300
|
}
|
|
257
301
|
let bottomFreeSpace = data.center[1] - segmentMaxRadius - haloSize;
|
|
@@ -267,14 +311,16 @@ export function preparePieData(args) {
|
|
|
267
311
|
const bottomAdjustment = Math.max(0, Math.min(bottomFreeSpace, maxLeftRightFreeSpace));
|
|
268
312
|
if (topAdjustment && topAdjustment >= bottomAdjustment) {
|
|
269
313
|
data.segments.forEach((s) => {
|
|
270
|
-
|
|
314
|
+
let nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
315
|
+
nextPossibleRadius = Math.max(nextPossibleRadius, minRadius);
|
|
271
316
|
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
272
317
|
});
|
|
273
318
|
data.center[1] -= (topAdjustment - bottomAdjustment) / 2;
|
|
274
319
|
}
|
|
275
320
|
else if (bottomAdjustment) {
|
|
276
321
|
data.segments.forEach((s) => {
|
|
277
|
-
|
|
322
|
+
let nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
323
|
+
nextPossibleRadius = Math.max(nextPossibleRadius, minRadius);
|
|
278
324
|
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
279
325
|
});
|
|
280
326
|
data.center[1] += (bottomAdjustment - topAdjustment) / 2;
|
|
@@ -285,6 +331,14 @@ export function preparePieData(args) {
|
|
|
285
331
|
series: items,
|
|
286
332
|
allowOverlow: false,
|
|
287
333
|
});
|
|
334
|
+
if (typeof ((_a = items[0]) === null || _a === void 0 ? void 0 : _a.innerRadius) !== 'undefined') {
|
|
335
|
+
const resultSegmentMaxRadius = Math.max(...data.segments.map((s) => s.data.radius));
|
|
336
|
+
const resultInnerRadius = calculateNumericProperty({
|
|
337
|
+
value: items[0].innerRadius,
|
|
338
|
+
base: resultSegmentMaxRadius,
|
|
339
|
+
}) || 0;
|
|
340
|
+
data.innerRadius = resultInnerRadius;
|
|
341
|
+
}
|
|
288
342
|
data.labels = labels;
|
|
289
343
|
data.htmlLabels = htmlLabels;
|
|
290
344
|
data.connectors = connectors;
|
|
@@ -7,18 +7,24 @@ function getLabels(args) {
|
|
|
7
7
|
const { data, options: { html, padding, align, style }, } = args;
|
|
8
8
|
return data.reduce((acc, d) => {
|
|
9
9
|
const texts = Array.isArray(d.data.name) ? d.data.name : [d.data.name];
|
|
10
|
-
|
|
10
|
+
const left = d.x0 + padding;
|
|
11
|
+
const right = d.x1 - padding;
|
|
12
|
+
const spaceWidth = Math.max(0, right - left);
|
|
13
|
+
let availableSpaceHeight = Math.max(0, d.y1 - d.y0 - padding);
|
|
14
|
+
let prevLabelsHeight = 0;
|
|
15
|
+
texts.forEach((text) => {
|
|
11
16
|
var _a;
|
|
12
17
|
const label = getFormattedValue(Object.assign({ value: text }, args.options));
|
|
13
|
-
const { maxHeight:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
const { maxHeight: labelMaxHeight, maxWidth: labelMaxWidth } = (_a = getLabelsSize({
|
|
19
|
+
labels: [label],
|
|
20
|
+
style: Object.assign(Object.assign({}, style), { maxWidth: `${spaceWidth}px`, maxHeight: `${availableSpaceHeight}px` }),
|
|
21
|
+
html,
|
|
22
|
+
})) !== null && _a !== void 0 ? _a : {};
|
|
18
23
|
let x = left;
|
|
19
|
-
const y =
|
|
24
|
+
const y = prevLabelsHeight + d.y0 + padding;
|
|
20
25
|
const labelWidth = Math.min(labelMaxWidth, spaceWidth);
|
|
21
|
-
|
|
26
|
+
const labelHeight = Math.min(labelMaxHeight, availableSpaceHeight);
|
|
27
|
+
if (!labelWidth || y > d.y1) {
|
|
22
28
|
return;
|
|
23
29
|
}
|
|
24
30
|
switch (align) {
|
|
@@ -35,8 +41,8 @@ function getLabels(args) {
|
|
|
35
41
|
break;
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
|
-
const bottom = y +
|
|
39
|
-
if (bottom > d.y1) {
|
|
44
|
+
const bottom = y + labelMaxHeight;
|
|
45
|
+
if (!html && bottom > d.y1) {
|
|
40
46
|
return;
|
|
41
47
|
}
|
|
42
48
|
const item = html
|
|
@@ -44,7 +50,7 @@ function getLabels(args) {
|
|
|
44
50
|
content: label,
|
|
45
51
|
x,
|
|
46
52
|
y,
|
|
47
|
-
size: { width: labelWidth, height:
|
|
53
|
+
size: { width: labelWidth, height: labelHeight },
|
|
48
54
|
}
|
|
49
55
|
: {
|
|
50
56
|
text: label,
|
|
@@ -54,6 +60,8 @@ function getLabels(args) {
|
|
|
54
60
|
nodeData: d.data,
|
|
55
61
|
};
|
|
56
62
|
acc.push(item);
|
|
63
|
+
prevLabelsHeight += labelHeight;
|
|
64
|
+
availableSpaceHeight = Math.max(0, availableSpaceHeight - labelHeight);
|
|
57
65
|
});
|
|
58
66
|
return acc;
|
|
59
67
|
}, []);
|
|
@@ -46,6 +46,14 @@ export interface PieSeries<T = MeaningfulAny> extends BaseSeries {
|
|
|
46
46
|
innerRadius?: string | number;
|
|
47
47
|
/** The radius of the pie relative to the chart area. The default behaviour is to scale to the chart area. */
|
|
48
48
|
radius?: string | number;
|
|
49
|
+
/**
|
|
50
|
+
* The minimum allowable radius of the pie.
|
|
51
|
+
*
|
|
52
|
+
* If specified as a percentage, the base for calculation is the height or width of the chart (the minimum value is taken) minus the halo effect.
|
|
53
|
+
*
|
|
54
|
+
* If not specified, the minimum radius is calculated as 30% of the height or width of the chart (the minimum value is taken) minus the halo effect.
|
|
55
|
+
*/
|
|
56
|
+
minRadius?: string | number;
|
|
49
57
|
/** Individual series legend options. Has higher priority than legend options in widget data */
|
|
50
58
|
legend?: ChartLegend & {
|
|
51
59
|
symbol?: RectLegendSymbolOptions;
|
|
@@ -11,7 +11,7 @@ export declare function hasOverlappingLabels({ width, labels, padding, style, }:
|
|
|
11
11
|
}): boolean;
|
|
12
12
|
export declare function getLabelsSize({ labels, style, rotation, html, }: {
|
|
13
13
|
labels: string[];
|
|
14
|
-
style?: BaseTextStyle;
|
|
14
|
+
style?: BaseTextStyle & React.CSSProperties;
|
|
15
15
|
rotation?: number;
|
|
16
16
|
html?: boolean;
|
|
17
17
|
}): {
|
|
@@ -14,12 +14,17 @@ export function handleOverflowingText(tSpan, maxWidth) {
|
|
|
14
14
|
revertRotation.setRotate(-angle, 0, 0);
|
|
15
15
|
textNode === null || textNode === void 0 ? void 0 : textNode.transform.baseVal.appendItem(revertRotation);
|
|
16
16
|
let text = tSpan.textContent || '';
|
|
17
|
-
|
|
17
|
+
// We believe that if the text goes beyond the boundaries of less than a pixel, it's not a big deal.
|
|
18
|
+
// Math.floor helps to solve the problem with the difference in rounding when comparing textLength with maxWidth.
|
|
19
|
+
let textLength = Math.floor(((_b = tSpan.getBoundingClientRect()) === null || _b === void 0 ? void 0 : _b.width) || 0);
|
|
18
20
|
while (textLength > maxWidth && text.length > 1) {
|
|
19
21
|
text = text.slice(0, -1);
|
|
20
22
|
tSpan.textContent = text + '…';
|
|
21
23
|
textLength = ((_c = tSpan.getBoundingClientRect()) === null || _c === void 0 ? void 0 : _c.width) || 0;
|
|
22
24
|
}
|
|
25
|
+
if (textLength > maxWidth) {
|
|
26
|
+
tSpan.textContent = '';
|
|
27
|
+
}
|
|
23
28
|
textNode === null || textNode === void 0 ? void 0 : textNode.transform.baseVal.removeItem((textNode === null || textNode === void 0 ? void 0 : textNode.transform.baseVal.length) - 1);
|
|
24
29
|
}
|
|
25
30
|
export function setEllipsisForOverflowText(selection, maxWidth) {
|
|
@@ -64,21 +69,22 @@ function renderLabels(selection, { labels, style = {}, attrs = {}, }) {
|
|
|
64
69
|
return text;
|
|
65
70
|
}
|
|
66
71
|
export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
67
|
-
var _a, _b, _c, _d, _e;
|
|
72
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
68
73
|
if (!labels.filter(Boolean).length) {
|
|
69
74
|
return { maxHeight: 0, maxWidth: 0 };
|
|
70
75
|
}
|
|
71
76
|
const container = select(document.body).append('div');
|
|
72
|
-
// TODO: Why do we need this styles?
|
|
73
|
-
// .attr('class', 'chartkit chartkit-theme_common');
|
|
74
77
|
const result = { maxHeight: 0, maxWidth: 0 };
|
|
75
78
|
let labelWrapper;
|
|
76
79
|
if (html) {
|
|
77
80
|
labelWrapper = container
|
|
78
81
|
.append('div')
|
|
79
82
|
.style('position', 'absolute')
|
|
83
|
+
.style('display', 'inline-block')
|
|
80
84
|
.style('font-size', (_a = style === null || style === void 0 ? void 0 : style.fontSize) !== null && _a !== void 0 ? _a : '')
|
|
81
85
|
.style('font-weight', (_b = style === null || style === void 0 ? void 0 : style.fontWeight) !== null && _b !== void 0 ? _b : '')
|
|
86
|
+
.style('max-width', (_c = style === null || style === void 0 ? void 0 : style.maxWidth) !== null && _c !== void 0 ? _c : '')
|
|
87
|
+
.style('max-height', (_d = style === null || style === void 0 ? void 0 : style.maxHeight) !== null && _d !== void 0 ? _d : '')
|
|
82
88
|
.node();
|
|
83
89
|
const { height, width } = labels.reduce((acc, l) => {
|
|
84
90
|
var _a, _b;
|
|
@@ -102,9 +108,9 @@ export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
|
102
108
|
.attr('text-anchor', rotation > 0 ? 'start' : 'end')
|
|
103
109
|
.style('transform', `rotate(${rotation}deg)`);
|
|
104
110
|
}
|
|
105
|
-
const rect = (
|
|
106
|
-
result.maxWidth = (
|
|
107
|
-
result.maxHeight = (
|
|
111
|
+
const rect = (_e = svg.select('g').node()) === null || _e === void 0 ? void 0 : _e.getBoundingClientRect();
|
|
112
|
+
result.maxWidth = (_f = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _f !== void 0 ? _f : 0;
|
|
113
|
+
result.maxHeight = (_g = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _g !== void 0 ? _g : 0;
|
|
108
114
|
}
|
|
109
115
|
container.remove();
|
|
110
116
|
return result;
|