gim: move view code into its own package
This commit is contained in:
parent
0a6526d2c3
commit
c277bab368
38
gim/main.go
38
gim/main.go
@ -1,36 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mediocregopher/ginger/gg"
|
||||
"github.com/mediocregopher/ginger/gim/geo"
|
||||
"github.com/mediocregopher/ginger/gim/terminal"
|
||||
"github.com/mediocregopher/ginger/gim/view"
|
||||
)
|
||||
|
||||
// Leave room for:
|
||||
// - Changing the "flow" direction
|
||||
// - Absolute positioning of some/all vertices
|
||||
|
||||
// TODO be able to draw circular graphs
|
||||
// TODO audit all steps, make sure everything is deterministic
|
||||
// TODO self-edges
|
||||
|
||||
const (
|
||||
framerate = 10
|
||||
frameperiod = time.Second / time.Duration(framerate)
|
||||
)
|
||||
//const (
|
||||
// framerate = 10
|
||||
// frameperiod = time.Second / time.Duration(framerate)
|
||||
//)
|
||||
|
||||
func debugf(str string, args ...interface{}) {
|
||||
if !strings.HasSuffix(str, "\n") {
|
||||
str += "\n"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, str, args...)
|
||||
}
|
||||
//func debugf(str string, args ...interface{}) {
|
||||
// if !strings.HasSuffix(str, "\n") {
|
||||
// str += "\n"
|
||||
// }
|
||||
// fmt.Fprintf(os.Stderr, str, args...)
|
||||
//}
|
||||
|
||||
func mkGraph() (*gg.Graph, gg.Value) {
|
||||
a := gg.NewValue("a")
|
||||
@ -83,15 +77,9 @@ func main() {
|
||||
center := geo.Zero.Midpoint(wSize)
|
||||
|
||||
g, start := mkGraph()
|
||||
v := view{
|
||||
g: g,
|
||||
primFlowDir: geo.Right,
|
||||
secFlowDir: geo.Down,
|
||||
start: start,
|
||||
}
|
||||
|
||||
view := view.New(g, start, geo.Right, geo.Down)
|
||||
viewBuf := terminal.NewBuffer()
|
||||
v.draw(viewBuf)
|
||||
view.Draw(viewBuf)
|
||||
|
||||
buf := terminal.NewBuffer()
|
||||
buf.DrawBufferCentered(center, viewBuf)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/mediocregopher/ginger/gim/geo"
|
@ -1,98 +1,49 @@
|
||||
package main
|
||||
// Package view implements rendering a graph to a terminal.
|
||||
package view
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/mediocregopher/ginger/gg"
|
||||
"github.com/mediocregopher/ginger/gim/constraint"
|
||||
"github.com/mediocregopher/ginger/gim/geo"
|
||||
"github.com/mediocregopher/ginger/gim/terminal"
|
||||
"github.com/mediocregopher/ginger/gim/view/constraint"
|
||||
)
|
||||
|
||||
// "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, map[string]int, map[string]int) {
|
||||
primEng := constraint.NewEngine()
|
||||
secEng := constraint.NewEngine()
|
||||
|
||||
strM := g.ByID()
|
||||
for _, v := range strM {
|
||||
var prevIn *gg.Vertex
|
||||
for _, e := range v.In {
|
||||
primEng.AddConstraint(constraint.Constraint{
|
||||
Elem: e.From.ID,
|
||||
LT: v.ID,
|
||||
})
|
||||
if prevIn != nil {
|
||||
secEng.AddConstraint(constraint.Constraint{
|
||||
Elem: prevIn.ID,
|
||||
LT: e.From.ID,
|
||||
})
|
||||
}
|
||||
prevIn = e.From
|
||||
}
|
||||
|
||||
var prevOut *gg.Vertex
|
||||
for _, e := range v.Out {
|
||||
if prevOut == nil {
|
||||
continue
|
||||
}
|
||||
secEng.AddConstraint(constraint.Constraint{
|
||||
Elem: prevOut.ID,
|
||||
LT: e.To.ID,
|
||||
})
|
||||
prevOut = e.To
|
||||
}
|
||||
}
|
||||
prim := primEng.Solve()
|
||||
sec := secEng.Solve()
|
||||
|
||||
// determine maximum primary level
|
||||
var maxPrim int
|
||||
for _, lvl := range prim {
|
||||
if lvl > maxPrim {
|
||||
maxPrim = lvl
|
||||
}
|
||||
}
|
||||
|
||||
outStr := make([][]string, maxPrim+1)
|
||||
for v, lvl := range prim {
|
||||
outStr[lvl] = append(outStr[lvl], v)
|
||||
}
|
||||
|
||||
// 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]
|
||||
}
|
||||
}
|
||||
return out, prim, sec
|
||||
}
|
||||
|
||||
type view struct {
|
||||
// View wraps a single Graph instance and a set of display options for it, and
|
||||
// generates renderable terminal output for it.
|
||||
type View struct {
|
||||
g *gg.Graph
|
||||
primFlowDir, secFlowDir geo.XY
|
||||
start gg.Value
|
||||
|
||||
primFlowDir, secFlowDir geo.XY
|
||||
}
|
||||
|
||||
func (view *view) draw(buf *terminal.Buffer) {
|
||||
// New instantiates and returns a view around the given Graph instance, with
|
||||
// start indicating the value vertex to consider the "root" of the graph.
|
||||
//
|
||||
// Drawing is done by aligning the vertices into rows and columns in such a way
|
||||
// as to reduce edge crossings. primaryDir indicates the direction edges will
|
||||
// primarily be pointed in. For example, if it is geo.Down then adjacent
|
||||
// vertices will be arranged into columns.
|
||||
//
|
||||
// secondaryDir indicates the direction vertices should be arranged when they
|
||||
// end up in the same "rank" (e.g. when primaryDir is geo.Down, all vertices on
|
||||
// the same row will be the same "rank").
|
||||
//
|
||||
// A primaryDir/secondaryDir of either geo.Down/geo.Right or geo.Right/geo.Down
|
||||
// are recommended, but any combination of perpendicular directions is allowed.
|
||||
func New(g *gg.Graph, start gg.Value, primaryDir, secondaryDir geo.XY) *View {
|
||||
return &View{
|
||||
g: g,
|
||||
start: start,
|
||||
primFlowDir: primaryDir,
|
||||
secFlowDir: secondaryDir,
|
||||
}
|
||||
}
|
||||
|
||||
// Draw renders and draws the View's Graph to the Buffer.
|
||||
func (view *View) Draw(buf *terminal.Buffer) {
|
||||
relPos, _, secSol := posSolve(view.g)
|
||||
|
||||
// create boxes
|
||||
@ -183,3 +134,80 @@ func (view *view) draw(buf *terminal.Buffer) {
|
||||
line.draw(buf, view.primFlowDir, view.secFlowDir)
|
||||
}
|
||||
}
|
||||
|
||||
// "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, map[string]int, map[string]int) {
|
||||
primEng := constraint.NewEngine()
|
||||
secEng := constraint.NewEngine()
|
||||
|
||||
strM := g.ByID()
|
||||
for _, v := range strM {
|
||||
var prevIn *gg.Vertex
|
||||
for _, e := range v.In {
|
||||
primEng.AddConstraint(constraint.Constraint{
|
||||
Elem: e.From.ID,
|
||||
LT: v.ID,
|
||||
})
|
||||
if prevIn != nil {
|
||||
secEng.AddConstraint(constraint.Constraint{
|
||||
Elem: prevIn.ID,
|
||||
LT: e.From.ID,
|
||||
})
|
||||
}
|
||||
prevIn = e.From
|
||||
}
|
||||
|
||||
var prevOut *gg.Vertex
|
||||
for _, e := range v.Out {
|
||||
if prevOut == nil {
|
||||
continue
|
||||
}
|
||||
secEng.AddConstraint(constraint.Constraint{
|
||||
Elem: prevOut.ID,
|
||||
LT: e.To.ID,
|
||||
})
|
||||
prevOut = e.To
|
||||
}
|
||||
}
|
||||
prim := primEng.Solve()
|
||||
sec := secEng.Solve()
|
||||
|
||||
// determine maximum primary level
|
||||
var maxPrim int
|
||||
for _, lvl := range prim {
|
||||
if lvl > maxPrim {
|
||||
maxPrim = lvl
|
||||
}
|
||||
}
|
||||
|
||||
outStr := make([][]string, maxPrim+1)
|
||||
for v, lvl := range prim {
|
||||
outStr[lvl] = append(outStr[lvl], v)
|
||||
}
|
||||
|
||||
// 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]
|
||||
}
|
||||
}
|
||||
return out, prim, sec
|
||||
}
|
Loading…
Reference in New Issue
Block a user