@ledgerhq/lumen-ui-rnative-visualization 0.1.2 → 0.1.3

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 (152) hide show
  1. package/dist/module/lib/Components/Axis/Axis.types.js +2 -0
  2. package/dist/module/lib/Components/Axis/Axis.types.js.map +1 -0
  3. package/dist/module/lib/Components/Axis/XAxis/XAxis.js +81 -0
  4. package/dist/module/lib/Components/Axis/XAxis/XAxis.js.map +1 -0
  5. package/dist/module/lib/Components/Axis/XAxis/XAxis.test.js +121 -0
  6. package/dist/module/lib/Components/Axis/XAxis/XAxis.test.js.map +1 -0
  7. package/dist/module/lib/Components/Axis/XAxis/index.js +4 -0
  8. package/dist/module/lib/Components/Axis/XAxis/index.js.map +1 -0
  9. package/dist/module/lib/Components/Axis/XAxis/types.js +4 -0
  10. package/dist/module/lib/Components/Axis/XAxis/types.js.map +1 -0
  11. package/dist/module/lib/Components/Axis/YAxis/YAxis.js +81 -0
  12. package/dist/module/lib/Components/Axis/YAxis/YAxis.js.map +1 -0
  13. package/dist/module/lib/Components/Axis/YAxis/YAxis.test.js +121 -0
  14. package/dist/module/lib/Components/Axis/YAxis/YAxis.test.js.map +1 -0
  15. package/dist/module/lib/Components/Axis/YAxis/index.js +4 -0
  16. package/dist/module/lib/Components/Axis/YAxis/index.js.map +1 -0
  17. package/dist/module/lib/Components/Axis/YAxis/types.js +4 -0
  18. package/dist/module/lib/Components/Axis/YAxis/types.js.map +1 -0
  19. package/dist/module/lib/Components/CartesianChart/CartesianChart.js +123 -0
  20. package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -0
  21. package/dist/module/lib/Components/CartesianChart/context/cartesianChartContext.js +10 -0
  22. package/dist/module/lib/Components/CartesianChart/context/cartesianChartContext.js.map +1 -0
  23. package/dist/module/lib/Components/CartesianChart/context/index.js +5 -0
  24. package/dist/module/lib/Components/CartesianChart/context/index.js.map +1 -0
  25. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js +75 -0
  26. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js.map +1 -0
  27. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js +279 -0
  28. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js.map +1 -0
  29. package/dist/module/lib/Components/CartesianChart/index.js +5 -0
  30. package/dist/module/lib/Components/CartesianChart/index.js.map +1 -0
  31. package/dist/module/lib/Components/CartesianChart/types.js +4 -0
  32. package/dist/module/lib/Components/CartesianChart/types.js.map +1 -0
  33. package/dist/module/lib/Components/Line/Line.js +72 -0
  34. package/dist/module/lib/Components/Line/Line.js.map +1 -0
  35. package/dist/module/lib/Components/Line/index.js +4 -0
  36. package/dist/module/lib/Components/Line/index.js.map +1 -0
  37. package/dist/module/lib/Components/Line/types.js +2 -0
  38. package/dist/module/lib/Components/Line/types.js.map +1 -0
  39. package/dist/module/lib/Components/Line/utils.js +38 -0
  40. package/dist/module/lib/Components/Line/utils.js.map +1 -0
  41. package/dist/module/lib/Components/LineChart/LineChart.js +70 -31
  42. package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
  43. package/dist/module/lib/Components/LineChart/LineChart.stories.js +271 -2
  44. package/dist/module/lib/Components/LineChart/LineChart.stories.js.map +1 -1
  45. package/dist/module/lib/Components/LineChart/LineChart.test.js +84 -3
  46. package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
  47. package/dist/module/lib/Components/LineChart/types.js +4 -0
  48. package/dist/module/lib/Components/LineChart/types.js.map +1 -0
  49. package/dist/module/lib/utils/domain/domain.js +93 -0
  50. package/dist/module/lib/utils/domain/domain.js.map +1 -0
  51. package/dist/module/lib/utils/domain/domain.test.js +152 -0
  52. package/dist/module/lib/utils/domain/domain.test.js.map +1 -0
  53. package/dist/module/lib/utils/index.js +0 -2
  54. package/dist/module/lib/utils/index.js.map +1 -1
  55. package/dist/module/lib/utils/scales/scales.js +41 -0
  56. package/dist/module/lib/utils/scales/scales.js.map +1 -0
  57. package/dist/module/lib/utils/scales/scales.test.js +173 -0
  58. package/dist/module/lib/utils/scales/scales.test.js.map +1 -0
  59. package/dist/module/lib/utils/ticks/ticks.js +55 -0
  60. package/dist/module/lib/utils/ticks/ticks.js.map +1 -0
  61. package/dist/module/lib/utils/ticks/ticks.test.js +121 -0
  62. package/dist/module/lib/utils/ticks/ticks.test.js.map +1 -0
  63. package/dist/module/lib/utils/types.js +2 -0
  64. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts +33 -0
  65. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts.map +1 -0
  66. package/dist/typescript/src/lib/Components/Axis/XAxis/XAxis.d.ts +4 -0
  67. package/dist/typescript/src/lib/Components/Axis/XAxis/XAxis.d.ts.map +1 -0
  68. package/dist/typescript/src/lib/Components/Axis/XAxis/index.d.ts +3 -0
  69. package/dist/typescript/src/lib/Components/Axis/XAxis/index.d.ts.map +1 -0
  70. package/dist/typescript/src/lib/Components/Axis/XAxis/types.d.ts +9 -0
  71. package/dist/typescript/src/lib/Components/Axis/XAxis/types.d.ts.map +1 -0
  72. package/dist/typescript/src/lib/Components/Axis/YAxis/YAxis.d.ts +4 -0
  73. package/dist/typescript/src/lib/Components/Axis/YAxis/YAxis.d.ts.map +1 -0
  74. package/dist/typescript/src/lib/Components/Axis/YAxis/index.d.ts +3 -0
  75. package/dist/typescript/src/lib/Components/Axis/YAxis/index.d.ts.map +1 -0
  76. package/dist/typescript/src/lib/Components/Axis/YAxis/types.d.ts +14 -0
  77. package/dist/typescript/src/lib/Components/Axis/YAxis/types.d.ts.map +1 -0
  78. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts +3 -0
  79. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts.map +1 -0
  80. package/dist/typescript/src/lib/Components/CartesianChart/context/cartesianChartContext.d.ts +8 -0
  81. package/dist/typescript/src/lib/Components/CartesianChart/context/cartesianChartContext.d.ts.map +1 -0
  82. package/dist/typescript/src/lib/Components/CartesianChart/context/index.d.ts +3 -0
  83. package/dist/typescript/src/lib/Components/CartesianChart/context/index.d.ts.map +1 -0
  84. package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts +23 -0
  85. package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts.map +1 -0
  86. package/dist/typescript/src/lib/Components/CartesianChart/index.d.ts +4 -0
  87. package/dist/typescript/src/lib/Components/CartesianChart/index.d.ts.map +1 -0
  88. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts +46 -0
  89. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts.map +1 -0
  90. package/dist/typescript/src/lib/Components/Line/Line.d.ts +3 -0
  91. package/dist/typescript/src/lib/Components/Line/Line.d.ts.map +1 -0
  92. package/dist/typescript/src/lib/Components/Line/index.d.ts +3 -0
  93. package/dist/typescript/src/lib/Components/Line/index.d.ts.map +1 -0
  94. package/dist/typescript/src/lib/Components/Line/types.d.ts +24 -0
  95. package/dist/typescript/src/lib/Components/Line/types.d.ts.map +1 -0
  96. package/dist/typescript/src/lib/Components/Line/utils.d.ts +19 -0
  97. package/dist/typescript/src/lib/Components/Line/utils.d.ts.map +1 -0
  98. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts +2 -8
  99. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts.map +1 -1
  100. package/dist/typescript/src/lib/Components/LineChart/index.d.ts +1 -0
  101. package/dist/typescript/src/lib/Components/LineChart/index.d.ts.map +1 -1
  102. package/dist/typescript/src/lib/Components/LineChart/types.d.ts +62 -0
  103. package/dist/typescript/src/lib/Components/LineChart/types.d.ts.map +1 -0
  104. package/dist/typescript/src/lib/utils/domain/domain.d.ts +17 -0
  105. package/dist/typescript/src/lib/utils/domain/domain.d.ts.map +1 -0
  106. package/dist/typescript/src/lib/utils/index.d.ts +1 -1
  107. package/dist/typescript/src/lib/utils/index.d.ts.map +1 -1
  108. package/dist/typescript/src/lib/utils/scales/scales.d.ts +24 -0
  109. package/dist/typescript/src/lib/utils/scales/scales.d.ts.map +1 -0
  110. package/dist/typescript/src/lib/utils/ticks/ticks.d.ts +27 -0
  111. package/dist/typescript/src/lib/utils/ticks/ticks.d.ts.map +1 -0
  112. package/dist/typescript/src/lib/utils/types.d.ts +90 -6
  113. package/dist/typescript/src/lib/utils/types.d.ts.map +1 -1
  114. package/package.json +11 -1
  115. package/src/lib/Components/Axis/Axis.types.ts +32 -0
  116. package/src/lib/Components/Axis/XAxis/XAxis.test.tsx +89 -0
  117. package/src/lib/Components/Axis/XAxis/XAxis.tsx +111 -0
  118. package/src/lib/Components/Axis/XAxis/index.ts +2 -0
  119. package/src/lib/Components/Axis/XAxis/types.ts +9 -0
  120. package/src/lib/Components/Axis/YAxis/YAxis.test.tsx +89 -0
  121. package/src/lib/Components/Axis/YAxis/YAxis.tsx +110 -0
  122. package/src/lib/Components/Axis/YAxis/index.ts +2 -0
  123. package/src/lib/Components/Axis/YAxis/types.ts +14 -0
  124. package/src/lib/Components/CartesianChart/CartesianChart.tsx +153 -0
  125. package/src/lib/Components/CartesianChart/context/cartesianChartContext.ts +14 -0
  126. package/src/lib/Components/CartesianChart/context/index.ts +10 -0
  127. package/src/lib/Components/CartesianChart/context/useBuildChartContext.test.ts +224 -0
  128. package/src/lib/Components/CartesianChart/context/useBuildChartContext.ts +115 -0
  129. package/src/lib/Components/CartesianChart/index.ts +3 -0
  130. package/src/lib/Components/CartesianChart/types.ts +47 -0
  131. package/src/lib/Components/Line/Line.tsx +88 -0
  132. package/src/lib/Components/Line/index.ts +2 -0
  133. package/src/lib/Components/Line/types.ts +23 -0
  134. package/src/lib/Components/Line/utils.ts +72 -0
  135. package/src/lib/Components/LineChart/LineChart.stories.tsx +287 -4
  136. package/src/lib/Components/LineChart/LineChart.test.tsx +71 -2
  137. package/src/lib/Components/LineChart/LineChart.tsx +90 -34
  138. package/src/lib/Components/LineChart/index.ts +1 -0
  139. package/src/lib/Components/LineChart/types.ts +63 -0
  140. package/src/lib/utils/domain/domain.test.ts +129 -0
  141. package/src/lib/utils/domain/domain.ts +101 -0
  142. package/src/lib/utils/index.ts +11 -1
  143. package/src/lib/utils/scales/scales.test.ts +133 -0
  144. package/src/lib/utils/scales/scales.ts +67 -0
  145. package/src/lib/utils/ticks/ticks.test.ts +136 -0
  146. package/src/lib/utils/ticks/ticks.ts +82 -0
  147. package/src/lib/utils/types.ts +97 -6
  148. package/dist/module/lib/utils/math/index.js +0 -4
  149. package/dist/module/lib/utils/math/index.js.map +0 -1
  150. package/dist/typescript/src/lib/utils/math/index.d.ts +0 -1
  151. package/dist/typescript/src/lib/utils/math/index.d.ts.map +0 -1
  152. package/src/lib/utils/math/index.ts +0 -2
