1

I'm trying to use the Cytoscape cola layout to render a graph that should apply a force directed layout while using it (so when dragging nodes around, they should act as if there is some gravity involved). Relevant libraries:

My first problem is that adding nodes to the graph via add(node) doesn't include them in the cola layout algorithm. The only way I found around that is to destroy the layout, re-initialize it and start it again. But this causes the nodes to jump in some cases.

I assumed that this was due to the fact that I completely destroyed the old layout but when setting up a minimal example, I realized that even just calling layout.stop() and layout.run() leads to nodes being repositioned.

In the following example, there is only one node. Moving the node via drag and drop, then pressing the "stop" button and then the "start" button causes the node to jump back to its initial position:

document.addEventListener('DOMContentLoaded', function(){
        
  // Register cola layout
  cytoscapeCola(cytoscape);

  var nodes = [{ data: { id: 1, name: 1 } }]
  var edges = [];

  var cy = window.cy = cytoscape({
    container: document.getElementById('cy'),

    style: [
      {
        selector: 'node[name]',
        style: {
          'content': 'data(name)'
        }
      },

      {
        selector: 'edge',
        style: {
          'curve-style': 'bezier',
          'target-arrow-shape': 'triangle'
        }
      },
    ],

    elements: {
      nodes: nodes,
      edges: edges
    }
  });

  var layout = cy.layout({
    name: 'cola',
    infinite: true,
    fit: false,
  });
  layout.run();

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

  document.querySelector('#stop').addEventListener('click', function() {
    layout.stop();
  });

  document.querySelector('#add-node').addEventListener('click', function() {
    var id = Math.random();
    cy.add({ group: 'nodes', data: { id: id, name: id } });
    cy.add({ group: 'edges', data: { source: id, target: _.head(nodes).data.id } });
    layout.stop();
    layout.destroy();
    layout = cy.layout({
      name: 'cola',
      infinite: true,
      fit: false,
    });
    layout.run();
  });

});
body {
  font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
  font-size: 14px;
}

#cy {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;
  z-index: 999;
}

h1 {
  opacity: 0.5;
  font-size: 1em;
  font-weight: bold;
}

#buttons {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 99999;
}
<!DOCTYPE>

<html>

  <head>
    <title>cytoscape-edgehandles.js demo for infinite layout</title>

    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">

    <script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>

    <script src="https://unpkg.com/webcola/WebCola/cola.min.js"></script> 
    <script src="https://cdn.jsdelivr.net/npm/cytoscape-cola@2.4.0/cytoscape-cola.min.js"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.js"></script>
    <script src="cytoscape-edgehandles.js"></script>
    
    </head>

  <body>
    <h1>cytoscape-edgehandles demo with an infinite layout</h1>
    <div id="cy"></div>
    <div id="buttons">
      <button id="start">Start</button>
      <button id="stop">Stop</button>
      <button id="add-node">Add Node</button>
    </div>
  </body>

</html>

Is this a bug or am I doing something wrong? Does anyone know how to stop and restart the layout without the nodes changing their position?

Thanks a lot, Jesse

Scooter Morris
  • 1,269
  • 1
  • 7
  • 4
Jesse
  • 422
  • 3
  • 19
  • I think the graph is just centering on your node (cy.fit() and cy.center() maybe?) – Stephan T. Feb 25 '21 at 22:22
  • Hi Stephan, thanks for your idea. In this minimal example it might look like the graph is just centering the node. But in an example with two clusters, you would see that one cluster stays in place whereas the one that was dragged, before stopping and restarting the layout, will jump upon restart :/ – Jesse Feb 26 '21 at 07:54

1 Answers1

3

Okay actually you were very close @Stephan. The problem was that WebCola centers the nodes when calling start by default: https://github.com/tgdwyer/WebCola/blob/78a24fc0dbf0b4eb4a12386db9c09b087633267d/src/layout.ts#L504

The cytoscape wrapper for WebCola does not currently support this option, so I forked it and added the option myself: https://github.com/deje1011/cytoscape.js-cola/commit/f357b97aba900327e12f97b1530c4df624ff9d61

I'll open a pull request at some point.

Now you can smoothly restart the layout like this:

layout.stop();
layout.destroy(); // cleanup event listeners
layout = graph.layout({ name: 'cola', infinite: true, fit: false, centerGraph: false });
layout.run()

This way, the nodes keep their position

Jesse
  • 422
  • 3
  • 19