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 March 10, 2005
9    */
10  package edu.uci.ics.jung.visualization.decorators;
11  
12  import static com.google.common.base.Preconditions.checkNotNull;
13  
14  import java.awt.Shape;
15  import java.awt.geom.AffineTransform;
16  import java.awt.geom.CubicCurve2D;
17  import java.awt.geom.Ellipse2D;
18  import java.awt.geom.GeneralPath;
19  import java.awt.geom.Line2D;
20  import java.awt.geom.QuadCurve2D;
21  import java.awt.geom.Rectangle2D;
22  import java.awt.geom.RectangularShape;
23  
24  import com.google.common.base.Function;
25  
26  import edu.uci.ics.jung.graph.Graph;
27  import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
28  import edu.uci.ics.jung.graph.util.EdgeType;
29  import edu.uci.ics.jung.graph.util.Pair;
30  import edu.uci.ics.jung.visualization.util.ArrowFactory;
31  
32  
33  /**
34   * An interface for decorators that return a 
35   * <code>Shape</code> for a specified edge.
36   * 
37   * All edge shapes must be defined so that their endpoints are at
38   * (0,0) and (1,0). They will be scaled, rotated and translated into
39   * position by the PluggableRenderer.
40   *  
41   * @author Tom Nelson
42   * @param <V> the vertex type
43   * @param <E> the edge type
44   */
45  public class EdgeShape<V,E> {
46      private static final Line2D LINE = new Line2D.Float(0.0f, 0.0f, 1.0f, 0.0f);
47      private static final GeneralPath BENT_LINE = new GeneralPath();
48      private static final QuadCurve2D QUAD_CURVE = new QuadCurve2D.Float();
49      private static final CubicCurve2D CUBIC_CURVE = new CubicCurve2D.Float();
50      private static final Ellipse2D ELLIPSE = new Ellipse2D.Float(-.5f, -.5f, 1, 1);
51      private static Rectangle2D BOX = new Rectangle2D.Float();
52  
53      private static GeneralPath triangle;
54      private static GeneralPath bowtie;
55  
56  	protected final Graph<V, E> graph;
57  	
58      /**
59       * A convenience instance for other edge shapes to use for self-loop edges 
60       * where parallel instances will not overlay each other.
61       */
62      protected final Loop loop;
63      
64      /**
65       * A convenience instance for other edge shapes to use for self-loop edges
66       * where parallel instances overlay each other.
67       */
68      protected final SimpleLoop simpleLoop;
69  
70      protected final Box box;
71  
72  	public EdgeShape(Graph<V, E> g) {
73  		this.graph = g;
74  		this.box = new Box();
75  		this.loop = new Loop();
76  		this.simpleLoop = new SimpleLoop();
77  	}
78  	
79  	private Shape getLoopOrNull(E e) {
80  		return getLoopOrNull(e, loop);
81  	}
82  	
83  	private Shape getLoopOrNull(E e, Function<? super E, Shape> loop) {
84          Pair<V> endpoints = graph.getEndpoints(e);
85          checkNotNull(endpoints);
86      	boolean isLoop = endpoints.getFirst().equals(endpoints.getSecond());
87      	if (isLoop) {
88      		return loop.apply(e);
89      	}
90          return null;
91  	}
92  	
93  	public static <V, E> EdgeShape<V, E>.Line line(Graph<V, E> graph) {
94          return new EdgeShape<V, E>(graph).new Line();
95  	}
96  	
97  	public static <V, E> EdgeShape<V, E>.QuadCurve quadCurve(Graph<V, E> graph) {
98          return new EdgeShape<V, E>(graph).new QuadCurve();
99  	}
100 	
101 	public static <V, E> EdgeShape<V, E>.QuadCurve cubicCurve(Graph<V, E> graph) {
102         return new EdgeShape<V, E>(graph).new QuadCurve();
103 	}
104 	
105 	public static <V, E> EdgeShape<V, E>.Orthogonal orthogonal(Graph<V, E> graph) {
106 		return new EdgeShape<V, E>(graph).new Orthogonal();
107 	}
108 	
109 	public static <V, E> EdgeShape<V, E>.Wedge wedge(Graph<V, E> graph, int width) {
110 		return new EdgeShape<V, E>(graph).new Wedge(width);
111 	}
112 	
113     /**
114      * An edge shape that renders as a straight line between
115      * the vertex endpoints.
116      */
117     public class Line implements Function<E, Shape> {
118         /**
119          * Get the shape for this edge, returning either the
120          * shared instance or, in the case of self-loop edges, the 
121          * Loop shared instance.
122          */
123 		public Shape apply(E e) {
124 			Shape loop = getLoopOrNull(e);
125 			return loop == null
126 					? LINE
127 					: loop;
128         }
129     }
130 
131     private int getIndex(E e, EdgeIndexFunction<V, E> edgeIndexFunction) {
132     	return edgeIndexFunction == null
133     			? 1
134     			: edgeIndexFunction.getIndex(null, e);
135     }
136     
137     /**
138      * An edge shape that renders as a bent-line between the
139      * vertex endpoints.
140      */
141     public class BentLine extends ParallelEdgeShapeTransformer<V,E> {
142 		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> edgeIndexFunction) {
143 			this.edgeIndexFunction = edgeIndexFunction;
144             loop.setEdgeIndexFunction(edgeIndexFunction);
145         }
146 
147 		/**
148          * Get the shape for this edge, returning either the
149          * shared instance or, in the case of self-loop edges, the
150          * Loop shared instance.
151          */
152 		public Shape apply(E e) {
153         	Shape edgeShape = getLoopOrNull(e);
154         	if (edgeShape != null) {
155         		return edgeShape;
156         	}
157 
158         	int index = getIndex(e, edgeIndexFunction);
159             float controlY = control_offset_increment + control_offset_increment * index;
160             BENT_LINE.reset();
161             BENT_LINE.moveTo(0.0f, 0.0f);
162             BENT_LINE.lineTo(0.5f, controlY);
163             BENT_LINE.lineTo(1.0f, 1.0f);
164             return BENT_LINE;
165         }
166 
167     }
168     
169     /**
170      * An edge shape that renders as a QuadCurve between vertex
171      * endpoints.
172      */
173     public class QuadCurve extends ParallelEdgeShapeTransformer<V,E> {
174     	@Override
175 		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> parallelEdgeIndexFunction) {
176             this.edgeIndexFunction = parallelEdgeIndexFunction;
177             loop.setEdgeIndexFunction(parallelEdgeIndexFunction);
178         }
179 
180     	/**
181          * Get the shape for this edge, returning either the
182          * shared instance or, in the case of self-loop edges, the
183          * Loop shared instance.
184          */
185 		public Shape apply(E e) {
186         	Shape edgeShape = getLoopOrNull(e);
187         	if (edgeShape != null) {
188         		return edgeShape;
189         	}
190             
191             int index = getIndex(e, edgeIndexFunction);
192             
193             float controlY = control_offset_increment + 
194                 control_offset_increment * index;
195             QUAD_CURVE.setCurve(0.0f, 0.0f, 0.5f, controlY, 1.0f, 0.0f);
196             return QUAD_CURVE;
197         }
198     }
199     
200     /**
201      * An edge shape that renders as a CubicCurve between vertex
202      * endpoints.  The two control points are at 
203      * (1/3*length, 2*controlY) and (2/3*length, controlY)
204      * giving a 'spiral' effect.
205      */
206     public class CubicCurve extends ParallelEdgeShapeTransformer<V,E> {
207 		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> edgeIndexFunction) {
208             this.edgeIndexFunction = edgeIndexFunction;
209             loop.setEdgeIndexFunction(edgeIndexFunction);
210        }
211 
212 		/**
213          * Get the shape for this edge, returning either the
214          * shared instance or, in the case of self-loop edges, the
215          * Loop shared instance.
216          */
217 		public Shape apply(E e) {
218         	Shape edgeShape = getLoopOrNull(e);
219         	if (edgeShape != null) {
220         		return edgeShape;
221         	}
222             
223             int index = getIndex(e, edgeIndexFunction);
224 
225 			float controlY = control_offset_increment
226 			    + control_offset_increment * index;
227 			CUBIC_CURVE.setCurve(0.0f, 0.0f, 0.33f, 2 * controlY, .66f, -controlY,
228 					1.0f, 0.0f);
229             return CUBIC_CURVE;
230         }
231     }
232     
233     /**
234 	 * An edge shape that renders as a loop with its nadir at the center of the
235 	 * vertex. Parallel instances will overlap.
236 	 * 
237      * @author Tom Nelson 
238      */
239     public class SimpleLoop extends ParallelEdgeShapeTransformer<V,E> {
240         public Shape apply(E e) {
241             return ELLIPSE;
242         }
243     }
244     
245     private Shape buildFrame(RectangularShape shape, int index) {
246         float x = -.5f;
247         float y = -.5f;
248         float diam = 1.f;
249         diam += diam * index/2;
250         x += x * index/2;
251         y += y * index/2;
252     	
253         shape.setFrame(x, y, diam, diam);
254         
255         return shape;
256     }
257     
258     /**
259      * An edge shape that renders as a loop with its nadir at the
260      * center of the vertex. Parallel instances will not overlap.
261      */
262     public class Loop extends ParallelEdgeShapeTransformer<V,E> {
263         public Shape apply(E e) {
264             return buildFrame(ELLIPSE, getIndex(e, edgeIndexFunction));
265         }
266     }
267 
268     /**
269      * An edge shape that renders as an isosceles triangle whose
270      * apex is at the destination vertex for directed edges,
271      * and as a "bowtie" shape for undirected edges.
272      * @author Joshua O'Madadhain
273      */
274     public class Wedge extends ParallelEdgeShapeTransformer<V,E> {
275         public Wedge(int width)  {
276             triangle = ArrowFactory.getWedgeArrow(width, 1);
277             triangle.transform(AffineTransform.getTranslateInstance(1,0));
278             bowtie = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
279             bowtie.moveTo(0, width/2);
280             bowtie.lineTo(1, -width/2);
281             bowtie.lineTo(1, width/2);
282             bowtie.lineTo(0, -width/2);
283             bowtie.closePath();
284         }
285         
286         public Shape apply(E e) {
287         	Shape edgeShape = getLoopOrNull(e);
288         	if (edgeShape != null) {
289         		return edgeShape;
290         	}
291         	return (graph.getEdgeType(e) == EdgeType.DIRECTED)
292         			? triangle
293         			: bowtie;
294         }
295     }
296     
297     /**
298      * An edge shape that renders as a diamond with its nadir at the
299      * center of the vertex. Parallel instances will not overlap.
300      */
301     public class Box extends ParallelEdgeShapeTransformer<V,E> {
302         public Shape apply(E e) {
303             return buildFrame(BOX, getIndex(e, edgeIndexFunction));
304         }
305     }
306 
307 
308     /**
309      * An edge shape that renders as a bent-line between the vertex endpoints.
310      */
311     public class Orthogonal extends ParallelEdgeShapeTransformer<V,E> {
312 		public Shape apply(E e) {
313 			Shape loop = getLoopOrNull(e, box);
314 			return loop == null
315 					? LINE
316 					: loop;
317         }
318     }
319 }
320     
321