@ledgerhq/lumen-ui-rnative-visualization 0.1.17 → 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 (219) hide show
  1. package/dist/module/lib/Components/Axis/XAxis/XAxis.js +4 -3
  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 +4 -3
  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 +30 -23
  10. package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -1
  11. package/dist/module/lib/Components/CartesianChart/ChartEmptyLabel/ChartEmptyLabel.js +61 -0
  12. package/dist/module/lib/Components/CartesianChart/ChartEmptyLabel/ChartEmptyLabel.js.map +1 -0
  13. package/dist/module/lib/Components/CartesianChart/ChartEmptyLabel/index.js +4 -0
  14. package/dist/module/lib/Components/CartesianChart/ChartEmptyLabel/index.js.map +1 -0
  15. package/dist/module/lib/Components/CartesianChart/{RevealClip/RevealClipDefs.js → RevealAnimation/RevealAnimationProvider.js} +13 -13
  16. package/dist/module/lib/Components/CartesianChart/RevealAnimation/RevealAnimationProvider.js.map +1 -0
  17. package/dist/module/lib/Components/CartesianChart/{RevealClip/RevealClipDefs.test.js → RevealAnimation/RevealAnimationProvider.test.js} +51 -12
  18. package/dist/module/lib/Components/CartesianChart/RevealAnimation/RevealAnimationProvider.test.js.map +1 -0
  19. package/dist/module/lib/Components/CartesianChart/RevealAnimation/context.js +35 -0
  20. package/dist/module/lib/Components/CartesianChart/RevealAnimation/context.js.map +1 -0
  21. package/dist/module/lib/Components/CartesianChart/RevealAnimation/index.js +5 -0
  22. package/dist/module/lib/Components/CartesianChart/RevealAnimation/index.js.map +1 -0
  23. package/dist/module/lib/Components/CartesianChart/RevealAnimation/types.js.map +1 -0
  24. package/dist/module/lib/Components/CartesianChart/RevealAnimation/utils.js +64 -0
  25. package/dist/module/lib/Components/CartesianChart/RevealAnimation/utils.js.map +1 -0
  26. package/dist/module/lib/Components/CartesianChart/hooks/useShimmerAnimation.js +37 -0
  27. package/dist/module/lib/Components/CartesianChart/hooks/useShimmerAnimation.js.map +1 -0
  28. package/dist/module/lib/Components/CartesianChart/utils.js +1 -1
  29. package/dist/module/lib/Components/CartesianChart/utils.js.map +1 -1
  30. package/dist/module/lib/Components/Line/Line.js +18 -11
  31. package/dist/module/lib/Components/Line/Line.js.map +1 -1
  32. package/dist/module/lib/Components/Line/constants.js +5 -0
  33. package/dist/module/lib/Components/Line/constants.js.map +1 -0
  34. package/dist/module/lib/Components/Line/types.js +2 -0
  35. package/dist/module/lib/Components/Line/utils.js +38 -11
  36. package/dist/module/lib/Components/Line/utils.js.map +1 -1
  37. package/dist/module/lib/Components/Line/utils.test.js +49 -3
  38. package/dist/module/lib/Components/Line/utils.test.js.map +1 -1
  39. package/dist/module/lib/Components/LineChart/LineChart.js +131 -27
  40. package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
  41. package/dist/module/lib/Components/LineChart/LineChart.test.js +147 -0
  42. package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
  43. package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.js +72 -0
  44. package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.js.map +1 -0
  45. package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.test.js +47 -0
  46. package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.test.js.map +1 -0
  47. package/dist/module/lib/Components/LineChart/LineChartEmptyState/index.js +4 -0
  48. package/dist/module/lib/Components/LineChart/LineChartEmptyState/index.js.map +1 -0
  49. package/dist/module/lib/Components/LineChart/LineChartEmptyState/types.js +4 -0
  50. package/dist/module/lib/Components/LineChart/LineChartEmptyState/types.js.map +1 -0
  51. package/dist/module/lib/Components/LineChart/LineChartEmptyState/utils.js +22 -0
  52. package/dist/module/lib/Components/LineChart/LineChartEmptyState/utils.js.map +1 -0
  53. package/dist/module/lib/Components/LineChart/LineChartEmptyState/utils.test.js +21 -0
  54. package/dist/module/lib/Components/LineChart/LineChartEmptyState/utils.test.js.map +1 -0
  55. package/dist/module/lib/Components/LineChart/utils.js +83 -0
  56. package/dist/module/lib/Components/LineChart/utils.js.map +1 -0
  57. package/dist/module/lib/Components/LineChart/utils.test.js +174 -0
  58. package/dist/module/lib/Components/LineChart/utils.test.js.map +1 -0
  59. package/dist/module/lib/Components/Point/Point.js +6 -4
  60. package/dist/module/lib/Components/Point/Point.js.map +1 -1
  61. package/dist/module/lib/Components/ReferenceLine/ReferenceLine.js +4 -4
  62. package/dist/module/lib/Components/ReferenceLine/ReferenceLine.js.map +1 -1
  63. package/dist/module/lib/Components/ReferenceLine/constants.js +2 -2
  64. package/dist/module/lib/Components/ReferenceLine/constants.js.map +1 -1
  65. package/dist/module/lib/Components/ReferenceLine/utils.js +2 -1
  66. package/dist/module/lib/Components/ReferenceLine/utils.js.map +1 -1
  67. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js +2 -1
  68. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js.map +1 -1
  69. package/dist/module/lib/Components/Scrubber/Scrubber.js +2 -2
  70. package/dist/module/lib/Components/Scrubber/Scrubber.js.map +1 -1
  71. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js +10 -2
  72. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js.map +1 -1
  73. package/dist/module/lib/Components/Scrubber/ScrubberProvider.test.js +79 -1
  74. package/dist/module/lib/Components/Scrubber/ScrubberProvider.test.js.map +1 -1
  75. package/dist/module/lib/Components/Scrubber/utils.js +3 -2
  76. package/dist/module/lib/Components/Scrubber/utils.js.map +1 -1
  77. package/dist/module/lib/utils/numbers.js +16 -0
  78. package/dist/module/lib/utils/numbers.js.map +1 -0
  79. package/dist/module/lib/utils/ticks/ticks.js +0 -16
  80. package/dist/module/lib/utils/ticks/ticks.js.map +1 -1
  81. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts +6 -0
  82. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts.map +1 -1
  83. package/dist/typescript/src/lib/Components/Axis/XAxis/XAxis.d.ts +1 -1
  84. package/dist/typescript/src/lib/Components/Axis/XAxis/XAxis.d.ts.map +1 -1
  85. package/dist/typescript/src/lib/Components/Axis/YAxis/YAxis.d.ts +1 -1
  86. package/dist/typescript/src/lib/Components/Axis/YAxis/YAxis.d.ts.map +1 -1
  87. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts +1 -1
  88. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts.map +1 -1
  89. package/dist/typescript/src/lib/Components/CartesianChart/ChartEmptyLabel/ChartEmptyLabel.d.ts +18 -0
  90. package/dist/typescript/src/lib/Components/CartesianChart/ChartEmptyLabel/ChartEmptyLabel.d.ts.map +1 -0
  91. package/dist/typescript/src/lib/Components/CartesianChart/ChartEmptyLabel/index.d.ts +2 -0
  92. package/dist/typescript/src/lib/Components/CartesianChart/ChartEmptyLabel/index.d.ts.map +1 -0
  93. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/RevealAnimationProvider.d.ts +3 -0
  94. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/RevealAnimationProvider.d.ts.map +1 -0
  95. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/context.d.ts +22 -0
  96. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/context.d.ts.map +1 -0
  97. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/index.d.ts +3 -0
  98. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/index.d.ts.map +1 -0
  99. package/dist/typescript/src/lib/Components/CartesianChart/{RevealClip → RevealAnimation}/types.d.ts +15 -2
  100. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/types.d.ts.map +1 -0
  101. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/utils.d.ts +33 -0
  102. package/dist/typescript/src/lib/Components/CartesianChart/RevealAnimation/utils.d.ts.map +1 -0
  103. package/dist/typescript/src/lib/Components/CartesianChart/hooks/useShimmerAnimation.d.ts +13 -0
  104. package/dist/typescript/src/lib/Components/CartesianChart/hooks/useShimmerAnimation.d.ts.map +1 -0
  105. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts +12 -0
  106. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts.map +1 -1
  107. package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts +1 -1
  108. package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts.map +1 -1
  109. package/dist/typescript/src/lib/Components/Line/Line.d.ts +1 -1
  110. package/dist/typescript/src/lib/Components/Line/Line.d.ts.map +1 -1
  111. package/dist/typescript/src/lib/Components/Line/constants.d.ts +3 -0
  112. package/dist/typescript/src/lib/Components/Line/constants.d.ts.map +1 -0
  113. package/dist/typescript/src/lib/Components/Line/index.d.ts +1 -1
  114. package/dist/typescript/src/lib/Components/Line/index.d.ts.map +1 -1
  115. package/dist/typescript/src/lib/Components/Line/types.d.ts +15 -0
  116. package/dist/typescript/src/lib/Components/Line/types.d.ts.map +1 -1
  117. package/dist/typescript/src/lib/Components/Line/utils.d.ts +11 -6
  118. package/dist/typescript/src/lib/Components/Line/utils.d.ts.map +1 -1
  119. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts +1 -1
  120. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts.map +1 -1
  121. package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.d.ts +9 -0
  122. package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.d.ts.map +1 -0
  123. package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/index.d.ts +3 -0
  124. package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/index.d.ts.map +1 -0
  125. package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/types.d.ts +3 -0
  126. package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/types.d.ts.map +1 -0
  127. package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/utils.d.ts +14 -0
  128. package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/utils.d.ts.map +1 -0
  129. package/dist/typescript/src/lib/Components/LineChart/types.d.ts +41 -1
  130. package/dist/typescript/src/lib/Components/LineChart/types.d.ts.map +1 -1
  131. package/dist/typescript/src/lib/Components/LineChart/utils.d.ts +43 -0
  132. package/dist/typescript/src/lib/Components/LineChart/utils.d.ts.map +1 -0
  133. package/dist/typescript/src/lib/Components/Point/Point.d.ts.map +1 -1
  134. package/dist/typescript/src/lib/Components/ReferenceLine/ReferenceLine.d.ts.map +1 -1
  135. package/dist/typescript/src/lib/Components/ReferenceLine/constants.d.ts +2 -2
  136. package/dist/typescript/src/lib/Components/ReferenceLine/constants.d.ts.map +1 -1
  137. package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts.map +1 -1
  138. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts.map +1 -1
  139. package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts.map +1 -1
  140. package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts.map +1 -1
  141. package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts.map +1 -1
  142. package/dist/typescript/src/lib/utils/index.d.ts +1 -1
  143. package/dist/typescript/src/lib/utils/index.d.ts.map +1 -1
  144. package/dist/typescript/src/lib/utils/numbers.d.ts +13 -0
  145. package/dist/typescript/src/lib/utils/numbers.d.ts.map +1 -0
  146. package/dist/typescript/src/lib/utils/ticks/ticks.d.ts +1 -11
  147. package/dist/typescript/src/lib/utils/ticks/ticks.d.ts.map +1 -1
  148. package/dist/typescript/src/lib/utils/types.d.ts +27 -2
  149. package/dist/typescript/src/lib/utils/types.d.ts.map +1 -1
  150. package/package.json +3 -3
  151. package/src/lib/Components/Axis/Axis.types.ts +6 -0
  152. package/src/lib/Components/Axis/XAxis/XAxis.test.tsx +12 -0
  153. package/src/lib/Components/Axis/XAxis/XAxis.tsx +29 -32
  154. package/src/lib/Components/Axis/YAxis/YAxis.test.tsx +12 -0
  155. package/src/lib/Components/Axis/YAxis/YAxis.tsx +29 -32
  156. package/src/lib/Components/CartesianChart/CartesianChart.tsx +30 -24
  157. package/src/lib/Components/CartesianChart/ChartEmptyLabel/ChartEmptyLabel.tsx +68 -0
  158. package/src/lib/Components/CartesianChart/ChartEmptyLabel/index.ts +1 -0
  159. package/src/lib/Components/CartesianChart/{RevealClip/RevealClipDefs.test.tsx → RevealAnimation/RevealAnimationProvider.test.tsx} +60 -17
  160. package/src/lib/Components/CartesianChart/{RevealClip/RevealClipDefs.tsx → RevealAnimation/RevealAnimationProvider.tsx} +11 -11
  161. package/src/lib/Components/CartesianChart/RevealAnimation/context.ts +39 -0
  162. package/src/lib/Components/CartesianChart/RevealAnimation/index.ts +2 -0
  163. package/src/lib/Components/CartesianChart/{RevealClip → RevealAnimation}/types.ts +16 -2
  164. package/src/lib/Components/CartesianChart/RevealAnimation/utils.ts +87 -0
  165. package/src/lib/Components/CartesianChart/hooks/useShimmerAnimation.ts +52 -0
  166. package/src/lib/Components/CartesianChart/types.ts +12 -0
  167. package/src/lib/Components/CartesianChart/utils.ts +2 -1
  168. package/src/lib/Components/Line/Line.tsx +26 -14
  169. package/src/lib/Components/Line/constants.ts +2 -0
  170. package/src/lib/Components/Line/index.ts +1 -1
  171. package/src/lib/Components/Line/types.ts +17 -0
  172. package/src/lib/Components/Line/utils.test.ts +77 -3
  173. package/src/lib/Components/Line/utils.ts +63 -12
  174. package/src/lib/Components/LineChart/LineChart.test.tsx +135 -0
  175. package/src/lib/Components/LineChart/LineChart.tsx +157 -38
  176. package/src/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.test.tsx +39 -0
  177. package/src/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.tsx +72 -0
  178. package/src/lib/Components/LineChart/LineChartEmptyState/index.ts +2 -0
  179. package/src/lib/Components/LineChart/LineChartEmptyState/types.ts +3 -0
  180. package/src/lib/Components/LineChart/LineChartEmptyState/utils.test.ts +22 -0
  181. package/src/lib/Components/LineChart/LineChartEmptyState/utils.ts +24 -0
  182. package/src/lib/Components/LineChart/types.ts +49 -1
  183. package/src/lib/Components/LineChart/utils.test.ts +196 -0
  184. package/src/lib/Components/LineChart/utils.ts +107 -0
  185. package/src/lib/Components/Point/Point.tsx +7 -4
  186. package/src/lib/Components/ReferenceLine/ReferenceLine.tsx +8 -4
  187. package/src/lib/Components/ReferenceLine/constants.ts +2 -2
  188. package/src/lib/Components/ReferenceLine/utils.ts +2 -1
  189. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.ts +2 -1
  190. package/src/lib/Components/Scrubber/Scrubber.tsx +13 -2
  191. package/src/lib/Components/Scrubber/ScrubberProvider.test.tsx +83 -1
  192. package/src/lib/Components/Scrubber/ScrubberProvider.tsx +17 -2
  193. package/src/lib/Components/Scrubber/utils.ts +3 -5
  194. package/src/lib/utils/index.ts +1 -0
  195. package/src/lib/utils/numbers.ts +15 -0
  196. package/src/lib/utils/ticks/ticks.ts +1 -23
  197. package/src/lib/utils/types.ts +35 -2
  198. package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.js.map +0 -1
  199. package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.test.js.map +0 -1
  200. package/dist/module/lib/Components/CartesianChart/RevealClip/context.js +0 -8
  201. package/dist/module/lib/Components/CartesianChart/RevealClip/context.js.map +0 -1
  202. package/dist/module/lib/Components/CartesianChart/RevealClip/index.js +0 -5
  203. package/dist/module/lib/Components/CartesianChart/RevealClip/index.js.map +0 -1
  204. package/dist/module/lib/Components/CartesianChart/RevealClip/types.js.map +0 -1
  205. package/dist/module/lib/Components/CartesianChart/RevealClip/utils.js +0 -32
  206. package/dist/module/lib/Components/CartesianChart/RevealClip/utils.js.map +0 -1
  207. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.d.ts +0 -3
  208. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.d.ts.map +0 -1
  209. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/context.d.ts +0 -7
  210. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/context.d.ts.map +0 -1
  211. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/index.d.ts +0 -3
  212. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/index.d.ts.map +0 -1
  213. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/types.d.ts.map +0 -1
  214. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/utils.d.ts +0 -17
  215. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/utils.d.ts.map +0 -1
  216. package/src/lib/Components/CartesianChart/RevealClip/context.ts +0 -13
  217. package/src/lib/Components/CartesianChart/RevealClip/index.ts +0 -2
  218. package/src/lib/Components/CartesianChart/RevealClip/utils.ts +0 -51
  219. /package/dist/module/lib/Components/CartesianChart/{RevealClip → RevealAnimation}/types.js +0 -0
