Question about nodesep ranksep and minlen attributes in dot layout


let’s say we have this graph

digraph  {  
    graph[rankdir=TB nodesep=0.02 ranksep=0.02]
    edge[arrowsize=0 style=invis minlen=1 len=0]
    a b c
    a -> c 

The documentation says for
Specifies separation between ranks (type: double | doubleList, default: 0.5 (dot) , 1.0 (twopi) , minimum: 0.02)
In dot, sets the desired rank separation, in inches.
This is the minimum vertical distance between the bottom of the nodes in one rank and the tops of nodes in the next

Minimum edge length (rank difference between head and tail) type: int, default: 1, minimum: 0

Based on my comprehension, my expectation is to have a distance of 0.02inch between a and c.

But I have this :

Could you explain to me why I have this gap?
Is the edge responsible for something?


My monthly PSA: the forum supports code blocks with Graphviz syntax highlighting:

digraph {
  like -> so[with="a label"];

which produces:

digraph {
  like -> so[with="a label"];
1 Like

Good question. It seems to be strictly related to rank. So ranksep should do the trick.
Removing all references to edges does not change things.

digraph {
graph[nodesep=0 ranksep=.02]
{rank=source a b}
{rank=sink c}


I explored the documentation but so far I didn’t find any solution.
I am sure there is a way otherwise It’s really surprising.
Do you think that it’s a bug ? or the documentation is not correct?
I think I am missing something here :woozy_face:

My guess is that it is a bug/feature - working as originally designed but incorrectly documented. The clearest documentation I found is here Addressing ranksep, it says:

The second deals with rank separation, which is the minimum vertical space between the bottoms of nodes in one rank and the tops of nodes in the next.

Generally, I believe the developers expected users to use record and html nodes to create nodes that abutted each other

This appears to be a bug. The relevant code is in set_ycoords

while (--r >= GD_minrank(g)) {
d0 = rank[r + 1].pht2 + rank[r].pht1 + GD_ranksep(g);	/* prim node sep */
d1 = rank[r + 1].ht2 + rank[r].ht1 + CL_OFFSET;	/* cluster sep */
delta = MAX(d0, d1);
if (rank[r].n > 0)	/* this may reflect some problem */
	(ND_coord(rank[r].v[0])).y = (ND_coord(rank[r + 1].v[0])).y + delta;

Notice that because of the definition of delta from d1, the offset between ranks can never be less than CL_OFFSET. I don’t know why we believed this. The subsequent comment about needing a guard to skip potentially empty ranks is a little unsettling but doesn’t seem to be a problem, it’s just conservative coding, I would like to think. I don’t see why one multiple of CL_OFFSET is necessarily even enough. I don’t see any recursion in set_ycoords so not sure how nesting of clusters is accounted for. The whole thing seems a little foolish; why not just solve a second linear program for y coords. Our code is too brittle.

1 Like

Thanks for your investigation.

Should I open an issue in the repo?

Good idea