View Javadoc
1   /*
2    * Copyright (c) 2003, 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 7, 2003
9    * 
10   */
11  package edu.uci.ics.jung.algorithms.layout;
12  
13  import java.awt.Dimension;
14  import java.awt.geom.Point2D;
15  import java.util.ConcurrentModificationException;
16  import java.util.HashSet;
17  import java.util.Set;
18  
19  import com.google.common.base.Function;
20  import com.google.common.base.Functions;
21  import com.google.common.base.Preconditions;
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.Graph;
27  
28  /**
29   * Abstract class for implementations of {@code Layout}.  It handles some of the
30   * basic functions: storing coordinates, maintaining the dimensions, initializing
31   * the locations, maintaining locked vertices.
32   * 
33   * @author Danyel Fisher, Scott White
34   * @author Tom Nelson - converted to jung2
35   * @param <V> the vertex type
36   * @param <E> the edge type
37   */
38  abstract public class AbstractLayout<V, E> implements Layout<V,E> {
39  
40      /**
41       * A set of vertices that are fixed in place and not affected by the layout algorithm
42       */
43  	private Set<V> dontmove = new HashSet<V>();
44  
45  	protected Dimension size;
46  	protected Graph<V, E> graph;
47  	protected boolean initialized;
48  
49      protected LoadingCache<V, Point2D> locations =
50      	CacheBuilder.newBuilder().build(new CacheLoader<V, Point2D>() {
51  	    	public Point2D load(V vertex) {
52  	    		return new Point2D.Double();
53  	    	}
54      });
55  
56  	/**
57  	 * Creates an instance for {@code graph} which does not initialize the vertex locations.
58  	 * 
59  	 * @param graph the graph on which the layout algorithm is to operate
60  	 */
61  	protected AbstractLayout(Graph<V, E> graph) {
62  	    if (graph == null) 
63  	    {
64  	        throw new IllegalArgumentException("Graph must be non-null");
65  	    }
66  		this.graph = graph;
67  	}
68  	
69  	/**
70  	 * Creates an instance for {@code graph} which initializes the vertex locations
71  	 * using {@code initializer}.
72  	 * 
73  	 * @param graph the graph on which the layout algorithm is to operate
74  	 * @param initializer specifies the starting positions of the vertices
75  	 */
76      protected AbstractLayout(Graph<V,E> graph, Function<V,Point2D> initializer) {
77  		this.graph = graph;
78  		Function<V, Point2D> chain = 
79  			Functions.<V,Point2D,Point2D>compose(
80  					new Function<Point2D,Point2D>(){
81  						public Point2D apply(Point2D p) {
82  							return (Point2D)p.clone();
83  						}}, 
84  					initializer
85  					);
86  		this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); 
87  		initialized = true;
88  	}
89  	
90  	/**
91  	 * Creates an instance for {@code graph} which sets the size of the layout to {@code size}.
92  	 * 
93  	 * @param graph the graph on which the layout algorithm is to operate
94       * @param size the dimensions of the region in which the layout algorithm will place vertices
95  	 */
96  	protected AbstractLayout(Graph<V,E> graph, Dimension size) {
97  		this.graph = graph;
98  		this.size = size;
99  	}
100 	
101 	/**
102 	 * Creates an instance for {@code graph} which initializes the vertex locations
103 	 * using {@code initializer} and sets the size of the layout to {@code size}.
104 	 * 
105 	 * @param graph the graph on which the layout algorithm is to operate
106 	 * @param initializer specifies the starting positions of the vertices
107      * @param size the dimensions of the region in which the layout algorithm will place vertices
108 	 */
109     protected AbstractLayout(Graph<V,E> graph, Function<V,Point2D> initializer, Dimension size) {
110 		this.graph = graph;
111 		Function<V, Point2D> chain = 
112 			Functions.<V,Point2D,Point2D>compose(
113 					new Function<Point2D,Point2D>(){
114 						public Point2D apply(Point2D p) {
115 							return (Point2D)p.clone();
116 						}}, 
117 					initializer
118 					);
119 		this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); 
120 		this.size = size;
121 	}
122     
123     public void setGraph(Graph<V,E> graph) {
124         this.graph = graph;
125         if(size != null && graph != null) {
126         	initialize();
127         }
128     }
129     
130 	/**
131 	 * When a visualization is resized, it presumably wants to fix the
132 	 * locations of the vertices and possibly to reinitialize its data. The
133 	 * current method calls <tt>initializeLocations</tt> followed by <tt>initialize_local</tt>.
134 	 */
135 	public void setSize(Dimension size) {
136 		
137 		if(size != null && graph != null) {
138 			
139 			Dimension oldSize = this.size;
140 			this.size = size;
141 			initialize();
142 			
143 			if(oldSize != null) {
144 				adjustLocations(oldSize, size);
145 			}
146 		}
147 	}
148 	
149 	private void adjustLocations(Dimension oldSize, Dimension size) {
150 
151 		int xOffset = (size.width - oldSize.width) / 2;
152 		int yOffset = (size.height - oldSize.height) / 2;
153 
154 		// now, move each vertex to be at the new screen center
155 		while(true) {
156 		    try {
157                 for(V v : getGraph().getVertices()) {
158 		            offsetVertex(v, xOffset, yOffset);
159 		        }
160 		        break;
161 		    } catch(ConcurrentModificationException cme) {
162 		    }
163 		}
164 	}
165     
166     public boolean isLocked(V v) {
167         return dontmove.contains(v);
168     }
169     
170     public void setInitializer(Function<V,Point2D> initializer) {
171     	if(this.equals(initializer)) {
172     		throw new IllegalArgumentException("Layout cannot be initialized with itself");
173     	}
174 		Function<V, Point2D> chain = 
175 			Functions.<V,Point2D,Point2D>compose(
176 					new Function<Point2D,Point2D>(){
177 						public Point2D apply(Point2D p) {
178 							return (Point2D)p.clone();
179 						}}, 
180 					initializer
181 					);
182 		this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); 
183     	initialized = true;
184     }
185     
186 	/**
187 	 * Returns the current size of the visualization space, accoring to the
188 	 * last call to resize().
189 	 * 
190 	 * @return the current size of the screen
191 	 */
192 	public Dimension getSize() {
193 		return size;
194 	}
195 
196 	/**
197 	 * Returns the Coordinates object that stores the vertex' x and y location.
198 	 * 
199 	 * @param v
200 	 *            A Vertex that is a part of the Graph being visualized.
201 	 * @return A Coordinates object with x and y locations.
202 	 */
203 	private Point2D getCoordinates(V v) {
204         return locations.getUnchecked(v);
205 	}
206 	
207 	public Point2D apply(V v) {
208 		return getCoordinates(v);
209 	}
210 	
211 	/**
212 	 * Returns the x coordinate of the vertex from the Coordinates object.
213 	 * in most cases you will be better off calling transform(v).
214 	 * 
215 	 * @param v the vertex whose x coordinate is to be returned
216 	 * @return the x coordinate of {@code v}
217 	 */
218 	public double getX(V v) {
219         Preconditions.checkNotNull(getCoordinates(v), "Cannot getX for an unmapped vertex "+v);
220         return getCoordinates(v).getX();
221 	}
222 
223 	/**
224 	 * Returns the y coordinate of the vertex from the Coordinates object.
225 	 * In most cases you will be better off calling transform(v).
226 	 * 
227 	 * @param v the vertex whose y coordinate is to be returned
228 	 * @return the y coordinate of {@code v}
229 	 */
230 	public double getY(V v) {
231         Preconditions.checkNotNull(getCoordinates(v), "Cannot getY for an unmapped vertex "+v);
232         return getCoordinates(v).getY();
233 	}
234 	
235 	/**
236 	 * @param v the vertex whose coordinates are to be offset
237 	 * @param xOffset the change to apply to this vertex's x coordinate
238 	 * @param yOffset the change to apply to this vertex's y coordinate
239 	 */
240 	protected void offsetVertex(V v, double xOffset, double yOffset) {
241 		Point2D c = getCoordinates(v);
242         c.setLocation(c.getX()+xOffset, c.getY()+yOffset);
243 		setLocation(v, c);
244 	}
245 
246 	/**
247 	 * @return the graph that this layout operates on
248 	 */
249 	public Graph<V, E> getGraph() {
250 	    return graph;
251 	}
252 	
253 	/**
254 	 * Forcibly moves a vertex to the (x,y) location by setting its x and y
255 	 * locations to the specified location. Does not add the vertex to the
256 	 * "dontmove" list, and (in the default implementation) does not make any
257 	 * adjustments to the rest of the graph.
258 	 * @param picked the vertex whose location is being set
259 	 * @param x the x coordinate of the location to set
260 	 * @param y the y coordinate of the location to set
261 	 */
262 	public void setLocation(V picked, double x, double y) {
263 		Point2D coord = getCoordinates(picked);
264 		coord.setLocation(x, y);
265 	}
266 
267 	public void setLocation(V picked, Point2D p) {
268 		Point2D coord = getCoordinates(picked);
269 		coord.setLocation(p);
270 	}
271 
272 	/**
273 	 * Locks {@code v} in place if {@code state} is {@code true}, otherwise unlocks it.
274 	 * @param v the vertex whose position is to be (un)locked
275 	 * @param state {@code true} if the vertex is to be locked, {@code false} if to be unlocked
276 	 */
277 	public void lock(V v, boolean state) {
278 		if(state == true) 
279 		    dontmove.add(v);
280 		else 
281 		    dontmove.remove(v);
282 	}
283 	
284 	/**
285 	 * @param lock {@code true} to lock all vertices in place, {@code false} to unlock all vertices
286 	 */
287 	public void lock(boolean lock) {
288 		for(V v : graph.getVertices()) {
289 			lock(v, lock);
290 		}
291 	}
292 }