Source code: com/port80/graph/impl/GraphShape.java
1 //
2 // Copyright(c) 2002, Chris Leung
3 //
4
5 package com.port80.graph.impl;
6
7 import java.awt.Color;
8 import java.awt.Font;
9 import java.awt.Graphics2D;
10 import java.awt.Paint;
11 import java.awt.Rectangle;
12 import java.awt.Shape;
13 import java.awt.Stroke;
14 import java.awt.font.FontRenderContext;
15 import java.awt.font.TextAttribute;
16 import java.awt.font.TextLayout;
17 import java.awt.geom.AffineTransform;
18 import java.awt.geom.Line2D;
19 import java.awt.geom.PathIterator;
20 import java.awt.geom.Point2D;
21 import java.awt.geom.Rectangle2D;
22 import java.awt.image.BufferedImage;
23 import java.text.AttributedString;
24 import java.util.ArrayList;
25 import java.util.List;
26
27 import com.port80.graph.IGraphShape;
28 import com.port80.graph.IGraphStroke;
29 import com.port80.text.AttrTextParser;
30 import com.port80.text.TextAttr;
31 import com.port80.util.msg;
32 import com.port80.util.struct.IntList;
33
34 /** Basic graph shape class that implements IGraphShape interface.
35 *
36 * . Wrapper around a java.awt.Shape objects and an enclosed unit
37 * square. When the unit square is scaled to accommodate the label
38 * bounds, the shape is scaled accordingly.
39 *
40 */
41 public class GraphShape implements IGraphShape {
42
43 // Static fields ///////////////////////////////////////////////////////
44 //
45
46 private static final String NAME = "GraphShape";
47 private static final boolean DEBUG = false;
48
49 private static final Graphics2D defaultGraphics;
50 static {
51 defaultGraphics = (Graphics2D) (new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getGraphics());
52 }
53
54 // Instance fields /////////////////////////////////////////////////////
55 //
56
57 private String name;
58 private Shape original;
59 private Rectangle2D clientRect;
60 private boolean keepRatio;
61 //
62 private String label;
63 private Point2D origin;
64 private Font font;
65 private IGraphStroke borderStroke;
66 private Color fontColor;
67 private Color borderColor;
68 private Color fillColor;
69 private int minWidth;
70 private int minHeight;
71 private int exclude;
72 //
73 private TextLayout[] layouts;
74 private int[] aligns;
75 private BufferedImage imageBuffer;
76 //
77 private Shape shapeObject;
78 private double xScale = 1.0;
79 private double yScale = 1.0;
80 private double baseX = 0.0;
81 private double baseY = 0.0;
82 private boolean dirty = false;
83 //
84 // Constants.
85 //
86 private double xPadding = 5.0;
87 private double yPadding = 2.0;
88 private double textSpacing = 0.0;
89 private double hrSpacing = 2.0;
90
91 // Constructors ////////////////////////////////////////////////////////
92 //
93
94 public GraphShape(String name, Shape shape, Rectangle2D rect, boolean keepratio) {
95 this.name = name;
96 this.original = shape;
97 this.clientRect = (Rectangle2D) rect.clone();
98 this.keepRatio = keepratio;
99 this.origin = new Point2D.Double(0.0, 0.0);
100 }
101
102 public GraphShape(String name, IGraphShape gs) {
103 this.name = name;
104 this.original = gs.getTemplateShape();
105 this.clientRect = (Rectangle2D) gs.getClientRect().clone();
106 this.keepRatio = gs.isKeepRatio();
107 this.origin = gs.getOrigin();
108 }
109
110 // IGraphShape interface ///////////////////////////////////////////////
111 //
112
113 public String getName() {
114 return name;
115 }
116 public Shape getShape() {
117 return shapeObject;
118 }
119 public Shape getTemplateShape() {
120 return original;
121 }
122 public Rectangle2D getClientRect() {
123 return clientRect;
124 }
125 public boolean isKeepRatio() {
126 return keepRatio;
127 }
128 public Point2D getOrigin() {
129 return origin;
130 }
131 public double getBaseX() {
132 return baseX;
133 }
134 public double getBaseY() {
135 return baseY;
136 }
137 public TextLayout[] getLayouts() {
138 return layouts;
139 }
140 public BufferedImage getImageBuffer() {
141 return imageBuffer;
142 }
143
144 public void setOrigin(Point2D p) {
145 origin = p;
146 }
147
148 /** Update shape size and label and render content into an image buffer.
149 */
150 public void update(
151 Graphics2D g2d,
152 String label,
153 Point2D pos,
154 int minwidth,
155 int minheight,
156 Font font,
157 IGraphStroke stroke,
158 Color fontcolor,
159 Color bordercolor,
160 Color fillcolor,
161 int exclude) {
162 if (label == null)
163 msg.err(NAME + ".update(): label==null");
164 this.label = label;
165 this.minWidth = minwidth;
166 this.minHeight = minHeight;
167 this.font = font;
168 this.borderStroke = stroke;
169 this.fontColor = fontcolor;
170 this.borderColor = bordercolor;
171 this.fillColor = fillcolor;
172 this.exclude = exclude;
173 if (this.fontColor == null)
174 this.fontColor = Color.black;
175 if (this.borderColor == null)
176 this.borderColor = Color.black;
177
178 // Determine bounds for label, scale shape accordingly and
179 // render shape into an BufferedImage.
180
181 imageBuffer = null;
182
183 int index = 0;
184 int nlines = 1;
185 /*
186 while((index=label.indexOf("\n",index)+1)>0) ++nlines;
187 layouts=new TextLayout[nlines];
188 aligns=new int[nlines];
189 */
190
191 Rectangle2D bounds;
192 TextLayout layout;
193 String str;
194 List astack = new ArrayList();
195 String text = label;
196 double textwidth = 0;
197 double textheight = 0;
198 FontRenderContext frc = defaultGraphics.getFontRenderContext();
199 nlines = 0;
200 int align = 0;
201 List layoutList = new ArrayList();
202 IntList alignList = new IntList();
203 if (DEBUG)
204 msg.println(NAME + ".update(): text=|" + text + "|");
205 do {
206 index = text.indexOf("\n");
207 if (index < 0)
208 str = text;
209 else {
210 str = text.substring(0, index);
211 text = text.substring(index + 1);
212 }
213 if (DEBUG)
214 msg.println(NAME + ".update(): str=|" + str + "|");
215 AttrTextParser parser = AttrTextParser.parse(str, font, astack);
216 align = parser.getAlign();
217 if (exclude > 0 && (align & parser.ALIGN_ALT) != 0)
218 continue;
219 if ((align & parser.ALIGN_HR_BEFORE) != 0)
220 textheight += hrSpacing;
221 str = parser.getText();
222 AttributedString atext = null;
223 if (str.length() > 0) {
224 atext = new AttributedString(str);
225 List alist = parser.getAttrs();
226 atext.addAttribute(TextAttribute.FONT, font);
227 for (int i = 0; i < alist.size(); ++i) {
228 TextAttr a = (TextAttr) alist.get(i);
229 if (false)
230 msg.println(NAME + ".update(): a=" + a);
231 for (int n = 0; n < a.size(); ++n) {
232 atext.addAttribute(
233 (TextAttribute) a.getKey(n),
234 a.getValue(n),
235 a.getStart(),
236 a.getEnd());
237 }
238 }
239 } else {
240 atext = new AttributedString(" ");
241 }
242 //
243 layout = new TextLayout(atext.getIterator(), frc);
244 bounds = layout.getBounds();
245 if (bounds.getWidth() > textwidth)
246 textwidth = bounds.getWidth();
247 if (nlines > 0)
248 textheight += textSpacing;
249 textheight += layout.getAscent() + layout.getDescent();
250 if ((align & parser.ALIGN_HR_AFTER) != 0)
251 textheight += hrSpacing;
252 alignList.add(align);
253 layoutList.add(layout);
254 //aligns[nlines]=align;
255 //layouts[nlines]=layout;
256 ++nlines;
257 } while (index >= 0);
258 layouts = new TextLayout[layoutList.size()];
259 aligns = new int[alignList.size()];
260 for (int i = 0; i < layoutList.size(); ++i) {
261 layouts[i] = (TextLayout) layoutList.get(i);
262 aligns[i] = alignList.get(i);
263 }
264 double xscale = minWidth;
265 double yscale = minHeight;
266 if (textwidth > xscale)
267 xscale = textwidth;
268 if (textheight > yscale)
269 yscale = textheight;
270 xscale += 2 * xPadding;
271 yscale += 2 * yPadding;
272 reshape(xscale, yscale, pos, textwidth, textheight);
273 }
274
275 /** Render shape content to given Graphics2D.
276 */
277 public void render(Graphics2D g2d) {
278 //
279 // Fill
280 //
281 if (fillColor != null) {
282 g2d.setPaint(fillColor);
283 g2d.fill(shapeObject);
284 if (DEBUG)
285 msg.println(
286 NAME
287 + ".render(): name="
288 + name
289 + ", stroke="
290 + borderStroke.getName()
291 + ", fillcolor="
292 + fillColor
293 + ", shapeObject="
294 + shapeObject.toString());
295 }
296 //
297 // Border
298 //
299 Stroke stroke = borderStroke.getStroke();
300 if (stroke != null) {
301 g2d.setStroke(stroke);
302 g2d.setPaint(borderColor);
303 g2d.draw(shapeObject);
304 if (DEBUG)
305 msg.println(
306 NAME
307 + ".render(): border: color="
308 + borderColor
309 + ", stroke="
310 + stroke
311 + ", shape="
312 + shapeObject);
313 }
314 //
315 // Draw client rectangle for debugging.
316 //
317 //g2d.setPaint(Color.red);
318 //g2d.setStroke(new BasicStroke(1.0f,BasicStroke.CAP_SQUARE,BasicStroke.JOIN_MITER,10.0f,
319 // new float[] {2.0f,2.0f},0f));
320 //g2d.draw(getClientRect());
321 //g2d.setStroke(stroke);
322 //
323 g2d.setFont(font);
324 g2d.setPaint(fontColor);
325 Rectangle2D bounds;
326 double w = clientRect.getWidth();
327 double basex = clientRect.getX();
328 double x = 0;
329 double y = clientRect.getY();
330 Stroke hrstroke = StrokeFactory.create("solid").getStroke();
331 for (int i = 0; i < layouts.length; ++i) {
332 if ((aligns[i] & AttrTextParser.ALIGN_HR_BEFORE) != 0) {
333 Paint color = g2d.getPaint();
334 Stroke savedstroke = g2d.getStroke();
335 g2d.setPaint(Color.gray);
336 g2d.setStroke(hrstroke);
337 g2d.draw(new Line2D.Double(basex, y + (hrSpacing / 2), basex + w, y + hrSpacing / 2));
338 y += hrSpacing;
339 g2d.setPaint(color);
340 g2d.setStroke(savedstroke);
341 }
342 TextLayout layout = layouts[i];
343 bounds = layout.getBounds();
344 x = basex;
345 if ((aligns[i] & AttrTextParser.ALIGN_LEFT) != 0);
346 else if ((aligns[i] & AttrTextParser.ALIGN_RIGHT) != 0)
347 x += w - bounds.getWidth();
348 else
349 x += (w - bounds.getWidth()) / 2f;
350 y += layout.getAscent();
351 layout.draw(g2d, (float) x, (float) y);
352 y += layout.getDescent();
353 if ((aligns[i] & AttrTextParser.ALIGN_HR_AFTER) != 0) {
354 Paint color = g2d.getPaint();
355 Stroke savedstroke = g2d.getStroke();
356 g2d.setPaint(Color.gray);
357 g2d.setStroke(hrstroke);
358 g2d.draw(new Line2D.Double(basex, y + (hrSpacing / 2), basex + w, y + hrSpacing / 2));
359 y += hrSpacing;
360 g2d.setPaint(color);
361 g2d.setStroke(savedstroke);
362 }
363 }
364 }
365
366 /** Scale shape and set new original.
367 *
368 * . xscale and yscale is usually the same as textwidth and
369 * textheight. However, xscale may > textwidth to set a minimium
370 * width regardless of textwidth or add more margins to the text.
371 *
372 * @param xscale x-axis scaling factor.
373 * @param yscale y-axis scaling factor.
374 * @param neworigin the new origin coordinate.
375 * @param fm FontMetrics used for the text label.
376 * @param textwidth actual text width.
377 * @param textheight actual text height.
378 */
379 public void reshape(double xscale, double yscale, Point2D neworigin, double textwidth, double textheight) {
380 if (keepRatio) {
381 double scale = Math.max(xscale, yscale);
382 xscale = scale;
383 yscale = scale;
384 }
385 Rectangle2D r = original.getBounds2D();
386 double linewidth= (double) borderStroke.getLineWidth();
387 xscale *= (r.getWidth()*xscale + linewidth) / (r.getWidth()*xscale);
388 yscale *= (r.getWidth()*yscale + linewidth) / (r.getWidth()*yscale);
389 AffineTransform tx = AffineTransform.getTranslateInstance(neworigin.getX(), neworigin.getY());
390 tx.scale(xscale, yscale);
391 // if (origin.getX() != 0 || origin.getY() != 0)
392 // tx.translate(-origin.getX(), -origin.getY());
393 shapeObject = tx.createTransformedShape(original);
394 // We have simplified a bit here assuming that origin is
395 // always at center of the client rectangle.
396 double w = textwidth;
397 double h = textheight;
398 clientRect.setRect(neworigin.getX() - w / 2.0, neworigin.getY() - h / 2.0, w, h);
399 xScale = xscale;
400 yScale = yscale;
401 origin = neworigin;
402 }
403
404 public String toString() {
405 return name
406 + ": origin="
407 + origin.getX()
408 + ","
409 + origin.getY()
410 + ": scale="
411 + xScale
412 + ", "
413 + yScale
414 + "\n\t"
415 + ": clientRect="
416 + (int) clientRect.getX()
417 + ","
418 + (int) clientRect.getY()
419 + ","
420 + (int) clientRect.getWidth()
421 + ","
422 + (int) clientRect.getHeight()
423 + ": base="
424 + baseX
425 + ", "
426 + baseY;
427 }
428
429 // Cloneable interface /////////////////////////////////////////////////
430
431 public Object clone() {
432 return new GraphShape(name, this);
433 }
434
435 // Shape interface /////////////////////////////////////////////////////
436
437 public boolean contains(double x, double y) {
438 return shapeObject.contains(x, y);
439 }
440 public boolean contains(double x, double y, double w, double h) {
441 return shapeObject.contains(x, y, w, h);
442 }
443 public boolean contains(Point2D p) {
444 return shapeObject.contains(p);
445 }
446 public boolean contains(Rectangle2D r) {
447 return shapeObject.contains(r);
448 }
449 public Rectangle getBounds() {
450 Rectangle ret = shapeObject.getBounds();
451 float lw = borderStroke.getLineWidth();
452 ret.setRect(ret.getX() - lw / 2, ret.getY() - lw / 2, ret.getWidth() + lw, ret.getHeight() + lw);
453 return ret;
454 }
455 public Rectangle2D getBounds2D() {
456 Rectangle2D ret = shapeObject.getBounds2D();
457 float lw = borderStroke.getLineWidth();
458 ret.setRect(ret.getX() - lw / 2, ret.getY() - lw / 2, ret.getWidth() + lw, ret.getHeight() + lw);
459 return ret;
460 }
461 public PathIterator getPathIterator(AffineTransform t) {
462 return shapeObject.getPathIterator(t);
463 }
464 public PathIterator getPathIterator(AffineTransform t, double flatness) {
465 return shapeObject.getPathIterator(t, flatness);
466 }
467 public boolean intersects(double x, double y, double w, double h) {
468 return shapeObject.intersects(x, y, w, h);
469 }
470 public boolean intersects(Rectangle2D r) {
471 return shapeObject.intersects(r);
472 }
473
474 ////////////////////////////////////////////////////////////////////////
475
476 }