Line | Hits | Source |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2003, 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 | package edu.uci.ics.jung.visualization; | |
9 | ||
10 | import java.awt.BasicStroke; | |
11 | import java.awt.Color; | |
12 | import java.awt.Component; | |
13 | import java.awt.Dimension; | |
14 | import java.awt.Font; | |
15 | import java.awt.Graphics; | |
16 | import java.awt.Graphics2D; | |
17 | import java.awt.Paint; | |
18 | import java.awt.Rectangle; | |
19 | import java.awt.Shape; | |
20 | import java.awt.Stroke; | |
21 | import java.awt.geom.AffineTransform; | |
22 | import java.awt.geom.Ellipse2D; | |
23 | import java.awt.geom.GeneralPath; | |
24 | import java.awt.geom.Line2D; | |
25 | import java.awt.geom.PathIterator; | |
26 | import java.awt.geom.Point2D; | |
27 | import java.awt.geom.Rectangle2D; | |
28 | ||
29 | import javax.swing.CellRendererPane; | |
30 | import javax.swing.Icon; | |
31 | import javax.swing.JComponent; | |
32 | ||
33 | import org.apache.commons.collections.Predicate; | |
34 | import org.apache.commons.collections.functors.TruePredicate; | |
35 | ||
36 | import edu.uci.ics.jung.graph.ArchetypeEdge; | |
37 | import edu.uci.ics.jung.graph.ArchetypeVertex; | |
38 | import edu.uci.ics.jung.graph.Edge; | |
39 | import edu.uci.ics.jung.graph.Graph; | |
40 | import edu.uci.ics.jung.graph.UndirectedEdge; | |
41 | import edu.uci.ics.jung.graph.Vertex; | |
42 | import edu.uci.ics.jung.graph.decorators.ConstantDirectionalEdgeValue; | |
43 | import edu.uci.ics.jung.graph.decorators.ConstantEdgeFontFunction; | |
44 | import edu.uci.ics.jung.graph.decorators.ConstantEdgePaintFunction; | |
45 | import edu.uci.ics.jung.graph.decorators.ConstantEdgeStringer; | |
46 | import edu.uci.ics.jung.graph.decorators.ConstantEdgeStrokeFunction; | |
47 | import edu.uci.ics.jung.graph.decorators.ConstantVertexAspectRatioFunction; | |
48 | import edu.uci.ics.jung.graph.decorators.ConstantVertexFontFunction; | |
49 | import edu.uci.ics.jung.graph.decorators.ConstantVertexSizeFunction; | |
50 | import edu.uci.ics.jung.graph.decorators.ConstantVertexStringer; | |
51 | import edu.uci.ics.jung.graph.decorators.ConstantVertexStrokeFunction; | |
52 | import edu.uci.ics.jung.graph.decorators.DirectionalEdgeArrowFunction; | |
53 | import edu.uci.ics.jung.graph.decorators.EdgeArrowFunction; | |
54 | import edu.uci.ics.jung.graph.decorators.EdgeColorFunction; | |
55 | import edu.uci.ics.jung.graph.decorators.EdgeFontFunction; | |
56 | import edu.uci.ics.jung.graph.decorators.EdgePaintFunction; | |
57 | import edu.uci.ics.jung.graph.decorators.EdgeShape; | |
58 | import edu.uci.ics.jung.graph.decorators.EdgeShapeFunction; | |
59 | import edu.uci.ics.jung.graph.decorators.EdgeStringer; | |
60 | import edu.uci.ics.jung.graph.decorators.EdgeStrokeFunction; | |
61 | import edu.uci.ics.jung.graph.decorators.EllipseVertexShapeFunction; | |
62 | import edu.uci.ics.jung.graph.decorators.NumberEdgeValue; | |
63 | import edu.uci.ics.jung.graph.decorators.PickableVertexPaintFunction; | |
64 | import edu.uci.ics.jung.graph.decorators.VertexColorFunction; | |
65 | import edu.uci.ics.jung.graph.decorators.VertexFontFunction; | |
66 | import edu.uci.ics.jung.graph.decorators.VertexIconFunction; | |
67 | import edu.uci.ics.jung.graph.decorators.VertexPaintFunction; | |
68 | import edu.uci.ics.jung.graph.decorators.VertexShapeFunction; | |
69 | import edu.uci.ics.jung.graph.decorators.VertexStringer; | |
70 | import edu.uci.ics.jung.graph.decorators.VertexStrokeFunction; | |
71 | import edu.uci.ics.jung.graph.predicates.EdgePredicate; | |
72 | import edu.uci.ics.jung.graph.predicates.SelfLoopEdgePredicate; | |
73 | import edu.uci.ics.jung.utils.Pair; | |
74 | import edu.uci.ics.jung.utils.ParallelEdgeIndexFunction; | |
75 | import edu.uci.ics.jung.utils.ParallelEdgeIndexSingleton; | |
76 | import edu.uci.ics.jung.visualization.transform.MutableAffineTransformer; | |
77 | import edu.uci.ics.jung.visualization.transform.MutableTransformer; | |
78 | ||
79 | /** | |
80 | * <p>A renderer with all sorts of buttons to press and dials to turn. | |
81 | * Using the appropriate methods, the user can override the default | |
82 | * properties/behaviors for vertex paint, stroke, shape, label, label font, | |
83 | * and label centering; and for edge paint, stroke, label, arrows, label font, | |
84 | * label positioning, and drawing. | |
85 | * </p> | |
86 | * <p>Notes on these decorators: | |
87 | * <ul> | |
88 | * <li/>The decorators are all orthogonal; changing one does not change the behavior of any | |
89 | * other (unless your decorator implementations depend on one another). | |
90 | * <li/>The default properties apply to each vertices/edges, but the decorators allow these | |
91 | * properties to be specified individually for each vertex/edge. See the documentation for | |
92 | * each of these decorators for specific instructions on their use. | |
93 | * <li/>Implementations of these decorator interfaces are provided that allow the user | |
94 | * to specify a single (constant) property to apply to all vertices/edges. | |
95 | * <li/>There are additional interfaces and classes that allow the size and aspect ratio | |
96 | * of the vertex shape to be independently manipulated, and that provide factory methods | |
97 | * for generating various standard shapes; see <code>SettableVertexShapeFunction</code>, | |
98 | * <code>AbstractVertexShapeFunction</code>, <code>VertexShapeFactory</code>, and the | |
99 | * sample <code>samples.graph.PluggableRendererDemo</code>. | |
100 | * <li/>This class provides default <code>Stroke</code> implementations for dotted and | |
101 | * dashed lines: the <code>DOTTED</code> and <code>DASHED</code> static constants, | |
102 | * respectively. | |
103 | * <li/>The <code>EdgeArrowPredicate</code> specifies the edges for which arrows | |
104 | * should be drawn; the <code>EdgeArrowFunction</code> specifies the shapes of | |
105 | * the arrowheads for those edges that pass the <code>EdgeArrowPredicate</code>. | |
106 | * <li/>If the specified vertex inclusion <code>Predicate</code> indicates that | |
107 | * vertex <code>v</code> is not to be drawn, none of its incident edges will be drawn either. | |
108 | * </ul> | |
109 | * | |
110 | * <p>By default, self-loops are drawn as circles.</p> | |
111 | * | |
112 | * <p>By default, undirected edges are drawn as straight lines, directed edges are | |
113 | * drawn as bent lines, and parallel edges are drawn on top | |
114 | * of one another.</p> | |
115 | ||
116 | * <p>Arrowheads are drawn so that the point of the arrow is at the boundary of the | |
117 | * vertex shapes. In the current implementation, finding the place | |
118 | * where arrows are drawn is fairly slow; for large graphs, this should | |
119 | * be turned off.</p> | |
120 | * | |
121 | * <p>Setting a stroke width other than 1, or using transparency, | |
122 | * may slow down rendering of the visualization. | |
123 | * </p> | |
124 | * | |
125 | * @author Danyel Fisher | |
126 | * @author Joshua O'Madadhain | |
127 | * @author Tom Nelson | |
128 | */ | |
129 | public class PluggableRenderer extends AbstractRenderer implements PickedInfo, HasShapeFunctions | |
130 | { | |
131 | ||
132 | 0 | protected float arrow_placement_tolerance = 1; |
133 | 0 | protected final static float[] dotting = {1.0f, 3.0f}; |
134 | /** | |
135 | * A stroke for a dotted line: 1 pixel width, round caps, round joins, and an | |
136 | * array of {1.0f, 3.0f}. | |
137 | */ | |
138 | 0 | public final static Stroke DOTTED = new BasicStroke(1.0f, BasicStroke.CAP_ROUND, |
139 | BasicStroke.JOIN_ROUND, 1.0f, dotting, 0f); | |
140 | ||
141 | 0 | protected final static float[] dashing = {5.0f}; |
142 | /** | |
143 | * A stroke for a dashed line: 1 pixel width, square caps, beveled joins, and an | |
144 | * array of {5.0f}. | |
145 | */ | |
146 | 0 | public final static Stroke DASHED = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, |
147 | BasicStroke.JOIN_BEVEL, 1.0f, dashing, 0f); | |
148 | ||
149 | /** | |
150 | * Specifies the offset for the edge labels. | |
151 | */ | |
152 | public static final int LABEL_OFFSET = 10; | |
153 | ||
154 | /** | |
155 | * Specifies the maximum number of iterations to run the edge subdivision loop | |
156 | * in <code>getLastOutsideSegment</code>; this is done to fix the arrow | |
157 | * orientation problem noted in bug report #1450529. | |
158 | */ | |
159 | protected static final int MAX_ITERATIONS = 10; | |
160 | ||
161 | 0 | protected Predicate vertexIncludePredicate = TruePredicate.getInstance(); |
162 | 0 | protected VertexStrokeFunction vertexStrokeFunction = |
163 | new ConstantVertexStrokeFunction(1.0f); | |
164 | 0 | protected VertexShapeFunction vertexShapeFunction = |
165 | new EllipseVertexShapeFunction( | |
166 | new ConstantVertexSizeFunction(20), | |
167 | new ConstantVertexAspectRatioFunction(1.0f)); | |
168 | 0 | protected VertexStringer vertexStringer = |
169 | new ConstantVertexStringer(null); | |
170 | protected VertexIconFunction vertexIconFunction; | |
171 | 0 | protected VertexFontFunction vertexFontFunction = |
172 | new ConstantVertexFontFunction(new Font("Helvetica", Font.PLAIN, 12)); | |
173 | 0 | protected boolean centerVertexLabel = false; |
174 | ||
175 | 0 | protected VertexPaintFunction vertexPaintFunction = |
176 | new PickableVertexPaintFunction(this, Color.BLACK, Color.RED, Color.ORANGE); | |
177 | ||
178 | 0 | protected EdgeStringer edgeStringer = |
179 | new ConstantEdgeStringer(null); | |
180 | 0 | protected EdgeStrokeFunction edgeStrokeFunction = |
181 | new ConstantEdgeStrokeFunction(1.0f); | |
182 | 0 | protected EdgeArrowFunction edgeArrowFunction = |
183 | new DirectionalEdgeArrowFunction(10, 8, 4); | |
184 | 0 | protected Predicate edgeArrowPredicate = Graph.DIRECTED_EDGE; |
185 | 0 | protected Predicate edgeIncludePredicate = TruePredicate.getInstance(); |
186 | 0 | protected EdgeFontFunction edgeFontFunction = |
187 | new ConstantEdgeFontFunction(new Font("Helvetica", Font.PLAIN, 12)); | |
188 | 0 | protected NumberEdgeValue edgeLabelClosenessFunction = |
189 | new ConstantDirectionalEdgeValue(0.5, 0.65); | |
190 | 0 | protected EdgeShapeFunction edgeShapeFunction = |
191 | new EdgeShape.QuadCurve(); | |
192 | 0 | protected EdgePaintFunction edgePaintFunction = |
193 | new ConstantEdgePaintFunction(Color.black, null); | |
194 | 0 | protected ParallelEdgeIndexFunction parallelEdgeIndexFunction = |
195 | ParallelEdgeIndexSingleton.getInstance(); | |
196 | 0 | protected MutableTransformer viewTransformer = new MutableAffineTransformer(); |
197 | ||
198 | /** | |
199 | * the JComponent that this Renderer will display the graph on | |
200 | */ | |
201 | protected JComponent screenDevice; | |
202 | ||
203 | /** | |
204 | * The CellRendererPane is used here just as it is in JTree | |
205 | * and JTable, to allow a pluggable JLabel-based renderer for | |
206 | * Vertex and Edge label strings and icons. | |
207 | */ | |
208 | 0 | protected CellRendererPane rendererPane = new CellRendererPane(); |
209 | ||
210 | /** | |
211 | * A default GraphLabelRenderer - picked Vertex labels are | |
212 | * blue, picked edge labels are cyan | |
213 | */ | |
214 | 0 | protected GraphLabelRenderer graphLabelRenderer = |
215 | new DefaultGraphLabelRenderer(Color.blue, Color.cyan); | |
216 | ||
217 | 0 | protected final static EdgePredicate self_loop = SelfLoopEdgePredicate.getInstance(); |
218 | ||
219 | public PluggableRenderer() | |
220 | 0 | { |
221 | 0 | this.setEdgeShapeFunction(new EdgeShape.QuadCurve()); |
222 | 0 | } |
223 | ||
224 | /** | |
225 | * @return Returns the edgeArrowFunction. | |
226 | */ | |
227 | public EdgeArrowFunction getEdgeArrowFunction() { | |
228 | 0 | return edgeArrowFunction; |
229 | } | |
230 | ||
231 | /** | |
232 | * @return Returns the edgeArrowPredicate. | |
233 | */ | |
234 | public Predicate getEdgeArrowPredicate() { | |
235 | 0 | return edgeArrowPredicate; |
236 | } | |
237 | ||
238 | /** | |
239 | * @return Returns the edgeFontFunction. | |
240 | */ | |
241 | public EdgeFontFunction getEdgeFontFunction() { | |
242 | 0 | return edgeFontFunction; |
243 | } | |
244 | ||
245 | /** | |
246 | * @return Returns the edgeIncludePredicate. | |
247 | */ | |
248 | public Predicate getEdgeIncludePredicate() { | |
249 | 0 | return edgeIncludePredicate; |
250 | } | |
251 | ||
252 | /** | |
253 | * @return Returns the edgeLabelClosenessFunction. | |
254 | */ | |
255 | public NumberEdgeValue getEdgeLabelClosenessFunction() { | |
256 | 0 | return edgeLabelClosenessFunction; |
257 | } | |
258 | ||
259 | /** | |
260 | * @return Returns the edgePaintFunction. | |
261 | */ | |
262 | public EdgePaintFunction getEdgePaintFunction() { | |
263 | 0 | return edgePaintFunction; |
264 | } | |
265 | ||
266 | /** | |
267 | * @return Returns the edgeStringer. | |
268 | */ | |
269 | public EdgeStringer getEdgeStringer() { | |
270 | 0 | return edgeStringer; |
271 | } | |
272 | ||
273 | /** | |
274 | * @return Returns the edgeStrokeFunction. | |
275 | */ | |
276 | public EdgeStrokeFunction getEdgeStrokeFunction() { | |
277 | 0 | return edgeStrokeFunction; |
278 | } | |
279 | ||
280 | /** | |
281 | * @return Returns the screenDevice. | |
282 | */ | |
283 | public JComponent getScreenDevice() { | |
284 | 0 | return screenDevice; |
285 | } | |
286 | ||
287 | /** | |
288 | * @return Returns the vertexFontFunction. | |
289 | */ | |
290 | public VertexFontFunction getVertexFontFunction() { | |
291 | 0 | return vertexFontFunction; |
292 | } | |
293 | ||
294 | /** | |
295 | * @return Returns the vertexIncludePredicate. | |
296 | */ | |
297 | public Predicate getVertexIncludePredicate() { | |
298 | 0 | return vertexIncludePredicate; |
299 | } | |
300 | ||
301 | /** | |
302 | * @return Returns the vertexPaintFunction. | |
303 | */ | |
304 | public VertexPaintFunction getVertexPaintFunction() { | |
305 | 0 | return vertexPaintFunction; |
306 | } | |
307 | ||
308 | /** | |
309 | * @return Returns the vertexStringer. | |
310 | */ | |
311 | public VertexStringer getVertexStringer() { | |
312 | 0 | return vertexStringer; |
313 | } | |
314 | ||
315 | /** | |
316 | * @return Returns the vertexIconFunction | |
317 | */ | |
318 | public VertexIconFunction getVertexIconFunction() { | |
319 | 0 | return vertexIconFunction; |
320 | } | |
321 | ||
322 | /** | |
323 | * @param vertexIconFunction The VertexIconFunction to set. | |
324 | */ | |
325 | public void setVertexIconFunction(VertexIconFunction vertexIconFunction) { | |
326 | 0 | this.vertexIconFunction = vertexIconFunction; |
327 | 0 | } |
328 | ||
329 | /** | |
330 | * @return Returns the vertexStrokeFunction. | |
331 | */ | |
332 | public VertexStrokeFunction getVertexStrokeFunction() { | |
333 | 0 | return vertexStrokeFunction; |
334 | } | |
335 | ||
336 | /** | |
337 | * The screen device is the JComponent on which the renderer | |
338 | * will display the graph. It is used here to assist with removing | |
339 | * unnecessary calls to position and draw graph elements that will | |
340 | * not be visible in the display. It is also used as the container | |
341 | * for the CellRendererPane. | |
342 | * @param screenDevice | |
343 | */ | |
344 | public void setScreenDevice(JComponent screenDevice) { | |
345 | 0 | this.screenDevice = screenDevice; |
346 | 0 | this.screenDevice.add(rendererPane); |
347 | 0 | } |
348 | ||
349 | /** | |
350 | * Specifies the smallest (squared) distance that an arrowhead | |
351 | * must be moved in order for the placement code to decide that | |
352 | * it's "close enough". Default value is 1. | |
353 | */ | |
354 | public void setArrowPlacementTolerance(float tolerance) { | |
355 | 0 | this.arrow_placement_tolerance = tolerance; |
356 | 0 | } |
357 | ||
358 | /** | |
359 | * Sets the <code>EdgeArrowFunction</code> that specifies the | |
360 | * <code>Shape</code> of the arrowheads for each edge. | |
361 | * The same shape will be used for both ends of an undirected | |
362 | * edge. The default arrow-drawing implementations assume that arrows | |
363 | * are drawn with their base on the y-axis, pointed left (in the negative | |
364 | * x-direction), centered on the x-axis. | |
365 | * Note that the <code>EdgeArrowFunction</code> must return a valid shape | |
366 | * for any edge for which the edge arrow <code>Predicate</code> | |
367 | * returns <code>true</code>. | |
368 | * <br>Default: wedge arrows for undirected edges, notched arrows for directed edges | |
369 | * (<code>DirectionalEdgeArrowFunction</code>) | |
370 | * @see edu.uci.ics.jung.decorators.EdgeArrowFunction | |
371 | * @see ArrowFactory | |
372 | */ | |
373 | public void setEdgeArrowFunction(EdgeArrowFunction eaf) | |
374 | { | |
375 | 0 | this.edgeArrowFunction = eaf; |
376 | 0 | } |
377 | ||
378 | /** | |
379 | * @return Returns the graphLabelRenderer. | |
380 | */ | |
381 | public GraphLabelRenderer getGraphLabelRenderer() { | |
382 | 0 | return graphLabelRenderer; |
383 | } | |
384 | /** | |
385 | * @param graphLabelRenderer The graphLabelRenderer to set. | |
386 | */ | |
387 | public void setGraphLabelRenderer(GraphLabelRenderer graphLabelRenderer) { | |
388 | 0 | this.graphLabelRenderer = graphLabelRenderer; |
389 | 0 | } |
390 | /** | |
391 | * Sets the <code>EdgeArrowPredicate</code> that specifies whether | |
392 | * arrowheads should be drawn for each edge. If the predicate evaluates | |
393 | * to <code>true</code> for a specified edge, arrows should be drawn | |
394 | * for that edge. | |
395 | * <br>Default: only directed edges have arrows (<code>Graph.DIRECTED_EDGE</code> instance) | |
396 | * @see EdgeArrowFunction | |
397 | */ | |
398 | public void setEdgeArrowPredicate(Predicate p) | |
399 | { | |
400 | 0 | this.edgeArrowPredicate = p; |
401 | 0 | } |
402 | ||
403 | /** | |
404 | * Sets the <code>EdgeColorFunction</code> that specifies the paint to | |
405 | * draw each edge. | |
406 | * <br>Default: Color.BLACK | |
407 | * @see java.awt.Color | |
408 | * @deprecated Use setEdgePaintFunction instead | |
409 | */ | |
410 | public void setEdgeColorFunction(EdgeColorFunction ecf) | |
411 | { | |
412 | 0 | this.edgePaintFunction = new EdgeColorToEdgePaintFunctionConverter( ecf ); |
413 | 0 | } |
414 | ||
415 | ||
416 | /** | |
417 | * Sets the <code>EdgeFontFunction</code> that specifies the font | |
418 | * to use for drawing each edge label. This can be used (for example) to | |
419 | * emphasize (or to de-emphasize) edges that have a specific property. | |
420 | * <br>Default: 12-point Helvetica | |
421 | * @see EdgeFontFunction | |
422 | */ | |
423 | public void setEdgeFontFunction(EdgeFontFunction eff) | |
424 | { | |
425 | 0 | this.edgeFontFunction = eff; |
426 | 0 | } |
427 | ||
428 | /** | |
429 | * Sets the <code>Predicate</code> that specifies whether each | |
430 | * edge should be drawn; only those edges for which this | |
431 | * predicate returns <code>true</code> will be drawn. This can be | |
432 | * used to selectively display only those edges that have a | |
433 | * specific property, such as a particular decoration or value, or | |
434 | * only those edges of a specific type (such as directed edges, | |
435 | * or everything except self-loops). | |
436 | * <br>Default: all edges drawn (<code>TruePredicate</code> instance) | |
437 | * @see org.apache.commons.collections.Predicate | |
438 | */ | |
439 | public void setEdgeIncludePredicate(Predicate p) | |
440 | { | |
441 | 0 | this.edgeIncludePredicate = p; |
442 | 0 | } |
443 | ||
444 | /** | |
445 | * Sets the <code>NumberEdgeValue</code> that specifies where to draw | |
446 | * the label for each edge. A value of 0 draws the label on top of | |
447 | * the edge's first vertex; a value of 1.0 draws the label on top | |
448 | * of the edge's second vertex; values between 0 and 1 split the | |
449 | * difference (i.e., a value of 0.5 draws the label halfway in between | |
450 | * the two vertices). The effect of values outside the range [0,1] | |
451 | * is undefined. This function is not used for self-loops. | |
452 | * <br>Default: 0.5 for undirected edges, 0.75 for directed edges | |
453 | * (<code>ConstantDirectionalEdgeValue</code>) | |
454 | * @see edu.uci.ics.jung.graph.decorators.NumberEdgeValue | |
455 | */ | |
456 | public void setEdgeLabelClosenessFunction(NumberEdgeValue nev) | |
457 | { | |
458 | 0 | this.edgeLabelClosenessFunction = nev; |
459 | 0 | } |
460 | ||
461 | /** | |
462 | * @param impl | |
463 | */ | |
464 | public void setEdgePaintFunction(EdgePaintFunction impl) { | |
465 | 0 | edgePaintFunction = impl; |
466 | ||
467 | 0 | } |
468 | ||
469 | /** | |
470 | * setter for the EdgeShapeFunction | |
471 | * @param impl | |
472 | */ | |
473 | public void setEdgeShapeFunction(EdgeShapeFunction impl) { | |
474 | 0 | edgeShapeFunction = impl; |
475 | 0 | if(edgeShapeFunction instanceof EdgeShape.ParallelRendering) { |
476 | 0 | ((EdgeShape.ParallelRendering)edgeShapeFunction).setParallelEdgeIndexFunction(this.parallelEdgeIndexFunction); |
477 | } | |
478 | 0 | } |
479 | ||
480 | /** | |
481 | * @return Returns the EdgeShapeFunction that . | |
482 | */ | |
483 | public EdgeShapeFunction getEdgeShapeFunction() { | |
484 | 0 | return edgeShapeFunction; |
485 | } | |
486 | /** | |
487 | * Sets the <code>EdgeStringer</code> that specifies the label to | |
488 | * draw for each edge. | |
489 | * <br>Default: no labels | |
490 | * (<code>ConstantEdgeStringer</code>) | |
491 | * @see edu.uci.ics.jung.graph.decorators.EdgeStringer | |
492 | */ | |
493 | public void setEdgeStringer(EdgeStringer es) | |
494 | { | |
495 | 0 | this.edgeStringer = es; |
496 | 0 | } |
497 | ||
498 | /** | |
499 | * Sets the <code>EdgeStrokeFunction</code> that specifies the | |
500 | * <code>Stroke</code> to use when drawing each edge. | |
501 | * <br>Default: 1-pixel-width basic stroke | |
502 | * (<code>ConstantEdgeStrokeFunction</code>) | |
503 | * @see java.awt.Stroke | |
504 | * @see EdgeStrokeFunction | |
505 | */ | |
506 | public void setEdgeStrokeFunction(EdgeStrokeFunction esf) | |
507 | { | |
508 | 0 | this.edgeStrokeFunction = esf; |
509 | 0 | } |
510 | ||
511 | /** | |
512 | * <p>Sets the <code>VertexPaintFunction</code> to the parameter | |
513 | * @see #setVertexPaintFunction(VertexPaintFunction) | |
514 | * @see VertexColorFunction | |
515 | * @see VertexColorToVertexPaintConverter | |
516 | * @deprecated Use setVertexPaintFunction with a VertexPaintFunction if you can | |
517 | */ | |
518 | public void setVertexColorFunction(VertexColorFunction vcf) | |
519 | { | |
520 | 0 | this.vertexPaintFunction = new VertexColorToVertexPaintConverter( vcf ); |
521 | 0 | } |
522 | ||
523 | /** | |
524 | * <p>Sets the <code>VertexPaintFunction</code> which specifies the | |
525 | * draw (border and text) and fill (interior) Paint for each vertex.</p> | |
526 | * <p>If users want the <code>VertexPaintFunction</code> implementation | |
527 | * to highlight selected vertices, they should take this | |
528 | * PluggableRenderer instance as a constructor parameter, and call | |
529 | * the <code>isPicked</code> method on it to identify selected vertices.</p> | |
530 | * <p>Default: black borders, red foreground (selected vertex is orange).</p> | |
531 | */ | |
532 | public void setVertexPaintFunction( VertexPaintFunction vpf ) | |
533 | { | |
534 | 0 | this.vertexPaintFunction = vpf; |
535 | 0 | } |
536 | ||
537 | /** | |
538 | * Returns the <code>VertexShapeFunction</code> currently being | |
539 | * used by this instance. | |
540 | */ | |
541 | public VertexShapeFunction getVertexShapeFunction() | |
542 | { | |
543 | 0 | return vertexShapeFunction; |
544 | } | |
545 | ||
546 | /** | |
547 | * Sets the <code>VertexFontFunction</code> that specifies the font | |
548 | * to use for drawing each vertex label. This can be used (for example) to | |
549 | * emphasize (or to de-emphasize) vertices that have a specific property. | |
550 | * <br>Default: 12-point Helvetica | |
551 | * @see VertexFontFunction | |
552 | */ | |
553 | public void setVertexFontFunction(VertexFontFunction vff) | |
554 | { | |
555 | 0 | this.vertexFontFunction = vff; |
556 | 0 | } |
557 | ||
558 | /** | |
559 | * Sets the <code>Predicate</code> that specifies whether each | |
560 | * vertex should be drawn; only those vertices for which this | |
561 | * predicate returns <code>true</code> will be drawn. This can be | |
562 | * used to selectively display only those vertices that have a | |
563 | * specific property, such as a particular decoration or value. | |
564 | * <br>Default: all vertices drawn (<code>TruePredicate</code> instance) | |
565 | * @see org.apache.commons.collections.Predicate | |
566 | */ | |
567 | public void setVertexIncludePredicate(Predicate p) | |
568 | { | |
569 | 0 | this.vertexIncludePredicate = p; |
570 | 0 | } |
571 | ||
572 | /** | |
573 | * Specifies whether vertex labels are drawn centered on the vertex | |
574 | * position (<code>true</code>) or offset to one side (<code>false</code>). | |
575 | * <br>Default: offset | |
576 | */ | |
577 | public void setVertexLabelCentering(boolean b) | |
578 | { | |
579 | 0 | centerVertexLabel = b; |
580 | 0 | } |
581 | ||
582 | /** | |
583 | * | |
584 | * @return whether the vertex labels should be centered in the vertex | |
585 | */ | |
586 | public boolean getVertexLabelCentering() | |
587 | { | |
588 | 0 | return centerVertexLabel; |
589 | } | |
590 | ||
591 | /** | |
592 | * Sets the <code>VertexShapeFunction</code>, | |
593 | * which specifies the <code>Shape</code> for each vertex. | |
594 | * Users that wish to independently change the size and | |
595 | * aspect ratio of a vertex's shape should take a look | |
596 | * at the <code>SettableVertexShapeFunction</code> | |
597 | * interface and the <code>AbstractVertexShapeFunction</code> | |
598 | * abstract class. | |
599 | * <br>Default: 8-pixel-diameter circle | |
600 | * (<code>EllipseVertexShapeFunction</code>) | |
601 | * @see java.awt.Shape | |
602 | * @see VertexShapeFunction | |
603 | */ | |
604 | public void setVertexShapeFunction(VertexShapeFunction vsf) | |
605 | { | |
606 | 0 | this.vertexShapeFunction = vsf; |
607 | 0 | } |
608 | ||
609 | /** | |
610 | * Sets the <code>VertexStringer</code> that specifies the label to | |
611 | * draw for each vertex. | |
612 | * <br>Default: no labels | |
613 | * (<code>ConstantVertexStringer</code>) | |
614 | * @see edu.uci.ics.jung.graph.decorators.VertexStringer | |
615 | */ | |
616 | public void setVertexStringer(VertexStringer vs) | |
617 | { | |
618 | 0 | this.vertexStringer = vs; |
619 | 0 | } |
620 | ||
621 | /** | |
622 | * Sets the <code>VertexStrokeFunction</code> which | |
623 | * specifies the <code>Stroke</code> to use when drawing | |
624 | * each vertex border. | |
625 | * <br>Default: 1-pixel-width basic stroke. | |
626 | * @see java.awt.Stroke | |
627 | * @see VertexStrokeFunction | |
628 | */ | |
629 | public void setVertexStrokeFunction(VertexStrokeFunction vsf) | |
630 | { | |
631 | 0 | this.vertexStrokeFunction = vsf; |
632 | 0 | } |
633 | ||
634 | ||
635 | /** | |
636 | * Paints <code>e</code>, whose endpoints are at <code>(x1,y1)</code> | |
637 | * and <code>(x2,y2)</code>, on the graphics context <code>g</code>. | |
638 | * Uses the paint and stroke specified by this instance's | |
639 | * <code>EdgeColorFunction</code> and <code>EdgeStrokeFunction</code>, | |
640 | * respectively. (If the paint is unspecified, the existing | |
641 | * paint for the graphics context is used; the same applies to stroke.) | |
642 | * The details of the actual rendering are delegated to | |
643 | * <code>drawSelfLoop</code> or <code>drawSimpleEdge</code>, | |
644 | * depending on the type of the edge. | |
645 | * Note that <code>(x1, y1)</code> is the location of | |
646 | * e.getEndpoints.getFirst() and <code>(x2, y2)</code> is the location of | |
647 | * e.getEndpoints.getSecond(). | |
648 | * | |
649 | */ | |
650 | public void paintEdge(Graphics g, Edge e, int x1, int y1, int x2, int y2) | |
651 | { | |
652 | 0 | if (!edgeIncludePredicate.evaluate(e)) |
653 | 0 | return; |
654 | ||
655 | // don't draw edge if either incident vertex is not drawn | |
656 | 0 | Pair endpoints = e.getEndpoints(); |
657 | 0 | Vertex v1 = (Vertex)endpoints.getFirst(); |
658 | 0 | Vertex v2 = (Vertex)endpoints.getSecond(); |
659 | 0 | if (!vertexIncludePredicate.evaluate(v1) || |
660 | !vertexIncludePredicate.evaluate(v2)) | |
661 | 0 | return; |
662 | ||
663 | 0 | Graphics2D g2d = (Graphics2D) g; |
664 | ||
665 | 0 | Stroke new_stroke = edgeStrokeFunction.getStroke(e); |
666 | 0 | Stroke old_stroke = g2d.getStroke(); |
667 | 0 | if (new_stroke != null) |
668 | 0 | g2d.setStroke(new_stroke); |
669 | ||
670 | 0 | drawSimpleEdge(g2d, e, x1, y1, x2, y2); |
671 | ||
672 | // restore paint and stroke | |
673 | 0 | if (new_stroke != null) |
674 | 0 | g2d.setStroke(old_stroke); |
675 | ||
676 | 0 | } |
677 | ||
678 | /** | |
679 | * Draws the edge <code>e</code>, whose endpoints are at <code>(x1,y1)</code> | |
680 | * and <code>(x2,y2)</code>, on the graphics context <code>g</code>. | |
681 | * The <code>Shape</code> provided by the <code>EdgeShapeFunction</code> instance | |
682 | * is scaled in the x-direction so that its width is equal to the distance between | |
683 | * <code>(x1,y1)</code> and <code>(x2,y2)</code>. | |
684 | */ | |
685 | protected void drawSimpleEdge(Graphics2D g, Edge e, int x1, int y1, int x2, int y2) | |
686 | { | |
687 | 0 | Pair endpoints = e.getEndpoints(); |
688 | 0 | Vertex v1 = (Vertex)endpoints.getFirst(); |
689 | 0 | Vertex v2 = (Vertex)endpoints.getSecond(); |
690 | 0 | boolean isLoop = v1.equals(v2); |
691 | 0 | Shape s2 = vertexShapeFunction.getShape(v2); |
692 | 0 | Shape edgeShape = edgeShapeFunction.getShape(e); |
693 | ||
694 | 0 | boolean edgeHit = true; |
695 | 0 | boolean arrowHit = true; |
696 | 0 | Rectangle deviceRectangle = null; |
697 | 0 | if(screenDevice != null) { |
698 | 0 | Dimension d = screenDevice.getSize(); |
699 | 0 | if(d.width <= 0 || d.height <= 0) { |
700 | 0 | d = screenDevice.getPreferredSize(); |
701 | } | |
702 | 0 | deviceRectangle = new Rectangle(0,0,d.width,d.height); |
703 | } | |
704 | ||
705 | 0 | AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1); |
706 | ||
707 | 0 | if(isLoop) { |
708 | // this is a self-loop. scale it is larger than the vertex | |
709 | // it decorates and translate it so that its nadir is | |
710 | // at the center of the vertex. | |
711 | 0 | Rectangle2D s2Bounds = s2.getBounds2D(); |
712 | 0 | xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight()); |
713 | 0 | xform.translate(0, -edgeShape.getBounds2D().getWidth()/2); |
714 | } else { | |
715 | // this is a normal edge. Rotate it to the angle between | |
716 | // vertex endpoints, then scale it to the distance between | |
717 | // the vertices | |
718 | 0 | float dx = x2-x1; |
719 | 0 | float dy = y2-y1; |
720 | 0 | float thetaRadians = (float) Math.atan2(dy, dx); |
721 | 0 | xform.rotate(thetaRadians); |
722 | 0 | float dist = (float) Math.sqrt(dx*dx + dy*dy); |
723 | 0 | xform.scale(dist, 1.0); |
724 | } | |
725 | ||
726 | 0 | edgeShape = xform.createTransformedShape(edgeShape); |
727 | ||
728 | 0 | edgeHit = viewTransformer.transform(edgeShape).intersects(deviceRectangle); |
729 | ||
730 | 0 | if(edgeHit == true) { |
731 | ||
732 | 0 | Paint oldPaint = g.getPaint(); |
733 | ||
734 | // get Paints for filling and drawing | |
735 | // (filling is done first so that drawing and label use same Paint) | |
736 | 0 | Paint fill_paint = edgePaintFunction.getFillPaint(e); |
737 | 0 | if (fill_paint != null) |
738 | { | |
739 | 0 | g.setPaint(fill_paint); |
740 | 0 | g.fill(edgeShape); |
741 | } | |
742 | 0 | Paint draw_paint = edgePaintFunction.getDrawPaint(e); |
743 | 0 | if (draw_paint != null) |
744 | { | |
745 | 0 | g.setPaint(draw_paint); |
746 | 0 | g.draw(edgeShape); |
747 | } | |
748 | ||
749 | 0 | float scalex = (float)g.getTransform().getScaleX(); |
750 | 0 | float scaley = (float)g.getTransform().getScaleY(); |
751 | // see if arrows are too small to bother drawing | |
752 | 0 | if(scalex < .3 || scaley < .3) return; |
753 | ||
754 | 0 | if (edgeArrowPredicate.evaluate(e)) { |
755 | ||
756 | 0 | Shape destVertexShape = |
757 | vertexShapeFunction.getShape((Vertex)e.getEndpoints().getSecond()); | |
758 | 0 | AffineTransform xf = AffineTransform.getTranslateInstance(x2, y2); |
759 | 0 | destVertexShape = xf.createTransformedShape(destVertexShape); |
760 | ||
761 | 0 | arrowHit = viewTransformer.transform(destVertexShape).intersects(deviceRectangle); |
762 | 0 | if(arrowHit) { |
763 | ||
764 | AffineTransform at; | |
765 | 0 | if (edgeShape instanceof GeneralPath) |
766 | 0 | at = getArrowTransform((GeneralPath)edgeShape, destVertexShape); |
767 | else | |
768 | 0 | at = getArrowTransform(new GeneralPath(edgeShape), destVertexShape); |
769 | 0 | if(at == null) return; |
770 | 0 | Shape arrow = edgeArrowFunction.getArrow(e); |
771 | 0 | arrow = at.createTransformedShape(arrow); |
772 | // note that arrows implicitly use the edge's draw paint | |
773 | 0 | g.fill(arrow); |
774 | } | |
775 | 0 | if (e instanceof UndirectedEdge) { |
776 | 0 | Shape vertexShape = |
777 | vertexShapeFunction.getShape((Vertex)e.getEndpoints().getFirst()); | |
778 | 0 | xf = AffineTransform.getTranslateInstance(x1, y1); |
779 | 0 | vertexShape = xf.createTransformedShape(vertexShape); |
780 | ||
781 | 0 | arrowHit = viewTransformer.transform(vertexShape).intersects(deviceRectangle); |
782 | ||
783 | 0 | if(arrowHit) { |
784 | AffineTransform at; | |
785 | 0 | if (edgeShape instanceof GeneralPath) |
786 | 0 | at = getReverseArrowTransform((GeneralPath)edgeShape, vertexShape, !isLoop); |
787 | else | |
788 | 0 | at = getReverseArrowTransform(new GeneralPath(edgeShape), vertexShape, !isLoop); |
789 | 0 | if(at == null) return; |
790 | 0 | Shape arrow = edgeArrowFunction.getArrow(e); |
791 | 0 | arrow = at.createTransformedShape(arrow); |
792 | 0 | g.fill(arrow); |
793 | } | |
794 | } | |
795 | } | |
796 | // use existing paint for text if no draw paint specified | |
797 | 0 | if (draw_paint == null) |
798 | 0 | g.setPaint(oldPaint); |
799 | 0 | String label = edgeStringer.getLabel(e); |
800 | 0 | if (label != null) { |
801 | 0 | labelEdge(g, e, label, x1, x2, y1, y2); |
802 | } | |
803 | ||
804 | ||
805 | // restore old paint | |
806 | 0 | g.setPaint(oldPaint); |
807 | } | |
808 | 0 | } |
809 | ||
810 | /** | |
811 | * Returns a transform to position the arrowhead on this edge shape at the | |
812 | * point where it intersects the passed vertex shape. | |
813 | */ | |
814 | public AffineTransform getArrowTransform(GeneralPath edgeShape, Shape vertexShape) { | |
815 | 0 | float[] seg = new float[6]; |
816 | 0 | Point2D p1=null; |
817 | 0 | Point2D p2=null; |
818 | 0 | AffineTransform at = new AffineTransform(); |
819 | // when the PathIterator is done, switch to the line-subdivide | |
820 | // method to get the arrowhead closer. | |
821 | 0 | for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) { |
822 | 0 | int ret = i.currentSegment(seg); |
823 | 0 | if(ret == PathIterator.SEG_MOVETO) { |
824 | 0 | p2 = new Point2D.Float(seg[0],seg[1]); |
825 | 0 | } else if(ret == PathIterator.SEG_LINETO) { |
826 | 0 | p1 = p2; |
827 | 0 | p2 = new Point2D.Float(seg[0],seg[1]); |
828 | 0 | if(vertexShape.contains(p2)) { |
829 | 0 | at = getArrowTransform(new Line2D.Float(p1,p2),vertexShape); |
830 | 0 | break; |
831 | } | |
832 | } | |
833 | } | |
834 | 0 | return at; |
835 | } | |
836 | ||
837 | /** | |
838 | * Returns a transform to position the arrowhead on this edge shape at the | |
839 | * point where it intersects the passed vertex shape. | |
840 | */ | |
841 | public AffineTransform getReverseArrowTransform(GeneralPath edgeShape, Shape vertexShape) { | |
842 | 0 | return getReverseArrowTransform(edgeShape, vertexShape, true); |
843 | } | |
844 | ||
845 | /** | |
846 | * <p>Returns a transform to position the arrowhead on this edge shape at the | |
847 | * point where it intersects the passed vertex shape.</p> | |
848 | * | |
849 | * <p>The Loop edge is a special case because its staring point is not inside | |
850 | * the vertex. The passedGo flag handles this case.</p> | |
851 | * | |
852 | * @param edgeShape | |
853 | * @param vertexShape | |
854 | * @param passedGo - used only for Loop edges | |
855 | */ | |
856 | public AffineTransform getReverseArrowTransform(GeneralPath edgeShape, Shape vertexShape, | |
857 | boolean passedGo) { | |
858 | 0 | float[] seg = new float[6]; |
859 | 0 | Point2D p1=null; |
860 | 0 | Point2D p2=null; |
861 | ||
862 | 0 | AffineTransform at = new AffineTransform(); |
863 | 0 | for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) { |
864 | 0 | int ret = i.currentSegment(seg); |
865 | 0 | if(ret == PathIterator.SEG_MOVETO) { |
866 | 0 | p2 = new Point2D.Float(seg[0],seg[1]); |
867 | 0 | } else if(ret == PathIterator.SEG_LINETO) { |
868 | 0 | p1 = p2; |
869 | 0 | p2 = new Point2D.Float(seg[0],seg[1]); |
870 | 0 | if(passedGo == false && vertexShape.contains(p2)) { |
871 | 0 | passedGo = true; |
872 | 0 | } else if(passedGo==true && |
873 | vertexShape.contains(p2)==false) { | |
874 | 0 | at = getReverseArrowTransform(new Line2D.Float(p1,p2),vertexShape); |
875 | 0 | break; |
876 | } | |
877 | } | |
878 | } | |
879 | 0 | return at; |
880 | } | |
881 | ||
882 | /** | |
883 | * This is used for the arrow of a directed and for one of the | |
884 | * arrows for non-directed edges | |
885 | * Get a transform to place the arrow shape on the passed edge at the | |
886 | * point where it intersects the passed shape | |
887 | * @param edgeShape | |
888 | * @param vertexShape | |
889 | * @return | |
890 | */ | |
891 | public AffineTransform getArrowTransform(Line2D edgeShape, Shape vertexShape) { | |
892 | 0 | float dx = (float) (edgeShape.getX1()-edgeShape.getX2()); |
893 | 0 | float dy = (float) (edgeShape.getY1()-edgeShape.getY2()); |
894 | // iterate over the line until the edge shape will place the | |
895 | // arrowhead closer than 'arrowGap' to the vertex shape boundary | |
896 | 0 | while((dx*dx+dy*dy) > arrow_placement_tolerance) { |
897 | try { | |
898 | 0 | edgeShape = getLastOutsideSegment(edgeShape, vertexShape); |
899 | 0 | } catch(IllegalArgumentException e) { |
900 | 0 | System.err.println(e.toString()); |
901 | 0 | return null; |
902 | 0 | } |
903 | 0 | dx = (float) (edgeShape.getX1()-edgeShape.getX2()); |
904 | 0 | dy = (float) (edgeShape.getY1()-edgeShape.getY2()); |
905 | } | |
906 | 0 | double atheta = Math.atan2(dx,dy)+Math.PI/2; |
907 | 0 | AffineTransform at = |
908 | AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1()); | |
909 | 0 | at.rotate(-atheta); |
910 | 0 | return at; |
911 | } | |
912 | ||
913 | /** | |
914 | * This is used for the reverse-arrow of a non-directed edge | |
915 | * get a transform to place the arrow shape on the passed edge at the | |
916 | * point where it intersects the passed shape | |
917 | * @param edgeShape | |
918 | * @param vertexShape | |
919 | * @return | |
920 | */ | |
921 | protected AffineTransform getReverseArrowTransform(Line2D edgeShape, Shape vertexShape) { | |
922 | 0 | float dx = (float) (edgeShape.getX1()-edgeShape.getX2()); |
923 | 0 | float dy = (float) (edgeShape.getY1()-edgeShape.getY2()); |
924 | // iterate over the line until the edge shape will place the | |
925 | // arrowhead closer than 'arrowGap' to the vertex shape boundary | |
926 | 0 | while((dx*dx+dy*dy) > arrow_placement_tolerance) { |
927 | try { | |
928 | 0 | edgeShape = getFirstOutsideSegment(edgeShape, vertexShape); |
929 | 0 | } catch(IllegalArgumentException e) { |
930 | 0 | System.err.println(e.toString()); |
931 | 0 | return null; |
932 | 0 | } |
933 | 0 | dx = (float) (edgeShape.getX1()-edgeShape.getX2()); |
934 | 0 | dy = (float) (edgeShape.getY1()-edgeShape.getY2()); |
935 | } | |
936 | // calculate the angle for the arrowhead | |
937 | 0 | double atheta = Math.atan2(dx,dy)-Math.PI/2; |
938 | 0 | AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1()); |
939 | 0 | at.rotate(-atheta); |
940 | 0 | return at; |
941 | } | |
942 | ||
943 | /** | |
944 | * Passed Line's point2 must be inside the passed shape or | |
945 | * an IllegalArgumentException is thrown | |
946 | * @param line line to subdivide | |
947 | * @param shape shape to compare with line | |
948 | * @return a line that intersects the shape boundary | |
949 | * @throws IllegalArgumentException if the passed line's point1 is not inside the shape | |
950 | */ | |
951 | protected Line2D getLastOutsideSegment(Line2D line, Shape shape) { | |
952 | 0 | if(shape.contains(line.getP2())==false) { |
953 | 0 | String errorString = |
954 | "line end point: "+line.getP2()+" is not contained in shape: "+shape.getBounds2D(); | |
955 | 0 | throw new IllegalArgumentException(errorString); |
956 | //return null; | |
957 | } | |
958 | 0 | Line2D left = new Line2D.Double(); |
959 | 0 | Line2D right = new Line2D.Double(); |
960 | // subdivide the line until its left segment intersects | |
961 | // the shape boundary | |
962 | 0 | int iterations = 0; |
963 | do { | |
964 | 0 | subdivide(line, left, right); |
965 | 0 | line = right; |
966 | 0 | } while(shape.contains(line.getP1())==false && iterations++ < MAX_ITERATIONS); |
967 | // now that right is completely inside shape, | |
968 | // return left, which must be partially outside | |
969 | 0 | return left; |
970 | } | |
971 | ||
972 | /** | |
973 | * Passed Line's point1 must be inside the passed shape or | |
974 | * an IllegalArgumentException is thrown | |
975 | * @param line line to subdivide | |
976 | * @param shape shape to compare with line | |
977 | * @return a line that intersects the shape boundary | |
978 | * @throws IllegalArgumentException if the passed line's point1 is not inside the shape | |
979 | */ | |
980 | protected Line2D getFirstOutsideSegment(Line2D line, Shape shape) { | |
981 | ||
982 | 0 | if(shape.contains(line.getP1())==false) { |
983 | 0 | String errorString = |
984 | "line start point: "+line.getP1()+" is not contained in shape: "+shape.getBounds2D(); | |
985 | 0 | throw new IllegalArgumentException(errorString); |
986 | } | |
987 | 0 | Line2D left = new Line2D.Float(); |
988 | 0 | Line2D right = new Line2D.Float(); |
989 | // subdivide the line until its right side intersects the | |
990 | // shape boundary | |
991 | do { | |
992 | 0 | subdivide(line, left, right); |
993 | 0 | line = left; |
994 | 0 | } while(shape.contains(line.getP2())==false); |
995 | // now that left is completely inside shape, | |
996 | // return right, which must be partially outside | |
997 | 0 | return right; |
998 | } | |
999 | ||
1000 | /** | |
1001 | * divide a Line2D into 2 new Line2Ds that are returned | |
1002 | * in the passed left and right instances, if non-null | |
1003 | * @param src the line to divide | |
1004 | * @param left the left side, or null | |
1005 | * @param right the right side, or null | |
1006 | */ | |
1007 | protected void subdivide(Line2D src, | |
1008 | Line2D left, | |
1009 | Line2D right) { | |
1010 | 0 | double x1 = src.getX1(); |
1011 | 0 | double y1 = src.getY1(); |
1012 | 0 | double x2 = src.getX2(); |
1013 | 0 | double y2 = src.getY2(); |
1014 | ||
1015 | 0 | double mx = x1 + (x2-x1)/2.0; |
1016 | 0 | double my = y1 + (y2-y1)/2.0; |
1017 | 0 | if (left != null) { |
1018 | 0 | left.setLine(x1, y1, mx, my); |
1019 | } | |
1020 | 0 | if (right != null) { |
1021 | 0 | right.setLine(mx, my, x2, y2); |
1022 | } | |
1023 | 0 | } |
1024 | ||
1025 | public Component prepareRenderer(GraphLabelRenderer graphLabelRenderer, Object value, | |
1026 | boolean isSelected, Vertex vertex) { | |
1027 | 0 | return graphLabelRenderer.getGraphLabelRendererComponent(screenDevice, value, |
1028 | vertexFontFunction.getFont(vertex), isSelected, vertex); | |
1029 | } | |
1030 | ||
1031 | public Component prepareRenderer(GraphLabelRenderer renderer, Object value, | |
1032 | boolean isSelected, Edge edge) { | |
1033 | 0 | return graphLabelRenderer.getGraphLabelRendererComponent(screenDevice, value, |
1034 | edgeFontFunction.getFont(edge), isSelected, edge); | |
1035 | } | |
1036 | ||
1037 | /** | |
1038 | * Labels the specified non-self-loop edge with the specified label. | |
1039 | * Uses the font specified by this instance's | |
1040 | * <code>EdgeFontFunction</code>. (If the font is unspecified, the existing | |
1041 | * font for the graphics context is used.) Positions the | |
1042 | * label between the endpoints according to the coefficient returned | |
1043 | * by this instance's edge label closeness function. | |
1044 | */ | |
1045 | protected void labelEdge(Graphics2D g2d, Edge e, String label, int x1, int x2, int y1, int y2) | |
1046 | { | |
1047 | 0 | int distX = x2 - x1; |
1048 | 0 | int distY = y2 - y1; |
1049 | 0 | double totalLength = Math.sqrt(distX * distX + distY * distY); |
1050 | ||
1051 | 0 | double closeness = edgeLabelClosenessFunction.getNumber(e).doubleValue(); |
1052 | ||
1053 | 0 | int posX = (int) (x1 + (closeness) * distX); |
1054 | 0 | int posY = (int) (y1 + (closeness) * distY); |
1055 | ||
1056 | 0 | int xDisplacement = (int) (LABEL_OFFSET * (distY / totalLength)); |
1057 | 0 | int yDisplacement = (int) (LABEL_OFFSET * (-distX / totalLength)); |
1058 | ||
1059 | 0 | Component component = prepareRenderer(graphLabelRenderer, label, isPicked(e), e); |
1060 | ||
1061 | 0 | Dimension d = component.getPreferredSize(); |
1062 | ||
1063 | 0 | Shape edgeShape = edgeShapeFunction.getShape(e); |
1064 | ||
1065 | 0 | double parallelOffset = 1; |
1066 | ||
1067 | 0 | parallelOffset += parallelEdgeIndexFunction.getIndex(e); |
1068 | ||
1069 | 0 | if(edgeShape instanceof Ellipse2D) { |
1070 | 0 | parallelOffset += edgeShape.getBounds().getHeight(); |
1071 | 0 | parallelOffset = -parallelOffset; |
1072 | } | |
1073 | ||
1074 | 0 | parallelOffset *= d.height; |
1075 | ||
1076 | 0 | AffineTransform old = g2d.getTransform(); |
1077 | 0 | AffineTransform xform = new AffineTransform(old); |
1078 | 0 | xform.translate(posX+xDisplacement, posY+yDisplacement); |
1079 | 0 | double dx = x2 - x1; |
1080 | 0 | double dy = y2 - y1; |
1081 | 0 | if(graphLabelRenderer.isRotateEdgeLabels()) { |
1082 | 0 | double theta = Math.atan2(dy, dx); |
1083 | 0 | if(dx < 0) { |
1084 | 0 | theta += Math.PI; |
1085 | } | |
1086 | 0 | xform.rotate(theta); |
1087 | } | |
1088 | 0 | if(dx < 0) { |
1089 | 0 | parallelOffset = -parallelOffset; |
1090 | } | |
1091 | ||
1092 | 0 | xform.translate(-d.width/2, -(d.height/2-parallelOffset)); |
1093 | 0 | g2d.setTransform(xform); |
1094 | 0 | rendererPane.paintComponent(g2d, component, screenDevice, |
1095 | 0, 0, | |
1096 | d.width, d.height, true); | |
1097 | 0 | g2d.setTransform(old); |
1098 | 0 | } |
1099 | ||
1100 | /** | |
1101 | * Paints the vertex <code>v</code> at the location <code>(x,y)</code> | |
1102 | * on the graphics context <code>g_gen</code>. The vertex is painted | |
1103 | * using the shape returned by this instance's <code>VertexShapeFunction</code>, | |
1104 | * and the foreground and background (border) colors provided by this | |
1105 | * instance's <code>VertexColorFunction</code>. Delegates drawing the | |
1106 | * label (if any) for this vertex to <code>labelVertex</code>. | |
1107 | */ | |
1108 | public void paintVertex(Graphics g, Vertex v, int x, int y) | |
1109 | { | |
1110 | 0 | if (!vertexIncludePredicate.evaluate(v)) |
1111 | 0 | return; |
1112 | ||
1113 | 0 | boolean vertexHit = true; |
1114 | 0 | Rectangle deviceRectangle = null; |
1115 | 0 | Graphics2D g2d = (Graphics2D)g; |
1116 | 0 | if(screenDevice != null) { |
1117 | 0 | Dimension d = screenDevice.getSize(); |
1118 | 0 | if(d.width <= 0 || d.height <= 0) { |
1119 | 0 | d = screenDevice.getPreferredSize(); |
1120 | } | |
1121 | 0 | deviceRectangle = new Rectangle( |
1122 | 0,0, | |
1123 | d.width,d.height); | |
1124 | } | |
1125 | ||
1126 | ||
1127 | 0 | Stroke old_stroke = g2d.getStroke(); |
1128 | 0 | Stroke new_stroke = vertexStrokeFunction.getStroke(v); |
1129 | 0 | if (new_stroke != null) { |
1130 | 0 | g2d.setStroke(new_stroke); |
1131 | } | |
1132 | // get the shape to be rendered | |
1133 | 0 | Shape s = vertexShapeFunction.getShape(v); |
1134 | ||
1135 | // create a transform that translates to the location of | |
1136 | // the vertex to be rendered | |
1137 | 0 | AffineTransform xform = AffineTransform.getTranslateInstance(x,y); |
1138 | // transform the vertex shape with xtransform | |
1139 | 0 | s = xform.createTransformedShape(s); |
1140 | ||
1141 | 0 | vertexHit = viewTransformer.transform(s).intersects(deviceRectangle); |
1142 | ||
1143 | 0 | if (vertexHit) { |
1144 | ||
1145 | 0 | if (vertexIconFunction != null) { |
1146 | 0 | paintIconForVertex(g2d, v, x, y); |
1147 | } else { | |
1148 | 0 | paintShapeForVertex(g2d, v, s); |
1149 | } | |
1150 | ||
1151 | 0 | if (new_stroke != null) { |
1152 | 0 | g2d.setStroke(old_stroke); |
1153 | } | |
1154 | 0 | String label = vertexStringer.getLabel(v); |
1155 | 0 | if (label != null) { |
1156 | 0 | labelVertex(g, v, label, x, y); |
1157 | } | |
1158 | } | |
1159 | 0 | } |
1160 | ||
1161 | public void paintShapeForVertex(Graphics2D g2d, Vertex v, Shape shape) { | |
1162 | 0 | Paint oldPaint = g2d.getPaint(); |
1163 | 0 | Paint fillPaint = vertexPaintFunction.getFillPaint(v); |
1164 | 0 | if(fillPaint != null) { |
1165 | 0 | g2d.setPaint(fillPaint); |
1166 | 0 | g2d.fill(shape); |
1167 | 0 | g2d.setPaint(oldPaint); |
1168 | } | |
1169 | 0 | Paint drawPaint = vertexPaintFunction.getDrawPaint(v); |
1170 | 0 | if(drawPaint != null) { |
1171 | 0 | g2d.setPaint(drawPaint); |
1172 | 0 | g2d.draw(shape); |
1173 | 0 | g2d.setPaint(oldPaint); |
1174 | } | |
1175 | 0 | } |
1176 | ||
1177 | /** | |
1178 | * Paint <code>v</code>'s icon on <code>g</code> at <code>(x,y)</code>. | |
1179 | */ | |
1180 | public void paintIconForVertex(Graphics g, Vertex v, int x, int y) { | |
1181 | 0 | Icon icon = vertexIconFunction.getIcon(v); |
1182 | 0 | if(icon == null) { |
1183 | 0 | paintShapeForVertex((Graphics2D)g, v, vertexShapeFunction.getShape(v)); |
1184 | } | |
1185 | 0 | int xLoc = x - icon.getIconWidth()/2; |
1186 | 0 | int yLoc = y - icon.getIconHeight()/2; |
1187 | 0 | icon.paintIcon(screenDevice, g, xLoc, yLoc); |
1188 | 0 | } |
1189 | ||
1190 | /** | |
1191 | * Labels the specified vertex with the specified label. | |
1192 | * Uses the font specified by this instance's | |
1193 | * <code>VertexFontFunction</code>. (If the font is unspecified, the existing | |
1194 | * font for the graphics context is used.) If vertex label centering | |
1195 | * is active, the label is centered on the position of the vertex; otherwise | |
1196 | * the label is offset slightly. | |
1197 | */ | |
1198 | protected void labelVertex(Graphics g, Vertex v, String label, int x, int y) | |
1199 | { | |
1200 | 0 | Component component = prepareRenderer(graphLabelRenderer, label, isPicked(v), v); |
1201 | ||
1202 | 0 | Dimension d = component.getPreferredSize(); |
1203 | ||
1204 | int h_offset; | |
1205 | int v_offset; | |
1206 | 0 | if (centerVertexLabel) |
1207 | { | |
1208 | 0 | h_offset = -d.width / 2; |
1209 | 0 | v_offset = -d.height / 2; |
1210 | ||
1211 | } | |
1212 | else | |
1213 | { | |
1214 | 0 | Rectangle2D bounds = vertexShapeFunction.getShape(v).getBounds2D(); |
1215 | 0 | h_offset = (int)(bounds.getWidth() / 2) + 5; |
1216 | 0 | v_offset = (int)(bounds.getHeight() / 2) + 5 -d.height; |
1217 | } | |
1218 | ||
1219 | 0 | rendererPane.paintComponent(g, component, screenDevice, x+h_offset, y+v_offset, |
1220 | d.width, d.height, true); | |
1221 | ||
1222 | 0 | } |
1223 | ||
1224 | /** | |
1225 | * @see AbstractRenderer#isPicked(Vertex) | |
1226 | * @deprecated Use an independent PickedInfo instead of this version, | |
1227 | * which relies on the Renderer to supply an instance. | |
1228 | */ | |
1229 | public boolean isPicked(ArchetypeVertex v) | |
1230 | { | |
1231 | 0 | return super.isPicked(v); |
1232 | } | |
1233 | ||
1234 | /** | |
1235 | * @see AbstractRenderer#isPicked(Edge) | |
1236 | * @deprecated Use an independent PickedInfo instead of this version, | |
1237 | * which relies on the Renderer to supply an instance. | |
1238 | */ | |
1239 | public boolean isPicked(ArchetypeEdge e) { | |
1240 | 0 | return super.isPicked(e); |
1241 | } | |
1242 | ||
1243 | /** | |
1244 | * @return Returns the rendererPane. | |
1245 | */ | |
1246 | public CellRendererPane getRendererPane() { | |
1247 | 0 | return rendererPane; |
1248 | } | |
1249 | ||
1250 | /** | |
1251 | * @param rendererPane The rendererPane to set. | |
1252 | */ | |
1253 | public void setRendererPane(CellRendererPane rendererPane) { | |
1254 | 0 | this.rendererPane = rendererPane; |
1255 | 0 | } |
1256 | ||
1257 | public ParallelEdgeIndexFunction getParallelEdgeIndexFunction() { | |
1258 | 0 | return parallelEdgeIndexFunction; |
1259 | } | |
1260 | ||
1261 | public void setParallelEdgeIndexFunction( | |
1262 | ParallelEdgeIndexFunction parallelEdgeIndexFunction) { | |
1263 | 0 | this.parallelEdgeIndexFunction = parallelEdgeIndexFunction; |
1264 | 0 | } |
1265 | ||
1266 | public void setViewTransformer(MutableTransformer viewTransformer) { | |
1267 | 0 | this.viewTransformer = viewTransformer; |
1268 | 0 | } |
1269 | } |
this report was generated by version 1.0.5 of jcoverage. |
copyright © 2003, jcoverage ltd. all rights reserved. |