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.Dimension;
13  import java.awt.Paint;
14  import java.awt.Rectangle;
15  import java.awt.Shape;
16  import java.awt.geom.AffineTransform;
17  import java.awt.geom.GeneralPath;
18  import java.awt.geom.Point2D;
19  import java.awt.geom.Rectangle2D;
20  import java.awt.geom.RectangularShape;
21  
22  import javax.swing.JComponent;
23  
24  import edu.uci.ics.jung.algorithms.layout.Layout;
25  import edu.uci.ics.jung.graph.Graph;
26  import edu.uci.ics.jung.graph.util.Context;
27  import edu.uci.ics.jung.graph.util.EdgeType;
28  import edu.uci.ics.jung.graph.util.Pair;
29  import edu.uci.ics.jung.visualization.Layer;
30  import edu.uci.ics.jung.visualization.RenderContext;
31  import edu.uci.ics.jung.visualization.transform.LensTransformer;
32  import edu.uci.ics.jung.visualization.transform.MutableTransformer;
33  import edu.uci.ics.jung.visualization.transform.shape.TransformingGraphics;
34  
35  /**
36   * uses a flatness argument to break edges into
37   * smaller segments. This produces a more detailed
38   * transformation of the edge shape
39   * 
40   * @author Tom Nelson - tomnelson@dev.java.net
41   *
42   * @param <V>
43   * @param <E>
44   */
45  public class ReshapingEdgeRenderer<V,E> extends BasicEdgeRenderer<V,E>
46  	implements Renderer.Edge<V,E> {
47  
48      /**
49       * Draws the edge <code>e</code>, whose endpoints are at <code>(x1,y1)</code>
50       * and <code>(x2,y2)</code>, on the graphics context <code>g</code>.
51       * The <code>Shape</code> provided by the <code>EdgeShapeFunction</code> instance
52       * is scaled in the x-direction so that its width is equal to the distance between
53       * <code>(x1,y1)</code> and <code>(x2,y2)</code>.
54       */
55      protected void drawSimpleEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e) {
56          
57      	TransformingGraphics g = (TransformingGraphics)rc.getGraphicsContext();
58          Graph<V,E> graph = layout.getGraph();
59          Pair<V> endpoints = graph.getEndpoints(e);
60          V v1 = endpoints.getFirst();
61          V v2 = endpoints.getSecond();
62          Point2D p1 = layout.transform(v1);
63          Point2D p2 = layout.transform(v2);
64          p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
65          p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
66          float x1 = (float) p1.getX();
67          float y1 = (float) p1.getY();
68          float x2 = (float) p2.getX();
69          float y2 = (float) p2.getY();
70          
71          float flatness = 0;
72          MutableTransformer transformer = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
73          if(transformer instanceof LensTransformer) {
74              LensTransformer ht = (LensTransformer)transformer;
75              RectangularShape lensShape = ht.getLensShape();
76              if(lensShape.contains(x1,y1) || lensShape.contains(x2,y2)) {
77                  flatness = .05f;
78              }
79          }
80  
81          boolean isLoop = v1.equals(v2);
82          Shape s2 = rc.getVertexShapeTransformer().transform(v2);
83          Shape edgeShape = rc.getEdgeShapeTransformer().transform(Context.<Graph<V,E>,E>getInstance(graph, e));
84          
85          boolean edgeHit = true;
86          boolean arrowHit = true;
87          Rectangle deviceRectangle = null;
88          JComponent vv = rc.getScreenDevice();
89          if(vv != null) {
90              Dimension d = vv.getSize();
91              deviceRectangle = new Rectangle(0,0,d.width,d.height);
92          }
93  
94          AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
95          
96          if(isLoop) {
97              // this is a self-loop. scale it is larger than the vertex
98              // it decorates and translate it so that its nadir is
99              // at the center of the vertex.
100             Rectangle2D s2Bounds = s2.getBounds2D();
101             xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
102             xform.translate(0, -edgeShape.getBounds2D().getWidth()/2);
103         } else {
104             // this is a normal edge. Rotate it to the angle between
105             // vertex endpoints, then scale it to the distance between
106             // the vertices
107             float dx = x2-x1;
108             float dy = y2-y1;
109             float thetaRadians = (float) Math.atan2(dy, dx);
110             xform.rotate(thetaRadians);
111             float dist = (float) Math.sqrt(dx*dx + dy*dy);
112             xform.scale(dist, 1.0);
113         }
114         
115         edgeShape = xform.createTransformedShape(edgeShape);
116         
117         MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
118         if(vt instanceof LensTransformer) {
119         	vt = ((LensTransformer)vt).getDelegate();
120         }
121         edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);
122 
123         if(edgeHit == true) {
124             
125             Paint oldPaint = g.getPaint();
126             
127             // get Paints for filling and drawing
128             // (filling is done first so that drawing and label use same Paint)
129             Paint fill_paint = rc.getEdgeFillPaintTransformer().transform(e); 
130             if (fill_paint != null)
131             {
132                 g.setPaint(fill_paint);
133                 g.fill(edgeShape, flatness);
134             }
135             Paint draw_paint = rc.getEdgeDrawPaintTransformer().transform(e);
136             if (draw_paint != null)
137             {
138                 g.setPaint(draw_paint);
139                 g.draw(edgeShape, flatness);
140             }
141             
142             float scalex = (float)g.getTransform().getScaleX();
143             float scaley = (float)g.getTransform().getScaleY();
144             // see if arrows are too small to bother drawing
145             if(scalex < .3 || scaley < .3) return;
146             
147             if (rc.getEdgeArrowPredicate().evaluate(Context.<Graph<V,E>,E>getInstance(graph, e))) {
148                 
149                 Shape destVertexShape = 
150                     rc.getVertexShapeTransformer().transform(graph.getEndpoints(e).getSecond());
151 
152                 AffineTransform xf = AffineTransform.getTranslateInstance(x2, y2);
153                 destVertexShape = xf.createTransformedShape(destVertexShape);
154                 
155                 arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(destVertexShape).intersects(deviceRectangle);
156                 if(arrowHit) {
157                     
158                     AffineTransform at = 
159                         edgeArrowRenderingSupport.getArrowTransform(rc, new GeneralPath(edgeShape), destVertexShape);
160                     if(at == null) return;
161                     Shape arrow = rc.getEdgeArrowTransformer().transform(Context.<Graph<V,E>,E>getInstance(graph, e));
162                     arrow = at.createTransformedShape(arrow);
163                     g.setPaint(rc.getArrowFillPaintTransformer().transform(e));
164                     g.fill(arrow);
165                     g.setPaint(rc.getArrowDrawPaintTransformer().transform(e));
166                     g.draw(arrow);
167                 }
168                 if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) {
169                     Shape vertexShape = 
170                         rc.getVertexShapeTransformer().transform(graph.getEndpoints(e).getFirst());
171                     xf = AffineTransform.getTranslateInstance(x1, y1);
172                     vertexShape = xf.createTransformedShape(vertexShape);
173                     
174                     arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(vertexShape).intersects(deviceRectangle);
175                     
176                     if(arrowHit) {
177                         AffineTransform at = edgeArrowRenderingSupport.getReverseArrowTransform(rc, new GeneralPath(edgeShape), vertexShape, !isLoop);
178                         if(at == null) return;
179                         Shape arrow = rc.getEdgeArrowTransformer().transform(Context.<Graph<V,E>,E>getInstance(graph, e));
180                         arrow = at.createTransformedShape(arrow);
181                         g.setPaint(rc.getArrowFillPaintTransformer().transform(e));
182                         g.fill(arrow);
183                         g.setPaint(rc.getArrowDrawPaintTransformer().transform(e));
184                         g.draw(arrow);
185                     }
186                 }
187             }
188             // use existing paint for text if no draw paint specified
189             if (draw_paint == null)
190                 g.setPaint(oldPaint);
191 //            String label = edgeStringer.getLabel(e);
192 //            if (label != null) {
193 //                labelEdge(g, graph, e, label, x1, x2, y1, y2);
194 //            }
195             
196             
197             // restore old paint
198             g.setPaint(oldPaint);
199         }
200     }
201     
202     /**
203      * Returns a transform to position the arrowhead on this edge shape at the
204      * point where it intersects the passed vertex shape.
205      */
206 //    public AffineTransform getArrowTransform(RenderContext<V,E> rc, GeneralPath edgeShape, Shape vertexShape) {
207 //        float[] seg = new float[6];
208 //        Point2D p1=null;
209 //        Point2D p2=null;
210 //        AffineTransform at = new AffineTransform();
211 //        // when the PathIterator is done, switch to the line-subdivide
212 //        // method to get the arrowhead closer.
213 //        for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) {
214 //            int ret = i.currentSegment(seg);
215 //            if(ret == PathIterator.SEG_MOVETO) {
216 //                p2 = new Point2D.Float(seg[0],seg[1]);
217 //            } else if(ret == PathIterator.SEG_LINETO) {
218 //                p1 = p2;
219 //                p2 = new Point2D.Float(seg[0],seg[1]);
220 //                if(vertexShape.contains(p2)) {
221 //                    at = getArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
222 //                    break;
223 //                }
224 //            } 
225 //        }
226 //        return at;
227 //    }
228 
229     /**
230      * Returns a transform to position the arrowhead on this edge shape at the
231      * point where it intersects the passed vertex shape.
232      */
233 //    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, GeneralPath edgeShape, Shape vertexShape) {
234 //        return getReverseArrowTransform(rc, edgeShape, vertexShape, true);
235 //    }
236             
237     /**
238      * <p>Returns a transform to position the arrowhead on this edge shape at the
239      * point where it intersects the passed vertex shape.</p>
240      * 
241      * <p>The Loop edge is a special case because its staring point is not inside
242      * the vertex. The passedGo flag handles this case.</p>
243      * 
244      * @param edgeShape
245      * @param vertexShape
246      * @param passedGo - used only for Loop edges
247      */
248 //    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, GeneralPath edgeShape, Shape vertexShape,
249 //            boolean passedGo) {
250 //        float[] seg = new float[6];
251 //        Point2D p1=null;
252 //        Point2D p2=null;
253 //
254 //        AffineTransform at = new AffineTransform();
255 //        for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) {
256 //            int ret = i.currentSegment(seg);
257 //            if(ret == PathIterator.SEG_MOVETO) {
258 //                p2 = new Point2D.Float(seg[0],seg[1]);
259 //            } else if(ret == PathIterator.SEG_LINETO) {
260 //                p1 = p2;
261 //                p2 = new Point2D.Float(seg[0],seg[1]);
262 //                if(passedGo == false && vertexShape.contains(p2)) {
263 //                    passedGo = true;
264 //                 } else if(passedGo==true &&
265 //                        vertexShape.contains(p2)==false) {
266 //                     at = getReverseArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
267 //                    break;
268 //                }
269 //            } 
270 //        }
271 //        return at;
272 //    }
273 
274     /**
275      * This is used for the arrow of a directed and for one of the
276      * arrows for non-directed edges
277      * Get a transform to place the arrow shape on the passed edge at the
278      * point where it intersects the passed shape
279      * @param edgeShape
280      * @param vertexShape
281      * @return
282      */
283 //    public AffineTransform getArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
284 //        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
285 //        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
286 //        // iterate over the line until the edge shape will place the
287 //        // arrowhead closer than 'arrowGap' to the vertex shape boundary
288 //        while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
289 //            try {
290 //                edgeShape = getLastOutsideSegment(edgeShape, vertexShape);
291 //            } catch(IllegalArgumentException e) {
292 //                System.err.println(e.toString());
293 //                return null;
294 //            }
295 //            dx = (float) (edgeShape.getX1()-edgeShape.getX2());
296 //            dy = (float) (edgeShape.getY1()-edgeShape.getY2());
297 //        }
298 //        double atheta = Math.atan2(dx,dy)+Math.PI/2;
299 //        AffineTransform at = 
300 //            AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1());
301 //        at.rotate(-atheta);
302 //        return at;
303 //    }
304 
305     /**
306      * This is used for the reverse-arrow of a non-directed edge
307      * get a transform to place the arrow shape on the passed edge at the
308      * point where it intersects the passed shape
309      * @param edgeShape
310      * @param vertexShape
311      * @return
312      */
313 //    protected AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
314 //        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
315 //        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
316 //        // iterate over the line until the edge shape will place the
317 //        // arrowhead closer than 'arrowGap' to the vertex shape boundary
318 //        while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
319 //            try {
320 //                edgeShape = getFirstOutsideSegment(edgeShape, vertexShape);
321 //            } catch(IllegalArgumentException e) {
322 //                System.err.println(e.toString());
323 //                return null;
324 //            }
325 //            dx = (float) (edgeShape.getX1()-edgeShape.getX2());
326 //            dy = (float) (edgeShape.getY1()-edgeShape.getY2());
327 //        }
328 //        // calculate the angle for the arrowhead
329 //        double atheta = Math.atan2(dx,dy)-Math.PI/2;
330 //        AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1());
331 //        at.rotate(-atheta);
332 //        return at;
333 //    }
334     
335     /**
336      * Passed Line's point2 must be inside the passed shape or
337      * an IllegalArgumentException is thrown
338      * @param line line to subdivide
339      * @param shape shape to compare with line
340      * @return a line that intersects the shape boundary
341      * @throws IllegalArgumentException if the passed line's point1 is not inside the shape
342      */
343 //    protected Line2D getLastOutsideSegment(Line2D line, Shape shape) {
344 //        if(shape.contains(line.getP2())==false) {
345 //            String errorString =
346 //                "line end point: "+line.getP2()+" is not contained in shape: "+shape.getBounds2D();
347 //            throw new IllegalArgumentException(errorString);
348 //            //return null;
349 //        }
350 //        Line2D left = new Line2D.Double();
351 //        Line2D right = new Line2D.Double();
352 //        // subdivide the line until its left segment intersects
353 //        // the shape boundary
354 //        do {
355 //            subdivide(line, left, right);
356 //            line = right;
357 //        } while(shape.contains(line.getP1())==false);
358 //        // now that right is completely inside shape,
359 //        // return left, which must be partially outside
360 //        return left;
361 //    }
362    
363     /**
364      * Passed Line's point1 must be inside the passed shape or
365      * an IllegalArgumentException is thrown
366      * @param line line to subdivide
367      * @param shape shape to compare with line
368      * @return a line that intersects the shape boundary
369      * @throws IllegalArgumentException if the passed line's point1 is not inside the shape
370      */
371 //    protected Line2D getFirstOutsideSegment(Line2D line, Shape shape) {
372 //        
373 //        if(shape.contains(line.getP1())==false) {
374 //            String errorString = 
375 //                "line start point: "+line.getP1()+" is not contained in shape: "+shape.getBounds2D();
376 //            throw new IllegalArgumentException(errorString);
377 //        }
378 //        Line2D left = new Line2D.Float();
379 //        Line2D right = new Line2D.Float();
380 //        // subdivide the line until its right side intersects the
381 //        // shape boundary
382 //        do {
383 //            subdivide(line, left, right);
384 //            line = left;
385 //        } while(shape.contains(line.getP2())==false);
386 //        // now that left is completely inside shape,
387 //        // return right, which must be partially outside
388 //        return right;
389 //    }
390 
391     /**
392      * divide a Line2D into 2 new Line2Ds that are returned
393      * in the passed left and right instances, if non-null
394      * @param src the line to divide
395      * @param left the left side, or null
396      * @param right the right side, or null
397      */
398 //    protected void subdivide(Line2D src,
399 //            Line2D left,
400 //            Line2D right) {
401 //        double x1 = src.getX1();
402 //        double y1 = src.getY1();
403 //        double x2 = src.getX2();
404 //        double y2 = src.getY2();
405 //        
406 //        double mx = x1 + (x2-x1)/2.0;
407 //        double my = y1 + (y2-y1)/2.0;
408 //        if (left != null) {
409 //            left.setLine(x1, y1, mx, my);
410 //        }
411 //        if (right != null) {
412 //            right.setLine(mx, my, x2, y2);
413 //        }
414 //    }
415 
416 }