@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,10 +1,12 @@
1
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
1
2
  import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react';
2
- import { runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue, withDelay, withTiming } from 'react-native-reanimated';
3
+ import { runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated';
3
4
  import { useTheme } from '@coinbase/cds-mobile';
4
5
  import { Group, Rect } from '@shopify/react-native-skia';
5
6
  import { useCartesianChartContext } from '../ChartProvider';
6
7
  import { ReferenceLine } from '../line';
7
- import { accessoryFadeTransitionDelay, accessoryFadeTransitionDuration, getPointOnSerializableScale, useScrubberContext } from '../utils';
8
+ import { defaultAccessoryEnterTransition, getPointOnSerializableScale, getTransition, useScrubberContext } from '../utils';
9
+ import { buildTransition } from '../utils/transition';
8
10
  import { DefaultScrubberBeacon } from './DefaultScrubberBeacon';
9
11
  import { DefaultScrubberLabel } from './DefaultScrubberLabel';
10
12
  import { ScrubberBeaconGroup } from './ScrubberBeaconGroup';
@@ -16,6 +18,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
18
  export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
17
19
  let {
18
20
  seriesIds,
21
+ hideBeaconLabels,
19
22
  hideLine,
20
23
  label,
21
24
  lineStroke,
@@ -28,11 +31,14 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
28
31
  overlayOffset = 2,
29
32
  beaconLabelMinGap,
30
33
  beaconLabelHorizontalOffset,
34
+ beaconLabelPreferredSide,
31
35
  labelFont,
32
36
  labelBoundsInset,
33
37
  beaconLabelFont,
34
38
  idlePulse,
35
- beaconTransitions
39
+ beaconTransitions,
40
+ transitions = beaconTransitions,
41
+ beaconStroke
36
42
  } = _ref;
37
43
  const theme = useTheme();
38
44
  const beaconGroupRef = React.useRef(null);
@@ -40,28 +46,23 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
40
46
  scrubberPosition
41
47
  } = useScrubberContext();
42
48
  const {
49
+ layout,
43
50
  getXSerializableScale,
51
+ getYSerializableScale,
44
52
  getXAxis,
53
+ getYAxis,
45
54
  series,
46
55
  drawingArea,
47
56
  animate,
48
57
  dataLength
49
58
  } = useCartesianChartContext();
50
- const xAxis = useMemo(() => getXAxis(), [getXAxis]);
51
- const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
59
+ const categoryAxisIsX = useMemo(() => layout !== 'horizontal', [layout]);
60
+ const indexAxis = useMemo(() => categoryAxisIsX ? getXAxis() : getYAxis(), [categoryAxisIsX, getXAxis, getYAxis]);
61
+ const indexScale = useMemo(() => categoryAxisIsX ? getXSerializableScale() : getYSerializableScale(), [categoryAxisIsX, getXSerializableScale, getYSerializableScale]);
52
62
 
53
63
  // Animation state for delayed scrubber rendering (matches web timing)
54
64
  const scrubberOpacity = useSharedValue(animate ? 0 : 1);
55
65
 
56
- // Delay scrubber appearance until after path enter animation completes
57
- useEffect(() => {
58
- if (animate) {
59
- scrubberOpacity.value = withDelay(accessoryFadeTransitionDelay, withTiming(1, {
60
- duration: accessoryFadeTransitionDuration
61
- }));
62
- }
63
- }, [animate, scrubberOpacity]);
64
-
65
66
  // Expose imperative handle with pulse method
