diff --git a/packages/network/package.json b/packages/network/package.json
index ba7eed30e8..fa6f7fe006 100644
--- a/packages/network/package.json
+++ b/packages/network/package.json
@@ -33,9 +33,9 @@
"dependencies": {
"@nivo/colors": "0.76.0",
"@nivo/tooltip": "0.76.0",
+ "@react-spring/web": "9.3.1",
"d3-force": "^2.0.1",
- "lodash": "^4.17.21",
- "react-motion": "^0.5.2"
+ "lodash": "^4.17.21"
},
"devDependencies": {
"@nivo/core": "0.76.0"
diff --git a/packages/network/src/AnimatedLinks.tsx b/packages/network/src/AnimatedLinks.tsx
deleted file mode 100644
index 737ac8bc35..0000000000
--- a/packages/network/src/AnimatedLinks.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { memo } from 'react'
-import { TransitionMotion, spring } from 'react-motion'
-import { useMotionConfig } from '@nivo/core'
-import Link from './Link'
-
-const willEnter = ({ style, data }) => {
- const sourceX = data.previousSource ? data.previousSource.x : style.sourceX.val
- const sourceY = data.previousSource ? data.previousSource.y : style.sourceY.val
-
- return {
- sourceX,
- sourceY,
- targetX: sourceX,
- targetY: sourceY,
- }
-}
-
-interface AnimatedLinksProps {
- // links: PropTypes.array.isRequired,
- // linkThickness: PropTypes.func.isRequired,
- // linkColor: PropTypes.func.isRequired,
-}
-
-const AnimatedLinks = ({ links, linkThickness, linkColor }: AnimatedLinksProps) => {
- const { springConfig } = useMotionConfig()
-
- return (
- ({
- key: link.id,
- data: link,
- style: {
- sourceX: spring(link.source.x, springConfig),
- sourceY: spring(link.source.y, springConfig),
- targetX: spring(link.target.x, springConfig),
- targetY: spring(link.target.y, springConfig),
- },
- }))}
- >
- {interpolatedStyles => (
- <>
- {interpolatedStyles.map(({ key, style, data: link }) => {
- return (
-
- )
- })}
- >
- )}
-
- )
-}
-
-export default memo(AnimatedLinks)
diff --git a/packages/network/src/AnimatedNodes.tsx b/packages/network/src/AnimatedNodes.tsx
deleted file mode 100644
index 91db63a19b..0000000000
--- a/packages/network/src/AnimatedNodes.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { memo } from 'react'
-import { TransitionMotion, spring } from 'react-motion'
-import { useMotionConfig } from '@nivo/core'
-import Node from './Node'
-
-const willEnter = ({ style }) => ({
- x: style.x.val,
- y: style.y.val,
- radius: style.radius.val,
- scale: 0,
-})
-
-const willLeave =
- springConfig =>
- ({ style }) => ({
- x: style.x,
- y: style.y,
- radius: style.radius,
- scale: spring(0, springConfig),
- })
-
-interface AnimatedNodesProps {
- // nodes: PropTypes.array.isRequired,
- nodes: any[]
- // color: PropTypes.func.isRequired,
- color: Function
- borderWidth: number
- // borderColor: PropTypes.func.isRequired,
- borderColor: Function
- // handleNodeHover: PropTypes.func.isRequired,
- handleNodeHover: Function
- // handleNodeLeave: PropTypes.func.isRequired,
- handleNodeLeave: Function
-}
-
-const AnimatedNodes = ({ nodes, color, borderColor, ...props }: AnimatedNodesProps) => {
- const { springConfig } = useMotionConfig()
-
- return (
- ({
- key: node.id,
- data: node,
- style: {
- x: spring(node.x, springConfig),
- y: spring(node.y, springConfig),
- radius: spring(node.radius, springConfig),
- scale: spring(1, springConfig),
- },
- }))}
- >
- {interpolatedStyles => (
- <>
- {interpolatedStyles.map(({ key, style, data: node }) => {
- return (
-
- )
- })}
- >
- )}
-
- )
-}
-
-export default memo(AnimatedNodes)
diff --git a/packages/network/src/Link.tsx b/packages/network/src/Link.tsx
deleted file mode 100644
index 197181e1f0..0000000000
--- a/packages/network/src/Link.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { memo } from 'react'
-
-interface LinkProps {
- link: any
- sourceX: number
- sourceY: number
- targetX: number
- targetY: number
- thickness: number
- color: string
-}
-
-const Link = ({ sourceX, sourceY, targetX, targetY, thickness, color }: LinkProps) => {
- return (
-
- )
-}
-
-export default memo(Link)
diff --git a/packages/network/src/Network.tsx b/packages/network/src/Network.tsx
index ef836ac500..259fcb8e4c 100644
--- a/packages/network/src/Network.tsx
+++ b/packages/network/src/Network.tsx
@@ -1,44 +1,47 @@
-import { createElement, Fragment, useCallback } from 'react'
-import { withContainer, useDimensions, SvgWrapper, useTheme, useMotionConfig } from '@nivo/core'
+import { Fragment, ReactNode, useCallback } from 'react'
+import { Container, useDimensions, SvgWrapper, useTheme } from '@nivo/core'
import { useInheritedColor } from '@nivo/colors'
import { useTooltip } from '@nivo/tooltip'
-import { NetworkDefaultProps } from './props'
+import { svgDefaultProps } from './props'
import { useNetwork, useNodeColor, useLinkThickness } from './hooks'
-import AnimatedNodes from './AnimatedNodes'
-import StaticNodes from './StaticNodes'
-import AnimatedLinks from './AnimatedLinks'
-import StaticLinks from './StaticLinks'
+import { NetworkNodes } from './NetworkNodes'
+import { NetworkLinks } from './NetworkLinks'
import NetworkNodeTooltip from './NetworkNodeTooltip'
+import { NetworkLayerId, NetworkSvgProps } from './types'
-const Network = props => {
+type InnerNetworkProps = Omit<
+ NetworkSvgProps,
+ 'animate' | 'motionConfig' | 'renderWrapper' | 'theme'
+>
+
+const InnerNetwork = (props: InnerNetworkProps) => {
const {
width,
height,
margin: partialMargin,
- nodes: rawNodes,
- links: rawLinks,
+ data: { nodes: rawNodes, links: rawLinks },
- linkDistance,
- repulsivity,
- distanceMin,
- distanceMax,
- iterations,
+ linkDistance = svgDefaultProps.linkDistance,
+ repulsivity = svgDefaultProps.repulsivity,
+ distanceMin = svgDefaultProps.distanceMin,
+ distanceMax = svgDefaultProps.distanceMax,
+ iterations = svgDefaultProps.iterations,
- layers,
+ layers = svgDefaultProps.layers,
- nodeColor,
- nodeBorderWidth,
- nodeBorderColor,
+ nodeColor = svgDefaultProps.nodeColor,
+ nodeBorderWidth = svgDefaultProps.nodeBorderWidth,
+ nodeBorderColor = svgDefaultProps.nodeBorderColor,
- linkThickness,
- linkColor,
+ linkThickness = svgDefaultProps.linkThickness,
+ linkColor = svgDefaultProps.linkColor,
- tooltip,
- isInteractive,
+ tooltip = svgDefaultProps.tooltip,
+ isInteractive = svgDefaultProps.isInteractive,
onClick,
- role,
+ role = svgDefaultProps.role,
} = props
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
@@ -47,7 +50,6 @@ const Network = props => {
partialMargin
)
- const { animate } = useMotionConfig()
const theme = useTheme()
const getColor = useNodeColor(nodeColor)
const getBorderColor = useInheritedColor(nodeBorderColor, theme)
@@ -78,23 +80,36 @@ const Network = props => {
hideTooltip()
}, [hideTooltip])
- const layerById = {
- links: createElement(animate === true ? AnimatedLinks : StaticLinks, {
- key: 'links',
- links,
- linkThickness: getLinkThickness,
- linkColor: getLinkColor,
- }),
- nodes: createElement(animate === true ? AnimatedNodes : StaticNodes, {
- key: 'nodes',
- nodes,
- color: getColor,
- borderWidth: nodeBorderWidth,
- borderColor: getBorderColor,
- handleNodeClick: isInteractive ? onClick : undefined,
- handleNodeHover: isInteractive ? handleNodeHover : undefined,
- handleNodeLeave: isInteractive ? handleNodeLeave : undefined,
- }),
+ const layerById: Record = {
+ links: null,
+ nodes: null,
+ }
+
+ if (layers.includes('links')) {
+ layerById.links = (
+
+ )
+ }
+
+ if (layers.includes('crap')) {
+ layerById.nodes = (
+
+ )
}
return (
@@ -120,6 +135,23 @@ const Network = props => {
)
}
-Network.defaultProps = NetworkDefaultProps
-
-export default withContainer(Network)
+export const Network = ({
+ isInteractive = svgDefaultProps.isInteractive,
+ animate = svgDefaultProps.animate,
+ motionConfig = svgDefaultProps.motionConfig,
+ theme,
+ renderWrapper,
+ ...otherProps
+}: NetworkSvgProps) => (
+
+
+
+)
diff --git a/packages/network/src/NetworkCanvas.tsx b/packages/network/src/NetworkCanvas.tsx
index d4be12b130..a88705eb10 100644
--- a/packages/network/src/NetworkCanvas.tsx
+++ b/packages/network/src/NetworkCanvas.tsx
@@ -1,39 +1,41 @@
-import { useCallback, useRef, useEffect } from 'react'
+import { useCallback, useRef, useEffect, ForwardedRef } from 'react'
import * as React from 'react'
-import { getDistance, getRelativeCursor, withContainer, useDimensions, useTheme } from '@nivo/core'
+import { getDistance, getRelativeCursor, Container, useDimensions, useTheme } from '@nivo/core'
import { useInheritedColor } from '@nivo/colors'
import { useTooltip } from '@nivo/tooltip'
-import { NetworkCanvasDefaultProps } from './props'
+import { canvasDefaultProps } from './props'
import { useNetwork, useNodeColor, useLinkThickness } from './hooks'
import NetworkNodeTooltip from './NetworkNodeTooltip'
+import { NetworkCanvasProps } from './types'
-const NetworkCanvas = props => {
+type InnerNetworkCanvasProps = Omit
+
+const InnerNetworkCanvas = (props: InnerNetworkCanvasProps) => {
const {
width,
height,
margin: partialMargin,
- pixelRatio,
+ pixelRatio = canvasDefaultProps.pixelRatio,
- nodes: rawNodes,
- links: rawLinks,
+ data: { nodes: rawNodes, links: rawLinks },
- linkDistance,
- repulsivity,
- distanceMin,
- distanceMax,
- iterations,
+ linkDistance = canvasDefaultProps.linkDistance,
+ repulsivity = canvasDefaultProps.repulsivity,
+ distanceMin = canvasDefaultProps.distanceMin,
+ distanceMax = canvasDefaultProps.distanceMax,
+ iterations = canvasDefaultProps.iterations,
- layers,
+ layers = canvasDefaultProps.layers,
- nodeColor,
- nodeBorderWidth,
- nodeBorderColor,
+ nodeColor = canvasDefaultProps.nodeColor,
+ nodeBorderWidth = canvasDefaultProps.nodeBorderWidth,
+ nodeBorderColor = canvasDefaultProps.nodeBorderColor,
- linkThickness,
- linkColor,
+ linkThickness = canvasDefaultProps.linkThickness,
+ linkColor = canvasDefaultProps.linkColor,
- isInteractive,
- tooltip,
+ isInteractive = canvasDefaultProps.isInteractive,
+ tooltip = canvasDefaultProps.tooltip,
onClick,
} = props
@@ -186,6 +188,15 @@ const NetworkCanvas = props => {
)
}
-NetworkCanvas.defaultProps = NetworkCanvasDefaultProps
-
-export default withContainer(NetworkCanvas)
+export const NetworkCanvas = ({
+ theme,
+ isInteractive = canvasDefaultProps.isInteractive,
+ animate = canvasDefaultProps.animate,
+ motionConfig = canvasDefaultProps.motionConfig,
+ renderWrapper,
+ ...otherProps
+}: NetworkCanvasProps) => (
+
+
+
+)
diff --git a/packages/network/src/NetworkLink.tsx b/packages/network/src/NetworkLink.tsx
new file mode 100644
index 0000000000..219226ca8e
--- /dev/null
+++ b/packages/network/src/NetworkLink.tsx
@@ -0,0 +1,28 @@
+import { AnimatedProps, animated } from '@react-spring/web'
+import { ComputedLink } from './types'
+
+interface NetworkLinkProps {
+ link: ComputedLink
+ thickness: number
+ animated: AnimatedProps<{
+ x1: number
+ y1: number
+ x2: number
+ y2: number
+ color: string
+ }>
+}
+
+export const NetworkLink = ({ thickness, animated: animatedProps }: NetworkLinkProps) => {
+ return (
+
+ )
+}
diff --git a/packages/network/src/NetworkLinks.tsx b/packages/network/src/NetworkLinks.tsx
new file mode 100644
index 0000000000..1553177449
--- /dev/null
+++ b/packages/network/src/NetworkLinks.tsx
@@ -0,0 +1,79 @@
+import { createElement } from 'react'
+import { useTransition } from '@react-spring/web'
+import { useMotionConfig } from '@nivo/core'
+import { NetworkLink } from './NetworkLink'
+import { ComputedLink } from './types'
+
+interface NetworkLinksProps {
+ links: ComputedLink[]
+ linkThickness: (link: ComputedLink) => number
+ linkColor: (link: ComputedLink) => string
+}
+
+export const NetworkLinks = ({ links, linkThickness, linkColor }: NetworkLinksProps) => {
+ const { animate, config: springConfig } = useMotionConfig()
+
+ console.log('immediate', !animate)
+
+ const transition = useTransition<
+ ComputedLink,
+ {
+ x1: number
+ y1: number
+ x2: number
+ y2: number
+ color: string
+ opacity: number
+ }
+ >(links, {
+ keys: link => link.id,
+ from: link => ({
+ x1: link.source.x,
+ y1: link.source.y,
+ x2: link.target.x,
+ y2: link.target.y,
+ color: linkColor(link),
+ opacity: 0,
+ }),
+ enter: link => ({
+ x1: link.source.x,
+ y1: link.source.y,
+ x2: link.target.x,
+ y2: link.target.y,
+ color: linkColor(link),
+ opacity: 1,
+ }),
+ update: link => ({
+ x1: link.source.x,
+ y1: link.source.y,
+ x2: link.target.x,
+ y2: link.target.y,
+ color: linkColor(link),
+ opacity: 1,
+ }),
+ leave: link => ({
+ x1: link.source.x,
+ y1: link.source.y,
+ x2: link.source.x,
+ y2: link.source.y,
+ color: linkColor(link),
+ opacity: 0,
+ }),
+ expires: true,
+ config: springConfig,
+ immediate: !animate,
+ })
+
+ return (
+ <>
+ {transition((transitionProps, link) => {
+ return createElement(NetworkLink, {
+ key: link.id,
+ link,
+ thickness: linkThickness(link),
+ animated: transitionProps,
+ })
+ })}
+ >
+ )
+}
diff --git a/packages/network/src/NetworkNode.tsx b/packages/network/src/NetworkNode.tsx
new file mode 100644
index 0000000000..6807113959
--- /dev/null
+++ b/packages/network/src/NetworkNode.tsx
@@ -0,0 +1,41 @@
+import { MouseEvent } from 'react'
+import { AnimatedProps, animated, to } from '@react-spring/web'
+import { ComputedNode, NodeAnimatedProps } from './types'
+
+interface NetworkNodeProps {
+ node: ComputedNode
+ animated: AnimatedProps
+ scale?: number
+ onClick?: (node: ComputedNode, event: MouseEvent) => void
+ onMouseEnter?: (node: ComputedNode, event: MouseEvent) => void
+ onMouseMove?: (node: ComputedNode, event: MouseEvent) => void
+ onMouseLeave?: (node: ComputedNode, event: MouseEvent) => void
+}
+
+export const NetworkNode = ({
+ node,
+ animated: animatedProps,
+ onClick,
+ onMouseEnter,
+ onMouseMove,
+ onMouseLeave,
+}: NetworkNodeProps) => {
+ return (
+ {
+ return `translate(${x},${y}) scale(${scale})`
+ }
+ )}
+ r={animatedProps.radius}
+ fill={animatedProps.color}
+ strokeWidth={animatedProps.borderWidth}
+ stroke={animatedProps.borderColor}
+ onClick={onClick ? event => onClick(node, event) : undefined}
+ onMouseEnter={onMouseEnter ? event => onMouseEnter(node, event) : undefined}
+ onMouseMove={onMouseMove ? event => onMouseMove(node, event) : undefined}
+ onMouseLeave={onMouseLeave ? event => onMouseLeave(node, event) : undefined}
+ />
+ )
+}
diff --git a/packages/network/src/NetworkNodeTooltip.tsx b/packages/network/src/NetworkNodeTooltip.tsx
index 8553b3d618..e96a500aec 100644
--- a/packages/network/src/NetworkNodeTooltip.tsx
+++ b/packages/network/src/NetworkNodeTooltip.tsx
@@ -1,22 +1,13 @@
import { memo } from 'react'
import { BasicTooltip } from '@nivo/tooltip'
+import { NetworkNodeTooltipProps } from './types'
-interface NetworkNodeTooltipProps {
- // node: PropTypes.shape({
- // id: PropTypes.string.isRequired,
- // color: PropTypes.string.isRequired,
- // }).isRequired,
- // format: PropTypes.func,
- // tooltip: PropTypes.func,
-}
-
-const NetworkNodeTooltip = ({ node, format, tooltip }: NetworkNodeTooltipProps) => (
+const NetworkNodeTooltip = ({ node }: NetworkNodeTooltipProps) => (
)
diff --git a/packages/network/src/NetworkNodes.tsx b/packages/network/src/NetworkNodes.tsx
new file mode 100644
index 0000000000..8f225082e1
--- /dev/null
+++ b/packages/network/src/NetworkNodes.tsx
@@ -0,0 +1,87 @@
+import { createElement, MouseEvent } from 'react'
+import { useTransition } from '@react-spring/web'
+import { useMotionConfig } from '@nivo/core'
+import { NetworkNode } from './NetworkNode'
+import { ComputedNode, NodeAnimatedProps } from './types'
+
+interface NetworkNodesProps {
+ nodes: ComputedNode[]
+ color: (node: ComputedNode) => string
+ borderWidth: number
+ borderColor: (node: ComputedNode) => string
+ onClick?: (node: ComputedNode, event: MouseEvent) => void
+ onMouseEnter?: (node: ComputedNode, event: MouseEvent) => void
+ onMouseMove?: (node: ComputedNode, event: MouseEvent) => void
+ onMouseLeave?: (node: ComputedNode, event: MouseEvent) => void
+}
+
+export const NetworkNodes = ({
+ nodes,
+ color,
+ borderColor,
+ borderWidth,
+ onClick,
+ onMouseEnter,
+ onMouseMove,
+ onMouseLeave,
+}: NetworkNodesProps) => {
+ const { animate, config: springConfig } = useMotionConfig()
+
+ const transition = useTransition(nodes, {
+ keys: node => node.id,
+ initial: node => ({
+ x: node.x,
+ y: node.y,
+ radius: node.radius,
+ color: color(node),
+ borderWidth,
+ borderColor: borderColor(node),
+ scale: 1,
+ }),
+ enter: node => ({
+ x: node.x,
+ y: node.y,
+ radius: node.radius,
+ color: color(node),
+ borderWidth,
+ borderColor: borderColor(node),
+ scale: 1,
+ }),
+ update: node => ({
+ x: node.x,
+ y: node.y,
+ radius: node.radius,
+ color: color(node),
+ borderWidth,
+ borderColor: borderColor(node),
+ scale: 1,
+ }),
+ leave: node => ({
+ x: node.x,
+ y: node.y,
+ radius: node.radius,
+ color: color(node),
+ borderWidth,
+ borderColor: borderColor(node),
+ scale: 0,
+ }),
+ config: springConfig,
+ immediate: !animate,
+ })
+
+ return (
+ <>
+ {transition((transitionProps, node) =>
+ createElement(NetworkNode, {
+ key: node.id,
+ node,
+ animated: transitionProps,
+ onClick,
+ onMouseEnter,
+ onMouseMove,
+ onMouseLeave,
+ })
+ )}
+ >
+ )
+}
diff --git a/packages/network/src/Node.tsx b/packages/network/src/Node.tsx
deleted file mode 100644
index 465d156ef4..0000000000
--- a/packages/network/src/Node.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { memo } from 'react'
-
-interface NodeProps {
- node: any
- x: number
- y: number
- radius: number
- color: string
- borderWidth: number
- borderColor: string
- scale?: number
- handleNodeClick: Function
- handleNodeHover: Function
- handleNodeLeave: Function
-}
-
-const Node = ({
- node,
- x,
- y,
- radius,
- color,
- borderWidth,
- borderColor,
- scale = 1,
- handleNodeClick,
- handleNodeHover,
- handleNodeLeave,
-}: NodeProps) => {
- return (
- handleNodeClick(node, event)}
- onMouseEnter={event => handleNodeHover(node, event)}
- onMouseMove={event => handleNodeHover(node, event)}
- onMouseLeave={handleNodeLeave}
- />
- )
-}
-
-export default memo(Node)
diff --git a/packages/network/src/ResponsiveNetwork.tsx b/packages/network/src/ResponsiveNetwork.tsx
index 40486841ea..11dbd13bbb 100644
--- a/packages/network/src/ResponsiveNetwork.tsx
+++ b/packages/network/src/ResponsiveNetwork.tsx
@@ -1,10 +1,9 @@
import { ResponsiveWrapper } from '@nivo/core'
-import Network from './Network'
+import { NetworkSvgProps } from './types'
+import { Network } from './Network'
-const ResponsiveNetwork = props => (
+export const ResponsiveNetwork = (props: Omit) => (
{({ width, height }) => }
)
-
-export default ResponsiveNetwork
diff --git a/packages/network/src/ResponsiveNetworkCanvas.tsx b/packages/network/src/ResponsiveNetworkCanvas.tsx
index 6177d392de..d76b02eea3 100644
--- a/packages/network/src/ResponsiveNetworkCanvas.tsx
+++ b/packages/network/src/ResponsiveNetworkCanvas.tsx
@@ -1,10 +1,9 @@
import { ResponsiveWrapper } from '@nivo/core'
-import NetworkCanvas from './NetworkCanvas'
+import { NetworkCanvasProps } from './types'
+import { NetworkCanvas } from './NetworkCanvas'
-const ResponsiveNetworkCanvas = props => (
+export const ResponsiveNetworkCanvas = (props: Omit) => (
{({ width, height }) => }
)
-
-export default ResponsiveNetworkCanvas
diff --git a/packages/network/src/StaticLinks.tsx b/packages/network/src/StaticLinks.tsx
deleted file mode 100644
index f205fe85c8..0000000000
--- a/packages/network/src/StaticLinks.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { memo } from 'react'
-import Link from './Link'
-
-interface StaticLinksProps {
- // links: PropTypes.array.isRequired,
- // linkThickness: PropTypes.func.isRequired,
- // linkColor: PropTypes.func.isRequired,
-}
-
-const StaticLinks = ({ links, linkThickness, linkColor }: StaticLinksProps) => {
- return links.map(link => {
- return (
-
- )
- })
-}
-
-export default memo(StaticLinks)
diff --git a/packages/network/src/StaticNodes.tsx b/packages/network/src/StaticNodes.tsx
deleted file mode 100644
index 57ffcd718b..0000000000
--- a/packages/network/src/StaticNodes.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { memo } from 'react'
-import Node from './Node'
-
-interface StaticNodesProps {
- nodes: any[]
- color: Function
- borderWidth: number
- borderColor: Function
- handleNodeHover: Function
- handleNodeLeave: Function
-}
-
-const StaticNodes = ({ nodes, color, borderColor, ...props }: StaticNodesProps) => {
- return nodes.map(node => {
- return (
-
- )
- })
-}
-
-export default memo(StaticNodes)
diff --git a/packages/network/src/hooks.ts b/packages/network/src/hooks.ts
index ae8c9964c4..e057776d70 100644
--- a/packages/network/src/hooks.ts
+++ b/packages/network/src/hooks.ts
@@ -3,8 +3,21 @@ import get from 'lodash/get'
import isString from 'lodash/isString'
import isNumber from 'lodash/isNumber'
import { forceSimulation, forceManyBody, forceCenter, forceLink } from 'd3-force'
+import { InputLink, InputNode, NetworkCommonProps } from './types'
-const computeForces = ({ linkDistance, repulsivity, distanceMin, distanceMax, center }) => {
+const computeForces = ({
+ linkDistance,
+ repulsivity,
+ distanceMin,
+ distanceMax,
+ center,
+}: {
+ linkDistance: NetworkCommonProps['linkDistance']
+ repulsivity: NetworkCommonProps['repulsivity']
+ distanceMin: NetworkCommonProps['distanceMin']
+ distanceMax: NetworkCommonProps['distanceMax']
+ center: [number, number]
+}) => {
let computedLinkDistance
if (typeof linkDistance === 'function') {
computedLinkDistance = linkDistance
@@ -37,6 +50,15 @@ export const useNetwork = ({
distanceMax,
center,
iterations,
+}: {
+ nodes: InputNode[]
+ links: InputLink[]
+ linkDistance: NetworkCommonProps['linkDistance']
+ repulsivity: NetworkCommonProps['repulsivity']
+ distanceMin: NetworkCommonProps['distanceMin']
+ distanceMax: NetworkCommonProps['distanceMax']
+ center: [number, number]
+ iterations: NetworkCommonProps['iterations']
}) => {
const [currentNodes, setCurrentNodes] = useState([])
const [currentLinks, setCurrentLinks] = useState([])
@@ -50,8 +72,8 @@ export const useNetwork = ({
center,
})
- const nodesCopy = nodes.map(node => ({ ...node }))
- const linksCopy = links.map(link => ({
+ const nodesCopy: InputNode[] = nodes.map(node => ({ ...node }))
+ const linksCopy: InputLink[] = links.map(link => ({
id: `${link.source}.${link.target}`,
...link,
}))
diff --git a/packages/network/src/index.ts b/packages/network/src/index.ts
index c40e2bce6b..0d5b93d4a1 100644
--- a/packages/network/src/index.ts
+++ b/packages/network/src/index.ts
@@ -1,6 +1,7 @@
-export { default as Network } from './Network'
-export { default as ResponsiveNetwork } from './ResponsiveNetwork'
-export { default as NetworkCanvas } from './NetworkCanvas'
-export { default as ResponsiveNetworkCanvas } from './ResponsiveNetworkCanvas'
+export * from './Network'
+export * from './ResponsiveNetwork'
+export * from './NetworkCanvas'
+export * from './ResponsiveNetworkCanvas'
export * from './props'
export * from './hooks'
+export * from './types'
diff --git a/packages/network/src/props.ts b/packages/network/src/props.ts
index ce246a7cdc..3adf51a817 100644
--- a/packages/network/src/props.ts
+++ b/packages/network/src/props.ts
@@ -14,17 +14,22 @@ const commonDefaultProps = {
linkColor: { from: 'source.color' },
isInteractive: true,
+
+ animate: true,
+ motionConfig: 'gentle' as const,
+
+ role: 'img',
}
export const NetworkDefaultProps = {
...commonDefaultProps,
- animate: true,
- motionStiffness: 90,
- motionDamping: 15,
- role: 'img',
}
+export const svgDefaultProps = NetworkDefaultProps
+
export const NetworkCanvasDefaultProps = {
...commonDefaultProps,
pixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,
}
+
+export const canvasDefaultProps = NetworkCanvasDefaultProps
diff --git a/packages/network/src/types.ts b/packages/network/src/types.ts
index 282dab1c15..98a48e66c3 100644
--- a/packages/network/src/types.ts
+++ b/packages/network/src/types.ts
@@ -4,29 +4,50 @@ import { InheritedColorConfig } from '@nivo/colors'
export interface InputNode {
id: string
- [key: string]: any
+ [key: string]: unknown
}
export interface ComputedNode {
id: string
- x: string
- y: string
- radius: string
+ x: number
+ y: number
+ radius: number
color: string
- [key: string]: any
+ data: InputNode
+ [key: string]: unknown
+}
+
+export interface NodeAnimatedProps {
+ x: number
+ y: number
+ radius: number
+ color: string
+ borderWidth: number
+ borderColor: string
+ opacity: number
+ scale: number
}
export interface InputLink {
source: string
target: string
- [key: string]: any
+ [key: string]: unknown
}
export interface ComputedLink {
id: string
source: ComputedNode
target: ComputedNode
- [key: string]: any
+ [key: string]: unknown
+}
+
+export interface LinkAnimatedProps {
+ x1: number
+ y1: number
+ x2: number
+ y2: number
+ color: string
+ opacity: number
}
export interface NetworkDataProps {
@@ -43,40 +64,10 @@ export interface NetworkCustomLayerProps {
}
export type NetworkCustomLayer = FunctionComponent
-/*
-nodes: PropTypes.arrayOf(
- PropTypes.shape({
- id: PropTypes.string.isRequired,
- })
-).isRequired,
-links: PropTypes.arrayOf(
- PropTypes.shape({
- source: PropTypes.string.isRequired,
- target: PropTypes.string.isRequired,
- })
-).isRequired,
-
-layers: PropTypes.arrayOf(
- PropTypes.oneOfType([PropTypes.oneOf(['links', 'nodes']), PropTypes.func])
-).isRequired,
-
-linkDistance: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.number])
- .isRequired,
-repulsivity: PropTypes.number.isRequired,
-distanceMin: PropTypes.number.isRequired,
-distanceMax: PropTypes.number.isRequired,
-iterations: PropTypes.number.isRequired,
-
-nodeColor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
-nodeBorderWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired,
-nodeBorderColor: inheritedColorPropType.isRequired,
-
-linkThickness: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired,
-linkColor: inheritedColorPropType.isRequired,
-
-isInteractive: PropTypes.bool.isRequired,
-onClick: PropTypes.func,
-*/
+export interface NetworkNodeTooltipProps {
+ node: ComputedNode
+}
+export type NetworkNodeTooltipComponent = FunctionComponent
export interface NetworkCommonProps {
margin: Box
@@ -99,6 +90,7 @@ export interface NetworkCommonProps {
linkColor: InheritedColorConfig
isInteractive: boolean
+ tooltip: NetworkNodeTooltipComponent
onClick: (node: ComputedNode, event: MouseEvent) => void
renderWrapper: boolean
@@ -116,6 +108,8 @@ export type NetworkSvgProps = Partial &
export type NetworkCanvasProps = Partial &
NetworkDataProps &
- Dimensions & {
+ Dimensions &
+ // only used by tooltips
+ ModernMotionProps & {
pixelRatio?: number
}
diff --git a/packages/network/tsconfig.json b/packages/network/tsconfig.json
new file mode 100644
index 0000000000..855b4b2b74
--- /dev/null
+++ b/packages/network/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.types.json",
+ "compilerOptions": {
+ "outDir": "./dist/types",
+ "rootDir": "./src"
+ },
+ "include": ["src/**/*"]
+}
diff --git a/website/src/data/components/network/props.ts b/website/src/data/components/network/props.ts
index bd802b3292..820dd96e64 100644
--- a/website/src/data/components/network/props.ts
+++ b/website/src/data/components/network/props.ts
@@ -1,20 +1,45 @@
// @ts-ignore
import { NetworkDefaultProps } from '@nivo/network'
import { motionProperties, groupProperties } from '../../../lib/componentProperties'
-import { chartDimensions } from '../../../lib/chart-properties'
+import { chartDimensions, pixelRatio } from '../../../lib/chart-properties'
import { ChartProperty, Flavor } from '../../../types'
const allFlavors: Flavor[] = ['svg', 'canvas']
const props: ChartProperty[] = [
+ {
+ key: 'data',
+ group: 'Base',
+ type: '{ nodes: object[], links: object[] }',
+ required: true,
+ help: 'Chart data defining nodes and links.',
+ flavors: allFlavors,
+ description: `
+ Chart data, which must conform to this structure:
+
+ \`\`\`
+ {
+ nodes: {
+ id: string
+ }[],
+ links: {
+ source: string, // ref to node id
+ target: string, // ref to node id
+ value: number
+ }[]
+ }
+ \`\`\`
+ `,
+ },
...chartDimensions(allFlavors),
+ pixelRatio(),
{
key: 'linkDistance',
group: 'Simulation',
type: 'number | string | (link: Link) => number',
required: false,
help: `Control links' distance.`,
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
description: `
If you set a **number**, this value will be used for all links.
@@ -36,7 +61,7 @@ const props: ChartProperty[] = [
This value will also affect the strength
of \`distanceMin\` and \`distanceMax\`.
`,
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
control: {
type: 'range',
min: 1,
@@ -50,7 +75,7 @@ const props: ChartProperty[] = [
type: 'number',
required: false,
help: 'Sets the minimum distance between nodes for the many-body force.',
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
defaultValue: NetworkDefaultProps.distanceMin,
},
{
@@ -59,7 +84,7 @@ const props: ChartProperty[] = [
type: 'number',
required: false,
help: 'Sets the maximum disteance between nodes for the many-body force.',
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
defaultValue: NetworkDefaultProps.distanceMax,
},
{
@@ -73,7 +98,7 @@ const props: ChartProperty[] = [
type: 'number',
required: false,
defaultValue: NetworkDefaultProps.iterations,
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
control: {
type: 'range',
min: 60,
@@ -86,7 +111,7 @@ const props: ChartProperty[] = [
type: 'string | (node: Node) => string',
required: false,
help: `Control nodes' color.`,
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
},
{
key: 'nodeBorderWidth',
@@ -95,7 +120,7 @@ const props: ChartProperty[] = [
required: false,
help: `Control nodes' border width.`,
defaultValue: NetworkDefaultProps.nodeBorderWidth,
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
control: { type: 'lineWidth' },
},
{
@@ -105,7 +130,7 @@ const props: ChartProperty[] = [
required: false,
help: `Control nodes' border color.`,
defaultValue: NetworkDefaultProps.nodeBorderColor,
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
control: { type: 'inheritedColor' },
},
{
@@ -115,7 +140,7 @@ const props: ChartProperty[] = [
type: 'number | (link: Link) => number',
required: false,
help: `Control links' thickness.`,
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
defaultValue: NetworkDefaultProps.linkThickness,
control: { type: 'lineWidth' },
},
@@ -126,7 +151,7 @@ const props: ChartProperty[] = [
required: false,
help: `Control links' color.`,
defaultValue: NetworkDefaultProps.linkColor,
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
control: {
type: 'inheritedColor',
inheritableProperties: ['source.color', 'target.color'],
@@ -138,7 +163,7 @@ const props: ChartProperty[] = [
type: 'Function',
required: false,
help: 'Custom tooltip component.',
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
description: `
A function allowing complete tooltip customisation,
it must return a valid HTML
@@ -151,7 +176,7 @@ const props: ChartProperty[] = [
help: 'onClick handler.',
type: '(node, event) => void',
required: false,
- flavors: ['svg', 'canvas'],
+ flavors: allFlavors,
},
{
key: 'layers',
@@ -162,7 +187,7 @@ const props: ChartProperty[] = [
defaultValue: NetworkDefaultProps.layers,
flavors: ['svg', 'canvas'],
},
- ...motionProperties(['svg'], NetworkDefaultProps),
+ ...motionProperties(['svg'], NetworkDefaultProps, 'react-spring'),
]
export const groups = groupProperties(props)
diff --git a/website/src/pages/network/canvas.js b/website/src/pages/network/canvas.tsx
similarity index 97%
rename from website/src/pages/network/canvas.js
rename to website/src/pages/network/canvas.tsx
index 8f684e9ccb..7ddfb2b588 100644
--- a/website/src/pages/network/canvas.js
+++ b/website/src/pages/network/canvas.tsx
@@ -72,8 +72,7 @@ const NetworkCanvas = () => {
{(properties, data, theme, logAction) => {
return (
{
diff --git a/website/src/pages/network/index.js b/website/src/pages/network/index.tsx
similarity index 94%
rename from website/src/pages/network/index.js
rename to website/src/pages/network/index.tsx
index 9bb59d6794..c7b634cc5b 100644
--- a/website/src/pages/network/index.js
+++ b/website/src/pages/network/index.tsx
@@ -31,8 +31,7 @@ const initialProperties = Object.freeze({
isInteractive: true,
animate: true,
- motionStiffness: 160,
- motionDamping: 12,
+ motionConfig: 'wobbly',
})
const generateData = () => generateNetworkData()
@@ -69,8 +68,7 @@ const Network = () => {
{(properties, data, theme, logAction) => {
return (
{