Skip to content

Commit

Permalink
feat(scatterplot): migrate package to new architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Benitte authored and Raphaël Benitte committed Jun 8, 2019
1 parent 0ed7eb8 commit 4397dab
Show file tree
Hide file tree
Showing 14 changed files with 698 additions and 257 deletions.
12 changes: 10 additions & 2 deletions packages/scatterplot/index.d.ts
Expand Up @@ -21,7 +21,13 @@ declare module '@nivo/scatterplot' {
event: React.MouseEvent<any>
) => void

export type ScatterPlotSizeGetter = (data: ScatterPlotDatum) => number
type DatumAccessor<T> = (datum: ScatterPlotDatum) => T

export interface DynamicSizeSpec {
key: string
values: [number, number]
sizes: [number, number]
}

export interface ScatterPlotProps {
data: Array<{
Expand All @@ -40,6 +46,8 @@ declare module '@nivo/scatterplot' {

margin?: Box

layers: any[]

axisTop?: AxisProps | null
axisRight?: AxisProps | null
axisBottom?: AxisProps | null
Expand All @@ -48,7 +56,7 @@ declare module '@nivo/scatterplot' {
enableGridX?: boolean
enableGridY?: boolean

symbolSize?: number | ScatterPlotSizeGetter
symbolSize?: number | DatumAccessor<number> | DynamicSizeSpec
symbolShape?: 'circle' | 'square'

isInteractive?: boolean
Expand Down
84 changes: 84 additions & 0 deletions packages/scatterplot/src/AnimatedNodes.js
@@ -0,0 +1,84 @@
/*
* 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, { memo } from 'react'
import PropTypes from 'prop-types'
import { TransitionMotion, spring } from 'react-motion'
import { useMotionConfig } from '@nivo/core'
import { NodePropType } from './props'
import NodeWrapper from './NodeWrapper'

const AnimatedNodes = ({
nodes,
renderNode,
isInteractive,
onMouseEnter,
onMouseMove,
onMouseLeave,
onClick,
tooltip,
}) => {
const { springConfig } = useMotionConfig()

return (
<TransitionMotion
styles={nodes.map(node => ({
key: node.id,
data: node,
style: {
x: spring(node.x, springConfig),
y: spring(node.y, springConfig),
size: spring(node.size, springConfig),
},
}))}
>
{interpolatedStyles => (
<>
{interpolatedStyles.map(({ key, style, data: node }) => (
<NodeWrapper
key={key}
node={node}
renderNode={renderNode}
x={style.x}
y={style.y}
size={style.size}
color={node.style.color}
isInteractive={isInteractive}
onMouseEnter={onMouseEnter}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={onClick}
tooltip={tooltip}
/>
))}
</>
)}
</TransitionMotion>
)
}

AnimatedNodes.propTypes = {
nodes: PropTypes.arrayOf(NodePropType).isRequired,
renderNode: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object,
]).isRequired,

isInteractive: PropTypes.bool.isRequired,
onMouseEnter: PropTypes.func,
onMouseMove: PropTypes.func,
onMouseLeave: PropTypes.func,
onClick: PropTypes.func,

tooltip: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object
]).isRequired,
}

export default memo(AnimatedNodes)
Expand Up @@ -6,13 +6,10 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React from 'react'
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import compose from 'recompose/compose'
import withPropsOnChange from 'recompose/withPropsOnChange'
import pure from 'recompose/pure'

const ScatterPlotItem = ({
const Node = ({
x,
y,
size,
Expand All @@ -34,8 +31,8 @@ const ScatterPlotItem = ({
/>
)

ScatterPlotItem.propTypes = {
point: PropTypes.shape({
Node.propTypes = {
node: PropTypes.shape({
data: PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
x: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Date)])
Expand All @@ -59,21 +56,6 @@ ScatterPlotItem.propTypes = {
onMouseMove: PropTypes.func,
onMouseLeave: PropTypes.func,
onClick: PropTypes.func,

theme: PropTypes.object.isRequired,
}

const enhance = compose(
withPropsOnChange(
['point', 'onMouseEnter', 'onMouseMove', 'onMouseLeave', 'onClick'],
({ point, onMouseEnter, onMouseMove, onMouseLeave, onClick }) => ({
onMouseEnter: event => onMouseEnter(point, event),
onMouseMove: event => onMouseMove(point, event),
onMouseLeave: event => onMouseLeave(point, event),
onClick: event => onClick(point, event),
})
),
pure
)

export default enhance(ScatterPlotItem)
export default memo(Node)
94 changes: 94 additions & 0 deletions packages/scatterplot/src/NodeWrapper.js
@@ -0,0 +1,94 @@
/*
* 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, { memo, useCallback } from 'react'
import PropTypes from 'prop-types'
import { useTooltip } from '@nivo/tooltip'
import { NodePropType } from './props'

const NodeWrapper = ({
node,
renderNode: NodeComponent,
x,
y,
size,
color,
isInteractive,
onMouseEnter,
onMouseMove,
onMouseLeave,
onClick,
tooltip,
}) => {
const { showTooltipFromEvent, hideTooltip } = useTooltip()

const handleMouseEnter = useCallback(
event => {
showTooltipFromEvent(React.createElement(tooltip, { node }), event)
onMouseEnter && onMouseEnter(node, event)
},
[node, tooltip, showTooltipFromEvent, onMouseEnter]
)

const handleMouseMove = useCallback(
event => {
showTooltipFromEvent(React.createElement(tooltip, { node }), event)
onMouseMove && onMouseMove(node, event)
},
[node, tooltip, showTooltipFromEvent, onMouseMove]
)

const handleMouseLeave = useCallback(
event => {
hideTooltip()
onMouseLeave && onMouseLeave(node, event)
},
[node, hideTooltip, onMouseLeave]
)

const handleClick = useCallback(
event => {
onClick && onClick(node, event)
},
[node, onClick]
)

return React.createElement(NodeComponent, {
node,
x,
y,
size,
color,
onMouseEnter: isInteractive ? handleMouseEnter : undefined,
onMouseMove: isInteractive ? handleMouseMove : undefined,
onMouseLeave: isInteractive ? handleMouseLeave : undefined,
onClick: (isInteractive && onClick) ? handleClick : undefined,
})
}

NodeWrapper.propTypes = {
node: NodePropType.isRequired,

x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
color: PropTypes.string.isRequired,

isInteractive: PropTypes.bool.isRequired,
onMouseEnter: PropTypes.func,
onMouseMove: PropTypes.func,
onMouseLeave: PropTypes.func,
onClick: PropTypes.func,

tooltip: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object
]).isRequired,
}

export default memo(NodeWrapper)

0 comments on commit 4397dab

Please sign in to comment.