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