// Copyright ©2019 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file package layout_test import ( "fmt" "image/color" "log" "math" "gonum.org/v1/gonum/graph/layout" "gonum.org/v1/gonum/graph/simple" "gonum.org/v1/plot" "gonum.org/v1/plot/font" "gonum.org/v1/plot/plotter" "gonum.org/v1/plot/vg" "gonum.org/v1/plot/vg/draw" ) func ExampleEadesR2() { // Make a simple graph and render it as a PNG // with the EadesR2 force-directed layout. g := simple.NewUndirectedGraph() const n = 6 for i := 0; i < n; i++ { for j := i + 1; j < n; j++ { g.SetEdge(g.NewEdge(simple.Node(i), simple.Node(j))) } } // Use the Eades layout algorithm with reasonable defaults. eades := layout.EadesR2{Repulsion: 1, Rate: 0.05, Updates: 30, Theta: 0.2} // Make a layout optimizer with the target graph and update function. optimizer := layout.NewOptimizerR2(g, eades.Update) // Perform layout optimization. for optimizer.Update() { } p := plot.New() // Add to plot. p.Add(render{optimizer}) p.HideAxes() // Render graph on save. err := p.Save(10*vg.Centimeter, 10*vg.Centimeter, "k6_eades.png") if err != nil { log.Fatal(err) } } const radius = vg.Length(15) // render implements the plot.Plotter interface for graphs. type render struct { layout.GraphR2 } func (p render) Plot(c draw.Canvas, plt *plot.Plot) { nodes := p.GraphR2.Nodes() if nodes.Len() == 0 { return } var ( xys plotter.XYs ids []string ) if nodes.Len() >= 0 { xys = make(plotter.XYs, 0, nodes.Len()) ids = make([]string, 0, nodes.Len()) } for nodes.Next() { u := nodes.Node() uid := u.ID() ur2 := p.GraphR2.LayoutNodeR2(uid) xys = append(xys, plotter.XY(ur2.Coord2)) ids = append(ids, fmt.Sprint(uid)) to := p.GraphR2.From(uid) for to.Next() { v := to.Node() vid := v.ID() vr2 := p.GraphR2.LayoutNodeR2(vid) l, err := plotter.NewLine(plotter.XYs{plotter.XY(ur2.Coord2), plotter.XY(vr2.Coord2)}) if err != nil { panic(err) } l.Plot(c, plt) if err != nil { panic(err) } } } n, err := plotter.NewScatter(xys) if err != nil { panic(err) } n.GlyphStyle.Shape = nodeGlyph{} n.GlyphStyle.Radius = radius n.Plot(c, plt) l, err := plotter.NewLabels(plotter.XYLabels{XYs: xys, Labels: ids}) if err != nil { panic(err) } fnt := font.From(plot.DefaultFont, 18) for i := range l.TextStyle { l.TextStyle[i] = draw.TextStyle{ Font: fnt, Handler: plot.DefaultTextHandler, XAlign: draw.XCenter, YAlign: -0.4, } } l.Plot(c, plt) } // DataRange returns the minimum and maximum X and Y values. func (p render) DataRange() (xmin, xmax, ymin, ymax float64) { nodes := p.GraphR2.Nodes() if nodes.Len() == 0 { return } var xys plotter.XYs if nodes.Len() >= 0 { xys = make(plotter.XYs, 0, nodes.Len()) } for nodes.Next() { u := nodes.Node() uid := u.ID() ur2 := p.GraphR2.LayoutNodeR2(uid) xys = append(xys, plotter.XY(ur2.Coord2)) } return plotter.XYRange(xys) } // GlyphBoxes returns a slice of plot.GlyphBoxes, implementing the // plot.GlyphBoxer interface. func (p render) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox { nodes := p.GraphR2.Nodes() if nodes.Len() == 0 { return nil } var b []plot.GlyphBox if nodes.Len() >= 0 { b = make([]plot.GlyphBox, 0, nodes.Len()) } for i := 0; nodes.Next(); i++ { u := nodes.Node() uid := u.ID() ur2 := p.GraphR2.LayoutNodeR2(uid) b = append(b, plot.GlyphBox{}) b[i].X = plt.X.Norm(ur2.Coord2.X) b[i].Y = plt.Y.Norm(ur2.Coord2.Y) r := radius b[i].Rectangle = vg.Rectangle{ Min: vg.Point{X: -r, Y: -r}, Max: vg.Point{X: +r, Y: +r}, } } return b } // nodeGlyph is a glyph that draws a filled circle. type nodeGlyph struct{} // DrawGlyph implements the GlyphDrawer interface. func (nodeGlyph) DrawGlyph(c *draw.Canvas, sty draw.GlyphStyle, pt vg.Point) { var p vg.Path c.Push() c.SetColor(color.White) p.Move(vg.Point{X: pt.X + sty.Radius, Y: pt.Y}) p.Arc(pt, sty.Radius, 0, 2*math.Pi) p.Close() c.Fill(p) c.Pop() c.Stroke(p) }