Why does Graphviz no longer minimise edge lengths when subgraphs are introduced - graphviz

I have this Graphviz graph:
digraph
{
rankdir="LR";
overlap = true;
Node[shape=record, height="0.4", width="0.4"];
Edge[dir=none];
A B C D E F G H I
A -> B -> C
D -> E -> F
G -> H -> I
Edge[constraint=false]
A -> D -> G
subgraph clusterX
{
A
B
}
subgraph clusterY
{
E
H
F
I
}
}
which produces this output:
I would have expected the length of the edge between A and D to be minimised so that the nodes would be arranged as:
A B C
D E F
G H I
rather than
D E F
G H I
A B C
This works as expected if I remove the subgraph definitions.
Why does Graphviz place A B C at the bottom when the subgraphs are introduced?

This is not really about minimizing edge lengths, especially since in the example the edges are defined with the attribute constraint=false.
While this is not a complete answer, I think it can be found somewhere within the following two points:
The order of appearance of nodes in the graph is important.
Changing rankdir to LR contains unpredictable (or at least difficult to predict) behaviour, and/or probably still a bug or two (search rankdir).
I'll try to explain as good as I can and understand graphviz, but you may want to go ahead and read right away this reply of Emden R. Gansner on the graphviz mailing list as well as the following answer of Stephen North - they ought to know, so I will cite some of it...
Why is the order of appearance of nodes important? By default, in a top-down graph, first mentioned nodes will appear on the left of the following nodes unless edges and constraints result in a better layout.
Therefore, without clusters and rankdir=LR, the graphs appears like this (no surprises):
A D G
B E H
C F I
So far, so good. But what happens when rankdir=LR is applied?
ERG wrote:
Dot handles rankdir=LR by a normal TB layout and then rotating the
layout counterclockwise by 90 degrees (and then, of course, handling
node rotation, edge direction, etc.). Thus, subgraph one is
positioned to the left of subgraph two in the TB layout as you would
expect, and then ends up lower than it after rotation. If you want
subgraph one to be on top, list it second in the graph.
So if that would be correct, without clusters, the nodes should appear like this:
G H I
D E F
A B C
In reality, they do appear like this:
A B C
D E F
G H I
Why? Stephen North replied:
At some point we decided that top-to-bottom should be the default,
even if the graph is rotated, so there's code that flips the flat
edges internally.
So, the graph is layed out TB, rotated counterclock wise and flat edges flipped:
A D G G H I A B C
B E H --> D E F --> D E F
C F I A B C G H I
While this works quite well for simple graphs, it seems that when clusters are involved, things are a little different. Usually edges are also flipped within clusters (as in clusterY), but there are cases where the flat edge flipping does not work as one would think. Your example is one of those cases.
Why is the error or limitation in the flipping of those edges? Because the same graphs usually display correctly when using rankdir=TB.
Fortunately, workarounds are often easy - for example, you may use the order of appearance of the nodes to influence the layout:
digraph
{
rankdir="LR";
node[shape=record, height="0.4", width="0.4"];
edge[dir=none];
E; // E is first node to appear
A -> B -> C;
D -> E -> F;
G -> H -> I;
edge[constraint=false]
A -> D -> G;
subgraph clusterX { A; B; }
subgraph clusterY { E; F; H; I; }
}

Related

In graphviz, can you bring two vertices closer together?

