@ledgerhq/lumen-ui-rnative-visualization 0.1.10 → 0.1.11
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/dist/module/lib/Components/Axis/XAxis/XAxis.js +1 -1
- package/dist/module/lib/Components/Axis/XAxis/XAxis.js.map +1 -1
- package/dist/module/lib/Components/Axis/YAxis/YAxis.js +1 -1
- package/dist/module/lib/Components/Axis/YAxis/YAxis.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/CartesianChart.js +31 -28
- package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.js +53 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.test.js +144 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.test.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/context.js +8 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/context.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/index.js +5 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/index.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/types.js +4 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/types.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/utils.js +32 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/utils.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/utils.js +4 -4
- package/dist/module/lib/Components/Line/Line.js +5 -2
- package/dist/module/lib/Components/Line/Line.js.map +1 -1
- package/dist/module/lib/Components/LineChart/LineChart.js +7 -1
- package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
- package/dist/module/lib/Components/LineChart/LineChart.test.js +25 -0
- package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
- package/dist/module/lib/Components/Point/Point.js +3 -0
- package/dist/module/lib/Components/Point/Point.js.map +1 -1
- package/dist/module/lib/Components/ReferenceLine/ReferenceLine.js +125 -0
- package/dist/module/lib/Components/ReferenceLine/ReferenceLine.js.map +1 -0
- package/dist/module/lib/Components/ReferenceLine/ReferenceLine.stories.js +111 -0
- package/dist/module/lib/Components/ReferenceLine/ReferenceLine.stories.js.map +1 -0
- package/dist/module/lib/Components/ReferenceLine/ReferenceLine.test.js +338 -0
- package/dist/module/lib/Components/ReferenceLine/ReferenceLine.test.js.map +1 -0
- package/dist/module/lib/Components/ReferenceLine/constants.js +5 -0
- package/dist/module/lib/Components/ReferenceLine/constants.js.map +1 -0
- package/dist/module/lib/Components/ReferenceLine/index.js +5 -0
- package/dist/module/lib/Components/ReferenceLine/index.js.map +1 -0
- package/dist/module/lib/Components/ReferenceLine/types.js +2 -0
- package/dist/module/lib/Components/ReferenceLine/types.js.map +1 -0
- package/dist/module/lib/Components/ReferenceLine/utils.js +121 -0
- package/dist/module/lib/Components/ReferenceLine/utils.js.map +1 -0
- package/dist/module/lib/Components/ReferenceLine/utils.test.js +297 -0
- package/dist/module/lib/Components/ReferenceLine/utils.test.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/Scrubber.js +152 -0
- package/dist/module/lib/Components/Scrubber/Scrubber.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/Scrubber.stories.js +113 -0
- package/dist/module/lib/Components/Scrubber/Scrubber.stories.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/Scrubber.test.js +132 -0
- package/dist/module/lib/Components/Scrubber/Scrubber.test.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.js +105 -0
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.test.js +217 -0
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.test.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/constants.js +10 -0
- package/dist/module/lib/Components/Scrubber/constants.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/context/index.js +4 -0
- package/dist/module/lib/Components/Scrubber/context/index.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/context/scrubberContext.js +10 -0
- package/dist/module/lib/Components/Scrubber/context/scrubberContext.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/index.js +5 -0
- package/dist/module/lib/Components/Scrubber/index.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/types.js +4 -0
- package/dist/module/lib/Components/Scrubber/types.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/utils.js +83 -0
- package/dist/module/lib/Components/Scrubber/utils.js.map +1 -0
- package/dist/module/lib/Components/Scrubber/utils.test.js +192 -0
- package/dist/module/lib/Components/Scrubber/utils.test.js.map +1 -0
- package/dist/module/lib/Components/index.js +2 -0
- package/dist/module/lib/Components/index.js.map +1 -1
- package/dist/module/lib/utils/scales/scales.js +16 -8
- package/dist/module/lib/utils/scales/scales.js.map +1 -1
- package/dist/module/lib/utils/scales/scales.test.js +32 -1
- package/dist/module/lib/utils/scales/scales.test.js.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.d.ts +3 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/context.d.ts +7 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/context.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/types.d.ts +38 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/utils.d.ts +17 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/utils.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts +17 -0
- package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Line/Line.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts +1 -1
- package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/LineChart/types.d.ts +16 -0
- package/dist/typescript/src/lib/Components/LineChart/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Point/Point.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/ReferenceLine/ReferenceLine.d.ts +3 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/ReferenceLine.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/constants.d.ts +3 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/constants.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/types.d.ts +74 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts +55 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts +17 -0
- package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts +16 -0
- package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Scrubber/constants.d.ts +8 -0
- package/dist/typescript/src/lib/Components/Scrubber/constants.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Scrubber/context/index.d.ts +2 -0
- package/dist/typescript/src/lib/Components/Scrubber/context/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Scrubber/context/scrubberContext.d.ts +8 -0
- package/dist/typescript/src/lib/Components/Scrubber/context/scrubberContext.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Scrubber/index.d.ts +4 -0
- package/dist/typescript/src/lib/Components/Scrubber/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Scrubber/types.d.ts +57 -0
- package/dist/typescript/src/lib/Components/Scrubber/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts +22 -0
- package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/index.d.ts +2 -0
- package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/scales/scales.d.ts +6 -0
- package/dist/typescript/src/lib/utils/scales/scales.d.ts.map +1 -1
- package/package.json +6 -3
- package/src/lib/Components/Axis/XAxis/XAxis.tsx +1 -1
- package/src/lib/Components/Axis/YAxis/YAxis.tsx +1 -1
- package/src/lib/Components/CartesianChart/CartesianChart.tsx +32 -32
- package/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.test.tsx +129 -0
- package/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.tsx +59 -0
- package/src/lib/Components/CartesianChart/RevealClip/context.ts +13 -0
- package/src/lib/Components/CartesianChart/RevealClip/index.ts +2 -0
- package/src/lib/Components/CartesianChart/RevealClip/types.ts +38 -0
- package/src/lib/Components/CartesianChart/RevealClip/utils.ts +51 -0
- package/src/lib/Components/CartesianChart/types.ts +17 -0
- package/src/lib/Components/CartesianChart/utils.ts +4 -4
- package/src/lib/Components/Line/Line.tsx +5 -3
- package/src/lib/Components/LineChart/LineChart.test.tsx +23 -0
- package/src/lib/Components/LineChart/LineChart.tsx +6 -0
- package/src/lib/Components/LineChart/types.ts +16 -0
- package/src/lib/Components/Point/Point.tsx +3 -1
- package/src/lib/Components/ReferenceLine/ReferenceLine.stories.tsx +109 -0
- package/src/lib/Components/ReferenceLine/ReferenceLine.test.tsx +269 -0
- package/src/lib/Components/ReferenceLine/ReferenceLine.tsx +149 -0
- package/src/lib/Components/ReferenceLine/constants.ts +2 -0
- package/src/lib/Components/ReferenceLine/index.ts +2 -0
- package/src/lib/Components/ReferenceLine/types.ts +79 -0
- package/src/lib/Components/ReferenceLine/utils.test.ts +332 -0
- package/src/lib/Components/ReferenceLine/utils.ts +175 -0
- package/src/lib/Components/Scrubber/Scrubber.stories.tsx +122 -0
- package/src/lib/Components/Scrubber/Scrubber.test.tsx +110 -0
- package/src/lib/Components/Scrubber/Scrubber.tsx +189 -0
- package/src/lib/Components/Scrubber/ScrubberProvider.test.tsx +223 -0
- package/src/lib/Components/Scrubber/ScrubberProvider.tsx +132 -0
- package/src/lib/Components/Scrubber/constants.ts +7 -0
- package/src/lib/Components/Scrubber/context/index.ts +1 -0
- package/src/lib/Components/Scrubber/context/scrubberContext.ts +14 -0
- package/src/lib/Components/Scrubber/index.ts +3 -0
- package/src/lib/Components/Scrubber/types.ts +59 -0
- package/src/lib/Components/Scrubber/utils.test.ts +172 -0
- package/src/lib/Components/Scrubber/utils.ts +126 -0
- package/src/lib/Components/index.ts +2 -0
- package/src/lib/utils/scales/scales.test.ts +22 -0
- package/src/lib/utils/scales/scales.ts +19 -9
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
|
|
3
|
+
import { ThemeProvider } from '@ledgerhq/lumen-ui-rnative';
|
|
4
|
+
import { render } from '@testing-library/react-native';
|
|
5
|
+
import { Svg } from 'react-native-svg';
|
|
6
|
+
|
|
7
|
+
import type { DrawingArea, Series } from '../../../utils/types';
|
|
8
|
+
import { OVERFLOW_BUFFER } from '../utils';
|
|
9
|
+
|
|
10
|
+
import { useRevealClip } from './context';
|
|
11
|
+
import { RevealClipDefs } from './RevealClipDefs';
|
|
12
|
+
|
|
13
|
+
const drawingArea: DrawingArea = {
|
|
14
|
+
x: 10,
|
|
15
|
+
y: 10,
|
|
16
|
+
width: 380,
|
|
17
|
+
height: 180,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const series: Series[] = [{ id: 'a', stroke: '#000', data: [1, 2, 3] }];
|
|
21
|
+
|
|
22
|
+
function ClipConsumer() {
|
|
23
|
+
const clipPath = useRevealClip();
|
|
24
|
+
return <Svg testID='consumer' clipPath={clipPath} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const renderInSvg = (ui: React.ReactElement) =>
|
|
28
|
+
render(
|
|
29
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='light'>
|
|
30
|
+
<Svg>{ui}</Svg>
|
|
31
|
+
</ThemeProvider>,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
describe('RevealClipDefs', () => {
|
|
35
|
+
it('renders children', () => {
|
|
36
|
+
const { getByTestId } = renderInSvg(
|
|
37
|
+
<RevealClipDefs
|
|
38
|
+
drawingArea={drawingArea}
|
|
39
|
+
series={series}
|
|
40
|
+
transitions={{ enter: { duration: 0.5 } }}
|
|
41
|
+
>
|
|
42
|
+
<Svg testID='child' />
|
|
43
|
+
</RevealClipDefs>,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect(getByTestId('child')).toBeTruthy();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders a ClipPath with a Rect when animate is true', () => {
|
|
50
|
+
const { UNSAFE_root } = renderInSvg(
|
|
51
|
+
<RevealClipDefs
|
|
52
|
+
drawingArea={drawingArea}
|
|
53
|
+
series={series}
|
|
54
|
+
transitions={{ enter: { duration: 0.5 } }}
|
|
55
|
+
>
|
|
56
|
+
<Svg testID='child' />
|
|
57
|
+
</RevealClipDefs>,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const clipPaths = UNSAFE_root.findAllByType('ClipPath' as any);
|
|
61
|
+
expect(clipPaths.length).toBeGreaterThan(0);
|
|
62
|
+
|
|
63
|
+
const rects = clipPaths[0].findAllByType('Rect' as any);
|
|
64
|
+
expect(rects.length).toBeGreaterThan(0);
|
|
65
|
+
|
|
66
|
+
const rect = rects[0];
|
|
67
|
+
expect(rect.props.x).toBe(drawingArea.x - OVERFLOW_BUFFER.left);
|
|
68
|
+
expect(rect.props.y).toBe(drawingArea.y - OVERFLOW_BUFFER.top);
|
|
69
|
+
expect(rect.props.height).toBe(
|
|
70
|
+
drawingArea.height + OVERFLOW_BUFFER.top + OVERFLOW_BUFFER.bottom,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('provides clipPathAttr to consumers via context', () => {
|
|
75
|
+
const { UNSAFE_root } = renderInSvg(
|
|
76
|
+
<RevealClipDefs
|
|
77
|
+
drawingArea={drawingArea}
|
|
78
|
+
series={series}
|
|
79
|
+
transitions={{ enter: { duration: 0.5 } }}
|
|
80
|
+
>
|
|
81
|
+
<ClipConsumer />
|
|
82
|
+
</RevealClipDefs>,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const consumer = UNSAFE_root.findByProps({ testID: 'consumer' });
|
|
86
|
+
expect(consumer.props.clipPath).toMatch(/^url\(#/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('skips ClipPath when animate is false', () => {
|
|
90
|
+
const { UNSAFE_root, getByTestId } = renderInSvg(
|
|
91
|
+
<RevealClipDefs drawingArea={drawingArea} series={series} animate={false}>
|
|
92
|
+
<Svg testID='child' />
|
|
93
|
+
</RevealClipDefs>,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(getByTestId('child')).toBeTruthy();
|
|
97
|
+
|
|
98
|
+
const clipPaths = UNSAFE_root.findAllByType('ClipPath' as any);
|
|
99
|
+
expect(clipPaths).toHaveLength(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('does not provide clipPathAttr when animate is false', () => {
|
|
103
|
+
const { UNSAFE_root } = renderInSvg(
|
|
104
|
+
<RevealClipDefs drawingArea={drawingArea} series={series} animate={false}>
|
|
105
|
+
<ClipConsumer />
|
|
106
|
+
</RevealClipDefs>,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const consumer = UNSAFE_root.findByProps({ testID: 'consumer' });
|
|
110
|
+
expect(consumer.props.clipPath).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('renders without crashing when transitions.enter is not defined', () => {
|
|
114
|
+
const { UNSAFE_root, getByTestId } = renderInSvg(
|
|
115
|
+
<RevealClipDefs
|
|
116
|
+
drawingArea={drawingArea}
|
|
117
|
+
series={series}
|
|
118
|
+
transitions={{}}
|
|
119
|
+
>
|
|
120
|
+
<Svg testID='child' />
|
|
121
|
+
</RevealClipDefs>,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(getByTestId('child')).toBeTruthy();
|
|
125
|
+
|
|
126
|
+
const clipPaths = UNSAFE_root.findAllByType('ClipPath' as any);
|
|
127
|
+
expect(clipPaths.length).toBeGreaterThan(0);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import Animated from 'react-native-reanimated';
|
|
3
|
+
import { ClipPath, Defs, Rect } from 'react-native-svg';
|
|
4
|
+
|
|
5
|
+
import { OVERFLOW_BUFFER } from '../utils';
|
|
6
|
+
|
|
7
|
+
import { RevealClipContext } from './context';
|
|
8
|
+
import type { RevealClipDefsProps } from './types';
|
|
9
|
+
import { useComputeDataFingerprint, useRevealClipAnimation } from './utils';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_DURATION_IN_SECONDS = 0.8;
|
|
12
|
+
|
|
13
|
+
const AnimatedRect = Animated.createAnimatedComponent(Rect);
|
|
14
|
+
|
|
15
|
+
export function RevealClipDefs({
|
|
16
|
+
children,
|
|
17
|
+
drawingArea,
|
|
18
|
+
series,
|
|
19
|
+
animate = true,
|
|
20
|
+
transitions,
|
|
21
|
+
}: RevealClipDefsProps) {
|
|
22
|
+
const isDisabled = !animate;
|
|
23
|
+
const durationMs =
|
|
24
|
+
(transitions?.enter?.duration ?? DEFAULT_DURATION_IN_SECONDS) * 1000;
|
|
25
|
+
|
|
26
|
+
const dataFingerprint = useComputeDataFingerprint({ series });
|
|
27
|
+
const { clipId, animatedRectProps } = useRevealClipAnimation({
|
|
28
|
+
durationMs,
|
|
29
|
+
drawingArea,
|
|
30
|
+
dataFingerprint,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const contextValue = useMemo(
|
|
34
|
+
() => ({ clipPathAttr: `url(#${clipId})` }),
|
|
35
|
+
[clipId],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (isDisabled) {
|
|
39
|
+
return children;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<RevealClipContext.Provider key={dataFingerprint} value={contextValue}>
|
|
44
|
+
<Defs>
|
|
45
|
+
<ClipPath id={clipId}>
|
|
46
|
+
<AnimatedRect
|
|
47
|
+
x={drawingArea.x - OVERFLOW_BUFFER.left}
|
|
48
|
+
y={drawingArea.y - OVERFLOW_BUFFER.top}
|
|
49
|
+
height={
|
|
50
|
+
drawingArea.height + OVERFLOW_BUFFER.top + OVERFLOW_BUFFER.bottom
|
|
51
|
+
}
|
|
52
|
+
animatedProps={animatedRectProps}
|
|
53
|
+
/>
|
|
54
|
+
</ClipPath>
|
|
55
|
+
</Defs>
|
|
56
|
+
{children}
|
|
57
|
+
</RevealClipContext.Provider>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
type RevealClipContextValue = {
|
|
4
|
+
clipPathAttr: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const RevealClipContext = createContext<
|
|
8
|
+
RevealClipContextValue | undefined
|
|
9
|
+
>(undefined);
|
|
10
|
+
|
|
11
|
+
export const useRevealClip = (): string | undefined => {
|
|
12
|
+
return useContext(RevealClipContext)?.clipPathAttr;
|
|
13
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { DrawingArea, Series } from '../../../utils';
|
|
4
|
+
|
|
5
|
+
export type EnterTransitionConfig = {
|
|
6
|
+
/**
|
|
7
|
+
* Duration in seconds. @default 0.8
|
|
8
|
+
*/
|
|
9
|
+
duration?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Reanimated easing is configured internally.
|
|
12
|
+
* This field is kept for API parity with the web variant but is unused on native.
|
|
13
|
+
*/
|
|
14
|
+
easing?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type RevealClipDefsProps = {
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* The drawing area whose bounds define the clip rectangle.
|
|
21
|
+
*/
|
|
22
|
+
drawingArea: DrawingArea;
|
|
23
|
+
/**
|
|
24
|
+
* Whether to animate the chart.
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
animate?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Transition configuration.
|
|
30
|
+
* @default { enter: { duration: 0.8 } }
|
|
31
|
+
*/
|
|
32
|
+
transitions?: { enter?: EnterTransitionConfig };
|
|
33
|
+
/**
|
|
34
|
+
* Data series used to compute a fingerprint that restarts
|
|
35
|
+
* the animation when data changes.
|
|
36
|
+
*/
|
|
37
|
+
series: Series[];
|
|
38
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useEffect, useId } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useAnimatedProps,
|
|
4
|
+
useSharedValue,
|
|
5
|
+
withTiming,
|
|
6
|
+
} from 'react-native-reanimated';
|
|
7
|
+
|
|
8
|
+
import type { DrawingArea, Series } from '../../../utils/types';
|
|
9
|
+
import { OVERFLOW_BUFFER } from '../utils';
|
|
10
|
+
|
|
11
|
+
type RevealClipAnimationConfig = {
|
|
12
|
+
durationMs: number;
|
|
13
|
+
drawingArea: DrawingArea;
|
|
14
|
+
dataFingerprint: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type RevealClipAnimationResult = {
|
|
18
|
+
clipId: string;
|
|
19
|
+
animatedRectProps: ReturnType<typeof useAnimatedProps>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const useRevealClipAnimation = ({
|
|
23
|
+
durationMs,
|
|
24
|
+
drawingArea,
|
|
25
|
+
dataFingerprint,
|
|
26
|
+
}: RevealClipAnimationConfig): RevealClipAnimationResult => {
|
|
27
|
+
const clipId = useId();
|
|
28
|
+
const width = useSharedValue(0);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
width.value = 0;
|
|
32
|
+
width.value = withTiming(
|
|
33
|
+
drawingArea.width + OVERFLOW_BUFFER.left + OVERFLOW_BUFFER.right,
|
|
34
|
+
{ duration: durationMs },
|
|
35
|
+
);
|
|
36
|
+
}, [drawingArea.width, durationMs, width, dataFingerprint]);
|
|
37
|
+
|
|
38
|
+
const animatedRectProps = useAnimatedProps(() => ({
|
|
39
|
+
width: width.value,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
return { clipId, animatedRectProps };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const useComputeDataFingerprint = ({
|
|
46
|
+
series,
|
|
47
|
+
}: {
|
|
48
|
+
series: Series[];
|
|
49
|
+
}): string => {
|
|
50
|
+
return series.map((s) => s.data?.join(',') ?? '').join('|');
|
|
51
|
+
};
|
|
@@ -46,4 +46,21 @@ export type CartesianChartProps = {
|
|
|
46
46
|
* SVG content rendered inside the chart's context provider.
|
|
47
47
|
*/
|
|
48
48
|
children?: ReactNode;
|
|
49
|
+
/**
|
|
50
|
+
* Enables scrubbing (pan gesture) interactions on the chart.
|
|
51
|
+
* When true, add a `<Scrubber>` as a child to visualise the interaction.
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
enableScrubbing?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Callback fired whenever the scrubber moves to a new data index or is cleared.
|
|
57
|
+
* Receives `undefined` when the scrub gesture ends or the scrubber position is otherwise cleared.
|
|
58
|
+
*/
|
|
59
|
+
onScrubberPositionChange?: (index: number | undefined) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Whether to animate the chart on mount and data changes.
|
|
62
|
+
* Duration is 0.8 seconds.
|
|
63
|
+
* @default true
|
|
64
|
+
*/
|
|
65
|
+
animate?: boolean;
|
|
49
66
|
};
|
|
@@ -9,10 +9,10 @@ export const DEFAULT_HEIGHT = 160;
|
|
|
9
9
|
* wrapper so the chart's visual footprint stays unchanged.
|
|
10
10
|
*/
|
|
11
11
|
export const OVERFLOW_BUFFER: ChartInset = {
|
|
12
|
-
top:
|
|
13
|
-
right:
|
|
14
|
-
bottom:
|
|
15
|
-
left:
|
|
12
|
+
top: 50,
|
|
13
|
+
right: 50,
|
|
14
|
+
bottom: 50,
|
|
15
|
+
left: 50,
|
|
16
16
|
};
|
|
17
17
|
export const OVERFLOW_NEGATIVE_MARGIN = {
|
|
18
18
|
marginTop: -OVERFLOW_BUFFER.top,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useId, useMemo } from 'react';
|
|
2
|
-
import { Defs, LinearGradient, Path, Stop } from 'react-native-svg';
|
|
2
|
+
import { Defs, G, LinearGradient, Path, Stop } from 'react-native-svg';
|
|
3
3
|
|
|
4
4
|
import { isNumericScale } from '../../utils/scales/scales';
|
|
5
5
|
import { useCartesianChartContext } from '../CartesianChart/context';
|
|
6
|
+
import { useRevealClip } from '../CartesianChart/RevealClip';
|
|
6
7
|
|
|
7
8
|
import type { LineProps } from './types';
|
|
8
9
|
import { buildAreaPath, buildLinePath, toScaledPoints } from './utils';
|
|
@@ -18,6 +19,7 @@ export const Line = ({
|
|
|
18
19
|
}: LineProps) => {
|
|
19
20
|
const { getXScale, getYScale, getXAxisConfig, drawingArea, seriesMap } =
|
|
20
21
|
useCartesianChartContext();
|
|
22
|
+
const clipPath = useRevealClip();
|
|
21
23
|
|
|
22
24
|
const xScale = getXScale();
|
|
23
25
|
const yScale = getYScale();
|
|
@@ -53,7 +55,7 @@ export const Line = ({
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
return (
|
|
56
|
-
|
|
58
|
+
<G clipPath={clipPath}>
|
|
57
59
|
{showArea && areaPath && resolvedStroke && (
|
|
58
60
|
<>
|
|
59
61
|
<Defs>
|
|
@@ -83,6 +85,6 @@ export const Line = ({
|
|
|
83
85
|
strokeLinecap='round'
|
|
84
86
|
strokeLinejoin='round'
|
|
85
87
|
/>
|
|
86
|
-
|
|
88
|
+
</G>
|
|
87
89
|
);
|
|
88
90
|
};
|
|
@@ -76,4 +76,27 @@ describe('LineChart', () => {
|
|
|
76
76
|
);
|
|
77
77
|
expect(queryAllByTestId('line-area')).toHaveLength(0);
|
|
78
78
|
});
|
|
79
|
+
|
|
80
|
+
it('renders with animate true by default', () => {
|
|
81
|
+
const { getByTestId } = render(
|
|
82
|
+
<LineChartWrapper>
|
|
83
|
+
<LineChart series={sampleSeries} width={400} height={200} />
|
|
84
|
+
</LineChartWrapper>,
|
|
85
|
+
);
|
|
86
|
+
getByTestId('chart-svg');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('renders with animate false', () => {
|
|
90
|
+
const { getByTestId } = render(
|
|
91
|
+
<LineChartWrapper>
|
|
92
|
+
<LineChart
|
|
93
|
+
series={sampleSeries}
|
|
94
|
+
width={400}
|
|
95
|
+
height={200}
|
|
96
|
+
animate={false}
|
|
97
|
+
/>
|
|
98
|
+
</LineChartWrapper>,
|
|
99
|
+
);
|
|
100
|
+
getByTestId('chart-svg');
|
|
101
|
+
});
|
|
79
102
|
});
|
|
@@ -20,6 +20,9 @@ export const LineChart = ({
|
|
|
20
20
|
height = 160,
|
|
21
21
|
inset,
|
|
22
22
|
children,
|
|
23
|
+
enableScrubbing = false,
|
|
24
|
+
onScrubberPositionChange,
|
|
25
|
+
animate,
|
|
23
26
|
}: LineChartProps) => {
|
|
24
27
|
const {
|
|
25
28
|
scaleType: xScaleType,
|
|
@@ -77,6 +80,9 @@ export const LineChart = ({
|
|
|
77
80
|
height={height}
|
|
78
81
|
inset={inset}
|
|
79
82
|
axisPadding={axisPadding}
|
|
83
|
+
enableScrubbing={enableScrubbing}
|
|
84
|
+
onScrubberPositionChange={onScrubberPositionChange}
|
|
85
|
+
animate={animate}
|
|
80
86
|
>
|
|
81
87
|
{showXAxis && <XAxis {...xAxisVisualProps} />}
|
|
82
88
|
{showYAxis && <YAxis {...yAxisVisualProps} />}
|
|
@@ -60,4 +60,20 @@ export type LineChartProps = {
|
|
|
60
60
|
* Additional SVG content rendered inside the chart after lines and axes.
|
|
61
61
|
*/
|
|
62
62
|
children?: ReactNode;
|
|
63
|
+
/**
|
|
64
|
+
* Enables scrubbing (pan gesture) interactions on the chart.
|
|
65
|
+
* When true, add a `<Scrubber>` as a child to visualise the interaction.
|
|
66
|
+
* @default false
|
|
67
|
+
*/
|
|
68
|
+
enableScrubbing?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Callback fired whenever the scrubber moves to a new data index or is cleared.
|
|
71
|
+
* Receives `undefined` when the scrub gesture ends or the scrubber position is otherwise cleared.
|
|
72
|
+
*/
|
|
73
|
+
onScrubberPositionChange?: (index: number | undefined) => void;
|
|
74
|
+
/**
|
|
75
|
+
* Whether to animate the chart on mount and data changes.
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
animate?: boolean;
|
|
63
79
|
};
|
|
@@ -4,6 +4,7 @@ import { Circle, G, Polygon, Text as SvgText } from 'react-native-svg';
|
|
|
4
4
|
|
|
5
5
|
import { projectPoint } from '../../utils/scales/scales';
|
|
6
6
|
import { useCartesianChartContext } from '../CartesianChart/context';
|
|
7
|
+
import { useRevealClip } from '../CartesianChart/RevealClip';
|
|
7
8
|
|
|
8
9
|
import type { PointLabelProps, PointProps } from './types';
|
|
9
10
|
import {
|
|
@@ -46,6 +47,7 @@ export function Point({
|
|
|
46
47
|
onPress,
|
|
47
48
|
}: Readonly<PointProps>) {
|
|
48
49
|
const { getXScale, getYScale, drawingArea } = useCartesianChartContext();
|
|
50
|
+
const clipPath = useRevealClip();
|
|
49
51
|
const { theme } = useTheme();
|
|
50
52
|
|
|
51
53
|
const xScale = getXScale();
|
|
@@ -71,7 +73,7 @@ export function Point({
|
|
|
71
73
|
const Label = LabelComponent ?? PointLabel;
|
|
72
74
|
|
|
73
75
|
return (
|
|
74
|
-
<G testID='point-group' onPress={onPress}>
|
|
76
|
+
<G testID='point-group' clipPath={clipPath} onPress={onPress}>
|
|
75
77
|
{!hidePoint && (
|
|
76
78
|
<Circle
|
|
77
79
|
testID='point-circle'
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useTheme } from '@ledgerhq/lumen-ui-rnative';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { StoryDecorator } from '../../../../.storybook/StoryDecorator.tsx';
|
|
6
|
+
import { LineChart } from '../LineChart';
|
|
7
|
+
|
|
8
|
+
import { ReferenceLine } from './ReferenceLine';
|
|
9
|
+
|
|
10
|
+
const sampleSeries = [
|
|
11
|
+
{
|
|
12
|
+
id: 'prices',
|
|
13
|
+
stroke: '#7B61FF',
|
|
14
|
+
data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
19
|
+
component: ReferenceLine,
|
|
20
|
+
title: 'Visualization/ReferenceLine',
|
|
21
|
+
tags: ['experimental'],
|
|
22
|
+
decorators: [
|
|
23
|
+
(Story, context) => {
|
|
24
|
+
return (
|
|
25
|
+
<StoryDecorator context={context}>
|
|
26
|
+
<View style={{ width: 600, padding: 16 }}>
|
|
27
|
+
<Story />
|
|
28
|
+
</View>
|
|
29
|
+
</StoryDecorator>
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
} satisfies Meta<typeof ReferenceLine>;
|
|
34
|
+
|
|
35
|
+
export default meta;
|
|
36
|
+
type Story = StoryObj<typeof ReferenceLine>;
|
|
37
|
+
|
|
38
|
+
export const HorizontalLine: Story = {
|
|
39
|
+
parameters: {
|
|
40
|
+
layout: 'centered',
|
|
41
|
+
backgrounds: { default: 'light' },
|
|
42
|
+
},
|
|
43
|
+
render: () => (
|
|
44
|
+
<LineChart series={sampleSeries} height={250} showArea>
|
|
45
|
+
<ReferenceLine
|
|
46
|
+
dataY={50}
|
|
47
|
+
label='Target'
|
|
48
|
+
labelDy={-4}
|
|
49
|
+
labelVerticalAlignment='start'
|
|
50
|
+
labelHorizontalAlignment='start'
|
|
51
|
+
/>
|
|
52
|
+
</LineChart>
|
|
53
|
+
),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const VerticalLine: Story = {
|
|
57
|
+
parameters: {
|
|
58
|
+
layout: 'centered',
|
|
59
|
+
backgrounds: { default: 'light' },
|
|
60
|
+
},
|
|
61
|
+
render: () => (
|
|
62
|
+
<LineChart series={sampleSeries} height={250} showArea>
|
|
63
|
+
<ReferenceLine
|
|
64
|
+
dataX={7}
|
|
65
|
+
label='Midpoint'
|
|
66
|
+
labelDx={4}
|
|
67
|
+
labelHorizontalAlignment='end'
|
|
68
|
+
labelVerticalAlignment='end'
|
|
69
|
+
labelPosition='start'
|
|
70
|
+
/>
|
|
71
|
+
</LineChart>
|
|
72
|
+
),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const Combined: Story = {
|
|
76
|
+
parameters: {
|
|
77
|
+
layout: 'centered',
|
|
78
|
+
backgrounds: { default: 'light' },
|
|
79
|
+
},
|
|
80
|
+
render: () => {
|
|
81
|
+
const { theme } = useTheme();
|
|
82
|
+
return (
|
|
83
|
+
<LineChart series={sampleSeries} height={250} showArea>
|
|
84
|
+
<ReferenceLine
|
|
85
|
+
dataY={98}
|
|
86
|
+
label='ATH'
|
|
87
|
+
labelDy={4}
|
|
88
|
+
labelVerticalAlignment='start'
|
|
89
|
+
stroke={theme.colors.border.success}
|
|
90
|
+
/>
|
|
91
|
+
<ReferenceLine
|
|
92
|
+
dataY={4}
|
|
93
|
+
label='Low'
|
|
94
|
+
labelDy={-4}
|
|
95
|
+
labelVerticalAlignment='end'
|
|
96
|
+
stroke={theme.colors.border.error}
|
|
97
|
+
/>
|
|
98
|
+
<ReferenceLine
|
|
99
|
+
dataX={4}
|
|
100
|
+
label='Peak'
|
|
101
|
+
labelDx={8}
|
|
102
|
+
labelDy={8}
|
|
103
|
+
labelHorizontalAlignment='end'
|
|
104
|
+
labelPosition='start'
|
|
105
|
+
/>
|
|
106
|
+
</LineChart>
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
};
|