@coinbase/cds-web-visualization 3.4.0-beta.8 → 3.4.0

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 (156) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/dts/chart/CartesianChart.d.ts +40 -4
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/ChartProvider.d.ts +3 -0
  5. package/dts/chart/ChartProvider.d.ts.map +1 -1
  6. package/dts/chart/Path.d.ts +51 -12
  7. package/dts/chart/Path.d.ts.map +1 -1
  8. package/dts/chart/PeriodSelector.d.ts +25 -4
  9. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  10. package/dts/chart/area/Area.d.ts +13 -11
  11. package/dts/chart/area/Area.d.ts.map +1 -1
  12. package/dts/chart/area/AreaChart.d.ts +18 -5
  13. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  14. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  15. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  16. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  17. package/dts/chart/axis/Axis.d.ts +29 -29
  18. package/dts/chart/axis/Axis.d.ts.map +1 -1
  19. package/dts/chart/axis/XAxis.d.ts +6 -0
  20. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  21. package/dts/chart/axis/YAxis.d.ts +2 -1
  22. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  23. package/dts/chart/bar/Bar.d.ts +51 -51
  24. package/dts/chart/bar/Bar.d.ts.map +1 -1
  25. package/dts/chart/bar/BarChart.d.ts +29 -6
  26. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  27. package/dts/chart/bar/BarPlot.d.ts +2 -1
  28. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  29. package/dts/chart/bar/BarStack.d.ts +58 -20
  30. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  31. package/dts/chart/bar/BarStackGroup.d.ts +2 -1
  32. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  33. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  34. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  35. package/dts/chart/gradient/Gradient.d.ts +7 -0
  36. package/dts/chart/gradient/Gradient.d.ts.map +1 -1
  37. package/dts/chart/index.d.ts +1 -0
  38. package/dts/chart/index.d.ts.map +1 -1
  39. package/dts/chart/legend/DefaultLegendEntry.d.ts +21 -0
  40. package/dts/chart/legend/DefaultLegendEntry.d.ts.map +1 -0
  41. package/dts/chart/legend/DefaultLegendShape.d.ts +7 -0
  42. package/dts/chart/legend/DefaultLegendShape.d.ts.map +1 -0
  43. package/dts/chart/legend/Legend.d.ts +169 -0
  44. package/dts/chart/legend/Legend.d.ts.map +1 -0
  45. package/dts/chart/legend/index.d.ts +4 -0
  46. package/dts/chart/legend/index.d.ts.map +1 -0
  47. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  48. package/dts/chart/line/Line.d.ts +45 -24
  49. package/dts/chart/line/Line.d.ts.map +1 -1
  50. package/dts/chart/line/LineChart.d.ts +5 -3
  51. package/dts/chart/line/LineChart.d.ts.map +1 -1
  52. package/dts/chart/line/ReferenceLine.d.ts +9 -0
  53. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  54. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  55. package/dts/chart/point/Point.d.ts +26 -2
  56. package/dts/chart/point/Point.d.ts.map +1 -1
  57. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +34 -17
  58. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -1
  59. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -1
  60. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +2 -1
  61. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -1
  62. package/dts/chart/scrubber/Scrubber.d.ts +148 -46
  63. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  64. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +10 -0
  65. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -1
  66. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +25 -1
  67. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -1
  68. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  69. package/dts/chart/text/ChartText.d.ts.map +1 -1
  70. package/dts/chart/utils/axis.d.ts +48 -9
  71. package/dts/chart/utils/axis.d.ts.map +1 -1
  72. package/dts/chart/utils/bar.d.ts +188 -0
  73. package/dts/chart/utils/bar.d.ts.map +1 -1
  74. package/dts/chart/utils/chart.d.ts +32 -0
  75. package/dts/chart/utils/chart.d.ts.map +1 -1
  76. package/dts/chart/utils/context.d.ts +20 -4
  77. package/dts/chart/utils/context.d.ts.map +1 -1
  78. package/dts/chart/utils/gradient.d.ts +3 -1
  79. package/dts/chart/utils/gradient.d.ts.map +1 -1
  80. package/dts/chart/utils/path.d.ts +26 -0
  81. package/dts/chart/utils/path.d.ts.map +1 -1
  82. package/dts/chart/utils/point.d.ts +18 -7
  83. package/dts/chart/utils/point.d.ts.map +1 -1
  84. package/dts/chart/utils/scale.d.ts +11 -0
  85. package/dts/chart/utils/scale.d.ts.map +1 -1
  86. package/dts/chart/utils/scrubber.d.ts +2 -1
  87. package/dts/chart/utils/scrubber.d.ts.map +1 -1
  88. package/dts/chart/utils/transition.d.ts +50 -14
  89. package/dts/chart/utils/transition.d.ts.map +1 -1
  90. package/dts/sparkline/Sparkline.d.ts +2 -1
  91. package/dts/sparkline/Sparkline.d.ts.map +1 -1
  92. package/dts/sparkline/SparklineArea.d.ts +2 -1
  93. package/dts/sparkline/SparklineArea.d.ts.map +1 -1
  94. package/dts/sparkline/SparklineGradient.d.ts +2 -1
  95. package/dts/sparkline/SparklineGradient.d.ts.map +1 -1
  96. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts +2 -1
  97. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts.map +1 -1
  98. package/esm/chart/CartesianChart.js +154 -90
  99. package/esm/chart/ChartProvider.js +2 -2
  100. package/esm/chart/Path.js +35 -19
  101. package/esm/chart/PeriodSelector.js +33 -15
  102. package/esm/chart/area/Area.js +21 -9
  103. package/esm/chart/area/AreaChart.js +28 -25
  104. package/esm/chart/area/DottedArea.js +18 -9
  105. package/esm/chart/area/GradientArea.js +16 -8
  106. package/esm/chart/area/SolidArea.js +8 -3
  107. package/esm/chart/axis/Axis.js +3 -17
  108. package/esm/chart/axis/XAxis.js +153 -50
  109. package/esm/chart/axis/YAxis.js +146 -36
  110. package/esm/chart/bar/Bar.js +16 -8
  111. package/esm/chart/bar/BarChart.js +38 -33
  112. package/esm/chart/bar/BarPlot.js +20 -25
  113. package/esm/chart/bar/BarStack.js +109 -505
  114. package/esm/chart/bar/BarStackGroup.js +36 -27
  115. package/esm/chart/bar/DefaultBar.js +34 -26
  116. package/esm/chart/bar/DefaultBarStack.js +31 -18
  117. package/esm/chart/gradient/Gradient.js +3 -2
  118. package/esm/chart/index.js +1 -0
  119. package/esm/chart/legend/DefaultLegendEntry.css +1 -0
  120. package/esm/chart/legend/DefaultLegendEntry.js +50 -0
  121. package/esm/chart/legend/DefaultLegendShape.css +5 -0
  122. package/esm/chart/legend/DefaultLegendShape.js +47 -0
  123. package/esm/chart/legend/Legend.js +76 -0
  124. package/esm/chart/legend/index.js +3 -0
  125. package/esm/chart/line/DottedLine.js +7 -2
  126. package/esm/chart/line/Line.js +41 -42
  127. package/esm/chart/line/LineChart.js +18 -13
  128. package/esm/chart/line/ReferenceLine.js +6 -2
  129. package/esm/chart/line/SolidLine.js +8 -3
  130. package/esm/chart/point/Point.js +41 -24
  131. package/esm/chart/scrubber/DefaultScrubberBeacon.js +64 -65
  132. package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +25 -14
  133. package/esm/chart/scrubber/DefaultScrubberLabel.js +26 -8
  134. package/esm/chart/scrubber/Scrubber.js +54 -43
  135. package/esm/chart/scrubber/ScrubberBeaconGroup.js +60 -35
  136. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +31 -8
  137. package/esm/chart/scrubber/ScrubberProvider.js +44 -39
  138. package/esm/chart/text/ChartText.js +3 -2
  139. package/esm/chart/utils/axis.js +90 -43
  140. package/esm/chart/utils/bar.js +863 -0
  141. package/esm/chart/utils/chart.js +34 -7
  142. package/esm/chart/utils/context.js +7 -0
  143. package/esm/chart/utils/gradient.js +6 -4
  144. package/esm/chart/utils/path.js +88 -61
  145. package/esm/chart/utils/point.js +57 -30
  146. package/esm/chart/utils/scale.js +13 -2
  147. package/esm/chart/utils/scrubber.js +9 -4
  148. package/esm/chart/utils/transition.js +68 -41
  149. package/esm/sparkline/Sparkline.js +2 -1
  150. package/esm/sparkline/SparklineArea.js +2 -1
  151. package/esm/sparkline/SparklineGradient.js +2 -1
  152. package/esm/sparkline/__figma__/Sparkline.figma.js +1 -1
  153. package/esm/sparkline/sparkline-interactive/SparklineInteractive.js +2 -1
  154. package/esm/sparkline/sparkline-interactive/__figma__/SparklineInteractive.figma.js +1 -1
  155. package/esm/sparkline/sparkline-interactive-header/__figma__/SparklineInteractiveHeader.figma.js +1 -1
  156. package/package.json +12 -11
