diff --git a/src/components/charts/bar/Bar.js b/src/components/charts/bar/Bar.js index bbde785933..f4b23c71c8 100644 --- a/src/components/charts/bar/Bar.js +++ b/src/components/charts/bar/Bar.js @@ -17,6 +17,7 @@ import SvgWrapper from '../SvgWrapper' import Grid from '../../axes/Grid' import CartesianMarkers from '../../cartesian/markers/CartesianMarkers' import Axes from '../../axes/Axes' +import BarSlices from './BarSlices' const barWillEnterHorizontal = ({ style }) => ({ x: style.x.val, @@ -103,6 +104,9 @@ const Bar = ({ // interactivity isInteractive, onClick, + + // stackTooltip + enableStackTooltip, }) => { const options = { layout, @@ -163,6 +167,7 @@ const Bar = ({ hideTooltip, onClick, theme, + enableStackTooltip, } let bars @@ -245,6 +250,18 @@ const Bar = ({ {...motionProps} /> {bars} + {isInteractive && + enableStackTooltip && ( + + )} { - const handleTooltip = e => - showTooltip( - , - e - ) + const handleTooltip = e => { + if (enableStackTooltip === false) { + showTooltip( + , + e + ) + } + } return ( diff --git a/src/components/charts/bar/BarSlices.js b/src/components/charts/bar/BarSlices.js new file mode 100644 index 0000000000..944e30a3df --- /dev/null +++ b/src/components/charts/bar/BarSlices.js @@ -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 React from 'react' +import PropTypes from 'prop-types' +import pure from 'recompose/pure' +import BarSlicesItem from './BarSlicesItem' + +function getSliceDimensions(slice, height, width, layout) { + if (layout === 'horizontal') { + return { + height: slice.height, + width, + } + } + + return { + height, + width: slice.width, + } +} + +const BarSlices = ({ slices, height, showTooltip, hideTooltip, theme, width, layout }) => ( + + {slices.map(slice => { + const dimensions = getSliceDimensions(slice, height, width, layout) + + return ( + + ) + })} + +) + +BarSlices.propTypes = { + slices: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + bars: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + color: PropTypes.string.isRequired, + }) + ).isRequired, + }) + ).isRequired, + height: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + showTooltip: PropTypes.func.isRequired, + hideTooltip: PropTypes.func.isRequired, + theme: PropTypes.object.isRequired, + layout: PropTypes.oneOf(['horizontal', 'vertical']).isRequired, +} + +export default pure(BarSlices) diff --git a/src/components/charts/bar/BarSlicesItem.js b/src/components/charts/bar/BarSlicesItem.js new file mode 100644 index 0000000000..2f3c1d1d71 --- /dev/null +++ b/src/components/charts/bar/BarSlicesItem.js @@ -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 React from 'react' +import PropTypes from 'prop-types' +import compose from 'recompose/compose' +import pure from 'recompose/pure' +import withState from 'recompose/withState' +import withHandlers from 'recompose/withHandlers' +import withPropsOnChange from 'recompose/withPropsOnChange' +import TableTooltip from '../../tooltip/TableTooltip' + +const Chip = ({ color }) => ( + +) + +const BarSlicesItem = ({ slice, height, width, showTooltip, hideTooltip, isHover }) => ( + + {isHover && } + + +) + +BarSlicesItem.propTypes = { + slice: PropTypes.object.isRequired, + height: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + showTooltip: PropTypes.func.isRequired, + hideTooltip: PropTypes.func.isRequired, + isHover: PropTypes.bool.isRequired, + theme: PropTypes.object.isRequired, +} + +const enhance = compose( + withState('isHover', 'setIsHover', false), + withPropsOnChange(['slice', 'theme'], ({ slice, theme }) => ({ + tooltip: ( + [, p.id, p.value])} + /> + ), + })), + withHandlers({ + showTooltip: ({ showTooltip, setIsHover, tooltip }) => e => { + setIsHover(true) + showTooltip(tooltip, e) + }, + hideTooltip: ({ hideTooltip, setIsHover }) => () => { + setIsHover(false) + hideTooltip() + }, + }), + pure +) + +export default enhance(BarSlicesItem) diff --git a/src/components/charts/bar/props.js b/src/components/charts/bar/props.js index 97a508a1d3..6f3e5cedf3 100644 --- a/src/components/charts/bar/props.js +++ b/src/components/charts/bar/props.js @@ -64,6 +64,9 @@ export const BarPropTypes = { // canvas specific pixelRatio: PropTypes.number.isRequired, + + // stackTooltip + enableStackTooltip: PropTypes.bool.isRequired, } export const BarDefaultProps = { @@ -109,4 +112,7 @@ export const BarDefaultProps = { // canvas specific pixelRatio: global.window && global.window.devicePixelRatio ? global.window.devicePixelRatio : 1, + + // stackTooltip + enableStackTooltip: false, } diff --git a/src/lib/charts/bar/stacked.js b/src/lib/charts/bar/stacked.js index 70f9d7d7d6..1ff4a251b0 100644 --- a/src/lib/charts/bar/stacked.js +++ b/src/lib/charts/bar/stacked.js @@ -10,6 +10,7 @@ import { flattenDepth, min, max } from 'lodash' import { scaleLinear } from 'd3-scale' import { stack, stackOffsetDiverging } from 'd3-shape' import { getIndexedScale } from './common' +import groupBy from 'lodash/groupBy' /** * Generates scale for stacked bar chart. @@ -38,6 +39,56 @@ export const getStackedScale = (data, _minValue, _maxValue, range) => { .domain([minValue, maxValue]) } +function getVerticalSlices(bars, xScale) { + const groups = groupBy(bars, 'data.indexValue') + + return xScale.domain().map(id => { + const groupBars = groups[id] + + return { + id, + x: xScale(id), + y: 0, + width: groupBars[0].width, + bars: groupBars.map(bar => { + const { data: { id, value }, color } = bar + + return { + id, + value, + color, + } + }), + } + }) +} + +function getHorizontalSlices(bars, yScale) { + const groups = groupBy(bars, 'data.indexValue') + + const res = yScale.domain().map(id => { + const groupBars = groups[id] + + return { + id, + x: 0, + y: yScale(id), + height: groupBars[0].height, + bars: groupBars.map(bar => { + const { data: { id, value }, color } = bar + + return { + id, + value, + color, + } + }), + } + }) + + return res +} + /** * Generates x/y scales & bars for vertical stacked bar chart. * @@ -121,7 +172,9 @@ export const generateVerticalStackedBars = ({ }) } - return { xScale, yScale, bars } + const slices = getVerticalSlices(bars, xScale) + + return { xScale, yScale, bars, slices } } /** @@ -207,7 +260,9 @@ export const generateHorizontalStackedBars = ({ }) } - return { xScale, yScale, bars } + const slices = getHorizontalSlices(bars, yScale) + + return { xScale, yScale, bars, slices } } /** diff --git a/stories/charts/bar.stories.js b/stories/charts/bar.stories.js index 07b8ae0604..3cf7f57622 100644 --- a/stories/charts/bar.stories.js +++ b/stories/charts/bar.stories.js @@ -8,6 +8,7 @@ import '../style.css' import { Bar } from '../../src' const keys = ['hot dogs', 'burgers', 'sandwich', 'kebab', 'fries', 'donut'] + const commonProps = { width: 1000, height: 600, @@ -27,10 +28,22 @@ const stories = storiesOf('Bar', module).addDecorator(story => ( stories.add('stacked', () => ) +stories.add('stacked with stack tooltip', () => ) + stories.add('stacked horizontal', () => ( )) +stories.add('stacked horizontal with stack tooltip', () => ( + +)) + stories.add('grouped', () => ) stories.add('grouped horizontal', () => (