Skip to content

Commit 87945f8

Browse files
committed
- PR borisyankov#46: Add gap support for null/NaN/Infinity values in data - Modified dataToPoints to mark invalid values with valid flag - Created segmentPoints utility to split data into valid segments - Updated SparklinesLine, SparklinesCurve, and SparklinesBars to handle gaps - Added comprehensive tests for gap handling - PR borisyankov#113: Add fillInvert style option to SparklinesLine - Allows inverting the fill direction for line charts - PR borisyankov#119: Add SparklinesInteractiveLayer component - New component for interactive hover/click with visual feedback - Shows active point with circle and vertical line - PR borisyankov#122: Remove empty onMouseMove handler from SparklinesLine - Made tooltips conditional on onMouseMove presence - PR borisyankov#130: Update README with improved code examples - Use proper JSX syntax highlighting - Wrap examples in function components All tests passing (25 tests across 5 test files)
1 parent 10989b5 commit 87945f8

File tree

12 files changed

+469
-113
lines changed

12 files changed

+469
-113
lines changed

README.md

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,16 @@ Import the Sparklines components that you need; for example to generate a simple
2727

2828
![](http://borisyankov.github.io/react-sparklines/img/basic.png)
2929

30-
```
30+
```jsx
3131
import React from 'react';
3232
import { Sparklines } from 'react-sparklines';
33-
...
34-
<Sparklines data={[5, 10, 5, 20, 8, 15]} limit={5} width={100} height={20} margin={5}>
35-
</Sparklines>
33+
34+
function MyComponent() {
35+
return (
36+
<Sparklines data={[5, 10, 5, 20, 8, 15]} limit={5} width={100} height={20} margin={5}>
37+
</Sparklines>
38+
);
39+
}
3640
```
3741

3842
Sparklines component is a container with the following properties:
@@ -56,27 +60,34 @@ min, max - optional, bound the chart
5660

5761
![](http://borisyankov.github.io/react-sparklines/img/customizable.png)
5862

59-
```
63+
```jsx
6064
import React from 'react';
6165
import { Sparklines, SparklinesLine } from 'react-sparklines';
62-
...
63-
<Sparklines data={[5, 10, 5, 20]}>
64-
<SparklinesLine color="blue" />
65-
</Sparklines>
66+
67+
function MyComponent() {
68+
return (
69+
<Sparklines data={[5, 10, 5, 20]}>
70+
<SparklinesLine color="blue" />
71+
</Sparklines>
72+
);
73+
}
6674
```
6775

6876
#### Bars
6977

7078
![](http://borisyankov.github.io/react-sparklines/img/bars.png)
7179

72-
73-
```
80+
```jsx
7481
import React from 'react';
7582
import { Sparklines, SparklinesBars } from 'react-sparklines';
76-
...
77-
<Sparklines data={[5, 10, 5, 20]}>
78-
<SparklinesBars />
79-
</Sparklines>
83+
84+
function MyComponent() {
85+
return (
86+
<Sparklines data={[5, 10, 5, 20]}>
87+
<SparklinesBars />
88+
</Sparklines>
89+
);
90+
}
8091
```
8192

8293
#### Spots

__tests__/dataToPoints.js

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,58 +18,84 @@ describe('dataToPoints', () => {
1818

1919
it('should return proper values for 1 value', () => {
2020
expect(dataToPoints({ data: [1] })).toEqual([
21-
{x: 0, y: 0.5}
21+
{x: 0, y: 0.5, valid: true}
2222
])
2323
});
2424

2525
it('should return proper values 2+ values', () => {
2626
expect(dataToPoints({ data: [1,1] })).toEqual([
27-
{x: 0, y: 0.5},
28-
{x: 1, y: 0.5}
27+
{x: 0, y: 0.5, valid: true},
28+
{x: 1, y: 0.5, valid: true}
2929
])
3030

3131
expect(dataToPoints({ data: [0,1] })).toEqual([
32-
{x: 0, y: 1},
33-
{x: 1, y: 0}
32+
{x: 0, y: 1, valid: true},
33+
{x: 1, y: 0, valid: true}
3434
])
3535

3636
expect(dataToPoints({ data: [1,0] })).toEqual([
37-
{x: 0, y: 0},
38-
{x: 1, y: 1}
37+
{x: 0, y: 0, valid: true},
38+
{x: 1, y: 1, valid: true}
3939
])
4040

4141
expect(dataToPoints({ data: [0,1,2] })).toEqual([
42-
{x: 0, y: 1},
43-
{x: 0.5, y: 0.5},
44-
{x: 1, y: 0}
42+
{x: 0, y: 1, valid: true},
43+
{x: 0.5, y: 0.5, valid: true},
44+
{x: 1, y: 0, valid: true}
4545
])
4646
});
4747

4848
it('should inerpolate values properly', () => {
4949
expect(dataToPoints({data: [0,1,2], width: 10, height: 10 })).toEqual([
50-
{x: 0, y: 10},
51-
{x: 5, y: 5},
52-
{x: 10, y: 0}
50+
{x: 0, y: 10, valid: true},
51+
{x: 5, y: 5, valid: true},
52+
{x: 10, y: 0, valid: true}
5353
])
5454
});
5555

5656
it('should take min and max into account', () => {
5757
expect(dataToPoints({ data: [1,2,3,4], width: 6, height: 10, max: 2, min: 3 })).toEqual([
58-
{x: 0, y: -10},
59-
{x: 2, y: 0},
60-
{x: 4, y: 10},
61-
{x: 6, y: 20}
58+
{x: 0, y: -10, valid: true},
59+
{x: 2, y: 0, valid: true},
60+
{x: 4, y: 10, valid: true},
61+
{x: 6, y: 20, valid: true}
6262
])
6363
});
6464

6565
it('should return y == height for 0 and null values', () => {
6666
expect(dataToPoints({ data: [0] })).toEqual([
67-
{x: 0, y: 0.5}
67+
{x: 0, y: 0.5, valid: true}
6868
])
6969
expect(dataToPoints({ data: [0, null, 0] })).toEqual([
70-
{x: 0, y: 0.5},
71-
{x: 0.5, y: 0.5},
72-
{x: 1, y: 0.5}
70+
{x: 0, y: 0.5, valid: true},
71+
{x: 0.5, y: null, valid: false},
72+
{x: 1, y: 0.5, valid: true}
7373
])
7474
});
75+
76+
it('should mark invalid values (NaN, Infinity, null, undefined) as invalid', () => {
77+
const result = dataToPoints({ data: [1, NaN, 2, Infinity, 3, null, 4, undefined, 5] });
78+
expect(result[0].valid).toBe(true);
79+
expect(result[1].valid).toBe(false);
80+
expect(result[2].valid).toBe(true);
81+
expect(result[3].valid).toBe(false);
82+
expect(result[4].valid).toBe(true);
83+
expect(result[5].valid).toBe(false);
84+
expect(result[6].valid).toBe(true);
85+
expect(result[7].valid).toBe(false);
86+
expect(result[8].valid).toBe(true);
87+
88+
// Check that invalid values have null y
89+
expect(result[1].y).toBe(null);
90+
expect(result[3].y).toBe(null);
91+
expect(result[5].y).toBe(null);
92+
expect(result[7].y).toBe(null);
93+
94+
// Check that valid values have proper y coordinates
95+
expect(typeof result[0].y).toBe('number');
96+
expect(typeof result[2].y).toBe('number');
97+
expect(typeof result[4].y).toBe('number');
98+
expect(typeof result[6].y).toBe('number');
99+
expect(typeof result[8].y).toBe('number');
100+
});
75101
});

__tests__/segmentPoints.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { describe, it, expect } from 'vitest';
2+
import segmentPoints from '../src/dataProcessing/segmentPoints';
3+
4+
describe('segmentPoints', () => {
5+
it('should return a single segment for all valid points', () => {
6+
const points = [
7+
{ x: 0, y: 0, valid: true },
8+
{ x: 1, y: 1, valid: true },
9+
{ x: 2, y: 2, valid: true }
10+
];
11+
const segments = segmentPoints(points);
12+
13+
expect(segments).toHaveLength(1);
14+
expect(segments[0]).toEqual(points);
15+
});
16+
17+
it('should split into multiple segments when invalid points are present', () => {
18+
const points = [
19+
{ x: 0, y: 0, valid: true },
20+
{ x: 1, y: 1, valid: true },
21+
{ x: 2, y: null, valid: false },
22+
{ x: 3, y: 3, valid: true },
23+
{ x: 4, y: 4, valid: true }
24+
];
25+
const segments = segmentPoints(points);
26+
27+
expect(segments).toHaveLength(2);
28+
expect(segments[0]).toHaveLength(2);
29+
expect(segments[1]).toHaveLength(2);
30+
expect(segments[0]).toEqual([points[0], points[1]]);
31+
expect(segments[1]).toEqual([points[3], points[4]]);
32+
});
33+
34+
it('should handle multiple gaps', () => {
35+
const points = [
36+
{ x: 0, y: 0, valid: true },
37+
{ x: 1, y: null, valid: false },
38+
{ x: 2, y: 2, valid: true },
39+
{ x: 3, y: null, valid: false },
40+
{ x: 4, y: 4, valid: true }
41+
];
42+
const segments = segmentPoints(points);
43+
44+
expect(segments).toHaveLength(3);
45+
expect(segments[0]).toEqual([points[0]]);
46+
expect(segments[1]).toEqual([points[2]]);
47+
expect(segments[2]).toEqual([points[4]]);
48+
});
49+
50+
it('should return empty array for all invalid points', () => {
51+
const points = [
52+
{ x: 0, y: null, valid: false },
53+
{ x: 1, y: null, valid: false },
54+
{ x: 2, y: null, valid: false }
55+
];
56+
const segments = segmentPoints(points);
57+
58+
expect(segments).toHaveLength(0);
59+
});
60+
61+
it('should return empty array for empty input', () => {
62+
const segments = segmentPoints([]);
63+
64+
expect(segments).toHaveLength(0);
65+
});
66+
67+
it('should handle invalid points at the beginning', () => {
68+
const points = [
69+
{ x: 0, y: null, valid: false },
70+
{ x: 1, y: null, valid: false },
71+
{ x: 2, y: 2, valid: true },
72+
{ x: 3, y: 3, valid: true }
73+
];
74+
const segments = segmentPoints(points);
75+
76+
expect(segments).toHaveLength(1);
77+
expect(segments[0]).toEqual([points[2], points[3]]);
78+
});
79+
80+
it('should handle invalid points at the end', () => {
81+
const points = [
82+
{ x: 0, y: 0, valid: true },
83+
{ x: 1, y: 1, valid: true },
84+
{ x: 2, y: null, valid: false },
85+
{ x: 3, y: null, valid: false }
86+
];
87+
const segments = segmentPoints(points);
88+
89+
expect(segments).toHaveLength(1);
90+
expect(segments[0]).toEqual([points[0], points[1]]);
91+
});
92+
93+
it('should handle consecutive invalid points', () => {
94+
const points = [
95+
{ x: 0, y: 0, valid: true },
96+
{ x: 1, y: null, valid: false },
97+
{ x: 2, y: null, valid: false },
98+
{ x: 3, y: null, valid: false },
99+
{ x: 4, y: 4, valid: true }
100+
];
101+
const segments = segmentPoints(points);
102+
103+
expect(segments).toHaveLength(2);
104+
expect(segments[0]).toEqual([points[0]]);
105+
expect(segments[1]).toEqual([points[4]]);
106+
});
107+
});

build/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)