001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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-2007, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * Changes
036 * -------
037 * 03-Nov-2006 : Version 1 (DG);
038 * 17-Oct-2007 : Updated equals() (DG);
039 * 24-Oct-2007 : Added default constructor and missing event notification (DG);
040 *
041 */
042
043 package org.jfree.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 * @since 1.0.7
076 */
077 public class DialValueIndicator extends AbstractDialLayer implements DialLayer,
078 Cloneable, PublicCloneable, Serializable {
079
080 /** For serialization. */
081 static final long serialVersionUID = 803094354130942585L;
082
083 /** The dataset index. */
084 private int datasetIndex;
085
086 /** The angle that defines the anchor point. */
087 private double angle;
088
089 /** The radius that defines the anchor point. */
090 private double radius;
091
092 /** The frame anchor. */
093 private RectangleAnchor frameAnchor;
094
095 /** The template value. */
096 private Number templateValue;
097
098 /** The formatter. */
099 private NumberFormat formatter;
100
101 /** The font. */
102 private Font font;
103
104 /** The paint. */
105 private transient Paint paint;
106
107 /** The background paint. */
108 private transient Paint backgroundPaint;
109
110 /** The outline stroke. */
111 private transient Stroke outlineStroke;
112
113 /** The outline paint. */
114 private transient Paint outlinePaint;
115
116 /** The insets. */
117 private RectangleInsets insets;
118
119 /** The value anchor. */
120 private RectangleAnchor valueAnchor;
121
122 /** The text anchor for displaying the value. */
123 private TextAnchor textAnchor;
124
125 /**
126 * Creates a new instance of <code>DialValueIndicator</code>.
127 */
128 public DialValueIndicator() {
129 this(0);
130 }
131
132 /**
133 * Creates a new instance of <code>DialValueIndicator</code>.
134 *
135 * @param datasetIndex the dataset index.
136 */
137 public DialValueIndicator(int datasetIndex) {
138 this.datasetIndex = datasetIndex;
139 this.angle = -90.0;
140 this.radius = 0.3;
141 this.frameAnchor = RectangleAnchor.CENTER;
142 this.templateValue = new Double(100.0);
143 this.formatter = new DecimalFormat("0.0");
144 this.font = new Font("Dialog", Font.BOLD, 14);
145 this.paint = Color.black;
146 this.backgroundPaint = Color.white;
147 this.outlineStroke = new BasicStroke(1.0f);
148 this.outlinePaint = Color.blue;
149 this.insets = new RectangleInsets(4, 4, 4, 4);
150 this.valueAnchor = RectangleAnchor.RIGHT;
151 this.textAnchor = TextAnchor.CENTER_RIGHT;
152 }
153
154 /**
155 * Returns the index of the dataset from which this indicator fetches its
156 * current value.
157 *
158 * @return The dataset index.
159 *
160 * @see #setDatasetIndex(int)
161 */
162 public int getDatasetIndex() {
163 return this.datasetIndex;
164 }
165
166 /**
167 * Sets the dataset index and sends a {@link DialLayerChangeEvent} to all
168 * registered listeners.
169 *
170 * @param index the index.
171 *
172 * @see #getDatasetIndex()
173 */
174 public void setDatasetIndex(int index) {
175 this.datasetIndex = index;
176 notifyListeners(new DialLayerChangeEvent(this));
177 }
178
179 /**
180 * Returns the angle for the anchor point. The angle is specified in
181 * degrees using the same orientation as Java's <code>Arc2D</code> class.
182 *
183 * @return The angle (in degrees).
184 *
185 * @see #setAngle(double)
186 */
187 public double getAngle() {
188 return this.angle;
189 }
190
191 /**
192 * Sets the angle for the anchor point and sends a
193 * {@link DialLayerChangeEvent} to all registered listeners.
194 *
195 * @param angle the angle (in degrees).
196 *
197 * @see #getAngle()
198 */
199 public void setAngle(double angle) {
200 this.angle = angle;
201 notifyListeners(new DialLayerChangeEvent(this));
202 }
203
204 /**
205 * Returns the radius.
206 *
207 * @return The radius.
208 *
209 * @see #setRadius(double)
210 */
211 public double getRadius() {
212 return this.radius;
213 }
214
215 /**
216 * Sets the radius and sends a {@link DialLayerChangeEvent} to all
217 * registered listeners.
218 *
219 * @param radius the radius.
220 *
221 * @see #getRadius()
222 */
223 public void setRadius(double radius) {
224 this.radius = radius;
225 notifyListeners(new DialLayerChangeEvent(this));
226 }
227
228 /**
229 * Returns the frame anchor.
230 *
231 * @return The frame anchor.
232 *
233 * @see #setFrameAnchor(RectangleAnchor)
234 */
235 public RectangleAnchor getFrameAnchor() {
236 return this.frameAnchor;
237 }
238
239 /**
240 * Sets the frame anchor and sends a {@link DialLayerChangeEvent} to all
241 * registered listeners.
242 *
243 * @param anchor the anchor (<code>null</code> not permitted).
244 *
245 * @see #getFrameAnchor()
246 */
247 public void setFrameAnchor(RectangleAnchor anchor) {
248 if (anchor == null) {
249 throw new IllegalArgumentException("Null 'anchor' argument.");
250 }
251 this.frameAnchor = anchor;
252 notifyListeners(new DialLayerChangeEvent(this));
253 }
254
255 /**
256 * Returns the template value.
257 *
258 * @return The template value (never <code>null</code>).
259 *
260 * @see #setTemplateValue(Number)
261 */
262 public Number getTemplateValue() {
263 return this.templateValue;
264 }
265
266 /**
267 * Sets the template value and sends a {@link DialLayerChangeEvent} to
268 * all registered listeners.
269 *
270 * @param value the value (<code>null</code> not permitted).
271 *
272 * @see #setTemplateValue(Number)
273 */
274 public void setTemplateValue(Number value) {
275 if (value == null) {
276 throw new IllegalArgumentException("Null 'value' argument.");
277 }
278 this.templateValue = value;
279 notifyListeners(new DialLayerChangeEvent(this));
280 }
281
282 /**
283 * Returns the formatter used to format the value.
284 *
285 * @return The formatter (never <code>null</code>).
286 *
287 * @see #setNumberFormat(NumberFormat)
288 */
289 public NumberFormat getNumberFormat() {
290 return this.formatter;
291 }
292
293 /**
294 * Sets the formatter used to format the value and sends a
295 * {@link DialLayerChangeEvent} to all registered listeners.
296 *
297 * @param formatter the formatter (<code>null</code> not permitted).
298 *
299 * @see #getNumberFormat()
300 */
301 public void setNumberFormat(NumberFormat formatter) {
302 if (formatter == null) {
303 throw new IllegalArgumentException("Null 'formatter' argument.");
304 }
305 this.formatter = formatter;
306 notifyListeners(new DialLayerChangeEvent(this));
307 }
308
309 /**
310 * Returns the font.
311 *
312 * @return The font (never <code>null</code>).
313 *
314 * @see #getFont()
315 */
316 public Font getFont() {
317 return this.font;
318 }
319
320 /**
321 * Sets the font and sends a {@link DialLayerChangeEvent} to all registered
322 * listeners.
323 *
324 * @param font the font (<code>null</code> not permitted).
325 */
326 public void setFont(Font font) {
327 if (font == null) {
328 throw new IllegalArgumentException("Null 'font' argument.");
329 }
330 this.font = font;
331 notifyListeners(new DialLayerChangeEvent(this));
332 }
333
334 /**
335 * Returns the paint.
336 *
337 * @return The paint (never <code>null</code>).
338 *
339 * @see #setPaint(Paint)
340 */
341 public Paint getPaint() {
342 return this.paint;
343 }
344
345 /**
346 * Sets the paint and sends a {@link DialLayerChangeEvent} to all
347 * registered listeners.
348 *
349 * @param paint the paint (<code>null</code> not permitted).
350 *
351 * @see #getPaint()
352 */
353 public void setPaint(Paint paint) {
354 if (paint == null) {
355 throw new IllegalArgumentException("Null 'paint' argument.");
356 }
357 this.paint = paint;
358 notifyListeners(new DialLayerChangeEvent(this));
359 }
360
361 /**
362 * Returns the background paint.
363 *
364 * @return The background paint.
365 *
366 * @see #setBackgroundPaint(Paint)
367 */
368 public Paint getBackgroundPaint() {
369 return this.backgroundPaint;
370 }
371
372 /**
373 * Sets the background paint and sends a {@link DialLayerChangeEvent} to
374 * all registered listeners.
375 *
376 * @param paint the paint (<code>null</code> not permitted).
377 *
378 * @see #getBackgroundPaint()
379 */
380 public void setBackgroundPaint(Paint paint) {
381 if (paint == null) {
382 throw new IllegalArgumentException("Null 'paint' argument.");
383 }
384 this.backgroundPaint = paint;
385 notifyListeners(new DialLayerChangeEvent(this));
386 }
387
388 /**
389 * Returns the outline stroke.
390 *
391 * @return The outline stroke (never <code>null</code>).
392 *
393 * @see #setOutlineStroke(Stroke)
394 */
395 public Stroke getOutlineStroke() {
396 return this.outlineStroke;
397 }
398
399 /**
400 * Sets the outline stroke and sends a {@link DialLayerChangeEvent} to
401 * all registered listeners.
402 *
403 * @param stroke the stroke (<code>null</code> not permitted).
404 *
405 * @see #getOutlineStroke()
406 */
407 public void setOutlineStroke(Stroke stroke) {
408 if (stroke == null) {
409 throw new IllegalArgumentException("Null 'stroke' argument.");
410 }
411 this.outlineStroke = stroke;
412 notifyListeners(new DialLayerChangeEvent(this));
413 }
414
415 /**
416 * Returns the outline paint.
417 *
418 * @return The outline paint (never <code>null</code>).
419 *
420 * @see #setOutlinePaint(Paint)
421 */
422 public Paint getOutlinePaint() {
423 return this.outlinePaint;
424 }
425
426 /**
427 * Sets the outline paint and sends a {@link DialLayerChangeEvent} to all
428 * registered listeners.
429 *
430 * @param paint the paint (<code>null</code> not permitted).
431 *
432 * @see #getOutlinePaint()
433 */
434 public void setOutlinePaint(Paint paint) {
435 if (paint == null) {
436 throw new IllegalArgumentException("Null 'paint' argument.");
437 }
438 this.outlinePaint = paint;
439 notifyListeners(new DialLayerChangeEvent(this));
440 }
441
442 /**
443 * Returns the insets.
444 *
445 * @return The insets (never <code>null</code>).
446 *
447 * @see #setInsets(RectangleInsets)
448 */
449 public RectangleInsets getInsets() {
450 return this.insets;
451 }
452
453 /**
454 * Sets the insets and sends a {@link DialLayerChangeEvent} to all
455 * registered listeners.
456 *
457 * @param insets the insets (<code>null</code> not permitted).
458 *
459 * @see #getInsets()
460 */
461 public void setInsets(RectangleInsets insets) {
462 if (insets == null) {
463 throw new IllegalArgumentException("Null 'insets' argument.");
464 }
465 this.insets = insets;
466 notifyListeners(new DialLayerChangeEvent(this));
467 }
468
469 /**
470 * Returns the value anchor.
471 *
472 * @return The value anchor (never <code>null</code>).
473 *
474 * @see #setValueAnchor(RectangleAnchor)
475 */
476 public RectangleAnchor getValueAnchor() {
477 return this.valueAnchor;
478 }
479
480 /**
481 * Sets the value anchor and sends a {@link DialLayerChangeEvent} to all
482 * registered listeners.
483 *
484 * @param anchor the anchor (<code>null</code> not permitted).
485 *
486 * @see #getValueAnchor()
487 */
488 public void setValueAnchor(RectangleAnchor anchor) {
489 if (anchor == null) {
490 throw new IllegalArgumentException("Null 'anchor' argument.");
491 }
492 this.valueAnchor = anchor;
493 notifyListeners(new DialLayerChangeEvent(this));
494 }
495
496 /**
497 * Returns the text anchor.
498 *
499 * @return The text anchor (never <code>null</code>).
500 *
501 * @see #setTextAnchor(TextAnchor)
502 */
503 public TextAnchor getTextAnchor() {
504 return this.textAnchor;
505 }
506
507 /**
508 * Sets the text anchor and sends a {@link DialLayerChangeEvent} to all
509 * registered listeners.
510 *
511 * @param anchor the anchor (<code>null</code> not permitted).
512 *
513 * @see #getTextAnchor()
514 */
515 public void setTextAnchor(TextAnchor anchor) {
516 if (anchor == null) {
517 throw new IllegalArgumentException("Null 'anchor' argument.");
518 }
519 this.textAnchor = anchor;
520 notifyListeners(new DialLayerChangeEvent(this));
521 }
522
523 /**
524 * Returns <code>true</code> to indicate that this layer should be
525 * clipped within the dial window.
526 *
527 * @return <code>true</code>.
528 */
529 public boolean isClippedToWindow() {
530 return true;
531 }
532
533 /**
534 * Draws the background to the specified graphics device. If the dial
535 * frame specifies a window, the clipping region will already have been
536 * set to this window before this method is called.
537 *
538 * @param g2 the graphics device (<code>null</code> not permitted).
539 * @param plot the plot (ignored here).
540 * @param frame the dial frame (ignored here).
541 * @param view the view rectangle (<code>null</code> not permitted).
542 */
543 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
544 Rectangle2D view) {
545
546 // work out the anchor point
547 Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius,
548 this.radius);
549 Arc2D arc = new Arc2D.Double(f, this.angle, 0.0, Arc2D.OPEN);
550 Point2D pt = arc.getStartPoint();
551
552 // calculate the bounds of the template value
553 FontMetrics fm = g2.getFontMetrics(this.font);
554 String s = this.formatter.format(this.templateValue);
555 Rectangle2D tb = TextUtilities.getTextBounds(s, g2, fm);
556
557 // align this rectangle to the frameAnchor
558 Rectangle2D bounds = RectangleAnchor.createRectangle(new Size2D(
559 tb.getWidth(), tb.getHeight()), pt.getX(), pt.getY(),
560 this.frameAnchor);
561
562 // add the insets
563 Rectangle2D fb = this.insets.createOutsetRectangle(bounds);
564
565 // draw the background
566 g2.setPaint(this.backgroundPaint);
567 g2.fill(fb);
568
569 // draw the border
570 g2.setStroke(this.outlineStroke);
571 g2.setPaint(this.outlinePaint);
572 g2.draw(fb);
573
574
575 // now find the text anchor point
576 double value = plot.getValue(this.datasetIndex);
577 String valueStr = this.formatter.format(value);
578 Point2D pt2 = RectangleAnchor.coordinates(bounds, this.valueAnchor);
579 g2.setPaint(this.paint);
580 g2.setFont(this.font);
581 TextUtilities.drawAlignedString(valueStr, g2, (float) pt2.getX(),
582 (float) pt2.getY(), this.textAnchor);
583
584 }
585
586 /**
587 * Tests this instance for equality with an arbitrary object.
588 *
589 * @param obj the object (<code>null</code> permitted).
590 *
591 * @return A boolean.
592 */
593 public boolean equals(Object obj) {
594 if (obj == this) {
595 return true;
596 }
597 if (!(obj instanceof DialValueIndicator)) {
598 return false;
599 }
600 DialValueIndicator that = (DialValueIndicator) obj;
601 if (this.datasetIndex != that.datasetIndex) {
602 return false;
603 }
604 if (this.angle != that.angle) {
605 return false;
606 }
607 if (this.radius != that.radius) {
608 return false;
609 }
610 if (!this.frameAnchor.equals(that.frameAnchor)) {
611 return false;
612 }
613 if (!this.templateValue.equals(that.templateValue)) {
614 return false;
615 }
616 if (!this.font.equals(that.font)) {
617 return false;
618 }
619 if (!PaintUtilities.equal(this.paint, that.paint)) {
620 return false;
621 }
622 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
623 return false;
624 }
625 if (!this.outlineStroke.equals(that.outlineStroke)) {
626 return false;
627 }
628 if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
629 return false;
630 }
631 if (!this.insets.equals(that.insets)) {
632 return false;
633 }
634 if (!this.valueAnchor.equals(that.valueAnchor)) {
635 return false;
636 }
637 if (!this.textAnchor.equals(that.textAnchor)) {
638 return false;
639 }
640
641 return super.equals(obj);
642 }
643
644 /**
645 * Returns a hash code for this instance.
646 *
647 * @return The hash code.
648 */
649 public int hashCode() {
650 int result = 193;
651 result = 37 * result + HashUtilities.hashCodeForPaint(this.paint);
652 result = 37 * result + HashUtilities.hashCodeForPaint(
653 this.backgroundPaint);
654 result = 37 * result + HashUtilities.hashCodeForPaint(
655 this.outlinePaint);
656 result = 37 * result + this.outlineStroke.hashCode();
657 return result;
658 }
659
660 /**
661 * Returns a clone of this instance.
662 *
663 * @return The clone.
664 *
665 * @throws CloneNotSupportedException if some attribute of this instance
666 * cannot be cloned.
667 */
668 public Object clone() throws CloneNotSupportedException {
669 return super.clone();
670 }
671
672 /**
673 * Provides serialization support.
674 *
675 * @param stream the output stream.
676 *
677 * @throws IOException if there is an I/O error.
678 */
679 private void writeObject(ObjectOutputStream stream) throws IOException {
680 stream.defaultWriteObject();
681 SerialUtilities.writePaint(this.paint, stream);
682 SerialUtilities.writePaint(this.backgroundPaint, stream);
683 SerialUtilities.writePaint(this.outlinePaint, stream);
684 SerialUtilities.writeStroke(this.outlineStroke, stream);
685 }
686
687 /**
688 * Provides serialization support.
689 *
690 * @param stream the input stream.
691 *
692 * @throws IOException if there is an I/O error.
693 * @throws ClassNotFoundException if there is a classpath problem.
694 */
695 private void readObject(ObjectInputStream stream)
696 throws IOException, ClassNotFoundException {
697 stream.defaultReadObject();
698 this.paint = SerialUtilities.readPaint(stream);
699 this.backgroundPaint = SerialUtilities.readPaint(stream);
700 this.outlinePaint = SerialUtilities.readPaint(stream);
701 this.outlineStroke = SerialUtilities.readStroke(stream);
702 }
703
704 }