View Javadoc

1   /*
2    * Copyright (c) 2005, the JUNG Project and the Regents of the University of
3    * California All rights reserved.
4    * 
5    * This software is open-source under the BSD license; see either "license.txt"
6    * or http://jung.sourceforge.net/license.txt for a description.
7    * 
8    * Created on March 10, 2005
9    */
10  package edu.uci.ics.jung.visualization.decorators;
11  
12  import java.awt.Shape;
13  import java.awt.geom.AffineTransform;
14  import java.awt.geom.CubicCurve2D;
15  import java.awt.geom.Ellipse2D;
16  import java.awt.geom.GeneralPath;
17  import java.awt.geom.Line2D;
18  import java.awt.geom.QuadCurve2D;
19  import java.awt.geom.Rectangle2D;
20  
21  import edu.uci.ics.jung.graph.Graph;
22  import edu.uci.ics.jung.graph.util.Context;
23  import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
24  import edu.uci.ics.jung.graph.util.EdgeType;
25  import edu.uci.ics.jung.graph.util.Pair;
26  import edu.uci.ics.jung.visualization.util.ArrowFactory;
27  
28  
29  /**
30   * An interface for decorators that return a 
31   * <code>Shape</code> for a specified edge.
32   * 
33   * All edge shapes must be defined so that their endpoints are at
34   * (0,0) and (1,0). They will be scaled, rotated and translated into
35   * position by the PluggableRenderer.
36   *  
37   * @author Tom Nelson
38   * @param <Edge>
39   */
40  public class EdgeShape<V,E>  {
41      
42      /**
43       * a convenience instance for other edge shapes to use
44       * for self-loop edges where parallel instances will not
45       * overlay each other.
46       */
47      @SuppressWarnings("unchecked")
48      protected static Loop loop = new Loop();
49      
50      /**
51       * a convenience instance for other edge shapes to use
52       * for self-loop edges where parallel instances overlay each
53       * other
54       */
55      @SuppressWarnings("unchecked")
56      protected static SimpleLoop simpleLoop = new SimpleLoop();
57      
58      @SuppressWarnings("unchecked")
59      protected static Box box = new Box();
60  
61      /**
62       * An edge shape that renders as a straight line between
63       * the vertex endpoints.
64       */
65      public static class Line<V,E> extends AbstractEdgeShapeTransformer<V,E> {
66  
67          /**
68           * Singleton instance of the Line2D edge shape
69           */
70          private static Line2D instance = new Line2D.Float(0.0f, 0.0f, 1.0f, 0.0f);
71          /**
72           * Get the shape for this edge, returning either the
73           * shared instance or, in the case of self-loop edges, the
74           * SimpleLoop shared instance.
75           */
76          @SuppressWarnings("unchecked")
77  		public Shape transform(Context<Graph<V,E>,E> context) {
78          	Graph<V,E> graph = context.graph;
79          	E e = context.element;
80              
81              Pair<V> endpoints = graph.getEndpoints(e);
82              if(endpoints != null) {
83              	boolean isLoop = endpoints.getFirst().equals(endpoints.getSecond());
84              	if (isLoop) {
85              		return loop.transform(context);
86              	}
87              }
88              return instance;
89          }
90      }
91      
92      /**
93       * An edge shape that renders as a bent-line between the
94       * vertex endpoints.
95       */
96      public static class BentLine<V,E> 
97               extends AbstractEdgeShapeTransformer<V,E> implements IndexedRendering<V,E> {
98          
99          /**
100          * singleton instance of the BentLine shape
101          */
102         private static GeneralPath instance = new GeneralPath();
103         
104         protected EdgeIndexFunction<V,E> parallelEdgeIndexFunction;
105 
106         @SuppressWarnings("unchecked")
107 		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> parallelEdgeIndexFunction) {
108             this.parallelEdgeIndexFunction = parallelEdgeIndexFunction;
109             loop.setEdgeIndexFunction(parallelEdgeIndexFunction);
110         }
111         
112         
113 
114         /**
115 		 * @return the parallelEdgeIndexFunction
116 		 */
117 		public EdgeIndexFunction<V, E> getEdgeIndexFunction() {
118 			return parallelEdgeIndexFunction;
119 		}
120 
121 
122 
123 		/**
124          * Get the shape for this edge, returning either the
125          * shared instance or, in the case of self-loop edges, the
126          * Loop shared instance.
127          */
128         @SuppressWarnings("unchecked")
129 		public Shape transform(Context<Graph<V,E>,E> context) {
130         	Graph<V,E> graph = context.graph;
131         	E e = context.element;
132             Pair<V> endpoints = graph.getEndpoints(e);
133             if(endpoints != null) {
134             	boolean isLoop = endpoints.getFirst().equals(endpoints.getSecond());
135             	if (isLoop) {
136             		return loop.transform(context);
137             	}
138             }
139             
140             int index = 1;
141             if(parallelEdgeIndexFunction != null) {
142                 index = parallelEdgeIndexFunction.getIndex(graph, e);
143             }
144             float controlY = control_offset_increment + control_offset_increment*index;
145             instance.reset();
146             instance.moveTo(0.0f, 0.0f);
147             instance.lineTo(0.5f, controlY);
148             instance.lineTo(1.0f, 1.0f);
149             return instance;
150         }
151 
152     }
153     
154     /**
155      * An edge shape that renders as a QuadCurve between vertex
156      * endpoints.
157      */
158     public static class QuadCurve<V,E>
159            extends AbstractEdgeShapeTransformer<V,E> implements IndexedRendering<V,E> {
160         
161         /**
162          * singleton instance of the QuadCurve shape
163          */
164         private static QuadCurve2D instance = new QuadCurve2D.Float();
165         
166         protected EdgeIndexFunction<V,E> parallelEdgeIndexFunction;
167 
168         @SuppressWarnings("unchecked")
169 		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> parallelEdgeIndexFunction) {
170             this.parallelEdgeIndexFunction = parallelEdgeIndexFunction;
171             loop.setEdgeIndexFunction(parallelEdgeIndexFunction);
172         }
173 
174        /**
175 		 * @return the parallelEdgeIndexFunction
176 		 */
177 		public EdgeIndexFunction<V, E> getEdgeIndexFunction() {
178 			return parallelEdgeIndexFunction;
179 		}
180 
181 	/**
182          * Get the shape for this edge, returning either the
183          * shared instance or, in the case of self-loop edges, the
184          * Loop shared instance.
185          */
186         @SuppressWarnings("unchecked")
187 		public Shape transform(Context<Graph<V,E>,E> context) {
188         	Graph<V,E> graph = context.graph;
189         	E e = context.element;
190             Pair<V> endpoints = graph.getEndpoints(e);
191             if(endpoints != null) {
192             	boolean isLoop = endpoints.getFirst().equals(endpoints.getSecond());
193             	if (isLoop) {
194             		return loop.transform(context);
195             	}
196             }
197             
198             int index = 1;
199             if(parallelEdgeIndexFunction != null) {
200                 index = parallelEdgeIndexFunction.getIndex(graph, e);
201             }
202             
203             float controlY = control_offset_increment + 
204                 control_offset_increment * index;
205             instance.setCurve(0.0f, 0.0f, 0.5f, controlY, 1.0f, 0.0f);
206             return instance;
207         }
208     }
209     
210     /**
211      * An edge shape that renders as a CubicCurve between vertex
212      * endpoints.  The two control points are at 
213      * (1/3*length, 2*controlY) and (2/3*length, controlY)
214      * giving a 'spiral' effect.
215      */
216     public static class CubicCurve<V,E> 
217          extends AbstractEdgeShapeTransformer<V,E> implements IndexedRendering<V,E> {
218         
219         /**
220          * singleton instance of the CubicCurve edge shape
221          */
222         private static CubicCurve2D instance = new CubicCurve2D.Float();
223         
224         protected EdgeIndexFunction<V,E> parallelEdgeIndexFunction;
225 
226         @SuppressWarnings("unchecked")
227 		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> parallelEdgeIndexFunction) {
228             this.parallelEdgeIndexFunction = parallelEdgeIndexFunction;
229             loop.setEdgeIndexFunction(parallelEdgeIndexFunction);
230        }
231 
232         /**
233 		 * @return the parallelEdgeIndexFunction
234 		 */
235 		public EdgeIndexFunction<V, E> getEdgeIndexFunction() {
236 			return parallelEdgeIndexFunction;
237 		}
238 
239 		/**
240          * Get the shape for this edge, returning either the
241          * shared instance or, in the case of self-loop edges, the
242          * Loop shared instance.
243          */
244         @SuppressWarnings("unchecked")
245 		public Shape transform(Context<Graph<V,E>,E> context) {
246         	Graph<V,E> graph = context.graph;
247         	E e = context.element;
248            Pair<V> endpoints = graph.getEndpoints(e);
249            if(endpoints != null) {
250         	   boolean isLoop = endpoints.getFirst().equals(endpoints.getSecond());
251         	   if (isLoop) {
252         		   return loop.transform(context);
253         	   }
254            }
255            
256            int index = 1;
257            if(parallelEdgeIndexFunction != null) {
258                index = parallelEdgeIndexFunction.getIndex(graph, e);
259            }
260 
261 			float controlY = control_offset_increment
262 			    + control_offset_increment * index;
263 			instance.setCurve(0.0f, 0.0f, 0.33f, 2 * controlY, .66f, -controlY,
264 					1.0f, 0.0f);
265             return instance;
266         }
267     }
268     
269     /**
270 	 * An edge shape that renders as a loop with its nadir at the center of the
271 	 * vertex. Parallel instances will overlap.
272 	 * 
273      * @author Tom Nelson 
274      */
275     public static class SimpleLoop<V,E> extends AbstractEdgeShapeTransformer<V,E> {
276         
277         /**
278          * singleton instance of the SimpleLoop shape
279          */
280         private static Ellipse2D instance = new Ellipse2D.Float(-.5f, -.5f, 1, 1);
281         
282         /**
283          * getter for the shape
284          * @return the shared instance
285          */
286         public Shape transform(Context<Graph<V,E>,E> context) {
287             return instance;
288         }
289     }
290     
291     /**
292      * An edge shape that renders as a loop with its nadir at the
293      * center of the vertex. Parallel instances will not overlap.
294      */
295     public static class Loop<V,E>
296            extends AbstractEdgeShapeTransformer<V,E> implements IndexedRendering<V,E> {
297         
298         /**
299          * singleton instance of the Loop shape
300          */
301         private static Ellipse2D instance = new Ellipse2D.Float();
302         
303         protected EdgeIndexFunction<V,E> parallelEdgeIndexFunction;
304 
305         public void setEdgeIndexFunction(EdgeIndexFunction<V,E> parallelEdgeIndexFunction) {
306             this.parallelEdgeIndexFunction = parallelEdgeIndexFunction;
307         }
308 
309 
310         /**
311 		 * @return the parallelEdgeIndexFunction
312 		 */
313 		public EdgeIndexFunction<V, E> getEdgeIndexFunction() {
314 			return parallelEdgeIndexFunction;
315 		}
316 
317 
318 		/**
319          * Get the shape for this edge, modifying the diameter in the
320          * case of parallel edges, so they do not overlap
321          */
322         public Shape transform(Context<Graph<V,E>,E> context) {
323         	Graph<V,E> graph = context.graph;
324         	E e = context.element;
325             int count = 1;
326             if(parallelEdgeIndexFunction != null) {
327                 count = parallelEdgeIndexFunction.getIndex(graph, e);
328             }
329             
330             float x = -.5f;
331             float y = -.5f;
332             float diam = 1.f;
333             diam += diam*count/2;
334             x += x*count/2;
335             y += y*count/2;
336             instance.setFrame(x,y,diam,diam);
337             return instance;
338         }
339     }
340 
341     /**
342      * An edge shape that renders as an isosceles triangle whose
343      * apex is at the destination vertex for directed edges,
344      * and as a "bowtie" shape for undirected edges.
345      * @author Joshua O'Madadhain
346      */
347     public static class Wedge<V,E> extends AbstractEdgeShapeTransformer<V,E> {
348         private static GeneralPath triangle;
349         private static GeneralPath bowtie;
350         
351         public Wedge(int width)  {
352             triangle = ArrowFactory.getWedgeArrow(width, 1);
353             triangle.transform(AffineTransform.getTranslateInstance(1,0));
354             bowtie = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
355             bowtie.moveTo(0, width/2);
356             bowtie.lineTo(1, -width/2);
357             bowtie.lineTo(1, width/2);
358             bowtie.lineTo(0, -width/2);
359             bowtie.closePath();
360         }
361         
362         public Shape transform(Context<Graph<V,E>,E> context) {
363         	Graph<V,E> graph = context.graph;
364         	E e = context.element;
365         
366             Pair<V> endpoints = graph.getEndpoints(e);
367             if(endpoints != null) {
368             	boolean isLoop = endpoints.getFirst().equals(endpoints.getSecond());
369             	if (isLoop) {
370             		return Loop.instance;
371             	}
372             }
373             if (graph.getEdgeType(e) == EdgeType.DIRECTED)
374                 return triangle;
375             else
376                 return bowtie;
377         }
378     }
379     
380     /**
381      * An edge shape that renders as a loop with its nadir at the
382      * center of the vertex. Parallel instances will not overlap.
383      */
384     public static class Box<V,E>
385            extends AbstractEdgeShapeTransformer<V,E> implements IndexedRendering<V,E> {
386         
387         /**
388          * singleton instance of the Loop shape
389          */
390         private static Rectangle2D instance = new Rectangle2D.Float();
391         
392         protected EdgeIndexFunction<V,E> parallelEdgeIndexFunction;
393 
394         public void setEdgeIndexFunction(EdgeIndexFunction<V,E> parallelEdgeIndexFunction) {
395             this.parallelEdgeIndexFunction = parallelEdgeIndexFunction;
396         }
397 
398         /**
399 		 * @return the parallelEdgeIndexFunction
400 		 */
401 		public EdgeIndexFunction<V, E> getEdgeIndexFunction() {
402 			return parallelEdgeIndexFunction;
403 		}
404 
405 		/**
406          * Get the shape for this edge, modifying the diameter in the
407          * case of parallel edges, so they do not overlap
408          */
409         public Shape transform(Context<Graph<V,E>,E> context) {
410         	Graph<V,E> graph = context.graph;
411         	E e = context.element;
412             int count = 1;
413             if(parallelEdgeIndexFunction != null) {
414                 count = parallelEdgeIndexFunction.getIndex(graph, e);
415             }
416             
417             float x = -.5f;
418             float y = -.5f;
419             float diam = 1.f;
420             diam += diam*count/2;
421             x += x*count/2;
422             y += y*count/2;
423             instance.setFrame(x,y,diam,diam);
424             return instance;
425         }
426     }
427 
428 
429     /**
430      * An edge shape that renders as a bent-line between the
431      * vertex endpoints.
432      */
433     public static class Orthogonal<V,E> 
434              extends AbstractEdgeShapeTransformer<V,E> implements IndexedRendering<V,E> {
435         
436         /**
437          * singleton instance of the BentLine shape
438          */
439         private static Line2D instance = new Line2D.Float(0.0f, 0.0f, 1.0f, 0.0f);
440         
441         protected EdgeIndexFunction<V,E> edgeIndexFunction;
442 
443         @SuppressWarnings("unchecked")
444 		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> edgeIndexFunction) {
445             this.edgeIndexFunction = edgeIndexFunction;
446             box.setEdgeIndexFunction(edgeIndexFunction);
447         }
448 
449         /**
450 		 * @return the parallelEdgeIndexFunction
451 		 */
452 		public EdgeIndexFunction<V, E> getEdgeIndexFunction() {
453 			return edgeIndexFunction;
454 		}
455 
456 		/**
457          * Get the shape for this edge, returning either the
458          * shared instance or, in the case of self-loop edges, the
459          * Loop shared instance.
460          */
461         @SuppressWarnings("unchecked")
462 		public Shape transform(Context<Graph<V,E>,E> context) {
463         	Graph<V,E> graph = context.graph;
464         	E e = context.element;
465             Pair<V> endpoints = graph.getEndpoints(e);
466             if(endpoints != null) {
467             	boolean isLoop = endpoints.getFirst().equals(endpoints.getSecond());
468             	if (isLoop) {
469             		return box.transform(context);
470             	}
471             }
472             return instance;
473         }
474     }
475     
476     public static interface IndexedRendering<V,E> {
477         void setEdgeIndexFunction(EdgeIndexFunction<V,E> peif);
478         EdgeIndexFunction<V,E> getEdgeIndexFunction();
479     }
480 }
481     
482