|
|
|
@ -1,4 +1,6 @@ |
|
|
|
|
import { useCallback, useEffect, useRef } from 'react'; |
|
|
|
|
import { useEffect, useRef } from 'react'; |
|
|
|
|
import type { DataSet } from 'vis-data'; |
|
|
|
|
import type { Network, Options, Data, Edge, Node } from 'vis-network'; |
|
|
|
|
|
|
|
|
|
import { GraphEdge, GraphNode } from './utils'; |
|
|
|
|
|
|
|
|
@ -6,7 +8,6 @@ interface OwnProps { |
|
|
|
|
nodes: GraphNode[]; |
|
|
|
|
edges: GraphEdge[]; |
|
|
|
|
direction?: 'UD' | 'DU' | 'LR' | 'RL'; |
|
|
|
|
onDoubleClick?: (node: string) => void; |
|
|
|
|
width?: string; |
|
|
|
|
height?: string; |
|
|
|
|
} |
|
|
|
@ -17,28 +18,19 @@ interface DispatchProps {} |
|
|
|
|
|
|
|
|
|
export type Props = OwnProps & ConnectedProps & DispatchProps; |
|
|
|
|
|
|
|
|
|
export const NetworkGraph = ({ nodes, edges, direction, width, height, onDoubleClick }: Props) => { |
|
|
|
|
const network = useRef<any>(null); |
|
|
|
|
const ref = useRef(null); |
|
|
|
|
|
|
|
|
|
const onNodeDoubleClick = useCallback( |
|
|
|
|
(params: { nodes: string[] }) => { |
|
|
|
|
if (onDoubleClick) { |
|
|
|
|
onDoubleClick(params.nodes[0]); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
[onDoubleClick] |
|
|
|
|
); |
|
|
|
|
export const NetworkGraph = ({ nodes, edges, direction, width, height }: Props) => { |
|
|
|
|
const network = useRef<Network | null>(null); |
|
|
|
|
const ref = useRef<HTMLDivElement>(null); |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
const createNetwork = async () => { |
|
|
|
|
// @ts-ignore no types yet for visjs-network
|
|
|
|
|
const visJs = await import(/* webpackChunkName: "visjs-network" */ 'visjs-network'); |
|
|
|
|
const data = { |
|
|
|
|
nodes: toVisNetworkNodes(visJs, nodes), |
|
|
|
|
edges: toVisNetworkEdges(visJs, edges), |
|
|
|
|
const visJs = await import(/* webpackChunkName: "vis-network" */ 'vis-network'); |
|
|
|
|
const visData = await import(/* webpackChunkName: "vis-data" */ 'vis-data'); |
|
|
|
|
const data: Data = { |
|
|
|
|
nodes: toVisNetworkNodes(visData, nodes), |
|
|
|
|
edges: toVisNetworkEdges(visData, edges), |
|
|
|
|
}; |
|
|
|
|
const options = { |
|
|
|
|
const options: Options = { |
|
|
|
|
width: '100%', |
|
|
|
|
height: '100%', |
|
|
|
|
autoResize: true, |
|
|
|
@ -55,20 +47,13 @@ export const NetworkGraph = ({ nodes, edges, direction, width, height, onDoubleC |
|
|
|
|
dragNodes: false, |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
network.current = new visJs.Network(ref.current, data, options); |
|
|
|
|
network.current?.on('doubleClick', onNodeDoubleClick); |
|
|
|
|
if (ref.current) { |
|
|
|
|
network.current = new visJs.Network(ref.current, data, options); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
createNetwork(); |
|
|
|
|
|
|
|
|
|
return () => { |
|
|
|
|
// unsubscribe event handlers
|
|
|
|
|
if (network.current) { |
|
|
|
|
network.current.off('doubleClick'); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
}, [direction, edges, nodes, onNodeDoubleClick]); |
|
|
|
|
}, [direction, edges, nodes]); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div> |
|
|
|
@ -77,15 +62,15 @@ export const NetworkGraph = ({ nodes, edges, direction, width, height, onDoubleC |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
function toVisNetworkNodes(visJs: any, nodes: GraphNode[]): any[] { |
|
|
|
|
function toVisNetworkNodes(visData: any, nodes: GraphNode[]): DataSet<Node> { |
|
|
|
|
const nodesWithStyle = nodes.map((node) => ({ |
|
|
|
|
...node, |
|
|
|
|
shape: 'box', |
|
|
|
|
})); |
|
|
|
|
return new visJs.DataSet(nodesWithStyle); |
|
|
|
|
return new visData.DataSet(nodesWithStyle); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function toVisNetworkEdges(visJs: any, edges: GraphEdge[]): any[] { |
|
|
|
|
function toVisNetworkEdges(visData: any, edges: GraphEdge[]): DataSet<Edge> { |
|
|
|
|
const edgesWithStyle = edges.map((edge) => ({ ...edge, arrows: 'to', dashes: true })); |
|
|
|
|
return new visJs.DataSet(edgesWithStyle); |
|
|
|
|
return new visData.DataSet(edgesWithStyle); |
|
|
|
|
} |
|
|
|
|