Skip to content

Commit 8e85cf1

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

File tree

8 files changed

+112
-62
lines changed

8 files changed

+112
-62
lines changed
 

‎packages/network/src/Network.tsx

+4-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Container, useDimensions, SvgWrapper, useTheme } from '@nivo/core'
33
import { useInheritedColor } from '@nivo/colors'
44
import { useTooltip } from '@nivo/tooltip'
55
import { svgDefaultProps } from './defaults'
6-
import { useNetwork, useNodeColor, useLinkThickness } from './hooks'
6+
import { useNetwork, useLinkThickness } from './hooks'
77
import { NetworkNodes } from './NetworkNodes'
88
import { NetworkLinks } from './NetworkLinks'
99
import { NetworkInputNode, NetworkLayerId, NetworkSvgProps } from './types'
@@ -49,8 +49,6 @@ const InnerNetwork = <N extends NetworkInputNode>({
4949
)
5050

5151
const theme = useTheme()
52-
const getColor = useNodeColor<N>(nodeColor)
53-
const getBorderColor = useInheritedColor(nodeBorderColor, theme)
5452
const getLinkThickness = useLinkThickness(linkThickness)
5553
const getLinkColor = useInheritedColor(linkColor, theme)
5654

@@ -63,6 +61,9 @@ const InnerNetwork = <N extends NetworkInputNode>({
6361
distanceMax,
6462
iterations,
6563
center: [innerWidth / 2, innerHeight / 2],
64+
nodeColor,
65+
nodeBorderWidth,
66+
nodeBorderColor,
6667
})
6768

6869
const { showTooltipFromEvent, hideTooltip } = useTooltip()
@@ -100,9 +101,6 @@ const InnerNetwork = <N extends NetworkInputNode>({
100101
key="nodes"
101102
nodes={nodes}
102103
nodeComponent={nodeComponent}
103-
color={getColor}
104-
borderWidth={nodeBorderWidth}
105-
borderColor={getBorderColor}
106104
onClick={isInteractive ? onClick : undefined}
107105
onMouseEnter={isInteractive ? handleNodeHover : undefined}
108106
onMouseMove={isInteractive ? handleNodeHover : undefined}

‎packages/network/src/NetworkCanvas.tsx

+7-16
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getDistance, getRelativeCursor, Container, useDimensions, useTheme } fr
33
import { useInheritedColor } from '@nivo/colors'
44
import { useTooltip } from '@nivo/tooltip'
55
import { canvasDefaultProps } from './defaults'
6-
import { useNetwork, useNodeColor, useLinkThickness } from './hooks'
6+
import { useNetwork, useLinkThickness } from './hooks'
77
import { NetworkCanvasProps, NetworkInputNode } from './types'
88

99
type InnerNetworkCanvasProps<N extends NetworkInputNode> = Omit<
@@ -27,6 +27,7 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
2727

2828
layers = canvasDefaultProps.layers,
2929

30+
renderNode = canvasDefaultProps.renderNode,
3031
nodeColor = canvasDefaultProps.nodeColor,
3132
nodeBorderWidth = canvasDefaultProps.nodeBorderWidth,
3233
nodeBorderColor = canvasDefaultProps.nodeBorderColor,
@@ -54,11 +55,12 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
5455
distanceMax,
5556
iterations,
5657
center: [innerWidth / 2, innerHeight / 2],
58+
nodeColor,
59+
nodeBorderWidth,
60+
nodeBorderColor,
5761
})
5862

5963
const theme = useTheme()
60-
const getNodeColor = useNodeColor(nodeColor)
61-
const getBorderColor = useInheritedColor(nodeBorderColor, theme)
6264
const getLinkThickness = useLinkThickness(linkThickness)
6365
const getLinkColor = useInheritedColor(linkColor, theme)
6466

