@coinbase/cds-web-visualization 3.4.0-beta.21 → 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 (102) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dts/chart/CartesianChart.d.ts +23 -4
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/Path.d.ts.map +1 -1
  5. package/dts/chart/PeriodSelector.d.ts +22 -5
  6. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  7. package/dts/chart/area/Area.d.ts +7 -0
  8. package/dts/chart/area/Area.d.ts.map +1 -1
  9. package/dts/chart/area/AreaChart.d.ts +3 -3
  10. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  11. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  12. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  13. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  14. package/dts/chart/axis/Axis.d.ts +10 -10
  15. package/dts/chart/axis/Axis.d.ts.map +1 -1
  16. package/dts/chart/axis/XAxis.d.ts +6 -0
  17. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  18. package/dts/chart/axis/YAxis.d.ts +1 -0
  19. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  20. package/dts/chart/bar/Bar.d.ts +4 -3
  21. package/dts/chart/bar/Bar.d.ts.map +1 -1
  22. package/dts/chart/bar/BarChart.d.ts +25 -5
  23. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  24. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  25. package/dts/chart/bar/BarStack.d.ts +47 -12
  26. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  27. package/dts/chart/bar/BarStackGroup.d.ts +1 -1
  28. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  29. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  30. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  31. package/dts/chart/gradient/Gradient.d.ts +7 -0
  32. package/dts/chart/gradient/Gradient.d.ts.map +1 -1
  33. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  34. package/dts/chart/line/Line.d.ts +7 -0
  35. package/dts/chart/line/Line.d.ts.map +1 -1
  36. package/dts/chart/line/LineChart.d.ts +3 -3
  37. package/dts/chart/line/LineChart.d.ts.map +1 -1
  38. package/dts/chart/line/ReferenceLine.d.ts +1 -0
  39. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  40. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  41. package/dts/chart/point/Point.d.ts +7 -0
  42. package/dts/chart/point/Point.d.ts.map +1 -1
  43. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -1
  44. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +2 -1
  45. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -1
  46. package/dts/chart/scrubber/Scrubber.d.ts +8 -0
  47. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  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 +23 -8
  51. package/dts/chart/utils/axis.d.ts.map +1 -1
  52. package/dts/chart/utils/bar.d.ts +6 -5
  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 +20 -4
  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/dts/chart/utils/transition.d.ts +3 -3
  65. package/dts/chart/utils/transition.d.ts.map +1 -1
  66. package/esm/chart/CartesianChart.js +89 -57
  67. package/esm/chart/Path.js +21 -6
  68. package/esm/chart/area/Area.js +19 -9
  69. package/esm/chart/area/AreaChart.js +23 -25
  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/axis/XAxis.js +11 -12
  74. package/esm/chart/axis/YAxis.js +4 -4
  75. package/esm/chart/bar/Bar.js +11 -5
  76. package/esm/chart/bar/BarChart.js +34 -31
  77. package/esm/chart/bar/BarPlot.js +6 -3
  78. package/esm/chart/bar/BarStack.js +155 -356
  79. package/esm/chart/bar/BarStackGroup.js +36 -27
  80. package/esm/chart/bar/DefaultBar.js +26 -10
  81. package/esm/chart/bar/DefaultBarStack.js +27 -13
  82. package/esm/chart/gradient/Gradient.js +3 -2
  83. package/esm/chart/line/DottedLine.js +3 -1
  84. package/esm/chart/line/Line.js +29 -16
  85. package/esm/chart/line/LineChart.js +12 -11
  86. package/esm/chart/line/SolidLine.js +3 -1
  87. package/esm/chart/point/Point.js +3 -2
  88. package/esm/chart/scrubber/DefaultScrubberBeacon.js +3 -3
  89. package/esm/chart/scrubber/DefaultScrubberLabel.js +26 -8
  90. package/esm/chart/scrubber/Scrubber.js +36 -28
  91. package/esm/chart/scrubber/ScrubberBeaconGroup.js +49 -32
  92. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +1 -1
  93. package/esm/chart/scrubber/ScrubberProvider.js +44 -39
  94. package/esm/chart/utils/axis.js +44 -13
  95. package/esm/chart/utils/bar.js +6 -4
  96. package/esm/chart/utils/chart.js +18 -5
  97. package/esm/chart/utils/context.js +7 -0
  98. package/esm/chart/utils/gradient.js +6 -4
  99. package/esm/chart/utils/path.js +87 -61
  100. package/esm/chart/utils/point.js +30 -21
  101. package/esm/chart/utils/transition.js +8 -3
  102. package/package.json +5 -5
@@ -50,8 +50,11 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
50
50
  scrubberPosition
51
51
  } = useScrubberContext();
52
52
  const {
53
+ layout,
53
54
  getXScale,
55
+ getYScale,
54
56
  getXAxis,
57
+ getYAxis,
55
58
  animate,
56
59
  series,
57
60
  drawingArea,
@@ -73,30 +76,31 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
73
76
  return seriesIds;
74
77
  }, [series, seriesIds]);
