Source code: com/port80/graph/impl/ArrowFactory.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.geom.*;
9 import java.util.*;
10 import com.port80.util.*;
11 import com.port80.util.attr.*;
12 import com.port80.util.struct.*;
13 import com.port80.graph.*;
14
15 /** Arrow factory.
16 *
17 * Arrow factory provides a named collection of of stock Arrow object
18 * templates. Each template is parameterized by the leadinWidth,
19 * which determines the leadin shape. Default templates have
20 * leadinWidth=1.0f.
21 *
22 * The template produced have default colors from the Arrow class.
23 * Typically client have to set the appropriate attributes for the
24 * Arrow template and render it into an BufferedImage and then
25 * applied AffineTransform and composition to merge the rendered
26 * image to client area.
27 *
28 * //FIXME:
29 * . Scale square down to 1/sqrt(2) with leadin.
30 */
31 public class ArrowFactory implements IAttrFactory {
32
33 ////////////////////////////////////////////////////////////////////////
34
35 private static final String NAME = "ArrowFactory";
36 private static ArrowFactory instance = null;
37
38 public static final int ARROW_NONE = 0;
39 public static final int ARROW_NORMAL = 1;
40 public static final int ARROW_INVERT = 2;
41 public static final int ARROW_DOT = 3;
42 public static final int ARROW_ODOT = 4;
43 public static final int ARROW_INVDOT = 5;
44 public static final int ARROW_INVODOT = 6;
45
46 private static Map table;
47 private static final float SQRT2 = (float) Math.sqrt(2.0);
48 private static final float H = 12f;
49 private static final float LW = 1f;
50 static {
51 table = new HashMap();
52 table.put("none",null);
53 table.put("hide",null);
54 stockNormal("default", 1, 1, LW, LW);
55 stockNormal("normal", 1, 1, LW, LW);
56 stockLine("line", 1, 1, LW, LW);
57 stockDoubleLine("doubleline", 1, 1, LW, LW);
58 //
59 stockTriangle("otriangle", 1, 1, LW, LW);
60 stockSpear("ospear", 1, 1, LW, LW);
61 stockDot("odot", 1, 1, LW, LW);
62 stockDiamond("odiamond", 1, 1, LW, LW);
63 stockSquare("osquare", 1, 1, LW, LW);
64 //
65 stockTriangle("triangle", 1, 1, LW, 0f);
66 stockSpear("spear", 1, 1, LW, 0f);
67 stockDot("dot", 1, 1, LW, 0f);
68 stockDiamond("diamond", 1, 1, LW, 0f);
69 stockSquare("square", 1, 1, LW, 0f);
70 }
71
72 ////////////////////////////////////////////////////////////////////////
73
74 public static ArrowFactory getInstance() {
75 if (instance == null)
76 instance = new ArrowFactory();
77 return instance;
78 }
79 /** String format:
80 * stockname
81 * | typename,height
82 * | typename,height,linewidth
83 * | typename,height,width,linewidth
84 * | typename,height,width,linewidth,leadinwidth
85 */
86 public static Arrow create(String stringvalue) {
87 if(table.containsKey(stringvalue)) {
88 return (Arrow) table.get(stringvalue);
89 }
90 // Custom.
91 int index = stringvalue.indexOf(',');
92 float leadinwidth = 1f;
93 float linewidth = 0f;
94 float w = 1f;
95 float h = 1f;
96 if (index > 0) {
97 String name = stringvalue.substring(0, index);
98 index = msg.skipWS(stringvalue, ++index, stringvalue.length());
99 FloatList f = msg.parseFloats(stringvalue.substring(index));
100 if (f.size() == 1) {
101 h = f.get(0) / H;
102 w = h;
103 } else if (f.size() == 2) {
104 h = f.get(0) / H;
105 w = h;
106 linewidth=f.get(1);
107 } else if (f.size() == 3) {
108 h = f.get(0) / H;
109 w = f.get(1) / H;
110 linewidth = f.get(2);
111 } else if (f.size() == 4) {
112 h = f.get(0) / H;
113 w = f.get(1) / H;
114 linewidth = f.get(2);
115 leadinwidth = f.get(3);
116 } else {
117 index = -1;
118 }
119 if (index > 0) {
120 if (name.equals("line"))
121 return stockLine(stringvalue, w, h, leadinwidth, linewidth);
122 if (name.equals("dot"))
123 return stockDot(stringvalue, w, h, leadinwidth, linewidth);
124 if (name.equals("triangle"))
125 return stockTriangle(stringvalue, w, h, leadinwidth, linewidth);
126 if (name.equals("square"))
127 return stockSquare(stringvalue, w, h, leadinwidth, linewidth);
128 if (name.equals("diamond"))
129 return stockDiamond(stringvalue, w, h, leadinwidth, linewidth);
130 if (name.equals("spear"))
131 return stockSpear(stringvalue, w, h, leadinwidth, linewidth);
132 if (name.equals("doubleline"))
133 return stockDoubleLine(stringvalue, w, h, leadinwidth, linewidth);
134 //
135 if (name.equals("odot"))
136 return stockDot(stringvalue, w, h, leadinwidth, linewidth==0? 1f: linewidth);
137 if (name.equals("otriangle"))
138 return stockTriangle(stringvalue, w, h, leadinwidth, linewidth==0? 1f: linewidth);
139 if (name.equals("osquare"))
140 return stockSquare(stringvalue, w, h, leadinwidth, linewidth==0? 1f: linewidth);
141 if (name.equals("odiamond"))
142 return stockDiamond(stringvalue, w, h, leadinwidth, linewidth==0? 1f: linewidth);
143 if (name.equals("ospear"))
144 return stockSpear(stringvalue, w, h, leadinwidth, linewidth==0? 1f: linewidth);
145 }
146 }
147 msg.err(
148 NAME
149 + ".create(String): invalid arrow spec, : "
150 + stringvalue
151 + "\n\t Expected format: type,height | type,height,width | type,height,width,linewidth | height,width,linewidth,leadwidth");
152 return (Arrow) table.get("normal");
153 }
154 private ArrowFactory() {}
155
156 ////////////////////////////////////////////////////////////////////////
157
158 /** Convert String representation of an attribute value to the
159 * appropriate object for storing in AttrTable.
160 */
161 public Object createObject(String str) {
162 return create(str);
163 }
164
165 public boolean isValid(Object a) {
166 return (a instanceof Arrow);
167 }
168
169 /** The String representation of an attribute value. */
170 public String toString(Object attr) {
171 Arrow a = (Arrow) attr;
172 return a.getName();
173 }
174
175 /** The String representation of attribute type itself. */
176 public String toString() {
177 return NAME;
178 }
179
180 /** Prompt user and present a user interface to obtain an
181 * attribute value from user. */
182 public Object promptUser(String prompt) {
183 return null;
184 }
185
186 // Stock arrow shapes //////////////////////////////////////////////////
187 //
188
189 /** These stock shapes are scaled and positioned to make drawing of
190 * arrows easier.
191 *
192 * . Origin (0,0) is always at the tail connection point of the
193 * arrow so rotation of the arrow should keep the tail connection
194 * point unchanged.
195 *
196 * . Each arrow is consist of two parts, the arrow shape and a
197 * lead-in shape. The bounding box for the whole arrow is alway
198 * a unit square.
199 *
200 * . Stroke line width and color of the arrow outline and the
201 * leadin line are independently, so the lead-in line width can
202 * be adjusted to match the incoming line width. There are 2
203 * parameters for each stock arrow template:
204 *
205 * lineWidth,leadinWidth
206 */
207
208 /** Line arrow with same line width for leadin and arrow head.*/
209 private static Arrow stockNormal(String name, float w, float h, float leadinwidth, float linewidth) {
210 GeneralPath p = new GeneralPath();
211 h *= H;
212 w *= H/2;
213 float hw = w / 2f;
214 float dw = linewidth / 2f;
215 p.moveTo(0, -hw);
216 p.lineTo(h - dw, 0);
217 p.lineTo(0, hw);
218 p.moveTo(h - dw, 0);
219 p.lineTo(0, 0);
220 Arrow ret = new Arrow(name, p, null, linewidth);
221 table.put(name, ret);
222 return ret;
223 }
224
225 /** A triangle inside a unit square.
226 */
227 private static Arrow stockTriangle(String name, float w, float h, float leadinwidth, float linewidth) {
228 GeneralPath p = new GeneralPath();
229 h *= H;
230 w *= H/2;
231 float dw = linewidth / 2f;
232 float hw = w / 2f;
233 p.moveTo(0,0);
234 p.moveTo(dw, -hw);
235 p.lineTo(h - dw, 0);
236 p.lineTo(dw, hw);
237 p.closePath();
238 Arrow ret = new Arrow(name, p, null, linewidth);
239 table.put(name, ret);
240 return ret;
241 }
242
243 /** A unit square. */
244 private static Arrow stockSquare(String name, float w, float h, float leadinwidth, float linewidth) {
245 GeneralPath p = new GeneralPath();
246 h *= H / SQRT2;
247 w *= H / SQRT2;
248 float hw = w / 2f;
249 float dw = linewidth / 2f;
250 p.moveTo(0,0);
251 p.moveTo(dw, -hw + dw);
252 p.lineTo(h - dw, -hw + dw);
253 p.lineTo(h - dw, hw - dw);
254 p.lineTo(dw, hw - dw);
255 p.lineTo(dw, -hw + dw);
256 Arrow ret = new Arrow(name, p, null, linewidth);
257 table.put(name, ret);
258 return ret;
259 }
260
261 /** A diamond shape. */
262 private static Arrow stockDiamond(String name, float w, float h, float leadinwidth, float linewidth) {
263 GeneralPath p = new GeneralPath();
264 h *= H;
265 w *= H;
266 float hw = w / 2f;
267 float hh = h / 2f;
268 float dw = linewidth / 2f;
269 p.moveTo(0,0);
270 p.moveTo(dw, 0);
271 p.lineTo(hw, -hh + dw);
272 p.lineTo(h - dw, 0);
273 p.lineTo(hw, hh - dw);
274 p.closePath();
275 /*
276 GeneralPath pin=new GeneralPath();
277 hw=leadinwidth/2f;
278 pin.moveTo(-hw,0f);
279 pin.lineTo(hw,0f);
280 pin.lineTo(hw,hw);
281 pin.lineTo(0f,0f);
282 pin.lineTo(-hw,hw);
283 pin.closePath();
284 */
285 Arrow ret = new Arrow(name, p, null, linewidth);
286 table.put(name, ret);
287 return ret;
288 }
289
290 /** A circle. */
291 private static Arrow stockDot(String name, float w, float h, float leadinwidth, float linewidth) {
292 // Approximate a cirle by cubic curves.
293 h *= H;
294 w *= H;
295 float dw = linewidth / 2f;
296 float hw = w / 2f;
297 Ellipse2D p = new Ellipse2D.Float(dw, -hw + dw, w - linewidth, h - linewidth);
298 /*
299 GeneralPath p=new GeneralPath();
300 float hw=w/2;
301 float hh=h/2;
302 float l=linewidth;
303 float dw=linewidth/2;
304 //FIXME: Offsets are not quite right.
305 p.moveTo(hw-dw,hh);
306 p.curveTo(hw-dw,hh+0.27614236f*(h-l), 0.2761424f*(w-l),h-dw, 0f,h-dw);
307 p.curveTo(-0.2761424f*(w-l),h-dw, -hw+dw,hh+0.27614236f*(h-dw), -hw+dw,hh);
308 p.curveTo(-hw+dw,dw+0.22385763f*(h-l), -0.2761424f*(w-l),dw, 0.0f,dw);
309 p.curveTo(0.2761424f*(w-l),dw, hw-dw,dw+0.22385763f*(h-l), hw-dw,hh);
310 p.closePath();
311 */
312 Arrow ret = new Arrow(name, p,
313 //FIXME: leadin is arc.
314 null, linewidth);
315 table.put(name, ret);
316 return ret;
317 }
318
319 /** An spear shape stock. */
320 private static Arrow stockSpear(String name, float w, float h, float leadinwidth, float linewidth) {
321 GeneralPath p = new GeneralPath();
322 h *= H;
323 w *= H/2;
324 float hw = w / 2f;
325 float dw = linewidth / 2;
326 p.moveTo(0,0);
327 p.moveTo(0, -hw);
328 p.lineTo(h - dw, 0);
329 p.lineTo(0, hw);
330 p.lineTo(h / 4f, 0);
331 p.closePath();
332 GeneralPath pin = new GeneralPath();
333 hw = leadinwidth / 2f;
334 pin.moveTo(0, -hw);
335 pin.lineTo(h / 4f - (hw / 2f), -hw);
336 pin.lineTo(h / 4f, 0);
337 pin.lineTo(h / 4f - (hw / 2f), hw);
338 pin.lineTo(0, hw);
339 pin.closePath();
340 Arrow ret = new Arrow(name, p, pin, linewidth);
341 table.put(name, ret);
342 return ret;
343 }
344
345 /** Arrow by line only. */
346 private static Arrow stockLine(String name, float w, float h, float leadinwidth, float linewidth) {
347 if (linewidth <= 0)
348 linewidth = 1f;
349 GeneralPath p = new GeneralPath();
350 h *= H;
351 w *= H/2;
352 float hh = h / 2f;
353 float hw = w / 2f;
354 float dw = linewidth / 2f;
355 p.moveTo(0,0);
356 p.moveTo(hh - dw, -hw);
357 p.lineTo(h - dw, 0);
358 p.lineTo(hh - dw, hw);
359 GeneralPath pin = new GeneralPath();
360 hw = leadinwidth / 2f;
361 pin.moveTo(0, -hw);
362 pin.lineTo(h - dw - hw, -hw);
363 pin.lineTo(h - dw, 0);
364 pin.lineTo(h - dw - hw, hw);
365 pin.lineTo(0, hw);
366 pin.closePath();
367 Arrow ret = new Arrow(name, p, pin, linewidth);
368 table.put(name, ret);
369 return ret;
370 }
371
372 /** Double arrow by line only. */
373 private static Arrow stockDoubleLine(String name, float w, float h, float leadinwidth, float linewidth) {
374 if (linewidth <= 0)
375 linewidth = 1f;
376 GeneralPath p = new GeneralPath();
377 h *= H;
378 w *= H/2;
379 float hw = w / 2f;
380 float hh = h / 2f;
381 float dw = linewidth / 2f;
382 p.moveTo(0,0);
383 p.moveTo(hh - dw, -hw);
384 p.lineTo(h - dw, 0);
385 p.lineTo(hh - dw, hw);
386 p.moveTo(0, hw);
387 p.lineTo(hh - dw, 0);
388 p.lineTo(0, -hw);
389 GeneralPath pin = new GeneralPath();
390 hw = leadinwidth / 2f;
391 pin.moveTo(0, -hw);
392 pin.lineTo(hh - dw - hw, -hw);
393 pin.lineTo(hh - dw, 0);
394 pin.lineTo(hh - dw - hw, hw);
395 pin.lineTo(0, hw);
396 pin.closePath();
397 Arrow ret = new Arrow(name, p, pin, linewidth);
398 table.put(name, ret);
399 return ret;
400 }
401
402 ////////////////////////////////////////////////////////////////////////
403 }