@ledgerhq/lumen-ui-rnative-visualization 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/dist/module/lib/Components/Axis/XAxis/XAxis.js +1 -1
  2. package/dist/module/lib/Components/Axis/XAxis/XAxis.js.map +1 -1
  3. package/dist/module/lib/Components/Axis/YAxis/YAxis.js +1 -1
  4. package/dist/module/lib/Components/Axis/YAxis/YAxis.js.map +1 -1
  5. package/dist/module/lib/Components/CartesianChart/CartesianChart.js +31 -28
  6. package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -1
  7. package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.js +53 -0
  8. package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.js.map +1 -0
  9. package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.test.js +144 -0
  10. package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.test.js.map +1 -0
  11. package/dist/module/lib/Components/CartesianChart/RevealClip/context.js +8 -0
  12. package/dist/module/lib/Components/CartesianChart/RevealClip/context.js.map +1 -0
  13. package/dist/module/lib/Components/CartesianChart/RevealClip/index.js +5 -0
  14. package/dist/module/lib/Components/CartesianChart/RevealClip/index.js.map +1 -0
  15. package/dist/module/lib/Components/CartesianChart/RevealClip/types.js +4 -0
  16. package/dist/module/lib/Components/CartesianChart/RevealClip/types.js.map +1 -0
  17. package/dist/module/lib/Components/CartesianChart/RevealClip/utils.js +32 -0
  18. package/dist/module/lib/Components/CartesianChart/RevealClip/utils.js.map +1 -0
  19. package/dist/module/lib/Components/CartesianChart/utils.js +4 -4
  20. package/dist/module/lib/Components/Line/Line.js +5 -2
  21. package/dist/module/lib/Components/Line/Line.js.map +1 -1
  22. package/dist/module/lib/Components/LineChart/LineChart.js +7 -1
  23. package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
  24. package/dist/module/lib/Components/LineChart/LineChart.test.js +25 -0
  25. package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
  26. package/dist/module/lib/Components/Point/Point.js +3 -0
  27. package/dist/module/lib/Components/Point/Point.js.map +1 -1
  28. package/dist/module/lib/Components/ReferenceLine/ReferenceLine.js +125 -0
  29. package/dist/module/lib/Components/ReferenceLine/ReferenceLine.js.map +1 -0
  30. package/dist/module/lib/Components/ReferenceLine/ReferenceLine.stories.js +111 -0
  31. package/dist/module/lib/Components/ReferenceLine/ReferenceLine.stories.js.map +1 -0
  32. package/dist/module/lib/Components/ReferenceLine/ReferenceLine.test.js +338 -0
  33. package/dist/module/lib/Components/ReferenceLine/ReferenceLine.test.js.map +1 -0
  34. package/dist/module/lib/Components/ReferenceLine/constants.js +5 -0
  35. package/dist/module/lib/Components/ReferenceLine/constants.js.map +1 -0
  36. package/dist/module/lib/Components/ReferenceLine/index.js +5 -0
  37. package/dist/module/lib/Components/ReferenceLine/index.js.map +1 -0
  38. package/dist/module/lib/Components/ReferenceLine/types.js +2 -0
  39. package/dist/module/lib/Components/ReferenceLine/types.js.map +1 -0
  40. package/dist/module/lib/Components/ReferenceLine/utils.js +121 -0
  41. package/dist/module/lib/Components/ReferenceLine/utils.js.map +1 -0
  42. package/dist/module/lib/Components/ReferenceLine/utils.test.js +297 -0
  43. package/dist/module/lib/Components/ReferenceLine/utils.test.js.map +1 -0
  44. package/dist/module/lib/Components/Scrubber/Scrubber.js +152 -0
  45. package/dist/module/lib/Components/Scrubber/Scrubber.js.map +1 -0
  46. package/dist/module/lib/Components/Scrubber/Scrubber.stories.js +113 -0
  47. package/dist/module/lib/Components/Scrubber/Scrubber.stories.js.map +1 -0
  48. package/dist/module/lib/Components/Scrubber/Scrubber.test.js +132 -0
  49. package/dist/module/lib/Components/Scrubber/Scrubber.test.js.map +1 -0
  50. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js +105 -0
  51. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js.map +1 -0
  52. package/dist/module/lib/Components/Scrubber/ScrubberProvider.test.js +217 -0
  53. package/dist/module/lib/Components/Scrubber/ScrubberProvider.test.js.map +1 -0
  54. package/dist/module/lib/Components/Scrubber/constants.js +10 -0
  55. package/dist/module/lib/Components/Scrubber/constants.js.map +1 -0
  56. package/dist/module/lib/Components/Scrubber/context/index.js +4 -0
  57. package/dist/module/lib/Components/Scrubber/context/index.js.map +1 -0
  58. package/dist/module/lib/Components/Scrubber/context/scrubberContext.js +10 -0
  59. package/dist/module/lib/Components/Scrubber/context/scrubberContext.js.map +1 -0
  60. package/dist/module/lib/Components/Scrubber/index.js +5 -0
  61. package/dist/module/lib/Components/Scrubber/index.js.map +1 -0
  62. package/dist/module/lib/Components/Scrubber/types.js +4 -0
  63. package/dist/module/lib/Components/Scrubber/types.js.map +1 -0
  64. package/dist/module/lib/Components/Scrubber/utils.js +83 -0
  65. package/dist/module/lib/Components/Scrubber/utils.js.map +1 -0
  66. package/dist/module/lib/Components/Scrubber/utils.test.js +192 -0
  67. package/dist/module/lib/Components/Scrubber/utils.test.js.map +1 -0
  68. package/dist/module/lib/Components/index.js +2 -0
  69. package/dist/module/lib/Components/index.js.map +1 -1
  70. package/dist/module/lib/utils/scales/scales.js +16 -8
  71. package/dist/module/lib/utils/scales/scales.js.map +1 -1
  72. package/dist/module/lib/utils/scales/scales.test.js +32 -1
  73. package/dist/module/lib/utils/scales/scales.test.js.map +1 -1
  74. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts +1 -1
  75. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts.map +1 -1
  76. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.d.ts +3 -0
  77. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.d.ts.map +1 -0
  78. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/context.d.ts +7 -0
  79. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/context.d.ts.map +1 -0
  80. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/index.d.ts +3 -0
  81. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/index.d.ts.map +1 -0
  82. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/types.d.ts +38 -0
  83. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/types.d.ts.map +1 -0
  84. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/utils.d.ts +17 -0
  85. package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/utils.d.ts.map +1 -0
  86. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts +17 -0
  87. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts.map +1 -1
  88. package/dist/typescript/src/lib/Components/Line/Line.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 +16 -0
  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.map +1 -1
  94. package/dist/typescript/src/lib/Components/ReferenceLine/ReferenceLine.d.ts +3 -0
  95. package/dist/typescript/src/lib/Components/ReferenceLine/ReferenceLine.d.ts.map +1 -0
  96. package/dist/typescript/src/lib/Components/ReferenceLine/constants.d.ts +3 -0
  97. package/dist/typescript/src/lib/Components/ReferenceLine/constants.d.ts.map +1 -0
  98. package/dist/typescript/src/lib/Components/ReferenceLine/index.d.ts +3 -0
  99. package/dist/typescript/src/lib/Components/ReferenceLine/index.d.ts.map +1 -0
  100. package/dist/typescript/src/lib/Components/ReferenceLine/types.d.ts +74 -0
  101. package/dist/typescript/src/lib/Components/ReferenceLine/types.d.ts.map +1 -0
  102. package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts +55 -0
  103. package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts.map +1 -0
  104. package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts +17 -0
  105. package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts.map +1 -0
  106. package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts +16 -0
  107. package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts.map +1 -0
  108. package/dist/typescript/src/lib/Components/Scrubber/constants.d.ts +8 -0
  109. package/dist/typescript/src/lib/Components/Scrubber/constants.d.ts.map +1 -0
  110. package/dist/typescript/src/lib/Components/Scrubber/context/index.d.ts +2 -0
  111. package/dist/typescript/src/lib/Components/Scrubber/context/index.d.ts.map +1 -0
  112. package/dist/typescript/src/lib/Components/Scrubber/context/scrubberContext.d.ts +8 -0
  113. package/dist/typescript/src/lib/Components/Scrubber/context/scrubberContext.d.ts.map +1 -0
  114. package/dist/typescript/src/lib/Components/Scrubber/index.d.ts +4 -0
  115. package/dist/typescript/src/lib/Components/Scrubber/index.d.ts.map +1 -0
  116. package/dist/typescript/src/lib/Components/Scrubber/types.d.ts +57 -0
  117. package/dist/typescript/src/lib/Components/Scrubber/types.d.ts.map +1 -0
  118. package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts +22 -0
  119. package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts.map +1 -0
  120. package/dist/typescript/src/lib/Components/index.d.ts +2 -0
  121. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  122. package/dist/typescript/src/lib/utils/scales/scales.d.ts +6 -0
  123. package/dist/typescript/src/lib/utils/scales/scales.d.ts.map +1 -1
  124. package/package.json +7 -4
  125. package/src/lib/Components/Axis/XAxis/XAxis.tsx +1 -1
  126. package/src/lib/Components/Axis/YAxis/YAxis.tsx +1 -1
  127. package/src/lib/Components/CartesianChart/CartesianChart.tsx +32 -32
  128. package/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.test.tsx +129 -0
  129. package/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.tsx +59 -0
  130. package/src/lib/Components/CartesianChart/RevealClip/context.ts +13 -0
  131. package/src/lib/Components/CartesianChart/RevealClip/index.ts +2 -0
  132. package/src/lib/Components/CartesianChart/RevealClip/types.ts +38 -0
  133. package/src/lib/Components/CartesianChart/RevealClip/utils.ts +51 -0
  134. package/src/lib/Components/CartesianChart/types.ts +17 -0
  135. package/src/lib/Components/CartesianChart/utils.ts +4 -4
  136. package/src/lib/Components/Line/Line.tsx +5 -3
  137. package/src/lib/Components/LineChart/LineChart.test.tsx +23 -0
  138. package/src/lib/Components/LineChart/LineChart.tsx +6 -0
  139. package/src/lib/Components/LineChart/types.ts +16 -0
  140. package/src/lib/Components/Point/Point.tsx +3 -1
  141. package/src/lib/Components/ReferenceLine/ReferenceLine.stories.tsx +109 -0
  142. package/src/lib/Components/ReferenceLine/ReferenceLine.test.tsx +269 -0
  143. package/src/lib/Components/ReferenceLine/ReferenceLine.tsx +149 -0
  144. package/src/lib/Components/ReferenceLine/constants.ts +2 -0
  145. package/src/lib/Components/ReferenceLine/index.ts +2 -0
  146. package/src/lib/Components/ReferenceLine/types.ts +79 -0
  147. package/src/lib/Components/ReferenceLine/utils.test.ts +332 -0
  148. package/src/lib/Components/ReferenceLine/utils.ts +175 -0
  149. package/src/lib/Components/Scrubber/Scrubber.stories.tsx +122 -0
  150. package/src/lib/Components/Scrubber/Scrubber.test.tsx +110 -0
  151. package/src/lib/Components/Scrubber/Scrubber.tsx +189 -0
  152. package/src/lib/Components/Scrubber/ScrubberProvider.test.tsx +223 -0
  153. package/src/lib/Components/Scrubber/ScrubberProvider.tsx +132 -0
  154. package/src/lib/Components/Scrubber/constants.ts +7 -0
  155. package/src/lib/Components/Scrubber/context/index.ts +1 -0
  156. package/src/lib/Components/Scrubber/context/scrubberContext.ts +14 -0
  157. package/src/lib/Components/Scrubber/index.ts +3 -0
  158. package/src/lib/Components/Scrubber/types.ts +59 -0
  159. package/src/lib/Components/Scrubber/utils.test.ts +172 -0
  160. package/src/lib/Components/Scrubber/utils.ts +126 -0
  161. package/src/lib/Components/index.ts +2 -0
  162. package/src/lib/utils/scales/scales.test.ts +22 -0
  163. package/src/lib/utils/scales/scales.ts +19 -9
