plebeiangraphlibrary 2.2.2 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,29 +40,35 @@
40
40
  </div>
41
41
  <canvas id="displayCanvas" class="displayCanvas"></canvas>
42
42
  <script type="module">
43
- // Opt-in interaction layer: call enableInteraction() to add click/hover support.
44
- // Callbacks receive graph details (nodeId, neighbours, position, edge start/end, etc.).
45
43
  import * as PGL from "../Build/pgl_module.js";
46
44
 
45
+ // Interaction is 100% opt-in — nothing is registered until you call enableInteraction().
46
+ // Callbacks receive a details object: { nodeId, data, neighbours, position } for nodes,
47
+ // { edgeId, start, end, data } for edges.
48
+
47
49
  const G = await PGL.SampleData.LoadZKCSimulated();
48
- const width = 800;
49
- const height = 700;
50
50
  const canvas = document.getElementById("displayCanvas");
51
51
  const infoText = document.getElementById("infoText");
52
52
 
53
- const graph3d = new PGL.GraphDrawer.GraphDrawer3d({ graph: G, width, height, canvas });
53
+ const graph3d = new PGL.GraphDrawer.GraphDrawer3d({ graph: G, width: 800, height: 700, canvas });
54
54
  await graph3d.init();
55
55
 
56
56
  const bounds = 1;
57
+
58
+ // Add visual elements BEFORE enableInteraction.
59
+ // The interaction layer works by raycasting against objects already in the scene.
60
+ // If you call enableInteraction first, the raycast has nothing to hit.
61
+ // Thick edges are easier to pick than thin lines — prefer DrawTHREEGraphEdgesThick
62
+ // when you need edge click/hover callbacks.
57
63
  const nodeVisualElements = PGL.ThreeWrapper.DrawTHREEBoxBasedVertices(G, bounds, 0xffffff, 5);
58
64
  const edgeVisualElements = PGL.ThreeWrapper.DrawTHREEGraphEdgesThick(G, bounds, 0xffafcc, 10);
59
65
  graph3d.addVisElement(nodeVisualElements);
60
66
  graph3d.addVisElement(edgeVisualElements);
61
67
 
