@coinbase/cds-mobile-visualization 3.4.0-beta.17 → 3.4.0-beta.19
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 +12 -0
- package/dts/chart/Path.d.ts +35 -13
- package/dts/chart/Path.d.ts.map +1 -1
- package/dts/chart/area/Area.d.ts +7 -11
- package/dts/chart/area/Area.d.ts.map +1 -1
- package/dts/chart/area/AreaChart.d.ts +1 -1
- package/dts/chart/area/DottedArea.d.ts.map +1 -1
- package/dts/chart/area/GradientArea.d.ts.map +1 -1
- package/dts/chart/area/SolidArea.d.ts.map +1 -1
- package/dts/chart/bar/Bar.d.ts +32 -2
- package/dts/chart/bar/Bar.d.ts.map +1 -1
- package/dts/chart/bar/BarChart.d.ts +2 -0
- package/dts/chart/bar/BarChart.d.ts.map +1 -1
- package/dts/chart/bar/BarPlot.d.ts +2 -1
- package/dts/chart/bar/BarPlot.d.ts.map +1 -1
- package/dts/chart/bar/BarStack.d.ts +5 -10
- package/dts/chart/bar/BarStack.d.ts.map +1 -1
- package/dts/chart/bar/BarStackGroup.d.ts +1 -0
- package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
- package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
- package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
- package/dts/chart/line/DottedLine.d.ts.map +1 -1
- package/dts/chart/line/Line.d.ts +4 -9
- package/dts/chart/line/Line.d.ts.map +1 -1
- package/dts/chart/line/LineChart.d.ts +1 -1
- package/dts/chart/line/SolidLine.d.ts.map +1 -1
- package/dts/chart/point/Point.d.ts +18 -2
- package/dts/chart/point/Point.d.ts.map +1 -1
- package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +9 -1
- package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -1
- package/dts/chart/scrubber/Scrubber.d.ts +45 -24
- package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +9 -1
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -1
- package/dts/chart/utils/bar.d.ts +34 -0
- package/dts/chart/utils/bar.d.ts.map +1 -1
- package/dts/chart/utils/path.d.ts +6 -0
- package/dts/chart/utils/path.d.ts.map +1 -1
- package/dts/chart/utils/transition.d.ts +59 -21
- package/dts/chart/utils/transition.d.ts.map +1 -1
- package/esm/chart/Path.js +18 -17
- package/esm/chart/__stories__/CartesianChart.stories.js +3 -77
- package/esm/chart/__stories__/ChartTransitions.stories.js +629 -0
- package/esm/chart/area/Area.js +2 -0
- package/esm/chart/area/DottedArea.js +7 -3
- package/esm/chart/area/GradientArea.js +4 -2
- package/esm/chart/area/SolidArea.js +4 -2
- package/esm/chart/bar/Bar.js +2 -0
- package/esm/chart/bar/BarChart.js +4 -2
- package/esm/chart/bar/BarPlot.js +2 -0
- package/esm/chart/bar/BarStack.js +3 -0
- package/esm/chart/bar/DefaultBar.js +15 -15
- package/esm/chart/bar/DefaultBarStack.js +14 -3
- package/esm/chart/bar/__stories__/BarChart.stories.js +448 -52
- package/esm/chart/line/DottedLine.js +4 -2
- package/esm/chart/line/Line.js +6 -17
- package/esm/chart/line/SolidLine.js +4 -2
- package/esm/chart/line/__stories__/LineChart.stories.js +130 -235
- package/esm/chart/line/__stories__/ReferenceLine.stories.js +95 -1
- package/esm/chart/point/Point.js +33 -35
- package/esm/chart/scrubber/DefaultScrubberBeacon.js +2 -5
- package/esm/chart/scrubber/Scrubber.js +15 -14
- package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +29 -7
- package/esm/chart/utils/bar.js +43 -0
- package/esm/chart/utils/path.js +8 -0
- package/esm/chart/utils/transition.js +96 -61
- package/package.json +5 -5
- package/esm/chart/__stories__/Chart.stories.js +0 -77
package/esm/chart/point/Point.js
CHANGED
|
@@ -5,7 +5,7 @@ import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme';
|
|
|
5
5
|
import { Circle, Group, interpolateColors } from '@shopify/react-native-skia';
|
|
6
6
|
import { useCartesianChartContext } from '../ChartProvider';
|
|
7
7
|
import { projectPoint } from '../utils';
|
|
8
|
-
import { buildTransition, defaultTransition } from '../utils/transition';
|
|
8
|
+
import { buildTransition, defaultAccessoryEnterTransition, defaultTransition, getTransition } from '../utils/transition';
|
|
9
9
|
import { DefaultPointLabel } from './DefaultPointLabel';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -27,7 +27,8 @@ export const Point = /*#__PURE__*/memo(_ref => {
|
|
|
27
27
|
labelPosition = 'center',
|
|
28
28
|
labelOffset,
|
|
29
29
|
labelFont,
|
|
30
|
-
|
|
30
|
+
transitions,
|
|
31
|
+
transition,
|
|
31
32
|
animate: animateProp
|
|
32
33
|
} = _ref;
|
|
33
34
|
const theme = useTheme();
|
|
@@ -43,6 +44,8 @@ export const Point = /*#__PURE__*/memo(_ref => {
|
|
|
43
44
|
const xScale = getXScale();
|
|
44
45
|
const yScale = getYScale(yAxisId);
|
|
45
46
|
const shouldAnimate = animate != null ? animate : false;
|
|
47
|
+
const updateTransition = useMemo(() => getTransition((transitions == null ? void 0 : transitions.update) !== undefined ? transitions.update : transition, animate, defaultTransition), [animate, transitions == null ? void 0 : transitions.update, transition]);
|
|
48
|
+
const enterTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.enter, animate, defaultAccessoryEnterTransition), [animate, transitions == null ? void 0 : transitions.enter]);
|
|
46
49
|
|
|
47
50
|
// Calculate pixel coordinates from data coordinates
|
|
48
51
|
const pixelCoordinate = useMemo(() => {
|
|
@@ -62,9 +65,13 @@ export const Point = /*#__PURE__*/memo(_ref => {
|
|
|
62
65
|
// Animated values for position
|
|
63
66
|
const animatedX = useSharedValue(0);
|
|
64
67
|
const animatedY = useSharedValue(0);
|
|
65
|
-
|
|
66
|
-
// Animated value for color interpolation (0 = old color, 1 = new color)
|
|
68
|
+
const enterOpacity = useSharedValue(shouldAnimate ? 0 : 1);
|
|
67
69
|
const colorProgress = useSharedValue(1);
|
|
70
|
+
const isReady = !!xScale && !!yScale;
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!shouldAnimate || !isReady) return;
|
|
73
|
+
enterOpacity.value = buildTransition(1, enterTransition);
|
|
74
|
+
}, [shouldAnimate, isReady, enterTransition, enterOpacity]);
|
|
68
75
|
|
|
69
76
|
// Update position when coordinates change
|
|
70
77
|
useEffect(() => {
|
|
@@ -72,26 +79,26 @@ export const Point = /*#__PURE__*/memo(_ref => {
|
|
|
72
79
|
return;
|
|
73
80
|
}
|
|
74
81
|
if (shouldAnimate && previousPixelCoordinate) {
|
|
75
|
-
animatedX.value = buildTransition(pixelCoordinate.x,
|
|
76
|
-
animatedY.value = buildTransition(pixelCoordinate.y,
|
|
82
|
+
animatedX.value = buildTransition(pixelCoordinate.x, updateTransition);
|
|
83
|
+
animatedY.value = buildTransition(pixelCoordinate.y, updateTransition);
|
|
77
84
|
} else {
|
|
78
85
|
cancelAnimation(animatedX);
|
|
79
86
|
cancelAnimation(animatedY);
|
|
80
87
|
animatedX.value = pixelCoordinate.x;
|
|
81
88
|
animatedY.value = pixelCoordinate.y;
|
|
82
89
|
}
|
|
83
|
-
}, [pixelCoordinate, shouldAnimate, previousPixelCoordinate, animatedX, animatedY,
|
|
90
|
+
}, [pixelCoordinate, shouldAnimate, previousPixelCoordinate, animatedX, animatedY, updateTransition]);
|
|
84
91
|
|
|
85
92
|
// Update color when fill changes
|
|
86
93
|
useEffect(() => {
|
|
87
94
|
if (shouldAnimate && previousFill && previousFill !== fill) {
|
|
88
95
|
colorProgress.value = 0;
|
|
89
|
-
colorProgress.value = buildTransition(1,
|
|
96
|
+
colorProgress.value = buildTransition(1, updateTransition);
|
|
90
97
|
} else {
|
|
91
98
|
cancelAnimation(colorProgress);
|
|
92
99
|
colorProgress.value = 1;
|
|
93
100
|
}
|
|
94
|
-
}, [fill, shouldAnimate, previousFill, colorProgress,
|
|
101
|
+
}, [fill, shouldAnimate, previousFill, colorProgress, updateTransition]);
|
|
95
102
|
|
|
96
103
|
// Create animated point for circles
|
|
97
104
|
const animatedPoint = useDerivedValue(() => {
|
|
@@ -108,24 +115,19 @@ export const Point = /*#__PURE__*/memo(_ref => {
|
|
|
108
115
|
}
|
|
109
116
|
return interpolateColors(colorProgress.value, [0, 1], [previousFill, fill]);
|
|
110
117
|
}, [colorProgress, previousFill, fill]);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}, [animatedX, animatedY, drawingArea]);
|
|
116
|
-
|
|
117
|
-
// Compute effective opacity based on drawing area bounds
|
|
118
|
+
const isWithinDrawingArea = useMemo(() => {
|
|
119
|
+
if (!pixelCoordinate) return false;
|
|
120
|
+
return pixelCoordinate.x >= drawingArea.x && pixelCoordinate.x <= drawingArea.x + drawingArea.width && pixelCoordinate.y >= drawingArea.y && pixelCoordinate.y <= drawingArea.y + drawingArea.height;
|
|
121
|
+
}, [pixelCoordinate, drawingArea]);
|
|
118
122
|
const effectiveOpacity = useDerivedValue(() => {
|
|
119
123
|
const baseOpacity = opacity != null ? opacity : 1;
|
|
120
|
-
return isWithinDrawingArea
|
|
121
|
-
}, [isWithinDrawingArea, opacity]);
|
|
124
|
+
return isWithinDrawingArea ? baseOpacity * enterOpacity.value : 0;
|
|
125
|
+
}, [isWithinDrawingArea, opacity, enterOpacity]);
|
|
122
126
|
const offset = useMemo(() => labelOffset != null ? labelOffset : radius * 2, [labelOffset, radius]);
|
|
123
127
|
if (!pixelCoordinate) {
|
|
124
128
|
return null;
|
|
125
129
|
}
|
|
126
|
-
|
|
127
|
-
// If animation is disabled or on first render, use static rendering
|
|
128
|
-
if (!shouldAnimate || !previousPixelCoordinate) {
|
|
130
|
+
if (!shouldAnimate) {
|
|
129
131
|
const isWithinBounds = pixelCoordinate.x >= drawingArea.x && pixelCoordinate.x <= drawingArea.x + drawingArea.width && pixelCoordinate.y >= drawingArea.y && pixelCoordinate.y <= drawingArea.y + drawingArea.height;
|
|
130
132
|
const staticOpacity = isWithinBounds ? opacity != null ? opacity : 1 : 0;
|
|
131
133
|
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
@@ -159,20 +161,16 @@ export const Point = /*#__PURE__*/memo(_ref => {
|
|
|
159
161
|
})]
|
|
160
162
|
});
|
|
161
163
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
c: animatedPoint,
|
|
173
|
-
color: animatedFillColor,
|
|
174
|
-
r: radius - strokeWidth / 2
|
|
175
|
-
})]
|
|
164
|
+
return /*#__PURE__*/_jsxs(Group, {
|
|
165
|
+
opacity: effectiveOpacity,
|
|
166
|
+
children: [strokeWidth > 0 && /*#__PURE__*/_jsx(Circle, {
|
|
167
|
+
c: animatedPoint,
|
|
168
|
+
color: stroke,
|
|
169
|
+
r: radius + strokeWidth / 2
|
|
170
|
+
}), /*#__PURE__*/_jsx(Circle, {
|
|
171
|
+
c: animatedPoint,
|
|
172
|
+
color: animatedFillColor,
|
|
173
|
+
r: radius - strokeWidth / 2
|
|
176
174
|
}), label && /*#__PURE__*/_jsx(LabelComponent, {
|
|
177
175
|
dataX: dataX,
|
|
178
176
|
dataY: dataY,
|
|
@@ -5,7 +5,7 @@ import { Circle, Group } from '@shopify/react-native-skia';
|
|
|
5
5
|
import { useCartesianChartContext } from '../ChartProvider';
|
|
6
6
|
import { unwrapAnimatedValue } from '../utils';
|
|
7
7
|
import { projectPointWithSerializableScale } from '../utils/point';
|
|
8
|
-
import { buildTransition, defaultTransition } from '../utils/transition';
|
|
8
|
+
import { buildTransition, defaultTransition, getTransition } from '../utils/transition';
|
|
9
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
10
|
const defaultRadius = 5;
|
|
11
11
|
const defaultStrokeWidth = 2;
|
|
@@ -48,10 +48,7 @@ export const DefaultScrubberBeacon = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((
|
|
|
48
48
|
var _ref2;
|
|
49
49
|
return (_ref2 = colorProp != null ? colorProp : targetSeries == null ? void 0 : targetSeries.color) != null ? _ref2 : theme.color.fgPrimary;
|
|
50
50
|
}, [colorProp, targetSeries == null ? void 0 : targetSeries.color, theme.color.fgPrimary]);
|
|
51
|
-
const updateTransition = useMemo(() =>
|
|
52
|
-
var _transitions$update;
|
|
53
|
-
return (_transitions$update = transitions == null ? void 0 : transitions.update) != null ? _transitions$update : defaultTransition;
|
|
54
|
-
}, [transitions == null ? void 0 : transitions.update]);
|
|
51
|
+
const updateTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.update, animate, defaultTransition), [transitions == null ? void 0 : transitions.update, animate]);
|
|
55
52
|
const pulseTransition = useMemo(() => {
|
|
56
53
|
var _transitions$pulse;
|
|
57
54
|
return (_transitions$pulse = transitions == null ? void 0 : transitions.pulse) != null ? _transitions$pulse : defaultPulseTransition;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react';
|
|
2
|
-
import { runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue
|
|
2
|
+
import { runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated';
|
|
3
3
|
import { useTheme } from '@coinbase/cds-mobile';
|
|
4
4
|
import { Group, Rect } from '@shopify/react-native-skia';
|
|
5
5
|
import { useCartesianChartContext } from '../ChartProvider';
|
|
6
6
|
import { ReferenceLine } from '../line';
|
|
7
|
-
import {
|
|
7
|
+
import { defaultAccessoryEnterTransition, getPointOnSerializableScale, getTransition, useScrubberContext } from '../utils';
|
|
8
|
+
import { buildTransition } from '../utils/transition';
|
|
8
9
|
import { DefaultScrubberBeacon } from './DefaultScrubberBeacon';
|
|
9
10
|
import { DefaultScrubberLabel } from './DefaultScrubberLabel';
|
|
10
11
|
import { ScrubberBeaconGroup } from './ScrubberBeaconGroup';
|
|
@@ -35,6 +36,7 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
|
|
|
35
36
|
beaconLabelFont,
|
|
36
37
|
idlePulse,
|
|
37
38
|
beaconTransitions,
|
|
39
|
+
transitions = beaconTransitions,
|
|
38
40
|
beaconStroke
|
|
39
41
|
} = _ref;
|
|
40
42
|
const theme = useTheme();
|
|
@@ -56,15 +58,6 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
|
|
|
56
58
|
// Animation state for delayed scrubber rendering (matches web timing)
|
|
57
59
|
const scrubberOpacity = useSharedValue(animate ? 0 : 1);
|
|
58
60
|
|
|
59
|
-
// Delay scrubber appearance until after path enter animation completes
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (animate) {
|
|
62
|
-
scrubberOpacity.value = withDelay(accessoryFadeTransitionDelay, withTiming(1, {
|
|
63
|
-
duration: accessoryFadeTransitionDuration
|
|
64
|
-
}));
|
|
65
|
-
}
|
|
66
|
-
}, [animate, scrubberOpacity]);
|
|
67
|
-
|
|
68
61
|
// Expose imperative handle with pulse method
|
|
69
62
|
useImperativeHandle(ref, () => ({
|
|
70
63
|
pulse: () => {
|
|
@@ -132,7 +125,14 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
|
|
|
132
125
|
color: s.color
|
|
133
126
|
}))) != null ? _series$filter$filter : [];
|
|
134
127
|
}, [series, filteredSeriesIds]);
|
|
135
|
-
|
|
128
|
+
const isReady = !!xScale;
|
|
129
|
+
const groupEnterTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.enter, animate, defaultAccessoryEnterTransition), [transitions == null ? void 0 : transitions.enter, animate]);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (animate && isReady) {
|
|
132
|
+
scrubberOpacity.value = buildTransition(1, groupEnterTransition);
|
|
133
|
+
}
|
|
134
|
+
}, [animate, isReady, scrubberOpacity, groupEnterTransition]);
|
|
135
|
+
if (!isReady) return;
|
|
136
136
|
return /*#__PURE__*/_jsxs(Group, {
|
|
137
137
|
opacity: scrubberOpacity,
|
|
138
138
|
children: [!hideOverlay && /*#__PURE__*/_jsx(Rect, {
|
|
@@ -158,14 +158,15 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
|
|
|
158
158
|
idlePulse: idlePulse,
|
|
159
159
|
seriesIds: filteredSeriesIds,
|
|
160
160
|
stroke: beaconStroke,
|
|
161
|
-
transitions:
|
|
161
|
+
transitions: transitions
|
|
162
162
|
}), !hideBeaconLabels && beaconLabels.length > 0 && /*#__PURE__*/_jsx(ScrubberBeaconLabelGroup, {
|
|
163
163
|
BeaconLabelComponent: BeaconLabelComponent,
|
|
164
164
|
labelFont: beaconLabelFont,
|
|
165
165
|
labelHorizontalOffset: beaconLabelHorizontalOffset,
|
|
166
166
|
labelMinGap: beaconLabelMinGap,
|
|
167
167
|
labelPreferredSide: beaconLabelPreferredSide,
|
|
168
|
-
labels: beaconLabels
|
|
168
|
+
labels: beaconLabels,
|
|
169
|
+
transitions: transitions
|
|
169
170
|
})]
|
|
170
171
|
});
|
|
171
172
|
}));
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
2
2
|
import { memo, useCallback, useMemo, useState } from 'react';
|
|
3
|
-
import { useDerivedValue } from 'react-native-reanimated';
|
|
3
|
+
import { useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated';
|
|
4
4
|
import { useCartesianChartContext } from '../ChartProvider';
|
|
5
|
-
import { applySerializableScale, useScrubberContext } from '../utils';
|
|
5
|
+
import { applySerializableScale, unwrapAnimatedValue, useScrubberContext } from '../utils';
|
|
6
6
|
import { calculateLabelYPositions, getLabelPosition } from '../utils/scrubber';
|
|
7
|
+
import { buildTransition, defaultTransition, getTransition } from '../utils/transition';
|
|
7
8
|
import { DefaultScrubberBeaconLabel } from './DefaultScrubberBeaconLabel';
|
|
8
9
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
10
|
const PositionedLabel = /*#__PURE__*/memo(_ref => {
|
|
@@ -11,6 +12,8 @@ const PositionedLabel = /*#__PURE__*/memo(_ref => {
|
|
|
11
12
|
index,
|
|
12
13
|
positions,
|
|
13
14
|
position,
|
|
15
|
+
isIdle,
|
|
16
|
+
updateTransition,
|
|
14
17
|
label,
|
|
15
18
|
color,
|
|
16
19
|
seriesId,
|
|
@@ -24,10 +27,21 @@ const PositionedLabel = /*#__PURE__*/memo(_ref => {
|
|
|
24
27
|
var _positions$value$inde, _positions$value$inde2;
|
|
25
28
|
return (_positions$value$inde = (_positions$value$inde2 = positions.value[index]) == null ? void 0 : _positions$value$inde2.x) != null ? _positions$value$inde : 0;
|
|
26
29
|
}, [positions, index]);
|
|
27
|
-
const
|
|
30
|
+
const targetY = useDerivedValue(() => {
|
|
28
31
|
var _positions$value$inde3, _positions$value$inde4;
|
|
29
32
|
return (_positions$value$inde3 = (_positions$value$inde4 = positions.value[index]) == null ? void 0 : _positions$value$inde4.y) != null ? _positions$value$inde3 : 0;
|
|
30
33
|
}, [positions, index]);
|
|
34
|
+
const animatedY = useSharedValue(0);
|
|
35
|
+
useAnimatedReaction(() => ({
|
|
36
|
+
y: targetY.value,
|
|
37
|
+
idle: unwrapAnimatedValue(isIdle)
|
|
38
|
+
}), (current, previous) => {
|
|
39
|
+
if (previous === null || !previous.idle || !current.idle) {
|
|
40
|
+
animatedY.value = current.y;
|
|
41
|
+
} else {
|
|
42
|
+
animatedY.value = buildTransition(current.y, updateTransition);
|
|
43
|
+
}
|
|
44
|
+
}, [updateTransition]);
|
|
31
45
|
const dx = useDerivedValue(() => {
|
|
32
46
|
return position.value === 'right' ? labelHorizontalOffset : -labelHorizontalOffset;
|
|
33
47
|
}, [position, labelHorizontalOffset]);
|
|
@@ -42,7 +56,7 @@ const PositionedLabel = /*#__PURE__*/memo(_ref => {
|
|
|
42
56
|
opacity: opacity,
|
|
43
57
|
seriesId: seriesId,
|
|
44
58
|
x: x,
|
|
45
|
-
y:
|
|
59
|
+
y: animatedY
|
|
46
60
|
});
|
|
47
61
|
});
|
|
48
62
|
export const ScrubberBeaconLabelGroup = /*#__PURE__*/memo(_ref2 => {
|
|
@@ -52,7 +66,8 @@ export const ScrubberBeaconLabelGroup = /*#__PURE__*/memo(_ref2 => {
|
|
|
52
66
|
labelHorizontalOffset = 16,
|
|
53
67
|
labelFont,
|
|
54
68
|
labelPreferredSide = 'right',
|
|
55
|
-
BeaconLabelComponent = DefaultScrubberBeaconLabel
|
|
69
|
+
BeaconLabelComponent = DefaultScrubberBeaconLabel,
|
|
70
|
+
transitions
|
|
56
71
|
} = _ref2;
|
|
57
72
|
const {
|
|
58
73
|
getSeries,
|
|
@@ -61,11 +76,16 @@ export const ScrubberBeaconLabelGroup = /*#__PURE__*/memo(_ref2 => {
|
|
|
61
76
|
getYSerializableScale,
|
|
62
77
|
getXAxis,
|
|
63
78
|
drawingArea,
|
|
64
|
-
dataLength
|
|
79
|
+
dataLength,
|
|
80
|
+
animate
|
|
65
81
|
} = useCartesianChartContext();
|
|
66
82
|
const {
|
|
67
83
|
scrubberPosition
|
|
68
84
|
} = useScrubberContext();
|
|
85
|
+
const isIdle = useDerivedValue(() => {
|
|
86
|
+
return scrubberPosition.value === undefined;
|
|
87
|
+
}, [scrubberPosition]);
|
|
88
|
+
const updateTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.update, animate, defaultTransition), [transitions == null ? void 0 : transitions.update, animate]);
|
|
69
89
|
const [labelDimensions, setLabelDimensions] = useState({});
|
|
70
90
|
const handleDimensionsChange = useCallback((id, dimensions) => {
|
|
71
91
|
setLabelDimensions(prev => {
|
|
@@ -174,13 +194,15 @@ export const ScrubberBeaconLabelGroup = /*#__PURE__*/memo(_ref2 => {
|
|
|
174
194
|
BeaconLabelComponent: BeaconLabelComponent,
|
|
175
195
|
color: labelInfo.color,
|
|
176
196
|
index: index,
|
|
197
|
+
isIdle: isIdle,
|
|
177
198
|
label: labelInfo.label,
|
|
178
199
|
labelFont: labelFont,
|
|
179
200
|
labelHorizontalOffset: labelHorizontalOffset,
|
|
180
201
|
onDimensionsChange: handleDimensionsChange,
|
|
181
202
|
position: currentPosition,
|
|
182
203
|
positions: allLabelPositions,
|
|
183
|
-
seriesId: info.seriesId
|
|
204
|
+
seriesId: info.seriesId,
|
|
205
|
+
updateTransition: updateTransition
|
|
184
206
|
}, info.seriesId);
|
|
185
207
|
});
|
|
186
208
|
});
|
package/esm/chart/utils/bar.js
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
const _excluded = ["staggerDelay"];
|
|
2
|
+
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); }
|
|
3
|
+
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
|
|
4
|
+
import { defaultTransition } from './transition';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A bar-specific transition that extends Transition with stagger support.
|
|
8
|
+
* When `staggerDelay` is provided, bars will animate with increasing delays
|
|
9
|
+
* based on their horizontal position (leftmost starts first, rightmost last).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // Bars stagger in from left to right over 250ms, each animating for 750ms
|
|
13
|
+
* { type: 'timing', duration: 750, staggerDelay: 250 }
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Strips `staggerDelay` from a transition and computes a positional delay.
|
|
18
|
+
*
|
|
19
|
+
* @param transition - The transition config (may include staggerDelay)
|
|
20
|
+
* @param normalizedX - The bar's normalized x position (0 = left edge, 1 = right edge)
|
|
21
|
+
* @returns A standard Transition with computed delay
|
|
22
|
+
*/
|
|
23
|
+
export const withStaggerDelayTransition = (transition, normalizedX) => {
|
|
24
|
+
var _baseTransition$delay;
|
|
25
|
+
const {
|
|
26
|
+
staggerDelay
|
|
27
|
+
} = transition,
|
|
28
|
+
baseTransition = _objectWithoutPropertiesLoose(transition, _excluded);
|
|
29
|
+
if (!staggerDelay) return transition;
|
|
30
|
+
return _extends({}, baseTransition, {
|
|
31
|
+
delay: ((_baseTransition$delay = baseTransition == null ? void 0 : baseTransition.delay) != null ? _baseTransition$delay : 0) + normalizedX * staggerDelay
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Default bar enter transition. Uses the default spring with a stagger delay
|
|
37
|
+
* so bars spring into place from left to right.
|
|
38
|
+
* `{ type: 'spring', stiffness: 900, damping: 120, staggerDelay: 250 }`
|
|
39
|
+
*/
|
|
40
|
+
export const defaultBarEnterTransition = _extends({}, defaultTransition, {
|
|
41
|
+
staggerDelay: 250
|
|
42
|
+
});
|
|
43
|
+
|
|
1
44
|
/**
|
|
2
45
|
* Calculates the size adjustment needed for bars when accounting for gaps between them.
|
|
3
46
|
* This function helps determine how much to reduce each bar's width to accommodate
|
package/esm/chart/utils/path.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { area as d3Area, curveBumpX, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore, line as d3Line } from 'd3-shape';
|
|
2
2
|
import { projectPoint, projectPoints } from './point';
|
|
3
3
|
import { isCategoricalScale } from './scale';
|
|
4
|
+
/**
|
|
5
|
+
* Default enter transition for path-based components (Line, Area).
|
|
6
|
+
* `{ type: 'timing', duration: 500 }`
|
|
7
|
+
*/
|
|
8
|
+
export const defaultPathEnterTransition = {
|
|
9
|
+
type: 'timing',
|
|
10
|
+
duration: 500
|
|
11
|
+
};
|
|
4
12
|
/**
|
|
5
13
|
* Get the d3 curve function for a path.
|
|
6
14
|
* See https://d3js.org/d3-shape/curve
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
const _excluded = ["delay"];
|
|
2
|
+
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
|
|
1
3
|
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
-
import { useAnimatedReaction, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
|
|
4
|
+
import { useAnimatedReaction, useSharedValue, withDelay, withSpring, withTiming } from 'react-native-reanimated';
|
|
3
5
|
import { notifyChange, Skia } from '@shopify/react-native-skia';
|
|
4
6
|
import { interpolatePath } from 'd3-interpolate-path';
|
|
5
7
|
|
|
@@ -18,7 +20,8 @@ import { interpolatePath } from 'd3-interpolate-path';
|
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
|
-
* Default transition
|
|
23
|
+
* Default update transition used across all chart components.
|
|
24
|
+
* `{ type: 'spring', stiffness: 900, damping: 120 }`
|
|
22
25
|
*/
|
|
23
26
|
export const defaultTransition = {
|
|
24
27
|
type: 'spring',
|
|
@@ -26,6 +29,15 @@ export const defaultTransition = {
|
|
|
26
29
|
damping: 120
|
|
27
30
|
};
|
|
28
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Instant transition that completes immediately with no animation.
|
|
34
|
+
* Used when a transition is set to `null`.
|
|
35
|
+
*/
|
|
36
|
+
export const instantTransition = {
|
|
37
|
+
type: 'timing',
|
|
38
|
+
duration: 0
|
|
39
|
+
};
|
|
40
|
+
|
|
29
41
|
/**
|
|
30
42
|
* Duration in milliseconds for accessory elements to fade in.
|
|
31
43
|
*/
|
|
@@ -37,41 +49,23 @@ export const accessoryFadeTransitionDuration = 150;
|
|
|
37
49
|
export const accessoryFadeTransitionDelay = 350;
|
|
38
50
|
|
|
39
51
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @param progress - Shared value between 0 and 1
|
|
44
|
-
* @param fromPath - Starting path as SVG string
|
|
45
|
-
* @param toPath - Ending path as SVG string
|
|
46
|
-
* @returns Interpolated SkPath as a shared value
|
|
52
|
+
* Default enter transition for accessory elements (Point, Scrubber beacons).
|
|
53
|
+
* `{ type: 'timing', duration: 150, delay: 350 }`
|
|
47
54
|
*/
|
|
48
|
-
export const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
i1,
|
|
54
|
-
toSkiaPath
|
|
55
|
-
} = useMemo(() => {
|
|
56
|
-
var _Skia$Path$MakeFromSV, _Skia$Path$MakeFromSV2, _Skia$Path$MakeFromSV3, _Skia$Path$MakeFromSV4;
|
|
57
|
-
const pathInterpolator = interpolatePath(fromPath, toPath);
|
|
58
|
-
const d = 1e-3;
|
|
59
|
-
return {
|
|
60
|
-
fromSkiaPath: (_Skia$Path$MakeFromSV = Skia.Path.MakeFromSVGString(fromPath)) != null ? _Skia$Path$MakeFromSV : Skia.Path.Make(),
|
|
61
|
-
i0: (_Skia$Path$MakeFromSV2 = Skia.Path.MakeFromSVGString(pathInterpolator(d))) != null ? _Skia$Path$MakeFromSV2 : Skia.Path.Make(),
|
|
62
|
-
i1: (_Skia$Path$MakeFromSV3 = Skia.Path.MakeFromSVGString(pathInterpolator(1 - d))) != null ? _Skia$Path$MakeFromSV3 : Skia.Path.Make(),
|
|
63
|
-
toSkiaPath: (_Skia$Path$MakeFromSV4 = Skia.Path.MakeFromSVGString(toPath)) != null ? _Skia$Path$MakeFromSV4 : Skia.Path.Make()
|
|
64
|
-
};
|
|
65
|
-
}, [fromPath, toPath]);
|
|
66
|
-
const result = useSharedValue(fromSkiaPath);
|
|
67
|
-
useAnimatedReaction(() => progress.value, t => {
|
|
68
|
-
'worklet';
|
|
55
|
+
export const defaultAccessoryEnterTransition = {
|
|
56
|
+
type: 'timing',
|
|
57
|
+
duration: accessoryFadeTransitionDuration,
|
|
58
|
+
delay: accessoryFadeTransitionDelay
|
|
59
|
+
};
|
|
69
60
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Resolves a transition value based on the animation state and a default.
|
|
63
|
+
* @note Passing in null will disable an animation.
|
|
64
|
+
* @note Passing in undefined will use the provided default.
|
|
65
|
+
*/
|
|
66
|
+
export const getTransition = (value, animate, defaultValue) => {
|
|
67
|
+
if (!animate || value === null) return instantTransition;
|
|
68
|
+
return value != null ? value : defaultValue;
|
|
75
69
|
};
|
|
76
70
|
|
|
77
71
|
// Interpolator and useInterpolator are brought over from non exported code in @shopify/react-native-skia
|
|
@@ -114,21 +108,32 @@ export const useInterpolator = (factory, value, interpolator, input, output, opt
|
|
|
114
108
|
export const buildTransition = (targetValue, transition) => {
|
|
115
109
|
'worklet';
|
|
116
110
|
|
|
117
|
-
|
|
111
|
+
const {
|
|
112
|
+
delay: delayMs
|
|
113
|
+
} = transition,
|
|
114
|
+
config = _objectWithoutPropertiesLoose(transition, _excluded);
|
|
115
|
+
let animation;
|
|
116
|
+
switch (config.type) {
|
|
118
117
|
case 'timing':
|
|
119
118
|
{
|
|
120
|
-
|
|
119
|
+
animation = withTiming(targetValue, config);
|
|
120
|
+
break;
|
|
121
121
|
}
|
|
122
122
|
case 'spring':
|
|
123
123
|
{
|
|
124
|
-
|
|
124
|
+
animation = withSpring(targetValue, config);
|
|
125
|
+
break;
|
|
125
126
|
}
|
|
126
127
|
default:
|
|
127
128
|
{
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
animation = withSpring(targetValue, defaultTransition);
|
|
130
|
+
break;
|
|
130
131
|
}
|
|
131
132
|
}
|
|
133
|
+
if (delayMs && delayMs > 0) {
|
|
134
|
+
return withDelay(delayMs, animation);
|
|
135
|
+
}
|
|
136
|
+
return animation;
|
|
132
137
|
};
|
|
133
138
|
|
|
134
139
|
/**
|
|
@@ -136,15 +141,16 @@ export const buildTransition = (targetValue, transition) => {
|
|
|
136
141
|
*
|
|
137
142
|
* @param currentPath - Current target path to animate to
|
|
138
143
|
* @param initialPath - Initial path for enter animation. When provided, the first animation will go from initialPath to currentPath.
|
|
139
|
-
* @param
|
|
144
|
+
* @param transitions - Transition configuration for enter and update animations
|
|
140
145
|
* @returns Animated SkPath as a shared value
|
|
141
146
|
*
|
|
142
147
|
* @example
|
|
143
148
|
* // Simple path transition
|
|
144
149
|
* const path = usePathTransition({
|
|
145
150
|
* currentPath: d ?? '',
|
|
146
|
-
*
|
|
147
|
-
*
|
|
151
|
+
* transitions: {
|
|
152
|
+
* update: { type: 'timing', duration: 3000 },
|
|
153
|
+
* },
|
|
148
154
|
* });
|
|
149
155
|
*
|
|
150
156
|
* @example
|
|
@@ -152,34 +158,63 @@ export const buildTransition = (targetValue, transition) => {
|
|
|
152
158
|
* const path = usePathTransition({
|
|
153
159
|
* currentPath: targetPath,
|
|
154
160
|
* initialPath: baselinePath,
|
|
155
|
-
*
|
|
156
|
-
*
|
|
161
|
+
* transitions: {
|
|
162
|
+
* enter: { type: 'tween', duration: 500 },
|
|
163
|
+
* update: { type: 'spring', stiffness: 900, damping: 120 },
|
|
164
|
+
* },
|
|
157
165
|
* });
|
|
158
166
|
*/
|
|
159
167
|
export const usePathTransition = _ref => {
|
|
168
|
+
var _transitions$update, _Skia$Path$MakeFromSV;
|
|
160
169
|
let {
|
|
161
170
|
currentPath,
|
|
162
171
|
initialPath,
|
|
172
|
+
transitions,
|
|
163
173
|
transition = defaultTransition
|
|
164
174
|
} = _ref;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
175
|
+
const updateTransition = (_transitions$update = transitions == null ? void 0 : transitions.update) != null ? _transitions$update : transition;
|
|
176
|
+
const enterTransition = transitions == null ? void 0 : transitions.enter;
|
|
177
|
+
const targetPathRef = useRef(initialPath != null ? initialPath : currentPath);
|
|
178
|
+
const isFirstAnimation = useRef(!!initialPath);
|
|
179
|
+
const interpolatorRef = useRef(null);
|
|
168
180
|
const progress = useSharedValue(0);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
const
|
|
181
|
+
const initialSkiaPath = (_Skia$Path$MakeFromSV = Skia.Path.MakeFromSVGString(initialPath != null ? initialPath : currentPath)) != null ? _Skia$Path$MakeFromSV : Skia.Path.Make();
|
|
182
|
+
const normalizedStartShared = useSharedValue(initialSkiaPath);
|
|
183
|
+
const normalizedEndShared = useSharedValue(initialSkiaPath);
|
|
184
|
+
const fallbackPathShared = useSharedValue(initialSkiaPath);
|
|
185
|
+
const result = useSharedValue(initialSkiaPath);
|
|
173
186
|
useEffect(() => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
187
|
+
if (targetPathRef.current !== currentPath) {
|
|
188
|
+
var _Skia$Path$MakeFromSV2, _Skia$Path$MakeFromSV3, _Skia$Path$MakeFromSV4;
|
|
189
|
+
let fromPath = targetPathRef.current;
|
|
190
|
+
if (interpolatorRef.current) {
|
|
191
|
+
const p = Math.min(Math.max(progress.value, 0), 1);
|
|
192
|
+
fromPath = interpolatorRef.current(p);
|
|
193
|
+
}
|
|
194
|
+
targetPathRef.current = currentPath;
|
|
195
|
+
const pathInterpolator = interpolatePath(fromPath, currentPath);
|
|
196
|
+
interpolatorRef.current = pathInterpolator;
|
|
197
|
+
normalizedStartShared.value = (_Skia$Path$MakeFromSV2 = Skia.Path.MakeFromSVGString(pathInterpolator(0))) != null ? _Skia$Path$MakeFromSV2 : Skia.Path.Make();
|
|
198
|
+
normalizedEndShared.value = (_Skia$Path$MakeFromSV3 = Skia.Path.MakeFromSVGString(pathInterpolator(1))) != null ? _Skia$Path$MakeFromSV3 : Skia.Path.Make();
|
|
199
|
+
fallbackPathShared.value = (_Skia$Path$MakeFromSV4 = Skia.Path.MakeFromSVGString(currentPath)) != null ? _Skia$Path$MakeFromSV4 : Skia.Path.Make();
|
|
200
|
+
const activeTransition = isFirstAnimation.current && enterTransition !== undefined ? enterTransition : updateTransition;
|
|
201
|
+
isFirstAnimation.current = false;
|
|
180
202
|
progress.value = 0;
|
|
181
|
-
progress.value = buildTransition(1,
|
|
203
|
+
progress.value = buildTransition(1, activeTransition);
|
|
182
204
|
}
|
|
183
|
-
}, [currentPath,
|
|
184
|
-
|
|
205
|
+
}, [currentPath, updateTransition, enterTransition, progress, normalizedStartShared, normalizedEndShared, fallbackPathShared]);
|
|
206
|
+
useAnimatedReaction(() => ({
|
|
207
|
+
p: progress.value,
|
|
208
|
+
to: fallbackPathShared.value
|
|
209
|
+
}), _ref2 => {
|
|
210
|
+
'worklet';
|
|
211
|
+
|
|
212
|
+
var _normalizedEndShared$;
|
|
213
|
+
let {
|
|
214
|
+
p
|
|
215
|
+
} = _ref2;
|
|
216
|
+
result.value = (_normalizedEndShared$ = normalizedEndShared.value.interpolate(normalizedStartShared.value, p)) != null ? _normalizedEndShared$ : fallbackPathShared.value;
|
|
217
|
+
notifyChange(result);
|
|
218
|
+
}, []);
|
|
219
|
+
return result;
|
|
185
220
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coinbase/cds-mobile-visualization",
|
|
3
|
-
"version": "3.4.0-beta.
|
|
3
|
+
"version": "3.4.0-beta.19",
|
|
4
4
|
"description": "Coinbase Design System - Mobile Visualization Native",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"CHANGELOG"
|
|
37
37
|
],
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@coinbase/cds-common": "^8.
|
|
39
|
+
"@coinbase/cds-common": "^8.48.0",
|
|
40
40
|
"@coinbase/cds-lottie-files": "^3.3.4",
|
|
41
|
-
"@coinbase/cds-mobile": "^8.
|
|
41
|
+
"@coinbase/cds-mobile": "^8.48.0",
|
|
42
42
|
"@coinbase/cds-utils": "^2.3.5",
|
|
43
43
|
"@shopify/react-native-skia": "^1.12.4 || ^2.0.0",
|
|
44
44
|
"react": "^18.3.1",
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"@babel/preset-env": "^7.28.0",
|
|
58
58
|
"@babel/preset-react": "^7.27.1",
|
|
59
59
|
"@babel/preset-typescript": "^7.27.1",
|
|
60
|
-
"@coinbase/cds-common": "^8.
|
|
60
|
+
"@coinbase/cds-common": "^8.48.0",
|
|
61
61
|
"@coinbase/cds-lottie-files": "^3.3.4",
|
|
62
|
-
"@coinbase/cds-mobile": "^8.
|
|
62
|
+
"@coinbase/cds-mobile": "^8.48.0",
|
|
63
63
|
"@coinbase/cds-utils": "^2.3.5",
|
|
64
64
|
"@shopify/react-native-skia": "1.12.4",
|
|
65
65
|
"@types/react": "^18.3.12",
|