How can I center nodes in a Graphviz cluster?

I have the follow dot definition of a graph:

[dot verbose=true]
digraph G {
bgcolor=“gray”

node [
    style=filled
    shape="circle"
    fillcolor="green4"
    penwidth="2"
    color="green1"
    fontcolor="white"
]

root [
   shape="box"
   color="white"
   fontcolor="white"
   label="hello"
]

AAA_1 [
    label="AAA"
]

BBB_2 [
    label="BBB"
]

subgraph cluster_ccc {
    color=gray
    fillcolor="blue"
    style=filled
    label="ccccc"
    labelloc=b
    fontcolor="white"
    labeljust=l

    subgraph cluster_south_a {
        label="aaaaaaaaaaa"

        5 [ label="" fillcolor="red" ]
        6 [ label="" fillcolor="red" ]

        5 -> 6
    }

    subgraph cluster_south_b {
        label="bbbbbbbbbbb"

        7 [ label="" fillcolor="red" ]
        8 [ label="" fillcolor="red" ]

        7 -> 8
    }
}

root -> AAA_1
root -> BBB_2

AAA_1 -> 7
BBB_2 -> 5

}
[/dot]

The red nodes are not horizontally centered in their cluster.

Is it possible to have them horizontally centered? If so, how?

No simple binary attribute, but nodesep can do it. Tried various values until .75 seemed “close enough”.

digraph G {
    bgcolor="gray"
nodesep=.75  // inches
    node [
        style=filled
        shape="circle"
        fillcolor="green4"
        penwidth="2"
        color="green1"
        fontcolor="white"
    ]

    root [
       shape="box"
       color="white"
       fontcolor="white"
       label="hello"
    ]

    AAA_1 [
        label="AAA"
    ]

    BBB_2 [
        label="BBB"
    ]

    subgraph cluster_ccc {
        color=gray
        fillcolor="blue"
        style=filled
        label="ccccc"
        labelloc=b
        fontcolor="white"
        labeljust=l

        subgraph cluster_south_a {
            label="aaaaaaaaaaa"

            5 [ label="" fillcolor="red" ]
            6 [ label="" fillcolor="red" ]

            5 -> 6
        }

        subgraph cluster_south_b {
            label="bbbbbbbbbbb"

            7 [ label="" fillcolor="red" ]
            8 [ label="" fillcolor="red" ]

            7 -> 8
        }
    }

    root -> AAA_1
    root -> BBB_2

    AAA_1 -> 7
    BBB_2 -> 5
}

Giving:
centerNodes2

Thank you. An interesting solution. Any idea why that works?

I do worry, as my graph adds nodes, etc., if that attribute will have unintended side effects leaving me no better off.

Might it be worth filing a feature request for something like this?

It looks like nodesep=.75 works for my initial case, but doesn’t work in this one.

I need a generic solution which doesn’t require manual fine-tuning. I guess there isn’t one. How can a feature request be submitted?

[dot verbose=true]
digraph G {
bgcolor=“gray”
nodesep=.75 // inches
node [
style=filled
shape=“circle”
fillcolor=“green4”
penwidth=“2”
color=“green1”
fontcolor=“white”
]

root [
   shape="box"
   color="white"
   fontcolor="white"
   label="hello"
]

AAA_1 [
    label="AAA"
]

BBB_2 [
    label="BBB"
]

subgraph cluster_ccc {
    color=gray
    fillcolor="blue"
    style=filled
    label="ccccc"
    labelloc=b
    fontcolor="white"
    labeljust=l

    subgraph cluster_south_a {
        label="aaaaaaaaaaaaaaaaaaaaaa"

        5 [ label="" fillcolor="red" ]
        6 [ label="" fillcolor="red" ]

        5 -> 6
    }

    subgraph cluster_south_b {
        label="bbbbbbbbbbbbbbbbbbbbbb"

        7 [ label="" fillcolor="red" ]
        8 [ label="" fillcolor="red" ]
        9 [ label="" fillcolor="red" ]
        10 [ label="" fillcolor="red" ]

        7 -> 8
        7 -> 8
        7 -> 9
        8 -> 9
        8 -> 10
        9 -> 10
    }
}

root -> AAA_1
root -> BBB_2

AAA_1 -> 7
BBB_2 -> 5

}
[/dot]

  • here is the place to make enhancement requests & bug reports Issues · graphviz / graphviz · GitLab. But there is quite a backlog, do not hold your breath for an enhancement
  • nodesep is probably the worst way to produce a “good” graph, all the nodes are affected
  • margin applied to the two clusters might be a bit better, but not much
  • the underlying problem is caused by the wide cluster labels. They cause cluster bounding-boxes that are much wider that required by the enclosed graph. Quite “legal”, but Graphviz does not have a “center graph within the cluster bounding box” option.
  • Below is a “pretty good” solution that :
    • adds 2 new levels of clusters
    • explicitly sets rank for a new subgraph containing root
    • sets constraint=false for some edges
      Note that the two side-by-side clusters have flipped left & right. If that is a problem, try reversing the order within the file or adding another enclosing cluster
