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 Aug 23, 2005
9    */
10  package edu.uci.ics.jung.visualization.renderers;
11  
12  import java.awt.Shape;
13  import java.awt.geom.AffineTransform;
14  import java.awt.geom.GeneralPath;
15  import java.awt.geom.Line2D;
16  import java.awt.geom.PathIterator;
17  import java.awt.geom.Point2D;
18  
19  import edu.uci.ics.jung.visualization.RenderContext;
20  
21  public class BasicEdgeArrowRenderingSupport<V,E> implements EdgeArrowRenderingSupport<V, E>  {
22  
23      /* (non-Javadoc)
24  	 * @see edu.uci.ics.jung.visualization.renderers.EdgeArrowRenderingSupport#getArrowTransform(edu.uci.ics.jung.visualization.RenderContext, java.awt.geom.GeneralPath, java.awt.Shape)
25  	 */
26      public AffineTransform getArrowTransform(RenderContext<V,E> rc, Shape edgeShape, Shape vertexShape) {
27      	GeneralPath path = new GeneralPath(edgeShape);
28          float[] seg = new float[6];
29          Point2D p1=null;
30          Point2D p2=null;
31          AffineTransform at = new AffineTransform();
32          // when the PathIterator is done, switch to the line-subdivide
33          // method to get the arrowhead closer.
34          for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) {
35              int ret = i.currentSegment(seg);
36              if(ret == PathIterator.SEG_MOVETO) {
37                  p2 = new Point2D.Float(seg[0],seg[1]);
38              } else if(ret == PathIterator.SEG_LINETO) {
39                  p1 = p2;
40                  p2 = new Point2D.Float(seg[0],seg[1]);
41                  if(vertexShape.contains(p2)) {
42                      at = getArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
43                      break;
44                  }
45              } 
46          }
47          return at;
48      }
49  
50      /* (non-Javadoc)
51  	 * @see edu.uci.ics.jung.visualization.renderers.EdgeArrowRenderingSupport#getReverseArrowTransform(edu.uci.ics.jung.visualization.RenderContext, java.awt.geom.GeneralPath, java.awt.Shape)
52  	 */
53      public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Shape edgeShape, Shape vertexShape) {
54          return getReverseArrowTransform(rc, edgeShape, vertexShape, true);
55      }
56              
57      /* (non-Javadoc)
58  	 * @see edu.uci.ics.jung.visualization.renderers.EdgeArrowRenderingSupport#getReverseArrowTransform(edu.uci.ics.jung.visualization.RenderContext, java.awt.geom.GeneralPath, java.awt.Shape, boolean)
59  	 */
60      public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Shape edgeShape, Shape vertexShape,
61              boolean passedGo) {
62      	GeneralPath path = new GeneralPath(edgeShape);
63          float[] seg = new float[6];
64          Point2D p1=null;
65          Point2D p2=null;
66  
67          AffineTransform at = new AffineTransform();
68          for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) {
69              int ret = i.currentSegment(seg);
70              if(ret == PathIterator.SEG_MOVETO) {
71                  p2 = new Point2D.Float(seg[0],seg[1]);
72              } else if(ret == PathIterator.SEG_LINETO) {
73                  p1 = p2;
74                  p2 = new Point2D.Float(seg[0],seg[1]);
75                  if(passedGo == false && vertexShape.contains(p2)) {
76                      passedGo = true;
77                   } else if(passedGo==true &&
78                          vertexShape.contains(p2)==false) {
79                       at = getReverseArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
80                      break;
81                  }
82              } 
83          }
84          return at;
85      }
86  
87      /* (non-Javadoc)
88  	 * @see edu.uci.ics.jung.visualization.renderers.EdgeArrowRenderingSupport#getArrowTransform(edu.uci.ics.jung.visualization.RenderContext, java.awt.geom.Line2D, java.awt.Shape)
89  	 */
90      public AffineTransform getArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
91          float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
92          float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
93          // iterate over the line until the edge shape will place the
94          // arrowhead closer than 'arrowGap' to the vertex shape boundary
95          while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
96              try {
97                  edgeShape = getLastOutsideSegment(edgeShape, vertexShape);
98              } catch(IllegalArgumentException e) {
99                  System.err.println(e.toString());
100                 return null;
101             }
102             dx = (float) (edgeShape.getX1()-edgeShape.getX2());
103             dy = (float) (edgeShape.getY1()-edgeShape.getY2());
104         }
105         double atheta = Math.atan2(dx,dy)+Math.PI/2;
106         AffineTransform at = 
107             AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1());
108         at.rotate(-atheta);
109         return at;
110     }
111 
112     /**
113      * This is used for the reverse-arrow of a non-directed edge
114      * get a transform to place the arrow shape on the passed edge at the
115      * point where it intersects the passed shape
116      * @param edgeShape
117      * @param vertexShape
118      * @return
119      */
120     protected AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
121         float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
122         float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
123         // iterate over the line until the edge shape will place the
124         // arrowhead closer than 'arrowGap' to the vertex shape boundary
125         while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
126             try {
127                 edgeShape = getFirstOutsideSegment(edgeShape, vertexShape);
128             } catch(IllegalArgumentException e) {
129                 System.err.println(e.toString());
130                 return null;
131             }
132             dx = (float) (edgeShape.getX1()-edgeShape.getX2());
133             dy = (float) (edgeShape.getY1()-edgeShape.getY2());
134         }
135         // calculate the angle for the arrowhead
136         double atheta = Math.atan2(dx,dy)-Math.PI/2;
137         AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1());
138         at.rotate(-atheta);
139         return at;
140     }
141     
142     /**
143      * Passed Line's point2 must be inside the passed shape or
144      * an IllegalArgumentException is thrown
145      * @param line line to subdivide
146      * @param shape shape to compare with line
147      * @return a line that intersects the shape boundary
148      * @throws IllegalArgumentException if the passed line's point1 is not inside the shape
149      */
150     protected Line2D getLastOutsideSegment(Line2D line, Shape shape) {
151         if(shape.contains(line.getP2())==false) {
152             String errorString =
153                 "line end point: "+line.getP2()+" is not contained in shape: "+shape.getBounds2D();
154             throw new IllegalArgumentException(errorString);
155             //return null;
156         }
157         Line2D left = new Line2D.Double();
158         Line2D right = new Line2D.Double();
159         // subdivide the line until its left segment intersects
160         // the shape boundary
161         do {
162             subdivide(line, left, right);
163             line = right;
164         } while(shape.contains(line.getP1())==false);
165         // now that right is completely inside shape,
166         // return left, which must be partially outside
167         return left;
168     }
169    
170     /**
171      * Passed Line's point1 must be inside the passed shape or
172      * an IllegalArgumentException is thrown
173      * @param line line to subdivide
174      * @param shape shape to compare with line
175      * @return a line that intersects the shape boundary
176      * @throws IllegalArgumentException if the passed line's point1 is not inside the shape
177      */
178     protected Line2D getFirstOutsideSegment(Line2D line, Shape shape) {
179         
180         if(shape.contains(line.getP1())==false) {
181             String errorString = 
182                 "line start point: "+line.getP1()+" is not contained in shape: "+shape.getBounds2D();
183             throw new IllegalArgumentException(errorString);
184         }
185         Line2D left = new Line2D.Float();
186         Line2D right = new Line2D.Float();
187         // subdivide the line until its right side intersects the
188         // shape boundary
189         do {
190             subdivide(line, left, right);
191             line = left;
192         } while(shape.contains(line.getP2())==false);
193         // now that left is completely inside shape,
194         // return right, which must be partially outside
195         return right;
196     }
197 
198     /**
199      * divide a Line2D into 2 new Line2Ds that are returned
200      * in the passed left and right instances, if non-null
201      * @param src the line to divide
202      * @param left the left side, or null
203      * @param right the right side, or null
204      */
205     protected void subdivide(Line2D src,
206             Line2D left,
207             Line2D right) {
208         double x1 = src.getX1();
209         double y1 = src.getY1();
210         double x2 = src.getX2();
211         double y2 = src.getY2();
212         
213         double mx = x1 + (x2-x1)/2.0;
214         double my = y1 + (y2-y1)/2.0;
215         if (left != null) {
216             left.setLine(x1, y1, mx, my);
217         }
218         if (right != null) {
219             right.setLine(mx, my, x2, y2);
220         }
221     }
222 
223 }