1
2
3
4
5
6
7
8
9
10 package edu.uci.ics.jung.algorithms.layout;
11
12
13
14
15
16 import java.awt.Dimension;
17 import java.awt.geom.Point2D;
18 import java.util.ConcurrentModificationException;
19
20 import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
21 import edu.uci.ics.jung.algorithms.shortestpath.Distance;
22 import edu.uci.ics.jung.algorithms.shortestpath.DistanceStatistics;
23 import edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath;
24 import edu.uci.ics.jung.algorithms.util.IterativeContext;
25 import edu.uci.ics.jung.graph.Graph;
26
27
28
29
30
31
32
33
34
35
36 public class KKLayout<V,E> extends AbstractLayout<V,E> implements IterativeContext {
37
38 private double EPSILON = 0.1d;
39
40 private int currentIteration;
41 private int maxIterations = 2000;
42 private String status = "KKLayout";
43
44 private double L;
45 private double K = 1;
46 private double[][] dm;
47
48 private boolean adjustForGravity = true;
49 private boolean exchangeVertices = true;
50
51 private V[] vertices;
52 private Point2D[] xydata;
53
54
55
56
57 protected Distance<V> distance;
58
59
60
61
62
63 protected double diameter;
64
65
66
67
68 private double length_factor = 0.9;
69
70
71
72
73
74 private double disconnected_multiplier = 0.5;
75
76 public KKLayout(Graph<V,E> g)
77 {
78 this(g, new UnweightedShortestPath<V,E>(g));
79 }
80
81
82
83
84
85
86 public KKLayout(Graph<V,E> g, Distance<V> distance){
87 super(g);
88 this.distance = distance;
89 }
90
91
92
93
94
95 public void setLengthFactor(double length_factor){
96 this.length_factor = length_factor;
97 }
98
99
100
101
102
103 public void setDisconnectedDistanceMultiplier(double disconnected_multiplier){
104 this.disconnected_multiplier = disconnected_multiplier;
105 }
106
107
108
109
110 public String getStatus() {
111 return status + this.getSize();
112 }
113
114 public void setMaxIterations(int maxIterations) {
115 this.maxIterations = maxIterations;
116 }
117
118
119
120
121 public boolean isIncremental() {
122 return true;
123 }
124
125
126
127
128 public boolean done() {
129 if (currentIteration > maxIterations) {
130 return true;
131 }
132 return false;
133 }
134
135 @SuppressWarnings("unchecked")
136 public void initialize() {
137 currentIteration = 0;
138
139 if(graph != null && size != null) {
140
141 double height = size.getHeight();
142 double width = size.getWidth();
143
144 int n = graph.getVertexCount();
145 dm = new double[n][n];
146 vertices = (V[])graph.getVertices().toArray();
147 xydata = new Point2D[n];
148
149
150 while(true) {
151 try {
152 int index = 0;
153 for(V v : graph.getVertices()) {
154 Point2D xyd = apply(v);
155 vertices[index] = v;
156 xydata[index] = xyd;
157 index++;
158 }
159 break;
160 } catch(ConcurrentModificationException cme) {}
161 }
162
163 diameter = DistanceStatistics.<V,E>diameter(graph, distance, true);
164
165 double L0 = Math.min(height, width);
166 L = (L0 / diameter) * length_factor;
167
168
169 for (int i = 0; i < n - 1; i++) {
170 for (int j = i + 1; j < n; j++) {
171 Number d_ij = distance.getDistance(vertices[i], vertices[j]);
172 Number d_ji = distance.getDistance(vertices[j], vertices[i]);
173 double dist = diameter * disconnected_multiplier;
174 if (d_ij != null)
175 dist = Math.min(d_ij.doubleValue(), dist);
176 if (d_ji != null)
177 dist = Math.min(d_ji.doubleValue(), dist);
178 dm[i][j] = dm[j][i] = dist;
179 }
180 }
181 }
182 }
183
184 public void step() {
185 try {
186 currentIteration++;
187 double energy = calcEnergy();
188 status = "Kamada-Kawai V=" + getGraph().getVertexCount()
189 + "(" + getGraph().getVertexCount() + ")"
190 + " IT: " + currentIteration
191 + " E=" + energy
192 ;
193
194 int n = getGraph().getVertexCount();
195 if (n == 0)
196 return;
197
198 double maxDeltaM = 0;
199 int pm = -1;
200 for (int i = 0; i < n; i++) {
201 if (isLocked(vertices[i]))
202 continue;
203 double deltam = calcDeltaM(i);
204
205 if (maxDeltaM < deltam) {
206 maxDeltaM = deltam;
207 pm = i;
208 }
209 }
210 if (pm == -1)
211 return;
212
213 for (int i = 0; i < 100; i++) {
214 double[] dxy = calcDeltaXY(pm);
215 xydata[pm].setLocation(xydata[pm].getX()+dxy[0], xydata[pm].getY()+dxy[1]);
216
217 double deltam = calcDeltaM(pm);
218 if (deltam < EPSILON)
219 break;
220 }
221
222 if (adjustForGravity)
223 adjustForGravity();
224
225 if (exchangeVertices && maxDeltaM < EPSILON) {
226 energy = calcEnergy();
227 for (int i = 0; i < n - 1; i++) {
228 if (isLocked(vertices[i]))
229 continue;
230 for (int j = i + 1; j < n; j++) {
231 if (isLocked(vertices[j]))
232 continue;
233 double xenergy = calcEnergyIfExchanged(i, j);
234 if (energy > xenergy) {
235 double sx = xydata[i].getX();
236 double sy = xydata[i].getY();
237 xydata[i].setLocation(xydata[j]);
238 xydata[j].setLocation(sx, sy);
239 return;
240 }
241 }
242 }
243 }
244 }
245 finally {
246
247 }
248 }
249
250
251
252
253
254 public void adjustForGravity() {
255 Dimension d = getSize();
256 double height = d.getHeight();
257 double width = d.getWidth();
258 double gx = 0;
259 double gy = 0;
260 for (int i = 0; i < xydata.length; i++) {
261 gx += xydata[i].getX();
262 gy += xydata[i].getY();
263 }
264 gx /= xydata.length;
265 gy /= xydata.length;
266 double diffx = width / 2 - gx;
267 double diffy = height / 2 - gy;
268 for (int i = 0; i < xydata.length; i++) {
269 xydata[i].setLocation(xydata[i].getX()+diffx, xydata[i].getY()+diffy);
270 }
271 }
272
273 @Override
274 public void setSize(Dimension size) {
275 if(initialized == false)
276 setInitializer(new RandomLocationTransformer<V>(size));
277 super.setSize(size);
278 }
279
280 public void setAdjustForGravity(boolean on) {
281 adjustForGravity = on;
282 }
283
284 public boolean getAdjustForGravity() {
285 return adjustForGravity;
286 }
287
288
289
290
291
292
293 public void setExchangeVertices(boolean on) {
294 exchangeVertices = on;
295 }
296
297 public boolean getExchangeVertices() {
298 return exchangeVertices;
299 }
300
301
302
303
304 private double[] calcDeltaXY(int m) {
305 double dE_dxm = 0;
306 double dE_dym = 0;
307 double d2E_d2xm = 0;
308 double d2E_dxmdym = 0;
309 double d2E_dymdxm = 0;
310 double d2E_d2ym = 0;
311
312 for (int i = 0; i < vertices.length; i++) {
313 if (i != m) {
314
315 double dist = dm[m][i];
316 double l_mi = L * dist;
317 double k_mi = K / (dist * dist);
318 double dx = xydata[m].getX() - xydata[i].getX();
319 double dy = xydata[m].getY() - xydata[i].getY();
320 double d = Math.sqrt(dx * dx + dy * dy);
321 double ddd = d * d * d;
322
323 dE_dxm += k_mi * (1 - l_mi / d) * dx;
324 dE_dym += k_mi * (1 - l_mi / d) * dy;
325 d2E_d2xm += k_mi * (1 - l_mi * dy * dy / ddd);
326 d2E_dxmdym += k_mi * l_mi * dx * dy / ddd;
327 d2E_d2ym += k_mi * (1 - l_mi * dx * dx / ddd);
328 }
329 }
330
331 d2E_dymdxm = d2E_dxmdym;
332
333 double denomi = d2E_d2xm * d2E_d2ym - d2E_dxmdym * d2E_dymdxm;
334 double deltaX = (d2E_dxmdym * dE_dym - d2E_d2ym * dE_dxm) / denomi;
335 double deltaY = (d2E_dymdxm * dE_dxm - d2E_d2xm * dE_dym) / denomi;
336 return new double[]{deltaX, deltaY};
337 }
338
339
340
341
342 private double calcDeltaM(int m) {
343 double dEdxm = 0;
344 double dEdym = 0;
345 for (int i = 0; i < vertices.length; i++) {
346 if (i != m) {
347 double dist = dm[m][i];
348 double l_mi = L * dist;
349 double k_mi = K / (dist * dist);
350
351 double dx = xydata[m].getX() - xydata[i].getX();
352 double dy = xydata[m].getY() - xydata[i].getY();
353 double d = Math.sqrt(dx * dx + dy * dy);
354
355 double common = k_mi * (1 - l_mi / d);
356 dEdxm += common * dx;
357 dEdym += common * dy;
358 }
359 }
360 return Math.sqrt(dEdxm * dEdxm + dEdym * dEdym);
361 }
362
363
364
365
366 private double calcEnergy() {
367 double energy = 0;
368 for (int i = 0; i < vertices.length - 1; i++) {
369 for (int j = i + 1; j < vertices.length; j++) {
370 double dist = dm[i][j];
371 double l_ij = L * dist;
372 double k_ij = K / (dist * dist);
373 double dx = xydata[i].getX() - xydata[j].getX();
374 double dy = xydata[i].getY() - xydata[j].getY();
375 double d = Math.sqrt(dx * dx + dy * dy);
376
377
378 energy += k_ij / 2 * (dx * dx + dy * dy + l_ij * l_ij -
379 2 * l_ij * d);
380 }
381 }
382 return energy;
383 }
384
385
386
387
388
389 private double calcEnergyIfExchanged(int p, int q) {
390 if (p >= q)
391 throw new RuntimeException("p should be < q");
392 double energy = 0;
393 for (int i = 0; i < vertices.length - 1; i++) {
394 for (int j = i + 1; j < vertices.length; j++) {
395 int ii = i;
396 int jj = j;
397 if (i == p) ii = q;
398 if (j == q) jj = p;
399
400 double dist = dm[i][j];
401 double l_ij = L * dist;
402 double k_ij = K / (dist * dist);
403 double dx = xydata[ii].getX() - xydata[jj].getX();
404 double dy = xydata[ii].getY() - xydata[jj].getY();
405 double d = Math.sqrt(dx * dx + dy * dy);
406
407 energy += k_ij / 2 * (dx * dx + dy * dy + l_ij * l_ij -
408 2 * l_ij * d);
409 }
410 }
411 return energy;
412 }
413
414 public void reset() {
415 currentIteration = 0;
416 }
417 }