    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    }