Skip to content
  1. Examples

Force layout large graph

This example shows how the force-directed layout performs with large graphs. You can play with the theta parametter to make it faster/less precise. But we recommend to use the GPU version of the force layout for large graphs. The GPU mode is much faster and provides better final layout quality.

ts
import Ogma from '@linkurious/ogma';

const ogma = new Ogma({
  container: 'graph-container',
  options: { interactions: { drag: { enabled: false } } }
});
const padding = 100;

const run = (locate?: boolean) => {
  const timeOutput = document.getElementById('time')!;
  timeOutput.innerHTML = timeOutput.getAttribute('data-placeholder')!;
  const start = Date.now();
  return ogma.layouts
    .force({
      steps: steps,
      charge: charge,
      theta: theta,
      gravity: gravity,
      elasticity: elasticity,
      locate
    })
    .then(() => {
      timeOutput.innerHTML = (Date.now() - start) / 1000 + 's';
    });
};

const randomize = () => {
  ogma.getNodes().forEach(node => {
    node.setAttributes({
      x: Math.random() * 150,
      y: Math.random() * 150
    });
  });
};

// generate random graph
Ogma.parse
  .jsonFromUrl('files/relativity.json')
  .then(graph => {
    graph.nodes.forEach(node => {
      node.attributes = { radius: 15 /*color: '#0E948A' */ };
    });
    graph.edges.forEach(edge => {
      //edge.attributes = { color: '#aaaaaa' };
    });
    document.getElementById('n')!.textContent = 'nodes: ' + graph.nodes.length;
    document.getElementById('e')!.textContent = 'edges: ' + graph.edges.length;
    return ogma.setGraph(graph);
  })
  .then(() => {
    randomize();
    ogma.view.setZoom(0.1, { ignoreZoomLimits: true });
  })
  .then(() => {
    return run();
  })
  .then(() => {
    return ogma.view.locateGraph({ padding: padding });
  });

const form = document.querySelector('#params') as HTMLFormElement;
let callTimer: ReturnType<typeof setTimeout>;

let charge: number,
  gravity: number,
  theta: number,
  elasticity: number,
  steps: number;
const update = () => {
  steps = parseFloat(form['steps'].value);
  charge = parseFloat(form['charge'].value);
  gravity = parseFloat(form['gravity'].value);
  elasticity = parseFloat(form['elasticity'].value);
  theta = parseFloat(form['theta'].value);

  document.getElementById('steps-value')!.innerHTML = steps.toString();
  document.getElementById('charge-value')!.innerHTML = charge.toString();
  document.getElementById('gravity-value')!.innerHTML = gravity.toString();
  document.getElementById('elasticity-value')!.innerHTML =
    elasticity.toString();
  document.getElementById('theta-value')!.innerHTML = theta.toString();
};

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

document.querySelector('#randomize')!.addEventListener('click', randomize);
document.querySelector('#run')!.addEventListener('click', () => run());

update();
html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />

    <link type="text/css" rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div id="graph-container"></div>
    <form class="control" id="params">
      <label>
        <span id="steps-value" class="value">80</span>
        <input
          type="range"
          min="10"
          max="300"
          value="80"
          name="steps"
          step="10"
        />
        Steps
      </label>
      <label>
        <span id="charge-value" class="value">10</span>
        <input
          type="range"
          min="0.5"
          max="50"
          value="10"
          name="charge"
          step="0.5"
        />
        Charge
      </label>
      <label>
        <span id="theta-value" class="value">0.81</span>
        <input
          type="range"
          min="0.15"
          max="1"
          value="0.81"
          name="theta"
          step="0.01"
        />
        &theta; (precision)
      </label>
      <label>
        <span id="elasticity-value" class="value">0.1</span>
        <input
          type="range"
          min="0"
          max="1.0"
          value="0.1"
          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>
      <div id="time" data-placeholder="&hellip;">&hellip;</div>
      <p>
        <button type="button" id="randomize">Randomize</button
        ><button type="button" id="run">Run</button>
      </p>
    </form>
    <div id="n" class="info n">
      loading a large graph, it can take a few seconds...
    </div>
    <div id="e" class="info e"></div>
    <script type="module" src="index.ts"></script>
  </body>
</html>
css
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;
}
.control #time {
  text-align: center;
}
.info {
  position: absolute;
  color: #fff;
  background: #141229;
  font-size: 12px;
  font-family: monospace;
  padding: 5px;
}
.info.n {
  top: 0;
  left: 0;
}
.info.e {
  top: 20px;
  left: 0;
}