@coinbase/cds-mobile-visualization 3.4.0-beta.10 → 3.4.0-beta.11

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.
@@ -1,15 +1,14 @@
1
- const _excluded = ["axisId", "position", "showGrid", "requestedTickCount", "ticks", "tickLabelFormatter", "TickLabelComponent", "GridLineComponent", "LineComponent", "TickMarkLineComponent", "tickMarkLabelGap", "minTickLabelGap", "showTickMarks", "showLine", "tickMarkSize", "tickInterval", "label", "labelGap", "width"];
1
+ const _excluded = ["axisId", "position", "showGrid", "requestedTickCount", "ticks", "tickLabelFormatter", "TickLabelComponent", "GridLineComponent", "LineComponent", "TickMarkLineComponent", "tickMarkLabelGap", "minTickLabelGap", "showTickMarks", "showLine", "tickMarkSize", "tickInterval", "label", "labelGap", "width", "bandGridLinePlacement", "bandTickMarkPlacement"];
2
2
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
3
3
  import { memo, useCallback, useEffect, useId, useMemo } from 'react';
4
4
  import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme';
5
5
  import { Group, vec } from '@shopify/react-native-skia';
6
6
  import { useCartesianChartContext } from '../ChartProvider';
7
7
  import { DottedLine } from '../line/DottedLine';
8
- import { ReferenceLine } from '../line/ReferenceLine';
9
8
  import { SolidLine } from '../line/SolidLine';
10
9
  import { ChartText } from '../text/ChartText';
11
10
  import { ChartTextGroup } from '../text/ChartTextGroup';
12
- import { getAxisTicksData, isCategoricalScale, lineToPath } from '../utils';
11
+ import { getAxisTicksData, getPointOnScale, isCategoricalScale, lineToPath, toPointAnchor } from '../utils';
13
12
  import { DefaultAxisTickLabel } from './DefaultAxisTickLabel';
14
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
15
14
  const AXIS_WIDTH = 44;
@@ -34,13 +33,16 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
34
33
  tickInterval,
35
34
  label,
36
35
  labelGap = 4,
37
- width = label ? AXIS_WIDTH + LABEL_SIZE : AXIS_WIDTH
36
+ width = label ? AXIS_WIDTH + LABEL_SIZE : AXIS_WIDTH,
37
+ bandGridLinePlacement = 'edges',
38
+ bandTickMarkPlacement = 'middle'
38
39
  } = _ref,
39
40
  props = _objectWithoutPropertiesLoose(_ref, _excluded);
40
41
  const theme = useTheme();
41
42
  const registrationId = useId();
42
43
  const {
43
44
  animate,
45
+ drawingArea,
44
46
  getYScale,
45
47
  getYAxis,
46
48
  registerAxis,
@@ -50,10 +52,6 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
50
52
  const yScale = getYScale(axisId);
51
53
  const yAxis = getYAxis(axisId);
52
54
  const axisBounds = getAxisBounds(registrationId);
53
-
54
- // Note: gridOpacity not currently used in Skia version
55
- // const gridOpacity = useSharedValue(1);
56
-
57
55
  useEffect(() => {
58
56
  registerAxis(registrationId, position, width);
59
57
  return () => unregisterAxis(registrationId);
@@ -102,6 +100,72 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
102
100
  tickInterval: tickInterval
103
101
  });
104
102
  }, [ticks, yScale, requestedTickCount, tickInterval, yAxis == null ? void 0 : yAxis.data]);
