github.com/gopherd/gonum@v0.0.4/graph/layout/eades.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 6 7 import ( 8 "math" 9 10 "math/rand" 11 12 "github.com/gopherd/gonum/graph" 13 "github.com/gopherd/gonum/spatial/barneshut" 14 "github.com/gopherd/gonum/spatial/r2" 15 ) 16 17 // EadesR2 implements the graph layout algorithm essentially as 18 // described in "A heuristic for graph drawing", Congressus 19 // numerantium 42:149-160. 20 // The implementation here uses the Barnes-Hut approximation for 21 // global repulsion calculation, and edge weights are considered 22 // when calculating adjacent node attraction. 23 type EadesR2 struct { 24 // Updates is the number of updates to perform. 25 Updates int 26 27 // Repulsion is the strength of the global 28 // repulsive force between nodes in the 29 // layout. It corresponds to C3 in the paper. 30 Repulsion float64 31 32 // Rate is the gradient descent rate. It 33 // corresponds to C4 in the paper. 34 Rate float64 35 36 // Theta is the Barnes-Hut theta constant. 37 Theta float64 38 39 // Src is the source of randomness used 40 // to initialize the nodes' locations. If 41 // Src is nil, the global random number 42 // generator is used. 43 Src rand.Source 44 45 nodes graph.Nodes 46 indexOf map[int64]int 47 48 particles []barneshut.Particle2 49 forces []r2.Vec 50 } 51 52 // Update is the EadesR2 spatial graph update function. 53 func (u *EadesR2) Update(g graph.Graph, layout LayoutR2) bool { 54 if u.Updates <= 0 { 55 return false 56 } 57 u.Updates-- 58 59 if !layout.IsInitialized() { 60 var rnd func() float64 61 if u.Src == nil { 62 rnd = rand.Float64 63 } else { 64 rnd = rand.New(u.Src).Float64 65 } 66 u.nodes = g.Nodes() 67 u.indexOf = make(map[int64]int, u.nodes.Len()) 68 if u.nodes.Len() >= 0 { 69 u.particles = make([]barneshut.Particle2, 0, u.nodes.Len()) 70 } 71 for u.nodes.Next() { 72 id := u.nodes.Node().ID() 73 u.indexOf[id] = len(u.particles) 74 u.particles = append(u.particles, eadesR2Node{id: id, pos: r2.Vec{X: rnd(), Y: rnd()}}) 75 } 76 u.forces = make([]r2.Vec, len(u.particles)) 77 } 78 u.nodes.Reset() 79 80 // Apply global repulsion. 81 plane, err := barneshut.NewPlane(u.particles) 82 if err != nil { 83 return false 84 } 85 var updated bool 86 for i, p := range u.particles { 87 f := r2.Scale(-u.Repulsion, plane.ForceOn(p, u.Theta, barneshut.Gravity2)) 88 // Prevent marginal updates that can be caused by 89 // floating point error when nodes are very far apart. 90 if math.Hypot(f.X, f.Y) > 1e-12 { 91 updated = true 92 } 93 u.forces[i] = f 94 } 95 96 // Handle edge weighting for attraction. 97 var weight func(uid, vid int64) float64 98 if wg, ok := g.(graph.Weighted); ok { 99 if _, ok := g.(graph.Directed); ok { 100 weight = func(xid, yid int64) float64 { 101 var w float64 102 f, ok := wg.Weight(xid, yid) 103 if ok { 104 w += f 105 } 106 r, ok := wg.Weight(yid, xid) 107 if ok { 108 w += r 109 } 110 return w 111 } 112 } else { 113 weight = func(xid, yid int64) float64 { 114 w, ok := wg.Weight(xid, yid) 115 if ok { 116 return w 117 } 118 return 0 119 } 120 } 121 } else { 122 // This is only called when the adjacency is known so just return unit. 123 weight = func(_, _ int64) float64 { return 1 } 124 } 125 126 seen := make(map[[2]int64]bool) 127 for u.nodes.Next() { 128 xid := u.nodes.Node().ID() 129 xidx := u.indexOf[xid] 130 to := g.From(xid) 131 for to.Next() { 132 yid := to.Node().ID() 133 if seen[[2]int64{xid, yid}] { 134 continue 135 } 136 seen[[2]int64{yid, xid}] = true 137 yidx := u.indexOf[yid] 138 139 // Apply adjacent node attraction. 140 v := r2.Sub(u.particles[yidx].Coord2(), u.particles[xidx].Coord2()) 141 f := r2.Scale(weight(xid, yid)*math.Log(math.Hypot(v.X, v.Y)), v) 142 if math.IsInf(f.X, 0) || math.IsInf(f.Y, 0) { 143 return false 144 } 145 if math.Hypot(f.X, f.Y) > 1e-12 { 146 updated = true 147 } 148 u.forces[xidx] = r2.Add(u.forces[xidx], f) 149 u.forces[yidx] = r2.Sub(u.forces[yidx], f) 150 } 151 } 152 153 if !updated { 154 return false 155 } 156 157 rate := u.Rate 158 if rate == 0 { 159 rate = 0.1 160 } 161 for i, f := range u.forces { 162 n := u.particles[i].(eadesR2Node) 163 n.pos = r2.Add(n.pos, r2.Scale(rate, f)) 164 u.particles[i] = n 165 layout.SetCoord2(n.id, n.pos) 166 } 167 return true 168 } 169 170 type eadesR2Node struct { 171 id int64 172 pos r2.Vec 173 } 174 175 func (p eadesR2Node) Coord2() r2.Vec { return p.pos } 176 func (p eadesR2Node) Mass() float64 { return 1 }