Dynamic minlen to account for plot scaling

Hi,

I am developing an R package to generate a DOT plot based on user input. The text might have different length or the number of nodes in parallel will be different. I have managed to calculate the minlen for some cases. I calculated the width of the text in each node and multiplied by some values. This method works if there are only a few nodes in parallel or the text is not too wide, but it fails when the text in the node is too long or there are too many nodes in parallel. The Graphviz scaled down the figure to fit the window but the minlen is calculated without scaling. So I have wide distance between nodes. I can manually decrease the minlen and get it working, but I am trying to find a solution, where the minlen is calculated based accounting for the scaling.

[dot verbose=true]
digraph consort_diagram {
graph [layout = dot, splines=ortho, overlap=prism]

node definitions with substituted label text

node [shape = rectangle, fillcolor = Biege, style=“”, fillcolor = “”, color = “”]

node1 [label = “Cohort 1 (n=6)”]
node2 [label = “Cohort 2 (n=6)”]
node3 [label = “Cohort 3 (n=6)”]
node4 [label = “Excluded (n=1)\l”]
node5 [label = “Excluded (n=3)\l”]
node7 [label = “Cohort 1 (n=5)”]
node8 [label = “Cohort 2 (n=3)”]
node10 [label = “Total (n=14)”]

Invisible point node for joints

node [shape = point, width = 0, style=invis]

P1 P2 P3 P4 P5

subgraph {
rank = same; rankdir = LR; node1; node2; node3;
}
subgraph {
rank = same; rankdir = LR; P1; node4;
}
subgraph {
rank = same; rankdir = LR; node7; node8; node9;
}
subgraph {
rank = same; rankdir = LR; node4; node5; node6;
}
subgraph {
rank = same; rankdir = LR; P2; node5;
}
subgraph {
rank = same; rankdir = LR; node4; node5;
}
subgraph {
rank = same; rankdir = LR; P3; P4; P5;
}

edge[style=“”];

node1 → P1 [arrowhead = none];
P1 → node4;
P1 → node7;
node2 → P2 [arrowhead = none];
P2 → node5;
P2 → node8;
P4 → node10;
node7 → P3 [arrowhead = none];
node8 → P4 [arrowhead = none];
node9 → P5 [arrowhead = none];
P3 → P4 → P5 [arrowhead = none, minlen = 7.0];

node3 → node9 [arrowhead = none];
}
[/dot]

But the distance between nodes are increased in the example below:

[dot verbose=true]
digraph consort_diagram {
graph [layout = dot, splines=ortho, overlap=prism]

node definitions with substituted label text

node [shape = rectangle, fillcolor = Biege, style=“”, fillcolor = “”, color = “”]

node1 [label = “Cohort 1 (n=6)”]
node2 [label = “Cohort 2 (n=6)”]
node3 [label = “Cohort 3 (n=6)”]
node4 [label = “Cohort 4 (n=6)”]
node5 [label = “Cohort 5 (n=6)”]
node6 [label = “Excluded (n=1)\l”]
node7 [label = “Excluded (n=3)\lPatient died before surgery\l”]
node9 [label = “Excluded (n=3)\l”]
node10 [label = “Excluded (n=3)\lPatient declined to participate\l”]
node11 [label = “Cohort 1 (n=5)”]
node12 [label = “Cohort 2 (n=3)”]
node14 [label = “Cohort 5 (n=6)”]
node15 [label = “Cohort 5 (n=6)”]
node16 [label = “Total (n=14)”]

Invisible point node for joints

node [shape = point, width = 0, style=invis]

P1 P2 P3 P4 P5 P6 P7 P8 P9

subgraph {
rank = same; rankdir = LR; node1; node2; node3; node4; node5;
}
subgraph {
rank = same; rankdir = LR; P1; node6;
}
subgraph {
rank = same; rankdir = LR; node11; node12; node13; node14; node15;
}
subgraph {
rank = same; rankdir = LR; node6; node7; node8; node9; node10;
}
subgraph {
rank = same; rankdir = LR; P2; node7;
}
subgraph {
rank = same; rankdir = LR; node6; node7; node9; node10;
}
subgraph {
rank = same; rankdir = LR; P3; node9;
}
subgraph {
rank = same; rankdir = LR; P4; node10;
}
subgraph {
rank = same; rankdir = LR; P5; P6; P7; P8; P9;
}

edge[style=“”];

node1 → P1 [arrowhead = none];
P1 → node6;
P1 → node11;
node2 → P2 [arrowhead = none];
P2 → node7;
P2 → node12;
node4 → P3 [arrowhead = none];
P3 → node9;
P3 → node14;
node5 → P4 [arrowhead = none];
P4 → node10;
P4 → node15;
P7 → node16;
node11 → P5 [arrowhead = none];
node12 → P6 [arrowhead = none];
node13 → P7 [arrowhead = none];
node14 → P8 [arrowhead = none];
node15 → P9 [arrowhead = none];
P5 → P6 → P7 → P8 → P9 [arrowhead = none, minlen = 16.0];

node3 → node13 [arrowhead = none];
}

