View Javadoc

1   /*
2    * Copyright (c) 2005, the JUNG Project and the Regents of the University 
3    * of California
4    * All rights reserved.
5    *
6    * This software is open-source under the BSD license; see either
7    * "license.txt" or
8    * http://jung.sourceforge.net/license.txt for a description.
9    * Created on Mar 11, 2005
10   *
11   */
12  package edu.uci.ics.jung.visualization.picking;
13  
14  import java.awt.Shape;
15  import java.awt.geom.AffineTransform;
16  import java.awt.geom.GeneralPath;
17  import java.awt.geom.PathIterator;
18  import java.awt.geom.Point2D;
19  import java.awt.geom.Rectangle2D;
20  import java.util.Collection;
21  import java.util.ConcurrentModificationException;
22  import java.util.HashSet;
23  import java.util.Set;
24  
25  import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
26  import edu.uci.ics.jung.algorithms.layout.Layout;
27  import edu.uci.ics.jung.graph.Graph;
28  import edu.uci.ics.jung.graph.util.Context;
29  import edu.uci.ics.jung.graph.util.Pair;
30  import edu.uci.ics.jung.visualization.Layer;
31  import edu.uci.ics.jung.visualization.VisualizationServer;
32  import edu.uci.ics.jung.visualization.transform.MutableTransformerDecorator;
33  
34  /**
35   * ShapePickSupport provides access to Vertices and EdgeType based on
36   * their actual shapes. 
37   * 
38   * @author Tom Nelson
39   *
40   */
41  public class ViewLensShapePickSupport<V, E> extends ShapePickSupport<V,E>
42  	implements GraphElementAccessor<V,E> {
43  
44      /**
45       * Create an instance.
46       * The HasGraphLayout is used as the source of the current
47       * Graph Layout. The HasShapes
48       * is used to access the VertexShapes and the EdgeShapes
49       * @param hasGraphLayout source of the current layout.
50       * @param hasShapeFunctions source of Vertex and Edge shapes.
51       * @param pickSize how large to make the pick footprint for line edges
52       */
53      public ViewLensShapePickSupport(VisualizationServer<V,E> vv, float pickSize) {
54      	super(vv, pickSize);
55      }
56      
57      /**
58       * Create an instance.
59       * The pickSize footprint defaults to 2.
60       */
61      public ViewLensShapePickSupport(VisualizationServer<V,E> vv) {
62          this(vv, 2);
63      }
64  
65      /** 
66       * Iterates over Vertices, checking to see if x,y is contained in the
67       * Vertex's Shape. If (x,y) is contained in more than one vertex, use
68       * the vertex whose center is closest to the pick point.
69       * @see edu.uci.ics.jung.visualization.picking.PickSupport#getVertex(double, double)
70       */
71      public V getVertex(Layout<V, E> layout, double x, double y) {
72  
73          V closest = null;
74          double minDistance = Double.MAX_VALUE;
75          Point2D ip = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(new Point2D.Double(x,y));
76          x = ip.getX();
77          y = ip.getY();
78  
79          while(true) {
80              try {
81                  for(V v : getFilteredVertices(layout)) {
82                  	// get the shape
83                      Shape shape = vv.getRenderContext().getVertexShapeTransformer().transform(v);
84                      // transform the vertex location to screen coords
85                      Point2D p = layout.transform(v);
86                      if(p == null) continue;
87                      AffineTransform xform = 
88                          AffineTransform.getTranslateInstance(p.getX(), p.getY());
89                      shape = xform.createTransformedShape(shape);
90                      
91                      // use the LAYOUT transform to move the shape center without
92                      // modifying the actual shape
93                      Point2D lp = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p);
94                      AffineTransform xlate = AffineTransform.getTranslateInstance(
95                      		lp.getX()-p.getX(),lp.getY()-p.getY());
96                      shape = xlate.createTransformedShape(shape);
97                      // now use the VIEW transform to modify the actual shape
98                      
99                      shape = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.VIEW, shape);
100                     	//vv.getRenderContext().getMultiLayerTransformer().transform(shape);
101                     
102                     // see if this vertex center is closest to the pick point
103                     // among any other containing vertices
104                     if(shape.contains(x, y)) {
105 
106                     	if(style == Style.LOWEST) {
107                     		// return the first match
108                     		return v;
109                     	} else if(style == Style.HIGHEST) {
110                     		// will return the last match
111                     		closest = v;
112                     	} else {
113                     		Rectangle2D bounds = shape.getBounds2D();
114                     		double dx = bounds.getCenterX() - x;
115                     		double dy = bounds.getCenterY() - y;
116                     		double dist = dx * dx + dy * dy;
117                     		if (dist < minDistance) {
118                     			minDistance = dist;
119                     			closest = v;
120                     		}
121                     	}
122                     }
123                 }
124                 break;
125             } catch(ConcurrentModificationException cme) {}
126         }
127         return closest;
128     }
129 
130     /**
131      * returns the vertices that are contained in the passed shape.
132      * The shape is in screen coordinates, and the graph vertices
133      * are transformed to screen coordinates before they are tested
134      * for inclusion
135      */
136     public Collection<V> getVertices(Layout<V, E> layout, Shape rectangle) {
137     	Set<V> pickedVertices = new HashSet<V>();
138     	
139 //    	 remove the view transform from the rectangle
140     	rectangle = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(rectangle);
141 
142         while(true) {
143             try {
144                 for(V v : getFilteredVertices(layout)) {
145                     Point2D p = layout.transform(v);
146                     if(p == null) continue;
147                    	// get the shape
148                     Shape shape = vv.getRenderContext().getVertexShapeTransformer().transform(v);
149 
150                     AffineTransform xform = 
151                         AffineTransform.getTranslateInstance(p.getX(), p.getY());
152                     shape = xform.createTransformedShape(shape);
153                     
154                     shape = vv.getRenderContext().getMultiLayerTransformer().transform(shape);
155                     Rectangle2D bounds = shape.getBounds2D();
156                     p.setLocation(bounds.getCenterX(),bounds.getCenterY());
157 
158                     if(rectangle.contains(p)) {
159                     	pickedVertices.add(v);
160                     }
161                 }
162                 break;
163             } catch(ConcurrentModificationException cme) {}
164         }
165         return pickedVertices;
166     }
167     /**
168      * return an edge whose shape intersects the 'pickArea' footprint of the passed
169      * x,y, coordinates.
170      */
171     public E getEdge(Layout<V, E> layout, double x, double y) {
172 
173         Point2D ip = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(new Point2D.Double(x,y));
174         x = ip.getX();
175         y = ip.getY();
176 
177         // as a Line has no area, we can't always use edgeshape.contains(point) so we
178         // make a small rectangular pickArea around the point and check if the
179         // edgeshape.intersects(pickArea)
180         Rectangle2D pickArea = 
181             new Rectangle2D.Float((float)x-pickSize/2,(float)y-pickSize/2,pickSize,pickSize);
182         E closest = null;
183         double minDistance = Double.MAX_VALUE;
184         while(true) {
185             try {
186                 for(E e : getFilteredEdges(layout)) {
187 
188                     Pair<V> pair = layout.getGraph().getEndpoints(e);
189                     V v1 = pair.getFirst();
190                     V v2 = pair.getSecond();
191                     boolean isLoop = v1.equals(v2);
192                     Point2D p1 = layout.transform(v1);
193                     	//vv.getRenderContext().getBasicTransformer().transform(layout.transform(v1));
194                     Point2D p2 = layout.transform(v2);
195                     	//vv.getRenderContext().getBasicTransformer().transform(layout.transform(v2));
196                     if(p1 == null || p2 == null) continue;
197                     float x1 = (float) p1.getX();
198                     float y1 = (float) p1.getY();
199                     float x2 = (float) p2.getX();
200                     float y2 = (float) p2.getY();
201 
202                     // translate the edge to the starting vertex
203                     AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
204 
205                     Shape edgeShape = 
206                     	vv.getRenderContext().getEdgeShapeTransformer().transform(Context.<Graph<V,E>,E>getInstance(vv.getGraphLayout().getGraph(),e));
207                     if(isLoop) {
208                         // make the loops proportional to the size of the vertex
209                         Shape s2 = vv.getRenderContext().getVertexShapeTransformer().transform(v2);
210                         Rectangle2D s2Bounds = s2.getBounds2D();
211                         xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
212                         // move the loop so that the nadir is centered in the vertex
213                         xform.translate(0, -edgeShape.getBounds2D().getHeight()/2);
214                     } else {
215                         float dx = x2 - x1;
216                         float dy = y2 - y1;
217                         // rotate the edge to the angle between the vertices
218                         double theta = Math.atan2(dy,dx);
219                         xform.rotate(theta);
220                         // stretch the edge to span the distance between the vertices
221                         float dist = (float) Math.sqrt(dx*dx + dy*dy);
222                         xform.scale(dist, 1.0f);
223                     }
224 
225                     // transform the edge to its location and dimensions
226                     edgeShape = xform.createTransformedShape(edgeShape);
227                     
228                     edgeShape = vv.getRenderContext().getMultiLayerTransformer().transform(edgeShape);
229 
230                     // because of the transform, the edgeShape is now a GeneralPath
231                     // see if this edge is the closest of any that intersect
232                     if(edgeShape.intersects(pickArea)) {
233                         float cx=0;
234                         float cy=0;
235                         float[] f = new float[6];
236                         PathIterator pi = new GeneralPath(edgeShape).getPathIterator(null);
237                         if(pi.isDone()==false) {
238                             pi.next();
239                             pi.currentSegment(f);
240                             cx = f[0];
241                             cy = f[1];
242                             if(pi.isDone()==false) {
243                                 pi.currentSegment(f);
244                                 cx = f[0];
245                                 cy = f[1];
246                             }
247                         }
248                         float dx = (float) (cx - x);
249                         float dy = (float) (cy - y);
250                         float dist = dx * dx + dy * dy;
251                         if (dist < minDistance) {
252                             minDistance = dist;
253                             closest = e;
254                         }
255                     }
256 		        }
257 		        break;
258 		    } catch(ConcurrentModificationException cme) {}
259 		}
260 		return closest;
261     }
262 }