@@ -0,0 +1,52 @@
1
+ import { useEffect } from 'react';
2
+ import {
3
+ cancelAnimation,
4
+ Easing,
5
+ useAnimatedProps,
6
+ useReducedMotion,
7
+ useSharedValue,
8
+ withRepeat,
9
+ withTiming,
10
+ } from 'react-native-reanimated';
11
+
12
+ const HALF_PULSE_DURATION_IN_MS = 1000;
13
+ const MIN_OPACITY = 0.5;
14
+
15
+ type ShimmerAnimationResult = {
16
+ animatedProps: ReturnType<typeof useAnimatedProps>;
17
+ };
18
+
19
+ /**
20
+ * Self-contained shimmer (opacity pulse) for SVG content, the RN counterpart of
21
+ * the web's CSS-keyframe shimmer. Shared by the initial loading placeholder and
22
+ * the transition-loading line. Honors the user's reduced-motion preference by
23
+ * keeping the content fully opaque.
24
+ */
25
+ export const useShimmerAnimation = (animate = true): ShimmerAnimationResult => {
26
+ const opacity = useSharedValue(1);
27
+ const reducedMotion = useReducedMotion();
28
+
29
+ useEffect(() => {
30
+ if (animate && !reducedMotion) {
31
+ opacity.value = withRepeat(
32
+ withTiming(MIN_OPACITY, {
33
+ duration: HALF_PULSE_DURATION_IN_MS,
34
+ easing: Easing.inOut(Easing.ease),
35
+ }),
36
+ -1,
37
+ true,
38
+ );
39
+ } else {
40
+ cancelAnimation(opacity);
41
+ opacity.value = 1;
42
+ }
43
+
44
+ return () => {
45
+ cancelAnimation(opacity);
46
+ };
47
+ }, [animate, reducedMotion, opacity]);
48
+
49
+ const animatedProps = useAnimatedProps(() => ({ opacity: opacity.value }));
50
+
51
+ return { animatedProps };
52
+ };
@@ -43,6 +43,18 @@ export type CartesianChartProps = {
43
43
  * @default 'Chart'
44
44
  */
45
45
  ariaLabel?: string;
46
+ /**
47
+ * Marks the chart as busy for assistive technologies (via the container's
48
+ * accessibility state) to signal that its content is loading.
49
+ * @default false
50
+ */
51
+ ariaBusy?: boolean;
52
+ /**
53
+ * Content rendered as an absolutely-positioned overlay above the chart SVG,
54
+ * within the same positioned container (so it aligns with the chart
55
+ * footprint). Used for e.g. an empty-state label.
56
+ */
57
+ overlay?: ReactNode;
46
58
  /**
47
59
  * SVG content rendered inside the chart's context provider.
48
60
  */
@@ -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,
@@ -1,53 +1,65 @@
1
+ import { useTheme } from '@ledgerhq/lumen-ui-rnative';
1
2
  import { useId, useMemo } from 'react';
2
3
  import { Defs, G, LinearGradient, Path, Stop } from 'react-native-svg';
3
4
 
4
5
  import { isNumericScale } from '../../utils/scales/scales';
5
6
  import { useCartesianChartContext } from '../CartesianChart/context';
6
- import { useRevealClip } from '../CartesianChart/RevealClip';
7
+ import { usePathReveal } from '../CartesianChart/RevealAnimation';
7
8
 
9
+ import { LINE_AREA_GRADIENT_OPACITY, LINE_STROKE_WIDTH } from './constants';
8
10
  import type { LineProps } from './types';
9
11
  import { buildAreaPath, buildLinePath, toScaledPoints } from './utils';
10
12
 
11
- const STROKE_WIDTH = 2;
12
- const AREA_GRADIENT_OPACITY = 0.2;
13
-
14
13
  export const Line = ({
15
14
  seriesId,
16
15
  stroke,
17
16
  showArea = false,
18
17
  areaType: _areaType = 'gradient',
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();
26
27
  const xAxisConfig = getXAxisConfig();
27
28
 
29
+ const { theme } = useTheme();
28
30
  const gradientId = useId();
29
31
  const seriesData = seriesMap.get(seriesId);
30
- const resolvedStroke = stroke ?? seriesData?.stroke;
32
+ const resolvedStroke =
33
+ stroke ?? seriesData?.stroke ?? theme.colors.border.muted;
34
+ const resolvedCurve = curve ?? seriesData?.curve;
35
+ const resolvedConnectNulls =
36
+ connectNulls ?? seriesData?.connectNulls ?? false;
31
37
 
32
38
  const points = useMemo(
33
39
  () =>
34
40
  seriesData?.data && xScale && yScale && isNumericScale(yScale)
35
- ? toScaledPoints(seriesData.data, xScale, yScale, xAxisConfig?.data)
41
+ ? toScaledPoints(
42
+ seriesData.data,
43
+ xScale,
44
+ yScale,
45
+ xAxisConfig?.data,
46
+ resolvedConnectNulls,
47
+ )
36
48
  : null,
37
- [seriesData, xScale, yScale, xAxisConfig],
49
+ [seriesData, xScale, yScale, xAxisConfig, resolvedConnectNulls],
38
50
  );
39
51
 
40
52
  const linePath = useMemo(
41
- () => (points ? buildLinePath(points) : null),
42
- [points],
53
+ () => (points ? buildLinePath(points, resolvedCurve) : null),
54
+ [points, resolvedCurve],
43
55
  );
44
56
 
45
57
  const areaPath = useMemo(
46
58
  () =>
47
59
  showArea && points && drawingArea
48
- ? buildAreaPath(points, drawingArea)
60
+ ? buildAreaPath(points, drawingArea, resolvedCurve)
49
61
  : null,
50
- [showArea, points, drawingArea],
62
+ [showArea, points, drawingArea, resolvedCurve],
51
63
  );
52
64
 
53
65
  if (!linePath) {
@@ -63,7 +75,7 @@ export const Line = ({
63
75
  <Stop
64
76
  offset='0%'
65
77
  stopColor={resolvedStroke}
66
- stopOpacity={AREA_GRADIENT_OPACITY}
78
+ stopOpacity={LINE_AREA_GRADIENT_OPACITY}
67
79
  />
68
80
  <Stop offset='100%' stopColor={resolvedStroke} stopOpacity={0} />
69
81
  </LinearGradient>
@@ -81,7 +93,7 @@ export const Line = ({
81
93
  d={linePath}
82
94
  fill='none'
83
95
  stroke={resolvedStroke}
84
- strokeWidth={STROKE_WIDTH}
96
+ strokeWidth={LINE_STROKE_WIDTH}
85
97
  strokeLinecap='round'
86
98
  strokeLinejoin='round'
87
99
  />
@@ -0,0 +1,2 @@
1
+ export const LINE_STROKE_WIDTH = 2;
2
+ export const LINE_AREA_GRADIENT_OPACITY = 0.25;
@@ -1,2 +1,2 @@
1
1
  export { Line } from './Line';
2
- export type { LineProps } from './types';
2
+ export type { CurveType, LineProps } from './types';
@@ -1,3 +1,7 @@
1
+ import type { CurveType } from '../../utils/types';
2
+
3
+ export type { CurveType };
4
+
1
5
  export type LineProps = {
2
6
  /**
3
7
  * The ID of the series to render.
@@ -20,4 +24,17 @@ export type LineProps = {
20
24
  * @default 'gradient'
21
25
  */
22
26
  areaType?: 'gradient';
27
+ /**
28
+ * Override interpolation used to draw the line between data points.
29
+ * When omitted, falls back to the `curve` defined on the series.
30
+ * @default 'bump'
31
+ */
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;
23
40
  };
@@ -5,7 +5,7 @@ import {
5
5
  getNumericScale,
6
6
  } from '../../utils/scales/scales';
7
7
 
8
- import { toScaledPoints } from './utils';
8
+ import { buildLinePath, toScaledPoints } from './utils';
9
9
 
10
10
  const xScale = getNumericScale({
11
11
  scaleType: 'linear',
@@ -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];
@@ -88,3 +100,65 @@ describe('toScaledPoints', () => {
88
100
  expect(toScaledPoints(data, xScale, yScale, xData)).toBeNull();
89
101
  });
90
102
  });
103
+
104
+ describe('buildLinePath', () => {
105
+ const points: [number, number][] = [
106
+ [0, 100],
107
+ [50, 20],
108
+ [100, 60],
109
+ ];
110
+
111
+ it('defaults to the bump curve (cubic beziers)', () => {
112
+ const path = buildLinePath(points);
113
+
114
+ expect(path).not.toBeNull();
115
+ expect(path).toContain('C');
116
+ });
117
+
118
+ it('draws straight segments for the linear curve', () => {
119
+ const path = buildLinePath(points, 'linear');
120
+
121
+ expect(path).toBe('M0,100L50,20L100,60');
122
+ });
123
+
124
+ it('uses cubic beziers for smooth curves', () => {
125
+ expect(buildLinePath(points, 'natural')).toContain('C');
126
+ expect(buildLinePath(points, 'monotone')).toContain('C');
127
+ });
128
+
129
+ it('draws orthogonal segments for the step curve', () => {
130
+ const path = buildLinePath(points, 'step');
131
+
132
+ expect(path).not.toBeNull();
133
+ expect(path).not.toContain('C');
134
+ expect(path).toBe('M0,100L25,100L25,20L75,20L75,60L100,60');
135
+ });
136
+
137
+ it('falls back to the default curve for unknown values', () => {
138
+ const path = buildLinePath(points, 'unknown' as never);
139
+
140
+ expect(path).toBe(buildLinePath(points));
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
+ });
164
+ });
@@ -1,16 +1,47 @@
1
- import { area, curveBumpX, line } from 'd3-shape';
1
+ import {
2
+ area,
3
+ curveBumpX,
4
+ curveLinear,
5
+ curveMonotoneX,
6
+ curveNatural,
7
+ curveStep,
8
+ curveStepAfter,
9
+ curveStepBefore,
10
+ line,
11
+ type CurveFactory,
12
+ } from 'd3-shape';
2
13
 
14
+ import { isFiniteNumber } from '../../utils/numbers';
3
15
  import { isCategoricalScale } from '../../utils/scales/scales';
4
16
  import type {
5
17
  ChartScaleFunction,
18
+ CurveType,
6
19
  DrawingArea,
7
20
  NumericScale,
8
21
  } from '../../utils/types';
9
22
 
10
- type Point = [x: number, y: number];
23
+ type Point = [x: number, y: number | null];
11
24
 
12
25
  /**
13
- * Project series data into scaled [x, y] pixel coordinates, skipping nulls.
26
+ * Maps the public `CurveType` values to their d3-shape curve factories.
27
+ */
28
+ const CURVE_FACTORIES: Record<CurveType, CurveFactory> = {
29
+ bump: curveBumpX,
30
+ linear: curveLinear,
31
+ monotone: curveMonotoneX,
32
+ natural: curveNatural,
33
+ step: curveStep,
34
+ stepAfter: curveStepAfter,
35
+ stepBefore: curveStepBefore,
36
+ };
37
+
38
+ const DEFAULT_CURVE: CurveType = 'bump';
39
+
40
+ const getCurveFactory = (curve: CurveType = DEFAULT_CURVE): CurveFactory =>
41
+ CURVE_FACTORIES[curve] ?? CURVE_FACTORIES[DEFAULT_CURVE];
42
+
43
+ /**
44
+ * Project series data into scaled [x, y] pixel coordinates.
14
45
  *
15
46
  * When `xData` contains numeric values, those values are fed into the scale
16
47
  * instead of the array index so the points honour a numeric X domain.
@@ -18,19 +49,28 @@ type Point = [x: number, y: number];
18
49
  * When `xData` is provided, iteration is capped at `xData.length` so a series
19
50
  * with more points than the axis has labels does not overflow past the right
20
51
  * edge of the chart. The axis is treated as authoritative for the X domain.
52
+ *
53
+ * Non-finite values (e.g. `null`) are handled according to `connectNulls`:
54
+ * - `false` (default): kept as `[x, null]` holes so the line/area break into
55
+ * gaps via the generators' `.defined()` check.
56
+ * - `true`: skipped entirely so the line is drawn continuously across gaps.
21
57
  */
22
58
  export const toScaledPoints = (
23
59
  data: (number | null)[],
24
60
  xScale: ChartScaleFunction,
25
61
  yScale: NumericScale,
26
62
  xData?: readonly (string | number)[],
63
+ connectNulls = false,
27
64
  ): Point[] | null => {
28
65
  const pts: Point[] = [];
29
66
  const limit = xData ? Math.min(data.length, xData.length) : data.length;
67
+ let finiteCount = 0;
30
68
 
31
69
  for (let i = 0; i < limit; i++) {
32
70
  const value = data[i];
33
- if (value === null) continue;
71
+ const isFiniteValue = isFiniteNumber(value);
72
+
73
+ if (!isFiniteValue && connectNulls) continue;
34
74
 
35
75
  const xInput =
36
76
  xData && typeof xData[i] === 'number' ? (xData[i] as number) : i;
@@ -38,23 +78,32 @@ export const toScaledPoints = (
38
78
  const x = isCategoricalScale(xScale)
39
79
  ? (xScale(xInput) ?? 0) + xScale.bandwidth() / 2
40
80
  : xScale(xInput);
41
- const y = yScale(value);
42
81
 
43
- pts.push([x as number, y]);
82
+ if (!isFiniteValue) {
83
+ pts.push([x as number, null]);
84
+ continue;
85
+ }
86
+
87
+ pts.push([x as number, yScale(value)]);
88
+ finiteCount++;
44
89
  }
45
90
 
46
- return pts.length >= 2 ? pts : null;
91
+ return finiteCount >= 2 ? pts : null;
47
92
  };
48
93
 
49
94
  /**
50
95
  * Build the SVG `d` attribute for the line stroke.
51
96
  */
52
- export const buildLinePath = (points: Point[]): string | null => {
97
+ export const buildLinePath = (
98
+ points: Point[],
99
+ curve?: CurveType,
100
+ ): string | null => {
53
101
  return (
54
102
  line<Point>()
103
+ .defined((d) => d[1] !== null)
55
104
  .x((d) => d[0])
56
- .y((d) => d[1])
57
- .curve(curveBumpX)(points) ?? null
105
+ .y((d) => d[1] as number)
106
+ .curve(getCurveFactory(curve))(points) ?? null
58
107
  );
59
108
  };
60
109
 
@@ -64,14 +113,16 @@ export const buildLinePath = (points: Point[]): string | null => {
64
113
  export const buildAreaPath = (
65
114
  points: Point[],
66
115
  drawingArea: DrawingArea,
116
+ curve?: CurveType,
67
117
  ): string | null => {
68
118
  const yBottom = drawingArea.y + drawingArea.height;
69
119
 
70
120
  return (
71
121
  area<Point>()
122
+ .defined((d) => d[1] !== null)
72
123
  .x((d) => d[0])
73
124
  .y0(yBottom)
74
- .y1((d) => d[1])
75
- .curve(curveBumpX)(points) ?? null
125
+ .y1((d) => d[1] as number)
126
+ .curve(getCurveFactory(curve))(points) ?? null
76
127
  );
77
128
  };
@@ -255,4 +255,139 @@ describe('LineChart', () => {
255
255
  );
256
256
  getByTestId('chart-svg');
257
257
  });
258
+
259
+ describe('loading and empty states', () => {
260
+ it('renders the placeholder with no data while loading (state 1)', () => {
261
+ const { getByTestId, queryByTestId } = render(
262
+ <LineChartWrapper>
263
+ <LineChart width={400} height={200} loading />
264
+ </LineChartWrapper>,
265
+ );
266
+
267
+ getByTestId('chart-empty-state');
268
+ expect(queryByTestId('line-path')).toBeNull();
269
+ expect(queryByTestId('chart-empty-label')).toBeNull();
270
+ });
271
+
272
+ it('renders the placeholder with the empty label when there is no data and not loading (state 2)', () => {
273
+ const { getByTestId, queryByTestId } = render(
274
+ <LineChartWrapper>
275
+ <LineChart width={400} height={200} emptyLabel='Nothing here' />
276
+ </LineChartWrapper>,
277
+ );
278
+
279
+ getByTestId('chart-empty-state');
280
+ expect(
281
+ getByTestId('chart-empty-label', { includeHiddenElements: true }),
282
+ ).toHaveTextContent('Nothing here');
283
+ expect(queryByTestId('line-path')).toBeNull();
284
+ });
285
+
286
+ it('defaults the empty label to "No data"', () => {
287
+ const { getByTestId } = render(
288
+ <LineChartWrapper>
289
+ <LineChart width={400} height={200} />
290
+ </LineChartWrapper>,
291
+ );
292
+
293
+ expect(
294
+ getByTestId('chart-empty-label', { includeHiddenElements: true }),
295
+ ).toHaveTextContent('No data');
296
+ });
297
+
298
+ it('keeps rendering the real line during a transition load (state 3)', () => {
299
+ const { getByTestId, queryByTestId } = render(
300
+ <LineChartWrapper>
301
+ <LineChart series={sampleSeries} width={400} height={200} loading />
302
+ </LineChartWrapper>,
303
+ );
304
+
305
+ getByTestId('line-path');
306
+ expect(queryByTestId('chart-empty-state')).toBeNull();
307
+ expect(queryByTestId('chart-empty-label')).toBeNull();
308
+ });
309
+
310
+ it('renders the normal chart when idle with data (state 4)', () => {
311
+ const { getByTestId, queryByTestId } = render(
312
+ <LineChartWrapper>
313
+ <LineChart series={sampleSeries} width={400} height={200} />
314
+ </LineChartWrapper>,
315
+ );
316
+
317
+ getByTestId('line-path');
318
+ expect(queryByTestId('chart-empty-state')).toBeNull();
319
+ expect(queryByTestId('chart-empty-label')).toBeNull();
320
+ });
321
+ });
322
+
323
+ describe('null gaps and connectNulls', () => {
324
+ const gappedSeries = [
325
+ {
326
+ id: 'gapped',
327
+ stroke: '#000',
328
+ data: [10, 20, null, 40, 50],
329
+ curve: 'linear' as const,
330
+ },
331
+ ];
332
+
333
+ const countMoves = (d: string): number => d.match(/M/g)?.length ?? 0;
334
+
335
+ it('breaks the line into segments at null values by default', () => {
336
+ const { getByTestId } = render(
337
+ <LineChartWrapper>
338
+ <LineChart series={gappedSeries} width={400} height={200} />
339
+ </LineChartWrapper>,
340
+ );
341
+
342
+ const d = getByTestId('line-path').props.d as string;
343
+ expect(countMoves(d)).toBe(2);
344
+ });
345
+
346
+ it('connects across nulls when connectNulls is set on the chart', () => {
347
+ const { getByTestId } = render(
348
+ <LineChartWrapper>
349
+ <LineChart
350
+ series={gappedSeries}
351
+ width={400}
352
+ height={200}
353
+ connectNulls
354
+ />
355
+ </LineChartWrapper>,
356
+ );
357
+
358
+ const d = getByTestId('line-path').props.d as string;
359
+ expect(countMoves(d)).toBe(1);
360
+ });
361
+
362
+ it('honours connectNulls set per-series', () => {
363
+ const { getByTestId } = render(
364
+ <LineChartWrapper>
365
+ <LineChart
366
+ series={[{ ...gappedSeries[0], connectNulls: true }]}
367
+ width={400}
368
+ height={200}
369
+ />
370
+ </LineChartWrapper>,
371
+ );
372
+
373
+ const d = getByTestId('line-path').props.d as string;
374
+ expect(countMoves(d)).toBe(1);
375
+ });
376
+
377
+ it('lets the chart-level prop override the per-series value', () => {
378
+ const { getByTestId } = render(
379
+ <LineChartWrapper>
380
+ <LineChart
381
+ series={[{ ...gappedSeries[0], connectNulls: true }]}
382
+ width={400}
383
+ height={200}
384
+ connectNulls={false}
385
+ />
386
+ </LineChartWrapper>,
387
+ );
388
+
389
+ const d = getByTestId('line-path').props.d as string;
390
+ expect(countMoves(d)).toBe(2);
391
+ });
392
+ });
258
393
  });