@ledgerhq/lumen-ui-rnative-visualization 0.1.1 → 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 +12 -2
  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
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect } 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
+
6
+ import { CartesianChart } from '../../CartesianChart';
7
+
8
+ import type { XAxisProps } from './types';
9
+ import { XAxis } from './XAxis';
10
+
11
+ const sampleSeries = [
12
+ { id: 'test', stroke: '#000', data: [10, 20, 30, 40, 50] },
13
+ ];
14
+
15
+ const renderXAxis = (props: XAxisProps = {}) =>
16
+ render(
17
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='light'>
18
+ <CartesianChart series={sampleSeries} width={400} height={200}>
19
+ <XAxis {...props} />
20
+ </CartesianChart>
21
+ </ThemeProvider>,
22
+ );
23
+
24
+ describe('XAxis', () => {
25
+ it('renders without crashing', () => {
26
+ const { getByTestId } = renderXAxis();
27
+ getByTestId('chart-svg');
28
+ });
29
+
30
+ it('renders tick labels for explicit ticks', () => {
31
+ const { getByText } = renderXAxis({ ticks: [0, 2, 4] });
32
+ getByText('0');
33
+ getByText('2');
34
+ getByText('4');
35
+ });
36
+
37
+ it('does not render tick labels for empty ticks', () => {
38
+ const { queryByText } = renderXAxis({ ticks: [] });
39
+ expect(queryByText('0')).toBeNull();
40
+ });
41
+
42
+ it('applies tickLabelFormatter', () => {
43
+ const formatter = (value: number | string) => `v${value}`;
44
+ const { getByText } = renderXAxis({
45
+ ticks: [0, 4],
46
+ tickLabelFormatter: formatter,
47
+ });
48
+ getByText('v0');
49
+ getByText('v4');
50
+ });
51
+
52
+ it('renders with showGrid enabled', () => {
53
+ const { toJSON } = renderXAxis({ showGrid: true, ticks: [0, 2] });
54
+ expect(toJSON()).toBeTruthy();
55
+ });
56
+
57
+ it('renders with showLine enabled', () => {
58
+ const { toJSON } = renderXAxis({ showLine: true, ticks: [] });
59
+ expect(toJSON()).toBeTruthy();
60
+ });
61
+
62
+ it('renders with showTickMark enabled', () => {
63
+ const { toJSON } = renderXAxis({ showTickMark: true, ticks: [0, 2] });
64
+ expect(toJSON()).toBeTruthy();
65
+ });
66
+
67
+ it('renders with all visual options enabled', () => {
68
+ const { getByText } = renderXAxis({
69
+ showGrid: true,
70
+ showLine: true,
71
+ showTickMark: true,
72
+ ticks: [0, 4],
73
+ });
74
+ getByText('0');
75
+ getByText('4');
76
+ });
77
+
78
+ it('does not render axis when width is 0', () => {
79
+ const { queryByText } = render(
80
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='light'>
81
+ <CartesianChart series={sampleSeries} width={0} height={200}>
82
+ <XAxis ticks={[0, 2, 4]} />
83
+ </CartesianChart>
84
+ </ThemeProvider>,
85
+ );
86
+ expect(queryByText('0')).toBeNull();
87
+ expect(queryByText('2')).toBeNull();
88
+ });
89
+ });
@@ -0,0 +1,111 @@
1
+ import { useTheme } from '@ledgerhq/lumen-ui-rnative';
2
+ import { useMemo } from 'react';
3
+ import { G, Line as SvgLine, Text as SvgText } from 'react-native-svg';
4
+
5
+ import { buildTicksData } from '../../../utils/ticks/ticks';
6
+ import { useCartesianChartContext } from '../../CartesianChart/context';
7
+
8
+ import type { XAxisProps } from './types';
9
+
10
+ const STROKE_WIDTH = 1;
11
+ const TICK_MARK_SIZE = 4;
12
+ const TICK_LABEL_OFFSET = 6;
13
+ export const DEFAULT_AXIS_HEIGHT = 28;
14
+
15
+ export const XAxis = ({
16
+ gridLineStyle = 'dashed',
17
+ position = 'bottom',
18
+ showGrid = false,
19
+ showLine = false,
20
+ showTickMark = false,
21
+ ticks: ticksProp,
22
+ tickLabelFormatter,
23
+ }: XAxisProps) => {
24
+ const { getXScale, getXAxisConfig, drawingArea } = useCartesianChartContext();
25
+ const { theme } = useTheme();
26
+
27
+ const xScale = getXScale();
28
+ const xAxisConfig = getXAxisConfig();
29
+
30
+ const ticksData = useMemo(
31
+ () =>
32
+ xScale
33
+ ? buildTicksData(xScale, xAxisConfig, ticksProp, tickLabelFormatter)
34
+ : [],
35
+ [xScale, xAxisConfig, ticksProp, tickLabelFormatter],
36
+ );
37
+
38
+ if (!xScale || drawingArea.width <= 0) {
39
+ return null;
40
+ }
41
+
42
+ const isTop = position === 'top';
43
+ const axisY = isTop ? drawingArea.y : drawingArea.y + drawingArea.height;
44
+
45
+ const fontSize = theme.typographies.body4.fontSize;
46
+ const tickDirection = isTop ? -1 : 1;
47
+ const labelY = axisY + tickDirection * (TICK_MARK_SIZE + TICK_LABEL_OFFSET);
48
+ const labelDy = isTop ? 0 : fontSize * 0.8;
49
+
50
+ const gridStroke = theme.colors.border.mutedSubtle;
51
+ const lineStroke = theme.colors.border.muted;
52
+ const textFill = theme.colors.text.muted;
53
+
54
+ return (
55
+ <G>
56
+ {showGrid &&
57
+ ticksData.map((tick, i) => (
58
+ <SvgLine
59
+ key={`grid-${tick.value}-${i}`}
60
+ x1={tick.position}
61
+ y1={drawingArea.y}
62
+ x2={tick.position}
63
+ y2={drawingArea.y + drawingArea.height}
64
+ stroke={gridStroke}
65
+ strokeWidth={STROKE_WIDTH}
66
+ strokeDasharray={gridLineStyle === 'dashed' ? '3 3' : undefined}
67
+ />
68
+ ))}
69
+
70
+ {showLine && (
71
+ <SvgLine
72
+ x1={drawingArea.x}
73
+ y1={axisY}
74
+ x2={drawingArea.x + drawingArea.width}
75
+ y2={axisY}
76
+ stroke={lineStroke}
77
+ strokeWidth={STROKE_WIDTH}
78
+ strokeLinecap='square'
79
+ />
80
+ )}
81
+
82
+ {showTickMark &&
83
+ ticksData.map((tick, i) => (
84
+ <SvgLine
85
+ key={`tick-${tick.value}-${i}`}
86
+ x1={tick.position}
87
+ y1={axisY}
88
+ x2={tick.position}
89
+ y2={axisY + tickDirection * TICK_MARK_SIZE}
90
+ stroke={lineStroke}
91
+ strokeWidth={STROKE_WIDTH}
92
+ />
93
+ ))}
94
+
95
+ {ticksData.map((tick, i) => (
96
+ <SvgText
97
+ key={`label-${tick.value}-${i}`}
98
+ x={tick.position}
99
+ y={labelY}
100
+ dy={labelDy}
101
+ textAnchor='middle'
102
+ fill={textFill}
103
+ fontSize={fontSize}
104
+ fontFamily={theme.fontFamilies.sans}
105
+ >
106
+ {tick.label}
107
+ </SvgText>
108
+ ))}
109
+ </G>
110
+ );
111
+ };
@@ -0,0 +1,2 @@
1
+ export { XAxis, DEFAULT_AXIS_HEIGHT } from './XAxis';
2
+ export type { XAxisProps } from './types';
@@ -0,0 +1,9 @@
1
+ import type { BaseAxisProps } from '../Axis.types';
2
+
3
+ export type XAxisProps = BaseAxisProps & {
4
+ /**
5
+ * Where the x-axis is rendered relative to the drawing area.
6
+ * @default 'bottom'
7
+ */
8
+ position?: 'top' | 'bottom';
9
+ };
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect } 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
+
6
+ import { CartesianChart } from '../../CartesianChart';
7
+
8
+ import type { YAxisProps } from './types';
9
+ import { YAxis } from './YAxis';
10
+
11
+ const sampleSeries = [
12
+ { id: 'test', stroke: '#000', data: [10, 20, 30, 40, 50] },
13
+ ];
14
+
15
+ const renderYAxis = (props: YAxisProps = {}) =>
16
+ render(
17
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='light'>
18
+ <CartesianChart series={sampleSeries} width={400} height={200}>
19
+ <YAxis {...props} />
20
+ </CartesianChart>
21
+ </ThemeProvider>,
22
+ );
23
+
24
+ describe('YAxis', () => {
25
+ it('renders without crashing', () => {
26
+ const { getByTestId } = renderYAxis();
27
+ getByTestId('chart-svg');
28
+ });
29
+
30
+ it('renders tick labels for explicit ticks', () => {
31
+ const { getByText } = renderYAxis({ ticks: [10, 30, 50] });
32
+ getByText('10');
33
+ getByText('30');
34
+ getByText('50');
35
+ });
36
+
37
+ it('does not render tick labels for empty ticks', () => {
38
+ const { queryByText } = renderYAxis({ ticks: [] });
39
+ expect(queryByText('10')).toBeNull();
40
+ });
41
+
42
+ it('applies tickLabelFormatter', () => {
43
+ const formatter = (value: number | string) => `$${value}`;
44
+ const { getByText } = renderYAxis({
45
+ ticks: [10, 50],
46
+ tickLabelFormatter: formatter,
47
+ });
48
+ getByText('$10');
49
+ getByText('$50');
50
+ });
51
+
52
+ it('renders with showGrid enabled', () => {
53
+ const { toJSON } = renderYAxis({ showGrid: true, ticks: [10, 30] });
54
+ expect(toJSON()).toBeTruthy();
55
+ });
56
+
57
+ it('renders with showLine enabled', () => {
58
+ const { toJSON } = renderYAxis({ showLine: true, ticks: [] });
59
+ expect(toJSON()).toBeTruthy();
60
+ });
61
+
62
+ it('renders with showTickMark enabled', () => {
63
+ const { toJSON } = renderYAxis({ showTickMark: true, ticks: [10, 30] });
64
+ expect(toJSON()).toBeTruthy();
65
+ });
66
+
67
+ it('renders with all visual options enabled', () => {
68
+ const { getByText } = renderYAxis({
69
+ showGrid: true,
70
+ showLine: true,
71
+ showTickMark: true,
72
+ ticks: [10, 50],
73
+ });
74
+ getByText('10');
75
+ getByText('50');
76
+ });
77
+
78
+ it('does not render axis when height is 0', () => {
79
+ const { queryByText } = render(
80
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='light'>
81
+ <CartesianChart series={sampleSeries} width={400} height={0}>
82
+ <YAxis ticks={[10, 30, 50]} />
83
+ </CartesianChart>
84
+ </ThemeProvider>,
85
+ );
86
+ expect(queryByText('10')).toBeNull();
87
+ expect(queryByText('30')).toBeNull();
88
+ });
89
+ });
@@ -0,0 +1,110 @@
1
+ import { useTheme } from '@ledgerhq/lumen-ui-rnative';
2
+ import { useMemo } from 'react';
3
+ import { G, Line as SvgLine, Text as SvgText } from 'react-native-svg';
4
+
5
+ import { buildTicksData } from '../../../utils/ticks/ticks';
6
+ import { useCartesianChartContext } from '../../CartesianChart/context';
7
+
8
+ import type { YAxisProps } from './types';
9
+
10
+ const STROKE_WIDTH = 1;
11
+ const TICK_MARK_SIZE = 4;
12
+ const TICK_LABEL_OFFSET = 6;
13
+ export const DEFAULT_AXIS_WIDTH = 40;
14
+
15
+ export const YAxis = ({
16
+ position = 'start',
17
+ showGrid = false,
18
+ showLine = false,
19
+ showTickMark = false,
20
+ gridLineStyle = 'dashed',
21
+ ticks: ticksProp,
22
+ tickLabelFormatter,
23
+ }: YAxisProps) => {
24
+ const { getYScale, getYAxisConfig, drawingArea } = useCartesianChartContext();
25
+ const { theme } = useTheme();
26
+
27
+ const yScale = getYScale();
28
+ const yAxisConfig = getYAxisConfig();
29
+
30
+ const ticksData = useMemo(
31
+ () =>
32
+ yScale
33
+ ? buildTicksData(yScale, yAxisConfig, ticksProp, tickLabelFormatter)
34
+ : [],
35
+ [yScale, yAxisConfig, ticksProp, tickLabelFormatter],
36
+ );
37
+
38
+ if (!yScale || drawingArea.height <= 0) {
39
+ return null;
40
+ }
41
+
42
+ const isStart = position === 'start';
43
+ const axisX = isStart ? drawingArea.x : drawingArea.x + drawingArea.width;
44
+ const tickDirection = isStart ? -1 : 1;
45
+ const labelX = axisX + tickDirection * (TICK_MARK_SIZE + TICK_LABEL_OFFSET);
46
+ const fontSize = theme.typographies.body4.fontSize;
47
+ const labelDy = fontSize * 0.35;
48
+
49
+ const gridStroke = theme.colors.border.mutedSubtle;
50
+ const lineStroke = theme.colors.border.muted;
51
+ const textFill = theme.colors.text.muted;
52
+
53
+ return (
54
+ <G>
55
+ {showGrid &&
56
+ ticksData.map((tick, i) => (
57
+ <SvgLine
58
+ key={`grid-${tick.value}-${i}`}
59
+ x1={drawingArea.x}
60
+ y1={tick.position}
61
+ x2={drawingArea.x + drawingArea.width}
62
+ y2={tick.position}
63
+ stroke={gridStroke}
64
+ strokeWidth={STROKE_WIDTH}
65
+ strokeDasharray={gridLineStyle === 'dashed' ? '3 3' : undefined}
66
+ />
67
+ ))}
68
+
69
+ {showLine && (
70
+ <SvgLine
71
+ x1={axisX}
72
+ y1={drawingArea.y}
73
+ x2={axisX}
74
+ y2={drawingArea.y + drawingArea.height}
75
+ stroke={lineStroke}
76
+ strokeWidth={STROKE_WIDTH}
77
+ strokeLinecap='square'
78
+ />
79
+ )}
80
+
81
+ {showTickMark &&
82
+ ticksData.map((tick, i) => (
83
+ <SvgLine
84
+ key={`tick-${tick.value}-${i}`}
85
+ x1={axisX}
86
+ y1={tick.position}
87
+ x2={axisX + tickDirection * TICK_MARK_SIZE}
88
+ y2={tick.position}
89
+ stroke={lineStroke}
90
+ strokeWidth={STROKE_WIDTH}
91
+ />
92
+ ))}
93
+
94
+ {ticksData.map((tick, i) => (
95
+ <SvgText
96
+ key={`label-${tick.value}-${i}`}
97
+ x={labelX}
98
+ y={tick.position}
99
+ dy={labelDy}
100
+ textAnchor={position === 'start' ? 'end' : 'start'}
101
+ fill={textFill}
102
+ fontSize={theme.typographies.body4.fontSize}
103
+ fontFamily={theme.fontFamilies.sans}
104
+ >
105
+ {tick.label}
106
+ </SvgText>
107
+ ))}
108
+ </G>
109
+ );
110
+ };
@@ -0,0 +1,2 @@
1
+ export { YAxis, DEFAULT_AXIS_WIDTH } from './YAxis';
2
+ export type { YAxisProps } from './types';
@@ -0,0 +1,14 @@
1
+ import type { BaseAxisProps } from '../Axis.types';
2
+
3
+ export type YAxisProps = BaseAxisProps & {
4
+ /**
5
+ * Where the y-axis is rendered relative to the drawing area.
6
+ * @default 'start'
7
+ */
8
+ position?: 'start' | 'end';
9
+ /**
10
+ * Width of the axis in pixels.
11
+ * @default 40
12
+ */
13
+ width?: number;
14
+ };
@@ -0,0 +1,153 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ import { View, type LayoutChangeEvent } from 'react-native';
3
+ import { Svg } from 'react-native-svg';
4
+
5
+ import type { ChartInset } from '../../utils/types';
6
+
7
+ import { CartesianChartProvider, useBuildChartContext } from './context';
8
+ import type { CartesianChartProps } from './types';
9
+
10
+ const DEFAULT_HEIGHT = 160;
11
+ const DEFAULT_INSET: ChartInset = {
12
+ top: 5,
13
+ right: 20,
14
+ bottom: 0,
15
+ left: 20,
16
+ };
17
+
18
+ const DEFAULT_NEGATIVE_MARGIN = {
19
+ marginLeft: -20,
20
+ marginRight: -20,
21
+ };
22
+
23
+ const ZERO_PADDING: ChartInset = {
24
+ top: 0,
25
+ right: 0,
26
+ bottom: 0,
27
+ left: 0,
28
+ };
29
+
30
+ const resolveInset = (inset: CartesianChartProps['inset']): ChartInset => {
31
+ if (inset === undefined) {
32
+ return DEFAULT_INSET;
33
+ }
34
+
35
+ if (typeof inset === 'number') {
36
+ return {
37
+ top: inset + DEFAULT_INSET.top,
38
+ right: inset + DEFAULT_INSET.right,
39
+ bottom: inset + DEFAULT_INSET.bottom,
40
+ left: inset + DEFAULT_INSET.left,
41
+ };
42
+ }
43
+ return {
44
+ top: (inset.top ?? 0) + DEFAULT_INSET.top,
45
+ right: (inset.right ?? 0) + DEFAULT_INSET.right,
46
+ bottom: (inset.bottom ?? 0) + DEFAULT_INSET.bottom,
47
+ left: (inset.left ?? 0) + DEFAULT_INSET.left,
48
+ };
49
+ };
50
+
51
+ const resolveAxisPadding = (
52
+ padding: CartesianChartProps['axisPadding'],
53
+ ): ChartInset => {
54
+ if (padding === undefined) {
55
+ return ZERO_PADDING;
56
+ }
57
+
58
+ return {
59
+ top: padding.top ?? 0,
60
+ right: padding.right ?? 0,
61
+ bottom: padding.bottom ?? 0,
62
+ left: padding.left ?? 0,
63
+ };
64
+ };
65
+
66
+ export const CartesianChart = ({
67
+ series,
68
+ xAxis,
69
+ yAxis,
70
+ width,
71
+ height = DEFAULT_HEIGHT,
72
+ inset,
73
+ axisPadding,
74
+ ariaLabel = 'Chart',
75
+ children,
76
+ }: CartesianChartProps) => {
77
+ const [measuredWidth, setMeasuredWidth] = useState<number | undefined>(width);
78
+
79
+ const needsMeasurement = width === undefined;
80
+
81
+ const handleLayout = useCallback(
82
+ (e: LayoutChangeEvent) => {
83
+ if (needsMeasurement) {
84
+ setMeasuredWidth(e.nativeEvent.layout.width);
85
+ }
86
+ },
87
+ [needsMeasurement],
88
+ );
89
+
90
+ const resolvedWidth = width ?? measuredWidth ?? 0;
91
+
92
+ const resolvedInset = useMemo(() => resolveInset(inset), [inset]);
93
+ const resolvedAxisPadding = useMemo(
94
+ () => resolveAxisPadding(axisPadding),
95
+ [axisPadding],
96
+ );
97
+
98
+ const contextValue = useBuildChartContext({
99
+ series,
100
+ xAxis,
101
+ yAxis,
102
+ width: resolvedWidth,
103
+ height,
104
+ inset: resolvedInset,
105
+ axisPadding: resolvedAxisPadding,
106
+ });
107
+
108
+ const svgContent = resolvedWidth > 0 && (
109
+ <Svg
110
+ testID='chart-svg'
111
+ width={resolvedWidth}
112
+ height={height}
113
+ style={{ overflow: 'visible' }}
114
+ >
115
+ <CartesianChartProvider value={contextValue}>
116
+ {children}
117
+ </CartesianChartProvider>
118
+ </Svg>
119
+ );
120
+
121
+ if (needsMeasurement) {
122
+ return (
123
+ <View
124
+ testID='chart-container'
125
+ onLayout={handleLayout}
126
+ style={{
127
+ height,
128
+ ...DEFAULT_NEGATIVE_MARGIN,
129
+ }}
130
+ accessibilityRole='image'
131
+ accessibilityLabel={ariaLabel}
132
+ >
133
+ {svgContent}
134
+ </View>
135
+ );
136
+ }
137
+
138
+ return (
139
+ <View
140
+ testID='chart-container'
141
+ accessibilityRole='image'
142
+ accessibilityLabel={ariaLabel}
143
+ style={{
144
+ width: resolvedWidth,
145
+ height,
146
+ marginLeft: -20,
147
+ marginRight: -20,
148
+ }}
149
+ >
150
+ {svgContent}
151
+ </View>
152
+ );
153
+ };
@@ -0,0 +1,14 @@
1
+ import { createSafeContext } from '@ledgerhq/lumen-utils-shared';
2
+
3
+ import type { CartesianChartContextValue } from '../../../utils/types';
4
+
5
+ const [CartesianChartProvider, _useCartesianChartSafeContext] =
6
+ createSafeContext<CartesianChartContextValue>('CartesianChart');
7
+
8
+ export const useCartesianChartContext = () =>
9
+ _useCartesianChartSafeContext({
10
+ consumerName: 'useCartesianChartContext',
11
+ contextRequired: true,
12
+ });
13
+
14
+ export { CartesianChartProvider };
@@ -0,0 +1,10 @@
1
+ export {
2
+ CartesianChartProvider,
3
+ useCartesianChartContext,
4
+ } from './cartesianChartContext';
5
+ export {
6
+ buildScale,
7
+ computeAxisRange,
8
+ computeDrawingArea,
9
+ useBuildChartContext,
10
+ } from './useBuildChartContext';