3

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

all items in one row

However I want (or similar):

items arranged in two rows with keeping the connection

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): enter image description here

'rank': 'same' seems not change anything. Also when applied to nodes A and D.

  • 1
    That's strange. When `A` and `D` are in the same rank, they have to be on the same height in the graph - that's the whole purpose of `rank = same`. Maybe a syntax issue in Python - how / where to put the `rank = same` instruction? – vaettchen Jan 15 '19 at 17:29
  • @vaettchen, yes, it is syntax issue, graphviz has [example](https://graphviz.readthedocs.io/en/stable/examples.html#rank-same-py) of using `rank=same`. – kirogasa Oct 15 '22 at 13:10

2 Answers2

2

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

enter image description here

vaettchen
  • 7,299
  • 22
  • 41
0

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:
break wide graph after certain number of nodes made with graphviz

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:
non-consistent last arrow made with graphviz
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:
break wide graph after certain number of nodes made with graphviz

kirogasa
  • 627
  • 5
  • 19