digraph G {
bgcolor="gray"

node [
style=filled
shape="circle"
fillcolor="green4"
penwidth="2"
color="green1"
fontcolor="white"
]

{
rank=source
root [
   shape="box"
   color="white"
   fontcolor="white"
   label="hello"
]
}

subgraph clusterXXX{
peripheries=0
AAA_1 [
    label="AAA"
]

BBB_2 [
    label="BBB"
]

subgraph cluster_ccc {
    color=gray
    fillcolor="blue"
    style=filled
    label="ccccc"
    labelloc=b
    fontcolor="white"
    labeljust=l
peripheries=1

    subgraph cluster_south_a {
      label="aaaaaaaaaaaaaaaaaaaaaa"
      subgraph cluster_south_a1 {
        label="" peripheries=0
        5 [ label="" fillcolor="red" ]
        6 [ label="" fillcolor="red" ]

        5 -> 6
      }
    }


    subgraph cluster_south_b {
      label="bbbbbbbbbbbbbbbbbbbbbb"
      subgraph cluster_south_b1 {
        label="" peripheries=0
        7 [ label="" fillcolor="red" ]
        8 [ label="" fillcolor="red" ]
        9 [ label="" fillcolor="red" ]
        10 [ label="" fillcolor="red" ]

        7 -> 8
        7 -> 8
        7 -> 9
        8 -> 9
        8 -> 10
        9 -> 10
      }
    }
  }
}

{
edge [constraint=false]
root -> AAA_1
root -> BBB_2
}
AAA_1 -> 7
BBB_2 -> 5
}

Giving:

You might also checkout the osage layout it centers nodes in clusters by default, and allows other ways of aligning as well. As the ranking is not as strict as in dot i did need to define some additional clusters for ordering the graph, but you can get to something like this:

example:

digraph G {
layout="osage";
splines="spline";
packmode="array_ut1";
pack=20;
bgcolor="gray"node [
    style=filled
    shape="circle"
    fillcolor="green4"
    penwidth="2"
    color="green1"
    fontcolor="white"
    

]
subgraph cluster_top{ sortv=1 packmode="array_ut1"
root [
   shape="box"
   color="white"
   fontcolor="white"
   label="hello"
]
}
subgraph cluster_middle{ sortv=2 packmode="array_ut2"
AAA_1 [ sortv=2
    label="AAA"
]

BBB_2 [sortv=1
    label="BBB"
]
}
subgraph cluster_ccc { sortv=3 packmode="array_ut2"
    color=gray
    fillcolor="blue"
    style=filled
    label="ccccc"
    labelloc=b
    fontcolor="white"
    labeljust=l;

    subgraph cluster_south_a {
        label="aaaaaaaaaaaaaa";
        pack=20; packmode="array_ut1"; sortv="1";

        5 [ label="" fillcolor="red" ]
        6 [ label="" fillcolor="red" ]

        5 -> 6
    }

     subgraph cluster_south_b {
        label="bbbbbbbbbbbbbbbbbbbbbb" pack=20 packmode="array_ut1" sortv="2"

        7 [ label="" fillcolor="red" ]
        8 [ label="" fillcolor="red" ]
        9 [ label="" fillcolor="red" ]
        10 [ label="" fillcolor="red" ]

        7 -> 8
        7 -> 8
        7 -> 9
        8 -> 9
        8 -> 10
        9 -> 10
    }
}

root -> AAA_1
root -> BBB_2

AAA_1 -> 7
BBB_2 -> 5
}

[dot]
digraph G {
layout=“osage”;
splines=“spline”;
packmode=“array_ut1”;
pack=20;
bgcolor="gray"node [
style=filled
shape=“circle”
fillcolor=“green4”
penwidth=“2”
color=“green1”
fontcolor=“white”

]
subgraph cluster_top{ sortv=1 packmode=“array_ut1”
root [
shape=“box”
color=“white”
fontcolor=“white”
label=“hello”
]
}
subgraph cluster_middle{ sortv=2 packmode=“array_ut2”
AAA_1 [ sortv=2
label=“AAA”
]

BBB_2 [sortv=1
label=“BBB”
]
}
subgraph cluster_ccc { sortv=3 packmode=“array_ut2”
color=gray
fillcolor=“blue”
style=filled
label=“ccccc”
labelloc=b
fontcolor=“white”
labeljust=l;

subgraph cluster_south_a {
    label="aaaaaaaaaaaaaa";
    pack=20; packmode="array_ut1"; sortv="1";

    5 [ label="" fillcolor="red" ]
    6 [ label="" fillcolor="red" ]

    5 -> 6
}

 subgraph cluster_south_b {
    label="bbbbbbbbbbbbbbbbbbbbbb" pack=20 packmode="array_ut1" sortv="2"

    7 [ label="" fillcolor="red" ]
    8 [ label="" fillcolor="red" ]
    9 [ label="" fillcolor="red" ]
    10 [ label="" fillcolor="red" ]

    7 -> 8
    7 -> 8
    7 -> 9
    8 -> 9
    8 -> 10
    9 -> 10
}

}

root → AAA_1
root → BBB_2

AAA_1 → 7
BBB_2 → 5
}
[/dot]

