@coinbase/cds-mobile-visualization 0.0.0 → 3.4.0-beta.1

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 (245) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +3 -0
  3. package/dts/chart/CartesianChart.d.ts +101 -0
  4. package/dts/chart/CartesianChart.d.ts.map +1 -0
  5. package/dts/chart/ChartProvider.d.ts +6 -0
  6. package/dts/chart/ChartProvider.d.ts.map +1 -0
  7. package/dts/chart/Path.d.ts +48 -0
  8. package/dts/chart/Path.d.ts.map +1 -0
  9. package/dts/chart/PeriodSelector.d.ts +85 -0
  10. package/dts/chart/PeriodSelector.d.ts.map +1 -0
  11. package/dts/chart/Point.d.ts +103 -0
  12. package/dts/chart/Point.d.ts.map +1 -0
  13. package/dts/chart/area/Area.d.ts +62 -0
  14. package/dts/chart/area/Area.d.ts.map +1 -0
  15. package/dts/chart/area/AreaChart.d.ts +90 -0
  16. package/dts/chart/area/AreaChart.d.ts.map +1 -0
  17. package/dts/chart/area/DottedArea.d.ts +27 -0
  18. package/dts/chart/area/DottedArea.d.ts.map +1 -0
  19. package/dts/chart/area/GradientArea.d.ts +30 -0
  20. package/dts/chart/area/GradientArea.d.ts.map +1 -0
  21. package/dts/chart/area/SolidArea.d.ts +8 -0
  22. package/dts/chart/area/SolidArea.d.ts.map +1 -0
  23. package/dts/chart/area/index.d.ts +6 -0
  24. package/dts/chart/area/index.d.ts.map +1 -0
  25. package/dts/chart/axis/Axis.d.ts +204 -0
  26. package/dts/chart/axis/Axis.d.ts.map +1 -0
  27. package/dts/chart/axis/XAxis.d.ts +16 -0
  28. package/dts/chart/axis/XAxis.d.ts.map +1 -0
  29. package/dts/chart/axis/YAxis.d.ts +21 -0
  30. package/dts/chart/axis/YAxis.d.ts.map +1 -0
  31. package/dts/chart/axis/index.d.ts +4 -0
  32. package/dts/chart/axis/index.d.ts.map +1 -0
  33. package/dts/chart/bar/Bar.d.ts +89 -0
  34. package/dts/chart/bar/Bar.d.ts.map +1 -0
  35. package/dts/chart/bar/BarChart.d.ts +97 -0
  36. package/dts/chart/bar/BarChart.d.ts.map +1 -0
  37. package/dts/chart/bar/BarPlot.d.ts +29 -0
  38. package/dts/chart/bar/BarPlot.d.ts.map +1 -0
  39. package/dts/chart/bar/BarStack.d.ts +111 -0
  40. package/dts/chart/bar/BarStack.d.ts.map +1 -0
  41. package/dts/chart/bar/BarStackGroup.d.ts +35 -0
  42. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -0
  43. package/dts/chart/bar/DefaultBar.d.ts +7 -0
  44. package/dts/chart/bar/DefaultBar.d.ts.map +1 -0
  45. package/dts/chart/bar/DefaultBarStack.d.ts +7 -0
  46. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -0
  47. package/dts/chart/bar/index.d.ts +8 -0
  48. package/dts/chart/bar/index.d.ts.map +1 -0
  49. package/dts/chart/index.d.ts +13 -0
  50. package/dts/chart/index.d.ts.map +1 -0
  51. package/dts/chart/line/DottedLine.d.ts +12 -0
  52. package/dts/chart/line/DottedLine.d.ts.map +1 -0
  53. package/dts/chart/line/GradientLine.d.ts +45 -0
  54. package/dts/chart/line/GradientLine.d.ts.map +1 -0
  55. package/dts/chart/line/Line.d.ts +78 -0
  56. package/dts/chart/line/Line.d.ts.map +1 -0
  57. package/dts/chart/line/LineChart.d.ts +84 -0
  58. package/dts/chart/line/LineChart.d.ts.map +1 -0
  59. package/dts/chart/line/ReferenceLine.d.ts +91 -0
  60. package/dts/chart/line/ReferenceLine.d.ts.map +1 -0
  61. package/dts/chart/line/SolidLine.d.ts +12 -0
  62. package/dts/chart/line/SolidLine.d.ts.map +1 -0
  63. package/dts/chart/line/index.d.ts +7 -0
  64. package/dts/chart/line/index.d.ts.map +1 -0
  65. package/dts/chart/scrubber/Scrubber.d.ts +104 -0
  66. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -0
  67. package/dts/chart/scrubber/ScrubberBeacon.d.ts +75 -0
  68. package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +1 -0
  69. package/dts/chart/scrubber/ScrubberProvider.d.ts +17 -0
  70. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -0
  71. package/dts/chart/scrubber/index.d.ts +2 -0
  72. package/dts/chart/scrubber/index.d.ts.map +1 -0
  73. package/dts/chart/text/ChartText.d.ts +90 -0
  74. package/dts/chart/text/ChartText.d.ts.map +1 -0
  75. package/dts/chart/text/SmartChartTextGroup.d.ts +55 -0
  76. package/dts/chart/text/SmartChartTextGroup.d.ts.map +1 -0
  77. package/dts/chart/text/index.d.ts +3 -0
  78. package/dts/chart/text/index.d.ts.map +1 -0
  79. package/dts/chart/utils/axis.d.ts +342 -0
  80. package/dts/chart/utils/axis.d.ts.map +1 -0
  81. package/dts/chart/utils/bar.d.ts +20 -0
  82. package/dts/chart/utils/bar.d.ts.map +1 -0
  83. package/dts/chart/utils/chart.d.ts +97 -0
  84. package/dts/chart/utils/chart.d.ts.map +1 -0
  85. package/dts/chart/utils/context.d.ts +95 -0
  86. package/dts/chart/utils/context.d.ts.map +1 -0
  87. package/dts/chart/utils/index.d.ts +8 -0
  88. package/dts/chart/utils/index.d.ts.map +1 -0
  89. package/dts/chart/utils/path.d.ts +107 -0
  90. package/dts/chart/utils/path.d.ts.map +1 -0
  91. package/dts/chart/utils/point.d.ts +75 -0
  92. package/dts/chart/utils/point.d.ts.map +1 -0
  93. package/dts/chart/utils/scale.d.ts +43 -0
  94. package/dts/chart/utils/scale.d.ts.map +1 -0
  95. package/dts/index.d.ts +3 -0
  96. package/dts/index.d.ts.map +1 -0
  97. package/dts/sparkline/Counter.d.ts +8 -0
  98. package/dts/sparkline/Counter.d.ts.map +1 -0
  99. package/dts/sparkline/Sparkline.d.ts +73 -0
  100. package/dts/sparkline/Sparkline.d.ts.map +1 -0
  101. package/dts/sparkline/SparklineArea.d.ts +14 -0
  102. package/dts/sparkline/SparklineArea.d.ts.map +1 -0
  103. package/dts/sparkline/SparklineAreaPattern.d.ts +14 -0
  104. package/dts/sparkline/SparklineAreaPattern.d.ts.map +1 -0
  105. package/dts/sparkline/SparklineGradient.d.ts +23 -0
  106. package/dts/sparkline/SparklineGradient.d.ts.map +1 -0
  107. package/dts/sparkline/__figma__/Sparkline.figma.d.ts +2 -0
  108. package/dts/sparkline/__figma__/Sparkline.figma.d.ts.map +1 -0
  109. package/dts/sparkline/generateSparklineWithId.d.ts +11 -0
  110. package/dts/sparkline/generateSparklineWithId.d.ts.map +1 -0
  111. package/dts/sparkline/index.d.ts +6 -0
  112. package/dts/sparkline/index.d.ts.map +1 -0
  113. package/dts/sparkline/sparkline-interactive/SparklineAccessibleView.d.ts +23 -0
  114. package/dts/sparkline/sparkline-interactive/SparklineAccessibleView.d.ts.map +1 -0
  115. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts +184 -0
  116. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts.map +1 -0
  117. package/dts/sparkline/sparkline-interactive/SparklineInteractiveAnimatedPath.d.ts +25 -0
  118. package/dts/sparkline/sparkline-interactive/SparklineInteractiveAnimatedPath.d.ts.map +1 -0
  119. package/dts/sparkline/sparkline-interactive/SparklineInteractiveHoverDate.d.ts +28 -0
  120. package/dts/sparkline/sparkline-interactive/SparklineInteractiveHoverDate.d.ts.map +1 -0
  121. package/dts/sparkline/sparkline-interactive/SparklineInteractiveLineVertical.d.ts +13 -0
  122. package/dts/sparkline/sparkline-interactive/SparklineInteractiveLineVertical.d.ts.map +1 -0
  123. package/dts/sparkline/sparkline-interactive/SparklineInteractiveMarkerDates.d.ts +17 -0
  124. package/dts/sparkline/sparkline-interactive/SparklineInteractiveMarkerDates.d.ts.map +1 -0
  125. package/dts/sparkline/sparkline-interactive/SparklineInteractiveMinMax.d.ts +11 -0
  126. package/dts/sparkline/sparkline-interactive/SparklineInteractiveMinMax.d.ts.map +1 -0
  127. package/dts/sparkline/sparkline-interactive/SparklineInteractivePanGestureHandler.d.ts +26 -0
  128. package/dts/sparkline/sparkline-interactive/SparklineInteractivePanGestureHandler.d.ts.map +1 -0
  129. package/dts/sparkline/sparkline-interactive/SparklineInteractivePaths.d.ts +25 -0
  130. package/dts/sparkline/sparkline-interactive/SparklineInteractivePaths.d.ts.map +1 -0
  131. package/dts/sparkline/sparkline-interactive/SparklineInteractivePeriodSelector.d.ts +25 -0
  132. package/dts/sparkline/sparkline-interactive/SparklineInteractivePeriodSelector.d.ts.map +1 -0
  133. package/dts/sparkline/sparkline-interactive/SparklineInteractiveProvider.d.ts +39 -0
  134. package/dts/sparkline/sparkline-interactive/SparklineInteractiveProvider.d.ts.map +1 -0
  135. package/dts/sparkline/sparkline-interactive/SparklineInteractiveTimeseriesPaths.d.ts +31 -0
  136. package/dts/sparkline/sparkline-interactive/SparklineInteractiveTimeseriesPaths.d.ts.map +1 -0
  137. package/dts/sparkline/sparkline-interactive/__figma__/SparklineInteractive.figma.d.ts +2 -0
  138. package/dts/sparkline/sparkline-interactive/__figma__/SparklineInteractive.figma.d.ts.map +1 -0
  139. package/dts/sparkline/sparkline-interactive/useInterruptiblePathAnimation.d.ts +13 -0
  140. package/dts/sparkline/sparkline-interactive/useInterruptiblePathAnimation.d.ts.map +1 -0
  141. package/dts/sparkline/sparkline-interactive/useMinMaxTransform.d.ts +16 -0
  142. package/dts/sparkline/sparkline-interactive/useMinMaxTransform.d.ts.map +1 -0
  143. package/dts/sparkline/sparkline-interactive/useOpacityAnimation.d.ts +6 -0
  144. package/dts/sparkline/sparkline-interactive/useOpacityAnimation.d.ts.map +1 -0
  145. package/dts/sparkline/sparkline-interactive/useSparklineInteractiveConstants.d.ts +22 -0
  146. package/dts/sparkline/sparkline-interactive/useSparklineInteractiveConstants.d.ts.map +1 -0
  147. package/dts/sparkline/sparkline-interactive/useSparklineInteractiveLineStyles.d.ts +34 -0
  148. package/dts/sparkline/sparkline-interactive/useSparklineInteractiveLineStyles.d.ts.map +1 -0
  149. package/dts/sparkline/sparkline-interactive-header/SparklineInteractiveHeader.d.ts +118 -0
  150. package/dts/sparkline/sparkline-interactive-header/SparklineInteractiveHeader.d.ts.map +1 -0
  151. package/dts/sparkline/sparkline-interactive-header/__figma__/SparklineInteractiveHeader.figma.d.ts +2 -0
  152. package/dts/sparkline/sparkline-interactive-header/__figma__/SparklineInteractiveHeader.figma.d.ts.map +1 -0
  153. package/dts/sparkline/sparkline-interactive-header/useSparklineInteractiveHeaderStyles.d.ts +29 -0
  154. package/dts/sparkline/sparkline-interactive-header/useSparklineInteractiveHeaderStyles.d.ts.map +1 -0
  155. package/esm/chart/CartesianChart.js +241 -0
  156. package/esm/chart/ChartProvider.js +10 -0
  157. package/esm/chart/Path.js +133 -0
  158. package/esm/chart/PeriodSelector.js +136 -0
  159. package/esm/chart/Point.js +111 -0
  160. package/esm/chart/__stories__/CartesianChart.stories.js +476 -0
  161. package/esm/chart/__stories__/Chart.stories.js +79 -0
  162. package/esm/chart/__stories__/PeriodSelector.stories.js +294 -0
  163. package/esm/chart/area/Area.js +85 -0
  164. package/esm/chart/area/AreaChart.js +146 -0
  165. package/esm/chart/area/DottedArea.js +128 -0
  166. package/esm/chart/area/GradientArea.js +110 -0
  167. package/esm/chart/area/SolidArea.js +24 -0
  168. package/esm/chart/area/__stories__/AreaChart.stories.js +100 -0
  169. package/esm/chart/area/index.js +7 -0
  170. package/esm/chart/axis/Axis.js +43 -0
  171. package/esm/chart/axis/XAxis.js +181 -0
  172. package/esm/chart/axis/YAxis.js +170 -0
  173. package/esm/chart/axis/__stories__/Axis.stories.js +277 -0
  174. package/esm/chart/axis/index.js +5 -0
  175. package/esm/chart/bar/Bar.js +67 -0
  176. package/esm/chart/bar/BarChart.js +147 -0
  177. package/esm/chart/bar/BarPlot.js +96 -0
  178. package/esm/chart/bar/BarStack.js +514 -0
  179. package/esm/chart/bar/BarStackGroup.js +89 -0
  180. package/esm/chart/bar/DefaultBar.js +78 -0
  181. package/esm/chart/bar/DefaultBarStack.js +82 -0
  182. package/esm/chart/bar/__stories__/BarChart.stories.js +282 -0
  183. package/esm/chart/bar/index.js +9 -0
  184. package/esm/chart/index.js +14 -0
  185. package/esm/chart/line/DottedLine.js +35 -0
  186. package/esm/chart/line/GradientLine.js +62 -0
  187. package/esm/chart/line/Line.js +139 -0
  188. package/esm/chart/line/LineChart.js +115 -0
  189. package/esm/chart/line/ReferenceLine.js +115 -0
  190. package/esm/chart/line/SolidLine.js +31 -0
  191. package/esm/chart/line/__stories__/LineChart.stories.js +2248 -0
  192. package/esm/chart/line/__stories__/ReferenceLine.stories.js +77 -0
  193. package/esm/chart/line/index.js +8 -0
  194. package/esm/chart/scrubber/Scrubber.js +186 -0
  195. package/esm/chart/scrubber/ScrubberBeacon.js +199 -0
  196. package/esm/chart/scrubber/ScrubberProvider.js +143 -0
  197. package/esm/chart/scrubber/index.js +2 -0
  198. package/esm/chart/text/ChartText.js +237 -0
  199. package/esm/chart/text/SmartChartTextGroup.js +210 -0
  200. package/esm/chart/text/index.js +4 -0
  201. package/esm/chart/utils/axis.js +592 -0
  202. package/esm/chart/utils/bar.js +24 -0
  203. package/esm/chart/utils/chart.js +229 -0
  204. package/esm/chart/utils/context.js +15 -0
  205. package/esm/chart/utils/index.js +9 -0
  206. package/esm/chart/utils/path.js +206 -0
  207. package/esm/chart/utils/point.js +118 -0
  208. package/esm/chart/utils/scale.js +48 -0
  209. package/esm/index.js +4 -0
  210. package/esm/sparkline/Counter.js +45 -0
  211. package/esm/sparkline/Sparkline.js +164 -0
  212. package/esm/sparkline/SparklineArea.js +19 -0
  213. package/esm/sparkline/SparklineAreaPattern.js +38 -0
  214. package/esm/sparkline/SparklineGradient.js +76 -0
  215. package/esm/sparkline/__figma__/Sparkline.figma.js +22 -0
  216. package/esm/sparkline/__stories__/Sparkline.stories.js +120 -0
  217. package/esm/sparkline/__stories__/SparklineGradient.stories.js +123 -0
  218. package/esm/sparkline/generateSparklineWithId.js +7 -0
  219. package/esm/sparkline/index.js +5 -0
  220. package/esm/sparkline/sparkline-interactive/SparklineAccessibleView.js +75 -0
  221. package/esm/sparkline/sparkline-interactive/SparklineInteractive.js +307 -0
  222. package/esm/sparkline/sparkline-interactive/SparklineInteractiveAnimatedPath.js +116 -0
  223. package/esm/sparkline/sparkline-interactive/SparklineInteractiveHoverDate.js +131 -0
  224. package/esm/sparkline/sparkline-interactive/SparklineInteractiveLineVertical.js +99 -0
  225. package/esm/sparkline/sparkline-interactive/SparklineInteractiveMarkerDates.js +82 -0
  226. package/esm/sparkline/sparkline-interactive/SparklineInteractiveMinMax.js +103 -0
  227. package/esm/sparkline/sparkline-interactive/SparklineInteractivePanGestureHandler.js +104 -0
  228. package/esm/sparkline/sparkline-interactive/SparklineInteractivePaths.js +57 -0
  229. package/esm/sparkline/sparkline-interactive/SparklineInteractivePeriodSelector.js +124 -0
  230. package/esm/sparkline/sparkline-interactive/SparklineInteractiveProvider.js +80 -0
  231. package/esm/sparkline/sparkline-interactive/SparklineInteractiveTimeseriesPaths.js +109 -0
  232. package/esm/sparkline/sparkline-interactive/__figma__/SparklineInteractive.figma.js +85 -0
  233. package/esm/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories.js +501 -0
  234. package/esm/sparkline/sparkline-interactive/useInterruptiblePathAnimation.js +58 -0
  235. package/esm/sparkline/sparkline-interactive/useInterruptiblePathAnimation.test.disable.js +37 -0
  236. package/esm/sparkline/sparkline-interactive/useMinMaxTransform.js +56 -0
  237. package/esm/sparkline/sparkline-interactive/useOpacityAnimation.js +23 -0
  238. package/esm/sparkline/sparkline-interactive/useSparklineInteractiveConstants.js +47 -0
  239. package/esm/sparkline/sparkline-interactive/useSparklineInteractiveLineStyles.js +34 -0
  240. package/esm/sparkline/sparkline-interactive-header/SparklineInteractiveHeader.js +233 -0
  241. package/esm/sparkline/sparkline-interactive-header/__figma__/SparklineInteractiveHeader.figma.js +104 -0
  242. package/esm/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories.js +555 -0
  243. package/esm/sparkline/sparkline-interactive-header/useSparklineInteractiveHeaderStyles.js +117 -0
  244. package/package.json +65 -6
  245. package/index.js +0 -1
