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 |
|
---|
22 | using System;
|
---|
23 | using System.Collections.Generic;
|
---|
24 | using System.Drawing;
|
---|
25 | using System.Linq;
|
---|
26 | using Cola;
|
---|
27 | using HeuristicLab.Core;
|
---|
28 | using HeuristicLab.Random;
|
---|
29 | using Point = Cola.Point;
|
---|
30 |
|
---|
31 | namespace 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 | }
|
---|