@@ -0,0 +1,269 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+ import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
+ import { ThemeProvider } from '@ledgerhq/lumen-ui-rnative';
4
+ import { render } from '@testing-library/react-native';
5
+ import React from 'react';
6
+
7
+ import { CartesianChart } from '../CartesianChart';
8
+
9
+ import { ReferenceLine } from './ReferenceLine';
10
+
11
+ const sampleSeries = [{ id: 's1', stroke: '#000', data: [10, 20, 30, 40, 50] }];
12
+
13
+ const Wrapper = ({ children }: { children: React.ReactNode }) => (
14
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='light'>
15
+ {children}
16
+ </ThemeProvider>
17
+ );
18
+
19
+ const renderInChart = (
20
+ children: React.ReactNode,
21
+ {
22
+ width = 400,
23
+ height = 200,
24
+ xAxis,
25
+ yAxis,
26
+ }: {
27
+ width?: number;
28
+ height?: number;
29
+ xAxis?: Parameters<typeof CartesianChart>[0]['xAxis'];
30
+ yAxis?: Parameters<typeof CartesianChart>[0]['yAxis'];
31
+ } = {},
32
+ ) =>
33
+ render(
34
+ <Wrapper>
35
+ <CartesianChart
36
+ series={sampleSeries}
37
+ width={width}
38
+ height={height}
39
+ xAxis={xAxis}
40
+ yAxis={yAxis}
41
+ >
42
+ {children}
43
+ </CartesianChart>
44
+ </Wrapper>,
45
+ );
46
+
47
+ describe('ReferenceLine', () => {
48
+ describe('horizontal line (dataY)', () => {
49
+ it('renders a horizontal line at the correct y-pixel', () => {
50
+ const { getByTestId } = renderInChart(<ReferenceLine dataY={30} />);
51
+ const line = getByTestId('reference-line-line');
52
+ expect(line).toBeTruthy();
53
+ expect(line.props.y1).toBe(line.props.y2);
54
+ });
55
+
56
+ it('spans the full width of the drawing area', () => {
57
+ const { getByTestId } = renderInChart(<ReferenceLine dataY={30} />);
58
+ const line = getByTestId('reference-line-line');
59
+ expect(line.props.x2).toBeGreaterThan(line.props.x1);
60
+ });
61
+
62
+ it('does not render when dataY is outside domain bounds', () => {
63
+ const { queryByTestId } = renderInChart(<ReferenceLine dataY={999} />);
64
+ expect(queryByTestId('reference-line')).toBeNull();
65
+ });
66
+
67
+ it('renders label with textAnchor start when labelPosition is end', () => {
68
+ const { getByTestId } = renderInChart(
69
+ <ReferenceLine dataY={30} label='Target' labelPosition='end' />,
70
+ );
71
+ const label = getByTestId('reference-line-label');
72
+ expect(label.props.textAnchor).toBe('start');
73
+ });
74
+
75
+ it('renders label with textAnchor end when labelPosition is start', () => {
76
+ const { getByTestId } = renderInChart(
77
+ <ReferenceLine dataY={30} label='Target' labelPosition='start' />,
78
+ );
79
+ const label = getByTestId('reference-line-label');
80
+ expect(label.props.textAnchor).toBe('end');
81
+ });
82
+
83
+ it('renders label with textAnchor middle when labelPosition is center', () => {
84
+ const { getByTestId } = renderInChart(
85
+ <ReferenceLine dataY={30} label='Target' labelPosition='center' />,
86
+ );
87
+ const label = getByTestId('reference-line-label');
88
+ expect(label.props.textAnchor).toBe('middle');
89
+ });
90
+
91
+ it('defaults labelPosition to end', () => {
92
+ const { getByTestId } = renderInChart(
93
+ <ReferenceLine dataY={30} label='Target' />,
94
+ );
95
+ const label = getByTestId('reference-line-label');
96
+ expect(label.props.textAnchor).toBe('start');
97
+ });
98
+ });
99
+
100
+ describe('vertical line (dataX)', () => {
101
+ it('renders a vertical line at the correct x-pixel', () => {
102
+ const { getByTestId } = renderInChart(<ReferenceLine dataX={2} />);
103
+ const line = getByTestId('reference-line-line');
104
+ expect(line).toBeTruthy();
105
+ expect(line.props.x1).toBe(line.props.x2);
106
+ });
107
+
108
+ it('spans the full height of the drawing area', () => {
109
+ const { getByTestId } = renderInChart(<ReferenceLine dataX={2} />);
110
+ const line = getByTestId('reference-line-line');
111
+ expect(line.props.y2).toBeGreaterThan(line.props.y1);
112
+ });
113
+
114
+ it('does not render when dataX is outside domain bounds', () => {
115
+ const { queryByTestId } = renderInChart(<ReferenceLine dataX={999} />);
116
+ expect(queryByTestId('reference-line')).toBeNull();
117
+ });
118
+
119
+ it('defaults labelPosition to end with dy offset for hanging baseline', () => {
120
+ const { getByTestId } = renderInChart(
121
+ <ReferenceLine dataX={2} label='Event' />,
122
+ );
123
+ const label = getByTestId('reference-line-label');
124
+ expect(label.props.dy).toBe(8);
125
+ });
126
+
127
+ it('works with band (categorical) x-axis scale', () => {
128
+ const { getByTestId } = renderInChart(<ReferenceLine dataX={2} />, {
129
+ xAxis: { scaleType: 'band' },
130
+ });
131
+ expect(getByTestId('reference-line-line')).toBeTruthy();
132
+ });
133
+
134
+ it('does not render when dataX is outside band scale domain', () => {
135
+ const { queryByTestId } = renderInChart(<ReferenceLine dataX={999} />, {
136
+ xAxis: { scaleType: 'band' },
137
+ });
138
+ expect(queryByTestId('reference-line')).toBeNull();
139
+ });
140
+
141
+ it('resolves dataX through numeric xAxis.data', () => {
142
+ const numericXData = [100, 200, 300, 400, 500];
143
+ const { getByTestId: getIdx0 } = renderInChart(
144
+ <ReferenceLine dataX={0} />,
145
+ { xAxis: { data: numericXData } },
146
+ );
147
+ const { getByTestId: getIdx2 } = renderInChart(
148
+ <ReferenceLine dataX={2} />,
149
+ { xAxis: { data: numericXData } },
150
+ );
151
+ const x1Idx0 = getIdx0('reference-line-line').props.x1;
152
+ const x1Idx2 = getIdx2('reference-line-line').props.x1;
153
+
154
+ expect(x1Idx2).toBeGreaterThan(x1Idx0);
155
+ });
156
+
157
+ it('does not render when dataX index is out of bounds for xAxis.data', () => {
158
+ const { queryByTestId } = renderInChart(<ReferenceLine dataX={10} />, {
159
+ xAxis: { data: [100, 200, 300] },
160
+ });
161
+ expect(queryByTestId('reference-line')).toBeNull();
162
+ });
163
+ });
164
+
165
+ describe('label offsets', () => {
166
+ it('applies labelDx offset to label x position', () => {
167
+ const { getByTestId, unmount } = renderInChart(
168
+ <ReferenceLine dataY={30} label='A' />,
169
+ );
170
+ const xWithoutOffset = getByTestId('reference-line-label').props.x;
171
+ unmount();
172
+
173
+ const { getByTestId: getWithOffset } = renderInChart(
174
+ <ReferenceLine dataY={30} label='A' labelDx={10} />,
175
+ );
176
+ const xWithOffset = getWithOffset('reference-line-label').props.x;
177
+ expect(xWithOffset).toBe(xWithoutOffset + 10);
178
+ });
179
+
180
+ it('applies labelDy offset to label y position', () => {
181
+ const { getByTestId, unmount } = renderInChart(
182
+ <ReferenceLine dataY={30} label='A' />,
183
+ );
184
+ const yWithoutOffset = getByTestId('reference-line-label').props.y;
185
+ unmount();
186
+
187
+ const { getByTestId: getWithOffset } = renderInChart(
188
+ <ReferenceLine dataY={30} label='A' labelDy={-4} />,
189
+ );
190
+ const yWithOffset = getWithOffset('reference-line-label').props.y;
191
+ expect(yWithOffset).toBe(yWithoutOffset - 4);
192
+ });
193
+ });
194
+
195
+ describe('styling', () => {
196
+ it('uses dashed line style by default', () => {
197
+ const { getByTestId } = renderInChart(<ReferenceLine dataY={30} />);
198
+ const line = getByTestId('reference-line-line');
199
+ expect(line.props.strokeDasharray).toBe('0.1 6');
200
+ });
201
+
202
+ it('removes dasharray when lineStyle is solid', () => {
203
+ const { getByTestId } = renderInChart(
204
+ <ReferenceLine dataY={30} lineStyle='solid' />,
205
+ );
206
+ const line = getByTestId('reference-line-line');
207
+ expect(line.props.strokeDasharray).toBeUndefined();
208
+ });
209
+
210
+ it('applies custom stroke color', () => {
211
+ const { getByTestId } = renderInChart(
212
+ <ReferenceLine dataY={30} stroke='#FF0000' />,
213
+ );
214
+ const line = getByTestId('reference-line-line');
215
+ expect(line.props.stroke).toBe('#FF0000');
216
+ });
217
+
218
+ it('has round linecap', () => {
219
+ const { getByTestId } = renderInChart(<ReferenceLine dataY={30} />);
220
+ const line = getByTestId('reference-line-line');
221
+ expect(line.props.strokeLinecap).toBe('round');
222
+ });
223
+ });
224
+
225
+ describe('label alignment overrides', () => {
226
+ it('applies labelHorizontalAlignment=start on vertical line', () => {
227
+ const { getByTestId } = renderInChart(
228
+ <ReferenceLine
229
+ dataX={2}
230
+ label='Test'
231
+ labelHorizontalAlignment='start'
232
+ />,
233
+ );
234
+ const label = getByTestId('reference-line-label');
235
+ expect(label.props.textAnchor).toBe('end');
236
+ });
237
+
238
+ it('applies labelVerticalAlignment=start on horizontal line', () => {
239
+ const { getByTestId } = renderInChart(
240
+ <ReferenceLine
241
+ dataY={30}
242
+ label='Test'
243
+ labelVerticalAlignment='start'
244
+ />,
245
+ );
246
+ const label = getByTestId('reference-line-label');
247
+ expect(label.props.dy).toBe(0);
248
+ });
249
+
250
+ it('applies labelVerticalAlignment=center on horizontal line', () => {
251
+ const { getByTestId } = renderInChart(
252
+ <ReferenceLine
253
+ dataY={30}
254
+ label='Test'
255
+ labelVerticalAlignment='center'
256
+ />,
257
+ );
258
+ const label = getByTestId('reference-line-label');
259
+ expect(label.props.dy).toBe(3.5);
260
+ });
261
+ });
262
+
263
+ describe('no label', () => {
264
+ it('does not render label element when label is not provided', () => {
265
+ const { queryByTestId } = renderInChart(<ReferenceLine dataY={30} />);
266
+ expect(queryByTestId('reference-line-label')).toBeNull();
267
+ });
268
+ });
269
+ });
@@ -0,0 +1,149 @@
1
+ import { useTheme } from '@ledgerhq/lumen-ui-rnative';
2
+ import { G, Line as SvgLine, Text as SvgText } from 'react-native-svg';
3
+
4
+ import { useCartesianChartContext } from '../CartesianChart/context';
5
+
6
+ import { DASH_ARRAY, STROKE_WIDTH } from './constants';
7
+ import type { ReferenceLineProps } from './types';
8
+ import {
9
+ computeHorizontalLabelCoordinates,
10
+ computeVerticalLabelCoordinates,
11
+ dominantBaselineToDy,
12
+ resolvePixel,
13
+ } from './utils';
14
+
15
+ export function ReferenceLine({
16
+ label,
17
+ labelDx = 0,
18
+ labelDy = 0,
19
+ labelHorizontalAlignment,
20
+ labelVerticalAlignment,
21
+ labelPosition = 'end',
22
+ stroke,
23
+ lineStyle = 'dashed',
24
+ ...props
25
+ }: Readonly<ReferenceLineProps>) {
26
+ const { getXScale, getYScale, getXAxisConfig, getYAxisConfig, drawingArea } =
27
+ useCartesianChartContext();
28
+ const { theme } = useTheme();
29
+
30
+ const resolvedStroke = stroke ?? theme.colors.border.muted;
31
+ const dashArray = lineStyle === 'dashed' ? DASH_ARRAY : undefined;
32
+ const fontSize = theme.typographies.body4.fontSize;
33
+
34
+ if (props.dataY !== undefined) {
35
+ const yPixel = resolvePixel({
36
+ dataValue: props.dataY,
37
+ scale: getYScale(),
38
+ axis: 'y',
39
+ drawingArea,
40
+ axisConfig: getYAxisConfig(),
41
+ });
42
+ if (yPixel === undefined) return null;
43
+
44
+ const labelCoords = label
45
+ ? computeHorizontalLabelCoordinates({
46
+ pixel: yPixel,
47
+ labelPosition,
48
+ drawingArea,
49
+ dx: labelDx,
50
+ dy: labelDy,
51
+ horizontalAlignment: labelHorizontalAlignment,
52
+ verticalAlignment: labelVerticalAlignment,
53
+ })
54
+ : null;
55
+ const baselineDy = labelCoords
56
+ ? dominantBaselineToDy(labelCoords.dominantBaseline, fontSize)
57
+ : 0;
58
+
59
+ return (
60
+ <G testID='reference-line'>
61
+ <SvgLine
62
+ testID='reference-line-line'
63
+ x1={drawingArea.x}
64
+ y1={yPixel}
65
+ x2={drawingArea.x + drawingArea.width}
66
+ y2={yPixel}
67
+ stroke={resolvedStroke}
68
+ strokeWidth={STROKE_WIDTH}
69
+ strokeDasharray={dashArray}
70
+ strokeLinecap='round'
71
+ />
72
+ {labelCoords && (
73
+ <SvgText
74
+ testID='reference-line-label'
75
+ x={labelCoords.x}
76
+ y={labelCoords.y}
77
+ dy={baselineDy}
78
+ textAnchor={labelCoords.textAnchor}
79
+ fill={theme.colors.text.muted}
80
+ fontSize={fontSize}
81
+ fontWeight={theme.typographies.body4.fontWeight}
82
+ fontFamily={theme.fontFamilies.sans}
83
+ >
84
+ {label}
85
+ </SvgText>
86
+ )}
87
+ </G>
88
+ );
89
+ }
90
+
91
+ if (props.dataX !== undefined) {
92
+ const xPixel = resolvePixel({
93
+ dataValue: props.dataX,
94
+ scale: getXScale(),
95
+ axis: 'x',
96
+ drawingArea,
97
+ axisConfig: getXAxisConfig(),
98
+ });
99
+ if (xPixel === undefined) return null;
100
+
101
+ const labelCoords = label
102
+ ? computeVerticalLabelCoordinates({
103
+ pixel: xPixel,
104
+ labelPosition,
105
+ drawingArea,
106
+ dx: labelDx,
107
+ dy: labelDy,
108
+ horizontalAlignment: labelHorizontalAlignment,
109
+ verticalAlignment: labelVerticalAlignment,
110
+ })
111
+ : null;
112
+ const baselineDy = labelCoords
113
+ ? dominantBaselineToDy(labelCoords.dominantBaseline, fontSize)
114
+ : 0;
115
+
116
+ return (
117
+ <G testID='reference-line'>
118
+ <SvgLine
119
+ testID='reference-line-line'
120
+ x1={xPixel}
121
+ y1={drawingArea.y}
122
+ x2={xPixel}
123
+ y2={drawingArea.y + drawingArea.height}
124
+ stroke={resolvedStroke}
125
+ strokeWidth={STROKE_WIDTH}
126
+ strokeDasharray={dashArray}
127
+ strokeLinecap='round'
128
+ />
129
+ {labelCoords && (
130
+ <SvgText
131
+ testID='reference-line-label'
132
+ x={labelCoords.x}
133
+ y={labelCoords.y}
134
+ dy={baselineDy}
135
+ textAnchor={labelCoords.textAnchor}
136
+ fill={theme.colors.text.muted}
137
+ fontSize={fontSize}
138
+ fontWeight={theme.typographies.body4.fontWeight}
139
+ fontFamily={theme.fontFamilies.sans}
140
+ >
141
+ {label}
142
+ </SvgText>
143
+ )}
144
+ </G>
145
+ );
146
+ }
147
+
148
+ return null;
149
+ }
@@ -0,0 +1,2 @@
1
+ export const STROKE_WIDTH = 2;
2
+ export const DASH_ARRAY = '0.1 6';
@@ -0,0 +1,2 @@
1
+ export * from './ReferenceLine';
2
+ export * from './types';
@@ -0,0 +1,79 @@
1
+ export type LabelAlignment = 'start' | 'center' | 'end';
2
+ export type LabelPosition = 'start' | 'center' | 'end';
3
+
4
+ export type ReferenceLineBaseProps = {
5
+ /**
6
+ * Label text displayed near the reference line.
7
+ */
8
+ label?: string;
9
+ /**
10
+ * Horizontal pixel offset applied to the label position.
11
+ * @default 0
12
+ */
13
+ labelDx?: number;
14
+ /**
15
+ * Vertical pixel offset applied to the label position.
16
+ * @default 0
17
+ */
18
+ labelDy?: number;
19
+ /**
20
+ * Position of the label along the line.
21
+ * @default 'end'
22
+ */
23
+ labelPosition?: LabelPosition;
24
+ /**
25
+ * Controls the direction the label text extends from its position.
26
+ * - `'start'`: text extends toward the start of the axis (left)
27
+ * - `'center'`: text is centered on the position
28
+ * - `'end'`: text extends toward the end of the axis (right)
29
+ *
30
+ * Defaults: `'center'` for vertical lines, inferred from `labelPosition` for horizontal lines.
31
+ */
32
+ labelHorizontalAlignment?: LabelAlignment;
33
+ /**
34
+ * Controls the direction the label text extends from its position.
35
+ * - `'start'`: text extends toward the start of the axis (up)
36
+ * - `'center'`: text is vertically centered on the position
37
+ * - `'end'`: text extends toward the end of the axis (down)
38
+ *
39
+ * Defaults: `'end'` for horizontal lines, inferred from `labelPosition` for vertical lines.
40
+ */
41
+ labelVerticalAlignment?: LabelAlignment;
42
+ /**
43
+ * Line color.
44
+ * @default theme.colors.border.muted
45
+ */
46
+ stroke?: string;
47
+ /**
48
+ * Line dash style.
49
+ * - `'dashed'`: rounded dots with `strokeDasharray="0.1 6"`
50
+ * - `'solid'`: continuous line
51
+ * @default 'dashed'
52
+ */
53
+ lineStyle?: 'solid' | 'dashed';
54
+ };
55
+
56
+ export type HorizontalReferenceLineProps = ReferenceLineBaseProps & {
57
+ /**
58
+ * Y-axis value in data space where the horizontal line is drawn.
59
+ * The horizontal line is rendered at this Y position spanning the full
60
+ * width of the drawing area.
61
+ */
62
+ dataY: number;
63
+ dataX?: never;
64
+ };
65
+
66
+ export type VerticalReferenceLineProps = ReferenceLineBaseProps & {
67
+ /**
68
+ * Data index along the X axis. When `xAxis.data` contains numeric values,
69
+ * the index is mapped to the corresponding axis value automatically.
70
+ * The vertical line is drawn at this position spanning the full height
71
+ * of the drawing area.
72
+ */
73
+ dataX: number;
74
+ dataY?: never;
75
+ };
76
+
77
+ export type ReferenceLineProps =
78
+ | HorizontalReferenceLineProps
79
+ | VerticalReferenceLineProps;