@ledgerhq/lumen-ui-rnative-visualization 0.1.18 → 0.1.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.
Files changed (126) hide show
  1. package/dist/module/lib/Components/Axis/XAxis/XAxis.js +2 -1
  2. package/dist/module/lib/Components/Axis/XAxis/XAxis.js.map +1 -1
  3. package/dist/module/lib/Components/Axis/XAxis/XAxis.test.js +14 -0
  4. package/dist/module/lib/Components/Axis/XAxis/XAxis.test.js.map +1 -1
  5. package/dist/module/lib/Components/Axis/YAxis/YAxis.js +2 -1
  6. package/dist/module/lib/Components/Axis/YAxis/YAxis.js.map +1 -1
  7. package/dist/module/lib/Components/Axis/YAxis/YAxis.test.js +14 -0
  8. package/dist/module/lib/Components/Axis/YAxis/YAxis.test.js.map +1 -1
  9. package/dist/module/lib/Components/CartesianChart/CartesianChart.js +2 -2
  10. package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -1
  11. package/dist/module/lib/Components/CartesianChart/{RevealClip/RevealClipDefs.js → RevealAnimation/RevealAnimationProvider.js} +12 -11
  12. package/dist/module/lib/Components/CartesianChart/RevealAnimation/RevealAnimationProvider.js.map +1 -0
  13. package/dist/module/lib/Components/CartesianChart/{RevealClip/RevealClipDefs.test.js → RevealAnimation/RevealAnimationProvider.test.js} +51 -12
  14. package/dist/module/lib/Components/CartesianChart/RevealAnimation/RevealAnimationProvider.test.js.map +1 -0
  15. package/dist/module/lib/Components/CartesianChart/RevealAnimation/context.js +35 -0
  16. package/dist/module/lib/Components/CartesianChart/RevealAnimation/context.js.map +1 -0
  17. package/dist/module/lib/Components/CartesianChart/RevealAnimation/index.js +5 -0
  18. package/dist/module/lib/Components/CartesianChart/RevealAnimation/index.js.map +1 -0
  19. package/dist/module/lib/Components/CartesianChart/RevealAnimation/types.js.map +1 -0
  20. package/dist/module/lib/Components/CartesianChart/RevealAnimation/utils.js +64 -0
  21. package/dist/module/lib/Components/CartesianChart/RevealAnimation/utils.js.map +1 -0
  22. package/dist/module/lib/Components/CartesianChart/utils.js +1 -1
  23. package/dist/module/lib/Components/CartesianChart/utils.js.map +1 -1
  24. package/dist/module/lib/Components/Line/Line.js +6 -4
  25. package/dist/module/lib/Components/Line/Line.js.map +1 -1
  26. package/dist/module/lib/Components/Line/utils.js +19 -9
  27. package/dist/module/lib/Components/Line/utils.js.map +1 -1
  28. package/dist/module/lib/Components/Line/utils.test.js +22 -2
  29. package/dist/module/lib/Components/Line/utils.test.js.map +1 -1
  30. package/dist/module/lib/Components/LineChart/LineChart.js +23 -8
  31. package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
  32. package/dist/module/lib/Components/LineChart/LineChart.test.js +69 -0
  33. package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
  34. package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.js +2 -2
  35. package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.js.map +1 -1
  36. package/dist/module/lib/Components/Point/Point.js +6 -4
  37. package/dist/module/lib/Components/Point/Point.js.map +1 -1
  38. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js +8 -1
  39. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js.map +1 -1
  40. package/dist/module/lib/Components/Scrubber/ScrubberProvider.test.js +79 -1
  41. package/dist/module/lib/Components/Scrubber/ScrubberProvider.test.js.map +1 -1
  42. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts +6 -0
  43. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts.map +1 -1
  44. package/dist/typescript/src/lib/Components/Axis/XAxis/XAxis.d.ts +1 -1
  45. package/dist/typescript/src/lib/Components/Axis/XAxis/XAxis.d.ts.map +1 -1
  46. package/dist/typescript/src/lib/Components/Axis/YAxis/YAxis.d.ts +1 -1
  47. package/dist/typescript/src/lib/Components/Axis/YAxis/YAxis.d.ts.map +1 -1
  48. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/RevealAnimationProvider.d.ts +3 -0
  49. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/RevealAnimationProvider.d.ts.map +1 -0
  50. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/context.d.ts +22 -0
  51. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/context.d.ts.map +1 -0
  52. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/index.d.ts +3 -0
  53. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/index.d.ts.map +1 -0
  54. package/dist/typescript/src/lib/Components/CartesianChart/{RevealClip → RevealAnimation}/types.d.ts +14 -1
  55. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/types.d.ts.map +1 -0
  56. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/utils.d.ts +33 -0
  57. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/utils.d.ts.map +1 -0
  58. package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts +1 -1
  59. package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts.map +1 -1
  60. package/dist/typescript/src/lib/Components/Line/Line.d.ts +1 -1
  61. package/dist/typescript/src/lib/Components/Line/Line.d.ts.map +1 -1
  62. package/dist/typescript/src/lib/Components/Line/types.d.ts +7 -0
  63. package/dist/typescript/src/lib/Components/Line/types.d.ts.map +1 -1
  64. package/dist/typescript/src/lib/Components/Line/utils.d.ts +8 -4
  65. package/dist/typescript/src/lib/Components/Line/utils.d.ts.map +1 -1
  66. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts +1 -1
  67. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts.map +1 -1
  68. package/dist/typescript/src/lib/Components/LineChart/types.d.ts +13 -1
  69. package/dist/typescript/src/lib/Components/LineChart/types.d.ts.map +1 -1
  70. package/dist/typescript/src/lib/Components/Point/Point.d.ts.map +1 -1
  71. package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts.map +1 -1
  72. package/dist/typescript/src/lib/utils/types.d.ts +7 -0
  73. package/dist/typescript/src/lib/utils/types.d.ts.map +1 -1
  74. package/package.json +2 -2
  75. package/src/lib/Components/Axis/Axis.types.ts +6 -0
  76. package/src/lib/Components/Axis/XAxis/XAxis.test.tsx +12 -0
  77. package/src/lib/Components/Axis/XAxis/XAxis.tsx +16 -14
  78. package/src/lib/Components/Axis/YAxis/YAxis.test.tsx +12 -0
  79. package/src/lib/Components/Axis/YAxis/YAxis.tsx +16 -14
  80. package/src/lib/Components/CartesianChart/CartesianChart.tsx +3 -3
  81. package/src/lib/Components/CartesianChart/{RevealClip/RevealClipDefs.test.tsx → RevealAnimation/RevealAnimationProvider.test.tsx} +60 -17
  82. package/src/lib/Components/CartesianChart/{RevealClip/RevealClipDefs.tsx → RevealAnimation/RevealAnimationProvider.tsx} +10 -11
  83. package/src/lib/Components/CartesianChart/RevealAnimation/context.ts +39 -0
  84. package/src/lib/Components/CartesianChart/RevealAnimation/index.ts +2 -0
  85. package/src/lib/Components/CartesianChart/{RevealClip → RevealAnimation}/types.ts +15 -1
  86. package/src/lib/Components/CartesianChart/RevealAnimation/utils.ts +87 -0
  87. package/src/lib/Components/CartesianChart/utils.ts +2 -1
  88. package/src/lib/Components/Line/Line.tsx +13 -4
  89. package/src/lib/Components/Line/types.ts +7 -0
  90. package/src/lib/Components/Line/utils.test.ts +36 -2
  91. package/src/lib/Components/Line/utils.ts +24 -9
  92. package/src/lib/Components/LineChart/LineChart.test.tsx +71 -0
  93. package/src/lib/Components/LineChart/LineChart.tsx +21 -3
  94. package/src/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.tsx +2 -2
  95. package/src/lib/Components/LineChart/types.ts +15 -1
  96. package/src/lib/Components/Point/Point.tsx +7 -4
  97. package/src/lib/Components/Scrubber/ScrubberProvider.test.tsx +83 -1
  98. package/src/lib/Components/Scrubber/ScrubberProvider.tsx +15 -1
  99. package/src/lib/utils/types.ts +7 -0
  100. package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.js.map +0 -1
  101. package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.test.js.map +0 -1
  102. package/dist/module/lib/Components/CartesianChart/RevealClip/context.js +0 -8
  103. package/dist/module/lib/Components/CartesianChart/RevealClip/context.js.map +0 -1
  104. package/dist/module/lib/Components/CartesianChart/RevealClip/index.js +0 -5
  105. package/dist/module/lib/Components/CartesianChart/RevealClip/index.js.map +0 -1
  106. package/dist/module/lib/Components/CartesianChart/RevealClip/types.js.map +0 -1
  107. package/dist/module/lib/Components/CartesianChart/hooks/useDataFingerprint.js +0 -13
  108. package/dist/module/lib/Components/CartesianChart/hooks/useDataFingerprint.js.map +0 -1
  109. package/dist/module/lib/Components/CartesianChart/hooks/useRevealClipAnimation.js +0 -27
  110. package/dist/module/lib/Components/CartesianChart/hooks/useRevealClipAnimation.js.map +0 -1
  111. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.d.ts +0 -3
  112. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.d.ts.map +0 -1
  113. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/context.d.ts +0 -7
  114. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/context.d.ts.map +0 -1
  115. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/index.d.ts +0 -3
  116. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/index.d.ts.map +0 -1
  117. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/types.d.ts.map +0 -1
  118. package/dist/typescript/src/lib/Components/CartesianChart/hooks/useDataFingerprint.d.ts +0 -9
  119. package/dist/typescript/src/lib/Components/CartesianChart/hooks/useDataFingerprint.d.ts.map +0 -1
  120. package/dist/typescript/src/lib/Components/CartesianChart/hooks/useRevealClipAnimation.d.ts +0 -14
  121. package/dist/typescript/src/lib/Components/CartesianChart/hooks/useRevealClipAnimation.d.ts.map +0 -1
  122. package/src/lib/Components/CartesianChart/RevealClip/context.ts +0 -13
  123. package/src/lib/Components/CartesianChart/RevealClip/index.ts +0 -2
  124. package/src/lib/Components/CartesianChart/hooks/useDataFingerprint.ts +0 -16
  125. package/src/lib/Components/CartesianChart/hooks/useRevealClipAnimation.ts +0 -43
  126. /package/dist/module/lib/Components/CartesianChart/{RevealClip → RevealAnimation}/types.js +0 -0
