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   * Created on Dec 4, 2003
11   */
12  package edu.uci.ics.jung.algorithms.layout;
13  
14  import java.awt.Dimension;
15  import java.awt.geom.Point2D;
16  import java.util.HashMap;
17  import java.util.Map;
18  
19  import edu.uci.ics.jung.graph.Graph;
20  import edu.uci.ics.jung.graph.util.Pair;
21  
22  /**
23   * An implementation of {@code Layout} suitable for tree-like directed
24   * acyclic graphs. Parts of it will probably not terminate if the graph is
25   * cyclic! The layout will result in directed edges pointing generally upwards.
26   * Any vertices with no successors are considered to be level 0, and tend
27   * towards the top of the layout. Any vertex has a level one greater than the
28   * maximum level of all its successors.
29   *
30   *
31   * @author John Yesberg
32   */
33  public class DAGLayout<V, E> extends SpringLayout<V,E> {
34  
35      /**
36       * Each vertex has a minimumLevel. Any vertex with no successors has
37       * minimumLevel of zero. The minimumLevel of any vertex must be strictly
38       * greater than the minimumLevel of its parents. (Vertex A is a parent of
39       * Vertex B iff there is an edge from B to A.) Typically, a vertex will
40       * have a minimumLevel which is one greater than the minimumLevel of its
41       * parent's. However, if the vertex has two parents, its minimumLevel will
42       * be one greater than the maximum of the parents'. We need to calculate
43       * the minimumLevel for each vertex. When we layout the graph, vertices
44       * cannot be drawn any higher than the minimumLevel. The graphHeight of a
45       * graph is the greatest minimumLevel that is used. We will modify the
46       * SpringLayout calculations so that nodes cannot move above their assigned
47       * minimumLevel.
48       */
49  	private Map<V,Number> minLevels = new HashMap<V,Number>();
50  	// Simpler than the "pair" technique.
51  	static int graphHeight;
52  	static int numRoots;
53  	final double SPACEFACTOR = 1.3;
54  	// How much space do we allow for additional floating at the bottom.
55  	final double LEVELATTRACTIONRATE = 0.8;
56  
57  	/**
58  	 * A bunch of parameters to help work out when to stop quivering.
59  	 *
60  	 * If the MeanSquareVel(ocity) ever gets below the MSV_THRESHOLD, then we
61  	 * will start a final cool-down phase of COOL_DOWN_INCREMENT increments. If
62  	 * the MeanSquareVel ever exceeds the threshold, we will exit the cool down
63  	 * phase, and continue looking for another opportunity.
64  	 */
65  	final double MSV_THRESHOLD = 10.0;
66  	double meanSquareVel;
67  	boolean stoppingIncrements = false;
68  	int incrementsLeft;
69  	final int COOL_DOWN_INCREMENTS = 200;
70  
71  	public DAGLayout(Graph<V,E> g) {
72  		super(g);
73  	}
74  
75  	/**
76  	 * Calculates the level of each vertex in the graph. Level 0 is
77  	 * allocated to each vertex with no successors. Level n+1 is allocated to
78  	 * any vertex whose successors' maximum level is n.
79  	 */
80  	public void setRoot() {
81  		numRoots = 0;
82  		Graph<V, E> g = getGraph();
83  		for(V v : g.getVertices()) {
84  			if (g.getSuccessors(v).isEmpty()) {
85  				setRoot(v);
86  				numRoots++;
87  			}
88  		}
89  	}
90  
91  	/**
92  	 * Set vertex v to be level 0.
93  	 * @param v the vertex to set as root
94  	 */
95  	public void setRoot(V v) {
96  		minLevels.put(v, new Integer(0));
97  		// set all the levels.
98  		propagateMinimumLevel(v);
99  	}
100 
101 	/**
102 	 * A recursive method for allocating the level for each vertex. Ensures
103 	 * that all predecessors of v have a level which is at least one greater
104 	 * than the level of v.
105 	 *
106 	 * @param v the vertex whose minimum level is to be calculated
107 	 */
108 	public void propagateMinimumLevel(V v) {
109 		int level = minLevels.get(v).intValue();
110 		for(V child : getGraph().getPredecessors(v)) {
111 			int oldLevel, newLevel;
112 			Number o = minLevels.get(child);
113 			if (o != null)
114 				oldLevel = o.intValue();
115 			else
116 				oldLevel = 0;
117 			newLevel = Math.max(oldLevel, level + 1);
118 			minLevels.put(child, new Integer(newLevel));
119 
120 			if (newLevel > graphHeight)
121 				graphHeight = newLevel;
122 			propagateMinimumLevel(child);
123 		}
124 	}
125 
126 	/**
127 	 * Sets a random location for a vertex within the dimensions of the space.
128 	 *
129 	 * @param v the vertex whose position is to be set
130 	 * @param coord the coordinates of the vertex once the position has been set
131 	 * @param d the dimensions of the space
132 	 */
133 	private void initializeLocation(
134 		V v,
135 		Point2D coord,
136 		Dimension d) {
137 
138 		int level = minLevels.get(v).intValue();
139 		int minY = (int) (level * d.getHeight() / (graphHeight * SPACEFACTOR));
140 		double x = Math.random() * d.getWidth();
141 		double y = Math.random() * (d.getHeight() - minY) + minY;
142 		coord.setLocation(x,y);
143 	}
144 
145 	@Override
146 	public void setSize(Dimension size) {
147 		super.setSize(size);
148 		for(V v : getGraph().getVertices()) {
149 			initializeLocation(v,apply(v),getSize());
150 		}
151 	}
152 
153 	/**
154 	 * Had to override this one as well, to ensure that setRoot() is called.
155 	 */
156 	@Override
157 	public void initialize() {
158 		super.initialize();
159 		setRoot();
160 	}
161 
162 	/**
163 	 * Override the moveNodes() method from SpringLayout. The only change we
164 	 * need to make is to make sure that nodes don't float higher than the minY
165 	 * coordinate, as calculated by their minimumLevel.
166 	 */
167 	@Override
168 	protected void moveNodes() {
169 		// Dimension d = currentSize;
170 		double oldMSV = meanSquareVel;
171 		meanSquareVel = 0;
172 
173 		synchronized (getSize()) {
174 
175 			for(V v : getGraph().getVertices()) {
176 				if (isLocked(v))
177 					continue;
178 				SpringLayout.SpringVertexData vd = springVertexData.getUnchecked(v);
179 				Point2D xyd = apply(v);
180 
181 				int width = getSize().width;
182 				int height = getSize().height;
183 
184 				// (JY addition: three lines are new)
185 				int level =
186 					minLevels.get(v).intValue();
187 				int minY = (int) (level * height / (graphHeight * SPACEFACTOR));
188 				int maxY =
189 					level == 0
190 						? (int) (height / (graphHeight * SPACEFACTOR * 2))
191 						: height;
192 
193 				// JY added 2* - double the sideways repulsion.
194 				vd.dx += 2 * vd.repulsiondx + vd.edgedx;
195 				vd.dy += vd.repulsiondy + vd.edgedy;
196 
197 				// JY Addition: Attract the vertex towards it's minimumLevel
198 				// height.
199 				double delta = xyd.getY() - minY;
200 				vd.dy -= delta * LEVELATTRACTIONRATE;
201 				if (level == 0)
202 					vd.dy -= delta * LEVELATTRACTIONRATE;
203 				// twice as much at the top.
204 
205 				// JY addition:
206 				meanSquareVel += (vd.dx * vd.dx + vd.dy * vd.dy);
207 
208 				// keeps nodes from moving any faster than 5 per time unit
209 				xyd.setLocation(xyd.getX()+Math.max(-5, Math.min(5, vd.dx)) , xyd.getY()+Math.max(-5, Math.min(5, vd.dy)) );
210 
211 				if (xyd.getX() < 0) {
212 					xyd.setLocation(0, xyd.getY());
213 				} else if (xyd.getX() > width) {
214 					xyd.setLocation(width, xyd.getY());
215 				}
216 
217 				// (JY addition: These two lines replaced 0 with minY)
218 				if (xyd.getY() < minY) {
219 					xyd.setLocation(xyd.getX(), minY);
220 					// (JY addition: replace height with maxY)
221 				} else if (xyd.getY() > maxY) {
222 					xyd.setLocation(xyd.getX(), maxY);
223 				}
224 
225 				// (JY addition: if there's only one root, anchor it in the
226 				// middle-top of the screen)
227 				if (numRoots == 1 && level == 0) {
228 					xyd.setLocation(width/2, xyd.getY());
229 				}
230 			}
231 		}
232 		//System.out.println("MeanSquareAccel="+meanSquareVel);
233 		if (!stoppingIncrements
234 			&& Math.abs(meanSquareVel - oldMSV) < MSV_THRESHOLD) {
235 			stoppingIncrements = true;
236 			incrementsLeft = COOL_DOWN_INCREMENTS;
237 		} else if (
238 			stoppingIncrements
239 				&& Math.abs(meanSquareVel - oldMSV) <= MSV_THRESHOLD) {
240 			incrementsLeft--;
241 			if (incrementsLeft <= 0)
242 				incrementsLeft = 0;
243 		}
244 	}
245 
246 	/**
247 	 * Override incrementsAreDone so that we can eventually stop.
248 	 */
249 	@Override
250 	public boolean done() {
251 		if (stoppingIncrements && incrementsLeft == 0)
252 			return true;
253 		else
254 			return false;
255 	}
256 
257 	/**
258 	 * Override forceMove so that if someone moves a node, we can re-layout
259 	 * everything.
260      * @param picked the vertex whose location is to be set
261      * @param x the x coordinate of the location to set
262      * @param y the y coordinate of the location to set
263 	 */
264 	@Override
265 	public void setLocation(V picked, double x, double y) {
266 		Point2D coord = apply(picked);
267 		coord.setLocation(x,y);
268 		stoppingIncrements = false;
269 	}
270 
271 	/**
272 	 * Override forceMove so that if someone moves a node, we can re-layout
273 	 * everything.
274      * @param picked the vertex whose location is to be set
275      * @param p the location to set
276 	 */
277 	@Override
278 	public void setLocation(V picked, Point2D p) {
279 		setLocation(picked, p.getX(), p.getY());
280 	}
281 
282 	/**
283 	 * Overridden relaxEdges. This one reduces the effect of edges between
284 	 * greatly different levels.
285 	 *
286 	 */
287 	@Override
288 	protected void relaxEdges() {
289 		for(E e : getGraph().getEdges()) {
290 		    Pair<V> endpoints = getGraph().getEndpoints(e);
291 			V v1 = endpoints.getFirst();
292 			V v2 = endpoints.getSecond();
293 
294 			Point2D p1 = apply(v1);
295 			Point2D p2 = apply(v2);
296 			double vx = p1.getX() - p2.getX();
297 			double vy = p1.getY() - p2.getY();
298 			double len = Math.sqrt(vx * vx + vy * vy);
299 
300 			// JY addition.
301 			int level1 =
302 				minLevels.get(v1).intValue();
303 			int level2 =
304 				minLevels.get(v2).intValue();
305 
306 			double desiredLen = lengthFunction.apply(e);
307 
308 			// round from zero, if needed [zero would be Bad.].
309 			len = (len == 0) ? .0001 : len;
310 
311 			// force factor: optimal length minus actual length,
312 			// is made smaller as the current actual length gets larger.
313 			// why?
314 
315 			// System.out.println("Desired : " + getLength( e ));
316 			double f = force_multiplier * (desiredLen - len) / len;
317 
318 			f = f * Math.pow(stretch / 100.0,
319 					(getGraph().degree(v1) + getGraph().degree(v2) -2));
320 
321 			// JY addition. If this is an edge which stretches a long way,
322 			// don't be so concerned about it.
323 			if (level1 != level2)
324 				f = f / Math.pow(Math.abs(level2 - level1), 1.5);
325 
326 			// the actual movement distance 'dx' is the force multiplied by the
327 			// distance to go.
328 			double dx = f * vx;
329 			double dy = f * vy;
330 			SpringVertexData v1D, v2D;
331 			v1D = springVertexData.getUnchecked(v1);
332 			v2D = springVertexData.getUnchecked(v2);
333 
334 			v1D.edgedx += dx;
335 			v1D.edgedy += dy;
336 			v2D.edgedx += -dx;
337 			v2D.edgedy += -dy;
338 		}
339 	}
340 }