1
2
3
4
5
6
7 package pl.edu.agh.cast.schema.layout.algorithm;
8
9 import java.util.Arrays;
10 import java.util.Collection;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.Iterator;
14 import java.util.LinkedList;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Queue;
18 import java.util.Set;
19
20 import org.eclipse.core.runtime.IProgressMonitor;
21 import org.eclipse.draw2d.geometry.Point;
22
23 import pl.edu.agh.cast.model.visual.backward.ConnectionGroup;
24 import pl.edu.agh.cast.model.visual.backward.Node;
25 import pl.edu.agh.cast.schema.util.Messages;
26
27
28
29
30
31
32
33
34 public class Peacock3LayoutAlgorithm extends AbstractGroupingLayoutAlgorithm {
35
36 private final int STEPS_NUMBER = 5;
37
38 private final int AREA_CENTER_X = 0;
39
40 private final int AREA_CENTER_Y = 0;
41
42 private final double RADIUS_SCALE = 40;
43
44 private final double MIN_RADIUS_SCALE_FACTOR = 1;
45
46 private final double MAX_RADIUS_SCALE_FACTOR = 7;
47
48 private final double DISTANCE_FACTOR = 0.001;
49
50 private final double ANGLE_RANGE_FACTOR = 1.4;
51
52 private int maxWeight = -1;
53
54 private int minWeight = -1;
55
56 private int treeSize = 0;
57
58 private double radius = 150;
59
60 private double radiusScaleFactor = 2;
61
62 private Collection<Node> nodes = null;
63
64 private boolean[][] adiacentTable = null;
65
66 private Map<String, Integer> distanceSums = new HashMap<String, Integer>();
67
68 private Map<String, Integer> adiacentTablePositions = new HashMap<String, Integer>();
69
70 private Map<String, Map<String, Integer>> distancesTable = new HashMap<String, Map<String, Integer>>();
71
72
73
74
75
76
77
78
79 private class TreeNode {
80
81 Node node;
82
83 TreeNode parent;
84
85 Set<TreeNode> children = new HashSet<TreeNode>();
86
87
88
89
90
91
92
93 public TreeNode(Node node) {
94 this.node = node;
95 }
96
97 }
98
99 @Override
100
101
102
103
104
105 protected void setNodesPositions(Collection<Node> positionNodes, IProgressMonitor monitor) {
106
107 monitor.subTask(Messages.Peacock3LayoutAlgorithm_0);
108
109 this.nodes = positionNodes;
110 this.setRadiusScaleFactor();
111 this.setRootNode(new LinkedList<Node>(positionNodes));
112
113 List<Edge> edges = new LinkedList<Edge>();
114 minWeight = maxWeight = -1;
115 for (Node n : nodes) {
116 for (Node neighbor : getNeighbors(nodes, n)) {
117 if (findEdge(edges, n, neighbor) == null) {
118 int weight = getWeight(n, neighbor);
119 edges.add(new Edge(n, neighbor, weight));
120 if (minWeight == -1) {
121 minWeight = maxWeight = weight;
122 } else if (weight > maxWeight) {
123 maxWeight = weight;
124 } else if (weight < minWeight) {
125 minWeight = weight;
126 }
127 }
128 }
129 monitor.worked(positionNodes.size());
130 }
131
132 for (int i = 0; i < this.STEPS_NUMBER; i++) {
133 steepestDescent(edges);
134 }
135 monitor.worked(positionNodes.size());
136
137 }
138
139
140
141
142 private void setRadiusScaleFactor() {
143 this.radiusScaleFactor = this.nodes.size() / this.RADIUS_SCALE;
144 if (this.radiusScaleFactor < this.MIN_RADIUS_SCALE_FACTOR) {
145 this.radiusScaleFactor = this.MIN_RADIUS_SCALE_FACTOR;
146 }
147 if (this.radiusScaleFactor > this.MAX_RADIUS_SCALE_FACTOR) {
148 this.radiusScaleFactor = this.MAX_RADIUS_SCALE_FACTOR;
149 }
150 }
151
152
153
154
155
156
157
158
159 private class Edge {
160
161 Node n1, n2;
162
163 int weight;
164
165
166
167
168
169
170
171
172
173
174
175 public Edge(Node n1, Node n2, int weight) {
176 this.n1 = n1;
177 this.n2 = n2;
178 this.weight = weight;
179 }
180
181 }
182
183
184
185
186
187
188
189
190
191
192
193
194 private Edge findEdge(List<Edge> edges, Node n1, Node n2) {
195 for (Edge e : edges) {
196 if (e.n2 == n1 && e.n1 == n2) {
197 return e;
198 }
199 }
200 return null;
201 }
202
203
204
205
206
207
208
209
210
211
212 private int getWeight(Node n1, Node n2) {
213 for (ConnectionGroup cg : n1.getConnectionGroups()) {
214 if (cg.getTarget() == n2 || cg.getSource() == n2) {
215 int weight = cg.getConnectionCount();
216 return weight > 40 ? 40 : weight;
217 }
218 }
219 return 0;
220 }
221
222
223
224
225
226
227
228
229
230
231 private double distance(Node n1, Node n2) {
232 double length = n1.getLocation().x - n2.getLocation().x;
233 double height = n1.getLocation().y - n2.getLocation().y;
234 return Math.sqrt(length * length + height * height);
235 }
236
237
238
239
240
241
242
243
244
245
246
247 private double potential(List<Edge> edges) {
248 double result = 0;
249 for (Edge e : edges) {
250 double a = 1 - DISTANCE_FACTOR * getWeight(e.n1, e.n2) * distance(e.n1, e.n2);
251 result += a * a;
252 }
253 return result;
254 }
255
256
257
258
259
260
261
262
263
264
265 private double derivativeX(List<Edge> edges, Node n) {
266 double result = 0;
267 double x1 = n.getLocation().x;
268 double x2 = 0;
269 for (Edge e : edges) {
270 if (e.n1 == n || e.n2 == n) {
271 if (e.n1 == n) {
272 x2 = e.n2.getLocation().x;
273 } else {
274 x2 = e.n1.getLocation().x;
275 }
276
277 double a = x1 - x2;
278 double b = e.n1.getLocation().y - e.n2.getLocation().y;
279 double sqrt = Math.sqrt(a * a + b * b);
280 double w = getWeight(e.n1, e.n2);
281 w *= DISTANCE_FACTOR;
282 result += -(2 * a * w * (w * sqrt - 1)) / sqrt;
283 }
284 }
285 return result;
286 }
287
288
289
290
291
292
293
294
295
296
297 private double derivativeY(List<Edge> edges, Node n) {
298 double result = 0;
299 double x1 = n.getLocation().y;
300 double x2 = 0;
301 for (Edge e : edges) {
302 if (e.n1 == n || e.n2 == n) {
303 if (e.n1 == n) {
304 x2 = e.n2.getLocation().y;
305 } else {
306 x2 = e.n1.getLocation().y;
307 }
308
309 double a = x1 - x2;
310 double b = e.n1.getLocation().x - e.n2.getLocation().x;
311 double sqrt = Math.sqrt(a * a + b * b);
312 double w = getWeight(e.n1, e.n2);
313 w *= DISTANCE_FACTOR;
314
315 result += -(2 * a * w * (w * sqrt - 1)) / sqrt;
316 }
317 }
318 return result;
319 }
320
321
322
323
324
325
326
327
328
329
330
331
332
333 private double potentialForGradient(double[] gradient, int multiplier, List<Edge> edges) {
334 int i = 0;
335 Point[] oldCoords = new Point[nodes.size()];
336 for (Node n : nodes) {
337 int x = n.getLocation().x;
338 int y = n.getLocation().y;
339 oldCoords[i / 2] = n.getLocation();
340
341 n.setLocation(new Point(x + gradient[i++] / multiplier, y + gradient[i++] / multiplier));
342 }
343 double result = potential(edges);
344 i = 0;
345 for (Node n : nodes) {
346 n.setLocation(oldCoords[i++]);
347 }
348 return result;
349 }
350
351
352
353
354
355
356
357
358 private void steepestDescent(List<Edge> edges) {
359 double[] gradient = new double[nodes.size() * 2];
360 int i = 0;
361 for (Node n : nodes) {
362 gradient[i++] = derivativeX(edges, n) * 1000000;
363 gradient[i++] = derivativeY(edges, n) * 1000000;
364 }
365
366 int multi = 8;
367 double result = potentialForGradient(gradient, multi, edges);
368 double resultForLess = potentialForGradient(gradient, multi / 2, edges);
369 double resultForMore = potentialForGradient(gradient, multi * 2, edges);
370 if (resultForLess < result) {
371 result = resultForLess;
372 multi /= 2;
373 resultForLess = potentialForGradient(gradient, multi / 2, edges);
374 while (resultForLess < result) {
375 multi /= 2;
376 result = resultForLess;
377 resultForLess = potentialForGradient(gradient, multi / 2, edges);
378 }
379 } else if (resultForMore < result) {
380 result = resultForMore;
381 multi *= 2;
382 resultForMore = potentialForGradient(gradient, multi * 2, edges);
383 while (resultForMore < result) {
384 multi *= 2;
385 result = resultForMore;
386 resultForMore = potentialForGradient(gradient, multi * 2, edges);
387 }
388 }
389 int multiplier = multi;
390
391 i = 0;
392 for (Node n : nodes) {
393 int x = n.getLocation().x;
394 int y = n.getLocation().y;
395 n.setLocation(new Point(x + gradient[i++] / multiplier, y + gradient[i++] / multiplier));
396 }
397 }
398
399
400
401
402
403
404
405 private void makeTree(TreeNode rootNode, Collection<Node> nodes, List<String> connectionNodes,
406 List<String> rootNodes) {
407 TreeNode nextNode = null;
408 Queue<TreeNode> queue = new LinkedList<TreeNode>();
409 Map<Node, TreeNode> nodeToTreeNodeMap = new HashMap<Node, TreeNode>();
410
411 queue.offer(rootNode);
412 nodeToTreeNodeMap.put(rootNode.node, rootNode);
413
414 while (!queue.isEmpty()) {
415 nextNode = queue.poll();
416
417 if (connectionNodes.contains(nextNode.node.getId())) {
418 String connectionNodeID = nextNode.node.getId();
419 connectionNodes.remove(connectionNodeID);
420 Node fakeChild = this.getNodeFromID(rootNodes.remove(0), nodes);
421 TreeNode fakeTreeChild = new TreeNode(fakeChild);
422 nextNode.children.add(fakeTreeChild);
423 this.treeSize++;
424 nodes.remove(fakeChild);
425 queue.offer(fakeTreeChild);
426 }
427
428 for (Node neighbor : getNeighbors(nodes, nextNode.node)) {
429 if (!nodeToTreeNodeMap.containsKey(neighbor)) {
430 TreeNode treeNeighbor = new TreeNode(neighbor);
431 this.treeSize++;
432 nodes.remove(neighbor);
433 nodeToTreeNodeMap.put(neighbor, treeNeighbor);
434 treeNeighbor.parent = nextNode;
435 nextNode.children.add(treeNeighbor);
436
437 queue.offer(treeNeighbor);
438 }
439 }
440 }
441 }
442
443
444
445
446
447
448
449 private void fillAdiacentTable(List<Node> nodes) {
450 this.adiacentTable = new boolean[nodes.size()][nodes.size()];
451
452 int position = 0;
453 for (Node node : this.nodes) {
454 this.adiacentTablePositions.put(node.getId(), position++);
455 }
456
457 for (Node node1 : nodes) {
458 for (Node node2 : this.getNeighbors(nodes, node1)) {
459 this.adiacentTable[this.adiacentTablePositions.get(node1.getId())][this.adiacentTablePositions
460 .get(node2.getId())] = true;
461 }
462 }
463 }
464
465
466
467
468
469
470
471
472 private void initNodesDistanceTables(List<Node> nodes) {
473 for (Node node : nodes) {
474 Map<String, Integer> neighbours = new HashMap<String, Integer>();
475 this.distancesTable.put(node.getId(), neighbours);
476 for (Node neighbour : this.getNeighbors(nodes, node)) {
477 neighbours.put(neighbour.getId(), new Integer(1));
478 }
479 }
480 }
481
482
483
484
485
486
487
488 private void fillNodesDistanceTables(List<Node> nodes) {
489 boolean wasChange = true;
490
491 while (wasChange) {
492 wasChange = false;
493
494 for (Node node : nodes) {
495 Map<String, Integer> distancesFromNode = this.distancesTable.get(node.getId());
496 for (Node neighbour : this.getNeighbors(nodes, node)) {
497 String neighbourID = neighbour.getId();
498 for (String nodeID : distancesFromNode.keySet()) {
499 if (nodeID.compareTo(neighbourID) != 0) {
500 if (this.distancesTable.get(neighbourID).containsKey(nodeID)) {
501 if (this.distancesTable.get(neighbourID).get(nodeID) > distancesFromNode.get(nodeID) + 1) {
502 this.distancesTable.get(neighbourID).put(nodeID, distancesFromNode.get(nodeID) + 1);
503 wasChange = true;
504 }
505 } else {
506 this.distancesTable.get(neighbourID).put(nodeID, distancesFromNode.get(nodeID) + 1);
507 wasChange = true;
508 }
509 }
510 }
511 }
512 }
513 }
514 }
515
516
517
518
519 private void computeDistanceSums() {
520 for (String nodeID : this.distancesTable.keySet()) {
521 int distancesSum = 0;
522
523 for (Integer distance : this.distancesTable.get(nodeID).values()) {
524 distancesSum += distance.intValue();
525 }
526
527 this.distanceSums.put(nodeID, new Integer(distancesSum));
528 }
529 }
530
531
532
533
534
535
536
537
538 private List<String> getRootNodesIDs(List<Node> positionNodes) {
539 int nrOfNodesInGraph = this.distancesTable.size();
540 List<String> rootNodeIDs = new LinkedList<String>();
541 while (nrOfNodesInGraph > 0) {
542 String rootID = this.distanceSums.keySet().iterator().next();
543 Map<String, Integer> rootDistancesTable = this.distancesTable.get(rootID);
544 int distancesSum = this.distanceSums.remove(rootID);
545
546 nrOfNodesInGraph -= (rootDistancesTable.size() + 1);
547
548 for (String nodeID : rootDistancesTable.keySet()) {
549 if (distancesSum > this.distanceSums.get(nodeID).intValue()) {
550 distancesSum = this.distanceSums.get(nodeID).intValue();
551 rootID = nodeID;
552 }
553 this.distanceSums.remove(nodeID);
554 }
555 rootNodeIDs.add(rootID);
556 }
557 return rootNodeIDs;
558 }
559
560
561
562
563
564
565
566
567 private String getMainRoot(String[] rootNodesIDs) {
568 String rootNodeID = null;
569 if (rootNodesIDs.length > 0) {
570 rootNodeID = rootNodesIDs[0];
571 int maxSum = this.distanceSums.get(rootNodeID).intValue();
572 for (int i = 1; i < rootNodesIDs.length; i++) {
573 if (maxSum < this.distanceSums.get(rootNodesIDs[i]).intValue()) {
574 maxSum = this.distanceSums.get(rootNodesIDs[i]).intValue();
575 rootNodeID = rootNodesIDs[i];
576 }
577 }
578
579 }
580 return rootNodeID;
581
582 }
583
584
585
586
587
588
589
590
591
592
593
594
595 private List<String> getRootConnectionNodes(int numberOfNodesToReturn, String rootID) {
596 List<String> connectionNodesIDs = new LinkedList<String>();
597 int numberOfNodesAddedToResultTable = 0;
598
599 if (this.distancesTable.get(rootID).size() <= numberOfNodesToReturn) {
600 while (numberOfNodesAddedToResultTable < numberOfNodesToReturn) {
601 Iterator<String> it = this.distancesTable.get(rootID).keySet().iterator();
602 while (it.hasNext()) {
603 connectionNodesIDs.add(it.next());
604 numberOfNodesAddedToResultTable++;
605 if (numberOfNodesAddedToResultTable == numberOfNodesToReturn) {
606 break;
607 }
608 }
609 }
610 } else {
611 Integer[] distanceSumsArray = this.distancesTable.get(rootID).values().toArray(new Integer[0]);
612 Arrays.sort(distanceSumsArray);
613 int minimumSumValue = distanceSumsArray[distanceSumsArray.length - numberOfNodesToReturn];
614
615 for (String nodeID : this.distancesTable.get(rootID).keySet()) {
616 if (this.distancesTable.get(rootID).get(nodeID).intValue() >= minimumSumValue) {
617 connectionNodesIDs.add(nodeID);
618 numberOfNodesAddedToResultTable++;
619 if (numberOfNodesAddedToResultTable == numberOfNodesToReturn) {
620 break;
621 }
622 }
623 }
624 }
625 return connectionNodesIDs;
626 }
627
628
629
630
631
632
633
634
635
636
637 private Node getNodeFromID(String nodeID, Collection<Node> positionNodes) {
638 Iterator<Node> it = positionNodes.iterator();
639 while (it.hasNext()) {
640 Node node = it.next();
641 if (node.getId().compareTo(nodeID) == 0) {
642 return node;
643 }
644 }
645 return null;
646 }
647
648
649
650
651
652
653
654
655 private TreeNode mergeGraphAndMakeTree(List<Node> positionNodes) {
656 this.fillAdiacentTable(positionNodes);
657 this.initNodesDistanceTables(positionNodes);
658 this.fillNodesDistanceTables(positionNodes);
659 this.computeDistanceSums();
660 List<String> rootNodesIDs = this.getRootNodesIDs(positionNodes);
661 this.computeDistanceSums();
662 String mainRootNode = this.getMainRoot(rootNodesIDs.toArray(new String[0]));
663
664 rootNodesIDs.remove(mainRootNode);
665
666 List<String> connectionNodesIDs = null;
667 if (rootNodesIDs.size() > 0) {
668 connectionNodesIDs = this.getRootConnectionNodes(rootNodesIDs.size(), mainRootNode);
669 } else {
670 connectionNodesIDs = new LinkedList<String>();
671 }
672
673 positionNodes.remove(mainRootNode);
674 TreeNode treeRoot = new TreeNode(this.getNodeFromID(mainRootNode, positionNodes));
675 this.makeTree(treeRoot, positionNodes, connectionNodesIDs, rootNodesIDs);
676 return treeRoot;
677 }
678
679
680
681
682
683
684
685
686 private void setRootNode(List<Node> positionNodes) {
687 if (positionNodes == null || positionNodes.isEmpty()) {
688 return;
689 }
690
691 TreeNode treeRoot = this.mergeGraphAndMakeTree(positionNodes);
692 treeRoot.node.setLocation(new Point(this.AREA_CENTER_X, this.AREA_CENTER_Y));
693
694 boolean radiusFactorInvert = true;
695 double alfa = 0;
696 for (TreeNode treeNode : treeRoot.children) {
697 Node n = treeNode.node;
698 double temp = this.getAngleRange(2 * Math.PI, this.countChildren(treeRoot), this.treeSize);
699 int x = (int)((this.AREA_CENTER_X - this.radius) * Math.cos(alfa + temp / 2.0));
700 int y = (int)((this.AREA_CENTER_Y - this.radius) * Math.sin(alfa + temp / 2.0));
701 n.setLocation(new Point(x, y));
702
703 if (radiusFactorInvert) {
704 this.setNode(treeRoot, 2, countChildren(treeRoot), alfa, this.ANGLE_RANGE_FACTOR * temp,
705 (this.radiusScaleFactor + 1) * 0.8);
706 radiusFactorInvert = false;
707 } else {
708 this.setNode(treeRoot, 2, countChildren(treeRoot), alfa, this.ANGLE_RANGE_FACTOR * temp,
709 this.radiusScaleFactor + 1);
710 radiusFactorInvert = true;
711 }
712 alfa += temp;
713 }
714 this.treeSize = 0;
715 }
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731 private void setNode(TreeNode node, int deepLevel, int nrOfNodes, double angle, double angleRange,
732 double radiusFactor) {
733 boolean radiusFactorInvert = true;
734 for (TreeNode n : node.children) {
735 double temp = this.getAngleRange(angleRange, this.countChildren(n), nrOfNodes);
736 int x = (int)((this.AREA_CENTER_X - this.radius * radiusFactor * deepLevel) * Math.cos(angle + temp / 2.0));
737 int y = (int)((this.AREA_CENTER_Y - this.radius * radiusFactor * deepLevel) * Math.sin(angle + temp / 2.0));
738 n.node.setLocation(new Point(x, y));
739
740 if (radiusFactorInvert) {
741 this.setNode(n, deepLevel + 1, this.countChildren(n), angle, this.ANGLE_RANGE_FACTOR * temp,
742 this.radiusScaleFactor * 0.8);
743 radiusFactorInvert = false;
744 } else {
745 this.setNode(n, deepLevel + 1, this.countChildren(n), angle, this.ANGLE_RANGE_FACTOR * temp,
746 this.radiusScaleFactor);
747 radiusFactorInvert = true;
748 }
749 angle += temp;
750 }
751 }
752
753
754
755
756
757
758
759
760
761
762
763
764 private double getAngleRange(double angle, double nrOfChildren, double nrOfNodes) {
765 return angle * nrOfChildren / nrOfNodes;
766 }
767
768
769
770
771
772
773
774
775 private int countChildren(TreeNode node) {
776 Collection<TreeNode> children = node.children;
777 int result = 0;
778
779 if (children.size() == 0) {
780 result = 1;
781 }
782
783 for (TreeNode n : children) {
784 result += this.countChildren(n);
785 }
786
787 return result;
788 }
789
790
791
792
793
794
795 @Override
796 protected int getWorkUnits(int nodesCount) {
797 return (nodesCount + 1) * nodesCount;
798 }
799 }