@ledgerhq/lumen-ui-rnative-visualization 0.1.13 → 0.1.15

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 (182) hide show
  1. package/dist/module/lib/Components/Axis/Axis.constants.js +21 -0
  2. package/dist/module/lib/Components/Axis/Axis.constants.js.map +1 -0
  3. package/dist/module/lib/Components/Axis/index.js +5 -0
  4. package/dist/module/lib/Components/Axis/index.js.map +1 -0
  5. package/dist/module/lib/Components/CartesianChart/CartesianChart.js +34 -22
  6. package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -1
  7. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js +5 -4
  8. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js.map +1 -1
  9. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js +54 -0
  10. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js.map +1 -1
  11. package/dist/module/lib/Components/CartesianChart/utils.js +18 -8
  12. package/dist/module/lib/Components/CartesianChart/utils.js.map +1 -1
  13. package/dist/module/lib/Components/Line/utils.js +6 -1
  14. package/dist/module/lib/Components/Line/utils.js.map +1 -1
  15. package/dist/module/lib/Components/Line/utils.test.js +88 -0
  16. package/dist/module/lib/Components/Line/utils.test.js.map +1 -0
  17. package/dist/module/lib/Components/LineChart/LineChart.js +19 -31
  18. package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
  19. package/dist/module/lib/Components/LineChart/LineChart.stories.js +38 -0
  20. package/dist/module/lib/Components/LineChart/LineChart.stories.js.map +1 -1
  21. package/dist/module/lib/Components/LineChart/LineChart.test.js +192 -0
  22. package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
  23. package/dist/module/lib/Components/Point/Point.js +8 -2
  24. package/dist/module/lib/Components/Point/Point.js.map +1 -1
  25. package/dist/module/lib/Components/Point/constants.js +9 -0
  26. package/dist/module/lib/Components/Point/constants.js.map +1 -0
  27. package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.js +35 -0
  28. package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.js.map +1 -0
  29. package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.test.js +153 -0
  30. package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.test.js.map +1 -0
  31. package/dist/module/lib/Components/Point/pointContext/index.js +5 -0
  32. package/dist/module/lib/Components/Point/pointContext/index.js.map +1 -0
  33. package/dist/module/lib/Components/Point/pointContext/magneticPointsContext.js +10 -0
  34. package/dist/module/lib/Components/Point/pointContext/magneticPointsContext.js.map +1 -0
  35. package/dist/module/lib/Components/Point/utils.js +26 -6
  36. package/dist/module/lib/Components/Point/utils.js.map +1 -1
  37. package/dist/module/lib/Components/Point/utils.test.js +84 -2
  38. package/dist/module/lib/Components/Point/utils.test.js.map +1 -1
  39. package/dist/module/lib/Components/ReferenceLine/utils.js.map +1 -1
  40. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.js +52 -0
  41. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.js.map +1 -0
  42. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.js +81 -0
  43. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.js.map +1 -0
  44. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.js +340 -0
  45. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.js.map +1 -0
  46. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/constants.js +12 -0
  47. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/constants.js.map +1 -0
  48. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js +166 -0
  49. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js.map +1 -0
  50. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.js +253 -0
  51. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.js.map +1 -0
  52. package/dist/module/lib/Components/Scrubber/Scrubber.js +40 -21
  53. package/dist/module/lib/Components/Scrubber/Scrubber.js.map +1 -1
  54. package/dist/module/lib/Components/Scrubber/Scrubber.stories.js +80 -30
  55. package/dist/module/lib/Components/Scrubber/Scrubber.stories.js.map +1 -1
  56. package/dist/module/lib/Components/Scrubber/Scrubber.test.js +10 -5
  57. package/dist/module/lib/Components/Scrubber/Scrubber.test.js.map +1 -1
  58. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js +81 -29
  59. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js.map +1 -1
  60. package/dist/module/lib/Components/Scrubber/utils.js +64 -0
  61. package/dist/module/lib/Components/Scrubber/utils.js.map +1 -1
  62. package/dist/module/lib/Components/Scrubber/utils.test.js +128 -1
  63. package/dist/module/lib/Components/Scrubber/utils.test.js.map +1 -1
  64. package/dist/module/lib/utils/domain/domain.js.map +1 -1
  65. package/dist/module/lib/utils/scales/scales.js +13 -4
  66. package/dist/module/lib/utils/scales/scales.js.map +1 -1
  67. package/dist/module/lib/utils/scales/scales.test.js +29 -0
  68. package/dist/module/lib/utils/scales/scales.test.js.map +1 -1
  69. package/dist/module/lib/utils/ticks/ticks.js +13 -3
  70. package/dist/module/lib/utils/ticks/ticks.js.map +1 -1
  71. package/dist/module/lib/utils/ticks/ticks.test.js +34 -0
  72. package/dist/module/lib/utils/ticks/ticks.test.js.map +1 -1
  73. package/dist/typescript/src/lib/Components/Axis/Axis.constants.d.ts +5 -0
  74. package/dist/typescript/src/lib/Components/Axis/Axis.constants.d.ts.map +1 -0
  75. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts +39 -0
  76. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts.map +1 -1
  77. package/dist/typescript/src/lib/Components/Axis/index.d.ts +3 -0
  78. package/dist/typescript/src/lib/Components/Axis/index.d.ts.map +1 -0
  79. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts +1 -1
  80. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts.map +1 -1
  81. package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts +5 -4
  82. package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts.map +1 -1
  83. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts +11 -3
  84. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts.map +1 -1
  85. package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts +18 -8
  86. package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts.map +1 -1
  87. package/dist/typescript/src/lib/Components/Line/utils.d.ts +4 -0
  88. package/dist/typescript/src/lib/Components/Line/utils.d.ts.map +1 -1
  89. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts +1 -1
  90. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts.map +1 -1
  91. package/dist/typescript/src/lib/Components/LineChart/types.d.ts +10 -3
  92. package/dist/typescript/src/lib/Components/LineChart/types.d.ts.map +1 -1
  93. package/dist/typescript/src/lib/Components/Point/Point.d.ts +1 -1
  94. package/dist/typescript/src/lib/Components/Point/Point.d.ts.map +1 -1
  95. package/dist/typescript/src/lib/Components/Point/constants.d.ts +7 -0
  96. package/dist/typescript/src/lib/Components/Point/constants.d.ts.map +1 -0
  97. package/dist/typescript/src/lib/Components/Point/pointContext/MagneticPointsProvider.d.ts +7 -0
  98. package/dist/typescript/src/lib/Components/Point/pointContext/MagneticPointsProvider.d.ts.map +1 -0
  99. package/dist/typescript/src/lib/Components/Point/pointContext/index.d.ts +3 -0
  100. package/dist/typescript/src/lib/Components/Point/pointContext/index.d.ts.map +1 -0
  101. package/dist/typescript/src/lib/Components/Point/pointContext/magneticPointsContext.d.ts +13 -0
  102. package/dist/typescript/src/lib/Components/Point/pointContext/magneticPointsContext.d.ts.map +1 -0
  103. package/dist/typescript/src/lib/Components/Point/types.d.ts +7 -0
  104. package/dist/typescript/src/lib/Components/Point/types.d.ts.map +1 -1
  105. package/dist/typescript/src/lib/Components/Point/utils.d.ts +8 -6
  106. package/dist/typescript/src/lib/Components/Point/utils.d.ts.map +1 -1
  107. package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts +3 -2
  108. package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts.map +1 -1
  109. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.d.ts +9 -0
  110. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.d.ts.map +1 -0
  111. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.d.ts +12 -0
  112. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.d.ts.map +1 -0
  113. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.d.ts +10 -0
  114. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.d.ts.map +1 -0
  115. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts +69 -0
  116. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts.map +1 -0
  117. package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts +14 -2
  118. package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts.map +1 -1
  119. package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts +1 -1
  120. package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts.map +1 -1
  121. package/dist/typescript/src/lib/Components/Scrubber/index.d.ts +1 -1
  122. package/dist/typescript/src/lib/Components/Scrubber/index.d.ts.map +1 -1
  123. package/dist/typescript/src/lib/Components/Scrubber/types.d.ts +116 -4
  124. package/dist/typescript/src/lib/Components/Scrubber/types.d.ts.map +1 -1
  125. package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts +33 -3
  126. package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts.map +1 -1
  127. package/dist/typescript/src/lib/utils/domain/domain.d.ts +5 -4
  128. package/dist/typescript/src/lib/utils/domain/domain.d.ts.map +1 -1
  129. package/dist/typescript/src/lib/utils/index.d.ts +1 -1
  130. package/dist/typescript/src/lib/utils/index.d.ts.map +1 -1
  131. package/dist/typescript/src/lib/utils/scales/scales.d.ts +11 -5
  132. package/dist/typescript/src/lib/utils/scales/scales.d.ts.map +1 -1
  133. package/dist/typescript/src/lib/utils/ticks/ticks.d.ts +13 -5
  134. package/dist/typescript/src/lib/utils/ticks/ticks.d.ts.map +1 -1
  135. package/dist/typescript/src/lib/utils/types.d.ts +3 -28
  136. package/dist/typescript/src/lib/utils/types.d.ts.map +1 -1
  137. package/package.json +4 -4
  138. package/src/lib/Components/Axis/Axis.constants.ts +23 -0
  139. package/src/lib/Components/Axis/Axis.types.ts +40 -0
  140. package/src/lib/Components/Axis/index.ts +2 -0
  141. package/src/lib/Components/CartesianChart/CartesianChart.tsx +39 -24
  142. package/src/lib/Components/CartesianChart/context/useBuildChartContext.test.ts +56 -0
  143. package/src/lib/Components/CartesianChart/context/useBuildChartContext.ts +8 -8
  144. package/src/lib/Components/CartesianChart/types.ts +11 -3
  145. package/src/lib/Components/CartesianChart/utils.ts +18 -8
  146. package/src/lib/Components/Line/utils.test.ts +90 -0
  147. package/src/lib/Components/Line/utils.ts +6 -1
  148. package/src/lib/Components/LineChart/LineChart.stories.tsx +36 -0
  149. package/src/lib/Components/LineChart/LineChart.test.tsx +156 -0
  150. package/src/lib/Components/LineChart/LineChart.tsx +30 -40
  151. package/src/lib/Components/LineChart/types.ts +10 -3
  152. package/src/lib/Components/Point/Point.tsx +9 -3
  153. package/src/lib/Components/Point/constants.ts +6 -0
  154. package/src/lib/Components/Point/pointContext/MagneticPointsProvider.test.tsx +162 -0
  155. package/src/lib/Components/Point/pointContext/MagneticPointsProvider.tsx +45 -0
  156. package/src/lib/Components/Point/pointContext/index.ts +5 -0
  157. package/src/lib/Components/Point/pointContext/magneticPointsContext.ts +19 -0
  158. package/src/lib/Components/Point/types.ts +7 -0
  159. package/src/lib/Components/Point/utils.test.ts +95 -5
  160. package/src/lib/Components/Point/utils.ts +35 -7
  161. package/src/lib/Components/ReferenceLine/utils.ts +4 -7
  162. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.tsx +54 -0
  163. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.tsx +259 -0
  164. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.tsx +105 -0
  165. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.ts +9 -0
  166. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.ts +264 -0
  167. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.ts +243 -0
  168. package/src/lib/Components/Scrubber/Scrubber.stories.tsx +104 -42
  169. package/src/lib/Components/Scrubber/Scrubber.test.tsx +7 -5
  170. package/src/lib/Components/Scrubber/Scrubber.tsx +47 -24
  171. package/src/lib/Components/Scrubber/ScrubberProvider.tsx +92 -28
  172. package/src/lib/Components/Scrubber/index.ts +10 -1
  173. package/src/lib/Components/Scrubber/types.ts +122 -4
  174. package/src/lib/Components/Scrubber/utils.test.ts +150 -0
  175. package/src/lib/Components/Scrubber/utils.ts +93 -3
  176. package/src/lib/utils/domain/domain.ts +6 -5
  177. package/src/lib/utils/index.ts +0 -2
  178. package/src/lib/utils/scales/scales.test.ts +19 -0
  179. package/src/lib/utils/scales/scales.ts +15 -9
  180. package/src/lib/utils/ticks/ticks.test.ts +45 -0
  181. package/src/lib/utils/ticks/ticks.ts +19 -9
  182. package/src/lib/utils/types.ts +3 -26
