Repository: gonum/gonum
Pull request #1578 (2021-03-09 00:03:47)

A+C: add Matthew Connelly
Added myself to CONTRIBUTORS and AUTHORS.


Patch:
diff --git a/AUTHORS b/AUTHORS
index abe946d6..d50eb792 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -68,6 +68,7 @@ Marco Leogrande <dark.knight.ita@gmail.com>
 Mark Canning <argusdusty@gmail.com>
 Mark Skilbeck <markskilbeck@gmail.com>
 Martin Diz <github@martindiz.com.ar>
+Matthew Connelly <matthew.b.connelly@gmail.com>
 Matthieu Di Mercurio <matthieu.dimercurio@gmail.com>
 Max Halford <maxhalford25@gmail.com>
 Microsoft Corporation
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index d0f6d37a..810e6e41 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -76,6 +76,7 @@ Marco Leogrande <dark.knight.ita@gmail.com>
 Mark Canning <argusdusty@gmail.com>
 Mark Skilbeck <markskilbeck@gmail.com>
 Martin Diz <github@martindiz.com.ar>
+Matthew Connelly <matthew.b.connelly@gmail.com>
 Matthieu Di Mercurio <matthieu.dimercurio@gmail.com>
 Max Halford <maxhalford25@gmail.com>
 MinJae Kwon <k239507@gmail.com>


Test patch:
diff --git a/graph/layout/eades_example_test.go b/graph/layout/eades_example_test.go
new file mode 100644
index 00000000..6fad2438
--- /dev/null
+++ b/graph/layout/eades_example_test.go
@@ -0,0 +1,185 @@
+// Copyright ©2021 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/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, err := plot.New()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// 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, err := vg.MakeFont(plot.DefaultFont, vg.Points(18))
+	if err != nil {
+		panic(err)
+	}
+	for i := range l.TextStyle {
+		l.TextStyle[i] = draw.TextStyle{Font: fnt, XAlign: draw.XCenter, YAlign: -0.25}
+	}
+
+	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)
+}