Skip to content

Commit 2b26bf3

Browse files
committedDec 31, 2021
feat(network): add support for custom link component
1 parent 8e85cf1 commit 2b26bf3

8 files changed

+84
-75
lines changed
 

‎packages/network/src/Network.tsx

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Fragment, ReactNode, useCallback, createElement } from 'react'
2-
import { Container, useDimensions, SvgWrapper, useTheme } from '@nivo/core'
3-
import { useInheritedColor } from '@nivo/colors'
2+
import { Container, useDimensions, SvgWrapper } from '@nivo/core'
43
import { useTooltip } from '@nivo/tooltip'
54
import { svgDefaultProps } from './defaults'
6-
import { useNetwork, useLinkThickness } from './hooks'
5+
import { useNetwork } from './hooks'
76
import { NetworkNodes } from './NetworkNodes'
87
import { NetworkLinks } from './NetworkLinks'
98
import { NetworkInputNode, NetworkLayerId, NetworkSvgProps } from './types'
@@ -33,6 +32,7 @@ const InnerNetwork = <N extends NetworkInputNode>({
3332
nodeBorderWidth = svgDefaultProps.nodeBorderWidth,
3433
nodeBorderColor = svgDefaultProps.nodeBorderColor,
3534

35+
linkComponent = svgDefaultProps.linkComponent,
3636
linkThickness = svgDefaultProps.linkThickness,
3737
linkColor = svgDefaultProps.linkColor,
3838

@@ -48,10 +48,6 @@ const InnerNetwork = <N extends NetworkInputNode>({
4848
partialMargin
4949
)
5050

51-
const theme = useTheme()
52-
const getLinkThickness = useLinkThickness(linkThickness)
53-
const getLinkColor = useInheritedColor(linkColor, theme)
54-
5551
const [nodes, links] = useNetwork<N>({
5652
nodes: rawNodes,
5753
links: rawLinks,
@@ -64,6 +60,8 @@ const InnerNetwork = <N extends NetworkInputNode>({
6460
nodeColor,
6561
nodeBorderWidth,
6662
nodeBorderColor,
63+
linkThickness,
64+
linkColor,
6765
})
6866

6967
const { showTooltipFromEvent, hideTooltip } = useTooltip()
@@ -86,12 +84,7 @@ const InnerNetwork = <N extends NetworkInputNode>({
8684

8785
if (layers.includes('links') && links !== null) {
8886
layerById.links = (
89-
<NetworkLinks<N>
90-
key="links"
91-
links={links}
92-
linkThickness={getLinkThickness}
93-
linkColor={getLinkColor}
94-
/>
87+
<NetworkLinks<N> key="links" links={links} linkComponent={linkComponent} />
9588
)
9689
}
9790

‎packages/network/src/NetworkCanvas.tsx

+7-17
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { useCallback, useRef, useEffect, createElement, MouseEvent } from 'react'
22
import { getDistance, getRelativeCursor, Container, useDimensions, useTheme } from '@nivo/core'
3-
import { useInheritedColor } from '@nivo/colors'
43
import { useTooltip } from '@nivo/tooltip'
54
import { canvasDefaultProps } from './defaults'
6-
import { useNetwork, useLinkThickness } from './hooks'
5+
import { useNetwork } from './hooks'
76
import { NetworkCanvasProps, NetworkInputNode } from './types'
87

98
type InnerNetworkCanvasProps<N extends NetworkInputNode> = Omit<
@@ -32,6 +31,7 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
3231
nodeBorderWidth = canvasDefaultProps.nodeBorderWidth,
3332
nodeBorderColor = canvasDefaultProps.nodeBorderColor,
3433

34+
renderLink = canvasDefaultProps.renderLink,
3535
linkThickness = canvasDefaultProps.linkThickness,
3636
linkColor = canvasDefaultProps.linkColor,
3737

@@ -58,11 +58,11 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
5858
nodeColor,
5959
nodeBorderWidth,
6060
nodeBorderColor,
61+
linkThickness,
62+
linkColor,
6163
})
6264

6365
const theme = useTheme()
64-
const getLinkThickness = useLinkThickness(linkThickness)
65-
const getLinkColor = useInheritedColor(linkColor, theme)
6666

6767
useEffect(() => {
6868
if (canvasEl.current === null) return
@@ -80,18 +80,9 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
8080

8181
layers.forEach(layer => {
8282
if (layer === 'links' && links !== null) {
83-
links.forEach(link => {
84-
ctx.strokeStyle = getLinkColor(link)
85-
ctx.lineWidth = getLinkThickness(link)
86-
ctx.beginPath()
87-
ctx.moveTo(link.source.x, link.source.y)
88-
ctx.lineTo(link.target.x, link.target.y)
89-
ctx.stroke()
90-
})
83+
links.forEach(link => renderLink(ctx, link))
9184
} else if (layer === 'nodes' && nodes !== null) {
92-
nodes.forEach(node => {
93-
renderNode(ctx, node)
94-
})
85+
nodes.forEach(node => renderNode(ctx, node))
9586
} else if (typeof layer === 'function' && nodes !== null && links !== null) {
9687
layer(ctx, {
9788
// ...props,
@@ -112,8 +103,7 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
112103
nodes,
113104
links,
114105
renderNode,
115-
getLinkThickness,
116-
getLinkColor,
106+
renderLink,
117107
])
118108

119109
const getNodeFromMouseEvent = useCallback(

‎packages/network/src/NetworkLink.tsx

+4-16
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
1-
import { AnimatedProps, animated } from '@react-spring/web'
2-
import { ComputedLink, NetworkInputNode } from './types'
3-
4-
interface NetworkLinkProps<N extends NetworkInputNode> {
5-
link: ComputedLink<N>
6-
thickness: number
7-
animated: AnimatedProps<{
8-
x1: number
9-
y1: number
10-
x2: number
11-
y2: number
12-
color: string
13-
}>
14-
}
1+
import { animated } from '@react-spring/web'
2+
import { NetworkInputNode, NetworkLinkProps } from './types'
153

164
export const NetworkLink = <N extends NetworkInputNode>({
17-
thickness,
5+
link,
186
animated: animatedProps,
197
}: NetworkLinkProps<N>) => {
208
return (
219
<animated.line
2210
stroke={animatedProps.color}
23-
strokeWidth={thickness}
11+
strokeWidth={link.thickness}
2412
strokeLinecap="round"
2513
x1={animatedProps.x1}
2614
y1={animatedProps.y1}

‎packages/network/src/NetworkLinks.tsx

+12-26
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,49 @@
11
import { createElement, useMemo } from 'react'
22
import { useTransition } from '@react-spring/web'
33
import { useMotionConfig } from '@nivo/core'
4-
import { NetworkLink } from './NetworkLink'
5-
import { ComputedLink, NetworkInputNode } from './types'
4+
import { ComputedLink, NetworkInputNode, NetworkLinkComponent } from './types'
65

76
interface NetworkLinksProps<N extends NetworkInputNode> {
87
links: ComputedLink<N>[]
9-
linkThickness: (link: ComputedLink<N>) => number
10-
linkColor: (link: ComputedLink<N>) => string
8+
linkComponent: NetworkLinkComponent<N>
119
}
1210

13-
const getEnterTransition = <N extends NetworkInputNode>(
14-
linkColor: NetworkLinksProps<N>['linkColor']
15-
) => (link: ComputedLink<N>) => ({
11+
const getEnterTransition = <N extends NetworkInputNode>() => (link: ComputedLink<N>) => ({
1612
x1: link.source.x,
1713
y1: link.source.y,
1814
x2: link.source.x,
1915
y2: link.source.y,
20-
color: linkColor(link),
16+
color: link.color,
2117
opacity: 0,
2218
})
2319

24-
const getRegularTransition = <N extends NetworkInputNode>(
25-
linkColor: NetworkLinksProps<N>['linkColor']
26-
) => (link: ComputedLink<N>) => ({
20+
const getRegularTransition = <N extends NetworkInputNode>() => (link: ComputedLink<N>) => ({
2721
x1: link.source.x,
2822
y1: link.source.y,
2923
x2: link.target.x,
3024
y2: link.target.y,
31-
color: linkColor(link),
25+
color: link.color,
3226
opacity: 1,
3327
})
3428

35-
const getExitTransition = <N extends NetworkInputNode>(
36-
linkColor: NetworkLinksProps<N>['linkColor']
37-
) => (link: ComputedLink<N>) => ({
29+
const getExitTransition = <N extends NetworkInputNode>() => (link: ComputedLink<N>) => ({
3830
x1: link.source.x,
3931
y1: link.source.y,
4032
x2: link.source.x,
4133
y2: link.source.y,
42-
color: linkColor(link),
34+
color: link.color,
4335
opacity: 0,
4436
})
4537

4638
export const NetworkLinks = <N extends NetworkInputNode>({
4739
links,
48-
linkThickness,
49-
linkColor,
40+
linkComponent,
5041
}: NetworkLinksProps<N>) => {
5142
const { animate, config: springConfig } = useMotionConfig()
5243

5344
const [enterTransition, regularTransition, exitTransition] = useMemo(
54-
() => [
55-
getEnterTransition<N>(linkColor),
56-
getRegularTransition<N>(linkColor),
57-
getExitTransition<N>(linkColor),
58-
],
59-
[linkColor]
45+
() => [getEnterTransition<N>(), getRegularTransition<N>(), getExitTransition<N>()],
46+
[]
6047
)
6148

6249
const transition = useTransition<
@@ -84,10 +71,9 @@ export const NetworkLinks = <N extends NetworkInputNode>({
8471
return (
8572
<>
8673
{transition((transitionProps, link) => {
87-
return createElement(NetworkLink, {
74+
return createElement(linkComponent, {
8875
key: link.id,
8976
link,
90-
thickness: linkThickness(link),
9177
animated: transitionProps,
9278
})
9379
})}

‎packages/network/src/defaults.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { NetworkLayerId } from './types'
22
import { NetworkNode } from './NetworkNode'
33
import { renderCanvasNode } from './renderCanvasNode'
4+
import { NetworkLink } from './NetworkLink'
5+
import { renderCanvasLink } from './renderCanvasLink'
46
import { NetworkNodeTooltip } from './NetworkNodeTooltip'
57

68
export const commonDefaultProps = {
@@ -31,10 +33,12 @@ export const commonDefaultProps = {
3133
export const svgDefaultProps = {
3234
...commonDefaultProps,
3335
nodeComponent: NetworkNode,
36+
linkComponent: NetworkLink,
3437
}
3538

3639
export const canvasDefaultProps = {
3740
...commonDefaultProps,
3841
renderNode: renderCanvasNode,
42+
renderLink: renderCanvasLink,
3943
pixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,
4044
}

‎packages/network/src/hooks.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export const useNetwork = <N extends NetworkInputNode = NetworkInputNode>({
6363
nodeColor,
6464
nodeBorderWidth,
6565
nodeBorderColor,
66+
linkThickness,
67+
linkColor,
6668
}: {
6769
nodes: N[]
6870
links: InputLink[]
@@ -75,6 +77,8 @@ export const useNetwork = <N extends NetworkInputNode = NetworkInputNode>({
7577
nodeColor: NetworkCommonProps<N>['nodeColor']
7678
nodeBorderWidth: NetworkCommonProps<N>['nodeBorderWidth']
7779
nodeBorderColor: NetworkCommonProps<N>['nodeBorderColor']
80+
linkThickness: NetworkCommonProps<N>['linkThickness']
81+
linkColor: NetworkCommonProps<N>['linkColor']
7882
}): [null | NetworkComputedNode<N>[], null | ComputedLink<N>[]] => {
7983
const [currentNodes, setCurrentNodes] = useState<null | NetworkComputedNode<N>[]>(null)
8084
const [currentLinks, setCurrentLinks] = useState<null | ComputedLink<N>[]>(null)
@@ -135,6 +139,8 @@ export const useNetwork = <N extends NetworkInputNode = NetworkInputNode>({
135139
const theme = useTheme()
136140
const getNodeColor = useNodeColor<N>(nodeColor)
137141
const getNodeBorderColor = useInheritedColor(nodeBorderColor, theme)
142+
const getLinkThickness = useLinkThickness<N>(linkThickness)
143+
const getLinkColor = useInheritedColor(linkColor, theme)
138144

139145
const enhancedNodes: NetworkComputedNode<N>[] | null = useMemo(() => {
140146
if (currentNodes === null) return null
@@ -149,7 +155,19 @@ export const useNetwork = <N extends NetworkInputNode = NetworkInputNode>({
149155
})
150156
}, [currentNodes, getNodeColor, nodeBorderWidth, getNodeBorderColor])
151157

152-
return [enhancedNodes, currentLinks]
158+
const enhancedLinks: ComputedLink<N>[] | null = useMemo(() => {
159+
if (currentLinks === null) return null
160+
161+
return currentLinks.map(link => {
162+
return {
163+
...link,
164+
thickness: getLinkThickness(link),
165+
color: getLinkColor(link),
166+
}
167+
})
168+
}, [currentLinks, getLinkThickness, getLinkColor])
169+
170+
return [enhancedNodes, enhancedLinks]
153171
}
154172

155173
export const useNodeColor = <N extends NetworkInputNode>(color: NetworkNodeColor<N>) =>
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { NetworkInputNode, ComputedLink } from './types'
2+
3+
export const renderCanvasLink = <N extends NetworkInputNode>(
4+
ctx: CanvasRenderingContext2D,
5+
link: ComputedLink<N>
6+
) => {
7+
ctx.strokeStyle = link.color
8+
ctx.lineWidth = link.thickness
9+
10+
ctx.beginPath()
11+
ctx.moveTo(link.source.x, link.source.y)
12+
ctx.lineTo(link.target.x, link.target.y)
13+
ctx.stroke()
14+
}

‎packages/network/src/types.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@ export type NetworkNodeCanvasRenderer<N extends NetworkInputNode> = (
4949
export interface InputLink {
5050
source: string
5151
target: string
52-
[key: string]: unknown
5352
}
5453

5554
export interface ComputedLink<N extends NetworkInputNode> {
5655
id: string
5756
source: NetworkComputedNode<N>
57+
previousSource?: NetworkComputedNode<N>
5858
target: NetworkComputedNode<N>
59-
[key: string]: unknown
59+
previousTarget?: NetworkComputedNode<N>
60+
thickness: number
61+
color: string
6062
}
6163

6264
export interface LinkAnimatedProps {
@@ -68,6 +70,18 @@ export interface LinkAnimatedProps {
6870
opacity: number
6971
}
7072

73+
export interface NetworkLinkProps<N extends NetworkInputNode> {
74+
link: ComputedLink<N>
75+
animated: AnimatedProps<LinkAnimatedProps>
76+
}
77+
export type NetworkLinkComponent<N extends NetworkInputNode> = FunctionComponent<
78+
NetworkLinkProps<N>
79+
>
80+
export type NetworkLinkCanvasRenderer<N extends NetworkInputNode> = (
81+
ctx: CanvasRenderingContext2D,
82+
node: ComputedLink<N>
83+
) => void
84+
7185
export interface NetworkDataProps<N extends NetworkInputNode> {
7286
data: {
7387
nodes: N[]
@@ -145,6 +159,7 @@ export type NetworkSvgProps<N extends NetworkInputNode> = Partial<NetworkCommonP
145159
ModernMotionProps & {
146160
layers?: (NetworkLayerId | NetworkCustomLayer<N>)[]
147161
nodeComponent?: NetworkNodeComponent<N>
162+
linkComponent?: NetworkLinkComponent<N>
148163
}
149164

150165
export type NetworkCanvasProps<N extends NetworkInputNode> = Partial<NetworkCommonProps<N>> &
@@ -154,5 +169,6 @@ export type NetworkCanvasProps<N extends NetworkInputNode> = Partial<NetworkComm
154169
ModernMotionProps & {
155170
layers?: (NetworkLayerId | NetworkCustomCanvasLayer<N>)[]
156171
renderNode?: NetworkNodeCanvasRenderer<N>
172+
renderLink?: NetworkLinkCanvasRenderer<N>
157173
pixelRatio?: number
158174
}

0 commit comments

Comments
 (0)
Please sign in to comment.