All versions of this documentation
X

Incremental ungrouping

Double-click green nodes to incrementally expand the grouped nodes. Double-click expanded nodes to collapse their leafs.

Open in a new window.
          <!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <script src="../build/ogma.min.js"></script>
  <style>
    #graph-container { top: 0; bottom: 0; left: 0; right: 0; position: absolute; margin: 0; overflow: hidden; }
  </style>
</head>
<body>
  <div id="graph-container"></div>

<script>
'use strict';

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

var duration = 500;

function isLeaf(n) { return n.getDegree({ filter: 'all' }) === 1; }
function isRoot(n) { return n.getDegree() > 3;   }

// Generates graph with high-degree nodes
function generateDenseTree(N, maxR, minR) {
  maxR = maxR || 5;
  minR = minR || 5;
  return new Promise(function(resolve, reject) {
    var nodes = new Array(N).fill(0).map(function(_, i) {
      return {
        id: i,
        attributes: {
          text: 'Node #' + i,
          radius: minR + Math.random() * (maxR - minR),
          x: -50  + Math.random() * 100,
          y: -50  + Math.random() * 100
        }
      };
    });
    var edges = new Array(N - 1).fill(0).map(function(_, i) {
      return {
        id: i,
        source: Math.floor(Math.sqrt(i) / 2),
        target: i + 1
      };
    });

    resolve({ nodes: nodes, edges: edges });
  });
}


// collapses node leafs
function collapseNode(node, duration) {
  var leafs = node.getAdjacentNodes().filter(isLeaf);
  var nodesToGroup = leafs.concat(node);
  var pos = node.getPosition();

  duration = duration || 0;

  // add collapse grouping rule
  return ogma.transformations.addNodeGrouping({
    restorePositions: false,
    selector: function(node) {
      return nodesToGroup.includes(node);
    },
    nodeGenerator: function(nodes, id, transformation) {
      return {
        // keep the same position
        id: 'p' + node.getId(),
        attributes: {
          text:  'Parent ' + node.getId(),
          color: '#008800'
        },
        data:  {
          groupId: transformation.getId(),
          centralNode: node.getId(), // link to central node,
          isMetaNode: true
        }
      };
    },
    duration: duration
  });
}


function getTransformationById(id) {
  var transformations = ogma.transformations.getList();
  for (var i = 0; i < transformations.length; i++) {
    if (transformations[i].getId() === id) {
      return transformations[i];
    }
  }
  return null;
}


// Expands high-degree node
function expandNode(node, duration) {
  // retrieve grouping data
  var centralNode = ogma.getNode(node.getData('centralNode'));
  var groupId = node.getData('groupId');
  // use filters to retrieve hidden grouped nodes
  var grouped = centralNode
    .getAdjacentNodes({ filter: 'all' })
    .filter(isLeaf)
    .concat(centralNode);
  // translate all nodes to origin before they re-appear on canvas
  var currentPos = node.getPosition();

  // remove group and use incremental positioning layout
  return getTransformationById(groupId)
    .delete()
    .then(function () {
      return grouped.setAttributes(currentPos);
    })
    .then(function() {
      return ogma.layouts.forceLink({
        nodes: grouped,
        incremental: true,
        randomize: 'locally', // to deal with node overlapping
        duration: duration || 0
      });
    });
}

// Utility function to group "flower"-nodes for this demo
function collapseFlowerNodes() {
  var coreNodes = ogma.getNodes().filter(isRoot);
  coreNodes.setAttributes({ color: 'green' });

  // collapse flower-nodes
  return Promise.all(coreNodes.map(function (n) {
    return collapseNode(n).whenApplied();
  }));
}

// generate graph and collapse the nodes
generateDenseTree(300)
  .then(function (graph) { return ogma.setGraph(graph); })
  .then(collapseFlowerNodes)
  .then(function () { return ogma.layouts.forceLink(); })
  .then(function () { return ogma.view.locateGraph(); });

// expand or collapse on double click
ogma.events.onDoubleClick(function (evt) {
  if (evt.target && evt.target.isNode) {
    var node = evt.target;
    if (node.getData('isMetaNode')) {  // expand grouped node
      expandNode(node, duration).then(function () {
        ogma.view.locateGraph({ duration: duration });
      });
    } else if (node.getDegree() > 3) { // collapse high-degree node
      collapseNode(node, duration);
    }
  }
});


</script>
</body>
</html>