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

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 (100) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dts/chart/CartesianChart.d.ts +39 -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 +5 -5
  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/ScrubberBeaconGroup.d.ts.map +1 -1
  47. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  48. package/dts/chart/utils/axis.d.ts +20 -9
  49. package/dts/chart/utils/axis.d.ts.map +1 -1
  50. package/dts/chart/utils/bar.d.ts +4 -3
  51. package/dts/chart/utils/bar.d.ts.map +1 -1
  52. package/dts/chart/utils/chart.d.ts +13 -0
  53. package/dts/chart/utils/chart.d.ts.map +1 -1
  54. package/dts/chart/utils/context.d.ts +21 -6
  55. package/dts/chart/utils/context.d.ts.map +1 -1
  56. package/dts/chart/utils/gradient.d.ts +3 -1
  57. package/dts/chart/utils/gradient.d.ts.map +1 -1
  58. package/dts/chart/utils/path.d.ts +20 -0
  59. package/dts/chart/utils/path.d.ts.map +1 -1
  60. package/dts/chart/utils/point.d.ts +7 -0
  61. package/dts/chart/utils/point.d.ts.map +1 -1
  62. package/esm/chart/CartesianChart.js +107 -68
  63. package/esm/chart/Path.js +10 -7
  64. package/esm/chart/area/Area.js +19 -9
  65. package/esm/chart/area/AreaChart.js +11 -9
  66. package/esm/chart/area/DottedArea.js +11 -6
  67. package/esm/chart/area/GradientArea.js +11 -6
  68. package/esm/chart/area/SolidArea.js +3 -1
  69. package/esm/chart/area/__stories__/AreaChart.stories.js +30 -2
  70. package/esm/chart/axis/XAxis.js +14 -21
  71. package/esm/chart/axis/YAxis.js +4 -3
  72. package/esm/chart/bar/Bar.js +9 -5
  73. package/esm/chart/bar/BarChart.js +34 -31
  74. package/esm/chart/bar/BarPlot.js +7 -5
  75. package/esm/chart/bar/BarStack.js +176 -36
  76. package/esm/chart/bar/BarStackGroup.js +37 -27
  77. package/esm/chart/bar/DefaultBar.js +24 -8
  78. package/esm/chart/bar/DefaultBarStack.js +24 -10
  79. package/esm/chart/bar/__stories__/BarChart.stories.js +99 -3
  80. package/esm/chart/gradient/Gradient.js +2 -1
  81. package/esm/chart/line/DottedLine.js +3 -1
  82. package/esm/chart/line/Line.js +32 -19
  83. package/esm/chart/line/LineChart.js +9 -8
  84. package/esm/chart/line/SolidLine.js +3 -1
  85. package/esm/chart/line/__stories__/LineChart.stories.js +31 -0
  86. package/esm/chart/point/Point.js +2 -1
  87. package/esm/chart/scrubber/DefaultScrubberBeacon.js +1 -1
  88. package/esm/chart/scrubber/DefaultScrubberLabel.js +26 -10
  89. package/esm/chart/scrubber/Scrubber.js +47 -21
  90. package/esm/chart/scrubber/ScrubberBeaconGroup.js +24 -20
  91. package/esm/chart/scrubber/ScrubberProvider.js +29 -24
  92. package/esm/chart/scrubber/__stories__/Scrubber.stories.js +135 -1
  93. package/esm/chart/utils/axis.js +42 -14
  94. package/esm/chart/utils/bar.js +5 -4
  95. package/esm/chart/utils/chart.js +18 -5
  96. package/esm/chart/utils/context.js +7 -0
  97. package/esm/chart/utils/gradient.js +8 -4
  98. package/esm/chart/utils/path.js +90 -61
  99. package/esm/chart/utils/point.js +28 -18
  100. package/package.json +5 -5
