View Javadoc
1   /*
2    * Copyright (c) 2003, The JUNG Authors 
3    *
4    * All rights reserved.
5    *
6    * This software is open-source under the BSD license; see either
7    * "license.txt" or
8    * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
9    */
10  package edu.uci.ics.jung.algorithms.layout;
11  /*
12   * This source is under the same license with JUNG.
13   * https://github.com/jrtom/jung/blob/master/LICENSE 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  	public KKLayout(Graph<V,E> g) 
77      {
78          this(g, new UnweightedShortestPath<V,E>(g));
79  	}
80  
81  	/**
82  	 * Creates an instance for the specified graph and distance metric.
83  	 * @param g the graph on which the layout algorithm is to operate
84  	 * @param distance specifies the distance between pairs of vertices
85  	 */
86      public KKLayout(Graph<V,E> g, Distance<V> distance){
87          super(g);
88          this.distance = distance;
89      }
90  
91      /**
92       * @param length_factor a multiplicative factor which partially specifies
93       *     the preferred length of an edge
94       */
95      public void setLengthFactor(double length_factor){
96          this.length_factor = length_factor;
97      }
98      
99      /**
100      * @param disconnected_multiplier a multiplicative factor that specifies the fraction of the
101      *     graph's diameter to be used as the inter-vertex distance between disconnected vertices
102      */
103     public void setDisconnectedDistanceMultiplier(double disconnected_multiplier){
104         this.disconnected_multiplier = disconnected_multiplier;
105     }
106     
107     /**
108      * @return a string with information about the current status of the algorithm.
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 	 * @return true
120 	 */
121 	public boolean isIncremental() {
122 		return true;
123 	}
124 
125 	/**
126 	 * @return true if the current iteration has passed the maximum count.
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     		// assign IDs to all visible vertices
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;  // length_factor used to be hardcoded to 0.9
167     		//L = 0.75 * Math.sqrt(height * width / n);
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;            // the node having max deltaM
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 //			fireStateChanged();
247 		}
248 	}
249 
250 	/**
251 	 * Shift all vertices so that the center of gravity is located at
252 	 * the center of the screen.
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 	 * Enable or disable the local minimum escape technique by
290 	 * exchanging vertices.
291 	 * @param on iff the local minimum escape technique is to be enabled
292 	 */
293 	public void setExchangeVertices(boolean on) {
294 		exchangeVertices = on;
295 	}
296 
297 	public boolean getExchangeVertices() {
298 		return exchangeVertices;
299 	}
300 
301 	/**
302 	 * Determines a step to new position of the vertex m.
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 		// d2E_dymdxm equals to d2E_dxmdym.
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 	 * Calculates the gradient of energy function at the vertex m.
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 	 * Calculates the energy function E.
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 	 * Calculates the energy function E as if positions of the
387 	 * specified vertices are exchanged.
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;		// < 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 }