github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/graph/layout/eades_example_test.go (about) 1 // Copyright ©2019 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file 4 5 package layout_test 6 7 import ( 8 "fmt" 9 "image/color" 10 "log" 11 "math" 12 13 "github.com/jingcheng-WU/gonum/graph/layout" 14 "github.com/jingcheng-WU/gonum/graph/simple" 15 "gonum.org/v1/plot" 16 "gonum.org/v1/plot/font" 17 "gonum.org/v1/plot/plotter" 18 "gonum.org/v1/plot/vg" 19 "gonum.org/v1/plot/vg/draw" 20 ) 21 22 func ExampleEadesR2() { 23 // Make a simple graph and render it as a PNG 24 // with the EadesR2 force-directed layout. 25 g := simple.NewUndirectedGraph() 26 const n = 6 27 for i := 0; i < n; i++ { 28 for j := i + 1; j < n; j++ { 29 g.SetEdge(g.NewEdge(simple.Node(i), simple.Node(j))) 30 } 31 } 32 33 // Use the Eades layout algorithm with reasonable defaults. 34 eades := layout.EadesR2{Repulsion: 1, Rate: 0.05, Updates: 30, Theta: 0.2} 35 36 // Make a layout optimizer with the target graph and update function. 37 optimizer := layout.NewOptimizerR2(g, eades.Update) 38 39 // Perform layout optimization. 40 for optimizer.Update() { 41 } 42 43 p := plot.New() 44 45 // Add to plot. 46 p.Add(render{optimizer}) 47 p.HideAxes() 48 49 // Render graph on save. 50 err := p.Save(10*vg.Centimeter, 10*vg.Centimeter, "k6_eades.png") 51 if err != nil { 52 log.Fatal(err) 53 } 54 } 55 56 const radius = vg.Length(15) 57 58 // render implements the plot.Plotter interface for graphs. 59 type render struct { 60 layout.GraphR2 61 } 62 63 func (p render) Plot(c draw.Canvas, plt *plot.Plot) { 64 nodes := p.GraphR2.Nodes() 65 if nodes.Len() == 0 { 66 return 67 } 68 var ( 69 xys plotter.XYs 70 ids []string 71 ) 72 if nodes.Len() >= 0 { 73 xys = make(plotter.XYs, 0, nodes.Len()) 74 ids = make([]string, 0, nodes.Len()) 75 } 76 for nodes.Next() { 77 u := nodes.Node() 78 uid := u.ID() 79 ur2 := p.GraphR2.LayoutNodeR2(uid) 80 xys = append(xys, plotter.XY(ur2.Coord2)) 81 ids = append(ids, fmt.Sprint(uid)) 82 to := p.GraphR2.From(uid) 83 for to.Next() { 84 v := to.Node() 85 vid := v.ID() 86 vr2 := p.GraphR2.LayoutNodeR2(vid) 87 88 l, err := plotter.NewLine(plotter.XYs{plotter.XY(ur2.Coord2), plotter.XY(vr2.Coord2)}) 89 if err != nil { 90 panic(err) 91 } 92 l.Plot(c, plt) 93 if err != nil { 94 panic(err) 95 } 96 } 97 } 98 99 n, err := plotter.NewScatter(xys) 100 if err != nil { 101 panic(err) 102 } 103 n.GlyphStyle.Shape = nodeGlyph{} 104 n.GlyphStyle.Radius = radius 105 n.Plot(c, plt) 106 107 l, err := plotter.NewLabels(plotter.XYLabels{XYs: xys, Labels: ids}) 108 if err != nil { 109 panic(err) 110 } 111 fnt := font.From(plot.DefaultFont, 18) 112 for i := range l.TextStyle { 113 l.TextStyle[i] = draw.TextStyle{ 114 Font: fnt, Handler: plot.DefaultTextHandler, 115 XAlign: draw.XCenter, YAlign: -0.4, 116 } 117 } 118 119 l.Plot(c, plt) 120 } 121 122 // DataRange returns the minimum and maximum X and Y values. 123 func (p render) DataRange() (xmin, xmax, ymin, ymax float64) { 124 nodes := p.GraphR2.Nodes() 125 if nodes.Len() == 0 { 126 return 127 } 128 var xys plotter.XYs 129 if nodes.Len() >= 0 { 130 xys = make(plotter.XYs, 0, nodes.Len()) 131 } 132 for nodes.Next() { 133 u := nodes.Node() 134 uid := u.ID() 135 ur2 := p.GraphR2.LayoutNodeR2(uid) 136 xys = append(xys, plotter.XY(ur2.Coord2)) 137 } 138 return plotter.XYRange(xys) 139 } 140 141 // GlyphBoxes returns a slice of plot.GlyphBoxes, implementing the 142 // plot.GlyphBoxer interface. 143 func (p render) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox { 144 nodes := p.GraphR2.Nodes() 145 if nodes.Len() == 0 { 146 return nil 147 } 148 var b []plot.GlyphBox 149 if nodes.Len() >= 0 { 150 b = make([]plot.GlyphBox, 0, nodes.Len()) 151 } 152 for i := 0; nodes.Next(); i++ { 153 u := nodes.Node() 154 uid := u.ID() 155 ur2 := p.GraphR2.LayoutNodeR2(uid) 156 157 b = append(b, plot.GlyphBox{}) 158 b[i].X = plt.X.Norm(ur2.Coord2.X) 159 b[i].Y = plt.Y.Norm(ur2.Coord2.Y) 160 r := radius 161 b[i].Rectangle = vg.Rectangle{ 162 Min: vg.Point{X: -r, Y: -r}, 163 Max: vg.Point{X: +r, Y: +r}, 164 } 165 } 166 return b 167 } 168 169 // nodeGlyph is a glyph that draws a filled circle. 170 type nodeGlyph struct{} 171 172 // DrawGlyph implements the GlyphDrawer interface. 173 func (nodeGlyph) DrawGlyph(c *draw.Canvas, sty draw.GlyphStyle, pt vg.Point) { 174 var p vg.Path 175 c.Push() 176 c.SetColor(color.White) 177 p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y}) 178 p.Arc(pt, sty.Radius, 0, 2*math.Pi) 179 p.Close() 180 c.Fill(p) 181 c.Pop() 182 c.Stroke(p) 183 }