66
67
  useImperativeHandle(ref, () => ({
67
68
  pulse: () => {
@@ -80,27 +81,43 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
80
81
  var _scrubberPosition$val;
81
82
  return (_scrubberPosition$val = scrubberPosition.value) != null ? _scrubberPosition$val : Math.max(0, dataLength - 1);
82
83
  }, [scrubberPosition, dataLength]);
83
- const dataX = useDerivedValue(() => {
84
- if (xAxis != null && xAxis.data && Array.isArray(xAxis.data) && xAxis.data[dataIndex.value] !== undefined) {
85
- const dataValue = xAxis.data[dataIndex.value];
86
- return typeof dataValue === 'string' ? dataIndex.value : dataValue;
84
+ const dataValue = useDerivedValue(() => {
85
+ if (indexAxis != null && indexAxis.data && Array.isArray(indexAxis.data) && indexAxis.data[dataIndex.value] !== undefined) {
86
+ const axisValue = indexAxis.data[dataIndex.value];
87
+ return typeof axisValue === 'string' ? dataIndex.value : axisValue;
87
88
  }
88
89
  return dataIndex.value;
89
- }, [xAxis, dataIndex]);
90
+ }, [indexAxis, dataIndex]);
90
91
  const lineOpacity = useDerivedValue(() => {
91
92
  return scrubberPosition.value !== undefined ? 1 : 0;
92
93
  }, [scrubberPosition]);
93
94
  const overlayOpacity = useDerivedValue(() => {
94
95
  return scrubberPosition.value !== undefined ? 0.8 : 0;
95
96
  }, [scrubberPosition]);
97
+ const pixelPosition = useDerivedValue(() => {
98
+ if (dataValue.value === undefined || !indexScale) return undefined;
99
+ return getPointOnSerializableScale(dataValue.value, indexScale);
100
+ }, [dataValue, indexScale]);
96
101
  const overlayWidth = useDerivedValue(() => {
97
- const pixelX = dataX.value !== undefined && xScale ? getPointOnSerializableScale(dataX.value, xScale) : 0;
98
- return drawingArea.x + drawingArea.width - pixelX + overlayOffset;
99
- }, [dataX, xScale]);
102
+ var _pixelPosition$value;
103
+ const pixel = (_pixelPosition$value = pixelPosition.value) != null ? _pixelPosition$value : 0;
104
+ return categoryAxisIsX ? drawingArea.x + drawingArea.width - pixel + overlayOffset : drawingArea.width + overlayOffset * 2;
105
+ }, [pixelPosition, categoryAxisIsX, drawingArea, overlayOffset]);
106
+ const overlayHeight = useDerivedValue(() => {
107
+ var _pixelPosition$value2;
108
+ const pixel = (_pixelPosition$value2 = pixelPosition.value) != null ? _pixelPosition$value2 : 0;
109
+ return categoryAxisIsX ? drawingArea.height + overlayOffset * 2 : drawingArea.y + drawingArea.height - pixel + overlayOffset;
110
+ }, [pixelPosition, categoryAxisIsX, drawingArea, overlayOffset]);
100
111
  const overlayX = useDerivedValue(() => {
101
- const xValue = dataX.value !== undefined && xScale ? getPointOnSerializableScale(dataX.value, xScale) : 0;
102
- return xValue;
103
- }, [dataX, xScale]);
112
+ var _pixelPosition$value3;
113
+ const pixel = (_pixelPosition$value3 = pixelPosition.value) != null ? _pixelPosition$value3 : 0;
114
+ return categoryAxisIsX ? pixel : drawingArea.x - overlayOffset;
115
+ }, [pixelPosition, categoryAxisIsX, drawingArea, overlayOffset]);
116
+ const overlayY = useDerivedValue(() => {
117
+ var _pixelPosition$value4;
118
+ const pixel = (_pixelPosition$value4 = pixelPosition.value) != null ? _pixelPosition$value4 : 0;
119
+ return categoryAxisIsX ? drawingArea.y - overlayOffset : pixel;
120
+ }, [pixelPosition, categoryAxisIsX, drawingArea, overlayOffset]);
104
121
  const resolvedLabelValue = useSharedValue('');
105
122
  const updateResolvedLabel = useCallback(index => {
106
123
  if (!label) {
@@ -129,38 +146,53 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
129
146
  color: s.color
130
147
  }))) != null ? _series$filter$filter : [];
131
148
  }, [series, filteredSeriesIds]);
