Notes from reading https://www.graphviz.org/Documentation/TSE93.pdf, which describes an algorithm for drawing an acyclic graph in basically the way which I want. This document assumes the primary flow of drawing is downward, and secondary is right. For all of this it might be easier to not even consider edge values yet, as those could be done by converting them into vertices themselves after the cyclic-edge-reversal and then converting them back later. Drawing the graph is a four step process: 1) Rank nodes in the Y axis - Graph must be acyclic. - This can be accomplished by strategically reversing edges which cause a cycle, and then reversing them back as a post-processing step. - Edges can be found by: - walking out from a particular node depth-first from some arbitrary node. - As you do so you assign a rank based on depth to each node you encounter. - If any edge is destined for a node which has already been seen you look at the ranks of the source and destination, and if the source is _greater_ than the destination you reverse the edge's direction. - I think that algorithm only works if there's a source/sink? might have to be modified, or the walk must traverse both to & from. - Assign all edges a weight, default 1, but possibly externally assigned to be greater. - Take a "feasible" minimum spanning tree (MST) of the graph - Feasibility is defined as each edge being "tight", meaning, once you rank each node by their distance from the root and define the length of an edge as the difference of rank of its head and tail, that each tree edge will have a length of 1. - Perform the following on the MST: - For each edge of the graph assign the cut value - If you were to remove any edge of an MST it would create two separate MSTs. The side the edge was pointing from is the tail, the side it was pointing to is the head. - Looking at edges _in the original graph_, sum the weights of all edges directed from the tail to the head (including the one removed) and subtract from that the sum of the weights of the edges directed from the head to the tail. This is the cut value. - "...note that the cut values can be computed using information local to an edge if the search is ordered from the leaves of the feasible tree inward. It is trivial to compute the cut value of a tree edge with one of its endpoints a leaf in the tree, since either the head or the tail component consists of a single node. Now, assuming the cut values are known for all the edges incident on a given node except one, the cut value of the remaining edge is the sum of the known cut values plus a term dependent only on the edges incident to the given node." - Take an edge with a negative cut value and remove it. Find the graph edge between the remaining head and tail MSTs with the smallest "slack" (distance in rank between its ends) and add that edge to the MST to make it connected again. - Repeat until there are no negative cut values. - Apparently searching "cyclically" through the negative edges, rather than iterating from the start each time, is worthwhile. - Normalize the MST by assigning the root node the rank of 0 (and so on), if it changed. - All edges in the MST are of length 1, and the rest can be inferred from that. - To reduce crowding, nodes with equal in/out edge weights and which could be placed on multiple rankings are moved to the ranking with the fewest nodes. 2) Order nodes in the X axis to reduce edge crossings 3) Compute node coordinates 4) Determine edge splines