All versions of this documentation
X

Force layout

New general-purpose force-directed layout. charge defines the strength with which the nodes repel each other. gravity stands for the global force, pulling the graph to its mass center. elasticity defines how the nodes collide with each other.

Open in a new window.
          <!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <script src="../build/ogma.min.js"></script>
  <style>
    html,
    body {
      font-family: Helvetica, Arial, Helvetica, sans-serif;
      padding: 0;
      margin: 0;
    }

    #graph-container {
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      position: absolute;
      margin: 0;
      overflow: hidden;
    }

    .control {
      position: absolute;
      top: 20px;
      right: 20px;
      padding: 10px;
      border-radius: 5px;
      box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
      background: #fff;
    }

    .control label {
      display: block;
      padding: 5px 0;
    }

    .control p {
      text-align: center;
    }

    .control .value {
      width: 50px;
      display: inline-block;
      font-family: Georgia, 'Times New Roman', Times, serif;
      font-weight: bold;
    }

    #time {
      text-align: center;
      font-family: Georgia, 'Times New Roman', Times, serif;
    }
  </style>
</head>

<body>
  <div id="graph-container"></div>
  <form class="control" id="params">
    <label>
      <span id="charge-value" class="value">5</span>
      <input type="range" min="0.5" max="50" value="5" name="charge"
        step="0.5" /> Charge
    </label>
    <label>
      <span id="elasticity-value" class="value">0.9</span>
      <input type="range" min="0" max="1.0" value="0.9" name="elasticity"
        step="0.01" /> Elasticity
    </label>
    <label>
      <span id="gravity-value" class="value">0.01</span>
      <input type="range" min="0.005" max="0.15" value="0.01" name="gravity"
        step="0.005" /> Gravity
    </label>
    <label>
      <span id="edgeStrength-value" class="value">0.75</span>
      <input type="range" min="0.0" max="30" value="0.75" name="edgeStrength"
        step="0.25" /> Edge strength
    </label>
    <p><button type="button" id="randomize">Randomize</button><button
        type="button" id="run">Run</button></p>
    <div id="time"></div>
  </form>

  <script>
    'use strict';

    let ogma = new Ogma({
      container: 'graph-container',
      options: { interactions: { drag: { enabled: false } } }
    });
    let padding = 100;
    let animationDuration = 300;
    // disable node dragging
    //ogma.setOptions({ interactions: { drag: { enabled: false }}});

    // generate random graph
    ogma.generate
      .barabasiAlbert({
        nodes: 500,
        m0: 10,
        m: 1
      })
      .then(function (graph) {
        // add some disconnected components and 'orphan' nodes
        graph.nodes.forEach(function (node) {
          delete node.attributes;
        });
        addSmallComponents(graph, Math.floor(Math.log(graph.nodes.length)), 5);
        addLooseNodes(graph, Math.floor(graph.nodes.length / 3));
        ogma.setGraph(graph);
        return ogma.view.locateGraph({ padding: 100 });
      })
      .then(function () {
        return run({ padding: padding });
      });

    let form = document.querySelector('#params');
    let callTimer = 0;

    let charge, gravity, elasticity, edgeStrength;
    function update() {
      charge = parseFloat(form['charge'].value);
      gravity = parseFloat(form['gravity'].value);
      elasticity = parseFloat(form['elasticity'].value);
      edgeStrength = parseFloat(form['edgeStrength'].value);

      document.getElementById('charge-value').innerHTML = charge;
      document.getElementById('gravity-value').innerHTML = gravity;
      document.getElementById('elasticity-value').innerHTML = elasticity;
      document.getElementById('edgeStrength-value').innerHTML = edgeStrength;
    }

    form.addEventListener(
      'input',
      function () {
        clearTimeout(callTimer);
        update();
        callTimer = setTimeout(run, 100);
      },
      true
    );

    document.querySelector('#randomize').addEventListener('click', randomize);
    document.querySelector('#run').addEventListener('click', function () {
      run();
    });

    function run(locate) {
      let start = Date.now();
      return ogma.layouts
        .force({
          //useWebWorker: false,
          charge: charge,
          gravity: gravity,
          elasticity: elasticity,
          locate: locate,
          duartion: animationDuration,
          edgeStrength: edgeStrength
        })
        .then(function () {
          let duration = Date.now() - start;
          document.getElementById('time').innerHTML =
            'done in ' +
            ((duration - animationDuration) / 1000).toFixed(2) +
            's';
        });
    }

    update();

    function addLooseNodes(graph, n) {
      let baseId = graph.nodes.length;
      for (let i = 0; i < n; i++) {
        graph.nodes.push({ id: baseId + i });
      }
    }

    function addSmallComponents(graph, n, m) {
      for (let i = 0; i < n; i++) {
        let baseId = graph.nodes.length;
        for (let j = 0; j < m + 1; j++) {
          graph.nodes.push({ id: baseId + j });
        }
        for (let k = 1; k < m + 1; k++) {
          graph.edges.push({ source: baseId, target: baseId + k });
        }
      }
    }

    function randomize() {
      ogma.getNodes().forEach(function (node) {
        node.setAttributes({
          x: Math.random() * 150,
          y: Math.random() * 150
        });
      });
    }
  </script>
</body>

</html>