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