diff --git a/gg/gg.go b/gg/gg.go index 70ed8ee..2d202c1 100644 --- a/gg/gg.go +++ b/gg/gg.go @@ -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. diff --git a/gim/NOTES b/gim/NOTES new file mode 100644 index 0000000..b227fcf --- /dev/null +++ b/gim/NOTES @@ -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 diff --git a/gim/main.go b/gim/main.go index 345a976..1b1acc5 100644 --- a/gim/main.go +++ b/gim/main.go @@ -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