@ledgerhq/lumen-ui-rnative-visualization 0.1.14 → 0.1.16
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.
- package/dist/module/lib/Components/Axis/Axis.constants.js +21 -0
- package/dist/module/lib/Components/Axis/Axis.constants.js.map +1 -0
- package/dist/module/lib/Components/Axis/index.js +5 -0
- package/dist/module/lib/Components/Axis/index.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/CartesianChart.js +34 -22
- package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js +5 -4
- package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js +54 -0
- package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/utils.js +22 -12
- package/dist/module/lib/Components/CartesianChart/utils.js.map +1 -1
- package/dist/module/lib/Components/Line/utils.js +6 -1
- package/dist/module/lib/Components/Line/utils.js.map +1 -1
- package/dist/module/lib/Components/Line/utils.test.js +88 -0
- package/dist/module/lib/Components/Line/utils.test.js.map +1 -0
- package/dist/module/lib/Components/LineChart/LineChart.js +19 -31
- package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
- package/dist/module/lib/Components/LineChart/LineChart.stories.js +38 -0
- package/dist/module/lib/Components/LineChart/LineChart.stories.js.map +1 -1
- package/dist/module/lib/Components/LineChart/LineChart.test.js +192 -0
- package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
- package/dist/module/lib/Components/Point/Point.js +8 -2
- package/dist/module/lib/Components/Point/Point.js.map +1 -1
- package/dist/module/lib/Components/Point/constants.js +9 -0
- package/dist/module/lib/Components/Point/constants.js.map +1 -0
- package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.js +35 -0
- package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.js.map +1 -0
- package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.test.js +153 -0
- package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.test.js.map +1 -0
- package/dist/module/lib/Components/Point/pointContext/index.js +5 -0
- package/dist/module/lib/Components/Point/pointContext/index.js.map +1 -0
- package/dist/module/lib/Components/Point/pointContext/magneticPointsContext.js +10 -0
- package/dist/module/lib/Components/Point/pointContext/magneticPointsContext.js.map +1 -0
- package/dist/module/lib/Components/Point/utils.js +26 -6
- package/dist/module/lib/Components/Point/utils.js.map +1 -1
- package/dist/module/lib/Components/Point/utils.test.js +84 -2
- package/dist/module/lib/Components/Point/utils.test.js.map +1 -1
- package/dist/module/lib/Components/ReferenceLine/utils.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.js +3 -3
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.js +5 -5
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.js +5 -5
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/constants.js +1 -2
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/constants.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js +31 -10
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.js +4 -4
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.js +35 -7
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/utils.js +64 -0
- package/dist/module/lib/Components/Scrubber/utils.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/utils.test.js +128 -1
- package/dist/module/lib/Components/Scrubber/utils.test.js.map +1 -1
- package/dist/module/lib/utils/domain/domain.js.map +1 -1
- package/dist/module/lib/utils/scales/scales.js +13 -4
- package/dist/module/lib/utils/scales/scales.js.map +1 -1
- package/dist/module/lib/utils/scales/scales.test.js +29 -0
- package/dist/module/lib/utils/scales/scales.test.js.map +1 -1
- package/dist/module/lib/utils/ticks/ticks.js +13 -3
- package/dist/module/lib/utils/ticks/ticks.js.map +1 -1
- package/dist/module/lib/utils/ticks/ticks.test.js +34 -0
- package/dist/module/lib/utils/ticks/ticks.test.js.map +1 -1
- package/dist/typescript/src/lib/Components/Axis/Axis.constants.d.ts +5 -0
- package/dist/typescript/src/lib/Components/Axis/Axis.constants.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts +39 -0
- package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Axis/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/Axis/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts +5 -4
- package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts +11 -3
- package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts +18 -8
- package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Line/utils.d.ts +4 -0
- package/dist/typescript/src/lib/Components/Line/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts +1 -1
- package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/LineChart/types.d.ts +10 -3
- package/dist/typescript/src/lib/Components/LineChart/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Point/Point.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Point/Point.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Point/constants.d.ts +7 -0
- package/dist/typescript/src/lib/Components/Point/constants.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/MagneticPointsProvider.d.ts +7 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/MagneticPointsProvider.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/magneticPointsContext.d.ts +13 -0
- package/dist/typescript/src/lib/Components/Point/pointContext/magneticPointsContext.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Point/types.d.ts +7 -0
- package/dist/typescript/src/lib/Components/Point/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Point/utils.d.ts +8 -6
- package/dist/typescript/src/lib/Components/Point/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts +3 -2
- package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.d.ts +1 -2
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts +5 -1
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/types.d.ts +13 -0
- package/dist/typescript/src/lib/Components/Scrubber/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts +33 -3
- package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/domain/domain.d.ts +5 -4
- package/dist/typescript/src/lib/utils/domain/domain.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/index.d.ts +1 -1
- package/dist/typescript/src/lib/utils/index.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/scales/scales.d.ts +11 -5
- package/dist/typescript/src/lib/utils/scales/scales.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/ticks/ticks.d.ts +13 -5
- package/dist/typescript/src/lib/utils/ticks/ticks.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/types.d.ts +3 -28
- package/dist/typescript/src/lib/utils/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/lib/Components/Axis/Axis.constants.ts +23 -0
- package/src/lib/Components/Axis/Axis.types.ts +40 -0
- package/src/lib/Components/Axis/index.ts +2 -0
- package/src/lib/Components/CartesianChart/CartesianChart.tsx +39 -23
- package/src/lib/Components/CartesianChart/context/useBuildChartContext.test.ts +56 -0
- package/src/lib/Components/CartesianChart/context/useBuildChartContext.ts +8 -8
- package/src/lib/Components/CartesianChart/types.ts +11 -3
- package/src/lib/Components/CartesianChart/utils.ts +22 -12
- package/src/lib/Components/Line/utils.test.ts +90 -0
- package/src/lib/Components/Line/utils.ts +6 -1
- package/src/lib/Components/LineChart/LineChart.stories.tsx +36 -0
- package/src/lib/Components/LineChart/LineChart.test.tsx +156 -0
- package/src/lib/Components/LineChart/LineChart.tsx +30 -40
- package/src/lib/Components/LineChart/types.ts +10 -3
- package/src/lib/Components/Point/Point.tsx +9 -3
- package/src/lib/Components/Point/constants.ts +6 -0
- package/src/lib/Components/Point/pointContext/MagneticPointsProvider.test.tsx +162 -0
- package/src/lib/Components/Point/pointContext/MagneticPointsProvider.tsx +45 -0
- package/src/lib/Components/Point/pointContext/index.ts +5 -0
- package/src/lib/Components/Point/pointContext/magneticPointsContext.ts +19 -0
- package/src/lib/Components/Point/types.ts +7 -0
- package/src/lib/Components/Point/utils.test.ts +95 -5
- package/src/lib/Components/Point/utils.ts +35 -7
- package/src/lib/Components/ReferenceLine/utils.ts +4 -7
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.tsx +3 -3
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.tsx +5 -5
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.tsx +4 -5
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.ts +1 -2
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.ts +4 -4
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.ts +29 -15
- package/src/lib/Components/Scrubber/ScrubberProvider.tsx +41 -5
- package/src/lib/Components/Scrubber/types.ts +13 -0
- package/src/lib/Components/Scrubber/utils.test.ts +150 -0
- package/src/lib/Components/Scrubber/utils.ts +93 -3
- package/src/lib/utils/domain/domain.ts +6 -5
- package/src/lib/utils/index.ts +0 -2
- package/src/lib/utils/scales/scales.test.ts +19 -0
- package/src/lib/utils/scales/scales.ts +15 -9
- package/src/lib/utils/ticks/ticks.test.ts +45 -0
- package/src/lib/utils/ticks/ticks.ts +19 -9
- package/src/lib/utils/types.ts +3 -26
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { ChartInset, Series } from '../../utils/types';
|
|
4
|
+
import type { BaseAxisProps } from '../Axis';
|
|
4
5
|
|
|
5
6
|
export type CartesianChartProps = {
|
|
6
7
|
/**
|
|
@@ -10,11 +11,11 @@ export type CartesianChartProps = {
|
|
|
10
11
|
/**
|
|
11
12
|
* Scale and domain configuration for the x-axis.
|
|
12
13
|
*/
|
|
13
|
-
xAxis?: Partial<
|
|
14
|
+
xAxis?: Partial<BaseAxisProps>;
|
|
14
15
|
/**
|
|
15
16
|
* Scale and domain configuration for the y-axis.
|
|
16
17
|
*/
|
|
17
|
-
yAxis?: Partial<
|
|
18
|
+
yAxis?: Partial<BaseAxisProps>;
|
|
18
19
|
/**
|
|
19
20
|
* Width of the chart in pixels.
|
|
20
21
|
* When omitted, the component auto-measures via `onLayout`.
|
|
@@ -63,4 +64,11 @@ export type CartesianChartProps = {
|
|
|
63
64
|
* @default true
|
|
64
65
|
*/
|
|
65
66
|
animate?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Pixel radius within which the scrubber magnetically snaps to registered
|
|
69
|
+
* magnetic `<Point>` components. Requires `enableScrubbing` to be `true`.
|
|
70
|
+
* Set to `0` to disable magnetisation.
|
|
71
|
+
* @default 6
|
|
72
|
+
*/
|
|
73
|
+
magnetRadius?: number;
|
|
66
74
|
};
|
|
@@ -4,21 +4,31 @@ import type { CartesianChartProps } from './types';
|
|
|
4
4
|
export const DEFAULT_HEIGHT = 160;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Internal buffer
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Internal buffer added around the drawing area so SVG content (labels, points,
|
|
8
|
+
* ticks) drawn at the edges is not clipped. The SVG canvas is enlarged by this
|
|
9
|
+
* buffer on every side and shifted back by {@link OVERFLOW_OFFSET} so
|
|
10
|
+
* the chart's layout footprint (and the drawing area) stays exactly the
|
|
11
|
+
* consumer-provided width/height.
|
|
10
12
|
*/
|
|
11
13
|
export const OVERFLOW_BUFFER: ChartInset = {
|
|
12
|
-
top:
|
|
13
|
-
right:
|
|
14
|
-
bottom:
|
|
15
|
-
left:
|
|
14
|
+
top: 25,
|
|
15
|
+
right: 25,
|
|
16
|
+
bottom: 25,
|
|
17
|
+
left: 25,
|
|
16
18
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Top/left offset applied to the (enlarged) SVG group so its drawing area aligns
|
|
21
|
+
* with the container's top-left. The extra width/height added to the SVG
|
|
22
|
+
* overflows symmetrically on every side via `overflow: visible`.
|
|
23
|
+
*
|
|
24
|
+
* Uses `position: relative` to keep parity with the web implementation, where a
|
|
25
|
+
* negative `margin-top` would collapse through the container instead of
|
|
26
|
+
* offsetting the SVG inside it.
|
|
27
|
+
*/
|
|
28
|
+
export const OVERFLOW_OFFSET = {
|
|
29
|
+
position: 'relative' as const,
|
|
30
|
+
top: -OVERFLOW_BUFFER.top,
|
|
31
|
+
left: -OVERFLOW_BUFFER.left,
|
|
22
32
|
};
|
|
23
33
|
export const ZERO_PADDING: ChartInset = {
|
|
24
34
|
top: 0,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getCategoricalScale,
|
|
5
|
+
getNumericScale,
|
|
6
|
+
} from '../../utils/scales/scales';
|
|
7
|
+
|
|
8
|
+
import { toScaledPoints } from './utils';
|
|
9
|
+
|
|
10
|
+
const xScale = getNumericScale({
|
|
11
|
+
scaleType: 'linear',
|
|
12
|
+
domain: { min: 0, max: 7 },
|
|
13
|
+
range: { min: 0, max: 800 },
|
|
14
|
+
nice: false,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const yScale = getNumericScale({
|
|
18
|
+
scaleType: 'linear',
|
|
19
|
+
domain: { min: 0, max: 100 },
|
|
20
|
+
range: { min: 200, max: 0 },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('toScaledPoints', () => {
|
|
24
|
+
it('uses index as x input when no xData is provided', () => {
|
|
25
|
+
const data = [10, 50, 90];
|
|
26
|
+
const points = toScaledPoints(data, xScale, yScale);
|
|
27
|
+
|
|
28
|
+
expect(points).not.toBeNull();
|
|
29
|
+
expect(points).toHaveLength(3);
|
|
30
|
+
expect(points![0][0]).toBe(0);
|
|
31
|
+
expect(points![1][0]).toBeCloseTo(800 / 7);
|
|
32
|
+
expect(points![2][0]).toBeCloseTo((800 / 7) * 2);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('caps iteration to xData.length when series is longer', () => {
|
|
36
|
+
const data = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
|
|
37
|
+
const xData = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'];
|
|
38
|
+
|
|
39
|
+
const points = toScaledPoints(data, xScale, yScale, xData);
|
|
40
|
+
|
|
41
|
+
expect(points).not.toBeNull();
|
|
42
|
+
expect(points).toHaveLength(xData.length);
|
|
43
|
+
expect(points![0][0]).toBe(0);
|
|
44
|
+
expect(points![points!.length - 1][0]).toBe(800);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('skips null values without affecting the cap', () => {
|
|
48
|
+
const data: (number | null)[] = [10, null, 30, 40, 50, 60, 70, 80, 90];
|
|
49
|
+
const xData = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
|
50
|
+
|
|
51
|
+
const points = toScaledPoints(data, xScale, yScale, xData);
|
|
52
|
+
|
|
53
|
+
expect(points).not.toBeNull();
|
|
54
|
+
expect(points).toHaveLength(7);
|
|
55
|
+
expect(points![points!.length - 1][0]).toBe((800 / 7) * 7);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('uses numeric xData values as the x input', () => {
|
|
59
|
+
const data = [10, 20, 30];
|
|
60
|
+
const customX = [0, 3.5, 7];
|
|
61
|
+
|
|
62
|
+
const points = toScaledPoints(data, xScale, yScale, customX);
|
|
63
|
+
|
|
64
|
+
expect(points![0][0]).toBe(0);
|
|
65
|
+
expect(points![1][0]).toBeCloseTo(400);
|
|
66
|
+
expect(points![2][0]).toBe(800);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('centers points within categorical band scales', () => {
|
|
70
|
+
const bandScale = getCategoricalScale({
|
|
71
|
+
domain: { min: 0, max: 2 },
|
|
72
|
+
range: { min: 0, max: 300 },
|
|
73
|
+
padding: 0,
|
|
74
|
+
});
|
|
75
|
+
const data = [10, 50, 90];
|
|
76
|
+
|
|
77
|
+
const points = toScaledPoints(data, bandScale, yScale);
|
|
78
|
+
|
|
79
|
+
expect(points).toHaveLength(3);
|
|
80
|
+
const bandwidth = bandScale.bandwidth();
|
|
81
|
+
expect(points![0][0]).toBe((bandScale(0) ?? 0) + bandwidth / 2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('returns null when fewer than 2 valid points remain', () => {
|
|
85
|
+
const data: (number | null)[] = [10, null, null];
|
|
86
|
+
const xData = ['A', 'B', 'C'];
|
|
87
|
+
|
|
88
|
+
expect(toScaledPoints(data, xScale, yScale, xData)).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -14,6 +14,10 @@ type Point = [x: number, y: number];
|
|
|
14
14
|
*
|
|
15
15
|
* When `xData` contains numeric values, those values are fed into the scale
|
|
16
16
|
* instead of the array index so the points honour a numeric X domain.
|
|
17
|
+
*
|
|
18
|
+
* When `xData` is provided, iteration is capped at `xData.length` so a series
|
|
19
|
+
* with more points than the axis has labels does not overflow past the right
|
|
20
|
+
* edge of the chart. The axis is treated as authoritative for the X domain.
|
|
17
21
|
*/
|
|
18
22
|
export const toScaledPoints = (
|
|
19
23
|
data: (number | null)[],
|
|
@@ -22,8 +26,9 @@ export const toScaledPoints = (
|
|
|
22
26
|
xData?: readonly (string | number)[],
|
|
23
27
|
): Point[] | null => {
|
|
24
28
|
const pts: Point[] = [];
|
|
29
|
+
const limit = xData ? Math.min(data.length, xData.length) : data.length;
|
|
25
30
|
|
|
26
|
-
for (let i = 0; i <
|
|
31
|
+
for (let i = 0; i < limit; i++) {
|
|
27
32
|
const value = data[i];
|
|
28
33
|
if (value === null) continue;
|
|
29
34
|
|
|
@@ -76,6 +76,42 @@ export const WithXAxis: Story = {
|
|
|
76
76
|
},
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
export const XAxisExplicitNumericData: 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
|
+
showLine: true,
|
|
91
|
+
showGrid: true,
|
|
92
|
+
data: [0, 2, 4],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const XAxisStringData: Story = {
|
|
98
|
+
parameters: {
|
|
99
|
+
layout: 'centered',
|
|
100
|
+
backgrounds: { default: 'light' },
|
|
101
|
+
},
|
|
102
|
+
args: {
|
|
103
|
+
series: sampleSeries,
|
|
104
|
+
width: 400,
|
|
105
|
+
height: 250,
|
|
106
|
+
showXAxis: true,
|
|
107
|
+
xAxis: {
|
|
108
|
+
showLine: true,
|
|
109
|
+
showGrid: true,
|
|
110
|
+
data: ['Jan', 'Feb', 'Mar'],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
79
115
|
export const WithStringLabels: Story = {
|
|
80
116
|
parameters: {
|
|
81
117
|
layout: 'centered',
|
|
@@ -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 {
|
|
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 {
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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 {...
|
|
88
|
-
{showYAxis && <YAxis {...
|
|
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 {
|
|
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?:
|
|
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?:
|
|
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
|
-
|
|
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 } =
|
|
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();
|