Skip to content

Commit

Permalink
feat(funnel): improve animation management
Browse files Browse the repository at this point in the history
  • Loading branch information
plouc committed Jun 17, 2020
1 parent bbdbc37 commit 99359f5
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 53 deletions.
1 change: 1 addition & 0 deletions packages/core/package.json
Expand Up @@ -27,6 +27,7 @@
"lodash": "^4.17.11",
"react-measure": "^2.2.4",
"react-motion": "^0.5.2",
"react-spring": "^8.0.27",
"recompose": "^0.30.0"
},
"peerDependencies": {
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/hocs/withContainer.js
Expand Up @@ -23,6 +23,7 @@ const Container = ({
animate,
motionStiffness,
motionDamping,
motionConfig,
}) => {
const container = useRef(null)
const {
Expand All @@ -41,6 +42,7 @@ const Container = ({
animate={animate}
stiffness={motionStiffness}
damping={motionDamping}
config={motionConfig}
>
<tooltipContext.Provider
value={{ showTooltipAt, showTooltipFromEvent, hideTooltip }}
Expand All @@ -66,9 +68,10 @@ const Container = ({
Container.propTypes = {
children: PropTypes.node.isRequired,
theme: PropTypes.object,
animate: PropTypes.bool,
motionStiffness: PropTypes.number,
motionDamping: PropTypes.number,
animate: MotionConfigProvider.propTypes.animate,
motionStiffness: MotionConfigProvider.propTypes.stiffness,
motionDamping: MotionConfigProvider.propTypes.damping,
motionConfig: MotionConfigProvider.propTypes.config,
renderWrapper: PropTypes.bool,
}

Expand All @@ -86,6 +89,7 @@ export const withContainer = WrappedComponent => {
animate={childProps.animate}
motionStiffness={childProps.motionStiffness}
motionDamping={childProps.motionDamping}
motionConfig={childProps.motionConfig}
>
<WrappedComponent {...childProps} />
</Container>
Expand Down
40 changes: 31 additions & 9 deletions packages/core/src/motion/context.js
Expand Up @@ -7,30 +7,52 @@
* file that was distributed with this source code.
*/
import React, { createContext, useMemo } from 'react'
import { isString } from 'lodash'
import PropTypes from 'prop-types'
import { config as presets } from 'react-spring'

export const motionConfigContext = createContext()

export const MotionConfigProvider = ({ children, animate, stiffness, damping }) => {
const value = useMemo(
() => ({
/**
* For now we're supporting both react-motion and react-spring,
* however, react-motion will be gradually replaced by react-spring.
*/
export const MotionConfigProvider = ({ children, animate, stiffness, damping, config }) => {
const value = useMemo(() => {
const reactSpringConfig = isString(config) ? presets[config] : config

return {
animate,
springConfig: { stiffness, damping },
}),
[animate, stiffness, damping]
)
config: reactSpringConfig,
}
}, [animate, stiffness, damping, config])

return <motionConfigContext.Provider value={value}>{children}</motionConfigContext.Provider>
}

MotionConfigProvider.propTypes = {
children: PropTypes.node.isRequired,
animate: PropTypes.bool.isRequired,
stiffness: PropTypes.number.isRequired,
damping: PropTypes.number.isRequired,
animate: PropTypes.bool,
stiffness: PropTypes.number,
damping: PropTypes.number,
config: PropTypes.oneOfType([
PropTypes.oneOf(Object.keys(presets)),
PropTypes.shape({
mass: PropTypes.number,
tension: PropTypes.number,
friction: PropTypes.number,
clamp: PropTypes.bool,
precision: PropTypes.number,
velocity: PropTypes.number,
duration: PropTypes.number,
easing: PropTypes.func,
}),
]),
}
MotionConfigProvider.defaultProps = {
animate: true,
stiffness: 90,
damping: 15,
config: 'default',
}
8 changes: 5 additions & 3 deletions packages/core/src/props/index.js
Expand Up @@ -7,6 +7,7 @@
* file that was distributed with this source code.
*/
import PropTypes from 'prop-types'
import { MotionConfigProvider } from '../motion'

export const marginPropType = PropTypes.shape({
top: PropTypes.number,
Expand All @@ -16,9 +17,10 @@ export const marginPropType = PropTypes.shape({
}).isRequired

export const motionPropTypes = {
animate: PropTypes.bool.isRequired,
motionStiffness: PropTypes.number.isRequired,
motionDamping: PropTypes.number.isRequired,
animate: MotionConfigProvider.propTypes.animate,
motionStiffness: MotionConfigProvider.propTypes.stiffness,
motionDamping: MotionConfigProvider.propTypes.damping,
motionConfig: MotionConfigProvider.propTypes.config,
}

export const blendModes = [
Expand Down
8 changes: 4 additions & 4 deletions packages/funnel/package.json
Expand Up @@ -23,15 +23,15 @@
"dist/"
],
"dependencies": {
"@nivo/annotations": "0.62.0",
"@nivo/colors": "0.62.0",
"@nivo/core": "0.62.0",
"@nivo/tooltip": "0.62.0",
"d3-scale": "^3.0.0",
"d3-shape": "^1.3.5",
"react-spring": "^8.0.27"
},
"peerDependencies": {
"@nivo/annotations": "0.62.0",
"@nivo/colors": "0.62.0",
"@nivo/core": "0.62.0",
"@nivo/tooltip": "0.62.0",
"prop-types": ">= 15.5.10 < 16.0.0",
"react": ">= 16.8.4 < 17.0.0"
},
Expand Down
26 changes: 7 additions & 19 deletions packages/funnel/src/Funnel.js
Expand Up @@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
import React, { Fragment } from 'react'
import { SvgWrapper, withContainer, useDimensions, useTheme, useMotionConfig } from '@nivo/core'
import { SvgWrapper, withContainer, useDimensions, useTheme } from '@nivo/core'
import { FunnelPropTypes, FunnelDefaultProps } from './props'
import { useFunnel } from './hooks'
import { Parts } from './Parts'
Expand Down Expand Up @@ -56,7 +56,6 @@ const Funnel = props => {
onMouseMove,
onMouseLeave,
onClick,
tooltip,
} = props

const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
Expand All @@ -66,15 +65,14 @@ const Funnel = props => {
)

const theme = useTheme()
const { animate } = useMotionConfig()

const {
areaGenerator,
borderGenerator,
parts,
beforeSeparators,
afterSeparators,
setCurrentPartId,
customLayerProps,
} = useFunnel({
data,
width: innerWidth,
Expand Down Expand Up @@ -142,18 +140,7 @@ const Funnel = props => {
<SvgWrapper width={outerWidth} height={outerHeight} margin={margin} theme={theme}>
{layers.map((layer, i) => {
if (typeof layer === 'function') {
return (
<Fragment key={i}>
{layer({
...props,
innerWidth,
innerHeight,
outerWidth,
outerHeight,
parts,
})}
</Fragment>
)
return <Fragment key={i}>{layer(customLayerProps)}</Fragment>
}

return layerById[layer]
Expand All @@ -162,7 +149,8 @@ const Funnel = props => {
)
}

Funnel.propTypes = FunnelPropTypes
Funnel.defaultProps = FunnelDefaultProps
const WrappedFunnel = withContainer(Funnel)
WrappedFunnel.propTypes = FunnelPropTypes
WrappedFunnel.defaultProps = FunnelDefaultProps

export default withContainer(Funnel)
export default WrappedFunnel
12 changes: 10 additions & 2 deletions packages/funnel/src/Part.js
Expand Up @@ -8,16 +8,19 @@
*/
import React from 'react'
import PropTypes from 'prop-types'
import { useSpring, animated, config } from 'react-spring'
import { useSpring, animated } from 'react-spring'
import { useMotionConfig } from '@nivo/core'

export const Part = ({ part, areaGenerator, borderGenerator }) => {
const { config: motionConfig } = useMotionConfig()

const animatedProps = useSpring({
areaPath: areaGenerator(part.areaPoints),
areaColor: part.color,
borderPath: borderGenerator(part.borderPoints),
borderWidth: part.borderWidth,
borderColor: part.borderColor,
config: config.wobbly,
config: motionConfig,
})

return (
Expand All @@ -37,6 +40,7 @@ export const Part = ({ part, areaGenerator, borderGenerator }) => {
fillOpacity={part.fillOpacity}
onMouseEnter={part.onMouseEnter}
onMouseLeave={part.onMouseLeave}
onMouseMove={part.onMouseMove}
onClick={part.onClick}
/>
</>
Expand All @@ -52,6 +56,10 @@ Part.propTypes = {
borderWidth: PropTypes.number.isRequired,
borderColor: PropTypes.string.isRequired,
borderOpacity: PropTypes.number.isRequired,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
onMouseMove: PropTypes.func,
onClick: PropTypes.func,
}).isRequired,
areaGenerator: PropTypes.func.isRequired,
borderGenerator: PropTypes.func.isRequired,
Expand Down
7 changes: 4 additions & 3 deletions packages/funnel/src/Separator.js
Expand Up @@ -8,18 +8,19 @@
*/
import React from 'react'
import PropTypes from 'prop-types'
import { config, useSpring, animated } from 'react-spring'
import { useTheme } from '@nivo/core'
import { useSpring, animated } from 'react-spring'
import { useTheme, useMotionConfig } from '@nivo/core'

export const Separator = ({ separator }) => {
const theme = useTheme()
const { config: motionConfig } = useMotionConfig()

const animatedProps = useSpring({
x1: separator.x0,
x2: separator.x1,
y1: separator.y0,
y2: separator.y1,
config: config.wobbly,
config: motionConfig,
})

return (
Expand Down
16 changes: 16 additions & 0 deletions packages/funnel/src/Separators.js
Expand Up @@ -7,6 +7,7 @@
* file that was distributed with this source code.
*/
import React from 'react'
import PropTypes from 'prop-types'
import { Separator } from './Separator'

export const Separators = ({ beforeSeparators, afterSeparators }) => (
Expand All @@ -19,3 +20,18 @@ export const Separators = ({ beforeSeparators, afterSeparators }) => (
))}
</>
)

const separatorsPropType = PropTypes.arrayOf(
PropTypes.shape({
partId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
x0: PropTypes.number.isRequired,
x1: PropTypes.number.isRequired,
y0: PropTypes.number.isRequired,
y1: PropTypes.number.isRequired,
})
).isRequired

Separators.propTypes = {
beforeSeparators: separatorsPropType,
afterSeparators: separatorsPropType,
}
24 changes: 24 additions & 0 deletions packages/funnel/src/hooks.js
Expand Up @@ -501,6 +501,29 @@ export const useFunnel = ({
]
)

const customLayerProps = useMemo(
() => ({
width,
height,
parts: partsWithHandlers,
areaGenerator,
borderGenerator,
beforeSeparators,
afterSeparators,
setCurrentPartId,
}),
[
width,
height,
partsWithHandlers,
areaGenerator,
borderGenerator,
beforeSeparators,
afterSeparators,
setCurrentPartId,
]
)

return {
parts: partsWithHandlers,
areaGenerator,
Expand All @@ -509,6 +532,7 @@ export const useFunnel = ({
afterSeparators,
setCurrentPartId,
currentPartId,
customLayerProps,
}
}

Expand Down
8 changes: 4 additions & 4 deletions packages/funnel/src/props.js
Expand Up @@ -8,6 +8,7 @@
*/
import PropTypes from 'prop-types'
import { ordinalColorsPropType, inheritedColorPropType } from '@nivo/colors'
import { MotionConfigProvider } from '@nivo/core'
import { motionPropTypes } from '@nivo/core'
import { annotationSpecPropType } from '@nivo/annotations'

Expand Down Expand Up @@ -77,7 +78,7 @@ export const FunnelDefaultProps = {
borderOpacity: 0.66,

enableLabel: true,
labelColor: { from: 'color', modifiers: [['darker', 2]] },
labelColor: { theme: 'background' },

enableBeforeSeparators: true,
beforeSeparatorLength: 0,
Expand All @@ -91,7 +92,6 @@ export const FunnelDefaultProps = {
isInteractive: true,
currentPartSizeExtension: 0,

animate: true,
motionDamping: 13,
motionStiffness: 90,
animate: MotionConfigProvider.defaultProps.animate,
motionConfig: MotionConfigProvider.defaultProps.config,
}
Expand Up @@ -34,7 +34,7 @@ const InheritedColorModifierControl = ({ modifier, onChange }) => {
type="range"
value={modifier[1]}
min={0}
max={2}
max={3}
step={0.1}
onChange={event => onChange([modifier[0], event.target.value])}
/>
Expand Down

0 comments on commit 99359f5

Please sign in to comment.