@coinbase/cds-mobile-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 (170) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/dts/chart/CartesianChart.d.ts +92 -7
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/ChartContextBridge.d.ts.map +1 -1
  5. package/dts/chart/ChartProvider.d.ts +3 -0
  6. package/dts/chart/ChartProvider.d.ts.map +1 -1
  7. package/dts/chart/Path.d.ts +36 -13
  8. package/dts/chart/Path.d.ts.map +1 -1
  9. package/dts/chart/PeriodSelector.d.ts +21 -6
  10. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  11. package/dts/chart/area/Area.d.ts +14 -11
  12. package/dts/chart/area/Area.d.ts.map +1 -1
  13. package/dts/chart/area/AreaChart.d.ts +33 -9
  14. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  15. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  16. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  17. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  18. package/dts/chart/axis/Axis.d.ts +22 -42
  19. package/dts/chart/axis/Axis.d.ts.map +1 -1
  20. package/dts/chart/axis/XAxis.d.ts +6 -0
  21. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  22. package/dts/chart/axis/YAxis.d.ts +1 -0
  23. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  24. package/dts/chart/bar/Bar.d.ts +51 -51
  25. package/dts/chart/bar/Bar.d.ts.map +1 -1
  26. package/dts/chart/bar/BarChart.d.ts +56 -11
  27. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  28. package/dts/chart/bar/BarPlot.d.ts +2 -1
  29. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  30. package/dts/chart/bar/BarStack.d.ts +45 -20
  31. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  32. package/dts/chart/bar/BarStackGroup.d.ts +2 -1
  33. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  34. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  35. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  36. package/dts/chart/gradient/Gradient.d.ts +5 -0
  37. package/dts/chart/gradient/Gradient.d.ts.map +1 -1
  38. package/dts/chart/index.d.ts +1 -0
  39. package/dts/chart/index.d.ts.map +1 -1
  40. package/dts/chart/legend/DefaultLegendEntry.d.ts +5 -0
  41. package/dts/chart/legend/DefaultLegendEntry.d.ts.map +1 -0
  42. package/dts/chart/legend/DefaultLegendShape.d.ts +5 -0
  43. package/dts/chart/legend/DefaultLegendShape.d.ts.map +1 -0
  44. package/dts/chart/legend/Legend.d.ts +168 -0
  45. package/dts/chart/legend/Legend.d.ts.map +1 -0
  46. package/dts/chart/legend/index.d.ts +4 -0
  47. package/dts/chart/legend/index.d.ts.map +1 -0
  48. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  49. package/dts/chart/line/Line.d.ts +23 -19
  50. package/dts/chart/line/Line.d.ts.map +1 -1
  51. package/dts/chart/line/LineChart.d.ts +26 -9
  52. package/dts/chart/line/LineChart.d.ts.map +1 -1
  53. package/dts/chart/line/ReferenceLine.d.ts +1 -0
  54. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  55. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  56. package/dts/chart/point/Point.d.ts +26 -2
  57. package/dts/chart/point/Point.d.ts.map +1 -1
  58. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +32 -2
  59. package/dts/chart/scrubber/DefaultScrubberBeacon.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 +86 -17
  63. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  64. package/dts/chart/scrubber/ScrubberAccessibilityView.d.ts +12 -0
  65. package/dts/chart/scrubber/ScrubberAccessibilityView.d.ts.map +1 -0
  66. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +10 -0
  67. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -1
  68. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +16 -1
  69. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -1
  70. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  71. package/dts/chart/utils/axis.d.ts +45 -10
  72. package/dts/chart/utils/axis.d.ts.map +1 -1
  73. package/dts/chart/utils/bar.d.ts +190 -0
  74. package/dts/chart/utils/bar.d.ts.map +1 -1
  75. package/dts/chart/utils/chart.d.ts +32 -0
  76. package/dts/chart/utils/chart.d.ts.map +1 -1
  77. package/dts/chart/utils/context.d.ts +21 -6
  78. package/dts/chart/utils/context.d.ts.map +1 -1
  79. package/dts/chart/utils/gradient.d.ts +3 -1
  80. package/dts/chart/utils/gradient.d.ts.map +1 -1
  81. package/dts/chart/utils/path.d.ts +26 -0
  82. package/dts/chart/utils/path.d.ts.map +1 -1
  83. package/dts/chart/utils/point.d.ts +24 -12
  84. package/dts/chart/utils/point.d.ts.map +1 -1
  85. package/dts/chart/utils/scale.d.ts +11 -0
  86. package/dts/chart/utils/scale.d.ts.map +1 -1
  87. package/dts/chart/utils/scrubber.d.ts +2 -1
  88. package/dts/chart/utils/scrubber.d.ts.map +1 -1
  89. package/dts/chart/utils/transition.d.ts +63 -22
  90. package/dts/chart/utils/transition.d.ts.map +1 -1
  91. package/dts/sparkline/Sparkline.d.ts +2 -1
  92. package/dts/sparkline/Sparkline.d.ts.map +1 -1
  93. package/dts/sparkline/SparklineArea.d.ts +2 -1
  94. package/dts/sparkline/SparklineArea.d.ts.map +1 -1
  95. package/dts/sparkline/SparklineGradient.d.ts +2 -1
  96. package/dts/sparkline/SparklineGradient.d.ts.map +1 -1
  97. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts +2 -1
  98. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts.map +1 -1
  99. package/esm/chart/CartesianChart.js +176 -82
  100. package/esm/chart/ChartContextBridge.js +14 -3
  101. package/esm/chart/ChartProvider.js +2 -2
  102. package/esm/chart/Path.js +34 -29
  103. package/esm/chart/PeriodSelector.js +6 -2
  104. package/esm/chart/__stories__/CartesianChart.stories.js +27 -86
  105. package/esm/chart/__stories__/ChartAccessibility.stories.js +721 -0
  106. package/esm/chart/__stories__/ChartTransitions.stories.js +625 -0
  107. package/esm/chart/__stories__/PeriodSelector.stories.js +102 -4
  108. package/esm/chart/area/Area.js +21 -9
  109. package/esm/chart/area/AreaChart.js +18 -13
  110. package/esm/chart/area/DottedArea.js +28 -18
  111. package/esm/chart/area/GradientArea.js +14 -7
  112. package/esm/chart/area/SolidArea.js +6 -2
  113. package/esm/chart/area/__stories__/AreaChart.stories.js +47 -5
  114. package/esm/chart/axis/Axis.js +5 -41
  115. package/esm/chart/axis/XAxis.js +116 -47
  116. package/esm/chart/axis/YAxis.js +105 -26
  117. package/esm/chart/axis/__stories__/Axis.stories.js +324 -48
  118. package/esm/chart/bar/Bar.js +17 -15
  119. package/esm/chart/bar/BarChart.js +38 -33
  120. package/esm/chart/bar/BarPlot.js +40 -45
  121. package/esm/chart/bar/BarStack.js +92 -475
  122. package/esm/chart/bar/BarStackGroup.js +37 -27
  123. package/esm/chart/bar/DefaultBar.js +27 -18
  124. package/esm/chart/bar/DefaultBarStack.js +25 -9
  125. package/esm/chart/bar/__stories__/BarChart.stories.js +728 -54
  126. package/esm/chart/gradient/Gradient.js +2 -1
  127. package/esm/chart/index.js +1 -0
  128. package/esm/chart/legend/DefaultLegendEntry.js +42 -0
  129. package/esm/chart/legend/DefaultLegendShape.js +64 -0
  130. package/esm/chart/legend/Legend.js +59 -0
  131. package/esm/chart/legend/__stories__/Legend.stories.js +574 -0
  132. package/esm/chart/legend/index.js +3 -0
  133. package/esm/chart/line/DottedLine.js +6 -2
  134. package/esm/chart/line/Line.js +42 -38
  135. package/esm/chart/line/LineChart.js +36 -12
  136. package/esm/chart/line/SolidLine.js +6 -2
  137. package/esm/chart/line/__stories__/LineChart.stories.js +241 -594
  138. package/esm/chart/line/__stories__/ReferenceLine.stories.js +95 -1
  139. package/esm/chart/point/Point.js +35 -36
  140. package/esm/chart/scrubber/DefaultScrubberBeacon.js +41 -38
  141. package/esm/chart/scrubber/DefaultScrubberLabel.js +26 -10
  142. package/esm/chart/scrubber/Scrubber.js +67 -35
  143. package/esm/chart/scrubber/ScrubberAccessibilityView.js +177 -0
  144. package/esm/chart/scrubber/ScrubberBeaconGroup.js +30 -22
  145. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +35 -8
  146. package/esm/chart/scrubber/ScrubberProvider.js +29 -24
  147. package/esm/chart/scrubber/__stories__/Scrubber.stories.js +946 -0
  148. package/esm/chart/utils/axis.js +88 -44
  149. package/esm/chart/utils/bar.js +820 -0
  150. package/esm/chart/utils/chart.js +34 -7
  151. package/esm/chart/utils/context.js +7 -0
  152. package/esm/chart/utils/gradient.js +8 -4
  153. package/esm/chart/utils/path.js +91 -61
  154. package/esm/chart/utils/point.js +92 -39
  155. package/esm/chart/utils/scale.js +13 -2
  156. package/esm/chart/utils/scrubber.js +12 -5
  157. package/esm/chart/utils/transition.js +108 -60
  158. package/esm/sparkline/Sparkline.js +2 -1
  159. package/esm/sparkline/SparklineArea.js +2 -1
  160. package/esm/sparkline/SparklineGradient.js +2 -1
  161. package/esm/sparkline/__figma__/Sparkline.figma.js +1 -1
  162. package/esm/sparkline/__stories__/Sparkline.stories.js +11 -7
  163. package/esm/sparkline/__stories__/SparklineGradient.stories.js +7 -4
  164. package/esm/sparkline/sparkline-interactive/SparklineInteractive.js +2 -1
  165. package/esm/sparkline/sparkline-interactive/__figma__/SparklineInteractive.figma.js +1 -1
  166. package/esm/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories.js +51 -26
  167. package/esm/sparkline/sparkline-interactive-header/__figma__/SparklineInteractiveHeader.figma.js +1 -1
  168. package/esm/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories.js +19 -9
  169. package/package.json +13 -10
  170. package/esm/chart/__stories__/Chart.stories.js +0 -77