@@ -2,6 +2,7 @@ import { describe, it, expect } from '@jest/globals';
2
2
  import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
3
  import { ThemeProvider } from '@ledgerhq/lumen-ui-rnative';
4
4
  import { render } from '@testing-library/react-native';
5
+ import { Circle } from 'react-native-svg';
5
6
 
6
7
  import { LineChart } from './LineChart';
7
8
 
@@ -99,4 +100,159 @@ describe('LineChart', () => {
99
100
  );
100
101
  getByTestId('chart-svg');
101
102
  });
103
+
104
+ it('renders a Path for each series when multiple series are provided', () => {
105
+ const multipleSeries = [
106
+ { id: 'a', stroke: '#000', data: [10, 20, 30, 40, 50] },
107
+ { id: 'b', stroke: '#f00', data: [5, 15, 25, 35, 45] },
108
+ ];
109
+ const { getAllByTestId } = render(
110
+ <LineChartWrapper>
111
+ <LineChart series={multipleSeries} width={400} height={200} />
112
+ </LineChartWrapper>,
113
+ );
114
+ expect(getAllByTestId('line-path')).toHaveLength(2);
115
+ });
116
+
117
+ it('renders an area fill for each series when showArea is true', () => {
118
+ const multipleSeries = [
119
+ { id: 'a', stroke: '#000', data: [10, 20, 30, 40, 50] },
120
+ { id: 'b', stroke: '#f00', data: [5, 15, 25, 35, 45] },
121
+ ];
122
+ const { getAllByTestId } = render(
123
+ <LineChartWrapper>
124
+ <LineChart series={multipleSeries} width={400} height={200} showArea />
125
+ </LineChartWrapper>,
126
+ );
127
+ expect(getAllByTestId('line-area')).toHaveLength(2);
128
+ });
129
+
130
+ it('renders custom children inside the chart', () => {
131
+ const { getByTestId } = render(
132
+ <LineChartWrapper>
133
+ <LineChart series={sampleSeries} width={400} height={200}>
134
+ <Circle testID='custom-child' cx={10} cy={10} r={5} />
135
+ </LineChart>
136
+ </LineChartWrapper>,
137
+ );
138
+ getByTestId('custom-child');
139
+ });
140
+
141
+ it('renders x-axis tick labels when showXAxis is true', () => {
142
+ const { getByText } = render(
143
+ <LineChartWrapper>
144
+ <LineChart
145
+ series={sampleSeries}
146
+ width={400}
147
+ height={200}
148
+ showXAxis
149
+ xAxis={{ ticks: [0, 2, 4] }}
150
+ />
151
+ </LineChartWrapper>,
152
+ );
153
+ getByText('0');
154
+ getByText('2');
155
+ getByText('4');
156
+ });
157
+
158
+ it('renders x-axis tick labels when positioned at the top', () => {
159
+ const { getByText } = render(
160
+ <LineChartWrapper>
161
+ <LineChart
162
+ series={sampleSeries}
163
+ width={400}
164
+ height={200}
165
+ showXAxis
166
+ xAxis={{ position: 'top', ticks: [0, 4] }}
167
+ />
168
+ </LineChartWrapper>,
169
+ );
170
+ getByText('0');
171
+ getByText('4');
172
+ });
173
+
174
+ it('renders y-axis tick labels when showYAxis is true', () => {
175
+ const { getByText } = render(
176
+ <LineChartWrapper>
177
+ <LineChart
178
+ series={sampleSeries}
179
+ width={400}
180
+ height={200}
181
+ showYAxis
182
+ yAxis={{ ticks: [10, 50] }}
183
+ />
184
+ </LineChartWrapper>,
185
+ );
186
+ getByText('10');
187
+ getByText('50');
188
+ });
189
+
190
+ it('renders y-axis tick labels when positioned at the end', () => {
191
+ const { getByText } = render(
192
+ <LineChartWrapper>
193
+ <LineChart
194
+ series={sampleSeries}
195
+ width={400}
196
+ height={200}
197
+ showYAxis
198
+ yAxis={{ position: 'end', ticks: [10, 50] }}
199
+ />
200
+ </LineChartWrapper>,
201
+ );
202
+ getByText('10');
203
+ getByText('50');
204
+ });
205
+
206
+ it('does not render axis tick labels by default', () => {
207
+ const { queryByText } = render(
208
+ <LineChartWrapper>
209
+ <LineChart
210
+ series={sampleSeries}
211
+ width={400}
212
+ height={200}
213
+ xAxis={{ ticks: [0, 2, 4] }}
214
+ />
215
+ </LineChartWrapper>,
216
+ );
217
+ expect(queryByText('0')).toBeNull();
218
+ expect(queryByText('2')).toBeNull();
219
+ expect(queryByText('4')).toBeNull();
220
+ });
221
+
222
+ it('renders with scrubbing enabled', () => {
223
+ const { getByTestId } = render(
224
+ <LineChartWrapper>
225
+ <LineChart
226
+ series={sampleSeries}
227
+ width={400}
228
+ height={200}
229
+ enableScrubbing
230
+ />
231
+ </LineChartWrapper>,
232
+ );
233
+ getByTestId('chart-svg');
234
+ });
235
+
236
+ it('renders with a custom inset', () => {
237
+ const { getByTestId } = render(
238
+ <LineChartWrapper>
239
+ <LineChart
240
+ series={sampleSeries}
241
+ width={400}
242
+ height={200}
243
+ inset={{ top: 10, bottom: 10, left: 20, right: 20 }}
244
+ />
245
+ </LineChartWrapper>,
246
+ );
247
+ expect(getByTestId('line-path')).toBeTruthy();
248
+ });
249
+
250
+ it('uses the default height when none is provided', () => {
251
+ const { getByTestId } = render(
252
+ <LineChartWrapper>
253
+ <LineChart series={sampleSeries} width={400} />
254
+ </LineChartWrapper>,
255
+ );
256
+ getByTestId('chart-svg');
257
+ });
102
258
  });
