Created
March 6, 2024 06:02
-
-
Save apowers313/73d9acd4c92d00e2f8d6ad1f97d67d15 to your computer and use it in GitHub Desktop.
Create a 3D Force Graph in Babylon.js using ngraph
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |
<title>Babylon Template</title> | |
<style> | |
html, | |
body { | |
overflow: hidden; | |
width: 100%; | |
height: 100%; | |
margin: 0; | |
padding: 0; | |
} | |
#renderCanvas { | |
width: 100%; | |
height: 100%; | |
touch-action: none; | |
} | |
</style> | |
<script src="https://cdn.babylonjs.com/babylon.js"></script> | |
<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script> | |
<script src="https://code.jquery.com/pep/0.4.3/pep.js"></script> | |
<!-- XXX: https://github.com/anvaka/ngraph.forcelayout/issues/40 --> | |
<script src='https://unpkg.com/ngraph.graph@19.1.0/dist/ngraph.graph.min.js'></script> | |
<script src='https://unpkg.com/ngraph.forcelayout/dist/ngraph.forcelayout.min.js'></script> | |
</head> | |
<body> | |
<canvas id="renderCanvas" touch-action="none"></canvas> | |
<!-- touch-action="none" for best results from PEP --> | |
<script> | |
const canvas = document.getElementById("renderCanvas"); // Get the canvas element | |
const engine = new BABYLON.Engine(canvas, true); // Generate the BABYLON 3D engine | |
const scene = new BABYLON.Scene(engine); | |
// create a stupid little graph | |
const graph = createGraph(); | |
graph.addLink(1, 2, { foo: "bar" }); | |
graph.addLink(1, 3); | |
graph.addLink(1, 4); | |
graph.addLink(2, 3); | |
graph.addLink(2, 4); | |
graph.addLink(3, 4); | |
// create our graph layout engine | |
const graphLayout = ngraphCreateLayout(graph, { dimensions: 3 }); | |
// updates the node mesh with the latest position from the graph | |
function updateNodePosition(node) { | |
let n = graphLayout.getNodePosition(node.id); | |
node.data.mesh.position.x = n.x; | |
node.data.mesh.position.y = n.y; | |
node.data.mesh.position.z = n.z; | |
} | |
// updates the link mesh with the latest position from the graph | |
function updateLinkPosition(link) { | |
let lnk = graphLayout.getLinkPosition(link.id); | |
const options = { | |
points: [ | |
new BABYLON.Vector3(lnk.from.x, lnk.from.y, lnk.from.z), | |
new BABYLON.Vector3(lnk.to.x, lnk.to.y, lnk.to.z), | |
], | |
updatable: true, | |
}; | |
const instance = link?.data?.mesh ?? null; | |
if (instance) options.instance = instance; | |
// create or update the line (depending on whether `instance` | |
// already exists) | |
link.data.mesh = BABYLON.MeshBuilder.CreateLines("lines", options); | |
} | |
// calculate new node and link positions and update corresponding meshes | |
function updateGraph() { | |
graphLayout.step(); | |
graph.forEachLink(updateLinkPosition); | |
graph.forEachNode(updateNodePosition); | |
} | |
// initial link setup | |
graph.forEachLink(function (link) { | |
link.data = link.data ?? {}; | |
// create link meshes (as part of the update function) | |
updateLinkPosition(link) | |
console.log(`Link ${link.id}:`, link); | |
}); | |
// initial node setup | |
graph.forEachNode(function (node) { | |
node.data = node.data ?? {}; | |
// create box meshes | |
node.data.mesh = BABYLON.MeshBuilder.CreateBox("box", {}); | |
updateNodePosition(node); | |
console.log(`Node ${node.id}:`, node); | |
}); | |
const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 15, new BABYLON.Vector3(0, 0, 0)); | |
camera.attachControl(canvas, true); | |
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0)); | |
// Register a render loop to repeatedly render the scene | |
engine.runRenderLoop(function () { | |
updateGraph(); | |
scene.render(); | |
}); | |
// Watch for browser/canvas resize events | |
window.addEventListener("resize", function () { | |
engine.resize(); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment