@coinbase/cds-mobile-visualization 3.4.0-beta.4 → 3.4.0-beta.6

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 (179) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dts/chart/CartesianChart.d.ts +57 -33
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/ChartContextBridge.d.ts +28 -0
  5. package/dts/chart/ChartContextBridge.d.ts.map +1 -0
  6. package/dts/chart/Path.d.ts +77 -34
  7. package/dts/chart/Path.d.ts.map +1 -1
  8. package/dts/chart/PeriodSelector.d.ts +1 -1
  9. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  10. package/dts/chart/area/Area.d.ts +42 -27
  11. package/dts/chart/area/Area.d.ts.map +1 -1
  12. package/dts/chart/area/AreaChart.d.ts +51 -10
  13. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  14. package/dts/chart/area/DottedArea.d.ts +21 -2
  15. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  16. package/dts/chart/area/GradientArea.d.ts +19 -13
  17. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  18. package/dts/chart/area/SolidArea.d.ts +17 -2
  19. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  20. package/dts/chart/axis/Axis.d.ts +68 -78
  21. package/dts/chart/axis/Axis.d.ts.map +1 -1
  22. package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
  23. package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
  24. package/dts/chart/axis/XAxis.d.ts +1 -1
  25. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  26. package/dts/chart/axis/YAxis.d.ts +2 -2
  27. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  28. package/dts/chart/axis/index.d.ts +1 -0
  29. package/dts/chart/axis/index.d.ts.map +1 -1
  30. package/dts/chart/bar/Bar.d.ts +16 -13
  31. package/dts/chart/bar/Bar.d.ts.map +1 -1
  32. package/dts/chart/bar/BarChart.d.ts +36 -20
  33. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  34. package/dts/chart/bar/BarPlot.d.ts +2 -1
  35. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  36. package/dts/chart/bar/BarStack.d.ts +39 -48
  37. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  38. package/dts/chart/bar/BarStackGroup.d.ts +1 -0
  39. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  40. package/dts/chart/bar/DefaultBar.d.ts +1 -1
  41. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  42. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  43. package/dts/chart/gradient/Gradient.d.ts +25 -0
  44. package/dts/chart/gradient/Gradient.d.ts.map +1 -0
  45. package/dts/chart/gradient/index.d.ts +2 -0
  46. package/dts/chart/gradient/index.d.ts.map +1 -0
  47. package/dts/chart/index.d.ts +3 -1
  48. package/dts/chart/index.d.ts.map +1 -1
  49. package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
  50. package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
  51. package/dts/chart/line/DottedLine.d.ts +13 -5
  52. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  53. package/dts/chart/line/Line.d.ts +62 -25
  54. package/dts/chart/line/Line.d.ts.map +1 -1
  55. package/dts/chart/line/LineChart.d.ts +43 -9
  56. package/dts/chart/line/LineChart.d.ts.map +1 -1
  57. package/dts/chart/line/ReferenceLine.d.ts +65 -22
  58. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  59. package/dts/chart/line/SolidLine.d.ts +8 -5
  60. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  61. package/dts/chart/line/index.d.ts +1 -1
  62. package/dts/chart/line/index.d.ts.map +1 -1
  63. package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
  64. package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
  65. package/dts/chart/point/Point.d.ts +120 -0
  66. package/dts/chart/point/Point.d.ts.map +1 -0
  67. package/dts/chart/point/index.d.ts +3 -0
  68. package/dts/chart/point/index.d.ts.map +1 -0
  69. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +8 -0
  70. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
  71. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
  72. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
  73. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +11 -0
  74. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
  75. package/dts/chart/scrubber/Scrubber.d.ts +168 -41
  76. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  77. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +44 -0
  78. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
  79. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +31 -0
  80. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
  81. package/dts/chart/scrubber/ScrubberProvider.d.ts +6 -3
  82. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  83. package/dts/chart/scrubber/index.d.ts +3 -0
  84. package/dts/chart/scrubber/index.d.ts.map +1 -1
  85. package/dts/chart/text/ChartText.d.ts +151 -77
  86. package/dts/chart/text/ChartText.d.ts.map +1 -1
  87. package/dts/chart/text/{SmartChartTextGroup.d.ts → ChartTextGroup.d.ts} +9 -3
  88. package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
  89. package/dts/chart/text/index.d.ts +1 -1
  90. package/dts/chart/text/index.d.ts.map +1 -1
  91. package/dts/chart/utils/chart.d.ts +34 -7
  92. package/dts/chart/utils/chart.d.ts.map +1 -1
  93. package/dts/chart/utils/context.d.ts +28 -7
  94. package/dts/chart/utils/context.d.ts.map +1 -1
  95. package/dts/chart/utils/gradient.d.ts +117 -0
  96. package/dts/chart/utils/gradient.d.ts.map +1 -0
  97. package/dts/chart/utils/index.d.ts +3 -0
  98. package/dts/chart/utils/index.d.ts.map +1 -1
  99. package/dts/chart/utils/path.d.ts +53 -0
  100. package/dts/chart/utils/path.d.ts.map +1 -1
  101. package/dts/chart/utils/point.d.ts +60 -1
  102. package/dts/chart/utils/point.d.ts.map +1 -1
  103. package/dts/chart/utils/scale.d.ts +91 -0
  104. package/dts/chart/utils/scale.d.ts.map +1 -1
  105. package/dts/chart/utils/scrubber.d.ts +39 -0
  106. package/dts/chart/utils/scrubber.d.ts.map +1 -0
  107. package/dts/chart/utils/transition.d.ts +140 -0
  108. package/dts/chart/utils/transition.d.ts.map +1 -0
  109. package/esm/chart/CartesianChart.js +164 -70
  110. package/esm/chart/ChartContextBridge.js +148 -0
  111. package/esm/chart/Path.js +196 -113
  112. package/esm/chart/PeriodSelector.js +1 -1
  113. package/esm/chart/__stories__/CartesianChart.stories.js +371 -129
  114. package/esm/chart/__stories__/Chart.stories.js +2 -4
  115. package/esm/chart/area/Area.js +25 -35
  116. package/esm/chart/area/AreaChart.js +17 -12
  117. package/esm/chart/area/DottedArea.js +61 -109
  118. package/esm/chart/area/GradientArea.js +35 -91
  119. package/esm/chart/area/SolidArea.js +22 -8
  120. package/esm/chart/area/__stories__/AreaChart.stories.js +1 -1
  121. package/esm/chart/axis/Axis.js +2 -0
  122. package/esm/chart/axis/DefaultAxisTickLabel.js +11 -0
  123. package/esm/chart/axis/XAxis.js +62 -56
  124. package/esm/chart/axis/YAxis.js +58 -52
  125. package/esm/chart/axis/__stories__/Axis.stories.js +0 -1
  126. package/esm/chart/axis/index.js +1 -0
  127. package/esm/chart/bar/Bar.js +3 -1
  128. package/esm/chart/bar/BarChart.js +15 -37
  129. package/esm/chart/bar/BarPlot.js +41 -35
  130. package/esm/chart/bar/BarStack.js +75 -38
  131. package/esm/chart/bar/BarStackGroup.js +6 -16
  132. package/esm/chart/bar/DefaultBar.js +26 -48
  133. package/esm/chart/bar/DefaultBarStack.js +23 -58
  134. package/esm/chart/bar/__stories__/BarChart.stories.js +463 -77
  135. package/esm/chart/gradient/Gradient.js +53 -0
  136. package/esm/chart/gradient/index.js +1 -0
  137. package/esm/chart/index.js +3 -1
  138. package/esm/chart/line/DefaultReferenceLineLabel.js +66 -0
  139. package/esm/chart/line/DottedLine.js +29 -14
  140. package/esm/chart/line/Line.js +106 -67
  141. package/esm/chart/line/LineChart.js +20 -14
  142. package/esm/chart/line/ReferenceLine.js +73 -62
  143. package/esm/chart/line/SolidLine.js +25 -10
  144. package/esm/chart/line/__stories__/LineChart.stories.js +2098 -1975
  145. package/esm/chart/line/__stories__/ReferenceLine.stories.js +83 -28
  146. package/esm/chart/line/index.js +1 -1
  147. package/esm/chart/point/DefaultPointLabel.js +39 -0
  148. package/esm/chart/point/Point.js +188 -0
  149. package/esm/chart/point/index.js +2 -0
  150. package/esm/chart/scrubber/DefaultScrubberBeacon.js +179 -0
  151. package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +43 -0
  152. package/esm/chart/scrubber/DefaultScrubberLabel.js +28 -0
  153. package/esm/chart/scrubber/Scrubber.js +130 -148
  154. package/esm/chart/scrubber/ScrubberBeaconGroup.js +161 -0
  155. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +185 -0
  156. package/esm/chart/scrubber/ScrubberProvider.js +46 -54
  157. package/esm/chart/scrubber/index.js +3 -1
  158. package/esm/chart/text/ChartText.js +242 -174
  159. package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +6 -5
  160. package/esm/chart/text/index.js +1 -1
  161. package/esm/chart/utils/chart.js +44 -3
  162. package/esm/chart/utils/gradient.js +305 -0
  163. package/esm/chart/utils/index.js +3 -0
  164. package/esm/chart/utils/path.js +76 -8
  165. package/esm/chart/utils/point.js +116 -5
  166. package/esm/chart/utils/scale.js +230 -1
  167. package/esm/chart/utils/scrubber.js +139 -0
  168. package/esm/chart/utils/transition.js +221 -0
  169. package/package.json +11 -9
  170. package/dts/chart/Point.d.ts +0 -103
  171. package/dts/chart/Point.d.ts.map +0 -1
  172. package/dts/chart/line/GradientLine.d.ts +0 -45
  173. package/dts/chart/line/GradientLine.d.ts.map +0 -1
  174. package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -75
  175. package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
  176. package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
  177. package/esm/chart/Point.js +0 -111
  178. package/esm/chart/line/GradientLine.js +0 -62
  179. package/esm/chart/scrubber/ScrubberBeacon.js +0 -199