@@ -1,21 +1,21 @@
1
- const _excluded = ["position", "showGrid", "requestedTickCount", "ticks", "tickLabelFormatter", "TickLabelComponent", "GridLineComponent", "LineComponent", "TickMarkLineComponent", "tickMarkLabelGap", "minTickLabelGap", "showTickMarks", "showLine", "tickMarkSize", "tickInterval", "tickMinStep", "tickMaxStep", "label", "labelGap", "height"];
1
+ const _excluded = ["axisId", "position", "showGrid", "requestedTickCount", "ticks", "tickLabelFormatter", "TickLabelComponent", "GridLineComponent", "LineComponent", "TickMarkLineComponent", "tickMarkLabelGap", "minTickLabelGap", "showTickMarks", "showLine", "tickMarkSize", "tickInterval", "tickMinStep", "tickMaxStep", "label", "labelGap", "height", "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 } 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_HEIGHT = 32;
16
15
  const LABEL_SIZE = 20;
17
16
  export const XAxis = /*#__PURE__*/memo(_ref => {
18
17
  let {
18
+ axisId,
19
19
  position = 'bottom',
20
20
  showGrid,
21
21
  requestedTickCount,
@@ -35,26 +35,26 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
35
35
  tickMaxStep,
36
36
  label,
37
37
  labelGap = 4,
38
- height = label ? AXIS_HEIGHT + LABEL_SIZE : AXIS_HEIGHT
38
+ height = label ? AXIS_HEIGHT + LABEL_SIZE : AXIS_HEIGHT,
39
+ bandGridLinePlacement = 'edges',
40
+ bandTickMarkPlacement = 'middle'
39
41
  } = _ref,
40
42
  props = _objectWithoutPropertiesLoose(_ref, _excluded);
41
43
  const theme = useTheme();
42
44
  const registrationId = useId();
43
45
  const {
44
46
  animate,
47
+ drawingArea,
48
+ layout,
45
49
  getXScale,
46
50
  getXAxis,
47
51
  registerAxis,
48
52
  unregisterAxis,
49
53
  getAxisBounds
50
54
  } = useCartesianChartContext();
51
- const xScale = getXScale();
52
- const xAxis = getXAxis();
55
+ const xScale = getXScale(axisId);
56
+ const xAxis = getXAxis(axisId);
53
57
  const axisBounds = getAxisBounds(registrationId);
54
-
55
- // Note: gridOpacity not currently used in Skia version
56
- // const gridOpacity = useSharedValue(1);
57
-
58
58
  useEffect(() => {
59
59
  registerAxis(registrationId, position, height);
60
60
  return () => unregisterAxis(registrationId);
@@ -64,15 +64,12 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
64
64
  // If we have string labels and no custom formatter, use the labels
65
65
  const axisData = xAxis == null ? void 0 : xAxis.data;
66
66
  const hasStringLabels = axisData && Array.isArray(axisData) && typeof axisData[0] === 'string';
67
- let finalValue = value;
68
-
69
- // For band scales with string data, value is an index
70
- if (hasStringLabels && typeof value === 'number' && axisData[value] !== undefined) {
71
- finalValue = axisData[value];
67
+ if (hasStringLabels && !tickLabelFormatter && axisData[value] !== undefined) {
68
+ return axisData[value];
72
69
  }
73
70
 
74
- // Use the formatter (if provided) or the value itself
75
- return (_tickLabelFormatter = tickLabelFormatter == null ? void 0 : tickLabelFormatter(finalValue)) != null ? _tickLabelFormatter : finalValue;
71
+ // Otherwise passes raw index to formatter
72
+ return (_tickLabelFormatter = tickLabelFormatter == null ? void 0 : tickLabelFormatter(value)) != null ? _tickLabelFormatter : value;
76
73
  }, [xAxis == null ? void 0 : xAxis.data, tickLabelFormatter]);
77
74
  const ticksData = useMemo(() => {
78
75
  if (!xScale) return [];
@@ -91,27 +88,87 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
91
88
  const domain = xScale.domain();
92
89
  categories = domain.map(String);
93
90
  }
94
- let possibleTickValues;
95
-
96
- // If we have discrete data, we can use the indices as possible tick values
97
- if (axisData && Array.isArray(axisData) && (typeof axisData[0] === 'string' || typeof axisData[0] === 'number' && isCategoricalScale(xScale))) {
98
- possibleTickValues = Array.from({
99
- length: axisData.length
100
- }, (_, i) => i);
101
- }
102
91
  return getAxisTicksData({
103
92
  scaleFunction: xScale,
104
93
  ticks,
105
- requestedTickCount,
94
+ requestedTickCount: requestedTickCount != null ? requestedTickCount : layout === 'horizontal' ? 5 : undefined,
106
95
  categories,
107
- possibleTickValues,
96
+ possibleTickValues: axisData && Array.isArray(axisData) && typeof axisData[0] === 'string' ? Array.from({
97
+ length: axisData.length
98
+ }, (_, i) => i) : undefined,
108
99
  tickInterval: tickInterval,
109
100
  options: {
110
101
  minStep: tickMinStep,
111
102
  maxStep: tickMaxStep
112
103
  }
113
104
  });
114
- }, [ticks, xScale, requestedTickCount, tickInterval, tickMinStep, tickMaxStep, xAxis == null ? void 0 : xAxis.data]);
105
+ }, [ticks, xScale, requestedTickCount, tickInterval, tickMinStep, tickMaxStep, xAxis == null ? void 0 : xAxis.data, layout]);
106
+ const isBandScale = useMemo(() => {
107
+ if (!xScale) return false;
108
+ return isCategoricalScale(xScale);
109
+ }, [xScale]);
110
+
111
+ // Compute grid line positions (including bounds closing line for band scales)
112
+ const gridLinePositions = useMemo(() => {
113
+ if (!xScale) return [];
114
+ return ticksData.flatMap((tick, index) => {
115
+ if (!isBandScale) {
116
+ return [{
117
+ x: tick.position,
118
+ key: "grid-" + tick.tick + "-" + index
119
+ }];
120
+ }
121
+ const bandScale = xScale;
122
+ const isLastTick = index === ticksData.length - 1;
123
+ const isEdges = bandGridLinePlacement === 'edges';
124
+ const startX = getPointOnScale(tick.tick, bandScale, toPointAnchor(bandGridLinePlacement));
125
+ const positions = [{
126
+ x: startX,
127
+ key: "grid-" + tick.tick + "-" + index
128
+ }];
129
+
130
+ // For edges on last tick, add the closing line at stepEnd
131
+ if (isLastTick && isEdges) {
132
+ const endX = getPointOnScale(tick.tick, bandScale, 'stepEnd');
133
+ positions.push({
134
+ x: endX,
135
+ key: "grid-" + tick.tick + "-" + index + "-end"
136
+ });
137
+ }
138
+ return positions;
139
+ });
140
+ }, [ticksData, xScale, isBandScale, bandGridLinePlacement]);
141
+
142
+ // Compute tick mark positions (including bounds closing tick for band scales)
143
+ const tickMarkPositions = useMemo(() => {
144
+ if (!xScale) return [];
145
+ return ticksData.flatMap((tick, index) => {
146
+ if (!isBandScale) {
147
+ return [{
148
+ x: tick.position,
149
+ key: "tick-mark-" + tick.tick + "-" + index
150
+ }];
151
+ }
152
+ const bandScale = xScale;
153
+ const isLastTick = index === ticksData.length - 1;
154
+ const isEdges = bandTickMarkPlacement === 'edges';
155
+ const startX = getPointOnScale(tick.tick, bandScale, toPointAnchor(bandTickMarkPlacement));
156
+ const positions = [{
157
+ x: startX,
158
+ key: "tick-mark-" + tick.tick + "-" + index
159
+ }];
160
+
161
+ // For edges on last tick, add the closing tick mark at stepEnd
162
+ if (isLastTick && isEdges) {
163
+ const endX = getPointOnScale(tick.tick, bandScale, 'stepEnd');
164
+ positions.push({
165
+ x: endX,
166
+ key: "tick-mark-" + tick.tick + "-" + index + "-end"
167
+ });
168
+ }
169
+ return positions;
170
+ });
171
+ }, [ticksData, xScale, isBandScale, bandTickMarkPlacement]);
115
172
  const chartTextData = useMemo(() => {
116
173
  if (!axisBounds) return null;
117
174
  return ticksData.map(tick => {
@@ -121,11 +178,7 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
121
178
  // This ensures tick labels are centered in the axis area, not including label space
122
179
  const availableSpace = AXIS_HEIGHT - tickOffset;
123
180
  const labelOffset = availableSpace / 2;
124
-
125
- // For bottom position: start at axisBounds.y
126
- // For top position with label: start at axisBounds.y + LABEL_SIZE
127
- const baseY = position === 'top' && label ? axisBounds.y + LABEL_SIZE : axisBounds.y;
128
- const labelY = position === 'top' ? baseY + labelOffset - tickOffset : baseY + labelOffset + tickOffset;
181
+ const labelY = position === 'top' ? axisBounds.y + axisBounds.height - tickOffset - labelOffset : axisBounds.y + labelOffset + tickOffset;
129
182
  return {
130
183
  x: tick.position,
131
184
  y: labelY,
@@ -137,20 +190,33 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
137
190
  }
138
191
  };
139
192
  });
140
- }, [axisBounds, ticksData, theme.color.fgMuted, tickMarkLabelGap, showTickMarks, tickMarkSize, position, formatTick, label]);
193
+ }, [axisBounds, ticksData, theme.color.fgMuted, tickMarkLabelGap, showTickMarks, tickMarkSize, position, formatTick]);
141
194
  if (!xScale || !axisBounds) return;