@@ -1,17 +1,300 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
2
+ import { View } from 'react-native';
3
+ import { StoryDecorator } from '../../../../.storybook/StoryDecorator.tsx';
4
+
2
5
  import { LineChart } from './LineChart';
3
6
 
4
- const meta: Meta<typeof LineChart> = {
7
+ const meta = {
5
8
  component: LineChart,
6
9
  title: 'Visualization/LineChart',
7
10
  tags: ['experimental'],
8
- };
11
+ decorators: [
12
+ (Story, context) => {
13
+ return (
14
+ <StoryDecorator context={context}>
15
+ <View style={{ width: 600, padding: 16 }}>
16
+ <Story />
17
+ </View>
18
+ </StoryDecorator>
19
+ );
20
+ },
21
+ ],
22
+ } satisfies Meta<typeof LineChart>;
9
23
 
10
24
  export default meta;
11
- type Story = StoryObj<typeof meta>;
25
+ type Story = StoryObj<typeof LineChart>;
26
+
27
+ const sampleSeries = [
28
+ {
29
+ id: 'prices',
30
+ stroke: '#7B61FF',
31
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
32
+ },
33
+ ];
34
+
35
+ const multiSeries = [
36
+ {
37
+ id: 'lineA',
38
+ label: 'Line A',
39
+ stroke: '#7B61FF',
40
+ data: [5, 15, 10, 90, 85, 70, 30, 25, 25],
41
+ },
42
+ {
43
+ id: 'lineB',
44
+ label: 'Line B',
45
+ stroke: '#44D7B6',
46
+ data: [90, 85, 70, 25, 23, 40, 45, 40, 50],
47
+ },
48
+ ];
12
49
 
