    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * ----------------
    028 * DialPointer.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 : Added equals() overrides (DG);
    039 * 24-Oct-2007 : Implemented PublicCloneable, changed default radius,
    040 *               and added argument checks (DG);
    041 * 23-Nov-2007 : Added fillPaint and outlinePaint attributes to
    042 *               DialPointer.Pointer (DG);
    043 *
    044 */
    045
    046package org.jfree.chart.plot.dial;
    047
    048import java.awt.BasicStroke;
    049import java.awt.Color;
    050import java.awt.Graphics2D;
    051import java.awt.Paint;
    052import java.awt.Stroke;
    053import java.awt.geom.Arc2D;
    054import java.awt.geom.GeneralPath;
    055import java.awt.geom.Line2D;
    056import java.awt.geom.Point2D;
    057import java.awt.geom.Rectangle2D;
    058import java.io.IOException;
    059import java.io.ObjectInputStream;
    060import java.io.ObjectOutputStream;
    061import java.io.Serializable;
    062
    063import org.jfree.chart.HashUtilities;
    064import org.jfree.io.SerialUtilities;
    065import org.jfree.util.PaintUtilities;
    066import org.jfree.util.PublicCloneable;
    067
    068/**
    069 * A base class for the pointer in a {@link DialPlot}.
    070 *
    071 * @since 1.0.7
    072 */
    073public abstract class DialPointer extends AbstractDialLayer
    074        implements DialLayer, Cloneable, PublicCloneable, Serializable {
    075
    076    /** The needle radius. */
    077    double radius;
    078
    079    /**
    080     * The dataset index for the needle.
    081     */
    082    int datasetIndex;
    083
    084    /**
    085     * Creates a new <code>DialPointer</code> instance.
    086     */
    087    protected DialPointer() {
    088        this(0);
    089    }
    090
    091    /**
    092     * Creates a new pointer for the specified dataset.
    093     *
    094     * @param datasetIndex  the dataset index.
    095     */
    096    protected DialPointer(int datasetIndex) {
    097        this.radius = 0.9;
    098        this.datasetIndex = datasetIndex;
    099    }
    100
    101    /**
    102     * Returns the dataset index that the pointer maps to.
    103     *
    104     * @return The dataset index.
    105     *
    106     * @see #getDatasetIndex()
    107     */
    108    public int getDatasetIndex() {
    109        return this.datasetIndex;
    110    }
    111
    112    /**
    113     * Sets the dataset index for the pointer and sends a
    114     * {@link DialLayerChangeEvent} to all registered listeners.
    115     *
    116     * @param index  the index.
    117     *
    118     * @see #getDatasetIndex()
    119     */
    120    public void setDatasetIndex(int index) {
    121        this.datasetIndex = index;
    122        notifyListeners(new DialLayerChangeEvent(this));
    123    }
    124
    125    /**
    126     * Returns the radius of the pointer, as a percentage of the dial's
    127     * framing rectangle.
    128     *
    129     * @return The radius.
    130     *
    131     * @see #setRadius(double)
    132     */
    133    public double getRadius() {
    134        return this.radius;
    135    }
    136
    137    /**
    138     * Sets the radius of the pointer and sends a
    139     * {@link DialLayerChangeEvent} to all registered listeners.
    140     *
    141     * @param radius  the radius.
    142     *
    143     * @see #getRadius()
    144     */
    145    public void setRadius(double radius) {
    146        this.radius = radius;
    147        notifyListeners(new DialLayerChangeEvent(this));
    148    }
    149
    150    /**
    151     * Returns <code>true</code> to indicate that this layer should be
    152     * clipped within the dial window.
    153     *
    154     * @return <code>true</code>.
    155     */
    156    public boolean isClippedToWindow() {
    157        return true;
    158    }
    159
    160    /**
    161     * Checks this instance for equality with an arbitrary object.
    162     *
    163     * @param obj  the object (<code>null</code> not permitted).
    164     *
    165     * @return A boolean.
    166     */
    167    public boolean equals(Object obj) {
    168        if (obj == this) {
    169            return true;
    170        }
    171        if (!(obj instanceof DialPointer)) {
    172            return false;
    173        }
    174        DialPointer that = (DialPointer) obj;
    175        if (this.datasetIndex != that.datasetIndex) {
    176            return false;
    177        }
    178        if (this.radius != that.radius) {
    179            return false;
    180        }
    181        return super.equals(obj);
    182    }
    183
    184    /**
    185     * Returns a hash code.
    186     *
    187     * @return A hash code.
    188     */
    189    public int hashCode() {
    190        int result = 23;
    191        result = HashUtilities.hashCode(result, this.radius);
    192        return result;
    193    }
    194
    195    /**
    196     * Returns a clone of the pointer.
    197     *
    198     * @return a clone.
    199     *
    200     * @throws CloneNotSupportedException if one of the attributes cannot
    201     *     be cloned.
    202     */
    203    public Object clone() throws CloneNotSupportedException {
    204        return super.clone();
    205    }
    206
    207    /**
    208     * A dial pointer that draws a thin line (like a pin).
    209     */
    210    public static class Pin extends DialPointer {
    211
    212        /** For serialization. */
    213        static final long serialVersionUID = -8445860485367689750L;
    214
    215        /** The paint. */
    216        private transient Paint paint;
    217
    218        /** The stroke. */
    219        private transient Stroke stroke;
    220
    221        /**
    222         * Creates a new instance.
    223         */
    224        public Pin() {
    225            this(0);
    226        }
    227
    228        /**
    229         * Creates a new instance.
    230         *
    231         * @param datasetIndex  the dataset index.
    232         */
    233        public Pin(int datasetIndex) {
    234            super(datasetIndex);
    235            this.paint = Color.red;
    236            this.stroke = new BasicStroke(3.0f, BasicStroke.CAP_ROUND,
    237                    BasicStroke.JOIN_BEVEL);
    238        }
    239
    240        /**
    241         * Returns the paint.
    242         *
    243         * @return The paint (never <code>null</code>).
    244         *
    245         * @see #setPaint(Paint)
    246         */
    247        public Paint getPaint() {
    248            return this.paint;
    249        }
    250
    251        /**
    252         * Sets the paint and sends a {@link DialLayerChangeEvent} to all
    253         * registered listeners.
    254         *
    255         * @param paint  the paint (<code>null</code> not permitted).
    256         *
    257         * @see #getPaint()
    258         */
    259        public void setPaint(Paint paint) {
    260            if (paint == null) {
    261                throw new IllegalArgumentException("Null 'paint' argument.");
    262            }
    263            this.paint = paint;
    264            notifyListeners(new DialLayerChangeEvent(this));
    265        }
    266
    267        /**
    268         * Returns the stroke.
    269         *
    270         * @return The stroke (never <code>null</code>).
    271         *
    272         * @see #setStroke(Stroke)
    273         */
    274        public Stroke getStroke() {
    275            return this.stroke;
    276        }
    277
    278        /**
    279         * Sets the stroke and sends a {@link DialLayerChangeEvent} to all
    280         * registered listeners.
    281         *
    282         * @param stroke  the stroke (<code>null</code> not permitted).
    283         *
    284         * @see #getStroke()
    285         */
    286        public void setStroke(Stroke stroke) {
    287            if (stroke == null) {
    288                throw new IllegalArgumentException("Null 'stroke' argument.");
    289            }
    290            this.stroke = stroke;
    291            notifyListeners(new DialLayerChangeEvent(this));
    292        }
    293
    294        /**
    295         * Draws the pointer.
    296         *
    297         * @param g2  the graphics target.
    298         * @param plot  the plot.
    299         * @param frame  the dial's reference frame.
    300         * @param view  the dial's view.
    301         */
    302        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
    303            Rectangle2D view) {
    304
    305            g2.setPaint(this.paint);
    306            g2.setStroke(this.stroke);
    307            Rectangle2D arcRect = DialPlot.rectangleByRadius(frame,
    308                    this.radius, this.radius);
    309
    310            double value = plot.getValue(this.datasetIndex);
    311            DialScale scale = plot.getScaleForDataset(this.datasetIndex);
    312            double angle = scale.valueToAngle(value);
    313
    314            Arc2D arc = new Arc2D.Double(arcRect, angle, 0, Arc2D.OPEN);
    315            Point2D pt = arc.getEndPoint();
    316
    317            Line2D line = new Line2D.Double(frame.getCenterX(),
    318                    frame.getCenterY(), pt.getX(), pt.getY());
    319            g2.draw(line);
    320        }
    321
    322        /**
    323         * Tests this pointer for equality with an arbitrary object.
    324         *
    325         * @param obj  the object (<code>null</code> permitted).
    326         *
    327         * @return A boolean.
    328         */
    329        public boolean equals(Object obj) {
    330            if (obj == this) {
    331                return true;
    332            }
    333            if (!(obj instanceof DialPointer.Pin)) {
    334                return false;
    335            }
    336            DialPointer.Pin that = (DialPointer.Pin) obj;
    337            if (!PaintUtilities.equal(this.paint, that.paint)) {
    338                return false;
    339            }
    340            if (!this.stroke.equals(that.stroke)) {
    341                return false;
    342            }
    343            return super.equals(obj);
    344        }
    345
    346        /**
    347         * Returns a hash code for this instance.
    348         *
    349         * @return A hash code.
    350         */
    351        public int hashCode() {
    352            int result = super.hashCode();
    353            result = HashUtilities.hashCode(result, this.paint);
    354            result = HashUtilities.hashCode(result, this.stroke);
    355            return result;
    356        }
    357
    358        /**
    359         * Provides serialization support.
    360         *
    361         * @param stream  the output stream.
    362         *
    363         * @throws IOException  if there is an I/O error.
    364         */
    365        private void writeObject(ObjectOutputStream stream) throws IOException {
    366            stream.defaultWriteObject();
    367            SerialUtilities.writePaint(this.paint, stream);
    368            SerialUtilities.writeStroke(this.stroke, stream);
    369        }
    370
    371        /**
    372         * Provides serialization support.
    373         *
    374         * @param stream  the input stream.
    375         *
    376         * @throws IOException  if there is an I/O error.
    377         * @throws ClassNotFoundException  if there is a classpath problem.
    378         */
    379        private void readObject(ObjectInputStream stream)
    380                throws IOException, ClassNotFoundException {
    381            stream.defaultReadObject();
    382            this.paint = SerialUtilities.readPaint(stream);
    383            this.stroke = SerialUtilities.readStroke(stream);
    384        }
    385
    386    }
    387
    388    /**
    389     * A dial pointer.
    390     */
    391    public static class Pointer extends DialPointer {
    392
    393        /** For serialization. */
    394        static final long serialVersionUID = -4180500011963176960L;
    395
    396        /**
    397         * The radius that defines the width of the pointer at the base.
    398         */
    399        private double widthRadius;
    400
    401        /**
    402         * The fill paint.
    403         *
    404         * @since 1.0.8
    405         */
    406        private transient Paint fillPaint;
    407
    408        /**
    409         * The outline paint.
    410         *
    411         * @since 1.0.8
    412         */
    413        private transient Paint outlinePaint;
    414
    415        /**
    416         * Creates a new instance.
    417         */
    418        public Pointer() {
    419            this(0);
    420        }
    421
    422        /**
    423         * Creates a new instance.
    424         *
    425         * @param datasetIndex  the dataset index.
    426         */
    427        public Pointer(int datasetIndex) {
    428            super(datasetIndex);
    429            this.widthRadius = 0.05;
    430            this.fillPaint = Color.gray;
    431            this.outlinePaint = Color.black;
    432        }
    433
    434        /**
    435         * Returns the width radius.
    436         *
    437         * @return The width radius.
    438         *
    439         * @see #setWidthRadius(double)
    440         */
    441        public double getWidthRadius() {
    442            return this.widthRadius;
    443        }
    444
    445        /**
    446         * Sets the width radius and sends a {@link DialLayerChangeEvent} to
    447         * all registered listeners.
    448         *
    449         * @param radius  the radius
    450         *
    451         * @see #getWidthRadius()
    452         */
    453        public void setWidthRadius(double radius) {
    454            this.widthRadius = radius;
    455            notifyListeners(new DialLayerChangeEvent(this));
    456        }
    457
    458        /**
    459         * Returns the fill paint.
    460         *
    461         * @return The paint (never <code>null</code>).
    462         *
    463         * @see #setFillPaint(Paint)
    464         *
    465         * @since 1.0.8
    466         */
    467        public Paint getFillPaint() {
    468            return this.fillPaint;
    469        }
    470
    471        /**
    472         * Sets the fill paint and sends a {@link DialLayerChangeEvent} to all
    473         * registered listeners.
    474         *
    475         * @param paint  the paint (<code>null</code> not permitted).
    476         *
    477         * @see #getFillPaint()
    478         *
    479         * @since 1.0.8
    480         */
    481        public void setFillPaint(Paint paint) {
    482            if (paint == null) {
    483                throw new IllegalArgumentException("Null 'paint' argument.");
    484            }
    485            this.fillPaint = paint;
    486            notifyListeners(new DialLayerChangeEvent(this));
    487        }
    488
    489        /**
    490         * Returns the outline paint.
    491         *
    492         * @return The paint (never <code>null</code>).
    493         *
    494         * @see #setOutlinePaint(Paint)
    495         *
    496         * @since 1.0.8
    497         */
    498        public Paint getOutlinePaint() {
    499            return this.outlinePaint;
    500        }
    501
    502        /**
    503         * Sets the outline paint and sends a {@link DialLayerChangeEvent} to
    504         * all registered listeners.
    505         *
    506         * @param paint  the paint (<code>null</code> not permitted).
    507         *
    508         * @see #getOutlinePaint()
    509         *
    510         * @since 1.0.8
    511         */
    512        public void setOutlinePaint(Paint paint) {
    513            if (paint == null) {
    514                throw new IllegalArgumentException("Null 'paint' argument.");
    515            }
    516            this.outlinePaint = paint;
    517            notifyListeners(new DialLayerChangeEvent(this));
    518        }
    519
    520        /**
    521         * Draws the pointer.
    522         *
    523         * @param g2  the graphics target.
    524         * @param plot  the plot.
    525         * @param frame  the dial's reference frame.
    526         * @param view  the dial's view.
    527         */
    528        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
    529                Rectangle2D view) {
    530
    531            g2.setPaint(Color.blue);
    532            g2.setStroke(new BasicStroke(1.0f));
    533            Rectangle2D lengthRect = DialPlot.rectangleByRadius(frame,
    534                    this.radius, this.radius);
    535            Rectangle2D widthRect = DialPlot.rectangleByRadius(frame,
    536                    this.widthRadius, this.widthRadius);
    537            double value = plot.getValue(this.datasetIndex);
    538            DialScale scale = plot.getScaleForDataset(this.datasetIndex);
    539            double angle = scale.valueToAngle(value);
    540
    541            Arc2D arc1 = new Arc2D.Double(lengthRect, angle, 0, Arc2D.OPEN);
    542            Point2D pt1 = arc1.getEndPoint();
    543            Arc2D arc2 = new Arc2D.Double(widthRect, angle - 90.0, 180.0,
    544                    Arc2D.OPEN);
    545            Point2D pt2 = arc2.getStartPoint();
    546            Point2D pt3 = arc2.getEndPoint();
    547            Arc2D arc3 = new Arc2D.Double(widthRect, angle - 180.0, 0.0,
    548                    Arc2D.OPEN);
    549            Point2D pt4 = arc3.getStartPoint();
    550
    551            GeneralPath gp = new GeneralPath();
    552            gp.moveTo((float) pt1.getX(), (float) pt1.getY());
    553            gp.lineTo((float) pt2.getX(), (float) pt2.getY());
    554            gp.lineTo((float) pt4.getX(), (float) pt4.getY());
    555            gp.lineTo((float) pt3.getX(), (float) pt3.getY());
    556            gp.closePath();
    557            g2.setPaint(this.fillPaint);
    558            g2.fill(gp);
    559
    560            g2.setPaint(this.outlinePaint);
    561            Line2D line = new Line2D.Double(frame.getCenterX(),
    562                    frame.getCenterY(), pt1.getX(), pt1.getY());
    563            g2.draw(line);
    564
    565            line.setLine(pt2, pt3);
    566            g2.draw(line);
    567
    568            line.setLine(pt3, pt1);
    569            g2.draw(line);
    570
    571            line.setLine(pt2, pt1);
    572            g2.draw(line);
    573
    574            line.setLine(pt2, pt4);
    575            g2.draw(line);
    576
    577            line.setLine(pt3, pt4);
    578            g2.draw(line);
    579        }
    580
    581        /**
    582         * Tests this pointer for equality with an arbitrary object.
    583         *
    584         * @param obj  the object (<code>null</code> permitted).
    585         *
    586         * @return A boolean.
    587         */
    588        public boolean equals(Object obj) {
    589            if (obj == this) {
    590                return true;
    591            }
    592            if (!(obj instanceof DialPointer.Pointer)) {
    593                return false;
    594            }
    595            DialPointer.Pointer that = (DialPointer.Pointer) obj;
    596
    597            if (this.widthRadius != that.widthRadius) {
    598                return false;
    599            }
    600            if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) {
    601                return false;
    602            }
    603            if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
    604                return false;
    605            }
    606            return super.equals(obj);
    607        }
    608
    609        /**
    610         * Returns a hash code for this instance.
    611         *
    612         * @return A hash code.
    613         */
    614        public int hashCode() {
    615            int result = super.hashCode();
    616            result = HashUtilities.hashCode(result, this.widthRadius);
    617            result = HashUtilities.hashCode(result, this.fillPaint);
    618            result = HashUtilities.hashCode(result, this.outlinePaint);
    619            return result;
    620        }
    621
    622        /**
    623         * Provides serialization support.
    624         *
    625         * @param stream  the output stream.
    626         *
    627         * @throws IOException  if there is an I/O error.
    628         */
    629        private void writeObject(ObjectOutputStream stream) throws IOException {
    630            stream.defaultWriteObject();
    631            SerialUtilities.writePaint(this.fillPaint, stream);
    632            SerialUtilities.writePaint(this.outlinePaint, stream);
    633        }
    634
    635        /**
    636         * Provides serialization support.
    637         *
    638         * @param stream  the input stream.
    639         *
    640         * @throws IOException  if there is an I/O error.
    641         * @throws ClassNotFoundException  if there is a classpath problem.
    642         */
    643        private void readObject(ObjectInputStream stream)
    644                throws IOException, ClassNotFoundException {
    645            stream.defaultReadObject();
    646            this.fillPaint = SerialUtilities.readPaint(stream);
    647            this.outlinePaint = SerialUtilities.readPaint(stream);
    648        }
    649
    650    }
    651
    652}