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