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 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 com.google.common.base.Function;
21  import com.google.common.base.Functions;
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 Function<? super V, Integer>}) and vertex aspect ratio function 
29   * (specified by a {@code Function<? super V, Float>}) 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 Function<? super V, Integer> vsf;
38      protected Function<? super V, Float> varf;
39      
40      /**
41       * Creates an instance with the specified vertex size and aspect ratio functions.
42       * 
43       * @param vsf provides a size (width) for each vertex
44       * @param varf provides a height/width ratio for each vertex
45       */
46      public VertexShapeFactory(Function<? super V,Integer> vsf, Function<? super V,Float> varf)
47      {
48          this.vsf = vsf;
49          this.varf = varf;
50      }
51      
52      /**
53       * Creates a <code>VertexShapeFactory</code> with a constant size of
54       * 10 and a constant aspect ratio of 1.
55       */
56  	public VertexShapeFactory()
57      {
58          this(Functions.constant(10), 
59              Functions.constant(1.0f));
60      }
61      
62      private static final Rectangle2D theRectangle = new Rectangle2D.Float();
63  
64      /**
65       * Returns a <code>Rectangle2D</code> whose width and 
66       * height are defined by this instance's size and
67       * aspect ratio functions for this vertex.
68       * 
69       * @param v the vertex for which the shape will be drawn
70       * @return a rectangle for this vertex
71       */
72      public Rectangle2D getRectangle(V v)
73      {
74          float width = vsf.apply(v);
75          float height = width * varf.apply(v);
76          float h_offset = -(width / 2);
77          float v_offset = -(height / 2);
78          theRectangle.setFrame(h_offset, v_offset, width, height);
79          return theRectangle;
80      }
81  
82      private static final Ellipse2D theEllipse = new Ellipse2D.Float();
83  
84      /**
85       * Returns a <code>Ellipse2D</code> whose width and 
86       * height are defined by this instance's size and
87       * aspect ratio functions for this vertex.
88       * 
89       * @param v the vertex for which the shape will be drawn
90       * @return an ellipse for this vertex
91       */
92      public Ellipse2D getEllipse(V v)
93      {
94          theEllipse.setFrame(getRectangle(v));
95          return theEllipse;
96      }
97      
98      private static final RoundRectangle2D theRoundRectangle =
99          new RoundRectangle2D.Float();
100     /**
101      * Returns a <code>RoundRectangle2D</code> whose width and 
102      * height are defined by this instance's size and
103      * aspect ratio functions for this vertex.  The arc size is
104      * set to be half the minimum of the height and width of the frame.
105      * 
106      * @param v the vertex for which the shape will be drawn
107      * @return an round rectangle for this vertex
108      */
109     public RoundRectangle2D getRoundRectangle(V v)
110     {
111         Rectangle2D frame = getRectangle(v);
112         float arc_size = (float)Math.min(frame.getHeight(), frame.getWidth()) / 2;
113         theRoundRectangle.setRoundRect(frame.getX(), frame.getY(),
114                 frame.getWidth(), frame.getHeight(), arc_size, arc_size);
115         return theRoundRectangle;
116     }
117     
118     private static final GeneralPath thePolygon = new GeneralPath();
119 
120     /**
121      * Returns a regular <code>num_sides</code>-sided 
122      * <code>Polygon</code> whose bounding 
123      * box's width and height are defined by this instance's size and
124      * aspect ratio functions for this vertex.
125      * 
126      * @param v the vertex for which the shape will be drawn
127      * @param num_sides the number of sides of the polygon; must be &ge; 3.
128      * @return a regular polygon for this vertex
129      */
130     public Shape getRegularPolygon(V v, int num_sides)
131     {
132         if (num_sides < 3)
133             throw new IllegalArgumentException("Number of sides must be >= 3");
134         Rectangle2D frame = getRectangle(v);
135         float width = (float)frame.getWidth();
136         float height = (float)frame.getHeight();
137         
138         // generate coordinates
139         double angle = 0;
140         thePolygon.reset();
141         thePolygon.moveTo(0,0);
142         thePolygon.lineTo(width, 0);
143         double theta = (2 * Math.PI) / num_sides; 
144         for (int i = 2; i < num_sides; i++)
145         {
146             angle -= theta; 
147             float delta_x = (float) (width * Math.cos(angle));
148             float delta_y = (float) (width * Math.sin(angle));
149             Point2D prev = thePolygon.getCurrentPoint();
150             thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
151         }
152         thePolygon.closePath();
153         
154         // scale polygon to be right size, translate to center at (0,0)
155         Rectangle2D r = thePolygon.getBounds2D();
156         double scale_x = width / r.getWidth();
157         double scale_y = height / r.getHeight();
158         float translationX = (float) (r.getMinX() + r.getWidth()/2);
159         float translationY = (float) (r.getMinY() + r.getHeight()/2);
160 
161         AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y);
162         at.translate(-translationX, -translationY);
163 
164         Shape shape = at.createTransformedShape(thePolygon);
165         return shape;
166     }
167     
168     /**
169      * Returns a regular <code>Polygon</code> of <code>num_points</code>
170      * points whose bounding 
171      * box's width and height are defined by this instance's size and
172      * aspect ratio functions for this vertex.
173      * 
174      * @param v the vertex for which the shape will be drawn
175      * @param num_points the number of points of the polygon; must be &ge; 5.
176      * @return an star shape for this vertex
177      */
178     public Shape getRegularStar(V v, int num_points)
179     {
180         if (num_points < 5)
181             throw new IllegalArgumentException("Number of sides must be >= 5");
182         Rectangle2D frame = getRectangle(v);
183         float width = (float) frame.getWidth();
184         float height = (float) frame.getHeight();
185         
186         // generate coordinates
187         double theta = (2 * Math.PI) / num_points;
188         double angle = -theta/2;
189         thePolygon.reset();
190         thePolygon.moveTo(0,0);
191         float delta_x = width * (float)Math.cos(angle);
192         float delta_y = width * (float)Math.sin(angle);
193         Point2D prev = thePolygon.getCurrentPoint();
194         thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
195         for (int i = 1; i < num_points; i++)
196         {
197             angle += theta; 
198             delta_x = width * (float)Math.cos(angle);
199             delta_y = width * (float)Math.sin(angle);
200             prev = thePolygon.getCurrentPoint();
201             thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
202             angle -= theta*2; 
203             delta_x = width * (float)Math.cos(angle);
204             delta_y = width * (float)Math.sin(angle);
205             prev = thePolygon.getCurrentPoint();
206             thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
207         }
208         thePolygon.closePath();
209         
210         // scale polygon to be right size, translate to center at (0,0)
211         Rectangle2D r = thePolygon.getBounds2D();
212         double scale_x = width / r.getWidth();
213         double scale_y = height / r.getHeight();
214 
215         float translationX = (float) (r.getMinX() + r.getWidth()/2);
216         float translationY = (float) (r.getMinY() + r.getHeight()/2);
217         
218         AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y);
219         at.translate(-translationX, -translationY);
220 
221         Shape shape = at.createTransformedShape(thePolygon);
222         return shape;
223     }
224 }