@czi-sds/data-viz 2.3.0 → 2.4.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.
Files changed (39) hide show
  1. package/dist/HeatmapChart.cjs.d.ts +2 -0
  2. package/dist/HeatmapChart.cjs.js +8 -0
  3. package/dist/HeatmapChart.esm.d.ts +2 -0
  4. package/dist/HeatmapChart.esm.js +3 -0
  5. package/dist/ProteinStructureViewer.cjs.d.ts +2 -0
  6. package/dist/ProteinStructureViewer.cjs.js +14 -0
  7. package/dist/ProteinStructureViewer.esm.d.ts +2 -0
  8. package/dist/ProteinStructureViewer.esm.js +3 -0
  9. package/dist/StackedBarChart.cjs.d.ts +2 -0
  10. package/dist/StackedBarChart.cjs.js +8 -0
  11. package/dist/StackedBarChart.esm.d.ts +2 -0
  12. package/dist/StackedBarChart.esm.js +3 -0
  13. package/dist/colorScales.cjs.d.ts +2 -0
  14. package/dist/colorScales.cjs.js +6 -0
  15. package/dist/colorScales.esm.d.ts +2 -0
  16. package/dist/colorScales.esm.js +3 -0
  17. package/dist/index.cjs.d.ts +4 -772
  18. package/dist/index.cjs.js +16 -5765
  19. package/dist/index.esm.d.ts +4 -774
  20. package/dist/index.esm.js +4 -5719
  21. package/dist/shared/HeatmapChart.cjs.js +349 -0
  22. package/dist/shared/HeatmapChart.esm.js +342 -0
  23. package/dist/shared/ProteinStructureViewer.cjs.js +4780 -0
  24. package/dist/shared/ProteinStructureViewer.esm.js +4728 -0
  25. package/dist/shared/StackedBarChart.cjs.js +489 -0
  26. package/dist/shared/StackedBarChart.esm.js +483 -0
  27. package/dist/shared/colorScales.cjs.d.ts +45 -0
  28. package/dist/shared/colorScales.cjs.js +126 -0
  29. package/dist/shared/colorScales.esm.d.ts +45 -0
  30. package/dist/shared/colorScales.esm.js +103 -0
  31. package/dist/shared/index.cjs.d.ts +183 -0
  32. package/dist/shared/index.esm.d.ts +183 -0
  33. package/dist/shared/index2.cjs.d.ts +389 -0
  34. package/dist/shared/index2.esm.d.ts +391 -0
  35. package/dist/shared/index3.cjs.d.ts +166 -0
  36. package/dist/shared/index3.esm.d.ts +166 -0
  37. package/dist/shared/objectSpread2.cjs.js +124 -0
  38. package/dist/shared/objectSpread2.esm.js +85 -0
  39. package/package.json +64 -2