13
50
  export const Base: Story = {
51
+ parameters: {
52
+ layout: 'centered',
53
+ backgrounds: { default: 'light' },
54
+ },
55
+ args: {
56
+ series: sampleSeries,
57
+ width: 400,
58
+ height: 250,
59
+ },
60
+ };
61
+
62
+ export const WithXAxis: Story = {
63
+ parameters: {
64
+ layout: 'centered',
65
+ backgrounds: { default: 'light' },
66
+ },
67
+ args: {
68
+ series: sampleSeries,
69
+ width: 400,
70
+ height: 250,
71
+ showXAxis: true,
72
+ xAxis: {
73
+ showLine: true,
74
+ showGrid: true,
75
+ },
76
+ },
77
+ };
78
+
79
+ export const WithStringLabels: Story = {
80
+ parameters: {
81
+ layout: 'centered',
82
+ backgrounds: { default: 'light' },
83
+ },
84
+ args: {
85
+ series: sampleSeries,
86
+ width: 400,
87
+ height: 250,
88
+ showXAxis: true,
89
+ xAxis: {
90
+ data: [
91
+ 'Jan',
92
+ 'Feb',
93
+ 'Mar',
94
+ 'Apr',
95
+ 'May',
96
+ 'Jun',
97
+ 'Jul',
98
+ 'Aug',
99
+ 'Sep',
100
+ 'Oct',
101
+ 'Nov',
102
+ 'Dec',
103
+ 'Jan',
104
+ 'Feb',
105
+ ],
106
+ showLine: true,
107
+ showGrid: true,
108
+ },
109
+ },
110
+ };
111
+
112
+ export const WithCustomTicks: Story = {
113
+ parameters: {
114
+ layout: 'centered',
115
+ backgrounds: { default: 'light' },
116
+ },
117
+ args: {
118
+ series: sampleSeries,
119
+ width: 400,
120
+ height: 250,
121
+ showXAxis: true,
122
+ xAxis: {
123
+ showLine: true,
124
+ showGrid: true,
125
+ ticks: [0, 3, 6, 9, 13],
126
+ },
127
+ },
128
+ };
129
+
130
+ export const WithTickFormatter: Story = {
131
+ parameters: {
132
+ layout: 'centered',
133
+ backgrounds: { default: 'light' },
134
+ },
135
+ args: {
136
+ series: sampleSeries,
137
+ width: 400,
138
+ height: 250,
139
+ showXAxis: true,
140
+ xAxis: {
141
+ data: [
142
+ 'Jan',
143
+ 'Feb',
144
+ 'Mar',
145
+ 'Apr',
146
+ 'May',
147
+ 'Jun',
148
+ 'Jul',
149
+ 'Aug',
150
+ 'Sep',
151
+ 'Oct',
152
+ 'Nov',
153
+ 'Dec',
154
+ 'Jan',
155
+ 'Feb',
156
+ ],
157
+ showLine: true,
158
+ tickLabelFormatter: (value) => `${String(value).slice(0, 1)}`,
159
+ },
160
+ },
161
+ };
162
+
163
+ export const XAxisTop: Story = {
164
+ parameters: {
165
+ layout: 'centered',
166
+ backgrounds: { default: 'light' },
167
+ },
168
+ args: {
169
+ series: sampleSeries,
170
+ width: 400,
171
+ height: 250,
172
+ showXAxis: true,
173
+ xAxis: {
174
+ position: 'top',
175
+ showLine: true,
176
+ showGrid: true,
177
+ },
178
+ },
179
+ };
180
+
181
+ export const MultipleSeries: Story = {
182
+ parameters: {
183
+ layout: 'centered',
184
+ backgrounds: { default: 'light' },
185
+ },
186
+ args: {
187
+ series: multiSeries,
188
+ width: 400,
189
+ height: 300,
190
+ showXAxis: true,
191
+ xAxis: {
192
+ showLine: true,
193
+ showGrid: true,
194
+ },
195
+ },
196
+ };
197
+
198
+ export const CustomDomain: Story = {
199
+ parameters: {
200
+ layout: 'centered',
201
+ backgrounds: { default: 'light' },
202
+ },
203
+ args: {
204
+ series: sampleSeries,
205
+ width: 400,
206
+ height: 250,
207
+ showXAxis: true,
208
+ xAxis: {
209
+ showLine: true,
210
+ showGrid: true,
211
+ },
212
+ yAxis: {
213
+ domain: { min: 0, max: 100 },
214
+ },
215
+ },
216
+ };
217
+
218
+ export const WithBothAxes: Story = {
219
+ parameters: {
220
+ layout: 'centered',
221
+ backgrounds: { default: 'light' },
222
+ },
223
+ args: {
224
+ series: sampleSeries,
225
+ width: 400,
226
+ height: 250,
227
+ showXAxis: true,
228
+ showYAxis: true,
229
+ xAxis: {
230
+ showLine: true,
231
+ showGrid: true,
232
+ },
233
+ yAxis: {
234
+ showLine: true,
235
+ showGrid: true,
236
+ tickLabelFormatter: (value) => `$${value}`,
237
+ },
238
+ },
239
+ };
240
+
241
+ export const WithBothAxesMultipleSeries: Story = {
242
+ parameters: {
243
+ layout: 'centered',
244
+ backgrounds: { default: 'light' },
245
+ },
246
+ args: {
247
+ series: multiSeries,
248
+ width: 400,
249
+ height: 300,
250
+ showXAxis: true,
251
+ showYAxis: true,
252
+ xAxis: {
253
+ showLine: true,
254
+ showGrid: true,
255
+ },
256
+ yAxis: {
257
+ showLine: true,
258
+ domain: { min: 0, max: 100 },
259
+ },
260
+ },
261
+ };
262
+
263
+ export const WithArea: Story = {
264
+ parameters: {
265
+ layout: 'centered',
266
+ backgrounds: { default: 'light' },
267
+ },
268
+ args: {
269
+ series: sampleSeries,
270
+ width: 400,
271
+ height: 250,
272
+ showArea: true,
273
+ showXAxis: true,
274
+ xAxis: {
275
+ showLine: true,
276
+ },
277
+ },
278
+ };
279
+
280
+ export const WithAreaMultipleSeries: Story = {
281
+ parameters: {
282
+ layout: 'centered',
283
+ backgrounds: { default: 'light' },
284
+ },
14
285
  args: {
15
- series: [],
286
+ series: multiSeries,
287
+ width: 400,
288
+ height: 300,
289
+ showArea: true,
290
+ showXAxis: true,
291
+ showYAxis: true,
292
+ xAxis: {
293
+ showLine: true,
294
+ },
295
+ yAxis: {
296
+ showLine: true,
297
+ domain: { min: 0, max: 100 },
298
+ },
16
299
  },
17
300
  };
