All versions of this documentation
X

Performance Workbench

In this demo it is possible to stress test Ogma on the performance side.
Load from tens of nodes up to thousands and check layout or animation effect on performances.
FPS is the measure used to measure performance here:

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

<head>
  <meta charset="utf-8">
  <script src="//mrdoob.github.io/stats.js/build/stats.min.js"></script>
  <script src="../build/ogma.min.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
      width: 100%;
      height: 100%;
      font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif;
    }

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

    #ui {
      text-align: center;
    }

    h4 {
      margin: 15px 5px 5px 5px;
    }

    .toolbar {
      display: block;
      position: absolute;
      top: 20px;
      right: 20px;
      padding: 10px;
      box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
      border-radius: 4px;
      background: #ffffff;
      color: #222222;
      font-weight: 300;
    }

    .controls {
      text-align: center;
      margin-top: 5px;
    }

    #stats {
      padding: 20px;
    }

    #stats div {
      margin: 0 auto;
      width: 80px;
    }

    .btn {
      padding: 6px 8px;
      background-color: white;
      cursor: pointer;
      font-size: 18px;
      border: none;
      border-radius: 5px;
      outline: none;
    }

    .btn:hover {
      color: #333;
      background-color: #e6e6e6;
    }

    .menu {
      border: 1px solid #ddd;
      width: 80%;
      font-size: 14px;
      margin-top: 10px;
    }
  </style>
  <link rel="stylesheet" type="text/css"
    href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" />
</head>

<body>
  <div id="graph-container"></div>
  <div class="toolbar" id="ui">
    <div class="controls">
      <button id="small" name="dataset" class="btn menu">10s of nodes</button>
      <button id="medium" name="dataset" class="btn menu">100s of nodes</button>
      <button id="big" name="dataset" class="btn menu">1000s of nodes</button>
    </div>
    <div class="controls">
      <h4>Stress tests</h4>
      <button id="animate" class="btn menu">Animate</button>
      <button id="layout" class="btn menu">Layout</button>
    </div>
    <h4>Metrics</h4>
    <div id="stats"></div>
    <div id="layout-stats">Layout time: ...</div>
  </div>
  <script>
    function addStatsPanel() {
      var stats = new Stats();
      stats.showPanel(0);
      document.querySelector('#stats').appendChild(stats.dom);
      requestAnimationFrame(function loop() {
        stats.update();
        requestAnimationFrame(loop)
      });
      stats.dom.style.position = null;
      stats.dom.style.left = null;
      stats.dom.style.top = null;
      stats.dom.addEventListener('click', function () {
        stats.showPanel(0);
      }, false);
    }

    var ogma = new Ogma({
      container: 'graph-container'
    });

    // add the stats utility
    addStatsPanel()

    var SIZES = {
      small: 50,
      medium: 500,
      big: 3000
    }
    var COLOR_STEPS = ['#c7e9c0', '#74c476', '#238b45', '#00441b']
    var minVisibleSize = 0;
    var w = document.documentElement.clientWidth;
    var h = document.documentElement.clientHeight;
    var isAnimated = false;

    function loadGraph(size) {
      return ogma.generate.randomTree({ nodes: size })
        .then(({ nodes, edges }) => {
          nodes.forEach((n, i) => {
            n.attributes = {
              text: { content: 'Node text ' + i, minVisibleSize },
              radius: 5,
              x: Math.random() * w,
              y: Math.random() * h,
              color: COLOR_STEPS[0],
              outerStroke: {
                scalingMethod: 'scaled',
                width: 1,
                color: COLOR_STEPS[3],
              },
              innerStroke: {
                width: 1,
                scalingMethod: 'scaled'
              },
              outline: false,
              icon: {
                font: 'Font Awesome 5 Free',
                content: '\uf007',
                style: 'bold',
                color: '#0f2560'
              }
            };
          });
          edges.forEach((e, i) => {
            e.attributes = {
              text: 'Edge text ' + i,
              color: COLOR_STEPS[4]
            };
          });
          return { nodes, edges };
        })
        .then((graph) => ogma.setGraph(graph))
        .then(() => {
          return ogma.view.locateGraph();
        });
    }

    function runLayout() {
      var start = Date.now();
      return ogma.layouts.force({
        locate: { padding: 50 },
      }).then(function () {
        updateLayoutTiming(Date.now() - start);
      });
    }

    // Animate nodes and edges by position and colors
    function animate(i) {
      var newColor = COLOR_STEPS[i] || COLOR_STEPS[0];
      var nodes = ogma.getNodes();
      var edges = ogma.getEdges();
      var duration = 3000;

      if (isAnimated) {
        ogma.styles.setNodeTextsVisibility(false);
        return Promise.all([
          edges.setAttribute('color', newColor, duration),
          nodes.setAttributes({
            x: function () { return Math.random() * w; },
            y: function () { return Math.random() * h; },
            color: newColor
          }, duration)
        ]).then(function () {
          return animate((i + 1) % COLOR_STEPS.length);
        });
      } else {
        ogma.styles.setNodeTextsVisibility(true);
      }
    }

    function updateLayoutTiming(duration) {
      var formattedTime = (duration / 1000).toFixed(2);
      var message = 'Layout time: ' + formattedTime + 's for ' + ogma.getNodes().size + ' nodes';
      document.querySelector('#layout-stats').innerText = message;
    }

    function updateAnimateButton() {
      var newLabel = isAnimated ? 'Stop animation' : 'Animate';
      document.querySelector('#animate').innerText = newLabel;
    }

    document.querySelectorAll('button[name="dataset"]').forEach(function (button) {
      button.addEventListener('click', function (evt) {

        isAnimated = false;
        updateAnimateButton();

        evt.preventDefault();
        var id = evt.target.id;
        var size = SIZES[id];
        loadGraph(size);
      }, false)
    });

    document.querySelector('#layout').addEventListener('click', function () {
      isAnimated = false;
      updateAnimateButton();
      runLayout();
    }, false);

    document.querySelector('#animate').addEventListener('click', function (evt) {
      isAnimated = evt.target.innerText === 'Animate';
      animate(0);
      updateAnimateButton();
    }, false);

    loadGraph(SIZES.medium).then(runLayout);
  </script>
</body>
</html>