Search code examples
javascriptvisualizationgrid-layoutcytoscape.js

Compound nodes overlap while using grid layout in cytoscape.js


I am using cytoscape.js for my visualization project in which I have to show a Hierarchical structure with compound nodes.

So I initially used Cose-Bilkent layout which worked like a charm but the requirement is that all the child nodes of a parent must be in a single row. So I tried to tweak around a bit but couldn't get the exact result.

Then I tried to use grid layout by giving hardcoded row and column numbers and I got the exact result but as my data is dynamic I realized its difficult to assign row numbers and column numbers on my own.

Here is the data I used,

elements: [ // list of graph elements to start with
            { // node a
                data: { id: 'X1', label: 'X1'}
            },
            {
                data: { id: 'X2', label: 'X2'}
            },
            {
                data: { id: 'X3', label: 'X3'}
            },
            {
                data: { id: 'X4', label: 'X4'}
            },
            {
                data: { id: 'X5', label: 'X5'}
            },
            {
                data: { id: 'X6', label: 'X6'}
            },
            {
                data: { id: 'X7', label: 'X7'}
            },
            {
                data: { id: 'X8', label: 'X8'}
            },
            {
                data: { id: 'X9', label: 'X9'}
            },
            {
                  data: { id: 'X10', label: 'X10'}
            },
            {
                data: { id: 'X1e1',label: 'e1', parent: 'X1', row: '1' ,col: '1'}
            },
            {
                data: { id: 'X1e5',label: 'e5', parent: 'X1', row: '1',col: '2'}
            },
            {
                data: { id: 'X1e6',label: 'e6', parent: 'X1', row: '1',col: '3'}
            },
            {
                data: { id: 'X2e2',label: 'e2', parent: 'X2', row: '3',col: '1'}
            },
            {
                data: { id: 'X2e3',label: 'e3', parent: 'X2', row: '3',col: '2'}
             },
            {
                data: { id: 'X3e4',label: 'e4', parent: 'X3', row: '4',col: '1'}
            },
            {
                data: { id: 'X4e5',label: 'e5', parent: 'X4', row: '2',col: '1'}
            },
            {
                data: { id: 'X4e6',label: 'e6', parent: 'X4', row: '2',col: '2'}
            },
            {
                data: { id: 'X5e7',label: 'e7', parent: 'X5', row: '7',col: '1'}
            },
            {
                data: { id: 'X6e8',label: 'e8', parent: 'X6', row: '5',col: '1'}
            },
            {
                data: { id: 'X6e9',label: 'e9', parent: 'X6', row: '5',col: '2'}
            },
            {
                data: { id: 'X7e10',label: 'e10', parent: 'X7', row: '7',col: '2'}
            },
            {
                data: { id: 'X7e11',label: 'e11', parent: 'X7', row: '7',col: '3'}
            },
            {
                data: { id: 'X7e12',label: 'e12', parent: 'X7', row: '7',col: '4'}
            },
            {
                data: { id: 'X8e13',label: 'e13', parent: 'X8', row: '6',col: '1'}
            },
            {
                data: { id: 'X8e14',label: 'e14', parent: 'X8', row: '6',col: '2'}
            },
            {
                data: { id: 'X8e15',label: 'e15', parent: 'X8', row: '6',col: '3'}
            },
            {
                data: { id: 'X8e16',label: 'e16', parent: 'X8', row: '6',col: '4'}
            },
            {
                data: { id: 'X9e17',label: 'e17', parent: 'X9', row: '8',col: '1'}
            },
            {
                data: { id: 'X10e18',label: 'e18', parent: 'X10', row: '8',col: '2'}
            },
            {
                data: { id: 'X1e5X4e5', source:'X1e5', target:'X4e5'}
            },
            {
                data: { id: 'X1e6X4e6', source:'X1e6', target:'X4e6'}
            },
            {
                data: { id: 'X1e1X2', source:'X1e1', target:'X2'}
            },
            {
                data: { id: 'X2e3X3', source:'X2e3', target:'X3'}
            },
            {
                data: { id: 'X4e5X5', source:'X4e5', target:'X5'}
            },
            {
                data: { id: 'X4e6X6', source:'X4e6', target:'X6'}
            },
            {
                data: { id: 'X6X8e16', source:'X6', target:'X8e16'}
            },
            {
                data: { id: 'X6e9X8', source:'X6e9', target:'X8'}
            },
            {
                data: { id: 'X6e8X7', source:'X6e8', target:'X7'}
            },
            {
                data: { id: 'X6X7e12', source:'X6', target:'X7e12'}
            }
        ]