[/dot]

I don’t want the nodes to have large space but want to keep the layout as it is.

So my question is: how to account for potential scaling when calculating the minlen based on the text width within the nodes? Or is there a way to draw a plot without specifying the minlen but keep the layout as in the example?

Much appreciate any help.

I’m confused (once again).

  • minlen is in units of rank (integer) (minlen | Graphviz).
  • Your examples seem to be ranked TB.
  • The poorly scaled distance is LR - within ranks.
  • This is me looking confused.
    Could you provide input files to test with?

Ah, sorry. I thought the code would display. I have added the code.

Yes, it is poorly scaled distance and I want to fix the problem with the distance. If I don’t minlen, the arrows connected to the nodes with various ways.

If you use [dot verbose=true] the code will display automatically.

Perfect, thanks. The code is displayed now.

1 Like

I don’t understand the specifics, but if the general idea is that some layout attribute (like minlen) is to be calculated at runtime using some other layout properties, I don’t see any way around just running two full layout passes.

It might be kind of cool if Graphviz had a richer way of defining attributes, allowing information like node sizes to be passed downstream, but we don’t have that. The closest thing are the, uh, escapes? macros? for text labels, like \N for the name of the current node, and \G, \T, \H etc. for current graph name, tail node name, head node name of an edge. See strdup_and_subst_obj0 for complete documentation. We already have bugs involving this notation and I wouldn’t try to push it any further, besides, those escapes only work in text labels. I admire this idea but it would be considerable work to interface to the current world that mixes graphviz attributes, runtime object, and state written in C that’s floating around inside Graphviz programs.

Some bugs:

  • overlap does not apply to dot. It will be ignored.
  • rankdir only applies to the root graph. All other references will be ignored.
  • minlen is used to set the number of ranks between tail & head. e.g. setting minlen=16 means that you want 16 (or more) ranks between tail & head. (i.e. not inches nor points)

Here is a modified version of your second graph. It uses the group attribute to keep nodes in columns. (It needs some headclip and tailclip fixes)

digraph consort_diagram {
graph [layout = dot, splines=ortho]

node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""]

node1 [label = "Cohort 1 (n=6)"       group=C1]
node2 [label = "Cohort 2 (n=6)"       group=C2]
node3 [label = "Cohort 3 (n=6)"       group=C3]
node4 [label = "Cohort 4 (n=6)"       group=C4]
node5 [label = "Cohort 5 (n=6)"       group=C5]
node6 [label = "Excluded (n=1)\l"]
node7 [label = "Excluded (n=3)\lPatient died before surgery\l"]
node9 [label = "Excluded (n=3)\l"]
node10 [label = "Excluded (n=3)\lPatient declined to participate\l"]
node11 [label = "Cohort 1 (n=5)"       group=C1]
node12 [label = "Cohort 2 (n=3)"       group=C2]
node14 [label = "Cohort 5 (n=6)"       group=C4]
node15 [label = "Cohort 5 (n=6)"       group=C5]
node16 [label = "Total (n=14)"       group=C3]

node [shape = point, width = 0, style=invis]

P1      [group=C1]
P2      [group=C2]
P3      [group=C4]
P4      [group=C5]
P5      [group=C1]
P6   [group=C2]
P7   [group=C3]
P8   [group=C4]
P9   [group=C5]

subgraph {
rank = same;  node1; node2; node3; node4; node5;
}
subgraph {
rank = same;  P1; node6;
}
subgraph {
rank = same; node11; node12; node13; node14; node15;
}
subgraph {
rank = same; node6; node7; node9; node10;
}
subgraph {
rank = same; P2; node7;
}
subgraph {
rank = same; node6; node7; node9; node10;
}
subgraph {
rank = same; P3; node9;
}
subgraph {
rank = same;  P4; node10;
}
subgraph {
rank = same;  P5; P6; P7; P8; P9;
}

edge[style=""];

node1 -> P1 [arrowhead = none];
P1 -> node6;
P1 -> node11;
node2 -> P2 [arrowhead = none];
P2 -> node7;
P2 -> node12;
node4 -> P3 [arrowhead = none];
P3 -> node9;
P3 -> node14;
node5 -> P4 [arrowhead = none];
P4 -> node10;
P4 -> node15;
P7 -> node16;

edge[headclip=false]
node11 -> P5 [arrowhead = none];
node12 -> P6 [arrowhead = none];
node13 -> P7 [arrowhead = none];
node14 -> P8 [arrowhead = none];
node15 -> P9 [arrowhead = none];
edge[headclip=false tailclip=false]
P5 -> P6 -> P7 -> P8 -> P9 [arrowhead = none]

node3 -> node13 [arrowhead = none];
}

Giving:

1 Like

This is amazing. Thank you very much. It looks like I was wrong about the minlen and I don’t need to do any numerical calculation. This is super helpful.

Cheers.