@@ -1,10 +1,79 @@
1
1
  import { describe, it, expect } from '@jest/globals';
2
+ import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
+ import { ThemeProvider } from '@ledgerhq/lumen-ui-rnative';
2
4
  import { render } from '@testing-library/react-native';
5
+
3
6
  import { LineChart } from './LineChart';
4
7
 
8
+ const sampleSeries = [
9
+ {
10
+ id: 'test',
11
+ stroke: '#000',
12
+ data: [10, 20, 30, 40, 50],
13
+ },
14
+ ];
15
+
16
+ const LineChartWrapper = ({ children }: { children: React.ReactNode }) => {
17
+ return (
18
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='light'>
19
+ {children}
20
+ </ThemeProvider>
21
+ );
22
+ };
23
+
5
24
  describe('LineChart', () => {
6
25
  it('renders the chart container', () => {
7
- const { getByTestId } = render(<LineChart series={[]} />);
8
- expect(getByTestId('line-chart')).toBeTruthy();
26
+ const { getByTestId } = render(
27
+ <LineChartWrapper>
28
+ <LineChart series={sampleSeries} width={400} height={200} />
29
+ </LineChartWrapper>,
30
+ );
31
+ getByTestId('chart-container');
32
+ });
33
+
34
+ it('renders an Svg element', () => {
35
+ const { getByTestId } = render(
36
+ <LineChartWrapper>
37
+ <LineChart series={sampleSeries} width={400} height={200} />
38
+ </LineChartWrapper>,
39
+ );
40
+ getByTestId('chart-svg');
41
+ });
42
+
43
+ it('renders a Path for each series', () => {
44
+ const { getAllByTestId } = render(
45
+ <LineChartWrapper>
46
+ <LineChart series={sampleSeries} width={400} height={200} />
47
+ </LineChartWrapper>,
48
+ );
49
+ expect(getAllByTestId('line-path')).toHaveLength(1);
50
+ });
51
+
52
+ it('renders with no series', () => {
53
+ const { getByTestId } = render(
54
+ <LineChartWrapper>
55
+ <LineChart width={400} height={200} />
56
+ </LineChartWrapper>,
57
+ );
58
+ getByTestId('chart-container');
59
+ });
60
+
61
+ it('renders area fill when showArea is true', () => {
62
+ const { getByTestId } = render(
63
+ <LineChartWrapper>
64
+ <LineChart series={sampleSeries} width={400} height={200} showArea />
65
+ </LineChartWrapper>,
66
+ );
67
+ getByTestId('line-path');
68
+ getByTestId('line-area');
69
+ });
70
+
71
+ it('does not render area fill when showArea is false', () => {
72
+ const { queryAllByTestId } = render(
73
+ <LineChartWrapper>
74
+ <LineChart series={sampleSeries} width={400} height={200} />
75
+ </LineChartWrapper>,
76
+ );
77
+ expect(queryAllByTestId('line-area')).toHaveLength(0);
9
78
  });
10
79
  });
