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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dts/chart/CartesianChart.d.ts +58 -7
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/Path.d.ts.map +1 -1
  5. package/dts/chart/area/Area.d.ts +7 -0
  6. package/dts/chart/area/Area.d.ts.map +1 -1
  7. package/dts/chart/area/AreaChart.d.ts +5 -5
  8. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  9. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  10. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  11. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  12. package/dts/chart/axis/Axis.d.ts +3 -1
  13. package/dts/chart/axis/Axis.d.ts.map +1 -1
  14. package/dts/chart/axis/XAxis.d.ts +6 -0
  15. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  16. package/dts/chart/axis/YAxis.d.ts +1 -0
  17. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  18. package/dts/chart/bar/Bar.d.ts +4 -2
  19. package/dts/chart/bar/Bar.d.ts.map +1 -1
  20. package/dts/chart/bar/BarChart.d.ts +49 -9
  21. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  22. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  23. package/dts/chart/bar/BarStack.d.ts +30 -9
  24. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  25. package/dts/chart/bar/BarStackGroup.d.ts +1 -1
  26. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  27. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  28. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  29. package/dts/chart/gradient/Gradient.d.ts +5 -0
  30. package/dts/chart/gradient/Gradient.d.ts.map +1 -1
  31. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  32. package/dts/chart/line/Line.d.ts +7 -0
  33. package/dts/chart/line/Line.d.ts.map +1 -1
  34. package/dts/chart/line/LineChart.d.ts +22 -8
  35. package/dts/chart/line/LineChart.d.ts.map +1 -1
  36. package/dts/chart/line/ReferenceLine.d.ts +1 -0
  37. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  38. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  39. package/dts/chart/point/Point.d.ts +7 -0
  40. package/dts/chart/point/Point.d.ts.map +1 -1
  41. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -1
  42. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +2 -1
  43. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -1
  44. package/dts/chart/scrubber/Scrubber.d.ts +8 -0
  45. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  46. package/dts/chart/scrubber/ScrubberAccessibilityView.d.ts +12 -0
  47. package/dts/chart/scrubber/ScrubberAccessibilityView.d.ts.map +1 -0
  48. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -1
  49. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  50. package/dts/chart/utils/axis.d.ts +20 -9
  51. package/dts/chart/utils/axis.d.ts.map +1 -1
  52. package/dts/chart/utils/bar.d.ts +4 -3
  53. package/dts/chart/utils/bar.d.ts.map +1 -1
  54. package/dts/chart/utils/chart.d.ts +13 -0
  55. package/dts/chart/utils/chart.d.ts.map +1 -1
  56. package/dts/chart/utils/context.d.ts +21 -6
  57. package/dts/chart/utils/context.d.ts.map +1 -1
  58. package/dts/chart/utils/gradient.d.ts +3 -1
  59. package/dts/chart/utils/gradient.d.ts.map +1 -1
  60. package/dts/chart/utils/path.d.ts +20 -0
  61. package/dts/chart/utils/path.d.ts.map +1 -1
  62. package/dts/chart/utils/point.d.ts +7 -0
  63. package/dts/chart/utils/point.d.ts.map +1 -1
  64. package/esm/chart/CartesianChart.js +145 -81
  65. package/esm/chart/Path.js +10 -7
  66. package/esm/chart/__stories__/CartesianChart.stories.js +10 -0
  67. package/esm/chart/__stories__/ChartAccessibility.stories.js +721 -0
  68. package/esm/chart/area/Area.js +19 -9
  69. package/esm/chart/area/AreaChart.js +11 -9
  70. package/esm/chart/area/DottedArea.js +11 -6
  71. package/esm/chart/area/GradientArea.js +11 -6
  72. package/esm/chart/area/SolidArea.js +3 -1
  73. package/esm/chart/area/__stories__/AreaChart.stories.js +47 -5
  74. package/esm/chart/axis/XAxis.js +14 -21
  75. package/esm/chart/axis/YAxis.js +4 -3
  76. package/esm/chart/axis/__stories__/Axis.stories.js +65 -48
  77. package/esm/chart/bar/Bar.js +9 -5
  78. package/esm/chart/bar/BarChart.js +34 -31
  79. package/esm/chart/bar/BarPlot.js +7 -5
  80. package/esm/chart/bar/BarStack.js +176 -36
  81. package/esm/chart/bar/BarStackGroup.js +37 -27
  82. package/esm/chart/bar/DefaultBar.js +24 -8
  83. package/esm/chart/bar/DefaultBarStack.js +24 -10
  84. package/esm/chart/bar/__stories__/BarChart.stories.js +105 -3
  85. package/esm/chart/gradient/Gradient.js +2 -1
  86. package/esm/chart/line/DottedLine.js +3 -1
  87. package/esm/chart/line/Line.js +32 -19
  88. package/esm/chart/line/LineChart.js +31 -9
  89. package/esm/chart/line/SolidLine.js +3 -1
  90. package/esm/chart/line/__stories__/LineChart.stories.js +115 -46
  91. package/esm/chart/point/Point.js +2 -1
  92. package/esm/chart/scrubber/DefaultScrubberBeacon.js +1 -1
  93. package/esm/chart/scrubber/DefaultScrubberLabel.js +26 -10
  94. package/esm/chart/scrubber/Scrubber.js +47 -21
  95. package/esm/chart/scrubber/ScrubberAccessibilityView.js +177 -0
  96. package/esm/chart/scrubber/ScrubberBeaconGroup.js +24 -20
  97. package/esm/chart/scrubber/ScrubberProvider.js +29 -24
  98. package/esm/chart/scrubber/__stories__/Scrubber.stories.js +192 -6
  99. package/esm/chart/utils/axis.js +42 -14
  100. package/esm/chart/utils/bar.js +5 -4
  101. package/esm/chart/utils/chart.js +18 -5
  102. package/esm/chart/utils/context.js +7 -0
  103. package/esm/chart/utils/gradient.js +8 -4
  104. package/esm/chart/utils/path.js +90 -61
  105. package/esm/chart/utils/point.js +28 -18
  106. package/package.json +5 -5
