Skip to content

Commit

Permalink
fix(bar): fix stacked bars when key is missing (#1291)
Browse files Browse the repository at this point in the history
* fix(bar): fix stacked bars when key is missing

Close #1289

* chore(bar): add tests that pass in v0.63.0

* fix(bar): update logic to reproduce old behavior
  • Loading branch information
wyze committed Nov 18, 2020
1 parent e984d72 commit 484235f
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 30 deletions.
48 changes: 26 additions & 22 deletions packages/bar/src/Bar.js
Expand Up @@ -192,16 +192,18 @@ const Bar = props => {
key="bars"
willEnter={willEnter}
willLeave={willLeave}
styles={result.bars.map(bar => ({
key: bar.key,
data: bar,
style: {
x: spring(bar.x, springConfig),
y: spring(bar.y, springConfig),
width: spring(bar.width, springConfig),
height: spring(bar.height, springConfig),
},
}))}
styles={result.bars
.filter(bar => bar.data.value !== null)
.map(bar => ({
key: bar.key,
data: bar,
style: {
x: spring(bar.x, springConfig),
y: spring(bar.y, springConfig),
width: spring(bar.width, springConfig),
height: spring(bar.height, springConfig),
},
}))}
>
{interpolatedStyles => (
<g>
Expand All @@ -226,18 +228,20 @@ const Bar = props => {
</TransitionMotion>
)
} else {
bars = result.bars.map(d =>
React.createElement(barComponent, {
key: d.key,
...d,
...commonProps,
label: getLabel(d.data),
shouldRenderLabel: shouldRenderLabel(d),
labelColor: getLabelTextColor(d, theme),
borderColor: getBorderColor(d),
theme,
})
)
bars = result.bars
.filter(bar => bar.data.value !== null)
.map(d =>
React.createElement(barComponent, {
key: d.key,
...d,
...commonProps,
label: getLabel(d.data),
shouldRenderLabel: shouldRenderLabel(d),
labelColor: getLabelTextColor(d, theme),
borderColor: getBorderColor(d),
theme,
})
)
}

const layerById = {
Expand Down
9 changes: 9 additions & 0 deletions packages/bar/src/compute/common.js
Expand Up @@ -19,3 +19,12 @@ import { scaleBand } from 'd3-scale'
*/
export const getIndexedScale = (data, getIndex, range, padding) =>
scaleBand().rangeRound(range).domain(data.map(getIndex)).padding(padding)

export const normalizeData = (data, keys) =>
data.map(item => ({
...keys.reduce((acc, key) => ({ ...acc, [key]: null }), {}),
...item,
}))

export const filterNullValues = data =>
Object.keys(data).reduce((acc, key) => (data[key] ? { ...acc, [key]: data[key] } : acc), {})
8 changes: 4 additions & 4 deletions packages/bar/src/compute/grouped.js
Expand Up @@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
import { computeScale } from '@nivo/scales'
import { getIndexedScale } from './common'
import { getIndexedScale, filterNullValues, normalizeData } from './common'

const gt = (value, other) => value > other
const lt = (value, other) => value < other
Expand Down Expand Up @@ -54,7 +54,7 @@ const generateVerticalGroupedBars = (
value: data[index][key],
index,
indexValue: getIndex(data[index]),
data: data[index],
data: filterNullValues(data[index]),
}

return {
Expand Down Expand Up @@ -110,7 +110,7 @@ const generateHorizontalGroupedBars = (
value: data[index][key],
index,
indexValue: getIndex(data[index]),
data: data[index],
data: filterNullValues(data[index]),
}

return {
Expand All @@ -136,7 +136,6 @@ const generateHorizontalGroupedBars = (
* @return {{ xScale: Function, yScale: Function, bars: Array.<Object> }}
*/
export const generateGroupedBars = ({
data,
layout,
keys,
minValue,
Expand All @@ -149,6 +148,7 @@ export const generateGroupedBars = ({
valueScale,
...props
}) => {
const data = normalizeData(props.data, keys)
const [axis, range] = layout === 'vertical' ? ['y', [0, width]] : ['x', [height, 0]]
const indexedScale = getIndexedScale(data, props.getIndex, range, padding)

Expand Down
8 changes: 4 additions & 4 deletions packages/bar/src/compute/stacked.js
Expand Up @@ -9,7 +9,7 @@
// import flattenDepth from 'lodash/flattenDepth'
import { computeScale } from '@nivo/scales'
import { stack, stackOffsetDiverging } from 'd3-shape'
import { getIndexedScale } from './common'
import { getIndexedScale, filterNullValues, normalizeData } from './common'

const flattenDeep = (array, depth = 1) =>
depth > 0
Expand Down Expand Up @@ -57,7 +57,7 @@ const generateVerticalStackedBars = (
value: d.data[stackedDataItem.key],
index: i,
indexValue: index,
data: d.data,
data: filterNullValues(d.data),
}

return {
Expand Down Expand Up @@ -113,7 +113,7 @@ const generateHorizontalStackedBars = (
value: d.data[stackedDataItem.key],
index: i,
indexValue: index,
data: d.data,
data: filterNullValues(d.data),
}

return {
Expand Down Expand Up @@ -151,7 +151,7 @@ export const generateStackedBars = ({
valueScale,
...props
}) => {
const stackedData = stack().keys(keys).offset(stackOffsetDiverging)(data)
const stackedData = stack().keys(keys).offset(stackOffsetDiverging)(normalizeData(data, keys))

const [axis, range] = layout === 'vertical' ? ['y', [0, width]] : ['x', [height, 0]]
const indexedScale = getIndexedScale(data, props.getIndex, range, padding)
Expand Down
111 changes: 111 additions & 0 deletions packages/bar/tests/Bar.test.js
Expand Up @@ -239,3 +239,114 @@ it(`should reverse legend items if chart layout is horizontal reversed`, () => {
expect(legendItems.at(0).prop('data').id).toEqual('B')
expect(legendItems.at(1).prop('data').id).toEqual('A')
})

it(`should generate grouped bars correctly when keys are mismatched`, () => {
const wrapper = mount(
<Bar
width={500}
height={300}
data={[
{ id: 'one', A: 10, C: 3 },
{ id: 'two', B: 9 },
]}
keys={['A', 'B', 'C']}
groupMode="grouped"
animate={false}
/>
)

const bars = wrapper.find('BarItem')

expect(bars).toHaveLength(3)

expect(bars.at(0).prop('data')).toEqual({
data: { A: 10, C: 3, id: 'one' },
id: 'A',
index: 0,
indexValue: 'one',
value: 10,
})
expect(bars.at(0).prop('x')).toEqual(24)
expect(bars.at(0).prop('y')).toEqual(0)
expect(bars.at(0).prop('height')).toEqual(300)
expect(bars.at(0).prop('width')).toEqual(71.33333333333333)

expect(bars.at(1).prop('data')).toEqual({
data: { B: 9, id: 'two' },
id: 'B',
index: 1,
indexValue: 'two',
value: 9,
})
expect(bars.at(1).prop('x')).toEqual(333.3333333333333)
expect(bars.at(1).prop('y')).toEqual(30)
expect(bars.at(1).prop('height')).toEqual(270)
expect(bars.at(1).prop('width')).toEqual(71.33333333333333)

expect(bars.at(2).prop('data')).toEqual({
data: { A: 10, C: 3, id: 'one' },
id: 'C',
index: 0,
indexValue: 'one',
value: 3,
})
expect(bars.at(2).prop('x')).toEqual(166.66666666666666)
expect(bars.at(2).prop('y')).toEqual(210)
expect(bars.at(2).prop('height')).toEqual(90)
expect(bars.at(2).prop('width')).toEqual(71.33333333333333)
})

it(`should generate stacked bars correctly when keys are mismatched`, () => {
const wrapper = mount(
<Bar
width={500}
height={300}
data={[
{ id: 'one', A: 10, C: 3 },
{ id: 'two', B: 9 },
]}
keys={['A', 'B', 'C']}
animate={false}
/>
)

const bars = wrapper.find('BarItem')

expect(bars).toHaveLength(3)

expect(bars.at(0).prop('data')).toEqual({
data: { A: 10, C: 3, id: 'one' },
id: 'A',
index: 0,
indexValue: 'one',
value: 10,
})
expect(bars.at(0).prop('x')).toEqual(24)
expect(bars.at(0).prop('y')).toEqual(69)
expect(bars.at(0).prop('height')).toEqual(231)
expect(bars.at(0).prop('width')).toEqual(214)

expect(bars.at(1).prop('data')).toEqual({
data: { B: 9, id: 'two' },
id: 'B',
index: 1,
indexValue: 'two',
value: 9,
})
expect(bars.at(1).prop('x')).toEqual(262)
expect(bars.at(1).prop('y')).toEqual(92)
expect(bars.at(1).prop('height')).toEqual(208)
expect(bars.at(1).prop('width')).toEqual(214)

expect(bars.at(2).prop('data')).toEqual({
data: { A: 10, C: 3, id: 'one' },
id: 'C',
index: 0,
indexValue: 'one',
value: 3,
})
expect(bars.at(2).prop('x')).toEqual(24)
expect(bars.at(2).prop('y')).toEqual(0)
expect(bars.at(2).prop('height')).toEqual(69)
expect(bars.at(2).prop('width')).toEqual(214)
})

0 comments on commit 484235f

Please sign in to comment.