Skip to content

Instantly share code, notes, and snippets.

@apowers313
Created March 6, 2024 06:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apowers313/73d9acd4c92d00e2f8d6ad1f97d67d15 to your computer and use it in GitHub Desktop.
Save apowers313/73d9acd4c92d00e2f8d6ad1f97d67d15 to your computer and use it in GitHub Desktop.
Create a 3D Force Graph in Babylon.js using ngraph
<!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