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.geom.Point2D;
12 import java.awt.geom.Rectangle2D;
13 import java.util.ConcurrentModificationException;
14 import java.util.HashMap;
15 import java.util.Map;
16
17 import org.apache.commons.collections15.Factory;
18 import org.apache.commons.collections15.map.LazyMap;
19
20 import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
21 import edu.uci.ics.jung.algorithms.util.IterativeContext;
22 import edu.uci.ics.jung.graph.Graph;
23 import edu.uci.ics.jung.graph.util.Pair;
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 public class FRLayout2<V, E> extends AbstractLayout<V, E> implements IterativeContext {
46
47 private double forceConstant;
48
49 private double temperature;
50
51 private int currentIteration;
52
53 private int maxIterations = 700;
54
55 private Map<V, Point2D> frVertexData =
56 LazyMap.decorate(new HashMap<V,Point2D>(), new Factory<Point2D>() {
57 public Point2D create() {
58 return new Point2D.Double();
59 }});
60
61 private double attraction_multiplier = 0.75;
62
63 private double attraction_constant;
64
65 private double repulsion_multiplier = 0.75;
66
67 private double repulsion_constant;
68
69 private double max_dimension;
70
71 private Rectangle2D innerBounds = new Rectangle2D.Double();
72
73 private boolean checked = false;
74
75
76
77
78 public FRLayout2(Graph<V, E> g) {
79 super(g);
80 }
81
82
83
84
85 public FRLayout2(Graph<V, E> g, Dimension d) {
86 super(g, new RandomLocationTransformer<V>(d), d);
87 max_dimension = Math.max(d.height, d.width);
88 initialize();
89 }
90
91 @Override
92 public void setSize(Dimension size) {
93 if(initialized == false)
94 setInitializer(new RandomLocationTransformer<V>(size));
95 super.setSize(size);
96 double t = size.width/50.0;
97 innerBounds.setFrameFromDiagonal(t,t,size.width-t,size.height-t);
98 max_dimension = Math.max(size.height, size.width);
99 }
100
101
102
103
104 public void setAttractionMultiplier(double attraction) {
105 this.attraction_multiplier = attraction;
106 }
107
108
109
110
111 public void setRepulsionMultiplier(double repulsion) {
112 this.repulsion_multiplier = repulsion;
113 }
114
115 public void reset() {
116 doInit();
117 }
118
119 public void initialize() {
120 doInit();
121 }
122
123 private void doInit() {
124 Graph<V,E> graph = getGraph();
125 Dimension d = getSize();
126 if(graph != null && d != null) {
127 currentIteration = 0;
128 temperature = d.getWidth() / 10;
129
130 forceConstant =
131 Math
132 .sqrt(d.getHeight()
133 * d.getWidth()
134 / graph.getVertexCount());
135
136 attraction_constant = attraction_multiplier * forceConstant;
137 repulsion_constant = repulsion_multiplier * forceConstant;
138 }
139 }
140
141 private double EPSILON = 0.000001D;
142
143
144
145
146
147 public synchronized void step() {
148 currentIteration++;
149
150
151
152
153 while(true) {
154
155 try {
156 for(V v1 : getGraph().getVertices()) {
157 calcRepulsion(v1);
158 }
159 break;
160 } catch(ConcurrentModificationException cme) {}
161 }
162
163
164
165
166 while(true) {
167 try {
168 for(E e : getGraph().getEdges()) {
169 calcAttraction(e);
170 }
171 break;
172 } catch(ConcurrentModificationException cme) {}
173 }
174
175
176 while(true) {
177 try {
178 for(V v : getGraph().getVertices()) {
179 if (isLocked(v)) continue;
180 calcPositions(v);
181 }
182 break;
183 } catch(ConcurrentModificationException cme) {}
184 }
185 cool();
186 }
187
188 protected synchronized void calcPositions(V v) {
189 Point2D fvd = this.frVertexData.get(v);
190 if(fvd == null) return;
191 Point2D xyd = transform(v);
192 double deltaLength = Math.max(EPSILON,
193 Math.sqrt(fvd.getX()*fvd.getX()+fvd.getY()*fvd.getY()));
194
195 double newXDisp = fvd.getX() / deltaLength
196 * Math.min(deltaLength, temperature);
197
198 assert Double.isNaN(newXDisp) == false : "Unexpected mathematical result in FRLayout:calcPositions [xdisp]";
199
200 double newYDisp = fvd.getY() / deltaLength
201 * Math.min(deltaLength, temperature);
202 double newX = xyd.getX()+Math.max(-5, Math.min(5,newXDisp));
203 double newY = xyd.getY()+Math.max(-5, Math.min(5,newYDisp));
204
205 newX = Math.max(innerBounds.getMinX(), Math.min(newX, innerBounds.getMaxX()));
206 newY = Math.max(innerBounds.getMinY(), Math.min(newY, innerBounds.getMaxY()));
207
208 xyd.setLocation(newX, newY);
209
210 }
211
212 protected void calcAttraction(E e) {
213 Pair<V> endpoints = getGraph().getEndpoints(e);
214 V v1 = endpoints.getFirst();
215 V v2 = endpoints.getSecond();
216 boolean v1_locked = isLocked(v1);
217 boolean v2_locked = isLocked(v2);
218
219 if(v1_locked && v2_locked) {
220
221 return;
222 }
223 Point2D p1 = transform(v1);
224 Point2D p2 = transform(v2);
225 if(p1 == null || p2 == null) return;
226 double xDelta = p1.getX() - p2.getX();
227 double yDelta = p1.getY() - p2.getY();
228
229 double deltaLength = Math.max(EPSILON, p1.distance(p2));
230
231 double force = deltaLength / attraction_constant;
232
233 assert Double.isNaN(force) == false : "Unexpected mathematical result in FRLayout:calcPositions [force]";
234
235 double dx = xDelta * force;
236 double dy = yDelta * force;
237 Point2D fvd1 = frVertexData.get(v1);
238 Point2D fvd2 = frVertexData.get(v2);
239 if(v2_locked) {
240
241
242 fvd1.setLocation(fvd1.getX()-2*dx, fvd1.getY()-2*dy);
243 } else {
244 fvd1.setLocation(fvd1.getX()-dx, fvd1.getY()-dy);
245 }
246 if(v1_locked) {
247
248
249 fvd2.setLocation(fvd2.getX()+2*dx, fvd2.getY()+2*dy);
250 } else {
251 fvd2.setLocation(fvd2.getX()+dx, fvd2.getY()+dy);
252 }
253 }
254
255 protected void calcRepulsion(V v1) {
256 Point2D fvd1 = frVertexData.get(v1);
257 if(fvd1 == null) return;
258 fvd1.setLocation(0, 0);
259 boolean v1_locked = isLocked(v1);
260
261 try {
262 for(V v2 : getGraph().getVertices()) {
263
264 boolean v2_locked = isLocked(v2);
265 if (v1_locked && v2_locked) continue;
266 if (v1 != v2) {
267 Point2D p1 = transform(v1);
268 Point2D p2 = transform(v2);
269 if(p1 == null || p2 == null) continue;
270 double xDelta = p1.getX() - p2.getX();
271 double yDelta = p1.getY() - p2.getY();
272
273 double deltaLength = Math.max(EPSILON, p1.distanceSq(p2));
274
275 double force = (repulsion_constant * repulsion_constant);
276
277 double forceOverDeltaLength = force / deltaLength;
278
279 assert Double.isNaN(force) == false : "Unexpected mathematical result in FRLayout:calcPositions [repulsion]";
280
281 if(v2_locked) {
282
283
284 fvd1.setLocation(fvd1.getX()+2 * xDelta * forceOverDeltaLength,
285 fvd1.getY()+ 2 * yDelta * forceOverDeltaLength);
286 } else {
287 fvd1.setLocation(fvd1.getX()+xDelta * forceOverDeltaLength,
288 fvd1.getY()+yDelta * forceOverDeltaLength);
289 }
290 }
291 }
292 } catch(ConcurrentModificationException cme) {
293 calcRepulsion(v1);
294 }
295 }
296
297 private void cool() {
298 temperature *= (1.0 - currentIteration / (double) maxIterations);
299 }
300
301
302
303
304 public void setMaxIterations(int maxIterations) {
305 this.maxIterations = maxIterations;
306 }
307
308
309
310
311 public boolean isIncremental() {
312 return true;
313 }
314
315
316
317
318
319 public boolean done() {
320 if (currentIteration > maxIterations || temperature < 1.0/max_dimension) {
321 if (!checked)
322 {
323
324
325 checked = true;
326 }
327 return true;
328 }
329 return false;
330 }
331 }