Search code examples
graphvizdot

Graphviz - aligning nodes and clusters


I'm trying to draw a finite state diagram with Graphviz but I cannot get it like I want it. Here's what I have written so far :

digraph G {
    rankdir = LR;

    subgraph cluster_op1 {

        subgraph cluster_0 {
            1 -> 2 [label="a"]  
        }

        subgraph cluster_1 {
            3 -> {4 6} [label="ε"]
            4 -> 5 [label="b"]
            5 -> 4 [label="ε"]
            5 -> 6 [label="ε"]
        }

        subgraph cluster_2 {
            7 -> {8 10} [label="ε"]
            8 -> 9 [label="c"]
            9 -> 8 [label="ε"]
            9 -> 10 [label="ε"]
        }   

        2 -> 3 [label="ε"]
        6 -> 7 [label="ε"]
    }

    subgraph cluster_op2 {

        subgraph cluster_3 {
            11 -> {12 14} [label="ε"]
            12 -> 13 [label="ab"]
            13 -> 12 [label="ε"]
            13 -> 14 [label="ε"]
        }   

        subgraph cluster_4 {
            15 -> 16 [label="c"]    
        }

        14->15 [label="ε"]
    }

    0 -> {1 11} [label="ε"]
    {10 16} -> 17 [label="ε"]
}

Here's what it looks like : enter image description here

What I want is :

  1. All the nodes within cluster_op1 to be aligned horizontally (same for cluster_op2)
  2. cluster_op2 to be centered below cluster_op1
  3. The edges from node 0 to cluster_op1 and cluster_op2 to be the same length (same for the edges from those two clusters to node 17)

Solution

  • This is a hack more than a real solution but it gives you what you want, at least for the case presented here.

    It uses three elements:

    group to facilitate straight lines between nodes,

    weight to help if group gets confused by one node pointing to two other nodes, all of which being in the same group; and to bring the clusters in line between them,

    invis edge to align cluster_op2 below cluster_op1 - so it's not centered but manually adjusted.

    In addition, I have replaced b -> a with a -> b[ dir = back ] where applicable; this I regard just as good practice to avoid bugs that are difficult to discover.

    So this is your edited code:

    digraph G {
        rankdir = LR;
        0, 1, 2, 15, 16, 17
        3, 4, 5, 6     [ group = 1 ];
        7, 8, 9, 10    [ group = 2 ];
        11, 12, 13, 14 [ group = 3 ];
        2 -> 11[ style = invis ];
    
        subgraph cluster_op1 {
    
            subgraph cluster_0 {
                1 -> 2 [ label="a" ]; 
            }
    
            subgraph cluster_1 {
                3 -> 4 [ label="ε", weight = 4 ];
                3 -> 6 [ label="ε" ];
                4 -> 5 [ label="b" ];
                4 -> 5 [ label="ε", dir = back];
                5 -> 6 [ label="ε", weight = 4 ];
            }
    
            subgraph cluster_2 {
                7 -> {8 10} [label="ε"]
                8 -> 9 [label="c"]
                8 -> 9 [ label="ε", dir = back ];
                9 -> 10 [label="ε"]
            }   
    
            2 -> 3 [ label="ε", weight = 10 ;]
            6 -> 7 [ label="ε" ];
        }
    
        subgraph cluster_op2 {
    
            subgraph cluster_3 {
                11 -> 12 [ label="ε", weight = 4 ];
                11 -> 14 [ label="ε" ];
                12 -> 13 [ label="ab" ];
                12 -> 13 [ label="ε", dir = back ];
                13 -> 14 [ label="ε", weight = 4 ];
            }   
    
            subgraph cluster_4 {
                15 -> 16 [label="c"]    
            }
    
            14->15 [ label="ε", weight = 10 ];
        }
    
        0 -> {1 11} [label="ε"]
        {10 16} -> 17 [label="ε"]
    }
    

    which yields

    enter image description here