@@ -88,16 +90,7 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
8890
})
8991
} else if (layer === 'nodes' && nodes !== null) {
9092
nodes.forEach(node => {
91-
ctx.fillStyle = getNodeColor(node)
92-
ctx.beginPath()
93-
ctx.arc(node.x, node.y, node.radius, 0, 2 * Math.PI)
94-
ctx.fill()
95-
96-
if (nodeBorderWidth > 0) {
97-
ctx.strokeStyle = getBorderColor(node)
98-
ctx.lineWidth = nodeBorderWidth
99-
ctx.stroke()
100-
}
93+
renderNode(ctx, node)
10194
})
10295
} else if (typeof layer === 'function' && nodes !== null && links !== null) {
10396
layer(ctx, {
@@ -118,9 +111,7 @@ const InnerNetworkCanvas = <N extends NetworkInputNode>({
118111
theme,
119112
nodes,
120113
links,
121-
getNodeColor,
122-
nodeBorderWidth,
123-
getBorderColor,
114+
renderNode,
124115
getLinkThickness,
125116
getLinkColor,
126117
])

‎packages/network/src/NetworkNodes.tsx

+14-37
Original file line numberDiff line numberDiff line change
@@ -5,70 +5,51 @@ import {
55
NetworkComputedNode,
66
NetworkInputNode,
77
NetworkNodeAnimatedProps,
8-
NetworkNodeColor,
98
NetworkNodeComponent,
109
} from './types'
1110

1211
interface NetworkNodesProps<N extends NetworkInputNode> {
1312
nodes: NetworkComputedNode<N>[]
1413
nodeComponent: NetworkNodeComponent<N>
15-
color: Exclude<NetworkNodeColor<N>, string>
16-
borderWidth: number
17-
borderColor: (node: NetworkComputedNode<N>) => string
1814
onClick?: (node: NetworkComputedNode<N>, event: MouseEvent) => void
1915
onMouseEnter?: (node: NetworkComputedNode<N>, event: MouseEvent) => void
2016
onMouseMove?: (node: NetworkComputedNode<N>, event: MouseEvent) => void
2117
onMouseLeave?: (node: NetworkComputedNode<N>, event: MouseEvent) => void
2218
}
2319

24-
const getEnterTransition = <N extends NetworkInputNode>(
25-
color: NetworkNodesProps<N>['color'],
26-
borderWidth: number,
27-
borderColor: NetworkNodesProps<N>['borderColor']
28-
) => (node: NetworkComputedNode<N>) => ({
20+
const getEnterTransition = <N extends NetworkInputNode>() => (node: NetworkComputedNode<N>) => ({
2921
x: node.x,
3022
y: node.y,
3123
radius: node.radius,
32-
color: color(node),
33-
borderWidth,
34-
borderColor: borderColor(node),
24+
color: node.color,
25+
borderWidth: node.borderWidth,
26+
borderColor: node.borderColor,
3527
scale: 0,
3628
})
3729

38-
const getRegularTransition = <N extends NetworkInputNode>(
39-
color: NetworkNodesProps<N>['color'],
40-
borderWidth: number,
41-
borderColor: NetworkNodesProps<N>['borderColor']
42-
) => (node: NetworkComputedNode<N>) => ({
30+
const getRegularTransition = <N extends NetworkInputNode>() => (node: NetworkComputedNode<N>) => ({
4331
x: node.x,
4432
y: node.y,
4533
radius: node.radius,
46-
color: color(node),
47-
borderWidth,
48-
borderColor: borderColor(node),
34+
color: node.color,
35+
borderWidth: node.borderWidth,
36+
borderColor: node.borderColor,
4937
scale: 1,
5038
})
5139

52-
const getExitTransition = <N extends NetworkInputNode>(
53-
color: NetworkNodesProps<N>['color'],
54-
borderWidth: number,
55-
borderColor: NetworkNodesProps<N>['borderColor']
56-
) => (node: NetworkComputedNode<N>) => ({
40+
const getExitTransition = <N extends NetworkInputNode>() => (node: NetworkComputedNode<N>) => ({
5741
x: node.x,
5842
y: node.y,
5943
radius: node.radius,
60-
color: color(node),
61-
borderWidth,
62-
borderColor: borderColor(node),
44+
color: node.color,
45+
borderWidth: node.borderWidth,
46+
borderColor: node.borderColor,
6347
scale: 0,
6448
})
6549

6650
export const NetworkNodes = <N extends NetworkInputNode>({
6751
nodes,
6852
nodeComponent,
69-
color,
70-
borderColor,
71-
borderWidth,
7253
onClick,
7354
onMouseEnter,
7455
onMouseMove,
@@ -77,12 +58,8 @@ export const NetworkNodes = <N extends NetworkInputNode>({
7758
const { animate, config: springConfig } = useMotionConfig()
7859

7960
const [enterTransition, regularTransition, exitTransition] = useMemo(
80-
() => [
81-
getEnterTransition(color, borderWidth, borderColor),
82-
getRegularTransition(color, borderWidth, borderColor),
83-
getExitTransition(color, borderWidth, borderColor),
84-
],
85-
[color, borderWidth, borderColor]
61+
() => [getEnterTransition<N>(), getRegularTransition<N>(), getExitTransition<N>()],
62+
[]
8663
)
8764

8865
const transition = useTransition<NetworkComputedNode<N>, NetworkNodeAnimatedProps>(nodes, {

‎packages/network/src/defaults.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { NetworkLayerId } from './types'
12
import { NetworkNode } from './NetworkNode'
3+
import { renderCanvasNode } from './renderCanvasNode'
24
import { NetworkNodeTooltip } from './NetworkNodeTooltip'
3-
import { NetworkLayerId } from './types'
45

56
export const commonDefaultProps = {
67
layers: ['links', 'nodes'] as NetworkLayerId[],
@@ -34,5 +35,6 @@ export const svgDefaultProps = {
3435

3536
export const canvasDefaultProps = {
3637
...commonDefaultProps,
38+
renderNode: renderCanvasNode,
3739
pixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,
3840
}

‎packages/network/src/hooks.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import get from 'lodash/get'
33
import isString from 'lodash/isString'
44
import isNumber from 'lodash/isNumber'
55
import { forceSimulation, forceManyBody, forceCenter, forceLink } from 'd3-force'
6+
import { useTheme } from '@nivo/core'
67
import {
78
InputLink,
89
NetworkInputNode,
@@ -12,6 +13,7 @@ import {
1213
NetworkComputedNode,
1314
ComputedLink,
1415
} from './types'
16+
import { useInheritedColor } from '@nivo/colors'
1517

1618
const computeForces = <N extends NetworkInputNode>({
1719
linkDistance,
@@ -58,6 +60,9 @@ export const useNetwork = <N extends NetworkInputNode = NetworkInputNode>({
5860
distanceMax,
5961
center,
6062
iterations,
63+
nodeColor,
64+
nodeBorderWidth,
65+
nodeBorderColor,
6166
}: {
6267
nodes: N[]
6368
links: InputLink[]
@@ -67,6 +72,9 @@ export const useNetwork = <N extends NetworkInputNode = NetworkInputNode>({
6772
distanceMax: NetworkCommonProps<N>['distanceMax']
6873
center: [number, number]
6974
iterations: NetworkCommonProps<N>['iterations']
75+
nodeColor: NetworkCommonProps<N>['nodeColor']
76+
nodeBorderWidth: NetworkCommonProps<N>['nodeBorderWidth']
77+
nodeBorderColor: NetworkCommonProps<N>['nodeBorderColor']
7078
}): [null | NetworkComputedNode<N>[], null | ComputedLink<N>[]] => {
7179
const [currentNodes, setCurrentNodes] = useState<null | NetworkComputedNode<N>[]>(null)
7280
const [currentLinks, setCurrentLinks] = useState<null | ComputedLink<N>[]>(null)
@@ -124,7 +132,24 @@ export const useNetwork = <N extends NetworkInputNode = NetworkInputNode>({
124132
center[1],
125133
])
126134

127-
return [currentNodes, currentLinks]
135+
const theme = useTheme()
136+
const getNodeColor = useNodeColor<N>(nodeColor)
137+
const getNodeBorderColor = useInheritedColor(nodeBorderColor, theme)
138+
139+
const enhancedNodes: NetworkComputedNode<N>[] | null = useMemo(() => {
140+
if (currentNodes === null) return null
141+
142+
return currentNodes.map(node => {
143+
return {
144+
...node,
145+
color: getNodeColor(node),
146+
borderWidth: nodeBorderWidth,
147+
borderColor: getNodeBorderColor(node),
148+
}
149+
})
150+
}, [currentNodes, getNodeColor, nodeBorderWidth, getNodeBorderColor])
151+
152+
return [enhancedNodes, currentLinks]
128153
}
129154

130155
export const useNodeColor = <N extends NetworkInputNode>(color: NetworkNodeColor<N>) =>
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { NetworkComputedNode, NetworkInputNode } from './types'
2+
3+
export const renderCanvasNode = <N extends NetworkInputNode>(
4+
ctx: CanvasRenderingContext2D,
5+
node: NetworkComputedNode<N>
6+
) => {
7+
ctx.fillStyle = node.color
8+
ctx.beginPath()
9+
ctx.arc(node.x, node.y, node.radius, 0, 2 * Math.PI)
10+
ctx.fill()
11+
12+
if (node.borderWidth > 0) {
13+
ctx.strokeStyle = node.borderColor
14+
ctx.lineWidth = node.borderWidth
15+
ctx.stroke()
16+
}
17+
}

‎packages/network/src/types.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ export interface NetworkComputedNode<N extends NetworkInputNode> {
1313
y: number
1414
radius: number
1515
color: string
16+
borderWidth: number
17+
borderColor: string
1618
data: N
17-
[key: string]: unknown
1819
}
1920

2021
export interface NetworkNodeAnimatedProps {
@@ -40,6 +41,10 @@ export interface NetworkNodeProps<N extends NetworkInputNode> {
4041
export type NetworkNodeComponent<N extends NetworkInputNode> = FunctionComponent<
4142
NetworkNodeProps<N>
4243
>
44+
export type NetworkNodeCanvasRenderer<N extends NetworkInputNode> = (
45+
ctx: CanvasRenderingContext2D,
46+
node: NetworkComputedNode<N>
47+
) => void
4348

4449
export interface InputLink {
4550
source: string
@@ -148,5 +153,6 @@ export type NetworkCanvasProps<N extends NetworkInputNode> = Partial<NetworkComm
148153
// only used by tooltips
149154
ModernMotionProps & {
150155
layers?: (NetworkLayerId | NetworkCustomCanvasLayer<N>)[]
156+
renderNode?: NetworkNodeCanvasRenderer<N>
151157
pixelRatio?: number
152158
}

‎website/src/data/components/network/props.ts

+34
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,23 @@ const props: ChartProperty[] = [
122122
},
123123
},
124124
themeProperty(['svg', 'canvas']),
125+
{
126+
key: 'nodeComponent',
127+
group: 'Nodes',
128+
type: 'NetworkNodeComponent',
129+
required: false,
130+
help: `Custom node component for the SVG implementation.`,
131+
flavors: ['svg'],
132+
defaultValue: 'NetworkNode',
133+
},
134+
{
135+
key: 'renderNode',
136+
group: 'Nodes',
137+
type: 'NetworkNodeCanvasRenderer',
138+
required: false,
139+
help: `Custom node rendering for the canvas implementation.`,
140+
flavors: ['canvas'],
141+
},
125142
{
126143
key: 'nodeColor',
127144
group: 'Nodes',
@@ -168,6 +185,23 @@ const props: ChartProperty[] = [
168185
controlType: 'inheritedColor',
169186
>>>>>>> feat(network): types are now valid
170187
},
188+
{
189+
key: 'linkComponent',
190+
group: 'Links',
191+
type: 'NetworkLinkComponent',
192+
required: false,
193+
help: `Custom link component for the SVG implementation.`,
194+
flavors: ['svg'],
195+
defaultValue: 'NetworkLink',
196+
},
197+
{
198+
key: 'renderLink',
199+
group: 'Links',
200+
type: 'NetworkLinkCanvasRenderer',
201+
required: false,
202+
help: `Custom link rendering for the canvas implementation.`,
203+
flavors: ['canvas'],
204+
},
171205
{
172206
key: 'linkThickness',
173207
enableControlForFlavors: ['canvas'],

0 commit comments

Comments
 (0)
Please sign in to comment.