@coinbase/cds-mobile-visualization 3.4.0-beta.22 → 3.4.0-beta.24

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 (106) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dts/chart/CartesianChart.d.ts +58 -7
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/Path.d.ts.map +1 -1
  5. package/dts/chart/area/Area.d.ts +7 -0
  6. package/dts/chart/area/Area.d.ts.map +1 -1
  7. package/dts/chart/area/AreaChart.d.ts +5 -5
  8. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  9. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  10. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  11. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  12. package/dts/chart/axis/Axis.d.ts +3 -1
  13. package/dts/chart/axis/Axis.d.ts.map +1 -1
  14. package/dts/chart/axis/XAxis.d.ts +6 -0
  15. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  16. package/dts/chart/axis/YAxis.d.ts +1 -0
  17. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  18. package/dts/chart/bar/Bar.d.ts +4 -2
  19. package/dts/chart/bar/Bar.d.ts.map +1 -1
  20. package/dts/chart/bar/BarChart.d.ts +49 -9
  21. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  22. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  23. package/dts/chart/bar/BarStack.d.ts +30 -9
  24. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  25. package/dts/chart/bar/BarStackGroup.d.ts +1 -1
  26. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  27. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  28. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  29. package/dts/chart/gradient/Gradient.d.ts +5 -0
  30. package/dts/chart/gradient/Gradient.d.ts.map +1 -1
  31. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  32. package/dts/chart/line/Line.d.ts +7 -0
  33. package/dts/chart/line/Line.d.ts.map +1 -1
  34. package/dts/chart/line/LineChart.d.ts +22 -8
  35. package/dts/chart/line/LineChart.d.ts.map +1 -1
  36. package/dts/chart/line/ReferenceLine.d.ts +1 -0
  37. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  38. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  39. package/dts/chart/point/Point.d.ts +7 -0
  40. package/dts/chart/point/Point.d.ts.map +1 -1
  41. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -1
  42. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +2 -1
  43. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -1
  44. package/dts/chart/scrubber/Scrubber.d.ts +8 -0
  45. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  46. package/dts/chart/scrubber/ScrubberAccessibilityView.d.ts +12 -0
  47. package/dts/chart/scrubber/ScrubberAccessibilityView.d.ts.map +1 -0
  48. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -1
  49. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  50. package/dts/chart/utils/axis.d.ts +20 -9
  51. package/dts/chart/utils/axis.d.ts.map +1 -1
  52. package/dts/chart/utils/bar.d.ts +4 -3
  53. package/dts/chart/utils/bar.d.ts.map +1 -1
  54. package/dts/chart/utils/chart.d.ts +13 -0
  55. package/dts/chart/utils/chart.d.ts.map +1 -1
  56. package/dts/chart/utils/context.d.ts +21 -6
  57. package/dts/chart/utils/context.d.ts.map +1 -1
  58. package/dts/chart/utils/gradient.d.ts +3 -1
  59. package/dts/chart/utils/gradient.d.ts.map +1 -1
  60. package/dts/chart/utils/path.d.ts +20 -0
  61. package/dts/chart/utils/path.d.ts.map +1 -1
  62. package/dts/chart/utils/point.d.ts +7 -0
  63. package/dts/chart/utils/point.d.ts.map +1 -1
  64. package/esm/chart/CartesianChart.js +145 -81
  65. package/esm/chart/Path.js +10 -7
  66. package/esm/chart/__stories__/CartesianChart.stories.js +10 -0
  67. package/esm/chart/__stories__/ChartAccessibility.stories.js +721 -0
  68. package/esm/chart/area/Area.js +19 -9
  69. package/esm/chart/area/AreaChart.js +11 -9
  70. package/esm/chart/area/DottedArea.js +11 -6
  71. package/esm/chart/area/GradientArea.js +11 -6
  72. package/esm/chart/area/SolidArea.js +3 -1
  73. package/esm/chart/area/__stories__/AreaChart.stories.js +47 -5
  74. package/esm/chart/axis/XAxis.js +14 -21
  75. package/esm/chart/axis/YAxis.js +4 -3
  76. package/esm/chart/axis/__stories__/Axis.stories.js +65 -48
  77. package/esm/chart/bar/Bar.js +9 -5
  78. package/esm/chart/bar/BarChart.js +34 -31
  79. package/esm/chart/bar/BarPlot.js +7 -5
  80. package/esm/chart/bar/BarStack.js +176 -36
  81. package/esm/chart/bar/BarStackGroup.js +37 -27
  82. package/esm/chart/bar/DefaultBar.js +24 -8
  83. package/esm/chart/bar/DefaultBarStack.js +24 -10
  84. package/esm/chart/bar/__stories__/BarChart.stories.js +105 -3
  85. package/esm/chart/gradient/Gradient.js +2 -1
  86. package/esm/chart/line/DottedLine.js +3 -1
  87. package/esm/chart/line/Line.js +32 -19
  88. package/esm/chart/line/LineChart.js +31 -9
  89. package/esm/chart/line/SolidLine.js +3 -1
  90. package/esm/chart/line/__stories__/LineChart.stories.js +115 -46
  91. package/esm/chart/point/Point.js +2 -1
  92. package/esm/chart/scrubber/DefaultScrubberBeacon.js +1 -1
  93. package/esm/chart/scrubber/DefaultScrubberLabel.js +26 -10
  94. package/esm/chart/scrubber/Scrubber.js +47 -21
  95. package/esm/chart/scrubber/ScrubberAccessibilityView.js +177 -0
  96. package/esm/chart/scrubber/ScrubberBeaconGroup.js +24 -20
  97. package/esm/chart/scrubber/ScrubberProvider.js +29 -24
  98. package/esm/chart/scrubber/__stories__/Scrubber.stories.js +192 -6
  99. package/esm/chart/utils/axis.js +42 -14
  100. package/esm/chart/utils/bar.js +5 -4
  101. package/esm/chart/utils/chart.js +18 -5
  102. package/esm/chart/utils/context.js +7 -0
  103. package/esm/chart/utils/gradient.js +8 -4
  104. package/esm/chart/utils/path.js +90 -61
  105. package/esm/chart/utils/point.js +28 -18
  106. package/package.json +5 -5
