├── examples
├── minimal.png
├── minimal.go
├── basic.go
├── complex.go
├── minimal.svg
├── basic.svg
└── complex.svg
├── doc.go
├── go.mod
├── go.sum
├── .gitignore
├── cmd
├── glay
│ ├── index.html
│ ├── world.gv
│ └── main.go
└── dot2graphml
│ └── main.go
├── internal
├── hier
│ ├── graph_cyclic_test.go
│ ├── graph_cyclic.go
│ ├── math.go
│ ├── order_crossings.go
│ ├── example_test.go
│ ├── virtual.go
│ ├── graph_edges.go
│ ├── graph_generate_test.go
│ ├── graph_generate.go
│ ├── graph_debug.go
│ ├── decycle_test.go
│ ├── graph_test.go
│ ├── graph.go
│ ├── graph_nodes.go
│ ├── RESEARCH.md
│ ├── order.go
│ ├── decycle.go
│ ├── rank.go
│ ├── graph_nodes_sort.go
│ └── position.go
└── cmd
│ ├── propagate
│ └── main.go
│ └── hier
│ └── main.go
├── geom.go
├── edge.go
├── format
├── tgf
│ └── write.go
├── dot
│ ├── write.go
│ └── parse.go
├── graphml
│ ├── write.go
│ ├── graphml.dtd
│ └── xml.go
└── svg
│ └── write.go
├── LICENSE
├── README.md
├── color.go
├── node.go
├── graph.go
├── layout.go
└── color_x11.go
/examples/minimal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loov/layout/HEAD/examples/minimal.png
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package layout implements different graph layouting functions
3 | */
4 | package layout
5 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/loov/layout
2 |
3 | go 1.22
4 |
5 | toolchain go1.23.4
6 |
7 | require (
8 | golang.org/x/net v0.34.0
9 | gonum.org/v1/gonum v0.15.1
10 | )
11 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
2 | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
3 | gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
4 | gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | *~*
7 | *.huge.*
8 |
9 | # Folders
10 | _obj
11 | _test
12 |
13 | # Architecture specific extensions/prefixes
14 | *.[568vq]
15 | [568vq].out
16 |
17 | *.cgo1.go
18 | *.cgo2.c
19 | _cgo_defun.c
20 | _cgo_gotypes.go
21 | _cgo_export.*
22 |
23 | _testmain.go
24 |
25 | *.exe
26 | *.test
27 | *.prof
28 |
29 |
--------------------------------------------------------------------------------
/cmd/glay/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/minimal.go:
--------------------------------------------------------------------------------
1 | // +build ignore
2 |
3 | package main
4 |
5 | import (
6 | "os"
7 |
8 | "github.com/loov/layout"
9 | "github.com/loov/layout/format/svg"
10 | )
11 |
12 | func main() {
13 | graph := layout.NewDigraph()
14 | graph.Edge("A", "B")
15 | graph.Edge("A", "C")
16 | graph.Edge("B", "D")
17 | graph.Edge("C", "D")
18 |
19 | layout.Hierarchical(graph)
20 |
21 | svg.Write(os.Stdout, graph)
22 | }
23 |
--------------------------------------------------------------------------------
/internal/hier/graph_cyclic_test.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | import "testing"
4 |
5 | func TestIsCyclic(t *testing.T) {
6 | for _, testgraph := range TestGraphs {
7 | graph := testgraph.Make()
8 | if graph.IsCyclic() != testgraph.Cyclic {
9 | t.Errorf("%v: expected IsCyclic = %v", testgraph.Name, testgraph.Cyclic)
10 | }
11 | if err := graph.CheckErrors(); err != nil {
12 | t.Errorf("%v: %v", testgraph.Name, err)
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/basic.go:
--------------------------------------------------------------------------------
1 | // +build ignore
2 |
3 | package main
4 |
5 | import (
6 | "os"
7 |
8 | "github.com/loov/layout"
9 | "github.com/loov/layout/format/svg"
10 | )
11 |
12 | func main() {
13 | graph := layout.NewDigraph()
14 | graph.Node("A")
15 | graph.Node("B")
16 | graph.Node("C")
17 | graph.Node("D")
18 | graph.Edge("A", "B")
19 | graph.Edge("A", "C")
20 | graph.Edge("B", "D")
21 | graph.Edge("C", "D")
22 | graph.Edge("D", "A")
23 |
24 | layout.Hierarchical(graph)
25 |
26 | svg.Write(os.Stdout, graph)
27 | }
28 |
--------------------------------------------------------------------------------
/geom.go:
--------------------------------------------------------------------------------
1 | package layout
2 |
3 | type Shape string
4 |
5 | const (
6 | Auto Shape = ""
7 | None = "none"
8 | Box = "box"
9 | Square = "square"
10 | Circle = "circle"
11 | Ellipse = "ellipse"
12 | )
13 |
14 | type Vector struct{ X, Y Length }
15 |
16 | // Length is a value represented in points
17 | type Length float64
18 |
19 | const (
20 | Point = 1
21 | Inch = 72
22 | Twip = Inch / 1440
23 |
24 | Meter = 39.3701 * Inch
25 | Centimeter = Meter * 0.01
26 | Millimeter = Meter * 0.001
27 | )
28 |
--------------------------------------------------------------------------------
/edge.go:
--------------------------------------------------------------------------------
1 | package layout
2 |
3 | type Edge struct {
4 | Directed bool
5 | From, To *Node
6 | Weight float64
7 |
8 | Tooltip string
9 |
10 | Label string
11 | FontName string
12 | FontSize Length
13 | FontColor Color
14 |
15 | LineWidth Length
16 | LineColor Color
17 |
18 | // computed in layouting
19 | Path []Vector
20 | }
21 |
22 | func NewEdge(from, to *Node) *Edge {
23 | edge := &Edge{}
24 | edge.From = from
25 | edge.To = to
26 | edge.Weight = 1.0
27 | edge.LineWidth = Point
28 | return edge
29 | }
30 |
31 | func (edge *Edge) String() string {
32 | return edge.From.String() + "->" + edge.To.String()
33 | }
34 |
--------------------------------------------------------------------------------
/format/tgf/write.go:
--------------------------------------------------------------------------------
1 | package tgf
2 |
3 | import (
4 | "fmt"
5 | "io"
6 |
7 | "github.com/loov/layout/internal/hier"
8 | )
9 |
10 | func writeLayout(out io.Writer, graph *hier.Graph) error {
11 | var err error
12 | write := func(format string, args ...interface{}) bool {
13 | if err != nil {
14 | return false
15 | }
16 |
17 | _, err = fmt.Fprintf(out, format, args...)
18 | return err == nil
19 | }
20 |
21 | for _, src := range graph.Nodes {
22 | if !src.Virtual {
23 | write("%v %v\n", src.ID, src.ID)
24 | } else {
25 | write("%v\n", src.ID)
26 | }
27 | }
28 |
29 | write("#\n")
30 |
31 | for _, src := range graph.Nodes {
32 | for _, dst := range src.Out {
33 | write("%v %v\n", src.ID, dst.ID)
34 | }
35 | }
36 |
37 | return err
38 | }
39 |
--------------------------------------------------------------------------------
/internal/hier/graph_cyclic.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | // IsCyclic checks whether graph is cyclic
4 | func (graph *Graph) IsCyclic() bool {
5 | visited := NewNodeSet(graph.NodeCount())
6 | recursing := NewNodeSet(graph.NodeCount())
7 |
8 | var isCyclic func(node *Node) bool
9 | isCyclic = func(node *Node) bool {
10 | if !visited.Include(node) {
11 | return false
12 | }
13 |
14 | recursing.Add(node)
15 | for _, child := range node.Out {
16 | if isCyclic(child) {
17 | return true
18 | } else if recursing.Contains(child) {
19 | return true
20 | }
21 | }
22 | recursing.Remove(node)
23 |
24 | return false
25 | }
26 |
27 | for _, node := range graph.Nodes {
28 | if isCyclic(node) {
29 | return true
30 | }
31 | }
32 |
33 | return false
34 | }
35 |
--------------------------------------------------------------------------------
/format/dot/write.go:
--------------------------------------------------------------------------------
1 | package dot
2 |
3 | import (
4 | "fmt"
5 | "io"
6 |
7 | "github.com/loov/layout/internal/hier"
8 | )
9 |
10 | func writeLayout(out io.Writer, graph *hier.Graph) error {
11 | var err error
12 |
13 | write := func(format string, args ...interface{}) bool {
14 | if err != nil {
15 | return false
16 | }
17 | _, err = fmt.Fprintf(out, format, args...)
18 | return err == nil
19 | }
20 |
21 | write("digraph G {\n")
22 | for _, src := range graph.Nodes {
23 | if !src.Virtual {
24 | write("\t%v[rank = %v];\n", src.ID, src.Rank)
25 | } else {
26 | write("\t%v[rank = %v; shape=circle];\n", src.ID, src.Rank)
27 | }
28 | for _, dst := range src.Out {
29 | write("\t%v -> %v;\n", src.ID, dst.ID)
30 | }
31 | }
32 | write("}")
33 | return err
34 | }
35 |
--------------------------------------------------------------------------------
/internal/hier/math.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | func abs(v int) int {
4 | if v < 0 {
5 | return -v
6 | }
7 | return v
8 | }
9 |
10 | func min(a, b int) int {
11 | if a < b {
12 | return a
13 | }
14 | return b
15 | }
16 |
17 | func max(a, b int) int {
18 | if a > b {
19 | return a
20 | }
21 | return b
22 | }
23 |
24 | func absf32(v float32) float32 {
25 | if v < 0 {
26 | return -v
27 | }
28 | return v
29 | }
30 |
31 | func minf32(a, b float32) float32 {
32 | if a < b {
33 | return a
34 | }
35 | return b
36 | }
37 |
38 | func maxf32(a, b float32) float32 {
39 | if a > b {
40 | return a
41 | }
42 | return b
43 | }
44 |
45 | func clampf32(v, min, max float32) float32 {
46 | if v < min {
47 | return min
48 | } else if v > max {
49 | return max
50 | }
51 | return v
52 | }
53 |
--------------------------------------------------------------------------------
/internal/hier/order_crossings.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | func (graph *Graph) CrossingsUp(u, v *Node) int {
4 | if u.Rank == 0 {
5 | return 0
6 | }
7 |
8 | count := 0
9 | prev := graph.ByRank[u.Rank-1]
10 | for _, w := range u.In {
11 | for _, z := range v.In {
12 | if prev.IndexOf(z) < prev.IndexOf(w) {
13 | count++
14 | }
15 | }
16 | }
17 | return count
18 | }
19 |
20 | func (graph *Graph) CrossingsDown(u, v *Node) int {
21 | if u.Rank == len(graph.ByRank)-1 {
22 | return 0
23 | }
24 |
25 | count := 0
26 | next := graph.ByRank[u.Rank+1]
27 | for _, w := range u.In {
28 | for _, z := range v.In {
29 | if next.IndexOf(z) < next.IndexOf(w) {
30 | count++
31 | }
32 | }
33 | }
34 | return count
35 | }
36 |
37 | func (graph *Graph) Crossings(u, v *Node) int {
38 | return graph.CrossingsDown(u, v) + graph.CrossingsUp(u, v)
39 | }
40 |
--------------------------------------------------------------------------------
/internal/hier/example_test.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | import "fmt"
4 |
5 | func Example() {
6 | // create a new graph
7 | graph := NewGraph()
8 | a, b, c, d := graph.AddNode(), graph.AddNode(), graph.AddNode(), graph.AddNode()
9 |
10 | graph.AddEdge(a, b)
11 | graph.AddEdge(a, c)
12 | graph.AddEdge(b, d)
13 | graph.AddEdge(c, d)
14 | graph.AddEdge(d, a)
15 |
16 | // remove cycles from the graph
17 | decycledGraph := DefaultDecycle(graph)
18 |
19 | // assign nodes to ranks
20 | rankedGraph := DefaultRank(decycledGraph)
21 |
22 | // create virtual nodes
23 | filledGraph := DefaultAddVirtuals(rankedGraph)
24 |
25 | // order nodes in ranks
26 | orderedGraph := DefaultOrderRanks(filledGraph)
27 |
28 | for _, node := range orderedGraph.Nodes {
29 | node.Radius.X = 10
30 | node.Radius.Y = 10
31 | }
32 |
33 | // position nodes
34 | positionedGraph := DefaultPosition(orderedGraph)
35 |
36 | for _, node := range positionedGraph.Nodes {
37 | fmt.Println(node.ID, node.Center.X, node.Center.Y)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/internal/hier/virtual.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | // DefaultAddVirtuals adds basic virtual nodes
4 | func DefaultAddVirtuals(graph *Graph) *Graph {
5 | AddVirtuals(graph)
6 | return graph
7 | }
8 |
9 | // AddVirtuals creates nodes for edges spanning multiple ranks
10 | //
11 | // Rank input output
12 | // 0 A A
13 | // /| / \
14 | // 1 B | => B V
15 | // \| \ /
16 | // 2 C C
17 | func AddVirtuals(graph *Graph) {
18 | if len(graph.ByRank) == 0 {
19 | return
20 | }
21 |
22 | for _, src := range graph.Nodes {
23 | for di, dst := range src.Out {
24 | if dst.Rank-src.Rank <= 1 {
25 | continue
26 | }
27 |
28 | src.Out[di] = nil
29 | dst.In.Remove(src)
30 |
31 | for rank := dst.Rank - 1; rank > src.Rank; rank-- {
32 | node := graph.AddNode()
33 | node.Rank = rank
34 | node.Virtual = true
35 | graph.ByRank[node.Rank].Append(node)
36 | graph.AddEdge(node, dst)
37 | dst = node
38 | }
39 |
40 | src.Out[di] = dst
41 | dst.In.Append(src)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/internal/hier/graph_edges.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | // NewGraphFromEdgeList creates a graph from edge list
4 | //
5 | // Example:
6 | // graph := NewGraphFromEdgeList([][]int{
7 | // 0: []int{1,2},
8 | // 1: []int{2,0},
9 | // })
10 | //
11 | // Creates an graph with edges 0 -> 1, 0 -> 2, 1 -> 2, 1 -> 0.
12 | //
13 | func NewGraphFromEdgeList(edgeList [][]int) *Graph {
14 | graph := NewGraph()
15 |
16 | for from, out := range edgeList {
17 | for _, to := range out {
18 | graph.ensureNode(from)
19 | graph.ensureNode(to)
20 | graph.AddEdge(graph.Nodes[from], graph.Nodes[to])
21 | }
22 | }
23 |
24 | return graph
25 | }
26 |
27 | // ConvertToEdgeList creates edge list
28 | // NewGraphFromEdgeList(edgeList).ConvertToEdgeList() == edgeList
29 | func (graph *Graph) ConvertToEdgeList() [][]int {
30 | edges := make([][]int, 0, graph.NodeCount())
31 | for _, node := range graph.Nodes {
32 | list := make([]int, 0, len(node.Out))
33 | for _, out := range node.Out {
34 | list = append(list, int(out.ID))
35 | }
36 | edges = append(edges, list)
37 | }
38 | return edges
39 | }
40 |
--------------------------------------------------------------------------------
/examples/complex.go:
--------------------------------------------------------------------------------
1 | // +build ignore
2 |
3 | package main
4 |
5 | import (
6 | "os"
7 |
8 | "github.com/loov/layout"
9 | "github.com/loov/layout/format/svg"
10 | )
11 |
12 | func main() {
13 | graph := layout.NewDigraph()
14 | graph.RowPadding = 30 * layout.Point
15 |
16 | a := graph.Node("A")
17 | a.Shape = layout.Box
18 | a.Label = "Lorem\nIpsum\nDolorem"
19 | a.FillColor = layout.RGB{0xFF, 0xA0, 0x20}
20 |
21 | b := graph.Node("B")
22 | b.Shape = layout.Ellipse
23 | b.Label = "Ignitus"
24 | b.FillColor = layout.HSL{0, 0.7, 0.7}
25 |
26 | c := graph.Node("C")
27 | c.Shape = layout.Square
28 | c.FontSize = 12 * layout.Point
29 | c.FontColor = layout.RGB{0x20, 0x20, 0x20}
30 |
31 | graph.Node("D")
32 |
33 | ab := graph.Edge("A", "B")
34 | ab.LineWidth = 4 * layout.Point
35 |
36 | ac := graph.Edge("A", "C")
37 | ac.LineWidth = 4 * layout.Point
38 | if col, ok := layout.ColorByName("blue"); ok {
39 | ac.LineColor = col
40 | }
41 |
42 | bd := graph.Edge("B", "D")
43 | bd.LineColor = layout.RGB{0xA0, 0xFF, 0xA0}
44 |
45 | graph.Edge("C", "D")
46 | graph.Edge("D", "A")
47 |
48 | layout.Hierarchical(graph)
49 |
50 | svg.Write(os.Stdout, graph)
51 | }
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Egon Elbre
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/internal/hier/graph_generate_test.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | import "testing"
4 |
5 | var BenchmarkGraphSizes = []struct {
6 | Name string
7 | Nodes, Connections int
8 | }{
9 | {"1x1", 1, 1},
10 | {"2x1", 2, 1},
11 | {"4x2", 4, 2},
12 | {"16x4", 16, 4},
13 | {"256x4", 256, 4},
14 | {"256x16", 256, 16},
15 | }
16 |
17 | func TestGenerateRegularGraph(t *testing.T) {
18 | for _, size := range BenchmarkGraphSizes {
19 | t.Run(size.Name, func(t *testing.T) {
20 | graph := GenerateRegularGraph(size.Nodes, size.Connections)
21 | cyclic := size.Connections > 0
22 | if graph.IsCyclic() != cyclic {
23 | t.Errorf("expected %v", cyclic)
24 | }
25 | })
26 | }
27 | }
28 |
29 | func BenchmarkGenerateRegularGraph(b *testing.B) {
30 | for _, size := range BenchmarkGraphSizes {
31 | b.Run(size.Name, func(b *testing.B) {
32 | for i := 0; i < b.N; i++ {
33 | _ = GenerateRegularGraph(size.Nodes, size.Connections)
34 | }
35 | })
36 | }
37 | }
38 |
39 | func BenchmarkNewGraphFromEdgeList(b *testing.B) {
40 | for _, size := range BenchmarkGraphSizes {
41 | graph := GenerateRegularGraph(size.Nodes, size.Connections)
42 | edgeList := graph.ConvertToEdgeList()
43 | b.Run(size.Name, func(b *testing.B) {
44 | for i := 0; i < b.N; i++ {
45 | _ = NewGraphFromEdgeList(edgeList)
46 | }
47 | })
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # layout [](https://godoc.org/github.com/loov/layout) [](https://goreportcard.com/report/github.com/loov/layout)
2 |
3 | ## Experimental
4 |
5 | Current version and API is in experimental stage. Property names may change.
6 |
7 | ## Installation
8 |
9 | The graph layouting can be used as a command-line tool and as a library.
10 |
11 | To install the command-line tool:
12 | ```
13 | go get -u github.com/loov/layout/cmd/glay
14 | ```
15 |
16 | To install the package:
17 | ```
18 | go get -u github.com/loov/layout
19 | ```
20 |
21 | ## Usage
22 |
23 | Minimal usage:
24 |
25 | ```
26 | package main
27 |
28 | import (
29 | "os"
30 |
31 | "github.com/loov/layout"
32 | "github.com/loov/layout/format/svg"
33 | )
34 |
35 | func main() {
36 | graph := layout.NewDigraph()
37 | graph.Edge("A", "B")
38 | graph.Edge("A", "C")
39 | graph.Edge("B", "D")
40 | graph.Edge("C", "D")
41 |
42 | layout.Hierarchical(graph)
43 |
44 | svg.Write(os.Stdout, graph)
45 | }
46 | ```
47 |
48 | 
49 |
50 | See other examples in `examples` folder.
51 |
52 | ## Quality
53 |
54 | Currently the `layout.Hierarchy` algorithm output is significantly worse than graphviz. It is recommended to use `graphviz dot`, if possible.
--------------------------------------------------------------------------------
/cmd/dot2graphml/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "io"
7 | "os"
8 |
9 | "github.com/loov/layout/format/dot"
10 | "github.com/loov/layout/format/graphml"
11 | )
12 |
13 | var (
14 | eraseLabels = flag.Bool("erase-labels", false, "erase custom labels")
15 | setShape = flag.String("set-shape", "", "override default shape")
16 | )
17 |
18 | func main() {
19 | flag.Parse()
20 | args := flag.Args()
21 |
22 | var in io.Reader = os.Stdin
23 | var out io.Writer = os.Stdout
24 |
25 | if len(args) >= 1 {
26 | filename := args[0]
27 | file, err := os.Open(filename)
28 | if err != nil {
29 | fmt.Fprintf(os.Stderr, "failed to open %v", filename)
30 | os.Exit(1)
31 | return
32 | }
33 | in = file
34 | defer file.Close()
35 | }
36 |
37 | if len(args) >= 2 {
38 | filename := args[1]
39 | file, err := os.Create(filename)
40 | if err != nil {
41 | fmt.Fprintf(os.Stderr, "failed to create %v", filename)
42 | os.Exit(1)
43 | return
44 | }
45 | out = file
46 | defer file.Close()
47 | }
48 |
49 | graphs, err := dot.Parse(in)
50 | if err != nil {
51 | fmt.Fprintln(os.Stderr, err.Error())
52 | fmt.Fprintln(os.Stderr, "failed to parse input")
53 | os.Exit(1)
54 | return
55 | }
56 |
57 | if *eraseLabels {
58 | for _, graph := range graphs {
59 | for _, node := range graph.Nodes {
60 | node.Label = ""
61 | }
62 | }
63 | }
64 |
65 | graphml.Write(out, graphs...)
66 | }
67 |
--------------------------------------------------------------------------------
/cmd/glay/world.gv:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | World Dynamics
4 | http://www.graphviz.org/Gallery/directed/world.html
5 | */
6 | digraph world {
7 | size="7,7";
8 |
9 | {rank=same; S8 S24 S1 S35 S30;}
10 | {rank=same; T8 T24 T1 T35 T30;}
11 | {rank=same; 43 37 36 10 2;}
12 | {rank=same; 25 9 38 40 13 17 12 18;}
13 | {rank=same; 26 42 11 3 33 19 39 14 16;}
14 | {rank=same; 4 31 34 21 41 28 20;}
15 | {rank=same; 27 5 22 32 29 15;}
16 | {rank=same; 6 23;}
17 | {rank=same; 7;}
18 |
19 | S8 -> 9;
20 | S24 -> 25;
21 | S24 -> 27;
22 | S1 -> 2;
23 | S1 -> 10;
24 | S35 -> 43;
25 | S35 -> 36;
26 | S30 -> 31;
27 | S30 -> 33;
28 | 9 -> 42;
29 | 9 -> T1;
30 | 25 -> T1;
31 | 25 -> 26;
32 | 27 -> T24;
33 | 2 -> {3 ; 16 ; 17 ; T1 ; 18}
34 | 10 -> { 11 ; 14 ; T1 ; 13; 12;}
35 | 31 -> T1;
36 | 31 -> 32;
37 | 33 -> T30;
38 | 33 -> 34;
39 | 42 -> 4;
40 | 26 -> 4;
41 | 3 -> 4;
42 | 16 -> 15;
43 | 17 -> 19;
44 | 18 -> 29;
45 | 11 -> 4;
46 | 14 -> 15;
47 | 37 -> {39 ; 41 ; 38 ; 40;}
48 | 13 -> 19;
49 | 12 -> 29;
50 | 43 -> 38;
51 | 43 -> 40;
52 | 36 -> 19;
53 | 32 -> 23;
54 | 34 -> 29;
55 | 39 -> 15;
56 | 41 -> 29;
57 | 38 -> 4;
58 | 40 -> 19;
59 | 4 -> 5;
60 | 19 -> {21 ; 20 ; 28;}
61 | 5 -> {6 ; T35 ; 23;}
62 | 21 -> 22;
63 | 20 -> 15;
64 | 28 -> 29;
65 | 6 -> 7;
66 | 15 -> T1;
67 | 22 -> T35;
68 | 22 -> 23;
69 | 29 -> T30;
70 | 7 -> T8;
71 | 23 -> T24;
72 | 23 -> T1;
73 | }
--------------------------------------------------------------------------------
/internal/hier/graph_generate.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | import (
4 | "math/rand"
5 | "reflect"
6 | )
7 |
8 | // GenerateRandomGraph creates a graph with n nodes and a P(src, dst) == p
9 | func GenerateRandomGraph(n int, p float64, rand *rand.Rand) *Graph {
10 | graph := NewGraph()
11 | for i := 0; i < n; i++ {
12 | graph.AddNode()
13 | }
14 |
15 | for _, src := range graph.Nodes {
16 | for _, dst := range graph.Nodes {
17 | if rand.Float64() < p {
18 | graph.AddEdge(src, dst)
19 | }
20 | }
21 | }
22 |
23 | return graph
24 | }
25 |
26 | // GenerateRegularGraph creates a circular graph
27 | func GenerateRegularGraph(n, connections int) *Graph {
28 | graph := NewGraph()
29 | for i := 0; i < n; i++ {
30 | graph.AddNode()
31 | }
32 |
33 | for i, node := range graph.Nodes {
34 | for k := i + 1; k <= i+connections; k++ {
35 | t := k
36 | for t >= n {
37 | t -= n
38 | }
39 | graph.AddEdge(node, graph.Nodes[t])
40 | }
41 | }
42 |
43 | return graph
44 | }
45 |
46 | // Generate implements quick.Generator interface
47 | func (_ *Graph) Generate(rand *rand.Rand, size int) reflect.Value {
48 | switch rand.Intn(4) {
49 | case 0:
50 | return reflect.ValueOf(GenerateRandomGraph(size, 0.1, rand))
51 | case 1:
52 | return reflect.ValueOf(GenerateRandomGraph(size, 0.3, rand))
53 | case 2:
54 | return reflect.ValueOf(GenerateRandomGraph(size, 0.7, rand))
55 | case 3:
56 | return reflect.ValueOf(GenerateRegularGraph(size, rand.Intn(size)))
57 | }
58 |
59 | return reflect.ValueOf(GenerateRandomGraph(size, 0.5, rand))
60 | }
61 |
--------------------------------------------------------------------------------
/examples/minimal.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/examples/basic.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/color.go:
--------------------------------------------------------------------------------
1 | package layout
2 |
3 | import "math"
4 |
5 | type Color interface {
6 | // RGBA returns the non-alpha-premultiplied red, green, blue and alpha values
7 | // for the color. Each value ranges within [0, 0xff].
8 | RGBA8() (r, g, b, a uint8)
9 | }
10 |
11 | // RGB represents an 24bit color
12 | type RGB struct{ R, G, B uint8 }
13 |
14 | func (rgb RGB) RGBA8() (r, g, b, a uint8) { return rgb.R, rgb.G, rgb.B, 0xFF }
15 |
16 | // RGBA represents an 24bit color
17 | type RGBA struct{ R, G, B, A uint8 }
18 |
19 | func (rgb RGBA) RGBA8() (r, g, b, a uint8) { return rgb.R, rgb.G, rgb.B, rgb.A }
20 |
21 | // HSL represents an color in hue, saturation and lightness space
22 | type HSL struct{ H, S, L float32 }
23 |
24 | func (hsl HSL) RGBA8() (r, g, b, a uint8) {
25 | return HSLA{hsl.H, hsl.S, hsl.L, 1.0}.RGBA8()
26 | }
27 |
28 | // HSLA represents an color in hue, saturation and lightness space
29 | type HSLA struct{ H, S, L, A float32 }
30 |
31 | func (hsl HSLA) RGBA8() (r, g, b, a uint8) {
32 | rf, gf, bf, af := hsla(hsl.H, hsl.S, hsl.L, hsl.A)
33 | return sat8(rf), sat8(gf), sat8(bf), sat8(af)
34 | }
35 |
36 | func hue(v1, v2, h float32) float32 {
37 | if h < 0 {
38 | h += 1
39 | }
40 | if h > 1 {
41 | h -= 1
42 | }
43 | if 6*h < 1 {
44 | return v1 + (v2-v1)*6*h
45 | } else if 2*h < 1 {
46 | return v2
47 | } else if 3*h < 2 {
48 | return v1 + (v2-v1)*(2.0/3.0-h)*6
49 | }
50 |
51 | return v1
52 | }
53 |
54 | func hsla(h, s, l, a float32) (r, g, b, ra float32) {
55 | if s == 0 {
56 | return l, l, l, a
57 | }
58 |
59 | h = float32(math.Mod(float64(h), 1))
60 |
61 | var v2 float32
62 | if l < 0.5 {
63 | v2 = l * (1 + s)
64 | } else {
65 | v2 = (l + s) - s*l
66 | }
67 |
68 | v1 := 2*l - v2
69 | r = hue(v1, v2, h+1.0/3.0)
70 | g = hue(v1, v2, h)
71 | b = hue(v1, v2, h-1.0/3.0)
72 | ra = a
73 |
74 | return
75 | }
76 |
77 | // sat8 converts 0..1 float to 0..0xFF uint16
78 | func sat8(v float32) uint8 {
79 | v = v * 0xFF
80 | if v >= 0xFF {
81 | return 0xFF
82 | } else if v <= 0 {
83 | return 0
84 | }
85 | return uint8(v)
86 | }
87 |
--------------------------------------------------------------------------------
/node.go:
--------------------------------------------------------------------------------
1 | package layout
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | type Node struct {
8 | ID string
9 |
10 | Label string
11 | Weight float64
12 |
13 | Tooltip string
14 | FontName string
15 | FontSize Length
16 | FontColor Color
17 |
18 | LineWidth Length
19 | LineColor Color
20 |
21 | Shape Shape
22 | FillColor Color
23 | Radius Vector
24 |
25 | // computed in layouting
26 | Center Vector
27 | }
28 |
29 | func NewNode(id string) *Node {
30 | node := &Node{}
31 | node.ID = id
32 | node.Weight = 1.0
33 | node.LineWidth = Point
34 | return node
35 | }
36 |
37 | func (node *Node) String() string {
38 | if node == nil {
39 | return "?"
40 | }
41 | if node.ID != "" {
42 | return node.ID
43 | }
44 | return node.Label
45 | }
46 |
47 | func (node *Node) DefaultLabel() string {
48 | if node.Label != "" {
49 | return node.Label
50 | }
51 | return node.ID
52 | }
53 |
54 | func (node *Node) approxLabelRadius(lineHeight Length) Vector {
55 | const HeightWidthRatio = 0.5
56 | if lineHeight < node.FontSize {
57 | lineHeight = node.FontSize
58 | }
59 |
60 | size := Vector{}
61 | lines := strings.Split(node.DefaultLabel(), "\n")
62 | for _, line := range lines {
63 | width := Length(len(line)) * node.FontSize * HeightWidthRatio
64 | if width > size.X {
65 | size.X = width
66 | }
67 | size.Y += lineHeight
68 | }
69 |
70 | size.X *= 0.5
71 | size.Y = Length(len(lines)) * lineHeight * 0.5
72 | return size
73 | }
74 |
75 | func (node *Node) TopLeft() Vector { return Vector{node.Left(), node.Top()} }
76 | func (node *Node) BottomRight() Vector { return Vector{node.Right(), node.Bottom()} }
77 |
78 | func (node *Node) TopCenter() Vector { return Vector{node.Center.X, node.Top()} }
79 | func (node *Node) BottomCenter() Vector { return Vector{node.Center.X, node.Bottom()} }
80 |
81 | func (node *Node) Left() Length { return node.Center.X - node.Radius.X }
82 | func (node *Node) Top() Length { return node.Center.Y - node.Radius.Y }
83 | func (node *Node) Right() Length { return node.Center.X + node.Radius.X }
84 | func (node *Node) Bottom() Length { return node.Center.Y + node.Radius.Y }
85 |
--------------------------------------------------------------------------------
/examples/complex.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/internal/hier/graph_debug.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | // CheckErrors does sanity check of content of graph
11 | func (graph *Graph) CheckErrors() error {
12 | var errors []error
13 | for _, node := range graph.Nodes {
14 | countIn := NewNodeSet(graph.NodeCount())
15 | countOut := NewNodeSet(graph.NodeCount())
16 |
17 | for _, dst := range node.In {
18 | if int(dst.ID) >= graph.NodeCount() {
19 | errors = append(errors, fmt.Errorf("overflow in: %v -> %v", dst.ID, node.ID))
20 | continue
21 | }
22 |
23 | if !countIn.Include(dst) {
24 | errors = append(errors, fmt.Errorf("dup in: %v -> %v", dst.ID, node.ID))
25 | }
26 | }
27 |
28 | for _, dst := range node.Out {
29 | if int(dst.ID) >= graph.NodeCount() {
30 | errors = append(errors, fmt.Errorf("overflow out: %v -> %v", node.ID, dst.ID))
31 | continue
32 | }
33 |
34 | if !countOut.Include(dst) {
35 | errors = append(errors, fmt.Errorf("dup out: %v -> %v", node.ID, dst.ID))
36 | }
37 | }
38 | }
39 |
40 | // TODO: check for in/out cross-references
41 | if len(errors) == 0 {
42 | return nil
43 | }
44 | return fmt.Errorf("%v", errors)
45 | }
46 |
47 | // EdgeMatrixString creates a debug output showing both inbound and outbound edges
48 | func (graph *Graph) EdgeMatrixString() string {
49 | lines := []string{}
50 |
51 | n := graph.NodeCount()
52 | stride := 2*n + 4
53 | table := make([]byte, n*stride)
54 | for i := range table {
55 | table[i] = ' '
56 | }
57 |
58 | createLine := func(nodes Nodes) string {
59 | line := make([]byte, n)
60 | for x := range line {
61 | line[x] = ' '
62 | }
63 | for _, node := range nodes {
64 | line[node.ID] = 'X'
65 | }
66 | return string(line)
67 | }
68 |
69 | formatEdges := func(node *Node) string {
70 | var b bytes.Buffer
71 | b.WriteString(strconv.Itoa(int(node.ID)))
72 | b.WriteString(": []int{")
73 | for i, dst := range node.Out {
74 | b.WriteString(strconv.Itoa(int(dst.ID)))
75 | if i+1 < len(node.Out) {
76 | b.WriteString(", ")
77 | }
78 | }
79 | b.WriteString("}")
80 | return b.String()
81 | }
82 |
83 | for _, node := range graph.Nodes {
84 | lines = append(lines, "|"+createLine(node.In)+"|"+createLine(node.Out)+"| "+formatEdges(node))
85 | }
86 |
87 | return strings.Join(lines, "\n")
88 | }
89 |
--------------------------------------------------------------------------------
/format/graphml/write.go:
--------------------------------------------------------------------------------
1 | package graphml
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "io"
7 |
8 | "github.com/loov/layout"
9 | )
10 |
11 | func Write(out io.Writer, graphs ...*layout.Graph) error {
12 | file := NewFile()
13 | for _, graph := range graphs {
14 | file.Graphs = append(file.Graphs, Convert(graph))
15 | }
16 |
17 | file.Key = []Key{
18 | Key{For: "node", ID: "label", AttrName: "label", AttrType: "string"},
19 | Key{For: "node", ID: "shape", AttrName: "shape", AttrType: "string"},
20 | Key{For: "edge", ID: "label", AttrName: "label", AttrType: "string"},
21 |
22 | Key{For: "node", ID: "ynodelabel", YFilesType: "nodegraphics"},
23 | Key{For: "edge", ID: "yedgelabel", YFilesType: "edgegraphics"},
24 | }
25 |
26 | enc := xml.NewEncoder(out)
27 | enc.Indent("", "\t")
28 | return enc.Encode(file)
29 | }
30 |
31 | func Convert(graph *layout.Graph) *Graph {
32 | out := &Graph{}
33 | out.ID = graph.ID
34 | if graph.Directed {
35 | out.EdgeDefault = Directed
36 | } else {
37 | out.EdgeDefault = Undirected
38 | }
39 |
40 | for _, node := range graph.Nodes {
41 | outnode := Node{}
42 | outnode.ID = node.ID
43 | addAttr(&outnode.Attrs, "label", node.DefaultLabel())
44 | addAttr(&outnode.Attrs, "shape", string(node.Shape))
45 | addAttr(&outnode.Attrs, "tooltip", node.Tooltip)
46 | addYedLabelAttr(&outnode.Attrs, "ynodelabel", node.DefaultLabel())
47 | out.Node = append(out.Node, outnode)
48 | }
49 |
50 | for _, edge := range graph.Edges {
51 | outedge := Edge{}
52 | outedge.Source = edge.From.ID
53 | outedge.Target = edge.To.ID
54 | addAttr(&outedge.Attrs, "label", edge.Label)
55 | addAttr(&outedge.Attrs, "tooltip", edge.Tooltip)
56 | addYedLabelAttr(&outedge.Attrs, "yedgelabel", edge.Label)
57 | out.Edge = append(out.Edge, outedge)
58 | }
59 |
60 | return out
61 | }
62 |
63 | func addAttr(attrs *[]Attr, key, value string) {
64 | if value == "" {
65 | return
66 | }
67 | *attrs = append(*attrs, Attr{key, escapeText(value)})
68 | }
69 |
70 | func addYedLabelAttr(attrs *[]Attr, key, value string) {
71 | if value == "" {
72 | return
73 | }
74 | var buf bytes.Buffer
75 | buf.WriteString(``)
76 | if err := xml.EscapeText(&buf, []byte(value)); err != nil {
77 | // this shouldn't ever happen
78 | panic(err)
79 | }
80 | buf.WriteString(``)
81 | *attrs = append(*attrs, Attr{key, buf.Bytes()})
82 | }
83 |
84 | func escapeText(s string) []byte {
85 | if s == "" {
86 | return []byte{}
87 | }
88 |
89 | var buf bytes.Buffer
90 | if err := xml.EscapeText(&buf, []byte(s)); err != nil {
91 | // this shouldn't ever happen
92 | panic(err)
93 | }
94 | return buf.Bytes()
95 | }
96 |
--------------------------------------------------------------------------------
/graph.go:
--------------------------------------------------------------------------------
1 | package layout
2 |
3 | type Graph struct {
4 | ID string
5 | Directed bool
6 |
7 | // Defaults
8 | LineHeight Length
9 | FontSize Length
10 | Shape Shape
11 |
12 | NodePadding Length
13 | RowPadding Length
14 | EdgePadding Length
15 |
16 | NodeByID map[string]*Node
17 | Nodes []*Node
18 | Edges []*Edge
19 | }
20 |
21 | func NewGraph() *Graph {
22 | graph := &Graph{}
23 |
24 | graph.LineHeight = 16 * Point
25 | graph.Shape = Auto
26 |
27 | graph.NodeByID = make(map[string]*Node)
28 | return graph
29 | }
30 |
31 | func NewDigraph() *Graph {
32 | graph := NewGraph()
33 | graph.Directed = true
34 | return graph
35 | }
36 |
37 | // Node finds or creates node with id
38 | func (graph *Graph) Node(id string) *Node {
39 | if id == "" {
40 | panic("invalid node id")
41 | }
42 |
43 | node, found := graph.NodeByID[id]
44 | if !found {
45 | node = NewNode(id)
46 | graph.AddNode(node)
47 | }
48 | return node
49 | }
50 |
51 | // Edge finds or creates new edge based on ids
52 | func (graph *Graph) Edge(from, to string) *Edge {
53 | source, target := graph.Node(from), graph.Node(to)
54 | for _, edge := range graph.Edges {
55 | if edge.From == source && edge.To == target {
56 | return edge
57 | }
58 | }
59 |
60 | edge := NewEdge(source, target)
61 | edge.Directed = graph.Directed
62 | graph.AddEdge(edge)
63 | return edge
64 | }
65 |
66 | // AddNode adds a new node.
67 | //
68 | // When a node with the specified id already it will return false
69 | // and the node is not added.
70 | func (graph *Graph) AddNode(node *Node) bool {
71 | if node.ID != "" {
72 | _, found := graph.NodeByID[node.ID]
73 | if found {
74 | return false
75 | }
76 | graph.NodeByID[node.ID] = node
77 | }
78 | graph.Nodes = append(graph.Nodes, node)
79 | return true
80 | }
81 |
82 | func (graph *Graph) AddEdge(edge *Edge) {
83 | graph.Edges = append(graph.Edges, edge)
84 | }
85 |
86 | func minvector(a *Vector, b Vector) {
87 | if b.X < a.X {
88 | a.X = b.X
89 | }
90 | if b.Y < a.Y {
91 | a.Y = b.Y
92 | }
93 | }
94 |
95 | func maxvector(a *Vector, b Vector) {
96 | if b.X > a.X {
97 | a.X = b.X
98 | }
99 | if b.Y > a.Y {
100 | a.Y = b.Y
101 | }
102 | }
103 |
104 | func (graph *Graph) Bounds() (min, max Vector) {
105 | for _, node := range graph.Nodes {
106 | minvector(&min, node.TopLeft())
107 | maxvector(&max, node.BottomRight())
108 | }
109 |
110 | for _, edge := range graph.Edges {
111 | for _, p := range edge.Path {
112 | minvector(&min, p)
113 | maxvector(&max, p)
114 | }
115 | }
116 |
117 | minvector(&min, max)
118 | maxvector(&max, min)
119 |
120 | return
121 | }
122 |
--------------------------------------------------------------------------------
/format/graphml/graphml.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
71 |
72 |
73 |
74 |
78 |
79 |
80 |
84 |
85 |
88 |
--------------------------------------------------------------------------------
/internal/hier/decycle_test.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | import (
4 | "testing"
5 | "testing/quick"
6 | )
7 |
8 | var decyclerCases = []struct {
9 | name string
10 | recurse, reorder bool
11 | }{
12 | {"Basic", false, false},
13 | {"Recurse", true, false},
14 | {"Reorder", false, true},
15 | {"RecurseReorder", true, true},
16 | }
17 |
18 | func TestDecycle(t *testing.T) {
19 | for _, decyclerCase := range decyclerCases {
20 | t.Run(decyclerCase.name, func(t *testing.T) {
21 | for _, testgraph := range TestGraphs {
22 | t.Run(testgraph.Name, func(t *testing.T) {
23 | graph := testgraph.Make()
24 | beforeCount := graph.CountUndirectedLinks()
25 |
26 | decycle := NewDecycle(graph)
27 | decycle.Recurse = decyclerCase.recurse
28 | decycle.Reorder = decyclerCase.reorder
29 | decycle.Run()
30 |
31 | printEdges := false
32 | if err := graph.CheckErrors(); err != nil {
33 | t.Errorf("got errors: %v", err)
34 | printEdges = true
35 | }
36 | if graph.IsCyclic() {
37 | t.Errorf("got cycles")
38 | printEdges = true
39 | }
40 |
41 | afterCount := graph.CountUndirectedLinks()
42 | if beforeCount != afterCount {
43 | t.Errorf("too many edges removed %v -> %v", beforeCount, afterCount)
44 | printEdges = true
45 | }
46 |
47 | if printEdges {
48 | t.Log("edge table: \n" + graph.EdgeMatrixString())
49 | }
50 | })
51 | }
52 | })
53 | }
54 | }
55 |
56 | func TestDecycleRandom(t *testing.T) {
57 | for _, decyclerCase := range decyclerCases {
58 | t.Run(decyclerCase.name, func(t *testing.T) {
59 | err := quick.Check(func(graph *Graph) bool {
60 | decycle := NewDecycle(graph)
61 | decycle.Recurse = decyclerCase.recurse
62 | decycle.Reorder = decyclerCase.reorder
63 | decycle.Run()
64 |
65 | err := graph.CheckErrors()
66 | if err != nil {
67 | t.Errorf("invalid %v:\n%v", err, graph.EdgeMatrixString())
68 | return false
69 | }
70 | if graph.IsCyclic() {
71 | t.Errorf("cyclic:\n%v", graph.EdgeMatrixString())
72 | return false
73 | }
74 | return true
75 | }, nil)
76 |
77 | if err != nil {
78 | t.Error(err)
79 | }
80 | })
81 | }
82 | }
83 |
84 | func BenchmarkDecycle(b *testing.B) {
85 | for _, decyclerCase := range decyclerCases {
86 | b.Run(decyclerCase.name, func(b *testing.B) {
87 | for _, size := range BenchmarkGraphSizes {
88 | b.Run(size.Name, func(b *testing.B) {
89 | graph := GenerateRegularGraph(size.Nodes, size.Connections)
90 |
91 | for i := 0; i < b.N; i++ {
92 | decycle := NewDecycle(graph)
93 | decycle.Recurse = decyclerCase.recurse
94 | decycle.Reorder = decyclerCase.reorder
95 | decycle.SkipUpdate = true
96 | decycle.Run()
97 | }
98 | })
99 | }
100 | })
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/internal/hier/graph_test.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | type TestGraph struct {
4 | Name string
5 | Cyclic bool
6 | Edges [][]int
7 | }
8 |
9 | func (testgraph *TestGraph) Make() *Graph {
10 | return NewGraphFromEdgeList(testgraph.Edges)
11 | }
12 |
13 | var TestGraphs = []TestGraph{
14 | // acyclic graphs
15 | {"null", false, [][]int{}},
16 | {"node", false, [][]int{
17 | 0: {},
18 | }},
19 | {"link", false, [][]int{
20 | 0: {1},
21 | 1: {},
22 | }},
23 | {"link-reverse", false, [][]int{
24 | 0: {},
25 | 1: {0},
26 | }},
27 | {"chain", false, [][]int{
28 | 0: {1},
29 | 1: {2},
30 | 2: {3},
31 | 3: {},
32 | }},
33 | {"chain-reverse", false, [][]int{
34 | 0: {},
35 | 1: {0},
36 | 2: {1},
37 | 3: {2},
38 | }},
39 | {"split", false, [][]int{
40 | 0: {1},
41 | 1: {2, 3},
42 | 2: {4},
43 | 3: {5},
44 | 4: {},
45 | 5: {},
46 | }},
47 | {"merge", false, [][]int{
48 | 0: {1},
49 | 1: {2, 3},
50 | 2: {4},
51 | 3: {4},
52 | 4: {},
53 | }},
54 | // acyclic graphs with 2 components
55 | {"2-node", false, [][]int{
56 | 0: {},
57 | 1: {},
58 | }},
59 | {"2-link", false, [][]int{
60 | 0: {1},
61 | 1: {},
62 |
63 | 2: {},
64 | 3: {2},
65 | }},
66 | {"2-split", false, [][]int{
67 | 0: {1, 2},
68 | 1: {},
69 | 2: {},
70 |
71 | 4: {},
72 | 5: {},
73 | 6: {5, 4},
74 | }},
75 | {"2-merge", false, [][]int{
76 | 0: {1, 2},
77 | 1: {3},
78 | 2: {3},
79 | 3: {},
80 |
81 | 4: {},
82 | 5: {4},
83 | 6: {4},
84 | 7: {6, 5},
85 | }},
86 |
87 | // cyclic graphs
88 | {"loop", true, [][]int{
89 | 0: {0},
90 | }},
91 | {"2-circle", true, [][]int{
92 | 0: {1},
93 | 1: {0},
94 | }},
95 | {"4-circle", true, [][]int{
96 | 0: {1},
97 | 1: {2},
98 | 2: {3},
99 | 3: {0},
100 | }},
101 | {"5-split-cycle", true, [][]int{
102 | 0: {1},
103 | 1: {2, 3},
104 | 2: {4},
105 | 3: {4},
106 | 4: {0},
107 | }},
108 | {"5-split-2-cycle", true, [][]int{
109 | 0: {1},
110 | 1: {2, 3, 0},
111 | 2: {4},
112 | 3: {4, 2},
113 | 4: {2},
114 | }},
115 | {"5-complete", true, [][]int{
116 | 0: {0, 1, 2, 3, 4},
117 | 1: {0, 1, 2, 3, 4},
118 | 2: {0, 1, 2, 3, 4},
119 | 3: {0, 1, 2, 3, 4},
120 | 4: {0, 1, 2, 3, 4},
121 | }},
122 |
123 | // regressions
124 | {"regression-0", true, [][]int{
125 | 0: {0, 1, 2, 4, 5},
126 | 1: {0, 2, 3},
127 | 2: {0, 1, 4, 5, 6},
128 | 3: {0, 3, 4},
129 | 4: {0, 1, 2, 3, 4, 5},
130 | 5: {0, 1, 2},
131 | 6: {0, 6},
132 | }},
133 | {"regression-1", true, [][]int{
134 | 0: {1, 2, 3, 4},
135 | 1: {1, 5},
136 | 2: {1},
137 | 3: {0, 1, 2, 3},
138 | 4: {0, 2},
139 | 5: {0, 1, 2, 6},
140 | 6: {1, 3, 4},
141 | }},
142 | {"regression-2", true, [][]int{
143 | 0: {1, 2, 3, 4, 5, 6},
144 | 1: {0, 1, 2, 3, 4, 5, 6},
145 | 2: {1},
146 | 3: {0, 3, 4, 5, 6},
147 | 4: {0, 1, 2, 3, 4, 5, 6},
148 | 5: {0, 1, 2, 5, 6},
149 | 6: {0, 1, 2, 3, 4, 5, 6},
150 | }},
151 | }
152 |
--------------------------------------------------------------------------------
/format/graphml/xml.go:
--------------------------------------------------------------------------------
1 | package graphml
2 |
3 | import "encoding/xml"
4 |
5 | type File struct {
6 | XMLName xml.Name `xml:"graphml"`
7 | XMLNS string `xml:"xmlns,attr"`
8 | XMLNSXSI string `xml:"xmlns:xsi,attr"`
9 | XMLNSY string `xml:"xmlns:y,attr"`
10 | XSISchemaLocation string `xml:"xsi:schemalocation,attr"`
11 |
12 | Key []Key `xml:"key"`
13 | Graphs []*Graph `xml:"graph"`
14 | }
15 |
16 | func NewFile() *File {
17 | file := &File{}
18 | file.XMLNS = "http://graphml.graphdrawing.org/xmlns"
19 | file.XMLNSXSI = "http://www.w3.org/2001/XMLSchema-instance"
20 | file.XMLNSY = "http://www.yworks.com/xml/graphml"
21 | file.XSISchemaLocation = "http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"
22 | return file
23 | }
24 |
25 | type Graph struct {
26 | // XMLName xml.Name `xml:"graph"`
27 | ID string `xml:"id,attr"`
28 | EdgeDefault EdgeDefault `xml:"edgedefault,attr"`
29 |
30 | Node []Node `xml:"node"`
31 | Edge []Edge `xml:"edge"`
32 | Hyperedge []Hyperedge `xml:"hyperedge"`
33 | // TODO: parse info
34 | }
35 |
36 | type Key struct {
37 | ID string `xml:"id,attr"`
38 | For string `xml:"for,attr"`
39 |
40 | AttrName string `xml:"attr.name,attr,omitempty"`
41 | AttrType string `xml:"attr.type,attr,omitempty"`
42 |
43 | YFilesType string `xml:"yfiles.type,attr,omitempty"`
44 | }
45 |
46 | type Node struct {
47 | // XMLName xml.Name `xml:"node"`
48 | ID string `xml:"id,attr"`
49 | Port []Port `xml:"port"`
50 | Graph []*Graph `xml:"graph"`
51 | Attrs []Attr `xml:"data"`
52 |
53 | // TODO: parse info
54 | }
55 |
56 | type Port struct {
57 | // XMLName xml.Name `xml:"port"`
58 | Name string `xml:"name,attr"`
59 | }
60 |
61 | type Edge struct {
62 | // XMLName xml.Name `xml:"edge"`
63 | ID string `xml:"id,attr,omitempty"`
64 |
65 | Source string `xml:"source,attr"`
66 | Target string `xml:"target,attr"`
67 | Directed *bool `xml:"directed,attr,omitempty"`
68 |
69 | SourcePort string `xml:"sourceport,attr,omitempty"`
70 | TargetPort string `xml:"targetport,attr,omitempty"`
71 |
72 | Attrs []Attr `xml:"data"`
73 | }
74 |
75 | type EdgeDefault string
76 |
77 | const (
78 | Undirected = EdgeDefault("undirected")
79 | Directed = EdgeDefault("directed")
80 | )
81 |
82 | type Attr struct {
83 | // XMLName xml.Name `xml:"data"`
84 | Key string `xml:"key,attr"`
85 | Value []byte `xml:",innerxml"`
86 | }
87 |
88 | type Hyperedge struct {
89 | // XMLName xml.Name `xml:"hyperedge"`
90 |
91 | ID string `xml:"id,attr,omitempty"`
92 | Endpoint []Endpoint `xml:"endpoint"`
93 | }
94 |
95 | type Endpoint struct {
96 | // XMLName xml.Name `xml:"endpoint"`
97 | Node string `xml:"node,attr"`
98 | Port string `xml:"port,attr,omitempty"`
99 | Type EndpointType `xml:"type,attr,omitempty"`
100 | }
101 |
102 | type EndpointType string
103 |
104 | const (
105 | EndpointIn = EndpointType("in")
106 | EndpointOut = EndpointType("out")
107 | EndpointUndir = EndpointType("undir")
108 | )
109 |
--------------------------------------------------------------------------------
/internal/hier/graph.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | import "strconv"
4 |
5 | // Graph is the basic graph
6 | type Graph struct {
7 | Nodes Nodes
8 | // Ranking
9 | ByRank []Nodes
10 | }
11 |
12 | // ID is an unique identifier to a Node
13 | type ID int
14 |
15 | // Node is the basic information about a node
16 | type Node struct {
17 | ID ID
18 |
19 | Virtual bool
20 |
21 | In Nodes
22 | Out Nodes
23 |
24 | Label string
25 |
26 | // Rank info
27 | Rank int
28 |
29 | // Ordering info
30 | Coef float32
31 | GridX float32
32 |
33 | // Visuals
34 | Center Vector
35 | Radius Vector
36 | }
37 |
38 | // String returns node label
39 | func (node *Node) String() string {
40 | if node.Label == "" && node.Virtual {
41 | return "v" + strconv.Itoa(int(node.ID))
42 | }
43 | if node.Label == "" {
44 | return "#" + strconv.Itoa(int(node.ID))
45 | }
46 | return node.Label
47 | }
48 |
49 | // InDegree returns count of inbound edges
50 | func (node *Node) InDegree() int { return len(node.In) }
51 |
52 | // OutDegree returns count of outbound edges
53 | func (node *Node) OutDegree() int { return len(node.Out) }
54 |
55 | // Vector represents a 2D vector
56 | type Vector struct {
57 | X, Y float32
58 | }
59 |
60 | // NewGraph creates an empty graph
61 | func NewGraph() *Graph { return &Graph{} }
62 |
63 | // ensureNode adds nodes until we have reached id
64 | func (graph *Graph) ensureNode(id int) {
65 | for id >= len(graph.Nodes) {
66 | graph.AddNode()
67 | }
68 | }
69 |
70 | // NodeCount returns count of nodes
71 | func (graph *Graph) NodeCount() int { return len(graph.Nodes) }
72 |
73 | // AddNode adds a new node and returns it's ID
74 | func (graph *Graph) AddNode() *Node {
75 | node := &Node{ID: ID(len(graph.Nodes))}
76 | graph.Nodes = append(graph.Nodes, node)
77 | return node
78 | }
79 |
80 | // AddEdge adds a new edge to the node
81 | func (graph *Graph) AddEdge(src, dst *Node) {
82 | src.Out.Append(dst)
83 | dst.In.Append(src)
84 | }
85 |
86 | // Roots returns nodes without any incoming edges
87 | func (graph *Graph) Roots() Nodes {
88 | nodes := Nodes{}
89 | for _, node := range graph.Nodes {
90 | if node.InDegree() == 0 {
91 | nodes.Append(node)
92 | }
93 | }
94 | return nodes
95 | }
96 |
97 | // CountRoots returns count of roots
98 | func (graph *Graph) CountRoots() int {
99 | total := 0
100 | for _, node := range graph.Nodes {
101 | if node.InDegree() == 0 {
102 | total++
103 | }
104 | }
105 | return total
106 | }
107 |
108 | // CountEdges counts all edges, including duplicates
109 | func (graph *Graph) CountEdges() int {
110 | total := 0
111 | for _, src := range graph.Nodes {
112 | total += len(src.Out)
113 | }
114 | return total
115 | }
116 |
117 | // CountUndirectedLinks counts unique edges in the graph excluding loops
118 | func (graph *Graph) CountUndirectedLinks() int {
119 | counted := map[[2]ID]struct{}{}
120 |
121 | for _, src := range graph.Nodes {
122 | for _, dst := range src.Out {
123 | if src == dst {
124 | continue
125 | }
126 |
127 | a, b := src.ID, dst.ID
128 | if a > b {
129 | a, b = b, a
130 | }
131 |
132 | counted[[2]ID{a, b}] = struct{}{}
133 | }
134 | }
135 | return len(counted)
136 | }
137 |
--------------------------------------------------------------------------------
/internal/hier/graph_nodes.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | // NodeSet is a dense node set
4 | type NodeSet []bool
5 |
6 | // NewNodeSet returns new set for n nodes
7 | func NewNodeSet(n int) NodeSet { return make(NodeSet, n) }
8 |
9 | // Add includes node in set
10 | func (nodes NodeSet) Add(n *Node) { nodes[n.ID] = true }
11 |
12 | // Remove removes node from set
13 | func (nodes NodeSet) Remove(n *Node) { nodes[n.ID] = false }
14 |
15 | // Contains whether node is in set
16 | func (nodes NodeSet) Contains(n *Node) bool { return nodes[n.ID] }
17 |
18 | // Include includes node in set, returns false when node already exists
19 | func (nodes NodeSet) Include(n *Node) bool {
20 | if nodes.Contains(n) {
21 | return false
22 | }
23 | nodes.Add(n)
24 | return true
25 | }
26 |
27 | // Nodes is a list of node identifiers
28 | type Nodes []*Node
29 |
30 | // Clear clears the list
31 | func (nodes *Nodes) Clear() { *nodes = nil }
32 |
33 | // Clone makes a clone of the list
34 | func (nodes *Nodes) Clone() *Nodes {
35 | result := make(Nodes, len(*nodes))
36 | copy(result, *nodes)
37 | return &result
38 | }
39 |
40 | // Contains returns whether node is present in this list
41 | func (nodes *Nodes) Contains(node *Node) bool { return nodes.IndexOf(node) >= 0 }
42 |
43 | // Append adds node to the list without checking for duplicates
44 | func (nodes *Nodes) Append(node *Node) { *nodes = append(*nodes, node) }
45 |
46 | // Includes adds node to the list if it already doesn't contain it
47 | func (nodes *Nodes) Include(node *Node) {
48 | if !nodes.Contains(node) {
49 | nodes.Append(node)
50 | }
51 | }
52 |
53 | // Remove removes node, including any duplicates
54 | func (nodes *Nodes) Remove(node *Node) {
55 | i := nodes.IndexOf(node)
56 | for i >= 0 {
57 | nodes.Delete(i)
58 | i = nodes.indexOfAt(node, i)
59 | }
60 | }
61 |
62 | // Delete deletes
63 | func (nodes *Nodes) Delete(i int) { *nodes = append((*nodes)[:i], (*nodes)[i+1:]...) }
64 |
65 | // Normalize sorts and removes duplicates from the list
66 | func (nodes *Nodes) Normalize() {
67 | nodes.SortBy(func(a, b *Node) bool {
68 | return a.ID < b.ID
69 | })
70 | // sort.Slice(*nodes, func(i, k int) bool {
71 | // return (*nodes)[i].ID < (*nodes)[k].ID
72 | // })
73 |
74 | { // remove duplicates from sorted array
75 | var p *Node
76 | unique := (*nodes)[:0]
77 | for _, n := range *nodes {
78 | if p != n {
79 | unique = append(unique, n)
80 | p = n
81 | }
82 | }
83 | *nodes = unique
84 | }
85 | }
86 |
87 | // SortDescending sorts nodes in descending order of outdegree
88 | func (nodes Nodes) SortDescending() Nodes {
89 | nodes.SortBy(func(a, b *Node) bool {
90 | if a.OutDegree() == b.OutDegree() {
91 | return a.InDegree() < b.InDegree()
92 | }
93 | return a.OutDegree() > b.OutDegree()
94 | })
95 |
96 | return nodes
97 | }
98 |
99 | // IndexOf finds the node index in this list
100 | func (nodes *Nodes) IndexOf(node *Node) int {
101 | for i, x := range *nodes {
102 | if x == node {
103 | return i
104 | }
105 | }
106 | return -1
107 | }
108 |
109 | // indexOfAt finds the node index starting from offset
110 | func (nodes *Nodes) indexOfAt(node *Node, offset int) int {
111 | for i, x := range (*nodes)[offset:] {
112 | if x == node {
113 | return offset + i
114 | }
115 | }
116 | return -1
117 | }
118 |
--------------------------------------------------------------------------------
/internal/hier/RESEARCH.md:
--------------------------------------------------------------------------------
1 | # Research
2 |
3 | ## graphviz - dot
4 |
5 | dot draws graphs in four main phases. Knowing this helps you to understand what kind of layouts dot makes and how you can control them. The layout procedure used by dot relies on the graph being acyclic. Thus, the first step is to break any cycles which occur in the input graph by reversing the internal direction of certain cyclic edges. The next step assigns nodes to discrete ranks or levels. In a top-to-bottom drawing, ranks determine Y coordinates. Edges that span more than one rank are broken into chains of “virtual” nodes and unit-length edges. The third step orders nodes within ranks to avoid crossings. The fourth step sets X coordinates of nodes to keep edges short, and the final step routes edge splines. This is the same general approach as most hierarchical graph drawing programs, based on the work of Warfield [War77], Carpano [Car80] and Sugiyama [STT81]. We refer the reader to [GKNV93] for a thorough explanation of dot’s algorithms.
6 |
7 | The dot algorithm produces a ranked layout of a graph respecting edge directions if possible. It is particularly appropriate for displaying hierarchies or directed acyclic graphs. The basic layout scheme is attributed to Sugiyama et al.[STT81] The specific algorithm used by dot follows the steps described by Gansner et al.[GKNV93]
8 |
9 | The steps in the dot layout are:
10 | 1) initialize
11 | 2) rank
12 | 3) mincross
13 | 4) position
14 | 5) sameports
15 | 6) splines
16 | 7) compoundEdges
17 |
18 | * http://www.graphviz.org/pdf/dotguide.pdf
19 | * http://www.graphviz.org/doc/libguide/libguide.pdf
20 |
21 | ## A Technique for Drawing Directed Graphs
22 |
23 | We describe a four-pass algorithm for drawing directed graphs. The first pass finds an optimal rank assignment using a network simplex algorithm. The second pass sets the vertex order within ranks by an iterative heuristic incorporating a novel weight function and local transpositions to reduce crossings. The third pass finds optimal coordinates for nodes by constructing and ranking an auxiliary graph. The fourth pass makes splines to draw edges. The algorithm makes good drawings and runs fast.
24 |
25 | * http://www.graphviz.org/Documentation/TSE93.pdf
26 |
27 | ## References
28 |
29 | * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.38.3837&rep=rep1&type=pdf
30 | * http://docs.yworks.com/yfiles/doc/api/y/layout/hierarchic/IncrementalHierarchicLayouter.html
31 | * http://hci.stanford.edu/courses/cs448b/w09/lectures/20090204-GraphsAndTrees.pdf
32 | * http://marvl.infotech.monash.edu/~dwyer/
33 | * http://mgarland.org/files/papers/layoutgpu.pdf
34 | * http://research.microsoft.com/en-us/um/people/holroyd/papers/bundle.pdf
35 | * http://stackoverflow.com/questions/19245350/graphviz-dot-algorithm
36 | * http://www.csse.monash.edu.au/~tdwyer/Dwyer2009FastConstraints.pdf
37 | * http://www.graphviz.org/Theory.php
38 | * http://www.graphviz.org/Documentation/TSE93.pdf
39 | * http://www.graphviz.org/pdf/dotguide.pdf
40 | * https://en.wikipedia.org/wiki/Graph_drawing
41 | * https://github.com/cpettitt/dagre/tree/master/lib/rank
42 | * https://github.com/cpettitt/dagre/wiki#recommended-reading
43 | * https://github.com/cpettitt/dig.js/tree/master/src/dig/dot
44 | * https://github.com/d3/d3/issues/349
45 | * https://github.com/ellson/graphviz/tree/master/lib/dotgen2
46 | * https://www.microsoft.com/en-us/research/publication/drawing-graphs-with-glee/
--------------------------------------------------------------------------------
/internal/hier/order.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | import (
4 | "sort"
5 | )
6 |
7 | // DefaultOrderRanks does recommended rank ordering
8 | func DefaultOrderRanks(graph *Graph) *Graph {
9 | OrderRanks(graph)
10 | return graph
11 | }
12 |
13 | // OrderRanks tries to minimize crossign edges
14 | func OrderRanks(graph *Graph) {
15 | OrderRanksDepthFirst(graph)
16 | for i := 0; i < 100; i++ {
17 | OrderRanksByCoef(graph, i%2 == 0)
18 | if OrderRanksTranspose(graph) == 0 {
19 | break
20 | }
21 | }
22 | }
23 |
24 | // OrderRanksDepthFirst reorders based on depth first traverse
25 | func OrderRanksDepthFirst(graph *Graph) {
26 | if len(graph.ByRank) == 0 {
27 | return
28 | }
29 |
30 | seen := NewNodeSet(graph.NodeCount())
31 | ranking := make([]Nodes, len(graph.ByRank))
32 |
33 | var process func(node *Node)
34 | process = func(src *Node) {
35 | if !seen.Include(src) {
36 | return
37 | }
38 |
39 | ranking[src.Rank].Append(src)
40 | for _, dst := range src.Out {
41 | process(dst)
42 | }
43 | }
44 |
45 | roots := graph.Roots()
46 | roots.SortDescending()
47 |
48 | for _, id := range roots {
49 | process(id)
50 | }
51 |
52 | graph.ByRank = ranking
53 | }
54 |
55 | // OrderRanksByCoef reorders based on target grid and coef
56 | func OrderRanksByCoef(graph *Graph, down bool) {
57 | OrderRanksAssignMetrics(graph, down)
58 |
59 | for _, nodes := range graph.ByRank {
60 | sort.Slice(nodes, func(i, k int) bool {
61 | a, b := nodes[i], nodes[k]
62 |
63 | if a.Coef == -1.0 {
64 | if b.Coef == -1.0 {
65 | return a.GridX < b.GridX
66 | } else {
67 | return a.GridX < b.Coef
68 | }
69 | } else {
70 | if b.Coef == -1.0 {
71 | return a.Coef < b.GridX
72 | } else {
73 | return a.Coef < b.Coef
74 | }
75 | }
76 | })
77 | }
78 | }
79 |
80 | // OrderRanksAssignMetrics recalculates metrics for ordering
81 | func OrderRanksAssignMetrics(graph *Graph, down bool) {
82 | for _, nodes := range graph.ByRank {
83 | for i, node := range nodes {
84 | node.GridX = float32(i)
85 | }
86 | }
87 |
88 | for _, node := range graph.Nodes {
89 | var adj Nodes
90 | if down {
91 | adj = node.Out
92 | } else {
93 | adj = node.In
94 | }
95 |
96 | if len(adj) == 0 {
97 | node.Coef = -1
98 | } else if len(adj)&1 == 1 {
99 | node.Coef = adj[len(adj)>>1].GridX
100 | } else if len(adj) == 2 {
101 | node.Coef = (adj[0].GridX + adj[1].GridX) / 2.0
102 | } else {
103 | leftx := adj[len(adj)>>1-1].GridX
104 | rightx := adj[len(adj)>>1].GridX
105 |
106 | left := leftx - adj[0].GridX
107 | right := adj[len(adj)-1].GridX - rightx
108 | node.Coef = (leftx*right + rightx*left) / (left + right)
109 | }
110 | }
111 | }
112 |
113 | // OrderRanksTranspose swaps nodes which are side by side and will use less crossings
114 | func OrderRanksTranspose(graph *Graph) (swaps int) {
115 | for limit := 0; limit < 20; limit++ {
116 | improved := false
117 |
118 | for _, nodes := range graph.ByRank[1:] {
119 | if len(nodes) == 0 {
120 | continue
121 | }
122 | left := nodes[0]
123 | for i, right := range nodes[1:] {
124 | if graph.CrossingsUp(left, right) > graph.CrossingsUp(right, left) {
125 | nodes[i], nodes[i+1] = right, left
126 | right, left = left, right
127 | swaps++
128 | }
129 | left = right
130 | }
131 | }
132 |
133 | if !improved {
134 | return
135 | }
136 | }
137 |
138 | return 0
139 | }
140 |
--------------------------------------------------------------------------------
/internal/hier/decycle.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | // DecycleDefault runs recommended decycling algorithm
4 | func DefaultDecycle(graph *Graph) *Graph {
5 | decycle := NewDecycle(graph)
6 | decycle.Recurse = true
7 | decycle.Reorder = true
8 | decycle.Run()
9 | return graph
10 | }
11 |
12 | // Decycle implements process for removing cycles from a Graph
13 | type Decycle struct {
14 | *Graph
15 |
16 | Recurse bool
17 | Reorder bool
18 |
19 | SkipUpdate bool
20 |
21 | info []DecycleNodeInfo
22 | edges [][2]ID
23 | }
24 |
25 | // NewDecycle creates new decycle process
26 | func NewDecycle(graph *Graph) *Decycle {
27 | dg := &Decycle{}
28 | dg.Graph = graph
29 | dg.Recurse = false
30 | dg.Reorder = true
31 | return dg
32 | }
33 |
34 | // DecycleNodeInfo contains running info necessary in decycling
35 | type DecycleNodeInfo struct {
36 | Processed bool
37 | In, Out int
38 | }
39 |
40 | // Run runs the default decycling process
41 | func (graph *Decycle) Run() {
42 | if !graph.IsCyclic() {
43 | return
44 | }
45 |
46 | graph.info = make([]DecycleNodeInfo, graph.NodeCount())
47 | for _, node := range graph.Nodes {
48 | graph.info[node.ID].In = node.InDegree()
49 | graph.info[node.ID].Out = node.OutDegree()
50 | }
51 |
52 | graph.processNodes(*graph.Nodes.Clone())
53 |
54 | if !graph.SkipUpdate {
55 | graph.updateEdges()
56 | }
57 | }
58 |
59 | // processNodes processes list of nodes
60 | func (graph *Decycle) processNodes(nodes Nodes) {
61 | if !graph.Reorder {
62 | for _, node := range nodes {
63 | graph.process(node)
64 | }
65 | } else {
66 | var node *Node
67 | for len(nodes) > 0 {
68 | graph.sortAscending(nodes)
69 | node, nodes = nodes[len(nodes)-1], nodes[:len(nodes)-1]
70 | graph.process(node)
71 | }
72 | }
73 | }
74 |
75 | // process flips unprocessed incoming edges in dst
76 | func (graph *Decycle) process(dst *Node) {
77 | if graph.info[dst.ID].Processed {
78 | return
79 | }
80 | graph.info[dst.ID].Processed = true
81 |
82 | var recurse Nodes
83 | for _, src := range dst.In {
84 | if src == dst {
85 | continue
86 | }
87 |
88 | if graph.info[src.ID].Processed {
89 | graph.addEdge(src, dst)
90 | } else {
91 | graph.addFlippedEdge(src, dst)
92 | if graph.Recurse {
93 | recurse.Append(src)
94 | }
95 | }
96 | }
97 |
98 | if graph.Recurse {
99 | graph.processNodes(recurse)
100 | }
101 | }
102 |
103 | // addEdge adds edge from src to dest
104 | func (graph *Decycle) addEdge(src, dst *Node) {
105 | graph.edges = append(graph.edges, [2]ID{src.ID, dst.ID})
106 | }
107 |
108 | // addFlippedEdge adds edge and flips it
109 | func (graph *Decycle) addFlippedEdge(src, dst *Node) {
110 | graph.info[src.ID].Out--
111 | graph.info[src.ID].In++
112 |
113 | graph.info[dst.ID].In--
114 | graph.info[dst.ID].Out++
115 |
116 | graph.addEdge(dst, src)
117 | }
118 |
119 | // updateEdges, updates graph with new edge information
120 | func (graph *Decycle) updateEdges() {
121 | // recreate inbound links from outbound
122 | for _, node := range graph.Nodes {
123 | node.In.Clear()
124 | node.Out.Clear()
125 | }
126 |
127 | for _, edge := range graph.edges {
128 | graph.AddEdge(graph.Nodes[edge[0]], graph.Nodes[edge[1]])
129 | }
130 |
131 | for _, node := range graph.Nodes {
132 | node.In.Normalize()
133 | node.Out.Normalize()
134 | }
135 | }
136 |
137 | // sortAscending sorts nodes such that the last node is most beneficial to process
138 | func (graph *Decycle) sortAscending(nodes Nodes) {
139 | nodes.SortBy(func(a, b *Node) bool {
140 | ai, bi := graph.info[a.ID], graph.info[b.ID]
141 | if ai.Out == bi.Out {
142 | return ai.In > bi.In
143 | }
144 | return ai.Out < bi.Out
145 | })
146 | }
147 |
--------------------------------------------------------------------------------
/internal/hier/rank.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | // DefaultRank does recommended ranking algorithm
4 | func DefaultRank(graph *Graph) *Graph {
5 | Rank(graph)
6 | return graph
7 | }
8 |
9 | // Rank implements basic ranking algorithm
10 | func Rank(graph *Graph) {
11 | RankFrontload(graph)
12 |
13 | for i := 0; i < 7; i++ {
14 | RankMinimizeEdgeStep(graph, i%2 == 0)
15 | }
16 |
17 | graph.ByRank = nil
18 | for _, node := range graph.Nodes {
19 | if node.Rank >= len(graph.ByRank) {
20 | byRank := make([]Nodes, node.Rank+1)
21 | copy(byRank, graph.ByRank)
22 | graph.ByRank = byRank
23 | }
24 | graph.ByRank[node.Rank].Append(node)
25 | }
26 | }
27 |
28 | // RankFrontload assigns node.Rank := max(node.In[i].Rank) + 1
29 | func RankFrontload(graph *Graph) {
30 | roots := graph.Roots()
31 |
32 | incount := make([]int, len(graph.Nodes))
33 | for _, node := range graph.Nodes {
34 | incount[node.ID] = len(node.In)
35 | }
36 |
37 | rank := 0
38 | for len(roots) > 0 {
39 | next := Nodes{}
40 | for _, src := range roots {
41 | src.Rank = rank
42 | for _, dst := range src.Out {
43 | incount[dst.ID]--
44 | if incount[dst.ID] == 0 {
45 | next.Append(dst)
46 | }
47 | }
48 | }
49 | roots = next
50 | rank++
51 | }
52 | }
53 |
54 | // RankBackload assigns node.Rank := min(node.Out[i].Rank) - 1
55 | func RankBackload(graph *Graph) {
56 | roots := Nodes{}
57 | outcount := make([]int, len(graph.Nodes))
58 | for _, node := range graph.Nodes {
59 | outcount[node.ID] = len(node.Out)
60 | if len(node.Out) == 0 {
61 | roots.Append(node)
62 | }
63 | }
64 |
65 | rank := 0
66 | graph.ByRank = nil
67 | for len(roots) > 0 {
68 | graph.ByRank = append(graph.ByRank, roots)
69 | next := Nodes{}
70 | for _, root := range roots {
71 | root.Rank = rank
72 | for _, src := range root.In {
73 | outcount[src.ID]--
74 | if outcount[src.ID] == 0 {
75 | next.Append(src)
76 | }
77 | }
78 | }
79 | roots = next
80 | rank++
81 | }
82 |
83 | for i := range graph.ByRank[:len(graph.ByRank)/2] {
84 | k := len(graph.ByRank) - i - 1
85 | graph.ByRank[i], graph.ByRank[k] = graph.ByRank[k], graph.ByRank[i]
86 | }
87 |
88 | for rank, nodes := range graph.ByRank {
89 | for _, node := range nodes {
90 | node.Rank = rank
91 | }
92 | }
93 | }
94 |
95 | // RankMinimizeEdgeStep moves nodes up/down to more equally distribute
96 | func RankMinimizeEdgeStep(graph *Graph, down bool) (changed bool) {
97 | if down {
98 | // try to move nodes down
99 | for _, node := range graph.Nodes {
100 | if len(node.In) <= len(node.Out) {
101 | // there are more edges below, try to move node downwards
102 | minrank := len(graph.Nodes)
103 | for _, dst := range node.Out {
104 | minrank = min(dst.Rank, minrank)
105 | }
106 | if node.Rank <= minrank-1 {
107 | if len(node.In) == len(node.Out) {
108 | // node.Rank = node.Rank
109 | node.Rank = (node.Rank + (minrank - 1) + 1) / 2
110 | // node.Rank = randbetween(node.Rank, minrank-1)
111 | } else {
112 | node.Rank = minrank - 1
113 | }
114 | changed = true
115 | }
116 | }
117 | }
118 | } else {
119 | for _, node := range graph.Nodes {
120 | if len(node.In) >= len(node.Out) {
121 | // there are more edges above, try to move node upwards
122 | maxrank := 0
123 | for _, src := range node.In {
124 | maxrank = max(src.Rank, maxrank)
125 | }
126 | if node.Rank >= maxrank+1 {
127 | if len(node.In) == len(node.Out) {
128 | // node.Rank = node.Rank
129 | node.Rank = (node.Rank + (maxrank + 1)) / 2
130 | // node.Rank = randbetween(node.Rank, maxrank+1)
131 | } else {
132 | node.Rank = maxrank + 1
133 | }
134 | changed = true
135 | }
136 | }
137 | }
138 | }
139 | return
140 | }
141 |
--------------------------------------------------------------------------------
/cmd/glay/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "os"
9 | "path/filepath"
10 | "runtime"
11 | "runtime/pprof"
12 | "strings"
13 |
14 | "github.com/loov/layout"
15 | "github.com/loov/layout/format/dot"
16 | "github.com/loov/layout/format/svg"
17 | )
18 |
19 | var (
20 | cpuprofile = flag.String("cpuprofile", "", "profile cpu usage")
21 | memprofile = flag.String("memprofile", "", "profile memory usage")
22 |
23 | informat = flag.String("s", "", "input format")
24 | outformat = flag.String("t", "svg", "output format")
25 |
26 | verbose = flag.Bool("v", false, "verbose output")
27 | )
28 |
29 | func infof(format string, args ...interface{}) {
30 | if *verbose {
31 | fmt.Fprintf(os.Stderr, format, args...)
32 | if !strings.HasSuffix("\n", format) {
33 | fmt.Fprint(os.Stderr, "\n")
34 | }
35 | }
36 | }
37 |
38 | func errorf(format string, args ...interface{}) {
39 | fmt.Fprintf(os.Stderr, format, args...)
40 | if !strings.HasSuffix("\n", format) {
41 | fmt.Fprint(os.Stderr, "\n")
42 | }
43 | }
44 |
45 | func main() {
46 | flag.Parse()
47 |
48 | input := flag.Arg(0)
49 | output := flag.Arg(1)
50 |
51 | if input == "" {
52 | errorf("input is missing")
53 | flag.Usage()
54 | return
55 | }
56 |
57 | if *informat == "" {
58 | // try to detect input format
59 | switch strings.ToLower(filepath.Ext(input)) {
60 | case ".dot":
61 | *informat = "dot"
62 | case ".gv":
63 | *informat = "dot"
64 | }
65 | }
66 |
67 | if output != "" {
68 | *outformat = ""
69 | }
70 | if *outformat == "" {
71 | // try to detect output format
72 | switch strings.ToLower(filepath.Ext(output)) {
73 | case ".svg":
74 | *outformat = "svg"
75 | default:
76 | *outformat = "svg"
77 | }
78 | }
79 |
80 | if *informat == "" || *outformat == "" {
81 | errorf("unable to detect input or output format")
82 | flag.Usage()
83 | os.Exit(1)
84 | return
85 | }
86 |
87 | if *cpuprofile != "" {
88 | f, err := os.Create(*cpuprofile)
89 | if err != nil {
90 | errorf("unable to create cpu-profile %q: %v", *cpuprofile, err)
91 | os.Exit(1)
92 | }
93 | defer f.Close()
94 | if err := pprof.StartCPUProfile(f); err != nil {
95 | errorf("unable to start cpu-profile: %v", err)
96 | os.Exit(1)
97 | }
98 | defer pprof.StopCPUProfile()
99 | }
100 |
101 | if *memprofile != "" {
102 | defer func() {
103 | f, err := os.Create(*memprofile)
104 | if err != nil {
105 | errorf("unable to create mem-profile %q: %v", *memprofile, err)
106 | os.Exit(1)
107 | }
108 | defer f.Close()
109 |
110 | runtime.GC()
111 | if err := pprof.WriteHeapProfile(f); err != nil {
112 | errorf("unable to start mem-profile: %v", err)
113 | os.Exit(1)
114 | }
115 | }()
116 | }
117 |
118 | var graphs []*layout.Graph
119 | var err error
120 |
121 | infof("parsing %q", input)
122 |
123 | switch *informat {
124 | case "dot":
125 | graphs, err = dot.ParseFile(input)
126 | default:
127 | errorf("unknown input format %q", *informat)
128 | flag.Usage()
129 | os.Exit(1)
130 | return
131 | }
132 |
133 | if err != nil || len(graphs) == 0 {
134 | if len(graphs) == 0 && err == nil {
135 | err = errors.New("file doesn't contain graphs")
136 | }
137 | errorf("failed to parse %q: %v", input, err)
138 | os.Exit(1)
139 | return
140 | }
141 |
142 | if len(graphs) != 1 {
143 | infof("parsed %v graphs", len(graphs))
144 | } else {
145 | infof("parsed 1 graph")
146 | }
147 |
148 | graph := graphs[0]
149 | if len(graphs) > 1 {
150 | errorf("file %q contains multiple graphs, processing only first\n", input)
151 | }
152 |
153 | // layout
154 | layout.Hierarchical(graph)
155 |
156 | // output
157 | var out io.Writer
158 | if output == "" {
159 | out = os.Stdout
160 | } else {
161 | file, err := os.Create(output)
162 | if err != nil {
163 | errorf("unable to create file %q: %v", output, err)
164 | os.Exit(1)
165 | return
166 | }
167 | defer file.Close()
168 | out = file
169 | }
170 |
171 | switch *outformat {
172 | case "svg":
173 | err = svg.Write(out, graph)
174 | default:
175 | errorf("unknown output format %q", *outformat)
176 | os.Exit(1)
177 | return
178 | }
179 |
180 | if err != nil {
181 | errorf("writing %q failed: %v", output, err)
182 | os.Exit(1)
183 | return
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/layout.go:
--------------------------------------------------------------------------------
1 | package layout
2 |
3 | import (
4 | "github.com/loov/layout/internal/hier"
5 | )
6 |
7 | const epsilon = 1e-6
8 |
9 | func (graph *Graph) AssignMissingValues() {
10 | if graph.FontSize <= 0 {
11 | graph.FontSize = graph.LineHeight * 14 / 16
12 | }
13 | if graph.NodePadding <= 0 {
14 | graph.NodePadding = graph.LineHeight
15 | }
16 | if graph.RowPadding <= 0 {
17 | graph.RowPadding = graph.LineHeight
18 | }
19 | if graph.EdgePadding <= 0 {
20 | graph.EdgePadding = 6 * Point
21 | }
22 |
23 | for _, node := range graph.Nodes {
24 | if node.Shape == "" {
25 | node.Shape = graph.Shape
26 | }
27 |
28 | if node.Weight < epsilon {
29 | node.Weight = epsilon
30 | }
31 |
32 | if node.FontSize <= 0 {
33 | node.FontSize = graph.FontSize
34 | }
35 |
36 | if node.Radius.X <= 0 || node.Radius.Y <= 0 {
37 | node.Radius.X = graph.LineHeight
38 | node.Radius.Y = graph.LineHeight
39 |
40 | labelRadius := node.approxLabelRadius(graph.LineHeight)
41 | labelRadius.X += node.FontSize * 0.5
42 | labelRadius.Y += node.FontSize * 0.25
43 |
44 | if node.Radius.X < labelRadius.X {
45 | node.Radius.X = labelRadius.X
46 | }
47 | if node.Radius.Y < labelRadius.Y {
48 | node.Radius.Y = labelRadius.Y
49 | }
50 | }
51 | }
52 |
53 | for _, edge := range graph.Edges {
54 | if edge.Weight < epsilon {
55 | edge.Weight = epsilon
56 | }
57 | }
58 | }
59 |
60 | func Hierarchical(graphdef *Graph) {
61 | graphdef.AssignMissingValues()
62 |
63 | nodes := map[*Node]hier.ID{}
64 | reverse := map[hier.ID]*Node{}
65 |
66 | // construct hierarchical graph
67 | graph := &hier.Graph{}
68 | for _, nodedef := range graphdef.Nodes {
69 | node := graph.AddNode()
70 | nodes[nodedef] = node.ID
71 | reverse[node.ID] = nodedef
72 | node.Label = nodedef.ID
73 | }
74 | for _, edge := range graphdef.Edges {
75 | from, to := nodes[edge.From], nodes[edge.To]
76 | graph.AddEdge(graph.Nodes[from], graph.Nodes[to])
77 | }
78 |
79 | // remove cycles
80 | decycledGraph := hier.DefaultDecycle(graph)
81 |
82 | // assign nodes to ranks
83 | rankedGraph := hier.DefaultRank(decycledGraph)
84 |
85 | // create virtual nodes
86 | filledGraph := hier.DefaultAddVirtuals(rankedGraph)
87 |
88 | // order nodes in ranks
89 | orderedGraph := hier.DefaultOrderRanks(filledGraph)
90 |
91 | // assign node sizes
92 | for id, node := range orderedGraph.Nodes {
93 | if node.Virtual {
94 | node.Radius.X = float32(graphdef.EdgePadding)
95 | node.Radius.Y = float32(graphdef.EdgePadding)
96 | continue
97 | }
98 |
99 | nodedef, ok := reverse[hier.ID(id)]
100 | if !ok {
101 | // TODO: handle missing node
102 | continue
103 | }
104 | node.Radius.X = float32(nodedef.Radius.X + graphdef.NodePadding)
105 | node.Radius.Y = float32(nodedef.Radius.Y + graphdef.RowPadding)
106 | }
107 |
108 | // position nodes
109 | positionedGraph := hier.DefaultPosition(orderedGraph)
110 |
111 | // assign final positions
112 | for nodedef, id := range nodes {
113 | node := positionedGraph.Nodes[id]
114 | nodedef.Center.X = Length(node.Center.X)
115 | nodedef.Center.Y = Length(node.Center.Y)
116 | }
117 |
118 | // calculate edges
119 | edgePaths := map[[2]hier.ID][]Vector{}
120 | for _, source := range positionedGraph.Nodes {
121 | if source.Virtual {
122 | continue
123 | }
124 |
125 | sourcedef := reverse[source.ID]
126 | for _, out := range source.Out {
127 | path := []Vector{}
128 | path = append(path, sourcedef.BottomCenter())
129 |
130 | target := out
131 | for target != nil && target.Virtual {
132 | if len(target.Out) < 1 { // should never happen
133 | target = nil
134 | break
135 | }
136 |
137 | path = append(path, Vector{
138 | Length(target.Center.X),
139 | Length(target.Center.Y),
140 | })
141 |
142 | target = target.Out[0]
143 | }
144 | if target == nil {
145 | continue
146 | }
147 |
148 | targetdef := reverse[target.ID]
149 | path = append(path, targetdef.TopCenter())
150 |
151 | edgePaths[[2]hier.ID{source.ID, target.ID}] = path
152 | }
153 | }
154 |
155 | for _, edge := range graphdef.Edges {
156 | sourceid := nodes[edge.From]
157 | targetid := nodes[edge.To]
158 |
159 | if sourceid == targetid {
160 | // TODO: improve loops
161 | edge.Path = []Vector{
162 | edge.From.BottomCenter(),
163 | edge.From.TopCenter(),
164 | }
165 | continue
166 | }
167 |
168 | path, ok := edgePaths[[2]hier.ID{sourceid, targetid}]
169 | if ok {
170 | edge.Path = path
171 | continue
172 | }
173 |
174 | // some paths may have been reversed
175 | revpath, ok := edgePaths[[2]hier.ID{targetid, sourceid}]
176 | if ok {
177 | edge.Path = reversePath(revpath)
178 | continue
179 | }
180 | }
181 | }
182 |
183 | func reversePath(path []Vector) []Vector {
184 | rs := make([]Vector, 0, len(path))
185 | for i := len(path) - 1; i >= 0; i-- {
186 | rs = append(rs, path[i])
187 | }
188 | return rs
189 | }
190 |
--------------------------------------------------------------------------------
/internal/hier/graph_nodes_sort.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | // Sort implementation is a modification of http://golang.org/pkg/sort/#Sort
4 | // Copyright 2009 The Go Authors. All rights reserved.
5 | // Use of this source code is governed by a BSD-style
6 | // license that can be found at http://golang.org/LICENSE.
7 |
8 | // SortBy sorts nodes in place
9 | func (nodes *Nodes) SortBy(less func(*Node, *Node) bool) {
10 | // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
11 | n := len(*nodes)
12 | maxDepth := 0
13 | for i := n; i > 0; i >>= 1 {
14 | maxDepth++
15 | }
16 | maxDepth *= 2
17 | nodes._quickSortNodeSlice(less, 0, n, maxDepth)
18 | }
19 |
20 | // Sort implementation based on http://golang.org/pkg/sort/#Sort
21 |
22 | func (rcv Nodes) _swapNodeSlice(a, b int) {
23 | rcv[a], rcv[b] = rcv[b], rcv[a]
24 | }
25 |
26 | // Insertion sort
27 | func (rcv Nodes) _insertionSortNodeSlice(less func(*Node, *Node) bool, a, b int) {
28 | for i := a + 1; i < b; i++ {
29 | for j := i; j > a && less(rcv[j], rcv[j-1]); j-- {
30 | rcv._swapNodeSlice(j, j-1)
31 | }
32 | }
33 | }
34 |
35 | // siftDown implements the heap property on rcv[lo, hi).
36 | // first is an offset into the array where the root of the heap lies.
37 | func (rcv Nodes) _siftDownNodeSlice(less func(*Node, *Node) bool, lo, hi, first int) {
38 | root := lo
39 | for {
40 | child := 2*root + 1
41 | if child >= hi {
42 | break
43 | }
44 | if child+1 < hi && less(rcv[first+child], rcv[first+child+1]) {
45 | child++
46 | }
47 | if !less(rcv[first+root], rcv[first+child]) {
48 | return
49 | }
50 | rcv._swapNodeSlice(first+root, first+child)
51 | root = child
52 | }
53 | }
54 |
55 | func (rcv Nodes) _heapSortNodeSlice(less func(*Node, *Node) bool, a, b int) {
56 | first := a
57 | lo := 0
58 | hi := b - a
59 |
60 | // Build heap with greatest element at top.
61 | for i := (hi - 1) / 2; i >= 0; i-- {
62 | rcv._siftDownNodeSlice(less, i, hi, first)
63 | }
64 |
65 | // Pop elements, largest first, into end of rcv._
66 | for i := hi - 1; i >= 0; i-- {
67 | rcv._swapNodeSlice(first, first+i)
68 | rcv._siftDownNodeSlice(less, lo, i, first)
69 | }
70 | }
71 |
72 | // Quicksort, following Bentley and McIlroy,
73 | // Engineering a Sort Function, SP&E November 1993.
74 |
75 | // medianOfThree moves the median of the three values rcv[a], rcv[b], rcv[c] into rcv[a].
76 | func (rcv Nodes) _medianOfThreeNodeSlice(less func(*Node, *Node) bool, a, b, c int) {
77 | m0 := b
78 | m1 := a
79 | m2 := c
80 | // bubble sort on 3 elements
81 | if less(rcv[m1], rcv[m0]) {
82 | rcv._swapNodeSlice(m1, m0)
83 | }
84 | if less(rcv[m2], rcv[m1]) {
85 | rcv._swapNodeSlice(m2, m1)
86 | }
87 | if less(rcv[m1], rcv[m0]) {
88 | rcv._swapNodeSlice(m1, m0)
89 | }
90 | // now rcv[m0] <= rcv[m1] <= rcv[m2]
91 | }
92 |
93 | func (rcv Nodes) _swapRangeNodeSlice(a, b, n int) {
94 | for i := 0; i < n; i++ {
95 | rcv._swapNodeSlice(a+i, b+i)
96 | }
97 | }
98 |
99 | func (rcv Nodes) _doPivotNodeSlice(less func(*Node, *Node) bool, lo, hi int) (midlo, midhi int) {
100 | m := lo + (hi-lo)/2 // Written like this to avoid integer overflow.
101 | if hi-lo > 40 {
102 | // Tukey's Ninther, median of three medians of three.
103 | s := (hi - lo) / 8
104 | rcv._medianOfThreeNodeSlice(less, lo, lo+s, lo+2*s)
105 | rcv._medianOfThreeNodeSlice(less, m, m-s, m+s)
106 | rcv._medianOfThreeNodeSlice(less, hi-1, hi-1-s, hi-1-2*s)
107 | }
108 | rcv._medianOfThreeNodeSlice(less, lo, m, hi-1)
109 |
110 | // Invariants are:
111 | // rcv[lo] = pivot (set up by ChoosePivot)
112 | // rcv[lo <= i < a] = pivot
113 | // rcv[a <= i < b] < pivot
114 | // rcv[b <= i < c] is unexamined
115 | // rcv[c <= i < d] > pivot
116 | // rcv[d <= i < hi] = pivot
117 | //
118 | // Once b meets c, can swap the "= pivot" sections
119 | // into the middle of the slice.
120 | pivot := lo
121 | a, b, c, d := lo+1, lo+1, hi, hi
122 | for {
123 | for b < c {
124 | if less(rcv[b], rcv[pivot]) { // rcv[b] < pivot
125 | b++
126 | } else if !less(rcv[pivot], rcv[b]) { // rcv[b] = pivot
127 | rcv._swapNodeSlice(a, b)
128 | a++
129 | b++
130 | } else {
131 | break
132 | }
133 | }
134 | for b < c {
135 | if less(rcv[pivot], rcv[c-1]) { // rcv[c-1] > pivot
136 | c--
137 | } else if !less(rcv[c-1], rcv[pivot]) { // rcv[c-1] = pivot
138 | rcv._swapNodeSlice(c-1, d-1)
139 | c--
140 | d--
141 | } else {
142 | break
143 | }
144 | }
145 | if b >= c {
146 | break
147 | }
148 | // rcv[b] > pivot; rcv[c-1] < pivot
149 | rcv._swapNodeSlice(b, c-1)
150 | b++
151 | c--
152 | }
153 |
154 | min := func(a, b int) int {
155 | if a < b {
156 | return a
157 | }
158 | return b
159 | }
160 |
161 | n := min(b-a, a-lo)
162 | rcv._swapRangeNodeSlice(lo, b-n, n)
163 |
164 | n = min(hi-d, d-c)
165 | rcv._swapRangeNodeSlice(c, hi-n, n)
166 |
167 | return lo + b - a, hi - (d - c)
168 | }
169 |
170 | func (rcv Nodes) _quickSortNodeSlice(less func(*Node, *Node) bool, a, b, maxDepth int) {
171 | for b-a > 7 {
172 | if maxDepth == 0 {
173 | rcv._heapSortNodeSlice(less, a, b)
174 | return
175 | }
176 | maxDepth--
177 | mlo, mhi := rcv._doPivotNodeSlice(less, a, b)
178 | // Avoiding recursion on the larger subproblem guarantees
179 | // a stack depth of at most lg(b-a).
180 | if mlo-a < b-mhi {
181 | rcv._quickSortNodeSlice(less, a, mlo, maxDepth)
182 | a = mhi // i.e., rcv._quickSortNodeSlice(mhi, b)
183 | } else {
184 | rcv._quickSortNodeSlice(less, mhi, b, maxDepth)
185 | b = mlo // i.e., rcv._quickSortNodeSlice(a, mlo)
186 | }
187 | }
188 | if b-a > 1 {
189 | rcv._insertionSortNodeSlice(less, a, b)
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/internal/hier/position.go:
--------------------------------------------------------------------------------
1 | package hier
2 |
3 | import "fmt"
4 |
5 | // DefaultPosition does recommended positioning algorithm
6 | func DefaultPosition(graph *Graph) *Graph {
7 | Position(graph)
8 | return graph
9 | }
10 |
11 | // Position does basic node positioning
12 | func Position(graph *Graph) {
13 | PositionInitial(graph)
14 |
15 | // TODO: fold nudge into Node parameter
16 | nudge := float32(10.0)
17 | for i := 0; i < 100; i++ {
18 | PositionOutgoing(graph, false, nudge)
19 | PositionIncoming(graph, false, nudge)
20 | PositionOutgoing(graph, true, nudge)
21 | PositionIncoming(graph, true, nudge)
22 | nudge = nudge * 0.9
23 | flushLeft(graph)
24 | }
25 |
26 | for i := 0; i < 10; i++ {
27 | PositionIncoming(graph, true, 0)
28 | PositionOutgoing(graph, true, 0)
29 |
30 | flushLeft(graph)
31 | }
32 | }
33 |
34 | // PositionInitial assigns location based on size
35 | func PositionInitial(graph *Graph) {
36 | top := float32(0)
37 | for _, nodes := range graph.ByRank {
38 | left := float32(0)
39 |
40 | halfrow := float32(0)
41 | for _, node := range nodes {
42 | halfrow = maxf32(halfrow, node.Radius.Y)
43 | }
44 |
45 | top += halfrow
46 | for _, node := range nodes {
47 | node.Center.X = left + node.Radius.X
48 | node.Center.Y = top
49 | left += node.Center.X + node.Radius.X
50 | }
51 | top += halfrow
52 | }
53 | }
54 |
55 | // iterateLayers can traverse layers/nodes in different directions
56 | func iterateLayers(graph *Graph, leftToRight bool, dy int, fn func(layer Nodes, i int, node *Node)) {
57 | var starty int
58 | if dy < 0 {
59 | starty = len(graph.ByRank) - 1
60 | }
61 |
62 | if leftToRight {
63 | for y := starty; 0 <= y && y < len(graph.ByRank); y += dy {
64 | layer := graph.ByRank[y]
65 | for i, node := range layer {
66 | fn(layer, i, node)
67 | }
68 | }
69 | } else {
70 | for y := starty; 0 <= y && y < len(graph.ByRank); y += dy {
71 | layer := graph.ByRank[y]
72 | for i := len(layer) - 1; i >= 0; i-- {
73 | fn(layer, i, layer[i])
74 | }
75 | }
76 | }
77 | }
78 |
79 | // NodeWalls calculates bounds where node can be moved
80 | func NodeWalls(graph *Graph, layer Nodes, i int, node *Node, leftToRight bool) (wallLeft, wallRight float32) {
81 | if i > 0 {
82 | wallLeft = layer[i-1].Center.X + layer[i-1].Radius.X
83 | }
84 |
85 | if i+1 < len(layer) {
86 | wallRight = layer[i+1].Center.X - layer[i+1].Radius.X
87 | } else {
88 | wallRight = float32(len(graph.Nodes)) * (2 * node.Radius.X)
89 | }
90 |
91 | // ensure we can fit at least one
92 | if leftToRight {
93 | if wallRight-node.Radius.X < wallLeft+node.Radius.X {
94 | wallRight = wallLeft + 2*node.Radius.X
95 | }
96 | } else {
97 | if wallRight-node.Radius.X < wallLeft+node.Radius.X {
98 | wallLeft = wallRight - 2*node.Radius.X
99 | }
100 | }
101 |
102 | if leftToRight {
103 | if node.Center.X < wallLeft+node.Radius.X {
104 | node.Center.X = wallLeft + node.Radius.X
105 | }
106 | } else {
107 | if node.Center.X > wallRight-node.Radius.X {
108 | node.Center.X = wallRight - node.Radius.X
109 | }
110 | }
111 |
112 | return wallLeft, wallRight
113 | }
114 |
115 | // PositionIncoming positions node based on incoming edges
116 | func PositionIncoming(graph *Graph, leftToRight bool, nudge float32) {
117 | iterateLayers(graph, leftToRight, 1,
118 | func(layer Nodes, i int, node *Node) {
119 | wallLeft, wallRight := NodeWalls(graph, layer, i, node, leftToRight)
120 |
121 | // calculate average location based on incoming
122 | if len(node.In) == 0 {
123 | return
124 | }
125 | center := float32(0.0)
126 | for _, node := range node.In {
127 | center += node.Center.X
128 | }
129 | center /= float32(len(node.In))
130 |
131 | center = clampf32(center, wallLeft+node.Radius.X-nudge, wallRight-node.Radius.Y+nudge)
132 |
133 | // is between sides
134 | node.Center.X = center
135 | })
136 | }
137 |
138 | // PositionOutgoing positions node based on outgoing edges
139 | func PositionOutgoing(graph *Graph, leftToRight bool, nudge float32) {
140 | iterateLayers(graph, leftToRight, -1,
141 | func(layer Nodes, i int, node *Node) {
142 | wallLeft, wallRight := NodeWalls(graph, layer, i, node, leftToRight)
143 |
144 | // calculate average location based on incoming
145 | if len(node.Out) == 0 {
146 | return
147 | }
148 | center := float32(0.0)
149 | for _, node := range node.Out {
150 | center += node.Center.X
151 | }
152 | center /= float32(len(node.Out))
153 |
154 | center = clampf32(center, wallLeft+node.Radius.X-nudge, wallRight-node.Radius.X+nudge)
155 |
156 | // is between sides
157 | node.Center.X = center
158 | })
159 | }
160 |
161 | // sanityCheckLayer checks whether any nodes are overlapping
162 | func sanityCheckLayer(graph *Graph, layer Nodes) {
163 | deltas := []float32{}
164 | positions := []float32{}
165 | fail := false
166 | wallLeft := float32(0)
167 | for _, node := range layer {
168 | delta := (node.Center.X - node.Radius.X) - wallLeft
169 | if delta < 0 {
170 | fail = true
171 | }
172 | deltas = append(deltas, delta)
173 | positions = append(positions, node.Center.X)
174 | wallLeft = node.Center.X + node.Radius.X
175 | }
176 |
177 | if fail {
178 | fmt.Println("=")
179 | fmt.Println(deltas)
180 | fmt.Println(positions)
181 | }
182 | }
183 |
184 | // flushLeft corrects for graph drift due to moving nodes around
185 | func flushLeft(graph *Graph) {
186 | node := graph.Nodes[0]
187 | minleft := node.Center.X - node.Radius.X
188 | for _, node := range graph.Nodes[1:] {
189 | if node.Center.X-node.Radius.X < minleft {
190 | minleft = node.Center.X - node.Radius.X
191 | }
192 | }
193 |
194 | for _, node := range graph.Nodes {
195 | node.Center.X -= minleft
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/internal/cmd/propagate/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | const (
9 | EdgeCount = 6
10 | EdgeMask = (1 << EdgeCount) - 1
11 | )
12 |
13 | type EdgeTable [EdgeCount][EdgeCount]byte
14 |
15 | func (Z *EdgeTable) Edge(x, y int) {
16 | (*Z)[x][y] = EdgeMask
17 | }
18 |
19 | func (Z *EdgeTable) DeleteOutbound() {
20 | for x := 0; x < EdgeCount; x++ {
21 | for y := 0; y < EdgeCount; y++ {
22 | (*Z)[x][y] &^= (1 << byte(x))
23 | }
24 | }
25 | }
26 |
27 | func (Z *EdgeTable) DeleteInbound() {
28 | for x := 0; x < EdgeCount; x++ {
29 | for y := 0; y < EdgeCount; y++ {
30 | (*Z)[x][y] &^= (1 << byte(y))
31 | }
32 | }
33 | }
34 |
35 | func (Z *EdgeTable) Or(A, B *EdgeTable) {
36 | for x := 0; x < EdgeCount; x++ {
37 | for y := 0; y < EdgeCount; y++ {
38 | (*Z)[x][y] = (*A)[x][y] | (*B)[x][y]
39 | }
40 | }
41 | }
42 |
43 | func (Z *EdgeTable) And(A, B *EdgeTable) {
44 | for x := 0; x < EdgeCount; x++ {
45 | for y := 0; y < EdgeCount; y++ {
46 | (*Z)[x][y] = (*A)[x][y] & (*B)[x][y]
47 | }
48 | }
49 | }
50 |
51 | func (Z *EdgeTable) Mul(A, B *EdgeTable) {
52 | for x := 0; x < EdgeCount; x++ {
53 | for y := 0; y < EdgeCount; y++ {
54 | t := byte(0)
55 | for k := 0; k < EdgeCount; k++ {
56 | t |= (*A)[x][k] & (*B)[k][y]
57 | }
58 | (*Z)[x][y] = t
59 | }
60 | }
61 | }
62 |
63 | func (Z *EdgeTable) Print() {
64 | for x := 0; x < EdgeCount; x++ {
65 | for y := 0; y < EdgeCount; y++ {
66 | v := (*Z)[x][y]
67 | if v == 0 {
68 | fmt.Printf(" · ")
69 | } else {
70 | fmt.Printf("%2d ", v)
71 | }
72 | }
73 | fmt.Printf("\n")
74 | }
75 | }
76 |
77 | func (Z *EdgeTable) PrintBit() {
78 | for x := 0; x < EdgeCount; x++ {
79 | for y := 0; y < EdgeCount; y++ {
80 | v := (*Z)[x][y]
81 | fmt.Printf("%2d ", v)
82 | s := fmt.Sprintf("%08b ", v)
83 | s = strings.Replace(s, "0", "░", -1)
84 | s = strings.Replace(s, "1", "█", -1)
85 | fmt.Print(s)
86 | }
87 | fmt.Printf("\n")
88 | }
89 | }
90 |
91 | func (Z *EdgeTable) PrintBool() {
92 | fmt.Printf(" ")
93 | for y := 0; y < EdgeCount; y++ {
94 | fmt.Printf("%d ", y)
95 | }
96 | fmt.Printf("\n")
97 | for x := 0; x < EdgeCount; x++ {
98 | fmt.Printf("%d ", x)
99 | for y := 0; y < EdgeCount; y++ {
100 | v := (*Z)[x][y]
101 | if v == 0 {
102 | fmt.Printf("░░")
103 | } else {
104 | fmt.Printf("██")
105 | }
106 | }
107 | fmt.Printf("\n")
108 | }
109 | }
110 |
111 | func (Z *EdgeTable) PrintLayer(n byte) {
112 | fmt.Printf(" ")
113 | for y := 0; y < EdgeCount; y++ {
114 | fmt.Printf("%d ", y)
115 | }
116 | fmt.Printf("\n")
117 | for x := 0; x < EdgeCount; x++ {
118 | fmt.Printf("%d ", x)
119 | for y := 0; y < EdgeCount; y++ {
120 | v := ((*Z)[x][y] >> n) & 1
121 | if v == 0 {
122 | fmt.Printf("░░")
123 | } else {
124 | fmt.Printf("██")
125 | }
126 | }
127 | fmt.Printf("\n")
128 | }
129 | }
130 |
131 | func PrintSideBySideLayer(A, B, C *EdgeTable, n byte) {
132 | fmt.Printf(" ")
133 | for y := 0; y < EdgeCount; y++ {
134 | fmt.Printf("%d ", y)
135 | }
136 | fmt.Printf(" ")
137 | for y := 0; y < EdgeCount; y++ {
138 | fmt.Printf("%d ", y)
139 | }
140 | fmt.Printf(" ")
141 | for y := 0; y < EdgeCount; y++ {
142 | fmt.Printf("%d ", y)
143 | }
144 | fmt.Printf("\n")
145 |
146 | for x := 0; x < EdgeCount; x++ {
147 | fmt.Printf("%d ", x)
148 |
149 | for y := 0; y < EdgeCount; y++ {
150 | v := ((*A)[x][y] >> n) & 1
151 | if v == 0 {
152 | fmt.Printf("░░")
153 | } else {
154 | fmt.Printf("██")
155 | }
156 | }
157 |
158 | fmt.Printf(" %d ", x)
159 |
160 | for y := 0; y < EdgeCount; y++ {
161 | v := ((*B)[x][y] >> n) & 1
162 | if v == 0 {
163 | fmt.Printf("░░")
164 | } else {
165 | fmt.Printf("██")
166 | }
167 | }
168 |
169 | fmt.Printf(" %d ", x)
170 |
171 | for y := 0; y < EdgeCount; y++ {
172 | v := ((*C)[x][y] >> n) & 1
173 | if v == 0 {
174 | fmt.Printf("░░")
175 | } else {
176 | fmt.Printf("██")
177 | }
178 | }
179 |
180 | fmt.Printf("\n")
181 | }
182 | }
183 |
184 | func (Z *EdgeTable) CountLayer(n byte) int {
185 | total := 0
186 | for x := 0; x < EdgeCount; x++ {
187 | for y := 0; y < EdgeCount; y++ {
188 | total += int(((*Z)[x][y] >> n) & 1)
189 | }
190 | }
191 | return total
192 | }
193 |
194 | const (
195 | NodeA = iota
196 | NodeB
197 | NodeC
198 | NodeD
199 | NodeE
200 | NodeF
201 | )
202 |
203 | func Process(input *EdgeTable) EdgeTable {
204 | var result, temp EdgeTable
205 |
206 | result = *input
207 | for i := 0; i < EdgeCount; i++ {
208 | temp.Mul(&result, input)
209 | result.Or(&result, &temp)
210 | }
211 |
212 | return result
213 | }
214 |
215 | func main() {
216 | var Input EdgeTable
217 | Input.Edge(NodeA, NodeB)
218 | Input.Edge(NodeA, NodeC)
219 | Input.Edge(NodeB, NodeD)
220 | Input.Edge(NodeC, NodeD)
221 | Input.Edge(NodeC, NodeE)
222 | Input.Edge(NodeD, NodeE)
223 | Input.Edge(NodeD, NodeF)
224 | Input.Edge(NodeE, NodeF)
225 | Input.Edge(NodeF, NodeA)
226 |
227 | Input.PrintBool()
228 |
229 | inbound := Input
230 | outbound := Input
231 |
232 | inbound.DeleteInbound()
233 |
234 | for layer := 0; layer < EdgeCount; layer++ {
235 | fmt.Println("------------------")
236 | inbound.PrintLayer(byte(layer))
237 | }
238 |
239 | inbound = Process(&inbound)
240 |
241 | outbound.DeleteOutbound()
242 | outbound = Process(&outbound)
243 |
244 | anded := inbound
245 | anded.And(&inbound, &outbound)
246 |
247 | fmt.Println("~~~~~~~~~~~~~~~~~~~~~")
248 | inbound.PrintBit()
249 | fmt.Println("~~~~~~~~~~~~~~~~~~~~~")
250 | outbound.PrintBit()
251 | fmt.Println("~~~~~~~~~~~~~~~~~~~~~")
252 | anded.PrintBit()
253 |
254 | for layer := 0; layer < EdgeCount; layer++ {
255 | fmt.Println("------------------")
256 | PrintSideBySideLayer(&inbound, &outbound, &anded, byte(layer))
257 |
258 | fmt.Println(
259 | "+ ",
260 | inbound.CountLayer(byte(layer)),
261 | outbound.CountLayer(byte(layer)),
262 | anded.CountLayer(byte(layer)),
263 | )
264 | }
265 |
266 | }
267 |
--------------------------------------------------------------------------------
/internal/cmd/hier/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "math"
9 | "os"
10 | "path/filepath"
11 | "strings"
12 | "time"
13 |
14 | "github.com/loov/layout"
15 | "github.com/loov/layout/format/dot"
16 | "github.com/loov/layout/internal/hier"
17 | )
18 |
19 | var (
20 | informat = flag.String("S", "", "input format")
21 | outformat = flag.String("T", "svg", "output format")
22 |
23 | verbose = flag.Bool("v", false, "verbose output")
24 | veryVerbose = flag.Bool("vv", false, "very-verbose output")
25 | )
26 |
27 | func info(format string, args ...interface{}) {
28 | if *verbose {
29 | fmt.Fprintf(os.Stderr, format, args...)
30 | if !strings.HasSuffix("\n", format) {
31 | fmt.Fprint(os.Stderr, "\n")
32 | }
33 | }
34 | }
35 |
36 | func main() {
37 | flag.Parse()
38 |
39 | input := flag.Arg(0)
40 | output := flag.Arg(1)
41 |
42 | if input == "" {
43 | info("input is missing")
44 | flag.Usage()
45 | return
46 | }
47 |
48 | *verbose = *verbose || *veryVerbose
49 |
50 | if *informat == "" {
51 | // try to detect input format
52 | switch strings.ToLower(filepath.Ext(input)) {
53 | case ".dot":
54 | *informat = "dot"
55 | case ".gv":
56 | *informat = "dot"
57 | }
58 | }
59 |
60 | if output != "" {
61 | *outformat = ""
62 | }
63 | if *outformat == "" {
64 | // try to detect output format
65 | switch strings.ToLower(filepath.Ext(output)) {
66 | case ".svg":
67 | *outformat = "svg"
68 | default:
69 | *outformat = "svg"
70 | }
71 | }
72 |
73 | if *informat == "" || *outformat == "" {
74 | info("unable to detect input or output format")
75 | flag.Usage()
76 | os.Exit(1)
77 | return
78 | }
79 |
80 | var graphs []*layout.Graph
81 | var err error
82 |
83 | info("parsing %q", input)
84 |
85 | switch *informat {
86 | case "dot":
87 | graphs, err = dot.ParseFile(input)
88 | default:
89 | info("unknown input format %q", *informat)
90 | flag.Usage()
91 | os.Exit(1)
92 | return
93 | }
94 |
95 | if err != nil || len(graphs) == 0 {
96 | if len(graphs) == 0 && err == nil {
97 | err = errors.New("file doesn't contain graphs")
98 | }
99 | info("failed to parse %q: %v", input, err)
100 | os.Exit(1)
101 | return
102 | }
103 |
104 | if len(graphs) != 1 {
105 | info("parsed %v graphs", len(graphs))
106 | } else {
107 | info("parsed 1 graph")
108 | }
109 |
110 | graphdef := graphs[0]
111 | if len(graphs) > 1 {
112 | fmt.Fprintf(os.Stderr, "file %q contains multiple graphs, processing only first\n", input)
113 | }
114 |
115 | var start, stop time.Time
116 |
117 | info("\nCONVERTING")
118 |
119 | nodeID := map[*layout.Node]hier.ID{}
120 |
121 | graph := &hier.Graph{}
122 | for _, nodedef := range graphdef.Nodes {
123 | node := graph.AddNode()
124 | nodeID[nodedef] = node.ID
125 | node.Label = nodedef.ID
126 | }
127 | for _, edge := range graphdef.Edges {
128 | from, to := nodeID[edge.From], nodeID[edge.To]
129 | graph.AddEdge(graph.Nodes[from], graph.Nodes[to])
130 | }
131 |
132 | if *verbose {
133 | info(" nodes: %-8v roots: %-8v", graph.NodeCount(), graph.CountRoots())
134 | info(" edges: %-8v links: %-8v", graph.CountEdges(), graph.CountUndirectedLinks())
135 | info(" cycle: %-8v", graph.IsCyclic())
136 | }
137 |
138 | info("\nDECYCLING")
139 | start = time.Now()
140 | decycle := hier.NewDecycle(graph)
141 | decycle.Run()
142 | stop = time.Now()
143 |
144 | if *verbose {
145 | info(" time: %.3f ms", float64(stop.Sub(start).Nanoseconds())/1e6)
146 | info(" nodes: %-8v roots: %-8v", graph.NodeCount(), graph.CountRoots())
147 | info(" edges: %-8v links: %-8v", graph.CountEdges(), graph.CountUndirectedLinks())
148 | }
149 |
150 | info("\nRANKING")
151 | start = time.Now()
152 | hier.Rank(graph)
153 | stop = time.Now()
154 | if *verbose {
155 | info(" time: %.3f ms", float64(stop.Sub(start).Nanoseconds())/1e6)
156 | info(" ranks: %-8v avg: %-8.2f var: %-8.2f", len(graph.ByRank), rankWidthAverage(graph), rankWidthVariance(graph))
157 | if *veryVerbose {
158 | for i, rank := range graph.ByRank {
159 | info(" %4d- count: %-2d %v", i, len(rank), rank)
160 | }
161 | }
162 | }
163 |
164 | info("\nADDING VIRTUALS")
165 | start = time.Now()
166 | hier.AddVirtualVertices(graph)
167 | stop = time.Now()
168 | if *verbose {
169 | info(" time: %.3f ms", float64(stop.Sub(start).Nanoseconds())/1e6)
170 | info(" nodes: %-8v roots: %-8v", graph.NodeCount(), graph.CountRoots())
171 | info(" edges: %-8v links: %-8v", graph.CountEdges(), graph.CountUndirectedLinks())
172 | // TODO: add info about crossings
173 | info(" ranks: %-8v avg: %-8.2f var: %-8.2f", len(graph.ByRank), rankWidthAverage(graph), rankWidthVariance(graph))
174 | if *veryVerbose {
175 | for i, rank := range graph.ByRank {
176 | info(" %4d- count: %-2d %v", i, len(rank), rank)
177 | }
178 | }
179 | }
180 |
181 | info("\nORDERING")
182 | start = time.Now()
183 | hier.OrderRanks(graph)
184 | stop = time.Now()
185 | if *verbose {
186 | info(" time: %.3f ms", float64(stop.Sub(start).Nanoseconds())/1e6)
187 | // TODO: add info about crossings
188 | if *veryVerbose {
189 | for i, rank := range graph.ByRank {
190 | info(" %4d- count: %-2d %v", i, len(rank), rank)
191 | }
192 | }
193 | }
194 |
195 | // TODO: add step about initial positions
196 | // TODO: add average, max, total edge length
197 |
198 | info("\nPOSITIONING")
199 | start = time.Now()
200 | hier.Position(graph)
201 | stop = time.Now()
202 | if *verbose {
203 | info(" time: %.3f ms", float64(stop.Sub(start).Nanoseconds())/1e6)
204 | // TODO: add average, max, total edge length
205 | }
206 |
207 | info("\nOUTPUTTING")
208 |
209 | start = time.Now()
210 |
211 | var out io.Writer
212 | if output == "" {
213 | out = os.Stdout
214 | } else {
215 | file, err := os.Create(output)
216 | if err != nil {
217 | info("unable to create file %q: %v", output, err)
218 | os.Exit(1)
219 | return
220 | }
221 | defer file.Close()
222 | out = file
223 | }
224 |
225 | if err != nil {
226 | info("writing %q failed: %v", output, err)
227 | os.Exit(1)
228 | return
229 | }
230 |
231 | stop = time.Now()
232 | if *verbose {
233 | info(" time: %.3f ms", float64(stop.Sub(start).Nanoseconds())/1e6)
234 | }
235 | }
236 |
237 | func rankWidthAverage(graph *hier.Graph) float64 {
238 | return float64(len(graph.Nodes)) / float64(len(graph.ByRank))
239 | }
240 |
241 | func rankWidthVariance(graph *hier.Graph) float64 {
242 | if graph.NodeCount() < 2 {
243 | return 0
244 | }
245 |
246 | averageWidth := rankWidthAverage(graph)
247 | total := float64(0)
248 | for _, rank := range graph.ByRank {
249 | deviation := float64(len(rank)) - averageWidth
250 | total += deviation * deviation
251 | }
252 |
253 | return math.Sqrt(total / float64(len(graph.ByRank)-1))
254 | }
255 |
--------------------------------------------------------------------------------
/format/dot/parse.go:
--------------------------------------------------------------------------------
1 | // package dot implements dot file format parsing
2 | package dot
3 |
4 | import (
5 | "io"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/loov/layout"
10 |
11 | "gonum.org/v1/gonum/graph/formats/dot"
12 | "gonum.org/v1/gonum/graph/formats/dot/ast"
13 | )
14 |
15 | func Parse(r io.Reader) ([]*layout.Graph, error) { return parse(dot.Parse(r)) }
16 | func ParseFile(path string) ([]*layout.Graph, error) { return parse(dot.ParseFile(path)) }
17 | func ParseString(s string) ([]*layout.Graph, error) { return parse(dot.ParseString(s)) }
18 |
19 | func parse(file *ast.File, err error) ([]*layout.Graph, error) {
20 | if err != nil {
21 | return nil, err
22 | }
23 |
24 | graphs := []*layout.Graph{}
25 | for _, graphStmt := range file.Graphs {
26 | parser := &parserContext{}
27 | parser.Graph = layout.NewGraph()
28 | parser.parse(graphStmt)
29 | graphs = append(graphs, parser.Graph)
30 | }
31 |
32 | return graphs, nil
33 | }
34 |
35 | type parserContext struct {
36 | Graph *layout.Graph
37 | Cluster string
38 |
39 | allAttrs []*ast.Attr
40 | nodeAttrs []*ast.Attr
41 | edgeAttrs []*ast.Attr
42 | }
43 |
44 | func (context *parserContext) parse(src *ast.Graph) {
45 | context.Graph.ID = src.ID
46 | context.Graph.Directed = src.Directed
47 | context.parseStmts(src.Stmts)
48 | }
49 |
50 | func (context *parserContext) parseStmts(stmts []ast.Stmt) {
51 | for _, stmt := range stmts {
52 | switch stmt := stmt.(type) {
53 | case *ast.NodeStmt:
54 | context.parseNode(stmt)
55 | case *ast.EdgeStmt:
56 | context.parseEdge(stmt)
57 | case *ast.AttrStmt:
58 | switch stmt.Kind {
59 | case ast.NodeKind:
60 | context.nodeAttrs = append(context.nodeAttrs, stmt.Attrs...)
61 | case ast.EdgeKind:
62 | context.edgeAttrs = append(context.edgeAttrs, stmt.Attrs...)
63 | case ast.GraphKind:
64 | context.allAttrs = append(context.allAttrs, stmt.Attrs...)
65 | default:
66 | panic("unknown attr target kind")
67 | }
68 | case *ast.Attr:
69 | context.allAttrs = append(context.allAttrs, stmt)
70 | case *ast.Subgraph:
71 | subcontext := &parserContext{}
72 | subcontext.Graph = context.Graph
73 | subcontext.allAttrs = append(subcontext.allAttrs, context.allAttrs...)
74 | subcontext.nodeAttrs = append(subcontext.nodeAttrs, context.nodeAttrs...)
75 | subcontext.edgeAttrs = append(subcontext.edgeAttrs, context.edgeAttrs...)
76 | subcontext.parseStmts(stmt.Stmts)
77 | }
78 | }
79 | }
80 |
81 | func (context *parserContext) ensureNode(id string) *layout.Node {
82 | if node, exists := context.Graph.NodeByID[id]; exists {
83 | return node
84 | }
85 |
86 | node := context.Graph.Node(fixstring(id))
87 | applyNodeAttrs(node, context.nodeAttrs)
88 | return node
89 | }
90 |
91 | func (context *parserContext) parseNode(src *ast.NodeStmt) *layout.Node {
92 | node := context.ensureNode(src.Node.ID)
93 | applyNodeAttrs(node, src.Attrs)
94 | return node
95 | }
96 |
97 | func (context *parserContext) parseEdge(edgeStmt *ast.EdgeStmt) {
98 | sources := context.ensureVertex(edgeStmt.From)
99 | to := edgeStmt.To
100 | for to != nil {
101 | targets := context.ensureVertex(to.Vertex)
102 | for _, source := range sources {
103 | for _, target := range targets {
104 | edge := layout.NewEdge(source, target)
105 |
106 | edge.Directed = to.Directed
107 | edge.From = source
108 | edge.To = target
109 |
110 | applyEdgeAttrs(edge, context.edgeAttrs)
111 | applyEdgeAttrs(edge, edgeStmt.Attrs)
112 |
113 | context.Graph.Edges = append(context.Graph.Edges, edge)
114 | }
115 | }
116 |
117 | sources = targets
118 | to = to.To
119 | }
120 | }
121 |
122 | func (context *parserContext) ensureVertex(src ast.Vertex) []*layout.Node {
123 | switch src := src.(type) {
124 | case *ast.Node:
125 | return []*layout.Node{context.ensureNode(src.ID)}
126 | case *ast.Subgraph:
127 | nodes := []*layout.Node{}
128 | for _, stmt := range src.Stmts {
129 | switch stmt := stmt.(type) {
130 | case *ast.NodeStmt:
131 | nodes = append(nodes, context.parseNode(stmt))
132 | default:
133 | panic("unsupported stmt inside subgraph")
134 | }
135 | }
136 | return nodes
137 | default:
138 | panic("vertex not supported")
139 | }
140 | }
141 |
142 | func applyNodeAttrs(node *layout.Node, attrs []*ast.Attr) {
143 | for _, attr := range attrs {
144 | switch attr.Key {
145 | case "weight":
146 | setFloat(&node.Weight, attr.Val)
147 | case "shape":
148 | setShape(&node.Shape, attr.Val)
149 | case "label":
150 | setString(&node.Label, attr.Val)
151 | case "color":
152 | setColor(&node.FontColor, attr.Val)
153 | case "fontname":
154 | setString(&node.FontName, attr.Val)
155 | case "fontsize":
156 | setLength(&node.FontSize, attr.Val, layout.Point)
157 | case "pencolor":
158 | setColor(&node.LineColor, attr.Val)
159 | case "penwidth":
160 | setLength(&node.LineWidth, attr.Val, layout.Point)
161 | case "fillcolor":
162 | setColor(&node.FillColor, attr.Val)
163 | case "width":
164 | setLength(&node.Radius.X, attr.Val, layout.Inch*0.5)
165 | case "height":
166 | setLength(&node.Radius.Y, attr.Val, layout.Inch*0.5)
167 | case "tooltip":
168 | setString(&node.Tooltip, attr.Val)
169 | }
170 | }
171 | }
172 |
173 | func applyEdgeAttrs(edge *layout.Edge, attrs []*ast.Attr) {
174 | for _, attr := range attrs {
175 | switch attr.Key {
176 | case "weight":
177 | setFloat(&edge.Weight, attr.Val)
178 | case "label":
179 | setString(&edge.Label, attr.Val)
180 | case "color":
181 | setColor(&edge.FontColor, attr.Val)
182 | case "fontname":
183 | setString(&edge.FontName, attr.Val)
184 | case "fontsize":
185 | setLength(&edge.FontSize, attr.Val, layout.Point)
186 | case "pencolor":
187 | setColor(&edge.LineColor, attr.Val)
188 | case "penwidth":
189 | setLength(&edge.LineWidth, attr.Val, layout.Point)
190 | case "tooltip":
191 | setString(&edge.Tooltip, attr.Val)
192 | }
193 | }
194 | }
195 |
196 | func setColor(t *layout.Color, value string) {
197 | if value == "" {
198 | return
199 | }
200 |
201 | if value[0] == '#' { // hex
202 | value = value[1:]
203 | if len(value) == 6 { // RRGGBB
204 | v, err := strconv.ParseInt(value, 16, 64)
205 | if err == nil {
206 | c := layout.RGB{}
207 | c.R = uint8(v >> 16)
208 | c.G = uint8(v >> 8)
209 | c.B = uint8(v >> 0)
210 | *t = c
211 | }
212 | } else if len(value) == 8 { // RRGGBBAA
213 | v, err := strconv.ParseInt(value, 16, 64)
214 | if err == nil {
215 | c := layout.RGBA{}
216 | c.R = uint8(v >> 24)
217 | c.G = uint8(v >> 16)
218 | c.B = uint8(v >> 8)
219 | c.A = uint8(v >> 0)
220 | *t = c
221 | }
222 | }
223 | return
224 | }
225 |
226 | color, ok := layout.ColorByName(value)
227 | if ok {
228 | *t = color
229 | }
230 | }
231 |
232 | func setFloat(t *float64, value string) {
233 | v, err := strconv.ParseFloat(value, 64)
234 | if err == nil {
235 | *t = v
236 | }
237 | }
238 |
239 | func setLength(t *layout.Length, value string, unit layout.Length) {
240 | v, err := strconv.ParseFloat(value, 64)
241 | if err == nil {
242 | *t = layout.Length(v) * unit
243 | }
244 | }
245 |
246 | func setShape(t *layout.Shape, value string) {
247 | switch value {
248 | case "box", "rect", "rectangle":
249 | *t = layout.Box
250 | case "square":
251 | *t = layout.Square
252 | case "circle":
253 | *t = layout.Circle
254 | case "ellipse", "oval":
255 | *t = layout.Ellipse
256 | case "none":
257 | *t = layout.None
258 | default:
259 | *t = layout.Auto
260 | }
261 | }
262 |
263 | func setString(t *string, value string) {
264 | *t = fixstring(value)
265 | }
266 |
267 | func fixstring(s string) string {
268 | if len(s) > 2 && s[0] == '"' && s[len(s)-1] == '"' {
269 | s = s[1 : len(s)-1]
270 | }
271 | return strings.Replace(s, "\\n", "\n", -1)
272 | }
273 |
--------------------------------------------------------------------------------
/format/svg/write.go:
--------------------------------------------------------------------------------
1 | package svg
2 |
3 | import (
4 | "fmt"
5 | stdhtml "html"
6 | "io"
7 | "strconv"
8 | "strings"
9 |
10 | "github.com/loov/layout"
11 | "golang.org/x/net/html"
12 | )
13 |
14 | type writer struct {
15 | w io.Writer
16 | err error
17 | }
18 |
19 | func (svg *writer) erred() bool { return svg.err != nil }
20 | func (svg *writer) Error() error { return svg.err }
21 |
22 | func (svg *writer) write(format string, args ...interface{}) {
23 | if svg.erred() {
24 | return
25 | }
26 | _, svg.err = fmt.Fprintf(svg.w, format, args...)
27 | }
28 |
29 | func (svg *writer) start(width, height layout.Length) {
30 | svg.write("\n")
34 | }
35 |
36 | func (svg *writer) writeStyle() {
37 | svg.write(`
38 | `)
41 | }
42 |
43 | func (svg *writer) startG() { svg.write("") }
44 | func (svg *writer) finishG() { svg.write("") }
45 |
46 | func (svg *writer) writeDefs() {
47 | svg.write(`
48 |
49 |
50 |
51 |
52 | `)
53 | }
54 |
55 | func colortext(color layout.Color) string {
56 | const hex = "0123456789ABCDEF"
57 | r, g, b, a := color.RGBA8()
58 | if a == 0 {
59 | return "none"
60 | }
61 | return string([]byte{'#',
62 | hex[r>>4], hex[r&7],
63 | hex[g>>4], hex[g&7],
64 | hex[b>>4], hex[b&7],
65 | //hex[a>>4], hex[a&7],
66 | })
67 | }
68 |
69 | func dkcolor(color layout.Color) string {
70 | if color == nil {
71 | return "#000000"
72 | }
73 | return colortext(color)
74 | }
75 |
76 | func ltcolor(color layout.Color) string {
77 | if color == nil {
78 | return "#FFFFFF"
79 | }
80 | return colortext(color)
81 | }
82 |
83 | func vec(x, y layout.Length) string {
84 | return strconv.FormatFloat(float64(x), 'f', -1, 32) + "," +
85 | strconv.FormatFloat(float64(y), 'f', -1, 32) + " "
86 | }
87 |
88 | func straightPath(graph *layout.Graph, path []layout.Vector) string {
89 | line := ""
90 |
91 | p0 := path[0]
92 | line += "M" + vec(p0.X, p0.Y)
93 | for _, p := range path[1:] {
94 | line += "L" + vec(p.X, p.Y)
95 | }
96 |
97 | return line
98 | }
99 |
100 | func bezierPath(graph *layout.Graph, path []layout.Vector) string {
101 | line := ""
102 |
103 | p0 := path[0]
104 | dir := layout.Length(1)
105 | if p0.Y > path[1].Y {
106 | dir *= -1
107 | }
108 | cpoff := dir * graph.RowPadding * 2
109 | line += "M" + vec(p0.X, p0.Y)
110 | for _, p1 := range path[1:] {
111 | line += "C" +
112 | vec(p0.X, p0.Y+cpoff) +
113 | vec(p1.X, p1.Y-cpoff) +
114 | vec(p1.X, p1.Y)
115 | p0 = p1
116 | }
117 |
118 | return line
119 | }
120 |
121 | func smartPath(graph *layout.Graph, path []layout.Vector) string {
122 | line := ""
123 |
124 | p0 := path[0]
125 | p1 := path[1]
126 | dir := layout.Length(1)
127 | if p0.Y > p1.Y {
128 | dir *= -1
129 | }
130 |
131 | if len(path) == 2 && p0.X == p1.X {
132 | return "M" + vec(p0.X, p0.Y) + "L " + vec(p1.X, p1.Y)
133 | }
134 |
135 | var sx, sy layout.Length
136 | line += "M" + vec(p0.X, p0.Y)
137 | for i, p2 := range path[2:] {
138 | sx = p0.X*0.2 + p1.X*0.8
139 | if (p0.X < p1.X) != (p1.X < p2.X) {
140 | sx = p1.X
141 | }
142 | sy = p1.Y - dir*graph.RowPadding
143 | if i == 0 {
144 | line += "C" + vec(p0.X, p0.Y+dir*graph.RowPadding) + vec(sx, sy) + vec(p1.X, p1.Y)
145 | } else {
146 | line += "S" + vec(sx, sy) + vec(p1.X, p1.Y)
147 | }
148 |
149 | p0, p1 = p1, p2
150 | }
151 | sx = p0.X*0.2 + p1.X*0.8
152 | sy = p1.Y - 2*dir*graph.RowPadding
153 |
154 | if len(path) == 2 {
155 | line += "C" + vec(p0.X, p0.Y+dir*graph.RowPadding) + vec(sx, sy) + vec(p1.X, p1.Y)
156 | } else {
157 | line += "S" + vec(sx, sy) + vec(p1.X, p1.Y)
158 | }
159 |
160 | return line
161 | }
162 |
163 | func Write(w io.Writer, graph *layout.Graph) error {
164 | svg := &writer{}
165 | svg.w = w
166 |
167 | _, bottomRight := graph.Bounds()
168 | svg.start(bottomRight.X+graph.NodePadding, bottomRight.Y+graph.RowPadding)
169 | svg.writeStyle()
170 | svg.writeDefs()
171 |
172 | svg.startG()
173 | for _, edge := range graph.Edges {
174 | if len(edge.Path) == 0 {
175 | // TODO: log invalid path
176 | continue
177 | }
178 |
179 | if edge.Directed {
180 | svg.write("", smartPath(graph, edge.Path))
188 |
189 | if edge.Tooltip != "" {
190 | svg.write("%v", escapeString(edge.Tooltip))
191 | }
192 |
193 | svg.write("")
194 | }
195 |
196 | for _, node := range graph.Nodes {
197 | // TODO: add other shapes
198 | svgtag := "circle"
199 | switch node.Shape {
200 | default:
201 | fallthrough
202 | case layout.Circle:
203 | svgtag = "circle"
204 | r := max(node.Radius.X, node.Radius.Y)
205 | svg.write("")
235 | if node.Tooltip != "" {
236 | svg.write("%v", escapeString(node.Tooltip))
237 | }
238 | svg.write("%v>", svgtag)
239 |
240 | if label := node.DefaultLabel(); label != "" {
241 | if label[0] == '<' && label[len(label)-1] == '>' {
242 | svg.write("%v`, lowercaseTags(label[1:len(label)-1]))
251 | svg.write("")
252 | } else {
253 | lines := strings.Split(label, "\n")
254 | top := node.Center.Y - graph.LineHeight*layout.Length(len(lines))*0.5
255 | top += graph.LineHeight * 0.5
256 | for _, line := range lines {
257 | svg.write("%v\n", escapeString(line))
266 | top += graph.LineHeight
267 | }
268 | }
269 | }
270 | }
271 | svg.finishG()
272 | svg.finish()
273 |
274 | return svg.err
275 | }
276 |
277 | func lowercaseTags(s string) string {
278 | root := &html.Node{Type: html.ElementNode}
279 | nodes, err := html.ParseFragment(strings.NewReader(s), root)
280 | if err != nil {
281 | return s
282 | }
283 |
284 | var out strings.Builder
285 | for _, node := range nodes {
286 | err := html.Render(&out, node)
287 | if err != nil {
288 | return s
289 | }
290 | }
291 | return out.String()
292 | }
293 |
294 | func max(a, b layout.Length) layout.Length {
295 | if a > b {
296 | return a
297 | }
298 | return b
299 | }
300 |
301 | func escapeString(s string) string {
302 | return stdhtml.EscapeString(s)
303 | }
304 |
--------------------------------------------------------------------------------
/color_x11.go:
--------------------------------------------------------------------------------
1 | package layout
2 |
3 | import "strings"
4 |
5 | // ColorByName returns RGB color based on X11 scheme
6 | func ColorByName(name string) (Color, bool) {
7 | name = strings.ToLower(name)
8 | name = strings.Replace(name, " ", "", -1)
9 | rgb, ok := x11colors[name]
10 | if !ok {
11 | return nil, false
12 | }
13 | return rgb, true
14 | }
15 |
16 | // x11colors table is based on /usr/lib/X11/rgb.txt
17 | var x11colors = map[string]RGB{
18 | "snow": {255, 250, 250},
19 | "ghostwhite": {248, 248, 255},
20 | "whitesmoke": {245, 245, 245},
21 | "gainsboro": {220, 220, 220},
22 | "floralwhite": {255, 250, 240},
23 | "oldlace": {253, 245, 230},
24 | "linen": {250, 240, 230},
25 | "antiquewhite": {250, 235, 215},
26 | "papayawhip": {255, 239, 213},
27 | "blanchedalmond": {255, 235, 205},
28 | "bisque": {255, 228, 196},
29 | "peachpuff": {255, 218, 185},
30 | "navajowhite": {255, 222, 173},
31 | "moccasin": {255, 228, 181},
32 | "cornsilk": {255, 248, 220},
33 | "ivory": {255, 255, 240},
34 | "lemonchiffon": {255, 250, 205},
35 | "seashell": {255, 245, 238},
36 | "honeydew": {240, 255, 240},
37 | "mintcream": {245, 255, 250},
38 | "azure": {240, 255, 255},
39 | "aliceblue": {240, 248, 255},
40 | "lavender": {230, 230, 250},
41 | "lavenderblush": {255, 240, 245},
42 | "mistyrose": {255, 228, 225},
43 | "white": {255, 255, 255},
44 | "black": {0, 0, 0},
45 | "darkslategray": {47, 79, 79},
46 | "darkslategrey": {47, 79, 79},
47 | "dimgray": {105, 105, 105},
48 | "dimgrey": {105, 105, 105},
49 | "slategray": {112, 128, 144},
50 | "slategrey": {112, 128, 144},
51 | "lightslategray": {119, 136, 153},
52 | "lightslategrey": {119, 136, 153},
53 | "gray": {190, 190, 190},
54 | "grey": {190, 190, 190},
55 | "x11gray": {190, 190, 190},
56 | "x11grey": {190, 190, 190},
57 | "webgray": {128, 128, 128},
58 | "webgrey": {128, 128, 128},
59 | "lightgrey": {211, 211, 211},
60 | "lightgray": {211, 211, 211},
61 | "midnightblue": {25, 25, 112},
62 | "navy": {0, 0, 128},
63 | "navyblue": {0, 0, 128},
64 | "cornflowerblue": {100, 149, 237},
65 | "darkslateblue": {72, 61, 139},
66 | "slateblue": {106, 90, 205},
67 | "mediumslateblue": {123, 104, 238},
68 | "lightslateblue": {132, 112, 255},
69 | "mediumblue": {0, 0, 205},
70 | "royalblue": {65, 105, 225},
71 | "blue": {0, 0, 255},
72 | "dodgerblue": {30, 144, 255},
73 | "deepskyblue": {0, 191, 255},
74 | "skyblue": {135, 206, 235},
75 | "lightskyblue": {135, 206, 250},
76 | "steelblue": {70, 130, 180},
77 | "lightsteelblue": {176, 196, 222},
78 | "lightblue": {173, 216, 230},
79 | "powderblue": {176, 224, 230},
80 | "paleturquoise": {175, 238, 238},
81 | "darkturquoise": {0, 206, 209},
82 | "mediumturquoise": {72, 209, 204},
83 | "turquoise": {64, 224, 208},
84 | "cyan": {0, 255, 255},
85 | "aqua": {0, 255, 255},
86 | "lightcyan": {224, 255, 255},
87 | "cadetblue": {95, 158, 160},
88 | "mediumaquamarine": {102, 205, 170},
89 | "aquamarine": {127, 255, 212},
90 | "darkgreen": {0, 100, 0},
91 | "darkolivegreen": {85, 107, 47},
92 | "darkseagreen": {143, 188, 143},
93 | "seagreen": {46, 139, 87},
94 | "mediumseagreen": {60, 179, 113},
95 | "lightseagreen": {32, 178, 170},
96 | "palegreen": {152, 251, 152},
97 | "springgreen": {0, 255, 127},
98 | "lawngreen": {124, 252, 0},
99 | "green": {0, 255, 0},
100 | "lime": {0, 255, 0},
101 | "x11green": {0, 255, 0},
102 | "webgreen": {0, 128, 0},
103 | "chartreuse": {127, 255, 0},
104 | "mediumspringgreen": {0, 250, 154},
105 | "greenyellow": {173, 255, 47},
106 | "limegreen": {50, 205, 50},
107 | "yellowgreen": {154, 205, 50},
108 | "forestgreen": {34, 139, 34},
109 | "olivedrab": {107, 142, 35},
110 | "darkkhaki": {189, 183, 107},
111 | "khaki": {240, 230, 140},
112 | "palegoldenrod": {238, 232, 170},
113 | "lightgoldenrodyellow": {250, 250, 210},
114 | "lightyellow": {255, 255, 224},
115 | "yellow": {255, 255, 0},
116 | "gold": {255, 215, 0},
117 | "lightgoldenrod": {238, 221, 130},
118 | "goldenrod": {218, 165, 32},
119 | "darkgoldenrod": {184, 134, 11},
120 | "rosybrown": {188, 143, 143},
121 | "indianred": {205, 92, 92},
122 | "saddlebrown": {139, 69, 19},
123 | "sienna": {160, 82, 45},
124 | "peru": {205, 133, 63},
125 | "burlywood": {222, 184, 135},
126 | "beige": {245, 245, 220},
127 | "wheat": {245, 222, 179},
128 | "sandybrown": {244, 164, 96},
129 | "tan": {210, 180, 140},
130 | "chocolate": {210, 105, 30},
131 | "firebrick": {178, 34, 34},
132 | "brown": {165, 42, 42},
133 | "darksalmon": {233, 150, 122},
134 | "salmon": {250, 128, 114},
135 | "lightsalmon": {255, 160, 122},
136 | "orange": {255, 165, 0},
137 | "darkorange": {255, 140, 0},
138 | "coral": {255, 127, 80},
139 | "lightcoral": {240, 128, 128},
140 | "tomato": {255, 99, 71},
141 | "orangered": {255, 69, 0},
142 | "red": {255, 0, 0},
143 | "hotpink": {255, 105, 180},
144 | "deeppink": {255, 20, 147},
145 | "pink": {255, 192, 203},
146 | "lightpink": {255, 182, 193},
147 | "palevioletred": {219, 112, 147},
148 | "maroon": {176, 48, 96},
149 | "x11maroon": {176, 48, 96},
150 | "webmaroon": {128, 0, 0},
151 | "mediumvioletred": {199, 21, 133},
152 | "violetred": {208, 32, 144},
153 | "magenta": {255, 0, 255},
154 | "fuchsia": {255, 0, 255},
155 | "violet": {238, 130, 238},
156 | "plum": {221, 160, 221},
157 | "orchid": {218, 112, 214},
158 | "mediumorchid": {186, 85, 211},
159 | "darkorchid": {153, 50, 204},
160 | "darkviolet": {148, 0, 211},
161 | "blueviolet": {138, 43, 226},
162 | "purple": {160, 32, 240},
163 | "x11purple": {160, 32, 240},
164 | "webpurple": {128, 0, 128},
165 | "mediumpurple": {147, 112, 219},
166 | "thistle": {216, 191, 216},
167 | "snow1": {255, 250, 250},
168 | "snow2": {238, 233, 233},
169 | "snow3": {205, 201, 201},
170 | "snow4": {139, 137, 137},
171 | "seashell1": {255, 245, 238},
172 | "seashell2": {238, 229, 222},
173 | "seashell3": {205, 197, 191},
174 | "seashell4": {139, 134, 130},
175 | "antiquewhite1": {255, 239, 219},
176 | "antiquewhite2": {238, 223, 204},
177 | "antiquewhite3": {205, 192, 176},
178 | "antiquewhite4": {139, 131, 120},
179 | "bisque1": {255, 228, 196},
180 | "bisque2": {238, 213, 183},
181 | "bisque3": {205, 183, 158},
182 | "bisque4": {139, 125, 107},
183 | "peachpuff1": {255, 218, 185},
184 | "peachpuff2": {238, 203, 173},
185 | "peachpuff3": {205, 175, 149},
186 | "peachpuff4": {139, 119, 101},
187 | "navajowhite1": {255, 222, 173},
188 | "navajowhite2": {238, 207, 161},
189 | "navajowhite3": {205, 179, 139},
190 | "navajowhite4": {139, 121, 94},
191 | "lemonchiffon1": {255, 250, 205},
192 | "lemonchiffon2": {238, 233, 191},
193 | "lemonchiffon3": {205, 201, 165},
194 | "lemonchiffon4": {139, 137, 112},
195 | "cornsilk1": {255, 248, 220},
196 | "cornsilk2": {238, 232, 205},
197 | "cornsilk3": {205, 200, 177},
198 | "cornsilk4": {139, 136, 120},
199 | "ivory1": {255, 255, 240},
200 | "ivory2": {238, 238, 224},
201 | "ivory3": {205, 205, 193},
202 | "ivory4": {139, 139, 131},
203 | "honeydew1": {240, 255, 240},
204 | "honeydew2": {224, 238, 224},
205 | "honeydew3": {193, 205, 193},
206 | "honeydew4": {131, 139, 131},
207 | "lavenderblush1": {255, 240, 245},
208 | "lavenderblush2": {238, 224, 229},
209 | "lavenderblush3": {205, 193, 197},
210 | "lavenderblush4": {139, 131, 134},
211 | "mistyrose1": {255, 228, 225},
212 | "mistyrose2": {238, 213, 210},
213 | "mistyrose3": {205, 183, 181},
214 | "mistyrose4": {139, 125, 123},
215 | "azure1": {240, 255, 255},
216 | "azure2": {224, 238, 238},
217 | "azure3": {193, 205, 205},
218 | "azure4": {131, 139, 139},
219 | "slateblue1": {131, 111, 255},
220 | "slateblue2": {122, 103, 238},
221 | "slateblue3": {105, 89, 205},
222 | "slateblue4": {71, 60, 139},
223 | "royalblue1": {72, 118, 255},
224 | "royalblue2": {67, 110, 238},
225 | "royalblue3": {58, 95, 205},
226 | "royalblue4": {39, 64, 139},
227 | "blue1": {0, 0, 255},
228 | "blue2": {0, 0, 238},
229 | "blue3": {0, 0, 205},
230 | "blue4": {0, 0, 139},
231 | "dodgerblue1": {30, 144, 255},
232 | "dodgerblue2": {28, 134, 238},
233 | "dodgerblue3": {24, 116, 205},
234 | "dodgerblue4": {16, 78, 139},
235 | "steelblue1": {99, 184, 255},
236 | "steelblue2": {92, 172, 238},
237 | "steelblue3": {79, 148, 205},
238 | "steelblue4": {54, 100, 139},
239 | "deepskyblue1": {0, 191, 255},
240 | "deepskyblue2": {0, 178, 238},
241 | "deepskyblue3": {0, 154, 205},
242 | "deepskyblue4": {0, 104, 139},
243 | "skyblue1": {135, 206, 255},
244 | "skyblue2": {126, 192, 238},
245 | "skyblue3": {108, 166, 205},
246 | "skyblue4": {74, 112, 139},
247 | "lightskyblue1": {176, 226, 255},
248 | "lightskyblue2": {164, 211, 238},
249 | "lightskyblue3": {141, 182, 205},
250 | "lightskyblue4": {96, 123, 139},
251 | "slategray1": {198, 226, 255},
252 | "slategray2": {185, 211, 238},
253 | "slategray3": {159, 182, 205},
254 | "slategray4": {108, 123, 139},
255 | "lightsteelblue1": {202, 225, 255},
256 | "lightsteelblue2": {188, 210, 238},
257 | "lightsteelblue3": {162, 181, 205},
258 | "lightsteelblue4": {110, 123, 139},
259 | "lightblue1": {191, 239, 255},
260 | "lightblue2": {178, 223, 238},
261 | "lightblue3": {154, 192, 205},
262 | "lightblue4": {104, 131, 139},
263 | "lightcyan1": {224, 255, 255},
264 | "lightcyan2": {209, 238, 238},
265 | "lightcyan3": {180, 205, 205},
266 | "lightcyan4": {122, 139, 139},
267 | "paleturquoise1": {187, 255, 255},
268 | "paleturquoise2": {174, 238, 238},
269 | "paleturquoise3": {150, 205, 205},
270 | "paleturquoise4": {102, 139, 139},
271 | "cadetblue1": {152, 245, 255},
272 | "cadetblue2": {142, 229, 238},
273 | "cadetblue3": {122, 197, 205},
274 | "cadetblue4": {83, 134, 139},
275 | "turquoise1": {0, 245, 255},
276 | "turquoise2": {0, 229, 238},
277 | "turquoise3": {0, 197, 205},
278 | "turquoise4": {0, 134, 139},
279 | "cyan1": {0, 255, 255},
280 | "cyan2": {0, 238, 238},
281 | "cyan3": {0, 205, 205},
282 | "cyan4": {0, 139, 139},
283 | "darkslategray1": {151, 255, 255},
284 | "darkslategray2": {141, 238, 238},
285 | "darkslategray3": {121, 205, 205},
286 | "darkslategray4": {82, 139, 139},
287 | "aquamarine1": {127, 255, 212},
288 | "aquamarine2": {118, 238, 198},
289 | "aquamarine3": {102, 205, 170},
290 | "aquamarine4": {69, 139, 116},
291 | "darkseagreen1": {193, 255, 193},
292 | "darkseagreen2": {180, 238, 180},
293 | "darkseagreen3": {155, 205, 155},
294 | "darkseagreen4": {105, 139, 105},
295 | "seagreen1": {84, 255, 159},
296 | "seagreen2": {78, 238, 148},
297 | "seagreen3": {67, 205, 128},
298 | "seagreen4": {46, 139, 87},
299 | "palegreen1": {154, 255, 154},
300 | "palegreen2": {144, 238, 144},
301 | "palegreen3": {124, 205, 124},
302 | "palegreen4": {84, 139, 84},
303 | "springgreen1": {0, 255, 127},
304 | "springgreen2": {0, 238, 118},
305 | "springgreen3": {0, 205, 102},
306 | "springgreen4": {0, 139, 69},
307 | "green1": {0, 255, 0},
308 | "green2": {0, 238, 0},
309 | "green3": {0, 205, 0},
310 | "green4": {0, 139, 0},
311 | "chartreuse1": {127, 255, 0},
312 | "chartreuse2": {118, 238, 0},
313 | "chartreuse3": {102, 205, 0},
314 | "chartreuse4": {69, 139, 0},
315 | "olivedrab1": {192, 255, 62},
316 | "olivedrab2": {179, 238, 58},
317 | "olivedrab3": {154, 205, 50},
318 | "olivedrab4": {105, 139, 34},
319 | "darkolivegreen1": {202, 255, 112},
320 | "darkolivegreen2": {188, 238, 104},
321 | "darkolivegreen3": {162, 205, 90},
322 | "darkolivegreen4": {110, 139, 61},
323 | "khaki1": {255, 246, 143},
324 | "khaki2": {238, 230, 133},
325 | "khaki3": {205, 198, 115},
326 | "khaki4": {139, 134, 78},
327 | "lightgoldenrod1": {255, 236, 139},
328 | "lightgoldenrod2": {238, 220, 130},
329 | "lightgoldenrod3": {205, 190, 112},
330 | "lightgoldenrod4": {139, 129, 76},
331 | "lightyellow1": {255, 255, 224},
332 | "lightyellow2": {238, 238, 209},
333 | "lightyellow3": {205, 205, 180},
334 | "lightyellow4": {139, 139, 122},
335 | "yellow1": {255, 255, 0},
336 | "yellow2": {238, 238, 0},
337 | "yellow3": {205, 205, 0},
338 | "yellow4": {139, 139, 0},
339 | "gold1": {255, 215, 0},
340 | "gold2": {238, 201, 0},
341 | "gold3": {205, 173, 0},
342 | "gold4": {139, 117, 0},
343 | "goldenrod1": {255, 193, 37},
344 | "goldenrod2": {238, 180, 34},
345 | "goldenrod3": {205, 155, 29},
346 | "goldenrod4": {139, 105, 20},
347 | "darkgoldenrod1": {255, 185, 15},
348 | "darkgoldenrod2": {238, 173, 14},
349 | "darkgoldenrod3": {205, 149, 12},
350 | "darkgoldenrod4": {139, 101, 8},
351 | "rosybrown1": {255, 193, 193},
352 | "rosybrown2": {238, 180, 180},
353 | "rosybrown3": {205, 155, 155},
354 | "rosybrown4": {139, 105, 105},
355 | "indianred1": {255, 106, 106},
356 | "indianred2": {238, 99, 99},
357 | "indianred3": {205, 85, 85},
358 | "indianred4": {139, 58, 58},
359 | "sienna1": {255, 130, 71},
360 | "sienna2": {238, 121, 66},
361 | "sienna3": {205, 104, 57},
362 | "sienna4": {139, 71, 38},
363 | "burlywood1": {255, 211, 155},
364 | "burlywood2": {238, 197, 145},
365 | "burlywood3": {205, 170, 125},
366 | "burlywood4": {139, 115, 85},
367 | "wheat1": {255, 231, 186},
368 | "wheat2": {238, 216, 174},
369 | "wheat3": {205, 186, 150},
370 | "wheat4": {139, 126, 102},
371 | "tan1": {255, 165, 79},
372 | "tan2": {238, 154, 73},
373 | "tan3": {205, 133, 63},
374 | "tan4": {139, 90, 43},
375 | "chocolate1": {255, 127, 36},
376 | "chocolate2": {238, 118, 33},
377 | "chocolate3": {205, 102, 29},
378 | "chocolate4": {139, 69, 19},
379 | "firebrick1": {255, 48, 48},
380 | "firebrick2": {238, 44, 44},
381 | "firebrick3": {205, 38, 38},
382 | "firebrick4": {139, 26, 26},
383 | "brown1": {255, 64, 64},
384 | "brown2": {238, 59, 59},
385 | "brown3": {205, 51, 51},
386 | "brown4": {139, 35, 35},
387 | "salmon1": {255, 140, 105},
388 | "salmon2": {238, 130, 98},
389 | "salmon3": {205, 112, 84},
390 | "salmon4": {139, 76, 57},
391 | "lightsalmon1": {255, 160, 122},
392 | "lightsalmon2": {238, 149, 114},
393 | "lightsalmon3": {205, 129, 98},
394 | "lightsalmon4": {139, 87, 66},
395 | "orange1": {255, 165, 0},
396 | "orange2": {238, 154, 0},
397 | "orange3": {205, 133, 0},
398 | "orange4": {139, 90, 0},
399 | "darkorange1": {255, 127, 0},
400 | "darkorange2": {238, 118, 0},
401 | "darkorange3": {205, 102, 0},
402 | "darkorange4": {139, 69, 0},
403 | "coral1": {255, 114, 86},
404 | "coral2": {238, 106, 80},
405 | "coral3": {205, 91, 69},
406 | "coral4": {139, 62, 47},
407 | "tomato1": {255, 99, 71},
408 | "tomato2": {238, 92, 66},
409 | "tomato3": {205, 79, 57},
410 | "tomato4": {139, 54, 38},
411 | "orangered1": {255, 69, 0},
412 | "orangered2": {238, 64, 0},
413 | "orangered3": {205, 55, 0},
414 | "orangered4": {139, 37, 0},
415 | "red1": {255, 0, 0},
416 | "red2": {238, 0, 0},
417 | "red3": {205, 0, 0},
418 | "red4": {139, 0, 0},
419 | "deeppink1": {255, 20, 147},
420 | "deeppink2": {238, 18, 137},
421 | "deeppink3": {205, 16, 118},
422 | "deeppink4": {139, 10, 80},
423 | "hotpink1": {255, 110, 180},
424 | "hotpink2": {238, 106, 167},
425 | "hotpink3": {205, 96, 144},
426 | "hotpink4": {139, 58, 98},
427 | "pink1": {255, 181, 197},
428 | "pink2": {238, 169, 184},
429 | "pink3": {205, 145, 158},
430 | "pink4": {139, 99, 108},
431 | "lightpink1": {255, 174, 185},
432 | "lightpink2": {238, 162, 173},
433 | "lightpink3": {205, 140, 149},
434 | "lightpink4": {139, 95, 101},
435 | "palevioletred1": {255, 130, 171},
436 | "palevioletred2": {238, 121, 159},
437 | "palevioletred3": {205, 104, 137},
438 | "palevioletred4": {139, 71, 93},
439 | "maroon1": {255, 52, 179},
440 | "maroon2": {238, 48, 167},
441 | "maroon3": {205, 41, 144},
442 | "maroon4": {139, 28, 98},
443 | "violetred1": {255, 62, 150},
444 | "violetred2": {238, 58, 140},
445 | "violetred3": {205, 50, 120},
446 | "violetred4": {139, 34, 82},
447 | "magenta1": {255, 0, 255},
448 | "magenta2": {238, 0, 238},
449 | "magenta3": {205, 0, 205},
450 | "magenta4": {139, 0, 139},
451 | "orchid1": {255, 131, 250},
452 | "orchid2": {238, 122, 233},
453 | "orchid3": {205, 105, 201},
454 | "orchid4": {139, 71, 137},
455 | "plum1": {255, 187, 255},
456 | "plum2": {238, 174, 238},
457 | "plum3": {205, 150, 205},
458 | "plum4": {139, 102, 139},
459 | "mediumorchid1": {224, 102, 255},
460 | "mediumorchid2": {209, 95, 238},
461 | "mediumorchid3": {180, 82, 205},
462 | "mediumorchid4": {122, 55, 139},
463 | "darkorchid1": {191, 62, 255},
464 | "darkorchid2": {178, 58, 238},
465 | "darkorchid3": {154, 50, 205},
466 | "darkorchid4": {104, 34, 139},
467 | "purple1": {155, 48, 255},
468 | "purple2": {145, 44, 238},
469 | "purple3": {125, 38, 205},
470 | "purple4": {85, 26, 139},
471 | "mediumpurple1": {171, 130, 255},
472 | "mediumpurple2": {159, 121, 238},
473 | "mediumpurple3": {137, 104, 205},
474 | "mediumpurple4": {93, 71, 139},
475 | "thistle1": {255, 225, 255},
476 | "thistle2": {238, 210, 238},
477 | "thistle3": {205, 181, 205},
478 | "thistle4": {139, 123, 139},
479 | "gray0": {0, 0, 0},
480 | "grey0": {0, 0, 0},
481 | "gray1": {3, 3, 3},
482 | "grey1": {3, 3, 3},
483 | "gray2": {5, 5, 5},
484 | "grey2": {5, 5, 5},
485 | "gray3": {8, 8, 8},
486 | "grey3": {8, 8, 8},
487 | "gray4": {10, 10, 10},
488 | "grey4": {10, 10, 10},
489 | "gray5": {13, 13, 13},
490 | "grey5": {13, 13, 13},
491 | "gray6": {15, 15, 15},
492 | "grey6": {15, 15, 15},
493 | "gray7": {18, 18, 18},
494 | "grey7": {18, 18, 18},
495 | "gray8": {20, 20, 20},
496 | "grey8": {20, 20, 20},
497 | "gray9": {23, 23, 23},
498 | "grey9": {23, 23, 23},
499 | "gray10": {26, 26, 26},
500 | "grey10": {26, 26, 26},
501 | "gray11": {28, 28, 28},
502 | "grey11": {28, 28, 28},
503 | "gray12": {31, 31, 31},
504 | "grey12": {31, 31, 31},
505 | "gray13": {33, 33, 33},
506 | "grey13": {33, 33, 33},
507 | "gray14": {36, 36, 36},
508 | "grey14": {36, 36, 36},
509 | "gray15": {38, 38, 38},
510 | "grey15": {38, 38, 38},
511 | "gray16": {41, 41, 41},
512 | "grey16": {41, 41, 41},
513 | "gray17": {43, 43, 43},
514 | "grey17": {43, 43, 43},
515 | "gray18": {46, 46, 46},
516 | "grey18": {46, 46, 46},
517 | "gray19": {48, 48, 48},
518 | "grey19": {48, 48, 48},
519 | "gray20": {51, 51, 51},
520 | "grey20": {51, 51, 51},
521 | "gray21": {54, 54, 54},
522 | "grey21": {54, 54, 54},
523 | "gray22": {56, 56, 56},
524 | "grey22": {56, 56, 56},
525 | "gray23": {59, 59, 59},
526 | "grey23": {59, 59, 59},
527 | "gray24": {61, 61, 61},
528 | "grey24": {61, 61, 61},
529 | "gray25": {64, 64, 64},
530 | "grey25": {64, 64, 64},
531 | "gray26": {66, 66, 66},
532 | "grey26": {66, 66, 66},
533 | "gray27": {69, 69, 69},
534 | "grey27": {69, 69, 69},
535 | "gray28": {71, 71, 71},
536 | "grey28": {71, 71, 71},
537 | "gray29": {74, 74, 74},
538 | "grey29": {74, 74, 74},
539 | "gray30": {77, 77, 77},
540 | "grey30": {77, 77, 77},
541 | "gray31": {79, 79, 79},
542 | "grey31": {79, 79, 79},
543 | "gray32": {82, 82, 82},
544 | "grey32": {82, 82, 82},
545 | "gray33": {84, 84, 84},
546 | "grey33": {84, 84, 84},
547 | "gray34": {87, 87, 87},
548 | "grey34": {87, 87, 87},
549 | "gray35": {89, 89, 89},
550 | "grey35": {89, 89, 89},
551 | "gray36": {92, 92, 92},
552 | "grey36": {92, 92, 92},
553 | "gray37": {94, 94, 94},
554 | "grey37": {94, 94, 94},
555 | "gray38": {97, 97, 97},
556 | "grey38": {97, 97, 97},
557 | "gray39": {99, 99, 99},
558 | "grey39": {99, 99, 99},
559 | "gray40": {102, 102, 102},
560 | "grey40": {102, 102, 102},
561 | "gray41": {105, 105, 105},
562 | "grey41": {105, 105, 105},
563 | "gray42": {107, 107, 107},
564 | "grey42": {107, 107, 107},
565 | "gray43": {110, 110, 110},
566 | "grey43": {110, 110, 110},
567 | "gray44": {112, 112, 112},
568 | "grey44": {112, 112, 112},
569 | "gray45": {115, 115, 115},
570 | "grey45": {115, 115, 115},
571 | "gray46": {117, 117, 117},
572 | "grey46": {117, 117, 117},
573 | "gray47": {120, 120, 120},
574 | "grey47": {120, 120, 120},
575 | "gray48": {122, 122, 122},
576 | "grey48": {122, 122, 122},
577 | "gray49": {125, 125, 125},
578 | "grey49": {125, 125, 125},
579 | "gray50": {127, 127, 127},
580 | "grey50": {127, 127, 127},
581 | "gray51": {130, 130, 130},
582 | "grey51": {130, 130, 130},
583 | "gray52": {133, 133, 133},
584 | "grey52": {133, 133, 133},
585 | "gray53": {135, 135, 135},
586 | "grey53": {135, 135, 135},
587 | "gray54": {138, 138, 138},
588 | "grey54": {138, 138, 138},
589 | "gray55": {140, 140, 140},
590 | "grey55": {140, 140, 140},
591 | "gray56": {143, 143, 143},
592 | "grey56": {143, 143, 143},
593 | "gray57": {145, 145, 145},
594 | "grey57": {145, 145, 145},
595 | "gray58": {148, 148, 148},
596 | "grey58": {148, 148, 148},
597 | "gray59": {150, 150, 150},
598 | "grey59": {150, 150, 150},
599 | "gray60": {153, 153, 153},
600 | "grey60": {153, 153, 153},
601 | "gray61": {156, 156, 156},
602 | "grey61": {156, 156, 156},
603 | "gray62": {158, 158, 158},
604 | "grey62": {158, 158, 158},
605 | "gray63": {161, 161, 161},
606 | "grey63": {161, 161, 161},
607 | "gray64": {163, 163, 163},
608 | "grey64": {163, 163, 163},
609 | "gray65": {166, 166, 166},
610 | "grey65": {166, 166, 166},
611 | "gray66": {168, 168, 168},
612 | "grey66": {168, 168, 168},
613 | "gray67": {171, 171, 171},
614 | "grey67": {171, 171, 171},
615 | "gray68": {173, 173, 173},
616 | "grey68": {173, 173, 173},
617 | "gray69": {176, 176, 176},
618 | "grey69": {176, 176, 176},
619 | "gray70": {179, 179, 179},
620 | "grey70": {179, 179, 179},
621 | "gray71": {181, 181, 181},
622 | "grey71": {181, 181, 181},
623 | "gray72": {184, 184, 184},
624 | "grey72": {184, 184, 184},
625 | "gray73": {186, 186, 186},
626 | "grey73": {186, 186, 186},
627 | "gray74": {189, 189, 189},
628 | "grey74": {189, 189, 189},
629 | "gray75": {191, 191, 191},
630 | "grey75": {191, 191, 191},
631 | "gray76": {194, 194, 194},
632 | "grey76": {194, 194, 194},
633 | "gray77": {196, 196, 196},
634 | "grey77": {196, 196, 196},
635 | "gray78": {199, 199, 199},
636 | "grey78": {199, 199, 199},
637 | "gray79": {201, 201, 201},
638 | "grey79": {201, 201, 201},
639 | "gray80": {204, 204, 204},
640 | "grey80": {204, 204, 204},
641 | "gray81": {207, 207, 207},
642 | "grey81": {207, 207, 207},
643 | "gray82": {209, 209, 209},
644 | "grey82": {209, 209, 209},
645 | "gray83": {212, 212, 212},
646 | "grey83": {212, 212, 212},
647 | "gray84": {214, 214, 214},
648 | "grey84": {214, 214, 214},
649 | "gray85": {217, 217, 217},
650 | "grey85": {217, 217, 217},
651 | "gray86": {219, 219, 219},
652 | "grey86": {219, 219, 219},
653 | "gray87": {222, 222, 222},
654 | "grey87": {222, 222, 222},
655 | "gray88": {224, 224, 224},
656 | "grey88": {224, 224, 224},
657 | "gray89": {227, 227, 227},
658 | "grey89": {227, 227, 227},
659 | "gray90": {229, 229, 229},
660 | "grey90": {229, 229, 229},
661 | "gray91": {232, 232, 232},
662 | "grey91": {232, 232, 232},
663 | "gray92": {235, 235, 235},
664 | "grey92": {235, 235, 235},
665 | "gray93": {237, 237, 237},
666 | "grey93": {237, 237, 237},
667 | "gray94": {240, 240, 240},
668 | "grey94": {240, 240, 240},
669 | "gray95": {242, 242, 242},
670 | "grey95": {242, 242, 242},
671 | "gray96": {245, 245, 245},
672 | "grey96": {245, 245, 245},
673 | "gray97": {247, 247, 247},
674 | "grey97": {247, 247, 247},
675 | "gray98": {250, 250, 250},
676 | "grey98": {250, 250, 250},
677 | "gray99": {252, 252, 252},
678 | "grey99": {252, 252, 252},
679 | "gray100": {255, 255, 255},
680 | "grey100": {255, 255, 255},
681 | "darkgrey": {169, 169, 169},
682 | "darkgray": {169, 169, 169},
683 | "darkblue": {0, 0, 139},
684 | "darkcyan": {0, 139, 139},
685 | "darkmagenta": {139, 0, 139},
686 | "darkred": {139, 0, 0},
687 | "lightgreen": {144, 238, 144},
688 | "crimson": {220, 20, 60},
689 | "indigo": {75, 0, 130},
690 | "olive": {128, 128, 0},
691 | "rebeccapurple": {102, 51, 153},
692 | "silver": {192, 192, 192},
693 | "teal": {0, 128, 128},
694 | }
695 |
--------------------------------------------------------------------------------