@@ -0,0 +1,489 @@
1
+ "use client";
2
+ const require_objectSpread2 = require("./objectSpread2.cjs.js");
3
+ let react = require("react");
4
+ let _emotion_styled = require("@emotion/styled");
5
+ _emotion_styled = require_objectSpread2.__toESM(_emotion_styled);
6
+ let react_jsx_runtime = require("react/jsx-runtime");
7
+ let _czi_sds_components = require("@czi-sds/components");
8
+ let _mui_material = require("@mui/material");
9
+ //#region src/core/StackedBarChart/style.ts
10
+ const ChartWrapper = (0, _emotion_styled.default)("div")`
11
+ display: flex;
12
+ flex-direction: column;
13
+ width: ${(props) => typeof props.width === "number" ? `${props.width}px` : props.width};
14
+ `;
15
+ const TitleContainer = (0, _emotion_styled.default)("div")`
16
+ display: flex;
17
+ align-items: center;
18
+
19
+ ${(props) => {
20
+ const spaces = (0, _czi_sds_components.getSpaces)(props);
21
+ return `
22
+ gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.s}px;
23
+ margin-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.l}px;
24
+ `;
25
+ }}
26
+ `;
27
+ const ChartTitle = (0, _emotion_styled.default)("div")`
28
+ ${_czi_sds_components.fontHeaderS}
29
+
30
+ ${(props) => {
31
+ var _semanticColors$base;
32
+ const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
33
+ return `
34
+ color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base = semanticColors.base) === null || _semanticColors$base === void 0 ? void 0 : _semanticColors$base.textPrimary};
35
+ `;
36
+ }}
37
+ `;
38
+ const StyledBadge = (0, _emotion_styled.default)("div")`
39
+ ${_czi_sds_components.fontTabularSemiboldXxxxs}
40
+
41
+ ${(props) => {
42
+ var _semanticColors$base2, _semanticColors$base3;
43
+ const spaces = (0, _czi_sds_components.getSpaces)(props);
44
+ const corners = (0, _czi_sds_components.getCorners)(props);
45
+ const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
46
+ return `
47
+ padding: ${spaces === null || spaces === void 0 ? void 0 : spaces.xxxs}px ${spaces === null || spaces === void 0 ? void 0 : spaces.xxs}px;
48
+ border-radius: ${corners === null || corners === void 0 ? void 0 : corners.m}px;
49
+ background-color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.backgroundTertiary};
50
+ color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.textSecondary};
51
+ `;
52
+ }}
53
+ `;
54
+ const BarContainer = (0, _emotion_styled.default)("div")`
55
+ ${(props) => {
56
+ var _semanticColors$base4;
57
+ const { width, barHeight, isEmpty } = props;
58
+ const corners = (0, _czi_sds_components.getCorners)(props);
59
+ const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
60
+ const finalWidth = typeof width === "number" ? `${width}px` : width;
61
+ return `
62
+ display: flex;
63
+ border-radius: ${corners === null || corners === void 0 ? void 0 : corners.s}px;
64
+ width: ${finalWidth};
65
+ height: ${barHeight}px;
66
+ background-color: ${isEmpty ? semanticColors === null || semanticColors === void 0 || (_semanticColors$base4 = semanticColors.base) === null || _semanticColors$base4 === void 0 ? void 0 : _semanticColors$base4.backgroundTertiary : "transparent"};
67
+ overflow: hidden;
68
+ `;
69
+ }}
70
+ `;
71
+ const BarSegment = (0, _emotion_styled.default)("div")`
72
+ ${(props) => {
73
+ const { color, height, percentage, opacity, disabled, isLast } = props;
74
+ const spaces = (0, _czi_sds_components.getSpaces)(props);
75
+ const gap = (spaces === null || spaces === void 0 ? void 0 : spaces.xxxs) || 0;
76
+ return `
77
+ background-color: ${color};
78
+ height: ${height}px;
79
+ flex-grow: ${percentage};
80
+ flex-basis: 0;
81
+ flex-shrink: 1;
82
+ margin-right: ${percentage > 0 && !isLast ? `${gap}px` : 0};
83
+ opacity: ${opacity};
84
+ transition: opacity 0.2s ease-in-out, flex-grow 0.25s ease-in-out, margin-right 0.25s ease-out;
85
+ cursor: ${disabled ? "default" : "pointer"};
86
+ pointer-events: ${disabled ? "none" : "auto"};
87
+ `;
88
+ }}
89
+ `;
90
+ const StyledStackedBarChartWrapper = (0, _emotion_styled.default)("div")`
91
+ ${(props) => {
92
+ const spaces = (0, _czi_sds_components.getSpaces)(props);
93
+ return `
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.m}px;
97
+ `;
98
+ }}
99
+ `;
100
+ //#endregion
101
+ //#region src/core/StackedBarChart/index.tsx
102
+ const _excluded = [
103
+ "title",
104
+ "badge",
105
+ "hideBadge",
106
+ "data",
107
+ "width",
108
+ "barHeight",
109
+ "showLegend",
110
+ "showLegendValues",
111
+ "legendValueFormat",
112
+ "selectedIndices",
113
+ "onSelectionChange",
114
+ "onSegmentMouseEnter",
115
+ "onSegmentMouseLeave",
116
+ "onLegendItemMouseEnter",
117
+ "onLegendItemMouseLeave",
118
+ "onSegmentClick",
119
+ "onLegendItemClick",
120
+ "selectionBehavior",
121
+ "mode",
122
+ "maxAmount",
123
+ "remainingLabel",
124
+ "remainingUnit",
125
+ "unit",
126
+ "colorGeneratorOptions"
127
+ ];
128
+ const calculateBadgeText = (totalItems, selectedCount) => {
129
+ if (selectedCount === 0 || selectedCount === totalItems) return `${totalItems}`;
130
+ return `${selectedCount} of ${totalItems}`;
131
+ };
132
+ const calculateLegendValue = (item, showLegendValues, legendValueFormat, globalUnit) => {
133
+ if (!showLegendValues) return void 0;
134
+ if (legendValueFormat === "percentage") return `${Math.round(item.percentage)}%`;
135
+ const effectiveUnit = item.unit || globalUnit;
136
+ return effectiveUnit ? `${item.value} ${effectiveUnit}` : `${item.value}`;
137
+ };
138
+ const getSegmentOpacity = (index, hoveredIndex, selectedIndicesSet, selectionBehavior) => {
139
+ const hasSelection = selectedIndicesSet.size > 0;
140
+ if (selectionBehavior === "hide") return 1;
141
+ if (hoveredIndex !== null) return hoveredIndex === index || selectedIndicesSet.has(index) ? 1 : .2;
142
+ if (hasSelection) return selectedIndicesSet.has(index) ? 1 : .2;
143
+ return 1;
144
+ };
145
+ const applyCumulativeHideStyling = (items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount) => {
146
+ const newRemainingValue = hasRemaining ? effectiveMaxAmount - selectedTotal : 0;
147
+ const newRemainingPercentage = hasRemaining ? newRemainingValue / effectiveMaxAmount * 100 : 0;
148
+ return {
149
+ styledItems: items.map((item) => {
150
+ const isSelected = selectedIndicesSet.has(item.originalIndex);
151
+ return require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, item), {}, { percentage: isSelected ? item.percentage : 0 });
152
+ }),
153
+ updatedRemainingPercentage: newRemainingPercentage,
154
+ updatedRemainingValue: newRemainingValue
155
+ };
156
+ };
157
+ const applyProportionalHideStyling = (items, selectedIndicesSet, selectedTotal) => {
158
+ if (selectedTotal === 0) return {
159
+ styledItems: items.map((item) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, item), {}, { percentage: 0 })),
160
+ updatedRemainingPercentage: 0,
161
+ updatedRemainingValue: 0
162
+ };
163
+ return {
164
+ styledItems: items.map((item) => {
165
+ const newPercentage = selectedIndicesSet.has(item.originalIndex) ? item.value / selectedTotal * 100 : 0;
166
+ return require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, item), {}, { percentage: newPercentage });
167
+ }),
168
+ updatedRemainingPercentage: 0,
169
+ updatedRemainingValue: 0
170
+ };
171
+ };
172
+ const applySelectionStyling = (items, selectedIndicesSet, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, baseRemainingPercentage, baseRemainingValue) => {
173
+ const hasSelection = selectedIndicesSet.size > 0;
174
+ if (selectionBehavior !== "hide" || !hasSelection) return {
175
+ styledItems: items,
176
+ updatedRemainingPercentage: baseRemainingPercentage,
177
+ updatedRemainingValue: baseRemainingValue
178
+ };
179
+ const selectedTotal = items.filter((item) => selectedIndicesSet.has(item.originalIndex)).reduce((sum, item) => sum + item.value, 0);
180
+ return mode === "cumulative" ? applyCumulativeHideStyling(items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount) : applyProportionalHideStyling(items, selectedIndicesSet, selectedTotal);
181
+ };
182
+ const buildLegendItems = (dataWithPercentages, options) => {
183
+ const { showLegendValues, legendValueFormat, unit, hasRemaining, showLegend, remainingValue, remainingPercentage, remainingLabel, remainingUnit, theme } = options;
184
+ const legendItems = dataWithPercentages.map((item) => ({
185
+ name: item.name,
186
+ value: calculateLegendValue(item, showLegendValues, legendValueFormat, unit),
187
+ color: item.color,
188
+ disabled: item.disabled
189
+ }));
190
+ if (hasRemaining && showLegend) {
191
+ var _theme$palette;
192
+ let remainingValueDisplay;
193
+ if (!showLegendValues) remainingValueDisplay = void 0;
194
+ else if (legendValueFormat === "percentage") remainingValueDisplay = `${Math.round(remainingPercentage)}%`;
195
+ else {
196
+ const effectiveRemainingUnit = remainingUnit || unit || "";
197
+ remainingValueDisplay = effectiveRemainingUnit ? `${remainingValue} ${effectiveRemainingUnit}` : `${remainingValue}`;
198
+ }
199
+ legendItems.push({
200
+ name: remainingLabel,
201
+ value: remainingValueDisplay,
202
+ 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,
203
+ disabled: true
204
+ });
205
+ }
206
+ return legendItems;
207
+ };
208
+ const renderBarSegment = (item, options) => {
209
+ const { barHeight, hoveredIndex, selectedIndicesSet, selectionBehavior, isLast, onMouseEnter, onMouseLeave, onClick } = options;
210
+ const isHidden = selectionBehavior === "hide" && item.percentage === 0;
211
+ const barSegment = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BarSegment, {
212
+ color: item.color,
213
+ percentage: item.isEntering || item.isExiting ? 0 : item.percentage,
214
+ height: barHeight,
215
+ opacity: getSegmentOpacity(item.originalIndex, hoveredIndex, selectedIndicesSet, selectionBehavior),
216
+ isLast,
217
+ "data-hide": isHidden,
218
+ onMouseEnter,
219
+ onMouseLeave,
220
+ onClick
221
+ });
222
+ return item.tooltip !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.Tooltip, {
223
+ title: null,
224
+ componentSlot: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.TooltipTable, require_objectSpread2._objectSpread2({}, item.tooltip)),
225
+ placement: "top",
226
+ open: hoveredIndex === item.originalIndex && !isHidden,
227
+ disableInteractive: true,
228
+ slotProps: { popper: {
229
+ disablePortal: false,
230
+ modifiers: [{
231
+ name: "offset",
232
+ options: { offset: [0, 0] }
233
+ }]
234
+ } },
235
+ children: barSegment
236
+ }, item.key) : (0, react.cloneElement)(barSegment, { key: item.key });
237
+ };
238
+ const StackedBarChart = (props) => {
239
+ var _theme$palette4;
240
+ const { title, badge, hideBadge = false, data, width = "100%", barHeight: barHeightProp = 16, showLegend = true, showLegendValues = true, legendValueFormat = "percentage", selectedIndices = [], onSelectionChange, onSegmentMouseEnter, onSegmentMouseLeave, onLegendItemMouseEnter, onLegendItemMouseLeave, onSegmentClick, onLegendItemClick, selectionBehavior = "dim", mode = "proportional", maxAmount, remainingLabel = "Remaining", remainingUnit, unit, colorGeneratorOptions } = props, rest = require_objectSpread2._objectWithoutProperties(props, _excluded);
241
+ const barHeight = Math.max(1, barHeightProp);
242
+ const theme = (0, _mui_material.useTheme)();
243
+ const [hoveredIndex, setHoveredIndex] = (0, react.useState)(null);
244
+ const [animatedItems, setAnimatedItems] = (0, react.useState)([]);
245
+ const [autoColorMap, setAutoColorMap] = (0, react.useState)(/* @__PURE__ */ new Map());
246
+ const [animatedRemainingPercentage, setAnimatedRemainingPercentage] = (0, react.useState)(0);
247
+ const previousProcessedRef = (0, react.useRef)([]);
248
+ const previousDataKeysRef = (0, react.useRef)(/* @__PURE__ */ new Set());
249
+ const previousColorOptionsRef = (0, react.useRef)(null);
250
+ const isDarkMode = (0, react.useMemo)(() => (0, _czi_sds_components.getMode)({ theme }) === "dark", [theme]);
251
+ const generateColorMap = (0, react.useCallback)((items) => {
252
+ const palette = (0, _czi_sds_components.generateDiscreteColors)(items.length, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, colorGeneratorOptions || {}), {}, { isDarkMode }));
253
+ const newColorMap = /* @__PURE__ */ new Map();
254
+ items.forEach((item, index) => {
255
+ var _theme$palette2;
256
+ newColorMap.set(item.name, palette[index] || (theme === null || theme === void 0 || (_theme$palette2 = theme.palette) === null || _theme$palette2 === void 0 || (_theme$palette2 = _theme$palette2.sds) === null || _theme$palette2 === void 0 || (_theme$palette2 = _theme$palette2.base) === null || _theme$palette2 === void 0 ? void 0 : _theme$palette2.ornamentSecondary));
257
+ });
258
+ return newColorMap;
259
+ }, [
260
+ colorGeneratorOptions,
261
+ isDarkMode,
262
+ theme
263
+ ]);
264
+ (0, react.useEffect)(() => {
265
+ const autoItems = data.filter((item) => !item.color);
266
+ const currentDataKeys = new Set(data.map((item) => item.name));
267
+ const previousDataKeys = previousDataKeysRef.current;
268
+ const previousColorOptions = previousColorOptionsRef.current;
269
+ const colorOptionsChanged = !previousColorOptions || previousColorOptions.isDarkMode !== isDarkMode || previousColorOptions.options !== colorGeneratorOptions;
270
+ previousDataKeysRef.current = currentDataKeys;
271
+ previousColorOptionsRef.current = {
272
+ options: colorGeneratorOptions,
273
+ isDarkMode
274
+ };
275
+ if (autoItems.length === 0) {
276
+ if (autoColorMap.size > 0) setAutoColorMap(/* @__PURE__ */ new Map());
277
+ return;
278
+ }
279
+ const hasNewItems = autoItems.some((item) => !previousDataKeys.has(item.name));
280
+ if (autoColorMap.size === 0 || hasNewItems || colorOptionsChanged) setAutoColorMap(generateColorMap(autoItems));
281
+ else {
282
+ const autoKeys = new Set(autoItems.map((item) => item.name));
283
+ const updatedMap = new Map(autoColorMap);
284
+ let hasChanges = false;
285
+ for (const key of Array.from(updatedMap.keys())) if (!autoKeys.has(key)) {
286
+ updatedMap.delete(key);
287
+ hasChanges = true;
288
+ }
289
+ if (hasChanges) setAutoColorMap(updatedMap);
290
+ }
291
+ }, [
292
+ data,
293
+ colorGeneratorOptions,
294
+ isDarkMode,
295
+ theme,
296
+ generateColorMap
297
+ ]);
298
+ const displayBadge = badge !== null && badge !== void 0 ? badge : calculateBadgeText(data.length, selectedIndices.length);
299
+ const { effectiveMaxAmount, hasRemaining, remainingValue, remainingPercentage } = (0, react.useMemo)(() => {
300
+ const total = data.reduce((sum, item) => sum + item.value, 0);
301
+ const maxAmountValue = mode === "cumulative" && maxAmount ? maxAmount : total;
302
+ const hasRemainingSegment = mode === "cumulative" && total < maxAmountValue;
303
+ const remaining = hasRemainingSegment ? maxAmountValue - total : 0;
304
+ return {
305
+ effectiveMaxAmount: maxAmountValue,
306
+ hasRemaining: hasRemainingSegment,
307
+ remainingValue: remaining,
308
+ remainingPercentage: hasRemainingSegment ? remaining / maxAmountValue * 100 : 0
309
+ };
310
+ }, [
311
+ data,
312
+ mode,
313
+ maxAmount
314
+ ]);
315
+ const dataWithPercentages = (0, react.useMemo)(() => {
316
+ return data.map((item, index) => {
317
+ var _theme$palette3;
318
+ 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);
319
+ return require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, item), {}, {
320
+ color: resolvedColor,
321
+ percentage: effectiveMaxAmount > 0 ? item.value / effectiveMaxAmount * 100 : 0,
322
+ key: item.name,
323
+ originalIndex: index,
324
+ isEntering: false,
325
+ isExiting: false
326
+ });
327
+ });
328
+ }, [
329
+ data,
330
+ effectiveMaxAmount,
331
+ autoColorMap,
332
+ theme
333
+ ]);
334
+ (0, react.useEffect)(() => {
335
+ const prevItems = previousProcessedRef.current;
336
+ const nextItems = dataWithPercentages;
337
+ const nextItemsMap = new Map(nextItems.map((item) => [item.key, item]));
338
+ if (prevItems.length === 0) {
339
+ setAnimatedItems(nextItems);
340
+ setAnimatedRemainingPercentage(remainingPercentage);
341
+ previousProcessedRef.current = nextItems;
342
+ return;
343
+ }
344
+ const step1Items = [...prevItems];
345
+ let currentInsertionIndex = 0;
346
+ for (const item of nextItems) {
347
+ const step1Index = step1Items.findIndex((step1Item) => step1Item.key === item.key);
348
+ if (step1Index === -1) {
349
+ step1Items.splice(currentInsertionIndex, 0, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, item), {}, { isEntering: true }));
350
+ currentInsertionIndex++;
351
+ } else currentInsertionIndex = step1Index + 1;
352
+ }
353
+ setAnimatedItems(step1Items);
354
+ const timeoutIds = [];
355
+ timeoutIds.push(window.setTimeout(() => {
356
+ setAnimatedItems(step1Items.map((item) => {
357
+ var _nextItemsMap$get;
358
+ return (_nextItemsMap$get = nextItemsMap.get(item.key)) !== null && _nextItemsMap$get !== void 0 ? _nextItemsMap$get : require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, item), {}, { isExiting: true });
359
+ }));
360
+ setAnimatedRemainingPercentage(remainingPercentage);
361
+ timeoutIds.push(window.setTimeout(() => {
362
+ setAnimatedItems(nextItems);
363
+ previousProcessedRef.current = nextItems;
364
+ }, 350));
365
+ }, 0));
366
+ return () => {
367
+ timeoutIds.forEach((id) => window.clearTimeout(id));
368
+ };
369
+ }, [dataWithPercentages, remainingPercentage]);
370
+ const selectedIndicesSet = (0, react.useMemo)(() => new Set(selectedIndices), [selectedIndices]);
371
+ const { styledItems, updatedRemainingPercentage, updatedRemainingValue } = (0, react.useMemo)(() => applySelectionStyling(animatedItems, selectedIndicesSet, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, animatedRemainingPercentage, remainingValue), [
372
+ animatedItems,
373
+ selectedIndicesSet,
374
+ selectionBehavior,
375
+ mode,
376
+ hasRemaining,
377
+ effectiveMaxAmount,
378
+ animatedRemainingPercentage,
379
+ remainingValue
380
+ ]);
381
+ const isHideMode = selectionBehavior === "hide" && selectedIndices.length > 0;
382
+ const displayRemainingPercentage = isHideMode ? updatedRemainingPercentage : animatedRemainingPercentage;
383
+ const displayRemainingValue = isHideMode ? updatedRemainingValue : remainingValue;
384
+ const legendItems = (0, react.useMemo)(() => buildLegendItems(dataWithPercentages, {
385
+ showLegendValues,
386
+ legendValueFormat,
387
+ unit,
388
+ hasRemaining,
389
+ showLegend,
390
+ remainingValue: displayRemainingValue,
391
+ remainingPercentage: displayRemainingPercentage,
392
+ remainingLabel,
393
+ remainingUnit,
394
+ theme
395
+ }), [
396
+ dataWithPercentages,
397
+ showLegendValues,
398
+ legendValueFormat,
399
+ unit,
400
+ hasRemaining,
401
+ showLegend,
402
+ displayRemainingValue,
403
+ displayRemainingPercentage,
404
+ remainingLabel,
405
+ remainingUnit,
406
+ theme
407
+ ]);
408
+ const handleLegendItemHover = (0, react.useCallback)((_item, index) => {
409
+ setHoveredIndex(index);
410
+ if (onLegendItemMouseEnter && data[index]) onLegendItemMouseEnter(data[index], index);
411
+ }, [onLegendItemMouseEnter, data]);
412
+ const handleLegendItemLeave = (0, react.useCallback)((_item, index) => {
413
+ setHoveredIndex(null);
414
+ if (onLegendItemMouseLeave && data[index]) onLegendItemMouseLeave(data[index], index);
415
+ }, [onLegendItemMouseLeave, data]);
416
+ const handleLegendItemClickInternal = (0, react.useCallback)((_item, index) => {
417
+ if (onLegendItemClick && data[index]) onLegendItemClick(data[index], index);
418
+ }, [onLegendItemClick, data]);
419
+ const handleSelectionChange = (0, react.useCallback)((newSelectedIndices) => {
420
+ if (!onSelectionChange) return;
421
+ onSelectionChange(newSelectedIndices, data.filter((item, itemIndex) => newSelectedIndices.includes(itemIndex)));
422
+ }, [onSelectionChange, data]);
423
+ const handleSegmentClick = (0, react.useCallback)((index) => {
424
+ if (onSegmentClick && data[index]) onSegmentClick(data[index], index);
425
+ if (!onSelectionChange) return;
426
+ handleSelectionChange(selectedIndicesSet.has(index) ? selectedIndices.filter((i) => i !== index) : [...selectedIndices, index]);
427
+ }, [
428
+ onSelectionChange,
429
+ onSegmentClick,
430
+ data,
431
+ selectedIndices,
432
+ selectedIndicesSet,
433
+ handleSelectionChange
434
+ ]);
435
+ const lastVisibleIndex = (0, react.useMemo)(() => {
436
+ for (let i = styledItems.length - 1; i >= 0; i--) if (styledItems[i].percentage > 0) return i;
437
+ return -1;
438
+ }, [styledItems]);
439
+ const chartContent = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StyledStackedBarChartWrapper, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(BarContainer, {
440
+ width,
441
+ barHeight,
442
+ isEmpty: styledItems.length === 0 && !hasRemaining,
443
+ children: [styledItems.map((item, index) => {
444
+ return renderBarSegment(item, {
445
+ barHeight,
446
+ hoveredIndex,
447
+ selectedIndicesSet,
448
+ selectionBehavior,
449
+ isLast: index === lastVisibleIndex && !hasRemaining,
450
+ onMouseEnter: () => {
451
+ if (!item.isExiting) {
452
+ setHoveredIndex(item.originalIndex);
453
+ if (onSegmentMouseEnter && data[item.originalIndex]) onSegmentMouseEnter(data[item.originalIndex], item.originalIndex);
454
+ }
455
+ },
456
+ onMouseLeave: () => {
457
+ setHoveredIndex(null);
458
+ if (onSegmentMouseLeave && data[item.originalIndex]) onSegmentMouseLeave(data[item.originalIndex], item.originalIndex);
459
+ },
460
+ onClick: () => !item.isExiting && handleSegmentClick(item.originalIndex)
461
+ });
462
+ }), previousProcessedRef.current.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BarSegment, {
463
+ color: 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.backgroundTertiary,
464
+ percentage: displayRemainingPercentage,
465
+ height: barHeight,
466
+ isLast: true,
467
+ opacity: 1,
468
+ disabled: true
469
+ }, "remaining")]
470
+ }), showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.Legend, {
471
+ items: legendItems,
472
+ showValues: showLegendValues,
473
+ onItemMouseEnter: handleLegendItemHover,
474
+ onItemMouseLeave: handleLegendItemLeave,
475
+ onItemClick: onLegendItemClick ? handleLegendItemClickInternal : void 0,
476
+ selectedIndices,
477
+ onSelectionChange: onSelectionChange ? handleSelectionChange : void 0,
478
+ hoveredIndex
479
+ })] });
480
+ if (!title) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartWrapper, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({ width }, rest), {}, { children: chartContent }));
481
+ 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)(StyledBadge, { children: displayBadge })] }), chartContent] }));
482
+ };
483
+ //#endregion
484
+ Object.defineProperty(exports, "StackedBarChart", {
485
+ enumerable: true,
486
+ get: function() {
487
+ return StackedBarChart;
488
+ }
489
+ });