Prevent upward connections in DAG

Hi,

I’m generating Graphviz diagrams. Specifically, I’m generating directed acyclic graphs, and relying on the resultant top-down layout. My issue is, I’ve been working on the assumption that Graphviz will always display a directed-acyclic graph in a one-directional manner. But now I’ve come across a counterexample, which breaks my application:

One of the lines points upwards! This is super rare. I’ve generated dozens of similarly-complicated diagram, and usually all the arrows point downwards. I can’t actually find any documentation to clarify whether this is expected behaviour though.

So, can anyone tell me:

  1. Is this expected behaviour, or a bug?
  2. If it is expected behaviour, is there anything I can do to force Graphviz to always direct edges downwards?

I am leaning towards thinking it is a bug. The reason is that if I add a minlen=2 attribute to the red line, it doesn’t get respected.

Add a “weight” attribute to that edge. I found that using a value >= 10 will get the edge to point down.

If you remove all the minlen attributes as I’ve done here the graph will appear as:

Thanks for your suggestions. But I don’t think either of these solutions are workable for me.

My diagram generator currently depends on the minlen attribute for other purposes, so I can’t just remove it. I’m also not sure the minlen attributes are the root cause of the issue. There are many minor things that you can be changed in the example that “fix” it. In fact, I have simplified it as much as I can such that almost any further simplification will “fix” it.

As for the weight attribute, that’s not an option because I’m generating the graphs. My generator would need to be able to predict exactly when this situation is going to occur in order to insert the weight attribute on the appropriate edges. Furthermore, the weight attribute will have other side effects which I’m not keen on.

I’m not sure about all the specifics, but the idea underlying cluster layout (originally) was that clusters should be compact (have as few levels as possible based on the intra-cluster edges) so inter-cluster edges are implemented as soft constraints that may not be satisfied depending on the global constraint optimization. A long time later we implemented clusterrank=global that is supposed to treat inter-cluster edges as hard constraints. I think this would make level assignment easier to control. This I notice this setting now disables the drawing of clusters – that is not what we intended or how it used to work, and looks like a bug.

My purpose for posting with the minlen attributes removed was to see if it had any influence on the graph. As you have determined, and the graph shows, minlen does not appear to be the root cause.

Another graph attribute you can try is newrank. Setting newrank=true will draw the arrow in the direction you desire, but has other effects you may or may not like. When I added newrank to your graph I get this result:

In my opinion, it is not as pretty or concise as your graph which I started with.

If you add splines=ortho it becomes a little cleaner:

I’m aware that this style is probably not what you are looking for, but it never hurts to know more options. After all, the arrow is pointing in the direction you desire, minlen is present, no weight attributes are used, and it could be programmatically generated.

Thanks for your newrank=true suggestion @jjlong. Indeed this does have other effects which are unfortunately incompatible with my specific use-case. The compactness of subgraphs is quite important to me. So is the use of rank=sink and rank=source within clusters, which seems to be broken by that setting.

I should probably mention that the style of graphs I am generating is considerably more complicated than the example I originally provided (which was intended only to showcase the Graphviz bug). So if you’re interested (or in case it’s helpful), you can see the unsimplified version here, with the troublesome edge highlighted in green this time. As you can see, I’m simulating ports (i.e. like the ports natively supported by the Eclipse Layout Kernel) which results in quite a fragile graph.

Thanks @scnorth for your analysis.

So it sounds like my specific issue isn’t a bug then?

I’m not sure I follow what you’re saying about clusterrank=global being bugged though. You seem to be saying that that setting shouldn’t switch off the special cluster processing. But the documentation actually states

The modes clusterrank=global and clusterrank=none appear to be identical, both turning off the special cluster processing.
clusterrank | Graphviz

You’re right. I wonder when that was done. We wouldn’t have added a definition for clusterrank=global without implementing it. I wonder if this was/is dependent on newrank=true per commit 9c773513d5f99538b7ad7ddc83e174ddcc134937 Probably only Emden would even know. We could try rolling way back to see if it’s there, but would it even help?

No don’t worry about trying that. Not for my sake anyway. Both those settings break my diagrams in countless other ways so, I doubt we’d find a solution that works for me there.

Does anyone know if it’s possible to explicitly set the global rank of a node? My dot generator is already somewhat aware of the rank of nodes as it creates them. In fact, it relies on minlen, and the (incorrect) assumption that minlen can be used to dictate a relative minimum rank. If there is a way to set rank manually instead, then I may be able to solve my problem.

A through perusal of the attributes documentation doesn’t give me much hope that such a feature exists, however.

Short answer - Nope, rank only applies to subgraphs. [Or so the documentation says]
Longer (confusing) answer - Maybe, kind-of.

  • It is pretty easy to calculate the rank of every node in a graph
    • run dot -Tdot myfile.gv >myfile.dot
    • using GVPR, Python, or whatever; use the Y pos of each node to identify the Y value of each rank and then the rank of each node. There is a GVPR program in the Graphviz source named addranks that does just this (pretty sure).
  • One could then programmatically add a rank=same subgraph, explicitly listing all nodes in each rank. [For a given graph, this might be sufficient or it might not]
  • Finally, one could add invisible nodes that explicitly positioned the ranks, similar to the shells.gv graph in the source package. (I think this would work, but I do not like it)

It is true, we do not provide any way to do this, and seems like a reasonable request.

A nice feature would be to support semi-manual layout (possibly based in dotgen, or in neato) where the input graph is just placed on a grid in a straightforward way, with ranks and positions given, or generated by default settings.

I’ve written a row/column layout engine as a pipelined pre-post processor:
any engine (to determine node sizes) | row/column node positioner | neato -n
I’m currently stuck on a weird bug - mine (probably) or Graphviz handler of undefined attributes, but with luck I’ll find/fix the bug sometime soon.
Even with feature creep the program is pretty simple.
There are some real limitations (and real advantages) to this concept. It can produce some interesting images, but it does not help much is you just want to tweak the output from dot (or any engine).

Just random thoughts - yes feature creep can be a problem. For example, the user wants to specify node positions and maybe some or all edge labels and their placement. They want clusters to work, in a certain way. They want to give node positions relative to clusters. They want to give some node positions but not all of them. I see you already thought of this. How do you cope with it?

@steveroush I’m not sure I understand your suggestion.

code

This is what I think you mean. I’ve added r0 through r9 (and 0, which is a new node I’ve added) to a rank=same scope at the end of the file. Well after their original declaration. I was hoping this would have no effect on the graph, and would reinforce their rank as 0. I’ve not gone as far as to add the other 4 rank=same scopes, because clearly the first one destroys the cluster that previously encapsulated r0 through r9.

Is this what you meant? If so, it doesn’t seem to be workable for a graph which has clusters.

Whoops, I said confusing, I should have said confused!! Useless circular thinking. Never mind.
However, would a program to find “upward” edges help? That I think we can do.

I think I’d be able to detect when this is occurring using y values as you described. Unfortunately, I don’t think just detecting when it is happening would be sufficient for my use case. Not least because any modification I make to the graph for a second pass through Graphviz has the potential to cause the layout to completely rearrange, invalidating any inferences made from the first pass.