Skip to content

Commit

Permalink
Error bar domain ignore null (#4297)
Browse files Browse the repository at this point in the history
Duplicate of [this PR](#4192).
I made this PR to also have this fix available in v2.x. Hope this is the
correct way!

## Description
The entry value for an error bar receives the default value `0` in case
it was null. This would cause the domain to be calculated incorrectly,
as `0` may not be a value in the data array.

When using error bars, the domain accounts for any potential in the
error values. Error values are expressed relative to the main entry
value. In case the main entry value is nullish, its default value would
be `0`. This may not be a value in the actual data.

## Related Issue
No known related issues yet

## Motivation and Context
Given this example data:
```
const data = [
  { x: 1, y: 200, errorY: [10, 10] },
  { x: 1, y: 300, errorY: [10, 10] },
  { x: 1, y: 400, errorY: [10, 10] },
]
```

The min value of the y-axis domain would be:
`[min(y - errorY[0]), max(y + errorY[1])] = [200 - 10, 400 + 10] = [190,
410]`

Now consider this other example:
```
const data = [
  { x: 1, y: 200, errorY: [10, 10] },
  { x: 1, y: null, errorY: [10, 10] },
  { x: 1, y: 400, errorY: [10, 10] },
]
```

The current implementation would report:
`[min(y - errorY[0]), max(y + errorY[1])] = [0 - 10, 400 + 10] = [-10,
410]`
While the expected behaviour is:
`[min(y - errorY[0]), max(y + errorY[1])] = [200 - 10, 400 + 10] = [190,
410]`

Some codesandbox examples can be found
[here](https://codesandbox.io/p/sandbox/recharts-issue-template-forked-yvpjfs?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522cls4stopf00073b6n4rx2oizn%2522%252C%2522sizes%2522%253A%255B70%252C30%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522cls4stope00033b6nv9wlqt8t%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522cls4stope00043b6n7de0duiv%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522cls4stope00063b6nu6bf8195%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522cls4stope00033b6nv9wlqt8t%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522cls4stope00023b6njx4kyzoc%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252Findex.js%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522cls4stope00033b6nv9wlqt8t%2522%252C%2522activeTabId%2522%253A%2522cls4stope00023b6njx4kyzoc%2522%257D%252C%2522cls4stope00063b6nu6bf8195%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522cls4stope00053b6n46m3ry4p%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A0%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522cls4stope00063b6nu6bf8195%2522%252C%2522activeTabId%2522%253A%2522cls4stope00053b6n46m3ry4p%2522%257D%252C%2522cls4stope00043b6n7de0duiv%2522%253A%257B%2522tabs%2522%253A%255B%255D%252C%2522id%2522%253A%2522cls4stope00043b6n7de0duiv%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D).

## How Has This Been Tested?
I have added unit tests for the domain creation.

## Types of changes
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist:
- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have added tests to cover my changes.
- [ ] I have added a storybook story or extended an existing story to
show my changes
- [x] All new and existing tests passed.
  • Loading branch information
rinkstiekema committed Mar 15, 2024
1 parent 73974dd commit 37b4df1
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 1 deletion.
4 changes: 3 additions & 1 deletion src/util/ChartUtils.ts
Expand Up @@ -467,7 +467,9 @@ export const getDomainOfErrorBars = (

return data.reduce<NumberDomain>(
(result: NumberDomain, entry: object): NumberDomain => {
const entryValue = getValueByDataKey(entry, dataKey, 0);
const entryValue = getValueByDataKey(entry, dataKey);
if (isNil(entryValue)) return result;

const mainValue = Array.isArray(entryValue) ? [min(entryValue), max(entryValue)] : [entryValue, entryValue];
const errorDomain = keys.reduce(
(prevErrorArr: [number, number], k: DataKey<any>): NumberDomain => {
Expand Down
62 changes: 62 additions & 0 deletions test/util/ChartUtils.spec.tsx
Expand Up @@ -613,6 +613,68 @@ describe('getDomainOfErrorBars', () => {
expect(getDomainOfErrorBars(data, line, 'y', 'horizontal', 'yAxis')).toEqual([85, 220]);
});
});

describe('with null-entries in data array', () => {
const scatter = (
<Scatter>
<ErrorBar dataKey="error" direction="y" />
<ErrorBar dataKey="error2" direction="x" />
</Scatter>
);

it('should ignore null values for domain with direction y in yAxis domain', () => {
const valueNull = {
x: 3,
y: null,
error: 30,
error2: 15,
};
expect(getDomainOfErrorBars([...data, valueNull], scatter, 'y', undefined, 'yAxis')).toEqual([90, 220]);

const valueAndErrorNull = {
x: 3,
y: null,
error: null,
error2: null,
};
expect(getDomainOfErrorBars([...data, valueAndErrorNull], scatter, 'y', undefined, 'yAxis')).toEqual([90, 220]);

const errorNull = {
x: 3,
y: 300,
error: null,
error2: null,
};
expect(getDomainOfErrorBars([...data, errorNull], scatter, 'y', undefined, 'yAxis')).toEqual([90, 300]);
});

it('should ignore null values for domain with direction x in xAxis domain', () => {
const valueNull = {
x: null,
y: 300,
error: 30,
error2: 15,
};

expect(getDomainOfErrorBars([...data, valueNull], scatter, 'x', undefined, 'xAxis')).toEqual([-14, 17]);

const valueAndErrorNull = {
x: null,
y: 300,
error: 30,
error2: null,
};
expect(getDomainOfErrorBars([...data, valueAndErrorNull], scatter, 'x', undefined, 'xAxis')).toEqual([-14, 17]);

const errorNull = {
x: 3,
y: 300,
error: 30,
error2: null,
};
expect(getDomainOfErrorBars([...data, errorNull], scatter, 'x', undefined, 'xAxis')).toEqual([-14, 17]);
});
});
});

describe('exports for backwards-compatibility', () => {
Expand Down

0 comments on commit 37b4df1

Please sign in to comment.