Skip to content

Commit 7fdff22

Browse files
committedDec 31, 2021
feat(network): add the ability to extend the links data structure via a generic
1 parent d70ad13 commit 7fdff22

15 files changed

+294
-240
lines changed
 

‎packages/network/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@
3535
"@nivo/colors": "0.76.0",
3636
"@nivo/tooltip": "0.76.0",
3737
"@react-spring/web": "9.3.1",
38-
"d3-force": "^2.0.1",
39-
"lodash": "^4.17.21"
38+
"d3-force": "^2.0.1"
4039
},
4140
"devDependencies": {
4241
"@nivo/core": "0.76.0"

‎packages/network/src/Network.tsx

+17-16
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ import { useNetwork } from './hooks'
66
import { NetworkLinks } from './NetworkLinks'
77
import { NetworkNodes } from './NetworkNodes'
88
import { NetworkNodeAnnotations } from './NetworkNodeAnnotations'
9+
import { InputNode, LayerId, NodeTooltip, NetworkSvgProps, ComputedNode, InputLink } from './types'
910

10-
import { InputNode, LayerId, NodeTooltip, NetworkSvgProps, ComputedNode } from './types'
11-
12-
type InnerNetworkProps<Node extends InputNode> = Omit<
13-
NetworkSvgProps<Node>,
11+
type InnerNetworkProps<Node extends InputNode, Link extends InputLink> = Omit<
12+
NetworkSvgProps<Node, Link>,
1413
'animate' | 'motionConfig' | 'renderWrapper' | 'theme'
1514
>
1615

17-
const InnerNetwork = <Node extends InputNode>({
16+
const InnerNetwork = <Node extends InputNode, Link extends InputLink>({
1817
width,
1918
height,
2019
margin: partialMargin,
@@ -30,7 +29,7 @@ const InnerNetwork = <Node extends InputNode>({
3029
layers = svgDefaultProps.layers,
3130

3231
nodeComponent = svgDefaultProps.nodeComponent as NonNullable<
33-
NetworkSvgProps<Node>['nodeComponent']
32+
NetworkSvgProps<Node, Link>['nodeComponent']
3433
>,
3534
nodeSize = svgDefaultProps.nodeSize,
3635
activeNodeSize = svgDefaultProps.activeNodeSize,
@@ -41,13 +40,15 @@ const InnerNetwork = <Node extends InputNode>({
4140
nodeBorderColor = svgDefaultProps.nodeBorderColor,
4241

4342
linkComponent = svgDefaultProps.linkComponent as NonNullable<
44-
NetworkSvgProps<Node>['linkComponent']
43+
NetworkSvgProps<Node, Link>['linkComponent']
4544
>,
4645
linkThickness = svgDefaultProps.linkThickness,
4746
linkColor = svgDefaultProps.linkColor,
4847
linkBlendMode = svgDefaultProps.linkBlendMode,
4948

50-
annotations = svgDefaultProps.annotations as NonNullable<NetworkSvgProps<Node>['annotations']>,
49+
annotations = svgDefaultProps.annotations as NonNullable<
50+
NetworkSvgProps<Node, Link>['annotations']
51+
>,
5152

5253
isInteractive = svgDefaultProps.isInteractive,
5354
nodeTooltip = svgDefaultProps.nodeTooltip as NodeTooltip<Node>,
@@ -57,14 +58,14 @@ const InnerNetwork = <Node extends InputNode>({
5758
ariaLabel,
5859
ariaLabelledBy,
5960
ariaDescribedBy,
60-
}: InnerNetworkProps<Node>) => {
61+
}: InnerNetworkProps<Node, Link>) => {
6162
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
6263
width,
6364
height,
6465
partialMargin
6566
)
6667

67-
const { nodes, links, setActiveNodeIds } = useNetwork<Node>({
68+
const { nodes, links, setActiveNodeIds } = useNetwork<Node, Link>({
6869
center: [innerWidth / 2, innerHeight / 2],
6970
nodes: rawNodes,
7071
links: rawLinks,
@@ -106,7 +107,7 @@ const InnerNetwork = <Node extends InputNode>({
106107

107108
if (layers.includes('links') && links !== null) {
108109
layerById.links = (
109-
<NetworkLinks<Node>
110+
<NetworkLinks<Node, Link>
110111
key="links"
111112
links={links}
112113
linkComponent={linkComponent}
@@ -117,7 +118,7 @@ const InnerNetwork = <Node extends InputNode>({
117118

118119
if (layers.includes('nodes') && nodes !== null) {
119120
layerById.nodes = (
120-
<NetworkNodes<Node>
121+
<NetworkNodes<Node, Link>
121122
key="nodes"
122123
nodes={nodes}
123124
nodeComponent={nodeComponent}
@@ -132,7 +133,7 @@ const InnerNetwork = <Node extends InputNode>({
132133

133134
if (layers.includes('annotations') && nodes !== null) {
134135
layerById.annotations = (
135-
<NetworkNodeAnnotations<Node>
136+
<NetworkNodeAnnotations<Node, Link>
136137
key="annotations"
137138
nodes={nodes}
138139
annotations={annotations}
@@ -171,14 +172,14 @@ const InnerNetwork = <Node extends InputNode>({
171172
)
172173
}
173174

174-
export const Network = <Node extends InputNode = InputNode>({
175+
export const Network = <Node extends InputNode = InputNode, Link extends InputLink = InputLink>({
175176
isInteractive = svgDefaultProps.isInteractive,
176177
animate = svgDefaultProps.animate,
177178
motionConfig = svgDefaultProps.motionConfig,
178179
theme,
179180
renderWrapper,
180181
...otherProps
181-
}: NetworkSvgProps<Node>) => (
182+
}: NetworkSvgProps<Node, Link>) => (
182183
<Container
183184
{...{
184185
animate,
@@ -188,6 +189,6 @@ export const Network = <Node extends InputNode = InputNode>({
188189
theme,
189190
}}
190191
>
191-
<InnerNetwork<Node> isInteractive={isInteractive} {...otherProps} />
192+
<InnerNetwork<Node, Link> isInteractive={isInteractive} {...otherProps} />
192193
</Container>
193194
)

‎packages/network/src/NetworkCanvas.tsx

+16-9
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { getDistance, getRelativeCursor, Container, useDimensions, useTheme } fr
33
import { useTooltip } from '@nivo/tooltip'
44
import { canvasDefaultProps } from './defaults'
55
import { useNetwork } from './hooks'
6-
import { NetworkCanvasProps, InputNode, NodeTooltip } from './types'
6+
import { NetworkCanvasProps, InputNode, NodeTooltip, InputLink } from './types'
77

8-
type InnerNetworkCanvasProps<Node extends InputNode> = Omit<
9-
NetworkCanvasProps<Node>,
8+
type InnerNetworkCanvasProps<Node extends InputNode, Link extends InputLink> = Omit<
9+
NetworkCanvasProps<Node, Link>,
1010
'renderWrapper' | 'theme'
1111
>
1212

13-
const InnerNetworkCanvas = <Node extends InputNode>({
13+
const InnerNetworkCanvas = <Node extends InputNode, Link extends InputLink>({
1414
width,
1515
height,
1616
margin: partialMargin,
@@ -28,6 +28,8 @@ const InnerNetworkCanvas = <Node extends InputNode>({
2828

2929
renderNode = canvasDefaultProps.renderNode,
3030
nodeSize = canvasDefaultProps.nodeSize,
31+
activeNodeSize = canvasDefaultProps.activeNodeSize,
32+
inactiveNodeSize = canvasDefaultProps.inactiveNodeSize,
3133
nodeColor = canvasDefaultProps.nodeColor,
3234
nodeBorderWidth = canvasDefaultProps.nodeBorderWidth,
3335
nodeBorderColor = canvasDefaultProps.nodeBorderColor,
@@ -39,15 +41,15 @@ const InnerNetworkCanvas = <Node extends InputNode>({
3941
isInteractive = canvasDefaultProps.isInteractive,
4042
nodeTooltip = canvasDefaultProps.nodeTooltip as NodeTooltip<Node>,
4143
onClick,
42-
}: InnerNetworkCanvasProps<Node>) => {
44+
}: InnerNetworkCanvasProps<Node, Link>) => {
4345
const canvasEl = useRef<HTMLCanvasElement | null>(null)
4446
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
4547
width,
4648
height,
4749
partialMargin
4850
)
4951

50-
const { nodes, links, setActiveNodeIds } = useNetwork<Node>({
52+
const { nodes, links, setActiveNodeIds } = useNetwork<Node, Link>({
5153
center: [innerWidth / 2, innerHeight / 2],
5254
nodes: rawNodes,
5355
links: rawLinks,
@@ -57,6 +59,8 @@ const InnerNetworkCanvas = <Node extends InputNode>({
5759
distanceMax,
5860
iterations,
5961
nodeSize,
62+
activeNodeSize,
63+
inactiveNodeSize,
6064
nodeColor,
6165
nodeBorderWidth,
6266
nodeBorderColor,
@@ -178,15 +182,18 @@ const InnerNetworkCanvas = <Node extends InputNode>({
178182
)
179183
}
180184

181-
export const NetworkCanvas = <Node extends InputNode = InputNode>({
185+
export const NetworkCanvas = <
186+
Node extends InputNode = InputNode,
187+
Link extends InputLink = InputLink
188+
>({
182189
theme,
183190
isInteractive = canvasDefaultProps.isInteractive,
184191
animate = canvasDefaultProps.animate,
185192
motionConfig = canvasDefaultProps.motionConfig,
186193
renderWrapper,
187194
...otherProps
188-
}: NetworkCanvasProps<Node>) => (
195+
}: NetworkCanvasProps<Node, Link>) => (
189196
<Container {...{ isInteractive, animate, motionConfig, theme, renderWrapper }}>
190-
<InnerNetworkCanvas<Node> isInteractive={isInteractive} {...otherProps} />
197+
<InnerNetworkCanvas<Node, Link> isInteractive={isInteractive} {...otherProps} />
191198
</Container>
192199
)

‎packages/network/src/NetworkLink.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { animated } from '@react-spring/web'
2-
import { InputNode, LinkProps } from './types'
2+
import { InputLink, InputNode, LinkProps } from './types'
33
import { memo } from 'react'
44

5-
const NonMemoizedNetworkLink = <Node extends InputNode>({
5+
const NonMemoizedNetworkLink = <Node extends InputNode, Link extends InputLink>({
66
link,
77
animated: animatedProps,
88
blendMode,
9-
}: LinkProps<Node>) => (
9+
}: LinkProps<Node, Link>) => (
1010
<animated.line
1111
data-testid={`link.${link.id}`}
1212
stroke={animatedProps.color}

‎packages/network/src/NetworkLinks.tsx

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { createElement, useMemo } from 'react'
22
import { useTransition } from '@react-spring/web'
33
import { useMotionConfig } from '@nivo/core'
4-
import { ComputedLink, InputNode, LinkComponent, NetworkSvgProps } from './types'
4+
import { ComputedLink, InputLink, InputNode, LinkComponent, NetworkSvgProps } from './types'
55

6-
interface NetworkLinksProps<Node extends InputNode> {
7-
links: ComputedLink<Node>[]
8-
linkComponent: LinkComponent<Node>
9-
blendMode: NonNullable<NetworkSvgProps<Node>['linkBlendMode']>
6+
interface NetworkLinksProps<Node extends InputNode, Link extends InputLink> {
7+
links: ComputedLink<Node, Link>[]
8+
linkComponent: LinkComponent<Node, Link>
9+
blendMode: NonNullable<NetworkSvgProps<Node, Link>['linkBlendMode']>
1010
}
1111

1212
const getEnterTransition =
13-
<Node extends InputNode>() =>
14-
(link: ComputedLink<Node>) => ({
13+
<Node extends InputNode, Link extends InputLink>() =>
14+
(link: ComputedLink<Node, Link>) => ({
1515
x1: link.source.x,
1616
y1: link.source.y,
1717
x2: link.source.x,
@@ -21,8 +21,8 @@ const getEnterTransition =
2121
})
2222

2323
const getRegularTransition =
24-
<Node extends InputNode>() =>
25-
(link: ComputedLink<Node>) => ({
24+
<Node extends InputNode, Link extends InputLink>() =>
25+
(link: ComputedLink<Node, Link>) => ({
2626
x1: link.source.x,
2727
y1: link.source.y,
2828
x2: link.target.x,
@@ -31,20 +31,20 @@ const getRegularTransition =
3131
opacity: 1,
3232
})
3333

34-
export const NetworkLinks = <Node extends InputNode>({
34+
export const NetworkLinks = <Node extends InputNode, Link extends InputLink>({
3535
links,
3636
linkComponent,
3737
blendMode,
38-
}: NetworkLinksProps<Node>) => {
38+
}: NetworkLinksProps<Node, Link>) => {
3939
const { animate, config: springConfig } = useMotionConfig()
4040

4141
const [enterTransition, regularTransition] = useMemo(
42-
() => [getEnterTransition<Node>(), getRegularTransition<Node>()],
42+
() => [getEnterTransition<Node, Link>(), getRegularTransition<Node, Link>()],
4343
[]
4444
)
4545

4646
const transition = useTransition<
47-
ComputedLink<Node>,
47+
ComputedLink<Node, Link>,
4848
{
4949
x1: number
5050
y1: number

‎packages/network/src/NetworkNode.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { memo } from 'react'
22
import { animated, to } from '@react-spring/web'
3-
import { InputNode, NodeProps } from './types'
3+
import { InputLink, InputNode, NodeProps } from './types'
44

5-
const NonMemoizedNetworkNode = <Node extends InputNode>({
5+
const NonMemoizedNetworkNode = <Node extends InputNode, Link extends InputLink>({
66
node,
77
animated: animatedProps,
88
blendMode,
99
onClick,
1010
onMouseEnter,
1111
onMouseMove,
1212
onMouseLeave,
13-
}: NodeProps<Node>) => (
13+
}: NodeProps<Node, Link>) => (
1414
<animated.circle
1515
data-testid={`node.${node.id}`}
1616
transform={to([animatedProps.x, animatedProps.y, animatedProps.scale], (x, y, scale) => {
@@ -21,6 +21,7 @@ const NonMemoizedNetworkNode = <Node extends InputNode>({
2121
style={{ mixBlendMode: blendMode }}
2222
strokeWidth={animatedProps.borderWidth}
2323
stroke={animatedProps.borderColor}
24+
opacity={animatedProps.opacity}
2425
onClick={onClick ? event => onClick(node, event) : undefined}
2526
onMouseEnter={onMouseEnter ? event => onMouseEnter(node, event) : undefined}
2627
onMouseMove={onMouseMove ? event => onMouseMove(node, event) : undefined}

‎packages/network/src/NetworkNodeAnnotations.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { Annotation } from '@nivo/annotations'
2-
import { ComputedNode, InputNode, NetworkSvgProps } from './types'
2+
import { ComputedNode, InputLink, InputNode, NetworkSvgProps } from './types'
33
import { useNodeAnnotations } from './hooks'
44

5-
interface NetworkNodeAnnotationsProps<Node extends InputNode> {
5+
interface NetworkNodeAnnotationsProps<Node extends InputNode, Link extends InputLink> {
66
nodes: ComputedNode<Node>[]
7-
annotations: NonNullable<NetworkSvgProps<Node>['annotations']>
7+
annotations: NonNullable<NetworkSvgProps<Node, Link>['annotations']>
88
}
99

10-
export const NetworkNodeAnnotations = <Node extends InputNode>({
10+
export const NetworkNodeAnnotations = <Node extends InputNode, Link extends InputLink>({
1111
nodes,
1212
annotations,
13-
}: NetworkNodeAnnotationsProps<Node>) => {
13+
}: NetworkNodeAnnotationsProps<Node, Link>) => {
1414
const boundAnnotations = useNodeAnnotations<Node>(nodes, annotations)
1515

1616
return (

‎packages/network/src/NetworkNodes.tsx

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import { createElement, MouseEvent, useMemo } from 'react'
22
import { useTransition } from '@react-spring/web'
33
import { useMotionConfig } from '@nivo/core'
4-
import { InputNode, ComputedNode, NodeAnimatedProps, NodeComponent, NetworkSvgProps } from './types'
4+
import {
5+
InputNode,
6+
ComputedNode,
7+
NodeAnimatedProps,
8+
NodeComponent,
9+
NetworkSvgProps,
10+
InputLink,
11+
} from './types'
512

6-
interface NetworkNodesProps<Node extends InputNode> {
13+
interface NetworkNodesProps<Node extends InputNode, Link extends InputLink> {
714
nodes: ComputedNode<Node>[]
8-
nodeComponent: NodeComponent<Node>
9-
blendMode: NonNullable<NetworkSvgProps<Node>['nodeBlendMode']>
15+
nodeComponent: NodeComponent<Node, Link>
16+
blendMode: NonNullable<NetworkSvgProps<Node, Link>['nodeBlendMode']>
1017
onClick?: (node: ComputedNode<Node>, event: MouseEvent) => void
1118
onMouseEnter?: (node: ComputedNode<Node>, event: MouseEvent) => void
1219
onMouseMove?: (node: ComputedNode<Node>, event: MouseEvent) => void
@@ -23,6 +30,7 @@ const getEnterTransition =
2330
borderWidth: node.borderWidth,
2431
borderColor: node.borderColor,
2532
scale: 0,
33+
opacity: 0,
2634
})
2735

2836
const getRegularTransition =
@@ -35,6 +43,7 @@ const getRegularTransition =
3543
borderWidth: node.borderWidth,
3644
borderColor: node.borderColor,
3745
scale: 1,
46+
opacity: 1,
3847
})
3948

4049
const getExitTransition =
@@ -47,17 +56,18 @@ const getExitTransition =
4756
borderWidth: node.borderWidth,
4857
borderColor: node.borderColor,
4958
scale: 0,
59+
opacity: 0,
5060
})
5161

52-
export const NetworkNodes = <Node extends InputNode>({
62+
export const NetworkNodes = <Node extends InputNode, Link extends InputLink>({
5363
nodes,
5464
nodeComponent,
5565
blendMode,
5666
onClick,
5767
onMouseEnter,
5868
onMouseMove,
5969
onMouseLeave,
60-
}: NetworkNodesProps<Node>) => {
70+
}: NetworkNodesProps<Node, Link>) => {
6171
const { animate, config: springConfig } = useMotionConfig()
6272

6373
const [enterTransition, regularTransition, exitTransition] = useMemo(
+7-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { ResponsiveWrapper } from '@nivo/core'
2-
import { InputNode, NetworkSvgProps } from './types'
2+
import { InputLink, InputNode, NetworkSvgProps } from './types'
33
import { Network } from './Network'
44

5-
export const ResponsiveNetwork = <Node extends InputNode = InputNode>(
6-
props: Omit<NetworkSvgProps<Node>, 'height' | 'width'>
5+
export const ResponsiveNetwork = <
6+
Node extends InputNode = InputNode,
7+
Link extends InputLink = InputLink
8+
>(
9+
props: Omit<NetworkSvgProps<Node, Link>, 'height' | 'width'>
710
) => (
811
<ResponsiveWrapper>
9-
{({ width, height }) => <Network<Node> width={width} height={height} {...props} />}
12+
{({ width, height }) => <Network<Node, Link> width={width} height={height} {...props} />}
1013
</ResponsiveWrapper>
1114
)
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { ResponsiveWrapper } from '@nivo/core'
2-
import { NetworkCanvasProps, InputNode } from './types'
2+
import { NetworkCanvasProps, InputNode, InputLink } from './types'
33
import { NetworkCanvas } from './NetworkCanvas'
44

5-
export const ResponsiveNetworkCanvas = <Node extends InputNode = InputNode>(
6-
props: Omit<NetworkCanvasProps<Node>, 'height' | 'width'>
5+
export const ResponsiveNetworkCanvas = <
6+
Node extends InputNode = InputNode,
7+
Link extends InputLink = InputLink
8+
>(
9+
props: Omit<NetworkCanvasProps<Node, Link>, 'height' | 'width'>
710
) => (
811
<ResponsiveWrapper>
9-
{({ width, height }) => <NetworkCanvas<Node> width={width} height={height} {...props} />}
12+
{({ width, height }) => (
13+
<NetworkCanvas<Node, Link> width={width} height={height} {...props} />
14+
)}
1015
</ResponsiveWrapper>
1116
)

‎packages/network/src/defaults.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { NetworkCommonProps, InputNode, LayerId, NetworkSvgProps } from './types'
1+
import { NetworkCommonProps, InputNode, LayerId, NetworkSvgProps, InputLink } from './types'
22
import { NetworkNode } from './NetworkNode'
33
import { renderCanvasNode } from './renderCanvasNode'
44
import { NetworkLink } from './NetworkLink'
55
import { renderCanvasLink } from './renderCanvasLink'
66
import { NetworkNodeTooltip } from './NetworkNodeTooltip'
77

88
export const commonDefaultProps: Omit<
9-
NetworkCommonProps<InputNode>,
9+
NetworkCommonProps<InputNode, InputLink>,
1010
| 'margin'
1111
| 'theme'
1212
| 'activeLinkThickness'
@@ -22,10 +22,10 @@ export const commonDefaultProps: Omit<
2222
layers: ['links', 'nodes', 'annotations'],
2323

2424
linkDistance: 30,
25-
repulsivity: 10,
25+
repulsivity: 3,
2626
distanceMin: 1,
2727
distanceMax: Infinity,
28-
iterations: 90,
28+
iterations: 160,
2929

3030
nodeSize: 12,
3131
activeNodeSize: 18,
@@ -50,10 +50,14 @@ export const commonDefaultProps: Omit<
5050

5151
export const svgDefaultProps = {
5252
...commonDefaultProps,
53-
nodeComponent: NetworkNode as NonNullable<NetworkSvgProps<InputNode>['nodeComponent']>,
54-
nodeBlendMode: 'normal' as NonNullable<NetworkSvgProps<InputNode>['nodeBlendMode']>,
55-
linkComponent: NetworkLink as NonNullable<NetworkSvgProps<InputNode>['linkComponent']>,
56-
linkBlendMode: 'normal' as NonNullable<NetworkSvgProps<InputNode>['linkBlendMode']>,
53+
nodeComponent: NetworkNode as NonNullable<
54+
NetworkSvgProps<InputNode, InputLink>['nodeComponent']
55+
>,
56+
nodeBlendMode: 'normal' as NonNullable<NetworkSvgProps<InputNode, InputLink>['nodeBlendMode']>,
57+
linkComponent: NetworkLink as NonNullable<
58+
NetworkSvgProps<InputNode, InputLink>['linkComponent']
59+
>,
60+
linkBlendMode: 'normal' as NonNullable<NetworkSvgProps<InputNode, InputLink>['linkBlendMode']>,
5761
}
5862

5963
export const canvasDefaultProps = {

‎packages/network/src/hooks.ts

+115-126
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
import { useState, useEffect, useMemo, useCallback } from 'react'
2-
import get from 'lodash/get'
3-
import isString from 'lodash/isString'
4-
import isNumber from 'lodash/isNumber'
52
import { forceSimulation, forceManyBody, forceCenter, forceLink } from 'd3-force'
63
import { useTheme } from '@nivo/core'
74
import { useInheritedColor } from '@nivo/colors'
@@ -14,53 +11,53 @@ import {
1411
DerivedProp,
1512
ComputedNode,
1613
ComputedLink,
14+
TransientNode,
15+
TransientLink,
1716
} from './types'
1817

19-
const computeForces = <Node extends InputNode>({
18+
const useDerivedProp = <Target, Output extends string | number>(
19+
instruction: DerivedProp<Target, Output>
20+
) =>
21+
useMemo(() => {
22+
if (typeof instruction === 'function') return instruction
23+
return () => instruction
24+
}, [instruction])
25+
26+
const useComputeForces = <Node extends InputNode, Link extends InputLink>({
2027
linkDistance,
2128
repulsivity,
2229
distanceMin,
2330
distanceMax,
2431
center,
2532
}: {
26-
linkDistance: NetworkCommonProps<Node>['linkDistance']
27-
repulsivity: NetworkCommonProps<Node>['repulsivity']
28-
distanceMin: NetworkCommonProps<Node>['distanceMin']
29-
distanceMax: NetworkCommonProps<Node>['distanceMax']
33+
linkDistance: NetworkCommonProps<Node, Link>['linkDistance']
34+
repulsivity: NetworkCommonProps<Node, Link>['repulsivity']
35+
distanceMin: NetworkCommonProps<Node, Link>['distanceMin']
36+
distanceMax: NetworkCommonProps<Node, Link>['distanceMax']
3037
center: [number, number]
3138
}) => {
32-
let getLinkDistance
33-
if (typeof linkDistance === 'function') {
34-
getLinkDistance = linkDistance
35-
} else if (isNumber(linkDistance)) {
36-
getLinkDistance = linkDistance
37-
} else if (isString(linkDistance)) {
38-
getLinkDistance = (link: InputLink) => get(link, linkDistance)
39-
}
39+
const getLinkDistance = useDerivedProp<Link, number>(linkDistance)
4040

41-
const linkForce = forceLink()
42-
.id((d: any) => d.id)
43-
.distance(getLinkDistance as any)
41+
const centerX = center[0]
42+
const centerY = center[1]
4443

45-
const chargeForce = forceManyBody()
46-
.strength(-repulsivity)
47-
.distanceMin(distanceMin)
48-
.distanceMax(distanceMax)
44+
return useMemo(() => {
45+
const linkForce = forceLink<TransientNode<Node>, TransientLink<Node, Link>>().distance(
46+
link => getLinkDistance(link.data)
47+
)
4948

50-
const centerForce = forceCenter(center[0], center[1])
49+
const chargeForce = forceManyBody()
50+
.strength(-repulsivity)
51+
.distanceMin(distanceMin)
52+
.distanceMax(distanceMax)
5153

52-
return { link: linkForce, charge: chargeForce, center: centerForce }
53-
}
54+
const centerForce = forceCenter(centerX, centerY)
5455

55-
const useDerivedProp = <Target, Output extends string | number>(
56-
instruction: DerivedProp<Target, Output>
57-
) =>
58-
useMemo(() => {
59-
if (typeof instruction === 'function') return instruction
60-
return () => instruction
61-
}, [instruction])
56+
return { link: linkForce, charge: chargeForce, center: centerForce }
57+
}, [getLinkDistance, repulsivity, distanceMin, distanceMax, centerX, centerY])
58+
}
6259

63-
const useNodeStyle = <Node extends InputNode>({
60+
const useNodeStyle = <Node extends InputNode, Link extends InputLink>({
6461
size,
6562
activeSize,
6663
inactiveSize,
@@ -70,25 +67,23 @@ const useNodeStyle = <Node extends InputNode>({
7067
isInteractive,
7168
activeNodeIds,
7269
}: {
73-
size: NetworkCommonProps<Node>['nodeSize']
74-
activeSize: NetworkCommonProps<Node>['activeNodeSize']
75-
inactiveSize: NetworkCommonProps<Node>['inactiveNodeSize']
76-
color: NetworkCommonProps<Node>['nodeColor']
77-
borderWidth: NetworkCommonProps<Node>['nodeBorderWidth']
78-
borderColor: NetworkCommonProps<Node>['nodeBorderColor']
79-
isInteractive: NetworkCommonProps<Node>['isInteractive']
70+
size: NetworkCommonProps<Node, Link>['nodeSize']
71+
activeSize: NetworkCommonProps<Node, Link>['activeNodeSize']
72+
inactiveSize: NetworkCommonProps<Node, Link>['inactiveNodeSize']
73+
color: NetworkCommonProps<Node, Link>['nodeColor']
74+
borderWidth: NetworkCommonProps<Node, Link>['nodeBorderWidth']
75+
borderColor: NetworkCommonProps<Node, Link>['nodeBorderColor']
76+
isInteractive: NetworkCommonProps<Node, Link>['isInteractive']
8077
activeNodeIds: string[]
8178
}) => {
82-
type IntermediateNode = Pick<ComputedNode<Node>, 'id' | 'data' | 'index' | 'x' | 'y'>
83-
8479
const theme = useTheme()
8580

8681
const getSize = useDerivedProp(size)
8782
const getColor = useDerivedProp(color)
8883
const getBorderWidth = useDerivedProp(borderWidth)
8984
const getBorderColor = useInheritedColor(borderColor, theme)
9085
const getNormalStyle = useCallback(
91-
(node: IntermediateNode) => {
86+
(node: TransientNode<Node>) => {
9287
const color = getColor(node.data)
9388

9489
return {
@@ -103,7 +98,7 @@ const useNodeStyle = <Node extends InputNode>({
10398

10499
const getActiveSize = useDerivedProp(activeSize)
105100
const getActiveStyle = useCallback(
106-
(node: IntermediateNode) => {
101+
(node: TransientNode<Node>) => {
107102
const color = getColor(node.data)
108103

109104
return {
@@ -118,7 +113,7 @@ const useNodeStyle = <Node extends InputNode>({
118113

119114
const getInactiveSize = useDerivedProp(inactiveSize)
120115
const getInactiveStyle = useCallback(
121-
(node: IntermediateNode) => {
116+
(node: TransientNode<Node>) => {
122117
const color = getColor(node.data)
123118

124119
return {
@@ -132,7 +127,7 @@ const useNodeStyle = <Node extends InputNode>({
132127
)
133128

134129
return useCallback(
135-
(node: IntermediateNode) => {
130+
(node: TransientNode<Node>) => {
136131
if (!isInteractive || activeNodeIds.length === 0) return getNormalStyle(node)
137132
if (activeNodeIds.includes(node.id)) return getActiveStyle(node)
138133
return getInactiveStyle(node)
@@ -141,7 +136,7 @@ const useNodeStyle = <Node extends InputNode>({
141136
)
142137
}
143138

144-
export const useNetwork = <Node extends InputNode = InputNode>({
139+
export const useNetwork = <Node extends InputNode = InputNode, Link extends InputLink = InputLink>({
145140
center,
146141
nodes,
147142
links,
@@ -162,87 +157,78 @@ export const useNetwork = <Node extends InputNode = InputNode>({
162157
}: {
163158
center: [number, number]
164159
nodes: Node[]
165-
links: InputLink[]
166-
linkDistance?: NetworkCommonProps<Node>['linkDistance']
167-
repulsivity?: NetworkCommonProps<Node>['repulsivity']
168-
distanceMin?: NetworkCommonProps<Node>['distanceMin']
169-
distanceMax?: NetworkCommonProps<Node>['distanceMax']
170-
iterations?: NetworkCommonProps<Node>['iterations']
171-
nodeSize?: NetworkCommonProps<Node>['nodeSize']
172-
activeNodeSize?: NetworkCommonProps<Node>['activeNodeSize']
173-
inactiveNodeSize?: NetworkCommonProps<Node>['inactiveNodeSize']
174-
nodeColor?: NetworkCommonProps<Node>['nodeColor']
175-
nodeBorderWidth?: NetworkCommonProps<Node>['nodeBorderWidth']
176-
nodeBorderColor?: NetworkCommonProps<Node>['nodeBorderColor']
177-
linkThickness?: NetworkCommonProps<Node>['linkThickness']
178-
linkColor?: NetworkCommonProps<Node>['linkColor']
179-
isInteractive?: NetworkCommonProps<Node>['isInteractive']
160+
links: Link[]
161+
linkDistance?: NetworkCommonProps<Node, Link>['linkDistance']
162+
repulsivity?: NetworkCommonProps<Node, Link>['repulsivity']
163+
distanceMin?: NetworkCommonProps<Node, Link>['distanceMin']
164+
distanceMax?: NetworkCommonProps<Node, Link>['distanceMax']
165+
iterations?: NetworkCommonProps<Node, Link>['iterations']
166+
nodeSize?: NetworkCommonProps<Node, Link>['nodeSize']
167+
activeNodeSize?: NetworkCommonProps<Node, Link>['activeNodeSize']
168+
inactiveNodeSize?: NetworkCommonProps<Node, Link>['inactiveNodeSize']
169+
nodeColor?: NetworkCommonProps<Node, Link>['nodeColor']
170+
nodeBorderWidth?: NetworkCommonProps<Node, Link>['nodeBorderWidth']
171+
nodeBorderColor?: NetworkCommonProps<Node, Link>['nodeBorderColor']
172+
linkThickness?: NetworkCommonProps<Node, Link>['linkThickness']
173+
linkColor?: NetworkCommonProps<Node, Link>['linkColor']
174+
isInteractive?: NetworkCommonProps<Node, Link>['isInteractive']
180175
}) => {
181176
// we're using `null` instead of empty array so that we can dissociate
182177
// initial rendering from updates when using transitions.
183-
const [currentNodes, setCurrentNodes] = useState<
184-
null | Pick<ComputedNode<Node>, 'id' | 'data' | 'index' | 'x' | 'y'>[]
185-
>(null)
186-
const [currentLinks, setCurrentLinks] = useState<null | ComputedLink<Node>[]>(null)
178+
const [transientNodes, setTransientNodes] = useState<null | TransientNode<Node>[]>(null)
179+
const [transientLinks, setTransientLinks] = useState<null | TransientLink<Node, Link>[]>(null)
187180

188-
const centerX = center[0]
189-
const centerY = center[1]
181+
const forces = useComputeForces<Node, Link>({
182+
linkDistance,
183+
repulsivity,
184+
distanceMin,
185+
distanceMax,
186+
center,
187+
})
190188

191189
useEffect(() => {
192-
const forces = computeForces<Node>({
193-
linkDistance,
194-
repulsivity,
195-
distanceMin,
196-
distanceMax,
197-
center: [centerX, centerY],
198-
})
199-
200-
const nodesCopy: Pick<ComputedNode<Node>, 'id' | 'data' | 'index' | 'x' | 'y'>[] =
201-
nodes.map(node => ({
202-
id: node.id,
203-
data: { ...node },
204-
// populated later by D3, mutation
205-
index: 0,
206-
x: 0,
207-
y: 0,
208-
}))
209-
const linksCopy: InputLink[] = links.map(link => ({
210-
// generate a unique id for each link
211-
id: `${link.source}.${link.target}`,
212-
...link,
190+
// copy the nodes & links to avoid mutating the original ones.
191+
const _transientNodes: TransientNode<Node>[] = nodes.map(node => ({
192+
id: node.id,
193+
data: { ...node },
194+
// the properties below are populated by D3, via mutations
195+
index: 0,
196+
x: 0,
197+
y: 0,
198+
vx: 0,
199+
vy: 0,
200+
}))
201+
const _transientLinks: TransientLink<Node, Link>[] = links.map(link => ({
202+
data: { ...link },
203+
// populated by D3, via mutation
204+
index: 0,
205+
// replace ids with objects, otherwise D3 does this automatically
206+
// which is a bit annoying with typings because then `source` & `target`
207+
// can be either strings (before the simulation) or an objects (after).
208+
source: _transientNodes.find(node => node.id === link.source)!,
209+
target: _transientNodes.find(node => node.id === link.target)!,
213210
}))
214211

215-
const simulation = forceSimulation(nodesCopy as any[])
216-
.force('link', forces.link.links(linksCopy))
212+
const simulation = forceSimulation(_transientNodes)
213+
.force('link', forces.link.links(_transientLinks))
217214
.force('charge', forces.charge)
218215
.force('center', forces.center)
219216
.stop()
220217

218+
// this will set `index`, `x`, `y`, `vx`, `vy` for each node.
221219
simulation.tick(iterations)
222220

223-
// d3 mutates data, hence the castings
224-
setCurrentNodes(nodesCopy)
225-
setCurrentLinks(linksCopy as unknown as ComputedLink<Node>[])
221+
setTransientNodes(_transientNodes)
222+
setTransientLinks(_transientLinks)
226223

227224
return () => {
228-
// prevent the simulation from continuing in case the data is updated.
229225
simulation.stop()
230226
}
231-
}, [
232-
centerX,
233-
centerY,
234-
nodes,
235-
links,
236-
linkDistance,
237-
repulsivity,
238-
distanceMin,
239-
distanceMax,
240-
iterations,
241-
])
227+
}, [nodes, links, forces, iterations, setTransientNodes, setTransientLinks])
242228

243229
const [activeNodeIds, setActiveNodeIds] = useState<string[]>([])
244230

245-
const getNodeStyle = useNodeStyle<Node>({
231+
const getNodeStyle = useNodeStyle<Node, Link>({
246232
size: nodeSize,
247233
activeSize: activeNodeSize,
248234
inactiveSize: inactiveNodeSize,
@@ -252,39 +238,42 @@ export const useNetwork = <Node extends InputNode = InputNode>({
252238
isInteractive,
253239
activeNodeIds,
254240
})
255-
const enhancedNodes: ComputedNode<Node>[] | null = useMemo(() => {
256-
if (currentNodes === null) return null
241+
const computedNodes: ComputedNode<Node>[] | null = useMemo(() => {
242+
if (transientNodes === null) return null
257243

258-
return currentNodes.map(node => ({
244+
return transientNodes.map(node => ({
259245
...node,
260246
...getNodeStyle(node),
261247
}))
262-
}, [currentNodes, getNodeStyle])
248+
}, [transientNodes, getNodeStyle])
263249

264250
const theme = useTheme()
265251
const getLinkThickness = useDerivedProp(linkThickness)
266252
const getLinkColor = useInheritedColor(linkColor, theme)
267-
const enhancedLinks: ComputedLink<Node>[] | null = useMemo(() => {
268-
if (currentLinks === null || enhancedNodes === null) return null
269-
270-
return currentLinks.map(link => {
271-
const linkWithEnhancedNodes = {
272-
...link,
273-
source: enhancedNodes.find(node => node.id === link.source.id)!,
274-
target: enhancedNodes.find(node => node.id === link.target.id)!,
253+
254+
const computedLinks: ComputedLink<Node, Link>[] | null = useMemo(() => {
255+
if (transientLinks === null || computedNodes === null) return null
256+
257+
return transientLinks.map(({ index, ...link }) => {
258+
const linkWithComputedNodes: Omit<ComputedLink<Node, Link>, 'color' | 'thickness'> = {
259+
id: `${link.source.id}.${link.target.id}`,
260+
data: link.data,
261+
index,
262+
source: computedNodes.find(node => node.id === link.source.id)!,
263+
target: computedNodes.find(node => node.id === link.target.id)!,
275264
}
276265

277266
return {
278-
...linkWithEnhancedNodes,
279-
thickness: getLinkThickness(linkWithEnhancedNodes),
280-
color: getLinkColor(linkWithEnhancedNodes),
267+
...linkWithComputedNodes,
268+
thickness: getLinkThickness(linkWithComputedNodes),
269+
color: getLinkColor(linkWithComputedNodes),
281270
}
282271
})
283-
}, [currentLinks, getLinkThickness, getLinkColor, enhancedNodes])
272+
}, [transientLinks, computedNodes, getLinkThickness, getLinkColor])
284273

285274
return {
286-
nodes: enhancedNodes,
287-
links: enhancedLinks,
275+
nodes: computedNodes,
276+
links: computedLinks,
288277
setActiveNodeIds,
289278
}
290279
}

‎packages/network/src/renderCanvasLink.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { InputNode, ComputedLink } from './types'
1+
import { InputNode, ComputedLink, InputLink } from './types'
22

3-
export const renderCanvasLink = <Node extends InputNode>(
3+
export const renderCanvasLink = <Node extends InputNode, Link extends InputLink>(
44
ctx: CanvasRenderingContext2D,
5-
link: ComputedLink<Node>
5+
link: ComputedLink<Node, Link>
66
) => {
77
ctx.strokeStyle = link.color
88
ctx.lineWidth = link.thickness

‎packages/network/src/types.ts

+65-34
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,34 @@ import { Box, Theme, Dimensions, ModernMotionProps, CssMixBlendMode } from '@niv
44
import { InheritedColorConfig } from '@nivo/colors'
55
import { AnnotationMatcher } from '@nivo/annotations'
66

7+
// minimal node data
78
export interface InputNode {
89
id: string
910
}
1011

1112
export interface ComputedNode<Node extends InputNode> {
1213
id: string
1314
data: Node
15+
// computed by D3
16+
index: number
1417
x: number
1518
y: number
16-
index: number
19+
vx: number
20+
vy: number
21+
// styles computed by nivo
1722
size: number
1823
color: string
1924
borderWidth: number
2025
borderColor: string
2126
}
2227

28+
// intermediate type for D3 as it mutates nodes,
29+
// `ComputedNode` is used for the final data structure.
30+
export type TransientNode<Node extends InputNode> = Omit<
31+
ComputedNode<Node>,
32+
'size' | 'color' | 'borderWidth' | 'borderColor'
33+
>
34+
2335
export interface NodeAnimatedProps {
2436
x: number
2537
y: number
@@ -31,16 +43,18 @@ export interface NodeAnimatedProps {
3143
scale: number
3244
}
3345

34-
export interface NodeProps<Node extends InputNode> {
46+
export interface NodeProps<Node extends InputNode, Link extends InputLink> {
3547
node: ComputedNode<Node>
3648
animated: AnimatedProps<NodeAnimatedProps>
37-
blendMode: NonNullable<NetworkSvgProps<Node>['nodeBlendMode']>
49+
blendMode: NonNullable<NetworkSvgProps<Node, Link>['nodeBlendMode']>
3850
onClick?: (node: ComputedNode<Node>, event: MouseEvent) => void
3951
onMouseEnter?: (node: ComputedNode<Node>, event: MouseEvent) => void
4052
onMouseMove?: (node: ComputedNode<Node>, event: MouseEvent) => void
4153
onMouseLeave?: (node: ComputedNode<Node>, event: MouseEvent) => void
4254
}
43-
export type NodeComponent<Node extends InputNode> = FunctionComponent<NodeProps<Node>>
55+
export type NodeComponent<Node extends InputNode, Link extends InputLink> = FunctionComponent<
56+
NodeProps<Node, Link>
57+
>
4458
export type NodeCanvasRenderer<Node extends InputNode> = (
4559
ctx: CanvasRenderingContext2D,
4660
node: ComputedNode<Node>
@@ -51,16 +65,25 @@ export interface InputLink {
5165
target: string
5266
}
5367

54-
export interface ComputedLink<Node extends InputNode> {
68+
export interface ComputedLink<Node extends InputNode, Link extends InputLink> {
5569
id: string
70+
data: Link
71+
index: number
5672
source: ComputedNode<Node>
57-
previousSource?: ComputedNode<Node>
5873
target: ComputedNode<Node>
59-
previousTarget?: ComputedNode<Node>
6074
thickness: number
6175
color: string
6276
}
6377

78+
// intermediate type for D3 as it mutates links,
79+
// `ComputedLink` is used for the final data structure.
80+
export interface TransientLink<Node extends InputNode, Link extends InputLink> {
81+
data: Link
82+
index: number
83+
source: TransientNode<Node>
84+
target: TransientNode<Node>
85+
}
86+
6487
export interface LinkAnimatedProps {
6588
x1: number
6689
y1: number
@@ -70,33 +93,37 @@ export interface LinkAnimatedProps {
7093
opacity: number
7194
}
7295

73-
export interface LinkProps<Node extends InputNode> {
74-
link: ComputedLink<Node>
96+
export interface LinkProps<Node extends InputNode, Link extends InputLink> {
97+
link: ComputedLink<Node, Link>
7598
animated: AnimatedProps<LinkAnimatedProps>
76-
blendMode: NonNullable<NetworkSvgProps<Node>['linkBlendMode']>
99+
blendMode: NonNullable<NetworkSvgProps<Node, Link>['linkBlendMode']>
77100
}
78-
export type LinkComponent<Node extends InputNode> = FunctionComponent<LinkProps<Node>>
79-
export type LinkCanvasRenderer<Node extends InputNode> = (
101+
export type LinkComponent<Node extends InputNode, Link extends InputLink> = FunctionComponent<
102+
LinkProps<Node, Link>
103+
>
104+
export type LinkCanvasRenderer<Node extends InputNode, Link extends InputLink> = (
80105
ctx: CanvasRenderingContext2D,
81-
node: ComputedLink<Node>
106+
node: ComputedLink<Node, Link>
82107
) => void
83108

84-
export interface NetworkDataProps<Node extends InputNode> {
109+
export interface NetworkDataProps<Node extends InputNode, Link extends InputLink> {
85110
data: {
86111
nodes: Node[]
87-
links: InputLink[]
112+
links: Link[]
88113
}
89114
}
90115

91116
export type LayerId = 'links' | 'nodes' | 'annotations'
92-
export interface CustomLayerProps<Node extends InputNode> {
117+
export interface CustomLayerProps<Node extends InputNode, Link extends InputLink> {
93118
nodes: ComputedNode<Node>[]
94-
links: ComputedLink<Node>[]
119+
links: ComputedLink<Node, Link>[]
95120
}
96-
export type CustomLayer<Node extends InputNode> = FunctionComponent<CustomLayerProps<Node>>
97-
export type CustomCanvasLayer<Node extends InputNode> = (
121+
export type CustomLayer<Node extends InputNode, Link extends InputLink> = FunctionComponent<
122+
CustomLayerProps<Node, Link>
123+
>
124+
export type CustomCanvasLayer<Node extends InputNode, Link extends InputLink> = (
98125
ctx: CanvasRenderingContext2D,
99-
props: CustomLayerProps<Node>
126+
props: CustomLayerProps<Node, Link>
100127
) => void
101128

102129
export interface NodeTooltipProps<Node extends InputNode> {
@@ -108,10 +135,10 @@ export type DerivedProp<Target, Output extends number | string> =
108135
| Output
109136
| ((target: Target) => Output)
110137

111-
export type NetworkCommonProps<Node extends InputNode> = {
138+
export type NetworkCommonProps<Node extends InputNode, Link extends InputLink> = {
112139
margin: Box
113140

114-
linkDistance: DerivedProp<ComputedLink<Node>, number>
141+
linkDistance: DerivedProp<Link, number>
115142
repulsivity: number
116143
distanceMin: number
117144
distanceMax: number
@@ -128,9 +155,9 @@ export type NetworkCommonProps<Node extends InputNode> = {
128155
Omit<ComputedNode<Node>, 'size' | 'borderWidth' | 'borderColor'>
129156
>
130157

131-
linkThickness: DerivedProp<ComputedLink<Node>, number>
132-
activeLinkThickness: DerivedProp<ComputedLink<Node>, number>
133-
linkColor: InheritedColorConfig<ComputedLink<Node>>
158+
linkThickness: DerivedProp<Omit<ComputedLink<Node, Link>, 'color' | 'thickness'>, number>
159+
activeLinkThickness: DerivedProp<Omit<ComputedLink<Node, Link>, 'color' | 'thickness'>, number>
160+
linkColor: InheritedColorConfig<Omit<ComputedLink<Node, Link>, 'color' | 'thickness'>>
134161

135162
annotations: AnnotationMatcher<ComputedNode<Node>>[]
136163

@@ -147,21 +174,25 @@ export type NetworkCommonProps<Node extends InputNode> = {
147174
ariaDescribedBy: AriaAttributes['aria-describedby']
148175
} & Required<ModernMotionProps>
149176

150-
export type NetworkSvgProps<Node extends InputNode> = Partial<NetworkCommonProps<Node>> &
151-
NetworkDataProps<Node> &
177+
export type NetworkSvgProps<Node extends InputNode, Link extends InputLink> = Partial<
178+
NetworkCommonProps<Node, Link>
179+
> &
180+
NetworkDataProps<Node, Link> &
152181
Dimensions & {
153-
layers?: (LayerId | CustomLayer<Node>)[]
154-
nodeComponent?: NodeComponent<Node>
182+
layers?: (LayerId | CustomLayer<Node, Link>)[]
183+
nodeComponent?: NodeComponent<Node, Link>
155184
nodeBlendMode?: CssMixBlendMode
156-
linkComponent?: LinkComponent<Node>
185+
linkComponent?: LinkComponent<Node, Link>
157186
linkBlendMode?: CssMixBlendMode
158187
}
159188

160-
export type NetworkCanvasProps<Node extends InputNode> = Partial<NetworkCommonProps<Node>> &
161-
NetworkDataProps<Node> &
189+
export type NetworkCanvasProps<Node extends InputNode, Link extends InputLink> = Partial<
190+
NetworkCommonProps<Node, Link>
191+
> &
192+
NetworkDataProps<Node, Link> &
162193
Dimensions & {
163-
layers?: (LayerId | CustomCanvasLayer<Node>)[]
194+
layers?: (LayerId | CustomCanvasLayer<Node, Link>)[]
164195
renderNode?: NodeCanvasRenderer<Node>
165-
renderLink?: LinkCanvasRenderer<Node>
196+
renderLink?: LinkCanvasRenderer<Node, Link>
166197
pixelRatio?: number
167198
}

‎website/src/pages/network/index.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import mapper, {
1111
} from '../../data/components/network/mapper'
1212
import { groups } from '../../data/components/network/props'
1313

14+
type Node = ReturnType<typeof generateData>['nodes'][number]
15+
type Link = ReturnType<typeof generateData>['links'][number]
16+
1417
const initialProperties = Object.freeze({
1518
margin: {
1619
top: 0,
@@ -19,9 +22,10 @@ const initialProperties = Object.freeze({
1922
left: 0,
2023
},
2124

22-
linkDistance: 30,
23-
repulsivity: 3,
24-
iterations: 60,
25+
linkDistance: (link: Link) => link.distance,
26+
distanceMax: 50,
27+
repulsivity: defaults.repulsivity,
28+
iterations: defaults.iterations,
2529

2630
nodeSize: dynamicNodeSizeValue,
2731
activeNodeSize: dynamicActiveNodeSizeValue,
@@ -80,7 +84,7 @@ const Network = () => {
8084
>
8185
{(properties, data, theme, logAction) => {
8286
return (
83-
<ResponsiveNetwork
87+
<ResponsiveNetwork<Node, Link>
8488
data={data}
8589
{...properties}
8690
theme={theme}

0 commit comments

Comments
 (0)
Please sign in to comment.