    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     * 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
    046    package org.jfree.chart.plot.dial;
    047
    048    import java.awt.BasicStroke;
    049    import java.awt.Color;
    050    import java.awt.Graphics2D;
    051    import java.awt.Paint;
    052    import java.awt.Stroke;
    053    import java.awt.geom.Arc2D;
    054    import java.awt.geom.GeneralPath;
    055    import java.awt.geom.Line2D;
    056    import java.awt.geom.Point2D;
    057    import java.awt.geom.Rectangle2D;
    058    import java.io.IOException;
    059    import java.io.ObjectInputStream;
    060    import java.io.ObjectOutputStream;
    061    import java.io.Serializable;
    062
    063    import org.jfree.chart.HashUtilities;
    064    import org.jfree.io.SerialUtilities;
    065    import org.jfree.util.PaintUtilities;
    066    import org.jfree.util.PublicCloneable;
    067
    068    /**
    069     * A base class for the pointer in a {@link DialPlot}.
    070     *
    071     * @since 1.0.7
    072     */
    073    public 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    }