| // 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 |
| |
| import ( |
| "gonum.org/v1/gonum/graph" |
| "gonum.org/v1/gonum/graph/path" |
| "gonum.org/v1/gonum/mat" |
| "gonum.org/v1/gonum/spatial/r2" |
| "gonum.org/v1/gonum/stat/mds" |
| ) |
| |
| // IsomapR2 implements a graph layout algorithm based on the Isomap |
| // non-linear dimensionality reduction method. Coordinates of nodes |
| // are computed by finding a Torgerson multidimensional scaling of |
| // the shortest path distances between all pairs of node in the graph. |
| // The all pair shortest path distances are calculated using the |
| // Floyd-Warshall algorithm and so IsomapR2 will not scale to large |
| // graphs. Graphs with more than one connected component cannot be |
| // laid out by IsomapR2. |
| type IsomapR2 struct{} |
| |
| // Update is the IsomapR2 spatial graph update function. |
| func (IsomapR2) Update(g graph.Graph, layout LayoutR2) bool { |
| nodes := graph.NodesOf(g.Nodes()) |
| v := isomap(g, nodes, 2) |
| if v == nil { |
| return false |
| } |
| |
| // FIXME(kortschak): The Layout types do not have the capacity to |
| // be cleared in the current API. Is this a problem? I don't know |
| // at this stage. It might be if the layout is reused between graphs. |
| // Someone may do this. |
| |
| for i, n := range nodes { |
| layout.SetCoord2(n.ID(), r2.Vec{X: v.At(i, 0), Y: v.At(i, 1)}) |
| } |
| return false |
| } |
| |
| func isomap(g graph.Graph, nodes []graph.Node, dims int) *mat.Dense { |
| p, ok := path.FloydWarshall(g) |
| if !ok { |
| return nil |
| } |
| |
| dist := mat.NewSymDense(len(nodes), nil) |
| for i, u := range nodes { |
| for j, v := range nodes { |
| dist.SetSym(i, j, p.Weight(u.ID(), v.ID())) |
| } |
| } |
| var v mat.Dense |
| k, _ := mds.TorgersonScaling(&v, nil, dist) |
| if k < dims { |
| return nil |
| } |
| |
| return &v |
| } |