001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2008, 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-2008, 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    }