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