142
195
  const labelX = axisBounds.x + axisBounds.width / 2;
143
196
  const labelY = position === 'bottom' ? axisBounds.y + axisBounds.height - LABEL_SIZE / 2 : axisBounds.y + LABEL_SIZE / 2;
197
+
198
+ // Pre-compute tick mark Y coordinates
199
+ const tickYTop = axisBounds.y;
200
+ const tickYBottom = axisBounds.y + axisBounds.height;
201
+ const tickYStart = position === 'bottom' ? tickYTop : tickYBottom;
202
+ const tickYEnd = position === 'bottom' ? tickYTop + tickMarkSize : tickYBottom - tickMarkSize;
203
+
204
+ // Note: Unlike web, mobile renders grid lines and tick marks immediately without fade animation.
205
+ // This is because Skia can measure text dimensions synchronously, so there's no need to hide
206
+ // elements while waiting for measurements (web uses async ResizeObserver).
144
207
  return /*#__PURE__*/_jsxs(Group, {
145
208
  children: [showGrid && /*#__PURE__*/_jsx(Group, {
146
- children: ticksData.map((tick, index) => {
147
- const verticalLine = /*#__PURE__*/_jsx(ReferenceLine, {
148
- LineComponent: GridLineComponent,
149
- dataX: tick.tick
150
- });
151
- return /*#__PURE__*/_jsx(Group, {
152
- children: verticalLine
153
- }, "grid-" + tick.tick + "-" + index);
209
+ children: gridLinePositions.map(_ref2 => {
210
+ let {
211
+ x,
212
+ key
213
+ } = _ref2;
214
+ return /*#__PURE__*/_jsx(GridLineComponent, {
215
+ animate: false,
216
+ clipPath: null,
217
+ d: lineToPath(x, drawingArea.y, x, drawingArea.y + drawingArea.height),
218
+ stroke: theme.color.bgLine
219
+ }, key);
154
220
  })
155
221
  }), chartTextData && /*#__PURE__*/_jsx(ChartTextGroup, {
156
222
  prioritizeEndLabels: true,
@@ -158,20 +224,23 @@ export const XAxis = /*#__PURE__*/memo(_ref => {
158
224
  labels: chartTextData,
159
225
  minGap: minTickLabelGap
160
226
  }), axisBounds && showTickMarks && /*#__PURE__*/_jsx(Group, {
161
- children: ticksData.map((tick, index) => {
162
- const tickY = position === 'bottom' ? axisBounds.y : axisBounds.y + axisBounds.height;
163
- const tickY2 = position === 'bottom' ? axisBounds.y + tickMarkSize : axisBounds.y + axisBounds.height - tickMarkSize;
227
+ children: tickMarkPositions.map(_ref3 => {
228
+ let {
229
+ x,
230
+ key
231
+ } = _ref3;
164
232
  return /*#__PURE__*/_jsx(TickMarkLineComponent, {
165
233
  animate: false,
166
234
  clipPath: null,
167
- d: lineToPath(tick.position, tickY, tick.position, tickY2),
235
+ d: lineToPath(x, tickYStart, x, tickYEnd),
168
236
  stroke: theme.color.fg,
169
237
  strokeCap: "square",
170
238
  strokeWidth: 1
171
- }, "tick-mark-" + tick.tick + "-" + index);
239
+ }, key);
172
240
  })
173
241
  }), showLine && /*#__PURE__*/_jsx(LineComponent, {
174
242
  animate: false,
243
+ clipPath: null,
175
244
  d: lineToPath(axisBounds.x, position === 'bottom' ? axisBounds.y : axisBounds.y + axisBounds.height, axisBounds.x + axisBounds.width, position === 'bottom' ? axisBounds.y : axisBounds.y + axisBounds.height),
176
245
  stroke: theme.color.fg,
177
246
  strokeCap: "square",
@@ -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;
@@ -19,7 +18,7 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
19
18
  axisId,
20
19
  position = 'right',
21
20
  showGrid,
22
- requestedTickCount = 5,
21
+ requestedTickCount,
23
22
  ticks,
24
23
  tickLabelFormatter,
25
24
  TickLabelComponent = DefaultAxisTickLabel,
@@ -34,13 +33,17 @@ 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,
46
+ layout,
44
47
  getYScale,
45
48
  getYAxis,
46
49
  registerAxis,
@@ -50,10 +53,6 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
50
53
  const yScale = getYScale(axisId);
51
54
  const yAxis = getYAxis(axisId);
52
55
  const axisBounds = getAxisBounds(registrationId);
53
-
54
- // Note: gridOpacity not currently used in Skia version
55
- // const gridOpacity = useSharedValue(1);
56
-
57
56
  useEffect(() => {
58
57
  registerAxis(registrationId, position, width);
59
58
  return () => unregisterAxis(registrationId);
@@ -96,12 +95,78 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
96
95
  return getAxisTicksData({
97
96
  scaleFunction: yScale,
98
97
  ticks,
99
- requestedTickCount: tickInterval !== undefined ? undefined : requestedTickCount != null ? requestedTickCount : 5,
98
+ requestedTickCount: tickInterval !== undefined ? undefined : requestedTickCount != null ? requestedTickCount : layout === 'horizontal' ? undefined : 5,
100
99
  categories,
101
100
  possibleTickValues: axisData && Array.isArray(axisData) && typeof axisData[0] === 'number' ? axisData : undefined,
102
101
  tickInterval: tickInterval
103
102
  });
104
- }, [ticks, yScale, requestedTickCount, tickInterval, yAxis == null ? void 0 : yAxis.data]);
103
+ }, [ticks, yScale, requestedTickCount, tickInterval, yAxis == null ? void 0 : yAxis.data, layout]);
104
+ const isBandScale = useMemo(() => {
105
+ if (!yScale) return false;
106
+ return isCategoricalScale(yScale);
107
+ }, [yScale]);
108
+
109
+ // Compute grid line positions (including bounds closing line for band scales)
110
+ const gridLinePositions = useMemo(() => {
111
+ if (!yScale) return [];
112
+ return ticksData.flatMap((tick, index) => {
113
+ if (!isBandScale) {
114
+ return [{
115
+ y: tick.position,
116
+ key: "grid-" + tick.tick + "-" + index
117
+ }];
118
+ }
119
+ const bandScale = yScale;
120
+ const isLastTick = index === ticksData.length - 1;
121
+ const isEdges = bandGridLinePlacement === 'edges';
122
+ const startY = getPointOnScale(tick.tick, bandScale, toPointAnchor(bandGridLinePlacement));
123
+ const positions = [{
124
+ y: startY,
125
+ key: "grid-" + tick.tick + "-" + index
126
+ }];
127
+
128
+ // For edges on last tick, add the closing line at stepEnd
129
+ if (isLastTick && isEdges) {
130
+ const endY = getPointOnScale(tick.tick, bandScale, 'stepEnd');
131
+ positions.push({
132
+ y: endY,
133
+ key: "grid-" + tick.tick + "-" + index + "-end"
134
+ });
135
+ }
136
+ return positions;
137
+ });
138
+ }, [ticksData, yScale, isBandScale, bandGridLinePlacement]);
139
+
140
+ // Compute tick mark positions (including bounds closing tick for band scales)
141
+ const tickMarkPositions = useMemo(() => {
142
+ if (!yScale) return [];
143
+ return ticksData.flatMap((tick, index) => {
144
+ if (!isBandScale) {
145
+ return [{
146
+ y: tick.position,
147
+ key: "tick-mark-" + tick.tick + "-" + index
148
+ }];
149
+ }
150
+ const bandScale = yScale;
151
+ const isLastTick = index === ticksData.length - 1;
152
+ const isEdges = bandTickMarkPlacement === 'edges';
153
+ const startY = getPointOnScale(tick.tick, bandScale, toPointAnchor(bandTickMarkPlacement));
154
+ const positions = [{
155
+ y: startY,
156
+ key: "tick-mark-" + tick.tick + "-" + index
157
+ }];
158
+
159
+ // For edges on last tick, add the closing tick mark at stepEnd
160
+ if (isLastTick && isEdges) {
161
+ const endY = getPointOnScale(tick.tick, bandScale, 'stepEnd');
162
+ positions.push({
163
+ y: endY,
164
+ key: "tick-mark-" + tick.tick + "-" + index + "-end"
165
+ });
166
+ }
167
+ return positions;
168
+ });
169
+ }, [ticksData, yScale, isBandScale, bandTickMarkPlacement]);
105
170
  const chartTextData = useMemo(() => {
106
171
  if (!axisBounds) return null;
107
172
  return ticksData.map(tick => {
@@ -122,17 +187,29 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
122
187
  if (!yScale || !axisBounds) return;
123
188
  const labelX = position === 'left' ? axisBounds.x + LABEL_SIZE / 2 : axisBounds.x + axisBounds.width - LABEL_SIZE / 2;
124
189
  const labelY = axisBounds.y + axisBounds.height / 2;
190
+
191
+ // Pre-compute tick mark X coordinates
192
+ const tickXLeft = axisBounds.x;
193
+ const tickXRight = axisBounds.x + axisBounds.width;
194
+ const tickXStart = position === 'left' ? tickXRight : tickXLeft;
195
+ const tickXEnd = position === 'left' ? tickXRight - tickMarkSize : tickXLeft + tickMarkSize;
196
+
197
+ // Note: Unlike web, mobile renders grid lines and tick marks immediately without fade animation.
198
+ // This is because Skia can measure text dimensions synchronously, so there's no need to hide
199
+ // elements while waiting for measurements (web uses async ResizeObserver).
125
200
  return /*#__PURE__*/_jsxs(Group, {
126
201
  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);
202
+ children: gridLinePositions.map(_ref2 => {
203
+ let {
204
+ y,
205
+ key
206
+ } = _ref2;
207
+ return /*#__PURE__*/_jsx(GridLineComponent, {
208
+ animate: false,
209
+ clipPath: null,
210
+ d: lineToPath(drawingArea.x, y, drawingArea.x + drawingArea.width, y),
211
+ stroke: theme.color.bgLine
212
+ }, key);
136
213
  })
137
214
  }), chartTextData && /*#__PURE__*/_jsx(ChartTextGroup, {
138
215
  prioritizeEndLabels: true,
@@ -140,21 +217,23 @@ export const YAxis = /*#__PURE__*/memo(_ref => {
140
217
  labels: chartTextData,
141
218
  minGap: minTickLabelGap
142
219
  }), 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;
220
+ children: tickMarkPositions.map(_ref3 => {
221
+ let {
222
+ y,
223
+ key
224
+ } = _ref3;
147
225
  return /*#__PURE__*/_jsx(TickMarkLineComponent, {
148
226
  animate: false,
149
227
  clipPath: null,
150
- d: lineToPath(tickX, tick.position, tickX2, tick.position),
228
+ d: lineToPath(tickXStart, y, tickXEnd, y),
151
229
  stroke: theme.color.fg,
152
230
  strokeCap: "square",
153
231
  strokeWidth: 1
154
- }, "tick-mark-" + tick.tick + "-" + index);
232
+ }, key);
155
233
  })
156
234
  }), showLine && /*#__PURE__*/_jsx(LineComponent, {
157
235
  animate: false,
236
+ clipPath: null,
158
237
  d: lineToPath(position === 'left' ? axisBounds.x + axisBounds.width : axisBounds.x, axisBounds.y, position === 'left' ? axisBounds.x + axisBounds.width : axisBounds.x, axisBounds.y + axisBounds.height),
159
238
  stroke: theme.color.fg,
160
239
  strokeCap: "square",