132
- if (!xScale) return;
149
+ const showBeaconLabels = !hideBeaconLabels && categoryAxisIsX && beaconLabels.length > 0;
150
+ const isReady = !!indexScale;
151
+ const groupEnterTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.enter, animate, defaultAccessoryEnterTransition), [transitions == null ? void 0 : transitions.enter, animate]);
152
+ useEffect(() => {
153
+ if (animate && isReady) {
154
+ scrubberOpacity.value = buildTransition(1, groupEnterTransition);
155
+ }
156
+ }, [animate, isReady, scrubberOpacity, groupEnterTransition]);
157
+ if (!isReady) return;
133
158
  return /*#__PURE__*/_jsxs(Group, {
134
159
  opacity: scrubberOpacity,
135
160
  children: [!hideOverlay && /*#__PURE__*/_jsx(Rect, {
136
161
  color: theme.color.bg,
137
- height: drawingArea.height + overlayOffset * 2,
162
+ height: overlayHeight,
138
163
  opacity: overlayOpacity,
139
164
  width: overlayWidth,
140
165
  x: overlayX,
141
- y: drawingArea.y - overlayOffset
142
- }), !hideLine && /*#__PURE__*/_jsx(ReferenceLine, {
166
+ y: overlayY
167
+ }), !hideLine && /*#__PURE__*/_jsx(ReferenceLine, _extends({
143
168
  LabelComponent: LabelComponent,
144
- LineComponent: LineComponent,
145
- dataX: dataX,
169
+ LineComponent: LineComponent
170
+ }, categoryAxisIsX ? {
171
+ dataX: dataValue
172
+ } : {
173
+ dataY: dataValue
174
+ }, {
146
175
  label: resolvedLabelValue,
147
176
  labelBoundsInset: labelBoundsInset,
148
177
  labelElevated: labelElevated,
149
178
  labelFont: labelFont,
150
179
  opacity: lineOpacity,
151
180
  stroke: lineStroke
152
- }), /*#__PURE__*/_jsx(ScrubberBeaconGroup, {
181
+ })), /*#__PURE__*/_jsx(ScrubberBeaconGroup, {
153
182
  ref: beaconGroupRef,
154
183
  BeaconComponent: BeaconComponent,
155
184
  idlePulse: idlePulse,
156
185
  seriesIds: filteredSeriesIds,
157
- transitions: beaconTransitions
158
- }), beaconLabels.length > 0 && /*#__PURE__*/_jsx(ScrubberBeaconLabelGroup, {
186
+ stroke: beaconStroke,
187
+ transitions: transitions
188
+ }), showBeaconLabels && /*#__PURE__*/_jsx(ScrubberBeaconLabelGroup, {
159
189
  BeaconLabelComponent: BeaconLabelComponent,
160
190
  labelFont: beaconLabelFont,
161
191
  labelHorizontalOffset: beaconLabelHorizontalOffset,
162
192
  labelMinGap: beaconLabelMinGap,
163
- labels: beaconLabels
193
+ labelPreferredSide: beaconLabelPreferredSide,
194
+ labels: beaconLabels,
195
+ transitions: transitions
164
196
  })]
165
197
  });
166
198
  }));
@@ -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,15 +12,17 @@ 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,
19
19
  animate,
20
20
  transitions,
21
- beaconRef
21
+ beaconRef,
22
+ stroke
22
23
  } = _ref;
23
24
  const {
25
+ layout,
24
26
  getSeries,
25
27
  getSeriesData,
26
28
  getXScale,
@@ -48,10 +50,10 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
48
50
  // Get scales for gradient evaluation
49
51
  const gradientScale = useMemo(() => {
50
52
  if (!gradient) return undefined;
51
- 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);
52
54
  if (!scale) return undefined;
53
55
  return convertToSerializableScale(scale);
54
- }, [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]);
55
57
  const gradientStops = useMemo(() => {
56
58
  if (!gradient || !gradientScale) return undefined;
57
59
  const domain = {
@@ -69,28 +71,29 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
69
71
  var _series$color;
70
72
  if (gradient && gradientScale && gradientStops) {
71
73
  var _gradient$axis;
72
- const axis = (_gradient$axis = gradient.axis) != null ? _gradient$axis : 'y';
73
- const dataValue = axis === 'x' ? dataX.value : dataY.value;
74
- if (dataValue !== undefined) {
75
- const evaluatedColor = evaluateGradientAtValue(gradientStops, dataValue, gradientScale);
76
- if (evaluatedColor) {
77
- return evaluatedColor;
78
- }
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;
79
80
  }
80
81
  }
81
82
 
82
83
  // Fallback to series color
83
84
  return (_series$color = series == null ? void 0 : series.color) != null ? _series$color : theme.color.fgPrimary;
84
- }, [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';
85
87
  return /*#__PURE__*/_jsx(BeaconComponent, {
86
88
  ref: beaconRef,
87
89
  animate: animate,
88
90
  color: color,
89
- dataX: dataX,
90
- dataY: dataY,
91
+ dataX: categoryAxisIsX ? dataIndexValue : dataY,
92
+ dataY: categoryAxisIsX ? dataY : dataIndexValue,
91
93
  idlePulse: idlePulse,
92
94
  isIdle: isIdle,
93
95
  seriesId: seriesId,
96
+ stroke: stroke,
94
97
  transitions: transitions
95
98
  });
96
99
  });
@@ -99,19 +102,23 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
99
102
  seriesIds,
100
103
  idlePulse,
101
104
  transitions,
102
- BeaconComponent = DefaultScrubberBeacon
105
+ BeaconComponent = DefaultScrubberBeacon,
106
+ stroke
103
107
  } = _ref2;
