diff --git a/src/components/charts/bar/BarCanvas.js b/src/components/charts/bar/BarCanvas.js index 1b4f9559c..8863aa430 100644 --- a/src/components/charts/bar/BarCanvas.js +++ b/src/components/charts/bar/BarCanvas.js @@ -7,14 +7,18 @@ * file that was distributed with this source code. */ import React, { Component } from 'react' -import { partial } from 'lodash' import { generateGroupedBars, generateStackedBars } from '../../../lib/charts/bar' import { renderAxes } from '../../../lib/canvas/axes' +import { getRelativeCursor, isCursorInRect } from '../../../lib/interactivity' import Container from '../Container' import BasicTooltip from '../../tooltip/BasicTooltip' import { BarPropTypes } from './props' import enhance from './enhance' -import { getRelativeCursor, isCursorInRect } from '../../../lib/interactivity' + +const findNodeUnderCursor = (nodes, margin, x, y) => + nodes.find(node => + isCursorInRect(node.x + margin.left, node.y + margin.top, node.width, node.height, x, y) + ) class BarCanvas extends Component { componentDidMount() { @@ -120,15 +124,13 @@ class BarCanvas extends Component { }) } - handleMouseHover = (showTooltip, hideTooltip, event) => { + handleMouseHover = (showTooltip, hideTooltip) => event => { if (!this.bars) return + const { margin, theme } = this.props const [x, y] = getRelativeCursor(this.surface, event) - const { margin, theme } = this.props - const bar = this.bars.find(bar => - isCursorInRect(bar.x + margin.left, bar.y + margin.top, bar.width, bar.height, x, y) - ) + const bar = findNodeUnderCursor(this.bars, margin, x, y) if (bar !== undefined) { showTooltip( @@ -146,10 +148,20 @@ class BarCanvas extends Component { } } - handleMouseLeave = hideTooltip => { + handleMouseLeave = hideTooltip => () => { hideTooltip() } + handleClick = event => { + if (!this.bars) 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) + } + render() { const { outerWidth, outerHeight, pixelRatio, isInteractive, theme } = this.props @@ -166,9 +178,10 @@ class BarCanvas extends Component { width: outerWidth, height: outerHeight, }} - onMouseEnter={partial(this.handleMouseHover, showTooltip, hideTooltip)} - onMouseMove={partial(this.handleMouseHover, showTooltip, hideTooltip)} - onMouseLeave={partial(this.handleMouseLeave, hideTooltip)} + onMouseEnter={this.handleMouseHover(showTooltip, hideTooltip)} + onMouseMove={this.handleMouseHover(showTooltip, hideTooltip)} + onMouseLeave={this.handleMouseLeave(hideTooltip)} + onClick={this.handleClick} /> )} diff --git a/src/components/charts/bubble/BubbleCanvas.js b/src/components/charts/bubble/BubbleCanvas.js index 316d17082..d7f598fac 100644 --- a/src/components/charts/bubble/BubbleCanvas.js +++ b/src/components/charts/bubble/BubbleCanvas.js @@ -7,7 +7,6 @@ * file that was distributed with this source code. */ import React, { Component } from 'react' -import _ from 'lodash' import Container from '../Container' import enhance from './enhance' diff --git a/src/components/charts/treemap/ResponsiveTreeMapPlaceholders.js b/src/components/charts/treemap/ResponsiveTreeMapCanvas.js similarity index 71% rename from src/components/charts/treemap/ResponsiveTreeMapPlaceholders.js rename to src/components/charts/treemap/ResponsiveTreeMapCanvas.js index 39509e572..07b97c7fa 100644 --- a/src/components/charts/treemap/ResponsiveTreeMapPlaceholders.js +++ b/src/components/charts/treemap/ResponsiveTreeMapCanvas.js @@ -8,10 +8,10 @@ */ import React from 'react' import ResponsiveWrapper from '../ResponsiveWrapper' -import TreeMapPlaceholders from './TreeMapPlaceholders' +import TreeMapCanvas from './TreeMapCanvas' export default props => ( - {({ width, height }) => } + {({ width, height }) => } ) diff --git a/src/components/charts/treemap/TreeMapCanvas.js b/src/components/charts/treemap/TreeMapCanvas.js new file mode 100644 index 000000000..b9cedc8b0 --- /dev/null +++ b/src/components/charts/treemap/TreeMapCanvas.js @@ -0,0 +1,153 @@ +/* + * 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, { Component } from 'react' +import { degreesToRadians } from '../../../lib/polar' +import { getRelativeCursor, isCursorInRect } from '../../../lib/interactivity' +import Container from '../Container' +import enhance from './enhance' +import TreeMapNodeTooltip from './TreeMapNodeTooltip' + +const findNodeUnderCursor = (nodes, margin, x, y) => + nodes.find(node => + isCursorInRect(node.x + margin.left, node.y + margin.top, node.width, node.height, x, y) + ) + +class TreeMapCanvas extends Component { + componentDidMount() { + this.ctx = this.surface.getContext('2d') + this.draw(this.props) + } + + componentDidUpdate() { + this.ctx = this.surface.getContext('2d') + this.draw(this.props) + } + + draw(props) { + const { + nodes, + + pixelRatio, + + // dimensions + margin, + outerWidth, + outerHeight, + + // styling + borderWidth, + getBorderColor, + + // labels + enableLabel, + getLabelTextColor, + orientLabel, + } = props + + this.surface.width = outerWidth * pixelRatio + this.surface.height = outerHeight * pixelRatio + + this.ctx.scale(pixelRatio, pixelRatio) + this.ctx.clearRect(0, 0, outerWidth, outerHeight) + this.ctx.translate(margin.left, margin.top) + + nodes.forEach(node => { + this.ctx.fillStyle = node.color + this.ctx.fillRect(node.x, node.y, node.width, node.height) + + if (borderWidth > 0) { + this.ctx.strokeStyle = getBorderColor(node) + this.ctx.lineWidth = borderWidth + this.ctx.strokeRect(node.x, node.y, node.width, node.height) + } + }) + + if (enableLabel) { + this.ctx.textAlign = 'center' + this.ctx.textBaseline = 'middle' + + // draw labels on top + nodes.filter(({ label }) => label !== undefined).forEach(node => { + const labelTextColor = getLabelTextColor(node) + + const rotate = orientLabel && node.height > node.width + + this.ctx.save() + this.ctx.translate(node.x + node.width / 2, node.y + node.height / 2) + this.ctx.rotate(degreesToRadians(rotate ? -90 : 0)) + + this.ctx.fillStyle = labelTextColor + this.ctx.fillText(node.label, 0, 0) + + this.ctx.restore() + }) + } + } + + handleMouseHover = (showTooltip, hideTooltip) => event => { + const { isInteractive, nodes, margin, theme } = this.props + + if (!isInteractive) return + + const [x, y] = getRelativeCursor(this.surface, event) + + const node = findNodeUnderCursor(nodes, margin, x, y) + + if (node !== undefined) { + showTooltip(, event) + } else { + hideTooltip() + } + } + + handleMouseLeave = hideTooltip => () => { + hideTooltip() + } + + handleClick = event => { + const { isInteractive, nodes, margin, onClick } = this.props + + if (!isInteractive) return + + const [x, y] = getRelativeCursor(this.surface, event) + + const node = findNodeUnderCursor(nodes, margin, x, y) + if (node !== undefined) onClick(node, event) + } + + render() { + const { outerWidth, outerHeight, pixelRatio, isInteractive, theme } = this.props + + return ( + + {({ showTooltip, hideTooltip }) => ( + { + this.surface = surface + }} + width={outerWidth * pixelRatio} + height={outerHeight * pixelRatio} + style={{ + width: outerWidth, + height: outerHeight, + }} + onMouseEnter={this.handleMouseHover(showTooltip, hideTooltip)} + onMouseMove={this.handleMouseHover(showTooltip, hideTooltip)} + onMouseLeave={this.handleMouseLeave(hideTooltip)} + onClick={this.handleClick} + /> + )} + + ) + } +} + +TreeMapCanvas.displayName = 'TreeMapCanvas' + +export default enhance(TreeMapCanvas) diff --git a/src/components/charts/treemap/TreeMapNodeTooltip.js b/src/components/charts/treemap/TreeMapNodeTooltip.js new file mode 100644 index 000000000..82beaf1e6 --- /dev/null +++ b/src/components/charts/treemap/TreeMapNodeTooltip.js @@ -0,0 +1,23 @@ +/* + * 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 TreeMapNodeTooltip = ({ node, theme }) => ( + +) + +export default pure(TreeMapNodeTooltip) diff --git a/src/components/charts/treemap/TreeMapPlaceholders.js b/src/components/charts/treemap/TreeMapPlaceholders.js deleted file mode 100644 index 2e4d30df5..000000000 --- a/src/components/charts/treemap/TreeMapPlaceholders.js +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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 { TransitionMotion, spring } from 'react-motion' -import { colorMotionSpring } from '../../../lib/colors' -import Container from '../Container' -import enhance from './enhance' - -const nodeWillEnter = ({ data: node }) => { - const width = node.x1 - node.x0 - const height = node.y1 - node.y0 - - return { - x: node.x0 + width / 2, - y: node.y0 + height / 2, - width: 0, - height: 0, - ...colorMotionSpring(node.color), - } -} - -const TreeMapPlaceholders = ({ - root, - getIdentity, - - namespace, - - margin, - outerWidth, - outerHeight, - - treemap, - leavesOnly, - - // motion - animate, - motionStiffness, - motionDamping, - - // theming - theme, - getColor, - - // interactivity - isInteractive, - - children, -}) => { - let wrapperTag - let containerTag - - const wrapperProps = {} - const containerProps = {} - - if (namespace === 'svg') { - wrapperTag = 'svg' - containerTag = 'g' - - wrapperProps.width = outerWidth - wrapperProps.height = outerHeight - wrapperProps.xmlns = 'http://www.w3.org/2000/svg' - containerProps.transform = `translate(${margin.left},${margin.top})` - } else { - wrapperTag = 'div' - containerTag = 'div' - - wrapperProps.style = { - position: 'relative', - width: outerWidth, - height: outerHeight, - } - containerProps.style = { - position: 'absolute', - top: margin.top, - left: margin.left, - } - } - - treemap(root) - - let nodes = leavesOnly ? root.leaves() : root.descendants() - nodes = nodes.map(d => { - d.color = getColor({ ...d.data, depth: d.depth }) - - d.data.id = getIdentity(d.data) - d.data.value = d.value - d.data.color = d.color - d.data.key = d - .ancestors() - .map(a => getIdentity(a.data)) - .join('.') - - return d - }) - - return ( - - {({ showTooltip, hideTooltip }) => { - if (animate === false) { - return React.createElement( - wrapperTag, - wrapperProps, - React.createElement( - containerTag, - containerProps, - children( - nodes.map(node => { - return { - key: node.data.key, - data: node.data, - style: { - x: node.x0, - y: node.y0, - width: node.x1 - node.x0, - height: node.y1 - node.y0, - color: node.color, - }, - } - }), - { showTooltip, hideTooltip, theme } - ) - ) - ) - } - - const springConfig = { - stiffness: motionStiffness, - damping: motionDamping, - } - - return React.createElement( - wrapperTag, - wrapperProps, - { - return { - key: node.data.key, - data: node.data, - style: { - x: spring(node.x0, springConfig), - y: spring(node.y0, springConfig), - width: spring(node.x1 - node.x0, springConfig), - height: spring(node.y1 - node.y0, springConfig), - ...colorMotionSpring(node.color, springConfig), - }, - } - })} - > - {interpolatedStyles => - React.createElement( - containerTag, - containerProps, - children( - interpolatedStyles.map(interpolatedStyle => { - const { - x, - y, - width, - height, - colorR, - colorG, - colorB, - } = interpolatedStyle.style - - return { - ...interpolatedStyle, - style: { - x, - y, - width: Math.max(0, width), - height: Math.max(0, height), - color: `rgb(${Math.round(colorR)},${Math.round( - colorG - )},${Math.round(colorB)})`, - }, - } - }), - { showTooltip, hideTooltip, theme } - ) - )} - - ) - }} - - ) -} - -TreeMapPlaceholders.displayName = 'TreeMapPlaceholders' - -export default enhance(TreeMapPlaceholders) diff --git a/src/components/charts/treemap/index.js b/src/components/charts/treemap/index.js index 88187c546..3818919e5 100644 --- a/src/components/charts/treemap/index.js +++ b/src/components/charts/treemap/index.js @@ -10,6 +10,6 @@ export { default as TreeMap } from './TreeMap' export { default as ResponsiveTreeMap } from './ResponsiveTreeMap' export { default as TreeMapHtml } from './TreeMapHtml' export { default as ResponsiveTreeMapHtml } from './ResponsiveTreeMapHtml' -export { default as TreeMapPlaceholders } from './TreeMapPlaceholders' -export { default as ResponsiveTreeMapPlaceholders } from './ResponsiveTreeMapPlaceholders' +export { default as TreeMapCanvas } from './TreeMapCanvas' +export { default as ResponsiveTreeMapCanvas } from './ResponsiveTreeMapCanvas' export * from './props' diff --git a/src/components/charts/treemap/interactivity.js b/src/components/charts/treemap/interactivity.js index 4b237e386..98e11080e 100644 --- a/src/components/charts/treemap/interactivity.js +++ b/src/components/charts/treemap/interactivity.js @@ -7,7 +7,7 @@ * file that was distributed with this source code. */ import React from 'react' -import BasicTooltip from '../../tooltip/BasicTooltip' +import TreeMapNodeTooltip from './TreeMapNodeTooltip' export const getNodeHandlers = ( node, @@ -16,16 +16,7 @@ export const getNodeHandlers = ( if (!isInteractive) return {} const handleTooltip = e => { - showTooltip( - , - e - ) + showTooltip(, e) } return {