View Javadoc
1   /*
2    * Copyright (c) 2005, The JUNG Authors
3    * All rights reserved.
4    *
5    * This software is open-source under the BSD license; see either "license.txt"
6    * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
7    *
8    * Created on Jul 9, 2005
9    */
10  
11  package edu.uci.ics.jung.algorithms.layout;
12  import java.awt.Dimension;
13  import java.awt.Point;
14  import java.awt.geom.Point2D;
15  import java.util.Collection;
16  import java.util.HashMap;
17  import java.util.HashSet;
18  import java.util.Map;
19  import java.util.Set;
20  
21  import com.google.common.base.Function;
22  import com.google.common.cache.CacheBuilder;
23  import com.google.common.cache.CacheLoader;
24  import com.google.common.cache.LoadingCache;
25  
26  import edu.uci.ics.jung.graph.Forest;
27  import edu.uci.ics.jung.graph.Graph;
28  import edu.uci.ics.jung.graph.util.TreeUtils;
29  
30  /**
31   * @author Karlheinz Toni
32   * @author Tom Nelson - converted to jung2
33   */
34  public class TreeLayout<V,E> implements Layout<V,E> {
35  
36  	protected Dimension size = new Dimension(600,600);
37  	protected Forest<V,E> graph;
38  	protected Map<V,Integer> basePositions = new HashMap<V,Integer>();
39  
40      protected LoadingCache<V, Point2D> locations =
41      	CacheBuilder.newBuilder().build(new CacheLoader<V, Point2D>() {
42  	    	public Point2D load(V vertex) {
43  	    		return new Point2D.Double();
44  	    	}
45      });
46      
47      protected transient Set<V> alreadyDone = new HashSet<V>();
48  
49      /**
50       * The default horizontal vertex spacing.  Initialized to 50.
51       */
52      public static int DEFAULT_DISTX = 50;
53      
54      /**
55       * The default vertical vertex spacing.  Initialized to 50.
56       */
57      public static int DEFAULT_DISTY = 50;
58      
59      /**
60       * The horizontal vertex spacing.  Defaults to {@code DEFAULT_XDIST}.
61       */
62      protected int distX = 50;
63      
64      /**
65       * The vertical vertex spacing.  Defaults to {@code DEFAULT_YDIST}.
66       */
67      protected int distY = 50;
68      
69      protected transient Point m_currentPoint = new Point();
70  
71      /**
72       * Creates an instance for the specified graph with default X and Y distances.
73  	 * @param g the graph on which the layout algorithm is to operate
74       */
75      public TreeLayout(Forest<V,E> g) {
76      	this(g, DEFAULT_DISTX, DEFAULT_DISTY);
77      }
78  
79      /**
80       * Creates an instance for the specified graph and X distance with
81       * default Y distance.
82  	 * @param g the graph on which the layout algorithm is to operate
83  	 * @param distx the horizontal spacing between adjacent siblings
84       */
85      public TreeLayout(Forest<V,E> g, int distx) {
86          this(g, distx, DEFAULT_DISTY);
87      }
88  
89      /**
90       * Creates an instance for the specified graph, X distance, and Y distance.
91  	 * @param g the graph on which the layout algorithm is to operate
92  	 * @param distx the horizontal spacing between adjacent siblings
93  	 * @param disty the vertical spacing between adjacent siblings
94       */
95      public TreeLayout(Forest<V,E> g, int distx, int disty) {
96          if (g == null)
97              throw new IllegalArgumentException("Graph must be non-null");
98          if (distx < 1 || disty < 1)
99              throw new IllegalArgumentException("X and Y distances must each be positive");
100     	this.graph = g;
101         this.distX = distx;
102         this.distY = disty;
103         buildTree();
104     }
105     
106 	protected void buildTree() {
107         this.m_currentPoint = new Point(0, 20);
108         Collection<V> roots = TreeUtils.getRoots(graph);
109         if (roots.size() > 0 && graph != null) {
110        		calculateDimensionX(roots);
111        		for(V v : roots) {
112         		calculateDimensionX(v);
113         		m_currentPoint.x += this.basePositions.get(v)/2 + this.distX;
114         		buildTree(v, this.m_currentPoint.x);
115         	}
116         }
117     }
118 
119     protected void buildTree(V v, int x) {
120 
121         if (alreadyDone.add(v)) {
122             //go one level further down
123             this.m_currentPoint.y += this.distY;
124             this.m_currentPoint.x = x;
125 
126             this.setCurrentPositionFor(v);
127 
128             int sizeXofCurrent = basePositions.get(v);
129 
130             int lastX = x - sizeXofCurrent / 2;
131 
132             int sizeXofChild;
133             int startXofChild;
134 
135             for (V element : graph.getSuccessors(v)) {
136                 sizeXofChild = this.basePositions.get(element);
137                 startXofChild = lastX + sizeXofChild / 2;
138                 buildTree(element, startXofChild);
139                 lastX = lastX + sizeXofChild + distX;
140             }
141             this.m_currentPoint.y -= this.distY;
142         }
143     }
144     
145     private int calculateDimensionX(V v) {
146 
147         int size = 0;
148         int childrenNum = graph.getSuccessors(v).size();
149 
150         if (childrenNum != 0) {
151             for (V element : graph.getSuccessors(v)) {
152                 size += calculateDimensionX(element) + distX;
153             }
154         }
155         size = Math.max(0, size - distX);
156         basePositions.put(v, size);
157 
158         return size;
159     }
160 
161     private int calculateDimensionX(Collection<V> roots) {
162 
163     	int size = 0;
164     	for(V v : roots) {
165     		int childrenNum = graph.getSuccessors(v).size();
166 
167     		if (childrenNum != 0) {
168     			for (V element : graph.getSuccessors(v)) {
169     				size += calculateDimensionX(element) + distX;
170     			}
171     		}
172     		size = Math.max(0, size - distX);
173     		basePositions.put(v, size);
174     	}
175 
176     	return size;
177     }
178     
179     /**
180      * This method is not supported by this class.  The size of the layout
181      * is determined by the topology of the tree, and by the horizontal 
182      * and vertical spacing (optionally set by the constructor).
183      */
184     public void setSize(Dimension size) {
185         throw new UnsupportedOperationException("Size of TreeLayout is set" +
186                 " by vertex spacing in constructor");
187     }
188 
189     protected void setCurrentPositionFor(V vertex) {
190     	int x = m_currentPoint.x;
191     	int y = m_currentPoint.y;
192     	if(x < 0) size.width -= x;
193     	
194     	if(x > size.width-distX) 
195     		size.width = x + distX;
196     	
197     	if(y < 0) size.height -= y;
198     	if(y > size.height-distY) 
199     		size.height = y + distY;
200     	locations.getUnchecked(vertex).setLocation(m_currentPoint);
201 
202     }
203 
204 	public Graph<V,E> getGraph() {
205 		return graph;
206 	}
207 
208 	public Dimension getSize() {
209 		return size;
210 	}
211 
212 	public void initialize() {
213 
214 	}
215 
216 	public boolean isLocked(V v) {
217 		return false;
218 	}
219 
220 	public void lock(V v, boolean state) {
221 	}
222 
223 	public void reset() {
224 	}
225 
226 	public void setGraph(Graph<V,E> graph) {
227 		if(graph instanceof Forest) {
228 			this.graph = (Forest<V,E>)graph;
229 			buildTree();
230 		} else {
231 			throw new IllegalArgumentException("graph must be a Forest");
232 		}
233 	}
234 
235 	public void setInitializer(Function<V, Point2D> initializer) {
236 	}
237 	
238     /**
239      * @return the center of this layout's area.
240      */
241 	public Point2D getCenter() {
242 		return new Point2D.Double(size.getWidth()/2,size.getHeight()/2);
243 	}
244 
245 	public void setLocation(V v, Point2D location) {
246 		locations.getUnchecked(v).setLocation(location);
247 	}
248 	
249 	public Point2D apply(V v) {
250 		return locations.getUnchecked(v);
251 	}
252 }