75
78
  const {
76
- dataX,
79
+ dataValue,
77
80
  dataIndex
78
81
  } = useMemo(() => {
79
- const xScale = getXScale();
80
- const xAxis = getXAxis();
81
- if (!xScale) return {
82
- dataX: undefined,
82
+ const categoryAxisIsX = layout !== 'horizontal';
83
+ const indexScale = categoryAxisIsX ? getXScale() : getYScale();
84
+ const indexAxis = categoryAxisIsX ? getXAxis() : getYAxis();
85
+ if (!indexScale) return {
86
+ dataValue: undefined,
83
87
  dataIndex: undefined
84
88
  };
85
89
  const dataIndex = scrubberPosition !== null && scrubberPosition !== void 0 ? scrubberPosition : Math.max(0, dataLength - 1);
86
90
 
87
- // Convert index to actual x value if axis has data
88
- let dataX;
89
- if (xAxis !== null && xAxis !== void 0 && xAxis.data && Array.isArray(xAxis.data) && xAxis.data[dataIndex] !== undefined) {
90
- const dataValue = xAxis.data[dataIndex];
91
- dataX = typeof dataValue === 'string' ? dataIndex : dataValue;
91
+ // Convert index to actual data value if axis has data
92
+ let dataValue;
93
+ if (indexAxis !== null && indexAxis !== void 0 && indexAxis.data && Array.isArray(indexAxis.data) && indexAxis.data[dataIndex] !== undefined) {
94
+ const val = indexAxis.data[dataIndex];
95
+ dataValue = typeof val === 'string' ? dataIndex : val;
92
96
  } else {
93
- dataX = dataIndex;
97
+ dataValue = dataIndex;
94
98
  }
95
99
  return {
96
- dataX,
100
+ dataValue,
97
101
  dataIndex
98
102
  };
99
- }, [getXScale, getXAxis, scrubberPosition, dataLength]);
103
+ }, [getXScale, getYScale, getXAxis, getYAxis, scrubberPosition, dataLength, layout]);
100
104
 
101
105
  // Compute resolved accessibility label
102
106
  const resolvedAccessibilityLabel = useMemo(() => {
@@ -120,11 +124,12 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
120
124
  }))) !== null && _series$filter$filter !== void 0 ? _series$filter$filter : [];
121
125
  }, [series, filteredSeriesIds]);
122
126
  const groupEnterTransition = useMemo(() => getTransition(transitions === null || transitions === void 0 ? void 0 : transitions.enter, animate, defaultAccessoryEnterTransition), [transitions === null || transitions === void 0 ? void 0 : transitions.enter, animate]);
