Large enough bounding box for graph cluster

195 Views Asked by At

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

  • neato - graphviz version 2.43.0 (0)
  • dot - graphviz version 2.43.0 (0)
  • gvpack (unknown version)
  • gvpr version 2.43.0 (0)
1

There are 1 best solutions below

3
On BEST ANSWER

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

  • wraps the result in two nested clusters (Two nested clusters make for better padding)
  • moves any graph labels to the inner cluster
  • moves "top-level" graphs, nodes, and edges to the inner cluster
  • adjusts node labels to prevent "label modification" if gvpack will be used downstream
  • sets bb values for both nested clusters

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