Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error bar domain fix #2863

Merged
merged 2 commits into from Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/chart/generateCategoricalChart.tsx
Expand Up @@ -325,6 +325,7 @@ const getAxisMapByAxes = (
graphicalItems.filter((item: any) => item.props[axisIdKey] === axisId && !item.props.hide),
dataKey,
axisType,
layout,
);

if (errorBarsDomain) {
Expand All @@ -349,6 +350,7 @@ const getAxisMapByAxes = (
displayedData,
graphicalItems.filter((item: any) => item.props[axisIdKey] === axisId && !item.props.hide),
type,
layout,
true,
);
}
Expand Down Expand Up @@ -439,6 +441,7 @@ const getAxisMapByItems = (
displayedData,
graphicalItems.filter((item: any) => item.props[axisIdKey] === axisId && !item.props.hide),
'number',
layout,
),
Axis.defaultProps.allowDataOverflow,
);
Expand Down
61 changes: 51 additions & 10 deletions src/util/ChartUtils.ts
Expand Up @@ -397,13 +397,39 @@ export const appendOffsetOfLegend = (offset: any, items: Array<FormattedGraphica
return newOffset;
};

export const getDomainOfErrorBars = (data: any[], item: any, dataKey: any, axisType?: AxisType) => {
const { children } = item.props;
const errorBars = findAllByType(children, 'ErrorBar').filter((errorBarChild: any) => {
const { direction } = errorBarChild.props;
const isErrorBarRelevantForAxis = (layout?: LayoutType, axisType?: AxisType, direction?: 'x' | 'y') => {
if (_.isNil(axisType)) {
return true;
}

return _.isNil(direction) || _.isNil(axisType) ? true : axisType.indexOf(direction) >= 0;
});
if (layout === 'horizontal') {
return axisType === 'yAxis';
}
if (layout === 'vertical') {
return axisType === 'xAxis';
}

if (direction === 'x') {
return axisType === 'xAxis';
}
if (direction === 'y') {
return axisType === 'yAxis';
}

return true;
};

export const getDomainOfErrorBars = (
data: any[],
item: any,
dataKey: any,
layout?: LayoutType,
axisType?: AxisType,
) => {
const { children } = item.props;
const errorBars = findAllByType(children, 'ErrorBar').filter((errorBarChild: any) =>
isErrorBarRelevantForAxis(layout, axisType, errorBarChild.props.direction),
);

if (errorBars && errorBars.length) {
const keys = errorBars.map((errorBarChild: any) => errorBarChild.props.dataKey);
Expand Down Expand Up @@ -431,9 +457,16 @@ export const getDomainOfErrorBars = (data: any[], item: any, dataKey: any, axisT

return null;
};
export const parseErrorBarsOfAxis = (data: any[], items: any[], dataKey: any, axisType: AxisType) => {

export const parseErrorBarsOfAxis = (
data: any[],
items: any[],
dataKey: any,
axisType: AxisType,
layout?: LayoutType,
) => {
const domains = items
.map(item => getDomainOfErrorBars(data, item, dataKey, axisType))
.map(item => getDomainOfErrorBars(data, item, dataKey, layout, axisType))
.filter(entry => !_.isNil(entry));

if (domains && domains.length) {
Expand All @@ -445,20 +478,28 @@ export const parseErrorBarsOfAxis = (data: any[], items: any[], dataKey: any, ax

return null;
};

/**
* Get domain of data by the configuration of item element
* @param {Array} data The data displayed in the chart
* @param {Array} items The instances of item
* @param {String} type The type of axis, number - Number Axis, category - Category Axis
* @param {LayoutType} layout The type of layout
* @param {Boolean} filterNil Whether or not filter nil values
* @return {Array} Domain
*/
export const getDomainOfItemsWithSameAxis = (data: any[], items: any[], type: string, filterNil?: boolean) => {
export const getDomainOfItemsWithSameAxis = (
data: any[],
items: any[],
type: string,
layout?: LayoutType,
filterNil?: boolean,
) => {
const domains = items.map(item => {
const { dataKey } = item.props;

if (type === 'number' && dataKey) {
return getDomainOfErrorBars(data, item, dataKey) || getDomainOfDataByKey(data, dataKey, type, filterNil);
return getDomainOfErrorBars(data, item, dataKey, layout) || getDomainOfDataByKey(data, dataKey, type, filterNil);
}
return getDomainOfDataByKey(data, dataKey, type, filterNil);
});
Expand Down
129 changes: 128 additions & 1 deletion test/specs/util/ChartUtilsSpec.js
@@ -1,20 +1,23 @@
import React from 'react';
import { expect } from 'chai';
import { scaleLinear, scaleBand } from 'd3-scale';
import {
calculateActiveTickIndex,
getDomainOfStackGroups,
getDomainOfDataByKey,
appendOffsetOfLegend,
getBandSizeOfAxis,
calculateDomainOfTicks,
parseSpecifiedDomain,
parseScale,
getTicksOfScale,
getValueByDataKey,
getDomainOfErrorBars,
offsetSign,
MIN_VALUE_REG,
MAX_VALUE_REG
} from '../../../src/util/ChartUtils';
import { Line, Bar, Scatter, Area, ErrorBar } from 'recharts';
import { mount } from 'enzyme';

describe('getBandSizeOfAxis', () => {
it('DataUtils.getBandSizeOfAxis() should return 0 ', () => {
Expand Down Expand Up @@ -280,3 +283,127 @@ describe('getDomainOfDataByKey', () => {
});
});
});

describe('getDomainOfErrorBars', () => {
const data = [
{
x: 1,
y: 100,
error: 10,
error2: 15,
},
{
x: 2,
y: 200,
error: 20,
error2: 15,
}
];

describe('within Line component', () => {
const line = mount(
<Line>
<ErrorBar dataKey="error" />
</Line>
).instance();

describe('with horizontal layout', () => {
it('should not include error bars in xAxis domain', () => {
expect(getDomainOfErrorBars(data, line, 'x', 'horizontal', 'xAxis')).to.be.null;
});
it('should include error bars in yAxis domain', () => {
expect(getDomainOfErrorBars(data, line, 'y', 'horizontal', 'yAxis')).to.deep.equal([90, 220]);
});
});

describe('with vertical layout', () => {
it('should include error bars in xAxis domain', () => {
expect(getDomainOfErrorBars(data, line, 'x', 'vertical', 'xAxis')).to.deep.equal([-18, 22]);
});
it('should not include error bars in yAxis domain', () => {
expect(getDomainOfErrorBars(data, line, 'y', 'vertical', 'yAxis')).to.be.null;
});
});
});

describe('within Bar component', () => {
const bar = mount(
<Bar>
<ErrorBar dataKey="error" />
</Bar>
).instance();

describe('with horizontal layout', () => {
it('should not include error bars in xAxis domain', () => {
expect(getDomainOfErrorBars(data, bar, 'x', 'horizontal', 'xAxis')).to.be.null;
});
it('should include error bars in yAxis domain', () => {
expect(getDomainOfErrorBars(data, bar, 'y', 'horizontal', 'yAxis')).to.deep.equal([90, 220]);
});
});

describe('with vertical layout', () => {
it('should include error bars in xAxis domain', () => {
expect(getDomainOfErrorBars(data, bar, 'x', 'vertical', 'xAxis')).to.deep.equal([-18, 22]);
});
it('should not include error bars in yAxis domain', () => {
expect(getDomainOfErrorBars(data, bar, 'y', 'vertical', 'yAxis')).to.be.null;
});
});
});

describe('within Area component', () => {
const area = mount(
<Area>
<ErrorBar dataKey="error" />
</Area>
).instance();

describe('with horizontal layout', () => {
it('should not include error bars in xAxis domain', () => {
expect(getDomainOfErrorBars(data, area, 'x', 'horizontal', 'xAxis')).to.be.null;
});
it('should include error bars in yAxis domain', () => {
expect(getDomainOfErrorBars(data, area, 'y', 'horizontal', 'yAxis')).to.deep.equal([90, 220]);
});
});

describe('with vertical layout', () => {
it('should include error bars in xAxis domain', () => {
expect(getDomainOfErrorBars(data, area, 'x', 'vertical', 'xAxis')).to.deep.equal([-18, 22]);
});
it('should not include error bars in yAxis domain', () => {
expect(getDomainOfErrorBars(data, area, 'y', 'vertical', 'yAxis')).to.be.null;
});
});
});

describe('within Scatter component', () => {
const scatter = mount(
<Scatter>
<ErrorBar dataKey="error" direction="y"/>
<ErrorBar dataKey="error2" direction="x"/>
</Scatter>
).instance();

it('should only include error bars with direction y in xAxis domain', () => {
expect(getDomainOfErrorBars(data, scatter, 'x', undefined, 'xAxis')).to.deep.equal([-14, 17]);
});
it('should only include error bars with direction x in yAxis domain', () => {
expect(getDomainOfErrorBars(data, scatter, 'y', undefined, 'yAxis')).to.deep.equal([90, 220]);
});
});

describe('with multiple ErrorBar children with same direction', () => {
const line = mount(
<Line>
<ErrorBar dataKey="error"/>
<ErrorBar dataKey="error2"/>
</Line>
).instance();

it('should return maximum domain of error bars', () => {
expect(getDomainOfErrorBars(data, line, 'y', 'horizontal', 'yAxis')).to.deep.equal([85, 220]);
})
});
});