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

[WIP] Feat: stacked tooltip for Bar #70

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/components/charts/bar/Bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -103,6 +104,9 @@ const Bar = ({
// interactivity
isInteractive,
onClick,

// stackTooltip
enableStackTooltip,
}) => {
const options = {
layout,
Expand Down Expand Up @@ -163,6 +167,7 @@ const Bar = ({
hideTooltip,
onClick,
theme,
enableStackTooltip,
}

let bars
Expand Down Expand Up @@ -245,6 +250,18 @@ const Bar = ({
{...motionProps}
/>
{bars}
{isInteractive &&
enableStackTooltip && (
<BarSlices
slices={result.slices}
height={height}
width={width}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
theme={theme}
layout={layout}
/>
)}
<CartesianMarkers
markers={markers}
width={width}
Expand Down
27 changes: 16 additions & 11 deletions src/components/charts/bar/BarItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,23 @@ const BarItem = ({
onClick,

theme,

enableStackTooltip,
}) => {
const handleTooltip = e =>
showTooltip(
<BasicTooltip
id={`${data.id} - ${data.indexValue}`}
value={data.value}
enableChip={true}
color={color}
theme={theme}
/>,
e
)
const handleTooltip = e => {
if (enableStackTooltip === false) {
showTooltip(
<BasicTooltip
id={`${data.id} - ${data.indexValue}`}
value={data.value}
enableChip={true}
color={color}
theme={theme}
/>,
e
)
}
}

return (
<g transform={`translate(${x}, ${y})`}>
Expand Down
70 changes: 70 additions & 0 deletions src/components/charts/bar/BarSlices.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 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 }) => (
<g>
{slices.map(slice => {
const dimensions = getSliceDimensions(slice, height, width, layout)

return (
<BarSlicesItem
key={slice.id}
slice={slice}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
theme={theme}
{...dimensions}
/>
)
})}
</g>
)

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)
70 changes: 70 additions & 0 deletions src/components/charts/bar/BarSlicesItem.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 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 }) => (
<span style={{ display: 'block', width: '12px', height: '12px', background: color }} />
)

const BarSlicesItem = ({ slice, height, width, showTooltip, hideTooltip, isHover }) => (
<g transform={`translate(${slice.x}, ${slice.y})`}>
{isHover && <rect width={width} height={height} fill="#000" fillOpacity={0.1} />}
<rect
width={width}
height={height}
fill="#000"
fillOpacity={0}
onMouseEnter={showTooltip}
onMouseMove={showTooltip}
onMouseLeave={hideTooltip}
/>
</g>
)

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: (
<TableTooltip
theme={theme}
rows={slice.bars.map(p => [<Chip color={p.color} />, 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)
6 changes: 6 additions & 0 deletions src/components/charts/bar/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export const BarPropTypes = {

// canvas specific
pixelRatio: PropTypes.number.isRequired,

// stackTooltip
enableStackTooltip: PropTypes.bool.isRequired,
}

export const BarDefaultProps = {
Expand Down Expand Up @@ -109,4 +112,7 @@ export const BarDefaultProps = {
// canvas specific
pixelRatio:
global.window && global.window.devicePixelRatio ? global.window.devicePixelRatio : 1,

// stackTooltip
enableStackTooltip: false,
}
59 changes: 57 additions & 2 deletions src/lib/charts/bar/stacked.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -121,7 +172,9 @@ export const generateVerticalStackedBars = ({
})
}

return { xScale, yScale, bars }
const slices = getVerticalSlices(bars, xScale)

return { xScale, yScale, bars, slices }
}

/**
Expand Down Expand Up @@ -207,7 +260,9 @@ export const generateHorizontalStackedBars = ({
})
}

return { xScale, yScale, bars }
const slices = getHorizontalSlices(bars, yScale)

return { xScale, yScale, bars, slices }
}

/**
Expand Down
13 changes: 13 additions & 0 deletions stories/charts/bar.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -27,10 +28,22 @@ const stories = storiesOf('Bar', module).addDecorator(story => (

stories.add('stacked', () => <Bar {...commonProps} />)

stories.add('stacked with stack tooltip', () => <Bar {...commonProps} enableStackTooltip={true} />)

stories.add('stacked horizontal', () => (
<Bar {...commonProps} layout="horizontal" enableGridY={false} enableGridX={true} />
))

stories.add('stacked horizontal with stack tooltip', () => (
<Bar
{...commonProps}
layout="horizontal"
enableGridY={false}
enableGridX={true}
enableStackTooltip={true}
/>
))

stories.add('grouped', () => <Bar {...commonProps} groupMode="grouped" />)

stories.add('grouped horizontal', () => (
Expand Down