@czi-sds/data-viz 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -1047,7 +1047,7 @@ const SDSLightThemeColors = {
1047
1047
  purple: {
1048
1048
  "100": "#efeafe",
1049
1049
  "200": "#e4dbfc",
1050
- "300": "#cbbaF8",
1050
+ "300": "#cbbaf8",
1051
1051
  "400": "#b296f2",
1052
1052
  "500": "#8b54e2",
1053
1053
  "600": "#6526b5",
@@ -1218,7 +1218,7 @@ const percentToHex = (percentOpacity) => {
1218
1218
  // Convert percentage to 0-255 range
1219
1219
  const alphaValue = Math.round((clampedPercent / 100) * 255);
1220
1220
  // Convert to hex and pad with leading zero if needed
1221
- return alphaValue.toString(16).padStart(2, "0").toUpperCase();
1221
+ return alphaValue.toString(16).padStart(2, "0").toLowerCase();
1222
1222
  };
1223
1223
  /**
1224
1224
  * Adds opacity to a hex color by appending the hex opacity value.
@@ -1897,10 +1897,10 @@ const fontBodyS$1 = fontBody("s", "regular");
1897
1897
  const fontBodyXs$2 = fontBody("xs", "regular");
1898
1898
  const fontBodyXxs$1 = fontBody("xxs", "regular");
1899
1899
  const fontBodyXxxs$1 = fontBody("xxxs", "regular");
1900
- const fontBodyXxxxs = fontBody("xxxxs", "regular");
1901
1900
  const fontBodyMediumL = fontBody("l", "medium");
1902
1901
  const fontBodyMediumS = fontBody("s", "medium");
1903
1902
  const fontBodyMediumXs = fontBody("xs", "medium");
1903
+ const fontBodyMediumXxxs = fontBody("xxxs", "medium");
1904
1904
  const fontBodySemiboldL = fontBody("l", "semibold");
1905
1905
  const fontBodySemiboldM = fontBody("m", "semibold");
1906
1906
  const fontBodySemiboldS = fontBody("s", "semibold");
@@ -10539,7 +10539,7 @@ const LegendItem = styled$2__default.default("div") `
10539
10539
  align-items: center;
10540
10540
  cursor: ${disabled ? "not-allowed" : "pointer"} !important;
10541
10541
  transition: background-color 0.2s ease-in-out;
10542
- padding: 0 ${spaces?.xxs}px;
10542
+ padding: ${spaces?.xxxs}px ${spaces?.xxs}px;
10543
10543
  border-radius: ${corners?.s}px;
10544
10544
  background-color: ${isSelected ? semanticColors?.base?.backgroundTertiary : "transparent"};
10545
10545
  pointer-events: ${disabled ? "none" : "auto"};
@@ -10556,8 +10556,8 @@ const LegendIcon = styled$2__default.default("div") `
10556
10556
  const spaces = getSpaces(props);
10557
10557
  const corners = getCorners(props);
10558
10558
  return `
10559
- width: 8px;
10560
- height: 8px;
10559
+ width: ${spaces?.m}px;
10560
+ height: ${spaces?.m}px;
10561
10561
  border-radius: ${corners?.s}px;
10562
10562
  background-color: ${color};
10563
10563
  flex-shrink: 0;
@@ -10568,7 +10568,7 @@ const LegendIcon = styled$2__default.default("div") `
10568
10568
  }}
10569
10569
  `;
10570
10570
  const LegendLabel = styled$2__default.default("span") `
10571
- ${fontBodyXxxxs}
10571
+ ${fontBodyMediumXxxs}
10572
10572
  ${(props) => {
10573
10573
  const semanticColors = getSemanticColors(props);
10574
10574
  return `
@@ -10577,13 +10577,13 @@ const LegendLabel = styled$2__default.default("span") `
10577
10577
  }}
10578
10578
  `;
10579
10579
  const LegendValue = styled$2__default.default("span") `
10580
- ${fontBodyXxxxs}
10580
+ ${fontBodyMediumXxxs}
10581
10581
  ${(props) => {
10582
10582
  const semanticColors = getSemanticColors(props);
10583
10583
  const spaces = getSpaces(props);
10584
10584
  return `
10585
10585
  color: ${semanticColors?.base?.textSecondary};
10586
- margin-left: ${spaces?.xxxs}px;
10586
+ margin-left: ${spaces?.xxs}px;
10587
10587
  `;
10588
10588
  }}
10589
10589
  `;
@@ -24775,7 +24775,7 @@ Object.assign(chroma, {
24775
24775
  const generateDiscreteColors = (count, options = {}) => {
24776
24776
  if (count === 0)
24777
24777
  return [];
24778
- const { isDarkMode = false, start = 249, rotations = 1, gamma = 0.7, lightness = [0.3, 0.7], correctLightness = true, } = options;
24778
+ const { isDarkMode = false, start = 250, rotations = 0.85, gamma = 0.7, lightness = [0.4, 0.7], correctLightness = true, } = options;
24779
24779
  let scale = chroma
24780
24780
  .cubehelix()
24781
24781
  .start(start)
@@ -24811,33 +24811,22 @@ var StyledBadge = styled$2__default.default("div")(templateObject_4 || (template
24811
24811
  return "\n padding: ".concat(spaces === null || spaces === void 0 ? void 0 : spaces.xxxs, "px ").concat(spaces === null || spaces === void 0 ? void 0 : spaces.xxs, "px;\n border-radius: ").concat(corners === null || corners === void 0 ? void 0 : corners.m, "px;\n background-color: ").concat((_a = semanticColors === null || semanticColors === void 0 ? void 0 : semanticColors.base) === null || _a === void 0 ? void 0 : _a.backgroundTertiary, ";\n color: ").concat((_b = semanticColors === null || semanticColors === void 0 ? void 0 : semanticColors.base) === null || _b === void 0 ? void 0 : _b.textSecondary, ";\n ");
24812
24812
  });
24813
24813
  var BarContainer = styled$2__default.default("div")(templateObject_5 || (templateObject_5 = __makeTemplateObject(["\n ", "\n"], ["\n ", "\n"])), function (props) {
24814
- var width = props.width, barHeight = props.barHeight;
24814
+ var _a;
24815
+ var width = props.width, barHeight = props.barHeight, isEmpty = props.isEmpty;
24815
24816
  var corners = getCorners(props);
24817
+ var semanticColors = getSemanticColors(props);
24816
24818
  var finalWidth = typeof width === "number" ? "".concat(width, "px") : width;
24817
- return "\n display: flex;\n border-radius: ".concat(corners === null || corners === void 0 ? void 0 : corners.s, "px;\n width: ").concat(finalWidth, ";\n height: ").concat(barHeight, "px;\n ");
24819
+ return "\n display: flex;\n border-radius: ".concat(corners === null || corners === void 0 ? void 0 : corners.s, "px;\n width: ").concat(finalWidth, ";\n height: ").concat(barHeight, "px;\n background-color: ").concat(isEmpty ? (_a = semanticColors === null || semanticColors === void 0 ? void 0 : semanticColors.base) === null || _a === void 0 ? void 0 : _a.backgroundTertiary : "transparent", ";\n overflow: hidden;\n ");
24818
24820
  });
24819
24821
  var BarSegment = styled$2__default.default("div")(templateObject_6 || (templateObject_6 = __makeTemplateObject(["\n ", "\n"], ["\n ", "\n"])), function (props) {
24820
- var isFirst = props.isFirst, isLast = props.isLast, color = props.color, height = props.height, percentage = props.percentage, opacity = props.opacity, disabled = props.disabled;
24822
+ var color = props.color, height = props.height, percentage = props.percentage, opacity = props.opacity, disabled = props.disabled, isLast = props.isLast;
24821
24823
  var spaces = getSpaces(props);
24822
24824
  var gap = (spaces === null || spaces === void 0 ? void 0 : spaces.xxxs) || 0;
24823
- var getBorderRadius = function () {
24824
- var corners = getCorners(props);
24825
- var borderRadius = corners === null || corners === void 0 ? void 0 : corners.s;
24826
- if (isFirst && isLast) {
24827
- return "border-radius: ".concat(borderRadius, "px;");
24828
- }
24829
- if (isFirst) {
24830
- return "\n border-top-left-radius: ".concat(borderRadius, "px; \n border-bottom-left-radius: ").concat(borderRadius, "px;\n ");
24831
- }
24832
- if (isLast) {
24833
- return "\n border-top-right-radius: ".concat(borderRadius, "px; \n border-bottom-right-radius: ").concat(borderRadius, "px;\n ");
24834
- }
24835
- return "";
24836
- };
24837
- // Add gap as margin-right, except for last item
24825
+ // Add gap as margin-right for spacing between segments
24838
24826
  // When percentage is 0, margin also becomes 0 (animates away with the segment)
24839
- var marginRight = !isLast && percentage > 0 ? "".concat(gap, "px") : 0;
24840
- return "\n background-color: ".concat(color, ";\n height: ").concat(height, "px;\n flex-grow: ").concat(percentage, ";\n flex-basis: 0;\n flex-shrink: 1;\n margin-right: ").concat(marginRight, ";\n opacity: ").concat(opacity, ";\n transition: opacity 0.2s ease-in-out, flex-grow 0.25s ease-out, margin-right 0.2s ease-out;\n cursor: ").concat(disabled ? "default" : "pointer", ";\n pointer-events: ").concat(disabled ? "none" : "auto", ";\n ").concat(getBorderRadius(), "\n ");
24827
+ // Last visible segment also gets no margin
24828
+ var marginRight = percentage > 0 && !isLast ? "".concat(gap, "px") : 0;
24829
+ return "\n background-color: ".concat(color, ";\n height: ").concat(height, "px;\n flex-grow: ").concat(percentage, ";\n flex-basis: 0;\n flex-shrink: 1;\n margin-right: ").concat(marginRight, ";\n opacity: ").concat(opacity, ";\n transition: opacity 0.2s ease-in-out, flex-grow 0.25s ease-in-out, margin-right 0.25s ease-out;\n cursor: ").concat(disabled ? "default" : "pointer", ";\n pointer-events: ").concat(disabled ? "none" : "auto", ";\n ");
24841
24830
  });
24842
24831
  var StyledStackedBarChartWrapper = styled$2__default.default("div")(templateObject_7 || (templateObject_7 = __makeTemplateObject(["\n ", "\n"], ["\n ", "\n"])), function (props) {
24843
24832
  var spaces = getSpaces(props);
@@ -24847,56 +24836,126 @@ var templateObject_1, templateObject_2, templateObject_3, templateObject_4, temp
24847
24836
 
24848
24837
  // Helper: Calculate badge display text based on selection state
24849
24838
  var calculateBadgeText = function (totalItems, selectedCount) {
24850
- if (selectedCount === 0)
24839
+ if (selectedCount === 0 || selectedCount === totalItems)
24851
24840
  return "".concat(totalItems);
24852
- if (selectedCount === totalItems)
24853
- return "All";
24854
24841
  return "".concat(selectedCount, " of ").concat(totalItems);
24855
24842
  };
24856
24843
  // Helper: Calculate legend value display
24857
- var calculateLegendValue = function (item, mode, showLegendValues, globalUnit) {
24844
+ var calculateLegendValue = function (item, showLegendValues, legendValueFormat, globalUnit) {
24858
24845
  if (!showLegendValues)
24859
24846
  return undefined;
24860
- if (mode === "percentage") {
24847
+ if (legendValueFormat === "percentage") {
24861
24848
  return "".concat(Math.round(item.percentage), "%");
24862
24849
  }
24863
- // Amount mode: show value with optional unit
24850
+ // Count format: show value with optional unit
24864
24851
  var effectiveUnit = item.unit || globalUnit;
24865
24852
  return effectiveUnit ? "".concat(item.value, " ").concat(effectiveUnit) : "".concat(item.value);
24866
24853
  };
24867
24854
  // Helper: Get segment opacity based on hover and selection state
24868
- var getSegmentOpacity = function (index, hoveredIndex, selectedIndices) {
24869
- var hasSelection = selectedIndices.length > 0;
24855
+ var getSegmentOpacity = function (index, hoveredIndex, selectedIndicesSet, selectionBehavior) {
24856
+ var hasSelection = selectedIndicesSet.size > 0;
24857
+ // Hide mode doesn't use opacity - segments animate to 0% width instead
24858
+ if (selectionBehavior === "hide") {
24859
+ return 1;
24860
+ }
24861
+ // Dim mode
24870
24862
  if (hoveredIndex !== null) {
24871
- return hoveredIndex === index || selectedIndices.includes(index) ? 1 : 0.3;
24863
+ return hoveredIndex === index || selectedIndicesSet.has(index) ? 1 : 0.2;
24872
24864
  }
24873
24865
  if (hasSelection) {
24874
- return selectedIndices.includes(index) ? 1 : 0.3;
24866
+ return selectedIndicesSet.has(index) ? 1 : 0.2;
24875
24867
  }
24876
24868
  return 1;
24877
24869
  };
24870
+ // Helper: Apply cumulative mode hide styling
24871
+ var applyCumulativeHideStyling = function (items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount) {
24872
+ var newRemainingValue = hasRemaining
24873
+ ? effectiveMaxAmount - selectedTotal
24874
+ : 0;
24875
+ var newRemainingPercentage = hasRemaining
24876
+ ? (newRemainingValue / effectiveMaxAmount) * 100
24877
+ : 0;
24878
+ var styledItems = items.map(function (item) {
24879
+ var isSelected = selectedIndicesSet.has(item.originalIndex);
24880
+ return __assign$1(__assign$1({}, item), { percentage: isSelected ? item.percentage : 0 });
24881
+ });
24882
+ return {
24883
+ styledItems: styledItems,
24884
+ updatedRemainingPercentage: newRemainingPercentage,
24885
+ updatedRemainingValue: newRemainingValue,
24886
+ };
24887
+ };
24888
+ // Helper: Apply proportional mode hide styling
24889
+ var applyProportionalHideStyling = function (items, selectedIndicesSet, selectedTotal) {
24890
+ if (selectedTotal === 0) {
24891
+ return {
24892
+ styledItems: items.map(function (item) { return (__assign$1(__assign$1({}, item), { percentage: 0 })); }),
24893
+ updatedRemainingPercentage: 0,
24894
+ updatedRemainingValue: 0,
24895
+ };
24896
+ }
24897
+ var styledItems = items.map(function (item) {
24898
+ var isSelected = selectedIndicesSet.has(item.originalIndex);
24899
+ var newPercentage = isSelected ? (item.value / selectedTotal) * 100 : 0;
24900
+ return __assign$1(__assign$1({}, item), { percentage: newPercentage });
24901
+ });
24902
+ return {
24903
+ styledItems: styledItems,
24904
+ updatedRemainingPercentage: 0,
24905
+ updatedRemainingValue: 0,
24906
+ };
24907
+ };
24908
+ // Helper: Apply selection styling and recalculate percentages for hide mode
24909
+ var applySelectionStyling = function (items, selectedIndicesSet, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, baseRemainingPercentage, baseRemainingValue) {
24910
+ var hasSelection = selectedIndicesSet.size > 0;
24911
+ // If no selection or dim mode, return items as-is with base remaining values
24912
+ if (selectionBehavior !== "hide" || !hasSelection) {
24913
+ return {
24914
+ styledItems: items,
24915
+ updatedRemainingPercentage: baseRemainingPercentage,
24916
+ updatedRemainingValue: baseRemainingValue,
24917
+ };
24918
+ }
24919
+ // Calculate total of selected items
24920
+ var selectedTotal = items
24921
+ .filter(function (item) { return selectedIndicesSet.has(item.originalIndex); })
24922
+ .reduce(function (sum, item) { return sum + item.value; }, 0);
24923
+ // Apply mode-specific styling
24924
+ return mode === "cumulative"
24925
+ ? applyCumulativeHideStyling(items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount)
24926
+ : applyProportionalHideStyling(items, selectedIndicesSet, selectedTotal);
24927
+ };
24878
24928
  // Helper: Build legend items from data with optional remaining segment
24879
24929
  var buildLegendItems = function (dataWithPercentages, options) {
24880
- var _a, _b, _c, _d;
24881
- var mode = options.mode, showLegendValues = options.showLegendValues, unit = options.unit, hasRemaining = options.hasRemaining, showLegend = options.showLegend, remainingValue = options.remainingValue, remainingLabel = options.remainingLabel, remainingUnit = options.remainingUnit, data = options.data, theme = options.theme;
24930
+ var _a, _b, _c;
24931
+ var showLegendValues = options.showLegendValues, legendValueFormat = options.legendValueFormat, unit = options.unit, hasRemaining = options.hasRemaining, showLegend = options.showLegend, remainingValue = options.remainingValue, remainingPercentage = options.remainingPercentage, remainingLabel = options.remainingLabel, remainingUnit = options.remainingUnit, theme = options.theme;
24882
24932
  var legendItems = dataWithPercentages.map(function (item) { return ({
24883
24933
  name: item.name,
24884
- value: calculateLegendValue(item, mode, showLegendValues, unit),
24934
+ value: calculateLegendValue(item, showLegendValues, legendValueFormat, unit),
24885
24935
  color: item.color,
24886
24936
  disabled: item.disabled,
24887
24937
  }); });
24888
24938
  // Add remaining item to legend if applicable
24889
24939
  if (hasRemaining && showLegend) {
24890
- var effectiveRemainingUnit = remainingUnit || ((_a = data[0]) === null || _a === void 0 ? void 0 : _a.unit) || unit || "";
24891
- var remainingValueDisplay = showLegendValues
24892
- ? effectiveRemainingUnit
24940
+ var remainingValueDisplay = void 0;
24941
+ if (!showLegendValues) {
24942
+ remainingValueDisplay = undefined;
24943
+ }
24944
+ else if (legendValueFormat === "percentage") {
24945
+ remainingValueDisplay = "".concat(Math.round(remainingPercentage), "%");
24946
+ }
24947
+ else {
24948
+ // Count format
24949
+ // Use remainingUnit or global unit (never a custom unit from data items)
24950
+ var effectiveRemainingUnit = remainingUnit || unit || "";
24951
+ remainingValueDisplay = effectiveRemainingUnit
24893
24952
  ? "".concat(remainingValue, " ").concat(effectiveRemainingUnit)
24894
- : "".concat(remainingValue)
24895
- : undefined;
24953
+ : "".concat(remainingValue);
24954
+ }
24896
24955
  legendItems.push({
24897
24956
  name: remainingLabel,
24898
24957
  value: remainingValueDisplay,
24899
- color: (_d = (_c = (_b = theme === null || theme === void 0 ? void 0 : theme.palette) === null || _b === void 0 ? void 0 : _b.sds) === null || _c === void 0 ? void 0 : _c.base) === null || _d === void 0 ? void 0 : _d.backgroundTertiary,
24958
+ color: (_c = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.palette) === null || _a === void 0 ? void 0 : _a.sds) === null || _b === void 0 ? void 0 : _b.base) === null || _c === void 0 ? void 0 : _c.backgroundTertiary,
24900
24959
  disabled: true,
24901
24960
  });
24902
24961
  }
@@ -24904,8 +24963,10 @@ var buildLegendItems = function (dataWithPercentages, options) {
24904
24963
  };
24905
24964
  // Helper: Render a bar segment with optional tooltip
24906
24965
  var renderBarSegment = function (item, index, options) {
24907
- var isFirst = options.isFirst, isLast = options.isLast, barHeight = options.barHeight, hoveredIndex = options.hoveredIndex, selectedIndices = options.selectedIndices, onMouseEnter = options.onMouseEnter, onMouseLeave = options.onMouseLeave, onClick = options.onClick;
24908
- var barSegment = (jsxRuntime.jsx(BarSegment, { color: item.color, percentage: item.percentage, height: barHeight, isFirst: isFirst, isLast: isLast, opacity: getSegmentOpacity(item.originalIndex, hoveredIndex, selectedIndices), onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onClick: onClick }));
24966
+ var barHeight = options.barHeight, hoveredIndex = options.hoveredIndex, selectedIndicesSet = options.selectedIndicesSet, selectionBehavior = options.selectionBehavior, isLast = options.isLast, onMouseEnter = options.onMouseEnter, onMouseLeave = options.onMouseLeave, onClick = options.onClick;
24967
+ // Mark hidden segments with data-hide attribute when in hide mode
24968
+ var isHidden = selectionBehavior === "hide" && item.percentage === 0;
24969
+ var barSegment = (jsxRuntime.jsx(BarSegment, { color: item.color, percentage: item.percentage, height: barHeight, opacity: getSegmentOpacity(item.originalIndex, hoveredIndex, selectedIndicesSet, selectionBehavior), isLast: isLast, "data-hide": isHidden, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onClick: onClick }));
24909
24970
  return item.tooltip !== undefined ? (jsxRuntime.jsx(Tooltip, { title: null, componentSlot: jsxRuntime.jsx(TooltipTableContent, __assign$1({}, item.tooltip)), placement: "top", hasInvertedStyle: false, open: hoveredIndex === item.originalIndex, disableInteractive: true, PopperProps: {
24910
24971
  disablePortal: false,
24911
24972
  modifiers: [
@@ -24920,18 +24981,20 @@ var renderBarSegment = function (item, index, options) {
24920
24981
  };
24921
24982
  var StackedBarChart = function (props) {
24922
24983
  var _a, _b, _c;
24923
- var title = props.title, badge = props.badge, _d = props.hideBadge, hideBadge = _d === void 0 ? false : _d, data = props.data, _e = props.width, width = _e === void 0 ? "100%" : _e, _f = props.barHeight, barHeight = _f === void 0 ? 16 : _f, _g = props.showLegend, showLegend = _g === void 0 ? true : _g, _h = props.showLegendValues, showLegendValues = _h === void 0 ? true : _h, _j = props.selectedIndices, selectedIndices = _j === void 0 ? [] : _j, onSelectionChange = props.onSelectionChange, _k = props.mode, mode = _k === void 0 ? "percentage" : _k, maxAmount = props.maxAmount, _l = props.remainingLabel, remainingLabel = _l === void 0 ? "Remaining" : _l, remainingUnit = props.remainingUnit, unit = props.unit, colorGeneratorOptions = props.colorGeneratorOptions, rest = __rest(props, ["title", "badge", "hideBadge", "data", "width", "barHeight", "showLegend", "showLegendValues", "selectedIndices", "onSelectionChange", "mode", "maxAmount", "remainingLabel", "remainingUnit", "unit", "colorGeneratorOptions"]);
24984
+ var title = props.title, badge = props.badge, _d = props.hideBadge, hideBadge = _d === void 0 ? false : _d, data = props.data, _e = props.width, width = _e === void 0 ? "100%" : _e, _f = props.barHeight, barHeightProp = _f === void 0 ? 16 : _f, _g = props.showLegend, showLegend = _g === void 0 ? true : _g, _h = props.showLegendValues, showLegendValues = _h === void 0 ? true : _h, _j = props.legendValueFormat, legendValueFormat = _j === void 0 ? "percentage" : _j, _k = props.selectedIndices, selectedIndices = _k === void 0 ? [] : _k, onSelectionChange = props.onSelectionChange, _l = props.selectionBehavior, selectionBehavior = _l === void 0 ? "dim" : _l, _m = props.mode, mode = _m === void 0 ? "proportional" : _m, maxAmount = props.maxAmount, _o = props.remainingLabel, remainingLabel = _o === void 0 ? "Remaining" : _o, remainingUnit = props.remainingUnit, unit = props.unit, colorGeneratorOptions = props.colorGeneratorOptions, rest = __rest(props, ["title", "badge", "hideBadge", "data", "width", "barHeight", "showLegend", "showLegendValues", "legendValueFormat", "selectedIndices", "onSelectionChange", "selectionBehavior", "mode", "maxAmount", "remainingLabel", "remainingUnit", "unit", "colorGeneratorOptions"]);
24985
+ // Ensure barHeight is at least 1px
24986
+ var barHeight = Math.max(1, barHeightProp);
24924
24987
  var theme = material.useTheme();
24925
- var _m = React.useState(null), hoveredIndex = _m[0], setHoveredIndex = _m[1];
24926
- var _o = React.useState([]), animatedItems = _o[0], setAnimatedItems = _o[1];
24927
- var _p = React.useState(new Map()), autoColorMap = _p[0], setAutoColorMap = _p[1];
24928
- var _q = React.useState(0), animatedRemainingPercentage = _q[0], setAnimatedRemainingPercentage = _q[1];
24988
+ var _p = React.useState(null), hoveredIndex = _p[0], setHoveredIndex = _p[1];
24989
+ var _q = React.useState([]), animatedItems = _q[0], setAnimatedItems = _q[1];
24990
+ var _r = React.useState(new Map()), autoColorMap = _r[0], setAutoColorMap = _r[1];
24991
+ var _s = React.useState(0), animatedRemainingPercentage = _s[0], setAnimatedRemainingPercentage = _s[1];
24929
24992
  var previousProcessedRef = React.useRef([]);
24930
24993
  var previousDataKeysRef = React.useRef(new Set());
24931
24994
  var previousColorOptionsRef = React.useRef(null);
24932
24995
  var previousRemainingPercentageRef = React.useRef(0);
24933
- // Helper to animate items when removals occur
24934
- var animateItemRemoval = function (previousItems, nextItems, currentRemainingPercentage, newRemainingPercentage) {
24996
+ // Animate items when removals occur
24997
+ var animateItemRemoval = React.useCallback(function (previousItems, nextItems, currentRemainingPercentage, newRemainingPercentage) {
24935
24998
  if (typeof window === "undefined") {
24936
24999
  setAnimatedItems(nextItems);
24937
25000
  previousProcessedRef.current = nextItems;
@@ -24990,9 +25053,9 @@ var StackedBarChart = function (props) {
24990
25053
  frameIds.forEach(function (id) { return window.cancelAnimationFrame(id); });
24991
25054
  window.clearTimeout(timeoutId);
24992
25055
  };
24993
- };
25056
+ }, []);
24994
25057
  // Detect dark mode from theme
24995
- var isDarkMode = getMode({ theme: theme }) === "dark";
25058
+ var isDarkMode = React.useMemo(function () { return getMode({ theme: theme }) === "dark"; }, [theme]);
24996
25059
  // Helper to generate color map for auto items
24997
25060
  var generateColorMap = React.useCallback(function (items) {
24998
25061
  var palette = generateDiscreteColors(items.length, __assign$1(__assign$1({}, (colorGeneratorOptions || {})), { isDarkMode: isDarkMode }));
@@ -25012,8 +25075,7 @@ var StackedBarChart = function (props) {
25012
25075
  // Check if color generation options changed
25013
25076
  var colorOptionsChanged = !previousColorOptions ||
25014
25077
  previousColorOptions.isDarkMode !== isDarkMode ||
25015
- JSON.stringify(previousColorOptions.options) !==
25016
- JSON.stringify(colorGeneratorOptions);
25078
+ previousColorOptions.options !== colorGeneratorOptions;
25017
25079
  // Update refs
25018
25080
  previousDataKeysRef.current = currentDataKeys;
25019
25081
  previousColorOptionsRef.current = {
@@ -25046,36 +25108,38 @@ var StackedBarChart = function (props) {
25046
25108
  setAutoColorMap(updatedMap);
25047
25109
  }
25048
25110
  }
25049
- }, [
25050
- data,
25051
- colorGeneratorOptions,
25052
- isDarkMode,
25053
- theme,
25054
- autoColorMap,
25055
- generateColorMap,
25056
- ]);
25057
- // Calculate default badge value
25058
- var defaultBadge = calculateBadgeText(data.length, selectedIndices.length);
25059
- var displayBadge = badge ? badge : defaultBadge;
25060
- // Calculate total value from data
25061
- var dataTotal = data.reduce(function (sum, item) { return sum + item.value; }, 0);
25062
- // Determine the effective max amount
25063
- var effectiveMaxAmount = mode === "amount" && maxAmount ? maxAmount : dataTotal;
25064
- // Calculate if there's a remaining segment
25065
- var hasRemaining = mode === "amount" && dataTotal < effectiveMaxAmount;
25066
- var remainingValue = hasRemaining ? effectiveMaxAmount - dataTotal : 0;
25067
- var remainingPercentage = hasRemaining
25068
- ? (remainingValue / effectiveMaxAmount) * 100
25069
- : 0;
25111
+ // eslint-disable-next-line react-hooks/exhaustive-deps
25112
+ }, [data, colorGeneratorOptions, isDarkMode, theme, generateColorMap]);
25113
+ // Calculate display badge value
25114
+ var displayBadge = badge !== null && badge !== void 0 ? badge : calculateBadgeText(data.length, selectedIndices.length);
25115
+ // Calculate totals and remaining segments for cumulative mode
25116
+ var _t = React.useMemo(function () {
25117
+ var total = data.reduce(function (sum, item) { return sum + item.value; }, 0);
25118
+ var maxAmountValue = mode === "cumulative" && maxAmount ? maxAmount : total;
25119
+ var hasRemainingSegment = mode === "cumulative" && total < maxAmountValue;
25120
+ var remaining = hasRemainingSegment ? maxAmountValue - total : 0;
25121
+ var remainingPct = hasRemainingSegment
25122
+ ? (remaining / maxAmountValue) * 100
25123
+ : 0;
25124
+ return {
25125
+ effectiveMaxAmount: maxAmountValue,
25126
+ hasRemaining: hasRemainingSegment,
25127
+ remainingValue: remaining,
25128
+ remainingPercentage: remainingPct,
25129
+ };
25130
+ }, [data, mode, maxAmount]), effectiveMaxAmount = _t.effectiveMaxAmount, hasRemaining = _t.hasRemaining, remainingValue = _t.remainingValue, remainingPercentage = _t.remainingPercentage;
25070
25131
  // Calculate percentages and assign colors for each segment
25071
25132
  // default to ornament secondary color if no color is provided
25072
- var dataWithPercentages = data.map(function (item, index) {
25073
- var _a, _b, _c;
25074
- var resolvedColor = item.color ||
25075
- autoColorMap.get(item.name) ||
25076
- ((_c = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.palette) === null || _a === void 0 ? void 0 : _a.sds) === null || _b === void 0 ? void 0 : _b.base) === null || _c === void 0 ? void 0 : _c.ornamentSecondary);
25077
- return __assign$1(__assign$1({}, item), { color: resolvedColor, percentage: effectiveMaxAmount > 0 ? (item.value / effectiveMaxAmount) * 100 : 0, key: item.name, originalIndex: index });
25078
- });
25133
+ // Memoize to prevent unnecessary recalculations and re-renders
25134
+ var dataWithPercentages = React.useMemo(function () {
25135
+ return data.map(function (item, index) {
25136
+ var _a, _b, _c;
25137
+ var resolvedColor = item.color ||
25138
+ autoColorMap.get(item.name) ||
25139
+ ((_c = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.palette) === null || _a === void 0 ? void 0 : _a.sds) === null || _b === void 0 ? void 0 : _b.base) === null || _c === void 0 ? void 0 : _c.ornamentSecondary);
25140
+ return __assign$1(__assign$1({}, item), { color: resolvedColor, percentage: effectiveMaxAmount > 0 ? (item.value / effectiveMaxAmount) * 100 : 0, key: item.name, originalIndex: index });
25141
+ });
25142
+ }, [data, effectiveMaxAmount, autoColorMap, theme]);
25079
25143
  // Handle data changes and animate exits
25080
25144
  React.useEffect(function () {
25081
25145
  var previousProcessed = previousProcessedRef.current;
@@ -25095,53 +25159,104 @@ var StackedBarChart = function (props) {
25095
25159
  previousProcessedRef.current = dataWithPercentages;
25096
25160
  setAnimatedRemainingPercentage(remainingPercentage);
25097
25161
  previousRemainingPercentageRef.current = remainingPercentage;
25098
- // eslint-disable-next-line react-hooks/exhaustive-deps
25099
- }, [data, effectiveMaxAmount, autoColorMap]);
25100
- // Convert data to legend items format
25101
- var legendItems = buildLegendItems(dataWithPercentages, {
25102
- mode: mode,
25103
- showLegendValues: showLegendValues,
25104
- unit: unit,
25105
- hasRemaining: hasRemaining,
25106
- showLegend: showLegend,
25107
- remainingValue: remainingValue,
25108
- remainingLabel: remainingLabel,
25109
- remainingUnit: remainingUnit,
25110
- data: data,
25111
- theme: theme,
25112
- });
25113
- // Handle legend item hover
25114
- var handleLegendItemHover = function (item, index) {
25115
- setHoveredIndex(index);
25116
- };
25117
- // Handle legend item leave
25118
- var handleLegendItemLeave = function () {
25119
- setHoveredIndex(null);
25120
- };
25121
- // Handle segment click - toggle selection
25122
- var handleSegmentClick = function (index) {
25162
+ }, [dataWithPercentages, remainingPercentage, animateItemRemoval]);
25163
+ // Create a Set for O(1) lookups of selected indices
25164
+ var selectedIndicesSet = React.useMemo(function () { return new Set(selectedIndices); }, [selectedIndices]);
25165
+ // Apply selection styling for hide mode
25166
+ // This modifies percentages but keeps all items (including hidden ones at 0%)
25167
+ // so animations continue to work smoothly
25168
+ var _u = React.useMemo(function () {
25169
+ return applySelectionStyling(animatedItems, selectedIndicesSet, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, animatedRemainingPercentage, remainingValue);
25170
+ }, [
25171
+ animatedItems,
25172
+ selectedIndicesSet,
25173
+ selectionBehavior,
25174
+ mode,
25175
+ hasRemaining,
25176
+ effectiveMaxAmount,
25177
+ animatedRemainingPercentage,
25178
+ remainingValue,
25179
+ ]), styledItems = _u.styledItems, updatedRemainingPercentage = _u.updatedRemainingPercentage, updatedRemainingValue = _u.updatedRemainingValue;
25180
+ // Calculate display values for hide mode
25181
+ var isHideMode = selectionBehavior === "hide" && selectedIndices.length > 0;
25182
+ var displayRemainingPercentage = isHideMode
25183
+ ? updatedRemainingPercentage
25184
+ : animatedRemainingPercentage;
25185
+ var displayRemainingValue = isHideMode
25186
+ ? updatedRemainingValue
25187
+ : remainingValue;
25188
+ // Convert data to legend items format (always use full data, not filtered)
25189
+ // Use updated remaining values when in hide mode with selections
25190
+ var legendItems = React.useMemo(function () {
25191
+ return buildLegendItems(dataWithPercentages, {
25192
+ showLegendValues: showLegendValues,
25193
+ legendValueFormat: legendValueFormat,
25194
+ unit: unit,
25195
+ hasRemaining: hasRemaining,
25196
+ showLegend: showLegend,
25197
+ remainingValue: displayRemainingValue,
25198
+ remainingPercentage: displayRemainingPercentage,
25199
+ remainingLabel: remainingLabel,
25200
+ remainingUnit: remainingUnit,
25201
+ theme: theme,
25202
+ });
25203
+ }, [
25204
+ dataWithPercentages,
25205
+ showLegendValues,
25206
+ legendValueFormat,
25207
+ unit,
25208
+ hasRemaining,
25209
+ showLegend,
25210
+ displayRemainingValue,
25211
+ displayRemainingPercentage,
25212
+ remainingLabel,
25213
+ remainingUnit,
25214
+ theme,
25215
+ ]);
25216
+ // Handle legend item hover and leave
25217
+ var handleLegendItemHover = React.useCallback(function (_item, index) { return setHoveredIndex(index); }, []);
25218
+ var handleLegendItemLeave = React.useCallback(function () { return setHoveredIndex(null); }, []);
25219
+ // Handle selection change - reusable logic for both segment and legend
25220
+ var handleSelectionChange = React.useCallback(function (newSelectedIndices) {
25123
25221
  if (!onSelectionChange)
25124
25222
  return;
25125
- var isSelected = selectedIndices.includes(index);
25126
- var newSelectedIndices;
25127
- if (isSelected) {
25128
- newSelectedIndices = selectedIndices.filter(function (i) { return i !== index; });
25129
- }
25130
- else {
25131
- newSelectedIndices = __spreadArray(__spreadArray([], selectedIndices, true), [index], false);
25132
- }
25133
25223
  var selectedData = data.filter(function (item, itemIndex) {
25134
25224
  return newSelectedIndices.includes(itemIndex);
25135
25225
  });
25136
25226
  onSelectionChange(newSelectedIndices, selectedData);
25137
- };
25138
- var chartContent = (jsxRuntime.jsxs(StyledStackedBarChartWrapper, { children: [jsxRuntime.jsxs(BarContainer, { width: width, barHeight: barHeight, children: [animatedItems.map(function (item, index) {
25227
+ }, [onSelectionChange, data]);
25228
+ // Handle segment click - toggle selection
25229
+ var handleSegmentClick = React.useCallback(function (index) {
25230
+ if (!onSelectionChange)
25231
+ return;
25232
+ var isSelected = selectedIndicesSet.has(index);
25233
+ var newSelectedIndices = isSelected
25234
+ ? selectedIndices.filter(function (i) { return i !== index; })
25235
+ : __spreadArray(__spreadArray([], selectedIndices, true), [index], false);
25236
+ handleSelectionChange(newSelectedIndices);
25237
+ }, [
25238
+ onSelectionChange,
25239
+ selectedIndices,
25240
+ selectedIndicesSet,
25241
+ handleSelectionChange,
25242
+ ]);
25243
+ // Calculate the last visible segment (percentage > 0)
25244
+ var lastVisibleIndex = React.useMemo(function () {
25245
+ for (var i = styledItems.length - 1; i >= 0; i--) {
25246
+ if (styledItems[i].percentage > 0) {
25247
+ return i;
25248
+ }
25249
+ }
25250
+ return -1;
25251
+ }, [styledItems]);
25252
+ var chartContent = (jsxRuntime.jsxs(StyledStackedBarChartWrapper, { children: [jsxRuntime.jsxs(BarContainer, { width: width, barHeight: barHeight, isEmpty: styledItems.length === 0 && !hasRemaining, children: [styledItems.map(function (item, index) {
25253
+ var isLastVisible = index === lastVisibleIndex && !hasRemaining;
25139
25254
  return renderBarSegment(item, index, {
25140
- isFirst: index === 0,
25141
- isLast: index === animatedItems.length - 1 && !hasRemaining,
25142
25255
  barHeight: barHeight,
25143
25256
  hoveredIndex: hoveredIndex,
25144
- selectedIndices: selectedIndices,
25257
+ selectedIndicesSet: selectedIndicesSet,
25258
+ selectionBehavior: selectionBehavior,
25259
+ isLast: isLastVisible,
25145
25260
  onMouseEnter: function () {
25146
25261
  return !item.isExiting && setHoveredIndex(item.originalIndex);
25147
25262
  },
@@ -25150,14 +25265,7 @@ var StackedBarChart = function (props) {
25150
25265
  return !item.isExiting && handleSegmentClick(item.originalIndex);
25151
25266
  },
25152
25267
  });
25153
- }), hasRemaining && (jsxRuntime.jsx(BarSegment, { color: (_c = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.palette) === null || _a === void 0 ? void 0 : _a.sds) === null || _b === void 0 ? void 0 : _b.base) === null || _c === void 0 ? void 0 : _c.backgroundTertiary, percentage: animatedRemainingPercentage, height: barHeight, isFirst: animatedItems.length === 0, isLast: true, opacity: 1, disabled: true }, "remaining"))] }), showLegend && (jsxRuntime.jsx(Legend, { items: legendItems, showValues: showLegendValues, onItemMouseEnter: handleLegendItemHover, onItemMouseLeave: handleLegendItemLeave, selectedIndices: selectedIndices, onSelectionChange: onSelectionChange
25154
- ? function (newSelectedIndices) {
25155
- var selectedData = data.filter(function (item, itemIndex) {
25156
- return newSelectedIndices.includes(itemIndex);
25157
- });
25158
- onSelectionChange(newSelectedIndices, selectedData);
25159
- }
25160
- : undefined, hoveredIndex: hoveredIndex }))] }));
25268
+ }), hasRemaining && (jsxRuntime.jsx(BarSegment, { color: (_c = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.palette) === null || _a === void 0 ? void 0 : _a.sds) === null || _b === void 0 ? void 0 : _b.base) === null || _c === void 0 ? void 0 : _c.backgroundTertiary, percentage: displayRemainingPercentage, height: barHeight, isLast: true, opacity: 1, disabled: true }, "remaining"))] }), showLegend && (jsxRuntime.jsx(Legend, { items: legendItems, showValues: showLegendValues, onItemMouseEnter: handleLegendItemHover, onItemMouseLeave: handleLegendItemLeave, selectedIndices: selectedIndices, onSelectionChange: onSelectionChange ? handleSelectionChange : undefined, hoveredIndex: hoveredIndex }))] }));
25161
25269
  // If no title, render chart and optional legend without wrapper
25162
25270
  if (!title) {
25163
25271
  return (jsxRuntime.jsx(ChartWrapper, __assign$1({ width: width }, rest, { children: chartContent })));