104
108
  const ScrubberBeaconRefs = useRefMap();
105
109
  const {
106
110
  scrubberPosition
107
111
  } = useScrubberContext();
108
112
  const {
113
+ layout,
109
114
  getXAxis,
115
+ getYAxis,
110
116
  series,
111
117
  dataLength,
112
118
  animate
113
119
  } = useCartesianChartContext();
114
- const xAxis = useMemo(() => getXAxis(), [getXAxis]);
120
+ const categoryAxisIsX = useMemo(() => layout !== 'horizontal', [layout]);
121
+ const indexAxis = useMemo(() => categoryAxisIsX ? getXAxis() : getYAxis(), [categoryAxisIsX, getXAxis, getYAxis]);
115
122
 
116
123
  // Expose imperative handle with pulse method
117
124
  useImperativeHandle(ref, () => ({
@@ -129,14 +136,14 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
129
136
  var _scrubberPosition$val;
130
137
  return (_scrubberPosition$val = scrubberPosition.value) != null ? _scrubberPosition$val : Math.max(0, dataLength - 1);
131
138
  }, [scrubberPosition, dataLength]);
132
- const dataX = useDerivedValue(() => {
133
- // Convert index to actual x value if axis has data
134
- if (xAxis != null && xAxis.data && Array.isArray(xAxis.data) && xAxis.data[dataIndex.value] !== undefined) {
135
- 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];
136
143
  return typeof dataValue === 'string' ? dataIndex.value : dataValue;
137
144
  }
138
145
  return dataIndex.value;
139
- }, [xAxis, dataIndex]);
146
+ }, [indexAxis, dataIndex]);
140
147
  const isIdle = useDerivedValue(() => {
141
148
  return scrubberPosition.value === undefined;
142
149
  }, [scrubberPosition]);
@@ -152,10 +159,11 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
152
159
  animate: animate,
153
160
  beaconRef: createBeaconRef(s.id),
154
161
  dataIndex: dataIndex,
155
- dataX: dataX,
162
+ dataIndexValue: dataIndexValue,
156
163
  idlePulse: idlePulse,
157
164
  isIdle: isIdle,
158
165
  seriesId: s.id,
166
+ stroke: stroke,
159
167
  transitions: transitions
160
168
  }, s.id));
161
169
  }));
@@ -1,9 +1,10 @@
1
1
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
2
  import { memo, useCallback, useMemo, useState } from 'react';
