Skip to content

Commit

Permalink
feat: add graphviz
Browse files Browse the repository at this point in the history
  • Loading branch information
moul committed Jun 30, 2019
1 parent 8e4f18f commit ad4db38
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 5 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.PHONY: test
test:
go test -v ./...
cd examples; go test -v ./...
43 changes: 38 additions & 5 deletions attr.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@ func (a *Attrs) Merge(b Attrs) {
}
}

func (a *Attrs) Del(key string) {
delete(*a, key)
}

func (a Attrs) IsEmpty() bool { return len(a) == 0 }

func (a Attrs) String() string {
if a == nil {
return "[INVALID]"
}
if len(a) == 0 {
return ""
return "[]"
}
elems := []string{}
for key, val := range a {
Expand All @@ -39,6 +46,13 @@ func (a Attrs) SetTitle(title string) Attrs {
return a
}

func (a Attrs) GetTitle() string {
if attr, found := a["title"]; found {
return attr.(string)
}
return ""
}

func (a Attrs) SetPert(opt, real, pess float64) Attrs {
a["pert"] = &PertAttrs{
Optimistic: opt,
Expand All @@ -49,9 +63,28 @@ func (a Attrs) SetPert(opt, real, pess float64) Attrs {
}

func (a Attrs) GetPert() *PertAttrs {
pa, found := a["pert"]
if !found {
return nil
if attr, found := a["pert"]; found {
return attr.(*PertAttrs)
}
return nil
}

func (a Attrs) Clone() Attrs {
newAttrs := Attrs{}
for k, v := range a {
newAttrs[k] = v
}
return newAttrs
}

func (a Attrs) SetColor(color string) Attrs {
a["color"] = color
return a
}

func (a Attrs) GetColor() string {
if attr, found := a["color"]; found {
return attr.(string)
}
return pa.(*PertAttrs)
return ""
}
3 changes: 3 additions & 0 deletions edge.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ func newEdge(src, dst *Vertex, attrs ...Attrs) *Edge {
}
}

func (e Edge) Dst() *Vertex { return e.dst }
func (e Edge) Src() *Vertex { return e.src }

func (e *Edge) Vertices() Vertices {
return Vertices{e.src, e.dst}
}
Expand Down
11 changes: 11 additions & 0 deletions examples/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module moul.io/graphman/examples

go 1.12

require moul.io/graphman v0.0.0

require moul.io/graphman/viz v0.0.0

replace moul.io/graphman => ../

replace moul.io/graphman/viz => ../viz/
2 changes: 2 additions & 0 deletions examples/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab h1:+cdNqtOJWjvepyhxy23G7z7vmpYCoC65AP0nqi1f53s=
github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs=
5 changes: 5 additions & 0 deletions examples/graphviz/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.PHONY: run
run:
set -eo pipefail; go run . | tee /tmp/graphman.dot
set -eo pipefail; cat /tmp/graphman.dot | dot -Tsvg > /tmp/graphman.svg
open /tmp/graphman.svg
61 changes: 61 additions & 0 deletions examples/graphviz/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"fmt"
"log"

"moul.io/graphman"
"moul.io/graphman/viz"
)

func main() {
graph := graphman.New()
graph.AddEdge("1", "2")
graph.AddEdge("2", "3")
graph.AddEdge("2", "4")
graph.AddEdge("3", "5")
graph.AddEdge("3", "6")
graph.AddEdge("4", "5")
graph.AddEdge("4", "9")
graph.AddEdge("5", "7")
graph.AddEdge("6", "8")
graph.AddEdge("6", "12")
graph.AddEdge("7", "8")
graph.AddEdge("7", "10")
graph.AddEdge("8", "11")
graph.AddEdge("9", "10")
graph.AddEdge("9", "15")
graph.AddEdge("10", "13")
graph.AddEdge("11", "12")
graph.AddEdge("11", "14")
graph.AddEdge("12", "17")
graph.AddEdge("13", "14")
graph.AddEdge("13", "15")
graph.AddEdge("14", "16")
graph.AddEdge("15", "18")
graph.AddEdge("16", "17")
graph.AddEdge("17", "19")
graph.AddEdge("18", "19")
graph.AddEdge("19", "20")

log.Println("all paths from 1 to 20:")
for _, path := range graph.FindAllPaths("1", "20") {
log.Println("-", path)
}
log.Println("shortest path from 1 to 20:")
path, dist := graph.FindShortestPath("1", "20")
log.Println("-", path, "dist:", dist)

for _, edge := range path {
edge.Dst().SetColor("red")
edge.SetColor("red")
}
path.FirstVertex().SetColor("blue")
path.LastVertex().SetColor("blue")

s, err := viz.ToGraphviz(graph)
if err != nil {
panic(err)
}
fmt.Println(s)
}
4 changes: 4 additions & 0 deletions graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ func (g *Graph) AddVertex(id string, attrs ...Attrs) *Vertex {
var a Attrs
if len(attrs) > 0 {
a = attrs[0]
} else {
a = make(Attrs)
}

v := g.GetVertex(id)
Expand Down Expand Up @@ -223,6 +225,8 @@ func (g *Graph) AddEdge(srcID, dstID string, attrs ...Attrs) *Edge {
var a Attrs
if len(attrs) > 0 {
a = attrs[0]
} else {
a = make(Attrs)
}

src := g.AddVertex(srcID)
Expand Down
5 changes: 5 additions & 0 deletions viz/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module moul.io/graphman/viz

go 1.12

require github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab
2 changes: 2 additions & 0 deletions viz/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab h1:+cdNqtOJWjvepyhxy23G7z7vmpYCoC65AP0nqi1f53s=
github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs=
93 changes: 93 additions & 0 deletions viz/graphviz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package viz // import "moul.io/graphman/viz"

import (
"fmt"

graphviz "github.com/awalterschulze/gographviz"
"moul.io/graphman"
)

func ToGraphviz(g *graphman.Graph) (string, error) {
gv := graphviz.NewGraph()
if err := gv.SetName("G"); err != nil {
return "", err
}
if err := gv.SetDir(true); err != nil {
return "", err
}
for k, v := range attrsFromGraph(g) {
if err := gv.AddAttr("G", k, v); err != nil {
return "", err
}
}
for _, vertex := range g.Vertices() {
if err := gv.AddNode(
"G",
vertex.ID(),
attrsFromVertex(vertex),
); err != nil {
return "", err
}
}
for _, edge := range g.Edges() {
if err := gv.AddEdge(
edge.Src().ID(),
edge.Dst().ID(),
true,
attrsFromEdge(edge),
); err != nil {
return "", err
}
}
return gv.String(), nil
}

func attrsFromGraph(graph *graphman.Graph) map[string]string {
attrs := map[string]string{}
attrs[string(graphviz.RankDir)] = "LR"
attrsGeneric(graph.Attrs, attrs)
return attrs
}

func attrsFromVertex(vertex *graphman.Vertex) map[string]string {
attrs := map[string]string{}
attrs[string(graphviz.Shape)] = "box"
attrs[string(graphviz.Style)] = "rounded"
attrs[string(graphviz.Label)] = vertex.ID()
attrsGeneric(vertex.Attrs, attrs)
return attrs
}

func attrsFromEdge(edge *graphman.Edge) map[string]string {
attrs := map[string]string{}
attrs[string(graphviz.Label)] = ""
attrsGeneric(edge.Attrs, attrs)
return attrs
}

func attrsGeneric(a graphman.Attrs, attrs map[string]string) {
ac := a.Clone()
if color := a.GetColor(); color != "" {
attrs[string(graphviz.Color)] = color
ac.Del("color")
}
if title := a.GetTitle(); title != "" {
attrs[string(graphviz.Label)] = title
ac.Del("title")
}
if len(ac) > 0 {
for k, v := range ac {
line := fmt.Sprintf("\n%s: %v", k, v)
attrs[string(graphviz.Label)] += line
}
}
if label := attrs[string(graphviz.Label)]; label != "" {
attrs[string(graphviz.Label)] = escape(label)
} else {
delete(attrs, string(graphviz.Label))
}
}

func escape(input string) string {
return fmt.Sprintf("%q", input)
}

0 comments on commit ad4db38

Please sign in to comment.