@@ -1,24 +1,17 @@
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';
1
+ import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react';
2
+ import { runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue, withDelay, withTiming } from 'react-native-reanimated';
6
3
  import { useTheme } from '@coinbase/cds-mobile';
4
+ import { Group, Rect } from '@shopify/react-native-skia';
7
5
  import { useCartesianChartContext } from '../ChartProvider';
8
6
  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
-
7
+ import { accessoryFadeTransitionDelay, accessoryFadeTransitionDuration, getPointOnSerializableScale, useScrubberContext } from '../utils';
8
+ import { DefaultScrubberBeacon } from './DefaultScrubberBeacon';
9
+ import { DefaultScrubberLabel } from './DefaultScrubberLabel';
10
+ import { ScrubberBeaconGroup } from './ScrubberBeaconGroup';
11
+ import { ScrubberBeaconLabelGroup } from './ScrubberBeaconLabelGroup';
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
13
  /**
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.
14
+ * Unified component that manages all scrubber elements (beacons, line, labels).
22
15
  */
23
16
  export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
24
17
  let {
@@ -26,161 +19,150 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
26
19
  hideLine,
27
20
  label,
28
21
  lineStroke,
29
- labelProps,
30
- BeaconComponent = ScrubberBeacon,
31
- LineComponent = ReferenceLine,
22
+ BeaconComponent = DefaultScrubberBeacon,
23
+ BeaconLabelComponent,
24
+ LineComponent,
25
+ LabelComponent = DefaultScrubberLabel,
26
+ labelElevated,
32
27
  hideOverlay,
33
28
  overlayOffset = 2,
34
- testID,
35
- idlePulse
29
+ beaconLabelMinGap,
30
+ beaconLabelHorizontalOffset,
31
+ labelFont,
32
+ labelBoundsInset,
33
+ beaconLabelFont,
34
+ idlePulse,
35
+ beaconTransitions
36
36
  } = _ref;
