Skip to content

Commit

Permalink
feat(canvas): add canvas support for bar & heatmap
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Benitte committed Aug 29, 2017
1 parent 7c57546 commit 94ad4d9
Show file tree
Hide file tree
Showing 16 changed files with 862 additions and 305 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ Several libraries already exist for React d3 integration, but just a few provide
## Features

- supports [d3 v4](https://github.com/d3/d3/blob/master/CHANGES.md)
- composable
- [responsive charts](http://nivo.rocks/#/components?term=responsive) (`<Responsive* />` components)
- highly customizable
- motion/transitions, even the non-d3 based components (DOM managed by React) support transitions within the help of [react-motion](https://github.com/chenglou/react-motion)
- [component playground](http://nivo.rocks)
- [exhaustive documentation](http://nivo.rocks)
- isomorphic rendering
- support for SVG and [HTML](http://nivo.rocks/#/components?term=html) (I'm also considering canvas support)
- support for SVG and [HTML](http://nivo.rocks/#/components?term=html) and [canvas](http://nivo.rocks/#/components?term=canvas) (for a subset of components)
- [placeholder components](http://nivo.rocks/#/components?term=placeholder) for advanced customization (`<*Placeholders />` components)
- [server side rendering API](https://github.com/plouc/nivo-api)

Expand All @@ -29,6 +28,8 @@ Several libraries already exist for React d3 integration, but just a few provide
- Bar
- [`<Bar />`](http://nivo.rocks/#/bar)
- [`<ResponsiveBar />`](http://nivo.rocks/#/bar)
- [`<BarCanvas />`](http://nivo.rocks/#/bar/canvas)
- [`<ResponsiveBarCanvas />`](http://nivo.rocks/#/bar/canvas)
- Bubble
- [`<Bubble />`](http://nivo.rocks/#/bubble)
- [`<ResponsiveBubble />`](http://nivo.rocks/#/bubble)
Expand All @@ -42,7 +43,9 @@ Several libraries already exist for React d3 integration, but just a few provide
- [`<ResponsiveChord />`](http://nivo.rocks/#/chord)
- HeatMap
- [`<HeatMap />`](http://nivo.rocks/#/heatmap)
- [`<ResponsiveHeatMap />`](http://nivo.rocks/#/heatmap)
- [`<ResponsiveHeatMap />`](http://nivo.rocks/#/heatmap)
- [`<HeatMapCanvas />`](http://nivo.rocks/#/heatmap/canvas)
- [`<ResponsiveHeatMapCanvas />`](http://nivo.rocks/#/heatmap/canvas)
- Line
- [`<Line />`](http://nivo.rocks/#/line)
- [`<ResponsiveLine />`](http://nivo.rocks/#/line)
Expand Down
92 changes: 3 additions & 89 deletions src/components/charts/bar/Bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,10 @@
* file that was distributed with this source code.
*/
import React from 'react'
import PropTypes from 'prop-types'
import { merge } from 'lodash'
import { TransitionMotion, spring } from 'react-motion'
import compose from 'recompose/compose'
import defaultProps from 'recompose/defaultProps'
import withPropsOnChange from 'recompose/withPropsOnChange'
import pure from 'recompose/pure'
import { withTheme, withColors, withDimensions, withMotion } from '../../../hocs'
import { getInheritedColorGenerator } from '../../../lib/colors'
import { generateGroupedBars, generateStackedBars } from '../../../lib/charts/bar'
import { getAccessorFor } from '../../../lib/propertiesConverters'
import enhance from './enhance'
import { BarPropTypes } from './props'
import Container from '../Container'
import SvgWrapper from '../SvgWrapper'
import Grid from '../../axes/Grid'
Expand Down Expand Up @@ -179,85 +172,6 @@ const Bar = ({
)
}

Bar.propTypes = {
// data
data: PropTypes.arrayOf(PropTypes.object).isRequired,
indexBy: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
getIndex: PropTypes.func.isRequired, // computed
keys: PropTypes.arrayOf(PropTypes.string).isRequired,

groupMode: PropTypes.oneOf(['stacked', 'grouped']).isRequired,
layout: PropTypes.oneOf(['horizontal', 'vertical']).isRequired,

xPadding: PropTypes.number.isRequired,

// axes & grid
axisTop: PropTypes.object,
axisRight: PropTypes.object,
axisBottom: PropTypes.object,
axisLeft: PropTypes.object,
enableGridX: PropTypes.bool.isRequired,
enableGridY: PropTypes.bool.isRequired,

// labels
enableLabels: PropTypes.bool.isRequired,
labelsTextColor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
getLabelsTextColor: PropTypes.func.isRequired, // computed
labelsLinkColor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
getLabelsLinkColor: PropTypes.func.isRequired, // computed

// interactions
onClick: PropTypes.func,

// theming
getColor: PropTypes.func.isRequired,

// interactivity
isInteractive: PropTypes.bool,
}

export const BarDefaultProps = {
indexBy: 'id',
keys: ['value'],

groupMode: 'stacked',
layout: 'vertical',

xPadding: 0.1,

// axes & grid
axisBottom: {},
axisLeft: {},
enableGridX: false,
enableGridY: true,

// labels
enableLabels: true,
labelsLinkColor: 'theme',
labelsTextColor: 'theme',

// interactivity
isInteractive: true,
}

Bar.defaultProps = BarDefaultProps

const enhance = compose(
defaultProps(BarDefaultProps),
withTheme(),
withColors(),
withDimensions(),
withMotion(),
withPropsOnChange(['indexBy'], ({ indexBy }) => ({
getIndex: getAccessorFor(indexBy),
})),
withPropsOnChange(['labelsTextColor'], ({ labelsTextColor }) => ({
getLabelsTextColor: getInheritedColorGenerator(labelsTextColor, 'axis.textColor'),
})),
withPropsOnChange(['labelsLinkColor'], ({ labelsLinkColor }) => ({
getLabelsLinkColor: getInheritedColorGenerator(labelsLinkColor, 'axis.tickColor'),
})),
pure
)
Bar.propTypes = BarPropTypes

export default enhance(Bar)
106 changes: 106 additions & 0 deletions src/components/charts/bar/BarCanvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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 React, { Component } from 'react'
import { generateGroupedBars, generateStackedBars } from '../../../lib/charts/bar'
import { renderAxes } from '../../../lib/canvas/axes'
import { BarPropTypes } from './props'
import enhance from './enhance'

class BarCanvas extends Component {
componentDidMount() {
this.ctx = this.surface.getContext('2d')
this.draw(this.props)
}

shouldComponentUpdate(props) {
this.draw(props)
return false
}

draw(props) {
const {
// data
data,
keys,
getIndex,

// dimensions
width,
height,
outerWidth,
outerHeight,
margin,

// layout
layout,
groupMode,
xPadding,

// axes
axisTop,
axisRight,
axisBottom,
axisLeft,

// theming
getColor,
} = props

this.surface.width = outerWidth
this.surface.height = outerHeight

let result
if (groupMode === 'grouped') {
result = generateGroupedBars(layout, data, getIndex, keys, width, height, getColor, {
xPadding,
})
} else if (groupMode === 'stacked') {
result = generateStackedBars(layout, data, getIndex, keys, width, height, getColor, {
xPadding,
})
}

this.ctx.clearRect(0, 0, outerWidth, outerHeight)
this.ctx.translate(margin.left, margin.top)

renderAxes(this.ctx, {
xScale: result.xScale,
yScale: result.yScale,
width,
height,
top: axisTop,
right: axisRight,
bottom: axisBottom,
left: axisLeft,
})

result.bars.forEach(({ x, y, color, width, height }) => {
this.ctx.fillStyle = color
this.ctx.fillRect(x, y, width, height)
})
}

render() {
const { outerWidth, outerHeight } = this.props

return (
<canvas
ref={surface => {
this.surface = surface
}}
width={outerWidth}
height={outerHeight}
/>
)
}
}

BarCanvas.propTypes = BarPropTypes

export default enhance(BarCanvas)
18 changes: 18 additions & 0 deletions src/components/charts/bar/ResponsiveBarCanvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 React from 'react'
import ResponsiveWrapper from '../ResponsiveWrapper'
import BarCanvas from './BarCanvas'

const ResponsiveBarCanvas = props =>
<ResponsiveWrapper>
{({ width, height }) => <BarCanvas width={width} height={height} {...props} />}
</ResponsiveWrapper>

export default ResponsiveBarCanvas
35 changes: 35 additions & 0 deletions src/components/charts/bar/enhance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 compose from 'recompose/compose'
import defaultProps from 'recompose/defaultProps'
import withPropsOnChange from 'recompose/withPropsOnChange'
import pure from 'recompose/pure'
import { withTheme, withColors, withDimensions, withMotion } from '../../../hocs'
import { getInheritedColorGenerator } from '../../../lib/colors'
import { getAccessorFor } from '../../../lib/propertiesConverters'
import { BarDefaultProps } from './props'

export default Component =>
compose(
defaultProps(BarDefaultProps),
withTheme(),
withColors(),
withDimensions(),
withMotion(),
withPropsOnChange(['indexBy'], ({ indexBy }) => ({
getIndex: getAccessorFor(indexBy),
})),
withPropsOnChange(['labelsTextColor'], ({ labelsTextColor }) => ({
getLabelsTextColor: getInheritedColorGenerator(labelsTextColor, 'axis.textColor'),
})),
withPropsOnChange(['labelsLinkColor'], ({ labelsLinkColor }) => ({
getLabelsLinkColor: getInheritedColorGenerator(labelsLinkColor, 'axis.tickColor'),
})),
pure
)(Component)
3 changes: 3 additions & 0 deletions src/components/charts/bar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@
* file that was distributed with this source code.
*/
export { default as Bar } from './Bar'
export { default as BarCanvas } from './BarCanvas'
export { default as ResponsiveBar } from './ResponsiveBar'
export { default as ResponsiveBarCanvas } from './ResponsiveBarCanvas'
export * from './props'
70 changes: 70 additions & 0 deletions src/components/charts/bar/props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 BarPropTypes = {
// data
data: PropTypes.arrayOf(PropTypes.object).isRequired,
indexBy: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
getIndex: PropTypes.func.isRequired, // computed
keys: PropTypes.arrayOf(PropTypes.string).isRequired,

groupMode: PropTypes.oneOf(['stacked', 'grouped']).isRequired,
layout: PropTypes.oneOf(['horizontal', 'vertical']).isRequired,

xPadding: PropTypes.number.isRequired,

// axes & grid
axisTop: PropTypes.object,
axisRight: PropTypes.object,
axisBottom: PropTypes.object,
axisLeft: PropTypes.object,
enableGridX: PropTypes.bool.isRequired,
enableGridY: PropTypes.bool.isRequired,

// labels
enableLabels: PropTypes.bool.isRequired,
labelsTextColor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
getLabelsTextColor: PropTypes.func.isRequired, // computed
labelsLinkColor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
getLabelsLinkColor: PropTypes.func.isRequired, // computed

// interactions
onClick: PropTypes.func,

// theming
getColor: PropTypes.func.isRequired,

// interactivity
isInteractive: PropTypes.bool,
}

export const BarDefaultProps = {
indexBy: 'id',
keys: ['value'],

groupMode: 'stacked',
layout: 'vertical',

xPadding: 0.1,

// axes & grid
axisBottom: {},
axisLeft: {},
enableGridX: false,
enableGridY: true,

// labels
enableLabels: true,
labelsLinkColor: 'theme',
labelsTextColor: 'theme',

// interactivity
isInteractive: true,
}
Loading

0 comments on commit 94ad4d9

Please sign in to comment.