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 }