112 lines
5.9 KiB
Plaintext
112 lines
5.9 KiB
Plaintext
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
|
|
- Add ephemeral vertices along edges with lengths greater than 1, so all
|
|
"spaces" are filled.
|
|
- If any vertices have edges to vertices on their same rank, those are
|
|
ordered so that all these "flag edges" are pointed in the same direction
|
|
across that rank, and the ordering of those particular vertices is always
|
|
kept.
|
|
- Iterate over the graph some fixed number of times (the paper recommends
|
|
24)
|
|
- possibly with some heuristic which looks at percentage improvement
|
|
each time to determine if it's worth the effort.
|
|
- on one iteration move "down" the graph, on the next move "up", etc...
|
|
shaker style
|
|
- On each iteration:
|
|
- For each vertex look at the median position of all of the vertices
|
|
it has edges to in the previous rank
|
|
- If the number of previous vertices is even do this complicated
|
|
thing (P is the set of positions previous):
|
|
```
|
|
if |P| = 2 then
|
|
return (P[0] + P[1])/2;
|
|
else
|
|
left = P[m-1] - P[0];
|
|
right = P[|P| -1] - P[m];
|
|
return (P[m-1]*right + P[m]*left)/(left+right);
|
|
endif
|
|
```
|
|
- Sort the vertices by their median position
|
|
- vertices with no previous vertices remain fixed
|
|
- Then, for each vertex in the rank attempt to transpose it with its
|
|
neighbor and see if that reduces the number of edge crossings
|
|
between the rank and its previous.
|
|
- If equality is found during these two steps (same median, or same
|
|
number of crossings) the vertices in question should be flipped.
|
|
|
|
3) Compute node coordinates
|
|
- Determining the Y coordinates is considered trivial: find the maxHeight of
|
|
each rank, and ensure they are separated by that much plus whatever the
|
|
separation value is.
|
|
- For the X coordinates: do some insane shit involving the network simplex
|
|
again.
|
|
|
|
4) Determine edge splines
|