Skip to content

Commit

Permalink
feat(stream): add support for tooltip on stream chart
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Benitte committed Aug 9, 2017
1 parent 060142e commit c3a997b
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 48 deletions.
74 changes: 74 additions & 0 deletions src/components/charts/Container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'

const containerStyle = {
position: 'relative',
}

const tooltipStyle = {
position: 'absolute',
background: '#FFF',
zIndex: 10,
top: 0,
left: 0,
borderRadius: '3px',
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.25)',
padding: '7px 12px',
}

const Tooltip = ({ x, y, children }) =>
<div style={{ ...tooltipStyle, top: y, left: x }}>
{children}
</div>

export default class Container extends Component {
static propTypes = {
children: PropTypes.func.isRequired,
}

state = {
isTooltipVisible: true,
tooltipContent: 'crap',
tooltipX: 0,
tooltipY: 0,
}

showTooltip = (content, event) => {
const { pageX, pageY } = event
const bounds = this.container.getBoundingClientRect()

this.setState({
isTooltipVisible: true,
tooltipContent: content,
tooltipX: pageX - bounds.left + 20,
tooltipY: pageY - bounds.top,
})
}

hideTooltip = () => {
this.setState({ isTooltipVisible: false, tooltipContent: null })
}

render() {
const { children } = this.props
const { isTooltipVisible, tooltipContent, tooltipX, tooltipY } = this.state

return (
<div
style={containerStyle}
ref={container => {
this.container = container
}}
>
{children({
showTooltip: this.showTooltip,
hideTooltip: this.hideTooltip,
})}
{isTooltipVisible &&
<Tooltip x={tooltipX} y={tooltipY}>
{tooltipContent}
</Tooltip>}
</div>
)
}
}
96 changes: 51 additions & 45 deletions src/components/charts/stream/Stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import {
stackOffsetPropType,
stackOffsetFromProp,
} from '../../../props'
import { getColorsGenerator } from '../../../lib/colorUtils'
import { getColorRange } from '../../../lib/colorUtils'
import SvgWrapper from '../SvgWrapper'
import Container from '../Container'
import Axes from '../../axes/Axes'
import Grid from '../../axes/Grid'
import StreamLayers from './StreamLayers'
Expand All @@ -37,6 +38,8 @@ const stackMax = layers => max(layers.reduce((acc, layer) => [...acc, ...layer.m

const Stream = ({
data,
keys,

order,
offsetType,
curve,
Expand All @@ -59,27 +62,20 @@ const Stream = ({
// theming
theme,
color,
fillOpacity,

// motion
animate,
motionStiffness,
motionDamping,
}) => {
const keys = range(5)

const stack = d3Stack()
.keys(keys)
.offset(stackOffsetFromProp(offsetType))
.order(stackOrderFromProp(order))

const layers = stack(data)

/*
console.log('DATA', data)
console.log('KEYS', keys)
console.log('LAYERS', layers)
*/

const minValue = stackMin(layers)
const maxValue = stackMax(layers)

Expand All @@ -92,50 +88,60 @@ const Stream = ({
.y1(d => yScale(d[1]))
.curve(curveFromProp(curve))

const enhancedLayers = layers.map((layer, i) => ({
id: keys[i],
layer,
path: area(layer),
color: color(i),
}))

const motionProps = {
animate,
motionDamping,
motionStiffness,
}

return (
<SvgWrapper width={fullWidth} height={fullHeight} margin={margin}>
<Grid
theme={theme}
width={width}
height={height}
xScale={enableGridX ? xScale : null}
yScale={enableGridY ? yScale : null}
{...motionProps}
/>
<StreamLayers
layers={layers.map((layer, i) => ({
layer,
path: area(layer),
color: color(i),
}))}
area={area}
{...motionProps}
/>
<Axes
xScale={xScale}
yScale={yScale.domain([0, Math.abs(minValue) + Math.abs(maxValue)])}
width={width}
height={height}
theme={theme}
top={axisTop}
right={axisRight}
bottom={axisBottom}
left={axisLeft}
{...motionProps}
/>
</SvgWrapper>
<Container>
{({ showTooltip, hideTooltip }) =>
<SvgWrapper width={fullWidth} height={fullHeight} margin={margin}>
<Grid
theme={theme}
width={width}
height={height}
xScale={enableGridX ? xScale : null}
yScale={enableGridY ? yScale : null}
{...motionProps}
/>
<StreamLayers
layers={enhancedLayers}
area={area}
fillOpacity={fillOpacity}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
{...motionProps}
/>
<Axes
xScale={xScale}
yScale={yScale.domain([0, Math.abs(minValue) + Math.abs(maxValue)])}
width={width}
height={height}
theme={theme}
top={axisTop}
right={axisRight}
bottom={axisBottom}
left={axisLeft}
{...motionProps}
/>
</SvgWrapper>}
</Container>
)
}

Stream.propTypes = {
// data
data: PropTypes.arrayOf(PropTypes.object).isRequired,
keys: PropTypes.array.isRequired,

order: stackOrderPropType.isRequired,
offsetType: stackOffsetPropType.isRequired,
Expand All @@ -157,7 +163,7 @@ Stream.propTypes = {
// theming
theme: PropTypes.object.isRequired,
colors: PropTypes.any.isRequired,
colorBy: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
fillOpacity: PropTypes.number.isRequired,
color: PropTypes.func.isRequired,

// motion
Expand All @@ -167,7 +173,7 @@ Stream.propTypes = {
export const StreamDefaultProps = {
order: 'none',
offsetType: 'wiggle',
curve: 'monotoneX',
curve: 'catmullRom',

// dimensions
margin: Nivo.defaults.margin,
Expand All @@ -180,7 +186,7 @@ export const StreamDefaultProps = {
// theming
theme: {},
colors: 'nivo',
colorBy: 'id',
fillOpacity: 1,

// motion
animate: true,
Expand All @@ -191,8 +197,8 @@ export const StreamDefaultProps = {
const enhance = compose(
defaultProps(StreamDefaultProps),
withPropsOnChange(['theme'], ({ theme }) => ({ theme: merge({}, defaultTheme, theme) })),
withPropsOnChange(['colors', 'colorBy'], ({ colors, colorBy }) => ({
color: getColorsGenerator(colors, d => Nivo.defaults.colorRange(d)),
withPropsOnChange(['colors'], ({ colors, colorBy }) => ({
color: getColorRange(colors),
})),
withPropsOnChange(
(props, nextProps) =>
Expand Down
36 changes: 33 additions & 3 deletions src/components/charts/stream/StreamLayers.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import SmartMotion from '../../SmartMotion'

const StreamLayers = ({
layers,
fillOpacity,

showTooltip,
hideTooltip,

// motion
animate,
Expand All @@ -22,7 +26,21 @@ const StreamLayers = ({
if (animate !== true) {
return (
<g>
{layers.map(({ path, color }, i) => <path key={i} d={path} fill={color} />)}
{layers.map(({ id, path, color }, i) =>
<path
key={i}
onMouseMove={e => {
showTooltip(id, e)
}}
onMouseEnter={e => {
showTooltip(id, e)
}}
onMouseLeave={hideTooltip}
d={path}
fill={color}
fillOpacity={fillOpacity}
/>
)}
</g>
)
}
Expand All @@ -34,15 +52,26 @@ const StreamLayers = ({

return (
<g>
{layers.map(({ path, color }, i) =>
{layers.map(({ id, path, color }, i) =>
<SmartMotion
key={i}
style={spring => ({
d: spring(path, springConfig),
fill: spring(color, springConfig),
fillOpacity: spring(fillOpacity, springConfig),
})}
>
{style => <path {...style} />}
{style =>
<path
onMouseMove={e => {
showTooltip(id, e)
}}
onMouseEnter={e => {
showTooltip(id, e)
}}
onMouseLeave={hideTooltip}
{...style}
/>}
</SmartMotion>
)}
</g>
Expand All @@ -51,6 +80,7 @@ const StreamLayers = ({

StreamLayers.propTypes = {
area: PropTypes.func.isRequired,
fillOpacity: PropTypes.number.isRequired,

// motion
...motionPropTypes,
Expand Down

0 comments on commit c3a997b

Please sign in to comment.