<head> <style> body { margin: 0; } </style> <script src="//unpkg.com/jquery"></script> <script src="//unpkg.com/3d-force-graph"></script> <script src="//unpkg.com/three"></script> <script src="//unpkg.com/three/examples/js/renderers/CSS2DRenderer.js"></script> <script src="//unpkg.com/three-spritetext"></script> <style> .node-div { font-size: 1rem; padding: 1px 4px; border-radius: 4px; background-color: rgba(0, 0, 0, 0.5); user-select: none; } .node-data-div { background-color: rgba(0, 0, 0, 0.9); } </style> </head> <body> <div id="3d-graph"></div> <script id="vars"> const typeNodeColors = { 'ble_server': '#0066ff', 'ssid': 'transparent', 'station': '#ffcc33', 'access_point': '#ff9900', 'endpoint': '#33cc33', 'gateway': '#006600' }; const typeColors = { 'ble_server': '#0066ff', 'ssid': '#ffff99', 'station': '#ffcc33', 'access_point': '#ff9900', 'endpoint': '#33cc33', 'gateway': '#006600' }; var targetNode = null; </script> <script id="functions"> function renderNodeHTML(node) { const nodeDiv = document.createElement('div'); switch (node.type) { case 'ssid': nodeDiv.innerHTML = `<small>${node.entity}</small>` break; case 'access_point': var ap = node.entity; nodeDiv.innerHTML = ` <center> <b>${ap.hostname}</b> (${ap.encryption}) <br/> ${ap.mac} ${ap.vendor? '<br/>(' + ap.vendor + ')' : ''} ${ap.wps.length? '<br/>' + JSON.stringify(ap.wps) : ''} </center>`; break; case 'station': var sta = node.entity; nodeDiv.innerHTML = ` <center> ${sta.mac} ${sta.vendor? '<br/>(' + sta.vendor + ')' : ''} </center>`; break; case 'ble_server': var dev = node.entity; nodeDiv.innerHTML = ` <center> ${dev.mac} ${dev.vendor? '<br/>(' + dev.vendor + ')' : ''} </center>`; break; case 'endpoint': var ip = node.entity; nodeDiv.innerHTML = ` <center> ${ip.hostname? '<b>' + ip.hostname + '</b><br/>' : ''} ${ip.ipv4? ip.ipv4 + '<br/>' : ''} ${ip.ipv6? ip.ipv6 + '<br/>' : ''} <br/> ${ip.mac} ${ip.vendor? '<br/>(' + ip.vendor + ')' : ''} ${ip.meta.values.length? '<br/>' + JSON.stringify(ip.meta.values) : ''} </center>`; break; case 'gateway': var ip = node.entity; nodeDiv.innerHTML = ` <center> ${ip.hostname? '<b>' + ip.hostname + '</b><br/>' : ''} ${ip.ipv4? ip.ipv4 + '<br/>' : ''} ${ip.ipv6? ip.ipv6 + '<br/>' : ''} <br/> ${ip.mac} ${ip.vendor? '<br/>(' + ip.vendor + ')' : ''} ${ip.meta.values.length? '<br/>' + JSON.stringify(ip.meta.values) : ''} </center>`; break; default: nodeDiv.innerHTML = `<b>${node.id}</b>` } nodeDiv.style.color = typeColors[node.type]; nodeDiv.className = 'node-div'; const dataDiv = document.createElement('div'); dataDiv.id = 'datadiv_' + node.id; dataDiv.className = 'node-data-div'; dataDiv.style.visibility = 'hidden'; dataDiv.style.display = 'none'; dataDiv.innerHTML = '<br/><pre>' + node.type + ' ' + JSON.stringify(node.entity, null, 2) + '</pre>'; nodeDiv.appendChild(dataDiv); return new THREE.CSS2DObject(nodeDiv); } </script> <script id="graph"> const Graph = ForceGraph3D({ extraRenderers: [new THREE.CSS2DRenderer()], controlType: 'orbit' }) (document.getElementById('3d-graph')) .jsonUrl('bettergraph.json') .nodeLabel('id') .nodeColor(node => typeNodeColors[node.type]) .linkDirectionalArrowLength(3.5) .linkDirectionalArrowRelPos(1) /* .linkThreeObjectExtend(true) .linkThreeObject(link => { const sprite = new SpriteText(link.edge.type); sprite.color = 'lightgrey'; sprite.textHeight = 1.5; return sprite; }) .linkPositionUpdate((sprite, {start, end}) => { const middlePos = Object.assign(...['x', 'y', 'z'].map(c => ({ [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point }))); Object.assign(sprite.position, middlePos); }) */ .nodeThreeObject(renderNodeHTML) .nodeThreeObjectExtend(true) .onNodeClick(node => { if( targetNode != null ) { const prev = document.getElementById('datadiv_' + targetNode.id); prev.style.visibility = 'hidden'; prev.style.display = 'none'; } targetNode = node; const curr = document.getElementById('datadiv_' + targetNode.id); curr.style.visibility = 'visible'; curr.style.display = 'block'; // Aim at node from outside it const distance = 40; const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z); Graph.cameraPosition( { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, // new position node, // lookAt ({ x, y, z }) 3000 // ms transition duration ); }); </script> </body>