ginger/gim/NOTES
2018-06-03 04:25:32 +00:00

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