View Javadoc

1   /*
2    * Copyright (c) 2003, the JUNG Project and the Regents of the University 
3    * of California
4    * All rights reserved.
5    *
6    * This software is open-source under the BSD license; see either
7    * "license.txt" or
8    * http://jung.sourceforge.net/license.txt for a description.
9    */
10  package edu.uci.ics.jung.algorithms.layout;
11  /*
12   * This source is under the same license with JUNG.
13   * http://jung.sourceforge.net/license.txt for a description.
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   * Implements the Kamada-Kawai algorithm for node layout.
29   * Does not respect filter calls, and sometimes crashes when the view changes to it.
30   *
31   * @see "Tomihisa Kamada and Satoru Kawai: An algorithm for drawing general indirect graphs. Information Processing Letters 31(1):7-15, 1989" 
32   * @see "Tomihisa Kamada: On visualization of abstract objects and relations. Ph.D. dissertation, Dept. of Information Science, Univ. of Tokyo, Dec. 1988."
33   *
34   * @author Masanori Harada
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;			// the ideal length of an edge
45  	private double K = 1;		// arbitrary const number
46  	private double[][] dm;     // distance matrix
47  
48  	private boolean adjustForGravity = true;
49  	private boolean exchangeVertices = true;
50  
51  	private V[] vertices;
52  	private Point2D[] xydata;
53  
54      /**
55       * Retrieves graph distances between vertices of the visible graph
56       */
57      protected Distance<V> distance;
58  
59      /**
60       * The diameter of the visible graph. In other words, the maximum over all pairs
61       * of vertices of the length of the shortest path between a and bf the visible graph.
62       */
63  	protected double diameter;
64  
65      /**
66       * A multiplicative factor which partly specifies the "preferred" length of an edge (L).
67       */
68      private double length_factor = 0.9;
69  
70      /**
71       * A multiplicative factor which specifies the fraction of the graph's diameter to be 
72       * used as the inter-vertex distance between disconnected vertices.
73       */
74      private double disconnected_multiplier = 0.5;
75      
76  	/**
77  	 * Creates an instance for the specified graph.
78  	 */
79  	public KKLayout(Graph<V,E> g) 
80      {
81          this(g, new UnweightedShortestPath<V,E>(g));
82  	}
83  
84  	/**
85  	 * Creates an instance for the specified graph and distance metric.
86  	 */
87      public KKLayout(Graph<V,E> g, Distance<V> distance){
88          super(g);
89          this.distance = distance;
90      }
91  
92      /**
93       * Sets a multiplicative factor which 
94       * partly specifies the "preferred" length of an edge (L).
95       */
96      public void setLengthFactor(double length_factor){
97          this.length_factor = length_factor;
98      }
99      
100     /**
101      * Sets a multiplicative factor that specifies the fraction of the graph's diameter to be 
102      * used as the inter-vertex distance between disconnected vertices.
103      */
104     public void setDisconnectedDistanceMultiplier(double disconnected_multiplier){
105         this.disconnected_multiplier = disconnected_multiplier;
106     }
107     
108     /**
109      * Returns a string with information about the current status of the algorithm.
110      */
111 	public String getStatus() {
112 		return status + this.getSize();
113 	}
114 
115 	/**
116 	 * Sets the maximum number of iterations.
117 	 */
118     public void setMaxIterations(int maxIterations) {
119         this.maxIterations = maxIterations;
120     }
121 
122 	/**
123 	 * This one is an incremental visualization.
124 	 */
125 	public boolean isIncremental() {
126 		return true;
127 	}
128 
129 	/**
130 	 * Returns true once the current iteration has passed the maximum count.
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     		// assign IDs to all visible vertices
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;  // length_factor used to be hardcoded to 0.9
171     		//L = 0.75 * Math.sqrt(height * width / n);
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;            // the node having max deltaM
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 //			fireStateChanged();
251 		}
252 	}
253 
254 	/**
255 	 * Shift all vertices so that the center of gravity is located at
256 	 * the center of the screen.
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     /* (non-Javadoc)
278 	 * @see edu.uci.ics.jung.visualization.layout.AbstractLayout#setSize(java.awt.Dimension)
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 	 * Enable or disable gravity point adjusting.
289 	 */
290 	public void setAdjustForGravity(boolean on) {
291 		adjustForGravity = on;
292 	}
293 
294 	/**
295 	 * Returns true if gravity point adjusting is enabled.
296 	 */
297 	public boolean getAdjustForGravity() {
298 		return adjustForGravity;
299 	}
300 
301 	/**
302 	 * Enable or disable the local minimum escape technique by
303 	 * exchanging vertices.
304 	 */
305 	public void setExchangeVertices(boolean on) {
306 		exchangeVertices = on;
307 	}
308 
309 	/**
310 	 * Returns true if the local minimum escape technique by
311 	 * exchanging vertices is enabled.
312 	 */
313 	public boolean getExchangeVertices() {
314 		return exchangeVertices;
315 	}
316 
317 	/**
318 	 * Determines a step to new position of the vertex m.
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 		// d2E_dymdxm equals to d2E_dxmdym.
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 	 * Calculates the gradient of energy function at the vertex m.
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 	 * Calculates the energy function E.
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 	 * Calculates the energy function E as if positions of the
403 	 * specified vertices are exchanged.
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;		// < 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 }