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 }