1+ < head >
2+ < style > body { margin : 0 ; } </ style >
3+
4+ < script src ="https://unpkg.com/3d-force-graph "> </ script >
5+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/graphlib/2.1.8/graphlib.min.js "> </ script >
6+ < script src ="
https://cdn.jsdelivr.net/npm/[email protected] /dist/graphlib-dot.min.js "
> </ script > 7+ </ head >
8+
9+ < body >
10+ < div id ="3d-graph "> </ div >
11+
12+ < script >
13+ const dot = `
14+ digraph {
15+ node [style=filled];
16+ A [color=red];
17+ B [color=green];
18+ C [color=blue];
19+ A -> B [color=purple];
20+ B -> C [color=orange];
21+ C -> A [color=cyan];
22+ }` ;
23+
24+ const graphlibGraph = graphlibDot . read ( dot ) ;
25+
26+ var allNodes = [ ] ;
27+ var allLinks = [ ] ;
28+
29+ graphlibGraph . nodes ( ) . forEach ( function ( node ) {
30+ var nodeData = graphlibGraph . node ( node ) ;
31+ allNodes . push ( {
32+ id : node ,
33+ color : nodeData . color || 'white' ,
34+ } ) ;
35+ } ) ;
36+
37+ graphlibGraph . edges ( ) . forEach ( function ( edge ) {
38+ allLinks . push ( {
39+ source : edge . v ,
40+ target : edge . w ,
41+ color : graphlibGraph . edge ( edge ) . color || 'white'
42+ } ) ;
43+ } ) ;
44+
45+ const gData = {
46+ nodes : allNodes ,
47+ links : allLinks
48+ } ;
49+
50+ // cross-link node objects
51+ gData . links . forEach ( link => {
52+ const a = gData . nodes . find ( node => node . id === link . source ) ;
53+ const b = gData . nodes . find ( node => node . id === link . target ) ;
54+ ! a . neighbors && ( a . neighbors = [ ] ) ;
55+ ! b . neighbors && ( b . neighbors = [ ] ) ;
56+ a . neighbors . push ( b ) ;
57+ b . neighbors . push ( a ) ;
58+
59+ ! a . links && ( a . links = [ ] ) ;
60+ ! b . links && ( b . links = [ ] ) ;
61+ a . links . push ( link ) ;
62+ b . links . push ( link ) ;
63+ } ) ;
64+
65+ const highlightNodes = new Set ( ) ;
66+ const highlightLinks = new Set ( ) ;
67+ let hoverNode = null ;
68+
69+ const Graph = new ForceGraph3D ( document . getElementById ( '3d-graph' ) )
70+ . graphData ( gData )
71+ . nodeLabel ( 'id' )
72+ . nodeColor ( node => highlightNodes . has ( node ) ? node === hoverNode ? 'rgb(255,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.6)' )
73+ . linkWidth ( link => highlightLinks . has ( link ) ? 4 : 1 )
74+ . linkDirectionalParticles ( link => highlightLinks . has ( link ) ? 4 : 0 )
75+ . linkDirectionalParticleWidth ( 4 )
76+
77+ . onNodeHover ( node => {
78+ // no state change
79+ if ( ( ! node && ! highlightNodes . size ) || ( node && hoverNode === node ) ) return ;
80+
81+ highlightNodes . clear ( ) ;
82+ highlightLinks . clear ( ) ;
83+ if ( node ) {
84+ highlightNodes . add ( node ) ;
85+ node . neighbors . forEach ( neighbor => highlightNodes . add ( neighbor ) ) ;
86+ node . links . forEach ( link => highlightLinks . add ( link ) ) ;
87+ }
88+
89+ hoverNode = node || null ;
90+
91+ updateHighlight ( Graph ) ;
92+ } )
93+ . onLinkHover ( link => {
94+ highlightNodes . clear ( ) ;
95+ highlightLinks . clear ( ) ;
96+
97+ if ( link ) {
98+ highlightLinks . add ( link ) ;
99+ highlightNodes . add ( link . source ) ;
100+ highlightNodes . add ( link . target ) ;
101+ }
102+
103+ updateHighlight ( Graph ) ;
104+ } ) ;
105+
106+ function updateHighlight ( Graph ) {
107+ // trigger update of highlighted objects in scene
108+ Graph
109+ . nodeColor ( Graph . nodeColor ( ) )
110+ . linkWidth ( Graph . linkWidth ( ) )
111+ . linkDirectionalParticles ( Graph . linkDirectionalParticles ( ) ) ;
112+ }
113+ </ script >
114+ </ body >
0 commit comments