001: /*************************************************************************
002: * *
003: * 1) This source code file, in unmodified form, and compiled classes *
004: * derived from it can be used and distributed without restriction, *
005: * including for commercial use. (Attribution is not required *
006: * but is appreciated.) *
007: * *
008: * 2) Modified versions of this file can be made and distributed *
009: * provided: the modified versions are put into a Java package *
010: * different from the original package, edu.hws; modified *
011: * versions are distributed under the same terms as the original; *
012: * and the modifications are documented in comments. (Modification *
013: * here does not include simply making subclasses that belong to *
014: * a package other than edu.hws, which can be done without any *
015: * restriction.) *
016: * *
017: * David J. Eck *
018: * Department of Mathematics and Computer Science *
019: * Hobart and William Smith Colleges *
020: * Geneva, New York 14456, USA *
021: * Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
022: * *
023: *************************************************************************/package edu.hws.jcm.draw;
024:
025: import edu.hws.jcm.data.*;
026: import edu.hws.jcm.awt.*;
027: import java.awt.*;
028:
029: /**
030: * A DrawGeometric object is a geometic figure such as a line or rectangle that can
031: * be drawn in a CoordinateRect. The data for the object always consists of four
032: * numbers, which are interpreted differenetly depending on the object. These numbers
033: * can be specified as Value objects. A DrawGeometric is a Computable, and the
034: * Values will be re-computed when its compute() method is called. It should be
035: * added to a Controller that can respond to any changes in the data that define
036: * the Values. If one of the Value objects has an undefined value, nothing will be drawn.
037: * <p>The type of object is given as one of the constants defined in this class:
038: * LINE_ABSOLUTE, OVAL_RELATIVE, CROSS, and so on. In the descriptions of these
039: * constants, x1, x2, y1, and y2 refer to the values of Value objects that provide data
040: * for the DrawGeomentric while h and v refer to int's that can be specified in place of
041: * x2 and y2 for certain types of figures. For those figures, h or v is used if
042: * x2 or y2, respectively, is null.
043: *
044: * @author David Eck
045: */
046:
047: public class DrawGeometric extends Drawable implements Computable {
048:
049: /**
050: * Specifies a line segment from (x1,y1) to (x2,y2).
051: */
052: public static final int LINE_ABSOLUTE = 0;
053:
054: /**
055: * Specifies a line that extends through the points (x1,y1) and (x2,y2) and beyond.
056: */
057: public static final int INFINITE_LINE_ABSOLUTE = 1;
058:
059: /**
060: * Specifies a rectangle with corners at (x1,y1) and (x2,y2).
061: */
062: public static final int RECT_ABSOLUTE = 2;
063:
064: /**
065: * Specifies an oval that just fits in the rectangle with corners at (x1,y1) and (x2,y2).
066: */
067: public static final int OVAL_ABSOLUTE = 3;
068:
069: /**
070: * Specifies a line segment from (x1,y1) to (x1+x2,y1+y2), or to (x1+h,y1+v) if x2,y2 are null.
071: * (Note that h,v are given in terms of pixels while x1,x2,y1,y2 are given
072: * in terms of the CoordinateRect. If you use h,v, you get a line
073: * of a fixed size and direction.)
074: */
075: public static final int LINE_RELATIVE = 4;
076:
077: /**
078: * Specifies an infinite line through (x1,y1) and (x1+x2,y1+y2), or through (x1,y1) and (x1+h,y1+v) if x2,y2 are null.
079: */
080: public static final int INFINITE_LINE_RELATIVE = 5;
081:
082: /**
083: * Specifies a rectangle with one corner at (x1,y1), and with width given by x2, or h if
084: * if x2 is null, and with height given by y2, or by v if y2 is null.
085: */
086: public static final int RECT_RELATIVE = 6;
087:
088: /**
089: * Specifies an oval that just fits inside the rect specified by RECT_RELATIVE.
090: */
091: public static final int OVAL_RELATIVE = 7;
092:
093: /**
094: * Specifies a line segment centered on (x1,y1). The amount it extends in each direction
095: * is given by x2,y2 or by h,v
096: */
097: public static final int LINE_CENTERED = 8;
098:
099: /**
100: * Specifies a Rectangle centered on (x1,y1). The amount it extends in each direction
101: * is given by x2,y2 or by h,v. (Thus, x2 or h is the HALF-width and y2 or v is the HALF-height.)
102: */
103: public static final int RECT_CENTERED = 9;
104:
105: /**
106: * Specifies an oval that just fits inside the rect specified by RECT_CENTERED.
107: */
108: public static final int OVAL_CENTERED = 10;
109:
110: /**
111: * Specifies a cross centered on the point (x1,y1). Its arms extend horizontally
112: * by a distance of x2, or h, in each direction. Its vertical
113: * arms extend y2, or v, in each direction.
114: */
115: public static final int CROSS = 11;
116:
117: /**
118: * One of the constants such as OVAL_CENTERED, specifying the shape to be drawn
119: */
120: protected int shape;
121:
122: /**
123: * One of the Value objects that determine the shape that is drawn.
124: * The shape is specified by two points, (x1,y1) and (x2,y2).
125: * x1 must be non-null.
126: */
127: protected Value x1;
128:
129: /**
130: * One of the Value objects that determine the shape that is drawn.
131: * The shape is specified by two points, (x1,y1) and (x2,y2).
132: * x2 must be non-null
133: * for the "ABSOLUTE" shapes. (If not, they revert to
134: * "RELATIVE" shapes and use h,v as the offset values.)
135: */
136: protected Value x2;
137:
138: /**
139: * One of the Value objects that determine the shape that is drawn.
140: * The shape is specified by two points, (x1,y1) and (x2,y2).
141: * y1 must be non-null.
142: */
143: protected Value y1;
144:
145: /**
146: * One of the Value objects that determine the shape that is drawn.
147: * The shape is specified by two points, (x1,y1) and (x2,y2).
148: * y2 must be non-null
149: * for the "ABSOLUTE" shapes. (If not, they revert to
150: * "RELATIVE" shapes and use h,v as the offset values.)
151: */
152: protected Value y2;
153:
154: /**
155: * Integer that gives horizontal pixel offset from x1.
156: * This is only used if x2 is null.
157: */
158: protected int h = 10;
159:
160: /**
161: * Integer that gives vertical pixel offset fromy1.
162: * This is only used if y2 is null.
163: */
164: protected int v = 10;
165:
166: /**
167: * Value of x1. This is re-computed when the compute() method is called.
168: */
169: protected double a = Double.NaN;
170:
171: /**
172: * Value of y1. This is re-computed when the compute() method is called.
173: */
174: protected double b;
175:
176: /**
177: * Value of x2. This is re-computed when the compute() method is called.
178: */
179: protected double c;
180:
181: /**
182: * Value of y2. This is re-computed when the compute() method is called.
183: */
184: protected double d;
185:
186: /**
187: * Color of the shappe. Color will be black if this is null. For shapes that
188: * have "insides", such as rects, this is the color of the outline.
189: */
190: protected Color color = Color.black;
191:
192: /**
193: * Rects and ovals are filled with this color, if it is non-null.
194: * If this is null, only the outline of the shape is drawn.
195: */
196: protected Color fillColor;
197:
198: /**
199: * The width, in pixels, of lines, including the outlines
200: * of rects and ovals. It is restricted to being an integer
201: * in the range from 0 to 10. A value of 0 means that lines
202: * won't be drawn at all; this would only be useful for a filled
203: * shape that has a colored interior.
204: */
205: protected int lineWidth = 1;
206:
207: private boolean changed = true; // set to true when values have to be recomputed.
208:
209: /**
210: * Create a DrawGeometric object. By default, it is a LINE_ABSOLUTE. However,
211: * nothing will be drawn as long as x1,y1,x2,y2 are null.
212: */
213: public DrawGeometric() {
214: }
215:
216: /**
217: * Create a DrawGeometric with the specified shape and values for x1,x2,y1,y2
218: * Any of the shapes makes sense in this context.
219: *
220: * @param shape One of the shape constants such as LINE_ABSOLUTE or RECT_RELATIVE.
221: */
222: public DrawGeometric(int shape, Value x1, Value y1, Value x2,
223: Value y2) {
224: setShape(shape);
225: setPoints(x1, y1, x2, y2);
226: }
227:
228: /**
229: * Create a DrawGeometric with a specified shape and values. The last two parameters
230: * give pixel offsets from x1,y1. The "ABSOLUTE" shapes don't make
231: * sense in this context. (They will be treated as the corresponding
232: * "RELATIVE" shapes.)
233: *
234: * @param shape One of the "RELATIVE" or "CENTERED" shape constants such as LINE_RELATIVE or OVAL_CENTERED or CROSS.
235: */
236: public DrawGeometric(int shape, Value x1, Value y1, int h, int v) {
237: setShape(shape);
238: setPoints(x1, y1, h, v);
239: }
240:
241: // ---------------- Routines for getting and setting properties --------------------------
242:
243: /**
244: * Set the shape, which should be given as one of the shape constants such as LINE_ABSOLUTE or CROSS.
245: */
246: public void setShape(int shape) {
247: if (shape < 0 || shape > CROSS)
248: throw new IllegalArgumentException(
249: "Internal error: Illegal value for shape of DrawGeometric object.");
250: this .shape = shape;
251: needsRedraw();
252: }
253:
254: /**
255: * Set the Value objects that specify the two points that determine the shape.
256: * The first two parameters, x1 and y1, must be non-null.
257: */
258: public void setPoints(Value x1, Value y1, Value x2, Value y2) {
259: this .x1 = x1;
260: this .y1 = y1;
261: this .x2 = x2;
262: this .y2 = y2;
263: compute();
264: }
265:
266: /**
267: * Set the values that specify a point (x1,y1) and an offset (h,v) from that point.
268: * This only makes sense for RELATIVE shapes. The Value objects x1 and y1 must be non-null
269: */
270: public void setPoints(Value x1, Value y1, int h, int v) {
271: this .x1 = x1;
272: this .y1 = y1;
273: this .x2 = null;
274: this .y2 = null;
275: this .h = h;
276: this .v = v;
277: compute();
278: }
279:
280: /**
281: * Set the value that gives the x-coordinate of the first point that determines the shape.
282: * This must be non-null, or nothing will be drawn.
283: */
284: public void setX1(Value x) {
285: x1 = x;
286: compute();
287: }
288:
289: /**
290: * Get the value that gives the x-coordinate of the first point that determines the shape.
291: */
292: public Value getX1() {
293: return x1;
294: }
295:
296: /**
297: * Set the value that gives the x-coordinate of the second point that determines the shape.
298: * If this is null, then the value of h is used instead.
299: */
300: public void setX2(Value x) {
301: x2 = x;
302: compute();
303: }
304:
305: /**
306: * Get the value that gives the x-coordinate of the second point that determines the shape.
307: */
308: public Value getX2() {
309: return x2;
310: }
311:
312: /**
313: * Set the value that gives the y-coordinate of the first point that determines the shape.
314: * This must be non-null, or nothing will be drawn.
315: */
316: public void setY1(Value y) {
317: y1 = y;
318: compute();
319: }
320:
321: /**
322: * Get the value that gives the y-coordinate of the first point that determines the shape.
323: */
324: public Value getY1() {
325: return y1;
326: }
327:
328: /**
329: * Set the value that gives the y-coordinate of the second point that determines the shape.
330: * If this is null, then the value of v is used instead.
331: */
332: public void setY2(Value y) {
333: y2 = y;
334: compute();
335: }
336:
337: /**
338: * Get the value that gives the y-coordinate of the second point that determines the shape.
339: */
340: public Value getY2() {
341: return y2;
342: }
343:
344: /**
345: * Set the integer that gives the horizontal offset from (x1,y1).
346: * This only makes sense for RELATIVE shapes. This method also sets x2 to null,
347: * since the h value is only used when x2 is null.
348: */
349: public void setH(int x) {
350: h = x;
351: x2 = null;
352: compute();
353: }
354:
355: /**
356: * Get the horizontal offset from (x1,y1).
357: */
358: public int getH() {
359: return h;
360: }
361:
362: /**
363: * Set the integer that gives the vertical offset from (x1,y1).
364: * This only makes sense for RELATIVE shapes. This method also sets y2 to null,
365: * since the v value is only used when y2 is null.
366: */
367: public void setV(int y) {
368: v = y;
369: y2 = null;
370: needsRedraw();
371: }
372:
373: /**
374: * Get the vertical offset from (x1,y1).
375: */
376: public int getV() {
377: return v;
378: }
379:
380: /**
381: * Set the color that is used for drawing the shape. If the color is null, black is used.
382: * For shapes that have interiors, such as rects, this is only the color of the outline of the shaape.
383: */
384: public void setColor(Color c) {
385: color = (c == null) ? Color.black : c;
386: needsRedraw();
387: }
388:
389: /**
390: * Get the non-null color that is used for drawing the shape.
391: */
392: public Color getColor() {
393: return color;
394: }
395:
396: /**
397: * Set the color that is used for filling ovals and rects. If the color is null, only the outline of the shape is drawn.
398: */
399: public void setFillColor(Color c) {
400: fillColor = c;
401: needsRedraw();
402: }
403:
404: /**
405: * Get the color that is used for filling ovals and rects. If null, no fill is done.
406: */
407: public Color getFillColor() {
408: return fillColor;
409: }
410:
411: /**
412: * Set the width, in pixels, of lines that are drawn. This is also used for outlines of rects and ovals.
413: */
414: public void setLineWidth(int width) {
415: if (width != lineWidth) {
416: lineWidth = width;
417: if (lineWidth > 10)
418: lineWidth = 10;
419: else if (lineWidth < 0)
420: lineWidth = 0;
421: needsRedraw();
422: }
423: }
424:
425: /**
426: * Get the width, in pixels, of lines that are drawn. This is also used for outlines of rects and ovals.
427: */
428: public int getLineWidth() {
429: return lineWidth;
430: }
431:
432: // ------------------------- Implementation details ------------------------------------
433:
434: /**
435: * Recompute the values that define the size/postion of the DrawGeometric.
436: * This is ordinarily only called by a Controller.
437: */
438: public void compute() {
439: changed = true;
440: needsRedraw();
441: }
442:
443: private void doValues() {
444: if (x1 != null)
445: a = x1.getVal();
446: if (y1 != null)
447: b = y1.getVal();
448: if (x2 != null)
449: c = x2.getVal();
450: if (y2 != null)
451: d = y2.getVal();
452: changed = false;
453: }
454:
455: /**
456: * Do the drawing. This is not meant to be called directly.
457: */
458: public void draw(Graphics g, boolean coordsChanged) {
459: if (changed)
460: doValues();
461: if (coords == null || x1 == null || y1 == null
462: || Double.isNaN(a) || Double.isNaN(b)
463: || Double.isInfinite(a) || Double.isInfinite(b))
464: return;
465: if (x2 != null && (Double.isNaN(c) || Double.isInfinite(c)))
466: return;
467: if (y2 != null && (Double.isNaN(d) || Double.isInfinite(d)))
468: return;
469:
470: // Get the four real numbers that determine the shape, in terms of pixels.
471:
472: double A, B, W, H;
473:
474: A = xToPixelDouble(a);
475: B = yToPixelDouble(b);
476: if (x2 == null)
477: W = h;
478: else if (shape <= OVAL_ABSOLUTE)
479: W = xToPixelDouble(c) - A;
480: else
481: W = c / coords.getPixelWidth();
482: if (y2 == null)
483: H = -v;
484: else if (shape <= OVAL_ABSOLUTE)
485: H = yToPixelDouble(d) - B;
486: else
487: H = -d / coords.getPixelHeight();
488:
489: if (shape == INFINITE_LINE_ABSOLUTE
490: || shape == INFINITE_LINE_RELATIVE)
491: drawInfiniteLine(g, A, B, W, H);
492: else if (shape == CROSS)
493: drawCross(g, (int) A, (int) B, (int) (Math.abs(W) + 0.5),
494: (int) (Math.abs(H) + 0.5));
495: else if (shape == LINE_RELATIVE || shape == LINE_ABSOLUTE)
496: drawLine(g, (int) A, (int) B, (int) (A + W), (int) (B + H));
497: else if (shape == LINE_CENTERED)
498: drawLine(g, (int) (A - Math.abs(W) + 1), (int) (B
499: - Math.abs(H) + 1), (int) (A + Math.abs(W)),
500: (int) (B + Math.abs(H)));
501: else if (shape <= OVAL_RELATIVE) {
502: if (W < 0) {
503: W = -W;
504: A = A - W;
505: }
506: if (H < 0) {
507: H = -H;
508: B = B - H;
509: }
510: drawShape(g, (int) A, (int) B, (int) (W + 0.5),
511: (int) (H + 0.5));
512: } else
513: drawShape(g, (int) (A - Math.abs(W) + 1), (int) (B
514: - Math.abs(H) + 1), (int) (2 * Math.abs(W) - 0.5),
515: (int) (2 * Math.abs(H) - 0.5));
516:
517: }
518:
519: private double xToPixelDouble(double x) {
520: return coords.getLeft()
521: + coords.getGap()
522: + ((x - coords.getXmin())
523: / (coords.getXmax() - coords.getXmin()) * (coords
524: .getWidth()
525: - 2 * coords.getGap() - 1));
526: }
527:
528: private double yToPixelDouble(double y) {
529: return coords.getTop()
530: + coords.getGap()
531: + ((coords.getYmax() - y)
532: / (coords.getYmax() - coords.getYmin()) * (coords
533: .getHeight()
534: - 2 * coords.getGap() - 1));
535: }
536:
537: private void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
538: int width = Math.abs(x2 - x1);
539: int height = Math.abs(y2 - y1);
540: g.setColor(color);
541: if (width == 0 && height == 0)
542: g.drawLine(x1, y1, x1, y1);
543: else if (width > height) {
544: for (int i = 0; i < lineWidth; i++)
545: g.drawLine(x1, y1 - lineWidth / 2 + i, x2, y2
546: - lineWidth / 2 + i);
547: } else {
548: for (int i = 0; i < lineWidth; i++)
549: g.drawLine(x1 - lineWidth / 2 + i, y1, x2 - lineWidth
550: / 2 + i, y2);
551: }
552: }
553:
554: /**
555: * Draws a rect or oval.
556: *
557: * @param x the top-left x value of the rect or the rect that contains the oval
558: * @param y the top-left y value of the rect or the rect that contains the oval
559: * @param width width of the rect
560: * @param height height of the rect
561: */
562: private void drawShape(Graphics g, int x, int y, int width,
563: int height) {
564: if (x > coords.getLeft() + coords.getWidth()
565: || y > coords.getTop() + coords.getHeight()
566: || x + width < coords.getLeft()
567: || y + height < coords.getTop()) {
568: return;
569: }
570: if (fillColor != null) {
571: g.setColor(fillColor);
572: if (shape == RECT_ABSOLUTE || shape == RECT_RELATIVE
573: || shape == RECT_CENTERED)
574: g.fillRect(x, y, width, height);
575: else
576: g.fillOval(x, y, width, height);
577: }
578: g.setColor(color);
579: if (shape == RECT_ABSOLUTE || shape == RECT_RELATIVE
580: || shape == RECT_CENTERED) {
581: for (int i = 0; i < lineWidth; i++)
582: g.drawRect(x + i, y + i, width - 2 * i, height - 2 * i);
583: } else {
584: for (int i = 0; i < lineWidth; i++)
585: g.drawOval(x + i, y + i, width - 2 * i, height - 2 * i);
586: }
587: }
588:
589: private void drawCross(Graphics g, int x, int y, int width,
590: int height) {
591: if (x - width > coords.getLeft() + coords.getWidth()
592: || y - height > coords.getTop() + coords.getHeight()
593: || x + width < coords.getLeft()
594: || y + height < coords.getTop()) {
595: return;
596: }
597: int left = x - lineWidth / 2;
598: int top = y - lineWidth / 2;
599: g.setColor(color);
600: for (int i = 0; i < lineWidth; i++)
601: g.drawLine(x - width, top + i, x + width, top + i);
602: for (int i = 0; i < lineWidth; i++)
603: g.drawLine(left + i, y - height, left + i, y + height);
604: }
605:
606: private void drawInfiniteLine(Graphics g, double x, double y,
607: double dx, double dy) {
608: if (Math.abs(dx) < 1e-10 && Math.abs(dy) < 1e-10)
609: return;
610: g.setColor(color);
611: if (Math.abs(dy) > Math.abs(dx)) {
612: double islope = dx / dy;
613: int y1 = coords.getTop() - 5;
614: int y2 = coords.getTop() + coords.getHeight() + 5;
615: int x1 = (int) (islope * (y1 - y) + x);
616: int x2 = (int) (islope * (y2 - y) + x);
617: if (Math.abs(x1) < 20000 && Math.abs(x2) < 20000)
618: for (int i = 0; i < lineWidth; i++)
619: g.drawLine(x1 - lineWidth / 2 + i, y1, x2
620: - lineWidth / 2 + i, y2);
621: } else {
622: double slope = dy / dx;
623: int x1 = coords.getLeft() - 5;
624: int x2 = coords.getLeft() + coords.getWidth() + 5;
625: int y1 = (int) (slope * (x1 - x) + y);
626: int y2 = (int) (slope * (x2 - x) + y);
627: if (Math.abs(y1) < 20000 && Math.abs(y2) < 20000)
628: for (int i = 0; i < lineWidth; i++)
629: g.drawLine(x1, y1 - lineWidth / 2 + i, x2, y2
630: - lineWidth / 2 + i);
631: }
632: }
633:
634: } // end class DrawGeometric
|