@@ -1,38 +1,94 @@
1
- import { View } from 'react-native';
2
- import { Rect, Svg, Text as SvgText } from 'react-native-svg';
3
- import type { Series } from '../../utils';
4
-
5
- type LineChartProps = {
6
- series: Series[];
7
- width?: number;
8
- height?: number;
9
- };
1
+ import { useMemo } from 'react';
2
+
3
+ import type { AxisConfigProps, ChartInset } from '../../utils/types';
4
+ import { DEFAULT_AXIS_HEIGHT, XAxis } from '../Axis/XAxis';
5
+ import { DEFAULT_AXIS_WIDTH, YAxis } from '../Axis/YAxis';
6
+ import { CartesianChart } from '../CartesianChart';
7
+ import { Line } from '../Line';
8
+
9
+ import type { LineChartProps } from './types';
10
+
11
+ export const LineChart = ({
12
+ series,
13
+ showArea = false,
14
+ areaType = 'gradient',
15
+ showXAxis = false,
16
+ showYAxis = false,
17
+ xAxis,
18
+ yAxis,
19
+ width,
20
+ height = 160,
21
+ inset,
22
+ children,
23
+ }: LineChartProps) => {
24
+ const {
25
+ scaleType: xScaleType,
26
+ data: xData,
27
+ domain: xDomain,
28
+ ...xAxisVisualProps
29
+ } = xAxis ?? {};
30
+
31
+ const {
32
+ scaleType: yScaleType,
33
+ data: yData,
34
+ domain: yDomain,
35
+ ...yAxisVisualProps
36
+ } = yAxis ?? {};
37
+
38
+ const xAxisConfig: Partial<AxisConfigProps> = {
39
+ scaleType: xScaleType,
40
+ data: xData,
41
+ domain: xDomain,
42
+ };
43
+
44
+ const yAxisConfig: Partial<AxisConfigProps> = {
45
+ scaleType: yScaleType,
46
+ data: yData,
47
+ domain: yDomain,
48
+ };
49
+
50
+ const axisPadding: Partial<ChartInset> | undefined = useMemo(() => {
51
+ if (!showXAxis && !showYAxis) return undefined;
52
+ const xAxisPosition =
53
+ xAxisVisualProps.position === 'top' ? 'top' : 'bottom';
54
+ const yAxisPosition =
55
+ yAxisVisualProps.position === 'end' ? 'right' : 'left';
56
+ const yAxisWidth = yAxisVisualProps.width ?? DEFAULT_AXIS_WIDTH;
57
+ return {
58
+ top: showXAxis && xAxisPosition === 'top' ? DEFAULT_AXIS_HEIGHT : 0,
59
+ bottom: showXAxis && xAxisPosition === 'bottom' ? DEFAULT_AXIS_HEIGHT : 0,
60
+ left: showYAxis && yAxisPosition === 'left' ? yAxisWidth : 0,
61
+ right: showYAxis && yAxisPosition === 'right' ? yAxisWidth : 0,
62
+ };
63
+ }, [
64
+ showXAxis,
65
+ showYAxis,
66
+ xAxisVisualProps.position,
67
+ yAxisVisualProps.position,
68
+ yAxisVisualProps.width,
69
+ ]);
10
70
 
11
- export function LineChart({
12
- series: _series,
13
- width = 400,
14
- height = 300,
15
- }: LineChartProps) {
16
71
  return (
17
- <View
18
- testID='line-chart'
19
- accessibilityRole='image'
20
- accessibilityLabel='Line chart'
72
+ <CartesianChart
73
+ series={series ?? []}
74
+ xAxis={xAxisConfig}
75
+ yAxis={yAxisConfig}
76
+ width={width}
77
+ height={height}
78
+ inset={inset}
79
+ axisPadding={axisPadding}
21
80
  >
22
- <Svg width={width} height={height}>
23
- <Rect width={width} height={height} rx={8} fill='#F3F0FA' />
24
- <SvgText
25
- x={width / 2}
26
- y={height / 2}
27
- textAnchor='middle'
28
- alignmentBaseline='central'
29
- fill='#7e4ea5'
30
- fontSize={14}
31
- fontFamily='Inter'
32
- >
33
- LineChart placeholder
34
- </SvgText>
35
- </Svg>
36
- </View>
81
+ {showXAxis && <XAxis {...xAxisVisualProps} />}
82
+ {showYAxis && <YAxis {...yAxisVisualProps} />}
83
+ {series?.map((s) => (
84
+ <Line
85
+ key={s.id}
86
+ seriesId={s.id}
87
+ showArea={showArea}
88
+ areaType={areaType}
89
+ />
90
+ ))}
91
+ {children}
92
+ </CartesianChart>
37
93
  );
38
- }
94
+ };
@@ -1 +1,2 @@
1
1
  export { LineChart } from './LineChart';
