@gravity-ui/charts 1.4.0 → 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.
@@ -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
- } & BasePreparedSeries & Omit<TreemapSeries, keyof BasePreparedSeries>;
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
- if (dataLabels.allowOverlap || !overlap) {
176
- const left = getLeftPosition(label);
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
- const topAdjustment = Math.min(data.center[1] - segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1]), ...preparedLabels.htmlLabels.map((l) => l.y));
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 nextPossibleRadius = s.data.radius + topAdjustment / 2;
227
- s.data.radius = Math.min(nextPossibleRadius, maxRadius);
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
- const bottomAdjustment = Math.floor(boundsHeight - bottom);
232
- if (bottomAdjustment > 0) {
233
- data.segments.forEach((s) => {
234
- const nextPossibleRadius = s.data.radius + bottomAdjustment / 2;
235
- s.data.radius = Math.min(nextPossibleRadius, maxRadius);
236
- });
237
- data.center[1] += bottomAdjustment / 2;
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 dataWithRootNode = getSeriesDataWithRootNode(series);
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
  }
@@ -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
- } & BasePreparedSeries & Omit<TreemapSeries, keyof BasePreparedSeries>;
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
- if (dataLabels.allowOverlap || !overlap) {
176
- const left = getLeftPosition(label);
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
- const topAdjustment = Math.min(data.center[1] - segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1]), ...preparedLabels.htmlLabels.map((l) => l.y));
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 nextPossibleRadius = s.data.radius + topAdjustment / 2;
227
- s.data.radius = Math.min(nextPossibleRadius, maxRadius);
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
- const bottomAdjustment = Math.floor(boundsHeight - bottom);
232
- if (bottomAdjustment > 0) {
233
- data.segments.forEach((s) => {
234
- const nextPossibleRadius = s.data.radius + bottomAdjustment / 2;
235
- s.data.radius = Math.min(nextPossibleRadius, maxRadius);
236
- });
237
- data.center[1] += bottomAdjustment / 2;
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 dataWithRootNode = getSeriesDataWithRootNode(series);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "React component used to render charts",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",