Skip to content

Commit

Permalink
feat(network): add support for active nodes and annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
plouc committed Dec 31, 2021
1 parent e91b04f commit 5575928
Show file tree
Hide file tree
Showing 21 changed files with 586 additions and 277 deletions.
1 change: 1 addition & 0 deletions packages/network/package.json
Expand Up @@ -31,6 +31,7 @@
"!dist/tsconfig.tsbuildinfo"
],
"dependencies": {
"@nivo/annotations": "0.76.0",
"@nivo/colors": "0.76.0",
"@nivo/tooltip": "0.76.0",
"@react-spring/web": "9.3.1",
Expand Down
71 changes: 52 additions & 19 deletions packages/network/src/Network.tsx
Expand Up @@ -3,16 +3,18 @@ import { Container, useDimensions, SvgWrapper } from '@nivo/core'
import { useTooltip } from '@nivo/tooltip'
import { svgDefaultProps } from './defaults'
import { useNetwork } from './hooks'
import { NetworkNodes } from './NetworkNodes'
import { NetworkLinks } from './NetworkLinks'
import { NetworkInputNode, NetworkLayerId, NetworkSvgProps } from './types'
import { NetworkNodes } from './NetworkNodes'
import { NetworkNodeAnnotations } from './NetworkNodeAnnotations'

import { InputNode, LayerId, NodeTooltip, NetworkSvgProps, ComputedNode } from './types'

type InnerNetworkProps<N extends NetworkInputNode> = Omit<
NetworkSvgProps<N>,
type InnerNetworkProps<Node extends InputNode> = Omit<
NetworkSvgProps<Node>,
'animate' | 'motionConfig' | 'renderWrapper' | 'theme'
>

const InnerNetwork = <N extends NetworkInputNode>({
const InnerNetwork = <Node extends InputNode>({
width,
height,
margin: partialMargin,
Expand All @@ -27,31 +29,42 @@ const InnerNetwork = <N extends NetworkInputNode>({

layers = svgDefaultProps.layers,

nodeComponent = svgDefaultProps.nodeComponent,
nodeComponent = svgDefaultProps.nodeComponent as NonNullable<
NetworkSvgProps<Node>['nodeComponent']
>,
nodeSize = svgDefaultProps.nodeSize,
activeNodeSize = svgDefaultProps.activeNodeSize,
inactiveNodeSize = svgDefaultProps.inactiveNodeSize,
nodeColor = svgDefaultProps.nodeColor,
nodeBlendMode = svgDefaultProps.nodeBlendMode,
nodeBorderWidth = svgDefaultProps.nodeBorderWidth,
nodeBorderColor = svgDefaultProps.nodeBorderColor,

linkComponent = svgDefaultProps.linkComponent,
linkComponent = svgDefaultProps.linkComponent as NonNullable<
NetworkSvgProps<Node>['linkComponent']
>,
linkThickness = svgDefaultProps.linkThickness,
linkColor = svgDefaultProps.linkColor,
linkBlendMode = svgDefaultProps.linkBlendMode,

annotations = svgDefaultProps.annotations as NonNullable<NetworkSvgProps<Node>['annotations']>,

isInteractive = svgDefaultProps.isInteractive,
nodeTooltip = svgDefaultProps.nodeTooltip,
nodeTooltip = svgDefaultProps.nodeTooltip as NodeTooltip<Node>,
onClick,

role = svgDefaultProps.role,
ariaLabel,
ariaLabelledBy,
ariaDescribedBy,
}: InnerNetworkProps<N>) => {
}: InnerNetworkProps<Node>) => {
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
width,
height,
partialMargin
)

const [nodes, links] = useNetwork<N>({
const { nodes, links, setActiveNodeIds } = useNetwork<Node>({
center: [innerWidth / 2, innerHeight / 2],
nodes: rawNodes,
links: rawLinks,
Expand All @@ -60,6 +73,9 @@ const InnerNetwork = <N extends NetworkInputNode>({
distanceMin,
distanceMax,
iterations,
nodeSize,
activeNodeSize,
inactiveNodeSize,
nodeColor,
nodeBorderWidth,
nodeBorderColor,
Expand All @@ -70,33 +86,44 @@ const InnerNetwork = <N extends NetworkInputNode>({
const { showTooltipFromEvent, hideTooltip } = useTooltip()

const handleNodeHover = useCallback(
(node, event) => {
(node: ComputedNode<Node>, event) => {
showTooltipFromEvent(createElement(nodeTooltip, { node }), event)
setActiveNodeIds([node.id])
},
[showTooltipFromEvent, nodeTooltip]
[showTooltipFromEvent, nodeTooltip, setActiveNodeIds]
)

const handleNodeLeave = useCallback(() => {
hideTooltip()
}, [hideTooltip])
setActiveNodeIds([])
}, [hideTooltip, setActiveNodeIds])

const layerById: Record<NetworkLayerId, ReactNode> = {
const layerById: Record<LayerId, ReactNode> = {
links: null,
nodes: null,
annotations: null,
}

console.log(nodes)

if (layers.includes('links') && links !== null) {
layerById.links = (
<NetworkLinks<N> key="links" links={links} linkComponent={linkComponent} />
<NetworkLinks<Node>
key="links"
links={links}
linkComponent={linkComponent}
blendMode={linkBlendMode}
/>
)
}

if (layers.includes('nodes') && nodes !== null) {
layerById.nodes = (
<NetworkNodes<N>
<NetworkNodes<Node>
key="nodes"
nodes={nodes}
nodeComponent={nodeComponent}
blendMode={nodeBlendMode}
onClick={isInteractive ? onClick : undefined}
onMouseEnter={isInteractive ? handleNodeHover : undefined}
onMouseMove={isInteractive ? handleNodeHover : undefined}
Expand All @@ -105,6 +132,12 @@ const InnerNetwork = <N extends NetworkInputNode>({
)
}

if (layers.includes('annotations') && nodes !== null) {
layerById.annotations = (
<NetworkNodeAnnotations<Node> nodes={nodes} annotations={annotations} />
)
}

return (
<SvgWrapper
width={outerWidth}
Expand Down Expand Up @@ -136,14 +169,14 @@ const InnerNetwork = <N extends NetworkInputNode>({
)
}

export const Network = <N extends NetworkInputNode = NetworkInputNode>({
export const Network = <Node extends InputNode = InputNode>({
isInteractive = svgDefaultProps.isInteractive,
animate = svgDefaultProps.animate,
motionConfig = svgDefaultProps.motionConfig,
theme,
renderWrapper,
...otherProps
}: NetworkSvgProps<N>) => (
}: NetworkSvgProps<Node>) => (
<Container
{...{
animate,
Expand All @@ -153,6 +186,6 @@ export const Network = <N extends NetworkInputNode = NetworkInputNode>({
theme,
}}
>
<InnerNetwork<N> isInteractive={isInteractive} {...otherProps} />
<InnerNetwork<Node> isInteractive={isInteractive} {...otherProps} />
</Container>
)
31 changes: 18 additions & 13 deletions packages/network/src/NetworkCanvas.tsx
Expand Up @@ -3,14 +3,14 @@ import { getDistance, getRelativeCursor, Container, useDimensions, useTheme } fr
import { useTooltip } from '@nivo/tooltip'
import { canvasDefaultProps } from './defaults'
import { useNetwork } from './hooks'
import { NetworkCanvasProps, NetworkInputNode } from './types'
import { NetworkCanvasProps, InputNode, NodeTooltip } from './types'

type InnerNetworkCanvasProps<N extends NetworkInputNode> = Omit<
NetworkCanvasProps<N>,
type InnerNetworkCanvasProps<Node extends InputNode> = Omit<
NetworkCanvasProps<Node>,
'renderWrapper' | 'theme'
>

const InnerNetworkCanvas = <N extends NetworkInputNode>({
const InnerNetworkCanvas = <Node extends InputNode>({
width,
height,
margin: partialMargin,
Expand All @@ -27,6 +27,7 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
layers = canvasDefaultProps.layers,

renderNode = canvasDefaultProps.renderNode,
nodeSize = canvasDefaultProps.nodeSize,
nodeColor = canvasDefaultProps.nodeColor,
nodeBorderWidth = canvasDefaultProps.nodeBorderWidth,
nodeBorderColor = canvasDefaultProps.nodeBorderColor,
Expand All @@ -36,17 +37,17 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
linkColor = canvasDefaultProps.linkColor,

isInteractive = canvasDefaultProps.isInteractive,
nodeTooltip = canvasDefaultProps.nodeTooltip,
nodeTooltip = canvasDefaultProps.nodeTooltip as NodeTooltip<Node>,
onClick,
}: InnerNetworkCanvasProps<N>) => {
}: InnerNetworkCanvasProps<Node>) => {
const canvasEl = useRef<HTMLCanvasElement | null>(null)
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
width,
height,
partialMargin
)

const [nodes, links] = useNetwork<N>({
const { nodes, links, setActiveNodeIds } = useNetwork<Node>({
center: [innerWidth / 2, innerHeight / 2],
nodes: rawNodes,
links: rawLinks,
Expand All @@ -55,6 +56,7 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
distanceMin,
distanceMax,
iterations,
nodeSize,
nodeColor,
nodeBorderWidth,
nodeBorderColor,
Expand Down Expand Up @@ -119,7 +121,7 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
x - margin.left,
y - margin.top
)
return distanceFromNode <= node.radius
return distanceFromNode <= node.size / 2
})
},
[canvasEl, margin, nodes]
Expand All @@ -132,16 +134,19 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
const node = getNodeFromMouseEvent(event)
if (node) {
showTooltipFromEvent(createElement(nodeTooltip, { node }), event)
setActiveNodeIds([node.id])
} else {
hideTooltip()
setActiveNodeIds([])
}
},
[getNodeFromMouseEvent, showTooltipFromEvent, nodeTooltip, hideTooltip]
[getNodeFromMouseEvent, showTooltipFromEvent, nodeTooltip, hideTooltip, setActiveNodeIds]
)

const handleMouseLeave = useCallback(() => {
hideTooltip()
}, [hideTooltip])
setActiveNodeIds([])
}, [hideTooltip, setActiveNodeIds])

const handleClick = useCallback(
(event: MouseEvent) => {
Expand Down Expand Up @@ -173,15 +178,15 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
)
}

export const NetworkCanvas = <N extends NetworkInputNode = NetworkInputNode>({
export const NetworkCanvas = <Node extends InputNode = InputNode>({
theme,
isInteractive = canvasDefaultProps.isInteractive,
animate = canvasDefaultProps.animate,
motionConfig = canvasDefaultProps.motionConfig,
renderWrapper,
...otherProps
}: NetworkCanvasProps<N>) => (
}: NetworkCanvasProps<Node>) => (
<Container {...{ isInteractive, animate, motionConfig, theme, renderWrapper }}>
<InnerNetworkCanvas<N> isInteractive={isInteractive} {...otherProps} />
<InnerNetworkCanvas<Node> isInteractive={isInteractive} {...otherProps} />
</Container>
)
8 changes: 5 additions & 3 deletions packages/network/src/NetworkLink.tsx
@@ -1,13 +1,15 @@
import { animated } from '@react-spring/web'
import { NetworkInputNode, NetworkLinkProps } from './types'
import { InputNode, LinkProps } from './types'

export const NetworkLink = <N extends NetworkInputNode>({
export const NetworkLink = <Node extends InputNode>({
link,
animated: animatedProps,
}: NetworkLinkProps<N>) => {
blendMode,
}: LinkProps<Node>) => {
return (
<animated.line
stroke={animatedProps.color}
style={{ mixBlendMode: blendMode }}
strokeWidth={link.thickness}
strokeLinecap="round"
x1={animatedProps.x1}
Expand Down
31 changes: 17 additions & 14 deletions packages/network/src/NetworkLinks.tsx
@@ -1,16 +1,17 @@
import { createElement, useMemo } from 'react'
import { useTransition } from '@react-spring/web'
import { useMotionConfig } from '@nivo/core'
import { ComputedLink, NetworkInputNode, NetworkLinkComponent } from './types'
import { ComputedLink, InputNode, LinkComponent, NetworkSvgProps } from './types'

interface NetworkLinksProps<N extends NetworkInputNode> {
links: ComputedLink<N>[]
linkComponent: NetworkLinkComponent<N>
interface NetworkLinksProps<Node extends InputNode> {
links: ComputedLink<Node>[]
linkComponent: LinkComponent<Node>
blendMode: NonNullable<NetworkSvgProps<Node>['linkBlendMode']>
}

const getEnterTransition =
<N extends NetworkInputNode>() =>
(link: ComputedLink<N>) => ({
<Node extends InputNode>() =>
(link: ComputedLink<Node>) => ({
x1: link.source.x,
y1: link.source.y,
x2: link.source.x,
Expand All @@ -20,8 +21,8 @@ const getEnterTransition =
})

const getRegularTransition =
<N extends NetworkInputNode>() =>
(link: ComputedLink<N>) => ({
<Node extends InputNode>() =>
(link: ComputedLink<Node>) => ({
x1: link.source.x,
y1: link.source.y,
x2: link.target.x,
Expand All @@ -31,8 +32,8 @@ const getRegularTransition =
})

const getExitTransition =
<N extends NetworkInputNode>() =>
(link: ComputedLink<N>) => ({
<Node extends InputNode>() =>
(link: ComputedLink<Node>) => ({
x1: link.source.x,
y1: link.source.y,
x2: link.source.x,
Expand All @@ -41,19 +42,20 @@ const getExitTransition =
opacity: 0,
})

export const NetworkLinks = <N extends NetworkInputNode>({
export const NetworkLinks = <Node extends InputNode>({
links,
linkComponent,
}: NetworkLinksProps<N>) => {
blendMode,
}: NetworkLinksProps<Node>) => {
const { animate, config: springConfig } = useMotionConfig()

const [enterTransition, regularTransition, exitTransition] = useMemo(
() => [getEnterTransition<N>(), getRegularTransition<N>(), getExitTransition<N>()],
() => [getEnterTransition<Node>(), getRegularTransition<Node>(), getExitTransition<Node>()],
[]
)

const transition = useTransition<
ComputedLink<N>,
ComputedLink<Node>,
{
x1: number
y1: number
Expand Down Expand Up @@ -81,6 +83,7 @@ export const NetworkLinks = <N extends NetworkInputNode>({
key: link.id,
link,
animated: transitionProps,
blendMode,
})
})}
</>
Expand Down

0 comments on commit 5575928

Please sign in to comment.