@ledgerhq/lumen-ui-rnative-visualization 0.1.16 → 0.1.18
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/XAxis/XAxis.js +2 -2
- package/dist/module/lib/Components/Axis/XAxis/XAxis.js.map +1 -1
- package/dist/module/lib/Components/Axis/YAxis/YAxis.js +2 -2
- package/dist/module/lib/Components/Axis/YAxis/YAxis.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/CartesianChart.js +29 -22
- package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/ChartEmptyLabel/ChartEmptyLabel.js +61 -0
- package/dist/module/lib/Components/CartesianChart/ChartEmptyLabel/ChartEmptyLabel.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/ChartEmptyLabel/index.js +4 -0
- package/dist/module/lib/Components/CartesianChart/ChartEmptyLabel/index.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.js +3 -4
- package/dist/module/lib/Components/CartesianChart/RevealClip/RevealClipDefs.js.map +1 -1
- package/dist/module/lib/Components/CartesianChart/hooks/useDataFingerprint.js +13 -0
- package/dist/module/lib/Components/CartesianChart/hooks/useDataFingerprint.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/{RevealClip/utils.js → hooks/useRevealClipAnimation.js} +1 -6
- package/dist/module/lib/Components/CartesianChart/hooks/useRevealClipAnimation.js.map +1 -0
- package/dist/module/lib/Components/CartesianChart/hooks/useShimmerAnimation.js +37 -0
- package/dist/module/lib/Components/CartesianChart/hooks/useShimmerAnimation.js.map +1 -0
- package/dist/module/lib/Components/Line/Line.js +13 -8
- package/dist/module/lib/Components/Line/Line.js.map +1 -1
- package/dist/module/lib/Components/Line/constants.js +5 -0
- package/dist/module/lib/Components/Line/constants.js.map +1 -0
- package/dist/module/lib/Components/Line/types.js +2 -0
- package/dist/module/lib/Components/Line/utils.js +24 -7
- package/dist/module/lib/Components/Line/utils.js.map +1 -1
- package/dist/module/lib/Components/Line/utils.test.js +27 -1
- package/dist/module/lib/Components/Line/utils.test.js.map +1 -1
- package/dist/module/lib/Components/LineChart/LineChart.js +116 -27
- package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
- package/dist/module/lib/Components/LineChart/LineChart.test.js +78 -0
- package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.js +72 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.js.map +1 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.test.js +47 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.test.js.map +1 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/index.js +4 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/index.js.map +1 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/types.js +4 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/types.js.map +1 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/utils.js +22 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/utils.js.map +1 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/utils.test.js +21 -0
- package/dist/module/lib/Components/LineChart/LineChartEmptyState/utils.test.js.map +1 -0
- package/dist/module/lib/Components/LineChart/utils.js +83 -0
- package/dist/module/lib/Components/LineChart/utils.js.map +1 -0
- package/dist/module/lib/Components/LineChart/utils.test.js +174 -0
- package/dist/module/lib/Components/LineChart/utils.test.js.map +1 -0
- package/dist/module/lib/Components/ReferenceLine/ReferenceLine.js +4 -4
- package/dist/module/lib/Components/ReferenceLine/ReferenceLine.js.map +1 -1
- package/dist/module/lib/Components/ReferenceLine/constants.js +2 -2
- package/dist/module/lib/Components/ReferenceLine/constants.js.map +1 -1
- package/dist/module/lib/Components/ReferenceLine/utils.js +2 -1
- package/dist/module/lib/Components/ReferenceLine/utils.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js +2 -1
- package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/Scrubber.js +2 -2
- package/dist/module/lib/Components/Scrubber/Scrubber.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.js +2 -1
- package/dist/module/lib/Components/Scrubber/ScrubberProvider.js.map +1 -1
- package/dist/module/lib/Components/Scrubber/utils.js +3 -2
- package/dist/module/lib/Components/Scrubber/utils.js.map +1 -1
- package/dist/module/lib/utils/numbers.js +16 -0
- package/dist/module/lib/utils/numbers.js.map +1 -0
- package/dist/module/lib/utils/ticks/ticks.js +0 -16
- package/dist/module/lib/utils/ticks/ticks.js.map +1 -1
- package/dist/typescript/src/lib/Components/Axis/XAxis/XAxis.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Axis/YAxis/YAxis.d.ts.map +1 -1
- 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/ChartEmptyLabel/ChartEmptyLabel.d.ts +18 -0
- package/dist/typescript/src/lib/Components/CartesianChart/ChartEmptyLabel/ChartEmptyLabel.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/ChartEmptyLabel/index.d.ts +2 -0
- package/dist/typescript/src/lib/Components/CartesianChart/ChartEmptyLabel/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/types.d.ts +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/CartesianChart/hooks/useDataFingerprint.d.ts +9 -0
- package/dist/typescript/src/lib/Components/CartesianChart/hooks/useDataFingerprint.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/{RevealClip/utils.d.ts → hooks/useRevealClipAnimation.d.ts} +2 -5
- package/dist/typescript/src/lib/Components/CartesianChart/hooks/useRevealClipAnimation.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/hooks/useShimmerAnimation.d.ts +13 -0
- package/dist/typescript/src/lib/Components/CartesianChart/hooks/useShimmerAnimation.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts +12 -0
- package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Line/Line.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Line/Line.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Line/constants.d.ts +3 -0
- package/dist/typescript/src/lib/Components/Line/constants.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/Line/index.d.ts +1 -1
- package/dist/typescript/src/lib/Components/Line/index.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Line/types.d.ts +8 -0
- package/dist/typescript/src/lib/Components/Line/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Line/utils.d.ts +5 -4
- 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/LineChartEmptyState/LineChartEmptyState.d.ts +9 -0
- package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/types.d.ts +3 -0
- package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/utils.d.ts +14 -0
- package/dist/typescript/src/lib/Components/LineChart/LineChartEmptyState/utils.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/LineChart/types.d.ts +29 -1
- package/dist/typescript/src/lib/Components/LineChart/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/LineChart/utils.d.ts +43 -0
- package/dist/typescript/src/lib/Components/LineChart/utils.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/ReferenceLine/ReferenceLine.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/ReferenceLine/constants.d.ts +2 -2
- package/dist/typescript/src/lib/Components/ReferenceLine/constants.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/Scrubber/utils.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/numbers.d.ts +13 -0
- package/dist/typescript/src/lib/utils/numbers.d.ts.map +1 -0
- package/dist/typescript/src/lib/utils/ticks/ticks.d.ts +1 -11
- package/dist/typescript/src/lib/utils/ticks/ticks.d.ts.map +1 -1
- package/dist/typescript/src/lib/utils/types.d.ts +20 -2
- package/dist/typescript/src/lib/utils/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/lib/Components/Axis/XAxis/XAxis.tsx +13 -18
- package/src/lib/Components/Axis/YAxis/YAxis.tsx +13 -18
- package/src/lib/Components/CartesianChart/CartesianChart.tsx +29 -23
- package/src/lib/Components/CartesianChart/ChartEmptyLabel/ChartEmptyLabel.tsx +68 -0
- package/src/lib/Components/CartesianChart/ChartEmptyLabel/index.ts +1 -0
- package/src/lib/Components/CartesianChart/RevealClip/RevealClipDefs.tsx +3 -2
- package/src/lib/Components/CartesianChart/RevealClip/types.ts +1 -1
- package/src/lib/Components/CartesianChart/hooks/useDataFingerprint.ts +16 -0
- package/src/lib/Components/CartesianChart/{RevealClip/utils.ts → hooks/useRevealClipAnimation.ts} +1 -9
- package/src/lib/Components/CartesianChart/hooks/useShimmerAnimation.ts +52 -0
- package/src/lib/Components/CartesianChart/types.ts +12 -0
- package/src/lib/Components/Line/Line.tsx +13 -10
- package/src/lib/Components/Line/constants.ts +2 -0
- package/src/lib/Components/Line/index.ts +1 -1
- package/src/lib/Components/Line/types.ts +10 -0
- package/src/lib/Components/Line/utils.test.ts +41 -1
- package/src/lib/Components/Line/utils.ts +42 -6
- package/src/lib/Components/LineChart/LineChart.test.tsx +64 -0
- package/src/lib/Components/LineChart/LineChart.tsx +139 -38
- package/src/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.test.tsx +39 -0
- package/src/lib/Components/LineChart/LineChartEmptyState/LineChartEmptyState.tsx +72 -0
- package/src/lib/Components/LineChart/LineChartEmptyState/index.ts +2 -0
- package/src/lib/Components/LineChart/LineChartEmptyState/types.ts +3 -0
- package/src/lib/Components/LineChart/LineChartEmptyState/utils.test.ts +22 -0
- package/src/lib/Components/LineChart/LineChartEmptyState/utils.ts +24 -0
- package/src/lib/Components/LineChart/types.ts +35 -1
- package/src/lib/Components/LineChart/utils.test.ts +196 -0
- package/src/lib/Components/LineChart/utils.ts +107 -0
- package/src/lib/Components/ReferenceLine/ReferenceLine.tsx +8 -4
- package/src/lib/Components/ReferenceLine/constants.ts +2 -2
- package/src/lib/Components/ReferenceLine/utils.ts +2 -1
- package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.ts +2 -1
- package/src/lib/Components/Scrubber/Scrubber.tsx +13 -2
- package/src/lib/Components/Scrubber/ScrubberProvider.tsx +2 -1
- package/src/lib/Components/Scrubber/utils.ts +3 -5
- package/src/lib/utils/index.ts +1 -0
- package/src/lib/utils/numbers.ts +15 -0
- package/src/lib/utils/ticks/ticks.ts +1 -23
- package/src/lib/utils/types.ts +28 -2
- package/dist/module/lib/Components/CartesianChart/RevealClip/utils.js.map +0 -1
- package/dist/typescript/src/lib/Components/CartesianChart/RevealClip/utils.d.ts.map +0 -1
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
area,
|
|
3
|
+
curveBumpX,
|
|
4
|
+
curveLinear,
|
|
5
|
+
curveMonotoneX,
|
|
6
|
+
curveNatural,
|
|
7
|
+
curveStep,
|
|
8
|
+
curveStepAfter,
|
|
9
|
+
curveStepBefore,
|
|
10
|
+
line,
|
|
11
|
+
type CurveFactory,
|
|
12
|
+
} from 'd3-shape';
|
|
2
13
|
|
|
14
|
+
import { isFiniteNumber } from '../../utils/numbers';
|
|
3
15
|
import { isCategoricalScale } from '../../utils/scales/scales';
|
|
4
16
|
import type {
|
|
5
17
|
ChartScaleFunction,
|
|
18
|
+
CurveType,
|
|
6
19
|
DrawingArea,
|
|
7
20
|
NumericScale,
|
|
8
21
|
} from '../../utils/types';
|
|
@@ -10,7 +23,26 @@ import type {
|
|
|
10
23
|
type Point = [x: number, y: number];
|
|
11
24
|
|
|
12
25
|
/**
|
|
13
|
-
*
|
|
26
|
+
* Maps the public `CurveType` values to their d3-shape curve factories.
|
|
27
|
+
*/
|
|
28
|
+
const CURVE_FACTORIES: Record<CurveType, CurveFactory> = {
|
|
29
|
+
bump: curveBumpX,
|
|
30
|
+
linear: curveLinear,
|
|
31
|
+
monotone: curveMonotoneX,
|
|
32
|
+
natural: curveNatural,
|
|
33
|
+
step: curveStep,
|
|
34
|
+
stepAfter: curveStepAfter,
|
|
35
|
+
stepBefore: curveStepBefore,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const DEFAULT_CURVE: CurveType = 'bump';
|
|
39
|
+
|
|
40
|
+
const getCurveFactory = (curve: CurveType = DEFAULT_CURVE): CurveFactory =>
|
|
41
|
+
CURVE_FACTORIES[curve] ?? CURVE_FACTORIES[DEFAULT_CURVE];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Project series data into scaled [x, y] pixel coordinates, skipping any
|
|
45
|
+
* non-finite entries.
|
|
14
46
|
*
|
|
15
47
|
* When `xData` contains numeric values, those values are fed into the scale
|
|
16
48
|
* instead of the array index so the points honour a numeric X domain.
|
|
@@ -30,7 +62,7 @@ export const toScaledPoints = (
|
|
|
30
62
|
|
|
31
63
|
for (let i = 0; i < limit; i++) {
|
|
32
64
|
const value = data[i];
|
|
33
|
-
if (value
|
|
65
|
+
if (!isFiniteNumber(value)) continue;
|
|
34
66
|
|
|
35
67
|
const xInput =
|
|
36
68
|
xData && typeof xData[i] === 'number' ? (xData[i] as number) : i;
|
|
@@ -49,12 +81,15 @@ export const toScaledPoints = (
|
|
|
49
81
|
/**
|
|
50
82
|
* Build the SVG `d` attribute for the line stroke.
|
|
51
83
|
*/
|
|
52
|
-
export const buildLinePath = (
|
|
84
|
+
export const buildLinePath = (
|
|
85
|
+
points: Point[],
|
|
86
|
+
curve?: CurveType,
|
|
87
|
+
): string | null => {
|
|
53
88
|
return (
|
|
54
89
|
line<Point>()
|
|
55
90
|
.x((d) => d[0])
|
|
56
91
|
.y((d) => d[1])
|
|
57
|
-
.curve(
|
|
92
|
+
.curve(getCurveFactory(curve))(points) ?? null
|
|
58
93
|
);
|
|
59
94
|
};
|
|
60
95
|
|
|
@@ -64,6 +99,7 @@ export const buildLinePath = (points: Point[]): string | null => {
|
|
|
64
99
|
export const buildAreaPath = (
|
|
65
100
|
points: Point[],
|
|
66
101
|
drawingArea: DrawingArea,
|
|
102
|
+
curve?: CurveType,
|
|
67
103
|
): string | null => {
|
|
68
104
|
const yBottom = drawingArea.y + drawingArea.height;
|
|
69
105
|
|
|
@@ -72,6 +108,6 @@ export const buildAreaPath = (
|
|
|
72
108
|
.x((d) => d[0])
|
|
73
109
|
.y0(yBottom)
|
|
74
110
|
.y1((d) => d[1])
|
|
75
|
-
.curve(
|
|
111
|
+
.curve(getCurveFactory(curve))(points) ?? null
|
|
76
112
|
);
|
|
77
113
|
};
|
|
@@ -255,4 +255,68 @@ describe('LineChart', () => {
|
|
|
255
255
|
);
|
|
256
256
|
getByTestId('chart-svg');
|
|
257
257
|
});
|
|
258
|
+
|
|
259
|
+
describe('loading and empty states', () => {
|
|
260
|
+
it('renders the placeholder with no data while loading (state 1)', () => {
|
|
261
|
+
const { getByTestId, queryByTestId } = render(
|
|
262
|
+
<LineChartWrapper>
|
|
263
|
+
<LineChart width={400} height={200} loading />
|
|
264
|
+
</LineChartWrapper>,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
getByTestId('chart-empty-state');
|
|
268
|
+
expect(queryByTestId('line-path')).toBeNull();
|
|
269
|
+
expect(queryByTestId('chart-empty-label')).toBeNull();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('renders the placeholder with the empty label when there is no data and not loading (state 2)', () => {
|
|
273
|
+
const { getByTestId, queryByTestId } = render(
|
|
274
|
+
<LineChartWrapper>
|
|
275
|
+
<LineChart width={400} height={200} emptyLabel='Nothing here' />
|
|
276
|
+
</LineChartWrapper>,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
getByTestId('chart-empty-state');
|
|
280
|
+
expect(
|
|
281
|
+
getByTestId('chart-empty-label', { includeHiddenElements: true }),
|
|
282
|
+
).toHaveTextContent('Nothing here');
|
|
283
|
+
expect(queryByTestId('line-path')).toBeNull();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('defaults the empty label to "No data"', () => {
|
|
287
|
+
const { getByTestId } = render(
|
|
288
|
+
<LineChartWrapper>
|
|
289
|
+
<LineChart width={400} height={200} />
|
|
290
|
+
</LineChartWrapper>,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
expect(
|
|
294
|
+
getByTestId('chart-empty-label', { includeHiddenElements: true }),
|
|
295
|
+
).toHaveTextContent('No data');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('keeps rendering the real line during a transition load (state 3)', () => {
|
|
299
|
+
const { getByTestId, queryByTestId } = render(
|
|
300
|
+
<LineChartWrapper>
|
|
301
|
+
<LineChart series={sampleSeries} width={400} height={200} loading />
|
|
302
|
+
</LineChartWrapper>,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
getByTestId('line-path');
|
|
306
|
+
expect(queryByTestId('chart-empty-state')).toBeNull();
|
|
307
|
+
expect(queryByTestId('chart-empty-label')).toBeNull();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('renders the normal chart when idle with data (state 4)', () => {
|
|
311
|
+
const { getByTestId, queryByTestId } = render(
|
|
312
|
+
<LineChartWrapper>
|
|
313
|
+
<LineChart series={sampleSeries} width={400} height={200} />
|
|
314
|
+
</LineChartWrapper>,
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
getByTestId('line-path');
|
|
318
|
+
expect(queryByTestId('chart-empty-state')).toBeNull();
|
|
319
|
+
expect(queryByTestId('chart-empty-label')).toBeNull();
|
|
320
|
+
});
|
|
321
|
+
});
|
|
258
322
|
});
|
|
@@ -1,13 +1,95 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
|
+
import Animated from 'react-native-reanimated';
|
|
3
|
+
import { G } from 'react-native-svg';
|
|
2
4
|
|
|
3
|
-
import type { ChartInset } from '../../utils/types';
|
|
4
5
|
import { defaultXAxisProps, defaultYAxisProps } from '../Axis';
|
|
5
|
-
import {
|
|
6
|
+
import { XAxis } from '../Axis/XAxis';
|
|
6
7
|
import { YAxis } from '../Axis/YAxis';
|
|
7
8
|
import { CartesianChart } from '../CartesianChart';
|
|
9
|
+
import { ChartEmptyLabel } from '../CartesianChart/ChartEmptyLabel';
|
|
10
|
+
import { useShimmerAnimation } from '../CartesianChart/hooks/useShimmerAnimation';
|
|
8
11
|
import { Line } from '../Line';
|
|
9
12
|
|
|
10
|
-
import
|
|
13
|
+
import { LineChartEmptyState } from './LineChartEmptyState';
|
|
14
|
+
import type {
|
|
15
|
+
LineChartContentProps,
|
|
16
|
+
LineChartLinesProps,
|
|
17
|
+
LineChartProps,
|
|
18
|
+
} from './types';
|
|
19
|
+
import {
|
|
20
|
+
canRenderLine,
|
|
21
|
+
computeAxisPadding,
|
|
22
|
+
getChartDisplayState,
|
|
23
|
+
} from './utils';
|
|
24
|
+
|
|
25
|
+
const AnimatedG = Animated.createAnimatedComponent(G);
|
|
26
|
+
|
|
27
|
+
const LineChartLines = ({
|
|
28
|
+
series,
|
|
29
|
+
showArea,
|
|
30
|
+
areaType,
|
|
31
|
+
}: Readonly<LineChartLinesProps>) => {
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
{series.map((s) => (
|
|
35
|
+
<Line
|
|
36
|
+
key={s.id}
|
|
37
|
+
seriesId={s.id}
|
|
38
|
+
stroke={s.stroke}
|
|
39
|
+
showArea={showArea}
|
|
40
|
+
areaType={areaType}
|
|
41
|
+
/>
|
|
42
|
+
))}
|
|
43
|
+
</>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const LineChartTransitionLines = ({
|
|
48
|
+
series,
|
|
49
|
+
showArea,
|
|
50
|
+
areaType,
|
|
51
|
+
}: Readonly<LineChartLinesProps>) => {
|
|
52
|
+
const { animatedProps } = useShimmerAnimation();
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<AnimatedG animatedProps={animatedProps}>
|
|
56
|
+
<LineChartLines series={series} showArea={showArea} areaType={areaType} />
|
|
57
|
+
</AnimatedG>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const LineChartContent = ({
|
|
62
|
+
series,
|
|
63
|
+
showArea,
|
|
64
|
+
areaType,
|
|
65
|
+
showXAxis,
|
|
66
|
+
showYAxis,
|
|
67
|
+
xAxisConfig,
|
|
68
|
+
yAxisConfig,
|
|
69
|
+
isTransitionLoading,
|
|
70
|
+
children,
|
|
71
|
+
}: Readonly<LineChartContentProps>) => {
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
{showXAxis && <XAxis {...xAxisConfig} />}
|
|
75
|
+
{showYAxis && <YAxis {...yAxisConfig} />}
|
|
76
|
+
{isTransitionLoading ? (
|
|
77
|
+
<LineChartTransitionLines
|
|
78
|
+
series={series}
|
|
79
|
+
showArea={showArea}
|
|
80
|
+
areaType={areaType}
|
|
81
|
+
/>
|
|
82
|
+
) : (
|
|
83
|
+
<LineChartLines
|
|
84
|
+
series={series}
|
|
85
|
+
showArea={showArea}
|
|
86
|
+
areaType={areaType}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
{children}
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
11
93
|
|
|
12
94
|
export const LineChart = ({
|
|
13
95
|
series,
|
|
@@ -18,47 +100,54 @@ export const LineChart = ({
|
|
|
18
100
|
xAxis,
|
|
19
101
|
yAxis,
|
|
20
102
|
width,
|
|
21
|
-
height =
|
|
103
|
+
height = 228,
|
|
22
104
|
inset,
|
|
23
105
|
children,
|
|
24
106
|
enableScrubbing = false,
|
|
25
107
|
onScrubberPositionChange,
|
|
26
108
|
animate,
|
|
27
109
|
magnetRadius,
|
|
28
|
-
|
|
110
|
+
loading = false,
|
|
111
|
+
emptyLabel = 'No data',
|
|
112
|
+
}: Readonly<LineChartProps>) => {
|
|
29
113
|
const xAxisConfig = {
|
|
30
114
|
...defaultXAxisProps,
|
|
31
115
|
...xAxis,
|
|
116
|
+
position: xAxis?.position ?? defaultXAxisProps.position,
|
|
32
117
|
};
|
|
33
118
|
const yAxisConfig = {
|
|
34
119
|
...defaultYAxisProps,
|
|
35
120
|
...yAxis,
|
|
121
|
+
position: yAxis?.position ?? defaultYAxisProps.position,
|
|
122
|
+
width: yAxis?.width ?? defaultYAxisProps.width,
|
|
36
123
|
};
|
|
37
124
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
125
|
+
const xAxisPosition = xAxisConfig.position;
|
|
126
|
+
const yAxisPosition = yAxisConfig.position;
|
|
127
|
+
const yAxisWidth = yAxisConfig.width;
|
|
42
128
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
129
|
+
const axisPadding = useMemo(
|
|
130
|
+
() =>
|
|
131
|
+
computeAxisPadding({
|
|
132
|
+
showXAxis,
|
|
133
|
+
showYAxis,
|
|
134
|
+
xAxisPosition,
|
|
135
|
+
yAxisPosition,
|
|
136
|
+
yAxisWidth,
|
|
137
|
+
}),
|
|
138
|
+
[showXAxis, showYAxis, xAxisPosition, yAxisPosition, yAxisWidth],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const hasData = canRenderLine(series, xAxisConfig.data);
|
|
142
|
+
|
|
143
|
+
const { status, ariaLabel } = getChartDisplayState({
|
|
144
|
+
loading,
|
|
145
|
+
hasData,
|
|
146
|
+
emptyLabel,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const isTransitionLoading = status === 'transition-loading';
|
|
150
|
+
const isPlaceholder = status === 'initial-loading' || status === 'empty';
|
|
62
151
|
|
|
63
152
|
return (
|
|
64
153
|
<CartesianChart
|
|
@@ -71,20 +160,32 @@ export const LineChart = ({
|
|
|
71
160
|
axisPadding={axisPadding}
|
|
72
161
|
enableScrubbing={enableScrubbing}
|
|
73
162
|
onScrubberPositionChange={onScrubberPositionChange}
|
|
74
|
-
animate={animate}
|
|
163
|
+
animate={isTransitionLoading ? false : animate}
|
|
75
164
|
magnetRadius={magnetRadius}
|
|
165
|
+
ariaLabel={ariaLabel}
|
|
166
|
+
ariaBusy={loading}
|
|
167
|
+
overlay={
|
|
168
|
+
status === 'empty' ? (
|
|
169
|
+
<ChartEmptyLabel>{emptyLabel}</ChartEmptyLabel>
|
|
170
|
+
) : undefined
|
|
171
|
+
}
|
|
76
172
|
>
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
seriesId={s.id}
|
|
173
|
+
{isPlaceholder ? (
|
|
174
|
+
<LineChartEmptyState loading={status === 'initial-loading'} />
|
|
175
|
+
) : (
|
|
176
|
+
<LineChartContent
|
|
177
|
+
series={series ?? []}
|
|
83
178
|
showArea={showArea}
|
|
84
179
|
areaType={areaType}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
180
|
+
showXAxis={showXAxis}
|
|
181
|
+
showYAxis={showYAxis}
|
|
182
|
+
xAxisConfig={xAxisConfig}
|
|
183
|
+
yAxisConfig={yAxisConfig}
|
|
184
|
+
isTransitionLoading={isTransitionLoading}
|
|
185
|
+
>
|
|
186
|
+
{children}
|
|
187
|
+
</LineChartContent>
|
|
188
|
+
)}
|
|
88
189
|
</CartesianChart>
|
|
89
190
|
);
|
|
90
191
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, 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
|
+
|
|
6
|
+
import { CartesianChart } from '../../CartesianChart';
|
|
7
|
+
import { LineChartEmptyState } from './LineChartEmptyState';
|
|
8
|
+
import type { LineChartEmptyStateProps } from './types';
|
|
9
|
+
|
|
10
|
+
const renderInChart = (props: LineChartEmptyStateProps) => {
|
|
11
|
+
return render(
|
|
12
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='light'>
|
|
13
|
+
<CartesianChart series={[]} width={400} height={200}>
|
|
14
|
+
<LineChartEmptyState {...props} />
|
|
15
|
+
</CartesianChart>
|
|
16
|
+
</ThemeProvider>,
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe('LineChartEmptyState', () => {
|
|
21
|
+
it('renders the placeholder line', () => {
|
|
22
|
+
const { getByTestId } = renderInChart({});
|
|
23
|
+
|
|
24
|
+
getByTestId('chart-empty-state-line');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders the grid lines', () => {
|
|
28
|
+
const { getByTestId } = renderInChart({});
|
|
29
|
+
|
|
30
|
+
getByTestId('chart-empty-state-grid');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('renders the placeholder while loading', () => {
|
|
34
|
+
const { getByTestId } = renderInChart({ loading: true });
|
|
35
|
+
|
|
36
|
+
getByTestId('chart-empty-state');
|
|
37
|
+
getByTestId('chart-empty-state-line');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useTheme } from '@ledgerhq/lumen-ui-rnative';
|
|
2
|
+
import Animated from 'react-native-reanimated';
|
|
3
|
+
import { G, Line as SvgLine, Path } from 'react-native-svg';
|
|
4
|
+
|
|
5
|
+
import { useCartesianChartContext } from '../../CartesianChart/context';
|
|
6
|
+
import { useShimmerAnimation } from '../../CartesianChart/hooks/useShimmerAnimation';
|
|
7
|
+
import { useRevealClip } from '../../CartesianChart/RevealClip';
|
|
8
|
+
|
|
9
|
+
import type { LineChartEmptyStateProps } from './types';
|
|
10
|
+
import { buildPlaceholderTransform, PLACEHOLDER_LINE_PATH } from './utils';
|
|
11
|
+
|
|
12
|
+
const GRID_LINE_RATIOS = [0.3, 0.5, 0.7];
|
|
13
|
+
const GRID_STROKE_WIDTH = 1;
|
|
14
|
+
const PLACEHOLDER_STROKE_WIDTH = 2;
|
|
15
|
+
|
|
16
|
+
const AnimatedG = Animated.createAnimatedComponent(G);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* SVG placeholder line shown when a LineChart is loading with no data yet (with
|
|
20
|
+
* a shimmer) or has no data to display (static). Uses a fixed line shape scaled
|
|
21
|
+
* into the chart's reserved drawing area, plus static horizontal grid lines, so
|
|
22
|
+
* axes are not needed.
|
|
23
|
+
*/
|
|
24
|
+
export function LineChartEmptyState({
|
|
25
|
+
loading = false,
|
|
26
|
+
}: Readonly<LineChartEmptyStateProps>) {
|
|
27
|
+
const { drawingArea } = useCartesianChartContext();
|
|
28
|
+
const { theme } = useTheme();
|
|
29
|
+
const { animatedProps } = useShimmerAnimation(loading);
|
|
30
|
+
const clipPath = useRevealClip();
|
|
31
|
+
|
|
32
|
+
if (drawingArea.width <= 0 || drawingArea.height <= 0) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<G testID='chart-empty-state'>
|
|
38
|
+
<G testID='chart-empty-state-grid'>
|
|
39
|
+
{GRID_LINE_RATIOS.map((ratio) => {
|
|
40
|
+
const y = drawingArea.y + drawingArea.height * ratio;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<SvgLine
|
|
44
|
+
key={ratio}
|
|
45
|
+
x1={drawingArea.x}
|
|
46
|
+
y1={y}
|
|
47
|
+
x2={drawingArea.x + drawingArea.width}
|
|
48
|
+
y2={y}
|
|
49
|
+
stroke={theme.colors.border.mutedSubtleTransparent}
|
|
50
|
+
strokeWidth={GRID_STROKE_WIDTH}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
})}
|
|
54
|
+
</G>
|
|
55
|
+
<G clipPath={clipPath}>
|
|
56
|
+
<AnimatedG animatedProps={animatedProps}>
|
|
57
|
+
<Path
|
|
58
|
+
testID='chart-empty-state-line'
|
|
59
|
+
d={PLACEHOLDER_LINE_PATH}
|
|
60
|
+
transform={buildPlaceholderTransform(drawingArea)}
|
|
61
|
+
vectorEffect='non-scaling-stroke'
|
|
62
|
+
fill='none'
|
|
63
|
+
stroke={theme.colors.border.muted}
|
|
64
|
+
strokeWidth={PLACEHOLDER_STROKE_WIDTH}
|
|
65
|
+
strokeLinecap='round'
|
|
66
|
+
strokeLinejoin='round'
|
|
67
|
+
/>
|
|
68
|
+
</AnimatedG>
|
|
69
|
+
</G>
|
|
70
|
+
</G>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
import { buildPlaceholderTransform, PLACEHOLDER_LINE_PATH } from './utils';
|
|
4
|
+
|
|
5
|
+
describe('PLACEHOLDER_LINE_PATH', () => {
|
|
6
|
+
it('is an svg path starting with a move command', () => {
|
|
7
|
+
expect(PLACEHOLDER_LINE_PATH).toMatch(/^M/);
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('buildPlaceholderTransform', () => {
|
|
12
|
+
it('translates to the drawing area origin and scales the viewBox to fit', () => {
|
|
13
|
+
const transform = buildPlaceholderTransform({
|
|
14
|
+
x: 10,
|
|
15
|
+
y: 20,
|
|
16
|
+
width: 364,
|
|
17
|
+
height: 104,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(transform).toBe('translate(10, 20) scale(0.5, 0.5)');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DrawingArea } from '../../../utils/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fixed placeholder line shape, authored against a {@link PLACEHOLDER_VIEW_WIDTH}
|
|
5
|
+
* x {@link PLACEHOLDER_VIEW_HEIGHT} viewBox. Reused for every empty/loading
|
|
6
|
+
* state so the placeholder always reads the same.
|
|
7
|
+
*/
|
|
8
|
+
export const PLACEHOLDER_LINE_PATH =
|
|
9
|
+
'M1.00 128.27 C4.02 127.46 13.09 125.96 19.13 123.47 C25.19 120.97 31.23 116.53 37.28 113.31 C43.33 110.10 49.38 106.15 55.42 104.20 C61.48 102.24 67.52 102.53 73.57 101.61 C79.61 100.69 85.65 99.89 91.71 98.70 C97.75 97.51 103.80 95.67 109.84 94.47 C115.90 93.27 121.94 91.71 127.99 91.49 C134.03 91.26 140.09 91.53 146.13 93.16 C152.19 94.79 158.23 98.37 164.27 101.25 C170.32 104.13 176.36 108.16 182.42 110.45 C188.46 112.74 194.51 114.41 200.55 115.00 C206.61 115.60 212.65 114.56 218.70 114.03 C224.75 113.50 230.80 112.56 236.84 111.86 C242.88 111.17 248.94 110.27 254.98 109.87 C261.03 109.48 267.07 109.14 273.13 109.49 C279.17 109.82 285.22 110.90 291.26 111.90 C297.32 112.90 303.36 114.47 309.41 115.50 C315.46 116.54 321.50 118.12 327.55 118.11 C333.59 118.10 339.65 118.08 345.69 115.45 C351.74 112.81 357.78 107.02 363.84 102.29 C369.88 97.54 375.93 90.87 381.97 87.01 C388.03 83.16 394.07 79.81 400.11 79.12 C406.17 78.43 412.21 80.79 418.26 82.88 C424.30 84.99 430.36 88.88 436.40 91.74 C442.45 94.59 448.49 98.24 454.55 100.02 C460.59 101.80 466.64 102.08 472.68 102.44 C478.72 102.81 484.78 102.31 490.82 102.21 C496.87 102.11 502.92 101.95 508.97 101.84 C515.01 101.74 521.07 101.47 527.11 101.58 C533.16 101.68 539.20 101.57 545.26 102.48 C551.30 103.39 557.34 105.37 563.39 107.02 C569.43 108.68 575.49 111.02 581.53 112.40 C587.59 113.79 593.63 114.62 599.68 115.30 C605.72 115.98 611.78 115.81 617.82 116.48 C623.87 117.16 629.91 118.39 635.95 119.32 C642.01 120.25 648.05 121.51 654.10 122.04 C660.15 122.57 666.20 123.89 672.24 122.47 C678.30 121.07 684.34 117.39 690.39 113.54 C696.43 109.69 702.49 103.53 708.53 99.38 C714.57 95.23 722.31 90.65 726.66 88.61';
|
|
10
|
+
|
|
11
|
+
const PLACEHOLDER_VIEW_WIDTH = 728;
|
|
12
|
+
const PLACEHOLDER_VIEW_HEIGHT = 208;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Transform that fits {@link PLACEHOLDER_LINE_PATH} into the drawing area. Pair
|
|
16
|
+
* with `vector-effect: non-scaling-stroke` on the path so the stroke keeps a
|
|
17
|
+
* constant width despite the (potentially non-uniform) scale.
|
|
18
|
+
*/
|
|
19
|
+
export const buildPlaceholderTransform = (drawingArea: DrawingArea): string => {
|
|
20
|
+
const scaleX = drawingArea.width / PLACEHOLDER_VIEW_WIDTH;
|
|
21
|
+
const scaleY = drawingArea.height / PLACEHOLDER_VIEW_HEIGHT;
|
|
22
|
+
|
|
23
|
+
return `translate(${drawingArea.x}, ${drawingArea.y}) scale(${scaleX}, ${scaleY})`;
|
|
24
|
+
};
|
|
@@ -48,7 +48,7 @@ export type LineChartProps = {
|
|
|
48
48
|
width?: number;
|
|
49
49
|
/**
|
|
50
50
|
* Height of the chart in pixels.
|
|
51
|
-
* @default
|
|
51
|
+
* @default 228
|
|
52
52
|
*/
|
|
53
53
|
height?: number;
|
|
54
54
|
/**
|
|
@@ -83,4 +83,38 @@ export type LineChartProps = {
|
|
|
83
83
|
* @default 6
|
|
84
84
|
*/
|
|
85
85
|
magnetRadius?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Whether the chart is loading. When there is no data, renders an animated
|
|
88
|
+
* shimmer placeholder line (initial loading). When data is present (e.g. a
|
|
89
|
+
* time-range change), the current line is recoloured to a muted grey and
|
|
90
|
+
* shimmers until the new `series` is provided (transition loading).
|
|
91
|
+
*
|
|
92
|
+
* To get the transition behaviour, keep passing the previous `series` while
|
|
93
|
+
* refetching; clearing it falls back to the initial placeholder.
|
|
94
|
+
* @default false
|
|
95
|
+
*/
|
|
96
|
+
loading?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Text shown in the centre of the chart when there is no data and the chart
|
|
99
|
+
* is not loading.
|
|
100
|
+
* @default 'No data'
|
|
101
|
+
*/
|
|
102
|
+
emptyLabel?: string;
|
|
86
103
|
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Series-render fields shared by `LineChart` and its internal line
|
|
107
|
+
* sub-components. Derived from {@link LineChartProps} so the option types stay
|
|
108
|
+
* in sync.
|
|
109
|
+
*/
|
|
110
|
+
export type LineChartLinesProps = Required<
|
|
111
|
+
Pick<LineChartProps, 'series' | 'showArea' | 'areaType'>
|
|
112
|
+
>;
|
|
113
|
+
|
|
114
|
+
export type LineChartContentProps = LineChartLinesProps &
|
|
115
|
+
Required<Pick<LineChartProps, 'showXAxis' | 'showYAxis'>> &
|
|
116
|
+
Pick<LineChartProps, 'children'> & {
|
|
117
|
+
xAxisConfig: XAxisProps;
|
|
118
|
+
yAxisConfig: YAxisProps;
|
|
119
|
+
isTransitionLoading: boolean;
|
|
120
|
+
};
|