and layout

layout:{
            name: 'grid',
            fit: true,
            position: function( node ){ return {row:node.data('row'), col:node.data('col') }} 
        }

And here is the result I got(and also expected) by setting manual rows and columns

Required visualization image

Any help would be appreciated. Thanks


Solution

  • Well there are two extensions, which would achieve just what you need:

    Coincidentally, both come from the same person, so this should not be a problem at all, all you have to do from there is to apply the right styles for the application to look like your example:

    document.addEventListener("DOMContentLoaded", function() {
      var cy = (window.cy = cytoscape({
        container: document.getElementById("cy"),
        layout: {
          name: "evenParent"
        },
        style: [{
            selector: "node",
            style: {
              "content": "data(id)",
              "background-color": "#ad1a66"
            }
          },
          {
            selector: ":parent",
            style: {
              "background-opacity": 0.333
            }
          },
          {
            selector: "edge",
            style: {
              width: 3,
              "line-color": "#ad1a66"
            }
          },
          {
            selector: "edge.meta",
            style: {
              width: 2,
              "line-color": "red"
            }
          },
          {
            selector: ":selected",
            style: {
              "border-width": 3,
              "border-color": "#DAA520"
            }
          }
        ],
        elements: {
          nodes: [{
              data: {
                id: "Jerry",
                name: "Jerry"
              }
            },
            {
              data: {
                id: "Elaine",
                name: "Elaine"
              }
            },
            {
              data: {
                id: "Kramer",
                name: "Kramer"
              }
            },
            {
              data: {
                id: "George",
                name: "George"
              }
            },
            {
              data: {
                id: "Martin",
                name: "Martin"
              }
            },
            {
              data: {
                id: "Philippe",
                name: "Philippe"
              }
            },
            {
              data: {
                id: "Louis",
                name: "Louis"
              }
            },
            {
              data: {
                id: "Genevieve",
                name: "Genevieve"
              }
            },
            {
              data: {
                id: "Leo",
                name: "Leo"
              }
            },
            {
              data: {
                id: "Larry",
                name: "Larry"
              }
            },
            {
              data: {
                id: "Logaina",
                name: "Logaina"
              }
            }
          ],
          edges: [{
              data: {
                source: "Jerry",
                target: "Elaine"
              }
            },
            {
              data: {
                source: "Jerry",
                target: "Kramer"
              }
            },
            {
              data: {
                source: "Jerry",
                target: "George"
              }
            },
            {
              data: {
                source: "Elaine",
                target: "Martin"
              }
            },
            {
              data: {
                source: "Elaine",
                target: "Philippe"
              }
            },
            {
              data: {
                source: "Elaine",
                target: "Louis"
              }
            },
            {
              data: {
                source: "Elaine",
                target: "Genevieve"
              }
            },
            {
              data: {
                source: "Elaine",
                target: "Leo"
              }
            },
            {
              data: {
                source: "Kramer",
                target: "Larry"
              }
            },
            {
              data: {
                source: "Kramer",
                target: "Logaina"
              }
            }
          ]
        }
      }));
    
      // demo your collection ext
      cy.nodes().noOverlap({
        padding: 5
      });
    });
    body {
      font: 14px helvetica neue, helvetica, arial, sans-serif;
    }
    
    #cy {
      height: 100%;
      width: 100%;
      position: absolute;
      left: 0;
      top: 0;
    }
    <html>
    
    <head>
      <script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/cytoscape-even-parent@1.1.1/cytoscape-even-parent.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/cytoscape-no-overlap@1.0.1/cytoscape-no-overlap.min.js"></script>
    </head>
    
    <body>
      <div id="cy"></div>
    </body>
    
    </html>