62
- // Enable opt-in interaction — pass graph and callbacks
63
68
  graph3d.enableInteraction({
64
69
  graph: G,
65
70
  onNodeClick: (d) => {
71
+ // d.neighbours is an array of neighbour node IDs
66
72
  infoText.innerHTML = `Node <b>${d.nodeId}</b><br>Neighbours: ${d.neighbours.length}<br>Position: (${d.position?.x?.toFixed(2) ?? "?"}, ${d.position?.y?.toFixed(2) ?? "?"}, ${d.position?.z?.toFixed(2) ?? "?"})`;
67
73
  },
68
74
  onEdgeClick: (d) => {
@@ -40,19 +40,20 @@
40
40
  </div>
41
41
  <canvas id="displayCanvas" class="displayCanvas"></canvas>
42
42
  <script type="module">
43
- // Rich interaction: use NodePickDetails to drive visual feedback.
44
- // ChangeTheVertexColours highlights neighbour nodes by node ID.
45
43
  import * as PGL from "../Build/pgl_module.js";
46
44
 
45
+ // This example combines hover interaction with dynamic vertex coloring.
46
+ // onNodeHover fires with a NodePickDetails object when entering a node,
47
+ // and with null when the cursor leaves — use the null case to reset colors.
48
+
47
49
  const G = await PGL.SampleData.LoadZKCSimulated();
48
- const width = 800;
49
- const height = 700;
50
50
  const canvas = document.getElementById("displayCanvas");
51
51
  const infoText = document.getElementById("infoText");
52
52
 
53
- const graph3d = new PGL.GraphDrawer.GraphDrawer3d({ graph: G, width, height, canvas });
53
+ const graph3d = new PGL.GraphDrawer.GraphDrawer3d({ graph: G, width: 800, height: 700, canvas });
54
54
  await graph3d.init();
55
55
 
56
+ // Disable camera auto-rotation so hover targets stay stable.
56
57
  graph3d.controls.autoRotate = false;
57
58
 
58
59
  const bounds = 1;
@@ -62,19 +63,27 @@
62
63
  graph3d.addVisElement(nodeVisualElements);
63
64
  graph3d.addVisElement(edgeVisualElements);
64
65
 
66
+ // IMPORTANT: ChangeTheVertexColours and ResetVertexColors take the inner
67
+ // instanced mesh, NOT the Group returned by the draw call.
68
+ // Always pass nodeVisualElements.children[0] — passing nodeVisualElements
69
+ // directly will silently do nothing.
70
+ const nodeMesh = nodeVisualElements.children[0];
65
71
  const HIGHLIGHT_COLOR = 0xff4444;
66
72
 
67
73
  graph3d.enableInteraction({
68
74
  graph: G,
69
75
  hoverEnabled: true,
70
76
  onNodeHover: (d) => {
71
- PGL.ThreeWrapper.ResetVertexColors(nodeVisualElements.children[0]);
77
+ // Reset all node colors before applying a new highlight each frame.
78
+ PGL.ThreeWrapper.ResetVertexColors(nodeMesh);
72
79
  if (d) {
80
+ // d.neighbours is the array of neighbour node IDs — color them directly.
73
81
  if (d.neighbours.length > 0) {
74
- PGL.ThreeWrapper.ChangeTheVertexColours(nodeVisualElements.children[0], d.neighbours, HIGHLIGHT_COLOR);
82
+ PGL.ThreeWrapper.ChangeTheVertexColours(nodeMesh, d.neighbours, HIGHLIGHT_COLOR);
75
83
  }
76
84
  infoText.innerHTML = `Node <b>${d.nodeId}</b><br>Degree: ${d.neighbours.length}<br>Neighbours highlighted in red`;
77
85
  } else {
86
+ // d is null when the pointer leaves a node — colors already reset above.
78
87
  infoText.innerHTML = "Hover a node";
79
88
  }
80
89
  },
@@ -19,61 +19,47 @@
19
19
  <div class="canvas-wrap">
20
20
  <canvas id="displayCanvas" class="displayCanvas"></canvas>
21
21
  <script type="module">
22
- // import the library
23
22
  import * as PGL from "../Build/pgl_module.js";
24
23
 
25
- // construct a simple ZKC graph
26
- // this graph is pre initialized (Since it is simulated in this case)
24
+ // STEP 1 Load the graph.
25
+ // LoadZKCSimulated returns the Zachary Karate Club graph with positions already
26
+ // computed (Kamada-Kawai), so you can skip the layout step entirely.
27
+ // Use LoadZKC() instead if you want to run your own layout.
27
28
  const zkcSimulated = await PGL.SampleData.LoadZKCSimulated();
28
- // console log this to see how this data is stored
29
- console.log(zkcSimulated);
30
- // also as a reference just see how this data is stored
31
- zkcSimulated.printData();
29
+ zkcSimulated.printData(); // logs node/edge counts to console useful for debugging
32
30
 
33
- // set the width and height
34
- const width = 800;
35
- const heigth = 700;
36
-
37
- // pass in the graph and the canvas into the drawing object to draw it
38
- // first get the canvas element
31
+ // STEP 2 — Create the renderer and attach it to the canvas element.
39
32
  const canvas = document.getElementById("displayCanvas");
40
- // then create a graph drawer object
41
- // to do that first make a options object
42
- const graphDrawerOptions = {
33
+ const graph3d = new PGL.GraphDrawer.GraphDrawer3d({
43
34
  graph: zkcSimulated,
44
- width: width,
45
- height: heigth,
46
- canvas: canvas
47
- };
48
- // then create the visualization window
49
- // with those settings
50
- const graph3d = new PGL.GraphDrawer.GraphDrawer3d(graphDrawerOptions);
51
- // initialize this object before adding things to it
35
+ width: 800,
36
+ height: 700,
37
+ canvas: canvas,
38
+ });
39
+
40
+ // STEP 3 — Initialize (async). Sets up the Three.js scene, camera, and
41
+ // WebGL renderer. Must complete before you add any geometry.
52
42
  await graph3d.init();
53
43
 
54
- // Create the 3d elements for the graph
55
- // first describe a global scaling factor
56
- const bounds = 1
57
- // first create all the node elements
44
+ // STEP 4 — Create visual geometry and add it to the scene.
45
+ // `bounds` is a global scale factor applied to all positions.
46
+ // DrawTHREEBoxBasedVertices uses box meshes (no texture file required).
47
+ // DrawTHREEGraphVertices uses billboard sprites but needs ./Textures/Square.png.
48
+ const bounds = 1;
58
49
  const nodeVisualElements = PGL.ThreeWrapper.DrawTHREEBoxBasedVertices(zkcSimulated, bounds, 0xffffff, 5);
59
- // add the node elements to the scene
60
50
  graph3d.addVisElement(nodeVisualElements);
61
- // then create the edge elements
51
+
62
52
  const edgeVisualElements = PGL.ThreeWrapper.DrawTHREEGraphEdgesThick(zkcSimulated, bounds, 0xffafcc, 10);
63
53
  graph3d.addVisElement(edgeVisualElements);
64
54
 
65
- // then there are two last steps for a 3d graph
66
- // this is done so that other 3d objects can be added in later
67
- // since the base library is three.js all standard three js things are possible
68
- // now moving on to the two steps :
69
- // make an animation function
55
+ // STEP 5 Start the animation loop.
56
+ // graph3d.rendercall() advances OrbitControls (auto-rotate, damping) and
57
+ // renders the frame. You can add your own per-frame logic here too — this is
58
+ // a standard Three.js render loop.
70
59
  function animate() {
71
60
  requestAnimationFrame(animate);
72
61
  graph3d.rendercall();
73
62
  }
74
-
75
- // append the graph renderer to the container
76
- // and then drawing render calls
77
63
  animate();
78
64
  </script>
79
65
  </div>
@@ -21,32 +21,36 @@
21
21
  <script type="module">
22
22
  import * as PGL from "../Build/pgl_module.js";
23
23
 
24
- // DWT 1005 graph from the paper (same as Example 12's 2D stress dataset)
24
+ // DWT-1005: a 1005-node structural engineering graph. Loaded pre-initialized.
25
25
  const G = await PGL.SampleData.LoadDwt1005();
26
26
  G.printData();
27
27
 
28
+ // createKamadaKawai3D returns a simulation object with step(dt) / getPositions().
29
+ // It does NOT run the layout immediately — positions evolve frame-by-frame in the
30
+ // animation loop. This lets you watch the graph "settle" in real time.
28
31
  const simulation = PGL.createKamadaKawai3D(G, {
29
- simulationBound: 800,
30
- cohesionValue: 1,
31
- repulsionValue: 1,
32
- iterationsPerStep: 1,
32
+ simulationBound: 800, // world-space radius the layout tries to fit within
33
+ cohesionValue: 1, // spring attraction strength
34
+ repulsionValue: 1, // node repulsion strength
35
+ iterationsPerStep: 1, // layout steps computed per call to step() — increase for faster convergence
33
36
  });
37
+
38
+ // Apply initial positions to the graph before drawing so the draw calls
39
+ // have valid geometry to start from (otherwise nodes all stack at the origin).
34
40
  G.apply_position_map(simulation.getPositionMap());
35
41
  const lmap = PGL.Drawing.DrawEdgeLinesDivisions(G, 1);
36
42
  G.apply_edge_pos_maps(lmap);
37
43
 
38
- const width = 800;
39
- const height = 700;
40
44
  const canvas = document.getElementById("displayCanvas");
41
- const graph3d = new PGL.GraphDrawer.GraphDrawer3d({
42
- graph: G,
43
- width,
44
- height,
45
- canvas,
46
- });
45
+ const graph3d = new PGL.GraphDrawer.GraphDrawer3d({ graph: G, width: 800, height: 700, canvas });
47
46
  await graph3d.init();
48
47
 
49
48
  const bounds = 0.1;
49
+
50
+ // KEY DECISION — use Mutable variants for any animation.
51
+ // Static variants (DrawTHREEBoxBasedVertices etc.) bake geometry at creation time
52
+ // and cannot be updated. Mutable variants return updatePositions() and updateEdges()
53
+ // functions that push new positions to the GPU each frame without recreating geometry.
50
54
  const { group, updatePositions } = PGL.ThreeWrapper.DrawTHREEGraphVerticesMutable(G, bounds, 1, 0xffffff, 1);
51
55
  graph3d.addVisElement(group);
52
56
  const { group: edgeGroup, updateEdges } = PGL.ThreeWrapper.DrawTHREEGraphEdgesThinMutable(G, bounds, 0xffafcc);
@@ -56,14 +60,18 @@
56
60
  function animate() {
57
61
  requestAnimationFrame(animate);
58
62
  const now = performance.now();
59
- const dt = (now - lastTime) / 1000;
63
+ const dt = (now - lastTime) / 1000; // delta time in seconds
60
64
  lastTime = now;
65
+
66
+ // Advance the layout by one step, then write the new positions back to
67
+ // the graph and push them to the GPU via the mutable updater functions.
61
68
  simulation.step(dt);
62
69
  G.apply_position_map(simulation.getPositionMap());
63
70
  const lmap = PGL.Drawing.DrawEdgeLinesDivisions(G, 1);
64
71
  G.apply_edge_pos_maps(lmap);
65
- updatePositions(simulation.getPositions());
66
- updateEdges();
72
+ updatePositions(simulation.getPositions()); // Float32Array [x0,y0,z0, x1,y1,z1, ...]
73
+ updateEdges(); // recomputes edge line geometry from node positions
74
+
67
75
  graph3d.rendercall();
68
76
  }
69
77
  animate();
package/README.md CHANGED
@@ -14,6 +14,51 @@ It can be a bit confusing especially when working with Nodes/Edges/Vertices/Line
14
14
  Lastly, there are a few helper classes like points and lines. Points are essentially vectors and are used for displacement and also for describing a place in relation to the global coordinate system. Line are an array of points that get translated into lines using one of the visualization methods. Points can have different visualizations like boxes, billboarded planes and cylinders etc.
15
15
 
16
16
 
17
+ ## Quick Start: The 5-Step Pipeline
18
+
19
+ Every PGL visualization follows the same five steps **in this order**. Getting the order wrong is the most common source of silent bugs.
20
+
21
+ ```
22
+ Step 1 — Load or build a Graph
23
+ PGL.SampleData.LoadZKCSimulated() ← positions already baked in (fastest start)
24
+ PGL.SampleData.LoadZKC() ← raw graph, you must run a layout first
25
+ GenerateErdosReyni_n_p(n, p) + await G.initialize() ← manual build
26
+
27
+ Step 2 — Create the renderer
28
+ new PGL.GraphDrawer.GraphDrawer3d({ graph, canvas, width, height })
29
+
30
+ Step 3 — Initialize (async — must complete before anything else)
31
+ await graph3d.init()
32
+
33
+ Step 4 — Draw visual elements and add them to the scene
34
+ const nodes = PGL.ThreeWrapper.DrawTHREEBoxBasedVertices(G, bounds, 0xffffff, 5)
35
+ graph3d.addVisElement(nodes)
36
+ const edges = PGL.ThreeWrapper.DrawTHREEGraphEdgesThick(G, bounds, 0xffafcc, 10)
37
+ graph3d.addVisElement(edges)
38
+ // If you need interaction, call enableInteraction() here — after addVisElement
39
+ graph3d.enableInteraction({ graph: G, onNodeClick: (d) => console.log(d) })
40
+
41
+ Step 5 — Start the animation loop
42
+ function animate() { requestAnimationFrame(animate); graph3d.rendercall(); }
43
+ animate()
44
+ ```
45
+
46
+ **Static vs. Mutable — choose before drawing:**
47
+
48
+ | Scenario | Use these draw calls | Returns |
49
+ |---|---|---|
50
+ | Fixed layout (no animation needed) | `DrawTHREEBoxBasedVertices`, `DrawTHREEGraphEdgesThick` | `THREE.Group` — pass directly to `addVisElement` |
51
+ | Live simulation, drag-to-reposition | `DrawTHREEBoxBasedVerticesMutable`, `DrawTHREEGraphEdgesThinMutable` | `{ group, updatePositions(), updateEdges() }` — call updaters each frame |
52
+
53
+ Mutable variants return an `updatePositions(posArray)` and `updateEdges()` function. Call both each frame *before* `rendercall()`.
54
+
55
+ **When do I need to call `await G.initialize()`?**
56
+
57
+ - `LoadZKCSimulated()`, `LoadZKC()`, `LoadDwt1005()`, `Graph.create()` → **auto-initialized, skip this step**
58
+ - `GenerateErdosReyni_n_p()` or building a graph manually with `add_node` / `add_edge` → **you must call `await G.initialize()` yourself** before drawing or running algorithms
59
+
60
+ ---
61
+
17
62
  ## Documentation
18
63
 
19
64
  The documentation for the package is available at [documentation](https://www.plebeiangraphlibrary.com/). You can also generate API docs locally with:
@@ -180,6 +225,230 @@ async function createVisualization() {
180
225
  createVisualization();
181
226
  ```
182
227
 
228
+ ## Cookbook: Copy-Paste Recipes
229
+
230
+ Each recipe below is self-contained and runnable. They assume you have imported the library as `import * as PGL from "plebeiangraphlibrary"` and have a `<canvas id="displayCanvas">` element in the page.
231
+
232
+ ---
233
+
234
+ ### Recipe 1 — Load your own graph from a plain edge list
235
+
236
+ PGL supports the same space-separated edge list format used by [(sgd)²](https://github.com/jxz12/s_gd2). Each line is `nodeA nodeB`, one edge per line.
237
+
238
+ ```javascript
239
+ const edgeListText = `
240
+ 0 1
241
+ 0 2
242
+ 1 2
243
+ 2 3
244
+ 3 4
245
+ `.trim();
246
+
247
+ const G = await PGL.SampleData.LoadGraphFromEdgeListText(edgeListText);
248
+ // G is already initialized and has random starting positions.
249
+ // Run a layout before drawing (positions are random without it):
250
+ const posMap = PGL.Drawing.SimulateKamadaKawai(G, 50, 200);
251
+ G.apply_position_map(posMap);
252
+ const lmap = PGL.Drawing.DrawEdgeLinesDivisions(G, 1);
253
+ G.apply_edge_pos_maps(lmap);
254
+ ```
255
+
256
+ ---
257
+
258
+ ### Recipe 2 — Build a graph programmatically and run a layout
259
+
260
+ ```javascript
261
+ import * as PGL from "plebeiangraphlibrary";
262
+
263
+ const G = new PGL.Graph();
264
+
265
+ // add_node(id, { pos: PointLike, ...yourData })
266
+ G.add_node(0, { pos: { x: 0, y: 0, z: 0 } });
267
+ G.add_node(1, { pos: { x: 0, y: 0, z: 0 } });
268
+ G.add_node(2, { pos: { x: 0, y: 0, z: 0 } });
269
+ G.add_edge(0, 1);
270
+ G.add_edge(1, 2);
271
+ G.add_edge(2, 0);
272
+
273
+ // Manual builds require an explicit initialize() call — loaders handle this automatically
274
+ await G.initialize();
275
+
276
+ // Now run a layout to compute real positions
277
+ const posMap = PGL.Drawing.SimulateKamadaKawai(G, 100, 200);
278
+ G.apply_position_map(posMap);
279
+ const lmap = PGL.Drawing.DrawEdgeLinesDivisions(G, 1);
280
+ G.apply_edge_pos_maps(lmap);
281
+
282
+ // Then draw normally (steps 2–5 of the pipeline)
283
+ ```
284
+
285
+ ---
286
+
287
+ ### Recipe 3 — Color nodes by a data property
288
+
289
+ Nodes are drawn as an instanced mesh. `ChangeTheVertexColours` takes the **inner mesh** (`.children[0]`), not the Group.
290
+
291
+ ```javascript
292
+ const G = await PGL.SampleData.LoadZKCSimulated();
293
+ // ... (pipeline steps 2–4) ...
294
+
295
+ const nodeGroup = PGL.ThreeWrapper.DrawTHREEBoxBasedVertices(G, 1, 0xffffff, 5);
296
+ graph3d.addVisElement(nodeGroup);
297
+
298
+ // Color specific nodes red — pass the inner mesh, not the Group
299
+ const mesh = nodeGroup.children[0];
300
+ const redNodeIds = [0, 3, 7, 12];
301
+ PGL.ThreeWrapper.ChangeTheVertexColours(mesh, redNodeIds, 0xff4444);
302
+
303
+ // To reset all colors back to white:
304
+ // PGL.ThreeWrapper.ResetVertexColors(mesh);
305
+ ```
306
+
307
+ To color nodes by a data property (e.g. a "community" field you stored in `node.data`):
308
+
309
+ ```javascript
310
+ const communityColors = { 0: 0xff6b6b, 1: 0x4ecdc4, 2: 0xffe66d, 3: 0xa8e6cf };
311
+ for (const [community, color] of Object.entries(communityColors)) {
312
+ const ids = [...G.nodes.entries()]
313
+ .filter(([, n]) => n.data?.community === Number(community))
314
+ .map(([id]) => id);
315
+ PGL.ThreeWrapper.ChangeTheVertexColours(mesh, ids, color);
316
+ }
317
+ ```
318
+
319
+ ---
320
+
321
+ ### Recipe 4 — Tooltip on hover (HTML overlay)
322
+
323
+ ```javascript
324
+ const G = await PGL.SampleData.LoadZKCSimulated();
325
+ const canvas = document.getElementById("displayCanvas");
326
+
327
+ const graph3d = new PGL.GraphDrawer.GraphDrawer3d({ graph: G, width: 800, height: 700, canvas });
328
+ await graph3d.init();
329
+
330
+ graph3d.controls.autoRotate = false; // disable rotation so hover is stable
331
+
332
+ const nodeGroup = PGL.ThreeWrapper.DrawTHREEBoxBasedVertices(G, 1, 0xffffff, 5);
333
+ graph3d.addVisElement(nodeGroup);
334
+ graph3d.addVisElement(PGL.ThreeWrapper.DrawTHREEGraphEdgesThick(G, 1, 0xffafcc, 10));
335
+
336
+ // Create a tooltip div in your HTML: <div id="tooltip" style="position:absolute;display:none;..."></div>
337
+ const tooltip = document.getElementById("tooltip");
338
+
339
+ // enableInteraction must come after addVisElement
340
+ graph3d.enableInteraction({
341
+ graph: G,
342
+ onNodeHover: (d) => {
343
+ if (d) {
344
+ tooltip.style.display = "block";
345
+ tooltip.textContent = `Node ${d.nodeId} — ${d.neighbours.length} neighbours`;
346
+ } else {
347
+ tooltip.style.display = "none";
348
+ }
349
+ },
350
+ });
351
+
352
+ function animate() { requestAnimationFrame(animate); graph3d.rendercall(); }
353
+ animate();
354
+ ```
355
+
356
+ ---
357
+
358
+ ### Recipe 5 — Highlight a node's neighbours on click
359
+
360
+ ```javascript
361
+ const G = await PGL.SampleData.LoadZKCSimulated();
362
+ const canvas = document.getElementById("displayCanvas");
363
+
364
+ const graph3d = new PGL.GraphDrawer.GraphDrawer3d({ graph: G, width: 800, height: 700, canvas });
365
+ await graph3d.init();
366
+
367
+ const nodeGroup = PGL.ThreeWrapper.DrawTHREEBoxBasedVertices(G, 1, 0xffffff, 5);
368
+ graph3d.addVisElement(nodeGroup);
369
+ graph3d.addVisElement(PGL.ThreeWrapper.DrawTHREEGraphEdgesThick(G, 1, 0x555555, 10));
370
+
371
+ const mesh = nodeGroup.children[0]; // inner instanced mesh
372
+
373
+ graph3d.enableInteraction({
374
+ graph: G,
375
+ onNodeClick: (d) => {
376
+ PGL.ThreeWrapper.ResetVertexColors(mesh); // clear previous highlight
377
+ PGL.ThreeWrapper.ChangeTheVertexColours(mesh, [d.nodeId], 0xffdd00); // clicked node = yellow
378
+ PGL.ThreeWrapper.ChangeTheVertexColours(mesh, d.neighbours, 0xff4444); // neighbours = red
379
+ },
380
+ });
381
+
382
+ function animate() { requestAnimationFrame(animate); graph3d.rendercall(); }
383
+ animate();
384
+ ```
385
+
386
+ ---
387
+
388
+ ### Recipe 6 — Live layout simulation (Kamada–Kawai)
389
+
390
+ Use `createKamadaKawai3D` + mutable draw calls so the geometry updates each frame without recreating it.
391
+
392
+ ```javascript
393
+ const G = await PGL.SampleData.LoadDwt1005();
394
+
395
+ const simulation = PGL.createKamadaKawai3D(G, {
396
+ simulationBound: 500,
397
+ cohesionValue: 1,
398
+ repulsionValue: 1,
399
+ iterationsPerStep: 1,
400
+ });
401
+
402
+ // Apply initial positions so the draw calls have something to start from
403
+ G.apply_position_map(simulation.getPositionMap());
404
+ const lmap = PGL.Drawing.DrawEdgeLinesDivisions(G, 1);
405
+ G.apply_edge_pos_maps(lmap);
406
+
407
+ const canvas = document.getElementById("displayCanvas");
408
+ const graph3d = new PGL.GraphDrawer.GraphDrawer3d({ graph: G, width: 800, height: 700, canvas });
409
+ await graph3d.init();
410
+
411
+ // Mutable variants return updater functions — required for animation
412
+ const { group, updatePositions } = PGL.ThreeWrapper.DrawTHREEGraphVerticesMutable(G, 0.1, 1, 0xffffff, 1);
413
+ const { group: edgeGroup, updateEdges } = PGL.ThreeWrapper.DrawTHREEGraphEdgesThinMutable(G, 0.1, 0xffafcc);
414
+ graph3d.addVisElement(group);
415
+ graph3d.addVisElement(edgeGroup);
416
+
417
+ let lastTime = performance.now();
418
+ function animate() {
419
+ requestAnimationFrame(animate);
420
+ const now = performance.now();
421
+ simulation.step((now - lastTime) / 1000); // advance the layout
422
+ lastTime = now;
423
+
424
+ G.apply_position_map(simulation.getPositionMap());
425
+ const lmap = PGL.Drawing.DrawEdgeLinesDivisions(G, 1);
426
+ G.apply_edge_pos_maps(lmap);
427
+ updatePositions(simulation.getPositions()); // push new positions to GPU
428
+ updateEdges(); // recompute edge geometry
429
+ graph3d.rendercall();
430
+ }
431
+ animate();
432
+ ```
433
+
434
+ ---
435
+
436
+ ## Common Mistakes
437
+
438
+ These are the issues that trip up most new users and AI-generated code. Check this list first when something doesn't render or a callback never fires.
439
+
440
+ | Mistake | Symptom | Fix |
441
+ |---|---|---|
442
+ | Passing the `Group` to `ChangeTheVertexColours` | Colors don't change | Pass `nodeVisualElements.children[0]` — the function needs the inner instanced mesh, not the outer Group. Same for `ResetVertexColors`. |
443
+ | Forgetting `await G.initialize()` after manually building a graph | BFS, Dijkstra, and layout return empty/wrong results | `GenerateErdosReyni_n_p` and manual `add_node` / `add_edge` loops do **not** auto-initialize. Always call `await G.initialize()` before running any algorithm or draw call. |
444
+ | Calling `initialize()` without `await` | Race condition — adjacency lists are half-built | It is async. Always `await G.initialize()`. |
445
+ | Using `DrawTHREEGraphVertices` in a bundler project (Vite, Parcel, Webpack) | Nodes invisible — texture `./Textures/Square.png` fails to load silently | Copy `Examples/Textures/` to your project's public folder, or use `DrawTHREEBoxBasedVertices` which needs no texture. |
446
+ | Calling `enableInteraction()` before `addVisElement()` | Callbacks never fire | The interaction layer raycasts against objects already in the scene. Always add all visual elements first, then call `enableInteraction()`. |
447
+ | Expecting `Dijkstra` to use edge weights | Shortest path looks wrong on weighted graphs | `Dijkstra` in PGL returns **hop counts** (unweighted BFS). There is no weighted shortest-path solver — use hop counts or implement your own on top of `get_adjacency()`. |
448
+ | Using a static draw call then trying to update positions | Node positions don't move in the animation loop | Static variants (`DrawTHREEBoxBasedVertices`, `DrawTHREEGraphEdgesThick`) bake geometry at creation. For live updates use the `Mutable` variants and call `updatePositions()` / `updateEdges()` each frame. |
449
+
450
+ ---
451
+
183
452
  ## Testing
184
453
 
185
454
  - **Unit tests:** `npm run test:unit` (Vitest; tests Utilities, Graph, Simulation, matrix helpers).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plebeiangraphlibrary",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "description": "A NetworkX like package for graphs in javascript",
5
5
  "source": "./Src/index.ts",
6
6
  "main": "./Build/pgl.js",