@gravity-ui/charts 1.3.1 → 1.5.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/components/Tooltip/DefaultContent.js +14 -9
- package/dist/cjs/components/Tooltip/index.js +1 -1
- package/dist/cjs/components/Tooltip/styles.css +15 -2
- package/dist/cjs/hooks/useSeries/prepare-treemap.js +3 -2
- package/dist/cjs/hooks/useSeries/types.d.ts +2 -1
- package/dist/cjs/hooks/useShapes/pie/prepare-data.js +65 -43
- package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +20 -12
- package/dist/cjs/types/chart/treemap.d.ts +11 -0
- package/dist/esm/components/Tooltip/DefaultContent.js +14 -9
- package/dist/esm/components/Tooltip/index.js +1 -1
- package/dist/esm/components/Tooltip/styles.css +15 -2
- package/dist/esm/hooks/useSeries/prepare-treemap.js +3 -2
- package/dist/esm/hooks/useSeries/types.d.ts +2 -1
- package/dist/esm/hooks/useShapes/pie/prepare-data.js +65 -43
- package/dist/esm/hooks/useShapes/treemap/prepare-data.js +20 -12
- package/dist/esm/types/chart/treemap.d.ts +11 -0
- package/package.json +1 -1
|
@@ -58,8 +58,9 @@ function getDefaultValueFormat({ axis }) {
|
|
|
58
58
|
}
|
|
59
59
|
export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
60
60
|
const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
|
|
61
|
-
return (React.createElement(
|
|
62
|
-
measureValue && React.createElement("div",
|
|
61
|
+
return (React.createElement("div", { className: b('content') },
|
|
62
|
+
measureValue && React.createElement("div", { className: b('series-name') }, measureValue),
|
|
63
|
+
// eslint-disable-next-line complexity
|
|
63
64
|
hovered.map((seriesItem, i) => {
|
|
64
65
|
var _a;
|
|
65
66
|
const { data, series, closest } = seriesItem;
|
|
@@ -79,9 +80,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
79
80
|
series.name,
|
|
80
81
|
": ",
|
|
81
82
|
formattedValue));
|
|
82
|
-
|
|
83
|
+
const active = closest && hovered.length > 1;
|
|
84
|
+
return (React.createElement("div", { key: id, className: b('content-row', { active }) },
|
|
83
85
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
84
|
-
React.createElement("div", null,
|
|
86
|
+
React.createElement("div", null, value)));
|
|
85
87
|
}
|
|
86
88
|
case 'waterfall': {
|
|
87
89
|
const isTotal = get(data, 'total', false);
|
|
@@ -119,9 +121,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
119
121
|
series.name,
|
|
120
122
|
": ",
|
|
121
123
|
formattedValue));
|
|
122
|
-
|
|
124
|
+
const active = closest && hovered.length > 1;
|
|
125
|
+
return (React.createElement("div", { key: id, className: b('content-row', { active }) },
|
|
123
126
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
124
|
-
React.createElement("div", null,
|
|
127
|
+
React.createElement("div", null, value)));
|
|
125
128
|
}
|
|
126
129
|
case 'pie':
|
|
127
130
|
case 'treemap': {
|
|
@@ -152,7 +155,8 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
152
155
|
React.createElement("span", null, "\u2192"),
|
|
153
156
|
" ", target === null || target === void 0 ? void 0 :
|
|
154
157
|
target.name,
|
|
155
|
-
":
|
|
158
|
+
":",
|
|
159
|
+
' ',
|
|
156
160
|
formattedValue)));
|
|
157
161
|
}
|
|
158
162
|
case 'radar': {
|
|
@@ -167,9 +171,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
167
171
|
radarSeries.name || radarSeries.id,
|
|
168
172
|
"\u00A0"),
|
|
169
173
|
React.createElement("span", null, formattedValue)));
|
|
170
|
-
|
|
174
|
+
const active = closest && hovered.length > 1;
|
|
175
|
+
return (React.createElement("div", { key: id, className: b('content-row', { active }) },
|
|
171
176
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
172
|
-
React.createElement("div", null,
|
|
177
|
+
React.createElement("div", null, value)));
|
|
173
178
|
}
|
|
174
179
|
default: {
|
|
175
180
|
return null;
|
|
@@ -22,6 +22,6 @@ export const Tooltip = (props) => {
|
|
|
22
22
|
window.dispatchEvent(new CustomEvent('scroll'));
|
|
23
23
|
}, [left, top]);
|
|
24
24
|
return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { anchorElement: anchor, className: b({ pinned: tooltipPinned }), disableTransition: true, floatingStyles: tooltipPinned ? undefined : { pointerEvents: 'none' }, offset: { mainAxis: 20 }, onOpenChange: tooltipPinned ? handleOnOpenChange : undefined, open: true, placement: ['right', 'left', 'top', 'bottom'] },
|
|
25
|
-
React.createElement("div", { className: b('content') },
|
|
25
|
+
React.createElement("div", { className: b('popup-content') },
|
|
26
26
|
React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer, valueFormat: tooltip.valueFormat })))) : null;
|
|
27
27
|
};
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
.gcharts-tooltip {
|
|
2
2
|
box-shadow: 0 2px 12px var(--g-color-sfx-shadow);
|
|
3
3
|
}
|
|
4
|
-
.gcharts-
|
|
5
|
-
padding: 8px 14px;
|
|
4
|
+
.gcharts-tooltip__popup-content {
|
|
6
5
|
text-wrap: nowrap;
|
|
7
6
|
border-radius: 4px;
|
|
8
7
|
background-color: var(--g-color-infographics-tooltip-bg);
|
|
9
8
|
}
|
|
9
|
+
.gcharts-tooltip__content {
|
|
10
|
+
padding: var(--gcharts-tooltip-content-padding, 8px 0);
|
|
11
|
+
}
|
|
12
|
+
.gcharts-tooltip__series-name {
|
|
13
|
+
padding: 2px 14px 6px;
|
|
14
|
+
font-size: 13px;
|
|
15
|
+
font-weight: 600;
|
|
16
|
+
}
|
|
10
17
|
.gcharts-tooltip__content-row {
|
|
11
18
|
display: flex;
|
|
12
19
|
align-items: center;
|
|
20
|
+
padding: 2px 14px;
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
}
|
|
23
|
+
.gcharts-tooltip__content-row_active {
|
|
24
|
+
font-weight: 600;
|
|
25
|
+
background-color: var(--g-color-base-info-medium);
|
|
13
26
|
}
|
|
14
27
|
.gcharts-tooltip__color {
|
|
15
28
|
display: inline-block;
|
|
@@ -6,7 +6,7 @@ import { prepareLegendSymbol } from './utils';
|
|
|
6
6
|
export function prepareTreemap(args) {
|
|
7
7
|
const { colorScale, legend, series } = args;
|
|
8
8
|
return series.map((s) => {
|
|
9
|
-
var _a, _b;
|
|
9
|
+
var _a, _b, _c;
|
|
10
10
|
const id = getUniqId();
|
|
11
11
|
const name = s.name || '';
|
|
12
12
|
const color = s.color || colorScale(name);
|
|
@@ -30,9 +30,10 @@ export function prepareTreemap(args) {
|
|
|
30
30
|
enabled: get(s, 'legend.enabled', legend.enabled),
|
|
31
31
|
symbol: prepareLegendSymbol(s),
|
|
32
32
|
},
|
|
33
|
-
levels: s.levels,
|
|
33
|
+
levels: (_c = s.levels) !== null && _c !== void 0 ? _c : [],
|
|
34
34
|
layoutAlgorithm: get(s, 'layoutAlgorithm', LayoutAlgorithm.Binary),
|
|
35
35
|
cursor: get(s, 'cursor', null),
|
|
36
|
+
sorting: Object.assign({ enabled: false, direction: 'desc' }, s.sorting),
|
|
36
37
|
};
|
|
37
38
|
return preparedSeries;
|
|
38
39
|
});
|
|
@@ -242,7 +242,8 @@ export type PreparedTreemapSeries = {
|
|
|
242
242
|
format?: ValueFormat;
|
|
243
243
|
};
|
|
244
244
|
layoutAlgorithm: `${LayoutAlgorithm}`;
|
|
245
|
-
|
|
245
|
+
sorting: Required<TreemapSeries['sorting']>;
|
|
246
|
+
} & BasePreparedSeries & Required<Omit<TreemapSeries, keyof BasePreparedSeries>>;
|
|
246
247
|
export type PreparedWaterfallSeriesData = WaterfallSeriesData & {
|
|
247
248
|
index: number;
|
|
248
249
|
};
|
|
@@ -23,7 +23,6 @@ export function preparePieData(args) {
|
|
|
23
23
|
const maxRadius = Math.min(boundsWidth, boundsHeight) / 2 - haloSize;
|
|
24
24
|
const minRadius = maxRadius * 0.3;
|
|
25
25
|
const groupedPieSeries = group(preparedSeries, (pieSeries) => pieSeries.stackId);
|
|
26
|
-
let maxMissingWidth = 0;
|
|
27
26
|
const prepareItem = (stackId, items) => {
|
|
28
27
|
var _a;
|
|
29
28
|
const series = items[0];
|
|
@@ -77,7 +76,7 @@ export function preparePieData(args) {
|
|
|
77
76
|
return data;
|
|
78
77
|
};
|
|
79
78
|
const prepareLabels = (prepareLabelsArgs) => {
|
|
80
|
-
const { data, series } = prepareLabelsArgs;
|
|
79
|
+
const { data, series, allowOverlow = true } = prepareLabelsArgs;
|
|
81
80
|
const { dataLabels } = series[0];
|
|
82
81
|
const labels = [];
|
|
83
82
|
const htmlLabels = [];
|
|
@@ -120,7 +119,6 @@ export function preparePieData(args) {
|
|
|
120
119
|
else {
|
|
121
120
|
y = y < 0 ? y - labelHeight : y;
|
|
122
121
|
}
|
|
123
|
-
x = Math.max(-boundsWidth / 2, x);
|
|
124
122
|
return [x, y];
|
|
125
123
|
};
|
|
126
124
|
const getConnectorPoints = (angle) => {
|
|
@@ -148,6 +146,15 @@ export function preparePieData(args) {
|
|
|
148
146
|
segment: relatedSegment.data,
|
|
149
147
|
angle: midAngle,
|
|
150
148
|
};
|
|
149
|
+
if (!allowOverlow) {
|
|
150
|
+
const labelLeftPosition = getLeftPosition(label);
|
|
151
|
+
const newMaxWidth = labelLeftPosition > 0
|
|
152
|
+
? Math.min(boundsWidth / 2 - labelLeftPosition, labelWidth)
|
|
153
|
+
: Math.min(labelWidth - (-labelLeftPosition - boundsWidth / 2), labelWidth);
|
|
154
|
+
if (newMaxWidth !== label.maxWidth) {
|
|
155
|
+
label.maxWidth = Math.max(0, newMaxWidth);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
151
158
|
let overlap = false;
|
|
152
159
|
if (prevLabel) {
|
|
153
160
|
overlap = isLabelsOverlapping(prevLabel, label, dataLabels.padding);
|
|
@@ -172,21 +179,8 @@ export function preparePieData(args) {
|
|
|
172
179
|
}
|
|
173
180
|
}
|
|
174
181
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (Math.abs(left) > boundsWidth / 2) {
|
|
178
|
-
const overflow = Math.abs(left) - boundsWidth / 2;
|
|
179
|
-
label.maxWidth = label.size.width - overflow;
|
|
180
|
-
maxMissingWidth = Math.max(maxMissingWidth, overflow);
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
const right = left + label.size.width;
|
|
184
|
-
if (right > boundsWidth / 2) {
|
|
185
|
-
const overflow = right - boundsWidth / 2;
|
|
186
|
-
label.maxWidth = label.size.width - overflow;
|
|
187
|
-
maxMissingWidth = Math.max(maxMissingWidth, overflow);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
182
|
+
const isLabelOverlapped = !dataLabels.allowOverlap && overlap;
|
|
183
|
+
if (!isLabelOverlapped && label.maxWidth > 0) {
|
|
190
184
|
if (shouldUseHtml) {
|
|
191
185
|
htmlLabels.push({
|
|
192
186
|
x: data.center[0] + label.x,
|
|
@@ -218,43 +212,71 @@ export function preparePieData(args) {
|
|
|
218
212
|
data,
|
|
219
213
|
series: items,
|
|
220
214
|
});
|
|
215
|
+
let maxLeftRightFreeSpace = Infinity;
|
|
216
|
+
let labelsOverflow = 0;
|
|
217
|
+
preparedLabels.labels.forEach((label) => {
|
|
218
|
+
const left = getLeftPosition(label);
|
|
219
|
+
let freeSpace = 0;
|
|
220
|
+
if (left < 0) {
|
|
221
|
+
freeSpace = boundsWidth / 2 - Math.abs(left);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
freeSpace = boundsWidth / 2 - (left + label.size.width);
|
|
225
|
+
}
|
|
226
|
+
maxLeftRightFreeSpace = Math.max(0, Math.min(maxLeftRightFreeSpace, freeSpace));
|
|
227
|
+
labelsOverflow = freeSpace < 0 ? Math.max(labelsOverflow, -freeSpace) : labelsOverflow;
|
|
228
|
+
});
|
|
221
229
|
const segmentMaxRadius = Math.max(...data.segments.map((s) => s.data.radius));
|
|
222
|
-
|
|
223
|
-
const bottom = Math.max(data.center[1] + segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1] + l.size.height), ...preparedLabels.htmlLabels.map((l) => l.y + l.size.height));
|
|
224
|
-
if (topAdjustment > 0) {
|
|
230
|
+
if (labelsOverflow) {
|
|
225
231
|
data.segments.forEach((s) => {
|
|
226
|
-
const
|
|
227
|
-
s.data.radius =
|
|
232
|
+
const neeSegmentRadius = Math.max(minRadius, s.data.radius - labelsOverflow);
|
|
233
|
+
s.data.radius = neeSegmentRadius;
|
|
228
234
|
});
|
|
229
|
-
data.center[1] -= topAdjustment / 2;
|
|
230
235
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
236
|
+
else {
|
|
237
|
+
let topFreeSpace = data.center[1] - segmentMaxRadius - haloSize;
|
|
238
|
+
if (preparedLabels.labels.length) {
|
|
239
|
+
const topSvgLabel = Math.max(0, ...preparedLabels.labels.map((l) => -l.y));
|
|
240
|
+
topFreeSpace = Math.min(topFreeSpace, data.center[1] - topSvgLabel);
|
|
241
|
+
}
|
|
242
|
+
if (preparedLabels.htmlLabels.length) {
|
|
243
|
+
const topHtmlLabel = Math.max(0, ...preparedLabels.htmlLabels.map((l) => l.y));
|
|
244
|
+
topFreeSpace = Math.min(topFreeSpace, topHtmlLabel);
|
|
245
|
+
}
|
|
246
|
+
let bottomFreeSpace = data.center[1] - segmentMaxRadius - haloSize;
|
|
247
|
+
if (preparedLabels.labels.length) {
|
|
248
|
+
const bottomSvgLabel = Math.max(0, ...preparedLabels.labels.map((l) => l.y + l.size.height));
|
|
249
|
+
bottomFreeSpace = Math.min(bottomFreeSpace, data.center[1] - bottomSvgLabel);
|
|
250
|
+
}
|
|
251
|
+
if (preparedLabels.htmlLabels.length) {
|
|
252
|
+
const bottomHtmlLabel = Math.max(0, ...preparedLabels.htmlLabels.map((l) => l.y + l.size.height));
|
|
253
|
+
bottomFreeSpace = Math.min(bottomFreeSpace, data.center[1] * 2 - bottomHtmlLabel);
|
|
254
|
+
}
|
|
255
|
+
const topAdjustment = Math.max(0, Math.min(topFreeSpace, maxLeftRightFreeSpace));
|
|
256
|
+
const bottomAdjustment = Math.max(0, Math.min(bottomFreeSpace, maxLeftRightFreeSpace));
|
|
257
|
+
if (topAdjustment && topAdjustment >= bottomAdjustment) {
|
|
258
|
+
data.segments.forEach((s) => {
|
|
259
|
+
const nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
260
|
+
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
261
|
+
});
|
|
262
|
+
data.center[1] -= (topAdjustment - bottomAdjustment) / 2;
|
|
263
|
+
}
|
|
264
|
+
else if (bottomAdjustment) {
|
|
265
|
+
data.segments.forEach((s) => {
|
|
266
|
+
const nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
267
|
+
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
268
|
+
});
|
|
269
|
+
data.center[1] += (bottomAdjustment - topAdjustment) / 2;
|
|
270
|
+
}
|
|
238
271
|
}
|
|
239
272
|
const { labels, htmlLabels, connectors } = prepareLabels({
|
|
240
273
|
data,
|
|
241
274
|
series: items,
|
|
275
|
+
allowOverlow: false,
|
|
242
276
|
});
|
|
243
277
|
data.labels = labels;
|
|
244
278
|
data.htmlLabels = htmlLabels;
|
|
245
279
|
data.connectors = connectors;
|
|
246
|
-
if (maxMissingWidth > 0) {
|
|
247
|
-
const { dataLabels } = items[0];
|
|
248
|
-
if (dataLabels.enabled) {
|
|
249
|
-
data.segments.forEach((s) => {
|
|
250
|
-
s.data.radius = Math.max(minRadius, s.data.radius - maxMissingWidth);
|
|
251
|
-
});
|
|
252
|
-
const finalLabels = prepareLabels({ data, series: items });
|
|
253
|
-
data.labels = finalLabels.labels;
|
|
254
|
-
data.htmlLabels = finalLabels.htmlLabels;
|
|
255
|
-
data.connectors = finalLabels.connectors;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
280
|
return data;
|
|
259
281
|
});
|
|
260
282
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stratify, treemap, treemapBinary, treemapDice, treemapSlice, treemapSliceDice, treemapSquarify, } from 'd3';
|
|
1
|
+
import { ascending, descending, sort, stratify, treemap, treemapBinary, treemapDice, treemapSlice, treemapSliceDice, treemapSquarify, } from 'd3';
|
|
2
2
|
import { LayoutAlgorithm } from '../../../constants';
|
|
3
3
|
import { getLabelsSize } from '../../../utils';
|
|
4
4
|
import { getFormattedValue } from '../../../utils/chart/format';
|
|
@@ -57,7 +57,25 @@ function getLabels(args) {
|
|
|
57
57
|
export function prepareTreemapData(args) {
|
|
58
58
|
var _a;
|
|
59
59
|
const { series, width, height } = args;
|
|
60
|
-
const
|
|
60
|
+
const parentNodeValues = {};
|
|
61
|
+
let dataWithRootNode = series.data.reduce((acc, d) => {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
const dataChunk = Object.assign({}, d);
|
|
64
|
+
if (!dataChunk.parentId) {
|
|
65
|
+
dataChunk.parentId = series.id;
|
|
66
|
+
}
|
|
67
|
+
if (dataChunk.parentId) {
|
|
68
|
+
parentNodeValues[dataChunk.parentId] =
|
|
69
|
+
((_a = parentNodeValues[dataChunk.parentId]) !== null && _a !== void 0 ? _a : 0) + ((_b = dataChunk.value) !== null && _b !== void 0 ? _b : 0);
|
|
70
|
+
}
|
|
71
|
+
acc.push(dataChunk);
|
|
72
|
+
return acc;
|
|
73
|
+
}, [{ name: series.name, id: series.id }]);
|
|
74
|
+
if (series.sorting.enabled) {
|
|
75
|
+
const getSortingValue = (d) => { var _a, _b; return (_a = d.value) !== null && _a !== void 0 ? _a : parentNodeValues[(_b = d.id) !== null && _b !== void 0 ? _b : '']; };
|
|
76
|
+
const comparator = series.sorting.direction === 'desc' ? descending : ascending;
|
|
77
|
+
dataWithRootNode = sort(dataWithRootNode, (a, b) => comparator(getSortingValue(a), getSortingValue(b)));
|
|
78
|
+
}
|
|
61
79
|
const hierarchy = stratify()
|
|
62
80
|
.id((d) => {
|
|
63
81
|
if (d.id) {
|
|
@@ -111,13 +129,3 @@ export function prepareTreemapData(args) {
|
|
|
111
129
|
}
|
|
112
130
|
return { labelData, leaves, series, htmlElements };
|
|
113
131
|
}
|
|
114
|
-
function getSeriesDataWithRootNode(series) {
|
|
115
|
-
return series.data.reduce((acc, d) => {
|
|
116
|
-
const dataChunk = Object.assign({}, d);
|
|
117
|
-
if (!dataChunk.parentId) {
|
|
118
|
-
dataChunk.parentId = series.id;
|
|
119
|
-
}
|
|
120
|
-
acc.push(dataChunk);
|
|
121
|
-
return acc;
|
|
122
|
-
}, [{ name: series.name, id: series.id }]);
|
|
123
|
-
}
|
|
@@ -43,4 +43,15 @@ export interface TreemapSeries<T = MeaningfulAny> extends BaseSeries {
|
|
|
43
43
|
/** Horizontal alignment of the data label inside the tile. */
|
|
44
44
|
align?: 'left' | 'center' | 'right';
|
|
45
45
|
};
|
|
46
|
+
/** Data sorting settings (affects the order in which blocks are displayed inside the chart).
|
|
47
|
+
* If the option is not specified, the data is displayed in the order defined by the user. */
|
|
48
|
+
sorting?: {
|
|
49
|
+
/** Enable or disable sorting. */
|
|
50
|
+
enabled?: boolean;
|
|
51
|
+
/** The sorting direction.
|
|
52
|
+
*
|
|
53
|
+
* @default: 'desc'
|
|
54
|
+
*/
|
|
55
|
+
direction?: 'asc' | 'desc';
|
|
56
|
+
};
|
|
46
57
|
}
|
|
@@ -58,8 +58,9 @@ function getDefaultValueFormat({ axis }) {
|
|
|
58
58
|
}
|
|
59
59
|
export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
60
60
|
const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
|
|
61
|
-
return (React.createElement(
|
|
62
|
-
measureValue && React.createElement("div",
|
|
61
|
+
return (React.createElement("div", { className: b('content') },
|
|
62
|
+
measureValue && React.createElement("div", { className: b('series-name') }, measureValue),
|
|
63
|
+
// eslint-disable-next-line complexity
|
|
63
64
|
hovered.map((seriesItem, i) => {
|
|
64
65
|
var _a;
|
|
65
66
|
const { data, series, closest } = seriesItem;
|
|
@@ -79,9 +80,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
79
80
|
series.name,
|
|
80
81
|
": ",
|
|
81
82
|
formattedValue));
|
|
82
|
-
|
|
83
|
+
const active = closest && hovered.length > 1;
|
|
84
|
+
return (React.createElement("div", { key: id, className: b('content-row', { active }) },
|
|
83
85
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
84
|
-
React.createElement("div", null,
|
|
86
|
+
React.createElement("div", null, value)));
|
|
85
87
|
}
|
|
86
88
|
case 'waterfall': {
|
|
87
89
|
const isTotal = get(data, 'total', false);
|
|
@@ -119,9 +121,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
119
121
|
series.name,
|
|
120
122
|
": ",
|
|
121
123
|
formattedValue));
|
|
122
|
-
|
|
124
|
+
const active = closest && hovered.length > 1;
|
|
125
|
+
return (React.createElement("div", { key: id, className: b('content-row', { active }) },
|
|
123
126
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
124
|
-
React.createElement("div", null,
|
|
127
|
+
React.createElement("div", null, value)));
|
|
125
128
|
}
|
|
126
129
|
case 'pie':
|
|
127
130
|
case 'treemap': {
|
|
@@ -152,7 +155,8 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
152
155
|
React.createElement("span", null, "\u2192"),
|
|
153
156
|
" ", target === null || target === void 0 ? void 0 :
|
|
154
157
|
target.name,
|
|
155
|
-
":
|
|
158
|
+
":",
|
|
159
|
+
' ',
|
|
156
160
|
formattedValue)));
|
|
157
161
|
}
|
|
158
162
|
case 'radar': {
|
|
@@ -167,9 +171,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
167
171
|
radarSeries.name || radarSeries.id,
|
|
168
172
|
"\u00A0"),
|
|
169
173
|
React.createElement("span", null, formattedValue)));
|
|
170
|
-
|
|
174
|
+
const active = closest && hovered.length > 1;
|
|
175
|
+
return (React.createElement("div", { key: id, className: b('content-row', { active }) },
|
|
171
176
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
172
|
-
React.createElement("div", null,
|
|
177
|
+
React.createElement("div", null, value)));
|
|
173
178
|
}
|
|
174
179
|
default: {
|
|
175
180
|
return null;
|
|
@@ -22,6 +22,6 @@ export const Tooltip = (props) => {
|
|
|
22
22
|
window.dispatchEvent(new CustomEvent('scroll'));
|
|
23
23
|
}, [left, top]);
|
|
24
24
|
return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { anchorElement: anchor, className: b({ pinned: tooltipPinned }), disableTransition: true, floatingStyles: tooltipPinned ? undefined : { pointerEvents: 'none' }, offset: { mainAxis: 20 }, onOpenChange: tooltipPinned ? handleOnOpenChange : undefined, open: true, placement: ['right', 'left', 'top', 'bottom'] },
|
|
25
|
-
React.createElement("div", { className: b('content') },
|
|
25
|
+
React.createElement("div", { className: b('popup-content') },
|
|
26
26
|
React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer, valueFormat: tooltip.valueFormat })))) : null;
|
|
27
27
|
};
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
.gcharts-tooltip {
|
|
2
2
|
box-shadow: 0 2px 12px var(--g-color-sfx-shadow);
|
|
3
3
|
}
|
|
4
|
-
.gcharts-
|
|
5
|
-
padding: 8px 14px;
|
|
4
|
+
.gcharts-tooltip__popup-content {
|
|
6
5
|
text-wrap: nowrap;
|
|
7
6
|
border-radius: 4px;
|
|
8
7
|
background-color: var(--g-color-infographics-tooltip-bg);
|
|
9
8
|
}
|
|
9
|
+
.gcharts-tooltip__content {
|
|
10
|
+
padding: var(--gcharts-tooltip-content-padding, 8px 0);
|
|
11
|
+
}
|
|
12
|
+
.gcharts-tooltip__series-name {
|
|
13
|
+
padding: 2px 14px 6px;
|
|
14
|
+
font-size: 13px;
|
|
15
|
+
font-weight: 600;
|
|
16
|
+
}
|
|
10
17
|
.gcharts-tooltip__content-row {
|
|
11
18
|
display: flex;
|
|
12
19
|
align-items: center;
|
|
20
|
+
padding: 2px 14px;
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
}
|
|
23
|
+
.gcharts-tooltip__content-row_active {
|
|
24
|
+
font-weight: 600;
|
|
25
|
+
background-color: var(--g-color-base-info-medium);
|
|
13
26
|
}
|
|
14
27
|
.gcharts-tooltip__color {
|
|
15
28
|
display: inline-block;
|
|
@@ -6,7 +6,7 @@ import { prepareLegendSymbol } from './utils';
|
|
|
6
6
|
export function prepareTreemap(args) {
|
|
7
7
|
const { colorScale, legend, series } = args;
|
|
8
8
|
return series.map((s) => {
|
|
9
|
-
var _a, _b;
|
|
9
|
+
var _a, _b, _c;
|
|
10
10
|
const id = getUniqId();
|
|
11
11
|
const name = s.name || '';
|
|
12
12
|
const color = s.color || colorScale(name);
|
|
@@ -30,9 +30,10 @@ export function prepareTreemap(args) {
|
|
|
30
30
|
enabled: get(s, 'legend.enabled', legend.enabled),
|
|
31
31
|
symbol: prepareLegendSymbol(s),
|
|
32
32
|
},
|
|
33
|
-
levels: s.levels,
|
|
33
|
+
levels: (_c = s.levels) !== null && _c !== void 0 ? _c : [],
|
|
34
34
|
layoutAlgorithm: get(s, 'layoutAlgorithm', LayoutAlgorithm.Binary),
|
|
35
35
|
cursor: get(s, 'cursor', null),
|
|
36
|
+
sorting: Object.assign({ enabled: false, direction: 'desc' }, s.sorting),
|
|
36
37
|
};
|
|
37
38
|
return preparedSeries;
|
|
38
39
|
});
|
|
@@ -242,7 +242,8 @@ export type PreparedTreemapSeries = {
|
|
|
242
242
|
format?: ValueFormat;
|
|
243
243
|
};
|
|
244
244
|
layoutAlgorithm: `${LayoutAlgorithm}`;
|
|
245
|
-
|
|
245
|
+
sorting: Required<TreemapSeries['sorting']>;
|
|
246
|
+
} & BasePreparedSeries & Required<Omit<TreemapSeries, keyof BasePreparedSeries>>;
|
|
246
247
|
export type PreparedWaterfallSeriesData = WaterfallSeriesData & {
|
|
247
248
|
index: number;
|
|
248
249
|
};
|
|
@@ -23,7 +23,6 @@ export function preparePieData(args) {
|
|
|
23
23
|
const maxRadius = Math.min(boundsWidth, boundsHeight) / 2 - haloSize;
|
|
24
24
|
const minRadius = maxRadius * 0.3;
|
|
25
25
|
const groupedPieSeries = group(preparedSeries, (pieSeries) => pieSeries.stackId);
|
|
26
|
-
let maxMissingWidth = 0;
|
|
27
26
|
const prepareItem = (stackId, items) => {
|
|
28
27
|
var _a;
|
|
29
28
|
const series = items[0];
|
|
@@ -77,7 +76,7 @@ export function preparePieData(args) {
|
|
|
77
76
|
return data;
|
|
78
77
|
};
|
|
79
78
|
const prepareLabels = (prepareLabelsArgs) => {
|
|
80
|
-
const { data, series } = prepareLabelsArgs;
|
|
79
|
+
const { data, series, allowOverlow = true } = prepareLabelsArgs;
|
|
81
80
|
const { dataLabels } = series[0];
|
|
82
81
|
const labels = [];
|
|
83
82
|
const htmlLabels = [];
|
|
@@ -120,7 +119,6 @@ export function preparePieData(args) {
|
|
|
120
119
|
else {
|
|
121
120
|
y = y < 0 ? y - labelHeight : y;
|
|
122
121
|
}
|
|
123
|
-
x = Math.max(-boundsWidth / 2, x);
|
|
124
122
|
return [x, y];
|
|
125
123
|
};
|
|
126
124
|
const getConnectorPoints = (angle) => {
|
|
@@ -148,6 +146,15 @@ export function preparePieData(args) {
|
|
|
148
146
|
segment: relatedSegment.data,
|
|
149
147
|
angle: midAngle,
|
|
150
148
|
};
|
|
149
|
+
if (!allowOverlow) {
|
|
150
|
+
const labelLeftPosition = getLeftPosition(label);
|
|
151
|
+
const newMaxWidth = labelLeftPosition > 0
|
|
152
|
+
? Math.min(boundsWidth / 2 - labelLeftPosition, labelWidth)
|
|
153
|
+
: Math.min(labelWidth - (-labelLeftPosition - boundsWidth / 2), labelWidth);
|
|
154
|
+
if (newMaxWidth !== label.maxWidth) {
|
|
155
|
+
label.maxWidth = Math.max(0, newMaxWidth);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
151
158
|
let overlap = false;
|
|
152
159
|
if (prevLabel) {
|
|
153
160
|
overlap = isLabelsOverlapping(prevLabel, label, dataLabels.padding);
|
|
@@ -172,21 +179,8 @@ export function preparePieData(args) {
|
|
|
172
179
|
}
|
|
173
180
|
}
|
|
174
181
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (Math.abs(left) > boundsWidth / 2) {
|
|
178
|
-
const overflow = Math.abs(left) - boundsWidth / 2;
|
|
179
|
-
label.maxWidth = label.size.width - overflow;
|
|
180
|
-
maxMissingWidth = Math.max(maxMissingWidth, overflow);
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
const right = left + label.size.width;
|
|
184
|
-
if (right > boundsWidth / 2) {
|
|
185
|
-
const overflow = right - boundsWidth / 2;
|
|
186
|
-
label.maxWidth = label.size.width - overflow;
|
|
187
|
-
maxMissingWidth = Math.max(maxMissingWidth, overflow);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
182
|
+
const isLabelOverlapped = !dataLabels.allowOverlap && overlap;
|
|
183
|
+
if (!isLabelOverlapped && label.maxWidth > 0) {
|
|
190
184
|
if (shouldUseHtml) {
|
|
191
185
|
htmlLabels.push({
|
|
192
186
|
x: data.center[0] + label.x,
|
|
@@ -218,43 +212,71 @@ export function preparePieData(args) {
|
|
|
218
212
|
data,
|
|
219
213
|
series: items,
|
|
220
214
|
});
|
|
215
|
+
let maxLeftRightFreeSpace = Infinity;
|
|
216
|
+
let labelsOverflow = 0;
|
|
217
|
+
preparedLabels.labels.forEach((label) => {
|
|
218
|
+
const left = getLeftPosition(label);
|
|
219
|
+
let freeSpace = 0;
|
|
220
|
+
if (left < 0) {
|
|
221
|
+
freeSpace = boundsWidth / 2 - Math.abs(left);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
freeSpace = boundsWidth / 2 - (left + label.size.width);
|
|
225
|
+
}
|
|
226
|
+
maxLeftRightFreeSpace = Math.max(0, Math.min(maxLeftRightFreeSpace, freeSpace));
|
|
227
|
+
labelsOverflow = freeSpace < 0 ? Math.max(labelsOverflow, -freeSpace) : labelsOverflow;
|
|
228
|
+
});
|
|
221
229
|
const segmentMaxRadius = Math.max(...data.segments.map((s) => s.data.radius));
|
|
222
|
-
|
|
223
|
-
const bottom = Math.max(data.center[1] + segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1] + l.size.height), ...preparedLabels.htmlLabels.map((l) => l.y + l.size.height));
|
|
224
|
-
if (topAdjustment > 0) {
|
|
230
|
+
if (labelsOverflow) {
|
|
225
231
|
data.segments.forEach((s) => {
|
|
226
|
-
const
|
|
227
|
-
s.data.radius =
|
|
232
|
+
const neeSegmentRadius = Math.max(minRadius, s.data.radius - labelsOverflow);
|
|
233
|
+
s.data.radius = neeSegmentRadius;
|
|
228
234
|
});
|
|
229
|
-
data.center[1] -= topAdjustment / 2;
|
|
230
235
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
236
|
+
else {
|
|
237
|
+
let topFreeSpace = data.center[1] - segmentMaxRadius - haloSize;
|
|
238
|
+
if (preparedLabels.labels.length) {
|
|
239
|
+
const topSvgLabel = Math.max(0, ...preparedLabels.labels.map((l) => -l.y));
|
|
240
|
+
topFreeSpace = Math.min(topFreeSpace, data.center[1] - topSvgLabel);
|
|
241
|
+
}
|
|
242
|
+
if (preparedLabels.htmlLabels.length) {
|
|
243
|
+
const topHtmlLabel = Math.max(0, ...preparedLabels.htmlLabels.map((l) => l.y));
|
|
244
|
+
topFreeSpace = Math.min(topFreeSpace, topHtmlLabel);
|
|
245
|
+
}
|
|
246
|
+
let bottomFreeSpace = data.center[1] - segmentMaxRadius - haloSize;
|
|
247
|
+
if (preparedLabels.labels.length) {
|
|
248
|
+
const bottomSvgLabel = Math.max(0, ...preparedLabels.labels.map((l) => l.y + l.size.height));
|
|
249
|
+
bottomFreeSpace = Math.min(bottomFreeSpace, data.center[1] - bottomSvgLabel);
|
|
250
|
+
}
|
|
251
|
+
if (preparedLabels.htmlLabels.length) {
|
|
252
|
+
const bottomHtmlLabel = Math.max(0, ...preparedLabels.htmlLabels.map((l) => l.y + l.size.height));
|
|
253
|
+
bottomFreeSpace = Math.min(bottomFreeSpace, data.center[1] * 2 - bottomHtmlLabel);
|
|
254
|
+
}
|
|
255
|
+
const topAdjustment = Math.max(0, Math.min(topFreeSpace, maxLeftRightFreeSpace));
|
|
256
|
+
const bottomAdjustment = Math.max(0, Math.min(bottomFreeSpace, maxLeftRightFreeSpace));
|
|
257
|
+
if (topAdjustment && topAdjustment >= bottomAdjustment) {
|
|
258
|
+
data.segments.forEach((s) => {
|
|
259
|
+
const nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
260
|
+
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
261
|
+
});
|
|
262
|
+
data.center[1] -= (topAdjustment - bottomAdjustment) / 2;
|
|
263
|
+
}
|
|
264
|
+
else if (bottomAdjustment) {
|
|
265
|
+
data.segments.forEach((s) => {
|
|
266
|
+
const nextPossibleRadius = s.data.radius + (topAdjustment + bottomAdjustment) / 2;
|
|
267
|
+
s.data.radius = Math.min(nextPossibleRadius, maxRadius);
|
|
268
|
+
});
|
|
269
|
+
data.center[1] += (bottomAdjustment - topAdjustment) / 2;
|
|
270
|
+
}
|
|
238
271
|
}
|
|
239
272
|
const { labels, htmlLabels, connectors } = prepareLabels({
|
|
240
273
|
data,
|
|
241
274
|
series: items,
|
|
275
|
+
allowOverlow: false,
|
|
242
276
|
});
|
|
243
277
|
data.labels = labels;
|
|
244
278
|
data.htmlLabels = htmlLabels;
|
|
245
279
|
data.connectors = connectors;
|
|
246
|
-
if (maxMissingWidth > 0) {
|
|
247
|
-
const { dataLabels } = items[0];
|
|
248
|
-
if (dataLabels.enabled) {
|
|
249
|
-
data.segments.forEach((s) => {
|
|
250
|
-
s.data.radius = Math.max(minRadius, s.data.radius - maxMissingWidth);
|
|
251
|
-
});
|
|
252
|
-
const finalLabels = prepareLabels({ data, series: items });
|
|
253
|
-
data.labels = finalLabels.labels;
|
|
254
|
-
data.htmlLabels = finalLabels.htmlLabels;
|
|
255
|
-
data.connectors = finalLabels.connectors;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
280
|
return data;
|
|
259
281
|
});
|
|
260
282
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stratify, treemap, treemapBinary, treemapDice, treemapSlice, treemapSliceDice, treemapSquarify, } from 'd3';
|
|
1
|
+
import { ascending, descending, sort, stratify, treemap, treemapBinary, treemapDice, treemapSlice, treemapSliceDice, treemapSquarify, } from 'd3';
|
|
2
2
|
import { LayoutAlgorithm } from '../../../constants';
|
|
3
3
|
import { getLabelsSize } from '../../../utils';
|
|
4
4
|
import { getFormattedValue } from '../../../utils/chart/format';
|
|
@@ -57,7 +57,25 @@ function getLabels(args) {
|
|
|
57
57
|
export function prepareTreemapData(args) {
|
|
58
58
|
var _a;
|
|
59
59
|
const { series, width, height } = args;
|
|
60
|
-
const
|
|
60
|
+
const parentNodeValues = {};
|
|
61
|
+
let dataWithRootNode = series.data.reduce((acc, d) => {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
const dataChunk = Object.assign({}, d);
|
|
64
|
+
if (!dataChunk.parentId) {
|
|
65
|
+
dataChunk.parentId = series.id;
|
|
66
|
+
}
|
|
67
|
+
if (dataChunk.parentId) {
|
|
68
|
+
parentNodeValues[dataChunk.parentId] =
|
|
69
|
+
((_a = parentNodeValues[dataChunk.parentId]) !== null && _a !== void 0 ? _a : 0) + ((_b = dataChunk.value) !== null && _b !== void 0 ? _b : 0);
|
|
70
|
+
}
|
|
71
|
+
acc.push(dataChunk);
|
|
72
|
+
return acc;
|
|
73
|
+
}, [{ name: series.name, id: series.id }]);
|
|
74
|
+
if (series.sorting.enabled) {
|
|
75
|
+
const getSortingValue = (d) => { var _a, _b; return (_a = d.value) !== null && _a !== void 0 ? _a : parentNodeValues[(_b = d.id) !== null && _b !== void 0 ? _b : '']; };
|
|
76
|
+
const comparator = series.sorting.direction === 'desc' ? descending : ascending;
|
|
77
|
+
dataWithRootNode = sort(dataWithRootNode, (a, b) => comparator(getSortingValue(a), getSortingValue(b)));
|
|
78
|
+
}
|
|
61
79
|
const hierarchy = stratify()
|
|
62
80
|
.id((d) => {
|
|
63
81
|
if (d.id) {
|
|
@@ -111,13 +129,3 @@ export function prepareTreemapData(args) {
|
|
|
111
129
|
}
|
|
112
130
|
return { labelData, leaves, series, htmlElements };
|
|
113
131
|
}
|
|
114
|
-
function getSeriesDataWithRootNode(series) {
|
|
115
|
-
return series.data.reduce((acc, d) => {
|
|
116
|
-
const dataChunk = Object.assign({}, d);
|
|
117
|
-
if (!dataChunk.parentId) {
|
|
118
|
-
dataChunk.parentId = series.id;
|
|
119
|
-
}
|
|
120
|
-
acc.push(dataChunk);
|
|
121
|
-
return acc;
|
|
122
|
-
}, [{ name: series.name, id: series.id }]);
|
|
123
|
-
}
|
|
@@ -43,4 +43,15 @@ export interface TreemapSeries<T = MeaningfulAny> extends BaseSeries {
|
|
|
43
43
|
/** Horizontal alignment of the data label inside the tile. */
|
|
44
44
|
align?: 'left' | 'center' | 'right';
|
|
45
45
|
};
|
|
46
|
+
/** Data sorting settings (affects the order in which blocks are displayed inside the chart).
|
|
47
|
+
* If the option is not specified, the data is displayed in the order defined by the user. */
|
|
48
|
+
sorting?: {
|
|
49
|
+
/** Enable or disable sorting. */
|
|
50
|
+
enabled?: boolean;
|
|
51
|
+
/** The sorting direction.
|
|
52
|
+
*
|
|
53
|
+
* @default: 'desc'
|
|
54
|
+
*/
|
|
55
|
+
direction?: 'asc' | 'desc';
|
|
56
|
+
};
|
|
46
57
|
}
|