@czi-sds/data-viz 2.5.1 → 3.0.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.
|
@@ -21,12 +21,12 @@ const TitleContainer = (0, _emotion_styled.default)("div")`
|
|
|
21
21
|
const spaces = (0, _czi_sds_components.getSpaces)(props);
|
|
22
22
|
return `
|
|
23
23
|
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
24
|
-
margin-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.
|
|
24
|
+
margin-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
25
25
|
`;
|
|
26
26
|
}}
|
|
27
27
|
`;
|
|
28
28
|
const ChartTitle = (0, _emotion_styled.default)("div")`
|
|
29
|
-
${_czi_sds_components.
|
|
29
|
+
${_czi_sds_components.fontBodyMediumXs}
|
|
30
30
|
|
|
31
31
|
${(props) => {
|
|
32
32
|
var _semanticColors$base;
|
|
@@ -36,36 +36,19 @@ const ChartTitle = (0, _emotion_styled.default)("div")`
|
|
|
36
36
|
`;
|
|
37
37
|
}}
|
|
38
38
|
`;
|
|
39
|
-
const StyledBadge = (0, _emotion_styled.default)("div")`
|
|
40
|
-
${_czi_sds_components.fontTabularSemiboldXxxxs}
|
|
41
|
-
|
|
42
|
-
${(props) => {
|
|
43
|
-
var _semanticColors$base2, _semanticColors$base3;
|
|
44
|
-
const spaces = (0, _czi_sds_components.getSpaces)(props);
|
|
45
|
-
const corners = (0, _czi_sds_components.getCorners)(props);
|
|
46
|
-
const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
|
|
47
|
-
return `
|
|
48
|
-
padding: ${spaces === null || spaces === void 0 ? void 0 : spaces.xxxs}px ${spaces === null || spaces === void 0 ? void 0 : spaces.xxs}px;
|
|
49
|
-
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
50
|
-
background-color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.backgroundTertiary};
|
|
51
|
-
color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.textSecondary};
|
|
52
|
-
`;
|
|
53
|
-
}}
|
|
54
|
-
`;
|
|
55
39
|
const BarContainer = (0, _emotion_styled.default)("div")`
|
|
56
40
|
${(props) => {
|
|
57
|
-
var _semanticColors$
|
|
41
|
+
var _semanticColors$base2;
|
|
58
42
|
const { width, barHeight, isEmpty } = props;
|
|
59
43
|
const corners = (0, _czi_sds_components.getCorners)(props);
|
|
60
44
|
const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
|
|
61
45
|
const finalWidth = typeof width === "number" ? `${width}px` : width;
|
|
62
46
|
return `
|
|
63
47
|
display: flex;
|
|
64
|
-
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.
|
|
48
|
+
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
65
49
|
width: ${finalWidth};
|
|
66
50
|
height: ${barHeight}px;
|
|
67
|
-
background-color: ${isEmpty ? semanticColors === null || semanticColors === void 0 || (_semanticColors$
|
|
68
|
-
overflow: hidden;
|
|
51
|
+
background-color: ${isEmpty ? semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.backgroundTertiary : "transparent"};
|
|
69
52
|
`;
|
|
70
53
|
}}
|
|
71
54
|
`;
|
|
@@ -73,14 +56,17 @@ const BarSegment = (0, _emotion_styled.default)("div")`
|
|
|
73
56
|
${(props) => {
|
|
74
57
|
const { color, height, percentage, opacity, disabled, isLast } = props;
|
|
75
58
|
const spaces = (0, _czi_sds_components.getSpaces)(props);
|
|
59
|
+
const corners = (0, _czi_sds_components.getCorners)(props);
|
|
76
60
|
const gap = (spaces === null || spaces === void 0 ? void 0 : spaces.xxxs) || 0;
|
|
61
|
+
const marginRight = percentage > 0 && !isLast ? `${gap}px` : 0;
|
|
77
62
|
return `
|
|
78
63
|
background-color: ${color};
|
|
64
|
+
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
79
65
|
height: ${height}px;
|
|
80
66
|
flex-grow: ${percentage};
|
|
81
67
|
flex-basis: 0;
|
|
82
68
|
flex-shrink: 1;
|
|
83
|
-
margin-right: ${
|
|
69
|
+
margin-right: ${marginRight};
|
|
84
70
|
opacity: ${opacity};
|
|
85
71
|
transition: opacity 0.2s ease-in-out, flex-grow 0.25s ease-in-out, margin-right 0.25s ease-out;
|
|
86
72
|
cursor: ${disabled ? "default" : "pointer"};
|
|
@@ -94,7 +80,7 @@ const StyledStackedBarChartWrapper = (0, _emotion_styled.default)("div")`
|
|
|
94
80
|
return `
|
|
95
81
|
display: flex;
|
|
96
82
|
flex-direction: column;
|
|
97
|
-
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.
|
|
83
|
+
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
98
84
|
`;
|
|
99
85
|
}}
|
|
100
86
|
`;
|
|
@@ -124,17 +110,18 @@ const _excluded = [
|
|
|
124
110
|
"remainingLabel",
|
|
125
111
|
"remainingUnit",
|
|
126
112
|
"unit",
|
|
127
|
-
"colorGeneratorOptions"
|
|
113
|
+
"colorGeneratorOptions",
|
|
114
|
+
"maxItems"
|
|
128
115
|
];
|
|
129
116
|
const calculateBadgeText = (totalItems, selectedCount) => {
|
|
130
117
|
if (selectedCount === 0 || selectedCount === totalItems) return `${totalItems}`;
|
|
131
118
|
return `${selectedCount} of ${totalItems}`;
|
|
132
119
|
};
|
|
133
120
|
const calculateLegendValue = (item, showLegendValues, legendValueFormat, globalUnit) => {
|
|
121
|
+
var _ref, _item$unit;
|
|
134
122
|
if (!showLegendValues) return void 0;
|
|
135
123
|
if (legendValueFormat === "percentage") return `${Math.round(item.percentage)}%`;
|
|
136
|
-
|
|
137
|
-
return effectiveUnit ? `${item.value} ${effectiveUnit}` : `${item.value}`;
|
|
124
|
+
return `${item.value.toLocaleString()} ${(_ref = (_item$unit = item.unit) !== null && _item$unit !== void 0 ? _item$unit : globalUnit) !== null && _ref !== void 0 ? _ref : ""}`.trim();
|
|
138
125
|
};
|
|
139
126
|
const getSegmentOpacity = (index, hoveredIndex, selectedIndicesSet, selectionBehavior) => {
|
|
140
127
|
const hasSelection = selectedIndicesSet.size > 0;
|
|
@@ -181,7 +168,7 @@ const applySelectionStyling = (items, selectedIndicesSet, selectionBehavior, mod
|
|
|
181
168
|
return mode === "cumulative" ? applyCumulativeHideStyling(items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount) : applyProportionalHideStyling(items, selectedIndicesSet, selectedTotal);
|
|
182
169
|
};
|
|
183
170
|
const buildLegendItems = (dataWithPercentages, options) => {
|
|
184
|
-
const { showLegendValues, legendValueFormat, unit, hasRemaining, showLegend, remainingValue, remainingPercentage, remainingLabel, remainingUnit, theme } = options;
|
|
171
|
+
const { showLegendValues, legendValueFormat, unit, hasRemaining, showLegend, remainingValue, remainingPercentage, remainingLabel, remainingUnit, remainingOpensMenu, theme } = options;
|
|
185
172
|
const legendItems = dataWithPercentages.map((item) => ({
|
|
186
173
|
name: item.name,
|
|
187
174
|
value: calculateLegendValue(item, showLegendValues, legendValueFormat, unit),
|
|
@@ -195,13 +182,13 @@ const buildLegendItems = (dataWithPercentages, options) => {
|
|
|
195
182
|
else if (legendValueFormat === "percentage") remainingValueDisplay = `${Math.round(remainingPercentage)}%`;
|
|
196
183
|
else {
|
|
197
184
|
const effectiveRemainingUnit = remainingUnit || unit || "";
|
|
198
|
-
remainingValueDisplay = effectiveRemainingUnit ? `${remainingValue} ${effectiveRemainingUnit}` : `${remainingValue}`;
|
|
185
|
+
remainingValueDisplay = effectiveRemainingUnit ? `${remainingValue.toLocaleString()} ${effectiveRemainingUnit}` : `${remainingValue.toLocaleString()}`;
|
|
199
186
|
}
|
|
200
187
|
legendItems.push({
|
|
201
188
|
name: remainingLabel,
|
|
202
189
|
value: remainingValueDisplay,
|
|
203
190
|
color: theme === null || theme === void 0 || (_theme$palette = theme.palette) === null || _theme$palette === void 0 || (_theme$palette = _theme$palette.sds) === null || _theme$palette === void 0 || (_theme$palette = _theme$palette.base) === null || _theme$palette === void 0 ? void 0 : _theme$palette.backgroundTertiary,
|
|
204
|
-
disabled:
|
|
191
|
+
disabled: !remainingOpensMenu
|
|
205
192
|
});
|
|
206
193
|
}
|
|
207
194
|
return legendItems;
|
|
@@ -237,10 +224,13 @@ const renderBarSegment = (item, options) => {
|
|
|
237
224
|
}, item.key) : (0, react.cloneElement)(barSegment, { key: item.key });
|
|
238
225
|
};
|
|
239
226
|
const StackedBarChart = (props) => {
|
|
240
|
-
var _theme$
|
|
241
|
-
const { title, badge, hideBadge = false, data, width = "100%", barHeight: barHeightProp =
|
|
227
|
+
var _theme$palette5;
|
|
228
|
+
const { title, badge, hideBadge = false, data, width = "100%", barHeight: barHeightProp = 8, showLegend = true, showLegendValues = true, legendValueFormat = "percentage", selectedIndices = [], onSelectionChange, onSegmentMouseEnter, onSegmentMouseLeave, onLegendItemMouseEnter, onLegendItemMouseLeave, onSegmentClick, onLegendItemClick, selectionBehavior = "dim", mode = "proportional", maxAmount, remainingLabel = "Remaining", remainingUnit, unit, colorGeneratorOptions, maxItems } = props, rest = require_objectSpread2._objectWithoutProperties(props, _excluded);
|
|
242
229
|
const barHeight = Math.max(1, barHeightProp);
|
|
243
230
|
const theme = (0, _mui_material.useTheme)();
|
|
231
|
+
const visibleCount = mode === "cumulative" && maxItems !== void 0 && maxItems < data.length ? Math.max(0, maxItems) : data.length;
|
|
232
|
+
const chartData = (0, react.useMemo)(() => data.slice(0, visibleCount), [data, visibleCount]);
|
|
233
|
+
const hiddenItems = (0, react.useMemo)(() => data.slice(visibleCount), [data, visibleCount]);
|
|
244
234
|
const [hoveredIndex, setHoveredIndex] = (0, react.useState)(null);
|
|
245
235
|
const [animatedItems, setAnimatedItems] = (0, react.useState)([]);
|
|
246
236
|
const [autoColorMap, setAutoColorMap] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
@@ -263,8 +253,8 @@ const StackedBarChart = (props) => {
|
|
|
263
253
|
theme
|
|
264
254
|
]);
|
|
265
255
|
(0, react.useEffect)(() => {
|
|
266
|
-
const autoItems =
|
|
267
|
-
const currentDataKeys = new Set(
|
|
256
|
+
const autoItems = chartData.filter((item) => !item.color);
|
|
257
|
+
const currentDataKeys = new Set(chartData.map((item) => item.name));
|
|
268
258
|
const previousDataKeys = previousDataKeysRef.current;
|
|
269
259
|
const previousColorOptions = previousColorOptionsRef.current;
|
|
270
260
|
const colorOptionsChanged = !previousColorOptions || previousColorOptions.isDarkMode !== isDarkMode || previousColorOptions.options !== colorGeneratorOptions;
|
|
@@ -290,7 +280,7 @@ const StackedBarChart = (props) => {
|
|
|
290
280
|
if (hasChanges) setAutoColorMap(updatedMap);
|
|
291
281
|
}
|
|
292
282
|
}, [
|
|
293
|
-
|
|
283
|
+
chartData,
|
|
294
284
|
colorGeneratorOptions,
|
|
295
285
|
isDarkMode,
|
|
296
286
|
theme,
|
|
@@ -299,9 +289,10 @@ const StackedBarChart = (props) => {
|
|
|
299
289
|
const displayBadge = badge !== null && badge !== void 0 ? badge : calculateBadgeText(data.length, selectedIndices.length);
|
|
300
290
|
const { effectiveMaxAmount, hasRemaining, remainingValue, remainingPercentage } = (0, react.useMemo)(() => {
|
|
301
291
|
const total = data.reduce((sum, item) => sum + item.value, 0);
|
|
292
|
+
const visibleTotal = chartData.reduce((sum, item) => sum + item.value, 0);
|
|
302
293
|
const maxAmountValue = mode === "cumulative" && maxAmount ? maxAmount : total;
|
|
303
|
-
const hasRemainingSegment = mode === "cumulative" &&
|
|
304
|
-
const remaining = hasRemainingSegment ? maxAmountValue -
|
|
294
|
+
const hasRemainingSegment = mode === "cumulative" && visibleTotal < maxAmountValue;
|
|
295
|
+
const remaining = hasRemainingSegment ? maxAmountValue - visibleTotal : 0;
|
|
305
296
|
return {
|
|
306
297
|
effectiveMaxAmount: maxAmountValue,
|
|
307
298
|
hasRemaining: hasRemainingSegment,
|
|
@@ -310,11 +301,20 @@ const StackedBarChart = (props) => {
|
|
|
310
301
|
};
|
|
311
302
|
}, [
|
|
312
303
|
data,
|
|
304
|
+
chartData,
|
|
313
305
|
mode,
|
|
314
306
|
maxAmount
|
|
315
307
|
]);
|
|
308
|
+
const hasOverflow = hasRemaining && hiddenItems.length > 0;
|
|
309
|
+
const overflowIndex = data.length;
|
|
310
|
+
const hiddenColors = (0, react.useMemo)(() => hasOverflow ? (0, _czi_sds_components.generateDiscreteColors)(hiddenItems.length, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, colorGeneratorOptions || {}), {}, { isDarkMode })) : [], [
|
|
311
|
+
hasOverflow,
|
|
312
|
+
hiddenItems.length,
|
|
313
|
+
colorGeneratorOptions,
|
|
314
|
+
isDarkMode
|
|
315
|
+
]);
|
|
316
316
|
const dataWithPercentages = (0, react.useMemo)(() => {
|
|
317
|
-
return
|
|
317
|
+
return chartData.map((item, index) => {
|
|
318
318
|
var _theme$palette3;
|
|
319
319
|
const resolvedColor = item.color || autoColorMap.get(item.name) || (theme === null || theme === void 0 || (_theme$palette3 = theme.palette) === null || _theme$palette3 === void 0 || (_theme$palette3 = _theme$palette3.sds) === null || _theme$palette3 === void 0 || (_theme$palette3 = _theme$palette3.base) === null || _theme$palette3 === void 0 ? void 0 : _theme$palette3.ornamentSecondary);
|
|
320
320
|
return require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, item), {}, {
|
|
@@ -327,7 +327,7 @@ const StackedBarChart = (props) => {
|
|
|
327
327
|
});
|
|
328
328
|
});
|
|
329
329
|
}, [
|
|
330
|
-
|
|
330
|
+
chartData,
|
|
331
331
|
effectiveMaxAmount,
|
|
332
332
|
autoColorMap,
|
|
333
333
|
theme
|
|
@@ -368,10 +368,16 @@ const StackedBarChart = (props) => {
|
|
|
368
368
|
timeoutIds.forEach((id) => window.clearTimeout(id));
|
|
369
369
|
};
|
|
370
370
|
}, [dataWithPercentages, remainingPercentage]);
|
|
371
|
-
const
|
|
372
|
-
|
|
371
|
+
const toChartIndex = (0, react.useCallback)((index) => hasOverflow && index >= visibleCount ? overflowIndex : index, [
|
|
372
|
+
hasOverflow,
|
|
373
|
+
visibleCount,
|
|
374
|
+
overflowIndex
|
|
375
|
+
]);
|
|
376
|
+
const hoveredChartIndex = hoveredIndex === null ? null : toChartIndex(hoveredIndex);
|
|
377
|
+
const selectedChartIndices = (0, react.useMemo)(() => new Set(selectedIndices.map(toChartIndex)), [selectedIndices, toChartIndex]);
|
|
378
|
+
const { styledItems, updatedRemainingPercentage, updatedRemainingValue } = (0, react.useMemo)(() => applySelectionStyling(animatedItems, selectedChartIndices, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, animatedRemainingPercentage, remainingValue), [
|
|
373
379
|
animatedItems,
|
|
374
|
-
|
|
380
|
+
selectedChartIndices,
|
|
375
381
|
selectionBehavior,
|
|
376
382
|
mode,
|
|
377
383
|
hasRemaining,
|
|
@@ -392,6 +398,7 @@ const StackedBarChart = (props) => {
|
|
|
392
398
|
remainingPercentage: displayRemainingPercentage,
|
|
393
399
|
remainingLabel,
|
|
394
400
|
remainingUnit,
|
|
401
|
+
remainingOpensMenu: hasOverflow,
|
|
395
402
|
theme
|
|
396
403
|
}), [
|
|
397
404
|
dataWithPercentages,
|
|
@@ -404,19 +411,51 @@ const StackedBarChart = (props) => {
|
|
|
404
411
|
displayRemainingPercentage,
|
|
405
412
|
remainingLabel,
|
|
406
413
|
remainingUnit,
|
|
414
|
+
hasOverflow,
|
|
407
415
|
theme
|
|
408
416
|
]);
|
|
409
|
-
const
|
|
417
|
+
const toLegendIndex = (0, react.useCallback)((index) => {
|
|
418
|
+
if (!hasOverflow || index < visibleCount) return index;
|
|
419
|
+
return index === overflowIndex ? visibleCount : index + 1;
|
|
420
|
+
}, [
|
|
421
|
+
hasOverflow,
|
|
422
|
+
visibleCount,
|
|
423
|
+
overflowIndex
|
|
424
|
+
]);
|
|
425
|
+
const fromLegendIndex = (0, react.useCallback)((index) => {
|
|
426
|
+
if (!hasOverflow || index < visibleCount) return index;
|
|
427
|
+
return index === visibleCount ? overflowIndex : index - 1;
|
|
428
|
+
}, [
|
|
429
|
+
hasOverflow,
|
|
430
|
+
visibleCount,
|
|
431
|
+
overflowIndex
|
|
432
|
+
]);
|
|
433
|
+
const handleLegendItemHover = (0, react.useCallback)((_item, legendIndex) => {
|
|
434
|
+
const index = fromLegendIndex(legendIndex);
|
|
410
435
|
setHoveredIndex(index);
|
|
411
436
|
if (onLegendItemMouseEnter && data[index]) onLegendItemMouseEnter(data[index], index);
|
|
412
|
-
}, [
|
|
413
|
-
|
|
437
|
+
}, [
|
|
438
|
+
onLegendItemMouseEnter,
|
|
439
|
+
data,
|
|
440
|
+
fromLegendIndex
|
|
441
|
+
]);
|
|
442
|
+
const handleLegendItemLeave = (0, react.useCallback)((_item, legendIndex) => {
|
|
443
|
+
const index = fromLegendIndex(legendIndex);
|
|
414
444
|
setHoveredIndex(null);
|
|
415
445
|
if (onLegendItemMouseLeave && data[index]) onLegendItemMouseLeave(data[index], index);
|
|
416
|
-
}, [
|
|
417
|
-
|
|
446
|
+
}, [
|
|
447
|
+
onLegendItemMouseLeave,
|
|
448
|
+
data,
|
|
449
|
+
fromLegendIndex
|
|
450
|
+
]);
|
|
451
|
+
const handleLegendItemClickInternal = (0, react.useCallback)((_item, legendIndex) => {
|
|
452
|
+
const index = fromLegendIndex(legendIndex);
|
|
418
453
|
if (onLegendItemClick && data[index]) onLegendItemClick(data[index], index);
|
|
419
|
-
}, [
|
|
454
|
+
}, [
|
|
455
|
+
onLegendItemClick,
|
|
456
|
+
data,
|
|
457
|
+
fromLegendIndex
|
|
458
|
+
]);
|
|
420
459
|
const handleSelectionChange = (0, react.useCallback)((newSelectedIndices) => {
|
|
421
460
|
if (!onSelectionChange) return;
|
|
422
461
|
onSelectionChange(newSelectedIndices, data.filter((item, itemIndex) => newSelectedIndices.includes(itemIndex)));
|
|
@@ -424,15 +463,29 @@ const StackedBarChart = (props) => {
|
|
|
424
463
|
const handleSegmentClick = (0, react.useCallback)((index) => {
|
|
425
464
|
if (onSegmentClick && data[index]) onSegmentClick(data[index], index);
|
|
426
465
|
if (!onSelectionChange) return;
|
|
427
|
-
handleSelectionChange(
|
|
466
|
+
handleSelectionChange(selectedChartIndices.has(index) ? selectedIndices.filter((i) => i !== index) : [...selectedIndices, index]);
|
|
428
467
|
}, [
|
|
429
468
|
onSelectionChange,
|
|
430
469
|
onSegmentClick,
|
|
431
470
|
data,
|
|
432
471
|
selectedIndices,
|
|
433
|
-
|
|
472
|
+
selectedChartIndices,
|
|
434
473
|
handleSelectionChange
|
|
435
474
|
]);
|
|
475
|
+
const remainingMenuItems = (0, react.useMemo)(() => hasOverflow ? hiddenItems.map((item, index) => {
|
|
476
|
+
var _theme$palette4;
|
|
477
|
+
return {
|
|
478
|
+
name: item.name,
|
|
479
|
+
value: item.value,
|
|
480
|
+
color: item.color || hiddenColors[index] || (theme === null || theme === void 0 || (_theme$palette4 = theme.palette) === null || _theme$palette4 === void 0 || (_theme$palette4 = _theme$palette4.sds) === null || _theme$palette4 === void 0 || (_theme$palette4 = _theme$palette4.base) === null || _theme$palette4 === void 0 ? void 0 : _theme$palette4.ornamentSecondary),
|
|
481
|
+
disabled: item.disabled
|
|
482
|
+
};
|
|
483
|
+
}) : [], [
|
|
484
|
+
hasOverflow,
|
|
485
|
+
hiddenItems,
|
|
486
|
+
hiddenColors,
|
|
487
|
+
theme
|
|
488
|
+
]);
|
|
436
489
|
const lastVisibleIndex = (0, react.useMemo)(() => {
|
|
437
490
|
for (let i = styledItems.length - 1; i >= 0; i--) if (styledItems[i].percentage > 0) return i;
|
|
438
491
|
return -1;
|
|
@@ -444,8 +497,8 @@ const StackedBarChart = (props) => {
|
|
|
444
497
|
children: [styledItems.map((item, index) => {
|
|
445
498
|
return renderBarSegment(item, {
|
|
446
499
|
barHeight,
|
|
447
|
-
hoveredIndex,
|
|
448
|
-
selectedIndicesSet,
|
|
500
|
+
hoveredIndex: hoveredChartIndex,
|
|
501
|
+
selectedIndicesSet: selectedChartIndices,
|
|
449
502
|
selectionBehavior,
|
|
450
503
|
isLast: index === lastVisibleIndex && !hasRemaining,
|
|
451
504
|
onMouseEnter: () => {
|
|
@@ -461,25 +514,31 @@ const StackedBarChart = (props) => {
|
|
|
461
514
|
onClick: () => !item.isExiting && handleSegmentClick(item.originalIndex)
|
|
462
515
|
});
|
|
463
516
|
}), previousProcessedRef.current.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BarSegment, {
|
|
464
|
-
color: theme === null || theme === void 0 || (_theme$
|
|
517
|
+
color: theme === null || theme === void 0 || (_theme$palette5 = theme.palette) === null || _theme$palette5 === void 0 || (_theme$palette5 = _theme$palette5.sds) === null || _theme$palette5 === void 0 || (_theme$palette5 = _theme$palette5.base) === null || _theme$palette5 === void 0 ? void 0 : _theme$palette5.backgroundTertiary,
|
|
465
518
|
percentage: displayRemainingPercentage,
|
|
466
519
|
height: barHeight,
|
|
467
520
|
isLast: true,
|
|
468
|
-
opacity: 1,
|
|
521
|
+
opacity: hasOverflow ? getSegmentOpacity(overflowIndex, hoveredChartIndex, selectedChartIndices, selectionBehavior) : 1,
|
|
469
522
|
disabled: true
|
|
470
523
|
}, "remaining")]
|
|
471
524
|
}), showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.Legend, {
|
|
472
525
|
items: legendItems,
|
|
526
|
+
lastItemMenuItems: remainingMenuItems,
|
|
473
527
|
showValues: showLegendValues,
|
|
474
528
|
onItemMouseEnter: handleLegendItemHover,
|
|
475
529
|
onItemMouseLeave: handleLegendItemLeave,
|
|
476
530
|
onItemClick: onLegendItemClick ? handleLegendItemClickInternal : void 0,
|
|
477
|
-
selectedIndices,
|
|
478
|
-
onSelectionChange: onSelectionChange ? handleSelectionChange : void 0,
|
|
479
|
-
hoveredIndex
|
|
531
|
+
selectedIndices: selectedIndices.map(toLegendIndex),
|
|
532
|
+
onSelectionChange: onSelectionChange ? (indices) => handleSelectionChange(indices.map(fromLegendIndex)) : void 0,
|
|
533
|
+
hoveredIndex: hoveredIndex === null ? null : toLegendIndex(hoveredIndex)
|
|
480
534
|
})] });
|
|
481
535
|
if (!title) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartWrapper, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({ width }, rest), {}, { children: chartContent }));
|
|
482
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartWrapper, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({ width }, rest), {}, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TitleContainer, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartTitle, { children: title }), !hideBadge && displayBadge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
536
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartWrapper, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({ width }, rest), {}, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TitleContainer, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartTitle, { children: title }), !hideBadge && displayBadge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.Tag, {
|
|
537
|
+
label: displayBadge,
|
|
538
|
+
sdsStyle: "outline",
|
|
539
|
+
color: "neutral",
|
|
540
|
+
size: "small"
|
|
541
|
+
})] }), chartContent] }));
|
|
483
542
|
};
|
|
484
543
|
//#endregion
|
|
485
544
|
Object.defineProperty(exports, "StackedBarChart", {
|
|
@@ -3,7 +3,7 @@ import { n as _objectWithoutProperties, t as _objectSpread2 } from "./objectSpre
|
|
|
3
3
|
import { cloneElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import styled from "@emotion/styled";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { Legend, Tooltip, TooltipTable,
|
|
6
|
+
import { Legend, Tag, Tooltip, TooltipTable, fontBodyMediumXs, generateDiscreteColors, getCorners, getMode, getSemanticColors, getSpaces } from "@czi-sds/components";
|
|
7
7
|
import { useTheme } from "@mui/material";
|
|
8
8
|
//#region src/core/StackedBarChart/style.ts
|
|
9
9
|
const ChartWrapper = styled("div")`
|
|
@@ -19,12 +19,12 @@ const TitleContainer = styled("div")`
|
|
|
19
19
|
const spaces = getSpaces(props);
|
|
20
20
|
return `
|
|
21
21
|
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
22
|
-
margin-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.
|
|
22
|
+
margin-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
23
23
|
`;
|
|
24
24
|
}}
|
|
25
25
|
`;
|
|
26
26
|
const ChartTitle = styled("div")`
|
|
27
|
-
${
|
|
27
|
+
${fontBodyMediumXs}
|
|
28
28
|
|
|
29
29
|
${(props) => {
|
|
30
30
|
var _semanticColors$base;
|
|
@@ -34,36 +34,19 @@ const ChartTitle = styled("div")`
|
|
|
34
34
|
`;
|
|
35
35
|
}}
|
|
36
36
|
`;
|
|
37
|
-
const StyledBadge = styled("div")`
|
|
38
|
-
${fontTabularSemiboldXxxxs}
|
|
39
|
-
|
|
40
|
-
${(props) => {
|
|
41
|
-
var _semanticColors$base2, _semanticColors$base3;
|
|
42
|
-
const spaces = getSpaces(props);
|
|
43
|
-
const corners = getCorners(props);
|
|
44
|
-
const semanticColors = getSemanticColors(props);
|
|
45
|
-
return `
|
|
46
|
-
padding: ${spaces === null || spaces === void 0 ? void 0 : spaces.xxxs}px ${spaces === null || spaces === void 0 ? void 0 : spaces.xxs}px;
|
|
47
|
-
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
48
|
-
background-color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.backgroundTertiary};
|
|
49
|
-
color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.textSecondary};
|
|
50
|
-
`;
|
|
51
|
-
}}
|
|
52
|
-
`;
|
|
53
37
|
const BarContainer = styled("div")`
|
|
54
38
|
${(props) => {
|
|
55
|
-
var _semanticColors$
|
|
39
|
+
var _semanticColors$base2;
|
|
56
40
|
const { width, barHeight, isEmpty } = props;
|
|
57
41
|
const corners = getCorners(props);
|
|
58
42
|
const semanticColors = getSemanticColors(props);
|
|
59
43
|
const finalWidth = typeof width === "number" ? `${width}px` : width;
|
|
60
44
|
return `
|
|
61
45
|
display: flex;
|
|
62
|
-
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.
|
|
46
|
+
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
63
47
|
width: ${finalWidth};
|
|
64
48
|
height: ${barHeight}px;
|
|
65
|
-
background-color: ${isEmpty ? semanticColors === null || semanticColors === void 0 || (_semanticColors$
|
|
66
|
-
overflow: hidden;
|
|
49
|
+
background-color: ${isEmpty ? semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.backgroundTertiary : "transparent"};
|
|
67
50
|
`;
|
|
68
51
|
}}
|
|
69
52
|
`;
|
|
@@ -71,14 +54,17 @@ const BarSegment = styled("div")`
|
|
|
71
54
|
${(props) => {
|
|
72
55
|
const { color, height, percentage, opacity, disabled, isLast } = props;
|
|
73
56
|
const spaces = getSpaces(props);
|
|
57
|
+
const corners = getCorners(props);
|
|
74
58
|
const gap = (spaces === null || spaces === void 0 ? void 0 : spaces.xxxs) || 0;
|
|
59
|
+
const marginRight = percentage > 0 && !isLast ? `${gap}px` : 0;
|
|
75
60
|
return `
|
|
76
61
|
background-color: ${color};
|
|
62
|
+
border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
|
|
77
63
|
height: ${height}px;
|
|
78
64
|
flex-grow: ${percentage};
|
|
79
65
|
flex-basis: 0;
|
|
80
66
|
flex-shrink: 1;
|
|
81
|
-
margin-right: ${
|
|
67
|
+
margin-right: ${marginRight};
|
|
82
68
|
opacity: ${opacity};
|
|
83
69
|
transition: opacity 0.2s ease-in-out, flex-grow 0.25s ease-in-out, margin-right 0.25s ease-out;
|
|
84
70
|
cursor: ${disabled ? "default" : "pointer"};
|
|
@@ -92,7 +78,7 @@ const StyledStackedBarChartWrapper = styled("div")`
|
|
|
92
78
|
return `
|
|
93
79
|
display: flex;
|
|
94
80
|
flex-direction: column;
|
|
95
|
-
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.
|
|
81
|
+
gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
|
|
96
82
|
`;
|
|
97
83
|
}}
|
|
98
84
|
`;
|
|
@@ -122,17 +108,18 @@ const _excluded = [
|
|
|
122
108
|
"remainingLabel",
|
|
123
109
|
"remainingUnit",
|
|
124
110
|
"unit",
|
|
125
|
-
"colorGeneratorOptions"
|
|
111
|
+
"colorGeneratorOptions",
|
|
112
|
+
"maxItems"
|
|
126
113
|
];
|
|
127
114
|
const calculateBadgeText = (totalItems, selectedCount) => {
|
|
128
115
|
if (selectedCount === 0 || selectedCount === totalItems) return `${totalItems}`;
|
|
129
116
|
return `${selectedCount} of ${totalItems}`;
|
|
130
117
|
};
|
|
131
118
|
const calculateLegendValue = (item, showLegendValues, legendValueFormat, globalUnit) => {
|
|
119
|
+
var _ref, _item$unit;
|
|
132
120
|
if (!showLegendValues) return void 0;
|
|
133
121
|
if (legendValueFormat === "percentage") return `${Math.round(item.percentage)}%`;
|
|
134
|
-
|
|
135
|
-
return effectiveUnit ? `${item.value} ${effectiveUnit}` : `${item.value}`;
|
|
122
|
+
return `${item.value.toLocaleString()} ${(_ref = (_item$unit = item.unit) !== null && _item$unit !== void 0 ? _item$unit : globalUnit) !== null && _ref !== void 0 ? _ref : ""}`.trim();
|
|
136
123
|
};
|
|
137
124
|
const getSegmentOpacity = (index, hoveredIndex, selectedIndicesSet, selectionBehavior) => {
|
|
138
125
|
const hasSelection = selectedIndicesSet.size > 0;
|
|
@@ -179,7 +166,7 @@ const applySelectionStyling = (items, selectedIndicesSet, selectionBehavior, mod
|
|
|
179
166
|
return mode === "cumulative" ? applyCumulativeHideStyling(items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount) : applyProportionalHideStyling(items, selectedIndicesSet, selectedTotal);
|
|
180
167
|
};
|
|
181
168
|
const buildLegendItems = (dataWithPercentages, options) => {
|
|
182
|
-
const { showLegendValues, legendValueFormat, unit, hasRemaining, showLegend, remainingValue, remainingPercentage, remainingLabel, remainingUnit, theme } = options;
|
|
169
|
+
const { showLegendValues, legendValueFormat, unit, hasRemaining, showLegend, remainingValue, remainingPercentage, remainingLabel, remainingUnit, remainingOpensMenu, theme } = options;
|
|
183
170
|
const legendItems = dataWithPercentages.map((item) => ({
|
|
184
171
|
name: item.name,
|
|
185
172
|
value: calculateLegendValue(item, showLegendValues, legendValueFormat, unit),
|
|
@@ -193,13 +180,13 @@ const buildLegendItems = (dataWithPercentages, options) => {
|
|
|
193
180
|
else if (legendValueFormat === "percentage") remainingValueDisplay = `${Math.round(remainingPercentage)}%`;
|
|
194
181
|
else {
|
|
195
182
|
const effectiveRemainingUnit = remainingUnit || unit || "";
|
|
196
|
-
remainingValueDisplay = effectiveRemainingUnit ? `${remainingValue} ${effectiveRemainingUnit}` : `${remainingValue}`;
|
|
183
|
+
remainingValueDisplay = effectiveRemainingUnit ? `${remainingValue.toLocaleString()} ${effectiveRemainingUnit}` : `${remainingValue.toLocaleString()}`;
|
|
197
184
|
}
|
|
198
185
|
legendItems.push({
|
|
199
186
|
name: remainingLabel,
|
|
200
187
|
value: remainingValueDisplay,
|
|
201
188
|
color: theme === null || theme === void 0 || (_theme$palette = theme.palette) === null || _theme$palette === void 0 || (_theme$palette = _theme$palette.sds) === null || _theme$palette === void 0 || (_theme$palette = _theme$palette.base) === null || _theme$palette === void 0 ? void 0 : _theme$palette.backgroundTertiary,
|
|
202
|
-
disabled:
|
|
189
|
+
disabled: !remainingOpensMenu
|
|
203
190
|
});
|
|
204
191
|
}
|
|
205
192
|
return legendItems;
|
|
@@ -235,10 +222,13 @@ const renderBarSegment = (item, options) => {
|
|
|
235
222
|
}, item.key) : cloneElement(barSegment, { key: item.key });
|
|
236
223
|
};
|
|
237
224
|
const StackedBarChart = (props) => {
|
|
238
|
-
var _theme$
|
|
239
|
-
const { title, badge, hideBadge = false, data, width = "100%", barHeight: barHeightProp =
|
|
225
|
+
var _theme$palette5;
|
|
226
|
+
const { title, badge, hideBadge = false, data, width = "100%", barHeight: barHeightProp = 8, showLegend = true, showLegendValues = true, legendValueFormat = "percentage", selectedIndices = [], onSelectionChange, onSegmentMouseEnter, onSegmentMouseLeave, onLegendItemMouseEnter, onLegendItemMouseLeave, onSegmentClick, onLegendItemClick, selectionBehavior = "dim", mode = "proportional", maxAmount, remainingLabel = "Remaining", remainingUnit, unit, colorGeneratorOptions, maxItems } = props, rest = _objectWithoutProperties(props, _excluded);
|
|
240
227
|
const barHeight = Math.max(1, barHeightProp);
|
|
241
228
|
const theme = useTheme();
|
|
229
|
+
const visibleCount = mode === "cumulative" && maxItems !== void 0 && maxItems < data.length ? Math.max(0, maxItems) : data.length;
|
|
230
|
+
const chartData = useMemo(() => data.slice(0, visibleCount), [data, visibleCount]);
|
|
231
|
+
const hiddenItems = useMemo(() => data.slice(visibleCount), [data, visibleCount]);
|
|
242
232
|
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
243
233
|
const [animatedItems, setAnimatedItems] = useState([]);
|
|
244
234
|
const [autoColorMap, setAutoColorMap] = useState(/* @__PURE__ */ new Map());
|
|
@@ -261,8 +251,8 @@ const StackedBarChart = (props) => {
|
|
|
261
251
|
theme
|
|
262
252
|
]);
|
|
263
253
|
useEffect(() => {
|
|
264
|
-
const autoItems =
|
|
265
|
-
const currentDataKeys = new Set(
|
|
254
|
+
const autoItems = chartData.filter((item) => !item.color);
|
|
255
|
+
const currentDataKeys = new Set(chartData.map((item) => item.name));
|
|
266
256
|
const previousDataKeys = previousDataKeysRef.current;
|
|
267
257
|
const previousColorOptions = previousColorOptionsRef.current;
|
|
268
258
|
const colorOptionsChanged = !previousColorOptions || previousColorOptions.isDarkMode !== isDarkMode || previousColorOptions.options !== colorGeneratorOptions;
|
|
@@ -288,7 +278,7 @@ const StackedBarChart = (props) => {
|
|
|
288
278
|
if (hasChanges) setAutoColorMap(updatedMap);
|
|
289
279
|
}
|
|
290
280
|
}, [
|
|
291
|
-
|
|
281
|
+
chartData,
|
|
292
282
|
colorGeneratorOptions,
|
|
293
283
|
isDarkMode,
|
|
294
284
|
theme,
|
|
@@ -297,9 +287,10 @@ const StackedBarChart = (props) => {
|
|
|
297
287
|
const displayBadge = badge !== null && badge !== void 0 ? badge : calculateBadgeText(data.length, selectedIndices.length);
|
|
298
288
|
const { effectiveMaxAmount, hasRemaining, remainingValue, remainingPercentage } = useMemo(() => {
|
|
299
289
|
const total = data.reduce((sum, item) => sum + item.value, 0);
|
|
290
|
+
const visibleTotal = chartData.reduce((sum, item) => sum + item.value, 0);
|
|
300
291
|
const maxAmountValue = mode === "cumulative" && maxAmount ? maxAmount : total;
|
|
301
|
-
const hasRemainingSegment = mode === "cumulative" &&
|
|
302
|
-
const remaining = hasRemainingSegment ? maxAmountValue -
|
|
292
|
+
const hasRemainingSegment = mode === "cumulative" && visibleTotal < maxAmountValue;
|
|
293
|
+
const remaining = hasRemainingSegment ? maxAmountValue - visibleTotal : 0;
|
|
303
294
|
return {
|
|
304
295
|
effectiveMaxAmount: maxAmountValue,
|
|
305
296
|
hasRemaining: hasRemainingSegment,
|
|
@@ -308,11 +299,20 @@ const StackedBarChart = (props) => {
|
|
|
308
299
|
};
|
|
309
300
|
}, [
|
|
310
301
|
data,
|
|
302
|
+
chartData,
|
|
311
303
|
mode,
|
|
312
304
|
maxAmount
|
|
313
305
|
]);
|
|
306
|
+
const hasOverflow = hasRemaining && hiddenItems.length > 0;
|
|
307
|
+
const overflowIndex = data.length;
|
|
308
|
+
const hiddenColors = useMemo(() => hasOverflow ? generateDiscreteColors(hiddenItems.length, _objectSpread2(_objectSpread2({}, colorGeneratorOptions || {}), {}, { isDarkMode })) : [], [
|
|
309
|
+
hasOverflow,
|
|
310
|
+
hiddenItems.length,
|
|
311
|
+
colorGeneratorOptions,
|
|
312
|
+
isDarkMode
|
|
313
|
+
]);
|
|
314
314
|
const dataWithPercentages = useMemo(() => {
|
|
315
|
-
return
|
|
315
|
+
return chartData.map((item, index) => {
|
|
316
316
|
var _theme$palette3;
|
|
317
317
|
const resolvedColor = item.color || autoColorMap.get(item.name) || (theme === null || theme === void 0 || (_theme$palette3 = theme.palette) === null || _theme$palette3 === void 0 || (_theme$palette3 = _theme$palette3.sds) === null || _theme$palette3 === void 0 || (_theme$palette3 = _theme$palette3.base) === null || _theme$palette3 === void 0 ? void 0 : _theme$palette3.ornamentSecondary);
|
|
318
318
|
return _objectSpread2(_objectSpread2({}, item), {}, {
|
|
@@ -325,7 +325,7 @@ const StackedBarChart = (props) => {
|
|
|
325
325
|
});
|
|
326
326
|
});
|
|
327
327
|
}, [
|
|
328
|
-
|
|
328
|
+
chartData,
|
|
329
329
|
effectiveMaxAmount,
|
|
330
330
|
autoColorMap,
|
|
331
331
|
theme
|
|
@@ -366,10 +366,16 @@ const StackedBarChart = (props) => {
|
|
|
366
366
|
timeoutIds.forEach((id) => window.clearTimeout(id));
|
|
367
367
|
};
|
|
368
368
|
}, [dataWithPercentages, remainingPercentage]);
|
|
369
|
-
const
|
|
370
|
-
|
|
369
|
+
const toChartIndex = useCallback((index) => hasOverflow && index >= visibleCount ? overflowIndex : index, [
|
|
370
|
+
hasOverflow,
|
|
371
|
+
visibleCount,
|
|
372
|
+
overflowIndex
|
|
373
|
+
]);
|
|
374
|
+
const hoveredChartIndex = hoveredIndex === null ? null : toChartIndex(hoveredIndex);
|
|
375
|
+
const selectedChartIndices = useMemo(() => new Set(selectedIndices.map(toChartIndex)), [selectedIndices, toChartIndex]);
|
|
376
|
+
const { styledItems, updatedRemainingPercentage, updatedRemainingValue } = useMemo(() => applySelectionStyling(animatedItems, selectedChartIndices, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, animatedRemainingPercentage, remainingValue), [
|
|
371
377
|
animatedItems,
|
|
372
|
-
|
|
378
|
+
selectedChartIndices,
|
|
373
379
|
selectionBehavior,
|
|
374
380
|
mode,
|
|
375
381
|
hasRemaining,
|
|
@@ -390,6 +396,7 @@ const StackedBarChart = (props) => {
|
|
|
390
396
|
remainingPercentage: displayRemainingPercentage,
|
|
391
397
|
remainingLabel,
|
|
392
398
|
remainingUnit,
|
|
399
|
+
remainingOpensMenu: hasOverflow,
|
|
393
400
|
theme
|
|
394
401
|
}), [
|
|
395
402
|
dataWithPercentages,
|
|
@@ -402,19 +409,51 @@ const StackedBarChart = (props) => {
|
|
|
402
409
|
displayRemainingPercentage,
|
|
403
410
|
remainingLabel,
|
|
404
411
|
remainingUnit,
|
|
412
|
+
hasOverflow,
|
|
405
413
|
theme
|
|
406
414
|
]);
|
|
407
|
-
const
|
|
415
|
+
const toLegendIndex = useCallback((index) => {
|
|
416
|
+
if (!hasOverflow || index < visibleCount) return index;
|
|
417
|
+
return index === overflowIndex ? visibleCount : index + 1;
|
|
418
|
+
}, [
|
|
419
|
+
hasOverflow,
|
|
420
|
+
visibleCount,
|
|
421
|
+
overflowIndex
|
|
422
|
+
]);
|
|
423
|
+
const fromLegendIndex = useCallback((index) => {
|
|
424
|
+
if (!hasOverflow || index < visibleCount) return index;
|
|
425
|
+
return index === visibleCount ? overflowIndex : index - 1;
|
|
426
|
+
}, [
|
|
427
|
+
hasOverflow,
|
|
428
|
+
visibleCount,
|
|
429
|
+
overflowIndex
|
|
430
|
+
]);
|
|
431
|
+
const handleLegendItemHover = useCallback((_item, legendIndex) => {
|
|
432
|
+
const index = fromLegendIndex(legendIndex);
|
|
408
433
|
setHoveredIndex(index);
|
|
409
434
|
if (onLegendItemMouseEnter && data[index]) onLegendItemMouseEnter(data[index], index);
|
|
410
|
-
}, [
|
|
411
|
-
|
|
435
|
+
}, [
|
|
436
|
+
onLegendItemMouseEnter,
|
|
437
|
+
data,
|
|
438
|
+
fromLegendIndex
|
|
439
|
+
]);
|
|
440
|
+
const handleLegendItemLeave = useCallback((_item, legendIndex) => {
|
|
441
|
+
const index = fromLegendIndex(legendIndex);
|
|
412
442
|
setHoveredIndex(null);
|
|
413
443
|
if (onLegendItemMouseLeave && data[index]) onLegendItemMouseLeave(data[index], index);
|
|
414
|
-
}, [
|
|
415
|
-
|
|
444
|
+
}, [
|
|
445
|
+
onLegendItemMouseLeave,
|
|
446
|
+
data,
|
|
447
|
+
fromLegendIndex
|
|
448
|
+
]);
|
|
449
|
+
const handleLegendItemClickInternal = useCallback((_item, legendIndex) => {
|
|
450
|
+
const index = fromLegendIndex(legendIndex);
|
|
416
451
|
if (onLegendItemClick && data[index]) onLegendItemClick(data[index], index);
|
|
417
|
-
}, [
|
|
452
|
+
}, [
|
|
453
|
+
onLegendItemClick,
|
|
454
|
+
data,
|
|
455
|
+
fromLegendIndex
|
|
456
|
+
]);
|
|
418
457
|
const handleSelectionChange = useCallback((newSelectedIndices) => {
|
|
419
458
|
if (!onSelectionChange) return;
|
|
420
459
|
onSelectionChange(newSelectedIndices, data.filter((item, itemIndex) => newSelectedIndices.includes(itemIndex)));
|
|
@@ -422,15 +461,29 @@ const StackedBarChart = (props) => {
|
|
|
422
461
|
const handleSegmentClick = useCallback((index) => {
|
|
423
462
|
if (onSegmentClick && data[index]) onSegmentClick(data[index], index);
|
|
424
463
|
if (!onSelectionChange) return;
|
|
425
|
-
handleSelectionChange(
|
|
464
|
+
handleSelectionChange(selectedChartIndices.has(index) ? selectedIndices.filter((i) => i !== index) : [...selectedIndices, index]);
|
|
426
465
|
}, [
|
|
427
466
|
onSelectionChange,
|
|
428
467
|
onSegmentClick,
|
|
429
468
|
data,
|
|
430
469
|
selectedIndices,
|
|
431
|
-
|
|
470
|
+
selectedChartIndices,
|
|
432
471
|
handleSelectionChange
|
|
433
472
|
]);
|
|
473
|
+
const remainingMenuItems = useMemo(() => hasOverflow ? hiddenItems.map((item, index) => {
|
|
474
|
+
var _theme$palette4;
|
|
475
|
+
return {
|
|
476
|
+
name: item.name,
|
|
477
|
+
value: item.value,
|
|
478
|
+
color: item.color || hiddenColors[index] || (theme === null || theme === void 0 || (_theme$palette4 = theme.palette) === null || _theme$palette4 === void 0 || (_theme$palette4 = _theme$palette4.sds) === null || _theme$palette4 === void 0 || (_theme$palette4 = _theme$palette4.base) === null || _theme$palette4 === void 0 ? void 0 : _theme$palette4.ornamentSecondary),
|
|
479
|
+
disabled: item.disabled
|
|
480
|
+
};
|
|
481
|
+
}) : [], [
|
|
482
|
+
hasOverflow,
|
|
483
|
+
hiddenItems,
|
|
484
|
+
hiddenColors,
|
|
485
|
+
theme
|
|
486
|
+
]);
|
|
434
487
|
const lastVisibleIndex = useMemo(() => {
|
|
435
488
|
for (let i = styledItems.length - 1; i >= 0; i--) if (styledItems[i].percentage > 0) return i;
|
|
436
489
|
return -1;
|
|
@@ -442,8 +495,8 @@ const StackedBarChart = (props) => {
|
|
|
442
495
|
children: [styledItems.map((item, index) => {
|
|
443
496
|
return renderBarSegment(item, {
|
|
444
497
|
barHeight,
|
|
445
|
-
hoveredIndex,
|
|
446
|
-
selectedIndicesSet,
|
|
498
|
+
hoveredIndex: hoveredChartIndex,
|
|
499
|
+
selectedIndicesSet: selectedChartIndices,
|
|
447
500
|
selectionBehavior,
|
|
448
501
|
isLast: index === lastVisibleIndex && !hasRemaining,
|
|
449
502
|
onMouseEnter: () => {
|
|
@@ -459,25 +512,31 @@ const StackedBarChart = (props) => {
|
|
|
459
512
|
onClick: () => !item.isExiting && handleSegmentClick(item.originalIndex)
|
|
460
513
|
});
|
|
461
514
|
}), previousProcessedRef.current.length > 0 && /* @__PURE__ */ jsx(BarSegment, {
|
|
462
|
-
color: theme === null || theme === void 0 || (_theme$
|
|
515
|
+
color: theme === null || theme === void 0 || (_theme$palette5 = theme.palette) === null || _theme$palette5 === void 0 || (_theme$palette5 = _theme$palette5.sds) === null || _theme$palette5 === void 0 || (_theme$palette5 = _theme$palette5.base) === null || _theme$palette5 === void 0 ? void 0 : _theme$palette5.backgroundTertiary,
|
|
463
516
|
percentage: displayRemainingPercentage,
|
|
464
517
|
height: barHeight,
|
|
465
518
|
isLast: true,
|
|
466
|
-
opacity: 1,
|
|
519
|
+
opacity: hasOverflow ? getSegmentOpacity(overflowIndex, hoveredChartIndex, selectedChartIndices, selectionBehavior) : 1,
|
|
467
520
|
disabled: true
|
|
468
521
|
}, "remaining")]
|
|
469
522
|
}), showLegend && /* @__PURE__ */ jsx(Legend, {
|
|
470
523
|
items: legendItems,
|
|
524
|
+
lastItemMenuItems: remainingMenuItems,
|
|
471
525
|
showValues: showLegendValues,
|
|
472
526
|
onItemMouseEnter: handleLegendItemHover,
|
|
473
527
|
onItemMouseLeave: handleLegendItemLeave,
|
|
474
528
|
onItemClick: onLegendItemClick ? handleLegendItemClickInternal : void 0,
|
|
475
|
-
selectedIndices,
|
|
476
|
-
onSelectionChange: onSelectionChange ? handleSelectionChange : void 0,
|
|
477
|
-
hoveredIndex
|
|
529
|
+
selectedIndices: selectedIndices.map(toLegendIndex),
|
|
530
|
+
onSelectionChange: onSelectionChange ? (indices) => handleSelectionChange(indices.map(fromLegendIndex)) : void 0,
|
|
531
|
+
hoveredIndex: hoveredIndex === null ? null : toLegendIndex(hoveredIndex)
|
|
478
532
|
})] });
|
|
479
533
|
if (!title) return /* @__PURE__ */ jsx(ChartWrapper, _objectSpread2(_objectSpread2({ width }, rest), {}, { children: chartContent }));
|
|
480
|
-
return /* @__PURE__ */ jsxs(ChartWrapper, _objectSpread2(_objectSpread2({ width }, rest), {}, { children: [/* @__PURE__ */ jsxs(TitleContainer, { children: [/* @__PURE__ */ jsx(ChartTitle, { children: title }), !hideBadge && displayBadge && /* @__PURE__ */ jsx(
|
|
534
|
+
return /* @__PURE__ */ jsxs(ChartWrapper, _objectSpread2(_objectSpread2({ width }, rest), {}, { children: [/* @__PURE__ */ jsxs(TitleContainer, { children: [/* @__PURE__ */ jsx(ChartTitle, { children: title }), !hideBadge && displayBadge && /* @__PURE__ */ jsx(Tag, {
|
|
535
|
+
label: displayBadge,
|
|
536
|
+
sdsStyle: "outline",
|
|
537
|
+
color: "neutral",
|
|
538
|
+
size: "small"
|
|
539
|
+
})] }), chartContent] }));
|
|
481
540
|
};
|
|
482
541
|
//#endregion
|
|
483
542
|
export { StackedBarChart as t };
|
|
@@ -64,7 +64,7 @@ interface StackedBarChartProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
64
64
|
/**
|
|
65
65
|
* Height of the bar in pixels. Values below 1 are clamped to 1, so the bar
|
|
66
66
|
* cannot disappear.
|
|
67
|
-
* @default
|
|
67
|
+
* @default 8
|
|
68
68
|
*/
|
|
69
69
|
barHeight?: number;
|
|
70
70
|
/**
|
|
@@ -139,6 +139,12 @@ interface StackedBarChartProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
139
139
|
* between it and the sum of the data is drawn as the Remaining segment.
|
|
140
140
|
*/
|
|
141
141
|
maxAmount?: number;
|
|
142
|
+
/**
|
|
143
|
+
* Cumulative mode only. How many items are drawn as their own segment. The
|
|
144
|
+
* rest fold into the Remaining segment, whose legend item opens a menu
|
|
145
|
+
* listing them.
|
|
146
|
+
*/
|
|
147
|
+
maxItems?: number;
|
|
142
148
|
/**
|
|
143
149
|
* Name of the grey gap segment in cumulative mode.
|
|
144
150
|
* @default "Remaining"
|
|
@@ -64,7 +64,7 @@ interface StackedBarChartProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
64
64
|
/**
|
|
65
65
|
* Height of the bar in pixels. Values below 1 are clamped to 1, so the bar
|
|
66
66
|
* cannot disappear.
|
|
67
|
-
* @default
|
|
67
|
+
* @default 8
|
|
68
68
|
*/
|
|
69
69
|
barHeight?: number;
|
|
70
70
|
/**
|
|
@@ -139,6 +139,12 @@ interface StackedBarChartProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
139
139
|
* between it and the sum of the data is drawn as the Remaining segment.
|
|
140
140
|
*/
|
|
141
141
|
maxAmount?: number;
|
|
142
|
+
/**
|
|
143
|
+
* Cumulative mode only. How many items are drawn as their own segment. The
|
|
144
|
+
* rest fold into the Remaining segment, whose legend item opens a menu
|
|
145
|
+
* listing them.
|
|
146
|
+
*/
|
|
147
|
+
maxItems?: number;
|
|
142
148
|
/**
|
|
143
149
|
* Name of the grey gap segment in cumulative mode.
|
|
144
150
|
* @default "Remaining"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@czi-sds/data-viz",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"types": "dist/index.cjs.d.ts",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"namespace-check": "tsc --p tsconfig-namespace-check.json"
|
|
122
122
|
},
|
|
123
123
|
"peerDependencies": {
|
|
124
|
-
"@czi-sds/components": "^
|
|
124
|
+
"@czi-sds/components": "^25.0.0",
|
|
125
125
|
"@emotion/styled": "^11.11.0",
|
|
126
126
|
"@mui/material": "^9.0.0",
|
|
127
127
|
"@phosphor-icons/react": "^2.1.10",
|
|
@@ -157,5 +157,5 @@
|
|
|
157
157
|
"echarts": "^6.0.0",
|
|
158
158
|
"lodash": "^4.17.21"
|
|
159
159
|
},
|
|
160
|
-
"gitHead": "
|
|
160
|
+
"gitHead": "d38c18e3aca773081cb5c0413dbfefb10da774b8"
|
|
161
161
|
}
|