Skip to content

Commit ca5ee4e

Browse files
committedDec 31, 2021
feat(network): add missing tests
1 parent aed3590 commit ca5ee4e

File tree

9 files changed

+547
-78
lines changed

9 files changed

+547
-78
lines changed
 

‎packages/network/src/Network.tsx

+35-35
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
import { Fragment, ReactNode, useCallback, createElement } from 'react'
1+
import { Fragment, ReactNode, createElement, useMemo } from 'react'
22
import { Container, useDimensions, SvgWrapper } from '@nivo/core'
3-
import { useTooltip } from '@nivo/tooltip'
43
import { svgDefaultProps } from './defaults'
54
import { useNetwork } from './hooks'
65
import { NetworkLinks } from './NetworkLinks'
76
import { NetworkNodes } from './NetworkNodes'
87
import { NetworkNodeAnnotations } from './NetworkNodeAnnotations'
9-
import { InputNode, LayerId, NodeTooltip, NetworkSvgProps, ComputedNode, InputLink } from './types'
8+
import {
9+
InputNode,
10+
LayerId,
11+
NodeTooltip,
12+
NetworkSvgProps,
13+
InputLink,
14+
CustomLayerProps,
15+
} from './types'
1016

1117
type InnerNetworkProps<Node extends InputNode, Link extends InputLink> = Omit<
1218
NetworkSvgProps<Node, Link>,
@@ -51,7 +57,11 @@ const InnerNetwork = <Node extends InputNode, Link extends InputLink>({
5157
>,
5258

5359
isInteractive = svgDefaultProps.isInteractive,
60+
defaultActiveNodeIds = svgDefaultProps.defaultActiveNodeIds,
5461
nodeTooltip = svgDefaultProps.nodeTooltip as NodeTooltip<Node>,
62+
onMouseEnter,
63+
onMouseMove,
64+
onMouseLeave,
5565
onClick,
5666

5767
role = svgDefaultProps.role,
@@ -65,7 +75,7 @@ const InnerNetwork = <Node extends InputNode, Link extends InputLink>({
6575
partialMargin
6676
)
6777

68-
const { nodes, links, setActiveNodeIds } = useNetwork<Node, Link>({
78+
const { nodes, links, activeNodeIds, setActiveNodeIds } = useNetwork<Node, Link>({
6979
center: [innerWidth / 2, innerHeight / 2],
7080
nodes: rawNodes,
7181
links: rawLinks,
@@ -83,23 +93,10 @@ const InnerNetwork = <Node extends InputNode, Link extends InputLink>({
8393
nodeBorderColor,
8494
linkThickness,
8595
linkColor,
96+
isInteractive,
97+
defaultActiveNodeIds,
8698
})
8799

88-
const { showTooltipFromEvent, hideTooltip } = useTooltip()
89-
90-
const handleNodeHover = useCallback(
91-
(node: ComputedNode<Node>, event) => {
92-
showTooltipFromEvent(createElement(nodeTooltip, { node }), event)
93-
setActiveNodeIds([node.id])
94-
},
95-
[showTooltipFromEvent, nodeTooltip, setActiveNodeIds]
96-
)
97-
98-
const handleNodeLeave = useCallback(() => {
99-
hideTooltip()
100-
setActiveNodeIds([])
101-
}, [hideTooltip, setActiveNodeIds])
102-
103100
const layerById: Record<LayerId, ReactNode> = {
104101
links: null,
105102
nodes: null,
@@ -119,14 +116,17 @@ const InnerNetwork = <Node extends InputNode, Link extends InputLink>({
119116

120117
if (layers.includes('nodes') && nodes !== null) {
121118
layerById.nodes = (
122-
<NetworkNodes<Node>
119+
<NetworkNodes<Node, Link>
123120
key="nodes"
124121
nodes={nodes}
125122
nodeComponent={nodeComponent}
126-
onClick={isInteractive ? onClick : undefined}
127-
onMouseEnter={isInteractive ? handleNodeHover : undefined}
128-
onMouseMove={isInteractive ? handleNodeHover : undefined}
129-
onMouseLeave={isInteractive ? handleNodeLeave : undefined}
123+
onMouseEnter={onMouseEnter}
124+
onMouseMove={onMouseMove}
125+
onMouseLeave={onMouseLeave}
126+
onClick={onClick}
127+
tooltip={nodeTooltip}
128+
setActiveNodeIds={setActiveNodeIds}
129+
isInteractive={isInteractive}
130130
/>
131131
)
132132
}
@@ -141,6 +141,16 @@ const InnerNetwork = <Node extends InputNode, Link extends InputLink>({
141141
)
142142
}
143143

144+
const customLayerProps: CustomLayerProps<Node, Link> = useMemo(
145+
() => ({
146+
nodes: nodes || [],
147+
links: links || [],
148+
activeNodeIds,
149+
setActiveNodeIds,
150+
}),
151+
[nodes, links, activeNodeIds, setActiveNodeIds]
152+
)
153+
144154
return (
145155
<SvgWrapper
146156
width={outerWidth}
@@ -153,17 +163,7 @@ const InnerNetwork = <Node extends InputNode, Link extends InputLink>({
153163
>
154164
{layers.map((layer, i) => {
155165
if (typeof layer === 'function') {
156-
return (
157-
<Fragment key={i}>
158-
{createElement(layer, {
159-
// ...props,
160-
// innerWidth,
161-
// innerHeight,
162-
nodes: nodes || [],
163-
links: links || [],
164-
})}
165-
</Fragment>
166-
)
166+
return <Fragment key={i}>{createElement(layer, customLayerProps)}</Fragment>
167167
}
168168

169169
return layerById?.[layer] ?? null

‎packages/network/src/NetworkCanvas.tsx

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useRef, useEffect, createElement, MouseEvent } from 'react'
1+
import { useCallback, useRef, useEffect, createElement, MouseEvent, useMemo } from 'react'
22
import { getDistance, getRelativeCursor, Container, useDimensions, useTheme } from '@nivo/core'
33
import { useTooltip } from '@nivo/tooltip'
44
import { useComputedAnnotations, renderAnnotationsToCanvas } from '@nivo/annotations'
@@ -11,6 +11,7 @@ import {
1111
NodeTooltip,
1212
InputLink,
1313
NetworkSvgProps,
14+
CustomLayerProps,
1415
} from './types'
1516

1617
type InnerNetworkCanvasProps<Node extends InputNode, Link extends InputLink> = Omit<
@@ -52,6 +53,7 @@ const InnerNetworkCanvas = <Node extends InputNode, Link extends InputLink>({
5253
>,
5354

5455
isInteractive = canvasDefaultProps.isInteractive,
56+
defaultActiveNodeIds = canvasDefaultProps.defaultActiveNodeIds,
5557
nodeTooltip = canvasDefaultProps.nodeTooltip as NodeTooltip<Node>,
5658
onClick,
5759
}: InnerNetworkCanvasProps<Node, Link>) => {
@@ -62,7 +64,7 @@ const InnerNetworkCanvas = <Node extends InputNode, Link extends InputLink>({
6264
partialMargin
6365
)
6466

65-
const { nodes, links, setActiveNodeIds } = useNetwork<Node, Link>({
67+
const { nodes, links, activeNodeIds, setActiveNodeIds } = useNetwork<Node, Link>({
6668
center: [innerWidth / 2, innerHeight / 2],
6769
nodes: rawNodes,
6870
links: rawLinks,
@@ -80,13 +82,25 @@ const InnerNetworkCanvas = <Node extends InputNode, Link extends InputLink>({
8082
nodeBorderColor,
8183
linkThickness,
8284
linkColor,
85+
isInteractive,
86+
defaultActiveNodeIds,
8387
})
8488

8589
const boundAnnotations = useNodeAnnotations<Node>(nodes!, annotations)
8690
const computedAnnotations = useComputedAnnotations<ComputedNode<Node>>({
8791
annotations: boundAnnotations,
8892
})
8993

94+
const customLayerProps: CustomLayerProps<Node, Link> = useMemo(
95+
() => ({
96+
nodes: nodes || [],
97+
links: links || [],
98+
activeNodeIds,
99+
setActiveNodeIds,
100+
}),
101+
[nodes, links, activeNodeIds, setActiveNodeIds]
102+
)
103+
90104
const theme = useTheme()
91105

92106
useEffect(() => {
@@ -114,11 +128,7 @@ const InnerNetworkCanvas = <Node extends InputNode, Link extends InputLink>({
114128
theme,
115129
})
116130
} else if (typeof layer === 'function' && nodes !== null && links !== null) {
117-
layer(ctx, {
118-
// ...props,
119-
nodes,
120-
links,
121-
})
131+
layer(ctx, customLayerProps)
122132
}
123133
})
124134
}, [
@@ -135,6 +145,7 @@ const InnerNetworkCanvas = <Node extends InputNode, Link extends InputLink>({
135145
renderNode,
136146
renderLink,
137147
computedAnnotations,
148+
customLayerProps,
138149
])
139150

140151
const getNodeFromMouseEvent = useCallback(

‎packages/network/src/NetworkNodes.tsx

+50-15
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
import { createElement, MouseEvent, useMemo } from 'react'
1+
import { createElement, useCallback, useMemo, MouseEvent } from 'react'
22
import { useTransition } from '@react-spring/web'
33
import { useMotionConfig } from '@nivo/core'
4-
import { InputNode, ComputedNode, NodeAnimatedProps, NodeComponent } from './types'
4+
import { useTooltip } from '@nivo/tooltip'
5+
import { InputNode, ComputedNode, NodeAnimatedProps, NetworkSvgProps, InputLink } from './types'
56

6-
interface NetworkNodesProps<Node extends InputNode> {
7+
interface NetworkNodesProps<Node extends InputNode, Link extends InputLink> {
78
nodes: ComputedNode<Node>[]
8-
nodeComponent: NodeComponent<Node>
9-
onClick?: (node: ComputedNode<Node>, event: MouseEvent) => void
10-
onMouseEnter?: (node: ComputedNode<Node>, event: MouseEvent) => void
11-
onMouseMove?: (node: ComputedNode<Node>, event: MouseEvent) => void
12-
onMouseLeave?: (node: ComputedNode<Node>, event: MouseEvent) => void
9+
nodeComponent: NonNullable<NetworkSvgProps<Node, Link>['nodeComponent']>
10+
onMouseEnter: NetworkSvgProps<Node, Link>['onMouseEnter']
11+
onMouseMove: NetworkSvgProps<Node, Link>['onMouseMove']
12+
onMouseLeave: NetworkSvgProps<Node, Link>['onMouseLeave']
13+
onClick: NetworkSvgProps<Node, Link>['onClick']
14+
tooltip: NonNullable<NetworkSvgProps<Node, Link>['nodeTooltip']>
15+
setActiveNodeIds: (nodeIds: string[]) => void
16+
isInteractive: NonNullable<NetworkSvgProps<Node, Link>['isInteractive']>
1317
}
1418

1519
const getEnterTransition =
@@ -51,14 +55,17 @@ const getExitTransition =
5155
opacity: 0,
5256
})
5357

54-
export const NetworkNodes = <Node extends InputNode>({
58+
export const NetworkNodes = <Node extends InputNode, Link extends InputLink>({
5559
nodes,
5660
nodeComponent,
57-
onClick,
5861
onMouseEnter,
5962
onMouseMove,
6063
onMouseLeave,
61-
}: NetworkNodesProps<Node>) => {
64+
onClick,
65+
tooltip,
66+
setActiveNodeIds,
67+
isInteractive,
68+
}: NetworkNodesProps<Node, Link>) => {
6269
const { animate, config: springConfig } = useMotionConfig()
6370

6471
const [enterTransition, regularTransition, exitTransition] = useMemo(
@@ -77,17 +84,45 @@ export const NetworkNodes = <Node extends InputNode>({
7784
immediate: !animate,
7885
})
7986

87+
const { showTooltipFromEvent, hideTooltip } = useTooltip()
88+
89+
const handleMouseEnter = useCallback(
90+
(node: ComputedNode<Node>, event: MouseEvent) => {
91+
showTooltipFromEvent(createElement(tooltip, { node }), event)
92+
setActiveNodeIds([node.id])
93+
onMouseEnter?.(node, event)
94+
},
95+
[showTooltipFromEvent, tooltip, setActiveNodeIds, onMouseEnter]
96+
)
97+
98+
const handleMouseMove = useCallback(
99+
(node: ComputedNode<Node>, event: MouseEvent) => {
100+
showTooltipFromEvent(createElement(tooltip, { node }), event)
101+
onMouseMove?.(node, event)
102+
},
103+
[showTooltipFromEvent, tooltip, onMouseMove]
104+
)
105+
106+
const handleMouseLeave = useCallback(
107+
(node: ComputedNode<Node>, event: MouseEvent) => {
108+
hideTooltip()
109+
setActiveNodeIds([])
110+
onMouseLeave?.(node, event)
111+
},
112+
[hideTooltip, setActiveNodeIds, onMouseLeave]
113+
)
114+
80115
return (
81116
<>
82117
{transition((transitionProps, node) =>
83118
createElement(nodeComponent, {
84119
key: node.id,
85120
node,
86121
animated: transitionProps,
87-
onClick,
88-
onMouseEnter,
89-
onMouseMove,
90-
onMouseLeave,
122+
onMouseEnter: isInteractive ? handleMouseEnter : undefined,
123+
onMouseMove: isInteractive ? handleMouseMove : undefined,
124+
onMouseLeave: isInteractive ? handleMouseLeave : undefined,
125+
onClick: isInteractive ? onClick : undefined,
91126
})
92127
)}
93128
</>

‎packages/network/src/defaults.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ export const commonDefaultProps: Omit<
99
NetworkCommonProps<InputNode, InputLink>,
1010
| 'margin'
1111
| 'theme'
12-
| 'activeLinkThickness'
13-
| 'defaultActiveNodeIds'
1412
| 'onClick'
1513
| 'renderWrapper'
1614
| 'ariaLabel'
@@ -39,6 +37,7 @@ export const commonDefaultProps: Omit<
3937
linkColor: { from: 'source.color' },
4038

4139
isInteractive: true,
40+
defaultActiveNodeIds: [],
4241
nodeTooltip: NetworkNodeTooltip,
4342

4443
annotations: [],

‎packages/network/src/hooks.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ export const useNetwork = <Node extends InputNode = InputNode, Link extends Inpu
165165
linkThickness = commonDefaultProps.linkThickness,
166166
linkColor = commonDefaultProps.linkColor,
167167
isInteractive = commonDefaultProps.isInteractive,
168+
defaultActiveNodeIds = commonDefaultProps.defaultActiveNodeIds,
168169
}: {
169170
center: [number, number]
170171
nodes: Node[]
@@ -184,6 +185,7 @@ export const useNetwork = <Node extends InputNode = InputNode, Link extends Inpu
184185
linkThickness?: NetworkCommonProps<Node, Link>['linkThickness']
185186
linkColor?: NetworkCommonProps<Node, Link>['linkColor']
186187
isInteractive?: NetworkCommonProps<Node, Link>['isInteractive']
188+
defaultActiveNodeIds?: NetworkCommonProps<Node, Link>['defaultActiveNodeIds']
187189
}) => {
188190
// we're using `null` instead of empty array so that we can dissociate
189191
// initial rendering from updates when using transitions.
@@ -239,7 +241,7 @@ export const useNetwork = <Node extends InputNode = InputNode, Link extends Inpu
239241
}
240242
}, [nodes, links, forces, iterations, setTransientNodes, setTransientLinks])
241243

242-
const [activeNodeIds, setActiveNodeIds] = useState<string[]>([])
244+
const [activeNodeIds, setActiveNodeIds] = useState<string[]>(defaultActiveNodeIds)
243245

244246
const getNodeStyle = useNodeStyle<Node, Link>({
245247
size: nodeSize,
@@ -287,6 +289,7 @@ export const useNetwork = <Node extends InputNode = InputNode, Link extends Inpu
287289
return {
288290
nodes: computedNodes,
289291
links: computedLinks,
292+
activeNodeIds,
290293
setActiveNodeIds,
291294
}
292295
}

‎packages/network/src/types.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ export type LayerId = 'links' | 'nodes' | 'annotations'
114114
export interface CustomLayerProps<Node extends InputNode, Link extends InputLink> {
115115
nodes: ComputedNode<Node>[]
116116
links: ComputedLink<Node, Link>[]
117+
activeNodeIds: string[]
118+
setActiveNodeIds: (nodeIds: string[]) => void
117119
}
118120
export type CustomLayer<Node extends InputNode, Link extends InputLink> = FunctionComponent<
119121
CustomLayerProps<Node, Link>
@@ -154,7 +156,6 @@ export type NetworkCommonProps<Node extends InputNode, Link extends InputLink> =
154156
>
155157

156158
linkThickness: DerivedProp<Omit<ComputedLink<Node, Link>, 'color' | 'thickness'>, number>
157-
activeLinkThickness: DerivedProp<Omit<ComputedLink<Node, Link>, 'color' | 'thickness'>, number>
158159
linkColor: InheritedColorConfig<Omit<ComputedLink<Node, Link>, 'color' | 'thickness'>>
159160

160161
annotations: AnnotationMatcher<ComputedNode<Node>>[]
@@ -181,6 +182,9 @@ export type NetworkSvgProps<Node extends InputNode, Link extends InputLink> = Pa
181182
nodeComponent?: NodeComponent<Node>
182183
linkComponent?: LinkComponent<Node, Link>
183184
linkBlendMode?: CssMixBlendMode
185+
onMouseEnter?: (node: ComputedNode<Node>, event: MouseEvent) => void
186+
onMouseMove?: (node: ComputedNode<Node>, event: MouseEvent) => void
187+
onMouseLeave?: (node: ComputedNode<Node>, event: MouseEvent) => void
184188
}
185189

186190
export type NetworkCanvasProps<Node extends InputNode, Link extends InputLink> = Partial<

‎packages/network/tests/Network.test.tsx

+429-6
Large diffs are not rendered by default.

‎website/src/components/controls/AnnotationsControl.tsx

+2-8
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,14 @@ export const AnnotationsControl = memo(
4646
control: ArrayControlConfig<AnnotationMatcher<any>>
4747
} = useMemo(
4848
() => ({
49-
key: property.key,
50-
group: property.group,
51-
help: property.help,
52-
type: property.type,
53-
required: property.required,
54-
flavors: property.flavors,
55-
defaultValue: property.defaultValue,
49+
...property,
5650
control: {
5751
type: 'array',
5852
shouldCreate: true,
5953
addLabel: 'add annotation',
6054
shouldRemove: true,
6155
getItemTitle: (index, annotation) =>
62-
`annotation[${index}] '${annotation.note}' (${annotation.type})`,
56+
`annotations[${index}] '${annotation.note}' (${annotation.type})`,
6357
defaults: createDefaults,
6458
props: [
6559
{

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -290,21 +290,21 @@ const props: ChartProperty[] = [
290290
group: 'Interactivity',
291291
help: 'onMouseEnter handler.',
292292
type: '(node: ComputedNode, event: MouseEvent) => void',
293-
flavors: allFlavors,
293+
flavors: ['svg'],
294294
},
295295
{
296296
key: 'onMouseMove',
297297
group: 'Interactivity',
298298
help: 'onMouseMove handler.',
299299
type: '(node: ComputedNode, event: MouseEvent) => void',
300-
flavors: allFlavors,
300+
flavors: ['svg'],
301301
},
302302
{
303303
key: 'onMouseLeave',
304304
group: 'Interactivity',
305305
help: 'onMouseLeave handler.',
306306
type: '(node: ComputedNode, event: MouseEvent) => void',
307-
flavors: allFlavors,
307+
flavors: ['svg'],
308308
},
309309
annotations({
310310
target: 'nodes',

0 commit comments

Comments
 (0)
Please sign in to comment.