2017-11-23 19:19:32 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2017-12-03 19:38:53 +00:00
|
|
|
"sort"
|
|
|
|
|
2017-11-23 19:19:32 +00:00
|
|
|
"github.com/mediocregopher/ginger/gg"
|
2017-12-03 19:38:53 +00:00
|
|
|
"github.com/mediocregopher/ginger/gim/constraint"
|
2017-11-23 19:19:32 +00:00
|
|
|
"github.com/mediocregopher/ginger/gim/geo"
|
|
|
|
"github.com/mediocregopher/ginger/gim/terminal"
|
|
|
|
)
|
|
|
|
|
2017-12-03 19:38:53 +00:00
|
|
|
// "Solves" vertex position by detemining relative positions of vertices in
|
|
|
|
// primary and secondary directions (independently), with relative positions
|
|
|
|
// being described by "levels", where multiple vertices can occupy one level.
|
|
|
|
//
|
|
|
|
// Primary determines relative position in the primary direction by trying
|
|
|
|
// to place vertices before their outs and after their ins.
|
|
|
|
//
|
|
|
|
// Secondary determines relative position in the secondary direction by
|
|
|
|
// trying to place vertices relative to vertices they share an edge with in
|
|
|
|
// the order that the edges appear on the shared node.
|
|
|
|
func posSolve(g *gg.Graph) [][]*gg.Vertex {
|
|
|
|
primEng := constraint.NewEngine()
|
|
|
|
secEng := constraint.NewEngine()
|
|
|
|
|
|
|
|
strM := g.ByID()
|
2018-01-21 15:39:25 +00:00
|
|
|
for _, v := range strM {
|
2017-12-03 19:38:53 +00:00
|
|
|
var prevIn *gg.Vertex
|
|
|
|
for _, e := range v.In {
|
|
|
|
primEng.AddConstraint(constraint.Constraint{
|
|
|
|
Elem: e.From.ID,
|
2018-01-21 15:39:25 +00:00
|
|
|
LT: v.ID,
|
2017-12-03 19:38:53 +00:00
|
|
|
})
|
|
|
|
if prevIn != nil {
|
|
|
|
secEng.AddConstraint(constraint.Constraint{
|
|
|
|
Elem: prevIn.ID,
|
|
|
|
LT: e.From.ID,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
prevIn = e.From
|
|
|
|
}
|
2017-11-23 19:19:32 +00:00
|
|
|
|
2017-12-03 19:38:53 +00:00
|
|
|
var prevOut *gg.Vertex
|
2017-11-23 19:19:32 +00:00
|
|
|
for _, e := range v.Out {
|
2017-12-03 19:38:53 +00:00
|
|
|
if prevOut == nil {
|
2017-11-23 19:19:32 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-12-03 19:38:53 +00:00
|
|
|
secEng.AddConstraint(constraint.Constraint{
|
|
|
|
Elem: prevOut.ID,
|
|
|
|
LT: e.To.ID,
|
|
|
|
})
|
|
|
|
prevOut = e.To
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
2017-12-03 19:38:53 +00:00
|
|
|
}
|
|
|
|
prim := primEng.Solve()
|
|
|
|
sec := secEng.Solve()
|
2017-11-23 19:19:32 +00:00
|
|
|
|
2017-12-03 19:38:53 +00:00
|
|
|
// determine maximum primary level
|
|
|
|
var maxPrim int
|
|
|
|
for _, lvl := range prim {
|
|
|
|
if lvl > maxPrim {
|
|
|
|
maxPrim = lvl
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
2017-12-03 19:38:53 +00:00
|
|
|
}
|
2017-11-23 19:19:32 +00:00
|
|
|
|
2017-12-03 19:38:53 +00:00
|
|
|
outStr := make([][]string, maxPrim+1)
|
|
|
|
for v, lvl := range prim {
|
|
|
|
outStr[lvl] = append(outStr[lvl], v)
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
|
|
|
|
2017-12-03 19:38:53 +00:00
|
|
|
// sort each primary level
|
|
|
|
for _, vv := range outStr {
|
|
|
|
sort.Slice(vv, func(i, j int) bool {
|
|
|
|
return sec[vv[i]] < sec[vv[j]]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert to vertices
|
|
|
|
out := make([][]*gg.Vertex, len(outStr))
|
|
|
|
for i, vv := range outStr {
|
|
|
|
out[i] = make([]*gg.Vertex, len(outStr[i]))
|
|
|
|
for j, v := range vv {
|
|
|
|
out[i][j] = strM[v]
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-03 19:38:53 +00:00
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
// mutates the boxes to be centered around the given point, keeping their
|
|
|
|
// relative position to each other
|
|
|
|
func centerBoxes(boxes []*box, around geo.XY) {
|
|
|
|
var graphRect geo.Rect
|
|
|
|
for _, b := range boxes {
|
|
|
|
graphRect = graphRect.Union(b.rect())
|
|
|
|
}
|
|
|
|
graphMid := graphRect.Center(rounder)
|
|
|
|
delta := around.Sub(graphMid)
|
|
|
|
for _, b := range boxes {
|
|
|
|
b.topLeft = b.topLeft.Add(delta)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type view struct {
|
|
|
|
g *gg.Graph
|
|
|
|
primFlowDir, secFlowDir geo.XY
|
2018-01-21 15:39:25 +00:00
|
|
|
start gg.Value
|
2017-12-03 19:38:53 +00:00
|
|
|
center geo.XY
|
|
|
|
}
|
|
|
|
|
|
|
|
func (view *view) draw(term *terminal.Terminal) {
|
|
|
|
relPos := posSolve(view.g)
|
2017-11-23 19:19:32 +00:00
|
|
|
|
|
|
|
// create boxes
|
2017-12-03 19:38:53 +00:00
|
|
|
var boxes []*box
|
|
|
|
boxesM := map[*box]*gg.Vertex{}
|
|
|
|
boxesMr := map[*gg.Vertex]*box{}
|
|
|
|
const (
|
|
|
|
primPadding = 5
|
|
|
|
secPadding = 3
|
|
|
|
)
|
|
|
|
var primPos int
|
|
|
|
for _, vv := range relPos {
|
|
|
|
var primBoxes []*box // boxes on just this level
|
|
|
|
var maxPrim int
|
|
|
|
var secPos int
|
|
|
|
for _, v := range vv {
|
|
|
|
primVec := view.primFlowDir.Scale(primPos)
|
|
|
|
secVec := view.secFlowDir.Scale(secPos)
|
|
|
|
|
|
|
|
b := boxFromVertex(v, view.primFlowDir)
|
|
|
|
b.topLeft = primVec.Add(secVec)
|
|
|
|
boxes = append(boxes, &b)
|
|
|
|
primBoxes = append(primBoxes, &b)
|
|
|
|
boxesM[&b] = v
|
|
|
|
boxesMr[v] = &b
|
|
|
|
|
2017-11-23 19:19:32 +00:00
|
|
|
bSize := b.rect().Size
|
2017-12-03 19:38:53 +00:00
|
|
|
primBoxLen := bSize.Mul(view.primFlowDir).Len(rounder)
|
|
|
|
secBoxLen := bSize.Mul(view.secFlowDir).Len(rounder)
|
|
|
|
if primBoxLen > maxPrim {
|
|
|
|
maxPrim = primBoxLen
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
2017-12-03 19:38:53 +00:00
|
|
|
secPos += secBoxLen + secPadding
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
2017-12-03 19:38:53 +00:00
|
|
|
centerBoxes(primBoxes, view.primFlowDir.Scale(primPos))
|
|
|
|
primPos += maxPrim + primPadding
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
|
|
|
|
2018-03-04 14:35:01 +00:00
|
|
|
// returns the index of this edge in from's Out
|
|
|
|
// TODO this might not be deterministic? Out is never ordered technically
|
|
|
|
findFromI := func(from *gg.Vertex, e gg.Edge) int {
|
|
|
|
for i, fe := range from.Out {
|
|
|
|
if fe == e {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
panic("edge not found in from.Out")
|
|
|
|
}
|
|
|
|
|
2017-11-23 19:19:32 +00:00
|
|
|
// create lines
|
|
|
|
var lines []line
|
2017-12-03 19:38:53 +00:00
|
|
|
for _, b := range boxes {
|
|
|
|
v := boxesM[b]
|
2018-03-03 17:32:40 +00:00
|
|
|
for i, e := range v.In {
|
2017-12-03 19:38:53 +00:00
|
|
|
bFrom := boxesMr[e.From]
|
2018-03-04 14:35:01 +00:00
|
|
|
lines = append(lines, line{
|
|
|
|
from: bFrom,
|
|
|
|
fromI: findFromI(e.From, e),
|
|
|
|
to: b,
|
|
|
|
toI: i,
|
|
|
|
})
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-03 19:38:53 +00:00
|
|
|
// translate all boxes so the graph is centered around v.center
|
|
|
|
centerBoxes(boxes, view.center)
|
2017-11-23 19:19:32 +00:00
|
|
|
|
|
|
|
// actually draw the boxes and lines
|
2017-12-03 19:38:53 +00:00
|
|
|
for _, b := range boxes {
|
|
|
|
b.draw(term)
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
|
|
|
for _, line := range lines {
|
2018-03-03 17:32:40 +00:00
|
|
|
line.draw(term, view.primFlowDir, view.secFlowDir)
|
2017-11-23 19:19:32 +00:00
|
|
|
}
|
|
|
|
}
|