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