ngraph.forcelayout

repository·main·Indexed 42 days ago

https://github.com/anvaka/ngraph.forcelayout

A high-performance, multi-dimensional force-directed graph layout algorithm (version 3.3.1) that uses a quadtree to accelerate long-distance force calculations. It supports 2D, 3D, and N-dimensional spaces, operating on ngraph.graph instances. Key features include node pinning, customizable physics settings (gravity, spring length, drag), and the ability to monitor graph changes. The layout is iterative, using a step() method to converge to a stable state.

Tokens
3.4K
Snippets
19
Records
20
Agent score
53%

What's inside ngraph.forcelayout

  1. Understand the ngraph.forcelayout demo stack

    main

    The demo application is composed of the following technologies:

    • ngraph.graph: Used as the underlying graph data structure.
    • ngraph.forcelayout: Provides the core graph layout engine.
    • w-gl: A high-performance WebGL renderer used for visualization.
    • Vue.js: Powers the user interface and developer tools.
  2. Node position and object reuse

    main

    For performance reasons, ngraph.forcelayout does not follow immutability principles. The objects returned by getNodePosition() and getLinkPosition() are reused.

    Calling layout.getNodePosition(id) multiple times will return the exact same object reference, which is updated in-place during each simulation step. If you need to persist a snapshot of a position, you must copy the values manually.

    // This will be true because the object is reused
    layout.getNodePosition(1) === layout.getNodePosition(1);
  3. Run a force-directed graph layout

    main

    The layout algorithm is iterative. You must call layout.step() multiple times to allow the graph to converge to a stable state. The layout operates on an ngraph.graph instance.

    To retrieve positions after iterations:

    • Use layout.getNodePosition(nodeId) to get {x, y, ...} coordinates.
    • Use layout.getLinkPosition(linkId) to get {from: {x, y}, to: {x, y}} coordinates.
    // graph is an instance of `ngraph.graph` object.
    var createLayout = require('ngraph.forcelayout');
    var layout = createLayout(graph);
    for (var i = 0; i < ITERATIONS_COUNT; ++i) {
      layout.step();
    }
    
    // now we can ask layout where each node/link is best positioned:
    graph.forEachNode(function(node) {
      console.log(layout.getNodePosition(node.id));
      // Node position is pair of x,y coordinates: {x: ... , y: ... }
    });
    
    graph.forEachLink(function(link) {
      console.log(layout.getLinkPosition(link.id));
      // link position is a pair of two positions: { from: {x, y}, to: {x, y} }
    });
  4. Use the precompiled version of ngraph.forcelayout

    main

    To use a precompiled version of the force layout without a complex build step, you can implement the logic directly within an HTML file. The implementation is minimal, typically requiring less than 70 lines of code, and can be contained entirely within a single index.html file.

    <!-- The implementation is contained within index.html -->
    <script src=".../ngraph.forcelayout.js"></script>
    <script>
      // Force layout logic goes here
    </script>
  5. Run the ngraph.forcelayout demo for development

    main
    To start the demo in development mode with hot-reloading enabled, use the npm start command. Once running, the demo renders a simple graph and allows for basic layout manipulation. You can load new graphs by dragging and dropping any .dot file directly into the browser window.
    npm start
  6. Install ngraph.forcelayout

    main

    You can install the library via npm or include it directly in your HTML using a CDN.

    If using a CDN, the library is available under the global name ngraphCreateLayout.

    npm install ngraph.forcelayout
    <script src='https://unpkg.com/ngraph.forcelayout@3.0.0/dist/ngraph.forcelayout.min.js'></script>
  7. Handle layout events

    main

    The layout object is an event emitter (via ngraph.events) and supports the following events:

    • 'step': Emitted after every call to step().
    • 'stable': Emitted when the system reaches a stable state (when step() returns true).
    • 'disposed': Emitted when dispose() is called to release resources.
    layout.on('stable', (isStable) => {
      console.log('Layout stability changed:', isStable);
    });
    
    layout.on('step', () => {
      // Perform actions after each step
    });
  8. Configure the Physics Simulator

    main

    The layout uses a physics simulator driven by three forces: Spring force (Hooke's law), Repulsion (Coulomb's law), and Drag. You can customize the simulation behavior via a settings object passed to createLayout.

    Available settings:

    • timeStep: Simulation time step.
    • dimensions: Number of dimensions.
    • gravity: Gravity strength.
    • theta: Barnes-Hut parameter.
    • springLength: Target distance for connected nodes.
    • springCoefficient: Strength of the spring force.
    • dragCoefficient: Strength of the drag force.
    var physicsSettings = {
      timeStep: 0.5,
      dimensions: 2,
      gravity: -12,
      theta: 0.8,
      springLength: 10,
      springCoefficient: 0.8,
      dragCoefficient: 0.9,
    };
    
    var layout = require('ngraph.forcelayout')(graph, physicsSettings);
    
    // Access the read-only simulator instance
    var simulator = layout.simulator;
  9. Monitor graph changes and dispose layout

    main

    The layout automatically monitors ngraph.graph events to keep the simulation up to date when nodes or links are added/removed. To stop this monitoring and clean up resources, call layout.dispose().

    var graph = require('ngraph.graph')();
    var layout = require('ngraph.forcelayout')(graph);
    
    graph.addLink(1, 2); // layout updates automatically
    
    layout.dispose(); // stop monitoring
  10. Pin nodes and set initial positions

    main

    You can prevent specific nodes from moving by "pinning" them. This is useful for fixed points or user-dragged nodes.

    • layout.pinNode(node, true): Prevents the node from moving.
    • layout.isNodePinned(node): Returns whether a node is currently pinned.
    • layout.setNodePosition(nodeId, x, y): Manually sets a node's position. This can be used to move a pinned node or set initial positions.
    var nodeToPin = graph.getNode(nodeId);
    layout.pinNode(nodeToPin, true); // now layout will not move this node
    
    // Toggle pinning state
    layout.pinNode(node, !layout.isNodePinned(node));
    
    // Manually set position
    layout.setNodePosition(nodeId, x, y);
  11. Get the graph bounding box

    main

    To find the spatial extent of the current layout, use layout.getGraphRect(). It returns an object containing the bounding box coordinates.

    var rect = layout.getGraphRect();
    // rect.min_x, rect.min_y - left top coordinates
    // rect.max_x, rect.max_y - right bottom coordinates