@@ -1,5 +1,18 @@
1
1
  import { stack as d3Stack, stackOffsetDiverging, stackOrderNone } from 'd3-shape';
2
2
  export const defaultStackId = 'DEFAULT_STACK_ID';
3
+
4
+ /**
5
+ * Shape variants available for legend items.
6
+ */
7
+
8
+ /**
9
+ * Shape for legend items. Can be a preset variant or a custom ReactNode.
10
+ */
11
+
12
+ /**
13
+ * Position of the legend relative to the chart.
14
+ */
15
+
3
16
  /**
4
17
  * Type guard to check if bounds are complete with both min and max values.
5
18
  * @param bounds - The bounds to validate
@@ -32,15 +45,16 @@ export const getChartDomain = (series, min, max) => {
32
45
  };
33
46
 
34
47
  /**
35
- * Creates a composite stack key that includes both stack ID and y-axis ID.
36
- * This ensures series with different y-scales don't get stacked together.
48
+ * Creates a composite stack key that includes stack ID and axis IDs.
49
+ * This ensures series with different scales don't get stacked together.
37
50
  */
38
51
  const createStackKey = series => {
39
52
  if (series.stackId === undefined) return undefined;
40
53
 
41
- // Include y-axis ID to prevent cross-scale stacking
54
+ // Include axis IDs to prevent cross-scale stacking
55
+ const xAxisId = series.xAxisId || 'default';
42
56
  const yAxisId = series.yAxisId || 'default';
43
- return "".concat(series.stackId, ":").concat(yAxisId);
57
+ return "".concat(series.stackId, ":").concat(xAxisId, ":").concat(yAxisId);
44
58
  };
