@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.
- package/CHANGELOG.md +26 -0
- package/README.md +3 -0
- package/dts/chart/CartesianChart.d.ts +101 -0
- package/dts/chart/CartesianChart.d.ts.map +1 -0
- package/dts/chart/ChartProvider.d.ts +6 -0
- package/dts/chart/ChartProvider.d.ts.map +1 -0
- package/dts/chart/Path.d.ts +48 -0
- package/dts/chart/Path.d.ts.map +1 -0
- package/dts/chart/PeriodSelector.d.ts +85 -0
- package/dts/chart/PeriodSelector.d.ts.map +1 -0
- package/dts/chart/Point.d.ts +103 -0
- package/dts/chart/Point.d.ts.map +1 -0
- package/dts/chart/area/Area.d.ts +62 -0
- package/dts/chart/area/Area.d.ts.map +1 -0
- package/dts/chart/area/AreaChart.d.ts +90 -0
- package/dts/chart/area/AreaChart.d.ts.map +1 -0
- package/dts/chart/area/DottedArea.d.ts +27 -0
- package/dts/chart/area/DottedArea.d.ts.map +1 -0
- package/dts/chart/area/GradientArea.d.ts +30 -0
- package/dts/chart/area/GradientArea.d.ts.map +1 -0
- package/dts/chart/area/SolidArea.d.ts +8 -0
- package/dts/chart/area/SolidArea.d.ts.map +1 -0
- package/dts/chart/area/index.d.ts +6 -0
- package/dts/chart/area/index.d.ts.map +1 -0
- package/dts/chart/axis/Axis.d.ts +204 -0
- package/dts/chart/axis/Axis.d.ts.map +1 -0
- package/dts/chart/axis/XAxis.d.ts +16 -0
- package/dts/chart/axis/XAxis.d.ts.map +1 -0
- package/dts/chart/axis/YAxis.d.ts +21 -0
- package/dts/chart/axis/YAxis.d.ts.map +1 -0
- package/dts/chart/axis/index.d.ts +4 -0
- package/dts/chart/axis/index.d.ts.map +1 -0
- package/dts/chart/bar/Bar.d.ts +89 -0
- package/dts/chart/bar/Bar.d.ts.map +1 -0
- package/dts/chart/bar/BarChart.d.ts +97 -0
- package/dts/chart/bar/BarChart.d.ts.map +1 -0
- package/dts/chart/bar/BarPlot.d.ts +29 -0
- package/dts/chart/bar/BarPlot.d.ts.map +1 -0
- package/dts/chart/bar/BarStack.d.ts +111 -0
- package/dts/chart/bar/BarStack.d.ts.map +1 -0
- package/dts/chart/bar/BarStackGroup.d.ts +35 -0
- package/dts/chart/bar/BarStackGroup.d.ts.map +1 -0
- package/dts/chart/bar/DefaultBar.d.ts +7 -0
- package/dts/chart/bar/DefaultBar.d.ts.map +1 -0
- package/dts/chart/bar/DefaultBarStack.d.ts +7 -0
- package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -0
- package/dts/chart/bar/index.d.ts +8 -0
- package/dts/chart/bar/index.d.ts.map +1 -0
- package/dts/chart/index.d.ts +13 -0
- package/dts/chart/index.d.ts.map +1 -0
- package/dts/chart/line/DottedLine.d.ts +12 -0
- package/dts/chart/line/DottedLine.d.ts.map +1 -0
- package/dts/chart/line/GradientLine.d.ts +45 -0
- package/dts/chart/line/GradientLine.d.ts.map +1 -0
- package/dts/chart/line/Line.d.ts +78 -0
- package/dts/chart/line/Line.d.ts.map +1 -0
- package/dts/chart/line/LineChart.d.ts +84 -0
- package/dts/chart/line/LineChart.d.ts.map +1 -0
- package/dts/chart/line/ReferenceLine.d.ts +91 -0
- package/dts/chart/line/ReferenceLine.d.ts.map +1 -0
- package/dts/chart/line/SolidLine.d.ts +12 -0
- package/dts/chart/line/SolidLine.d.ts.map +1 -0
- package/dts/chart/line/index.d.ts +7 -0
- package/dts/chart/line/index.d.ts.map +1 -0
- package/dts/chart/scrubber/Scrubber.d.ts +104 -0
- package/dts/chart/scrubber/Scrubber.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberBeacon.d.ts +75 -0
- package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberProvider.d.ts +17 -0
- package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -0
- package/dts/chart/scrubber/index.d.ts +2 -0
- package/dts/chart/scrubber/index.d.ts.map +1 -0
- package/dts/chart/text/ChartText.d.ts +90 -0
- package/dts/chart/text/ChartText.d.ts.map +1 -0
- package/dts/chart/text/SmartChartTextGroup.d.ts +55 -0
- package/dts/chart/text/SmartChartTextGroup.d.ts.map +1 -0
- package/dts/chart/text/index.d.ts +3 -0
- package/dts/chart/text/index.d.ts.map +1 -0
- package/dts/chart/utils/axis.d.ts +342 -0
- package/dts/chart/utils/axis.d.ts.map +1 -0
- package/dts/chart/utils/bar.d.ts +20 -0
- package/dts/chart/utils/bar.d.ts.map +1 -0
- package/dts/chart/utils/chart.d.ts +97 -0
- package/dts/chart/utils/chart.d.ts.map +1 -0
- package/dts/chart/utils/context.d.ts +95 -0
- package/dts/chart/utils/context.d.ts.map +1 -0
- package/dts/chart/utils/index.d.ts +8 -0
- package/dts/chart/utils/index.d.ts.map +1 -0
- package/dts/chart/utils/path.d.ts +107 -0
- package/dts/chart/utils/path.d.ts.map +1 -0
- package/dts/chart/utils/point.d.ts +75 -0
- package/dts/chart/utils/point.d.ts.map +1 -0
- package/dts/chart/utils/scale.d.ts +43 -0
- package/dts/chart/utils/scale.d.ts.map +1 -0
- package/dts/index.d.ts +3 -0
- package/dts/index.d.ts.map +1 -0
- package/dts/sparkline/Counter.d.ts +8 -0
- package/dts/sparkline/Counter.d.ts.map +1 -0
- package/dts/sparkline/Sparkline.d.ts +73 -0
- package/dts/sparkline/Sparkline.d.ts.map +1 -0
- package/dts/sparkline/SparklineArea.d.ts +14 -0
- package/dts/sparkline/SparklineArea.d.ts.map +1 -0
- package/dts/sparkline/SparklineAreaPattern.d.ts +14 -0
- package/dts/sparkline/SparklineAreaPattern.d.ts.map +1 -0
- package/dts/sparkline/SparklineGradient.d.ts +23 -0
- package/dts/sparkline/SparklineGradient.d.ts.map +1 -0
- package/dts/sparkline/__figma__/Sparkline.figma.d.ts +2 -0
- package/dts/sparkline/__figma__/Sparkline.figma.d.ts.map +1 -0
- package/dts/sparkline/generateSparklineWithId.d.ts +11 -0
- package/dts/sparkline/generateSparklineWithId.d.ts.map +1 -0
- package/dts/sparkline/index.d.ts +6 -0
- package/dts/sparkline/index.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineAccessibleView.d.ts +23 -0
- package/dts/sparkline/sparkline-interactive/SparklineAccessibleView.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts +184 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveAnimatedPath.d.ts +25 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveAnimatedPath.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveHoverDate.d.ts +28 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveHoverDate.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveLineVertical.d.ts +13 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveLineVertical.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveMarkerDates.d.ts +17 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveMarkerDates.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveMinMax.d.ts +11 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveMinMax.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractivePanGestureHandler.d.ts +26 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractivePanGestureHandler.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractivePaths.d.ts +25 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractivePaths.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractivePeriodSelector.d.ts +25 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractivePeriodSelector.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveProvider.d.ts +39 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveProvider.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveTimeseriesPaths.d.ts +31 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveTimeseriesPaths.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/__figma__/SparklineInteractive.figma.d.ts +2 -0
- package/dts/sparkline/sparkline-interactive/__figma__/SparklineInteractive.figma.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/useInterruptiblePathAnimation.d.ts +13 -0
- package/dts/sparkline/sparkline-interactive/useInterruptiblePathAnimation.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/useMinMaxTransform.d.ts +16 -0
- package/dts/sparkline/sparkline-interactive/useMinMaxTransform.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/useOpacityAnimation.d.ts +6 -0
- package/dts/sparkline/sparkline-interactive/useOpacityAnimation.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/useSparklineInteractiveConstants.d.ts +22 -0
- package/dts/sparkline/sparkline-interactive/useSparklineInteractiveConstants.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/useSparklineInteractiveLineStyles.d.ts +34 -0
- package/dts/sparkline/sparkline-interactive/useSparklineInteractiveLineStyles.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive-header/SparklineInteractiveHeader.d.ts +118 -0
- package/dts/sparkline/sparkline-interactive-header/SparklineInteractiveHeader.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive-header/__figma__/SparklineInteractiveHeader.figma.d.ts +2 -0
- package/dts/sparkline/sparkline-interactive-header/__figma__/SparklineInteractiveHeader.figma.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive-header/useSparklineInteractiveHeaderStyles.d.ts +29 -0
- package/dts/sparkline/sparkline-interactive-header/useSparklineInteractiveHeaderStyles.d.ts.map +1 -0
- package/esm/chart/CartesianChart.js +241 -0
- package/esm/chart/ChartProvider.js +10 -0
- package/esm/chart/Path.js +133 -0
- package/esm/chart/PeriodSelector.js +136 -0
- package/esm/chart/Point.js +111 -0
- package/esm/chart/__stories__/CartesianChart.stories.js +476 -0
- package/esm/chart/__stories__/Chart.stories.js +79 -0
- package/esm/chart/__stories__/PeriodSelector.stories.js +294 -0
- package/esm/chart/area/Area.js +85 -0
- package/esm/chart/area/AreaChart.js +146 -0
- package/esm/chart/area/DottedArea.js +128 -0
- package/esm/chart/area/GradientArea.js +110 -0
- package/esm/chart/area/SolidArea.js +24 -0
- package/esm/chart/area/__stories__/AreaChart.stories.js +100 -0
- package/esm/chart/area/index.js +7 -0
- package/esm/chart/axis/Axis.js +43 -0
- package/esm/chart/axis/XAxis.js +181 -0
- package/esm/chart/axis/YAxis.js +170 -0
- package/esm/chart/axis/__stories__/Axis.stories.js +277 -0
- package/esm/chart/axis/index.js +5 -0
- package/esm/chart/bar/Bar.js +67 -0
- package/esm/chart/bar/BarChart.js +147 -0
- package/esm/chart/bar/BarPlot.js +96 -0
- package/esm/chart/bar/BarStack.js +514 -0
- package/esm/chart/bar/BarStackGroup.js +89 -0
- package/esm/chart/bar/DefaultBar.js +78 -0
- package/esm/chart/bar/DefaultBarStack.js +82 -0
- package/esm/chart/bar/__stories__/BarChart.stories.js +282 -0
- package/esm/chart/bar/index.js +9 -0
- package/esm/chart/index.js +14 -0
- package/esm/chart/line/DottedLine.js +35 -0
- package/esm/chart/line/GradientLine.js +62 -0
- package/esm/chart/line/Line.js +139 -0
- package/esm/chart/line/LineChart.js +115 -0
- package/esm/chart/line/ReferenceLine.js +115 -0
- package/esm/chart/line/SolidLine.js +31 -0
- package/esm/chart/line/__stories__/LineChart.stories.js +2248 -0
- package/esm/chart/line/__stories__/ReferenceLine.stories.js +77 -0
- package/esm/chart/line/index.js +8 -0
- package/esm/chart/scrubber/Scrubber.js +186 -0
- package/esm/chart/scrubber/ScrubberBeacon.js +199 -0
- package/esm/chart/scrubber/ScrubberProvider.js +143 -0
- package/esm/chart/scrubber/index.js +2 -0
- package/esm/chart/text/ChartText.js +237 -0
- package/esm/chart/text/SmartChartTextGroup.js +210 -0
- package/esm/chart/text/index.js +4 -0
- package/esm/chart/utils/axis.js +592 -0
- package/esm/chart/utils/bar.js +24 -0
- package/esm/chart/utils/chart.js +229 -0
- package/esm/chart/utils/context.js +15 -0
- package/esm/chart/utils/index.js +9 -0
- package/esm/chart/utils/path.js +206 -0
- package/esm/chart/utils/point.js +118 -0
- package/esm/chart/utils/scale.js +48 -0
- package/esm/index.js +4 -0
- package/esm/sparkline/Counter.js +45 -0
- package/esm/sparkline/Sparkline.js +164 -0
- package/esm/sparkline/SparklineArea.js +19 -0
- package/esm/sparkline/SparklineAreaPattern.js +38 -0
- package/esm/sparkline/SparklineGradient.js +76 -0
- package/esm/sparkline/__figma__/Sparkline.figma.js +22 -0
- package/esm/sparkline/__stories__/Sparkline.stories.js +120 -0
- package/esm/sparkline/__stories__/SparklineGradient.stories.js +123 -0
- package/esm/sparkline/generateSparklineWithId.js +7 -0
- package/esm/sparkline/index.js +5 -0
- package/esm/sparkline/sparkline-interactive/SparklineAccessibleView.js +75 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractive.js +307 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractiveAnimatedPath.js +116 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractiveHoverDate.js +131 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractiveLineVertical.js +99 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractiveMarkerDates.js +82 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractiveMinMax.js +103 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractivePanGestureHandler.js +104 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractivePaths.js +57 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractivePeriodSelector.js +124 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractiveProvider.js +80 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractiveTimeseriesPaths.js +109 -0
- package/esm/sparkline/sparkline-interactive/__figma__/SparklineInteractive.figma.js +85 -0
- package/esm/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories.js +501 -0
- package/esm/sparkline/sparkline-interactive/useInterruptiblePathAnimation.js +58 -0
- package/esm/sparkline/sparkline-interactive/useInterruptiblePathAnimation.test.disable.js +37 -0
- package/esm/sparkline/sparkline-interactive/useMinMaxTransform.js +56 -0
- package/esm/sparkline/sparkline-interactive/useOpacityAnimation.js +23 -0
- package/esm/sparkline/sparkline-interactive/useSparklineInteractiveConstants.js +47 -0
- package/esm/sparkline/sparkline-interactive/useSparklineInteractiveLineStyles.js +34 -0
- package/esm/sparkline/sparkline-interactive-header/SparklineInteractiveHeader.js +233 -0
- package/esm/sparkline/sparkline-interactive-header/__figma__/SparklineInteractiveHeader.figma.js +104 -0
- package/esm/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories.js +555 -0
- package/esm/sparkline/sparkline-interactive-header/useSparklineInteractiveHeaderStyles.js +117 -0
- package/package.json +65 -6
- 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
|
+
};
|