Source code: com/port80/graph/impl/DirectedGraphRenderer.java
1 //
2 // Copyright(c) 2002, Chris Leung
3 //
4
5 package com.port80.graph.impl;
6
7 import java.awt.BasicStroke;
8 import java.awt.Color;
9 import java.awt.Font;
10 import java.awt.FontMetrics;
11 import java.awt.Graphics2D;
12 import java.awt.Shape;
13 import java.awt.geom.AffineTransform;
14 import java.awt.geom.GeneralPath;
15 import java.awt.geom.Point2D;
16 import java.awt.geom.Rectangle2D;
17 import java.util.Collection;
18 import java.util.Iterator;
19 import java.util.TreeSet;
20
21 import com.port80.graph.IArrow;
22 import com.port80.graph.IEdge;
23 import com.port80.graph.IGraph;
24 import com.port80.graph.IGraphRenderer;
25 import com.port80.graph.IGraphShape;
26 import com.port80.graph.IGraphStroke;
27 import com.port80.graph.IVertex;
28 import com.port80.graph.dot.impl.DotRoute;
29 import com.port80.util.msg;
30 import com.port80.util.sprint;
31 import com.port80.util.attr.FontFactory;
32 import com.port80.util.attr.IAttrTable;
33
34 /** Render directed graph.
35 */
36 public class DirectedGraphRenderer implements IGraphRenderer {
37
38 // Static fields ///////////////////////////////////////////////////////
39 //
40
41 private static final String NAME = "DirectedGraphRenderer";
42 private static final boolean DEBUG = false;
43
44 // Instance fields /////////////////////////////////////////////////////
45 //
46 private boolean fDirty = true;
47 private double fScale = 1.0;
48 private String fScaleStr = "";
49
50 // Constructors ////////////////////////////////////////////////////////
51 //
52
53 /** Constructor for GraphPanel. */
54 public DirectedGraphRenderer() {
55 }
56
57 // IGraphRenderer interface ////////////////////////////////////////////
58 //
59
60 /** Calculate the geometry of an IVertex and update its -shape attribute.
61 *
62 * @return The updated shape.
63 */
64 public Shape updateShape(Graphics2D g2d, IVertex v) {
65 if (false)
66 msg.println("DirectedGraphRenderer.updateShape(IVertex): " + v.getName());
67 String label = v.getAttrString("label");
68 if (DEBUG)
69 if (label == null)
70 msg.err(NAME + ".updateShape(IVertex): label==null");
71 //
72 Font font = updateFont(v);
73 int minwidth = v.getAttrInt("minwidth");
74 int minheight = v.getAttrInt("minheight");
75 int exclude = v.getAttrInt("exclude", 0);
76 Point2D pos = (Point2D) v.getAttr("pos");
77 Color fontcolor = (Color) v.getAttr("fontcolor");
78 Color bordercolor = (Color) v.getAttr("color");
79 Color fillcolor = (Color) v.getAttr("fillcolor");
80 IGraphStroke stroke = updateStroke(v, "style",false);
81 if (pos == null)
82 pos = new Point2D.Double(0.0, 0.0);
83 IGraphShape gshape = (IGraphShape) v.getAttrCached("shape");
84 gshape.update(
85 g2d,
86 label,
87 pos,
88 minwidth,
89 minheight,
90 font,
91 stroke,
92 fontcolor,
93 bordercolor,
94 fillcolor,
95 exclude);
96 Rectangle2D bounds = gshape.getBounds2D();
97 v.setAttr("-bounds", bounds);
98 if (DEBUG)
99 msg.println(
100 NAME
101 + ".updateShape(IVertex): v="
102 + v.getName()
103 + ", bounds="
104 + bounds.toString());
105 //
106 // Update out edges label dimensions.
107 //
108 IEdge[] outs = v.outs();
109 for (int i = 0, max = outs.length; i < max; ++i)
110 updateLabelBounds(g2d, outs[i]);
111 return gshape;
112 }
113
114 /** Calculate the geometry of all vertices in a graph. */
115 public void updateShape(Graphics2D g2d, IGraph graph) {
116 if (false)
117 msg.println(
118 "DirectedGraphRenderer.updateShape(IGraph): " + graph.getName());
119 Collection c = graph.allVertices();
120 IVertex v;
121 for (Iterator it = c.iterator(); it.hasNext();) {
122 v = (IVertex) it.next();
123 updateShape(g2d, v);
124 }
125 }
126
127 ////////////////////////////////////////////////////////////////////////
128
129 public void setScale(double d) {
130 fScale = d;
131 if (fScale != 1.0)
132 fScaleStr = sprint.f(",%.2f").a(1 / fScale).end();
133 else
134 fScaleStr = "";
135 }
136
137 public void render(Graphics2D g2d, IVertex v) {
138 if (false)
139 msg.println("DirectedGraphRenderer.render(IVertex): " + v.getName());
140 IGraphShape gshape = (IGraphShape) v.getAttr("-shape");
141 if (DEBUG)
142 msg.println("\tShape=" + gshape);
143 gshape.render(g2d);
144 }
145
146 public void renderBus(Graphics2D g2d, IVertex v) {
147 if (false)
148 msg.println("DirectedGraphRenderer.renderBus(IVertex): " + v.getName());
149 if (fDirty) {
150 updateShape(g2d, v);
151 // Update in edges just to make sure.
152 IEdge[] ins = v.ins();
153 for (int i = 0, max = ins.length; i < max; ++i) {
154 updateLabelBounds(g2d, ins[i]);
155 }
156 }
157 DotRoute tobus = (DotRoute) v.getAttrCached("tobus");
158 DotRoute frombus = (DotRoute) v.getAttrCached("frombus");
159 DotRoute inbus = (DotRoute) v.getAttrCached("inbus");
160 DotRoute outbus = (DotRoute) v.getAttrCached("outbus");
161 Color buscolor;
162 g2d.setStroke(StrokeFactory.create("bus").getStroke()); // + fScaleStr).getStroke());
163 buscolor = (Color) v.getAttr("ibuscolor");
164 g2d.setPaint(buscolor);
165 if (inbus != null)
166 g2d.draw(inbus.getPath());
167 if (frombus != null)
168 g2d.draw(frombus.getPath());
169 buscolor = (Color) v.getAttr("obuscolor");
170 g2d.setPaint(buscolor);
171 if (tobus != null)
172 g2d.draw(tobus.getPath());
173 if (outbus != null)
174 g2d.draw(outbus.getPath());
175 }
176
177 public void render(Graphics2D g2d, IEdge e) {
178 DotRoute route = (DotRoute) e.getAttrCached("pos");
179 if (route == null)
180 return;
181 //
182 Color color = (Color) e.getAttr("color");
183 if (color == null)
184 color = Color.black;
185 g2d.setPaint(color);
186 GraphStroke graphstroke = (GraphStroke) e.getAttrCached("style");
187 g2d.setStroke(graphstroke.getStroke());
188 //g2d.setStroke(StrokeFactory.create("solid" + scaleStr).getStroke());
189 //new BasicStroke(1.0f*sc,BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL));
190 //
191 GeneralPath p = route.getPath();
192 g2d.draw(p);
193 //
194 IArrow arrow;
195 arrow = (IArrow) e.getAttrCached("headarrow");
196 if (arrow != null)
197 arrow.render(g2d, route.getHeadTransform());
198 arrow = (IArrow) e.getAttrCached("tailarrow");
199 if (arrow != null)
200 arrow.render(g2d, route.getTailTransform());
201 }
202
203 public void render(Graphics2D g2d, IGraph g) {
204 //
205 if (false)
206 msg.println(NAME + "render(Graphics2D,IGraph): " + g.getName());
207 updateShape(g2d, g);
208 //
209 Rectangle2D bounds = (Rectangle2D) g.getAttr("bb");
210 Color bg = (Color) g.getAttr("bgcolor");
211 if (DEBUG)
212 if (bg == null)
213 msg.err(NAME + ".paint(IGraph): bg==null");
214 g2d.setBackground(bg);
215 g2d.clearRect(
216 (int) bounds.getX(),
217 (int) bounds.getY(),
218 (int) bounds.getWidth() + 1,
219 (int) bounds.getHeight() + 1);
220 //
221 g2d.setStroke(new BasicStroke(0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
222 g2d.setPaint(Color.black);
223 g2d.draw(bounds);
224 //
225 Collection c;
226 AffineTransform tx = null;
227 if (fScale != 1.0) {
228 tx = g2d.getTransform();
229 g2d.scale(fScale, fScale);
230 }
231 c = g.allVertices();
232 for (Iterator it = c.iterator(); it.hasNext();) {
233 renderBus(g2d,(IVertex)it.next());
234 //FIXME: For clip checking, draw edge first
235 if (DEBUG) {
236 render(g2d, (IVertex) it.next());
237 }
238 }
239 c=new TreeSet(new DirectedEdgeFactory.EdgeRenderingComparator());
240 c.addAll(g.allEdges());
241 for (Iterator it = c.iterator(); it.hasNext();) {
242 render(g2d, (IEdge) it.next());
243 }
244 if (!DEBUG) {
245 c = g.allVertices();
246 for (Iterator it = c.iterator(); it.hasNext();) {
247 render(g2d, (IVertex) it.next());
248 }
249 }
250 if (tx != null)
251 g2d.setTransform(tx);
252 }
253
254 ////////////////////////////////////////////////////////////////////////
255
256 private Font updateFont(IAttrTable table) {
257 String fontspec = table.getAttrString("fontname") + "-";
258 String fontstyle = table.getAttrString("fontstyle");
259 if (fontstyle != null)
260 fontspec += fontstyle;
261 fontspec += "-" + (int) table.getAttrDouble("fontsize");
262 Font font = FontFactory.create(fontspec);
263 table.setAttr("-font", font);
264 return font;
265 }
266
267 /**
268 * @param table Attribute table.
269 * @param attrname Attribute name that defined the stroke (eg. style).
270 * @param bold True to double outline stroke width.
271 */
272 private IGraphStroke updateStroke(IAttrTable table, String attrname, boolean bold) {
273 IGraphStroke stroke = (IGraphStroke) table.getAttr("-" + attrname);
274 if (stroke == null) {
275 if (bold) { // || fScale!=1.0) {
276 String strokename = table.getAttrAsString(attrname);
277 double w=(bold? 3.0: 1.0); // /fScale;
278 int index = strokename.indexOf(',');
279 if (index >= 0) {
280 String wstr=strokename.substring(index+1);
281 strokename=strokename.substring(0,index);
282 w*=msg.parseFloat(wstr,1.0f);
283 }
284 strokename += sprint.f(",%.2f").a(w).end();
285 stroke = StrokeFactory.create(strokename);
286 } else {
287 stroke=StrokeFactory.create(table.getAttrString(attrname));
288 }
289 table.setAttr("-"+attrname,stroke);
290 }
291 return stroke;
292 }
293
294 private Rectangle2D updateLabelBounds(Graphics2D g2d, IEdge e) {
295 String label = e.getAttrString("label");
296 if (label == null)
297 return null;
298 Font font = updateFont(e);
299 FontMetrics fm = g2d.getFontMetrics(font);
300 Rectangle2D bounds = fm.getStringBounds(label, g2d);
301 e.setAttr("-bounds", bounds);
302 if (DEBUG)
303 msg.println(
304 NAME + ".updateLabelBounds(IEdge): bounds=" + bounds.toString());
305 return bounds;
306 }
307
308 ////////////////////////////////////////////////////////////////////////
309
310 }