gonum.org/v1/gonum@v0.14.0/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  	"gonum.org/v1/gonum/graph/layout"
    14  	"gonum.org/v1/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  }