notes for gim on graph drawing algo, and some TODOs

This commit is contained in:
Brian Picciano 2018-06-02 08:31:12 +00:00
parent 1dc53518af
commit c16fc00bf7
3 changed files with 89 additions and 4 deletions

View File

@ -8,6 +8,22 @@ import (
"strings"
)
// TODO it's a bit unfortunate that it's possible to create disjointed graphs
// within the same graph instance. That's not really something that would be
// possible with any other type of datastructure. I think all that would be
// needed to get rid of this is to remove the Null instance and instead do a
// New(value) function. This would allow a graph to be just a single value with
// no edges, but I _think_ that's fine?
//
// Actually, that's kinda bogus, it really messes with how I conceptualized
// ginger being used. Which is maybe fine. I should do some research on the
// typical way that graphs and other structures are defined.
//
// It seems that disjoint unions, as they're called, are an accepted thing... if
// I need it for gim a Disjoin method might be the best bet, which would walk
// through the Graph and compute each disjointed Graph and return them
// individually.
// Value wraps a go value in a way such that it will be uniquely identified
// within any Graph and between Graphs. Use NewValue to create a Value instance.
// You can create an instance manually as long as ID is globally unique.

71
gim/NOTES Normal file
View File

@ -0,0 +1,71 @@
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

View File

@ -16,10 +16,8 @@ import (
// - Changing the "flow" direction
// - Absolute positioning of some/all vertices
// TODO
// - edge values
// - be able to draw circular graphs
// - audit all steps, make sure everything is deterministic
// TODO be able to draw circular graphs
// TODO audit all steps, make sure everything is deterministic
const (
framerate = 10