103
+ const isBandScale = useMemo(() => {
104
+ if (!yScale) return false;
105
+ return isCategoricalScale(yScale);
106
+ }, [yScale]);
107
+
108
+ // Compute grid line positions (including bounds closing line for band scales)
109
+ const gridLinePositions = useMemo(() => {
110
+ if (!yScale) return [];
111
+ return ticksData.flatMap((tick, index) => {
112
+ if (!isBandScale) {
113
+ return [{
114
+ y: tick.position,
115
+ key: "grid-" + tick.tick + "-" + index
116
+ }];
117
+ }
118
+ const bandScale = yScale;
119
+ const isLastTick = index === ticksData.length - 1;
120
+ const isEdges = bandGridLinePlacement === 'edges';
121
+ const startY = getPointOnScale(tick.tick, bandScale, toPointAnchor(bandGridLinePlacement));
122
+ const positions = [{
123
+ y: startY,
124
+ key: "grid-" + tick.tick + "-" + index
125
+ }];
126
+
127
+ // For edges on last tick, add the closing line at stepEnd
128
+ if (isLastTick && isEdges) {
129
+ const endY = getPointOnScale(tick.tick, bandScale, 'stepEnd');
130
+ positions.push({
131
+ y: endY,
132
+ key: "grid-" + tick.tick + "-" + index + "-end"
133
+ });
134
+ }
135
+ return positions;
136
+ });
137
+ }, [ticksData, yScale, isBandScale, bandGridLinePlacement]);
138
+
139
+ // Compute tick mark positions (including bounds closing tick for band scales)
140
+ const tickMarkPositions = useMemo(() => {
141
+ if (!yScale) return [];
142
+ return ticksData.flatMap((tick, index) => {
143
+ if (!isBandScale) {
144
+ return [{
145
+ y: tick.position,
146
+ key: "tick-mark-" + tick.tick + "-" + index
147
+ }];
148
+ }
149
+ const bandScale = yScale;
150
+ const isLastTick = index === ticksData.length - 1;
151
+ const isEdges = bandTickMarkPlacement === 'edges';
152
+ const startY = getPointOnScale(tick.tick, bandScale, toPointAnchor(bandTickMarkPlacement));
153
+ const positions = [{
154
+ y: startY,
155
+ key: "tick-mark-" + tick.tick + "-" + index
156
+ }];
157
+
158
+ // For edges on last tick, add the closing tick mark at stepEnd
159
+ if (isLastTick && isEdges) {
160
+ const endY = getPointOnScale(tick.tick, bandScale, 'stepEnd');
161
+ positions.push({
162
+ y: endY,
163
+ key: "tick-mark-" + tick.tick + "-" + index + "-end"
164
+ });
165
+ }
166
+ return positions;
167
+ });
168
+ }, [ticksData, yScale, isBandScale, bandTickMarkPlacement]);
105
169
  const chartTextData = useMemo(() => {
106
170
  if (!axisBounds) return null;
107
171
  return ticksData.map(tick => {
@@ -122,17 +186,29 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
122
186
  if (!yScale || !axisBounds) return;
123
187
  const labelX = position === 'left' ? axisBounds.x + LABEL_SIZE / 2 : axisBounds.x + axisBounds.width - LABEL_SIZE / 2;
124
188
  const labelY = axisBounds.y + axisBounds.height / 2;
189
+
190
+ // Pre-compute tick mark X coordinates
191
+ const tickXLeft = axisBounds.x;
192
+ const tickXRight = axisBounds.x + axisBounds.width;
193
+ const tickXStart = position === 'left' ? tickXRight : tickXLeft;
194
+ const tickXEnd = position === 'left' ? tickXRight - tickMarkSize : tickXLeft + tickMarkSize;
195
+
196
+ // Note: Unlike web, mobile renders grid lines and tick marks immediately without fade animation.
197
+ // This is because Skia can measure text dimensions synchronously, so there's no need to hide
198
+ // elements while waiting for measurements (web uses async ResizeObserver).
125
199
  return /*#__PURE__*/_jsxs(Group, {
126
200
  children: [showGrid && /*#__PURE__*/_jsx(Group, {
127
- children: ticksData.map((tick, index) => {
128
- const horizontalLine = /*#__PURE__*/_jsx(ReferenceLine, {
129
- LineComponent: GridLineComponent,
130
- dataY: tick.tick,
131
- yAxisId: axisId
132
- });
133
- return /*#__PURE__*/_jsx(Group, {
134
- children: horizontalLine
135
- }, "grid-" + tick.tick + "-" + index);
201
+ children: gridLinePositions.map(_ref2 => {
202
+ let {
203
+ y,
204
+ key
205
+ } = _ref2;
206
+ return /*#__PURE__*/_jsx(GridLineComponent, {
207
+ animate: false,
208
+ clipPath: null,
209
+ d: lineToPath(drawingArea.x, y, drawingArea.x + drawingArea.width, y),
210
+ stroke: theme.color.bgLine
211
+ }, key);
136
212
  })
137
213
  }), chartTextData && /*#__PURE__*/_jsx(ChartTextGroup, {
138
214
  prioritizeEndLabels: true,
@@ -140,18 +216,19 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
140
216
  labels: chartTextData,
141
217
  minGap: minTickLabelGap
142
218
  }), axisBounds && showTickMarks && /*#__PURE__*/_jsx(Group, {
143
- children: ticksData.map((tick, index) => {
144
- const tickX = position === 'left' ? axisBounds.x + axisBounds.width : axisBounds.x;
145
- const tickMarkSizePixels = tickMarkSize;
146
- const tickX2 = position === 'left' ? axisBounds.x + axisBounds.width - tickMarkSizePixels : axisBounds.x + tickMarkSizePixels;
219
+ children: tickMarkPositions.map(_ref3 => {
220
+ let {
221
+ y,
222
+ key
223
+ } = _ref3;
147
224
  return /*#__PURE__*/_jsx(TickMarkLineComponent, {
148
225
  animate: false,
149
226
  clipPath: null,
150
- d: lineToPath(tickX, tick.position, tickX2, tick.position),
227
+ d: lineToPath(tickXStart, y, tickXEnd, y),
151
228
  stroke: theme.color.fg,
152
229
  strokeCap: "square",
153
230
  strokeWidth: 1
154
- }, "tick-mark-" + tick.tick + "-" + index);
231
+ }, key);
155
232
  })
156
233
  }), showLine && /*#__PURE__*/_jsx(LineComponent, {
157
234
  animate: false,
@@ -2,6 +2,7 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
2
2
  import { memo, useCallback, useMemo } from 'react';
3
3
  import { Example, ExampleScreen } from '@coinbase/cds-mobile/examples/ExampleScreen';
4
4
  import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme';
5
+ import { BarPlot } from '../../bar';
5
6
  import { CartesianChart } from '../../CartesianChart';
6
7
  import { LineChart, SolidLine } from '../../line';
7
8
  import { Line } from '../../line/Line';
@@ -194,6 +195,105 @@ const MultipleYAxesExample = () => /*#__PURE__*/_jsxs(CartesianChart, {
194
195
  seriesId: "log"
195
196
  }), /*#__PURE__*/_jsx(Scrubber, {})]
196
197
  });
198
+ const AxesOnAllSides = () => {
199
+ const theme = useTheme();
200
+ const data = [30, 45, 60, 80, 55, 40, 65];
201
+ const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
202
+ return /*#__PURE__*/_jsxs(CartesianChart, {
203
+ height: defaultChartHeight,
204
+ series: [{
205
+ id: 'data',
206
+ data,
207
+ color: theme.color.accentBoldBlue
208
+ }],
209
+ xAxis: {
210
+ data: labels
211
+ },
212
+ yAxis: {
213
+ domain: {
214
+ min: 0,
215
+ max: 100
216
+ }
217
+ },
218
+ children: [/*#__PURE__*/_jsx(XAxis, {
219
+ showLine: true,
220
+ showTickMarks: true,
221
+ label: "Bottom Axis",
222
+ position: "bottom",
223
+ ticks: labels.map((label, index) => index)
224
+ }), /*#__PURE__*/_jsx(XAxis, {
225
+ showLine: true,
226
+ showTickMarks: true,
227
+ label: "Top Axis",
228
+ position: "top",
229
+ ticks: labels.map((label, index) => index)
230
+ }), /*#__PURE__*/_jsx(YAxis, {
231
+ showLine: true,
232
+ showTickMarks: true,
233
+ label: "Left Axis",
234
+ position: "left"
235
+ }), /*#__PURE__*/_jsx(YAxis, {
236
+ showLine: true,
237
+ showTickMarks: true,
238
+ label: "Right Axis",
239
+ position: "right"
240
+ }), /*#__PURE__*/_jsx(Line, {
241
+ curve: "natural",
242
+ seriesId: "data"
243
+ })]
244
+ });
245
+ };
246
+ const CustomTickMarkSizes = () => {
247
+ const theme = useTheme();
248
+ const data = [25, 50, 75, 60, 45, 80, 35];
249
+ return /*#__PURE__*/_jsxs(CartesianChart, {
250
+ height: 300,
251
+ series: [{
252
+ id: 'data',
253
+ data,
254
+ color: theme.color.accentBoldGreen
255
+ }],
256
+ xAxis: {
257
+ data: ['A', 'B', 'C', 'D', 'E', 'F', 'G']
258
+ },
259
+ yAxis: {
260
+ domain: {
261
+ min: 0,
262
+ max: 100
263
+ }
264
+ },
265
+ children: [/*#__PURE__*/_jsx(XAxis, {
266
+ showLine: true,
267
+ showTickMarks: true,
268
+ label: "tickMarkSize=4 (default)",
269
+ tickMarkSize: 4
270
+ }), /*#__PURE__*/_jsx(XAxis, {
271
+ showLine: true,
272
+ showTickMarks: true,
273
+ height: 60,
274
+ label: "tickMarkSize=8",
275
+ position: "top",
276
+ tickMarkSize: 8
277
+ }), /*#__PURE__*/_jsx(YAxis, {
278
+ showLine: true,
279
+ showTickMarks: true,
280
+ label: "tickMarkSize=16",
281
+ position: "left",
282
+ tickMarkSize: 16,
283
+ width: 76
284
+ }), /*#__PURE__*/_jsx(YAxis, {
285
+ showLine: true,
286
+ showTickMarks: true,
287
+ label: "tickMarkSize=24",
288
+ position: "right",
289
+ tickMarkSize: 24,
290
+ width: 84
291
+ }), /*#__PURE__*/_jsx(Line, {
292
+ curve: "monotone",
293
+ seriesId: "data"
294
+ })]
295
+ });
296
+ };
197
297
  const DomainLimitType = _ref => {
198
298
  let {
199
299
  limit
@@ -249,6 +349,97 @@ const DomainLimitType = _ref => {
249
349
  }), /*#__PURE__*/_jsx(Scrubber, {})]
250
350
  });
251
351
  };
352
+
353
+ // Band scale with tick filtering - show every other tick
354
+ const BandScaleTickFiltering = () => /*#__PURE__*/_jsxs(CartesianChart, {
355
+ height: defaultChartHeight,
356
+ inset: 8,
357
+ series: [{
358
+ id: 'data',
359
+ data: [10, 22, 29, 45, 98, 45, 22, 35, 42, 18, 55, 67]
360
+ }],
361
+ xAxis: {
362
+ scaleType: 'band',
363
+ data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
364
+ },
365
+ yAxis: {
366
+ domain: {
367
+ min: 0
368
+ }
369
+ },
370
+ children: [/*#__PURE__*/_jsx(XAxis, {
371
+ showGrid: true,
372
+ showLine: true,
373
+ showTickMarks: true,
374
+ label: "ticks={(i) => i % 2 === 0}",
375
+ ticks: i => i % 2 === 0
376
+ }), /*#__PURE__*/_jsx(BarPlot, {})]
377
+ });
378
+
379
+ // Band scale with explicit ticks array
380
+ const BandScaleExplicitTicks = () => /*#__PURE__*/_jsxs(CartesianChart, {
381
+ height: defaultChartHeight,
382
+ inset: 8,
383
+ series: [{
384
+ id: 'data',
385
+ data: [10, 22, 29, 45, 98, 45, 22]
386
+ }],
387
+ xAxis: {
388
+ scaleType: 'band',
389
+ data: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
390
+ },
391
+ yAxis: {
392
+ domain: {
393
+ min: 0
394
+ }
395
+ },
396
+ children: [/*#__PURE__*/_jsx(XAxis, {
397
+ showGrid: true,
398
+ showLine: true,
399
+ showTickMarks: true,
400
+ label: "ticks={[0, 3, 6]} (first, middle, last)",
401
+ ticks: [0, 3, 6]
402
+ }), /*#__PURE__*/_jsx(BarPlot, {})]
403
+ });
404
+
405
+ // Line chart on band scale - comparing grid placements
406
+ const LineChartOnBandScale = _ref2 => {
407
+ let {
408
+ bandGridLinePlacement
409
+ } = _ref2;
410
+ const theme = useTheme();
411
+ return /*#__PURE__*/_jsxs(CartesianChart, {
412
+ height: 180,
413
+ inset: 8,
414
+ series: [{
415
+ id: 'line1',
416
+ data: [10, 22, 29, 45, 98, 45, 22],
417
+ color: theme.color.accentBoldBlue
418
+ }],
419
+ xAxis: {
420
+ scaleType: 'band',
421
+ data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
422
+ },
423
+ yAxis: {
424
+ domain: {
425
+ min: 0
426
+ }
427
+ },
428
+ children: [/*#__PURE__*/_jsx(XAxis, {
429
+ showGrid: true,
430
+ showLine: true,
431
+ showTickMarks: true,
432
+ bandGridLinePlacement: bandGridLinePlacement,
433
+ bandTickMarkPlacement: bandGridLinePlacement,
434
+ label: "bandGridLinePlacement: " + bandGridLinePlacement
435
+ }), /*#__PURE__*/_jsx(YAxis, {
436
+ showGrid: true,
437
+ position: "left"
438
+ }), /*#__PURE__*/_jsx(Line, {
439
+ seriesId: "line1"
440
+ })]
441
+ });
442
+ };
252
443
  const AxisStories = () => {
253
444
  return /*#__PURE__*/_jsxs(ExampleScreen, {
254
445
  children: [/*#__PURE__*/_jsx(Example, {
@@ -270,6 +461,74 @@ const AxisStories = () => {
270
461
  children: /*#__PURE__*/_jsx(DomainLimitType, {
271
462
  limit: "nice"
272
463
  })
464
+ }), /*#__PURE__*/_jsx(Example, {
465
+ title: "Band Axis Grid Alignment",
466
+ children: /*#__PURE__*/_jsxs(CartesianChart, {
467
+ height: 350,
468
+ inset: 8,
469
+ series: [{
470
+ id: 'prices',
471
+ data: [10, 22, 29, 45, 98, 45, 22]
472
+ }],
473
+ xAxis: {
474
+ scaleType: 'band',
475
+ data: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
476
+ },
477
+ yAxis: {
478
+ domain: {
479
+ min: 0
480
+ }
481
+ },
482
+ children: [/*#__PURE__*/_jsx(XAxis, {
483
+ showGrid: true,
484
+ showLine: true,
485
+ showTickMarks: true,
486
+ label: "Default"
487
+ }), /*#__PURE__*/_jsx(XAxis, {
488
+ showLine: true,
489
+ showTickMarks: true,
490
+ bandTickMarkPlacement: "start",
491
+ label: "Start"
492
+ }), /*#__PURE__*/_jsx(XAxis, {
493
+ showLine: true,
494
+ showTickMarks: true,
495
+ bandTickMarkPlacement: "end",
496
+ label: "End"
497
+ }), /*#__PURE__*/_jsx(XAxis, {
498
+ showLine: true,
499
+ showTickMarks: true,
500
+ bandTickMarkPlacement: "middle",
501
+ label: "Middle"
502
+ }), /*#__PURE__*/_jsx(XAxis, {
503
+ showLine: true,
504
+ showTickMarks: true,
505
+ bandTickMarkPlacement: "edges",
506
+ label: "Edges"
507
+ }), /*#__PURE__*/_jsx(BarPlot, {})]
508
+ })
509
+ }), /*#__PURE__*/_jsx(Example, {
510
+ title: "Band Scale - Tick Filtering",
511
+ children: /*#__PURE__*/_jsx(BandScaleTickFiltering, {})
512
+ }), /*#__PURE__*/_jsx(Example, {
513
+ title: "Band Scale - Explicit Ticks",
514
+ children: /*#__PURE__*/_jsx(BandScaleExplicitTicks, {})
515
+ }), /*#__PURE__*/_jsxs(Example, {
516
+ title: "Line Chart on Band Scale - Grid Positions",
517
+ children: [/*#__PURE__*/_jsx(LineChartOnBandScale, {
518
+ bandGridLinePlacement: "edges"
519
+ }), /*#__PURE__*/_jsx(LineChartOnBandScale, {
520
+ bandGridLinePlacement: "start"
521
+ }), /*#__PURE__*/_jsx(LineChartOnBandScale, {
522
+ bandGridLinePlacement: "middle"
523
+ }), /*#__PURE__*/_jsx(LineChartOnBandScale, {
524
+ bandGridLinePlacement: "end"
525
+ })]
526
+ }), /*#__PURE__*/_jsx(Example, {
527
+ title: "Axes on All Sides",
528
+ children: /*#__PURE__*/_jsx(AxesOnAllSides, {})
529
+ }), /*#__PURE__*/_jsx(Example, {
530
+ title: "Custom Tick Mark Sizes",
531
+ children: /*#__PURE__*/_jsx(CustomTickMarkSizes, {})
273
532
  })]
274
533
  });
275
534
  };
@@ -621,6 +621,34 @@ const ColorMapWithOpacity = () => {
621
621
  }
622
622
  });
623
623
  };
624
+ const BandGridPositionExample = _ref6 => {
625
+ let {
626
+ position
627
+ } = _ref6;
628
+ return /*#__PURE__*/_jsxs(CartesianChart, {
629
+ height: 180,
630
+ inset: 4,
631
+ series: [{
632
+ id: 'data',
633
+ data: [30, 50, 40, 60, 35]
634
+ }],
635
+ xAxis: {
636
+ scaleType: 'band',
637
+ data: ['A', 'B', 'C', 'D', 'E']
638
+ },
639
+ yAxis: {
640
+ domain: {
641
+ min: 0
642
+ }
643
+ },
644
+ children: [/*#__PURE__*/_jsx(XAxis, {
645
+ showGrid: true,
646
+ showLine: true,
647
+ bandGridLinePlacement: position,
648
+ label: position
649
+ }), /*#__PURE__*/_jsx(BarPlot, {})]
650
+ });
651
+ };
624
652
  const BarChartStories = () => {
625
653
  return /*#__PURE__*/_jsxs(ExampleScreen, {
626
654
  children: [/*#__PURE__*/_jsx(Example, {
@@ -662,6 +690,17 @@ const BarChartStories = () => {
662
690
  }), /*#__PURE__*/_jsx(Example, {
663
691
  title: "ColorMap with Opacity",
664
692
  children: /*#__PURE__*/_jsx(ColorMapWithOpacity, {})
693
+ }), /*#__PURE__*/_jsxs(Example, {
694
+ title: "Band Grid Position",
695
+ children: [/*#__PURE__*/_jsx(BandGridPositionExample, {
696
+ position: "edges"
697
+ }), /*#__PURE__*/_jsx(BandGridPositionExample, {
698
+ position: "start"
699
+ }), /*#__PURE__*/_jsx(BandGridPositionExample, {
700
+ position: "middle"
701
+ }), /*#__PURE__*/_jsx(BandGridPositionExample, {
702
+ position: "end"
703
+ })]
665
704
  })]
666
705
  });
667
706
  };
@@ -3,10 +3,41 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
3
3
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
4
4
  import { useCallback, useMemo, useState } from 'react';
5
5
  import { getChartDomain, getChartRange, isValidBounds } from './chart';
6
+ import { getPointOnScale } from './point';
6
7
  import { getCategoricalScale, getNumericScale, isCategoricalScale, isNumericScale } from './scale';
7
8
  export const defaultAxisId = 'DEFAULT_AXIS_ID';
8
9
  export const defaultAxisScaleType = 'linear';
9
10
 
11
+ /**
12
+ * Position options for band scale axis elements (grid lines, tick marks, labels).
13
+ *
14
+ * - `'start'` - At the start of each step (before bar padding)
15
+ * - `'middle'` - At the center of each band
16
+ * - `'end'` - At the end of each step (after bar padding)
17
+ * - `'edges'` - At start of each tick, plus end for the last tick (encloses the chart)
18
+ *
19
+ * @note These properties only apply when using a band scale (`scaleType: 'band'`).
20
+ */
21
+
22
+ /**
23
+ * Converts an AxisBandPlacement to the corresponding PointAnchor for use with getPointOnScale.
24
+ *
25
+ * @param placement - The axis placement value
26
+ * @returns The corresponding PointAnchor for scale calculations
27
+ */
28
+ export const toPointAnchor = placement => {
29
+ switch (placement) {
30
+ case 'edges': // edges uses stepStart for each tick, stepEnd handled separately
31
+ case 'start':
32
+ return 'stepStart';
33
+ case 'end':
34
+ return 'stepEnd';
35
+ case 'middle':
36
+ default:
37
+ return 'middle';
38
+ }
39
+ };
40
+
10
41
  /**
11
42
  * Axis configuration with computed bounds
12
43
  */
@@ -457,6 +488,7 @@ const generateEvenlyDistributedTicks = (scale, tickInterval, possibleTickValues,
457
488
  * // Returns tick positions centered in each selected band
458
489
  */
459
490
  export const getAxisTicksData = _ref4 => {
491
+ var _options$anchor;
460
492
  let {
461
493
  ticks,
462
494
  scaleFunction,
@@ -466,53 +498,37 @@ export const getAxisTicksData = _ref4 => {
466
498
  tickInterval,
467
499
  options
468
500
  } = _ref4;
501
+ const anchor = (_options$anchor = options == null ? void 0 : options.anchor) != null ? _options$anchor : 'middle';
502
+
469
503
  // Handle band scales
470
504
  if (isCategoricalScale(scaleFunction)) {
505
+ const bandScale = scaleFunction;
506
+
471
507
  // If explicit ticks are provided as array, use them
472
508
  if (Array.isArray(ticks)) {
473
- return ticks.filter(index => index >= 0 && index < categories.length).map(index => {
474
- var _bandwidth;
475
- // Band scales expect numeric indices, not category strings
476
- const position = scaleFunction(index);
477
- if (position === undefined) return null;
478
- return {
479
- tick: index,
480
- position: position + ((_bandwidth = scaleFunction.bandwidth == null ? void 0 : scaleFunction.bandwidth()) != null ? _bandwidth : 0) / 2
481
- };
482
- }).filter(Boolean);
509
+ return ticks.filter(index => index >= 0 && index < categories.length).map(index => ({
510
+ tick: index,
511
+ position: getPointOnScale(index, bandScale, anchor)
512
+ }));
483
513
  }
484
514
 
485
515
  // If a tick function is provided, use it to filter
486
516
  if (typeof ticks === 'function') {
487
517
  return categories.map((category, index) => {
488
- var _bandwidth2;
489
518
  if (!ticks(index)) return null;
490
-
491
- // Band scales expect numeric indices, not category strings
492
- const position = scaleFunction(index);
493
- if (position === undefined) return null;
494
519
  return {
495
520
  tick: index,
496
- position: position + ((_bandwidth2 = scaleFunction.bandwidth == null ? void 0 : scaleFunction.bandwidth()) != null ? _bandwidth2 : 0) / 2
521
+ position: getPointOnScale(index, bandScale, anchor)
497
522
  };
498
523
  }).filter(Boolean);
499
524
  }
500
- if (typeof ticks === 'boolean' && !ticks) {
501
- return [];
502
- }
503
525
 
504
526
  // For band scales without explicit ticks, show all categories
505
527
  // requestedTickCount is ignored for categorical scales - use ticks parameter to control visibility
506
- return categories.map((category, index) => {
507
- var _bandwidth3;
508
- // Band scales expect numeric indices, not category strings
509
- const position = scaleFunction(index);
510
- if (position === undefined) return null;
511
- return {
512
- tick: index,
513
- position: position + ((_bandwidth3 = scaleFunction.bandwidth == null ? void 0 : scaleFunction.bandwidth()) != null ? _bandwidth3 : 0) / 2
514
- };
515
- }).filter(Boolean);
528
+ return categories.map((_, index) => ({
529
+ tick: index,
530
+ position: getPointOnScale(index, bandScale, anchor)
531
+ }));
516
532
  }
517
533
 
518
534
  // Handle numeric scales