37
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);
38
+ const beaconGroupRef = React.useRef(null);
46
39
  const {
47
- scrubberPosition: scrubberPosition
40
+ scrubberPosition
48
41
  } = useScrubberContext();
49
42
  const {
50
- getXScale,
51
- getYScale,
52
- getSeriesData,
43
+ getXSerializableScale,
53
44
  getXAxis,
54
45
  series,
55
- drawingArea
46
+ drawingArea,
47
+ animate,
48
+ dataLength
56
49
  } = useCartesianChartContext();
57
- const getStackedSeriesData = getSeriesData; // getSeriesData now returns stacked data
50
+ const xAxis = useMemo(() => getXAxis(), [getXAxis]);
51
+ const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
58
52
 
59
- // Animated props for scrubber line
60
- const scrubberLineAnimatedProps = useAnimatedProps(() => ({
61
- x1: scrubberLineX.value,
62
- x2: scrubberLineX.value
63
- }));
53
+ // Animation state for delayed scrubber rendering (matches web timing)
54
+ const scrubberOpacity = useSharedValue(animate ? 0 : 1);
55
+
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
64
 
65
65
  // Expose imperative handle with pulse method
66
66
  useImperativeHandle(ref, () => ({
67
67
  pulse: () => {
68
- // Pulse all registered scrubber beacons
69
- Object.values(ScrubberBeaconRefs.refs).forEach(beaconRef => {
70
- beaconRef == null || beaconRef.pulse();
71
- });
68
+ var _beaconGroupRef$curre;
69
+ (_beaconGroupRef$curre = beaconGroupRef.current) == null || _beaconGroupRef$curre.pulse();
72
70
  }
73
71
  }));
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;
72
+ const filteredSeriesIds = useMemo(() => {
73
+ if (seriesIds === undefined) {
74
+ var _series$map;
75
+ return (_series$map = series == null ? void 0 : series.map(s => s.id)) != null ? _series$map : [];
99
76
  }
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);
77
+ return seriesIds;
78
+ }, [series, seriesIds]);
79
+ const dataIndex = useDerivedValue(() => {
80
+ var _scrubberPosition$val;
81
+ return (_scrubberPosition$val = scrubberPosition.value) != null ? _scrubberPosition$val : Math.max(0, dataLength - 1);
82
+ }, [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;
146
87
  }
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);
88
+ return dataIndex.value;
89
+ }, [xAxis, dataIndex]);
90
+ const lineOpacity = useDerivedValue(() => {
91
+ return scrubberPosition.value !== undefined ? 1 : 0;
92
+ }, [scrubberPosition]);
93
+ const overlayOpacity = useDerivedValue(() => {
94
+ return scrubberPosition.value !== undefined ? 0.8 : 0;
95
+ }, [scrubberPosition]);
96
+ 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]);
100
+ const overlayX = useDerivedValue(() => {
101
+ const xValue = dataX.value !== undefined && xScale ? getPointOnSerializableScale(dataX.value, xScale) : 0;
102
+ return xValue;
103
+ }, [dataX, xScale]);
104
+ const resolvedLabelValue = useSharedValue('');
105
+ const updateResolvedLabel = useCallback(index => {
106
+ if (!label) {
107
+ resolvedLabelValue.value = '';
108
+ return;
154
109
  }
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,
110
+ if (typeof label === 'function') {
111
+ const result = label(index);
112
+ resolvedLabelValue.value = result != null ? result : '';
113
+ } else if (typeof label === 'string') {
114
+ resolvedLabelValue.value = label;
115
+ }
116
+ }, [label, resolvedLabelValue]);
117
+
118
+ // Update resolved label when dataIndex changes
119
+ useAnimatedReaction(() => dataIndex.value, currentIndex => {
120
+ 'worklet';
121
+
122
+ runOnJS(updateResolvedLabel)(currentIndex);
123
+ }, [updateResolvedLabel]);
124
+ const beaconLabels = useMemo(() => {
125
+ var _series$filter$filter;
126
+ return (_series$filter$filter = series == null ? void 0 : series.filter(s => filteredSeriesIds.includes(s.id)).filter(s => s.label !== undefined && s.label.length > 0).map(s => ({
127
+ seriesId: s.id,
128
+ label: s.label,
129
+ color: s.color
130
+ }))) != null ? _series$filter$filter : [];
131
+ }, [series, filteredSeriesIds]);
132
+ if (!xScale) return;
133
+ return /*#__PURE__*/_jsxs(Group, {
134
+ opacity: scrubberOpacity,
135
+ children: [!hideOverlay && /*#__PURE__*/_jsx(Rect, {
136
+ color: theme.color.bg,
160
137
  height: drawingArea.height + overlayOffset * 2,
161
- opacity: 0.8,
138
+ opacity: overlayOpacity,
162
139
  width: overlayWidth,
163
140
  x: overlayX,
164
141
  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);
142
+ }), !hideLine && /*#__PURE__*/_jsx(Group, {
143
+ opacity: lineOpacity,
144
+ children: /*#__PURE__*/_jsx(ReferenceLine, {
145
+ LabelComponent: LabelComponent,
146
+ LineComponent: LineComponent,
147
+ dataX: dataX,
148
+ label: resolvedLabelValue,
149
+ labelBoundsInset: labelBoundsInset,
150
+ labelElevated: labelElevated,
151
+ labelFont: labelFont,
152
+ stroke: lineStroke
153
+ })
154
+ }), /*#__PURE__*/_jsx(ScrubberBeaconGroup, {
155
+ ref: beaconGroupRef,
156
+ BeaconComponent: BeaconComponent,
157
+ idlePulse: idlePulse,
158
+ seriesIds: filteredSeriesIds,
159
+ transitions: beaconTransitions
160
+ }), beaconLabels.length > 0 && /*#__PURE__*/_jsx(ScrubberBeaconLabelGroup, {
161
+ BeaconLabelComponent: BeaconLabelComponent,
162
+ labelFont: beaconLabelFont,
163
+ labelHorizontalOffset: beaconLabelHorizontalOffset,
164
+ labelMinGap: beaconLabelMinGap,
165
+ labels: beaconLabels
184
166
  })]
185
167
  });
