Skip to content

Commit

Permalink
feat(scatterplot): add support for tooltips on ScatterPlotCanvas
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Benitte committed Dec 9, 2017
1 parent eb4e6bb commit fc01970
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 147 deletions.
10 changes: 2 additions & 8 deletions packages/nivo-chord/src/ChordArcs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ import { TransitionMotion, spring } from 'react-motion'
import pure from 'recompose/pure'
import { colorMotionSpring, getInterpolatedColor } from '@nivo/core'
import ChordArcTooltip from './ChordArcTooltip'
import {
defaultAnimate,
defaultMotionDamping,
defaultMotionStiffness,
} from '@nivo/core'
import { motionPropTypes } from '@nivo/core'

const ChordArcs = ({
arcs,
Expand Down Expand Up @@ -134,9 +130,7 @@ ChordArcs.propTypes = {
showTooltip: PropTypes.func.isRequired,
hideTooltip: PropTypes.func.isRequired,
tooltipFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
animate: defaultAnimate,
motionDamping: defaultMotionDamping,
motionStiffness: defaultMotionStiffness,
...motionPropTypes,
}

export default pure(ChordArcs)
10 changes: 2 additions & 8 deletions packages/nivo-chord/src/ChordLabels.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { TransitionMotion, spring } from 'react-motion'
import { midAngle, getPolarLabelProps } from '@nivo/core'
import {
defaultAnimate,
defaultMotionDamping,
defaultMotionStiffness,
} from '@nivo/core'
import { motionPropTypes } from '@nivo/core'

const ChordLabels = ({
arcs,
Expand Down Expand Up @@ -107,9 +103,7 @@ ChordLabels.propTypes = {
getLabel: PropTypes.func.isRequired,
getColor: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
animate: defaultAnimate,
motionDamping: defaultMotionDamping,
motionStiffness: defaultMotionStiffness,
...motionPropTypes,
}

export default ChordLabels
10 changes: 2 additions & 8 deletions packages/nivo-chord/src/ChordRibbons.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ import pure from 'recompose/pure'
import { colorMotionSpring, getInterpolatedColor } from '@nivo/core'
import { midAngle } from '@nivo/core'
import { TableTooltip, Chip } from '@nivo/core'
import {
defaultAnimate,
defaultMotionDamping,
defaultMotionStiffness,
} from '@nivo/core'
import { motionPropTypes } from '@nivo/core'

/**
* Used to get ribbon angles, instead of using source and target arcs,
Expand Down Expand Up @@ -228,9 +224,7 @@ ChordRibbons.propTypes = {
showTooltip: PropTypes.func.isRequired,
hideTooltip: PropTypes.func.isRequired,
tooltipFormat: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
animate: defaultAnimate,
motionDamping: defaultMotionDamping,
motionStiffness: defaultMotionStiffness,
...motionPropTypes,
}

const enhance = compose(
Expand Down
4 changes: 2 additions & 2 deletions packages/nivo-core/src/lib/interactivity/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
export * from './detect'

export const getRelativeCursor = (el, event) => {
const { pageX, pageY } = event
const { clientX, clientY } = event
const bounds = el.getBoundingClientRect()

return [pageX - bounds.left, pageY - bounds.top]
return [clientX - bounds.left, clientY - bounds.top]
}
171 changes: 94 additions & 77 deletions packages/nivo-scatterplot/src/ScatterPlot.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const ScatterPlot = ({
theme,
getColor,

// symbols,
symbolSize,

// motion
animate,
motionStiffness,
Expand Down Expand Up @@ -67,89 +70,103 @@ const ScatterPlot = ({
fill: getColor(serie),
}))

const symbols = data.reduce((agg, serie) => {
return [
const symbols = data.reduce(
(agg, serie) => [
...agg,
...serie.data.map(d => {
return {
id: `${serie.id}.${d.id}`,
x: xScale(d.x),
y: yScale(d.y),
color: getColor(serie),
data: { ...d, serie: serie.id },
}
}),
]
}, [])
...serie.data.map(d => ({
id: `${serie.id}.${d.id}`,
x: xScale(d.x),
y: yScale(d.y),
color: getColor(serie),
data: { ...d, serie: serie.id },
})),
],
[]
)

return (
<Container isInteractive={isInteractive} theme={theme}>
{({ showTooltip, hideTooltip }) => {
return (
<SvgWrapper width={outerWidth} height={outerHeight} margin={margin}>
<Grid
theme={theme}
width={width}
height={height}
xScale={enableGridX ? xScale : null}
yScale={enableGridY ? yScale : null}
{...motionProps}
/>
<Axes
xScale={xScale}
yScale={yScale}
width={width}
height={height}
theme={theme}
top={axisTop}
right={axisRight}
bottom={axisBottom}
left={axisLeft}
{...motionProps}
/>
{animate === true && (
<TransitionMotion
styles={symbols.map(symbol => ({
key: symbol.id,
data: symbol,
style: {
x: spring(symbol.x, springConfig),
y: spring(symbol.y, springConfig),
},
}))}
>
{interpolatedStyles => (
<g>
{interpolatedStyles.map(({ key, style, data: symbol }) => (
<ScatterPlotItem
key={key}
x={style.x}
y={style.y}
color={symbol.color}
data={symbol.data}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
tooltipFormat={tooltipFormat}
onClick={onClick}
theme={theme}
/>
))}
</g>
)}
</TransitionMotion>
)}
{legends.map((legend, i) => (
<BoxLegendSvg
key={i}
{...legend}
containerWidth={width}
containerHeight={height}
data={legendData}
{({ showTooltip, hideTooltip }) => (
<SvgWrapper width={outerWidth} height={outerHeight} margin={margin}>
<Grid
theme={theme}
width={width}
height={height}
xScale={enableGridX ? xScale : null}
yScale={enableGridY ? yScale : null}
{...motionProps}
/>
<Axes
xScale={xScale}
yScale={yScale}
width={width}
height={height}
theme={theme}
top={axisTop}
right={axisRight}
bottom={axisBottom}
left={axisLeft}
{...motionProps}
/>
{!animate &&
symbols.map(symbol => (
<ScatterPlotItem
key={symbol.id}
x={symbol.x}
y={symbol.y}
size={symbolSize}
color={symbol.color}
data={symbol.data}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
tooltipFormat={tooltipFormat}
onClick={onClick}
theme={theme}
/>
))}
</SvgWrapper>
)
}}
{animate === true && (
<TransitionMotion
styles={symbols.map(symbol => ({
key: symbol.id,
data: symbol,
style: {
x: spring(symbol.x, springConfig),
y: spring(symbol.y, springConfig),
},
}))}
>
{interpolatedStyles => (
<g>
{interpolatedStyles.map(({ key, style, data: symbol }) => (
<ScatterPlotItem
key={key}
x={style.x}
y={style.y}
size={symbolSize}
color={symbol.color}
data={symbol.data}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
tooltipFormat={tooltipFormat}
onClick={onClick}
theme={theme}
/>
))}
</g>
)}
</TransitionMotion>
)}
{legends.map((legend, i) => (
<BoxLegendSvg
key={i}
{...legend}
containerWidth={width}
containerHeight={height}
data={legendData}
/>
))}
</SvgWrapper>
)}
</Container>
)
}
Expand Down
55 changes: 39 additions & 16 deletions packages/nivo-scatterplot/src/ScatterPlotCanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ import enhance from './enhance'

const findNodeUnderCursor = (nodes, margin, x, y) =>
nodes.find(node =>
isCursorInRect(node.x + margin.left, node.y + margin.top, node.width, node.height, x, y)
isCursorInRect(
node.x + margin.left - node.size / 2,
node.y + margin.top - node.size / 2,
node.size,
node.size,
x,
y
)
)

class ScatterPlotCanvas extends Component {
Expand Down Expand Up @@ -69,6 +76,9 @@ class ScatterPlotCanvas extends Component {
enableGridX,
enableGridY,

// symbols
symbolSize,

// theming
getColor,

Expand Down Expand Up @@ -111,13 +121,27 @@ class ScatterPlotCanvas extends Component {
left: axisLeft,
})

data.forEach(serie => {
serie.data.forEach(d => {
this.ctx.fillStyle = getColor(serie)
this.ctx.fillRect(xScale(d.x) - 2, yScale(d.y) - 2, 4, 4)
})
const items = data.reduce(
(agg, serie) => [
...agg,
...serie.data.map(d => ({
x: xScale(d.x),
y: yScale(d.y),
size: symbolSize,
color: getColor(serie),
data: { ...d, serie: serie.id },
})),
],
[]
)

items.forEach(d => {
this.ctx.fillStyle = d.color
this.ctx.fillRect(d.x - symbolSize / 2, d.y - symbolSize / 2, symbolSize, symbolSize)
})

this.items = items

const legendData = data.map(serie => ({
label: serie.id,
fill: getColor(serie),
Expand All @@ -134,20 +158,19 @@ class ScatterPlotCanvas extends Component {
}

handleMouseHover = (showTooltip, hideTooltip) => event => {
if (!this.bars) return
if (!this.items) return

const { margin, theme } = this.props
const [x, y] = getRelativeCursor(this.surface, event)

const bar = findNodeUnderCursor(this.bars, margin, x, y)

if (bar !== undefined) {
const item = findNodeUnderCursor(this.items, margin, x, y)
if (item !== undefined) {
showTooltip(
<BasicTooltip
id={`${bar.data.id} - ${bar.data.indexValue}`}
value={bar.data.value}
id={item.data.serie}
value={`x: ${item.data.x}, y: ${item.data.y}`}
enableChip={true}
color={bar.color}
color={item.color}
theme={theme}
/>,
event
Expand All @@ -162,13 +185,13 @@ class ScatterPlotCanvas extends Component {
}

handleClick = event => {
if (!this.bars) return
if (!this.items) return

const { margin, onClick } = this.props
const [x, y] = getRelativeCursor(this.surface, event)

const node = findNodeUnderCursor(this.bars, margin, x, y)
if (node !== undefined) onClick(node.data, event)
const item = findNodeUnderCursor(this.items, margin, x, y)
if (item !== undefined) onClick(item.data, event)
}

render() {
Expand Down
4 changes: 0 additions & 4 deletions packages/nivo-scatterplot/src/ScatterPlotItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,6 @@ ScatterPlotItem.propTypes = {
}).isRequired,
}

ScatterPlotItem.defaultProps = {
size: 6,
}

const enhance = compose(
withPropsOnChange(['data', 'onClick'], ({ data, onClick }) => ({
onClick: event => onClick(data, event),
Expand Down
Loading

0 comments on commit fc01970

Please sign in to comment.