@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.esm.js CHANGED
@@ -1023,7 +1023,7 @@ const SDSLightThemeColors = {
1023
1023
  purple: {
1024
1024
  "100": "#efeafe",
1025
1025
  "200": "#e4dbfc",
1026
- "300": "#cbbaF8",
1026
+ "300": "#cbbaf8",
1027
1027
  "400": "#b296f2",
1028
1028
  "500": "#8b54e2",
1029
1029
  "600": "#6526b5",
@@ -1194,7 +1194,7 @@ const percentToHex = (percentOpacity) => {
1194
1194
  // Convert percentage to 0-255 range
1195
1195
  const alphaValue = Math.round((clampedPercent / 100) * 255);
1196
1196
  // Convert to hex and pad with leading zero if needed
1197
- return alphaValue.toString(16).padStart(2, "0").toUpperCase();
1197
+ return alphaValue.toString(16).padStart(2, "0").toLowerCase();
1198
1198
  };
1199
1199
  /**
1200
1200
  * Adds opacity to a hex color by appending the hex opacity value.
@@ -1873,10 +1873,10 @@ const fontBodyS$1 = fontBody("s", "regular");
1873
1873
  const fontBodyXs$2 = fontBody("xs", "regular");
1874
1874
  const fontBodyXxs$1 = fontBody("xxs", "regular");
1875
1875
  const fontBodyXxxs$1 = fontBody("xxxs", "regular");
1876
- const fontBodyXxxxs = fontBody("xxxxs", "regular");
1877
1876
  const fontBodyMediumL = fontBody("l", "medium");
1878
1877
  const fontBodyMediumS = fontBody("s", "medium");
1879
1878
  const fontBodyMediumXs = fontBody("xs", "medium");
1879
+ const fontBodyMediumXxxs = fontBody("xxxs", "medium");
1880
1880
  const fontBodySemiboldL = fontBody("l", "semibold");
1881
1881
  const fontBodySemiboldM = fontBody("m", "semibold");
1882
1882
  const fontBodySemiboldS = fontBody("s", "semibold");
@@ -10515,7 +10515,7 @@ const LegendItem = styled$2("div") `
10515
10515
  align-items: center;
10516
10516
  cursor: ${disabled ? "not-allowed" : "pointer"} !important;
10517
10517
  transition: background-color 0.2s ease-in-out;
10518
- padding: 0 ${spaces?.xxs}px;
10518
+ padding: ${spaces?.xxxs}px ${spaces?.xxs}px;
10519
10519
  border-radius: ${corners?.s}px;
10520
10520
  background-color: ${isSelected ? semanticColors?.base?.backgroundTertiary : "transparent"};
10521
10521
  pointer-events: ${disabled ? "none" : "auto"};
@@ -10532,8 +10532,8 @@ const LegendIcon = styled$2("div") `
10532
10532
  const spaces = getSpaces(props);
10533
10533
  const corners = getCorners(props);
10534
10534
  return `
10535
- width: 8px;
10536
- height: 8px;
10535
+ width: ${spaces?.m}px;
10536
+ height: ${spaces?.m}px;
10537
10537
  border-radius: ${corners?.s}px;
10538
10538
  background-color: ${color};
10539
10539
  flex-shrink: 0;
@@ -10544,7 +10544,7 @@ const LegendIcon = styled$2("div") `
10544
10544
  }}
10545
10545
  `;
10546
10546
  const LegendLabel = styled$2("span") `
10547
- ${fontBodyXxxxs}
10547
+ ${fontBodyMediumXxxs}
10548
10548
  ${(props) => {
10549
10549
  const semanticColors = getSemanticColors(props);
10550
10550
  return `
@@ -10553,13 +10553,13 @@ const LegendLabel = styled$2("span") `
10553
10553
  }}
10554
10554
  `;
10555
10555
  const LegendValue = styled$2("span") `
10556
- ${fontBodyXxxxs}
10556
+ ${fontBodyMediumXxxs}
10557
10557
  ${(props) => {
10558
10558
  const semanticColors = getSemanticColors(props);
10559
10559
  const spaces = getSpaces(props);
10560
10560
  return `
10561
10561
  color: ${semanticColors?.base?.textSecondary};
10562
- margin-left: ${spaces?.xxxs}px;
10562
+ margin-left: ${spaces?.xxs}px;
10563
10563
  `;
10564
10564
  }}
10565
10565
  `;
@@ -24751,7 +24751,7 @@ Object.assign(chroma, {
24751
24751
  const generateDiscreteColors = (count, options = {}) => {
24752
24752
  if (count === 0)
24753
24753
  return [];
24754
- const { isDarkMode = false, start = 249, rotations = 1, gamma = 0.7, lightness = [0.3, 0.7], correctLightness = true, } = options;
24754
+ const { isDarkMode = false, start = 250, rotations = 0.85, gamma = 0.7, lightness = [0.4, 0.7], correctLightness = true, } = options;
24755
24755
  let scale = chroma
24756
24756
  .cubehelix()
24757
24757
  .start(start)
@@ -24787,33 +24787,22 @@ var StyledBadge = styled$2("div")(templateObject_4 || (templateObject_4 = __make
24787
24787
  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 ");
24788
24788
  });
24789
24789
  var BarContainer = styled$2("div")(templateObject_5 || (templateObject_5 = __makeTemplateObject(["\n ", "\n"], ["\n ", "\n"])), function (props) {
24790
- var width = props.width, barHeight = props.barHeight;
24790
+ var _a;
24791
+ var width = props.width, barHeight = props.barHeight, isEmpty = props.isEmpty;
24791
24792
  var corners = getCorners(props);
24793
+ var semanticColors = getSemanticColors(props);
24792
24794
  var finalWidth = typeof width === "number" ? "".concat(width, "px") : width;
24793
- 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 ");
24795
+ 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 ");
24794
24796
  });
24795
24797
  var BarSegment = styled$2("div")(templateObject_6 || (templateObject_6 = __makeTemplateObject(["\n ", "\n"], ["\n ", "\n"])), function (props) {
24796
- var isFirst = props.isFirst, isLast = props.isLast, color = props.color, height = props.height, percentage = props.percentage, opacity = props.opacity, disabled = props.disabled;
24798
+ var color = props.color, height = props.height, percentage = props.percentage, opacity = props.opacity, disabled = props.disabled, isLast = props.isLast;
24797
24799
  var spaces = getSpaces(props);
24798
24800
  var gap = (spaces === null || spaces === void 0 ? void 0 : spaces.xxxs) || 0;
24799
- var getBorderRadius = function () {
24800
- var corners = getCorners(props);
24801
- var borderRadius = corners === null || corners === void 0 ? void 0 : corners.s;
24802
- if (isFirst && isLast) {
24803
- return "border-radius: ".concat(borderRadius, "px;");
24804
- }
24805
- if (isFirst) {
24806
- return "\n border-top-left-radius: ".concat(borderRadius, "px; \n border-bottom-left-radius: ").concat(borderRadius, "px;\n ");
24807
- }
24808
- if (isLast) {
24809
- return "\n border-top-right-radius: ".concat(borderRadius, "px; \n border-bottom-right-radius: ").concat(borderRadius, "px;\n ");
24810
- }
24811
- return "";
24812
- };
24813
- // Add gap as margin-right, except for last item
24801
+ // Add gap as margin-right for spacing between segments
24814
24802
  // When percentage is 0, margin also becomes 0 (animates away with the segment)
24815
- var marginRight = !isLast && percentage > 0 ? "".concat(gap, "px") : 0;
24816
- 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 ");
24803
+ // Last visible segment also gets no margin
24804
+ var marginRight = percentage > 0 && !isLast ? "".concat(gap, "px") : 0;
24805
+ 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 ");
24817
24806
  });
24818
24807
  var StyledStackedBarChartWrapper = styled$2("div")(templateObject_7 || (templateObject_7 = __makeTemplateObject(["\n ", "\n"], ["\n ", "\n"])), function (props) {
24819
24808
  var spaces = getSpaces(props);
@@ -24823,56 +24812,126 @@ var templateObject_1, templateObject_2, templateObject_3, templateObject_4, temp
24823
24812
 
24824
24813
  // Helper: Calculate badge display text based on selection state
24825
24814
  var calculateBadgeText = function (totalItems, selectedCount) {
24826
- if (selectedCount === 0)
24815
+ if (selectedCount === 0 || selectedCount === totalItems)
24827
24816
  return "".concat(totalItems);
24828
- if (selectedCount === totalItems)
24829
- return "All";
24830
24817
  return "".concat(selectedCount, " of ").concat(totalItems);
24831
24818
  };
24832
24819
  // Helper: Calculate legend value display
24833
- var calculateLegendValue = function (item, mode, showLegendValues, globalUnit) {
24820
+ var calculateLegendValue = function (item, showLegendValues, legendValueFormat, globalUnit) {
24834
24821
  if (!showLegendValues)
24835
24822
  return undefined;
24836
- if (mode === "percentage") {
24823
+ if (legendValueFormat === "percentage") {
24837
24824
  return "".concat(Math.round(item.percentage), "%");
24838
24825
  }
24839
- // Amount mode: show value with optional unit
24826
+ // Count format: show value with optional unit
24840
24827
  var effectiveUnit = item.unit || globalUnit;
24841
24828
  return effectiveUnit ? "".concat(item.value, " ").concat(effectiveUnit) : "".concat(item.value);
24842
24829
  };
24843
24830
  // Helper: Get segment opacity based on hover and selection state
24844
- var getSegmentOpacity = function (index, hoveredIndex, selectedIndices) {
24845
- var hasSelection = selectedIndices.length > 0;
24831
+ var getSegmentOpacity = function (index, hoveredIndex, selectedIndicesSet, selectionBehavior) {
24832
+ var hasSelection = selectedIndicesSet.size > 0;
24833
+ // Hide mode doesn't use opacity - segments animate to 0% width instead
24834
+ if (selectionBehavior === "hide") {
24835
+ return 1;
24836
+ }
24837
+ // Dim mode
24846
24838
  if (hoveredIndex !== null) {
24847
- return hoveredIndex === index || selectedIndices.includes(index) ? 1 : 0.3;
24839
+ return hoveredIndex === index || selectedIndicesSet.has(index) ? 1 : 0.2;
24848
24840
  }
24849
24841
  if (hasSelection) {
24850
- return selectedIndices.includes(index) ? 1 : 0.3;
24842
+ return selectedIndicesSet.has(index) ? 1 : 0.2;
24851
24843
  }
24852
24844
  return 1;
24853
24845
  };
24846
+ // Helper: Apply cumulative mode hide styling
24847
+ var applyCumulativeHideStyling = function (items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount) {
24848
+ var newRemainingValue = hasRemaining
24849
+ ? effectiveMaxAmount - selectedTotal
24850
+ : 0;
24851
+ var newRemainingPercentage = hasRemaining
24852
+ ? (newRemainingValue / effectiveMaxAmount) * 100
24853
+ : 0;
24854
+ var styledItems = items.map(function (item) {
24855
+ var isSelected = selectedIndicesSet.has(item.originalIndex);
24856
+ return __assign$1(__assign$1({}, item), { percentage: isSelected ? item.percentage : 0 });
24857
+ });
24858
+ return {
24859
+ styledItems: styledItems,
24860
+ updatedRemainingPercentage: newRemainingPercentage,
24861
+ updatedRemainingValue: newRemainingValue,
24862
+ };
24863
+ };
24864
+ // Helper: Apply proportional mode hide styling
24865
+ var applyProportionalHideStyling = function (items, selectedIndicesSet, selectedTotal) {
24866
+ if (selectedTotal === 0) {
24867
+ return {
24868
+ styledItems: items.map(function (item) { return (__assign$1(__assign$1({}, item), { percentage: 0 })); }),
24869
+ updatedRemainingPercentage: 0,
24870
+ updatedRemainingValue: 0,
24871
+ };
24872
+ }
24873
+ var styledItems = items.map(function (item) {
24874
+ var isSelected = selectedIndicesSet.has(item.originalIndex);
24875
+ var newPercentage = isSelected ? (item.value / selectedTotal) * 100 : 0;
24876
+ return __assign$1(__assign$1({}, item), { percentage: newPercentage });
24877
+ });
24878
+ return {
24879
+ styledItems: styledItems,
24880
+ updatedRemainingPercentage: 0,
24881
+ updatedRemainingValue: 0,
24882
+ };
24883
+ };
24884
+ // Helper: Apply selection styling and recalculate percentages for hide mode
24885
+ var applySelectionStyling = function (items, selectedIndicesSet, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, baseRemainingPercentage, baseRemainingValue) {
24886
+ var hasSelection = selectedIndicesSet.size > 0;
24887
+ // If no selection or dim mode, return items as-is with base remaining values
24888
+ if (selectionBehavior !== "hide" || !hasSelection) {
24889
+ return {
24890
+ styledItems: items,
24891
+ updatedRemainingPercentage: baseRemainingPercentage,
24892
+ updatedRemainingValue: baseRemainingValue,
24893
+ };
24894
+ }
24895
+ // Calculate total of selected items
24896
+ var selectedTotal = items
24897
+ .filter(function (item) { return selectedIndicesSet.has(item.originalIndex); })
24898
+ .reduce(function (sum, item) { return sum + item.value; }, 0);
24899
+ // Apply mode-specific styling
24900
+ return mode === "cumulative"
24901
+ ? applyCumulativeHideStyling(items, selectedIndicesSet, selectedTotal, hasRemaining, effectiveMaxAmount)
24902
+ : applyProportionalHideStyling(items, selectedIndicesSet, selectedTotal);
24903
+ };
24854
24904
  // Helper: Build legend items from data with optional remaining segment
24855
24905
  var buildLegendItems = function (dataWithPercentages, options) {
24856
- var _a, _b, _c, _d;
24857
- 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;
24906
+ var _a, _b, _c;
24907
+ 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;
24858
24908
  var legendItems = dataWithPercentages.map(function (item) { return ({
24859
24909
  name: item.name,
24860
- value: calculateLegendValue(item, mode, showLegendValues, unit),
24910
+ value: calculateLegendValue(item, showLegendValues, legendValueFormat, unit),
24861
24911
  color: item.color,
24862
24912
  disabled: item.disabled,
24863
24913
  }); });
24864
24914
  // Add remaining item to legend if applicable
24865
24915
  if (hasRemaining && showLegend) {
24866
- var effectiveRemainingUnit = remainingUnit || ((_a = data[0]) === null || _a === void 0 ? void 0 : _a.unit) || unit || "";
24867
- var remainingValueDisplay = showLegendValues
24868
- ? effectiveRemainingUnit
24916
+ var remainingValueDisplay = void 0;
24917
+ if (!showLegendValues) {
24918
+ remainingValueDisplay = undefined;
24919
+ }
24920
+ else if (legendValueFormat === "percentage") {
24921
+ remainingValueDisplay = "".concat(Math.round(remainingPercentage), "%");
24922
+ }
24923
+ else {
24924
+ // Count format
24925
+ // Use remainingUnit or global unit (never a custom unit from data items)
24926
+ var effectiveRemainingUnit = remainingUnit || unit || "";
24927
+ remainingValueDisplay = effectiveRemainingUnit
24869
24928
  ? "".concat(remainingValue, " ").concat(effectiveRemainingUnit)
24870
- : "".concat(remainingValue)
24871
- : undefined;
24929
+ : "".concat(remainingValue);
24930
+ }
24872
24931
  legendItems.push({
24873
24932
  name: remainingLabel,
24874
24933
  value: remainingValueDisplay,
24875
- 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,
24934
+ 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,
24876
24935
  disabled: true,
24877
24936
  });
24878
24937
  }
@@ -24880,8 +24939,10 @@ var buildLegendItems = function (dataWithPercentages, options) {
24880
24939
  };
24881
24940
  // Helper: Render a bar segment with optional tooltip
24882
24941
  var renderBarSegment = function (item, index, options) {
24883
- 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;
24884
- var barSegment = (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 }));
24942
+ 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;
24943
+ // Mark hidden segments with data-hide attribute when in hide mode
24944
+ var isHidden = selectionBehavior === "hide" && item.percentage === 0;
24945
+ var barSegment = (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 }));
24885
24946
  return item.tooltip !== undefined ? (jsx(Tooltip, { title: null, componentSlot: jsx(TooltipTableContent, __assign$1({}, item.tooltip)), placement: "top", hasInvertedStyle: false, open: hoveredIndex === item.originalIndex, disableInteractive: true, PopperProps: {
24886
24947
  disablePortal: false,
24887
24948
  modifiers: [
@@ -24896,18 +24957,20 @@ var renderBarSegment = function (item, index, options) {
24896
24957
  };
24897
24958
  var StackedBarChart = function (props) {
24898
24959
  var _a, _b, _c;
24899
- 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"]);
24960
+ 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"]);
24961
+ // Ensure barHeight is at least 1px
24962
+ var barHeight = Math.max(1, barHeightProp);
24900
24963
  var theme = useTheme$4();
24901
- var _m = useState(null), hoveredIndex = _m[0], setHoveredIndex = _m[1];
24902
- var _o = useState([]), animatedItems = _o[0], setAnimatedItems = _o[1];
24903
- var _p = useState(new Map()), autoColorMap = _p[0], setAutoColorMap = _p[1];
24904
- var _q = useState(0), animatedRemainingPercentage = _q[0], setAnimatedRemainingPercentage = _q[1];
24964
+ var _p = useState(null), hoveredIndex = _p[0], setHoveredIndex = _p[1];
24965
+ var _q = useState([]), animatedItems = _q[0], setAnimatedItems = _q[1];
24966
+ var _r = useState(new Map()), autoColorMap = _r[0], setAutoColorMap = _r[1];
24967
+ var _s = useState(0), animatedRemainingPercentage = _s[0], setAnimatedRemainingPercentage = _s[1];
24905
24968
  var previousProcessedRef = useRef([]);
24906
24969
  var previousDataKeysRef = useRef(new Set());
24907
24970
  var previousColorOptionsRef = useRef(null);
24908
24971
  var previousRemainingPercentageRef = useRef(0);
24909
- // Helper to animate items when removals occur
24910
- var animateItemRemoval = function (previousItems, nextItems, currentRemainingPercentage, newRemainingPercentage) {
24972
+ // Animate items when removals occur
24973
+ var animateItemRemoval = useCallback(function (previousItems, nextItems, currentRemainingPercentage, newRemainingPercentage) {
24911
24974
  if (typeof window === "undefined") {
24912
24975
  setAnimatedItems(nextItems);
24913
24976
  previousProcessedRef.current = nextItems;
@@ -24966,9 +25029,9 @@ var StackedBarChart = function (props) {
24966
25029
  frameIds.forEach(function (id) { return window.cancelAnimationFrame(id); });
24967
25030
  window.clearTimeout(timeoutId);
24968
25031
  };
24969
- };
25032
+ }, []);
24970
25033
  // Detect dark mode from theme
24971
- var isDarkMode = getMode({ theme: theme }) === "dark";
25034
+ var isDarkMode = useMemo(function () { return getMode({ theme: theme }) === "dark"; }, [theme]);
24972
25035
  // Helper to generate color map for auto items
24973
25036
  var generateColorMap = useCallback(function (items) {
24974
25037
  var palette = generateDiscreteColors(items.length, __assign$1(__assign$1({}, (colorGeneratorOptions || {})), { isDarkMode: isDarkMode }));
@@ -24988,8 +25051,7 @@ var StackedBarChart = function (props) {
24988
25051
  // Check if color generation options changed
24989
25052
  var colorOptionsChanged = !previousColorOptions ||
24990
25053
  previousColorOptions.isDarkMode !== isDarkMode ||
24991
- JSON.stringify(previousColorOptions.options) !==
24992
- JSON.stringify(colorGeneratorOptions);
25054
+ previousColorOptions.options !== colorGeneratorOptions;
24993
25055
  // Update refs
24994
25056
  previousDataKeysRef.current = currentDataKeys;
24995
25057
  previousColorOptionsRef.current = {
@@ -25022,36 +25084,38 @@ var StackedBarChart = function (props) {
25022
25084
  setAutoColorMap(updatedMap);
25023
25085
  }
25024
25086
  }
25025
- }, [
25026
- data,
25027
- colorGeneratorOptions,
25028
- isDarkMode,
25029
- theme,
25030
- autoColorMap,
25031
- generateColorMap,
25032
- ]);
25033
- // Calculate default badge value
25034
- var defaultBadge = calculateBadgeText(data.length, selectedIndices.length);
25035
- var displayBadge = badge ? badge : defaultBadge;
25036
- // Calculate total value from data
25037
- var dataTotal = data.reduce(function (sum, item) { return sum + item.value; }, 0);
25038
- // Determine the effective max amount
25039
- var effectiveMaxAmount = mode === "amount" && maxAmount ? maxAmount : dataTotal;
25040
- // Calculate if there's a remaining segment
25041
- var hasRemaining = mode === "amount" && dataTotal < effectiveMaxAmount;
25042
- var remainingValue = hasRemaining ? effectiveMaxAmount - dataTotal : 0;
25043
- var remainingPercentage = hasRemaining
25044
- ? (remainingValue / effectiveMaxAmount) * 100
25045
- : 0;
25087
+ // eslint-disable-next-line react-hooks/exhaustive-deps
25088
+ }, [data, colorGeneratorOptions, isDarkMode, theme, generateColorMap]);
25089
+ // Calculate display badge value
25090
+ var displayBadge = badge !== null && badge !== void 0 ? badge : calculateBadgeText(data.length, selectedIndices.length);
25091
+ // Calculate totals and remaining segments for cumulative mode
25092
+ var _t = useMemo(function () {
25093
+ var total = data.reduce(function (sum, item) { return sum + item.value; }, 0);
25094
+ var maxAmountValue = mode === "cumulative" && maxAmount ? maxAmount : total;
25095
+ var hasRemainingSegment = mode === "cumulative" && total < maxAmountValue;
25096
+ var remaining = hasRemainingSegment ? maxAmountValue - total : 0;
25097
+ var remainingPct = hasRemainingSegment
25098
+ ? (remaining / maxAmountValue) * 100
25099
+ : 0;
25100
+ return {
25101
+ effectiveMaxAmount: maxAmountValue,
25102
+ hasRemaining: hasRemainingSegment,
25103
+ remainingValue: remaining,
25104
+ remainingPercentage: remainingPct,
25105
+ };
25106
+ }, [data, mode, maxAmount]), effectiveMaxAmount = _t.effectiveMaxAmount, hasRemaining = _t.hasRemaining, remainingValue = _t.remainingValue, remainingPercentage = _t.remainingPercentage;
25046
25107
  // Calculate percentages and assign colors for each segment
25047
25108
  // default to ornament secondary color if no color is provided
25048
- var dataWithPercentages = data.map(function (item, index) {
25049
- var _a, _b, _c;
25050
- var resolvedColor = item.color ||
25051
- autoColorMap.get(item.name) ||
25052
- ((_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);
25053
- return __assign$1(__assign$1({}, item), { color: resolvedColor, percentage: effectiveMaxAmount > 0 ? (item.value / effectiveMaxAmount) * 100 : 0, key: item.name, originalIndex: index });
25054
- });
25109
+ // Memoize to prevent unnecessary recalculations and re-renders
25110
+ var dataWithPercentages = useMemo(function () {
25111
+ return data.map(function (item, index) {
25112
+ var _a, _b, _c;
25113
+ var resolvedColor = item.color ||
25114
+ autoColorMap.get(item.name) ||
25115
+ ((_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);
25116
+ return __assign$1(__assign$1({}, item), { color: resolvedColor, percentage: effectiveMaxAmount > 0 ? (item.value / effectiveMaxAmount) * 100 : 0, key: item.name, originalIndex: index });
25117
+ });
25118
+ }, [data, effectiveMaxAmount, autoColorMap, theme]);
25055
25119
  // Handle data changes and animate exits
25056
25120
  useEffect(function () {
25057
25121
  var previousProcessed = previousProcessedRef.current;
@@ -25071,53 +25135,104 @@ var StackedBarChart = function (props) {
25071
25135
  previousProcessedRef.current = dataWithPercentages;
25072
25136
  setAnimatedRemainingPercentage(remainingPercentage);
25073
25137
  previousRemainingPercentageRef.current = remainingPercentage;
25074
- // eslint-disable-next-line react-hooks/exhaustive-deps
25075
- }, [data, effectiveMaxAmount, autoColorMap]);
25076
- // Convert data to legend items format
25077
- var legendItems = buildLegendItems(dataWithPercentages, {
25078
- mode: mode,
25079
- showLegendValues: showLegendValues,
25080
- unit: unit,
25081
- hasRemaining: hasRemaining,
25082
- showLegend: showLegend,
25083
- remainingValue: remainingValue,
25084
- remainingLabel: remainingLabel,
25085
- remainingUnit: remainingUnit,
25086
- data: data,
25087
- theme: theme,
25088
- });
25089
- // Handle legend item hover
25090
- var handleLegendItemHover = function (item, index) {
25091
- setHoveredIndex(index);
25092
- };
25093
- // Handle legend item leave
25094
- var handleLegendItemLeave = function () {
25095
- setHoveredIndex(null);
25096
- };
25097
- // Handle segment click - toggle selection
25098
- var handleSegmentClick = function (index) {
25138
+ }, [dataWithPercentages, remainingPercentage, animateItemRemoval]);
25139
+ // Create a Set for O(1) lookups of selected indices
25140
+ var selectedIndicesSet = useMemo(function () { return new Set(selectedIndices); }, [selectedIndices]);
25141
+ // Apply selection styling for hide mode
25142
+ // This modifies percentages but keeps all items (including hidden ones at 0%)
25143
+ // so animations continue to work smoothly
25144
+ var _u = useMemo(function () {
25145
+ return applySelectionStyling(animatedItems, selectedIndicesSet, selectionBehavior, mode, hasRemaining, effectiveMaxAmount, animatedRemainingPercentage, remainingValue);
25146
+ }, [
25147
+ animatedItems,
25148
+ selectedIndicesSet,
25149
+ selectionBehavior,
25150
+ mode,
25151
+ hasRemaining,
25152
+ effectiveMaxAmount,
25153
+ animatedRemainingPercentage,
25154
+ remainingValue,
25155
+ ]), styledItems = _u.styledItems, updatedRemainingPercentage = _u.updatedRemainingPercentage, updatedRemainingValue = _u.updatedRemainingValue;
25156
+ // Calculate display values for hide mode
25157
+ var isHideMode = selectionBehavior === "hide" && selectedIndices.length > 0;
25158
+ var displayRemainingPercentage = isHideMode
25159
+ ? updatedRemainingPercentage
25160
+ : animatedRemainingPercentage;
25161
+ var displayRemainingValue = isHideMode
25162
+ ? updatedRemainingValue
25163
+ : remainingValue;
25164
+ // Convert data to legend items format (always use full data, not filtered)
25165
+ // Use updated remaining values when in hide mode with selections
25166
+ var legendItems = useMemo(function () {
25167
+ return buildLegendItems(dataWithPercentages, {
25168
+ showLegendValues: showLegendValues,
25169
+ legendValueFormat: legendValueFormat,
25170
+ unit: unit,
25171
+ hasRemaining: hasRemaining,
25172
+ showLegend: showLegend,
25173
+ remainingValue: displayRemainingValue,
25174
+ remainingPercentage: displayRemainingPercentage,
25175
+ remainingLabel: remainingLabel,
25176
+ remainingUnit: remainingUnit,
25177
+ theme: theme,
25178
+ });
25179
+ }, [
25180
+ dataWithPercentages,
25181
+ showLegendValues,
25182
+ legendValueFormat,
25183
+ unit,
25184
+ hasRemaining,
25185
+ showLegend,
25186
+ displayRemainingValue,
25187
+ displayRemainingPercentage,
25188
+ remainingLabel,
25189
+ remainingUnit,
25190
+ theme,
25191
+ ]);
25192
+ // Handle legend item hover and leave
25193
+ var handleLegendItemHover = useCallback(function (_item, index) { return setHoveredIndex(index); }, []);
25194
+ var handleLegendItemLeave = useCallback(function () { return setHoveredIndex(null); }, []);
25195
+ // Handle selection change - reusable logic for both segment and legend
25196
+ var handleSelectionChange = useCallback(function (newSelectedIndices) {
25099
25197
  if (!onSelectionChange)
25100
25198
  return;
25101
- var isSelected = selectedIndices.includes(index);
25102
- var newSelectedIndices;
25103
- if (isSelected) {
25104
- newSelectedIndices = selectedIndices.filter(function (i) { return i !== index; });
25105
- }
25106
- else {
25107
- newSelectedIndices = __spreadArray(__spreadArray([], selectedIndices, true), [index], false);
25108
- }
25109
25199
  var selectedData = data.filter(function (item, itemIndex) {
25110
25200
  return newSelectedIndices.includes(itemIndex);
25111
25201
  });
25112
25202
  onSelectionChange(newSelectedIndices, selectedData);
25113
- };
25114
- var chartContent = (jsxs(StyledStackedBarChartWrapper, { children: [jsxs(BarContainer, { width: width, barHeight: barHeight, children: [animatedItems.map(function (item, index) {
25203
+ }, [onSelectionChange, data]);
25204
+ // Handle segment click - toggle selection
25205
+ var handleSegmentClick = useCallback(function (index) {
25206
+ if (!onSelectionChange)
25207
+ return;
25208
+ var isSelected = selectedIndicesSet.has(index);
25209
+ var newSelectedIndices = isSelected
25210
+ ? selectedIndices.filter(function (i) { return i !== index; })
25211
+ : __spreadArray(__spreadArray([], selectedIndices, true), [index], false);
25212
+ handleSelectionChange(newSelectedIndices);
25213
+ }, [
25214
+ onSelectionChange,
25215
+ selectedIndices,
25216
+ selectedIndicesSet,
25217
+ handleSelectionChange,
25218
+ ]);
25219
+ // Calculate the last visible segment (percentage > 0)
25220
+ var lastVisibleIndex = useMemo(function () {
25221
+ for (var i = styledItems.length - 1; i >= 0; i--) {
25222
+ if (styledItems[i].percentage > 0) {
25223
+ return i;
25224
+ }
25225
+ }
25226
+ return -1;
25227
+ }, [styledItems]);
25228
+ var chartContent = (jsxs(StyledStackedBarChartWrapper, { children: [jsxs(BarContainer, { width: width, barHeight: barHeight, isEmpty: styledItems.length === 0 && !hasRemaining, children: [styledItems.map(function (item, index) {
25229
+ var isLastVisible = index === lastVisibleIndex && !hasRemaining;
25115
25230
  return renderBarSegment(item, index, {
25116
- isFirst: index === 0,
25117
- isLast: index === animatedItems.length - 1 && !hasRemaining,
25118
25231
  barHeight: barHeight,
25119
25232
  hoveredIndex: hoveredIndex,
25120
- selectedIndices: selectedIndices,
25233
+ selectedIndicesSet: selectedIndicesSet,
25234
+ selectionBehavior: selectionBehavior,
25235
+ isLast: isLastVisible,
25121
25236
  onMouseEnter: function () {
25122
25237
  return !item.isExiting && setHoveredIndex(item.originalIndex);
25123
25238
  },
@@ -25126,14 +25241,7 @@ var StackedBarChart = function (props) {
25126
25241
  return !item.isExiting && handleSegmentClick(item.originalIndex);
25127
25242
  },
25128
25243
  });
25129
- }), hasRemaining && (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 && (jsx(Legend, { items: legendItems, showValues: showLegendValues, onItemMouseEnter: handleLegendItemHover, onItemMouseLeave: handleLegendItemLeave, selectedIndices: selectedIndices, onSelectionChange: onSelectionChange
25130
- ? function (newSelectedIndices) {
25131
- var selectedData = data.filter(function (item, itemIndex) {
25132
- return newSelectedIndices.includes(itemIndex);
25133
- });
25134
- onSelectionChange(newSelectedIndices, selectedData);
25135
- }
25136
- : undefined, hoveredIndex: hoveredIndex }))] }));
25244
+ }), hasRemaining && (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 && (jsx(Legend, { items: legendItems, showValues: showLegendValues, onItemMouseEnter: handleLegendItemHover, onItemMouseLeave: handleLegendItemLeave, selectedIndices: selectedIndices, onSelectionChange: onSelectionChange ? handleSelectionChange : undefined, hoveredIndex: hoveredIndex }))] }));
25137
25245
  // If no title, render chart and optional legend without wrapper
25138
25246
  if (!title) {
25139
25247
  return (jsx(ChartWrapper, __assign$1({ width: width }, rest, { children: chartContent })));