@@ -17,6 +17,7 @@ export const YAxis = ({
17
17
  showGrid = false,
18
18
  showLine = false,
19
19
  showTickMark = false,
20
+ showLabels = true,
20
21
  gridLineStyle = 'dashed',
21
22
  ticks: ticksProp,
22
23
  tickLabelFormatter,
@@ -91,20 +92,21 @@ export const YAxis = ({
91
92
  />
92
93
  ))}
93
94
 
94
- {ticksData.map((tick, i) => (
95
- <SvgText
96
- key={`label-${tick.value}-${i}`}
97
- x={labelX}
98
- y={tick.position}
99
- dy={labelDy}
100
- textAnchor={position === 'start' ? 'end' : 'start'}
101
- fill={textFill}
102
- fontSize={theme.typographies.body4.fontSize}
103
- fontFamily={theme.fontFamilies.sans}
104
- >
105
- {tick.label}
106
- </SvgText>
107
- ))}
95
+ {showLabels &&
96
+ ticksData.map((tick, i) => (
97
+ <SvgText
98
+ key={`label-${tick.value}-${i}`}
99
+ x={labelX}
100
+ y={tick.position}
101
+ dy={labelDy}
102
+ textAnchor={position === 'start' ? 'end' : 'start'}
103
+ fill={textFill}
104
+ fontSize={theme.typographies.body4.fontSize}
105
+ fontFamily={theme.fontFamilies.sans}
106
+ >
107
+ {tick.label}
108
+ </SvgText>
109
+ ))}
108
110
  </G>
109
111
  );
