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.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> the vertex type
43   * @param <V> the edge type
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.apply(v1);
63          Point2D p2 = layout.apply(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().apply(v2);
83          Shape edgeShape = rc.getEdgeShapeTransformer().apply(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().apply(e); 
130             if (fill_paint != null)
131             {
132                 g.setPaint(fill_paint);
133                 g.fill(edgeShape, flatness);
134             }
135             Paint draw_paint = rc.getEdgeDrawPaintTransformer().apply(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().apply(Context.<Graph<V,E>,E>getInstance(graph, e))) {
148                 
149                 Shape destVertexShape = 
150                     rc.getVertexShapeTransformer().apply(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().apply(Context.<Graph<V,E>,E>getInstance(graph, e));
162                     arrow = at.createTransformedShape(arrow);
163                     g.setPaint(rc.getArrowFillPaintTransformer().apply(e));
164                     g.fill(arrow);
165                     g.setPaint(rc.getArrowDrawPaintTransformer().apply(e));
166                     g.draw(arrow);
167                 }
168                 if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) {
169                     Shape vertexShape = 
170                         rc.getVertexShapeTransformer().apply(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().apply(Context.<Graph<V,E>,E>getInstance(graph, e));
180                         arrow = at.createTransformedShape(arrow);
181                         g.setPaint(rc.getArrowFillPaintTransformer().apply(e));
182                         g.fill(arrow);
183                         g.setPaint(rc.getArrowDrawPaintTransformer().apply(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             
192             // restore old paint
193             g.setPaint(oldPaint);
194         }
195     }
196     
197     /**
198      * Returns a transform to position the arrowhead on this edge shape at the
199      * point where it intersects the passed vertex shape.
200      */
201 //    public AffineTransform getArrowTransform(RenderContext<V,E> rc, GeneralPath edgeShape, Shape vertexShape) {
202 //        float[] seg = new float[6];
203 //        Point2D p1=null;
204 //        Point2D p2=null;
205 //        AffineTransform at = new AffineTransform();
206 //        // when the PathIterator is done, switch to the line-subdivide
207 //        // method to get the arrowhead closer.
208 //        for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) {
209 //            int ret = i.currentSegment(seg);
210 //            if(ret == PathIterator.SEG_MOVETO) {
211 //                p2 = new Point2D.Float(seg[0],seg[1]);
212 //            } else if(ret == PathIterator.SEG_LINETO) {
213 //                p1 = p2;
214 //                p2 = new Point2D.Float(seg[0],seg[1]);
215 //                if(vertexShape.contains(p2)) {
216 //                    at = getArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
217 //                    break;
218 //                }
219 //            } 
220 //        }
221 //        return at;
222 //    }
223 
224     /**
225      * Returns a transform to position the arrowhead on this edge shape at the
226      * point where it intersects the passed vertex shape.
227      */
228 //    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, GeneralPath edgeShape, Shape vertexShape) {
229 //        return getReverseArrowTransform(rc, edgeShape, vertexShape, true);
230 //    }
231             
232     /**
233      * <p>Returns a transform to position the arrowhead on this edge shape at the
234      * point where it intersects the passed vertex shape.
235      * 
236      * <p>The Loop edge is a special case because its staring point is not inside
237      * the vertex. The passedGo flag handles this case.
238      * 
239      * @param edgeShape
240      * @param vertexShape
241      * @param passedGo - used only for Loop edges
242      */
243 //    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, GeneralPath edgeShape, Shape vertexShape,
244 //            boolean passedGo) {
245 //        float[] seg = new float[6];
246 //        Point2D p1=null;
247 //        Point2D p2=null;
248 //
249 //        AffineTransform at = new AffineTransform();
250 //        for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) {
251 //            int ret = i.currentSegment(seg);
252 //            if(ret == PathIterator.SEG_MOVETO) {
253 //                p2 = new Point2D.Float(seg[0],seg[1]);
254 //            } else if(ret == PathIterator.SEG_LINETO) {
255 //                p1 = p2;
256 //                p2 = new Point2D.Float(seg[0],seg[1]);
257 //                if(passedGo == false && vertexShape.contains(p2)) {
258 //                    passedGo = true;
259 //                 } else if(passedGo==true &&
260 //                        vertexShape.contains(p2)==false) {
261 //                     at = getReverseArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
262 //                    break;
263 //                }
264 //            } 
265 //        }
266 //        return at;
267 //    }
268 
269     /**
270      * This is used for the arrow of a directed and for one of the
271      * arrows for non-directed edges
272      * Get a transform to place the arrow shape on the passed edge at the
273      * point where it intersects the passed shape
274      * @param edgeShape
275      * @param vertexShape
276      * @return
277      */
278 //    public AffineTransform getArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
279 //        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
280 //        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
281 //        // iterate over the line until the edge shape will place the
282 //        // arrowhead closer than 'arrowGap' to the vertex shape boundary
283 //        while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
284 //            try {
285 //                edgeShape = getLastOutsideSegment(edgeShape, vertexShape);
286 //            } catch(IllegalArgumentException e) {
287 //                System.err.println(e.toString());
288 //                return null;
289 //            }
290 //            dx = (float) (edgeShape.getX1()-edgeShape.getX2());
291 //            dy = (float) (edgeShape.getY1()-edgeShape.getY2());
292 //        }
293 //        double atheta = Math.atan2(dx,dy)+Math.PI/2;
294 //        AffineTransform at = 
295 //            AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1());
296 //        at.rotate(-atheta);
297 //        return at;
298 //    }
299 
300     /**
301      * This is used for the reverse-arrow of a non-directed edge
302      * get a transform to place the arrow shape on the passed edge at the
303      * point where it intersects the passed shape
304      * @param edgeShape
305      * @param vertexShape
306      * @return
307      */
308 //    protected AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
309 //        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
310 //        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
311 //        // iterate over the line until the edge shape will place the
312 //        // arrowhead closer than 'arrowGap' to the vertex shape boundary
313 //        while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
314 //            try {
315 //                edgeShape = getFirstOutsideSegment(edgeShape, vertexShape);
316 //            } catch(IllegalArgumentException e) {
317 //                System.err.println(e.toString());
318 //                return null;
319 //            }
320 //            dx = (float) (edgeShape.getX1()-edgeShape.getX2());
321 //            dy = (float) (edgeShape.getY1()-edgeShape.getY2());
322 //        }
323 //        // calculate the angle for the arrowhead
324 //        double atheta = Math.atan2(dx,dy)-Math.PI/2;
325 //        AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1());
326 //        at.rotate(-atheta);
327 //        return at;
328 //    }
329     
330     /**
331      * Passed Line's point2 must be inside the passed shape or
332      * an IllegalArgumentException is thrown
333      * @param line line to subdivide
334      * @param shape shape to compare with line
335      * @return a line that intersects the shape boundary
336      * @throws IllegalArgumentException if the passed line's point1 is not inside the shape
337      */
338 //    protected Line2D getLastOutsideSegment(Line2D line, Shape shape) {
339 //        if(shape.contains(line.getP2())==false) {
340 //            String errorString =
341 //                "line end point: "+line.getP2()+" is not contained in shape: "+shape.getBounds2D();
342 //            throw new IllegalArgumentException(errorString);
343 //            //return null;
344 //        }
345 //        Line2D left = new Line2D.Double();
346 //        Line2D right = new Line2D.Double();
347 //        // subdivide the line until its left segment intersects
348 //        // the shape boundary
349 //        do {
350 //            subdivide(line, left, right);
351 //            line = right;
352 //        } while(shape.contains(line.getP1())==false);
353 //        // now that right is completely inside shape,
354 //        // return left, which must be partially outside
355 //        return left;
356 //    }
357    
358     /**
359      * Passed Line's point1 must be inside the passed shape or
360      * an IllegalArgumentException is thrown
361      * @param line line to subdivide
362      * @param shape shape to compare with line
363      * @return a line that intersects the shape boundary
364      * @throws IllegalArgumentException if the passed line's point1 is not inside the shape
365      */
366 //    protected Line2D getFirstOutsideSegment(Line2D line, Shape shape) {
367 //        
368 //        if(shape.contains(line.getP1())==false) {
369 //            String errorString = 
370 //                "line start point: "+line.getP1()+" is not contained in shape: "+shape.getBounds2D();
371 //            throw new IllegalArgumentException(errorString);
372 //        }
373 //        Line2D left = new Line2D.Float();
374 //        Line2D right = new Line2D.Float();
375 //        // subdivide the line until its right side intersects the
376 //        // shape boundary
377 //        do {
378 //            subdivide(line, left, right);
379 //            line = left;
380 //        } while(shape.contains(line.getP2())==false);
381 //        // now that left is completely inside shape,
382 //        // return right, which must be partially outside
383 //        return right;
384 //    }
385 
386     /**
387      * divide a Line2D into 2 new Line2Ds that are returned
388      * in the passed left and right instances, if non-null
389      * @param src the line to divide
390      * @param left the left side, or null
391      * @param right the right side, or null
392      */
393 //    protected void subdivide(Line2D src,
394 //            Line2D left,
395 //            Line2D right) {
396 //        double x1 = src.getX1();
397 //        double y1 = src.getY1();
398 //        double x2 = src.getX2();
399 //        double y2 = src.getY2();
400 //        
401 //        double mx = x1 + (x2-x1)/2.0;
402 //        double my = y1 + (y2-y1)/2.0;
403 //        if (left != null) {
404 //            left.setLine(x1, y1, mx, my);
405 //        }
406 //        if (right != null) {
407 //            right.setLine(mx, my, x2, y2);
408 //        }
409 //    }
410 
411 }