45
59
 
46
60
  /**
@@ -126,9 +140,9 @@ export const getLineData = data => {
126
140
  const firstNonNull = data.find(d => d !== null);
127
141
  if (Array.isArray(firstNonNull)) {
128
142
  return data.map(d => {
129
- var _d$at;
143
+ var _d;
130
144
  if (d === null) return null;
131
- if (Array.isArray(d)) return (_d$at = d.at(-1)) !== null && _d$at !== void 0 ? _d$at : null;
145
+ if (Array.isArray(d)) return (_d = d[d.length - 1]) !== null && _d !== void 0 ? _d : null;
132
146
  return d;
133
147
  });
134
148
  }
@@ -211,12 +225,25 @@ export const getChartRange = (series, min, max) => {
211
225
  }
212
226
  return range;
213
227
  };
214
- export const defaultChartInset = {
228
+ export const defaultVerticalLayoutChartInset = {
215
229
  top: 32,
216
230
  left: 16,
217
231
  bottom: 16,
218
232
  right: 16
219
233
  };
234
+ export const defaultHorizontalLayoutChartInset = {
235
+ top: 16,
236
+ left: 16,
237
+ bottom: 16,
238
+ right: 48
239
+ };
240
+
241
+ /**
242
+ * @deprecated Use `defaultVerticalLayoutChartInset` for vertical layout charts or. This will be removed in a future major release.
243
+ * @deprecationExpectedRemoval v4
244
+ * `defaultHorizontalLayoutChartInset` for horizontal layout charts.
245
+ */
246
+ export const defaultChartInset = defaultVerticalLayoutChartInset;
220
247
 