1 Like

Thanks @steveroush for your potential solution. It was close again. However, if I add BBB_2 -> 7, the techniques used to have the nodes in the cluster centered no longer work. It seems a minimum requirement for my use case is that the layout system used supports explicit centering. I assume there will also be a manual fine-tuning technique for a specific layout, but use case does not lend itself to that. I am going to try the osage layout that @BartB suggested. I am hopeful it will work because it has explicit centering of nodes in a cluster.

@BartB Your solution to use the osage layout is working out well. However, I am seeing edges drawn over nodes.

With splines set to spline, the output is:

With splines set to curved, the output is:

With splines set to ortho, the output is:

The splines documentation says:

If splines=true , edges are drawn as splines routed around nodes

Only with ortho are the edges routed around nodes.

Is this a bug with the osage layout? Or, is there a way to use curved or spline with the splines attribute and not have the edges overlap nodes?

digraph G {
  layout    = "osage"
  splines   = spline
  packmode  = "array_ut1"
  pack      = 20
  bgcolor   = "gray"

  edge [ arrowhead = none ]

  node [
    style     = filled
    shape     = "circle"
    fillcolor = "green4"
    penwidth  = "2"
    color     = "green1"
    fontcolor = "white"
  ]

  subgraph cluster_root {
    sortv     = 1
    packmode  = "array_ut1"
    color     = transparent

    root [
      shape     = "box"
      color     = "white"
      fontcolor = "white"
      label     = "hello"
    ]
  }

  subgraph cluster_parents {
    sortv     = 2
    packmode  = array_rt3
    color     = transparent

    AAA_1 [
      sortv = 1
      label = "AAA"
    ]

    BBB_2 [
      sortv = 2
      label = "BBB"
    ]

    CCC_3 [
      sortv = 3
      label = "CCC"
    ]
  }

  subgraph cluster_children {
    sortv     = 3
    color     = transparent
    packmode  = array_rt3

    subgraph cluster_child_south {
      sortv     = 3
      packmode  = "array_ut2"
      color     = gray
      fillcolor = blue
      style     = filled
      label     = "South"
      labelloc  = b
      fontcolor = "white"
      labeljust = l
      style     = "rounded, filled"

      subgraph cluster_child_south_a {
        label = "aaaaaaaa"
        sortv = 1
        style = "rounded, filled"

        5 [ label="" fillcolor="red" ]
        6 [ label="" fillcolor="red" ]

        5 -> 6
      }

      subgraph cluster_child_south_b {
        label = "bbbbbbbbbbbbbbbbbbbbbb"
        sortv = 2
        style = "rounded, filled"

         7 [ label = "" fillcolor = "red" ]
         8 [ label = "" fillcolor = "red" ]
         9 [ label = "" fillcolor = "red" ]
        10 [ label = "" fillcolor = "red" ]

        7 -> 8
        7 -> 9
        8 -> 9
        8 -> 10
        9 -> 10
      }
    }

    subgraph cluster_child_east {
      sortv     = 3
      packmode  = "array_ut2"
      color     = gray
      fillcolor = blue
      style     = filled
      label     = "East"
      labelloc  = b
      fontcolor = "white"
      labeljust = l
      style     = "rounded, filled"

      subgraph cluster_child_east_a {
        label = "aaaaaaaa"
        sortv = 1
        style = "rounded, filled"

        ea1 [ label="" fillcolor="red" ]
        ea2 [ label="" fillcolor="red" ]

        ea1 -> ea2
      }

      subgraph cluster_child_east_b {
        label = "bbbbbbbbbbbbbbbbbbbbbb"
        sortv = 2
        style = "rounded, filled"

        eb1 [ label = "" fillcolor = "red" ]
        eb2 [ label = "" fillcolor = "red" ]
        eb3 [ label = "" fillcolor = "red" ]

        eb1 -> eb2
        eb1 -> eb3
        eb2 -> eb3
      }
    }
  }

  root -> AAA_1
  root -> BBB_2
  root -> CCC_3

  AAA_1 -> 7

  BBB_2 -> 5
  BBB_2 -> 7
  BBB_2 -> eb1

  CCC_3 -> 5
  CCC_3 -> ea1
}

@eric_g_97477 , in my experience both splines=spline or ortho are respecting the fact that edges are not overlapping nodes. I think the problem starts when circular shapes are used with a larger penwidth. But even in that case they will only touch the circle on the border and not go through it completely, which, in my case, would be still good enough. When using rectangles , like in the below example, i see edges to be non node overlapping when using splines=spline.

I think you may be encountering Incorrect edge splines with fdp and neato (#2168) · Issues · graphviz / graphviz · GitLab.

  • osage seems to use the neato spline-routing code
  • neato spline-routing will route edges much closer to nodes that dot
  • that code seems to have a bug, resulting in edges drawn over nodes

The good-ish news:

  • add the attribute esep=“+18” to your Root graph (esep | Graphviz) (undocumented for osage), to get the edges routed further from the nodes.
  • you might also increase pack a bit
  • seemingly you can set pack at the cluster-level, not just Root-level

With esep=“+18”:

2 Likes