1
2
3
4
5
6
7
8 package edu.uci.ics.jung.algorithms.layout;
9
10 import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
11 import edu.uci.ics.jung.algorithms.util.IterativeContext;
12 import edu.uci.ics.jung.graph.Graph;
13 import edu.uci.ics.jung.graph.util.Pair;
14
15 import org.apache.commons.collections15.Factory;
16 import org.apache.commons.collections15.Transformer;
17 import org.apache.commons.collections15.functors.ConstantTransformer;
18 import org.apache.commons.collections15.map.LazyMap;
19
20 import java.awt.Dimension;
21 import java.awt.event.ComponentAdapter;
22 import java.awt.event.ComponentEvent;
23 import java.awt.geom.Point2D;
24 import java.util.ConcurrentModificationException;
25 import java.util.HashMap;
26 import java.util.Map;
27
28
29
30
31
32
33
34
35
36
37 public class SpringLayout<V, E> extends AbstractLayout<V,E> implements IterativeContext {
38
39 protected double stretch = 0.70;
40 protected Transformer<E, Integer> lengthFunction;
41 protected int repulsion_range_sq = 100 * 100;
42 protected double force_multiplier = 1.0 / 3.0;
43
44 protected Map<V, SpringVertexData> springVertexData =
45 LazyMap.decorate(new HashMap<V, SpringVertexData>(),
46 new Factory<SpringVertexData>() {
47 public SpringVertexData create() {
48 return new SpringVertexData();
49 }});
50
51
52
53
54
55
56 @SuppressWarnings("unchecked")
57 public SpringLayout(Graph<V,E> g) {
58 this(g, new ConstantTransformer(30));
59 }
60
61
62
63
64
65
66
67 public SpringLayout(Graph<V,E> g, Transformer<E, Integer> length_function)
68 {
69 super(g);
70 this.lengthFunction = length_function;
71 }
72
73
74
75
76
77 public double getStretch() {
78 return stretch;
79 }
80
81
82
83
84 @Override
85 public void setSize(Dimension size) {
86 if(initialized == false)
87 setInitializer(new RandomLocationTransformer<V>(size));
88 super.setSize(size);
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 public void setStretch(double stretch) {
105 this.stretch = stretch;
106 }
107
108
109
110
111
112 public int getRepulsionRange() {
113 return (int)(Math.sqrt(repulsion_range_sq));
114 }
115
116
117
118
119
120
121
122 public void setRepulsionRange(int range) {
123 this.repulsion_range_sq = range * range;
124 }
125
126
127
128
129
130 public double getForceMultiplier() {
131 return force_multiplier;
132 }
133
134
135
136
137
138
139
140
141
142
143
144 public void setForceMultiplier(double force) {
145 this.force_multiplier = force;
146 }
147
148 public void initialize() {
149 }
150
151
152
153
154 public void step() {
155 try {
156 for(V v : getGraph().getVertices()) {
157 SpringVertexData svd = springVertexData.get(v);
158 if (svd == null) {
159 continue;
160 }
161 svd.dx /= 4;
162 svd.dy /= 4;
163 svd.edgedx = svd.edgedy = 0;
164 svd.repulsiondx = svd.repulsiondy = 0;
165 }
166 } catch(ConcurrentModificationException cme) {
167 step();
168 }
169
170 relaxEdges();
171 calculateRepulsion();
172 moveNodes();
173 }
174
175 protected void relaxEdges() {
176 try {
177 for(E e : getGraph().getEdges()) {
178 Pair<V> endpoints = getGraph().getEndpoints(e);
179 V v1 = endpoints.getFirst();
180 V v2 = endpoints.getSecond();
181
182 Point2D p1 = transform(v1);
183 Point2D p2 = transform(v2);
184 if(p1 == null || p2 == null) continue;
185 double vx = p1.getX() - p2.getX();
186 double vy = p1.getY() - p2.getY();
187 double len = Math.sqrt(vx * vx + vy * vy);
188
189 double desiredLen = lengthFunction.transform(e);
190
191
192 len = (len == 0) ? .0001 : len;
193
194 double f = force_multiplier * (desiredLen - len) / len;
195
196 f = f * Math.pow(stretch, (getGraph().degree(v1) + getGraph().degree(v2) - 2));
197
198
199
200 double dx = f * vx;
201 double dy = f * vy;
202 SpringVertexData v1D, v2D;
203 v1D = springVertexData.get(v1);
204 v2D = springVertexData.get(v2);
205
206 v1D.edgedx += dx;
207 v1D.edgedy += dy;
208 v2D.edgedx += -dx;
209 v2D.edgedy += -dy;
210 }
211 } catch(ConcurrentModificationException cme) {
212 relaxEdges();
213 }
214 }
215
216 protected void calculateRepulsion() {
217 try {
218 for (V v : getGraph().getVertices()) {
219 if (isLocked(v)) continue;
220
221 SpringVertexData svd = springVertexData.get(v);
222 if(svd == null) continue;
223 double dx = 0, dy = 0;
224
225 for (V v2 : getGraph().getVertices()) {
226 if (v == v2) continue;
227 Point2D p = transform(v);
228 Point2D p2 = transform(v2);
229 if(p == null || p2 == null) continue;
230 double vx = p.getX() - p2.getX();
231 double vy = p.getY() - p2.getY();
232 double distanceSq = p.distanceSq(p2);
233 if (distanceSq == 0) {
234 dx += Math.random();
235 dy += Math.random();
236 } else if (distanceSq < repulsion_range_sq) {
237 double factor = 1;
238 dx += factor * vx / distanceSq;
239 dy += factor * vy / distanceSq;
240 }
241 }
242 double dlen = dx * dx + dy * dy;
243 if (dlen > 0) {
244 dlen = Math.sqrt(dlen) / 2;
245 svd.repulsiondx += dx / dlen;
246 svd.repulsiondy += dy / dlen;
247 }
248 }
249 } catch(ConcurrentModificationException cme) {
250 calculateRepulsion();
251 }
252 }
253
254 protected void moveNodes()
255 {
256 synchronized (getSize()) {
257 try {
258 for (V v : getGraph().getVertices()) {
259 if (isLocked(v)) continue;
260 SpringVertexData vd = springVertexData.get(v);
261 if(vd == null) continue;
262 Point2D xyd = transform(v);
263
264 vd.dx += vd.repulsiondx + vd.edgedx;
265 vd.dy += vd.repulsiondy + vd.edgedy;
266
267
268 xyd.setLocation(xyd.getX()+Math.max(-5, Math.min(5, vd.dx)),
269 xyd.getY()+Math.max(-5, Math.min(5, vd.dy)));
270
271 Dimension d = getSize();
272 int width = d.width;
273 int height = d.height;
274
275 if (xyd.getX() < 0) {
276 xyd.setLocation(0, xyd.getY());
277 } else if (xyd.getX() > width) {
278 xyd.setLocation(width, xyd.getY());
279 }
280 if (xyd.getY() < 0) {
281 xyd.setLocation(xyd.getX(), 0);
282 } else if (xyd.getY() > height) {
283 xyd.setLocation(xyd.getX(), height);
284 }
285
286 }
287 } catch(ConcurrentModificationException cme) {
288 moveNodes();
289 }
290 }
291 }
292
293 protected static class SpringVertexData {
294 protected double edgedx;
295 protected double edgedy;
296 protected double repulsiondx;
297 protected double repulsiondy;
298
299
300 protected double dx;
301
302
303 protected double dy;
304 }
305
306
307
308
309
310 public class SpringDimensionChecker extends ComponentAdapter {
311 @Override
312 public void componentResized(ComponentEvent e) {
313 setSize(e.getComponent().getSize());
314 }
315 }
316
317
318
319
320 public boolean isIncremental() {
321 return true;
322 }
323
324
325
326
327 public boolean done() {
328 return false;
329 }
330
331
332
333
334 public void reset() {
335 }
336 }