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!
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)
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:
Related
I have a code that allows me to perform a cluster. i want to move the label to the border of the cluster in the output. I have tried labelloc=l and other options but i am unable to make it work. attached is the one i got from graphviz while the other one is what i was expecting. is there a way to modify the code, add plugin or gvpr to get the desired output?
digraph AlignmentMap {
/*
/*
Author: Lars Barkman
Created: 2015-08-25
Changelog: See version control system
This is an example of an Alignment Map visualized with the help of Graphviz.
This solution depends on either generation or maintaining a .dot file and from that generating a image or document.
Personally, I think that editing the file by hand should be fine if the naming conventions used are intuitive.
Alignment maps first came to my attention on Martin Fowlers blog (http://martinfowler.com/bliki/AlignmentMap.html).
*/
// General layout of the graph
rankdir=LR; // Direction of the graph Left to Right
node [style="rounded,filled",color=black,shape=box,fillcolor=white]; // Defines the default layout of the nodes
graph [style=filled, splines=line]; // Fills the subgraphs and defines the layout of the connections
rank = same; // Makes sure that nodes are properly aligned even without a connection
// Column for Business Outcomes
subgraph cluster_business_outcome {
label="Business Outcomes"
graph [color=pink];
business_outcome_Customer_Acquisition [label="Customer\nAcquisition"];
business_outcome_Customer_Retention [label="Customer\nRetention"];
business_outcome_Cost_of_Operations [label="Cost of\nOperations"];
}
// Column for IT Outcomes
subgraph cluster_IT_outcome {
label=< <table>
<tr><td fixedsize="true" width="50" height="50"><img src="./Azure-PlantUML-master/dist/Identity/AzureActiveDirectory.svg" /></td></tr>
<tr><td>Active Directory</td></tr>
</table>
>
graph [color=mistyrose2];
IT_outcome_Platform_Unbundling [label="Platform\nUnbundling"];
IT_outcome_Site_Ux [label="Site Ux"];
IT_outcome_Site_Performance [label="Site\nPerformance"];
IT_outcome_Site_Scalability [label="Site\nScalability"];
}
// Column for IT Initiatives
subgraph cluster_IT_initiatives {
label="IT Initiatives"
graph [color=papayawhip];
IT_initiatives_API [label="API"];
IT_initiatives_Pluginize [label="Pluginize"];
IT_initiatives_Responsive_Rewrite [label="Responsive\nRewrite"];
IT_initiatives_Catalog_Performance [label="Catalog\nPerformance"];
IT_initiatives_Sharding [label="Sharding"];
}
// Column for Action Items
subgraph cluster_action_items {
label="Action Items"
graph [color=darkseagreen1];
action_items_0 [label="..."];
action_items_1 [label="..."];
action_items_App_X [label="App X"];
action_items_Search_In_One [label="Search-\nIn-One"];
action_items_4 [label="..."];
}
// Connections between nodes in the different columns
// business_outcome_* -> IT_outcome_Platform_*
business_outcome_Customer_Acquisition -> IT_outcome_Platform_Unbundling;
business_outcome_Customer_Acquisition -> IT_outcome_Site_Ux;
business_outcome_Customer_Retention -> IT_outcome_Site_Ux;
business_outcome_Customer_Retention -> IT_outcome_Site_Performance;
business_outcome_Cost_of_Operations -> IT_outcome_Site_Performance;
business_outcome_Cost_of_Operations -> IT_outcome_Site_Scalability;
// IT_outcome_* -> IT_initiatives_*
IT_outcome_Platform_Unbundling -> IT_initiatives_API;
IT_outcome_Platform_Unbundling -> IT_initiatives_Pluginize;
IT_outcome_Site_Ux -> IT_initiatives_Responsive_Rewrite;
IT_outcome_Site_Performance -> IT_initiatives_Catalog_Performance;
IT_outcome_Site_Scalability -> IT_initiatives_Sharding;
// IT_initiatives_* -> action_items_*
IT_initiatives_API -> action_items_0;
IT_initiatives_Pluginize -> action_items_1;
IT_initiatives_Responsive_Rewrite -> action_items_App_X;
IT_initiatives_Catalog_Performance -> action_items_Search_In_One;
IT_initiatives_Sharding -> action_items_4;
}
The one on right is graphviz and left is what i expect
(sorry, I misunderstood the request)
Here is a gvpr "fix".
run dot -Tdot to position the nodes, edges, clusters
run that result through gvpr (program below) to remove the label and replace it with a (new) node
run that (modified) result through neato -n2 -Tsvg (see https://graphviz.org/faq/#FaqDottyWithCoords)
[note that I had to tweak the html to remove borders:]
label=< <table BGCOLOR="transparent" BORDER="0" CELLBORDER="0" CELLSPACING="0">
<tr><td fixedsize="true" width="50" height="50"><img src="image_dir/Face-smile.svg" /></td></tr>
<tr><td>Active Directory</td></tr>
</table>
>
commandline:
dot -Tdot myfile.gv|gvpr -c -f moveLabel.gvpr | neato -n2 -Tsvg >myfile.svg
moveLable.gvpr:
BEGIN{
string saveLbl, saveX, saveY, workS;
graph_t theG;
node_t newNode;
}
BEG_G{
print("// ONE");
theG=subg($G, "cluster_IT_outcome");
saveLbl=theG.label;
// next 3 steps get the left X coordinate of the cluster
workS=llOf(theG.bb);
workS=xOf(workS);
saveX=(float)workS;
// now get Y coordinate of label
workS=yOf(theG.lp);
saveY=(float)workS;
theG.label=""; // remove label from cluster
}
END_G{
// create new node to replace cluster label
newNode=node($G, "__myNewNode");
newNode.pos=sprintf("%.2f,%.2f", saveX, saveY);
newNode.shape="plain";
newNode.fillcolor="none";
newNode.label=html($G, saveLbl);
}
I am trying to build a dependecy scheme of a program as a digraph in dot. Therefore I used the following code:
digraph LINK {
rankdir=LR;
ranksep=0.65;
nodesep=0.40;
splines=false;
overlap=false;
concentrate=false;
node[shape=box];
subgraph clusterAPP {
label="Application";
style=dashed;
nodeA[label="d = func(...);"];
};
subgraph clusterFB{
color=red;
label="Wrapper";
style=dashed;
rank=same;
wrapper[label="wrapper"];
real[label="pointer to\nreal func"];
wrapper -> real [constraint=false,label="dlopen\ndlsym"];
}
subgraph clusterBACKEND {
label="Backend"
style=dashed;
func[label="float func(...)"];
};
nodeA -> wrapper;
real -> func[weight=10];
func->real[color=blue];
}
This results in
The problems are now:
The egdes between real and func overlap. How can I separate them that they are easy to recognize.
Why has the edge from wrapper to real the wrong direction?
The answer to your first question has already been given elsewhere here on Stackoverflow, what it boils down to is you can either use ports postions or use the trick mentioned by the infamous marapet to add an extra edge in the same direction, remove it's constraint and reverse the edge's direction and hide the edge pointing back.
Or to put it in code:
real -> func [weight=10] // Remains unaltered
real -> func [constraint=false, dir=back] // Remove the constraint and reverse the edge
func -> real [style=invis] // Hide the edge pointing pack
As to why the other edge is pointing in the wrong direction, this has to do with a bug in relation to constraint and should be fixed in version 2.27. You may be working with an older version. (I know I am, as a lot of *NIX package managers still have 2.26.X by default).
To fix this you will either need to manually update your Graphviz to a newer version or (if you already have a newer version or can't/don't want to update) add dir=back to that nodes attribute.
Putting everything together the result is this:
Using the following DOT code:
digraph LINK {
rankdir=LR;
ranksep=0.65;
nodesep=0.40;
splines=false;
overlap=false;
concentrate=false;
node[shape=box];
subgraph clusterAPP {
label="Application";
style=dashed;
nodeA[label="d = func(...);"];
};
subgraph clusterFB{
color=red;
label="Wrapper";
style=dashed;
rank=same;
wrapper[label="wrapper"];
real[label="pointer to\nreal func"];
}
subgraph clusterBACKEND {
label="Backend"
style=dashed;
func[label="float func(...)"];
};
nodeA -> wrapper;
wrapper -> real [constraint=false, dir=back, label="dlopen\ndlsym"]; // Added reverse direction
real -> func [weight=10] // Remains unaltered
real -> func [constraint=false, dir=back] // Remove the constraint and reverse the edge
func -> real [style=invis] // Hide the edge pointing pack
}
I'm currently having issues with designing UML-like diagrams on graphiz. The reason for the problem is that they are not exactly UML diagrams. The main difference is that I make use of indentations to add an hierarchy to an object's properties. Implementing these idiosyncrasies is a little difficult for me. What I'm trying to achieve is this:
I normally use a node shape called record to design these diagrams. The problem arises when I have to link two of these UML-like diagrams just like relationships in UML i.e. aggregation, association, composition, etc.
When I have the diagrams, I can't make the relationship with the arrows because the arrows only go from a random part of one node to another random part of the other node.
The way I have the UML-like diagrams is good, but the relationship arrow causes it not to be what I want as I want the arrows to go from a specific point of one node to another specific point of another node.
The DOT code I used to create this graph is like this:
digraph G {
fontname = "Bitstream Vera Sans"
fontsize = 8
node [
fontname = "Bitstream Vera Sans"
fontsize = 8
shape = "record"
]
edge [
fontname = "Bitstream Vera Sans"
fontsize = 8
]
Person [
label = "{Person \l\l \ age : int\l \ livesIn : City \l \ \ \ sinceYear : int}"
] // \l -new line, \ -indentation
City [
label = "{City \l \ \ name : string}"
]
Person -> City
}
I tried getting around this problem by using horizontal line divisions within the nodes even though I didn't want the lines. The horizontal line divisions make it possible for me to make this specific relationship possible by using ports, but they create a new problem of their own. The problem they create is that they get rid of the indentations I want and had in the previous graph. The way I tried to get around the arrow problems works, but new problems are created - the indentation disappears and the horizontal line divisions can't be made invisible
.
The code I used to create this graph is:
digraph G {
fontname = "Bitstream Vera Sans"
fontsize = 8
node [
fontname = "Bitstream Vera Sans"
fontsize = 8
shape = "record"
penwidth = 0.5
]
edge [
fontname = "Bitstream Vera Sans"
fontsize = 8
]
Person [
label = "{<g0> Person | <g1> age : int | <g2> livesIn : City | <g3> sinceYear : int}"
] // \l -new line, \ -indentation
City [
label = "{<f0> City | <f1> name : string}"
]
Person:<g2> -> City:<f1> [arrowhead = "empty", headlabel = "*"]
}
These indentations are a big part of the relationship, so I'm wondering if anyone knows what I can do to have these indentations back in the diagrams as well what I can do to make the horizontal line divisions invisible?
I'll appreciate if someone has a better way/idea that's also totally different from what I have done in diagrams 2 & 3, that will help me achieve diagram 1.
Your original attempt wasn't bad. I would say using ports is definitely the way to go.
If you place the node in a cluster you can use the cluster's border and hide the border of the record node, getting rid of those divider lines.
As you noted, using a backslash \ no longer works to escape a space. The workaround is to either use \ instead, this will escape the whitespace. As an alternative you could also replace each space with an &nnbsp;. Either one will achieve the required effect.
I made some minor changes to make things more readable, like put Graph properties in a graph block instead of in the root of the graph and rename the port-names to something more sensible. I also removed any ports not in use.
The final result I came up with was this:
...and this is the DOT code I used:
digraph G {
graph [
compound = true // To clip the head at the cluster border
penwidth = 2 // Make the cluster's borders a bit thicker
rankdir = "LR" // Make the arrow and nodes go from Left to Right
ranksep = 1 // Add a bit more space inbetween nodes
]
node [
color = none // Hide the node's border
fontname = "Bitstream Vera Sans"
height = 0 // Make the node as small as possible (it will grow if it needs more space)
margin = 0 // Remove unneeded whitespace
shape = "record" // So we can use ports
]
edge [
arrowhead = "open"
labelangle = -5 // Place the asteriks closer to the line
labeldistance = 2.5 // Place the asteriks further away from the arrow head
penwidth = 2 // Make the line a bit thicker
]
/* #NOTE: escaping spaces in the label using '\' doesn't work so use ' ' or '\' instead. */
subgraph cluster_Person {
Person [
label = "\N\l | \ \ \ age : int\l | <livesIn> \ \ \ livesIn : City\l | \ \ \ \ \ \ sinceYear : int\l"
]
}
subgraph cluster_City {
City [
label = "<city> \N\l | \ \ \ name : string\l"
]
}
Person:livesIn -> City:city [headlabel = "*", lhead = "cluster_City"] // lhead allows us to point to the cluster's border instead of the node, as long as we add `compound = true` to the graph
}
I am trying to have a node (or a subgraph, enclosing a node - whichever is possible/easier) rotated, like shown in this image:
(Note that it doesn't matter to me if the "B" label is rotated - only that the 'verti-*' texts in the record [or rather, the whole record node] are rotated as shown)
However, the closest I can to that, is the following dot code:
digraph graphname {
node [fontname=Monospace, fontsize=14];
subgraph clusterMasterBox {
node [shape=record];
l1 [label = "{ horiz-1 \r| \
horiz-2 \r| \
horiz-3 \r| \
horiz-4 \r} \
"];
subgraph clusterSubRotateBox {
rotate=90;
node [shape=record,rotate=90];
l2 [label = "{ verti-1 \r| \
verti-2 \r| \
verti-3 \r| \
verti-4 \r} \
"];
label="B";
}
label="A"
}
}
The only reason I have the subgraph clusterSubRotateBox there (and the only reason why it is nested inside the clusterMasterBox), is because I hoped I could assign rotation to it, but apparently I cannot - as the above code generates this image:
So my question is - is there a way to rotate a record node; if not on its own, then maybe as a part of subgraph (or a different kind of 'object')?
Thanks in advance for any suggestions,
Cheers!
If you want to rotate a single record based node then rankdir will work. I tried it for my graph,
digraph plugnoid {
rankdir=LR;
node[shape=Mrecord];
plugnoid [label="swarm| {<load0> onLoad|<plugin0> Plugin|<quit0> onQuit}|{<run0>run|<rehash0>rehash}"];}
The rankdir can have values LR,RL and TB(default). When I changed the rankdir to TB the output changed,
You may want to try them on your graph to get desired results. I experienced that when I used subgraph and set different rankdir the result was not as good. Please see http://www.graphviz.org/doc/info/shapes.html#record for more details.
For a single node, there is the orientation attribute. I just used
node[shape=hexagon, orientation=30]
To make a hexagon with a point at the top rather than a flat top.
Unfortunately doesn't seem to work on 'record' types :-(
there should be a "rotation" attribute on the graph object (see http://www.graphviz.org/doc/info/attrs.html#drotation) but it didn't do anything in my test. and, it would only apply to the whole graph (not cluster/subgraph) according to the docs.
i guess you'd first render the subgraph to postscript and then include it in the final graph as a custom shape for a single placeholder node. if you can't get "rotation" to do its thing, surely postscript has a simple way to apply a transform (rotation in this case), probably as simple as prefixing the generated code with some coordinates definition. sorry for the handwaving but i don't have time to try it now.
Graphviz does not support partial rotation - only entire graphs or polygonial nodes (https://www.graphviz.org/docs/attrs/rotate/ and https://graphviz.org/docs/attrs/orientation/).
To accomplish your goal, create a separate graph for each node or cluster you want rotated, and use dot (or desired engine) to create images (png, svg, etc.) Then include each image into an node using the image attribute (https://graphviz.org/docs/attrs/image/).
Note that Graphviz only supports rotate=90, while you are asking for -90. Use some other image manipulation program to rotate the result by 180 (good grief)
of the complete graphLike so:
rotatePart1a.gv
digraph graphname {
node [fontname=Monospace, fontsize=14];
subgraph clusterMasterBox {
label="A"
{rank=same // keep on same rank
l2 [shape=plain label="" image="rotatePart1b.png"]
node [shape=record];
l1 [label = "{ horiz-1 \r| \
horiz-2 \r| \
horiz-3 \r| \
horiz-4 \r} \
"];
}
}
}
rotatePart1b.gv
digraph graphname {
rotate=90;
node [fontname=Monospace, fontsize=14];
subgraph clusterSubRotateBox {
node [shape=record] // rotation at node-level is silently ignored
l2 [label = "{ verti-1 \r| \
verti-2 \r| \
verti-3 \r| \
verti-4 \r} \
"];
label="B";
}
}
Giving:
I need to print a large number of graphs using Graphviz DOT. To distinguish which input each graph corresponds to, I want to also have a caption for each graph. Is there anyway to embed this into the DOT representation of the graphs.
You can use label to add a caption to the graph.
Example:
digraph {
A -> B;
label="Graph";
labelloc=top;
labeljust=left;
}
labelloc and labeljust can be used to determine top/bottom and left/right position of the graph label.
All the details and other attributes that can be used to modify the label (font etc) in the graphviz attribute reference.
Tip: Define the graph label end of your dot file, otherwise subgraphs will inherit those properties.
Graph's can have attributes just like nodes and edges do:
digraph {
graph [label="The Tale of Two Cities", labelloc=t, fontsize=30];
node [color=blue];
rankdir = LR;
London -> Paris;
Paris -> London;
}
That dot file produces this graph.
If you are looking for a way to add a caption to a Graph object of graphviz in python. Then the following code can help:
from graphviz import Graph
dot = Graph()
dot.node('1','1')
dot.node('2','2')
dot.edge('1','2', label="link")
dot.attr(label="My caption")
dot.attr(fontsize='25')
dot.render(view=True)
Output: