Maddy89
Maddy89

Reputation: 177

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

Upvotes: 2

Views: 1438

Answers (2)

Maddy89
Maddy89

Reputation: 177

The following code worked for me,

Please consider @Stephen's code if this doesn't work.

var cy = cytoscape({
    container: /* your div within which you want to render */ ,
    elements: [ /*list of graph elements to start with */ ] ,
    style: [ /* the stylesheet for the graph */ ] ,
    layout:{
                name: 'cola',
                fit: false,
                infinite: false,
                avoidOverlap: true
            }
});

//Used to make child nodes stay on the same row
cy.ready(function(){
                setTimeout(function(){
                    cy.zoom(0.5);
                    cy.nodes(':compound').forEach(function(comp,j,comps){
                        var nodePosition={};
                        if(comp.descendants().length>1)
                        {
                            var minX;
                            var maxY;
                            comp.descendants().forEach(function(ele,i,eles){
                                if(i==0)
                                {
                                    minX=ele.renderedPosition('x');
                                    maxY=ele.renderedPosition('y');
                                }
                                else
                                {
                                    var tempX=ele.renderedPosition('x');
                                    var tempY=ele.renderedPosition('y');
                                    if(tempX<minX)
                                    {
                                        minX=tempX;
                                    }
                                    if(tempY>maxY)
                                    {
                                        maxY=tempY;
                                    }
                                }
                            });
                            comp.descendants().forEach(function(ele,i,eles){
                                ele.renderedPosition({x:minX,y:maxY});
                                minX=minX+60;
                            });
                        }
                        cy.resize();
                        cy.fit();
                        cy.minZoom(cy.zoom());
                    });
                },1000);
            });

P.S: Don't forget to include the cytoscape scripts;)

Upvotes: 2

Stephan T.
Stephan T.

Reputation: 6074

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/[email protected]/cytoscape-even-parent.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/cytoscape-no-overlap.min.js"></script>
</head>

<body>
  <div id="cy"></div>
</body>

</html>

Upvotes: 4

Related Questions