@@ -47,7 +47,7 @@ export const toPointAnchor = placement => {
47
47
  */
48
48
 
49
49
  /**
50
- * Gets a D3 scale based on the axis configuration.
50
+ * Gets a D3 scale based on the cartesian axis configuration.
51
51
  * Handles both numeric (linear/log) and categorical (band) scales.
52
52
  *
53
53
  * For numeric scales, the domain limit controls whether bounds are "nice" (human-friendly)
@@ -57,19 +57,27 @@ export const toPointAnchor = placement => {
57
57
  * @returns The D3 scale function
58
58
  * @throws An Error if bounds are invalid
59
59
  */
60
- export const getAxisScale = _ref => {
60
+ export const getCartesianAxisScale = _ref => {
61
61
  var _config$scaleType;
62
62
  let {
63
63
  config,
64
64
  type,
65
65
  range,
66
- dataDomain
66
+ dataDomain,
67
+ layout = 'vertical'
67
68
  } = _ref;
68
69
  const scaleType = (_config$scaleType = config == null ? void 0 : config.scaleType) != null ? _config$scaleType : 'linear';
69
70
  let adjustedRange = range;
70
71
 
71
- // Invert range for Y axis for SVG coordinate system
72
- if (type === 'y') {
72
+ // Determine if this axis needs range inversion for SVG coordinate system.
73
+ // SVG Y coordinates increase downward, so we need to invert for value axes
74
+ // where we want higher values at the top.
75
+ //
76
+ // For vertical layout: Y axis is the value axis -> invert (higher values at top)
77
+ // For horizontal layout: Y axis is the category axis -> don't invert (first category at top is natural)
78
+ // X axis never needs inversion (left-to-right is natural for both layouts)
79
+ const shouldInvertRange = type === 'y' && layout !== 'horizontal';
80
+ if (shouldInvertRange) {
73
81
  adjustedRange = {
74
82
  min: adjustedRange.max,
75
83
  max: adjustedRange.min
@@ -121,6 +129,8 @@ export const getAxisConfig = function (type, axes, defaultId, defaultScaleType)
121
129
  defaultScaleType = defaultAxisScaleType;
122
130
  }
123
131
  const defaultDomainLimit = type === 'x' ? 'strict' : 'nice';
132
+ const axisName = type === 'x' ? 'x-axis' : 'y-axis';
133
+ const axisDocUrl = type === 'x' ? 'https://cds.coinbase.com/components/charts/XAxis' : 'https://cds.coinbase.com/components/charts/YAxis';
124
134
  if (!axes) {
125
135
  return [{
126
136
  id: defaultId,
@@ -137,16 +147,27 @@ export const getAxisConfig = function (type, axes, defaultId, defaultScaleType)
137
147
  } = _ref2;
138
148
  return id === undefined;
139
149
  })) {
140
- throw new Error('When defining multiple axes, each must have a unique id. See https://cds.coinbase.com/components/charts/YAxis/#multiple-y-axes.');
150
+ throw new Error("When defining multiple " + axisName + ", each must have a unique id. See " + axisDocUrl + ".");
141
151
  }
142
- return axes.map(_ref3 => {
152
+ if (axesLength > 1) {
153
+ const ids = axes.map(_ref3 => {
154
+ let {
155
+ id
156
+ } = _ref3;
157
+ return id;
158
+ }).filter(id => id !== undefined);
159
+ if (new Set(ids).size !== ids.length) {
160
+ throw new Error("When defining multiple " + axisName + ", each must have a unique id. See " + axisDocUrl + ".");
161
+ }
162
+ }
163
+ return axes.map(_ref4 => {
143
164
  let {
144
165
  id
145
- } = _ref3,
146
- axis = _objectWithoutPropertiesLoose(_ref3, _excluded);
166
+ } = _ref4,
167
+ axis = _objectWithoutPropertiesLoose(_ref4, _excluded);
147
168
  return _extends({
148
169
  // defaults the axis id if only a single axis is provided
149
- id: axesLength > 1 ? id != null ? id : defaultAxisId : id,
170
+ id: axesLength > 1 ? id != null ? id : defaultAxisId : id != null ? id : defaultId,
150
171
  scaleType: defaultScaleType,
151
172
  domainLimit: defaultDomainLimit
152
173
  }, axis);
@@ -168,10 +189,14 @@ export const getAxisConfig = function (type, axes, defaultId, defaultScaleType)
168
189
  * @param axisParam - The axis configuration
169
190
  * @param series - Array of series objects (for stacking support)
170
191
  * @param axisType - Whether this is an 'x' or 'y' axis
192
+ * @param layout - The chart layout orientation
171
193
  * @returns The calculated axis bounds
172
194
  */
173
- export const getAxisDomain = (axisParam, series, axisType) => {
195
+ export const getCartesianAxisDomain = function (axisParam, series, axisType, layout) {
174
196
  var _finalDomain$min, _finalDomain$max;
197
+ if (layout === void 0) {
198
+ layout = 'vertical';
199
+ }
175
200
  let dataDomain = null;
176
201
  if (axisParam.data && Array.isArray(axisParam.data) && axisParam.data.length > 0) {
177
202
  const firstItem = axisParam.data[0];
@@ -193,7 +218,10 @@ export const getAxisDomain = (axisParam, series, axisType) => {
193
218
  }
194
219
 
195
220
  // Calculate domain from series data
196
- const seriesDomain = axisType === 'x' ? getChartDomain(series) : getChartRange(series);
221
+ // In vertical layout: X is category (index), Y is value (value)
222
+ // In horizontal layout: Y is category (index), X is value (value)
223
+ const isCategoryAxis = layout !== 'horizontal' && axisType === 'x' || layout === 'horizontal' && axisType === 'y';
224
+ const seriesDomain = isCategoryAxis ? getChartDomain(series) : getChartRange(series);
197
225
 
198
226
  // If data sets the domain, use that instead of the series domain
199
227
  const preferredDataDomain = dataDomain != null ? dataDomain : seriesDomain;
@@ -487,7 +515,7 @@ const generateEvenlyDistributedTicks = (scale, tickInterval, possibleTickValues,
487
515
  * });
488
516
  * // Returns tick positions centered in each selected band
489
517
  */
490
- export const getAxisTicksData = _ref4 => {
518
+ export const getAxisTicksData = _ref5 => {
491
519
  var _options$anchor;
492
520
  let {
493
521
  ticks,
@@ -497,7 +525,7 @@ export const getAxisTicksData = _ref4 => {
497
525
  possibleTickValues,
498
526
  tickInterval,
499
527
  options
500
- } = _ref4;
528
+ } = _ref5;
501
529
  const anchor = (_options$anchor = options == null ? void 0 : options.anchor) != null ? _options$anchor : 'middle';
502
530
 
503
531
  // Handle band scales
@@ -6,7 +6,8 @@ import { defaultTransition } from './transition';
6
6
  /**
7
7
  * A bar-specific transition that extends Transition with stagger support.
8
8
  * When `staggerDelay` is provided, bars will animate with increasing delays
9
- * based on their horizontal position (leftmost starts first, rightmost last).
9
+ * based on their position along the category axis (vertical: left-to-right,
10
+ * horizontal: top-to-bottom).
10
11
  *
11
12
  * @example
12
13
  * // Bars stagger in from left to right over 250ms, each animating for 750ms
@@ -17,10 +18,10 @@ import { defaultTransition } from './transition';
17
18
  * Strips `staggerDelay` from a transition and computes a positional delay.
18
19
  *
19
20
  * @param transition - The transition config (may include staggerDelay)
20
- * @param normalizedX - The bar's normalized x position (0 = left edge, 1 = right edge)
21
+ * @param normalizedPosition - The bar's normalized position along the category axis (0–1)
21
22
  * @returns A standard Transition with computed delay
22
23
  */
23
- export const withStaggerDelayTransition = (transition, normalizedX) => {
24
+ export const withStaggerDelayTransition = (transition, normalizedPosition) => {
24
25
  var _baseTransition$delay;
25
26
  if (!transition) return null;
26
27
  const {
@@ -29,7 +30,7 @@ export const withStaggerDelayTransition = (transition, normalizedX) => {
29
30
  baseTransition = _objectWithoutPropertiesLoose(transition, _excluded);
30
31
  if (!staggerDelay) return transition;
31
32
  return _extends({}, baseTransition, {
32
- delay: ((_baseTransition$delay = baseTransition == null ? void 0 : baseTransition.delay) != null ? _baseTransition$delay : 0) + normalizedX * staggerDelay
33
+ delay: ((_baseTransition$delay = baseTransition == null ? void 0 : baseTransition.delay) != null ? _baseTransition$delay : 0) + normalizedPosition * staggerDelay
33
34
  });
34
35
  };
35
36
 
@@ -46,15 +46,16 @@ export const getChartDomain = (series, min, max) => {
46
46
  };
47
47
 
48
48
  /**
49
- * Creates a composite stack key that includes both stack ID and y-axis ID.
50
- * This ensures series with different y-scales don't get stacked together.
49
+ * Creates a composite stack key that includes stack ID and axis IDs.
50
+ * This ensures series with different scales don't get stacked together.
51
51
  */
52
52
  const createStackKey = series => {
53
53
  if (series.stackId === undefined) return undefined;
54
54
 
55
- // Include y-axis ID to prevent cross-scale stacking
55
+ // Include axis IDs to prevent cross-scale stacking
56
+ const xAxisId = series.xAxisId || 'default';
56
57
  const yAxisId = series.yAxisId || 'default';
57
- return series.stackId + ":" + yAxisId;
58
+ return series.stackId + ":" + xAxisId + ":" + yAxisId;
58
59
  };
59
60
 
60
61
  /**
@@ -225,12 +226,24 @@ export const getChartRange = (series, min, max) => {
225
226
  }
226
227
  return range;
227
228
  };
228
- export const defaultChartInset = {
229
+ export const defaultVerticalLayoutChartInset = {
229
230
  top: 32,
230
231
  left: 16,
231
232
  bottom: 16,
232
233
  right: 16
233
234
  };
235
+ export const defaultHorizontalLayoutChartInset = {
236
+ top: 16,
237
+ left: 16,
238
+ bottom: 16,
239
+ right: 48
240
+ };
241
+
242
+ /**
243
+ * @deprecated Use `defaultVerticalLayoutChartInset` for vertical layout charts or
244
+ * `defaultHorizontalLayoutChartInset` for horizontal layout charts.
245
+ */
246
+ export const defaultChartInset = defaultVerticalLayoutChartInset;
234
247
 
235
248
  /**
236
249
  * Normalize padding to include all sides with a value.
@@ -1,5 +1,12 @@
1
1
  import { createContext, useContext } from 'react';
2
2
 
3
+ /**
4
+ * Chart layout for Cartesian charts.
5
+ * Describes the direction bars/areas grow.
6
+ * - 'vertical': Bars grow vertically (up/down). X is category axis, Y is value axis.
7
+ * - 'horizontal': Bars grow horizontally (left/right). Y is category axis, X is value axis.
8
+ */
9
+
3
10
  /**
4
11
  * Context value for Cartesian (X/Y) coordinate charts.
5
12
  * Contains axis-specific methods and properties for rectangular coordinate systems.
@@ -262,9 +262,13 @@ export const getBaseline = function (axisBounds, baseline) {
262
262
  * @param fill - The color to use for the gradient
263
263
  * @param peakOpacity - Opacity at the peak of the gradient
264
264
  * @param baselineOpacity - Opacity at the baseline
265
- * @returns A gradient definition with y-axis stops in ascending order
265
+ * @param axis - The axis the gradient maps to ('y' for vertical, 'x' for horizontal layout)
266
+ * @returns A gradient definition with stops in ascending order
266
267
  */
267
- export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, baselineOpacity) => {
268
+ export const createGradient = function (axisBounds, baselineValue, fill, peakOpacity, baselineOpacity, axis) {
269
+ if (axis === void 0) {
270
+ axis = 'y';
271
+ }
268
272
  const {
269
273
  min,
270
274
  max
@@ -273,7 +277,7 @@ export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, bas
273
277
  const upperBound = Math.max(min, max);
274
278
  if (lowerBound < baselineValue && baselineValue < upperBound) {
275
279
  return {
276
- axis: 'y',
280
+ axis,
277
281
  stops: [{
278
282
  offset: lowerBound,
279
283
  color: fill,
@@ -291,7 +295,7 @@ export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, bas
291
295
  }
292
296
  const peakValue = Math.abs(min - baselineValue) > Math.abs(max - baselineValue) ? min : max;
293
297
  return {
294
- axis: 'y',
298
+ axis,
295
299
  stops: [{
296
300
  offset: peakValue,
297
301
  color: fill,
@@ -1,5 +1,5 @@
1
- import { area as d3Area, curveBumpX, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore, line as d3Line } from 'd3-shape';
2
- import { projectPoint, projectPoints } from './point';
1
+ import { area as d3Area, curveBumpX, curveBumpY, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveMonotoneY, curveNatural, curveStep, curveStepAfter, curveStepBefore, line as d3Line } from 'd3-shape';
2
+ import { getPointOnScale, projectPoints } from './point';
3
3
  import { isCategoricalScale } from './scale';
4
4
  /**
5
5
  * Default enter transition for path-based components (Line, Area).
@@ -13,18 +13,23 @@ export const defaultPathEnterTransition = {
13
13
  * Get the d3 curve function for a path.
14
14
  * See https://d3js.org/d3-shape/curve
15
15
  * @param curve - The curve type. Defaults to 'linear'.
16
+ * @param layout - The chart layout. Defaults to 'vertical'.
16
17
  * @returns The d3 curve function.
17
18
  */
18
- export const getPathCurveFunction = function (curve) {
19
+ export const getPathCurveFunction = function (curve, layout) {
19
20
  if (curve === void 0) {
20
21
  curve = 'linear';
21
22
  }
23
+ if (layout === void 0) {
24
+ layout = 'vertical';
25
+ }
22
26
  switch (curve) {
23
27
  case 'catmullRom':
24
28
  return curveCatmullRom;
25
29
  case 'monotone':
26
- // When we support layout="vertical" this should dynamically switch to curveMonotoneY
27
- return curveMonotoneX;
30
+ // For vertical layout, X is the independent axis (category/index), so use MonotoneX.
31
+ // For horizontal layout, Y is the independent axis (category/index), so use MonotoneY.
32
+ return layout !== 'horizontal' ? curveMonotoneX : curveMonotoneY;
28
33
  case 'natural':
29
34
  return curveNatural;
30
35
  case 'step':
@@ -34,8 +39,9 @@ export const getPathCurveFunction = function (curve) {
34
39
  case 'stepAfter':
35
40
  return curveStepAfter;
36
41
  case 'bump':
37
- // When we support layout="vertical" this should dynamically switch to curveBumpY
38
- return curveBumpX;
42
+ // For vertical layout, X is the independent axis (category/index), so use BumpX.
43
+ // For horizontal layout, Y is the independent axis (category/index), so use BumpY.
44
+ return layout !== 'horizontal' ? curveBumpX : curveBumpY;
39
45
  case 'linearClosed':
40
46
  return curveLinearClosed;
41
47
  case 'linear':
@@ -61,17 +67,21 @@ export const getLinePath = _ref => {
61
67
  xScale,
62
68
  yScale,
63
69
  xData,
64
- connectNulls = false
70
+ yData,
71
+ connectNulls = false,
72
+ layout = 'vertical'
65
73
  } = _ref;
66
74
  if (data.length === 0) {
67
75
  return '';
68
76
  }
69
- const curveFunction = getPathCurveFunction(curve);
77
+ const curveFunction = getPathCurveFunction(curve, layout);
70
78
  const dataPoints = projectPoints({
71
79
  data,
72
80
  xScale,
73
81
  yScale,
74
- xData
82
+ xData,
83
+ yData,
84
+ layout
75
85
  });
76
86
 
77
87
  // When connectNulls is true, filter out null values before rendering
@@ -110,14 +120,20 @@ export const getAreaPath = _ref2 => {
110
120
  xScale,
111
121
  yScale,
112
122
  xData,
113
- connectNulls = false
123
+ yData,
124
+ connectNulls = false,
125
+ layout = 'vertical'
114
126
  } = _ref2;
115
127
  if (data.length === 0) {
116
128
  return '';
117
129
  }
118
- const curveFunction = getPathCurveFunction(curve);
119
- const yDomain = yScale.domain();
120
- const yMin = Math.min(...yDomain);
130
+ const curveFunction = getPathCurveFunction(curve, layout);
131
+ const categoryAxisIsX = layout !== 'horizontal';
132
+
133
+ // Determine baseline from the value scale.
134
+ const valueScale = categoryAxisIsX ? yScale : xScale;
135
+ const domain = valueScale.domain();
136
+ const min = Math.min(...domain);
121
137
  const normalizedData = data.map((item, index) => {
122
138
  if (item === null) {
123
139
  return null;
@@ -129,7 +145,7 @@ export const getAreaPath = _ref2 => {
129
145
  return null;
130
146
  }
131
147
  if (typeof item === 'number') {
132
- return [yMin, item];
148
+ return [min, item];
133
149
  }
134
150
  return null;
135
151
  });
@@ -137,37 +153,28 @@ export const getAreaPath = _ref2 => {
137
153
  if (range === null) {
138
154
  return {
139
155
  x: 0,
156
+ y: 0,
140
157
  low: null,
141
158
  high: null,
142
159
  isValid: false
143
160
  };
144
161
  }
145
- let xValue = index;
146
- if (!isCategoricalScale(xScale) && xData && xData[index] !== undefined) {
147
- xValue = xData[index];
162
+
163
+ // Determine the position along the independent (index/category) axis.
164
+ let indexValue = index;
165
+ const indexScale = categoryAxisIsX ? xScale : yScale;
166
+ const indexData = categoryAxisIsX ? xData : yData;
167
+ if (!isCategoricalScale(indexScale) && indexData && indexData[index] !== undefined) {
168
+ indexValue = indexData[index];
148
169
  }
149
- const xPoint = projectPoint({
150
- x: xValue,
151
- y: 0,
152
- xScale,
153
- yScale
154
- });
155
- const lowPoint = projectPoint({
156
- x: xValue,
157
- y: range[0],
158
- xScale,
159
- yScale
160
- });
161
- const highPoint = projectPoint({
162
- x: xValue,
163
- y: range[1],
164
- xScale,
165
- yScale
166
- });
170
+ const position = getPointOnScale(indexValue, indexScale);
171
+ const low = getPointOnScale(range[0], valueScale);
172
+ const high = getPointOnScale(range[1], valueScale);
167
173
  return {
168
- x: xPoint.x,
169
- low: lowPoint.y,
170
- high: highPoint.y,
174
+ x: categoryAxisIsX ? position : 0,
175
+ y: !categoryAxisIsX ? position : 0,
176
+ low,
177
+ high,
171
178
  isValid: true
172
179
  };
173
180
  });
@@ -175,16 +182,25 @@ export const getAreaPath = _ref2 => {
175
182
  // When connectNulls is true, filter out invalid points before rendering
176
183
  // When false, use defined() to create gaps in the area
177
184
  const filteredPoints = connectNulls ? dataPoints.filter(d => d.isValid) : dataPoints;
178
- const areaGenerator = d3Area().x(d => d.x).y0(d => {
179
- var _d$low;
180
- return (_d$low = d.low) != null ? _d$low : 0;
181
- }) // Bottom boundary (low values), fallback to 0
182
- .y1(d => {
183
- var _d$high;
184
- return (_d$high = d.high) != null ? _d$high : 0;
185
- }) // Top boundary (high values), fallback to 0
186
- .curve(curveFunction).defined(d => connectNulls || d.isValid && d.low != null && d.high != null); // Only draw where both values exist
187
-
185
+ const areaGenerator = d3Area();
186
+ if (categoryAxisIsX) {
187
+ areaGenerator.x(d => d.x).y0(d => {
188
+ var _d$low;
189
+ return (_d$low = d.low) != null ? _d$low : 0;
190
+ }).y1(d => {
191
+ var _d$high;
192
+ return (_d$high = d.high) != null ? _d$high : 0;
193
+ });
194
+ } else {
195
+ areaGenerator.y(d => d.y).x0(d => {
196
+ var _d$low2;
197
+ return (_d$low2 = d.low) != null ? _d$low2 : 0;
198
+ }).x1(d => {
199
+ var _d$high2;
200
+ return (_d$high2 = d.high) != null ? _d$high2 : 0;
201
+ });
202
+ }
203
+ areaGenerator.curve(curveFunction).defined(d => connectNulls || d.isValid && d.low != null && d.high != null);
188
204
  const result = areaGenerator(filteredPoints);
189
205
  return result != null ? result : '';
190
206
  };
@@ -216,22 +232,35 @@ export const lineToPath = (x1, y1, x2, y2) => {
216
232
  * const roundedPath = getBarPath(10, 20, 50, 100, 8, true, false);
217
233
  * ```
218
234
  */
219
- export const getBarPath = (x, y, width, height, radius, roundTop, roundBottom) => {
235
+ export const getBarPath = function (x, y, width, height, radius, roundTop, roundBottom, layout) {
236
+ if (layout === void 0) {
237
+ layout = 'vertical';
238
+ }
239
+ const barsGrowVertically = layout !== 'horizontal';
220
240
  const roundBothSides = roundTop && roundBottom;
221
241
  const r = Math.min(radius, width / 2, roundBothSides ? height / 2 : height);
222
- const topR = roundTop ? r : 0;
223
- const bottomR = roundBottom ? r : 0;
242
+
243
+ // In vertical layout (bars grow up/down):
244
+ // - roundTop rounds the top face (min Y)
245
+ // - roundBottom rounds the bottom face (max Y)
246
+ // In horizontal layout (bars grow left/right):
247
+ // - roundTop rounds the right face (max X)
248
+ // - roundBottom rounds the left face (min X)
249
+ const rTL = barsGrowVertically ? roundTop ? r : 0 : roundBottom ? r : 0;
250
+ const rTR = barsGrowVertically ? roundTop ? r : 0 : roundTop ? r : 0;
251
+ const rBR = barsGrowVertically ? roundBottom ? r : 0 : roundTop ? r : 0;
252
+ const rBL = barsGrowVertically ? roundBottom ? r : 0 : roundBottom ? r : 0;
224
253
 
225
254
  // Build path with selective rounding
226
- let path = "M " + (x + (roundTop ? r : 0)) + " " + y;
227
- path += " L " + (x + width - topR) + " " + y;
228
- path += " A " + topR + " " + topR + " 0 0 1 " + (x + width) + " " + (y + topR);
229
- path += " L " + (x + width) + " " + (y + height - bottomR);
230
- path += " A " + bottomR + " " + bottomR + " 0 0 1 " + (x + width - bottomR) + " " + (y + height);
231
- path += " L " + (x + bottomR) + " " + (y + height);
232
- path += " A " + bottomR + " " + bottomR + " 0 0 1 " + x + " " + (y + height - bottomR);
233
- path += " L " + x + " " + (y + topR);
234
- path += " A " + topR + " " + topR + " 0 0 1 " + (x + topR) + " " + y;
255
+ let path = "M " + (x + rTL) + " " + y;
256
+ path += " L " + (x + width - rTR) + " " + y;
257
+ path += " A " + rTR + " " + rTR + " 0 0 1 " + (x + width) + " " + (y + rTR);
258
+ path += " L " + (x + width) + " " + (y + height - rBR);
259
+ path += " A " + rBR + " " + rBR + " 0 0 1 " + (x + width - rBR) + " " + (y + height);
260
+ path += " L " + (x + rBL) + " " + (y + height);
261
+ path += " A " + rBL + " " + rBL + " 0 0 1 " + x + " " + (y + height - rBL);
262
+ path += " L " + x + " " + (y + rTL);
263
+ path += " A " + rTL + " " + rTL + " 0 0 1 " + (x + rTL) + " " + y;
235
264
  path += ' Z';
236
265
  return path;
237
266
  };
@@ -1,4 +1,4 @@
1
- import { applyBandScale, applySerializableScale, isCategoricalScale, isLogScale, isNumericScale } from './scale';
1
+ import { applyBandScale, applySerializableScale, isCategoricalScale, isLogScale } from './scale';
2
2
 
3
3
  /**
4
4
  * Position a label should be placed relative to the point
@@ -164,7 +164,8 @@ export const projectPoints = _ref3 => {
164
164
  xScale,
165
165
  yScale,
166
166
  xData,
167
- yData
167
+ yData,
168
+ layout = 'vertical'
168
169
  } = _ref3;
169
170
  if (data.length === 0) {
170
171
  return [];
@@ -182,28 +183,37 @@ export const projectPoints = _ref3 => {
182
183
  });
183
184
  }
184
185
 
185
- // For scales with axis data, determine the correct x value
186
- let xValue = index;
186
+ // Determine values/scales based on role (index vs value) and layout.
187
+ const categoryAxisIsX = layout !== 'horizontal';
188
+ const indexScale = categoryAxisIsX ? xScale : yScale;
189
+ const indexData = categoryAxisIsX ? xData : yData;
187
190
 
188
- // For band scales, always use the index
189
- if (!isCategoricalScale(xScale)) {
190
- // For numeric scales with axis data, use the axis data values instead of indices
191
- if (xData && Array.isArray(xData) && xData.length > 0) {
192
- // Check if it's numeric data
193
- if (typeof xData[0] === 'number') {
194
- var _numericXData$index;
195
- const numericXData = xData;
196
- xValue = (_numericXData$index = numericXData[index]) != null ? _numericXData$index : index;
191
+ // 1. Calculate position along the index axis (categorical or numeric domain).
192
+ let indexValue = index;
193
+ if (!isCategoricalScale(indexScale)) {
194
+ if (indexData && Array.isArray(indexData) && indexData.length > 0) {
195
+ if (typeof indexData[0] === 'number') {
196
+ var _indexData$index;
197
+ indexValue = (_indexData$index = indexData[index]) != null ? _indexData$index : index;
197
198
  }
198
199
  }
199
200
  }
200
- let yValue = value;
201
- if (isNumericScale(yScale) && yData && Array.isArray(yData) && yData.length > 0 && typeof yData[0] === 'number' && typeof value === 'number') {
202
- yValue = value;
201
+
202
+ // 2. Calculate position along the value axis (measured magnitude).
203
+ const valueAsNumber = value;
204
+
205
+ // 3. Project final coordinates based on layout.
206
+ if (categoryAxisIsX) {
207
+ return projectPoint({
208
+ x: indexValue,
209
+ y: valueAsNumber,
210
+ xScale,
211
+ yScale
212
+ });
203
213
  }
204
214
  return projectPoint({
205
- x: xValue,
206
- y: yValue,
215
+ x: valueAsNumber,
216
+ y: indexValue,
207
217
  xScale,
208
218
  yScale
209
219
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinbase/cds-mobile-visualization",
3
- "version": "3.4.0-beta.22",
3
+ "version": "3.4.0-beta.23",
4
4
  "description": "Coinbase Design System - Mobile Visualization Native",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,9 +36,9 @@
36
36
  "CHANGELOG"
37
37
  ],
38
38
  "peerDependencies": {
39
- "@coinbase/cds-common": "^8.49.0",
39
+ "@coinbase/cds-common": "^8.52.0",
40
40
  "@coinbase/cds-lottie-files": "^3.3.4",
41
- "@coinbase/cds-mobile": "^8.49.0",
41
+ "@coinbase/cds-mobile": "^8.52.0",
42
42
  "@coinbase/cds-utils": "^2.3.5",
43
43
  "@shopify/react-native-skia": "^1.12.4 || ^2.0.0",
44
44
  "react": "^18.3.1",
@@ -57,9 +57,9 @@
57
57
  "@babel/preset-env": "^7.28.0",
58
58
  "@babel/preset-react": "^7.27.1",
59
59
  "@babel/preset-typescript": "^7.27.1",
60
- "@coinbase/cds-common": "^8.49.0",
60
+ "@coinbase/cds-common": "^8.52.0",
61
61
  "@coinbase/cds-lottie-files": "^3.3.4",
62
- "@coinbase/cds-mobile": "^8.49.0",
62
+ "@coinbase/cds-mobile": "^8.52.0",
63
63
  "@coinbase/cds-utils": "^2.3.5",
64
64
  "@shopify/react-native-skia": "1.12.4",
65
65
  "@types/react": "^18.3.12",