110
112
  };
@@ -5,7 +5,7 @@ import { Svg } from 'react-native-svg';
5
5
  import { MagneticPointsProvider } from '../Point/pointContext';
6
6
  import { ScrubberProvider } from '../Scrubber/ScrubberProvider';
7
7
  import { CartesianChartProvider, useBuildChartContext } from './context';
8
- import { RevealClipDefs } from './RevealClip';
8
+ import { RevealAnimationProvider } from './RevealAnimation';
9
9
  import type { CartesianChartProps } from './types';
10
10
  import {
11
11
  DEFAULT_HEIGHT,
@@ -102,13 +102,13 @@ export function CartesianChart({
102
102
  height={svgHeight}
103
103
  style={{ overflow: 'visible' }}
104
104
  >
105
- <RevealClipDefs
105
+ <RevealAnimationProvider
106
106
  drawingArea={contextValue.drawingArea}
107
107
  series={series}
108
108
  animate={animate}
109
109
  >
110
110
  {children}
111
- </RevealClipDefs>
111
+ </RevealAnimationProvider>
112
112
  </Svg>
113
113
  </ScrubberProvider>
114
114
  </MagneticPointsProvider>
@@ -7,8 +7,8 @@ import { Svg } from 'react-native-svg';
7
7
  import type { DrawingArea, Series } from '../../../utils/types';
8
8
  import { OVERFLOW_BUFFER } from '../utils';
9
9
 
10
- import { useRevealClip } from './context';
11
- import { RevealClipDefs } from './RevealClipDefs';
10
+ import { usePathReveal, usePointReveal } from './context';
11
+ import { RevealAnimationProvider } from './RevealAnimationProvider';
12
12
 
13
13
  const drawingArea: DrawingArea = {
14
14
  x: 10,
@@ -20,10 +20,15 @@ const drawingArea: DrawingArea = {
20
20
  const series: Series[] = [{ id: 'a', stroke: '#000', data: [1, 2, 3] }];
21
21
 
22
22
  function ClipConsumer() {
23
- const clipPath = useRevealClip();
23
+ const clipPath = usePathReveal();
24
24
  return <Svg testID='consumer' clipPath={clipPath} />;
25
25
  }
26
26
 
27
+ function PointConsumer() {
28
+ const pointOpacity = usePointReveal();
29
+ return <Svg testID='point-consumer' opacity={pointOpacity ? 1 : 0} />;
30
+ }
31
+
27
32
  const renderInSvg = (ui: React.ReactElement) =>
28
33
  render(
29
34
  <ThemeProvider themes={ledgerLiveThemes} colorScheme='light'>
@@ -31,16 +36,16 @@ const renderInSvg = (ui: React.ReactElement) =>
31
36
  </ThemeProvider>,
32
37
  );
33
38
 
34
- describe('RevealClipDefs', () => {
39
+ describe('RevealAnimationProvider', () => {
35
40
  it('renders children', () => {
36
41
  const { getByTestId } = renderInSvg(
37
- <RevealClipDefs
42
+ <RevealAnimationProvider
38
43
  drawingArea={drawingArea}
39
44
  series={series}
40
45
  transitions={{ enter: { duration: 0.5 } }}
41
46
  >
42
47
  <Svg testID='child' />
43
- </RevealClipDefs>,
48
+ </RevealAnimationProvider>,
44
49
  );
45
50
 
46
51
  expect(getByTestId('child')).toBeTruthy();
@@ -48,13 +53,13 @@ describe('RevealClipDefs', () => {
48
53
 
49
54
  it('renders a ClipPath with a Rect when animate is true', () => {
50
55
  const { UNSAFE_root } = renderInSvg(
51
- <RevealClipDefs
56
+ <RevealAnimationProvider
52
57
  drawingArea={drawingArea}
53
58
  series={series}
54
59
  transitions={{ enter: { duration: 0.5 } }}
55
60
  >
56
61
  <Svg testID='child' />
57
- </RevealClipDefs>,
62
+ </RevealAnimationProvider>,
58
63
  );
59
64
 
60
65
  const clipPaths = UNSAFE_root.findAllByType('ClipPath' as any);
@@ -71,26 +76,45 @@ describe('RevealClipDefs', () => {
71
76
  );
72
77
  });
73
78
 
74
- it('provides clipPathAttr to consumers via context', () => {
79
+ it('provides clipPathAttr to consumers via usePathReveal', () => {
75
80
  const { UNSAFE_root } = renderInSvg(
76
- <RevealClipDefs
81
+ <RevealAnimationProvider
77
82
  drawingArea={drawingArea}
78
83
  series={series}
79
84
  transitions={{ enter: { duration: 0.5 } }}
80
85
  >
81
86
  <ClipConsumer />
82
- </RevealClipDefs>,
87
+ </RevealAnimationProvider>,
83
88
  );
84
89
 
85
90
  const consumer = UNSAFE_root.findByProps({ testID: 'consumer' });
86
91
  expect(consumer.props.clipPath).toMatch(/^url\(#/);
87
92
  });
88
93
 
94
+ it('provides a point opacity shared value via usePointReveal', () => {
95
+ const { UNSAFE_root } = renderInSvg(
96
+ <RevealAnimationProvider
97
+ drawingArea={drawingArea}
98
+ series={series}
99
+ transitions={{ enter: { duration: 0.5 } }}
100
+ >
101
+ <PointConsumer />
102
+ </RevealAnimationProvider>,
103
+ );
104
+
105
+ const consumer = UNSAFE_root.findByProps({ testID: 'point-consumer' });
106
+ expect(consumer.props.opacity).toBe(1);
107
+ });
108
+
89
109
  it('skips ClipPath when animate is false', () => {
90
110
  const { UNSAFE_root, getByTestId } = renderInSvg(
91
- <RevealClipDefs drawingArea={drawingArea} series={series} animate={false}>
111
+ <RevealAnimationProvider
112
+ drawingArea={drawingArea}
113
+ series={series}
114
+ animate={false}
115
+ >
92
116
  <Svg testID='child' />
93
- </RevealClipDefs>,
117
+ </RevealAnimationProvider>,
94
118
  );
95
119
 
96
120
  expect(getByTestId('child')).toBeTruthy();
@@ -101,24 +125,43 @@ describe('RevealClipDefs', () => {
101
125
 
102
126
  it('does not provide clipPathAttr when animate is false', () => {
103
127
  const { UNSAFE_root } = renderInSvg(
104
- <RevealClipDefs drawingArea={drawingArea} series={series} animate={false}>
128
+ <RevealAnimationProvider
129
+ drawingArea={drawingArea}
130
+ series={series}
131
+ animate={false}
132
+ >
105
133
  <ClipConsumer />
106
- </RevealClipDefs>,
134
+ </RevealAnimationProvider>,
107
135
  );
108
136
 
109
137
  const consumer = UNSAFE_root.findByProps({ testID: 'consumer' });
110
138
  expect(consumer.props.clipPath).toBeUndefined();
111
139
  });
112
140
 
141
+ it('does not provide a point opacity value when animate is false', () => {
142
+ const { UNSAFE_root } = renderInSvg(
143
+ <RevealAnimationProvider
144
+ drawingArea={drawingArea}
145
+ series={series}
146
+ animate={false}
147
+ >
148
+ <PointConsumer />
149
+ </RevealAnimationProvider>,
150
+ );
151
+
152
+ const consumer = UNSAFE_root.findByProps({ testID: 'point-consumer' });
153
+ expect(consumer.props.opacity).toBe(0);
154
+ });
155
+
113
156
  it('renders without crashing when transitions.enter is not defined', () => {
114
157
  const { UNSAFE_root, getByTestId } = renderInSvg(
115
- <RevealClipDefs
158
+ <RevealAnimationProvider
116
159
  drawingArea={drawingArea}
117
160
  series={series}
118
161
  transitions={{}}
119
162
  >
120
163
  <Svg testID='child' />
121
- </RevealClipDefs>,
164
+ </RevealAnimationProvider>,
122
165
  );
123
166
 
124
167
  expect(getByTestId('child')).toBeTruthy();
@@ -4,24 +4,23 @@ import { useMemo } from 'react';
4
4
  import Animated from 'react-native-reanimated';
5
5
  import { ClipPath, Defs, Rect } from 'react-native-svg';
6
6
 
7
- import { useDataFingerprint } from '../hooks/useDataFingerprint';
8
- import { useRevealClipAnimation } from '../hooks/useRevealClipAnimation';
9
7
  import { OVERFLOW_BUFFER } from '../utils';
10
8
 
11
- import { RevealClipContext } from './context';
12
- import type { RevealClipDefsProps } from './types';
9
+ import { RevealAnimationContext } from './context';
10
+ import type { RevealAnimationProps } from './types';
11
+ import { useDataFingerprint, useRevealAnimation } from './utils';
13
12
 
14
13
  const DEFAULT_DURATION_IN_SECONDS = 0.8;
15
14
 
16
15
  const AnimatedRect = Animated.createAnimatedComponent(Rect);
17
16
 
18
- export function RevealClipDefs({
17
+ export function RevealAnimationProvider({
19
18
  children,
20
19
  drawingArea,
21
20
  series,
22
21
  animate = true,
23
22
  transitions,
24
- }: RevealClipDefsProps) {
23
+ }: RevealAnimationProps) {
25
24
  /**
26
25
  * Disable reveal animation on Android devices due to issues with SVG clip-path animation support.
27
26
  * On Android, the animation does not render correctly, so we opt out for a consistent user experience.
@@ -31,15 +30,15 @@ export function RevealClipDefs({
31
30
  (transitions?.enter?.duration ?? DEFAULT_DURATION_IN_SECONDS) * 1000;
32
31
 
33
32
  const dataFingerprint = useDataFingerprint(series);
34
- const { clipId, animatedRectProps } = useRevealClipAnimation({
33
+ const { clipId, animatedRectProps, pointOpacity } = useRevealAnimation({
35
34
  durationMs,
36
35
  drawingArea,
37
36
  dataFingerprint,
38
37
  });
39
38
 
40
39
  const contextValue = useMemo(
41
- () => ({ clipPathAttr: `url(#${clipId})` }),
42
- [clipId],
40
+ () => ({ clipPathAttr: `url(#${clipId})`, pointOpacity }),
41
+ [clipId, pointOpacity],
43
42
  );
44
43
 
45
44
  if (isDisabled) {
@@ -47,7 +46,7 @@ export function RevealClipDefs({
47
46
  }
48
47
 
49
48
  return (
50
- <RevealClipContext.Provider key={dataFingerprint} value={contextValue}>
49
+ <RevealAnimationContext.Provider value={contextValue}>
51
50
  <Defs>
52
51
  <ClipPath id={clipId}>
53
52
  <AnimatedRect
@@ -61,6 +60,6 @@ export function RevealClipDefs({
61
60
  </ClipPath>
62
61
  </Defs>
63
62
  {children}
64
- </RevealClipContext.Provider>
63
+ </RevealAnimationContext.Provider>
65
64
  );
66
65
  }
@@ -0,0 +1,39 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { SharedValue } from 'react-native-reanimated';
3
+ import { useAnimatedProps } from 'react-native-reanimated';
4
+
5
+ import type { RevealAnimationContextValue } from './types';
6
+
7
+ export const RevealAnimationContext = createContext<
8
+ RevealAnimationContextValue | undefined
9
+ >(undefined);
10
+
11
+ /**
12
+ * Clip-path attribute for path-based consumers (e.g. `Line`).
13
+ * Returns `undefined` when the reveal animation is disabled.
14
+ */
15
+ export const usePathReveal = (): string | undefined => {
16
+ return useContext(RevealAnimationContext)?.clipPathAttr;
17
+ };
18
+
19
+ /**
20
+ * Raw shared opacity value for the point/accessory fade. Returns `undefined`
21
+ * when the reveal animation is disabled. Prefer {@link useRevealFadeProps} in
22
+ * components; this is the low-level primitive for custom needs.
23
+ */
24
+ export const usePointReveal = (): SharedValue<number> | undefined => {
25
+ return useContext(RevealAnimationContext)?.pointOpacity;
26
+ };
27
+
28
+ /**
29
+ * Ready-to-use animated props for accessory components (e.g. `Point`) that
30
+ * fade in with the reveal instead of being clipped. Spread directly onto any
31
+ * animated SVG element via `animatedProps`
32
+ */
33
+ export const useRevealFadeProps = (): ReturnType<typeof useAnimatedProps> => {
34
+ const pointOpacity = usePointReveal();
35
+ return useAnimatedProps(
36
+ () => ({ opacity: pointOpacity?.value ?? 1 }),
37
+ [pointOpacity],
38
+ );
39
+ };
@@ -0,0 +1,2 @@
1
+ export { RevealAnimationProvider } from './RevealAnimationProvider';
2
+ export { usePathReveal, useRevealFadeProps } from './context';
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
+ import type { SharedValue } from 'react-native-reanimated';
2
3
 
3
4
  import type { DrawingArea, Series } from '../../../utils/types';
4
5
 
@@ -14,7 +15,7 @@ export type EnterTransitionConfig = {
14
15
  easing?: string;
15
16
  };
16
17
 
17
- export type RevealClipDefsProps = {
18
+ export type RevealAnimationProps = {
18
19
  children: ReactNode;
19
20
  /**
20
21
  * The drawing area whose bounds define the clip rectangle.
@@ -36,3 +37,16 @@ export type RevealClipDefsProps = {
36
37
  */
37
38
  series: Series[];
38
39
  };
40
+
41
+ /**
42
+ * Value shared by the reveal animation provider.
43
+ *
44
+ * - `clipPathAttr` drives the left-to-right clip wipe consumed by path-based
45
+ * components (e.g. `Line`, `LineChartEmptyState`).
46
+ * - `pointOpacity` is the shared opacity value for accessory components (e.g.
47
+ * `Point`) that must not be clipped, so they fade in after the wipe instead.
48
+ */
49
+ export type RevealAnimationContextValue = {
50
+ clipPathAttr: string;
51
+ pointOpacity: SharedValue<number>;
52
+ };
@@ -0,0 +1,87 @@
1
+ import { useEffect, useId, useMemo, useRef } from 'react';
2
+ import {
3
+ useAnimatedProps,
4
+ useSharedValue,
5
+ withDelay,
6
+ withTiming,
7
+ } from 'react-native-reanimated';
8
+
9
+ import type { DrawingArea, Series } from '../../../utils/types';
10
+ import { OVERFLOW_BUFFER } from '../utils';
11
+
12
+ /**
13
+ * Duration in ms of an individual point's opacity fade-in.
14
+ */
15
+ const POINT_FADE_DURATION_IN_MS = 200;
16
+ const POINT_FADE_IN_AFTER_CLIP_IN_MS = -100;
17
+
18
+ type RevealAnimationConfig = {
19
+ durationMs: number;
20
+ drawingArea: DrawingArea;
21
+ dataFingerprint: string;
22
+ };
23
+
24
+ type RevealAnimationResult = {
25
+ clipId: string;
26
+ animatedRectProps: ReturnType<typeof useAnimatedProps>;
27
+ pointOpacity: ReturnType<typeof useSharedValue<number>>;
28
+ };
29
+
30
+ /**
31
+ * Drives the two reveal animations:
32
+ * - the left-to-right clip-path wipe (animated `Rect` width, including the
33
+ * {@link OVERFLOW_BUFFER} so edge content is revealed too), and
34
+ * - the point fade-in, delayed so points appear as the wipe finishes.
35
+ *
36
+ * On a data change the shared values are reset to `0` (hidden) *synchronously
37
+ * during render* — before the new line/points are committed and painted — so
38
+ * they never paint once at the previous cycle's end values (a visible flash).
39
+ * The effect then animates them back in.
40
+ */
41
+ export const useRevealAnimation = ({
42
+ durationMs,
43
+ drawingArea,
44
+ dataFingerprint,
45
+ }: RevealAnimationConfig): RevealAnimationResult => {
46
+ const clipId = useId();
47
+ const clipWidth = useSharedValue(0);
48
+ const pointOpacity = useSharedValue(0);
49
+
50
+ const previousFingerprint = useRef<string | null>(null);
51
+ if (previousFingerprint.current !== dataFingerprint) {
52
+ previousFingerprint.current = dataFingerprint;
53
+ clipWidth.value = 0;
54
+ pointOpacity.value = 0;
55
+ }
56
+
57
+ useEffect(() => {
58
+ clipWidth.value = withTiming(
59
+ drawingArea.width + OVERFLOW_BUFFER.left + OVERFLOW_BUFFER.right,
60
+ { duration: durationMs },
61
+ );
62
+
63
+ pointOpacity.value = withDelay(
64
+ Math.max(durationMs + POINT_FADE_IN_AFTER_CLIP_IN_MS, 0),
65
+ withTiming(1, { duration: POINT_FADE_DURATION_IN_MS }),
66
+ );
67
+ }, [drawingArea.width, durationMs, clipWidth, pointOpacity, dataFingerprint]);
68
+
69
+ const animatedRectProps = useAnimatedProps(() => ({
70
+ width: clipWidth.value,
71
+ }));
72
+
73
+ return { clipId, animatedRectProps, pointOpacity };
74
+ };
75
+
76
+ /**
77
+ * Stable signature of every series' data points, memoized on the `series`
78
+ * reference. Used to key the reveal provider so the reveal animation only
79
+ * replays when the underlying data actually changes — not on unrelated
80
+ * re-renders such as scrubbing or hover.
81
+ */
82
+ export const useDataFingerprint = (series: Series[]): string => {
83
+ return useMemo(
84
+ () => series.map((s) => s.data?.join(',') ?? '').join('|'),
85
+ [series],
86
+ );
87
+ };
@@ -1,7 +1,7 @@
1
1
  import type { ChartInset } from '../../utils/types';
2
2
  import type { CartesianChartProps } from './types';
3
3
 
4
- export const DEFAULT_HEIGHT = 160;
4
+ export const DEFAULT_HEIGHT = 228;
5
5
 
6
6
  /**
7
7
  * Internal buffer added around the drawing area so SVG content (labels, points,
@@ -30,6 +30,7 @@ export const OVERFLOW_OFFSET = {
30
30
  top: -OVERFLOW_BUFFER.top,
31
31
  left: -OVERFLOW_BUFFER.left,
32
32
  };
33
+
33
34
  export const ZERO_PADDING: ChartInset = {
34
35
  top: 0,
35
36
  right: 0,
@@ -4,7 +4,7 @@ import { Defs, G, LinearGradient, Path, Stop } from 'react-native-svg';
4
4
 
5
5
  import { isNumericScale } from '../../utils/scales/scales';
6
6
  import { useCartesianChartContext } from '../CartesianChart/context';
7
- import { useRevealClip } from '../CartesianChart/RevealClip';
7
+ import { usePathReveal } from '../CartesianChart/RevealAnimation';
8
8
 
9
9
  import { LINE_AREA_GRADIENT_OPACITY, LINE_STROKE_WIDTH } from './constants';
10
10
  import type { LineProps } from './types';
@@ -16,10 +16,11 @@ export const Line = ({
16
16
  showArea = false,
17
17
  areaType: _areaType = 'gradient',
18
18
  curve,
19
+ connectNulls,
19
20
  }: LineProps) => {
20
21
  const { getXScale, getYScale, getXAxisConfig, drawingArea, seriesMap } =
21
22
  useCartesianChartContext();
22
- const clipPath = useRevealClip();
23
+ const clipPath = usePathReveal();
23
24
 
24
25
  const xScale = getXScale();
25
26
  const yScale = getYScale();
@@ -31,13 +32,21 @@ export const Line = ({
31
32
  const resolvedStroke =
32
33
  stroke ?? seriesData?.stroke ?? theme.colors.border.muted;
33
34
  const resolvedCurve = curve ?? seriesData?.curve;
35
+ const resolvedConnectNulls =
36
+ connectNulls ?? seriesData?.connectNulls ?? false;
34
37
 
35
38
  const points = useMemo(
36
39
  () =>
37
40
  seriesData?.data && xScale && yScale && isNumericScale(yScale)
38
- ? toScaledPoints(seriesData.data, xScale, yScale, xAxisConfig?.data)
41
+ ? toScaledPoints(
42
+ seriesData.data,
43
+ xScale,
44
+ yScale,
45
+ xAxisConfig?.data,
46
+ resolvedConnectNulls,
47
+ )
39
48
  : null,
40
- [seriesData, xScale, yScale, xAxisConfig],
49
+ [seriesData, xScale, yScale, xAxisConfig, resolvedConnectNulls],
41
50
  );
42
51
 
43
52
  const linePath = useMemo(
@@ -30,4 +30,11 @@ export type LineProps = {
30
30
  * @default 'bump'
31
31
  */
32
32
  curve?: CurveType;
33
+ /**
34
+ * When true, skips null values and draws a continuous line across gaps.
35
+ * When false, null values create gaps in the line.
36
+ * When omitted, falls back to the `connectNulls` defined on the series.
37
+ * @default false
38
+ */
39
+ connectNulls?: boolean;
33
40
  };
@@ -44,17 +44,29 @@ describe('toScaledPoints', () => {
44
44
  expect(points![points!.length - 1][0]).toBe(800);
45
45
  });
46
46
 
47
- it('skips null values without affecting the cap', () => {
47
+ it('keeps null values as holes by default so the line breaks', () => {
48
48
  const data: (number | null)[] = [10, null, 30, 40, 50, 60, 70, 80, 90];
49
49
  const xData = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
50
50
 
51
51
  const points = toScaledPoints(data, xScale, yScale, xData);
52
52
 
53
53
  expect(points).not.toBeNull();
54
- expect(points).toHaveLength(7);
54
+ expect(points).toHaveLength(8);
55
+ expect(points![1][1]).toBeNull();
55
56
  expect(points![points!.length - 1][0]).toBe((800 / 7) * 7);
56
57
  });
57
58
 
59
+ it('skips null values when connectNulls is true', () => {
60
+ const data: (number | null)[] = [10, null, 30, 40, 50, 60, 70, 80, 90];
61
+ const xData = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
62
+
63
+ const points = toScaledPoints(data, xScale, yScale, xData, true);
64
+
65
+ expect(points).not.toBeNull();
66
+ expect(points).toHaveLength(7);
67
+ expect(points!.every((p) => p[1] !== null)).toBe(true);
68
+ });
69
+
58
70
  it('uses numeric xData values as the x input', () => {
59
71
  const data = [10, 20, 30];
60
72
  const customX = [0, 3.5, 7];
@@ -127,4 +139,26 @@ describe('buildLinePath', () => {
127
139
 
128
140
  expect(path).toBe(buildLinePath(points));
129
141
  });
142
+
143
+ it('breaks the path into segments at null holes', () => {
144
+ const gapped: [number, number | null][] = [
145
+ [0, 100],
146
+ [50, 80],
147
+ [100, null],
148
+ [150, 60],
149
+ [200, 40],
150
+ ];
151
+
152
+ const path = buildLinePath(gapped, 'linear');
153
+
154
+ expect(path).not.toBeNull();
155
+ expect(path!.match(/M/g)).toHaveLength(2);
156
+ expect(path).toBe('M0,100L50,80M150,60L200,40');
157
+ });
158
+
159
+ it('draws a single continuous segment when there are no holes', () => {
160
+ const path = buildLinePath(points, 'linear');
161
+
162
+ expect(path!.match(/M/g)).toHaveLength(1);
163
+ });
130
164
  });