When describing a graph with graphviz, I sometimes find I want two vertices to appear closer together than the layout engine I chose places them. Is there a way to hint that I want them closer?
I'm mostly interested in the case of two connected vertices, so an answer specific to that case is fine.
Concrete example:
digraph G {
node [shape="circle"];
Start [shape="none" label=""];
C [shape="doublecircle"];
Start -> A;
A -> B [label="0,1"];
B -> C [label="0,1"];
C -> D [label="0,1"];
D -> D [label="0,1"];
}
I want the vertices Start and A to be closer.
You can't do that, but you can make nearly everything else twice as big, here is a start.
(But you can't increase the size of an edge to self)
digraph G {
rankdir=LR
edge[minlen=2 fontsize=28 arrowsize=2 penwidth=2]
node[fontsize=28 height=1 penwidth=2]
graph[fontsize=28 penwidth=2]
node [shape="circle"];
Start [shape="none" label=""];
C [shape="doublecircle"];
Start -> A[minlen=1]; // not twice the size to get the requested effect
A -> B [label="0,1"];
B -> C [label="0,1"];
C -> D [label="0,1"];
D -> D [label="0,1"];
}
[this answer applies specifically to dot]
there is no edge-level attribute that explicitly sets or changes edge length
the graph-level nodesep attribute sets minimum distance between two nodes of same rank
so:
digraph G {
nodesep=.17
{
rank=same
node [shape="circle"];
Start [shape="none" label=""];
C [shape="doublecircle"];
Start -> A;
A -> B [label="0,1"];
B -> C [label="0,1"];
C -> D [label="0,1"];
D -> D [label="0,1"];
}
}
produces:
To increase the distance between the other nodes, you can add spaces to the labels.
I'm not wild about it either, but this change:
B -> C [label=" 0,1 "]; // pad to make label (and edge) longer
produced this:

Node positions in Graphviz

I'm trying to set up the following graph so that the 'old_view' & 'new_views' are at the same rank at the top, and 'old_submits & 'new_sub' are on the same rank at the bottom. 'Continues' would sit in between the two rows.
I've tried using subgraphs as suggested by others but it hasn't helped me on this one.
c=Digraph('parent')
c.attr('graph', label='')
c.attr('node',fontname='helvetica')
c.attr('graph',fontname='helvetica')
c.attr('edge',fontname='helvetica')
c.attr('node', shape='box', color='lightgrey')
c.attr(rank='same')
c.node('old_views')
c.node('new_views')
c.node('continues')
g=Digraph('subgraph')
g.graph_attr.update(rank='same')
g.node('new_submits')
g.node('old_submits')
c.edge('new_views','continues')
c.edge('continues','new_submits')
c.edge('old_views','old_submits')
c.subgraph(g)
c
Example image:
A pure graphviz hint that you should be able to transfer to python easily:
If you want to "skip" a level, you have two possibilities (at least):
You can use an empty node - downside is that you have to define that empty node b, and that your edge pointing from a to b needs to have no arrowhead. Also, if you look carefully, you seen an empty pixel on the way from a to c.
The latter you can avoid by routing an extra invisible edge from A over E to C, creating not only the need for this extra edge but also for an increased weight on theD -> E -> E edge to keep it straight.
digraph so
{
b[ shape = point, width = 0]
a -> b[ dir = none ];
b -> c;
d -> e -> f;
A -> C;
A -> E -> C[ style = invis ];
D -> E -> F[ weight = 10 ];
}
The choice is yours!

In a graphviz dot digraph, how can I break a wide layout (rankdir LR)

With python I'm trying to generate a long graph where always one node points to the next. This ends up in having a long snail of nodes (rankdir LR). However I want to break it after a certain width or number or nodes. How can this be achived?
graph = gv.Digraph(format='svg')
graph.graph_attr.update({'rankdir': 'LR'})
graph.node('a', 'A')
graph.node('b', 'B')
graph.node('c', 'C')
graph.node('d', 'D')
graph.node('e', 'E')
graph.node('f', 'F')
...
graph.edges(['ab', 'bc', 'cd', 'de', 'ef', ...])
Output:
However I want (or similar):
I tried to use size, but that only zooms the whole graph.
As a workarround I tried to reduce ranksep, but that only makes it better for a few more items.
I also searched a lot but could not find an appropriate answer.
An unanswered question that goes into a similar direction is:
graphviz plot too wide.
For other related questions suggested answer was to use invisible elements but that does not work here either.
Update:
I've altered the code for edges according to the comment of #vaettchen:
graph.edge('a', 'b', None, {'weight':'5'})
graph.edge('b', 'c', None, {'weight':'5'})
graph.edge('d', 'e', None, {'weight':'5'})
graph.edge('e', 'f', None, {'weight':'5'})
graph.edge('c', 'd', None, {'weight':'1'})
graph.edge('a', 'd', None, {'style':'dashed', 'rank':'same'})
Unfortunately the result now looks like this (style 'dashed' instead of 'invis' for better visibility):
'rank': 'same' seems not change anything. Also when applied to nodes A and D.
This should be a comment rather than an answer as it doesn't address the python issue and I guess you are also looking for something more "automatic" - but maybe it gives some ideas; and as nobody else is picking it up, here a pure graphviz suggestion:
digraph so
{
// graph attributes
rankdir = LR; // horizontal graph
splines = ortho // edges with "corners"
// default/initial node style
node[ shape = box ];
// nodes where the "new lines" begin
// connected invisibly to keep them in order
{ rank = same; A -> E -> I[ style = invis ] }
// nodes that are to be in one line
// extra weight needed to keep the edges straight
edge[ weight = 5 ];
A -> B -> C -> D;
E -> F -> G -> H;
I -> J -> K -> etc;
// edges connecting the graph elements over the lines
edge[ weight = 1 ];
D -> E;
H -> I;
}
yields
There are several ways to make this "snake".
First, to create right-angle edge bends, apply to all edges attribute splines=ortho.
Variant 1
Use edge attributes such as constraint=false or weight=0 for C -> D edge to create "soft" edge and rank=same for A, D nodes to create "strong" alignment between these nodes.
DOT script:
digraph {
graph[rankdir=LR;splines=ortho]
node[shape=box]
A -> B -> C
D -> E -> F
C -> D [constraint=false]
{rank=same;A;D}
}
Variant 2
Use group attribute to create "strong" alignment between A, B, C nodes and between D, E. F nodes; and rank=same for A, D nodes to create "strong" alignment between these nodes.
DOT script:
digraph {
graph[rankdir=LR;splines=ortho]
node[shape=box]
A [group=g1]
B [group=g1]
C [group=g1]
D [group=g2]
E [group=g2]
F [group=g2]
A -> B -> C -> D -> E -> F
{rank=same;A;D}
}
Both variant give the same result, I suppose that you can also use the neato engine to set the exact coordinates of the nodes, but it looks overcomplicated.
Minimal code example (for variant 1) with comments:
import graphviz as gv
nodes = ['A','B','C','D','E','F']
# Count of nodes in row,
# can be changed for the desired graph width
columns = 3
graph = gv.Digraph(format='svg', filename = "output/mygraph.gv",
graph_attr=dict(rankdir='LR', splines='ortho'),
node_attr=dict(shape='box'))
# Set constraint=false only for desired edges, for example
# only for every 3rd edges, where `3` is set by `columns` variable
for i in range(1, len(nodes)):
if i % columns == 0 :
graph.edge(nodes[i-1], nodes[i], constraint='false')
else:
graph.edge(nodes[i-1], nodes[i])
# Add desired nodes to `rank=same` subgraph
with graph.subgraph() as s:
s.attr(rank='same')
for i in range(0, len(nodes)):
if i % columns == 0 :
s.node(nodes[i])
graph.view()
Result image:
Result mygraph.gv:
digraph {
graph [rankdir=LR splines=ortho]
node [shape=box]
A -> B
B -> C
C -> D [constraint=false]
D -> E
E -> F
{
rank=same
A
D
}
}
Possible improvements
If there is one node on the line, creates a non-consistent last arrow:
This can be corrected by creating an invisible node inv2 between the nodes F and G:
digraph {
graph [rankdir=LR splines=ortho nodesep=.2]
node [shape=box]
A -> B
B -> C
C -> inv1 [constraint=false arrowhead=none]
inv1 -> D [constraint=false ]
D -> E
E -> F
F -> inv2 [constraint=false arrowhead=none]
inv2 -> G [constraint=false]
{
rank=same
A
inv1 [shape=point width=.01]
D
inv2 [shape=point width=.01]
G
}
}
Result:

GraphViz: how to connect a node to the containing subgraph

I just learned how to connect nodes and subgraphs here on Stackoverflow. However, I want to connect a node to the containing subgraph:
digraph G {
compound=true;
subgraph cluster0 {
a -> b;
a -> c;
c -> {a b c} [lhead=cluster0];
}
c -> d;
d -> {a b c} [lhead=cluster0];
}
A quick sketch what I mean:
I want to connect d -> {a b c}, but for clarity reasons, I don't want to draw three different arrows, but just one arrow to the grouping of nodes. One way to do that is only list one arrow, like d -> a. That works, but is there a way to "collapse" three arrows into one when the head points to a cluster?
However, c -> {a b c} is not possible to point to a cluster, because c is part of that cluster. Is there a way to go around this?
you will need some scaffolding i.e. invisible node (and maybe edges) e.g.:
digraph top {
compound=true
node[shape=rectangle]
subgraph cluster1 {
a->{b c}
}
c->d
d->b[lhead=cluster1]
ca[shape=point height=0] // make ca invisible
a->ca:n[dir=back ltail=cluster1] // by drawing the arrow backward we get more control of the layout, n and s compass make the edge go smooth when enter and exiting ca
ca:s->c[dir=none] // no arrow on the tail part
}
rendered on viz-js.com:

How can I give a graph nodes fixed position in graphviz and how can i make the edges not overlapped?

I have seen some similar questions here, but the answers dont solve my problem.
I want to draw a graph. I write some code like this:
digraph {
{rank = same a b c d e f }
a -> b -> c -> d -> e -> f
a -> f
b -> d -> f
b -> f
}
but the result is that some of the edges overlapped each other.
So my question is how can I fix the edge to make it not overlap
and I also wanna know how can I give the node a fixed position? There is no problem this graph. But some times when I wanna a graph with a sequence of
a b c d e f
but when i create some edges and the sequence will change like:
a->e b c d f
You can use the attribute pos of a node or edge to specify coordinates. To see where dot places your nodes and edges you can simply run dot myinputfile.dot without any output parameter. This will produce the dot file with added coordinates (among other additions).
Based on this you can force dot to place some or all nodes at certain coordinates.

Resources