2
+ export type { LineChartProps } from './types';
@@ -0,0 +1,63 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import type { AxisConfigProps, ChartInset, Series } from '../../utils/types';
4
+ import type { XAxisProps } from '../Axis/XAxis';
5
+ import type { YAxisProps } from '../Axis/YAxis';
6
+
7
+ export type LineChartProps = {
8
+ /**
9
+ * Data series to render as lines.
10
+ * Each series produces one `<Line>` element.
11
+ */
12
+ series?: Series[];
13
+ /**
14
+ * Whether to show area fill under all lines.
15
+ * @default false
16
+ */
17
+ showArea?: boolean;
18
+ /**
19
+ * Area fill style applied to all lines.
20
+ * When `'gradient'`, renders a vertical gradient from the stroke color to transparent.
21
+ * @default 'gradient'
22
+ */
23
+ areaType?: 'gradient';
24
+ /**
25
+ * Whether to render an x-axis.
26
+ * @default false
27
+ */
28
+ showXAxis?: boolean;
29
+ /**
30
+ * Whether to render a y-axis.
31
+ * @default false
32
+ */
33
+ showYAxis?: boolean;
34
+ /**
35
+ * Combined axis configuration and visual props for the x-axis.
36
+ * Includes scale/domain settings as well as visual options like `showGrid` and `showLine`.
37
+ */
38
+ xAxis?: Partial<AxisConfigProps> & XAxisProps;
39
+ /**
40
+ * Combined axis configuration and visual props for the y-axis.
41
+ * Includes scale/domain settings as well as visual options like `showGrid` and `showLine`.
42
+ */
43
+ yAxis?: Partial<AxisConfigProps> & YAxisProps;
44
+ /**
45
+ * Width of the chart in pixels.
46
+ * When omitted, the component auto-measures via `onLayout`.
47
+ */
48
+ width?: number;
49
+ /**
50
+ * Height of the chart in pixels.
51
+ * @default 160
52
+ */
53
+ height?: number;
54
+ /**
55
+ * Padding between the SVG edge and the drawing area.
56
+ * A number applies uniformly; a partial object overrides individual sides.
57
+ */
58
+ inset?: number | Partial<ChartInset>;
59
+ /**
60
+ * Additional SVG content rendered inside the chart after lines and axes.
61
+ */
62
+ children?: ReactNode;
63
+ };
@@ -0,0 +1,129 @@
1
+ import { describe, expect, it } from '@jest/globals';
2
+
3
+ import type { Series } from '../types';
4
+
5
+ import { computeDataLength, computeXDomain, computeYDomain } from './domain';
6
+
7
+ const makeSeries = (data: (number | null)[]): Series => ({
8
+ id: 'test',
9
+ stroke: '#000',
10
+ data,
11
+ });
12
+
13
+ describe('computeXDomain', () => {
14
+ it('should derive domain from longest series length', () => {
15
+ const result = computeXDomain([
16
+ makeSeries([1, 2, 3]),
17
+ makeSeries([1, 2, 3, 4, 5]),
18
+ ]);
19
+ expect(result).toEqual({ min: 0, max: 4 });
20
+ });
21
+
22
+ it('should return { 0, 0 } for empty series', () => {
23
+ const result = computeXDomain([]);
24
+ expect(result).toEqual({ min: 0, max: 0 });
25
+ });
26
+
27
+ it('should use string axis data length', () => {
28
+ const result = computeXDomain([makeSeries([1, 2])], {
29
+ data: ['Mon', 'Tue', 'Wed'],
30
+ });
31
+ expect(result).toEqual({ min: 0, max: 2 });
32
+ });
33
+
34
+ it('should use numeric axis data min/max', () => {
35
+ const result = computeXDomain([makeSeries([1])], { data: [10, 30, 20] });
36
+ expect(result).toEqual({ min: 10, max: 30 });
37
+ });
38
+
39
+ it('should apply partial domain override', () => {
40
+ const result = computeXDomain([makeSeries([1, 2, 3, 4, 5])], {
41
+ domain: { min: 1 },
42
+ });
43
+ expect(result.min).toBe(1);
44
+ expect(result.max).toBe(4);
45
+ });
46
+
47
+ it('should apply full domain override', () => {
48
+ const result = computeXDomain([makeSeries([1, 2, 3])], {
49
+ domain: { min: 0, max: 10 },
50
+ });
51
+ expect(result).toEqual({ min: 0, max: 10 });
52
+ });
53
+
54
+ it('should apply domain function override', () => {
55
+ const result = computeXDomain([makeSeries([1, 2, 3, 4, 5])], {
56
+ domain: (bounds: { min: number; max: number }) => ({
57
+ min: bounds.min - 1,
58
+ max: bounds.max + 1,
59
+ }),
60
+ });
61
+ expect(result).toEqual({ min: -1, max: 5 });
62
+ });
63
+ });
64
+
65
+ describe('computeYDomain', () => {
66
+ it('should find min/max across all series values', () => {
67
+ const result = computeYDomain([
68
+ makeSeries([10, 50, 30]),
69
+ makeSeries([5, 80]),
70
+ ]);
71
+ expect(result).toEqual({ min: 5, max: 80 });
72
+ });
73
+
74
+ it('should skip null values', () => {
75
+ const result = computeYDomain([makeSeries([null, 10, null, 30, null])]);
76
+ expect(result).toEqual({ min: 10, max: 30 });
77
+ });
78
+
79
+ it('should return { 0, 1 } when no data values exist', () => {
80
+ const result = computeYDomain([makeSeries([])]);
81
+ expect(result).toEqual({ min: 0, max: 1 });
82
+ });
83
+
84
+ it('should handle series without data', () => {
85
+ const result = computeYDomain([{ id: 's', stroke: '#000' }]);
86
+ expect(result).toEqual({ min: 0, max: 1 });
87
+ });
88
+
89
+ it('should apply partial domain override', () => {
90
+ const result = computeYDomain([makeSeries([10, 50])], {
91
+ domain: { min: 0 },
92
+ });
93
+ expect(result).toEqual({ min: 0, max: 50 });
94
+ });
95
+
96
+ it('should apply domain function override', () => {
97
+ const result = computeYDomain([makeSeries([10, 20, 30])], {
98
+ domain: (bounds: { min: number; max: number }) => ({
99
+ min: bounds.min - 5,
100
+ max: bounds.max + 5,
101
+ }),
102
+ });
103
+ expect(result).toEqual({ min: 5, max: 35 });
104
+ });
105
+ });
106
+
107
+ describe('computeDataLength', () => {
108
+ it('should return longest series length', () => {
109
+ expect(
110
+ computeDataLength([makeSeries([1, 2]), makeSeries([1, 2, 3, 4])]),
111
+ ).toBe(4);
112
+ });
113
+
114
+ it('should return 0 for empty series', () => {
115
+ expect(computeDataLength([])).toBe(0);
116
+ });
117
+
118
+ it('should prefer axis data length when provided', () => {
119
+ expect(
120
+ computeDataLength([makeSeries([1, 2, 3])], {
121
+ data: ['A', 'B', 'C', 'D', 'E'],
122
+ }),
123
+ ).toBe(5);
124
+ });
125
+
126
+ it('should handle series without data', () => {
127
+ expect(computeDataLength([{ id: 's', stroke: '#000' }])).toBe(0);
128
+ });
129
+ });