3
- import { useDerivedValue } from 'react-native-reanimated';
3
+ import { useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated';
4
4
  import { useCartesianChartContext } from '../ChartProvider';
5
- import { applySerializableScale, useScrubberContext } from '../utils';
5
+ import { applySerializableScale, unwrapAnimatedValue, useScrubberContext } from '../utils';
6
6
  import { calculateLabelYPositions, getLabelPosition } from '../utils/scrubber';
7
+ import { buildTransition, defaultTransition, getTransition } from '../utils/transition';
7
8
  import { DefaultScrubberBeaconLabel } from './DefaultScrubberBeaconLabel';
8
9
  import { jsx as _jsx } from "react/jsx-runtime";
9
10
  const PositionedLabel = /*#__PURE__*/memo(_ref => {
@@ -11,6 +12,8 @@ const PositionedLabel = /*#__PURE__*/memo(_ref => {
11
12
  index,
12
13
  positions,
13
14
  position,
15
+ isIdle,
16
+ updateTransition,
14
17
  label,
15
18
  color,
16
19
  seriesId,
@@ -24,10 +27,25 @@ const PositionedLabel = /*#__PURE__*/memo(_ref => {
24
27
  var _positions$value$inde, _positions$value$inde2;
25
28
  return (_positions$value$inde = (_positions$value$inde2 = positions.value[index]) == null ? void 0 : _positions$value$inde2.x) != null ? _positions$value$inde : 0;
26
29
  }, [positions, index]);
27
- const y = useDerivedValue(() => {
30
+ const targetY = useDerivedValue(() => {
28
31
  var _positions$value$inde3, _positions$value$inde4;
29
32
  return (_positions$value$inde3 = (_positions$value$inde4 = positions.value[index]) == null ? void 0 : _positions$value$inde4.y) != null ? _positions$value$inde3 : 0;
30
33
  }, [positions, index]);
34
+ const idleAnimatedY = useSharedValue(null);
35
+ useAnimatedReaction(() => ({
36
+ y: targetY.value,
37
+ idle: unwrapAnimatedValue(isIdle)
38
+ }), (current, previous) => {
39
+ // Only animate idle-to-idle updates, immediately set the value for other changes.
40
+ if (previous != null && previous.idle && current.idle) {
41
+ idleAnimatedY.value = buildTransition(current.y, updateTransition);
42
+ } else {
43
+ idleAnimatedY.value = current.y;
44
+ }
45
+ }, [updateTransition]);
46
+
47
+ // When scrubbing, use the targetY value, when idle, use the idleAnimatedY value.
48
+ const y = useDerivedValue(() => unwrapAnimatedValue(isIdle) && idleAnimatedY.value !== null ? idleAnimatedY.value : targetY.value, [isIdle, idleAnimatedY, targetY]);
31
49
  const dx = useDerivedValue(() => {
32
50
  return position.value === 'right' ? labelHorizontalOffset : -labelHorizontalOffset;
33
51
  }, [position, labelHorizontalOffset]);
@@ -51,7 +69,9 @@ export const ScrubberBeaconLabelGroup = /*#__PURE__*/memo(_ref2 => {
51
69
  labelMinGap = 4,
52
70
  labelHorizontalOffset = 16,
53
71
  labelFont,
54
- BeaconLabelComponent = DefaultScrubberBeaconLabel
72
+ labelPreferredSide = 'right',
73
+ BeaconLabelComponent = DefaultScrubberBeaconLabel,
74
+ transitions
55
75
  } = _ref2;
56
76
  const {
57
77
  getSeries,
@@ -60,11 +80,16 @@ export const ScrubberBeaconLabelGroup = /*#__PURE__*/memo(_ref2 => {
60
80
  getYSerializableScale,
61
81
  getXAxis,
62
82
  drawingArea,
63
- dataLength
83
+ dataLength,
84
+ animate
64
85
  } = useCartesianChartContext();
65
86
  const {
66
87
  scrubberPosition
67
88
  } = useScrubberContext();
89
+ const isIdle = useDerivedValue(() => {
90
+ return scrubberPosition.value === undefined;
91
+ }, [scrubberPosition]);
92
+ const updateTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.update, animate, defaultTransition), [transitions == null ? void 0 : transitions.update, animate]);
68
93
  const [labelDimensions, setLabelDimensions] = useState({});
69
94
  const handleDimensionsChange = useCallback((id, dimensions) => {
70
95
  setLabelDimensions(prev => {
@@ -163,9 +188,9 @@ export const ScrubberBeaconLabelGroup = /*#__PURE__*/memo(_ref2 => {
163
188
  const currentPosition = useDerivedValue(() => {
164
189
  const pixelX = dataX.value !== undefined && xScale ? applySerializableScale(dataX.value, xScale) : 0;
165
190
  const maxWidth = Math.max(...Object.values(labelDimensions).map(dim => dim.width));
166
- const position = getLabelPosition(pixelX, maxWidth, drawingArea, labelHorizontalOffset);
191
+ const position = getLabelPosition(pixelX, maxWidth, drawingArea, labelHorizontalOffset, labelPreferredSide);
167
192
  return position;
168
- }, [dataX, xScale, labelDimensions, drawingArea, labelHorizontalOffset]);
193
+ }, [dataX, xScale, labelDimensions, drawingArea, labelHorizontalOffset, labelPreferredSide]);
169
194
  return seriesInfo.map((info, index) => {
170
195
  const labelInfo = labels.find(label => label.seriesId === info.seriesId);
171
196
  if (!labelInfo) return;
@@ -173,13 +198,15 @@ export const ScrubberBeaconLabelGroup = /*#__PURE__*/memo(_ref2 => {
173
198
  BeaconLabelComponent: BeaconLabelComponent,
174
199
  color: labelInfo.color,
175
200
  index: index,
201
+ isIdle: isIdle,
176
202
  label: labelInfo.label,
177
203
  labelFont: labelFont,
178
204
  labelHorizontalOffset: labelHorizontalOffset,
179
205
  onDimensionsChange: handleDimensionsChange,
180
206
  position: currentPosition,
181
207
  positions: allLabelPositions,
182
- seriesId: info.seriesId
208
+ seriesId: info.seriesId,
209
+ updateTransition: updateTransition
183
210
  }, info.seriesId);
184
211
  });
185
212
  });