View Javadoc

1   /*
2    * Copyright (c) 2003, 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 Jul 20, 2004
9    */
10  package edu.uci.ics.jung.visualization.util;
11  
12  import java.awt.Shape;
13  import java.awt.geom.AffineTransform;
14  import java.awt.geom.Ellipse2D;
15  import java.awt.geom.GeneralPath;
16  import java.awt.geom.Point2D;
17  import java.awt.geom.Rectangle2D;
18  import java.awt.geom.RoundRectangle2D;
19  
20  import org.apache.commons.collections15.Transformer;
21  import org.apache.commons.collections15.functors.ConstantTransformer;
22  
23  /**
24   * A utility class for generating <code>Shape</code>s for drawing vertices.  
25   * The available shapes include rectangles, rounded rectangles, ellipses,
26   * regular polygons, and regular stars.  The dimensions of the requested 
27   * shapes are defined by the specified vertex size function (specified by
28   * a <code>Transformer<V,Integer></code>) and vertex aspect ratio function 
29   * (specified by a <code>Transformer<V,Float></code>) implementations: the width
30   * of the bounding box of the shape is given by the vertex size, and the
31   * height is given by the size multiplied by the vertex's aspect ratio.
32   *  
33   * @author Joshua O'Madadhain
34   */
35  public class VertexShapeFactory<V>
36  {
37      protected Transformer<V,Integer> vsf;
38      protected Transformer<V,Float> varf;
39      
40      /**
41       * Creates a <code>VertexShapeFactory</code> with the specified 
42       * vertex size and aspect ratio functions.
43       */
44      public VertexShapeFactory(Transformer<V,Integer> vsf, Transformer<V,Float> varf)
45      {
46          this.vsf = vsf;
47          this.varf = varf;
48      }
49      
50      /**
51       * Creates a <code>VertexShapeFactory</code> with a constant size of
52       * 10 and a constant aspect ratio of 1.
53       */
54      @SuppressWarnings("unchecked")
55  	public VertexShapeFactory()
56      {
57          this(new ConstantTransformer(10), 
58              new ConstantTransformer(1.0f));
59      }
60      
61      private static final Rectangle2D theRectangle = new Rectangle2D.Float();
62      /**
63       * Returns a <code>Rectangle2D</code> whose width and 
64       * height are defined by this instance's size and
65       * aspect ratio functions for this vertex.
66       */
67      public Rectangle2D getRectangle(V v)
68      {
69          float width = vsf.transform(v);
70          float height = width * varf.transform(v);
71          float h_offset = -(width / 2);
72          float v_offset = -(height / 2);
73          theRectangle.setFrame(h_offset, v_offset, width, height);
74          return theRectangle;
75      }
76  
77      private static final Ellipse2D theEllipse = new Ellipse2D.Float();
78      /**
79       * Returns a <code>Ellipse2D</code> whose width and 
80       * height are defined by this instance's size and
81       * aspect ratio functions for this vertex.
82       */
83      public Ellipse2D getEllipse(V v)
84      {
85          theEllipse.setFrame(getRectangle(v));
86          return theEllipse;
87      }
88      
89      private static final RoundRectangle2D theRoundRectangle =
90          new RoundRectangle2D.Float();
91      /**
92       * Returns a <code>RoundRectangle2D</code> whose width and 
93       * height are defined by this instance's size and
94       * aspect ratio functions for this vertex.  The arc size is
95       * set to be half the minimum of the height and width of the frame.
96       */
97      public RoundRectangle2D getRoundRectangle(V v)
98      {
99          Rectangle2D frame = getRectangle(v);
100         float arc_size = (float)Math.min(frame.getHeight(), frame.getWidth()) / 2;
101         theRoundRectangle.setRoundRect(frame.getX(), frame.getY(),
102                 frame.getWidth(), frame.getHeight(), arc_size, arc_size);
103         return theRoundRectangle;
104     }
105     
106     private static final GeneralPath thePolygon = new GeneralPath();
107     /**
108      * Returns a regular <code>num_sides</code>-sided 
109      * <code>Polygon</code> whose bounding 
110      * box's width and height are defined by this instance's size and
111      * aspect ratio functions for this vertex.
112      * @param num_sides the number of sides of the polygon; must be >= 3.
113      */
114     public Shape getRegularPolygon(V v, int num_sides)
115     {
116         if (num_sides < 3)
117             throw new IllegalArgumentException("Number of sides must be >= 3");
118         Rectangle2D frame = getRectangle(v);
119         float width = (float)frame.getWidth();
120         float height = (float)frame.getHeight();
121         
122         // generate coordinates
123         double angle = 0;
124         thePolygon.reset();
125         thePolygon.moveTo(0,0);
126         thePolygon.lineTo(width, 0);
127         double theta = (2 * Math.PI) / num_sides; 
128         for (int i = 2; i < num_sides; i++)
129         {
130             angle -= theta; 
131             float delta_x = (float) (width * Math.cos(angle));
132             float delta_y = (float) (width * Math.sin(angle));
133             Point2D prev = thePolygon.getCurrentPoint();
134             thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
135         }
136         thePolygon.closePath();
137         
138         // scale polygon to be right size, translate to center at (0,0)
139         Rectangle2D r = thePolygon.getBounds2D();
140         double scale_x = width / r.getWidth();
141         double scale_y = height / r.getHeight();
142         float translationX = (float) (r.getMinX() + r.getWidth()/2);
143         float translationY = (float) (r.getMinY() + r.getHeight()/2);
144 
145         AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y);
146         at.translate(-translationX, -translationY);
147 
148         Shape shape = at.createTransformedShape(thePolygon);
149         return shape;
150     }
151     
152     /**
153      * Returns a regular <code>Polygon</code> of <code>num_points</code>
154      * points whose bounding 
155      * box's width and height are defined by this instance's size and
156      * aspect ratio functions for this vertex.
157      * @param num_points the number of points of the polygon; must be >= 5.
158      */
159     public Shape getRegularStar(V v, int num_points)
160     {
161         if (num_points < 5)
162             throw new IllegalArgumentException("Number of sides must be >= 5");
163         Rectangle2D frame = getRectangle(v);
164         float width = (float) frame.getWidth();
165         float height = (float) frame.getHeight();
166         
167         // generate coordinates
168         double theta = (2 * Math.PI) / num_points;
169         double angle = -theta/2;
170         thePolygon.reset();
171         thePolygon.moveTo(0,0);
172         float delta_x = width * (float)Math.cos(angle);
173         float delta_y = width * (float)Math.sin(angle);
174         Point2D prev = thePolygon.getCurrentPoint();
175         thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
176         for (int i = 1; i < num_points; i++)
177         {
178             angle += theta; 
179             delta_x = width * (float)Math.cos(angle);
180             delta_y = width * (float)Math.sin(angle);
181             prev = thePolygon.getCurrentPoint();
182             thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
183             angle -= theta*2; 
184             delta_x = width * (float)Math.cos(angle);
185             delta_y = width * (float)Math.sin(angle);
186             prev = thePolygon.getCurrentPoint();
187             thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
188         }
189         thePolygon.closePath();
190         
191         // scale polygon to be right size, translate to center at (0,0)
192         Rectangle2D r = thePolygon.getBounds2D();
193         double scale_x = width / r.getWidth();
194         double scale_y = height / r.getHeight();
195 
196         float translationX = (float) (r.getMinX() + r.getWidth()/2);
197         float translationY = (float) (r.getMinY() + r.getHeight()/2);
198         
199         AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y);
200         at.translate(-translationX, -translationY);
201 
202         Shape shape = at.createTransformedShape(thePolygon);
203         return shape;
204     }
205 }