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.
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!
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.
I'm using
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