@@ -1,5 +1,6 @@
1
1
  import React, { memo, useMemo } from 'react';
2
2
  import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme';
3
+ import { useCartesianChartContext } from '../ChartProvider';
3
4
  import { getBarPath } from '../utils';
4
5
  import { DefaultBar } from './DefaultBar';
5
6
  import { jsx as _jsx } from "react/jsx-runtime";
@@ -21,7 +22,7 @@ export const Bar = /*#__PURE__*/memo(_ref => {
21
22
  y,
22
23
  width,
23
24
  height,
24
- originY,
25
+ origin: originProp,
25
26
  dataX,
26
27
  dataY,
27
28
  seriesId,
@@ -37,14 +38,17 @@ export const Bar = /*#__PURE__*/memo(_ref => {
37
38
  transition
38
39
  } = _ref;
39
40
  const theme = useTheme();
41
+ const {
42
+ layout
43
+ } = useCartesianChartContext();
40
44
 
41
45
  // Use theme color as default if no fill is provided
42
46
  const effectiveFill = fill != null ? fill : theme.color.fgPrimary;
43
47
  const borderRadiusPixels = useMemo(() => borderRadius != null ? borderRadius : 0, [borderRadius]);
44
48
  const barPath = useMemo(() => {
45
- return getBarPath(x, y, width, height, borderRadiusPixels, roundTop, roundBottom);
46
- }, [x, y, width, height, borderRadiusPixels, roundTop, roundBottom]);
47
- const effectiveOriginY = originY != null ? originY : y + height;
49
+ return getBarPath(x, y, width, height, borderRadiusPixels, roundTop, roundBottom, layout);
50
+ }, [x, y, width, height, borderRadiusPixels, roundTop, roundBottom, layout]);
51
+ const effectiveOrigin = originProp != null ? originProp : layout === 'horizontal' ? x : y + height;
48
52
  if (!barPath) {
49
53
  return null;
50
54
  }
@@ -58,7 +62,7 @@ export const Bar = /*#__PURE__*/memo(_ref => {
58
62
  fill: effectiveFill,
59
63
  fillOpacity: fillOpacity,
60
64
  height: height,
61
- originY: effectiveOriginY,
65
+ origin: effectiveOrigin,
62
66
  roundBottom: roundBottom,
63
67
  roundTop: roundTop,
64
68
  seriesId: seriesId,
@@ -1,17 +1,17 @@
1
1
  const _excluded = ["series", "stacked", "showXAxis", "showYAxis", "xAxis", "yAxis", "inset", "children", "barPadding", "BarComponent", "fillOpacity", "stroke", "strokeWidth", "borderRadius", "roundBaseline", "BarStackComponent", "stackGap", "barMinSize", "stackMinSize", "transitions", "transition"],
2
- _excluded2 = ["scaleType", "data", "categoryPadding", "domain", "domainLimit", "range"],
2
+ _excluded2 = ["scaleType", "data", "categoryPadding", "domain", "domainLimit", "range", "id"],
3
3
  _excluded3 = ["scaleType", "data", "categoryPadding", "domain", "domainLimit", "range", "id"];
4
4
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
5
5
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
6
6
  import { forwardRef, memo, useMemo } from 'react';
7
7
  import { XAxis, YAxis } from '../axis';
8
8
  import { CartesianChart } from '../CartesianChart';
9
- import { defaultChartInset, defaultStackId, getChartInset } from '../utils';
9
+ import { defaultStackId } from '../utils';
10
10
  import { BarPlot } from './BarPlot';
11
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  export const BarChart = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
13
13
  let {
14
- series,
14
+ series: seriesProp,
15
15
  stacked,
16
16
  showXAxis,
17
17
  showYAxis,
@@ -34,21 +34,19 @@ export const BarChart = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
34
34
  transition
35
35
  } = _ref,
36
36
  chartProps = _objectWithoutPropertiesLoose(_ref, _excluded);
37
- const calculatedInset = useMemo(() => getChartInset(inset, defaultChartInset), [inset]);
38
- const transformedSeries = useMemo(() => {
39
- if (!stacked || !series) return series;
40
- return series.map(s => {
37
+ const series = useMemo(() => {
38
+ if (!stacked || !seriesProp) return seriesProp;
39
+ return seriesProp.map(s => {
41
40
  var _s$stackId;
42
41
  return _extends({}, s, {
43
42
  stackId: (_s$stackId = s.stackId) != null ? _s$stackId : defaultStackId
44
43
  });
45
44
  });
46
- }, [series, stacked]);
47
-
48
- // Unlike other charts with custom props per series, we do not need to pick out
49
- // the props from each series that shouldn't be passed to CartesianChart
50
- const seriesToRender = transformedSeries != null ? transformedSeries : series;
51
- const seriesIds = seriesToRender == null ? void 0 : seriesToRender.map(s => s.id);
45
+ }, [seriesProp, stacked]);
46
+ const seriesIds = useMemo(() => series == null ? void 0 : series.map(s => s.id), [series]);
47
+ const isHorizontal = chartProps.layout === 'horizontal';
48
+ const defaultXScaleType = isHorizontal ? 'linear' : 'band';
49
+ const defaultYScaleType = isHorizontal ? 'band' : 'linear';
52
50
 
53
51
  // Split axis props into config props for Chart and visual props for axis components
54
52
  const _ref2 = xAxis || {},
@@ -58,7 +56,8 @@ export const BarChart = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
58
56
  categoryPadding: xCategoryPadding,
59
57
  domain: xDomain,
60
58
  domainLimit: xDomainLimit,
61
- range: xRange
59
+ range: xRange,
60
+ id: xAxisId
62
61
  } = _ref2,
63
62
  xAxisVisualProps = _objectWithoutPropertiesLoose(_ref2, _excluded2);
64
63
  const _ref3 = yAxis || {},
@@ -72,14 +71,6 @@ export const BarChart = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
72
71
  id: yAxisId
73
72
  } = _ref3,
74
73
  yAxisVisualProps = _objectWithoutPropertiesLoose(_ref3, _excluded3);
75
- const xAxisConfig = {
76
- scaleType: xScaleType != null ? xScaleType : 'band',
77
- data: xData,
78
- categoryPadding: xCategoryPadding,
79
- domain: xDomain,
80
- domainLimit: xDomainLimit,
81
- range: xRange
82
- };
83
74
  const hasNegativeValues = useMemo(() => {
84
75
  if (!series) return false;
85
76
  return series.some(s => {
@@ -87,25 +78,37 @@ export const BarChart = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
87
78
  return (_s$data = s.data) == null ? void 0 : _s$data.some(value => typeof value === 'number' && value < 0 || Array.isArray(value) && value.some(v => typeof v === 'number' && v < 0));
88
79
  });
89
80
  }, [series]);
81
+ const xAxisConfig = useMemo(() => ({
82
+ scaleType: xScaleType != null ? xScaleType : defaultXScaleType,
83
+ data: xData,
84
+ categoryPadding: xCategoryPadding,
85
+ domain: isHorizontal && !hasNegativeValues ? _extends({
86
+ min: 0
87
+ }, xDomain) : xDomain,
88
+ domainLimit: xDomainLimit,
89
+ range: xRange
90
+ }), [xScaleType, defaultXScaleType, xData, xCategoryPadding, isHorizontal, hasNegativeValues, xDomain, xDomainLimit, xRange]);
90
91
 
91
- // Set default min domain to 0 for area chart, but only if there are no negative values
92
- const yAxisConfig = {
93
- scaleType: yScaleType,
92
+ // Set default min domain to 0 for bar chart, but only if there are no negative values.
93
+ const yAxisConfig = useMemo(() => ({
94
+ scaleType: yScaleType != null ? yScaleType : defaultYScaleType,
94
95
  data: yData,
95
96
  categoryPadding: yCategoryPadding,
96
- domain: hasNegativeValues ? yDomain : _extends({
97
+ domain: !isHorizontal && !hasNegativeValues ? _extends({
97
98
  min: 0
98
- }, yDomain),
99
+ }, yDomain) : yDomain,
99
100
  domainLimit: yDomainLimit,
100
101
  range: yRange
101
- };
102
+ }), [yScaleType, defaultYScaleType, yData, yCategoryPadding, isHorizontal, hasNegativeValues, yDomain, yDomainLimit, yRange]);
102
103
  return /*#__PURE__*/_jsxs(CartesianChart, _extends({}, chartProps, {
103
104
  ref: ref,
104
- inset: calculatedInset,
105
- series: seriesToRender,
105
+ inset: inset,
106
+ series: series,
106
107
  xAxis: xAxisConfig,
107
108
  yAxis: yAxisConfig,
108
- children: [showXAxis && /*#__PURE__*/_jsx(XAxis, _extends({}, xAxisVisualProps)), showYAxis && /*#__PURE__*/_jsx(YAxis, _extends({
109
+ children: [showXAxis && /*#__PURE__*/_jsx(XAxis, _extends({
110
+ axisId: xAxisId
111
+ }, xAxisVisualProps)), showYAxis && /*#__PURE__*/_jsx(YAxis, _extends({
109
112
  axisId: yAxisId
110
113
  }, yAxisVisualProps)), /*#__PURE__*/_jsx(BarPlot, {
111
114
  BarComponent: BarComponent,
@@ -1,4 +1,4 @@
1
- import { memo, useId, useMemo } from 'react';
1
+ import { memo, useMemo } from 'react';
2
2
  import { Group, Skia } from '@shopify/react-native-skia';
3
3
  import { useCartesianChartContext } from '../ChartProvider';
4
4
  import { defaultAxisId } from '../utils';
@@ -31,7 +31,6 @@ export const BarPlot = /*#__PURE__*/memo(_ref => {
31
31
  series: allSeries,
32
32
  drawingArea
33
33
  } = useCartesianChartContext();
34
- const clipPathId = useId();
35
34
  const targetSeries = useMemo(() => {
36
35
  // Then filter by seriesIds if provided
37
36
  if (seriesIds !== undefined) {
@@ -42,16 +41,18 @@ export const BarPlot = /*#__PURE__*/memo(_ref => {
42
41
  const stackGroups = useMemo(() => {
43
42
  const groups = new Map();
44
43
 
45
- // Group series into stacks based on stackId + yAxisId combination
44
+ // Group series into stacks based on stackId + axis ID combination
46
45
  targetSeries.forEach(series => {
47
- var _series$yAxisId;
46
+ var _series$xAxisId, _series$yAxisId;
47
+ const xAxisId = (_series$xAxisId = series.xAxisId) != null ? _series$xAxisId : defaultAxisId;
48
48
  const yAxisId = (_series$yAxisId = series.yAxisId) != null ? _series$yAxisId : defaultAxisId;
49
49
  const stackId = series.stackId || "individual-" + series.id;
50
- const stackKey = stackId + ":" + yAxisId;
50
+ const stackKey = stackId + ":" + xAxisId + ":" + yAxisId;
51
51
  if (!groups.has(stackKey)) {
52
52
  groups.set(stackKey, {
53
53
  stackId: stackKey,
54
54
  series: [],
55
+ xAxisId: series.xAxisId,
55
56
  yAxisId: series.yAxisId
56
57
  });
57
58
  }
@@ -98,6 +99,7 @@ export const BarPlot = /*#__PURE__*/memo(_ref => {
98
99
  totalStacks: stackGroups.length,
99
100
  transition: transition,
100
101
  transitions: transitions,
102
+ xAxisId: group.xAxisId,
101
103
  yAxisId: group.yAxisId
102
104
  }, group.stackId))
103
105
  });
@@ -21,10 +21,13 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
21
21
  let {
22
22
  series,
23
23
  categoryIndex,
24
- x,
25
- width,
26
- yScale,
24
+ indexPos,
25
+ thickness,
26
+ indexScale,
27
+ valueScale,
27
28
  rect,
29
+ xAxisId,
30
+ yAxisId,
28
31
  BarComponent: defaultBarComponent,
29
32
  fillOpacity: defaultFillOpacity,
30
33
  stroke: defaultStroke,
@@ -40,24 +43,30 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
40
43
  } = _ref;
41
44
  const theme = useTheme();
42
45
  const {
46
+ layout,
43
47
  getSeriesData,
44
48
  getXAxis,
45
- getXScale
49
+ getYAxis
46
50
  } = useCartesianChartContext();
47
- const xAxis = getXAxis();
48
- const xScale = getXScale();
51
+ const xAxis = getXAxis(xAxisId);
52
+ const yAxis = getYAxis(yAxisId);
53
+ const barsGrowVertically = layout !== 'horizontal';
49
54
  const baseline = useMemo(() => {
50
- var _yScale;
51
- const domain = yScale.domain();
55
+ var _valueScale;
56
+ const domain = valueScale.domain();
52
57
  const [domainMin, domainMax] = domain;
53
58
  const baselineValue = domainMin >= 0 ? domainMin : domainMax <= 0 ? domainMax : 0;
54
- const baseline = (_yScale = yScale(baselineValue)) != null ? _yScale : rect.y + rect.height;
55
- return Math.max(rect.y, Math.min(baseline, rect.y + rect.height));
56
- }, [rect.height, rect.y, yScale]);
59
+ const fallback = barsGrowVertically ? rect.y + rect.height : rect.x;
60
+ const baselinePos = (_valueScale = valueScale(baselineValue)) != null ? _valueScale : fallback;
61
+ if (barsGrowVertically) {
62
+ return Math.max(rect.y, Math.min(baselinePos, rect.y + rect.height));
63
+ }
64
+ return Math.max(rect.x, Math.min(baselinePos, rect.x + rect.width));
65
+ }, [rect, valueScale, barsGrowVertically]);
57
66
  const seriesGradients = useMemo(() => {
58
67
  return series.map(s => {
59
- if (!s.gradient || !xScale || !yScale) return;
60
- const gradientScale = s.gradient.axis === 'x' ? xScale : yScale;
68
+ if (!s.gradient) return;
69
+ const gradientScale = s.gradient.axis === 'x' ? barsGrowVertically ? indexScale : valueScale : barsGrowVertically ? valueScale : indexScale;
61
70
  const serializableScale = convertToSerializableScale(gradientScale);
62
71
  if (!serializableScale) return;
63
72
  const domain = {
@@ -72,14 +81,140 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
72
81
  stops
73
82
  };
74
83
  });
75
- }, [series, xScale, yScale]);
84
+ }, [series, indexScale, valueScale, barsGrowVertically]);
76
85
 
77
86
  // Calculate bars for this specific category
78
87
  const {
79
88
  bars,
80
89
  stackRect
81
90
  } = useMemo(() => {
91
+ const x = indexPos;
92
+ const width = thickness;
93
+ const yScale = valueScale;
82
94
  let allBars = [];
95
+ if (!barsGrowVertically) {
96
+ let minX = Infinity;
97
+ let maxX = -Infinity;
98
+ series.forEach(s => {
99
+ var _yScale, _yScale2;
100
+ const data = getSeriesData(s.id);
101
+ if (!data) return;
102
+ const value = data[categoryIndex];
103
+ if (value === null || value === undefined) return;
104
+ const originalData = s.data;
105
+ const originalValue = originalData == null ? void 0 : originalData[categoryIndex];
106
+ const shouldApplyGap = !Array.isArray(originalValue);
107
+ const [bottom, top] = value.sort((a, b) => a - b);
108
+ const edgeBottom = (_yScale = yScale(bottom)) != null ? _yScale : baseline;
109
+ const edgeTop = (_yScale2 = yScale(top)) != null ? _yScale2 : baseline;
110
+ const length = Math.abs(edgeBottom - edgeTop);
111
+ const barX = Math.min(edgeBottom, edgeTop);
112
+ if (length <= 0) return;
113
+ minX = Math.min(minX, barX);
114
+ maxX = Math.max(maxX, barX + length);
115
+ let barFill = s.color || theme.color.fgPrimary;
116
+ const seriesGradientConfig = seriesGradients.find(g => (g == null ? void 0 : g.seriesId) === s.id);
117
+ if (seriesGradientConfig && originalValue !== null && originalValue !== undefined) {
118
+ var _seriesGradientConfig;
119
+ const axis = (_seriesGradientConfig = seriesGradientConfig.gradient.axis) != null ? _seriesGradientConfig : 'y';
120
+ const evalValue = axis === 'x' ? Array.isArray(originalValue) ? originalValue[1] : originalValue : categoryIndex;
121
+ const evaluatedColor = evaluateGradientAtValue(seriesGradientConfig.stops, evalValue, seriesGradientConfig.scale);
122
+ if (evaluatedColor) {
123
+ barFill = evaluatedColor;
124
+ }
125
+ }
126
+ const roundTop = roundBaseline || Math.abs(edgeTop - baseline) >= EPSILON;
127
+ const roundBottom = roundBaseline || Math.abs(edgeBottom - baseline) >= EPSILON;
128
+ allBars.push({
129
+ seriesId: s.id,
130
+ x: barX,
131
+ y: x,
132
+ width: length,
133
+ height: width,
134
+ dataY: value,
135
+ fill: barFill,
136
+ roundTop,
137
+ roundBottom,
138
+ BarComponent: s.BarComponent,
139
+ shouldApplyGap
140
+ });
141
+ });
142
+ if (stackGap && allBars.length > 1) {
143
+ const barsAboveBaseline = allBars.filter(bar => {
144
+ const [bottom, top] = bar.dataY.sort((a, b) => a - b);
145
+ return bottom >= 0 && top !== bottom && bar.shouldApplyGap;
146
+ });
147
+ const barsBelowBaseline = allBars.filter(bar => {
148
+ const [bottom, top] = bar.dataY.sort((a, b) => a - b);
149
+ return bottom <= 0 && bottom !== top && bar.shouldApplyGap;
150
+ });
151
+ if (barsAboveBaseline.length > 1) {
152
+ const totalGapSpace = stackGap * (barsAboveBaseline.length - 1);
153
+ const totalDataLength = barsAboveBaseline.reduce((sum, bar) => sum + bar.width, 0);
154
+ const lengthReduction = totalGapSpace / totalDataLength;
155
+ const sortedBars = barsAboveBaseline.sort((a, b) => a.x - b.x);
156
+ let currentEdge = baseline;
157
+ sortedBars.forEach((bar, index) => {
158
+ const newLength = bar.width * (1 - lengthReduction);
159
+ const newX = currentEdge;
160
+ currentEdge = newX + newLength + (index < sortedBars.length - 1 ? stackGap : 0);
161
+ const barIndex = allBars.findIndex(b => b.seriesId === bar.seriesId);
162
+ if (barIndex !== -1) {
163
+ allBars[barIndex] = _extends({}, allBars[barIndex], {
164
+ width: newLength,
165
+ x: newX
166
+ });
167
+ }
168
+ });
169
+ }
170
+ if (barsBelowBaseline.length > 1) {
171
+ const totalGapSpace = stackGap * (barsBelowBaseline.length - 1);
172
+ const totalDataLength = barsBelowBaseline.reduce((sum, bar) => sum + bar.width, 0);
173
+ const lengthReduction = totalGapSpace / totalDataLength;
174
+ const sortedBars = barsBelowBaseline.sort((a, b) => b.x - a.x);
175
+ let currentEdge = baseline;
176
+ sortedBars.forEach((bar, index) => {
177
+ const newLength = bar.width * (1 - lengthReduction);
178
+ const newX = currentEdge - newLength;
179
+ currentEdge = newX - (index < sortedBars.length - 1 ? stackGap : 0);
180
+ const barIndex = allBars.findIndex(b => b.seriesId === bar.seriesId);
181
+ if (barIndex !== -1) {
182
+ allBars[barIndex] = _extends({}, allBars[barIndex], {
183
+ width: newLength,
184
+ x: newX
185
+ });
186
+ }
187
+ });
188
+ }
189
+ if (allBars.length > 0) {
190
+ minX = Math.min(...allBars.map(bar => bar.x));
191
+ maxX = Math.max(...allBars.map(bar => bar.x + bar.width));
192
+ }
193
+ }
194
+
195
+ // Horizontal border radius logic: left-to-right sorting.
196
+ const sortedBars = [...allBars].sort((a, b) => a.x - b.x);
197
+ const roundedBars = sortedBars.map((bar, index) => {
198
+ const barBefore = index > 0 ? sortedBars[index - 1] : null;
199
+ const barAfter = index < sortedBars.length - 1 ? sortedBars[index + 1] : null;
200
+ const shouldRoundLower = index === 0 || bar.shouldApplyGap && stackGap || !bar.shouldApplyGap && barAfter && barAfter.x + barAfter.width !== bar.x;
201
+ const shouldRoundHigher = index === sortedBars.length - 1 || bar.shouldApplyGap && stackGap || !bar.shouldApplyGap && barBefore && barBefore.x !== bar.x + bar.width;
202
+ return _extends({}, bar, {
203
+ roundTop: Boolean(bar.roundTop && shouldRoundHigher),
204
+ roundBottom: Boolean(bar.roundBottom && shouldRoundLower)
205
+ });
206
+ });
207
+ const stackBounds = {
208
+ x: minX === Infinity ? baseline : minX,
209
+ y: x,
210
+ width: maxX === -Infinity ? 0 : maxX - minX,
211
+ height: width
212
+ };
213
+ return {
214
+ bars: roundedBars,
215
+ stackRect: stackBounds
216
+ };
217
+ }
83
218
 
84
219
  // Track how many bars we've stacked in each direction for gap calculation
85
220
  let positiveBarCount = 0;
@@ -91,7 +226,7 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
91
226
 
92
227
  // Process each series in the stack
93
228
  series.forEach(s => {
94
- var _yScale2, _yScale3;
229
+ var _yScale3, _yScale4;
95
230
  const data = getSeriesData(s.id);
96
231
  if (!data) return;
97
232
  const value = data[categoryIndex];
@@ -105,8 +240,8 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
105
240
  const [bottom, top] = value.sort((a, b) => a - b);
106
241
  const isAboveBaseline = bottom >= 0 && top !== bottom;
107
242
  const isBelowBaseline = bottom <= 0 && bottom !== top;
108
- const barBottom = (_yScale2 = yScale(bottom)) != null ? _yScale2 : baseline;
109
- const barTop = (_yScale3 = yScale(top)) != null ? _yScale3 : baseline;
243
+ const barBottom = (_yScale3 = yScale(bottom)) != null ? _yScale3 : baseline;
244
+ const barTop = (_yScale4 = yScale(top)) != null ? _yScale4 : baseline;
110
245
 
111
246
  // Track bar counts for later gap calculations
112
247
  if (shouldApplyGap) {
@@ -136,8 +271,8 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
136
271
  // Evaluate gradient if provided (using precomputed stops)
137
272
  const seriesGradientConfig = seriesGradients.find(g => (g == null ? void 0 : g.seriesId) === s.id);
138
273
  if (seriesGradientConfig) {
139
- var _seriesGradientConfig;
140
- const axis = (_seriesGradientConfig = seriesGradientConfig.gradient.axis) != null ? _seriesGradientConfig : 'y';
274
+ var _seriesGradientConfig2;
275
+ const axis = (_seriesGradientConfig2 = seriesGradientConfig.gradient.axis) != null ? _seriesGradientConfig2 : 'y';
141
276
  // For x-axis gradient, use the categoryIndex
142
277
  // For y-axis gradient, use the actual data value
143
278
  const dataValue = axis === 'x' ? categoryIndex : top;
@@ -243,14 +378,14 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
243
378
  // First, expand bars that need it and track the expansion
244
379
  const expandedBars = allBars.map((bar, index) => {
245
380
  if (bar.height < barMinSize) {
246
- var _yScale4, _yScale5, _yScale6, _yScale7;
381
+ var _yScale5, _yScale6, _yScale7, _yScale8;
247
382
  const heightIncrease = barMinSize - bar.height;
248
383
  const [bottom, top] = bar.dataY.sort((a, b) => a - b);
249
384
 
250
385
  // Determine how to expand the bar
251
386
  let newBottom = bottom;
252
387
  let newTop = top;
253
- const scaleUnit = Math.abs(((_yScale4 = yScale(1)) != null ? _yScale4 : 0) - ((_yScale5 = yScale(0)) != null ? _yScale5 : 0));
388
+ const scaleUnit = Math.abs(((_yScale5 = yScale(1)) != null ? _yScale5 : 0) - ((_yScale6 = yScale(0)) != null ? _yScale6 : 0));
254
389
  if (bottom === 0) {
255
390
  // Expand away from baseline (upward for positive)
256
391
  newTop = top + heightIncrease / scaleUnit;
@@ -265,8 +400,8 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
265
400
  }
266
401
 
267
402
  // Recalculate bar position with new data values
268
- const newBarBottom = (_yScale6 = yScale(newBottom)) != null ? _yScale6 : baseline;
269
- const newBarTop = (_yScale7 = yScale(newTop)) != null ? _yScale7 : baseline;
403
+ const newBarBottom = (_yScale7 = yScale(newBottom)) != null ? _yScale7 : baseline;
404
+ const newBarTop = (_yScale8 = yScale(newTop)) != null ? _yScale8 : baseline;
270
405
  const newHeight = Math.abs(newBarBottom - newBarTop);
271
406
  const newY = Math.min(newBarBottom, newBarTop);
272
407
  return _extends({}, bar, {
@@ -383,7 +518,7 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
383
518
  // Apply stackMinSize constraints
384
519
  if (stackMinSize) {
385
520
  if (allBars.length === 1 && stackBounds.height < stackMinSize) {
386
- var _yScale8, _yScale9, _yScale0, _yScale1;
521
+ var _yScale9, _yScale0, _yScale1, _yScale10;
387
522
  // For single bars (non-stacked), treat stackMinSize like barMinSize
388
523
 
389
524
  const bar = allBars[0];
@@ -393,7 +528,7 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
393
528
  // Determine how to expand the bar (same logic as barMinSize)
394
529
  let newBottom = bottom;
395
530
  let newTop = top;
396
- const scaleUnit = Math.abs(((_yScale8 = yScale(1)) != null ? _yScale8 : 0) - ((_yScale9 = yScale(0)) != null ? _yScale9 : 0));
531
+ const scaleUnit = Math.abs(((_yScale9 = yScale(1)) != null ? _yScale9 : 0) - ((_yScale0 = yScale(0)) != null ? _yScale0 : 0));
397
532
  if (bottom === 0) {
398
533
  // Expand away from baseline (upward for positive)
399
534
  newTop = top + heightIncrease / scaleUnit;
@@ -408,8 +543,8 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
408
543
  }
409
544
 
410
545
  // Recalculate bar position with new data values
411
- const newBarBottom = (_yScale0 = yScale(newBottom)) != null ? _yScale0 : baseline;
412
- const newBarTop = (_yScale1 = yScale(newTop)) != null ? _yScale1 : baseline;
546
+ const newBarBottom = (_yScale1 = yScale(newBottom)) != null ? _yScale1 : baseline;
547
+ const newBarTop = (_yScale10 = yScale(newTop)) != null ? _yScale10 : baseline;
413
548
  const newHeight = Math.abs(newBarBottom - newBarTop);
414
549
  const newY = Math.min(newBarBottom, newBarTop);
415
550
  allBars[0] = _extends({}, bar, {
@@ -517,18 +652,19 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
517
652
  bars: allBars,
518
653
  stackRect: stackBounds
519
654
  };
520
- }, [series, x, width, getSeriesData, categoryIndex, roundBaseline, baseline, stackGap, barMinSize, stackMinSize, yScale, seriesGradients, theme.color.fgPrimary]);
521
- const xData = xAxis != null && xAxis.data && Array.isArray(xAxis.data) && typeof xAxis.data[0] === 'number' ? xAxis.data : undefined;
522
- const dataX = xData ? xData[categoryIndex] : categoryIndex;
655
+ }, [series, indexPos, thickness, getSeriesData, categoryIndex, roundBaseline, baseline, stackGap, barMinSize, stackMinSize, valueScale, seriesGradients, theme.color.fgPrimary, barsGrowVertically]);
656
+ const categoryAxis = barsGrowVertically ? xAxis : yAxis;
657
+ const categoryData = categoryAxis != null && categoryAxis.data && Array.isArray(categoryAxis.data) && typeof categoryAxis.data[0] === 'number' ? categoryAxis.data : undefined;
658
+ const categoryValue = categoryData ? categoryData[categoryIndex] : categoryIndex;
523
659
  const barElements = bars.map((bar, index) => /*#__PURE__*/_jsx(Bar, {
524
660
  BarComponent: bar.BarComponent || defaultBarComponent,
525
661
  borderRadius: borderRadius,
526
- dataX: dataX,
527
- dataY: bar.dataY,
662
+ dataX: barsGrowVertically ? categoryValue : bar.dataY,
663
+ dataY: barsGrowVertically ? bar.dataY : categoryValue,
528
664
  fill: bar.fill,
529
665
  fillOpacity: defaultFillOpacity,
530
666
  height: bar.height,
531
- originY: baseline,
667
+ origin: baseline,
532
668
  roundBottom: bar.roundBottom,
533
669
  roundTop: bar.roundTop,
534
670
  seriesId: bar.seriesId,
@@ -541,9 +677,13 @@ export const BarStack = /*#__PURE__*/memo(_ref => {
541
677
  y: bar.y
542
678
  }, bar.seriesId + "-" + categoryIndex + "-" + index));
543
679
 
544
- // Check if the bar should be rounded based on the baseline, with an epsilon to handle floating-point rounding
545
- const stackRoundBottom = roundBaseline || Math.abs(stackRect.y + stackRect.height - baseline) >= EPSILON;
546
- const stackRoundTop = roundBaseline || Math.abs(stackRect.y - baseline) >= EPSILON;
680
+ // Check if the stack should be rounded based on baseline, across both orientations.
681
+ const edge = barsGrowVertically ? stackRect.y : stackRect.x;
682
+ const size = barsGrowVertically ? stackRect.height : stackRect.width;
683
+ const stackRoundLower = roundBaseline || Math.abs(edge - baseline) >= EPSILON;
684
+ const stackRoundHigher = roundBaseline || Math.abs(edge + size - baseline) >= EPSILON;
685
+ const stackRoundTop = barsGrowVertically ? stackRoundLower : stackRoundHigher;
686
+ const stackRoundBottom = barsGrowVertically ? stackRoundHigher : stackRoundLower;
547
687
  return /*#__PURE__*/_jsx(BarStackComponent, {
548
688
  borderRadius: borderRadius,
549
689
  categoryIndex: categoryIndex,
@@ -1,4 +1,4 @@
1
- const _excluded = ["series", "yAxisId", "stackIndex", "totalStacks", "barPadding"];
1
+ const _excluded = ["series", "xAxisId", "yAxisId", "stackIndex", "totalStacks", "barPadding"];
2
2
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
3
3
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
4
4
  import { memo, useMemo, createElement as _createElement } from 'react';
@@ -12,6 +12,7 @@ import { BarStack } from './BarStack';
12
12
  export const BarStackGroup = /*#__PURE__*/memo(_ref => {
13
13
  let {
14
14
  series,
15
+ xAxisId = defaultAxisId,
15
16
  yAxisId = defaultAxisId,
16
17
  stackIndex,
17
18
  totalStacks,
@@ -19,61 +20,70 @@ export const BarStackGroup = /*#__PURE__*/memo(_ref => {
19
20
  } = _ref,
20
21
  props = _objectWithoutPropertiesLoose(_ref, _excluded);
21
22
  const {
23
+ layout,
22
24
  getXScale,
23
25
  getYScale,
24
26
  drawingArea,
25
27
  dataLength
26
28
  } = useCartesianChartContext();
27
- const xScale = getXScale();
29
+ const xScale = getXScale(xAxisId);
28
30
  const yScale = getYScale(yAxisId);
29
31
  const stackConfigs = useMemo(() => {
30
32
  if (!xScale || !yScale || !drawingArea || dataLength === 0) return [];
31
- if (!isCategoricalScale(xScale)) {
33
+ const indexScale = layout !== 'horizontal' ? xScale : yScale;
34
+ if (!isCategoricalScale(indexScale)) {
32
35
  return [];
33
36
  }
34
- const categoryWidth = xScale.bandwidth();
37
+ const categoryWidth = indexScale.bandwidth();
35
38
 
36
- // Calculate width for each stack within a category
37
- // Only apply barPadding when there are multiple stacks
38
- const gapWidth = totalStacks > 1 ? categoryWidth * barPadding / (totalStacks - 1) : 0;
39
- const barWidth = categoryWidth / totalStacks - getBarSizeAdjustment(totalStacks, gapWidth);
39
+ // Calculate thickness for each stack within a category.
40
+ const gapSize = totalStacks > 1 ? categoryWidth * barPadding / (totalStacks - 1) : 0;
41
+ const stackThickness = categoryWidth / totalStacks - getBarSizeAdjustment(totalStacks, gapSize);
40
42
  const configs = [];
41
43
 
42
- // Calculate position for each category
43
- // todo: look at using xDomain for this instead of dataLength
44
+ // Calculate position for each category.
44
45
  for (let categoryIndex = 0; categoryIndex < dataLength; categoryIndex++) {
45
- // Get x position for this category
46
- const categoryX = xScale(categoryIndex);
47
- if (categoryX !== undefined) {
48
- // Calculate x position for this specific stack within the category
49
- const stackX = categoryX + stackIndex * (barWidth + gapWidth);
46
+ // Get position for this category along the index axis.
47
+ const categoryPos = indexScale(categoryIndex);
48
+ if (categoryPos !== undefined) {
49
+ // Calculate position for this specific stack within the category.
50
+ const stackPos = categoryPos + stackIndex * (stackThickness + gapSize);
50
51
  configs.push({
51
52
  categoryIndex,
52
- x: stackX,
53
- width: barWidth
53
+ indexPos: stackPos,
54
+ thickness: stackThickness
54
55
  });
55
56
  }
56
57
  }
57
58
  return configs;
58
- }, [xScale, yScale, drawingArea, dataLength, stackIndex, totalStacks, barPadding]);
59
- if (xScale && !isCategoricalScale(xScale)) {
60
- throw new Error('BarStackGroup requires a band scale for x-axis. See https://cds.coinbase.com/components/charts/XAxis/#scale-type');
59
+ }, [xScale, yScale, drawingArea, dataLength, layout, totalStacks, barPadding, stackIndex]);
60
+ const indexScaleComputed = layout !== 'horizontal' ? xScale : yScale;
61
+ const valueScaleComputed = layout !== 'horizontal' ? yScale : xScale;
62
+ if (indexScaleComputed && !isCategoricalScale(indexScaleComputed)) {
63
+ throw new Error("BarStackGroup requires a band scale for " + (layout !== 'horizontal' ? 'x-axis' : 'y-axis') + ". See https://cds.coinbase.com/components/charts/" + (layout !== 'horizontal' ? 'XAxis' : 'YAxis') + "/#scale-type");
61
64
  }
62
- if (!yScale || !drawingArea || stackConfigs.length === 0) return;
63
- return stackConfigs.map(_ref2 => {
65
+ if (!indexScaleComputed || !valueScaleComputed || !drawingArea || stackConfigs.length === 0) return;
66
+
67
+ // In horizontal layout, render stacks in reverse order so top rows (lower categoryIndex)
68
+ // appear on top. Otherwise bottom rows would overlap and obscure top rows during animation.
69
+ const orderedConfigs = layout === 'horizontal' ? [...stackConfigs].reverse() : stackConfigs;
70
+ return orderedConfigs.map(_ref2 => {
64
71
  let {
65
72
  categoryIndex,
66
- x,
67
- width
73
+ indexPos,
74
+ thickness
68
75
  } = _ref2;
69
76
  return /*#__PURE__*/_createElement(BarStack, _extends({}, props, {
70
77
  key: "stack-" + stackIndex + "-category-" + categoryIndex,
71
78
  categoryIndex: categoryIndex,
79
+ indexPos: indexPos,
80
+ indexScale: indexScaleComputed,
72
81
  rect: drawingArea,
73
82
  series: series,
74
- width: width,
75
- x: x,
76
- yScale: yScale
83
+ thickness: thickness,
84
+ valueScale: valueScaleComputed,
85
+ xAxisId: xAxisId,
86
+ yAxisId: yAxisId
77
87
  }));
78
88
  });
79
89
  });