@gravity-ui/charts 1.5.0 → 1.5.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/hooks/useShapes/pie/prepare-data.js +15 -4
- package/dist/cjs/hooks/useShapes/pie/utils.d.ts +10 -0
- package/dist/cjs/hooks/useShapes/pie/utils.js +18 -0
- package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +7 -3
- package/dist/cjs/utils/chart/text.js +10 -5
- package/dist/esm/hooks/useShapes/pie/prepare-data.js +15 -4
- package/dist/esm/hooks/useShapes/pie/utils.d.ts +10 -0
- package/dist/esm/hooks/useShapes/pie/utils.js +18 -0
- package/dist/esm/hooks/useShapes/treemap/prepare-data.js +7 -3
- package/dist/esm/utils/chart/text.js +10 -5
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { arc, group, line as lineGenerator } from 'd3';
|
|
2
2
|
import { calculateNumericProperty, getLabelsSize, getLeftPosition, isLabelsOverlapping, } from '../../../utils';
|
|
3
3
|
import { getFormattedValue } from '../../../utils/chart/format';
|
|
4
|
-
import { getCurveFactory, pieGenerator } from './utils';
|
|
4
|
+
import { getCurveFactory, getInscribedAngle, pieGenerator } from './utils';
|
|
5
5
|
const FULL_CIRCLE = Math.PI * 2;
|
|
6
6
|
const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
7
7
|
var _a, _b;
|
|
@@ -103,6 +103,7 @@ export function preparePieData(args) {
|
|
|
103
103
|
const labelArcGenerator = arc()
|
|
104
104
|
.innerRadius((d) => d.data.radius + distance + connectorPadding)
|
|
105
105
|
.outerRadius((d) => d.data.radius + distance + connectorPadding);
|
|
106
|
+
let shouldStopLabelPlacement = false;
|
|
106
107
|
series.forEach((d, index) => {
|
|
107
108
|
const prevLabel = labels[labels.length - 1];
|
|
108
109
|
const text = getFormattedValue(Object.assign({ value: d.data.label || d.data.value }, d.dataLabels));
|
|
@@ -158,8 +159,13 @@ export function preparePieData(args) {
|
|
|
158
159
|
let overlap = false;
|
|
159
160
|
if (prevLabel) {
|
|
160
161
|
overlap = isLabelsOverlapping(prevLabel, label, dataLabels.padding);
|
|
162
|
+
const startAngle = relatedSegment.startAngle +
|
|
163
|
+
(relatedSegment.endAngle - relatedSegment.startAngle) / 2;
|
|
161
164
|
if (overlap) {
|
|
162
|
-
let shouldAdjustAngle =
|
|
165
|
+
let shouldAdjustAngle = !shouldStopLabelPlacement;
|
|
166
|
+
const connectorPoints = getConnectorPoints(startAngle);
|
|
167
|
+
const pointA = connectorPoints[0];
|
|
168
|
+
const pointB = connectorPoints[connectorPoints.length - 1];
|
|
163
169
|
const step = Math.PI / 180;
|
|
164
170
|
while (shouldAdjustAngle) {
|
|
165
171
|
const newAngle = label.angle + step;
|
|
@@ -171,6 +177,11 @@ export function preparePieData(args) {
|
|
|
171
177
|
const [newX, newY] = getLabelPosition(newAngle);
|
|
172
178
|
label.x = newX;
|
|
173
179
|
label.y = newY;
|
|
180
|
+
const inscribedAngle = getInscribedAngle(pointA, pointB, [newX, newY]);
|
|
181
|
+
if (inscribedAngle > 90) {
|
|
182
|
+
shouldAdjustAngle = false;
|
|
183
|
+
shouldStopLabelPlacement = true;
|
|
184
|
+
}
|
|
174
185
|
if (!isLabelsOverlapping(prevLabel, label, dataLabels.padding)) {
|
|
175
186
|
shouldAdjustAngle = false;
|
|
176
187
|
overlap = false;
|
|
@@ -180,7 +191,7 @@ export function preparePieData(args) {
|
|
|
180
191
|
}
|
|
181
192
|
}
|
|
182
193
|
const isLabelOverlapped = !dataLabels.allowOverlap && overlap;
|
|
183
|
-
if (!isLabelOverlapped && label.maxWidth > 0) {
|
|
194
|
+
if (!isLabelOverlapped && label.maxWidth > 0 && !shouldStopLabelPlacement) {
|
|
184
195
|
if (shouldUseHtml) {
|
|
185
196
|
htmlLabels.push({
|
|
186
197
|
x: data.center[0] + label.x,
|
|
@@ -194,7 +205,7 @@ export function preparePieData(args) {
|
|
|
194
205
|
labels.push(label);
|
|
195
206
|
}
|
|
196
207
|
const connector = {
|
|
197
|
-
path: line(getConnectorPoints(
|
|
208
|
+
path: line(getConnectorPoints(label.angle)),
|
|
198
209
|
color: relatedSegment.data.color,
|
|
199
210
|
};
|
|
200
211
|
connectors.push(connector);
|
|
@@ -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;
|
|
@@ -13,3 +13,21 @@ export function getCurveFactory(data) {
|
|
|
13
13
|
}
|
|
14
14
|
return undefined;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Inscribed angle at vertex A (opposite side/chord BC): the angle between rays AB and AC.
|
|
18
|
+
*
|
|
19
|
+
* The order of B and C does not affect the result.
|
|
20
|
+
*
|
|
21
|
+
* @see: https://en.wikipedia.org/wiki/Inscribed_angle
|
|
22
|
+
* @returns The angle in degrees, in the range [0, 180].
|
|
23
|
+
*/
|
|
24
|
+
export function getInscribedAngle(a, b, c) {
|
|
25
|
+
const ux = b[0] - a[0];
|
|
26
|
+
const uy = b[1] - a[1];
|
|
27
|
+
const vx = c[0] - a[0];
|
|
28
|
+
const vy = c[1] - a[1];
|
|
29
|
+
const dot = ux * vx + uy * vy;
|
|
30
|
+
const cross = ux * vy - uy * vx;
|
|
31
|
+
const radians = Math.atan2(Math.abs(cross), dot);
|
|
32
|
+
return (radians * 180) / Math.PI;
|
|
33
|
+
}
|
|
@@ -4,13 +4,13 @@ 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
|
texts.forEach((text, index) => {
|
|
11
11
|
var _a;
|
|
12
12
|
const label = getFormattedValue(Object.assign({ value: text }, args.options));
|
|
13
|
-
const { maxHeight: lineHeight, maxWidth: labelMaxWidth } = (_a = getLabelsSize({ labels: [label], html })) !== null && _a !== void 0 ? _a : {};
|
|
13
|
+
const { maxHeight: lineHeight, maxWidth: labelMaxWidth } = (_a = getLabelsSize({ labels: [label], style, html })) !== null && _a !== void 0 ? _a : {};
|
|
14
14
|
const left = d.x0 + padding;
|
|
15
15
|
const right = d.x1 - padding;
|
|
16
16
|
const spaceWidth = Math.max(0, right - left);
|
|
@@ -35,6 +35,10 @@ function getLabels(args) {
|
|
|
35
35
|
break;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
const bottom = y + lineHeight;
|
|
39
|
+
if (bottom > d.y1) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
38
42
|
const item = html
|
|
39
43
|
? {
|
|
40
44
|
content: label,
|
|
@@ -120,7 +124,7 @@ export function prepareTreemapData(args) {
|
|
|
120
124
|
const { html, style: dataLabelsStyle } = series.dataLabels;
|
|
121
125
|
const labels = getLabels({ data: leaves, options: series.dataLabels });
|
|
122
126
|
if (html) {
|
|
123
|
-
const htmlItems = labels.map((l) => (Object.assign({ style: dataLabelsStyle }, l)));
|
|
127
|
+
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
128
|
htmlElements.push(...htmlItems);
|
|
125
129
|
}
|
|
126
130
|
else {
|
|
@@ -64,7 +64,7 @@ function renderLabels(selection, { labels, style = {}, attrs = {}, }) {
|
|
|
64
64
|
return text;
|
|
65
65
|
}
|
|
66
66
|
export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
67
|
-
var _a, _b, _c;
|
|
67
|
+
var _a, _b, _c, _d, _e;
|
|
68
68
|
if (!labels.filter(Boolean).length) {
|
|
69
69
|
return { maxHeight: 0, maxWidth: 0 };
|
|
70
70
|
}
|
|
@@ -74,7 +74,12 @@ export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
|
74
74
|
const result = { maxHeight: 0, maxWidth: 0 };
|
|
75
75
|
let labelWrapper;
|
|
76
76
|
if (html) {
|
|
77
|
-
labelWrapper = container
|
|
77
|
+
labelWrapper = container
|
|
78
|
+
.append('div')
|
|
79
|
+
.style('position', 'absolute')
|
|
80
|
+
.style('font-size', (_a = style === null || style === void 0 ? void 0 : style.fontSize) !== null && _a !== void 0 ? _a : '')
|
|
81
|
+
.style('font-weight', (_b = style === null || style === void 0 ? void 0 : style.fontWeight) !== null && _b !== void 0 ? _b : '')
|
|
82
|
+
.node();
|
|
78
83
|
const { height, width } = labels.reduce((acc, l) => {
|
|
79
84
|
var _a, _b;
|
|
80
85
|
if (labelWrapper) {
|
|
@@ -97,9 +102,9 @@ export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
|
97
102
|
.attr('text-anchor', rotation > 0 ? 'start' : 'end')
|
|
98
103
|
.style('transform', `rotate(${rotation}deg)`);
|
|
99
104
|
}
|
|
100
|
-
const rect = (
|
|
101
|
-
result.maxWidth = (
|
|
102
|
-
result.maxHeight = (
|
|
105
|
+
const rect = (_c = svg.select('g').node()) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect();
|
|
106
|
+
result.maxWidth = (_d = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _d !== void 0 ? _d : 0;
|
|
107
|
+
result.maxHeight = (_e = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _e !== void 0 ? _e : 0;
|
|
103
108
|
}
|
|
104
109
|
container.remove();
|
|
105
110
|
return result;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { arc, group, line as lineGenerator } from 'd3';
|
|
2
2
|
import { calculateNumericProperty, getLabelsSize, getLeftPosition, isLabelsOverlapping, } from '../../../utils';
|
|
3
3
|
import { getFormattedValue } from '../../../utils/chart/format';
|
|
4
|
-
import { getCurveFactory, pieGenerator } from './utils';
|
|
4
|
+
import { getCurveFactory, getInscribedAngle, pieGenerator } from './utils';
|
|
5
5
|
const FULL_CIRCLE = Math.PI * 2;
|
|
6
6
|
const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
7
7
|
var _a, _b;
|
|
@@ -103,6 +103,7 @@ export function preparePieData(args) {
|
|
|
103
103
|
const labelArcGenerator = arc()
|
|
104
104
|
.innerRadius((d) => d.data.radius + distance + connectorPadding)
|
|
105
105
|
.outerRadius((d) => d.data.radius + distance + connectorPadding);
|
|
106
|
+
let shouldStopLabelPlacement = false;
|
|
106
107
|
series.forEach((d, index) => {
|
|
107
108
|
const prevLabel = labels[labels.length - 1];
|
|
108
109
|
const text = getFormattedValue(Object.assign({ value: d.data.label || d.data.value }, d.dataLabels));
|
|
@@ -158,8 +159,13 @@ export function preparePieData(args) {
|
|
|
158
159
|
let overlap = false;
|
|
159
160
|
if (prevLabel) {
|
|
160
161
|
overlap = isLabelsOverlapping(prevLabel, label, dataLabels.padding);
|
|
162
|
+
const startAngle = relatedSegment.startAngle +
|
|
163
|
+
(relatedSegment.endAngle - relatedSegment.startAngle) / 2;
|
|
161
164
|
if (overlap) {
|
|
162
|
-
let shouldAdjustAngle =
|
|
165
|
+
let shouldAdjustAngle = !shouldStopLabelPlacement;
|
|
166
|
+
const connectorPoints = getConnectorPoints(startAngle);
|
|
167
|
+
const pointA = connectorPoints[0];
|
|
168
|
+
const pointB = connectorPoints[connectorPoints.length - 1];
|
|
163
169
|
const step = Math.PI / 180;
|
|
164
170
|
while (shouldAdjustAngle) {
|
|
165
171
|
const newAngle = label.angle + step;
|
|
@@ -171,6 +177,11 @@ export function preparePieData(args) {
|
|
|
171
177
|
const [newX, newY] = getLabelPosition(newAngle);
|
|
172
178
|
label.x = newX;
|
|
173
179
|
label.y = newY;
|
|
180
|
+
const inscribedAngle = getInscribedAngle(pointA, pointB, [newX, newY]);
|
|
181
|
+
if (inscribedAngle > 90) {
|
|
182
|
+
shouldAdjustAngle = false;
|
|
183
|
+
shouldStopLabelPlacement = true;
|
|
184
|
+
}
|
|
174
185
|
if (!isLabelsOverlapping(prevLabel, label, dataLabels.padding)) {
|
|
175
186
|
shouldAdjustAngle = false;
|
|
176
187
|
overlap = false;
|
|
@@ -180,7 +191,7 @@ export function preparePieData(args) {
|
|
|
180
191
|
}
|
|
181
192
|
}
|
|
182
193
|
const isLabelOverlapped = !dataLabels.allowOverlap && overlap;
|
|
183
|
-
if (!isLabelOverlapped && label.maxWidth > 0) {
|
|
194
|
+
if (!isLabelOverlapped && label.maxWidth > 0 && !shouldStopLabelPlacement) {
|
|
184
195
|
if (shouldUseHtml) {
|
|
185
196
|
htmlLabels.push({
|
|
186
197
|
x: data.center[0] + label.x,
|
|
@@ -194,7 +205,7 @@ export function preparePieData(args) {
|
|
|
194
205
|
labels.push(label);
|
|
195
206
|
}
|
|
196
207
|
const connector = {
|
|
197
|
-
path: line(getConnectorPoints(
|
|
208
|
+
path: line(getConnectorPoints(label.angle)),
|
|
198
209
|
color: relatedSegment.data.color,
|
|
199
210
|
};
|
|
200
211
|
connectors.push(connector);
|
|
@@ -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;
|
|
@@ -13,3 +13,21 @@ export function getCurveFactory(data) {
|
|
|
13
13
|
}
|
|
14
14
|
return undefined;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Inscribed angle at vertex A (opposite side/chord BC): the angle between rays AB and AC.
|
|
18
|
+
*
|
|
19
|
+
* The order of B and C does not affect the result.
|
|
20
|
+
*
|
|
21
|
+
* @see: https://en.wikipedia.org/wiki/Inscribed_angle
|
|
22
|
+
* @returns The angle in degrees, in the range [0, 180].
|
|
23
|
+
*/
|
|
24
|
+
export function getInscribedAngle(a, b, c) {
|
|
25
|
+
const ux = b[0] - a[0];
|
|
26
|
+
const uy = b[1] - a[1];
|
|
27
|
+
const vx = c[0] - a[0];
|
|
28
|
+
const vy = c[1] - a[1];
|
|
29
|
+
const dot = ux * vx + uy * vy;
|
|
30
|
+
const cross = ux * vy - uy * vx;
|
|
31
|
+
const radians = Math.atan2(Math.abs(cross), dot);
|
|
32
|
+
return (radians * 180) / Math.PI;
|
|
33
|
+
}
|
|
@@ -4,13 +4,13 @@ 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
|
texts.forEach((text, index) => {
|
|
11
11
|
var _a;
|
|
12
12
|
const label = getFormattedValue(Object.assign({ value: text }, args.options));
|
|
13
|
-
const { maxHeight: lineHeight, maxWidth: labelMaxWidth } = (_a = getLabelsSize({ labels: [label], html })) !== null && _a !== void 0 ? _a : {};
|
|
13
|
+
const { maxHeight: lineHeight, maxWidth: labelMaxWidth } = (_a = getLabelsSize({ labels: [label], style, html })) !== null && _a !== void 0 ? _a : {};
|
|
14
14
|
const left = d.x0 + padding;
|
|
15
15
|
const right = d.x1 - padding;
|
|
16
16
|
const spaceWidth = Math.max(0, right - left);
|
|
@@ -35,6 +35,10 @@ function getLabels(args) {
|
|
|
35
35
|
break;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
const bottom = y + lineHeight;
|
|
39
|
+
if (bottom > d.y1) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
38
42
|
const item = html
|
|
39
43
|
? {
|
|
40
44
|
content: label,
|
|
@@ -120,7 +124,7 @@ export function prepareTreemapData(args) {
|
|
|
120
124
|
const { html, style: dataLabelsStyle } = series.dataLabels;
|
|
121
125
|
const labels = getLabels({ data: leaves, options: series.dataLabels });
|
|
122
126
|
if (html) {
|
|
123
|
-
const htmlItems = labels.map((l) => (Object.assign({ style: dataLabelsStyle }, l)));
|
|
127
|
+
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
128
|
htmlElements.push(...htmlItems);
|
|
125
129
|
}
|
|
126
130
|
else {
|
|
@@ -64,7 +64,7 @@ function renderLabels(selection, { labels, style = {}, attrs = {}, }) {
|
|
|
64
64
|
return text;
|
|
65
65
|
}
|
|
66
66
|
export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
67
|
-
var _a, _b, _c;
|
|
67
|
+
var _a, _b, _c, _d, _e;
|
|
68
68
|
if (!labels.filter(Boolean).length) {
|
|
69
69
|
return { maxHeight: 0, maxWidth: 0 };
|
|
70
70
|
}
|
|
@@ -74,7 +74,12 @@ export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
|
74
74
|
const result = { maxHeight: 0, maxWidth: 0 };
|
|
75
75
|
let labelWrapper;
|
|
76
76
|
if (html) {
|
|
77
|
-
labelWrapper = container
|
|
77
|
+
labelWrapper = container
|
|
78
|
+
.append('div')
|
|
79
|
+
.style('position', 'absolute')
|
|
80
|
+
.style('font-size', (_a = style === null || style === void 0 ? void 0 : style.fontSize) !== null && _a !== void 0 ? _a : '')
|
|
81
|
+
.style('font-weight', (_b = style === null || style === void 0 ? void 0 : style.fontWeight) !== null && _b !== void 0 ? _b : '')
|
|
82
|
+
.node();
|
|
78
83
|
const { height, width } = labels.reduce((acc, l) => {
|
|
79
84
|
var _a, _b;
|
|
80
85
|
if (labelWrapper) {
|
|
@@ -97,9 +102,9 @@ export function getLabelsSize({ labels, style, rotation, html, }) {
|
|
|
97
102
|
.attr('text-anchor', rotation > 0 ? 'start' : 'end')
|
|
98
103
|
.style('transform', `rotate(${rotation}deg)`);
|
|
99
104
|
}
|
|
100
|
-
const rect = (
|
|
101
|
-
result.maxWidth = (
|
|
102
|
-
result.maxHeight = (
|
|
105
|
+
const rect = (_c = svg.select('g').node()) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect();
|
|
106
|
+
result.maxWidth = (_d = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _d !== void 0 ? _d : 0;
|
|
107
|
+
result.maxHeight = (_e = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _e !== void 0 ? _e : 0;
|
|
103
108
|
}
|
|
104
109
|
container.remove();
|
|
105
110
|
return result;
|