View Javadoc
1   /*
2    * Copyright (c) 2005, The JUNG Authors 
3    *
4    * All rights reserved.
5    *
6    * This software is open-source under the BSD license; see either
7    * "license.txt" or
8    * https://github.com/jrtom/jung/blob/master/LICENSE 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.util.Pair;
28  import edu.uci.ics.jung.visualization.Layer;
29  import edu.uci.ics.jung.visualization.VisualizationServer;
30  import edu.uci.ics.jung.visualization.transform.MutableTransformerDecorator;
31  
32  /**
33   * ShapePickSupport provides access to Vertices and EdgeType based on
34   * their actual shapes. 
35   * 
36   * @param <V> the vertex type
37   * @param <E> the edge type
38   * 
39   * @author Tom Nelson
40   */
41  public class ViewLensShapePickSupport<V, E> extends ShapePickSupport<V,E>
42  	implements GraphElementAccessor<V,E> {
43  
44      public ViewLensShapePickSupport(VisualizationServer<V,E> vv, float pickSize) {
45      	super(vv, pickSize);
46      }
47      
48      public ViewLensShapePickSupport(VisualizationServer<V,E> vv) {
49          this(vv, 2);
50      }
51  
52      public V getVertex(Layout<V, E> layout, double x, double y) {
53  
54          V closest = null;
55          double minDistance = Double.MAX_VALUE;
56          Point2D ip = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(new Point2D.Double(x,y));
57          x = ip.getX();
58          y = ip.getY();
59  
60          while(true) {
61              try {
62                  for(V v : getFilteredVertices(layout)) {
63                  	// get the shape
64                      Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v);
65                      // transform the vertex location to screen coords
66                      Point2D p = layout.apply(v);
67                      if(p == null) continue;
68                      AffineTransform xform = 
69                          AffineTransform.getTranslateInstance(p.getX(), p.getY());
70                      shape = xform.createTransformedShape(shape);
71                      
72                      // use the LAYOUT transform to move the shape center without
73                      // modifying the actual shape
74                      Point2D lp = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p);
75                      AffineTransform xlate = AffineTransform.getTranslateInstance(
76                      		lp.getX()-p.getX(),lp.getY()-p.getY());
77                      shape = xlate.createTransformedShape(shape);
78                      // now use the VIEW transform to modify the actual shape
79                      
80                      shape = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.VIEW, shape);
81                      	//vv.getRenderContext().getMultiLayerTransformer().transform(shape);
82                      
83                      // see if this vertex center is closest to the pick point
84                      // among any other containing vertices
85                      if(shape.contains(x, y)) {
86  
87                      	if(style == Style.LOWEST) {
88                      		// return the first match
89                      		return v;
90                      	} else if(style == Style.HIGHEST) {
91                      		// will return the last match
92                      		closest = v;
93                      	} else {
94                      		Rectangle2D bounds = shape.getBounds2D();
95                      		double dx = bounds.getCenterX() - x;
96                      		double dy = bounds.getCenterY() - y;
97                      		double dist = dx * dx + dy * dy;
98                      		if (dist < minDistance) {
99                      			minDistance = dist;
100                     			closest = v;
101                     		}
102                     	}
103                     }
104                 }
105                 break;
106             } catch(ConcurrentModificationException cme) {}
107         }
108         return closest;
109     }
110 
111     public Collection<V> getVertices(Layout<V, E> layout, Shape rectangle) {
112     	Set<V> pickedVertices = new HashSet<V>();
113     	
114 //    	 remove the view transform from the rectangle
115     	rectangle = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(rectangle);
116 
117         while(true) {
118             try {
119                 for(V v : getFilteredVertices(layout)) {
120                     Point2D p = layout.apply(v);
121                     if(p == null) continue;
122                    	// get the shape
123                     Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v);
124 
125                     AffineTransform xform = 
126                         AffineTransform.getTranslateInstance(p.getX(), p.getY());
127                     shape = xform.createTransformedShape(shape);
128                     
129                     shape = vv.getRenderContext().getMultiLayerTransformer().transform(shape);
130                     Rectangle2D bounds = shape.getBounds2D();
131                     p.setLocation(bounds.getCenterX(),bounds.getCenterY());
132 
133                     if(rectangle.contains(p)) {
134                     	pickedVertices.add(v);
135                     }
136                 }
137                 break;
138             } catch(ConcurrentModificationException cme) {}
139         }
140         return pickedVertices;
141     }
142     
143     public E getEdge(Layout<V, E> layout, double x, double y) {
144 
145         Point2D ip = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(new Point2D.Double(x,y));
146         x = ip.getX();
147         y = ip.getY();
148 
149         // as a Line has no area, we can't always use edgeshape.contains(point) so we
150         // make a small rectangular pickArea around the point and check if the
151         // edgeshape.intersects(pickArea)
152         Rectangle2D pickArea = 
153             new Rectangle2D.Float((float)x-pickSize/2,(float)y-pickSize/2,pickSize,pickSize);
154         E closest = null;
155         double minDistance = Double.MAX_VALUE;
156         while(true) {
157             try {
158                 for(E e : getFilteredEdges(layout)) {
159 
160                     Pair<V> pair = layout.getGraph().getEndpoints(e);
161                     V v1 = pair.getFirst();
162                     V v2 = pair.getSecond();
163                     boolean isLoop = v1.equals(v2);
164                     Point2D p1 = layout.apply(v1);
165                     	//vv.getRenderContext().getBasicTransformer().transform(layout.transform(v1));
166                     Point2D p2 = layout.apply(v2);
167                     	//vv.getRenderContext().getBasicTransformer().transform(layout.transform(v2));
168                     if(p1 == null || p2 == null) continue;
169                     float x1 = (float) p1.getX();
170                     float y1 = (float) p1.getY();
171                     float x2 = (float) p2.getX();
172                     float y2 = (float) p2.getY();
173 
174                     // translate the edge to the starting vertex
175                     AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
176 
177                     Shape edgeShape = vv.getRenderContext().getEdgeShapeTransformer().apply(e);
178                     if(isLoop) {
179                         // make the loops proportional to the size of the vertex
180                         Shape s2 = vv.getRenderContext().getVertexShapeTransformer().apply(v2);
181                         Rectangle2D s2Bounds = s2.getBounds2D();
182                         xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
183                         // move the loop so that the nadir is centered in the vertex
184                         xform.translate(0, -edgeShape.getBounds2D().getHeight()/2);
185                     } else {
186                         float dx = x2 - x1;
187                         float dy = y2 - y1;
188                         // rotate the edge to the angle between the vertices
189                         double theta = Math.atan2(dy,dx);
190                         xform.rotate(theta);
191                         // stretch the edge to span the distance between the vertices
192                         float dist = (float) Math.sqrt(dx*dx + dy*dy);
193                         xform.scale(dist, 1.0f);
194                     }
195 
196                     // transform the edge to its location and dimensions
197                     edgeShape = xform.createTransformedShape(edgeShape);
198                     
199                     edgeShape = vv.getRenderContext().getMultiLayerTransformer().transform(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 }