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 ( {