1 package de.fzi.wim.guibase.graphview.layout;
2
3 import java.util.Map;
4 import java.util.HashMap;
5 import java.util.Collection;
6 import java.util.Iterator;
7
8 import de.fzi.wim.guibase.graphview.graph.*;
9
10 /***
11 * The layout strategy that models edges as springs. This algorithm is a refactoring of the graph layout algorithm originally implemented
12 * in the <a href="http://www.touchgraph.com/">TouchGraph</a> library.
13 */
14 public class SpringLayoutStrategy implements LayoutStrategy {
15 /*** Specifies how long a node remains in a special state after it is inserted into the graph. */
16 protected static final long TIME_NODE_REMAINS_CHANGED=200;
17
18 /*** The graph being manipulated by this strategy. */
19 protected Graph m_graph;
20 /*** The the graph manager. */
21 protected GraphManager m_graphManager;
22 /*** The damper value. A low damper value causes the graph to move slowly. A value of 1 means no damping. */
23 protected double m_damper=0.0;
24 /*** The maximum motion value after the last move. Used to see when the graph is stabilizinh. */
25 protected double m_maxMotion=0;
26 /*** The motion ration after the last move. */
27 protected double m_motionRatio=0;
28 /*** Then dampoing is true, the damper value decreases. */
29 protected boolean m_damping=true;
30 /*** The rigidity has the same effect as the damper, except that it is a constant. */
31 protected double m_rigidity=1;
32
33 /***
34 * Creates an instance of this class.
35 *
36 * @param graph the graph that is active
37 */
38 public SpringLayoutStrategy(Graph graph) {
39 m_graph=graph;
40 m_graphManager=new GraphManager();
41 }
42 /***
43 * Returns the graph of the strategy.
44 *
45 * @return the graph of the strategy
46 */
47 public Graph getGraph() {
48 return m_graph;
49 }
50 /***
51 * Notifies the strategy that elements were inserted into the graph.
52 *
53 * @param nodes the inserted nodes (may be <code>null</code>)
54 * @param edges the inserted edges (may be <code>null</code>)
55 */
56 public void elementsAdded(Collection nodes,Collection edges) {
57 m_graphManager.elementsAdded(nodes,edges);
58 }
59 /***
60 * Notifies the strategy that elements were removed from the graph.
61 *
62 * @param nodes the removed nodes (may be <code>null</code>)
63 * @param edges the removed edges (may be <code>null</code>)
64 */
65 public void elementsRemoved(Collection nodes,Collection edges) {
66 m_graphManager.elementsRemoved(nodes,edges);
67 }
68 /***
69 * Called when the graph is completely changed.
70 */
71 public void graphContentsChanged() {
72 m_graphManager.graphContentsChanged();
73 }
74 /***
75 * Notifies the strategy that the graph has changed due to an event that is not caused by
76 * the strategy itself.
77 */
78 public void notifyGraphLayoutUpdated() {
79 m_damping=true;
80 m_damper=1.0;
81 }
82 /***
83 * Executes one step in the layout.
84 */
85 public void executeGraphLayoutStep() {
86 for (int i=0;i<10;i++) {
87 relaxEdges();
88 adjustNodePairs();
89 moveNodes();
90 damp();
91 }
92 }
93 /***
94 * Returns <code>true</code> if more steps in the layout should be executed.
95 *
96 * @return <code>true</code> if more steps should be executed
97 */
98 public boolean shouldExecuteStep() {
99 return !(m_damper<0.1 && m_damping && m_maxMotion<0.001);
100 }
101 /***
102 * Relaxes the edges.
103 */
104 protected void relaxEdges() {
105 Iterator edges=m_graph.getEdges().iterator();
106 while (edges.hasNext()) {
107 Edge edge=(Edge)edges.next();
108 Node from=edge.getFrom();
109 Node to=edge.getTo();
110 double deltaX=to.getX()-from.getX();
111 double deltaY=to.getY()-from.getY();
112 double currentLength=Math.sqrt(deltaX*deltaX+deltaY*deltaY);
113 double factor=m_rigidity*currentLength/(edge.getLength()*100.0);
114 double dx=deltaX*factor;
115 double dy=deltaY*factor;
116
117
118
119 applyDelta(from,m_graphManager.getNodeMovement(from),dx,dy);
120 applyDelta(to,m_graphManager.getNodeMovement(to),-dx,-dy);
121 }
122 }
123 /***
124 * Adjusts the pairs of nodes.
125 */
126 private void adjustNodePairs() {
127 SubGraph currentSubGraph=m_graphManager.getFirstSubGraph();
128 while (currentSubGraph!=null) {
129 NodeMovement node1Movement=currentSubGraph.getFirstNodeMovement();
130 while (node1Movement!=null) {
131 Node node1=node1Movement.m_node;
132 NodeMovement node2Movement=currentSubGraph.getFirstNodeMovement();
133 while (node2Movement!=null) {
134 Node node2=node2Movement.m_node;
135 if (node1!=node2) {
136 double dx=0;
137 double dy=0;
138 double deltaX=node1.getX()-node2.getX();
139 double deltaY=node1.getY()-node2.getY();
140 double currentLengthSquared=deltaX*deltaX+deltaY*deltaY;
141 if (Math.abs(currentLengthSquared)<0.1) {
142 dx=Math.random();
143 dy=Math.random();
144 }
145 else if (currentLengthSquared<600*600) {
146 dx=deltaX/currentLengthSquared;
147 dy=deltaY/currentLengthSquared;
148
149
150
151 }
152 double factor=100.0*node1.getRepulsion()*node2.getRepulsion()*m_rigidity;
153 dx*=factor;
154 dy*=factor;
155 applyDelta(node1,node1Movement,dx,dy);
156 applyDelta(node2,node2Movement,-dx,-dy);
157 }
158 node2Movement=(NodeMovement)node2Movement.m_next;
159 }
160 node1Movement=(NodeMovement)node1Movement.m_next;
161 }
162 currentSubGraph=(SubGraph)currentSubGraph.m_next;
163 }
164 }
165 /***
166 * Moves nodes for the computed delta.
167 */
168 protected void moveNodes() {
169 double lastMaxMotion=m_maxMotion;
170 m_maxMotion=0;
171 Iterator iterator=m_graph.getNodes().iterator();
172 while (iterator.hasNext()) {
173 Node node=(Node)iterator.next();
174 NodeMovement movement=m_graphManager.getNodeMovement(node);
175 double dx=movement.m_dx;
176 double dy=movement.m_dy;
177 dx*=m_damper;
178 dy*=m_damper;
179
180
181
182 movement.m_dx=dx/2;
183 movement.m_dy=dy/2;
184 if (!node.isFixed()) {
185 double distanceMoved=Math.sqrt(dx*dx+dy*dy);
186
187 double x=node.getX()+Math.max(-30,Math.min(30,dx));
188 double y=node.getY()+Math.max(-30,Math.min(30,dy));
189 node.setLocation(x,y);
190 m_maxMotion=Math.max(distanceMoved,m_maxMotion);
191 }
192 if (movement.m_justChanged) {
193 if (System.currentTimeMillis()>movement.m_timeWhenNodeBecomesNormal)
194 movement.m_justChanged=false;
195 }
196 }
197 if (m_maxMotion>0)
198 m_motionRatio=lastMaxMotion/m_maxMotion-1;
199 else
200 m_motionRatio=0;
201 }
202 /***
203 * Applies a delta to a node.
204 *
205 * @param node the node to which the delta is added
206 * @param nodeMovement the node movement
207 * @param dx delta in the X direction
208 * @param dy delta in the Y direction
209 */
210 protected void applyDelta(Node node,NodeMovement nodeMovement,double dx,double dy) {
211 if (nodeMovement.m_justChanged) {
212 nodeMovement.m_dx+=dx/10;
213 nodeMovement.m_dy+=dy/10;
214 }
215 else {
216 nodeMovement.m_dx+=dx;
217 nodeMovement.m_dy+=dy;
218 }
219 }
220 /***
221 * Turns damping off.
222 */
223 public void stopDamper() {
224 m_damping=false;
225 m_damper=1.0;
226 }
227 /***
228 * Stabilizes the graph gently, by setting the damper to a low value.
229 */
230 public void stopMotion() {
231 m_damping=true;
232 if (m_damper>0.3)
233 m_damper=0.3;
234 else
235 m_damper=0;
236 }
237 /***
238 * Applies the damping.
239 */
240 public void damp() {
241 if (m_damping) {
242
243
244
245
246 if (m_motionRatio<=0.001) {
247
248
249
250 if ((m_maxMotion<0.2 || (m_maxMotion>1 && m_damper<0.9)) && m_damper > 0.01)
251 m_damper-=0.01;
252
253 else if (m_maxMotion<0.4 && m_damper > 0.003)
254 m_damper-=0.003;
255
256 else if(m_damper>0.0001)
257 m_damper -=0.0001;
258 }
259 }
260 if(m_maxMotion<0.001 && m_damping)
261 m_damper=0;
262 }
263
264 /***
265 * The abstract list node.
266 */
267 protected static class ListNode {
268 public ListNode m_next;
269 public ListNode m_previous;
270 public ListOfNodes m_list;
271 }
272
273 /***
274 * The list.
275 */
276 protected static class ListOfNodes {
277 public ListNode m_head;
278 public ListNode m_tail;
279 public int m_size;
280
281 public void add(ListNode node) {
282 if (m_head==null) {
283 m_head=node;
284 m_tail=node;
285 node.m_previous=node.m_next=null;
286 }
287 else {
288 m_tail.m_next=node;
289 node.m_previous=m_tail;
290 node.m_next=null;
291 m_tail=node;
292 }
293 node.m_list=this;
294 }
295 public void remove(ListNode node) {
296 if (m_head==node)
297 m_head=node.m_next;
298 else
299 node.m_previous.m_next=node.m_next;
300 if (m_tail==node)
301 m_tail=node.m_previous;
302 else
303 node.m_next.m_previous=node.m_previous;
304 node.m_next=node.m_previous=null;
305 node.m_list=null;
306 }
307 public void clear() {
308 m_head=m_tail=null;
309 m_size=0;
310 }
311 public void mergeWith(ListOfNodes other) {
312 if (m_tail==null) {
313 m_head=other.m_head;
314 m_tail=other.m_head;
315 }
316 else if (other.m_head!=null) {
317 m_tail.m_next=other.m_head;
318 other.m_head.m_previous=m_tail;
319 m_tail=other.m_tail;
320 }
321 ListNode pointer=other.m_head;
322 while (pointer!=null) {
323 pointer.m_list=this;
324 pointer=pointer.m_next;
325 }
326 m_size+=other.m_size;
327 other.m_head=other.m_tail=null;
328 other.m_size=0;
329 }
330 }
331
332 /***
333 * Contains information about the node movement.
334 */
335 protected static class NodeMovement extends ListNode {
336 public Node m_node;
337 public double m_dx=0.0;
338 public double m_dy=0.0;
339 public boolean m_justChanged=false;
340 public long m_timeWhenNodeBecomesNormal;
341
342 public NodeMovement(Node node) {
343 m_node=node;
344 }
345 public SubGraph getSubGraph() {
346 return ((SubGraphList)m_list).m_subGraph;
347 }
348 }
349
350 /***
351 * Contains information about the subgraphs in the overall graph.
352 */
353 protected class GraphManager extends ListOfNodes {
354 /*** The node movement objects indexed by the node. */
355 protected Map m_nodeMovement;
356
357 public GraphManager() {
358 m_nodeMovement=new HashMap();
359 }
360 public SubGraph getFirstSubGraph() {
361 return (SubGraph)m_head;
362 }
363 public NodeMovement getNodeMovement(Node node) {
364 return (NodeMovement)m_nodeMovement.get(node);
365 }
366 public void nodeChanged(Node node) {
367 NodeMovement nodeMovement=getNodeMovement(node);
368 if (nodeMovement!=null) {
369 nodeMovement.m_justChanged=true;
370 nodeMovement.m_timeWhenNodeBecomesNormal=System.currentTimeMillis()+TIME_NODE_REMAINS_CHANGED;
371 }
372 }
373 public void addNode(Node node) {
374 if (!m_nodeMovement.containsKey(node)) {
375 NodeMovement nodeMovement=new NodeMovement(node);
376 m_nodeMovement.put(node,nodeMovement);
377 add(new SubGraph(nodeMovement));
378 }
379 nodeChanged(node);
380 }
381 public void addEdge(Edge edge) {
382 NodeMovement nodeMovement1=getNodeMovement(edge.getFrom());
383 NodeMovement nodeMovement2=getNodeMovement(edge.getTo());
384 if (nodeMovement1.getSubGraph()!=nodeMovement2.getSubGraph())
385 mergeSubGraphs(nodeMovement1.getSubGraph(),nodeMovement2.getSubGraph());
386 }
387 public void graphContentsChanged() {
388 m_nodeMovement.clear();
389 clear();
390 Iterator nodes=m_graph.getNodes().iterator();
391 while (nodes.hasNext()) {
392 Node node=(Node)nodes.next();
393 addNode(node);
394 }
395 Iterator edges=m_graph.getEdges().iterator();
396 while (edges.hasNext()) {
397 Edge edge=(Edge)edges.next();
398 addEdge(edge);
399 }
400 }
401 public void elementsAdded(Collection nodes,Collection edges) {
402 if (nodes!=null) {
403 Iterator iterator=nodes.iterator();
404 while (iterator.hasNext()) {
405 Node node=(Node)iterator.next();
406 addNode(node);
407 }
408 }
409 if (edges!=null) {
410 Iterator iterator=edges.iterator();
411 while (iterator.hasNext()) {
412 Edge edge=(Edge)iterator.next();
413 addEdge(edge);
414 }
415 }
416 }
417 public void elementsRemoved(Collection nodes,Collection edges) {
418 if (nodes!=null) {
419 Iterator iterator=nodes.iterator();
420 while (iterator.hasNext()) {
421 Node node=(Node)iterator.next();
422 m_nodeMovement.remove(node);
423 }
424 }
425 if (nodes!=null || edges!=null) {
426 m_head=m_tail=null;
427 Iterator iterator=m_nodeMovement.values().iterator();
428 while (iterator.hasNext()) {
429 NodeMovement nodeMovement=(NodeMovement)iterator.next();
430 add(new SubGraph(nodeMovement));
431 }
432 if (edges!=null) {
433 iterator=edges.iterator();
434 while (iterator.hasNext()) {
435 Edge edge=(Edge)iterator.next();
436 nodeChanged(edge.getFrom());
437 nodeChanged(edge.getTo());
438 }
439 }
440 iterator=m_graph.getEdges().iterator();
441 while (iterator.hasNext()) {
442 Edge edge=(Edge)iterator.next();
443 addNode(edge.getFrom());
444 addNode(edge.getTo());
445 addEdge(edge);
446 }
447 }
448 }
449 public void mergeSubGraphs(SubGraph subGraph1,SubGraph subGraph2) {
450 if (subGraph1.getSize()<subGraph2.getSize()) {
451 subGraph2.mergeWith(subGraph1);
452 remove(subGraph1);
453 }
454 else {
455 subGraph1.mergeWith(subGraph2);
456 remove(subGraph2);
457 }
458 }
459 }
460
461 /***
462 * Information about a subgraph.
463 */
464 protected static class SubGraph extends ListNode {
465 protected SubGraphList m_nodes;
466
467 public SubGraph(NodeMovement nodeMovement) {
468 m_nodes=new SubGraphList(this);
469 m_nodes.add(nodeMovement);
470 nodeMovement.m_list=m_nodes;
471 }
472 public NodeMovement getFirstNodeMovement() {
473 return (NodeMovement)m_nodes.m_head;
474 }
475 public void mergeWith(SubGraph subGraph) {
476 m_nodes.mergeWith(subGraph.m_nodes);
477 }
478 public void removeNodeMovement(NodeMovement nodeMovement) {
479 m_nodes.remove(nodeMovement);
480 }
481 public int getSize() {
482 return m_nodes.m_size;
483 }
484 }
485
486 protected static class SubGraphList extends ListOfNodes {
487 public SubGraph m_subGraph;
488
489 public SubGraphList(SubGraph subGraph) {
490 m_subGraph=subGraph;
491 }
492 }
493 }