001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jfreechart/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * -----------------------
028: * DialValueIndicator.java
029: * -----------------------
030: * (C) Copyright 2006, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: DialValueIndicator.java,v 1.1.2.3 2006/11/07 16:11:12 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 03-Nov-2006 : Version 1 (DG);
040: *
041: */
042:
043: package org.jfree.experimental.chart.plot.dial;
044:
045: import java.awt.BasicStroke;
046: import java.awt.Color;
047: import java.awt.Font;
048: import java.awt.FontMetrics;
049: import java.awt.Graphics2D;
050: import java.awt.Paint;
051: import java.awt.Stroke;
052: import java.awt.geom.Arc2D;
053: import java.awt.geom.Point2D;
054: import java.awt.geom.Rectangle2D;
055: import java.io.IOException;
056: import java.io.ObjectInputStream;
057: import java.io.ObjectOutputStream;
058: import java.io.Serializable;
059: import java.text.DecimalFormat;
060: import java.text.NumberFormat;
061:
062: import org.jfree.chart.HashUtilities;
063: import org.jfree.io.SerialUtilities;
064: import org.jfree.text.TextUtilities;
065: import org.jfree.ui.RectangleAnchor;
066: import org.jfree.ui.RectangleInsets;
067: import org.jfree.ui.Size2D;
068: import org.jfree.ui.TextAnchor;
069: import org.jfree.util.PaintUtilities;
070: import org.jfree.util.PublicCloneable;
071:
072: /**
073: * A value indicator for a {@link DialPlot}.
074: */
075: public class DialValueIndicator extends AbstractDialLayer implements
076: DialLayer, Cloneable, PublicCloneable, Serializable {
077:
078: /** The dataset index. */
079: private int datasetIndex;
080:
081: /** The angle that defines the anchor point. */
082: private double angle;
083:
084: /** The radius that defines the anchor point. */
085: private double radius;
086:
087: /** The frame anchor. */
088: private RectangleAnchor frameAnchor;
089:
090: /** The template value. */
091: private Number templateValue;
092:
093: /** The formatter. */
094: private NumberFormat formatter;
095:
096: /** The font. */
097: private Font font;
098:
099: /** The paint. */
100: private transient Paint paint;
101:
102: /** The background paint. */
103: private transient Paint backgroundPaint;
104:
105: /** The outline stroke. */
106: private transient Stroke outlineStroke;
107:
108: /** The outline paint. */
109: private transient Paint outlinePaint;
110:
111: /** The insets. */
112: private RectangleInsets insets;
113:
114: /** The value anchor. */
115: private RectangleAnchor valueAnchor;
116:
117: /** The text anchor for displaying the value. */
118: private TextAnchor textAnchor;
119:
120: /**
121: * Creates a new instance of <code>DialValueIndicator</code>.
122: *
123: * @param datasetIndex the dataset index.
124: * @param label the label.
125: */
126: public DialValueIndicator(int datasetIndex, String label) {
127: this .datasetIndex = datasetIndex;
128: this .angle = -90.0;
129: this .radius = 0.3;
130: this .frameAnchor = RectangleAnchor.CENTER;
131: this .templateValue = new Double(100.0);
132: this .formatter = new DecimalFormat("0.0");
133: this .font = new Font("Dialog", Font.BOLD, 14);
134: this .paint = Color.black;
135: this .backgroundPaint = Color.white;
136: this .outlineStroke = new BasicStroke(1.0f);
137: this .outlinePaint = Color.blue;
138: this .insets = new RectangleInsets(4, 4, 4, 4);
139: this .valueAnchor = RectangleAnchor.RIGHT;
140: this .textAnchor = TextAnchor.CENTER_RIGHT;
141: }
142:
143: /**
144: * Returns the index of the dataset from which this indicator fetches its
145: * current value.
146: *
147: * @return The dataset index.
148: */
149: public int getDatasetIndex() {
150: return this .datasetIndex;
151: }
152:
153: /**
154: * Sets the dataset index.
155: *
156: * @param index the index.
157: */
158: public void setDatasetIndex(int index) {
159: this .datasetIndex = index;
160: }
161:
162: /**
163: * Returns the angle for the anchor point. The angle is specified in
164: * degrees using the same orientation as Java's <code>Arc2D</code> class.
165: *
166: * @return The angle (in degrees).
167: */
168: public double getAngle() {
169: return this .angle;
170: }
171:
172: /**
173: * Sets the angle for the anchor point and sends a
174: * {@link DialLayerChangeEvent} to all registered listeners.
175: *
176: * @param angle the angle (in degrees).
177: */
178: public void setAngle(double angle) {
179: this .angle = angle;
180: notifyListeners(new DialLayerChangeEvent(this ));
181: }
182:
183: /**
184: * Returns the radius.
185: *
186: * @return The radius.
187: */
188: public double getRadius() {
189: return this .radius;
190: }
191:
192: /**
193: * Sets the radius.
194: *
195: * @param radius the radius.
196: */
197: public void setRadius(double radius) {
198: // TODO: validation
199: this .radius = radius;
200: notifyListeners(new DialLayerChangeEvent(this ));
201: }
202:
203: /**
204: * Returns the frame anchor.
205: *
206: * @return The frame anchor.
207: */
208: public RectangleAnchor getFrameAnchor() {
209: return this .frameAnchor;
210: }
211:
212: /**
213: * Sets the frame anchor and sends a {@link DialLayerChangeEvent} to all
214: * registered listeners.
215: *
216: * @param anchor the anchor (<code>null</code> not permitted).
217: */
218: public void setFrameAnchor(RectangleAnchor anchor) {
219: if (anchor == null) {
220: throw new IllegalArgumentException(
221: "Null 'anchor' argument.");
222: }
223: this .frameAnchor = anchor;
224: notifyListeners(new DialLayerChangeEvent(this ));
225: }
226:
227: /**
228: * Returns the template value.
229: *
230: * @return The template value.
231: */
232: public Number getTemplateValue() {
233: return this .templateValue;
234: }
235:
236: /**
237: * Sets the template value and sends a {@link DialLayerChangeEvent} to
238: * all registered listeners.
239: *
240: * @param value the value (<code>null</code> not permitted).
241: */
242: public void setTemplateValue(Number value) {
243: this .templateValue = value;
244: notifyListeners(new DialLayerChangeEvent(this ));
245: }
246:
247: /**
248: * Returns the formatter used to format the value.
249: *
250: * @return The formatter (never <code>null</code>).
251: */
252: public NumberFormat getNumberFormat() {
253: return this .formatter;
254: }
255:
256: /**
257: * Sets the formatter used to format the value and sends a
258: * {@link DialLayerChangeEvent} to all registered listeners.
259: *
260: * @param formatter the formatter (<code>null</code> not permitted).
261: */
262: public void setNumberFormat(NumberFormat formatter) {
263: if (formatter == null) {
264: throw new IllegalArgumentException(
265: "Null 'formatter' argument.");
266: }
267: this .formatter = formatter;
268: notifyListeners(new DialLayerChangeEvent(this ));
269: }
270:
271: /**
272: * Returns the font.
273: *
274: * @return The font (never <code>null</code>).
275: */
276: public Font getFont() {
277: return this .font;
278: }
279:
280: /**
281: * Sets the font and sends a {@link DialLayerChangeEvent} to all registered
282: * listeners.
283: *
284: * @param font the font (<code>null</code> not permitted).
285: */
286: public void setFont(Font font) {
287: if (font == null) {
288: throw new IllegalArgumentException("Null 'font' argument.");
289: }
290: this .font = font;
291: notifyListeners(new DialLayerChangeEvent(this ));
292: }
293:
294: /**
295: * Returns the paint.
296: *
297: * @return The paint (never <code>null</code>).
298: */
299: public Paint getPaint() {
300: return this .paint;
301: }
302:
303: /**
304: * Sets the paint and sends a {@link DialLayerChangeEvent} to all
305: * registered listeners.
306: *
307: * @param paint the paint (<code>null</code> not permitted).
308: */
309: public void setPaint(Paint paint) {
310: if (paint == null) {
311: throw new IllegalArgumentException("Null 'paint' argument.");
312: }
313: this .paint = paint;
314: notifyListeners(new DialLayerChangeEvent(this ));
315: }
316:
317: /**
318: * Returns the background paint.
319: *
320: * @return The background paint.
321: */
322: public Paint getBackgroundPaint() {
323: return this .backgroundPaint;
324: }
325:
326: /**
327: * Sets the background paint.
328: *
329: * @param paint the paint (<code>null</code> not permitted).
330: */
331: public void setBackgroundPaint(Paint paint) {
332: if (paint == null) {
333: throw new IllegalArgumentException("Null 'paint' argument.");
334: }
335: this .backgroundPaint = paint;
336: notifyListeners(new DialLayerChangeEvent(this ));
337: }
338:
339: /**
340: * Returns the outline stroke.
341: *
342: * @return The outline stroke.
343: */
344: public Stroke getOutlineStroke() {
345: return this .outlineStroke;
346: }
347:
348: /**
349: * Sets the outline stroke.
350: *
351: * @param stroke the stroke (<code>null</code> not permitted).
352: */
353: public void setOutlineStroke(Stroke stroke) {
354: if (stroke == null) {
355: throw new IllegalArgumentException(
356: "Null 'stroke' argument.");
357: }
358: this .outlineStroke = stroke;
359: notifyListeners(new DialLayerChangeEvent(this ));
360: }
361:
362: /**
363: * Returns the outline paint.
364: *
365: * @return The outline paint.
366: */
367: public Paint getOutlinePaint() {
368: return this .outlinePaint;
369: }
370:
371: /**
372: * Sets the outline paint and sends a {@link DialLayerChangeEvent} to all
373: * registered listeners.
374: *
375: * @param paint the paint.
376: */
377: public void setOutlinePaint(Paint paint) {
378: if (paint == null) {
379: throw new IllegalArgumentException("Null 'paint' argument.");
380: }
381: this .outlinePaint = paint;
382: notifyListeners(new DialLayerChangeEvent(this ));
383: }
384:
385: /**
386: * Returns the insets.
387: *
388: * @return The insets (never <code>null</code>).
389: */
390: public RectangleInsets getInsets() {
391: return this .insets;
392: }
393:
394: /**
395: * Sets the insets.
396: *
397: * @param insets the insets (<code>null</code> not permitted).
398: */
399: public void setInsets(RectangleInsets insets) {
400: if (insets == null) {
401: throw new IllegalArgumentException(
402: "Null 'insets' argument.");
403: }
404: this .insets = insets;
405: notifyListeners(new DialLayerChangeEvent(this ));
406: }
407:
408: /**
409: * Returns the value anchor.
410: *
411: * @return The value anchor.
412: */
413: public RectangleAnchor getValueAnchor() {
414: return this .valueAnchor;
415: }
416:
417: /**
418: * Sets the value anchor.
419: *
420: * @param anchor the anchor (<code>null</code> not permitted).
421: */
422: public void setValueAnchor(RectangleAnchor anchor) {
423: if (anchor == null) {
424: throw new IllegalArgumentException(
425: "Null 'anchor' argument.");
426: }
427: this .valueAnchor = anchor;
428: notifyListeners(new DialLayerChangeEvent(this ));
429: }
430:
431: /**
432: * Returns the text anchor.
433: *
434: * @return The text anchor.
435: */
436: public TextAnchor getTextAnchor() {
437: return this .textAnchor;
438: }
439:
440: /**
441: * Sets the text anchor.
442: *
443: * @param anchor the anchor (<code>null</code> not permitted).
444: */
445: public void setTextAnchor(TextAnchor anchor) {
446: if (anchor == null) {
447: throw new IllegalArgumentException(
448: "Null 'anchor' argument.");
449: }
450: this .textAnchor = anchor;
451: notifyListeners(new DialLayerChangeEvent(this ));
452: }
453:
454: /**
455: * Returns <code>true</code> to indicate that this layer should be
456: * clipped within the dial window.
457: *
458: * @return <code>true</code>.
459: */
460: public boolean isClippedToWindow() {
461: return true;
462: }
463:
464: /**
465: * Draws the background to the specified graphics device. If the dial
466: * frame specifies a window, the clipping region will already have been
467: * set to this window before this method is called.
468: *
469: * @param g2 the graphics device (<code>null</code> not permitted).
470: * @param plot the plot (ignored here).
471: * @param frame the dial frame (ignored here).
472: * @param view the view rectangle (<code>null</code> not permitted).
473: */
474: public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
475: Rectangle2D view) {
476:
477: // work out the anchor point
478: Rectangle2D f = DialPlot.rectangleByRadius(frame, this .radius,
479: this .radius);
480: Arc2D arc = new Arc2D.Double(f, this .angle, 0.0, Arc2D.OPEN);
481: Point2D pt = arc.getStartPoint();
482:
483: // calculate the bounds of the template value
484: FontMetrics fm = g2.getFontMetrics(this .font);
485: String s = this .formatter.format(this .templateValue);
486: Rectangle2D tb = TextUtilities.getTextBounds(s, g2, fm);
487:
488: // align this rectangle to the frameAnchor
489: Rectangle2D bounds = RectangleAnchor.createRectangle(
490: new Size2D(tb.getWidth(), tb.getHeight()), pt.getX(),
491: pt.getY(), this .frameAnchor);
492:
493: // add the insets
494: Rectangle2D fb = this .insets.createOutsetRectangle(bounds);
495:
496: // draw the background
497: g2.setPaint(this .backgroundPaint);
498: g2.fill(fb);
499:
500: // draw the border
501: g2.setStroke(this .outlineStroke);
502: g2.setPaint(this .outlinePaint);
503: g2.draw(fb);
504:
505: // now find the text anchor point
506: double value = plot.getValue(this .datasetIndex);
507: String valueStr = this .formatter.format(value);
508: Point2D pt2 = RectangleAnchor.coordinates(bounds,
509: this .valueAnchor);
510: g2.setPaint(this .paint);
511: g2.setFont(this .font);
512: TextUtilities.drawAlignedString(valueStr, g2, (float) pt2
513: .getX(), (float) pt2.getY(), this .textAnchor);
514:
515: }
516:
517: /**
518: * Tests this instance for equality with an arbitrary object.
519: *
520: * @param obj the object (<code>null</code> permitted).
521: *
522: * @return A boolean.
523: */
524: public boolean equals(Object obj) {
525: if (obj == this ) {
526: return true;
527: }
528: if (!(obj instanceof DialValueIndicator)) {
529: return false;
530: }
531: DialValueIndicator that = (DialValueIndicator) obj;
532: if (this .datasetIndex != that.datasetIndex) {
533: return false;
534: }
535: if (this .angle != that.angle) {
536: return false;
537: }
538: if (this .radius != that.radius) {
539: return false;
540: }
541: if (!this .frameAnchor.equals(that.frameAnchor)) {
542: return false;
543: }
544: if (!this .templateValue.equals(that.templateValue)) {
545: return false;
546: }
547: if (!this .font.equals(that.font)) {
548: return false;
549: }
550: if (!PaintUtilities.equal(this .paint, that.paint)) {
551: return false;
552: }
553: if (!PaintUtilities.equal(this .backgroundPaint,
554: that.backgroundPaint)) {
555: return false;
556: }
557: if (!this .outlineStroke.equals(that.outlineStroke)) {
558: return false;
559: }
560: if (!PaintUtilities.equal(this .outlinePaint, that.outlinePaint)) {
561: return false;
562: }
563: if (!this .insets.equals(that.insets)) {
564: return false;
565: }
566: if (!this .valueAnchor.equals(that.valueAnchor)) {
567: return false;
568: }
569: if (!this .textAnchor.equals(that.textAnchor)) {
570: return false;
571: }
572:
573: return true;
574: }
575:
576: /**
577: * Returns a hash code for this instance.
578: *
579: * @return The hash code.
580: */
581: public int hashCode() {
582: int result = 193;
583: result = 37 * result
584: + HashUtilities.hashCodeForPaint(this .paint);
585: result = 37 * result
586: + HashUtilities.hashCodeForPaint(this .backgroundPaint);
587: result = 37 * result
588: + HashUtilities.hashCodeForPaint(this .outlinePaint);
589: result = 37 * result + this .outlineStroke.hashCode();
590: return result;
591: }
592:
593: /**
594: * Returns a clone of this instance.
595: *
596: * @return The clone.
597: *
598: * @throws CloneNotSupportedException if some attribute of this instance
599: * cannot be cloned.
600: */
601: public Object clone() throws CloneNotSupportedException {
602: return super .clone();
603: }
604:
605: /**
606: * Provides serialization support.
607: *
608: * @param stream the output stream.
609: *
610: * @throws IOException if there is an I/O error.
611: */
612: private void writeObject(ObjectOutputStream stream)
613: throws IOException {
614: stream.defaultWriteObject();
615: SerialUtilities.writePaint(this .paint, stream);
616: SerialUtilities.writePaint(this .backgroundPaint, stream);
617: SerialUtilities.writePaint(this .outlinePaint, stream);
618: SerialUtilities.writeStroke(this .outlineStroke, stream);
619: }
620:
621: /**
622: * Provides serialization support.
623: *
624: * @param stream the input stream.
625: *
626: * @throws IOException if there is an I/O error.
627: * @throws ClassNotFoundException if there is a classpath problem.
628: */
629: private void readObject(ObjectInputStream stream)
630: throws IOException, ClassNotFoundException {
631: stream.defaultReadObject();
632: this.paint = SerialUtilities.readPaint(stream);
633: this.backgroundPaint = SerialUtilities.readPaint(stream);
634: this.outlinePaint = SerialUtilities.readPaint(stream);
635: this.outlineStroke = SerialUtilities.readStroke(stream);
636: }
637:
638: }
|