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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/gg"
|
"github.com/mediocregopher/ginger/gg"
|
||||||
"github.com/mediocregopher/ginger/gim/geo"
|
"github.com/mediocregopher/ginger/gim/geo"
|
||||||
"github.com/mediocregopher/ginger/gim/terminal"
|
"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 be able to draw circular graphs
|
||||||
// TODO audit all steps, make sure everything is deterministic
|
// TODO audit all steps, make sure everything is deterministic
|
||||||
// TODO self-edges
|
// TODO self-edges
|
||||||
|
|
||||||
const (
|
//const (
|
||||||
framerate = 10
|
// framerate = 10
|
||||||
frameperiod = time.Second / time.Duration(framerate)
|
// frameperiod = time.Second / time.Duration(framerate)
|
||||||
)
|
//)
|
||||||
|
|
||||||
func debugf(str string, args ...interface{}) {
|
//func debugf(str string, args ...interface{}) {
|
||||||
if !strings.HasSuffix(str, "\n") {
|
// if !strings.HasSuffix(str, "\n") {
|
||||||
str += "\n"
|
// str += "\n"
|
||||||
}
|
// }
|
||||||
fmt.Fprintf(os.Stderr, str, args...)
|
// fmt.Fprintf(os.Stderr, str, args...)
|
||||||
}
|
//}
|
||||||
|
|
||||||
func mkGraph() (*gg.Graph, gg.Value) {
|
func mkGraph() (*gg.Graph, gg.Value) {
|
||||||
a := gg.NewValue("a")
|
a := gg.NewValue("a")
|
||||||
@ -83,15 +77,9 @@ func main() {
|
|||||||
center := geo.Zero.Midpoint(wSize)
|
center := geo.Zero.Midpoint(wSize)
|
||||||
|
|
||||||
g, start := mkGraph()
|
g, start := mkGraph()
|
||||||
v := view{
|
view := view.New(g, start, geo.Right, geo.Down)
|
||||||
g: g,
|
|
||||||
primFlowDir: geo.Right,
|
|
||||||
secFlowDir: geo.Down,
|
|
||||||
start: start,
|
|
||||||
}
|
|
||||||
|
|
||||||
viewBuf := terminal.NewBuffer()
|
viewBuf := terminal.NewBuffer()
|
||||||
v.draw(viewBuf)
|
view.Draw(viewBuf)
|
||||||
|
|
||||||
buf := terminal.NewBuffer()
|
buf := terminal.NewBuffer()
|
||||||
buf.DrawBufferCentered(center, viewBuf)
|
buf.DrawBufferCentered(center, viewBuf)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mediocregopher/ginger/gim/geo"
|
"github.com/mediocregopher/ginger/gim/geo"
|
@ -1,98 +1,49 @@
|
|||||||
package main
|
// Package view implements rendering a graph to a terminal.
|
||||||
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/gg"
|
"github.com/mediocregopher/ginger/gg"
|
||||||
"github.com/mediocregopher/ginger/gim/constraint"
|
|
||||||
"github.com/mediocregopher/ginger/gim/geo"
|
"github.com/mediocregopher/ginger/gim/geo"
|
||||||
"github.com/mediocregopher/ginger/gim/terminal"
|
"github.com/mediocregopher/ginger/gim/terminal"
|
||||||
|
"github.com/mediocregopher/ginger/gim/view/constraint"
|
||||||
)
|
)
|
||||||
|
|
||||||
// "Solves" vertex position by detemining relative positions of vertices in
|
// View wraps a single Graph instance and a set of display options for it, and
|
||||||
// primary and secondary directions (independently), with relative positions
|
// generates renderable terminal output for it.
|
||||||
// being described by "levels", where multiple vertices can occupy one level.
|
type View struct {
|
||||||
//
|
g *gg.Graph
|
||||||
// Primary determines relative position in the primary direction by trying
|
start gg.Value
|
||||||
// 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 {
|
|
||||||
g *gg.Graph
|
|
||||||
primFlowDir, secFlowDir geo.XY
|
primFlowDir, secFlowDir geo.XY
|
||||||
start gg.Value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
relPos, _, secSol := posSolve(view.g)
|
||||||
|
|
||||||
// create boxes
|
// create boxes
|
||||||
@ -183,3 +134,80 @@ func (view *view) draw(buf *terminal.Buffer) {
|
|||||||
line.draw(buf, view.primFlowDir, view.secFlowDir)
|
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