@gravity-ui/charts 1.5.0 → 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 +74 -27
- package/dist/cjs/hooks/useShapes/pie/utils.d.ts +10 -0
- package/dist/cjs/hooks/useShapes/pie/utils.js +20 -1
- package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +23 -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 +19 -8
- 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 +74 -27
- package/dist/esm/hooks/useShapes/pie/utils.d.ts +10 -0
- package/dist/esm/hooks/useShapes/pie/utils.js +20 -1
- package/dist/esm/hooks/useShapes/treemap/prepare-data.js +23 -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 +19 -8
- 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,7 +1,9 @@
|
|
|
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
|
-
import { getCurveFactory, pieGenerator } from './utils';
|
|
6
|
+
import { getCurveFactory, getInscribedAngle, pieGenerator } from './utils';
|
|
5
7
|
const FULL_CIRCLE = Math.PI * 2;
|
|
6
8
|
const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
7
9
|
var _a, _b;
|
|
@@ -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);
|
|
@@ -103,22 +111,36 @@ export function preparePieData(args) {
|
|
|
103
111
|
const labelArcGenerator = arc()
|
|
104
112
|
.innerRadius((d) => d.data.radius + distance + connectorPadding)
|
|
105
113
|
.outerRadius((d) => d.data.radius + distance + connectorPadding);
|
|
114
|
+
let shouldStopLabelPlacement = false;
|
|
115
|
+
// eslint-disable-next-line complexity
|
|
106
116
|
series.forEach((d, index) => {
|
|
107
117
|
const prevLabel = labels[labels.length - 1];
|
|
108
118
|
const text = getFormattedValue(Object.assign({ value: d.data.label || d.data.value }, d.dataLabels));
|
|
109
|
-
const
|
|
110
|
-
|
|
119
|
+
const labelSize = getLabelsSize({
|
|
120
|
+
labels: [text],
|
|
121
|
+
style: dataLabelsStyle,
|
|
122
|
+
html: shouldUseHtml,
|
|
123
|
+
});
|
|
111
124
|
const labelWidth = labelSize.maxWidth;
|
|
112
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
|
+
*/
|
|
113
138
|
const getLabelPosition = (angle) => {
|
|
114
139
|
let [x, y] = labelArcGenerator.centroid(Object.assign(Object.assign({}, relatedSegment), { startAngle: angle, endAngle: angle }));
|
|
115
140
|
if (shouldUseHtml) {
|
|
116
141
|
x = x < 0 ? x - labelWidth : x;
|
|
117
|
-
y = y - labelSize.maxHeight;
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
y = y < 0 ? y - labelHeight : y;
|
|
121
142
|
}
|
|
143
|
+
y = y < 0 ? y - labelHeight : y;
|
|
122
144
|
return [x, y];
|
|
123
145
|
};
|
|
124
146
|
const getConnectorPoints = (angle) => {
|
|
@@ -158,8 +180,13 @@ export function preparePieData(args) {
|
|
|
158
180
|
let overlap = false;
|
|
159
181
|
if (prevLabel) {
|
|
160
182
|
overlap = isLabelsOverlapping(prevLabel, label, dataLabels.padding);
|
|
183
|
+
const startAngle = relatedSegment.startAngle +
|
|
184
|
+
(relatedSegment.endAngle - relatedSegment.startAngle) / 2;
|
|
161
185
|
if (overlap) {
|
|
162
|
-
let shouldAdjustAngle =
|
|
186
|
+
let shouldAdjustAngle = !shouldStopLabelPlacement;
|
|
187
|
+
const connectorPoints = getConnectorPoints(startAngle);
|
|
188
|
+
const pointA = connectorPoints[0];
|
|
189
|
+
const pointB = connectorPoints[connectorPoints.length - 1];
|
|
163
190
|
const step = Math.PI / 180;
|
|
164
191
|
while (shouldAdjustAngle) {
|
|
165
192
|
const newAngle = label.angle + step;
|
|
@@ -167,10 +194,21 @@ export function preparePieData(args) {
|
|
|
167
194
|
shouldAdjustAngle = false;
|
|
168
195
|
}
|
|
169
196
|
else {
|
|
170
|
-
label.angle = newAngle;
|
|
171
197
|
const [newX, newY] = getLabelPosition(newAngle);
|
|
198
|
+
label.angle = newAngle;
|
|
199
|
+
label.textAnchor = newAngle < Math.PI ? 'start' : 'end';
|
|
172
200
|
label.x = newX;
|
|
173
201
|
label.y = newY;
|
|
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);
|
|
208
|
+
if (inscribedAngle > 90) {
|
|
209
|
+
shouldAdjustAngle = false;
|
|
210
|
+
shouldStopLabelPlacement = true;
|
|
211
|
+
}
|
|
174
212
|
if (!isLabelsOverlapping(prevLabel, label, dataLabels.padding)) {
|
|
175
213
|
shouldAdjustAngle = false;
|
|
176
214
|
overlap = false;
|
|
@@ -180,7 +218,8 @@ export function preparePieData(args) {
|
|
|
180
218
|
}
|
|
181
219
|
}
|
|
182
220
|
const isLabelOverlapped = !dataLabels.allowOverlap && overlap;
|
|
183
|
-
if (!isLabelOverlapped && label.maxWidth > 0) {
|
|
221
|
+
if (!isLabelOverlapped && label.maxWidth > 0 && !shouldStopLabelPlacement) {
|
|
222
|
+
labels.push(label);
|
|
184
223
|
if (shouldUseHtml) {
|
|
185
224
|
htmlLabels.push({
|
|
186
225
|
x: data.center[0] + label.x,
|
|
@@ -190,23 +229,21 @@ export function preparePieData(args) {
|
|
|
190
229
|
style: label.style,
|
|
191
230
|
});
|
|
192
231
|
}
|
|
193
|
-
else {
|
|
194
|
-
labels.push(label);
|
|
195
|
-
}
|
|
196
232
|
const connector = {
|
|
197
|
-
path: line(getConnectorPoints(
|
|
233
|
+
path: line(getConnectorPoints(label.angle)),
|
|
198
234
|
color: relatedSegment.data.color,
|
|
199
235
|
};
|
|
200
236
|
connectors.push(connector);
|
|
201
237
|
}
|
|
202
238
|
});
|
|
203
239
|
return {
|
|
204
|
-
labels,
|
|
240
|
+
labels: shouldUseHtml ? [] : labels,
|
|
205
241
|
htmlLabels,
|
|
206
242
|
connectors,
|
|
207
243
|
};
|
|
208
244
|
};
|
|
209
245
|
return Array.from(groupedPieSeries).map(([stackId, items]) => {
|
|
246
|
+
var _a;
|
|
210
247
|
const data = prepareItem(stackId, items);
|
|
211
248
|
const preparedLabels = prepareLabels({
|
|
212
249
|
data,
|
|
@@ -240,7 +277,7 @@ export function preparePieData(args) {
|
|
|
240
277
|
topFreeSpace = Math.min(topFreeSpace, data.center[1] - topSvgLabel);
|
|
241
278
|
}
|
|
242
279
|
if (preparedLabels.htmlLabels.length) {
|
|
243
|
-
const topHtmlLabel = Math.
|
|
280
|
+
const topHtmlLabel = Math.min(...preparedLabels.htmlLabels.map((l) => l.y));
|
|
244
281
|
topFreeSpace = Math.min(topFreeSpace, topHtmlLabel);
|
|
245
282
|
}
|
|
246
283
|
let bottomFreeSpace = data.center[1] - segmentMaxRadius - haloSize;
|
|
@@ -256,14 +293,16 @@ export function preparePieData(args) {
|
|
|
256
293
|
const bottomAdjustment = Math.max(0, Math.min(bottomFreeSpace, maxLeftRightFreeSpace));
|
|
257
294
|
if (topAdjustment && topAdjustment >= bottomAdjustment) {
|
|
258
295
|
data.segments.forEach((s) => {
|
|
259
|
-
|
|
296
|
+
let nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
297
|
+
nextPossibleRadius = Math.max(nextPossibleRadius, minRadius);
|
|
260
298
|
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
261
299
|
});
|
|
262
300
|
data.center[1] -= (topAdjustment - bottomAdjustment) / 2;
|
|
263
301
|
}
|
|
264
302
|
else if (bottomAdjustment) {
|
|
265
303
|
data.segments.forEach((s) => {
|
|
266
|
-
|
|
304
|
+
let nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
305
|
+
nextPossibleRadius = Math.max(nextPossibleRadius, minRadius);
|
|
267
306
|
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
268
307
|
});
|
|
269
308
|
data.center[1] += (bottomAdjustment - topAdjustment) / 2;
|
|
@@ -274,6 +313,14 @@ export function preparePieData(args) {
|
|
|
274
313
|
series: items,
|
|
275
314
|
allowOverlow: false,
|
|
276
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
|
+
}
|
|
277
324
|
data.labels = labels;
|
|
278
325
|
data.htmlLabels = htmlLabels;
|
|
279
326
|
data.connectors = connectors;
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { CurveFactory } from 'd3';
|
|
2
|
+
import type { PointPosition } from '../../../types';
|
|
2
3
|
import type { PreparedPieData, SegmentData } from './types';
|
|
3
4
|
export declare const pieGenerator: import("d3-shape").Pie<any, SegmentData>;
|
|
4
5
|
export declare function getCurveFactory(data: PreparedPieData): CurveFactory | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Inscribed angle at vertex A (opposite side/chord BC): the angle between rays AB and AC.
|
|
8
|
+
*
|
|
9
|
+
* The order of B and C does not affect the result.
|
|
10
|
+
*
|
|
11
|
+
* @see: https://en.wikipedia.org/wiki/Inscribed_angle
|
|
12
|
+
* @returns The angle in degrees, in the range [0, 180].
|
|
13
|
+
*/
|
|
14
|
+
export declare function getInscribedAngle(a: PointPosition, b: PointPosition, c: PointPosition): number;
|
|
@@ -10,6 +10,25 @@ export function getCurveFactory(data) {
|
|
|
10
10
|
case 'linear': {
|
|
11
11
|
return curveLinear;
|
|
12
12
|
}
|
|
13
|
+
default:
|
|
14
|
+
return undefined;
|
|
13
15
|
}
|
|
14
|
-
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Inscribed angle at vertex A (opposite side/chord BC): the angle between rays AB and AC.
|
|
19
|
+
*
|
|
20
|
+
* The order of B and C does not affect the result.
|
|
21
|
+
*
|
|
22
|
+
* @see: https://en.wikipedia.org/wiki/Inscribed_angle
|
|
23
|
+
* @returns The angle in degrees, in the range [0, 180].
|
|
24
|
+
*/
|
|
25
|
+
export function getInscribedAngle(a, b, c) {
|
|
26
|
+
const ux = b[0] - a[0];
|
|
27
|
+
const uy = b[1] - a[1];
|
|
28
|
+
const vx = c[0] - a[0];
|
|
29
|
+
const vy = c[1] - a[1];
|
|
30
|
+
const dot = ux * vx + uy * vy;
|
|
31
|
+
const cross = ux * vy - uy * vx;
|
|
32
|
+
const radians = Math.atan2(Math.abs(cross), dot);
|
|
33
|
+
return (radians * 180) / Math.PI;
|
|
15
34
|
}
|
|
@@ -4,21 +4,27 @@ import { getLabelsSize } from '../../../utils';
|
|
|
4
4
|
import { getFormattedValue } from '../../../utils/chart/format';
|
|
5
5
|
const DEFAULT_PADDING = 1;
|
|
6
6
|
function getLabels(args) {
|
|
7
|
-
const { data, options: { html, padding, align }, } = args;
|
|
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,12 +41,16 @@ function getLabels(args) {
|
|
|
35
41
|
break;
|
|
36
42
|
}
|
|
37
43
|
}
|
|
44
|
+
const bottom = y + labelMaxHeight;
|
|
45
|
+
if (!html && bottom > d.y1) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
38
48
|
const item = html
|
|
39
49
|
? {
|
|
40
50
|
content: label,
|
|
41
51
|
x,
|
|
42
52
|
y,
|
|
43
|
-
size: { width: labelWidth, height:
|
|
53
|
+
size: { width: labelWidth, height: labelHeight },
|
|
44
54
|
}
|
|
45
55
|
: {
|
|
46
56
|
text: label,
|
|
@@ -50,6 +60,8 @@ function getLabels(args) {
|
|
|
50
60
|
nodeData: d.data,
|
|
51
61
|
};
|
|
52
62
|
acc.push(item);
|
|
63
|
+
prevLabelsHeight += labelHeight;
|
|
64
|
+
availableSpaceHeight = Math.max(0, availableSpaceHeight - labelHeight);
|
|
53
65
|
});
|
|
54
66
|
return acc;
|
|
55
67
|
}, []);
|
|
@@ -120,7 +132,7 @@ export function prepareTreemapData(args) {
|
|
|
120
132
|
const { html, style: dataLabelsStyle } = series.dataLabels;
|
|
121
133
|
const labels = getLabels({ data: leaves, options: series.dataLabels });
|
|
122
134
|
if (html) {
|
|
123
|
-
const htmlItems = labels.map((l) => (Object.assign({ style: dataLabelsStyle }, l)));
|
|
135
|
+
const htmlItems = labels.map((l) => (Object.assign({ style: Object.assign(Object.assign({}, dataLabelsStyle), { maxWidth: l.size.width, maxHeight: l.size.height, overflow: 'hidden' }) }, l)));
|
|
124
136
|
htmlElements.push(...htmlItems);
|
|
125
137
|
}
|
|
126
138
|
else {
|
|
@@ -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,17 +69,23 @@ 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;
|
|
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
|
-
labelWrapper = container
|
|
80
|
+
labelWrapper = container
|
|
81
|
+
.append('div')
|
|
82
|
+
.style('position', 'absolute')
|
|
83
|
+
.style('display', 'inline-block')
|
|
84
|
+
.style('font-size', (_a = style === null || style === void 0 ? void 0 : style.fontSize) !== null && _a !== void 0 ? _a : '')
|
|
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 : '')
|
|
88
|
+
.node();
|
|
78
89
|
const { height, width } = labels.reduce((acc, l) => {
|
|
79
90
|
var _a, _b;
|
|
80
91
|
if (labelWrapper) {
|
|
@@ -97,9 +108,9 @@ export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
|
97
108
|
.attr('text-anchor', rotation > 0 ? 'start' : 'end')
|
|
98
109
|
.style('transform', `rotate(${rotation}deg)`);
|
|
99
110
|
}
|
|
100
|
-
const rect = (
|
|
101
|
-
result.maxWidth = (
|
|
102
|
-
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;
|
|
103
114
|
}
|
|
104
115
|
container.remove();
|
|
105
116
|
return result;
|