gim: move view code into its own package

This commit is contained in:
Brian Picciano 2018-06-08 02:01:55 +00:00
parent 0a6526d2c3
commit c277bab368
6 changed files with 125 additions and 109 deletions

View File

@ -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)

View File

@ -1,4 +1,4 @@
package main
package view
import (
"fmt"

View File

@ -1,4 +1,4 @@
package main
package view
import (
"github.com/mediocregopher/ginger/gim/geo"

View File

@ -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
}