Skip to content

Commit

Permalink
feat(bar): add the ability to round index scale (#1282)
Browse files Browse the repository at this point in the history
* Rebase

* Fix lint

* Refactor from `nice` to `indexScale: {round}` per comments

* Add unit tests

* Add TypeScript defs

* Add `indexedScale` to website

* Rebase; update tests to match style

* Address comments

* Tiny typo fix

* Pick up more comments

* Correct website default values
  • Loading branch information
ericsoco authored Nov 18, 2020
1 parent 484235f commit 1ab1257
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 12 deletions.
3 changes: 2 additions & 1 deletion packages/bar/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { AxisProps, GridValues } from '@nivo/axes'
import { OrdinalColorScaleConfig, InheritedColorConfig } from '@nivo/colors'
import { LegendProps } from '@nivo/legends'
import { Scale } from '@nivo/scales'
import { Scale, BandScale } from '@nivo/scales'

declare module '@nivo/bar' {
export type Value = string | number
Expand Down Expand Up @@ -99,6 +99,7 @@ declare module '@nivo/bar' {
padding: number

valueScale: Scale
indexScale: BandScale

axisBottom: AxisProps | null
axisLeft: AxisProps | null
Expand Down
2 changes: 2 additions & 0 deletions packages/bar/src/Bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const Bar = props => {
maxValue,

valueScale,
indexScale,

margin,
width,
Expand Down Expand Up @@ -128,6 +129,7 @@ const Bar = props => {
padding,
innerPadding,
valueScale,
indexScale,
})

const motionProps = {
Expand Down
2 changes: 2 additions & 0 deletions packages/bar/src/BarCanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class BarCanvas extends Component {
maxValue,

valueScale,
indexScale,

width,
height,
Expand Down Expand Up @@ -107,6 +108,7 @@ class BarCanvas extends Component {
padding,
innerPadding,
valueScale,
indexScale,
}

const result =
Expand Down
10 changes: 8 additions & 2 deletions packages/bar/src/compute/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ import { scaleBand } from 'd3-scale'
* @param {Function} getIndex
* @param {Array.<number>} range
* @param {number} padding
* @Param {scalePropType} indexScale
* @returns {Function}
*/
export const getIndexedScale = (data, getIndex, range, padding) =>
scaleBand().rangeRound(range).domain(data.map(getIndex)).padding(padding)
export const getIndexScale = (data, getIndex, range, padding, indexScale) => {
return scaleBand()
.domain(data.map(getIndex))
.range(range)
.round(Boolean(indexScale.round))
.padding(padding)
}

export const normalizeData = (data, keys) =>
data.map(item => ({
Expand Down
9 changes: 5 additions & 4 deletions packages/bar/src/compute/grouped.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
import { computeScale } from '@nivo/scales'
import { getIndexedScale, filterNullValues, normalizeData } from './common'
import { getIndexScale, filterNullValues, normalizeData } from './common'

const gt = (value, other) => value > other
const lt = (value, other) => value < other
Expand Down Expand Up @@ -146,11 +146,12 @@ export const generateGroupedBars = ({
padding = 0,
innerPadding = 0,
valueScale,
indexScale: indexScaleConfig,
...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)
const indexScale = getIndexScale(data, props.getIndex, range, padding, indexScaleConfig)

const scaleSpec = {
axis,
Expand All @@ -169,9 +170,9 @@ export const generateGroupedBars = ({

const scale = computeScale(scaleSpec, { [axis]: { min, max } }, width, height)

const [xScale, yScale] = layout === 'vertical' ? [indexedScale, scale] : [scale, indexedScale]
const [xScale, yScale] = layout === 'vertical' ? [indexScale, scale] : [scale, indexScale]

const bandwidth = (indexedScale.bandwidth() - innerPadding * (keys.length - 1)) / keys.length
const bandwidth = (indexScale.bandwidth() - innerPadding * (keys.length - 1)) / keys.length
const params = [
{ ...props, data, keys, innerPadding, xScale, yScale },
bandwidth,
Expand Down
9 changes: 5 additions & 4 deletions packages/bar/src/compute/stacked.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// import flattenDepth from 'lodash/flattenDepth'
import { computeScale } from '@nivo/scales'
import { stack, stackOffsetDiverging } from 'd3-shape'
import { getIndexedScale, filterNullValues, normalizeData } from './common'
import { getIndexScale, filterNullValues, normalizeData } from './common'

const flattenDeep = (array, depth = 1) =>
depth > 0
Expand Down Expand Up @@ -149,12 +149,13 @@ export const generateStackedBars = ({
height,
padding = 0,
valueScale,
indexScale: indexScaleConfig,
...props
}) => {
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)
const indexScale = getIndexScale(data, props.getIndex, range, padding, indexScaleConfig)

const scaleSpec = {
axis,
Expand All @@ -170,10 +171,10 @@ export const generateStackedBars = ({

const scale = computeScale(scaleSpec, { [axis]: { min, max } }, width, height)

const [xScale, yScale] = layout === 'vertical' ? [indexedScale, scale] : [scale, indexedScale]
const [xScale, yScale] = layout === 'vertical' ? [indexScale, scale] : [scale, indexScale]

const innerPadding = props.innerPadding > 0 ? props.innerPadding : 0
const bandwidth = indexedScale.bandwidth()
const bandwidth = indexScale.bandwidth()
const params = [
{ ...props, innerPadding, stackedData, xScale, yScale },
bandwidth,
Expand Down
5 changes: 4 additions & 1 deletion packages/bar/src/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from '@nivo/colors'
import { axisPropType } from '@nivo/axes'
import { LegendPropShape } from '@nivo/legends'
import { scalePropType } from '@nivo/scales'
import { scalePropType, bandScalePropTypes } from '@nivo/scales'
import BarItem from './BarItem'

export const BarPropTypes = {
Expand All @@ -34,6 +34,8 @@ export const BarPropTypes = {
layout: PropTypes.oneOf(['horizontal', 'vertical']).isRequired,
reverse: PropTypes.bool.isRequired,
valueScale: scalePropType.isRequired,
indexScale: bandScalePropTypes.isRequired,

minValue: PropTypes.oneOfType([PropTypes.number, PropTypes.oneOf(['auto'])]).isRequired,
maxValue: PropTypes.oneOfType([PropTypes.number, PropTypes.oneOf(['auto'])]).isRequired,
padding: PropTypes.number.isRequired,
Expand Down Expand Up @@ -113,6 +115,7 @@ export const BarDefaultProps = {
maxValue: 'auto',

valueScale: { type: 'linear' },
indexScale: { type: 'band', round: true },

padding: 0.1,
innerPadding: 0,
Expand Down
39 changes: 39 additions & 0 deletions packages/bar/tests/Bar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,42 @@ it(`should generate stacked bars correctly when keys are mismatched`, () => {
expect(bars.at(2).prop('height')).toEqual(69)
expect(bars.at(2).prop('width')).toEqual(214)
})

it(`should apply scale rounding by default`, () => {
const wrapper = mount(
<Bar
width={500}
height={300}
data={[
{ id: 'one', value: 10 },
{ id: 'two', value: 20 },
{ id: 'three', value: 30 },
]}
animate={false}
/>
)

const bars = wrapper.find('BarItem')
const firstBarWidth = bars.at(0).prop('width')
expect(firstBarWidth).toEqual(Math.floor(firstBarWidth))
})

it(`should not apply scale rounding when passed indexScale.round: false`, () => {
const wrapper = mount(
<Bar
width={500}
height={300}
data={[
{ id: 'one', value: 10 },
{ id: 'two', value: 20 },
{ id: 'three', value: 30 },
]}
animate={false}
indexScale={{ type: 'band', round: false }}
/>
)

const bars = wrapper.find('BarItem')
const firstBarWidth = bars.at(0).prop('width')
expect(firstBarWidth).not.toEqual(Math.floor(firstBarWidth))
})
6 changes: 6 additions & 0 deletions packages/scales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,19 @@ declare module '@nivo/scales' {
max?: 'auto' | number
}

export interface BandScale {
type: 'band'
round?: boolean
}

export type Scale =
| LinearScale
| PointScale
| TimeScale
| TimeScaleFormatted
| LogScale
| SymlogScale
| BandScale

export type ScaleFunc = (value: string | number | Date) => number
}
14 changes: 14 additions & 0 deletions packages/scales/src/bandScale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* This file is part of the nivo project.
*
* Copyright 2016-present, Raphaël Benitte.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import PropTypes from 'prop-types'

export const bandScalePropTypes = {
type: PropTypes.oneOf(['band']).isRequired,
round: PropTypes.bool,
}
4 changes: 4 additions & 0 deletions packages/scales/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,22 @@ import { logScalePropTypes } from './logScale'
import { symLogScalePropTypes } from './symlogScale'
import { pointScalePropTypes } from './pointScale'
import { timeScalePropTypes } from './timeScale'
import { bandScalePropTypes } from './bandScale'

export * from './compute'
export * from './linearScale'
export * from './logScale'
export * from './symlogScale'
export * from './pointScale'
export * from './timeScale'
export * from './timeHelpers'
export * from './bandScale'

export const scalePropType = PropTypes.oneOfType([
PropTypes.shape(linearScalePropTypes),
PropTypes.shape(pointScalePropTypes),
PropTypes.shape(timeScalePropTypes),
PropTypes.shape(logScalePropTypes),
PropTypes.shape(symLogScalePropTypes),
PropTypes.shape(bandScalePropTypes),
])
31 changes: 31 additions & 0 deletions website/src/data/components/bar/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,37 @@ const props = [
],
},
},
{
key: 'indexScale',
type: 'object',
group: 'Base',
help: `index scale configuration.`,
defaultValue: defaults.indexScale,
controlType: 'object',
controlOptions: {
props: [
{
key: 'type',
help: `Scale type.`,
type: 'string',
controlType: 'choices',
controlOptions: {
disabled: true,
choices: ['band'].map(v => ({
label: v,
value: v,
})),
},
},
{
key: 'round',
help: 'Toggle index scale (for bar width) rounding.',
type: 'boolean',
controlType: 'switch',
},
],
},
},
{
key: 'reverse',
help:
Expand Down
1 change: 1 addition & 0 deletions website/src/pages/bar/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const BarApi = () => {
reverse: false,

valueScale: { type: 'linear' },
indexScale: { type: 'band', round: true },

axisTop: {
enable: false,
Expand Down
1 change: 1 addition & 0 deletions website/src/pages/bar/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const initialProperties = {
reverse: false,

valueScale: { type: 'linear' },
indexScale: { type: 'band', round: true },

colors: { scheme: 'red_blue' },
colorBy: 'id',
Expand Down
1 change: 1 addition & 0 deletions website/src/pages/bar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const initialProperties = {
reverse: false,

valueScale: { type: 'linear' },
indexScale: { type: 'band', round: true },

colors: { scheme: 'nivo' },
colorBy: 'id',
Expand Down

0 comments on commit 1ab1257

Please sign in to comment.