@@ -1,8 +1,9 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
- import type { AxisConfigProps, ChartInset } from '../../utils/types';
3
+ import type { ChartInset } from '../../utils/types';
4
+ import { defaultXAxisProps, defaultYAxisProps } from '../Axis';
4
5
  import { DEFAULT_AXIS_HEIGHT, XAxis } from '../Axis/XAxis';
5
- import { DEFAULT_AXIS_WIDTH, YAxis } from '../Axis/YAxis';
6
+ import { YAxis } from '../Axis/YAxis';
6
7
  import { CartesianChart } from '../CartesianChart';
7
8
  import { Line } from '../Line';
8
9
 
@@ -23,52 +24,40 @@ export const LineChart = ({
23
24
  enableScrubbing = false,
24
25
  onScrubberPositionChange,
25
26
  animate,
27
+ magnetRadius,
26
28
  }: LineChartProps) => {
27
- const {
28
- scaleType: xScaleType,
29
- data: xData,
30
- domain: xDomain,
31
- ...xAxisVisualProps
32
- } = xAxis ?? {};
33
-
34
- const {
35
- scaleType: yScaleType,
36
- data: yData,
37
- domain: yDomain,
38
- ...yAxisVisualProps
39
- } = yAxis ?? {};
40
-
41
- const xAxisConfig: Partial<AxisConfigProps> = {
42
- scaleType: xScaleType,
43
- data: xData,
44
- domain: xDomain,
29
+ const xAxisConfig = {
30
+ ...defaultXAxisProps,
31
+ ...xAxis,
45
32
  };
46
-
47
- const yAxisConfig: Partial<AxisConfigProps> = {
48
- scaleType: yScaleType,
49
- data: yData,
50
- domain: yDomain,
33
+ const yAxisConfig = {
34
+ ...defaultYAxisProps,
35
+ ...yAxis,
51
36
  };
52
37
 
53
38
  const axisPadding: Partial<ChartInset> | undefined = useMemo(() => {
54
- if (!showXAxis && !showYAxis) return undefined;
55
- const xAxisPosition =
56
- xAxisVisualProps.position === 'top' ? 'top' : 'bottom';
57
- const yAxisPosition =
58
- yAxisVisualProps.position === 'end' ? 'right' : 'left';
59
- const yAxisWidth = yAxisVisualProps.width ?? DEFAULT_AXIS_WIDTH;
39
+ if (!showXAxis && !showYAxis) {
40
+ return undefined;
41
+ }
42
+
60
43
  return {
61
- top: showXAxis && xAxisPosition === 'top' ? DEFAULT_AXIS_HEIGHT : 0,
62
- bottom: showXAxis && xAxisPosition === 'bottom' ? DEFAULT_AXIS_HEIGHT : 0,
63
- left: showYAxis && yAxisPosition === 'left' ? yAxisWidth : 0,
64
- right: showYAxis && yAxisPosition === 'right' ? yAxisWidth : 0,
44
+ top:
45
+ showXAxis && xAxisConfig.position === 'top' ? DEFAULT_AXIS_HEIGHT : 0,
46
+ bottom:
47
+ showXAxis && xAxisConfig.position === 'bottom'
48
+ ? DEFAULT_AXIS_HEIGHT
49
+ : 0,
50
+ left:
51
+ showYAxis && yAxisConfig.position === 'start' ? yAxisConfig.width : 0,
52
+ right:
53
+ showYAxis && yAxisConfig.position === 'end' ? yAxisConfig.width : 0,
65
54
  };
66
55
  }, [
67
56
  showXAxis,
68
57
  showYAxis,
69
- xAxisVisualProps.position,
70
- yAxisVisualProps.position,
71
- yAxisVisualProps.width,
58
+ xAxisConfig?.position,
59
+ yAxisConfig?.position,
60
+ yAxisConfig?.width,
72
61
  ]);
73
62
 
74
63
  return (
@@ -83,9 +72,10 @@ export const LineChart = ({
83
72
  enableScrubbing={enableScrubbing}
84
73
  onScrubberPositionChange={onScrubberPositionChange}
85
74
  animate={animate}
75
+ magnetRadius={magnetRadius}
86
76
  >
87
- {showXAxis && <XAxis {...xAxisVisualProps} />}
88
- {showYAxis && <YAxis {...yAxisVisualProps} />}
77
+ {showXAxis && <XAxis {...xAxisConfig} />}
78
+ {showYAxis && <YAxis {...yAxisConfig} />}
89
79
  {series?.map((s) => (
90
80
  <Line
91
81
  key={s.id}
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
 
3
- import type { AxisConfigProps, ChartInset, Series } from '../../utils/types';
3
+ import type { ChartInset, Series } from '../../utils/types';
4
4
  import type { XAxisProps } from '../Axis/XAxis';
5
5
  import type { YAxisProps } from '../Axis/YAxis';
6
6
 
@@ -35,12 +35,12 @@ export type LineChartProps = {
35
35
  * Combined axis configuration and visual props for the x-axis.
36
36
  * Includes scale/domain settings as well as visual options like `showGrid` and `showLine`.
37
37
  */
38
- xAxis?: Partial<AxisConfigProps> & XAxisProps;
38
+ xAxis?: XAxisProps;
39
39
  /**
40
40
  * Combined axis configuration and visual props for the y-axis.
41
41
  * Includes scale/domain settings as well as visual options like `showGrid` and `showLine`.
42
42
  */
43
- yAxis?: Partial<AxisConfigProps> & YAxisProps;
43
+ yAxis?: YAxisProps;
44
44
  /**
45
45
  * Width of the chart in pixels.
46
46
  * When omitted, the component auto-measures via `onLayout`.
@@ -76,4 +76,11 @@ export type LineChartProps = {
76
76
  * @default true
77
77
  */
78
78
  animate?: boolean;
79
+ /**
80
+ * Pixel radius within which the scrubber magnetically snaps to registered
81
+ * magnetic `<Point>` components. Requires `enableScrubbing` to be `true`.
82
+ * Set to `0` to disable magnetisation.
83
+ * @default 6
84
+ */
85
+ magnetRadius?: number;
79
86
  };
@@ -5,15 +5,16 @@ import { Circle, G, Polygon, Text as SvgText } from 'react-native-svg';
5
5
  import { projectPoint } from '../../utils/scales/scales';
6
6
  import { useCartesianChartContext } from '../CartesianChart/context';
7
7
  import { useRevealClip } from '../CartesianChart/RevealClip';
8
+ import { DEFAULT_SIZE, STROKE_WIDTH } from './constants';
9
+ import { useMagneticPointsContext } from './pointContext';
8
10
 
9
11
  import type { PointLabelProps, PointProps } from './types';
10
12
  import {
11
13
  buildArrowPoints,
12
14
  computeLabelY,
13
- DEFAULT_SIZE,
14
15
  isWithinBounds,
15
16
  resolveLabel,
16
- STROKE_WIDTH,
17
+ useMagneticRegistration,
17
18
  } from './utils';
18
19
 
19
20
  export function PointLabel({
@@ -45,9 +46,14 @@ export function Point({
45
46
  showLabelArrow = true,
46
47
  size = DEFAULT_SIZE,
47
48
  onPress,
49
+ magnetic = false,
48
50
  }: Readonly<PointProps>) {
49
- const { getXScale, getYScale, drawingArea } = useCartesianChartContext();
51
+ const { getXScale, getYScale, getXAxisConfig, drawingArea } =
52
+ useCartesianChartContext();
50
53
  const clipPath = useRevealClip();
54
+ const magneticContext = useMagneticPointsContext();
55
+
56
+ useMagneticRegistration(magnetic, dataX, getXAxisConfig, magneticContext);
51
57
  const { theme } = useTheme();
52
58
 
53
59
  const xScale = getXScale();
@@ -0,0 +1,6 @@
1
+ export const DEFAULT_SIZE = 10;
2
+ export const STROKE_WIDTH = 2;
3
+ export const LABEL_FONT_SIZE = 10;
4
+ export const ARROW_WIDTH = 6;
5
+ export const ARROW_HEIGHT = 4;
6
+ export const GAP = 4;
@@ -0,0 +1,162 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { renderHook, act } from '@testing-library/react-native';
3
+ import type { ReactNode } from 'react';
4
+
5
+ import { useMagneticPointsContext } from './magneticPointsContext';
6
+ import { MagneticPointsProvider } from './MagneticPointsProvider';
7
+
8
+ const wrapper = ({ children }: { children: ReactNode }) => (
9
+ <MagneticPointsProvider>{children}</MagneticPointsProvider>
10
+ );
11
+
12
+ describe('MagneticPointsProvider', () => {
13
+ it('starts with an empty set', () => {
14
+ const { result } = renderHook(() => useMagneticPointsContext(), {
15
+ wrapper,
16
+ });
17
+ expect(result.current.getMagneticPoints().size).toBe(0);
18
+ });
19
+
20
+ it('registers a magnetic point', () => {
21
+ const { result } = renderHook(() => useMagneticPointsContext(), {
22
+ wrapper,
23
+ });
24
+
25
+ act(() => {
26
+ result.current.register(3);
27
+ });
28
+
29
+ expect(result.current.getMagneticPoints().has(3)).toBe(true);
30
+ expect(result.current.getMagneticPoints().size).toBe(1);
31
+ });
32
+
33
+ it('registers multiple magnetic points', () => {
34
+ const { result } = renderHook(() => useMagneticPointsContext(), {
35
+ wrapper,
36
+ });
37
+
38
+ act(() => {
39
+ result.current.register(2);
40
+ result.current.register(7);
41
+ });
42
+
43
+ const points = result.current.getMagneticPoints();
44
+ expect(points.has(2)).toBe(true);
45
+ expect(points.has(7)).toBe(true);
46
+ expect(points.size).toBe(2);
47
+ });
48
+
49
+ it('unregisters a magnetic point', () => {
50
+ const { result } = renderHook(() => useMagneticPointsContext(), {
51
+ wrapper,
52
+ });
53
+
54
+ act(() => {
55
+ result.current.register(5);
56
+ result.current.register(8);
57
+ });
58
+
59
+ act(() => {
60
+ result.current.unregister(5);
61
+ });
62
+
63
+ const points = result.current.getMagneticPoints();
64
+ expect(points.has(5)).toBe(false);
65
+ expect(points.has(8)).toBe(true);
66
+ expect(points.size).toBe(1);
67
+ });
68
+
69
+ it('handles duplicate registrations gracefully', () => {
70
+ const { result } = renderHook(() => useMagneticPointsContext(), {
71
+ wrapper,
72
+ });
73
+
74
+ act(() => {
75
+ result.current.register(4);
76
+ result.current.register(4);
77
+ });
78
+
79
+ expect(result.current.getMagneticPoints().size).toBe(1);
80
+ });
81
+
82
+ it('handles unregistering a non-existent point gracefully', () => {
83
+ const { result } = renderHook(() => useMagneticPointsContext(), {
84
+ wrapper,
85
+ });
86
+
87
+ act(() => {
88
+ result.current.unregister(99);
89
+ });
90
+
91
+ expect(result.current.getMagneticPoints().size).toBe(0);
92
+ });
93
+
94
+ describe('version counter', () => {
95
+ it('starts at 0', () => {
96
+ const { result } = renderHook(() => useMagneticPointsContext(), {
97
+ wrapper,
98
+ });
99
+ expect(result.current.version).toBe(0);
100
+ });
101
+
102
+ it('increments on register', () => {
103
+ const { result } = renderHook(() => useMagneticPointsContext(), {
104
+ wrapper,
105
+ });
106
+
107
+ act(() => {
108
+ result.current.register(1);
109
+ });
110
+
111
+ expect(result.current.version).toBe(1);
112
+ });
113
+
114
+ it('increments on unregister', () => {
115
+ const { result } = renderHook(() => useMagneticPointsContext(), {
116
+ wrapper,
117
+ });
118
+
119
+ act(() => {
120
+ result.current.register(1);
121
+ });
122
+
123
+ act(() => {
124
+ result.current.unregister(1);
125
+ });
126
+
127
+ expect(result.current.version).toBe(2);
128
+ });
129
+
130
+ it('does not increment on duplicate register', () => {
131
+ const { result } = renderHook(() => useMagneticPointsContext(), {
132
+ wrapper,
133
+ });
134
+
135
+ act(() => {
136
+ result.current.register(5);
137
+ });
138
+
139
+ const versionAfterFirst = result.current.version;
140
+
141
+ act(() => {
142
+ result.current.register(5);
143
+ });
144
+
145
+ expect(result.current.version).toBe(versionAfterFirst);
146
+ });
147
+
148
+ it('does not increment when unregistering a non-existent point', () => {
149
+ const { result } = renderHook(() => useMagneticPointsContext(), {
150
+ wrapper,
151
+ });
152
+
153
+ const versionBefore = result.current.version;
154
+
155
+ act(() => {
156
+ result.current.unregister(99);
157
+ });
158
+
159
+ expect(result.current.version).toBe(versionBefore);
160
+ });
161
+ });
162
+ });
@@ -0,0 +1,45 @@
1
+ import { useCallback, useMemo, useRef, useState } from 'react';
2
+ import type { ReactNode } from 'react';
3
+
4
+ import {
5
+ MagneticPointsContextProvider,
6
+ type MagneticPointsContextValue,
7
+ } from './magneticPointsContext';
8
+
9
+ type MagneticPointsProviderProps = {
10
+ children: ReactNode;
11
+ };
12
+
13
+ export function MagneticPointsProvider({
14
+ children,
15
+ }: Readonly<MagneticPointsProviderProps>) {
16
+ const pointsRef = useRef(new Set<number>());
17
+ const [version, setVersion] = useState(0);
18
+
19
+ const register = useCallback((dataIndex: number): void => {
20
+ if (pointsRef.current.has(dataIndex)) return;
21
+ pointsRef.current.add(dataIndex);
22
+ setVersion((v) => v + 1);
23
+ }, []);
24
+
25
+ const unregister = useCallback((dataIndex: number): void => {
26
+ if (!pointsRef.current.has(dataIndex)) return;
27
+ pointsRef.current.delete(dataIndex);
28
+ setVersion((v) => v + 1);
29
+ }, []);
30
+
31
+ const getMagneticPoints = useCallback((): ReadonlySet<number> => {
32
+ return pointsRef.current;
33
+ }, []);
34
+
35
+ const value: MagneticPointsContextValue = useMemo(
36
+ () => ({ register, unregister, getMagneticPoints, version }),
37
+ [register, unregister, getMagneticPoints, version],
38
+ );
39
+
40
+ return (
41
+ <MagneticPointsContextProvider value={value}>
42
+ {children}
43
+ </MagneticPointsContextProvider>
44
+ );
45
+ }
@@ -0,0 +1,5 @@
1
+ export {
2
+ useMagneticPointsContext,
3
+ type MagneticPointsContextValue,
4
+ } from './magneticPointsContext';
5
+ export { MagneticPointsProvider } from './MagneticPointsProvider';
@@ -0,0 +1,19 @@
1
+ import { createSafeContext } from '@ledgerhq/lumen-utils-shared';
2
+
3
+ export type MagneticPointsContextValue = {
4
+ register: (dataIndex: number) => void;
5
+ unregister: (dataIndex: number) => void;
6
+ version: number;
7
+ getMagneticPoints: () => ReadonlySet<number>;
8
+ };
9
+
10
+ const [MagneticPointsContextProvider, _useMagneticPointsSafeContext] =
11
+ createSafeContext<MagneticPointsContextValue>('MagneticPoints');
12
+
13
+ export const useMagneticPointsContext = (): MagneticPointsContextValue =>
14
+ _useMagneticPointsSafeContext({
15
+ consumerName: 'useMagneticPointsContext',
16
+ contextRequired: true,
17
+ });
18
+
19
+ export { MagneticPointsContextProvider };
@@ -73,4 +73,11 @@ export type PointProps = {
73
73
  * Called when the point is pressed.
74
74
  */
75
75
  onPress?: (event: GestureResponderEvent) => void;
76
+ /**
77
+ * When `true`, the scrubber magnetically snaps to this point within the
78
+ * chart's `magnetRadius`. Requires `enableScrubbing` to be set on the
79
+ * parent chart.
80
+ * @default false
81
+ */
82
+ magnetic?: boolean;
76
83
  };