@@ -0,0 +1,77 @@
1
+ import { useTheme } from '@coinbase/cds-mobile';
2
+ import { Example, ExampleScreen } from '@coinbase/cds-mobile/examples/ExampleScreen';
3
+ import { LineChart } from '../LineChart';
4
+ import { ReferenceLine } from '../ReferenceLine';
5
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
+ const ReferenceLineStories = () => {
7
+ const theme = useTheme();
8
+ return /*#__PURE__*/_jsxs(ExampleScreen, {
9
+ children: [/*#__PURE__*/_jsx(Example, {
10
+ title: "Basic",
11
+ children: /*#__PURE__*/_jsxs(LineChart, {
12
+ showArea: true,
13
+ curve: "monotone",
14
+ height: 250,
15
+ inset: 0,
16
+ series: [{
17
+ id: 'prices',
18
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
19
+ }],
20
+ children: [/*#__PURE__*/_jsx(ReferenceLine, {
21
+ dataX: 4,
22
+ label: "Vertical Reference Line",
23
+ labelProps: {
24
+ horizontalAlignment: 'left',
25
+ dx: 6,
26
+ inset: 0
27
+ }
28
+ }), /*#__PURE__*/_jsx(ReferenceLine, {
29
+ dataY: 70,
30
+ label: "Horizontal Reference Line",
31
+ labelProps: {
32
+ verticalAlignment: 'bottom',
33
+ dy: -6,
34
+ horizontalAlignment: 'right',
35
+ inset: 0
36
+ }
37
+ })]
38
+ })
39
+ }), /*#__PURE__*/_jsx(Example, {
40
+ title: "With Custom Label",
41
+ children: /*#__PURE__*/_jsx(LineChart, {
42
+ curve: "monotone",
43
+ height: 250,
44
+ inset: {
45
+ right: 32,
46
+ top: 0,
47
+ left: 0,
48
+ bottom: 0
49
+ },
50
+ series: [{
51
+ id: 'prices',
52
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
53
+ }],
54
+ children: /*#__PURE__*/_jsx(ReferenceLine, {
55
+ dataY: 25,
56
+ label: "Liquidation",
57
+ labelPosition: "left",
58
+ labelProps: {
59
+ dx: 4,
60
+ borderRadius: 100,
61
+ inset: {
62
+ top: 4,
63
+ bottom: 4,
64
+ left: 8,
65
+ right: 8
66
+ },
67
+ horizontalAlignment: 'left',
68
+ color: "rgb(" + theme.spectrum.yellow70 + ")",
69
+ background: theme.color.accentSubtleYellow
70
+ },
71
+ stroke: theme.color.bgWarning
72
+ })
73
+ })
74
+ })]
75
+ });
76
+ };
77
+ export default ReferenceLineStories;
@@ -0,0 +1,8 @@
1
+ // codegen:start {preset: barrel, include: ./*.tsx, exclude: ./__stories__/*.tsx}
2
+ export * from './DottedLine';
3
+ export * from './GradientLine';
4
+ export * from './Line';
5
+ export * from './LineChart';
6
+ export * from './ReferenceLine';
7
+ export * from './SolidLine';
8
+ // codegen:end
@@ -0,0 +1,186 @@
1
+ import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
2
+ import { Animated } from 'react-native';
3
+ import { useAnimatedProps, useSharedValue } from 'react-native-reanimated';
4
+ import { G, Rect } from 'react-native-svg';
5
+ import { useRefMap } from '@coinbase/cds-common/hooks/useRefMap';
6
+ import { useTheme } from '@coinbase/cds-mobile';
7
+ import { useCartesianChartContext } from '../ChartProvider';
8
+ import { ReferenceLine } from '../line';
9
+ import { useScrubberContext } from '../utils';
10
+ import { ScrubberBeacon } from './ScrubberBeacon';
11
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
+ const AnimatedRect = Animated.createAnimatedComponent(Rect);
13
+
14
+ /**
15
+ * Configuration for scrubber functionality across chart components.
16
+ * Provides consistent API with smart defaults and component customization.
17
+ */
18
+
19
+ /**
20
+ * Unified component that manages all scrubber elements (beacons, line, labels)
21
+ * with intelligent collision detection and consistent positioning.
22
+ */
23
+ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
24
+ let {
25
+ seriesIds,
26
+ hideLine,
27
+ label,
28
+ lineStroke,
29
+ labelProps,
30
+ BeaconComponent = ScrubberBeacon,
31
+ LineComponent = ReferenceLine,
32
+ hideOverlay,
33
+ overlayOffset = 2,
34
+ testID,
35
+ idlePulse
36
+ } = _ref;
37
+ const theme = useTheme();
38
+ const ScrubberBeaconRefs = useRefMap();
39
+
40
+ // Animated values for overlay positions (using react-native Animated)
41
+ const overlayX = useRef(new Animated.Value(0)).current;
42
+ const overlayWidth = useRef(new Animated.Value(0)).current;
43
+
44
+ // Reanimated shared value for scrubber line
45
+ const scrubberLineX = useSharedValue(0);
46
+ const {
47
+ scrubberPosition: scrubberPosition
48
+ } = useScrubberContext();
49
+ const {
50
+ getXScale,
51
+ getYScale,
52
+ getSeriesData,
53
+ getXAxis,
54
+ series,
55
+ drawingArea
56
+ } = useCartesianChartContext();
57
+ const getStackedSeriesData = getSeriesData; // getSeriesData now returns stacked data
58
+
59
+ // Animated props for scrubber line
60
+ const scrubberLineAnimatedProps = useAnimatedProps(() => ({
61
+ x1: scrubberLineX.value,
62
+ x2: scrubberLineX.value
63
+ }));
64
+
65
+ // Expose imperative handle with pulse method
66
+ useImperativeHandle(ref, () => ({
67
+ pulse: () => {
68
+ // Pulse all registered scrubber beacons
69
+ Object.values(ScrubberBeaconRefs.refs).forEach(beaconRef => {
70
+ beaconRef == null || beaconRef.pulse();
71
+ });
72
+ }
73
+ }));
74
+ const {
75
+ dataX,
76
+ dataIndex
77
+ } = useMemo(() => {
78
+ var _series$reduce;
79
+ const xScale = getXScale();
80
+ const xAxis = getXAxis();
81
+ if (!xScale) return {
82
+ dataX: undefined,
83
+ dataIndex: undefined
84
+ };
85
+ const maxDataLength = (_series$reduce = series == null ? void 0 : series.reduce((max, s) => {
86
+ var _seriesData$length;
87
+ const seriesData = getStackedSeriesData(s.id) || getSeriesData(s.id);
88
+ return Math.max(max, (_seriesData$length = seriesData == null ? void 0 : seriesData.length) != null ? _seriesData$length : 0);
89
+ }, 0)) != null ? _series$reduce : 0;
90
+ const dataIndex = scrubberPosition != null ? scrubberPosition : Math.max(0, maxDataLength - 1);
91
+
92
+ // Convert index to actual x value if axis has data
93
+ let dataX;
94
+ if (xAxis != null && xAxis.data && Array.isArray(xAxis.data) && xAxis.data[dataIndex] !== undefined) {
95
+ const dataValue = xAxis.data[dataIndex];
96
+ dataX = typeof dataValue === 'string' ? dataIndex : dataValue;
97
+ } else {
98
+ dataX = dataIndex;
99
+ }
100
+ return {
101
+ dataX,
102
+ dataIndex
103
+ };
104
+ }, [getXScale, getXAxis, series, scrubberPosition, getStackedSeriesData, getSeriesData]);
105
+ const beaconPositions = useMemo(() => {
106
+ var _series$filter$map$fi, _series$filter;
107
+ const xScale = getXScale();
108
+ if (!xScale || dataX === undefined || dataIndex === undefined) return [];
109
+ return (_series$filter$map$fi = series == null || (_series$filter = series.filter(s => {
110
+ if (seriesIds === undefined) return true;
111
+ return seriesIds.includes(s.id);
112
+ })) == null ? void 0 : _series$filter.map(s => {
113
+ const sourceData = getStackedSeriesData(s.id) || getSeriesData(s.id);
114
+ // Use dataIndex to get the y value from the series data array
115
+ const stuff = sourceData == null ? void 0 : sourceData[dataIndex];
116
+ let dataY;
117
+ if (Array.isArray(stuff)) {
118
+ dataY = stuff[stuff.length - 1];
119
+ } else if (typeof stuff === 'number') {
120
+ dataY = stuff;
121
+ }
122
+ if (dataY !== undefined) {
123
+ const resolvedLabel = typeof s.label === 'function' ? s.label(dataIndex) : s.label;
124
+ return {
125
+ x: dataX,
126
+ y: dataY,
127
+ label: resolvedLabel,
128
+ targetSeries: s
129
+ };
130
+ }
131
+ }).filter(beacon => beacon !== undefined)) != null ? _series$filter$map$fi : [];
132
+ }, [getXScale, dataX, dataIndex, series, seriesIds, getStackedSeriesData, getSeriesData]);
133
+ const createScrubberBeaconRef = useCallback(seriesId => {
134
+ return beaconRef => {
135
+ if (beaconRef) {
136
+ ScrubberBeaconRefs.registerRef(seriesId, beaconRef);
137
+ }
138
+ };
139
+ }, [ScrubberBeaconRefs]);
140
+ const defaultXScale = getXScale();
141
+ const pixelX = dataX !== undefined && defaultXScale ? defaultXScale(dataX) : undefined;
142
+ const memoizedScrubberLabel = useMemo(() => {
143
+ if (typeof label === 'function') {
144
+ if (dataIndex === undefined) return undefined;
145
+ return label(dataIndex);
146
+ }
147
+ return label;
148
+ }, [label, dataIndex]);
149
+ useEffect(() => {
150
+ if (pixelX !== undefined) {
151
+ scrubberLineX.value = pixelX;
152
+ overlayX.setValue(pixelX);
153
+ overlayWidth.setValue(drawingArea.x + drawingArea.width - pixelX + overlayOffset);
154
+ }
155
+ }, [pixelX, drawingArea, overlayOffset, scrubberLineX, overlayX, overlayWidth]);
156
+ if (!defaultXScale) return null;
157
+ return /*#__PURE__*/_jsxs(_Fragment, {
158
+ children: [!hideOverlay && dataX !== undefined && scrubberPosition !== undefined && pixelX !== undefined && /*#__PURE__*/_jsx(AnimatedRect, {
159
+ fill: theme.color.bg,
160
+ height: drawingArea.height + overlayOffset * 2,
161
+ opacity: 0.8,
162
+ width: overlayWidth,
163
+ x: overlayX,
164
+ y: drawingArea.y - overlayOffset
165
+ }), !hideLine && scrubberPosition !== undefined && dataX !== undefined && /*#__PURE__*/_jsx(LineComponent, {
166
+ dataX: dataX,
167
+ label: memoizedScrubberLabel,
168
+ labelProps: labelProps,
169
+ stroke: lineStroke
170
+ }), beaconPositions.filter(beacon => beacon !== undefined).map(beacon => {
171
+ var _beacon$targetSeries;
172
+ return /*#__PURE__*/_jsx(G, {
173
+ "data-component": "scrubber-beacon",
174
+ children: /*#__PURE__*/_jsx(BeaconComponent, {
175
+ ref: createScrubberBeaconRef(beacon.targetSeries.id),
176
+ color: (_beacon$targetSeries = beacon.targetSeries) == null ? void 0 : _beacon$targetSeries.color,
177
+ dataX: beacon.x,
178
+ dataY: beacon.y,
179
+ idlePulse: idlePulse,
180
+ seriesId: beacon.targetSeries.id,
181
+ testID: testID ? testID + "-" + beacon.targetSeries.id + "-dot" : undefined
182
+ })
183
+ }, beacon.targetSeries.id);
184
+ })]
185
+ });
186
+ }));
@@ -0,0 +1,199 @@
1
+ import { forwardRef, memo, useEffect, useImperativeHandle, useMemo } from 'react';
2
+ import Reanimated, { cancelAnimation, useAnimatedProps, useSharedValue, withRepeat, withSequence, withTiming } from 'react-native-reanimated';
3
+ import { Circle, G } from 'react-native-svg';
4
+ import { usePreviousValue } from '@coinbase/cds-common/hooks/usePreviousValue';
5
+ import { useTheme } from '@coinbase/cds-mobile';
6
+ import { useCartesianChartContext } from '../ChartProvider';
7
+ import { projectPoint, useScrubberContext } from '../utils';
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ const AnimatedCircle = Reanimated.createAnimatedComponent(Circle);
10
+ const radius = 5;
11
+ const glowRadius = 10;
12
+ const pulseRadius = 15;
13
+ const pulseDuration = 2000; // 2 seconds
14
+ const singlePulseDuration = 1000; // 1 second
15
+
16
+ /**
17
+ * The ScrubberBeacon is a special instance of a Point used to mark the scrubber's position on a specific series.
18
+ */
19
+ export const ScrubberBeacon = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
20
+ var _pixelCoordinate$x, _pixelCoordinate$y, _ref2;
21
+ let {
22
+ seriesId,
23
+ dataX: dataXProp,
24
+ dataY: dataYProp,
25
+ color,
26
+ testID,
27
+ idlePulse,
28
+ opacity = 1
29
+ } = _ref;
30
+ const theme = useTheme();
31
+ const {
32
+ getSeries,
33
+ getXScale,
34
+ getYScale,
35
+ getSeriesData,
36
+ animate
37
+ } = useCartesianChartContext();
38
+ const {
39
+ scrubberPosition
40
+ } = useScrubberContext();
41
+ const targetSeries = getSeries(seriesId);
42
+ const sourceData = getSeriesData(seriesId);
43
+ const xScale = getXScale();
44
+ const yScale = getYScale(targetSeries == null ? void 0 : targetSeries.yAxisId);
45
+ const isIdleState = scrubberPosition === undefined;
46
+ const {
47
+ dataX,
48
+ dataY
49
+ } = useMemo(() => {
50
+ let x;
51
+ let y;
52
+ if (xScale && yScale) {
53
+ if (dataXProp !== undefined && dataYProp !== undefined && !isNaN(dataYProp) && !isNaN(dataXProp)) {
54
+ // Use direct coordinates if provided
55
+ x = dataXProp;
56
+ y = dataYProp;
57
+ } else if (sourceData && scrubberPosition != null && scrubberPosition >= 0 && scrubberPosition < sourceData.length) {
58
+ // Use series data at highlight index
59
+ x = scrubberPosition;
60
+ const dataValue = sourceData[scrubberPosition];
61
+ if (typeof dataValue === 'number') {
62
+ y = dataValue;
63
+ } else if (Array.isArray(dataValue)) {
64
+ const validValues = dataValue.filter(val => val !== null);
65
+ if (validValues.length >= 2) {
66
+ y = validValues[1];
67
+ }
68
+ }
69
+ }
70
+ }
71
+ return {
72
+ dataX: x,
73
+ dataY: y
74
+ };
75
+ }, [dataXProp, dataYProp, sourceData, scrubberPosition, xScale, yScale]);
76
+ const previousIdleState = usePreviousValue(!!isIdleState);
77
+ const pixelCoordinate = useMemo(() => {
78
+ if (!xScale || !yScale || dataX === undefined || dataY === undefined) return undefined;
79
+ const point = projectPoint({
80
+ x: dataX,
81
+ y: dataY,
82
+ xScale,
83
+ yScale
84
+ });
85
+
86
+ // Return undefined if coordinates are invalid
87
+ if (!point || isNaN(point.x) || isNaN(point.y)) return undefined;
88
+ return point;
89
+ }, [xScale, yScale, dataX, dataY]);
90
+ const animatedX = useSharedValue((_pixelCoordinate$x = pixelCoordinate == null ? void 0 : pixelCoordinate.x) != null ? _pixelCoordinate$x : 0);
91
+ const animatedY = useSharedValue((_pixelCoordinate$y = pixelCoordinate == null ? void 0 : pixelCoordinate.y) != null ? _pixelCoordinate$y : 0);
92
+ const pulseOpacity = useSharedValue(0);
93
+ useImperativeHandle(ref, () => ({
94
+ pulse: () => {
95
+ if (isIdleState && animate) {
96
+ pulseOpacity.value = 0.1;
97
+ pulseOpacity.value = withTiming(0, {
98
+ duration: singlePulseDuration
99
+ });
100
+ }
101
+ }
102
+ }));
103
+ useEffect(() => {
104
+ const shouldPulse = animate && isIdleState && idlePulse;
105
+ if (shouldPulse) {
106
+ pulseOpacity.value = withRepeat(withSequence(withTiming(0.1, {
107
+ duration: pulseDuration / 2
108
+ }), withTiming(0, {
109
+ duration: pulseDuration / 2
110
+ })), -1,
111
+ // loop
112
+ false);
113
+ } else {
114
+ cancelAnimation(pulseOpacity);
115
+ pulseOpacity.value = withTiming(0, {
116
+ duration: 200
117
+ });
118
+ }
119
+ }, [animate, isIdleState, idlePulse, pulseOpacity]);
120
+
121
+ // Update position when data coordinates change
122
+ useEffect(() => {
123
+ if (!pixelCoordinate) return;
124
+
125
+ // When scrubbing or animations disabled: snap immediately
126
+ if (!isIdleState || !animate || !previousIdleState) {
127
+ // Cancel any ongoing animations before snapping
128
+ cancelAnimation(animatedX);
129
+ cancelAnimation(animatedY);
130
+ animatedX.value = pixelCoordinate.x;
131
+ animatedY.value = pixelCoordinate.y;
132
+ } else {
133
+ // When idle with animations enabled: animate smoothly
134
+ animatedX.value = withTiming(pixelCoordinate.x, {
135
+ duration: 300
136
+ });
137
+ animatedY.value = withTiming(pixelCoordinate.y, {
138
+ duration: 300
139
+ });
140
+ }
141
+ }, [pixelCoordinate, isIdleState, animate, previousIdleState, animatedX, animatedY]);
142
+
143
+ // Animated props for all circles in idle state
144
+ const glowAnimatedProps = useAnimatedProps(() => ({
145
+ cx: animatedX.value,
146
+ cy: animatedY.value
147
+ }));
148
+ const pointAnimatedProps = useAnimatedProps(() => ({
149
+ cx: animatedX.value,
150
+ cy: animatedY.value
151
+ }));
152
+ const pulseAnimatedProps = useAnimatedProps(() => ({
153
+ cx: animatedX.value,
154
+ cy: animatedY.value,
155
+ opacity: pulseOpacity.value
156
+ }));
157
+ if (!pixelCoordinate) return;
158
+ const pointColor = (_ref2 = color != null ? color : targetSeries == null ? void 0 : targetSeries.color) != null ? _ref2 : theme.color.fgPrimary;
159
+ if (!isIdleState) {
160
+ return /*#__PURE__*/_jsxs(G, {
161
+ opacity: opacity,
162
+ testID: testID,
163
+ children: [/*#__PURE__*/_jsx(Circle, {
164
+ cx: pixelCoordinate.x,
165
+ cy: pixelCoordinate.y,
166
+ fill: pointColor,
167
+ opacity: 0.15,
168
+ r: glowRadius
169
+ }), /*#__PURE__*/_jsx(Circle, {
170
+ cx: pixelCoordinate.x,
171
+ cy: pixelCoordinate.y,
172
+ fill: pointColor,
173
+ r: radius,
174
+ stroke: theme.color.bg,
175
+ strokeWidth: 2
176
+ })]
177
+ });
178
+ }
179
+ return /*#__PURE__*/_jsxs(G, {
180
+ opacity: opacity,
181
+ testID: testID,
182
+ children: [/*#__PURE__*/_jsx(AnimatedCircle, {
183
+ animatedProps: glowAnimatedProps,
184
+ fill: pointColor,
185
+ opacity: 0.15,
186
+ r: glowRadius
187
+ }), /*#__PURE__*/_jsx(AnimatedCircle, {
188
+ animatedProps: pulseAnimatedProps,
189
+ fill: pointColor,
190
+ r: pulseRadius
191
+ }), /*#__PURE__*/_jsx(AnimatedCircle, {
192
+ animatedProps: pointAnimatedProps,
193
+ fill: pointColor,
194
+ r: radius,
195
+ stroke: theme.color.bg,
196
+ strokeWidth: 2
197
+ })]
198
+ });
199
+ }));
@@ -0,0 +1,143 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import { Platform } from 'react-native';
3
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
4
+ import { runOnJS } from 'react-native-reanimated';
5
+ import { Haptics } from '@coinbase/cds-mobile/utils/haptics';
6
+ import { useCartesianChartContext } from '../ChartProvider';
7
+ import { isCategoricalScale, ScrubberContext } from '../utils';
8
+ import { jsx as _jsx } from "react/jsx-runtime";
9
+ /**
10
+ * A component which encapsulates the ScrubberContext.
11
+ * It depends on a ChartContext in order to provide accurate touch tracking.
12
+ */
13
+ export const ScrubberProvider = _ref => {
14
+ let {
15
+ children,
16
+ enableScrubbing,
17
+ onScrubberPositionChange,
18
+ allowOverflowGestures
19
+ } = _ref;
20
+ const chartContext = useCartesianChartContext();
21
+ if (!chartContext) {
22
+ throw new Error('ScrubberProvider must be used within a ChartContext');
23
+ }
24
+ const {
25
+ getXScale,
26
+ getXAxis,
27
+ series
28
+ } = chartContext;
29
+ const [scrubberPosition, setScrubberPosition] = useState(undefined);
30
+ const getDataIndexFromX = useCallback(touchX => {
31
+ const xScale = getXScale();
32
+ const xAxis = getXAxis();
33
+ if (!xScale || !xAxis) return 0;
34
+ if (isCategoricalScale(xScale)) {
35
+ var _ref2, _xScale$domain, _xScale$bandwidth;
36
+ const categories = (_ref2 = (_xScale$domain = xScale.domain == null ? void 0 : xScale.domain()) != null ? _xScale$domain : xAxis.data) != null ? _ref2 : [];
37
+ const bandwidth = (_xScale$bandwidth = xScale.bandwidth == null ? void 0 : xScale.bandwidth()) != null ? _xScale$bandwidth : 0;
38
+ let closestIndex = 0;
39
+ let closestDistance = Infinity;
40
+ for (let i = 0; i < categories.length; i++) {
41
+ const xPos = xScale(i);
42
+ if (xPos !== undefined) {
43
+ const distance = Math.abs(touchX - (xPos + bandwidth / 2));
44
+ if (distance < closestDistance) {
45
+ closestDistance = distance;
46
+ closestIndex = i;
47
+ }
48
+ }
49
+ }
50
+ return closestIndex;
51
+ } else {
52
+ // For numeric scales with axis data, find the nearest data point
53
+ const axisData = xAxis.data;
54
+ if (axisData && Array.isArray(axisData) && typeof axisData[0] === 'number') {
55
+ // We have numeric axis data - find the closest data point
56
+ const numericData = axisData;
57
+ let closestIndex = 0;
58
+ let closestDistance = Infinity;
59
+ for (let i = 0; i < numericData.length; i++) {
60
+ const xValue = numericData[i];
61
+ const xPos = xScale(xValue);
62
+ if (xPos !== undefined) {
63
+ const distance = Math.abs(touchX - xPos);
64
+ if (distance < closestDistance) {
65
+ closestDistance = distance;
66
+ closestIndex = i;
67
+ }
68
+ }
69
+ }
70
+ return closestIndex;
71
+ } else {
72
+ var _domain$min, _domain$max;
73
+ const xValue = xScale.invert(touchX);
74
+ const dataIndex = Math.round(xValue);
75
+ const domain = xAxis.domain;
76
+ return Math.max((_domain$min = domain.min) != null ? _domain$min : 0, Math.min(dataIndex, (_domain$max = domain.max) != null ? _domain$max : 0));
77
+ }
78
+ }
79
+ }, [getXScale, getXAxis]);
80
+ const handlePositionUpdate = useCallback(x => {
81
+ if (!enableScrubbing || !series || series.length === 0) return;
82
+ const dataIndex = getDataIndexFromX(x);
83
+ if (dataIndex !== scrubberPosition) {
84
+ setScrubberPosition(dataIndex);
85
+ onScrubberPositionChange == null || onScrubberPositionChange(dataIndex);
86
+ }
87
+ }, [enableScrubbing, series, getDataIndexFromX, scrubberPosition, onScrubberPositionChange]);
88
+ const handleInteractionEnd = useCallback(() => {
89
+ if (!enableScrubbing) return;
90
+ setScrubberPosition(undefined);
91
+ onScrubberPositionChange == null || onScrubberPositionChange(undefined);
92
+ }, [enableScrubbing, onScrubberPositionChange]);
93
+
94
+ // Gesture handler callbacks
95
+ const handleOnStartJsThread = useCallback(() => {
96
+ void Haptics.lightImpact();
97
+ // Could add onScrubStart callback here if needed
98
+ }, []);
99
+ const handleOnEndOrCancelledJsThread = useCallback(() => {
100
+ handleInteractionEnd();
101
+ }, [handleInteractionEnd]);
102
+ const handleOnUpdateJsThread = useCallback(x => {
103
+ handlePositionUpdate(x);
104
+ }, [handlePositionUpdate]);
105
+ const handleOnEndJsThread = useCallback(() => {
106
+ void Haptics.lightImpact();
107
+ handleOnEndOrCancelledJsThread();
108
+ }, [handleOnEndOrCancelledJsThread]);
109
+
110
+ // Create the long press pan gesture
111
+ const longPressGesture = useMemo(() => Gesture.Pan().activateAfterLongPress(110).shouldCancelWhenOutside(!allowOverflowGestures).onStart(function onStart(event) {
112
+ runOnJS(handleOnStartJsThread)();
113
+
114
+ // Android does not trigger onUpdate when the gesture starts. This achieves consistent behavior across both iOS and Android
115
+ if (Platform.OS === 'android') {
116
+ runOnJS(handleOnUpdateJsThread)(event.x);
117
+ }
118
+ }).onUpdate(function onUpdate(event) {
119
+ runOnJS(handleOnUpdateJsThread)(event.x);
120
+ }).onEnd(function onEnd() {
121
+ runOnJS(handleOnEndJsThread)();
122
+ }).onTouchesCancelled(function onTouchesCancelled() {
123
+ runOnJS(handleOnEndOrCancelledJsThread)();
124
+ }), [allowOverflowGestures, handleOnStartJsThread, handleOnUpdateJsThread, handleOnEndJsThread, handleOnEndOrCancelledJsThread]);
125
+ const contextValue = useMemo(() => ({
126
+ enableScrubbing: !!enableScrubbing,
127
+ scrubberPosition,
128
+ onScrubberPositionChange: setScrubberPosition
129
+ }), [enableScrubbing, scrubberPosition]);
130
+ const content = /*#__PURE__*/_jsx(ScrubberContext.Provider, {
131
+ value: contextValue,
132
+ children: children
133
+ });
134
+
135
+ // Wrap with gesture handler only if scrubbing is enabled
136
+ if (enableScrubbing) {
137
+ return /*#__PURE__*/_jsx(GestureDetector, {
138
+ gesture: longPressGesture,
139
+ children: content
140
+ });
141
+ }
142
+ return content;
143
+ };
@@ -0,0 +1,2 @@
1
+ // Only export Scrubber component
2
+ export * from './Scrubber';