diff --git a/src/components/charts/Container.js b/src/components/charts/Container.js index 9cbdb892b..df055281e 100644 --- a/src/components/charts/Container.js +++ b/src/components/charts/Container.js @@ -15,6 +15,7 @@ const containerStyle = { } const tooltipStyle = { + pointerEvents: 'none', position: 'absolute', zIndex: 10, top: 0, diff --git a/src/components/charts/heatmap/HeatMap.js b/src/components/charts/heatmap/HeatMap.js index 30a82bc71..ff01dd6ab 100644 --- a/src/components/charts/heatmap/HeatMap.js +++ b/src/components/charts/heatmap/HeatMap.js @@ -6,11 +6,12 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import React from 'react' -import { min, max, isEqual } from 'lodash' -import { TransitionMotion, spring } from 'react-motion' +import React, { Component } from 'react' +import { partial } from 'lodash' +import { TransitionMotion } from 'react-motion' import { colorMotionSpring, getInterpolatedColor } from '../../../lib/colors' import { HeatMapPropTypes } from './props' +import { computeNodes } from '../../../lib/charts/heatmap' import enhance from './enhance' import Container from '../Container' import SvgWrapper from '../SvgWrapper' @@ -18,191 +19,196 @@ import Grid from '../../axes/Grid' import Axes from '../../axes/Axes' import HeatMapCellRect from './HeatMapCellRect' import HeatMapCellCircle from './HeatMapCellCircle' +import HeatMapCellTooltip from './HeatMapCellTooltip' -const HeatMap = ({ - data, - getIndex, - keys, - - cellWidth, - cellHeight, - sizeScale, - xScale, - yScale, - offsetX, - offsetY, - - margin, - width, - height, - outerWidth, - outerHeight, - - // cells - cellShape, - cellOpacity, - cellBorderWidth, - getCellBorderColor, - - // axes & grid - axisTop, - axisRight, - axisBottom, - axisLeft, - enableGridX, - enableGridY, - - // labels - getLabelTextColor, - - // theming - theme, - colorScale, - - // motion - animate, - motionStiffness, - motionDamping, - - // interactivity - isInteractive, -}) => { - let Cell - if (cellShape === 'rect') { - Cell = HeatMapCellRect - } else if (cellShape === 'circle') { - Cell = HeatMapCellCircle - } else { - Cell = cellShape +class HeatMap extends Component { + static propTypes = HeatMapPropTypes + + handleNodeHover = (showTooltip, node, event) => { + const { setCurrentNode, theme } = this.props + setCurrentNode(node) + showTooltip(, event) } - const nodes = data.reduce((acc, d) => { - keys.forEach(key => { - acc.push({ - key: `${key}.${getIndex(d)}`, - xKey: key, - yKey: getIndex(d), - x: xScale(key), - y: yScale(getIndex(d)), - width: sizeScale ? Math.min(sizeScale(d[key]) * cellWidth, cellWidth) : cellWidth, - height: sizeScale - ? Math.min(sizeScale(d[key]) * cellHeight, cellHeight) - : cellHeight, - value: d[key], - color: colorScale(d[key]), - }) - }) - - return acc - }, []) - - const motionProps = { - animate, - motionDamping, - motionStiffness, + handleNodeLeave = hideTooltip => { + this.props.setCurrentNode(null) + hideTooltip() } - let cells - if (animate === true) { - cells = ( - { - return { - key: node.key, - data: node, - style: { - x: spring(node.x, motionProps), - y: spring(node.y, motionProps), - width: spring(node.width, motionProps), - height: spring(node.height, motionProps), - ...colorMotionSpring(node.color, motionProps), - }, - } - })} - > - {interpolatedStyles => { + render() { + const { + xScale, + yScale, + offsetX, + offsetY, + + margin, + width, + height, + outerWidth, + outerHeight, + + // cells + cellShape, + cellBorderWidth, + getCellBorderColor, + + // axes & grid + axisTop, + axisRight, + axisBottom, + axisLeft, + enableGridX, + enableGridY, + + // labels + enableLabels, + getLabelTextColor, + + // theming + theme, + + // motion + animate, + motionStiffness, + motionDamping, + boundSpring, + + // interactivity + isInteractive, + } = this.props + + let Cell + if (cellShape === 'rect') { + Cell = HeatMapCellRect + } else if (cellShape === 'circle') { + Cell = HeatMapCellCircle + } else { + Cell = cellShape + } + + const nodes = computeNodes(this.props) + + const motionProps = { + animate, + motionDamping, + motionStiffness, + } + + return ( + + {({ showTooltip, hideTooltip }) => { + const onHover = partial(this.handleNodeHover, showTooltip) + const onLeave = partial(this.handleNodeLeave, hideTooltip) + return ( - - {interpolatedStyles.map(({ key, style, data: node }) => { - const color = getInterpolatedColor(style) - - return React.createElement(Cell, { - key, - value: node.value, - x: style.x, - y: style.y, - width: Math.max(style.width, 0), - height: Math.max(style.height, 0), - color, - opacity: cellOpacity, - borderWidth: cellBorderWidth, - borderColor: getCellBorderColor({ ...node, color }), - textColor: getLabelTextColor({ ...node, color }), - }) + + > + + + {!animate && + nodes.map(node => + React.createElement(Cell, { + key: node.key, + value: node.value, + x: node.x, + y: node.y, + width: node.width, + height: node.height, + color: node.color, + opacity: node.opacity, + borderWidth: cellBorderWidth, + borderColor: getCellBorderColor(node), + textColor: getLabelTextColor(node), + onHover: partial(onHover, node), + onLeave, + }) + )} + + {animate === true && + { + return { + key: node.key, + data: node, + style: { + x: boundSpring(node.x), + y: boundSpring(node.y), + width: boundSpring(node.width), + height: boundSpring(node.height), + opacity: boundSpring(node.opacity), + ...colorMotionSpring(node.color, { + damping: motionDamping, + stiffness: motionStiffness, + }), + }, + } + })} + > + {interpolatedStyles => { + return ( + + {interpolatedStyles.map( + ({ key, style, data: node }) => { + const color = getInterpolatedColor(style) + + return React.createElement(Cell, { + key, + value: node.value, + x: style.x, + y: style.y, + width: Math.max(style.width, 0), + height: Math.max(style.height, 0), + color, + opacity: style.opacity, + borderWidth: cellBorderWidth, + borderColor: getCellBorderColor({ + ...node, + color, + }), + textColor: getLabelTextColor({ + ...node, + color, + }), + onHover: partial(onHover, node), + onLeave, + }) + } + )} + + ) + }} + } + ) }} - + ) - } else { - cells = nodes.map(node => { - return React.createElement(Cell, { - key: node.key, - value: node.value, - x: node.x, - y: node.y, - width: node.width, - height: node.height, - color: node.color, - opacity: cellOpacity, - borderWidth: cellBorderWidth, - borderColor: getCellBorderColor(node), - textColor: getLabelTextColor(node), - }) - }) } - - return ( - - {({ showTooltip, hideTooltip }) => { - return ( - - - - {cells} - - ) - }} - - ) } -HeatMap.propTypes = HeatMapPropTypes - export default enhance(HeatMap) diff --git a/src/components/charts/heatmap/HeatMapCanvas.js b/src/components/charts/heatmap/HeatMapCanvas.js index e999f3867..bbbf3a2b4 100644 --- a/src/components/charts/heatmap/HeatMapCanvas.js +++ b/src/components/charts/heatmap/HeatMapCanvas.js @@ -12,7 +12,7 @@ import { renderAxes } from '../../../lib/canvas/axes' import { getRelativeCursor, cursorInRect } from '../../../lib/interactivity' import { renderRect, renderCircle } from '../../../lib/canvas/charts/heatmap' import { computeNodes } from '../../../lib/charts/heatmap' -import BasicTooltip from '../../tooltip/BasicTooltip' +import HeatMapCellTooltip from './HeatMapCellTooltip' import Container from '../Container' import { HeatMapPropTypes } from './props' import enhance from './enhance' @@ -114,16 +114,7 @@ class HeatMapCanvas extends Component { if (node !== undefined) { setCurrentNode(node) - showTooltip( - , - event - ) + showTooltip(, event) } else { setCurrentNode(null) hideTooltip() diff --git a/src/components/charts/heatmap/HeatMapCellCircle.js b/src/components/charts/heatmap/HeatMapCellCircle.js index 1a7ebc873..5f95fb6b5 100644 --- a/src/components/charts/heatmap/HeatMapCellCircle.js +++ b/src/components/charts/heatmap/HeatMapCellCircle.js @@ -10,6 +10,8 @@ import React from 'react' import PropTypes from 'prop-types' import pure from 'recompose/pure' +const style = { cursor: 'pointer' } + const HeatMapCellCircle = ({ value, x, @@ -21,22 +23,33 @@ const HeatMapCellCircle = ({ borderWidth, borderColor, textColor, -}) => { - return ( - - - - {value} - - - ) -} + onHover, + onLeave, +}) => + + + + {value} + + HeatMapCellCircle.propTypes = { value: PropTypes.number.isRequired, @@ -49,6 +62,8 @@ HeatMapCellCircle.propTypes = { borderWidth: PropTypes.number.isRequired, borderColor: PropTypes.string.isRequired, textColor: PropTypes.string.isRequired, + onHover: PropTypes.func.isRequired, + onLeave: PropTypes.func.isRequired, } export default pure(HeatMapCellCircle) diff --git a/src/components/charts/heatmap/HeatMapCellRect.js b/src/components/charts/heatmap/HeatMapCellRect.js index dd9fc6a20..546e2de8c 100644 --- a/src/components/charts/heatmap/HeatMapCellRect.js +++ b/src/components/charts/heatmap/HeatMapCellRect.js @@ -10,6 +10,8 @@ import React from 'react' import PropTypes from 'prop-types' import pure from 'recompose/pure' +const style = { cursor: 'pointer' } + const HeatMapCellRect = ({ value, x, @@ -21,25 +23,36 @@ const HeatMapCellRect = ({ borderWidth, borderColor, textColor, -}) => { - return ( - - - - {value} - - - ) -} + onHover, + onLeave, +}) => + + + + {value} + + HeatMapCellRect.propTypes = { value: PropTypes.number.isRequired, @@ -52,6 +65,8 @@ HeatMapCellRect.propTypes = { borderWidth: PropTypes.number.isRequired, borderColor: PropTypes.string.isRequired, textColor: PropTypes.string.isRequired, + onHover: PropTypes.func.isRequired, + onLeave: PropTypes.func.isRequired, } export default pure(HeatMapCellRect) diff --git a/src/components/charts/heatmap/HeatMapCellTooltip.js b/src/components/charts/heatmap/HeatMapCellTooltip.js new file mode 100644 index 000000000..c6b58ab75 --- /dev/null +++ b/src/components/charts/heatmap/HeatMapCellTooltip.js @@ -0,0 +1,22 @@ +/* + * 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 pure from 'recompose/pure' +import BasicTooltip from '../../tooltip/BasicTooltip' + +const HeatMapCellTooltip = ({ node, theme }) => + + +export default pure(HeatMapCellTooltip) diff --git a/src/hocs/withMotion.js b/src/hocs/withMotion.js index 2d0fddd95..12bd52979 100644 --- a/src/hocs/withMotion.js +++ b/src/hocs/withMotion.js @@ -6,10 +6,12 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import { isEqual } from 'lodash' +import { partialRight } from 'lodash' import compose from 'recompose/compose' import defaultProps from 'recompose/defaultProps' +import withPropsOnChange from 'recompose/withPropsOnChange' import setPropTypes from 'recompose/setPropTypes' +import { spring } from 'react-motion' import { motionPropTypes } from '../props' import Nivo from '../Nivo' @@ -18,7 +20,16 @@ export default () => setPropTypes(motionPropTypes), defaultProps({ animate: true, - motionStiffness: Nivo.defaults.motionStiffness, motionDamping: Nivo.defaults.motionDamping, - }) + motionStiffness: Nivo.defaults.motionStiffness, + }), + withPropsOnChange( + ['motionDamping', 'motionStiffness'], + ({ motionDamping, motionStiffness }) => ({ + boundSpring: partialRight(spring, { + damping: motionDamping, + stiffness: motionStiffness, + }), + }) + ) ) diff --git a/src/lib/charts/heatmap/index.js b/src/lib/charts/heatmap/index.js index 1e6c185b4..cd648a1ee 100644 --- a/src/lib/charts/heatmap/index.js +++ b/src/lib/charts/heatmap/index.js @@ -21,6 +21,7 @@ export const computeNodes = ({ xScale, yScale, sizeScale, + cellOpacity, cellWidth, cellHeight, colorScale, @@ -41,6 +42,7 @@ export const computeNodes = ({ : cellHeight const node = { + key: `${key}.${getIndex(d)}`, xKey: key, yKey: getIndex(d), x: xScale(key), @@ -51,7 +53,7 @@ export const computeNodes = ({ color: colorScale(d[key]), } - let opacity = 1 + let opacity = cellOpacity if (currentNode) { opacity = isHoverTarget(node, currentNode) ? cellHoverOpacity