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