Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.VariableInteractionNetworks/HeuristicLab.VariableInteractionNetworks.Views/3.3/Layout/ConstrainedForceDirectedLayout.cs @ 13893

Last change on this file since 13893 was 13893, checked in by bburlacu, 8 years ago

#2288: Simplify and optimize code for cluster identification in ConstrainedForceDirectedLayout.cs. Introduce a TrackBar for adjusting network threshold in the RunCollectionVariableInteractionNetworkView. Minor improvements to the DirectedGraphChart (work in progress).

File size: 11.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Drawing;
25using System.Linq;
26using Cola;
27using HeuristicLab.Core;
28using HeuristicLab.Random;
29using Point = Cola.Point;
30
31namespace HeuristicLab.VariableInteractionNetworks.Views {
32  public class ConstrainedForceDirectedLayout {
33    public enum EdgeRouting : uint {
34      None,
35      Polyline,
36      Orthogonal
37    }
38    private ConstrainedFDLayout layoutEngine;
39    private TopologyNodePtrs topologyNodes;
40    private TopologyEdgePtrs topologyRoutes;
41    private Cola.Doubles edgeLengths;
42    private int topologyNodeCount;
43    private readonly IRandom random = new MersenneTwister();
44    private Dictionary<uint, uint> idMap;
45    private Dictionary<IVertex, int> indexMap;
46    public double IdealEdgeLength { get; set; }
47    public double DefaultEdgeLength { get; set; }
48    public bool PreventOverlaps { get; set; }
49    public int LayoutWidth { get; set; }
50    public int LayoutHeight { get; set; }
51    public bool PerformEdgeRouting { get; set; }
52    public int MinHorizontalSpacing { get; set; }
53    public int MinVerticalSpacing { get; set; }
54    public EdgeRouting EdgeRoutingMode { get; set; }
55
56    public Dictionary<IVertex, PointF> VertexPositions {
57      get {
58        var dict = new Dictionary<IVertex, PointF>();
59        foreach (var pair in indexMap) {
60          var node = topologyNodes[pair.Value];
61          var point = new PointF((float)node.rect.getMinX(), (float)node.rect.getMinY());
62          dict[pair.Key] = point;
63        }
64        return dict;
65      }
66    }
67
68    private List<IArc> arcs;
69    public Dictionary<IArc, PointF[]> ArcRoutes {
70      get {
71        var dict = new Dictionary<IArc, PointF[]>();
72        for (int i = 0; i < arcs.Count; ++i) {
73          var arc = arcs[i];
74          var route = topologyRoutes[i];
75          var vs = new TopologyEdgePointConstPtrs();
76          route.getPath(vs);
77          dict[arc] = vs.Select(x => new PointF((float)x.posX(), (float)x.posY())).ToArray();
78        }
79        return dict;
80      }
81    }
82
83    public ConstrainedForceDirectedLayout() {
84      IdealEdgeLength = 1;
85      DefaultEdgeLength = 200;
86      PreventOverlaps = true;
87      LayoutWidth = 800;
88      LayoutHeight = 600;
89      PerformEdgeRouting = true;
90      MinHorizontalSpacing = 0;
91      MinVerticalSpacing = 0;
92      EdgeRoutingMode = EdgeRouting.None;
93    }
94
95    public void Run(Dictionary<IVertex, RectangleF> nodeShapes) {
96      if (nodeShapes.Count == 0) return;
97      indexMap = new Dictionary<IVertex, int>();
98      idMap = new Dictionary<uint, uint>();
99      var rectangles = new Cola.RectanglePtrs();
100      int i = 0;
101      arcs = new List<IArc>();
102      foreach (var pair in nodeShapes) {
103        var v = pair.Key;
104        arcs.AddRange(v.InArcs);
105        var p = pair.Value;
106
107        var width = p.Size.Width;
108        var height = p.Size.Height;
109
110        var x = random.NextDouble() * (LayoutWidth - width);
111        var y = random.NextDouble() * (LayoutHeight - height);
112        // randomize coordinates before passing them to the layout engine
113        var rect = new Cola.Rectangle(x, x + width, y, y + height);
114        rectangles.Add(rect);
115
116        indexMap[v] = i;
117        ++i;
118      }
119      topologyNodeCount = i;
120      var edges = new ColaEdges(arcs.Select(x => new ColaEdge((uint)indexMap[x.Source], (uint)indexMap[x.Target])).ToList());
121      edgeLengths = new Cola.Doubles(Enumerable.Range(0, edges.Count).Select(x => DefaultEdgeLength).ToList());
122
123      layoutEngine = new ConstrainedFDLayout(rectangles, edges, IdealEdgeLength, PreventOverlaps, edgeLengths);
124      var clusters = IdentifyClusters(rectangles, edges).ToList();
125      if (clusters.Count > 1) {
126        var rootCluster = new RootCluster();
127        foreach (var cluster in clusters) {
128          rootCluster.addChildCluster(cluster);
129        }
130        layoutEngine.setClusterHierarchy(rootCluster);
131      }
132      var ccs = new CompoundConstraintPtrs();
133      for (i = 0; i < rectangles.Count - 1; ++i) {
134        for (int j = i + 1; j < rectangles.Count; ++j) {
135          if (MinHorizontalSpacing > 0)
136            ccs.Add(new SeparationConstraint(Dim.XDIM, (uint)i, (uint)j, MinHorizontalSpacing, false));
137          if (MinVerticalSpacing > 0)
138            ccs.Add(new SeparationConstraint(Dim.YDIM, (uint)i, (uint)j, MinVerticalSpacing, false));
139        }
140      }
141      if (ccs.Any()) layoutEngine.setConstraints(ccs);
142      var topology = new ColaTopologyAddon();
143      layoutEngine.setTopology(topology);
144      layoutEngine.makeFeasible();
145      // store topology after makeFeasible()
146      topology = ColaTopologyAddon.CastToConcreteType(layoutEngine.getTopology());
147      layoutEngine.run();
148
149      topologyNodes = topology.topologyNodes;
150      topologyRoutes = topology.topologyRoutes;
151
152      if (PerformEdgeRouting) {
153        RouteEdges(edges);
154      } else {
155        int id = topologyNodeCount;
156        foreach (var edge in edges) {
157          var eps = new TopologyEdgePointPtrs();
158          var src = topologyNodes[(int)edge.first];
159          var dst = topologyNodes[(int)edge.second];
160          eps.Add(new EdgePoint(src, EdgePoint.RectIntersect.CENTRE));
161          eps.Add(new EdgePoint(dst, EdgePoint.RectIntersect.CENTRE));
162          topologyRoutes.Add(new Edge((uint)id++, IdealEdgeLength, eps));
163        }
164      }
165    }
166
167    private static void ColourNeighbours(IVertex v) {
168      foreach (var arc in v.InArcs) {
169        if (arc.Source.Weight > 0) continue;
170        arc.Source.Weight = v.Weight;
171        ColourNeighbours(arc.Source);
172      }
173      foreach (var arc in v.OutArcs) {
174        if (arc.Target.Weight > 0) continue;
175        arc.Target.Weight = v.Weight;
176        ColourNeighbours(arc.Target);
177      }
178    }
179
180    private static IEnumerable<Cluster> IdentifyClusters(RectanglePtrs rectangles, ColaEdges edges) {
181      // build a graph using the rectangles and the edges
182      var vertices = new List<IVertex>(rectangles.Select(x => new Vertex()));
183      foreach (var e in edges) {
184        var i = (int)e.first;
185        var j = (int)e.second;
186        var arc = new Arc(vertices[i], vertices[j]);
187        vertices[i].AddArc(arc);
188        vertices[j].AddArc(arc);
189      }
190      // group vertices into clusters
191      var clusters = new List<RectangularCluster>();
192      for (int i = 0; i < vertices.Count; ++i) {
193        var v = vertices[i];
194        if (v.Weight > 0) {
195          clusters[(int)Math.Round(v.Weight) - 1].addChildNode((uint)i);
196        } else {
197          var cluster = new RectangularCluster();
198          cluster.addChildNode((uint)i);
199          clusters.Add(cluster);
200          v.Weight = clusters.Count;
201          ColourNeighbours(v);
202        }
203      }
204      return clusters;
205    }
206
207    private void RouteEdges(ColaEdges edges) {
208      // after the layout is done, we need to use a router to route edges
209      RouterFlag rf;
210      switch (EdgeRoutingMode) {
211        case EdgeRouting.Polyline:
212          rf = RouterFlag.PolyLineRouting;
213          break;
214        case EdgeRouting.Orthogonal:
215          rf = RouterFlag.OrthogonalRouting;
216          break;
217        default:
218          rf = RouterFlag.PolyLineRouting;
219          break;
220      }
221      var router = new Router((uint)rf);
222      router.setRoutingParameter(RoutingParameter.shapeBufferDistance, 5);
223      //router.setRoutingParameter(RoutingParameter.idealNudgingDistance, 0);
224      router.UseLeesAlgorithm = true;
225      router.InvisibilityGrph = false;
226      //router.RubberBandRouting = true;
227      var shapes = new List<ShapeRef>();
228      // consider topology routes only for the topologyNodes associated with our objects (the rectangles)
229
230      for (int i = 0; i < topologyNodeCount; ++i) {
231        var r = topologyNodes[i].rect;
232        var poly = new Polygon(4);
233        var xmin = r.getMinX();
234        var xmax = r.getMaxX();
235        var ymin = r.getMinY();
236        var ymax = r.getMaxY();
237
238        poly.ps[0] = new Cola.Point(xmax, ymin);
239        poly.ps[1] = new Cola.Point(xmax, ymax);
240        poly.ps[2] = new Cola.Point(xmin, ymax);
241        poly.ps[3] = new Cola.Point(xmin, ymin);
242        var shapeRef = new ShapeRef(router, poly);
243        idMap[shapeRef.id()] = (uint)i;
244        shapes.Add(shapeRef);
245      }
246      router.processTransaction();
247
248      var connRefs = new List<ConnRef>();
249      for (int i = 0; i < edges.Count; ++i) {
250        var edge = edges[i];
251        var src = topologyNodes[(int)edge.first];
252        var dst = topologyNodes[(int)edge.second];
253        var srcPt = new Cola.Point(src.rect.getCentreX(), src.rect.getCentreY());
254        var dstPt = new Cola.Point(dst.rect.getCentreX(), dst.rect.getCentreY());
255        var connRef = new ConnRef(router, new ConnEnd(srcPt), new ConnEnd(dstPt));
256        router.processTransaction();
257
258        //        var route = connRef.displayRoute().curvedPolyline(10);
259        var route = connRef.displayRoute();
260        var eps = new TopologyEdgePointPtrs(); // edge points
261        eps.Add(new EdgePoint(src, EdgePoint.RectIntersect.CENTRE));
262        for (int j = 1; j < route.size() - 1; ++j) {
263          var p = route.ps[j];
264          var ri = CalculateIntersect(p);
265          if (idMap.ContainsKey(p.id)) {
266            var node = topologyNodes[(int)idMap[p.id]];
267            eps.Add(new EdgePoint(node, ri));
268          } else {
269            // the node is not part of the topology and only represents a point in the route polyline
270            // therefore, we do not add it to the topologyNodes (although it may need to be treated separately in some cases)
271            var id = router.newObjectId();
272            var node = new Node(id, new Cola.Rectangle(p.x, p.x + 1, p.y, p.y + 1));
273            //topologyNodes.Add(node);
274            idMap[id] = id;
275            eps.Add(new EdgePoint(node, ri));
276          }
277        }
278        eps.Add(new EdgePoint(dst, EdgePoint.RectIntersect.CENTRE));
279        topologyRoutes.Add(new Edge((uint)i, IdealEdgeLength, eps));
280        connRefs.Add(connRef);
281      }
282    }
283
284    public void Run() { }
285
286    public void UpdateNodePosition(IVertex v, PointF point) {
287      var node = topologyNodes[indexMap[v]];
288    }
289
290    private static EdgePoint.RectIntersect CalculateIntersect(Point p) {
291      switch (p.vn) {
292        case 0:
293          return EdgePoint.RectIntersect.BR;
294        case 1:
295          return EdgePoint.RectIntersect.TR;
296        case 2:
297          return EdgePoint.RectIntersect.TL;
298        case 3:
299          return EdgePoint.RectIntersect.BL;
300        default:
301          return EdgePoint.RectIntersect.CENTRE;
302      }
303    }
304  }
305}
Note: See TracBrowser for help on using the repository browser.