graphvizdotgraph-drawingneato

Large enough bounding box for graph cluster


I want to programmatically draw graphs (preferably using dot, gvpack and neato). I can generate the graph structure using a library, then write a file containing the graph described with dot, and then pack all graphs in the same file. Each graph has a label that acts as a title for it, which contains essential information and cannot be omitted. Unfortunately, the label is sometimes too wide.

enter image description here

Now, the result is as I expected. However, I would like the bounding boxes of the individual graphs to be larger in order for them to fit the entirety of the "title". Then, I would like the graphs to be arranged in such a way that not even the bounding boxes overlap.

But I don't know how to tell dot, gvpack and/or neato to use a bounding box large enough to fit each individual graph while disallowing overlap.

Can this be done? If so, how? Thank you!

MWE

These are the only two trees of 4 vertices in the image above. File tree_04_00.raw:

graph {
    layout = neato
    subgraph cluster_0 {
        label = "idx= 0, centre=(0,1), centroid= (0,1)";
        nd_0 [label= "0"];
        nd_1 [label= "1"];
        nd_2 [label= "2"];
        nd_3 [label= "3"];
        nd_0 -- nd_1;
        nd_0 -- nd_3;
        nd_1 -- nd_2;
    }
}

File tree_04_01.raw:

graph {
    layout = neato
    subgraph cluster_1 {
        label = "idx= 1, centre=(0), centroid= (0)";
        nd_4 [label= "0"];
        nd_5 [label= "1"];
        nd_6 [label= "2"];
        nd_7 [label= "3"];
        nd_4 -- nd_5;
        nd_4 -- nd_6;
        nd_4 -- nd_7;
    }
}

To actually make the image above, I execute the following commands:

# dot for each .raw file
$ dot -Tdot tree_04_00.raw > tree_04_00.dot
$ dot -Tdot tree_04_01.raw > tree_04_01.dot
# now pack all the graphs
$ gvpack -array_it20 tree_04_*.dot > trees_04.dot
# now run neato
$ neato -n2 -Tsvg trees_04.dot > trees_04.svg

The result of the last command is shown in the image above.

Software

I'm using


Solution

  • neato has problems with clusters. Old (pre 2..43.0?) versions did not "do" clusters at all(?), and the current version incorrectly handles cluster labels (the problem you have encountered).

    Below is a gvpr (http://www.graphviz.org/pdf/gvpr.1.pdf) program that takes the output from any of the layout engines in dot/xdot format i.e. with pos values (positions calculated), and

    neato will do better with these clusters because neato -n/n2

    The gvpr program ( clusterWrap.gvpr):

    //
    // creates clusters around the entire graph, allowing user to save graph label
    // also "fixes" node labels by instantiating default values (\N -> actual node name)
    //
    BEGIN{
      graph_t aGraph, Root, innerCluster, outerCluster;
      node_t  aNode;
      edge_t  anEdge;
      int     i, cnt, topNode[], topSubgraph[], topEdge[];
      float   delta=8., deltaX, deltaY;
      string  st;
      string  copyAtt[int];
    
      /////////////////////////////////////////////////////////////////////////////
      split("label|lheight|lwidth|lp|bb", copyAtt, "|");
    
    //  deltaX=delta;
    //  deltaY=delta;
    }
    BEG_G{
      Root=$G;
      // get graphs, nodes, and edges(?) directly "under" Root graph  
      for (aNode=fstnode($G);aNode;aNode = nxtnode(aNode)){
        topNode[aNode]=1;
      }
      for (aGraph = fstsubg($G); aGraph; aGraph = nxtsubg(aGraph)) {
        topSubgraph[aGraph]=1;
      }
      // we will find top-level edges later
      
      // create wrapper clusters
      outerCluster=subg(Root,"clusterPad000");
      innerCluster=subg(outerCluster,"clusterWrapper000");
    
      if (hasAttr(Root, "layout")){
        Root.layout="neato";  // other values (including "") cause problems with later execution
      }
      //Root.OLDbb=Root.bb;
      // "move" attribute values to new cluster
      for (copyAtt[i]){
        if (hasAttr(Root, copyAtt[i])){
          st=aget(Root, copyAtt[i]);
          aset(innerCluster, copyAtt[i], st);
          aset(Root, copyAtt[i], "");      
        }
      }
      innerCluster.peripheries=0;
      // create a pad/margin between the two new clusters
      outerCluster.bb=(string)((float)xOf(llOf(innerCluster.bb))-delta) + "," +
              (string)((float)yOf(llOf(innerCluster.bb))-delta) + "," +
          (string)((float)xOf(urOf(innerCluster.bb))+delta) + "," +
          (string)((float)yOf(urOf(innerCluster.bb))+delta);
    }
    N{
      $.OLDpos=$.pos;
      // "fix" node labels by creating explicit label
      if (hasAttr($, "label") && ($.label!="")){
        print("//  starting label: ", $.label);
        $.label=gsub($.label, "\\\\N", $.name);   // ugh, backslashes
        $.label=gsub($.label, "\\\\G", $G.name);  // ugh, backslashes    
      }else{
        print("//  starting label: >", $.label,"<");  
        $.label=$.name;
      }
    }
    E{
      // find all edges defined directly under Root
      if (isSubedge(Root, $)){
        topEdge[$]=1;
      }  
    }
    END_G{
    // now move graphs, nodes, and edges "under" inner cluster
      for (topSubgraph[aGraph]){
        print("//  cloning subg :", aGraph.name);
        clone(innerCluster, aGraph);
        delete(Root, aGraph);
      }
      for (topNode[aNode]){
        print("//  moving node :", aNode.name);
        subnode(innerCluster, aNode);
      }
      for (topEdge[anEdge]){
        print("//  moving edge :", anEdge.name);
        subedge(innerCluster, anEdge);
      }  
    }
    

    One modified input file with cluster reference removed:

    graph {
        layout = neato
        //subgraph cluster_0 {
            label = "idx= 0, centre=(0,1), centroid= (0,1)";
            nd_0 [label= "0"];
            nd_1 [label= "1"];
            nd_2 [label= "2"];
            nd_3 [label= "3"];
            nd_0 -- nd_1;
            nd_0 -- nd_3;
            nd_1 -- nd_2;
        //}
    }
    

    A modified version of your command stream to show gvpr usage:

    # dot for each .raw file
    $ dot -Tdot tree_04_00.raw |gvpr -cf clusterWrap.gvpr > tree_04_00.dot
    $ dot -Tdot tree_04_01.raw |gvpr -cf clusterWrap.gvpr > tree_04_01.dot
    # now pack all the graphs
    $ gvpack -array_it20 tree_04_*.dot > trees_04.dot
    # now run neato
    $ neato -n2 -Tsvg trees_04.dot > trees_04.svg
    

    Giving:
    enter image description here