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 }