186
168
  }));
@@ -0,0 +1,161 @@
1
+ import { forwardRef, memo, useCallback, useImperativeHandle, useMemo } from 'react';
2
+ import { useDerivedValue } from 'react-native-reanimated';
3
+ import { useRefMap } from '@coinbase/cds-common/hooks/useRefMap';
4
+ import { useTheme } from '@coinbase/cds-mobile';
5
+ import { useCartesianChartContext } from '../ChartProvider';
6
+ import { evaluateGradientAtValue, getGradientStops, useScrubberContext } from '../utils';
7
+ import { convertToSerializableScale } from '../utils/scale';
8
+ import { DefaultScrubberBeacon } from './DefaultScrubberBeacon';
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ // Helper component to calculate beacon data for a specific series
11
+ const BeaconWithData = /*#__PURE__*/memo(_ref => {
12
+ let {
13
+ seriesId,
14
+ dataIndex,
15
+ dataX,
16
+ isIdle,
17
+ BeaconComponent,
18
+ idlePulse,
19
+ animate,
20
+ transitions,
21
+ beaconRef
22
+ } = _ref;
23
+ const {
24
+ getSeries,
25
+ getSeriesData,
26
+ getXScale,
27
+ getYScale
28
+ } = useCartesianChartContext();
29
+ const theme = useTheme();
30
+ const series = useMemo(() => getSeries(seriesId), [getSeries, seriesId]);
31
+ const sourceData = useMemo(() => getSeriesData(seriesId), [getSeriesData, seriesId]);
32
+ const gradient = series == null ? void 0 : series.gradient;
33
+ const dataY = useDerivedValue(() => {
34
+ if (sourceData && dataIndex.value !== undefined && dataIndex.value >= 0 && dataIndex.value < sourceData.length) {
35
+ const dataValue = sourceData[dataIndex.value];
36
+ if (typeof dataValue === 'number') {
37
+ return dataValue;
38
+ } else if (Array.isArray(dataValue)) {
39
+ const validValues = dataValue.filter(val => val !== null);
40
+ if (validValues.length >= 1) {
41
+ return validValues[validValues.length - 1];
42
+ }
43
+ }
44
+ }
45
+ return 0;
46
+ }, [sourceData, dataIndex]);
47
+
48
+ // Get scales for gradient evaluation
49
+ const gradientScale = useMemo(() => {
50
+ if (!gradient) return undefined;
51
+ const scale = gradient.axis === 'x' ? getXScale() : getYScale(series == null ? void 0 : series.yAxisId);
52
+ if (!scale) return undefined;
53
+ return convertToSerializableScale(scale);
54
+ }, [gradient, getXScale, getYScale, series == null ? void 0 : series.yAxisId]);
55
+ const gradientStops = useMemo(() => {
56
+ if (!gradient || !gradientScale) return undefined;
57
+ const domain = {
58
+ min: gradientScale.domain[0],
59
+ max: gradientScale.domain[1]
60
+ };
61
+ return getGradientStops(gradient.stops, domain);
62
+ }, [gradient, gradientScale]);
63
+
64
+ // Evaluate gradient color on UI thread
65
+ const color = useDerivedValue(() => {
66
+ 'worklet';
67
+
68
+ // Evaluate gradient if present
69
+ var _series$color;
70
+ if (gradient && gradientScale && gradientStops) {
71
+ 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
+ }
79
+ }
80
+ }
81
+
82
+ // Fallback to series color
83
+ 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
+ return /*#__PURE__*/_jsx(BeaconComponent, {
86
+ ref: beaconRef,
87
+ animate: animate,
88
+ color: color,
89
+ dataX: dataX,
90
+ dataY: dataY,
91
+ idlePulse: idlePulse,
92
+ isIdle: isIdle,
93
+ seriesId: seriesId,
94
+ transitions: transitions
95
+ });
96
+ });
97
+ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref2, ref) => {
98
+ let {
99
+ seriesIds,
100
+ idlePulse,
101
+ transitions,
102
+ BeaconComponent = DefaultScrubberBeacon
103
+ } = _ref2;
104
+ const ScrubberBeaconRefs = useRefMap();
105
+ const {
106
+ scrubberPosition
107
+ } = useScrubberContext();
108
+ const {
109
+ getXAxis,
110
+ series,
111
+ dataLength,
112
+ animate
113
+ } = useCartesianChartContext();
114
+ const xAxis = useMemo(() => getXAxis(), [getXAxis]);
115
+
116
+ // Expose imperative handle with pulse method
117
+ useImperativeHandle(ref, () => ({
118
+ pulse: () => {
119
+ Object.values(ScrubberBeaconRefs.refs).forEach(beaconRef => {
120
+ beaconRef == null || beaconRef.pulse();
121
+ });
122
+ }
123
+ }));
124
+ const filteredSeries = useMemo(() => {
125
+ var _series$filter;
126
+ return (_series$filter = series == null ? void 0 : series.filter(s => seriesIds.includes(s.id))) != null ? _series$filter : [];
127
+ }, [series, seriesIds]);
128
+ const dataIndex = useDerivedValue(() => {
129
+ var _scrubberPosition$val;
130
+ return (_scrubberPosition$val = scrubberPosition.value) != null ? _scrubberPosition$val : Math.max(0, dataLength - 1);
131
+ }, [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];
136
+ return typeof dataValue === 'string' ? dataIndex.value : dataValue;
137
+ }
138
+ return dataIndex.value;
139
+ }, [xAxis, dataIndex]);
140
+ const isIdle = useDerivedValue(() => {
141
+ return scrubberPosition.value === undefined;
142
+ }, [scrubberPosition]);
143
+ const createBeaconRef = useCallback(seriesId => {
144
+ return beaconRef => {
145
+ if (beaconRef) {
146
+ ScrubberBeaconRefs.registerRef(seriesId, beaconRef);
147
+ }
148
+ };
149
+ }, [ScrubberBeaconRefs]);
150
+ return filteredSeries.map(s => /*#__PURE__*/_jsx(BeaconWithData, {
151
+ BeaconComponent: BeaconComponent,
152
+ animate: animate,
153
+ beaconRef: createBeaconRef(s.id),
154
+ dataIndex: dataIndex,
155
+ dataX: dataX,
156
+ idlePulse: idlePulse,
157
+ isIdle: isIdle,
158
+ seriesId: s.id,
159
+ transitions: transitions
160
+ }, s.id));
161
+ }));
@@ -0,0 +1,185 @@
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
+ import { memo, useCallback, useMemo, useState } from 'react';
3
+ import { useDerivedValue } from 'react-native-reanimated';
4
+ import { useCartesianChartContext } from '../ChartProvider';
5
+ import { applySerializableScale, useScrubberContext } from '../utils';
6
+ import { calculateLabelYPositions, getLabelPosition } from '../utils/scrubber';
7
+ import { DefaultScrubberBeaconLabel } from './DefaultScrubberBeaconLabel';
8
+ import { jsx as _jsx } from "react/jsx-runtime";
9
+ const PositionedLabel = /*#__PURE__*/memo(_ref => {
10
+ let {
11
+ index,
12
+ positions,
13
+ position,
14
+ label,
15
+ color,
16
+ seriesId,
17
+ onDimensionsChange,
18
+ BeaconLabelComponent,
19
+ labelHorizontalOffset,
20
+ labelFont
21
+ } = _ref;
22
+ const opacity = useDerivedValue(() => positions.value[index] !== null ? 1 : 0, [positions, index]);
23
+ const x = useDerivedValue(() => {
24
+ var _positions$value$inde, _positions$value$inde2;
25
+ return (_positions$value$inde = (_positions$value$inde2 = positions.value[index]) == null ? void 0 : _positions$value$inde2.x) != null ? _positions$value$inde : 0;
26
+ }, [positions, index]);
27
+ const y = useDerivedValue(() => {
28
+ var _positions$value$inde3, _positions$value$inde4;
29
+ return (_positions$value$inde3 = (_positions$value$inde4 = positions.value[index]) == null ? void 0 : _positions$value$inde4.y) != null ? _positions$value$inde3 : 0;
30
+ }, [positions, index]);
31
+ const dx = useDerivedValue(() => {
32
+ return position.value === 'right' ? labelHorizontalOffset : -labelHorizontalOffset;
33
+ }, [position, labelHorizontalOffset]);
34
+ const horizontalAlignment = useDerivedValue(() => position.value === 'right' ? 'left' : 'right', [position]);
35
+ return /*#__PURE__*/_jsx(BeaconLabelComponent, {
36
+ color: color,
37
+ dx: dx,
38
+ font: labelFont,
39
+ horizontalAlignment: horizontalAlignment,
40
+ label: label,
41
+ onDimensionsChange: d => onDimensionsChange(seriesId, d),
42
+ opacity: opacity,
43
+ seriesId: seriesId,
44
+ x: x,
45
+ y: y
46
+ });
47
+ });
48
+ export const ScrubberBeaconLabelGroup = /*#__PURE__*/memo(_ref2 => {
49
+ let {
50
+ labels,
51
+ labelMinGap = 4,
52
+ labelHorizontalOffset = 16,
53
+ labelFont,
54
+ BeaconLabelComponent = DefaultScrubberBeaconLabel
55
+ } = _ref2;
56
+ const {
57
+ getSeries,
58
+ getSeriesData,
59
+ getXSerializableScale,
60
+ getYSerializableScale,
61
+ getXAxis,
62
+ drawingArea,
63
+ dataLength
64
+ } = useCartesianChartContext();
65
+ const {
66
+ scrubberPosition
67
+ } = useScrubberContext();
68
+ const [labelDimensions, setLabelDimensions] = useState({});
69
+ const handleDimensionsChange = useCallback((id, dimensions) => {
70
+ setLabelDimensions(prev => {
71
+ const existing = prev[id];
72
+ if (existing && existing.width === dimensions.width && existing.height === dimensions.height) {
73
+ return prev;
74
+ }
75
+ return _extends({}, prev, {
76
+ [id]: dimensions
77
+ });
78
+ });
79
+ }, []);
80
+ const seriesInfo = useMemo(() => {
81
+ return labels.map(label => {
82
+ const series = getSeries(label.seriesId);
83
+ if (!series) return null;
84
+ const sourceData = getSeriesData(label.seriesId);
85
+ const yScale = getYSerializableScale(series.yAxisId);
86
+ return {
87
+ seriesId: label.seriesId,
88
+ sourceData,
89
+ yScale
90
+ };
91
+ }).filter(info => info !== null);
92
+ }, [labels, getSeries, getSeriesData, getYSerializableScale]);
93
+ const xScale = getXSerializableScale();
94
+ const xAxis = getXAxis();
95
+ const dataIndex = useDerivedValue(() => {
96
+ var _scrubberPosition$val;
97
+ return (_scrubberPosition$val = scrubberPosition.value) != null ? _scrubberPosition$val : Math.max(0, dataLength - 1);
98
+ }, [scrubberPosition, dataLength]);
99
+ const dataX = useDerivedValue(() => {
100
+ if (xAxis != null && xAxis.data && Array.isArray(xAxis.data) && xAxis.data[dataIndex.value] !== undefined) {
101
+ const dataValue = xAxis.data[dataIndex.value];
102
+ return typeof dataValue === 'string' ? dataIndex.value : dataValue;
103
+ }
104
+ return dataIndex.value;
105
+ }, [xAxis, dataIndex]);
106
+ const allLabelPositions = useDerivedValue(() => {
107
+ const sharedPixelX = dataX.value !== undefined && xScale ? applySerializableScale(dataX.value, xScale) : 0;
108
+ const desiredPositions = seriesInfo.map(info => {
109
+ let dataY;
110
+ if (xScale && info.yScale) {
111
+ if (info.sourceData && dataIndex.value !== undefined && dataIndex.value >= 0 && dataIndex.value < info.sourceData.length) {
112
+ const dataValue = info.sourceData[dataIndex.value];
113
+ if (Array.isArray(dataValue)) {
114
+ const validValues = dataValue.filter(val => val !== null);
115
+ if (validValues.length >= 1) {
116
+ dataY = validValues[validValues.length - 1];
117
+ }
118
+ }
119
+ }
120
+ }
121
+ if (dataY !== undefined && info.yScale) {
122
+ return {
123
+ seriesId: info.seriesId,
124
+ x: sharedPixelX,
125
+ desiredY: applySerializableScale(dataY, info.yScale)
126
+ };
127
+ }
128
+
129
+ // Return null for invalid data
130
+ return null;
131
+ });
132
+ const maxLabelHeight = Math.max(...Object.values(labelDimensions).map(dim => dim.height));
133
+ const maxLabelWidth = Math.max(...Object.values(labelDimensions).map(dim => dim.width));
134
+ const validPositions = desiredPositions.filter(pos => pos !== null);
135
+
136
+ // Convert to LabelDimension format expected by utility
137
+ const dimensions = validPositions.map(pos => {
138
+ var _trackedDimensions$wi, _trackedDimensions$he;
139
+ const trackedDimensions = labelDimensions[pos.seriesId];
140
+ return {
141
+ seriesId: pos.seriesId,
142
+ width: (_trackedDimensions$wi = trackedDimensions == null ? void 0 : trackedDimensions.width) != null ? _trackedDimensions$wi : maxLabelWidth,
143
+ height: (_trackedDimensions$he = trackedDimensions == null ? void 0 : trackedDimensions.height) != null ? _trackedDimensions$he : maxLabelHeight,
144
+ preferredX: pos.x,
145
+ preferredY: pos.desiredY
146
+ };
147
+ });
148
+
149
+ // Calculate Y positions with collision resolution for valid positions only
150
+ const yPositions = calculateLabelYPositions(dimensions, drawingArea, maxLabelHeight, labelMinGap);
151
+
152
+ // Return all positions (including null ones)
153
+ return desiredPositions.map(pos => {
154
+ var _yPositions$get;
155
+ if (!pos) return null;
156
+ return {
157
+ seriesId: pos.seriesId,
158
+ x: pos.x,
159
+ y: (_yPositions$get = yPositions.get(pos.seriesId)) != null ? _yPositions$get : pos.desiredY
160
+ };
161
+ });
162
+ }, [seriesInfo, dataIndex, dataX, xScale, labelDimensions, labelMinGap]);
163
+ const currentPosition = useDerivedValue(() => {
164
+ const pixelX = dataX.value !== undefined && xScale ? applySerializableScale(dataX.value, xScale) : 0;
165
+ const maxWidth = Math.max(...Object.values(labelDimensions).map(dim => dim.width));
166
+ const position = getLabelPosition(pixelX, maxWidth, drawingArea, labelHorizontalOffset);
167
+ return position;
168
+ }, [dataX, xScale, labelDimensions, drawingArea, labelHorizontalOffset]);
169
+ return seriesInfo.map((info, index) => {
170
+ const labelInfo = labels.find(label => label.seriesId === info.seriesId);
171
+ if (!labelInfo) return;
172
+ return /*#__PURE__*/_jsx(PositionedLabel, {
173
+ BeaconLabelComponent: BeaconLabelComponent,
174
+ color: labelInfo.color,
175
+ index: index,
176
+ label: labelInfo.label,
177
+ labelFont: labelFont,
178
+ labelHorizontalOffset: labelHorizontalOffset,
179
+ onDimensionsChange: handleDimensionsChange,
180
+ position: currentPosition,
181
+ positions: allLabelPositions,
182
+ seriesId: info.seriesId
183
+ }, info.seriesId);
184
+ });
185
+ });