@@ -0,0 +1,177 @@
1
+ import React, { memo, useCallback, useMemo } from 'react';
2
+ import { Pressable, StyleSheet, View } from 'react-native';
3
+ import { useScreenReaderStatus } from '@coinbase/cds-mobile/hooks/useScreenReaderStatus';
4
+ import { useCartesianChartContext } from '../ChartProvider';
5
+ import { useScrubberContext } from '../utils';
6
+ import { getPointOnSerializableScale } from '../utils/point';
7
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+ const normalizeScrubberAccessibilityStep = function (step, defaultStep) {
9
+ if (defaultStep === void 0) {
10
+ defaultStep = 1;
11
+ }
12
+ const resolvedDefaultStep = Number.isFinite(defaultStep) ? Math.max(1, Math.floor(defaultStep)) : 1;
13
+ if (step === undefined || !Number.isFinite(step)) {
14
+ return resolvedDefaultStep;
15
+ }
16
+ return Math.max(1, Math.floor(step));
17
+ };
18
+ const getScrubberSampledIndices = (dataLength, step) => {
19
+ if (dataLength <= 0) return [];
20
+ const lastIndex = dataLength - 1;
21
+ if (lastIndex === 0) return [0];
22
+ const normalizedStep = Math.max(1, Math.floor(step));
23
+ const sampledIndices = [0];
24
+ for (let dataIndex = normalizedStep; dataIndex < lastIndex; dataIndex += normalizedStep) {
25
+ sampledIndices.push(dataIndex);
26
+ }
27
+ sampledIndices.push(lastIndex);
28
+ return sampledIndices;
29
+ };
30
+ const getCategoryValueForIndex = (index, scale, axis) => {
31
+ if (scale.type === 'band') {
32
+ return index;
33
+ }
34
+ const axisData = axis == null ? void 0 : axis.data;
35
+ if (axisData && Array.isArray(axisData) && typeof axisData[0] === 'number') {
36
+ var _numericData$index;
37
+ const numericData = axisData;
38
+ return (_numericData$index = numericData[index]) != null ? _numericData$index : index;
39
+ }
40
+ return index;
41
+ };
42
+ const getScrubberSegmentWeights = function (sampledIndices, dataLength, categoryScale, categoryAxis, drawingArea, orientation) {
43
+ if (orientation === void 0) {
44
+ orientation = 'horizontal';
45
+ }
46
+ const dimensionSize = orientation === 'horizontal' ? drawingArea.width : drawingArea.height;
47
+ const dimensionStart = orientation === 'horizontal' ? drawingArea.x : drawingArea.y;
48
+ const dimensionEnd = dimensionStart + dimensionSize;
49
+ if (sampledIndices.length === 0 || !categoryScale || !categoryAxis || dimensionSize <= 0) {
50
+ const segmentWeights = sampledIndices.map((index, position) => {
51
+ var _sampledIndices;
52
+ const nextIndex = (_sampledIndices = sampledIndices[position + 1]) != null ? _sampledIndices : dataLength;
53
+ return Math.max(1, nextIndex - index);
54
+ });
55
+ return {
56
+ leading: 0,
57
+ segmentWeights,
58
+ trailing: 0
59
+ };
60
+ }
61
+ if (categoryScale.type === 'band') {
62
+ const bandScale = categoryScale;
63
+ const segmentWeights = [];
64
+ let leading = 0;
65
+ let trailing = 0;
66
+ for (let i = 0; i < sampledIndices.length; i++) {
67
+ const categoryValue = getCategoryValueForIndex(sampledIndices[i], categoryScale, categoryAxis);
68
+ const posStart = getPointOnSerializableScale(categoryValue, bandScale, 'stepStart');
69
+ const posEnd = getPointOnSerializableScale(categoryValue, bandScale, 'stepEnd');
70
+ segmentWeights.push(Math.max(1, Math.abs(posEnd - posStart)));
71
+ if (i === 0) {
72
+ leading = Math.max(0, Math.min(posStart, posEnd) - dimensionStart);
73
+ }
74
+ if (i === sampledIndices.length - 1) {
75
+ trailing = Math.max(0, dimensionEnd - Math.max(posStart, posEnd));
76
+ }
77
+ }
78
+ return {
79
+ leading,
80
+ segmentWeights,
81
+ trailing
82
+ };
83
+ }
84
+ const segmentWeights = sampledIndices.map((index, position) => {
85
+ const prevIndex = position > 0 ? sampledIndices[position - 1] : -1;
86
+ const categoryValue = getCategoryValueForIndex(index, categoryScale, categoryAxis);
87
+ const posEnd = getPointOnSerializableScale(categoryValue, categoryScale);
88
+ const posStart = prevIndex < 0 ? dimensionStart : getPointOnSerializableScale(getCategoryValueForIndex(prevIndex, categoryScale, categoryAxis), categoryScale);
89
+ return Math.max(1, Math.abs(posEnd - posStart));
90
+ });
91
+ return {
92
+ leading: 0,
93
+ segmentWeights,
94
+ trailing: 0
95
+ };
96
+ };
97
+ const styles = StyleSheet.create({
98
+ container: {
99
+ position: 'absolute'
100
+ },
101
+ segments: {
102
+ flex: 1
103
+ }
104
+ });
105
+ export const ScrubberAccessibilityView = /*#__PURE__*/memo(_ref => {
106
+ let {
107
+ accessibilityLabel,
108
+ accessibilityStep
109
+ } = _ref;
110
+ const isScreenReaderEnabled = useScreenReaderStatus();
111
+ const {
112
+ dataLength,
113
+ drawingArea,
114
+ layout,
115
+ getXAxis,
116
+ getYAxis,
117
+ getXSerializableScale,
118
+ getYSerializableScale
119
+ } = useCartesianChartContext();
120
+ const {
121
+ enableScrubbing
122
+ } = useScrubberContext();
123
+ const isHorizontalLayout = layout === 'horizontal';
124
+ const categoryAxis = useMemo(() => isHorizontalLayout ? getYAxis() : getXAxis(), [isHorizontalLayout, getXAxis, getYAxis]);
125
+ const categoryScale = useMemo(() => isHorizontalLayout ? getYSerializableScale() : getXSerializableScale(), [isHorizontalLayout, getXSerializableScale, getYSerializableScale]);
126
+ const resolvedStep = useMemo(() => normalizeScrubberAccessibilityStep(accessibilityStep), [accessibilityStep]);
127
+ const sampledIndices = useMemo(() => getScrubberSampledIndices(dataLength, resolvedStep), [dataLength, resolvedStep]);
128
+ const segmentOrientation = isHorizontalLayout ? 'vertical' : 'horizontal';
129
+ const {
130
+ leading,
131
+ segmentWeights,
132
+ trailing
133
+ } = useMemo(() => getScrubberSegmentWeights(sampledIndices, dataLength, categoryScale, categoryAxis, drawingArea, segmentOrientation), [sampledIndices, dataLength, categoryScale, categoryAxis, drawingArea, segmentOrientation]);
134
+ const sampledSegments = useMemo(() => {
135
+ if (accessibilityLabel === undefined) return [];
136
+ return sampledIndices.map((index, position) => {
137
+ var _segmentWeights$posit;
138
+ const weight = (_segmentWeights$posit = segmentWeights[position]) != null ? _segmentWeights$posit : 1;
139
+ const pointLabel = accessibilityLabel(index);
140
+ return {
141
+ index,
142
+ weight,
143
+ accessibilityLabel: pointLabel || "Data point " + (index + 1)
144
+ };
145
+ });
146
+ }, [accessibilityLabel, sampledIndices, segmentWeights]);
147
+ const getSegmentStyle = useCallback(weight => ({
148
+ flex: weight
149
+ }), []);
150
+ const overlayStyle = useMemo(() => ({
151
+ left: drawingArea.x,
152
+ top: drawingArea.y,
153
+ width: drawingArea.width,
154
+ height: drawingArea.height
155
+ }), [drawingArea.x, drawingArea.y, drawingArea.width, drawingArea.height]);
156
+ const shouldHide = useMemo(() => !isScreenReaderEnabled || !enableScrubbing || !accessibilityLabel || dataLength <= 0 || drawingArea.width <= 0 || drawingArea.height <= 0 || sampledSegments.length === 0, [isScreenReaderEnabled, enableScrubbing, accessibilityLabel, dataLength, drawingArea.width, drawingArea.height, sampledSegments.length]);
157
+ if (shouldHide) return;
158
+ const segmentsFlexDirection = isHorizontalLayout ? 'column' : 'row';
159
+ return /*#__PURE__*/_jsx(View, {
160
+ pointerEvents: "box-none",
161
+ style: [styles.container, overlayStyle],
162
+ children: /*#__PURE__*/_jsxs(View, {
163
+ style: [styles.segments, {
164
+ flexDirection: segmentsFlexDirection
165
+ }],
166
+ children: [leading > 0 && /*#__PURE__*/_jsx(View, {
167
+ style: getSegmentStyle(leading)
168
+ }), sampledSegments.map(segment => /*#__PURE__*/_jsx(Pressable, {
169
+ accessible: true,
170
+ accessibilityLabel: segment.accessibilityLabel,
171
+ style: getSegmentStyle(segment.weight)
172
+ }, segment.index)), trailing > 0 && /*#__PURE__*/_jsx(View, {
173
+ style: getSegmentStyle(trailing)
174
+ })]
175
+ })
176
+ });
177
+ });
@@ -12,7 +12,7 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
12
12
  let {
13
13
  seriesId,
14
14
  dataIndex,
15
- dataX,
15
+ dataIndexValue,
16
16
  isIdle,
17
17
  BeaconComponent,
18
18
  idlePulse,
@@ -22,6 +22,7 @@ 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,
@@ -49,10 +50,10 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
49
50
  // Get scales for gradient evaluation
50
51
  const gradientScale = useMemo(() => {
51
52
  if (!gradient) return undefined;
52
- const scale = gradient.axis === 'x' ? getXScale() : getYScale(series == null ? void 0 : series.yAxisId);
53
+ const scale = gradient.axis === 'x' ? getXScale(series == null ? void 0 : series.xAxisId) : getYScale(series == null ? void 0 : series.yAxisId);
53
54
  if (!scale) return undefined;
54
55
  return convertToSerializableScale(scale);
55
- }, [gradient, getXScale, getYScale, series == null ? void 0 : series.yAxisId]);
56
+ }, [gradient, getXScale, getYScale, series == null ? void 0 : series.xAxisId, series == null ? void 0 : series.yAxisId]);
56
57
  const gradientStops = useMemo(() => {
57
58
  if (!gradient || !gradientScale) return undefined;
58
59
  const domain = {
@@ -70,25 +71,25 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
70
71
  var _series$color;
71
72
  if (gradient && gradientScale && gradientStops) {
72
73
  var _gradient$axis;
73
- const axis = (_gradient$axis = gradient.axis) != null ? _gradient$axis : 'y';
74
- const dataValue = axis === 'x' ? dataX.value : dataY.value;
75
- if (dataValue !== undefined) {
76
- const evaluatedColor = evaluateGradientAtValue(gradientStops, dataValue, gradientScale);
77
- if (evaluatedColor) {
78
- return evaluatedColor;
79
- }
74
+ const categoryAxisIsX = layout !== 'horizontal';
75
+ const gradientAxis = (_gradient$axis = gradient.axis) != null ? _gradient$axis : 'y';
76
+ const valueForAxis = gradientAxis === 'x' ? categoryAxisIsX ? dataIndexValue.value : dataY.value : categoryAxisIsX ? dataY.value : dataIndexValue.value;
77
+ const evaluatedColor = evaluateGradientAtValue(gradientStops, valueForAxis, gradientScale);
78
+ if (evaluatedColor) {
79
+ return evaluatedColor;
80
80
  }
81
81
  }
82
82
 
83
83
  // Fallback to series color
84
84
  return (_series$color = series == null ? void 0 : series.color) != null ? _series$color : theme.color.fgPrimary;
85
- }, [gradient, gradientScale, gradientStops, dataX, dataY, series == null ? void 0 : series.color, theme.color.fgPrimary]);
85
+ }, [gradient, gradientScale, gradientStops, dataIndexValue, dataY, series == null ? void 0 : series.color, theme.color.fgPrimary, layout]);
86
+ const categoryAxisIsX = layout !== 'horizontal';
86
87
  return /*#__PURE__*/_jsx(BeaconComponent, {
87
88
  ref: beaconRef,
88
89
  animate: animate,
89
90
  color: color,
90
- dataX: dataX,
91
- dataY: dataY,
91
+ dataX: categoryAxisIsX ? dataIndexValue : dataY,
92
+ dataY: categoryAxisIsX ? dataY : dataIndexValue,
92
93
  idlePulse: idlePulse,
93
94
  isIdle: isIdle,
94
95
  seriesId: seriesId,
@@ -109,12 +110,15 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
109
110
  scrubberPosition
110
111
  } = useScrubberContext();
111
112
  const {
113
+ layout,
112
114
  getXAxis,
115
+ getYAxis,
113
116
  series,
114
117
  dataLength,
115
118
  animate
116
119
  } = useCartesianChartContext();
117
- const xAxis = useMemo(() => getXAxis(), [getXAxis]);
120
+ const categoryAxisIsX = useMemo(() => layout !== 'horizontal', [layout]);
121
+ const indexAxis = useMemo(() => categoryAxisIsX ? getXAxis() : getYAxis(), [categoryAxisIsX, getXAxis, getYAxis]);
118
122
 
119
123
  // Expose imperative handle with pulse method
120
124
  useImperativeHandle(ref, () => ({
@@ -132,14 +136,14 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
132
136
  var _scrubberPosition$val;
133
137
  return (_scrubberPosition$val = scrubberPosition.value) != null ? _scrubberPosition$val : Math.max(0, dataLength - 1);
134
138
  }, [scrubberPosition, dataLength]);
135
- const dataX = useDerivedValue(() => {
136
- // Convert index to actual x value if axis has data
137
- if (xAxis != null && xAxis.data && Array.isArray(xAxis.data) && xAxis.data[dataIndex.value] !== undefined) {
138
- const dataValue = xAxis.data[dataIndex.value];
139
+ const dataIndexValue = useDerivedValue(() => {
140
+ // Convert index to actual category-axis value if axis has data.
141
+ if (indexAxis != null && indexAxis.data && Array.isArray(indexAxis.data) && indexAxis.data[dataIndex.value] !== undefined) {
142
+ const dataValue = indexAxis.data[dataIndex.value];
139
143
  return typeof dataValue === 'string' ? dataIndex.value : dataValue;
140
144
  }
141
145
  return dataIndex.value;
142
- }, [xAxis, dataIndex]);
146
+ }, [indexAxis, dataIndex]);
143
147
  const isIdle = useDerivedValue(() => {
144
148
  return scrubberPosition.value === undefined;
145
149
  }, [scrubberPosition]);
@@ -155,7 +159,7 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
155
159
  animate: animate,
156
160
  beaconRef: createBeaconRef(s.id),
157
161
  dataIndex: dataIndex,
158
- dataX: dataX,
162
+ dataIndexValue: dataIndexValue,
159
163
  idlePulse: idlePulse,
160
164
  isIdle: isIdle,
161
165
  seriesId: s.id,
@@ -23,25 +23,29 @@ export const ScrubberProvider = _ref => {
23
23
  throw new Error('ScrubberProvider must be used within a ChartContext');
24
24
  }
25
25
  const {
26
+ layout,
26
27
  getXSerializableScale,
27
- getXAxis
28
+ getYSerializableScale,
29
+ getXAxis,
30
+ getYAxis
28
31
  } = chartContext;
29
32
  const scrubberPosition = useSharedValue(undefined);
30
- const xAxis = useMemo(() => getXAxis(), [getXAxis]);
31
- const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
32
- const getDataIndexFromX = useCallback(touchX => {
33
+ const categoryAxisIsX = useMemo(() => layout !== 'horizontal', [layout]);
34
+ const categoryAxis = useMemo(() => categoryAxisIsX ? getXAxis() : getYAxis(), [categoryAxisIsX, getXAxis, getYAxis]);
35
+ const categoryScale = useMemo(() => categoryAxisIsX ? getXSerializableScale() : getYSerializableScale(), [categoryAxisIsX, getXSerializableScale, getYSerializableScale]);
36
+ const getDataIndexFromPosition = useCallback(touchPosition => {
33
37
  'worklet';
34
38
 
35
- if (!xScale || !xAxis) return 0;
36
- if (xScale.type === 'band') {
37
- const [domainMin, domainMax] = xScale.domain;
39
+ if (!categoryScale || !categoryAxis) return 0;
40
+ if (categoryScale.type === 'band') {
41
+ const [domainMin, domainMax] = categoryScale.domain;
38
42
  const categoryCount = domainMax - domainMin + 1;
39
43
  let closestIndex = 0;
40
44
  let closestDistance = Infinity;
41
45
  for (let i = 0; i < categoryCount; i++) {
42
- const xPos = getPointOnSerializableScale(i, xScale);
43
- if (xPos !== undefined) {
44
- const distance = Math.abs(touchX - xPos);
46
+ const categoryPos = getPointOnSerializableScale(i, categoryScale);
47
+ if (categoryPos !== undefined) {
48
+ const distance = Math.abs(touchPosition - categoryPos);
45
49
  if (distance < closestDistance) {
46
50
  closestDistance = distance;
47
51
  closestIndex = i;
@@ -50,18 +54,17 @@ export const ScrubberProvider = _ref => {
50
54
  }
51
55
  return closestIndex;
52
56
  } else {
53
- // For numeric scales with axis data, find the nearest data point
54
- const axisData = xAxis.data;
57
+ // For numeric scales with axis data, find the nearest data point.
58
+ const axisData = categoryAxis.data;
55
59
  if (axisData && Array.isArray(axisData) && typeof axisData[0] === 'number') {
56
- // We have numeric axis data - find the closest data point
57
60
  const numericData = axisData;
58
61
  let closestIndex = 0;
59
62
  let closestDistance = Infinity;
60
63
  for (let i = 0; i < numericData.length; i++) {
61
- const xValue = numericData[i];
62
- const xPos = getPointOnSerializableScale(xValue, xScale);
63
- if (xPos !== undefined) {
64
- const distance = Math.abs(touchX - xPos);
64
+ const dataValue = numericData[i];
65
+ const categoryPos = getPointOnSerializableScale(dataValue, categoryScale);
66
+ if (categoryPos !== undefined) {
67
+ const distance = Math.abs(touchPosition - categoryPos);
65
68
  if (distance < closestDistance) {
66
69
  closestDistance = distance;
67
70
  closestIndex = i;
@@ -71,13 +74,13 @@ export const ScrubberProvider = _ref => {
71
74
  return closestIndex;
72
75
  } else {
73
76
  var _domain$min, _domain$max;
74
- const xValue = invertSerializableScale(touchX, xScale);
75
- const dataIndex = Math.round(xValue);
76
- const domain = xAxis.domain;
77
+ const dataValue = invertSerializableScale(touchPosition, categoryScale);
78
+ const dataIndex = Math.round(dataValue);
79
+ const domain = categoryAxis.domain;
77
80
  return Math.max((_domain$min = domain.min) != null ? _domain$min : 0, Math.min(dataIndex, (_domain$max = domain.max) != null ? _domain$max : 0));
78
81
  }
79
82
  }
80
- }, [xAxis, xScale]);
83
+ }, [categoryAxis, categoryScale]);
81
84
  const handleStartEndHaptics = useCallback(() => {
82
85
  void Haptics.lightImpact();
83
86
  }, []);
@@ -95,13 +98,15 @@ export const ScrubberProvider = _ref => {
95
98
 
96
99
  // Android does not trigger onUpdate when the gesture starts. This achieves consistent behavior across both iOS and Android
97
100
  if (Platform.OS === 'android') {
98
- const newScrubberPosition = getDataIndexFromX(event.x);
101
+ const pointerPosition = categoryAxisIsX ? event.x : event.y;
102
+ const newScrubberPosition = getDataIndexFromPosition(pointerPosition);
99
103
  if (newScrubberPosition !== scrubberPosition.value) {
100
104
  scrubberPosition.value = newScrubberPosition;
101
105
  }
102
106
  }
103
107
  }).onUpdate(function onUpdate(event) {
104
- const newScrubberPosition = getDataIndexFromX(event.x);
108
+ const pointerPosition = categoryAxisIsX ? event.x : event.y;
109
+ const newScrubberPosition = getDataIndexFromPosition(pointerPosition);
105
110
  if (newScrubberPosition !== scrubberPosition.value) {
106
111
  scrubberPosition.value = newScrubberPosition;
107
112
  }
@@ -114,7 +119,7 @@ export const ScrubberProvider = _ref => {
114
119
  if (enableScrubbing) {
115
120
  scrubberPosition.value = undefined;
116
121
  }
117
- }), [allowOverflowGestures, handleStartEndHaptics, getDataIndexFromX, scrubberPosition, enableScrubbing]);
122
+ }), [allowOverflowGestures, handleStartEndHaptics, getDataIndexFromPosition, categoryAxisIsX, scrubberPosition, enableScrubbing]);
118
123
  const contextValue = useMemo(() => ({
119
124
  enableScrubbing: !!enableScrubbing,
120
125
  scrubberPosition