123
-
124
- // Check if we have at least the default X scale
125
- const defaultXScale = getXScale();
126
- if (!defaultXScale) return null;
127
- const pixelX = dataX !== undefined && defaultXScale ? getPointOnScale(dataX, defaultXScale) : undefined;
127
+ const shouldAnimateGroup = animate && groupEnterTransition !== null;
128
+ const categoryAxisIsX = layout !== 'horizontal';
129
+ const showBeaconLabels = !hideBeaconLabels && categoryAxisIsX && beaconLabels.length > 0;
130
+ const indexScale = categoryAxisIsX ? getXScale() : getYScale();
131
+ if (!indexScale) return null;
132
+ const pixelPos = dataValue !== undefined && indexScale ? getPointOnScale(dataValue, indexScale) : undefined;
128
133
  return /*#__PURE__*/_jsxs(motion.g, _objectSpread(_objectSpread({
129
134
  "aria-atomic": "true",
130
135
  "aria-label": resolvedAccessibilityLabel,
@@ -132,7 +137,7 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
132
137
  "data-component": "scrubber-group",
133
138
  "data-testid": testID,
134
139
  role: "status"
135
- }, animate ? {
140
+ }, shouldAnimateGroup ? {
136
141
  animate: {
137
142
  opacity: 1,
138
143
  transition: groupEnterTransition
@@ -141,23 +146,22 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
141
146
  opacity: 0
142
147
  }
143
148
  } : {}), {}, {
144
- children: [!hideOverlay && scrubberPosition !== undefined && pixelX !== undefined && /*#__PURE__*/_jsx("rect", {
149
+ children: [!hideOverlay && scrubberPosition !== undefined && pixelPos !== undefined && /*#__PURE__*/_jsx("rect", {
145
150
  className: classNames === null || classNames === void 0 ? void 0 : classNames.overlay,
146
151
  fill: "var(--color-bg)",
147
- height: drawingArea.height + overlayOffset * 2,
152
+ height: categoryAxisIsX ? drawingArea.height + overlayOffset * 2 : drawingArea.y + drawingArea.height - pixelPos + overlayOffset,
148
153
  opacity: 0.8,
149
154
  style: styles === null || styles === void 0 ? void 0 : styles.overlay,
150
- width: drawingArea.x + drawingArea.width - pixelX + overlayOffset,
151
- x: pixelX,
152
- y: drawingArea.y - overlayOffset
153
- }), !hideLine && scrubberPosition !== undefined && dataX !== undefined && /*#__PURE__*/_jsx(ReferenceLine, {
155
+ width: categoryAxisIsX ? drawingArea.x + drawingArea.width - pixelPos + overlayOffset : drawingArea.width + overlayOffset * 2,
156
+ x: categoryAxisIsX ? pixelPos : drawingArea.x - overlayOffset,
157
+ y: categoryAxisIsX ? drawingArea.y - overlayOffset : pixelPos
158
+ }), !hideLine && scrubberPosition !== undefined && dataValue !== undefined && dataIndex !== undefined && /*#__PURE__*/_jsx(ReferenceLine, _objectSpread({
154
159
  LabelComponent: LabelComponent,
155
160
  LineComponent: LineComponent,
156
161
  classNames: {
157
162
  label: classNames === null || classNames === void 0 ? void 0 : classNames.label,
158
163
  line: classNames === null || classNames === void 0 ? void 0 : classNames.line
159
164
  },
160
- dataX: dataX,
161
165
  label: typeof label === 'function' ? label(dataIndex) : label,
162
166
  labelBoundsInset: labelBoundsInset,
163
167
  labelElevated: labelElevated,
@@ -167,7 +171,11 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
167
171
  label: styles === null || styles === void 0 ? void 0 : styles.label,
168
172
  line: styles === null || styles === void 0 ? void 0 : styles.line
169
173
  }
170
- }), /*#__PURE__*/_jsx(ScrubberBeaconGroup, {
174
+ }, categoryAxisIsX ? {
175
+ dataX: dataValue
176
+ } : {
177
+ dataY: dataValue
178
+ })), /*#__PURE__*/_jsx(ScrubberBeaconGroup, {
171
179
  ref: beaconGroupRef,
172
180
  BeaconComponent: BeaconComponent,
173
181
  className: classNames === null || classNames === void 0 ? void 0 : classNames.beacon,
@@ -177,7 +185,7 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
177
185
  style: styles === null || styles === void 0 ? void 0 : styles.beacon,
178
186
  testID: testID,
179
187
  transitions: transitions
180
- }), !hideBeaconLabels && beaconLabels.length > 0 && /*#__PURE__*/_jsx(ScrubberBeaconLabelGroup, {
188
+ }), showBeaconLabels && /*#__PURE__*/_jsx(ScrubberBeaconLabelGroup, {
181
189
  BeaconLabelComponent: BeaconLabelComponent,
182
190
  className: classNames === null || classNames === void 0 ? void 0 : classNames.beaconLabel,
183
191
  labelFont: beaconLabelFont,
@@ -9,7 +9,7 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
9
9
  let {
10
10
  seriesId,
11
11
  dataIndex,
12
- dataX,
12
+ dataIndexValue,
13
13
  isIdle,
14
14
  BeaconComponent,
15
15
  idlePulse,
@@ -22,23 +22,26 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
22
22
  stroke
23
23
  } = _ref;
24
24
  const {
25
+ layout,
25
26
  getSeries,
26
27
  getSeriesData,
27
28
  getXScale,
28
- getYScale
29
+ getYScale,
30
+ getXAxis,
31
+ getYAxis
29
32
  } = useCartesianChartContext();
30
33
  const series = useMemo(() => getSeries(seriesId), [getSeries, seriesId]);
31
34
  const sourceData = useMemo(() => getSeriesData(seriesId), [getSeriesData, seriesId]);
32
35
  const gradient = series === null || series === void 0 ? void 0 : series.gradient;
33
36
 
34
- // Get dataY from series data
35
- const dataY = useMemo(() => {
37
+ // Get dataValue from series data
38
+ const dataValue = useMemo(() => {
36
39
  if (sourceData && dataIndex >= 0 && dataIndex < sourceData.length) {
37
- const dataValue = sourceData[dataIndex];
38
- if (typeof dataValue === 'number') {
39
- return dataValue;
40
- } else if (Array.isArray(dataValue)) {
41
- const validValues = dataValue.filter(val => val !== null);
40
+ const value = sourceData[dataIndex];
41
+ if (typeof value === 'number') {
42
+ return value;
43
+ } else if (Array.isArray(value)) {
44
+ const validValues = value.filter(val => val !== null);
42
45
  if (validValues.length >= 1) {
43
46
  return validValues[validValues.length - 1];
44
47
  }
@@ -50,18 +53,27 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
50
53
  // Evaluate gradient color
51
54
  const color = useMemo(() => {
52
55
  var _series$color, _series$color2;
53
- if (dataY === undefined) return (_series$color = series === null || series === void 0 ? void 0 : series.color) !== null && _series$color !== void 0 ? _series$color : 'var(--color-fgPrimary)';
56
+ if (dataValue === undefined) return (_series$color = series === null || series === void 0 ? void 0 : series.color) !== null && _series$color !== void 0 ? _series$color : 'var(--color-fgPrimary)';
54
57
  if (gradient) {
55
- const xScale = getXScale();
58
+ const xScale = getXScale(series === null || series === void 0 ? void 0 : series.xAxisId);
56
59
  const yScale = getYScale(series === null || series === void 0 ? void 0 : series.yAxisId);
57
60
  if (xScale && yScale) {
61
+ const categoryAxisIsX = layout !== 'horizontal';
58
62
  const gradientScale = gradient.axis === 'x' ? xScale : yScale;
59
63
  const stops = getGradientConfig(gradient, xScale, yScale);
60
64
  if (stops) {
61
65
  var _gradient$axis;
62
66
  const gradientAxis = (_gradient$axis = gradient.axis) !== null && _gradient$axis !== void 0 ? _gradient$axis : 'y';
63
- const dataValue = gradientAxis === 'x' ? dataX : dataY;
64
- const evaluatedColor = evaluateGradientAtValue(stops, dataValue, gradientScale);
67
+ // Determine the correct data value to evaluate against based on gradient axis and layout
68
+ let evalValue;
69
+ if (gradientAxis === 'x') {
70
+ // X-axis gradient: In vertical it's the index, in horizontal it's the value.
71
+ evalValue = categoryAxisIsX ? dataIndexValue : dataValue;
72
+ } else {
73
+ // Y-axis gradient: In vertical it's the value, in horizontal it's the index.
74
+ evalValue = categoryAxisIsX ? dataValue : dataIndexValue;
75
+ }
76
+ const evaluatedColor = evaluateGradientAtValue(stops, evalValue, gradientScale);
65
77
  if (evaluatedColor) {
66
78
  return evaluatedColor;
67
79
  }
@@ -69,15 +81,16 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
69
81
  }
70
82
  }
71
83
  return (_series$color2 = series === null || series === void 0 ? void 0 : series.color) !== null && _series$color2 !== void 0 ? _series$color2 : 'var(--color-fgPrimary)';
72
- }, [gradient, dataX, dataY, series === null || series === void 0 ? void 0 : series.color, series === null || series === void 0 ? void 0 : series.yAxisId, getXScale, getYScale]);
73
- if (dataY === undefined) return null;
84
+ }, [gradient, dataIndexValue, dataValue, series === null || series === void 0 ? void 0 : series.color, series === null || series === void 0 ? void 0 : series.xAxisId, series === null || series === void 0 ? void 0 : series.yAxisId, getXScale, getYScale, layout]);
85
+ if (dataValue === undefined) return null;
86
+ const categoryAxisIsX = layout !== 'horizontal';
74
87
  return /*#__PURE__*/_jsx(BeaconComponent, {
75
88
  ref: beaconRef,
76
89
  animate: animate,
77
90
  className: className,
78
91
  color: color,
79
- dataX: dataX,
80
- dataY: dataY,
92
+ dataX: categoryAxisIsX ? dataIndexValue : dataValue,
93
+ dataY: categoryAxisIsX ? dataValue : dataIndexValue,
81
94
  idlePulse: idlePulse,
82
95
  isIdle: isIdle,
83
96
  seriesId: seriesId,
@@ -103,8 +116,11 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
103
116
  scrubberPosition
104
117
  } = useScrubberContext();
105
118
  const {
119
+ layout,
106
120
  getXScale,
121
+ getYScale,
107
122
  getXAxis,
123
+ getYAxis,
108
124
  dataLength,
109
125
  series,
110
126
  animate
@@ -123,30 +139,31 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
123
139
  return (_series$filter = series === null || series === void 0 ? void 0 : series.filter(s => seriesIds.includes(s.id))) !== null && _series$filter !== void 0 ? _series$filter : [];
124
140
  }, [series, seriesIds]);
125
141
  const {
126
- dataX,
142
+ dataIndexValue,
127
143
  dataIndex
128
144
  } = useMemo(() => {
129
- const xScale = getXScale();
130
- const xAxis = getXAxis();
131
- if (!xScale) return {
132
- dataX: undefined,
145
+ const categoryAxisIsX = layout !== 'horizontal';
146
+ const indexScale = categoryAxisIsX ? getXScale() : getYScale();
147
+ const indexAxis = categoryAxisIsX ? getXAxis() : getYAxis();
148
+ if (!indexScale) return {
149
+ dataIndexValue: undefined,
133
150
  dataIndex: undefined
134
151
  };
135
152
  const dataIndex = scrubberPosition !== null && scrubberPosition !== void 0 ? scrubberPosition : Math.max(0, dataLength - 1);
136
153
 
137
- // Convert index to actual x value if axis has data
138
- let dataX;
139
- if (xAxis !== null && xAxis !== void 0 && xAxis.data && Array.isArray(xAxis.data) && xAxis.data[dataIndex] !== undefined) {
140
- const dataValue = xAxis.data[dataIndex];
141
- dataX = typeof dataValue === 'string' ? dataIndex : dataValue;
154
+ // Convert index to actual data value if axis has data
155
+ let dataIndexValue;
156
+ if (indexAxis !== null && indexAxis !== void 0 && indexAxis.data && Array.isArray(indexAxis.data) && indexAxis.data[dataIndex] !== undefined) {
157
+ const val = indexAxis.data[dataIndex];
158
+ dataIndexValue = typeof val === 'string' ? dataIndex : val;
142
159
  } else {
143
- dataX = dataIndex;
160
+ dataIndexValue = dataIndex;
144
161
  }
145
162
  return {
146
- dataX,
163
+ dataIndexValue,
147
164
  dataIndex
148
165
  };
149
- }, [getXScale, getXAxis, scrubberPosition, dataLength]);
166
+ }, [getXScale, getYScale, getXAxis, getYAxis, scrubberPosition, dataLength, layout]);
150
167
  const isIdle = scrubberPosition === undefined;
151
168
  const createBeaconRef = useCallback(seriesId => {
152
169
  return beaconRef => {
@@ -155,14 +172,14 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
155
172
  }
156
173
  };
157
174
  }, [ScrubberBeaconRefs]);
158
- if (dataX === undefined || dataIndex === undefined) return null;
175
+ if (dataIndexValue === undefined || dataIndex === undefined) return null;
159
176
  return filteredSeries.map(s => /*#__PURE__*/_jsx(BeaconWithData, {
160
177
  BeaconComponent: BeaconComponent,
161
178
  animate: animate,
162
179
  beaconRef: createBeaconRef(s.id),
163
180
  className: className,
164
181
  dataIndex: dataIndex,
165
- dataX: dataX,
182
+ dataIndexValue: dataIndexValue,
166
183
  idlePulse: idlePulse,
167
184
  isIdle: isIdle,
168
185
  seriesId: s.id,
@@ -46,7 +46,7 @@ const PositionedLabel = /*#__PURE__*/memo(_ref => {
46
46
  onDimensionsChange: d => onDimensionsChange(seriesId, d),
47
47
  seriesId: seriesId,
48
48
  style: style,
49
- transition: updateTransition,
49
+ transition: updateTransition !== null && updateTransition !== void 0 ? updateTransition : instantTransition,
50
50
  x: x,
51
51
  y: y
52
52
  });
@@ -18,25 +18,29 @@ export const ScrubberProvider = _ref => {
18
18
  throw new Error('ScrubberProvider must be used within a ChartContext');
19
19
  }
20
20
  const {
21
+ layout,
21
22
  getXScale,
23
+ getYScale,
22
24
  getXAxis,
25
+ getYAxis,
23
26
  series
24
27
  } = chartContext;
25
28
  const [scrubberPosition, setScrubberPosition] = useState(undefined);
26
- const getDataIndexFromX = useCallback(mouseX => {
27
- const xScale = getXScale();
28
- const xAxis = getXAxis();
29
- if (!xScale || !xAxis) return 0;
30
- if (isCategoricalScale(xScale)) {
31
- var _ref2, _xScale$domain, _xScale$domain2, _xScale$bandwidth, _xScale$bandwidth2;
32
- const categories = (_ref2 = (_xScale$domain = (_xScale$domain2 = xScale.domain) === null || _xScale$domain2 === void 0 ? void 0 : _xScale$domain2.call(xScale)) !== null && _xScale$domain !== void 0 ? _xScale$domain : xAxis.data) !== null && _ref2 !== void 0 ? _ref2 : [];
33
- const bandwidth = (_xScale$bandwidth = (_xScale$bandwidth2 = xScale.bandwidth) === null || _xScale$bandwidth2 === void 0 ? void 0 : _xScale$bandwidth2.call(xScale)) !== null && _xScale$bandwidth !== void 0 ? _xScale$bandwidth : 0;
29
+ const getDataIndexFromPosition = useCallback(mousePosition => {
30
+ const categoryAxisIsX = layout !== 'horizontal';
31
+ const categoryScale = categoryAxisIsX ? getXScale() : getYScale();
32
+ const categoryAxis = categoryAxisIsX ? getXAxis() : getYAxis();
33
+ if (!categoryScale || !categoryAxis) return 0;
34
+ if (isCategoricalScale(categoryScale)) {
35
+ var _ref2, _categoryScale$domain, _categoryScale$domain2, _categoryScale$bandwi, _categoryScale$bandwi2;
36
+ const categories = (_ref2 = (_categoryScale$domain = (_categoryScale$domain2 = categoryScale.domain) === null || _categoryScale$domain2 === void 0 ? void 0 : _categoryScale$domain2.call(categoryScale)) !== null && _categoryScale$domain !== void 0 ? _categoryScale$domain : categoryAxis.data) !== null && _ref2 !== void 0 ? _ref2 : [];
37
+ const bandwidth = (_categoryScale$bandwi = (_categoryScale$bandwi2 = categoryScale.bandwidth) === null || _categoryScale$bandwi2 === void 0 ? void 0 : _categoryScale$bandwi2.call(categoryScale)) !== null && _categoryScale$bandwi !== void 0 ? _categoryScale$bandwi : 0;
34
38
  let closestIndex = 0;
35
39
  let closestDistance = Infinity;
36
40
  for (let i = 0; i < categories.length; i++) {
37
- const xPos = xScale(i);
38
- if (xPos !== undefined) {
39
- const distance = Math.abs(mouseX - (xPos + bandwidth / 2));
41
+ const pos = categoryScale(i);
42
+ if (pos !== undefined) {
43
+ const distance = Math.abs(mousePosition - (pos + bandwidth / 2));
40
44
  if (distance < closestDistance) {
41
45
  closestDistance = distance;
42
46
  closestIndex = i;
@@ -46,17 +50,17 @@ export const ScrubberProvider = _ref => {
46
50
  return closestIndex;
47
51
  } else {
48
52
  // For numeric scales with axis data, find the nearest data point
49
- const axisData = xAxis.data;
53
+ const axisData = categoryAxis.data;
50
54
  if (axisData && Array.isArray(axisData) && typeof axisData[0] === 'number') {
51
55
  // We have numeric axis data - find the closest data point
52
56
  const numericData = axisData;
53
57
  let closestIndex = 0;
54
58
  let closestDistance = Infinity;
55
59
  for (let i = 0; i < numericData.length; i++) {
56
- const xValue = numericData[i];
57
- const xPos = xScale(xValue);
58
- if (xPos !== undefined) {
59
- const distance = Math.abs(mouseX - xPos);
60
+ const dataValue = numericData[i];
61
+ const pos = categoryScale(dataValue);
62
+ if (pos !== undefined) {
63
+ const distance = Math.abs(mousePosition - pos);
60
64
  if (distance < closestDistance) {
61
65
  closestDistance = distance;
62
66
  closestIndex = i;
@@ -66,26 +70,26 @@ export const ScrubberProvider = _ref => {
66
70
  return closestIndex;
67
71
  } else {
68
72
  var _domain$min, _domain$max;
69
- const xValue = xScale.invert(mouseX);
70
- const dataIndex = Math.round(xValue);
71
- const domain = xAxis.domain;
72
- return Math.max((_domain$min = domain.min) !== null && _domain$min !== void 0 ? _domain$min : 0, Math.min(dataIndex, (_domain$max = domain.max) !== null && _domain$max !== void 0 ? _domain$max : 0));
73
+ const dataValue = categoryScale.invert(mousePosition);
74
+ const dataIndexVal = Math.round(dataValue);
75
+ const domain = categoryAxis.domain;
76
+ return Math.max((_domain$min = domain.min) !== null && _domain$min !== void 0 ? _domain$min : 0, Math.min(dataIndexVal, (_domain$max = domain.max) !== null && _domain$max !== void 0 ? _domain$max : 0));
73
77
  }
74
78
  }
75
- }, [getXScale, getXAxis]);
76
- const handlePointerMove = useCallback((clientX, target) => {
79
+ }, [layout, getXScale, getYScale, getXAxis, getYAxis]);
80
+ const handlePointerMove = useCallback((clientX, clientY, target) => {
77
81
  if (!enableScrubbing || !series || series.length === 0) return;
78
82
  const rect = target.getBoundingClientRect();
79
- const x = clientX - rect.left;
80
- const dataIndex = getDataIndexFromX(x);
83
+ const position = layout === 'horizontal' ? clientY - rect.top : clientX - rect.left;
84
+ const dataIndex = getDataIndexFromPosition(position);
81
85
  if (dataIndex !== scrubberPosition) {
82
86
  setScrubberPosition(dataIndex);
83
87
  onScrubberPositionChange === null || onScrubberPositionChange === void 0 || onScrubberPositionChange(dataIndex);
84
88
  }
85
- }, [enableScrubbing, series, getDataIndexFromX, scrubberPosition, onScrubberPositionChange]);
89
+ }, [enableScrubbing, series, layout, getDataIndexFromPosition, scrubberPosition, onScrubberPositionChange]);
86
90
  const handleMouseMove = useCallback(event => {
87
91
  const target = event.currentTarget;
88
- handlePointerMove(event.clientX, target);
92
+ handlePointerMove(event.clientX, event.clientY, target);
89
93
  }, [handlePointerMove]);
90
94
  const handleTouchMove = useCallback(event => {
91
95
  if (!event.touches.length) return;
@@ -93,14 +97,14 @@ export const ScrubberProvider = _ref => {
93
97
  event.preventDefault();
94
98
  const touch = event.touches[0];
95
99
  const target = event.currentTarget;
96
- handlePointerMove(touch.clientX, target);
100
+ handlePointerMove(touch.clientX, touch.clientY, target);
97
101
  }, [handlePointerMove]);
98
102
  const handleTouchStart = useCallback(event => {
99
103
  if (!enableScrubbing || !event.touches.length) return;
100
104
  // Handle initial touch
101
105
  const touch = event.touches[0];
102
106
  const target = event.currentTarget;
103
- handlePointerMove(touch.clientX, target);
107
+ handlePointerMove(touch.clientX, touch.clientY, target);
104
108
  }, [enableScrubbing, handlePointerMove]);
105
109
  const handlePointerLeave = useCallback(() => {
106
110
  if (!enableScrubbing) return;
@@ -111,25 +115,26 @@ export const ScrubberProvider = _ref => {
111
115
  const handleTouchEnd = handlePointerLeave;
112
116
  const handleKeyDown = useCallback(event => {
113
117
  if (!enableScrubbing) return;
114
- const xScale = getXScale();
115
- const xAxis = getXAxis();
116
- if (!xScale || !xAxis) return;
117
- const isBand = isCategoricalScale(xScale);
118
+ const categoryAxisIsX = layout !== 'horizontal';
119
+ const categoryScale = categoryAxisIsX ? getXScale() : getYScale();
120
+ const categoryAxis = categoryAxisIsX ? getXAxis() : getYAxis();
121
+ if (!categoryScale || !categoryAxis) return;
122
+ const isBand = isCategoricalScale(categoryScale);
118
123
 
119
124
  // Determine the actual data indices we can navigate to
120
125
  let minIndex;
121
126
  let maxIndex;
122
127
  let dataPoints;
123
128
  if (isBand) {
124
- var _ref3, _xScale$domain3, _xScale$domain4;
129
+ var _ref3, _categoryScale$domain3, _categoryScale$domain4;
125
130
  // For categorical scales, use the categories
126
- const categories = (_ref3 = (_xScale$domain3 = (_xScale$domain4 = xScale.domain) === null || _xScale$domain4 === void 0 ? void 0 : _xScale$domain4.call(xScale)) !== null && _xScale$domain3 !== void 0 ? _xScale$domain3 : xAxis.data) !== null && _ref3 !== void 0 ? _ref3 : [];
131
+ const categories = (_ref3 = (_categoryScale$domain3 = (_categoryScale$domain4 = categoryScale.domain) === null || _categoryScale$domain4 === void 0 ? void 0 : _categoryScale$domain4.call(categoryScale)) !== null && _categoryScale$domain3 !== void 0 ? _categoryScale$domain3 : categoryAxis.data) !== null && _ref3 !== void 0 ? _ref3 : [];
127
132
  minIndex = 0;
128
133
  maxIndex = Math.max(0, categories.length - 1);
129
134
  dataPoints = categories.length;
130
135
  } else {
131
136
  // For numeric scales, check if we have specific data points
132
- const axisData = xAxis.data;
137
+ const axisData = categoryAxis.data;
133
138
  if (axisData && Array.isArray(axisData)) {
134
139
  // We have specific data points - use their indices
135
140
  minIndex = 0;
@@ -138,7 +143,7 @@ export const ScrubberProvider = _ref => {
138
143
  } else {
139
144
  var _domain$min2, _domain$max2;
140
145
  // Fall back to domain-based navigation for continuous scales without specific data
141
- const domain = xAxis.domain;
146
+ const domain = categoryAxis.domain;
142
147
  minIndex = (_domain$min2 = domain.min) !== null && _domain$min2 !== void 0 ? _domain$min2 : 0;
143
148
  maxIndex = (_domain$max2 = domain.max) !== null && _domain$max2 !== void 0 ? _domain$max2 : 0;
144
149
  dataPoints = maxIndex - minIndex + 1;
@@ -152,11 +157,11 @@ export const ScrubberProvider = _ref => {
152
157
  const stepSize = multiSkip ? Math.min(10, Math.max(1, Math.floor(dataRange * 0.1))) : 1;
153
158
  let newIndex;
154
159
  switch (event.key) {
155
- case 'ArrowLeft':
160
+ case categoryAxisIsX ? 'ArrowLeft' : 'ArrowUp':
156
161
  event.preventDefault();
157
162
  newIndex = Math.max(minIndex, currentIndex - stepSize);
158
163
  break;
159
- case 'ArrowRight':
164
+ case categoryAxisIsX ? 'ArrowRight' : 'ArrowDown':
160
165
  event.preventDefault();
161
166
  newIndex = Math.min(maxIndex, currentIndex + stepSize);
162
167
  break;
@@ -180,7 +185,7 @@ export const ScrubberProvider = _ref => {
180
185
  setScrubberPosition(newIndex);
181
186
  onScrubberPositionChange === null || onScrubberPositionChange === void 0 || onScrubberPositionChange(newIndex);
182
187
  }
183
- }, [enableScrubbing, getXScale, getXAxis, scrubberPosition, onScrubberPositionChange]);
188
+ }, [enableScrubbing, layout, getXScale, getYScale, getXAxis, getYAxis, scrubberPosition, onScrubberPositionChange]);
184
189
  const handleBlur = useCallback(() => {
185
190
  if (!enableScrubbing || scrubberPosition === undefined) return;
186
191
  setScrubberPosition(undefined);
@@ -58,23 +58,36 @@ export const toPointAnchor = placement => {
58
58
  * For numeric scales, the domain limit controls whether bounds are "nice" (human-friendly)
59
59
  * or "strict" (exact min/max). Range can be customized using function-based configuration.
60
60
  *
61
+ * Range inversion is determined by axis role (category vs value) and layout:
62
+ * - Vertical layout: Y axis (value) is inverted for SVG coordinate system
63
+ * - Horizontal layout: Y axis (category) is inverted (first category at top)
64
+ *
61
65
  * @param params - Scale parameters
62
66
  * @returns The D3 scale function
63
67
  * @throws An Error if bounds are invalid
64
68
  */
65
- export const getAxisScale = _ref => {
69
+ export const getCartesianAxisScale = _ref => {
66
70
  var _config$scaleType;
67
71
  let {
68
72
  config,
69
73
  type,
70
74
  range,
71
- dataDomain
75
+ dataDomain,
76
+ layout = 'vertical'
72
77
  } = _ref;
73
78
  const scaleType = (_config$scaleType = config === null || config === void 0 ? void 0 : config.scaleType) !== null && _config$scaleType !== void 0 ? _config$scaleType : 'linear';
74
79
  let adjustedRange = range;
75
80
 
76
- // Invert range for Y axis for SVG coordinate system
77
- if (type === 'y') {
81
+ // Determine if this axis needs range inversion for SVG coordinate system.
82
+ // SVG Y coordinates increase downward, so we need to invert for value axes
83
+ // where we want higher values at the top.
84
+ //
85
+ // For vertical layout: Y axis is the value axis → invert (higher values at top)
86
+ // For horizontal layout: Y axis is the category axis → don't invert (first category at top is natural)
87
+ // X axis never needs inversion (left-to-right is natural for both layouts)
88
+
89
+ const shouldInvertRange = type === 'y' && layout !== 'horizontal';
90
+ if (shouldInvertRange) {
78
91
  adjustedRange = {
79
92
  min: adjustedRange.max,
80
93
  max: adjustedRange.min
@@ -122,6 +135,8 @@ export const getAxisConfig = function (type, axes) {
122
135
  let defaultId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : defaultAxisId;
123
136
  let defaultScaleType = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : defaultAxisScaleType;
124
137
  const defaultDomainLimit = type === 'x' ? 'strict' : 'nice';
138
+ const axisName = type === 'x' ? 'x-axis' : 'y-axis';
139
+ const axisDocUrl = type === 'x' ? 'https://cds.coinbase.com/components/charts/XAxis' : 'https://cds.coinbase.com/components/charts/YAxis';
125
140
  if (!axes) {
126
141
  return [{
127
142
  id: defaultId,
@@ -138,16 +153,27 @@ export const getAxisConfig = function (type, axes) {
138
153
  } = _ref2;
139
154
  return id === undefined;
140
155
  })) {
141
- throw new Error('When defining multiple axes, each must have a unique id. See https://cds.coinbase.com/components/charts/YAxis/#multiple-y-axes.');
156
+ throw new Error("When defining multiple ".concat(axisName, ", each must have a unique id. See ").concat(axisDocUrl, "."));
157
+ }
158
+ if (axesLength > 1) {
159
+ const ids = axes.map(_ref3 => {
160
+ let {
161
+ id
162
+ } = _ref3;
163
+ return id;
164
+ }).filter(id => id !== undefined);
165
+ if (new Set(ids).size !== ids.length) {
166
+ throw new Error("When defining multiple ".concat(axisName, ", each must have a unique id. See ").concat(axisDocUrl, "."));
167
+ }
142
168
  }
143
- return axes.map(_ref3 => {
169
+ return axes.map(_ref4 => {
144
170
  let {
145
171
  id
146
- } = _ref3,
147
- axis = _objectWithoutProperties(_ref3, _excluded);
172
+ } = _ref4,
173
+ axis = _objectWithoutProperties(_ref4, _excluded);
148
174
  return _objectSpread({
149
175
  // defaults the axis id if only a single axis is provided
150
- id: axesLength > 1 ? id !== null && id !== void 0 ? id : defaultAxisId : id,
176
+ id: axesLength > 1 ? id !== null && id !== void 0 ? id : defaultAxisId : id !== null && id !== void 0 ? id : defaultId,
151
177
  scaleType: defaultScaleType,
152
178
  domainLimit: defaultDomainLimit
153
179
  }, axis);
@@ -169,10 +195,12 @@ export const getAxisConfig = function (type, axes) {
169
195
  * @param axisParam - The axis configuration
170
196
  * @param series - Array of series objects (for stacking support)
171
197
  * @param axisType - Whether this is an 'x' or 'y' axis
198
+ * @param layout - Chart layout ('horizontal' or 'vertical')
172
199
  * @returns The calculated axis bounds
173
200
  */
174
- export const getAxisDomain = (axisParam, series, axisType) => {
201
+ export const getCartesianAxisDomain = function (axisParam, series, axisType) {
175
202
  var _finalDomain$min, _finalDomain$max;
203
+ let layout = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'vertical';
176
204
  let dataDomain = null;
177
205
  if (axisParam.data && Array.isArray(axisParam.data) && axisParam.data.length > 0) {
178
206
  const firstItem = axisParam.data[0];
@@ -194,7 +222,10 @@ export const getAxisDomain = (axisParam, series, axisType) => {
194
222
  }
195
223
 
196
224
  // Calculate domain from series data
197
- const seriesDomain = axisType === 'x' ? getChartDomain(series) : getChartRange(series);
225
+ // In vertical layout: X is category (index), Y is value (value)
226
+ // In horizontal layout: Y is category (index), X is value (value)
227
+ const isCategoryAxis = layout !== 'horizontal' && axisType === 'x' || layout === 'horizontal' && axisType === 'y';
228
+ const seriesDomain = isCategoryAxis ? getChartDomain(series) : getChartRange(series);
198
229
 
199
230
  // If data sets the domain, use that instead of the series domain
200
231
  const preferredDataDomain = dataDomain !== null && dataDomain !== void 0 ? dataDomain : seriesDomain;
@@ -488,7 +519,7 @@ const generateEvenlyDistributedTicks = (scale, tickInterval, possibleTickValues,
488
519
  * });
489
520
  * // Returns tick positions centered in each selected band
490
521
  */
491
- export const getAxisTicksData = _ref4 => {
522
+ export const getAxisTicksData = _ref5 => {
492
523
  var _options$anchor;
493
524
  let {
494
525
  ticks,
@@ -498,7 +529,7 @@ export const getAxisTicksData = _ref4 => {
498
529
  possibleTickValues,
499
530
  tickInterval,
500
531
  options
501
- } = _ref4;
532
+ } = _ref5;
502
533
  const anchor = (_options$anchor = options === null || options === void 0 ? void 0 : options.anchor) !== null && _options$anchor !== void 0 ? _options$anchor : 'middle';
503
534
 
504
535
  // Handle band scales