Add image or text to border of rectangle or cluster - graphviz - graphviz

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);
}

Related

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.
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:

invisible edges layout issues

I have a dense dot graph with many edges and a node that has connections to nearly any other node.
For example:
digraph TEST
{
rankdir=LR;
node [shape=plaintext];
graph [compound=true];
A[label=<<table border="0" cellspacing="0" cellborder="1" >
<tr><td >A</td></tr>
<tr><td port='a1'>a1</td></tr>
<tr><td port='a2'>a2</td></tr>
</table>>];
B;
C;
D;
E;
F;
G;
H;
I;
A:a1:e->B:w;
A:a1:e->C:w;
A:a1:e->D:w;
A:a1:e->E:w;
A:a1:e->F:w;
A:a1:e->G:w;
A:a1:e->H:w;
A:a1:e->I:w;
A:a2:e->B:w;
B:e->C:w;
C:e->D:w;
D:e->E:w;
E:e->F:w;
E:e->G:w;
E:e->H:w;
F:e->I:w
G:e->I:w
H:e->I:w
}
I would like to hide the a1 connections and add some kind of jump labels to keep the information available for the reader.
E.g.:
A:a1:e->{a1_out[label="a1"]};
{a1_Bin[label="a1"]}->B:w;
{a1_Cin[label="a1"]}->C:w;
{a1_Din[label="a1"]}->D:w;
{a1_Ein[label="a1"]}->E:w;
{a1_Fin[label="a1"]}->F:w;
{a1_Gin[label="a1"]}->G:w;
{a1_Hin[label="a1"]}->H:w;
{a1_Iin[label="a1"]}->I:w;
Adding the attribute [style=invis] to the a1-connections just does not render them, but keeps the layout as it would be with them. with the result that the placement of the nodes and labels looks strange because of empty spaces and dense connections in other places.
Removing the connections completely does change the semantics of the graph and the ranks of the graph nodes (not in this example but it will in other ones).
So I am looking for a way to provide dot the information to correctly calculating all the dependencies between the nodes on the one side and to advise it, not to draw render and draw these connections on the other side.
An approach to clean up the edges in such a graph could be to use
concentrate=true
According to the documentation, this will
... merge multiedges into a single edge and cause partially parallel
edges to share part of their paths

Draw text in corners with graphviz

I have a very simple graph:
digraph {
node [shape=rect];
rankdir=LR;
A -> B
}
It outputs as I expect:
However, I need to place unique numbers in each corner of both A and B. I am currently only aware of xlabel, but from what I gather can only be used once and cannot be specified in a particular region. So how can I accomplish writing numbers in each corner?
Newest versions of Graphviz support HTML styling of nodes, including tables ("newer than mid-November 2003", that is). So you can make a 3x3 table like this:
Source:
digraph {
node [shape=rect];
rankdir=LR;
A [shape=none label=<
<TABLE BORDER="0" CELLBORDER="0">
<TR><TD>1</TD><TD></TD><TD>2</TD></TR>
<TR><TD COLSPAN="3" BORDER="1">A</TD></TR>
<TR><TD>3</TD><TD></TD><TD>4</TD></TR>
</TABLE>
>];
A -> B
}
Tested with http://sandbox.kidstrythisathome.com/erdos/; it also works with my local installed version (2.38.0).
See Graphviz: Node Shapes for the full set of supported HTML, and examples.

Horizontal line in ellipse

I want to use graphviz for my use-cases, is there an easy way of getting a horizontal line in an ellipse shape?
image for reference:
(source: highscore.de)
if I use
digraph G {
Case [
label = "{Use-Case C | Extension points \l blablabla}"
shape = "record"
]
}
I get a square with the horizontal line, but as soon as I change from record to ellipse the label has the text "{Use-Case C | Extension points \l blablabla}"
Actually, ellipse and record are different types of nodes. Vertical bar in record is used as a horizontal line just by convention. It is impossible to draw it in ellipse like that.
But there is a workaround here.
According to another question about horizontal lines you can try HTML labels while using tables. They won't give you exactly what you want. Still, it is possible to draw label like that:
digraph structs {
node [shape=ellipse]
A [label=<
<TABLE BORDER="0" CELLSPACING="0">
<TR><TD>top</TD></TR>
<HR/>
<TR><TD>bottom</TD></TR>
</TABLE>
>];
B [label="Hello, Graphviz"];
A -> B;
}
Whereas the result looks like:
Ultimately, you may try creating your custom shape

Graphviz: Color only a field in a Record-based Node

Is there a way to add color to only a field in a record-based node. Like in the following example, can the field struct2:f0 alone be in different color?
digraph structs {
node [shape=record];
struct1 [label="<f0> left|<f1> mid\ dle|<f2> right"];
struct2 [label="<f0> one|<f1> two"];
struct3 [label="hello\nworld |{ b |{c|<here> d|e}| f}| g | h"];
struct1:f1 -> struct2:f0;
struct1:f2 -> struct3:here;
}
Thx
I don't think this is possible.
You may consider using HTML-like labels - you should be able to do everything you can do with record-based nodes, and more.
From the above linked documentation page:
The record-based shape has largely been superseded and greatly
generalized by HTML-like labels. That is, instead of using
shape=record, one might consider using shape=none and an HTML-like
label.
and
Although HTML labels are not, strictly speaking, a shape, they can be
viewed as a generalization of the record shapes described above. In
particular, if a node has set its shape attribute to none or
plaintext, the HTML label will be the node's shape.
Try this:
digraph G {
"Record" [ label=<<table>
<tr>
<td>A</td>
<td bgcolor='#00CC11'>B</td>
</tr>
</table>
>
];
}

Resources