@gravity-ui/charts 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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-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/pie/prepare-data.js +60 -24
- 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/utils/chart/text.d.ts +1 -1
- package/dist/cjs/utils/chart/text.js +13 -7
- 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-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/pie/prepare-data.js +60 -24
- 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/utils/chart/text.d.ts +1 -1
- package/dist/esm/utils/chart/text.js +13 -7
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -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: {
|
|
@@ -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) => {
|
|
@@ -173,11 +194,17 @@ export function preparePieData(args) {
|
|
|
173
194
|
shouldAdjustAngle = false;
|
|
174
195
|
}
|
|
175
196
|
else {
|
|
176
|
-
label.angle = newAngle;
|
|
177
197
|
const [newX, newY] = getLabelPosition(newAngle);
|
|
198
|
+
label.angle = newAngle;
|
|
199
|
+
label.textAnchor = newAngle < Math.PI ? 'start' : 'end';
|
|
178
200
|
label.x = newX;
|
|
179
201
|
label.y = newY;
|
|
180
|
-
|
|
202
|
+
// See `getLabelPosition`: for HTML labels we return top-left,
|
|
203
|
+
// so shift x by labelWidth when textAnchor is 'end'.
|
|
204
|
+
const pointC = shouldUseHtml && label.textAnchor === 'end'
|
|
205
|
+
? [newX + labelWidth, newY]
|
|
206
|
+
: [newX, newY];
|
|
207
|
+
const inscribedAngle = getInscribedAngle(pointA, pointB, pointC);
|
|
181
208
|
if (inscribedAngle > 90) {
|
|
182
209
|
shouldAdjustAngle = false;
|
|
183
210
|
shouldStopLabelPlacement = true;
|
|
@@ -192,6 +219,7 @@ export function preparePieData(args) {
|
|
|
192
219
|
}
|
|
193
220
|
const isLabelOverlapped = !dataLabels.allowOverlap && overlap;
|
|
194
221
|
if (!isLabelOverlapped && label.maxWidth > 0 && !shouldStopLabelPlacement) {
|
|
222
|
+
labels.push(label);
|
|
195
223
|
if (shouldUseHtml) {
|
|
196
224
|
htmlLabels.push({
|
|
197
225
|
x: data.center[0] + label.x,
|
|
@@ -201,9 +229,6 @@ export function preparePieData(args) {
|
|
|
201
229
|
style: label.style,
|
|
202
230
|
});
|
|
203
231
|
}
|
|
204
|
-
else {
|
|
205
|
-
labels.push(label);
|
|
206
|
-
}
|
|
207
232
|
const connector = {
|
|
208
233
|
path: line(getConnectorPoints(label.angle)),
|
|
209
234
|
color: relatedSegment.data.color,
|
|
@@ -212,12 +237,13 @@ export function preparePieData(args) {
|
|
|
212
237
|
}
|
|
213
238
|
});
|
|
214
239
|
return {
|
|
215
|
-
labels,
|
|
240
|
+
labels: shouldUseHtml ? [] : labels,
|
|
216
241
|
htmlLabels,
|
|
217
242
|
connectors,
|
|
218
243
|
};
|
|
219
244
|
};
|
|
220
245
|
return Array.from(groupedPieSeries).map(([stackId, items]) => {
|
|
246
|
+
var _a;
|
|
221
247
|
const data = prepareItem(stackId, items);
|
|
222
248
|
const preparedLabels = prepareLabels({
|
|
223
249
|
data,
|
|
@@ -251,7 +277,7 @@ export function preparePieData(args) {
|
|
|
251
277
|
topFreeSpace = Math.min(topFreeSpace, data.center[1] - topSvgLabel);
|
|
252
278
|
}
|
|
253
279
|
if (preparedLabels.htmlLabels.length) {
|
|
254
|
-
const topHtmlLabel = Math.
|
|
280
|
+
const topHtmlLabel = Math.min(...preparedLabels.htmlLabels.map((l) => l.y));
|
|
255
281
|
topFreeSpace = Math.min(topFreeSpace, topHtmlLabel);
|
|
256
282
|
}
|
|
257
283
|
let bottomFreeSpace = data.center[1] - segmentMaxRadius - haloSize;
|
|
@@ -267,14 +293,16 @@ export function preparePieData(args) {
|
|
|
267
293
|
const bottomAdjustment = Math.max(0, Math.min(bottomFreeSpace, maxLeftRightFreeSpace));
|
|
268
294
|
if (topAdjustment && topAdjustment >= bottomAdjustment) {
|
|
269
295
|
data.segments.forEach((s) => {
|
|
270
|
-
|
|
296
|
+
let nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
297
|
+
nextPossibleRadius = Math.max(nextPossibleRadius, minRadius);
|
|
271
298
|
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
272
299
|
});
|
|
273
300
|
data.center[1] -= (topAdjustment - bottomAdjustment) / 2;
|
|
274
301
|
}
|
|
275
302
|
else if (bottomAdjustment) {
|
|
276
303
|
data.segments.forEach((s) => {
|
|
277
|
-
|
|
304
|
+
let nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
305
|
+
nextPossibleRadius = Math.max(nextPossibleRadius, minRadius);
|
|
278
306
|
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
279
307
|
});
|
|
280
308
|
data.center[1] += (bottomAdjustment - topAdjustment) / 2;
|
|
@@ -285,6 +313,14 @@ export function preparePieData(args) {
|
|
|
285
313
|
series: items,
|
|
286
314
|
allowOverlow: false,
|
|
287
315
|
});
|
|
316
|
+
if (typeof ((_a = items[0]) === null || _a === void 0 ? void 0 : _a.innerRadius) !== 'undefined') {
|
|
317
|
+
const resultSegmentMaxRadius = Math.max(...data.segments.map((s) => s.data.radius));
|
|
318
|
+
const resultInnerRadius = calculateNumericProperty({
|
|
319
|
+
value: items[0].innerRadius,
|
|
320
|
+
base: resultSegmentMaxRadius,
|
|
321
|
+
}) || 0;
|
|
322
|
+
data.innerRadius = resultInnerRadius;
|
|
323
|
+
}
|
|
288
324
|
data.labels = labels;
|
|
289
325
|
data.htmlLabels = htmlLabels;
|
|
290
326
|
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;
|
|
@@ -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;
|
|
@@ -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: {
|
|
@@ -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) => {
|
|
@@ -173,11 +194,17 @@ export function preparePieData(args) {
|
|
|
173
194
|
shouldAdjustAngle = false;
|
|
174
195
|
}
|
|
175
196
|
else {
|
|
176
|
-
label.angle = newAngle;
|
|
177
197
|
const [newX, newY] = getLabelPosition(newAngle);
|
|
198
|
+
label.angle = newAngle;
|
|
199
|
+
label.textAnchor = newAngle < Math.PI ? 'start' : 'end';
|
|
178
200
|
label.x = newX;
|
|
179
201
|
label.y = newY;
|
|
180
|
-
|
|
202
|
+
// See `getLabelPosition`: for HTML labels we return top-left,
|
|
203
|
+
// so shift x by labelWidth when textAnchor is 'end'.
|
|
204
|
+
const pointC = shouldUseHtml && label.textAnchor === 'end'
|
|
205
|
+
? [newX + labelWidth, newY]
|
|
206
|
+
: [newX, newY];
|
|
207
|
+
const inscribedAngle = getInscribedAngle(pointA, pointB, pointC);
|
|
181
208
|
if (inscribedAngle > 90) {
|
|
182
209
|
shouldAdjustAngle = false;
|
|
183
210
|
shouldStopLabelPlacement = true;
|
|
@@ -192,6 +219,7 @@ export function preparePieData(args) {
|
|
|
192
219
|
}
|
|
193
220
|
const isLabelOverlapped = !dataLabels.allowOverlap && overlap;
|
|
194
221
|
if (!isLabelOverlapped && label.maxWidth > 0 && !shouldStopLabelPlacement) {
|
|
222
|
+
labels.push(label);
|
|
195
223
|
if (shouldUseHtml) {
|
|
196
224
|
htmlLabels.push({
|
|
197
225
|
x: data.center[0] + label.x,
|
|
@@ -201,9 +229,6 @@ export function preparePieData(args) {
|
|
|
201
229
|
style: label.style,
|
|
202
230
|
});
|
|
203
231
|
}
|
|
204
|
-
else {
|
|
205
|
-
labels.push(label);
|
|
206
|
-
}
|
|
207
232
|
const connector = {
|
|
208
233
|
path: line(getConnectorPoints(label.angle)),
|
|
209
234
|
color: relatedSegment.data.color,
|
|
@@ -212,12 +237,13 @@ export function preparePieData(args) {
|
|
|
212
237
|
}
|
|
213
238
|
});
|
|
214
239
|
return {
|
|
215
|
-
labels,
|
|
240
|
+
labels: shouldUseHtml ? [] : labels,
|
|
216
241
|
htmlLabels,
|
|
217
242
|
connectors,
|
|
218
243
|
};
|
|
219
244
|
};
|
|
220
245
|
return Array.from(groupedPieSeries).map(([stackId, items]) => {
|
|
246
|
+
var _a;
|
|
221
247
|
const data = prepareItem(stackId, items);
|
|
222
248
|
const preparedLabels = prepareLabels({
|
|
223
249
|
data,
|
|
@@ -251,7 +277,7 @@ export function preparePieData(args) {
|
|
|
251
277
|
topFreeSpace = Math.min(topFreeSpace, data.center[1] - topSvgLabel);
|
|
252
278
|
}
|
|
253
279
|
if (preparedLabels.htmlLabels.length) {
|
|
254
|
-
const topHtmlLabel = Math.
|
|
280
|
+
const topHtmlLabel = Math.min(...preparedLabels.htmlLabels.map((l) => l.y));
|
|
255
281
|
topFreeSpace = Math.min(topFreeSpace, topHtmlLabel);
|
|
256
282
|
}
|
|
257
283
|
let bottomFreeSpace = data.center[1] - segmentMaxRadius - haloSize;
|
|
@@ -267,14 +293,16 @@ export function preparePieData(args) {
|
|
|
267
293
|
const bottomAdjustment = Math.max(0, Math.min(bottomFreeSpace, maxLeftRightFreeSpace));
|
|
268
294
|
if (topAdjustment && topAdjustment >= bottomAdjustment) {
|
|
269
295
|
data.segments.forEach((s) => {
|
|
270
|
-
|
|
296
|
+
let nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
297
|
+
nextPossibleRadius = Math.max(nextPossibleRadius, minRadius);
|
|
271
298
|
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
272
299
|
});
|
|
273
300
|
data.center[1] -= (topAdjustment - bottomAdjustment) / 2;
|
|
274
301
|
}
|
|
275
302
|
else if (bottomAdjustment) {
|
|
276
303
|
data.segments.forEach((s) => {
|
|
277
|
-
|
|
304
|
+
let nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
305
|
+
nextPossibleRadius = Math.max(nextPossibleRadius, minRadius);
|
|
278
306
|
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
279
307
|
});
|
|
280
308
|
data.center[1] += (bottomAdjustment - topAdjustment) / 2;
|
|
@@ -285,6 +313,14 @@ export function preparePieData(args) {
|
|
|
285
313
|
series: items,
|
|
286
314
|
allowOverlow: false,
|
|
287
315
|
});
|
|
316
|
+
if (typeof ((_a = items[0]) === null || _a === void 0 ? void 0 : _a.innerRadius) !== 'undefined') {
|
|
317
|
+
const resultSegmentMaxRadius = Math.max(...data.segments.map((s) => s.data.radius));
|
|
318
|
+
const resultInnerRadius = calculateNumericProperty({
|
|
319
|
+
value: items[0].innerRadius,
|
|
320
|
+
base: resultSegmentMaxRadius,
|
|
321
|
+
}) || 0;
|
|
322
|
+
data.innerRadius = resultInnerRadius;
|
|
323
|
+
}
|
|
288
324
|
data.labels = labels;
|
|
289
325
|
data.htmlLabels = htmlLabels;
|
|
290
326
|
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;
|