221
248
  /**
222
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.
@@ -214,9 +214,11 @@ export const getBaseline = function (axisBounds) {
214
214
  * @param fill - The color to use for the gradient
215
215
  * @param peakOpacity - Opacity at the peak of the gradient
216
216
  * @param baselineOpacity - Opacity at the baseline
217
- * @returns A gradient definition with y-axis stops in ascending order
217
+ * @param axis - The axis the gradient maps to ('y' for vertical, 'x' for horizontal layout)
218
+ * @returns A gradient definition with stops in ascending order
218
219
  */
219
- export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, baselineOpacity) => {
220
+ export const createGradient = function (axisBounds, baselineValue, fill, peakOpacity, baselineOpacity) {
221
+ let axis = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 'y';
220
222
  const {
221
223
  min,
222
224
  max
@@ -225,7 +227,7 @@ export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, bas
225
227
  const upperBound = Math.max(min, max);
226
228
  if (lowerBound < baselineValue && baselineValue < upperBound) {
227
229
  return {
228
- axis: 'y',
230
+ axis,
229
231
  stops: [{
230
232
  offset: lowerBound,
231
233
  color: fill,
@@ -243,7 +245,7 @@ export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, bas
243
245
  }
244
246
  const peakValue = Math.abs(min - baselineValue) > Math.abs(max - baselineValue) ? min : max;
245
247
  return {
246
- axis: 'y',
248
+ axis,
247
249
  stops: [{
248
250
  offset: peakValue,
249
251
  color: fill,
@@ -1,20 +1,32 @@
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
+
5
+ /**
6
+ * Default enter transition for path-based components (Line, Area).
7
+ * `{ type: 'tween', duration: 0.5 }`
8
+ */
9
+ export const defaultPathEnterTransition = {
10
+ type: 'tween',
11
+ duration: 0.5
12
+ };
4
13
  /**
5
14
  * Get the d3 curve function for a path.
6
15
  * See https://d3js.org/d3-shape/curve
7
16
  * @param curve - The curve type. Defaults to 'linear'.
17
+ * @param layout - The chart layout. Defaults to 'vertical'.
8
18
  * @returns The d3 curve function.
9
19
  */
10
20
  export const getPathCurveFunction = function () {
11
21
  let curve = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'linear';
22
+ let layout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'vertical';
12
23
  switch (curve) {
13
24
  case 'catmullRom':
14
25
  return curveCatmullRom;
15
26
  case 'monotone':
16
- // When we support layout="vertical" this should dynamically switch to curveMonotoneY
17
- return curveMonotoneX;
27
+ // For vertical layout, X is the independent axis (category/index), so use MonotoneX
28
+ // For horizontal layout, Y is the independent axis (category/index), so use MonotoneY
29
+ return layout !== 'horizontal' ? curveMonotoneX : curveMonotoneY;
18
30
  case 'natural':
19
31
  return curveNatural;
20
32
  case 'step':
@@ -24,8 +36,9 @@ export const getPathCurveFunction = function () {
24
36
  case 'stepAfter':
25
37
  return curveStepAfter;
26
38
  case 'bump':
27
- // When we support layout="vertical" this should dynamically switch to curveBumpY
28
- return curveBumpX;
39
+ // For vertical layout, X is the independent axis (category/index), so use BumpX
40
+ // For horizontal layout, Y is the independent axis (category/index), so use BumpY
41
+ return layout !== 'horizontal' ? curveBumpX : curveBumpY;
29
42
  case 'linearClosed':
30
43
  return curveLinearClosed;
31
44
  case 'linear':
@@ -51,17 +64,21 @@ export const getLinePath = _ref => {
51
64
  xScale,
52
65
  yScale,
53
66
  xData,
54
- connectNulls
67
+ yData,
68
+ connectNulls,
69
+ layout = 'vertical'
55
70
  } = _ref;
56
71
  if (data.length === 0) {
57
72
  return '';
58
73
  }
59
- const curveFunction = getPathCurveFunction(curve);
74
+ const curveFunction = getPathCurveFunction(curve, layout);
60
75
  const dataPoints = projectPoints({
61
76
  data,
62
77
  xScale,
63
78
  yScale,
64
- xData
79
+ xData,
80
+ yData,
81
+ layout
65
82
  });
66
83
 
67
84
  // When connectNulls is true, filter out null values before rendering
@@ -100,15 +117,21 @@ export const getAreaPath = _ref2 => {
100
117
  xScale,
101
118
  yScale,
102
119
  xData,
103
- connectNulls
120
+ yData,
121
+ connectNulls,
122
+ layout = 'vertical'
104
123
  } = _ref2;
105
124
  if (data.length === 0) {
106
125
  return '';
107
126
  }
108
- const curveFunction = getPathCurveFunction(curve);
109
- const yDomain = yScale.domain();
110
- const yMin = Math.min(...yDomain);
111
- const normalizedData = data.map((item, index) => {
127
+ const curveFunction = getPathCurveFunction(curve, layout);
128
+ const categoryAxisIsX = layout !== 'horizontal';
129
+
130
+ // Determine baseline from the value scale
131
+ const valueScale = categoryAxisIsX ? yScale : xScale;
132
+ const domain = valueScale.domain();
133
+ const min = Math.min(...domain);
134
+ const normalizedData = data.map(item => {
112
135
  if (item === null) {
113
136
  return null;
114
137
  }
@@ -119,7 +142,7 @@ export const getAreaPath = _ref2 => {
119
142
  return null;
120
143
  }
121
144
  if (typeof item === 'number') {
122
- return [yMin, item];
145
+ return [min, item];
123
146
  }
124
147
  return null;
125
148
  });
@@ -127,37 +150,28 @@ export const getAreaPath = _ref2 => {
127
150
  if (range === null) {
128
151
  return {
129
152
  x: 0,
153
+ y: 0,
130
154
  low: null,
131
155
  high: null,
132
156
  isValid: false
133
157
  };
134
158
  }
135
- let xValue = index;
136
- if (!isCategoricalScale(xScale) && xData && xData[index] !== undefined) {
137
- xValue = xData[index];
159
+
160
+ // Determine the position along the independent (index) axis
161
+ let indexValue = index;
162
+ const indexScale = categoryAxisIsX ? xScale : yScale;
163
+ const indexData = categoryAxisIsX ? xData : yData;
164
+ if (!isCategoricalScale(indexScale) && indexData && indexData[index] !== undefined) {
165
+ indexValue = indexData[index];
138
166
  }
139
- const xPoint = projectPoint({
140
- x: xValue,
141
- y: 0,
142
- xScale,
143
- yScale
144
- });
145
- const lowPoint = projectPoint({
146
- x: xValue,
147
- y: range[0],
148
- xScale,
149
- yScale
150
- });
151
- const highPoint = projectPoint({
152
- x: xValue,
153
- y: range[1],
154
- xScale,
155
- yScale
156
- });
167
+ const pos = getPointOnScale(indexValue, indexScale);
168
+ const low = getPointOnScale(range[0], valueScale);
169
+ const high = getPointOnScale(range[1], valueScale);
157
170
  return {
158
- x: xPoint.x,
159
- low: lowPoint.y,
160
- high: highPoint.y,
171
+ x: categoryAxisIsX ? pos : 0,
172
+ y: !categoryAxisIsX ? pos : 0,
173
+ low,
174
+ high,
161
175
  isValid: true
162
176
  };
163
177
  });
@@ -165,16 +179,25 @@ export const getAreaPath = _ref2 => {
165
179
  // When connectNulls is true, filter out invalid points before rendering
166
180
  // When false, use defined() to create gaps in the area
167
181
  const filteredPoints = connectNulls ? dataPoints.filter(d => d.isValid) : dataPoints;
168
- const areaGenerator = d3Area().x(d => d.x).y0(d => {
169
- var _d$low;
170
- return (_d$low = d.low) !== null && _d$low !== void 0 ? _d$low : 0;
171
- }) // Bottom boundary (low values), fallback to 0
172
- .y1(d => {
173
- var _d$high;
174
- return (_d$high = d.high) !== null && _d$high !== void 0 ? _d$high : 0;
175
- }) // Top boundary (high values), fallback to 0
176
- .curve(curveFunction).defined(d => connectNulls || d.isValid && d.low != null && d.high != null); // Only draw where both values exist
177
-
182
+ const areaGenerator = d3Area();
183
+ if (categoryAxisIsX) {
184
+ areaGenerator.x(d => d.x).y0(d => {
185
+ var _d$low;
186
+ return (_d$low = d.low) !== null && _d$low !== void 0 ? _d$low : 0;
187
+ }).y1(d => {
188
+ var _d$high;
189
+ return (_d$high = d.high) !== null && _d$high !== void 0 ? _d$high : 0;
190
+ });
191
+ } else {
192
+ areaGenerator.y(d => d.y).x0(d => {
193
+ var _d$low2;
194
+ return (_d$low2 = d.low) !== null && _d$low2 !== void 0 ? _d$low2 : 0;
195
+ }).x1(d => {
196
+ var _d$high2;
197
+ return (_d$high2 = d.high) !== null && _d$high2 !== void 0 ? _d$high2 : 0;
198
+ });
199
+ }
200
+ areaGenerator.curve(curveFunction).defined(d => connectNulls || d.isValid && d.low != null && d.high != null);
178
201
  const result = areaGenerator(filteredPoints);
179
202
  return result !== null && result !== void 0 ? result : '';
180
203
  };
@@ -206,22 +229,26 @@ export const lineToPath = (x1, y1, x2, y2) => {
206
229
  * const roundedPath = getBarPath(10, 20, 50, 100, 8, true, false);
207
230
  * ```
208
231
  */
209
- export const getBarPath = (x, y, width, height, radius, roundTop, roundBottom) => {
232
+ export const getBarPath = function (x, y, width, height, radius, roundTop, roundBottom) {
233
+ let layout = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 'vertical';
234
+ const isVerticalLayout = layout === 'vertical';
210
235
  const roundBothSides = roundTop && roundBottom;
211
236
  const r = Math.min(radius, width / 2, roundBothSides ? height / 2 : height);
212
- const topR = roundTop ? r : 0;
213
- const bottomR = roundBottom ? r : 0;
237
+ const rTL = isVerticalLayout ? roundTop ? r : 0 : roundBottom ? r : 0;
238
+ const rTR = isVerticalLayout ? roundTop ? r : 0 : roundTop ? r : 0;
239
+ const rBR = isVerticalLayout ? roundBottom ? r : 0 : roundTop ? r : 0;
240
+ const rBL = isVerticalLayout ? roundBottom ? r : 0 : roundBottom ? r : 0;
214
241
 
215
242
  // Build path with selective rounding
216
- let path = "M ".concat(x + (roundTop ? r : 0), " ").concat(y);
217
- path += " L ".concat(x + width - topR, " ").concat(y);
218
- path += " A ".concat(topR, " ").concat(topR, " 0 0 1 ").concat(x + width, " ").concat(y + topR);
219
- path += " L ".concat(x + width, " ").concat(y + height - bottomR);
220
- path += " A ".concat(bottomR, " ").concat(bottomR, " 0 0 1 ").concat(x + width - bottomR, " ").concat(y + height);
221
- path += " L ".concat(x + bottomR, " ").concat(y + height);
222
- path += " A ".concat(bottomR, " ").concat(bottomR, " 0 0 1 ").concat(x, " ").concat(y + height - bottomR);
223
- path += " L ".concat(x, " ").concat(y + topR);
224
- path += " A ".concat(topR, " ").concat(topR, " 0 0 1 ").concat(x + topR, " ").concat(y);
243
+ let path = "M ".concat(x + rTL, " ").concat(y);
244
+ path += " L ".concat(x + width - rTR, " ").concat(y);
245
+ path += " A ".concat(rTR, " ").concat(rTR, " 0 0 1 ").concat(x + width, " ").concat(y + rTR);
246
+ path += " L ".concat(x + width, " ").concat(y + height - rBR);
247
+ path += " A ".concat(rBR, " ").concat(rBR, " 0 0 1 ").concat(x + width - rBR, " ").concat(y + height);
248
+ path += " L ".concat(x + rBL, " ").concat(y + height);
249
+ path += " A ".concat(rBL, " ").concat(rBL, " 0 0 1 ").concat(x, " ").concat(y + height - rBL);
250
+ path += " L ".concat(x, " ").concat(y + rTL);
251
+ path += " A ".concat(rTL, " ").concat(rTL, " 0 0 1 ").concat(x + rTL, " ").concat(y);
225
252
  path += ' Z';
226
253
  return path;
227
254
  };
@@ -1,4 +1,4 @@
1
- import { isCategoricalScale, isLogScale, isNumericScale } from './scale';
1
+ import { isCategoricalScale, isLogScale } from './scale';
2
2
 
3
3
  /**
4
4
  * Position a label should be placed relative to the point
@@ -10,19 +10,36 @@ import { isCategoricalScale, isLogScale, isNumericScale } from './scale';
10
10
 
11
11
  /**
12
12
  * Get a point from a data value and a scale.
13
- * @note for categorical scales, the point will be centered within the band.
14
- * @note for log scales, zero and negative values are clamped to a small positive value.
15
- * @param data - the data value.
16
- * @param scale - the scale function.
17
- * @returns the pixel value (defaulting to 0 if data value is not defined in scale).
13
+ *
14
+ * @param dataValue - The data value to convert to a pixel position.
15
+ * @param scale - The scale function.
16
+ * @param anchor (@default 'middle') - For band scales, where to anchor the point within the band.
17
+ * @returns The pixel value (@default 0 if data value is not defined in scale).
18
18
  */
19
- export const getPointOnScale = (dataValue, scale) => {
20
- var _scale2;
19
+ export const getPointOnScale = function (dataValue, scale) {
20
+ var _scale;
21
+ let anchor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'middle';
21
22
  if (isCategoricalScale(scale)) {
22
- var _scale, _scale$bandwidth;
23
- const bandStart = (_scale = scale(dataValue)) !== null && _scale !== void 0 ? _scale : 0;
24
- const bandwidth = (_scale$bandwidth = scale.bandwidth()) !== null && _scale$bandwidth !== void 0 ? _scale$bandwidth : 0;
25
- return bandStart + bandwidth / 2;
23
+ var _bandScale$bandwidth, _bandScale$bandwidth2, _bandScale$step, _bandScale$step2;
24
+ const bandScale = scale;
25
+ const bandStart = bandScale(dataValue);
26
+ if (bandStart === undefined) return 0;
27
+ const bandwidth = (_bandScale$bandwidth = (_bandScale$bandwidth2 = bandScale.bandwidth) === null || _bandScale$bandwidth2 === void 0 ? void 0 : _bandScale$bandwidth2.call(bandScale)) !== null && _bandScale$bandwidth !== void 0 ? _bandScale$bandwidth : 0;
28
+ const step = (_bandScale$step = (_bandScale$step2 = bandScale.step) === null || _bandScale$step2 === void 0 ? void 0 : _bandScale$step2.call(bandScale)) !== null && _bandScale$step !== void 0 ? _bandScale$step : bandwidth;
29
+ const paddingOffset = (step - bandwidth) / 2;
30
+ const stepStart = bandStart - paddingOffset;
31
+ switch (anchor) {
32
+ case 'stepStart':
33
+ return stepStart;
34
+ case 'bandStart':
35
+ return bandStart;
36
+ case 'middle':
37
+ return bandStart + bandwidth / 2;
38
+ case 'bandEnd':
39
+ return bandStart + bandwidth;
40
+ case 'stepEnd':
41
+ return stepStart + step;
42
+ }
26
43
  }
27
44
 
28
45
  // For log scales, ensure the value is positive
@@ -30,7 +47,7 @@ export const getPointOnScale = (dataValue, scale) => {
30
47
  if (isLogScale(scale) && dataValue <= 0) {
31
48
  adjustedValue = 0.001; // Use a small positive value for log scales
32
49
  }
33
- return (_scale2 = scale(adjustedValue)) !== null && _scale2 !== void 0 ? _scale2 : 0;
50
+ return (_scale = scale(adjustedValue)) !== null && _scale !== void 0 ? _scale : 0;
34
51
  };
35
52
 
36
53
  /**
@@ -79,7 +96,8 @@ export const projectPoints = _ref2 => {
79
96
  xScale,
80
97
  yScale,
81
98
  xData,
82
- yData
99
+ yData,
100
+ layout = 'vertical'
83
101
  } = _ref2;
84
102
  if (data.length === 0) {
85
103
  return [];
@@ -97,28 +115,37 @@ export const projectPoints = _ref2 => {
97
115
  });
98
116
  }
99
117
 
100
- // For scales with axis data, determine the correct x value
101
- let xValue = index;
118
+ // Determine values/scales based on role (index vs value) and layout.
119
+ const categoryAxisIsX = layout !== 'horizontal';
120
+ const indexScale = categoryAxisIsX ? xScale : yScale;
121
+ const indexData = categoryAxisIsX ? xData : yData;
102
122
 
103
- // For band scales, always use the index
104
- if (!isCategoricalScale(xScale)) {
105
- // For numeric scales with axis data, use the axis data values instead of indices
106
- if (xData && Array.isArray(xData) && xData.length > 0) {
107
- // Check if it's numeric data
108
- if (typeof xData[0] === 'number') {
109
- var _numericXData$index;
110
- const numericXData = xData;
111
- xValue = (_numericXData$index = numericXData[index]) !== null && _numericXData$index !== void 0 ? _numericXData$index : index;
123
+ // 1. Calculate position along the index axis (categorical or numeric domain).
124
+ let indexValue = index;
125
+ if (!isCategoricalScale(indexScale)) {
126
+ if (indexData && Array.isArray(indexData) && indexData.length > 0) {
127
+ if (typeof indexData[0] === 'number') {
128
+ var _index;
129
+ indexValue = (_index = indexData[index]) !== null && _index !== void 0 ? _index : index;
112
130
  }
113
131
  }
114
132
  }
115
- let yValue = value;
116
- if (isNumericScale(yScale) && yData && Array.isArray(yData) && yData.length > 0 && typeof yData[0] === 'number' && typeof value === 'number') {
117
- yValue = value;
133
+
134
+ // 2. Calculate position along the value axis (measured magnitude).
135
+ const valueAsNumber = value;
136
+
137
+ // 3. Project final coordinates based on layout.
138
+ if (categoryAxisIsX) {
139
+ return projectPoint({
140
+ x: indexValue,
141
+ y: valueAsNumber,
142
+ xScale,
143
+ yScale
144
+ });
118
145
  }
119
146
  return projectPoint({
120
- x: xValue,
121
- y: yValue,
147
+ x: valueAsNumber,
148
+ y: indexValue,
122
149
  xScale,
123
150
  yScale
124
151
  });
@@ -43,6 +43,17 @@ export const getCategoricalScale = _ref2 => {
43
43
  const domainArray = Array.from({
44
44
  length: domain.max - domain.min + 1
45
45
  }, (_, i) => i);
46
- const scale = scaleBand().domain(domainArray).range([range.min, range.max]).padding(padding);
46
+ const scale = scaleBand().domain(domainArray).range([range.min, range.max]).paddingInner(padding).paddingOuter(padding / 2);
47
47
  return scale;
48
- };
48
+ };
49
+
50
+ /**
51
+ * Anchor position for points on a scale. Currently used only for band scales.
52
+ *
53
+ * For band scales, this determines where within the band to position a point:
54
+ * - `'stepStart'` - At the start of the step
55
+ * - `'bandStart'` - At the start of the band
56
+ * - `'middle'` - At the center of the band
57
+ * - `'bandEnd'` - At the end of the band
58
+ * - `'stepEnd'` - At the end of the step
59
+ */
@@ -1,15 +1,20 @@
1
1
  /**
2
2
  * Determines which side (left/right) to place scrubber labels based on available space.
3
- * Prefers right side, switches to left when labels would overflow.
3
+ * Honors the preferred side when there's enough space, otherwise switches to the opposite side.
4
4
  */
5
5
  export const getLabelPosition = function (beaconX, maxLabelWidth, drawingArea) {
6
6
  let xOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 16;
7
+ let preferredSide = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 'right';
7
8
  if (drawingArea.width <= 0 || drawingArea.height <= 0) {
8
- return 'right';
9
+ return preferredSide;
9
10
  }
10
- const availableRightSpace = drawingArea.x + drawingArea.width - beaconX;
11
11
  const requiredSpace = maxLabelWidth + xOffset;
12
- return requiredSpace <= availableRightSpace ? 'right' : 'left';
12
+ if (preferredSide === 'right') {
13
+ const availableSpace = drawingArea.x + drawingArea.width - beaconX;
14
+ return requiredSpace <= availableSpace ? 'right' : 'left';
15
+ }
16
+ const availableSpace = beaconX - drawingArea.x;
17
+ return requiredSpace <= availableSpace ? 'left' : 'right';
13
18
  };
14
19
  /**
15
20
  * Calculates Y positions for all labels avoiding overlaps while maintaining order.