    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ----------------------
    028     * StandardDialRange.java
    029     * ----------------------
    030     * (C) Copyright 2006, 2007, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * Changes
    036     * -------
    037     * 03-Nov-2006 : Version 1 (DG);
    038     * 08-Mar-2007 : Fix in hashCode() (DG);
    039     * 17-Oct-2007 : Removed increment attribute (DG);
    040     * 24-Oct-2007 : Added scaleIndex (DG);
    041     *
    042     */
    043
    044    package org.jfree.chart.plot.dial;
    045
    046    import java.awt.BasicStroke;
    047    import java.awt.Color;
    048    import java.awt.Graphics2D;
    049    import java.awt.Paint;
    050    import java.awt.geom.Arc2D;
    051    import java.awt.geom.Rectangle2D;
    052    import java.io.IOException;
    053    import java.io.ObjectInputStream;
    054    import java.io.ObjectOutputStream;
    055    import java.io.Serializable;
    056
    057    import org.jfree.chart.HashUtilities;
    058    import org.jfree.io.SerialUtilities;
    059    import org.jfree.util.PaintUtilities;
    060    import org.jfree.util.PublicCloneable;
    061
    062    /**
    063     * A layer that draws a range highlight on a dial plot.
    064     *
    065     * @since 1.0.7
    066     */
    067    public class StandardDialRange extends AbstractDialLayer implements DialLayer,
    068            Cloneable, PublicCloneable, Serializable {
    069
    070        /** For serialization. */
    071        static final long serialVersionUID = 345515648249364904L;
    072
    073        /** The scale index. */
    074        private int scaleIndex;
    075
    076        /** The minimum data value for the scale. */
    077        private double lowerBound;
    078
    079        /** The maximum data value for the scale. */
    080        private double upperBound;
    081
    082        /**
    083         * The paint used to draw the range highlight.  This field is transient
    084         * because it requires special handling for serialization.
    085         */
    086        private transient Paint paint;
    087
    088        /**
    089         * The factor (in the range 0.0 to 1.0) that determines the inside limit
    090         * of the range highlight.
    091         */
    092        private double innerRadius;
    093
    094        /**
    095         * The factor (in the range 0.0 to 1.0) that determines the outside limit
    096         * of the range highlight.
    097         */
    098        private double outerRadius;
    099
    100        /**
    101         * Creates a new instance of <code>StandardDialRange</code>.
    102         */
    103        public StandardDialRange() {
    104            this(0.0, 100.0, Color.white);
    105        }
    106
    107        /**
    108         * Creates a new instance of <code>StandardDialRange</code>.
    109         *
    110         * @param lower  the lower bound.
    111         * @param upper  the upper bound.
    112         * @param paint  the paint (<code>null</code> not permitted).
    113         */
    114        public StandardDialRange(double lower, double upper, Paint paint) {
    115            if (paint == null) {
    116                throw new IllegalArgumentException("Null 'paint' argument.");
    117            }
    118            this.scaleIndex = 0;
    119            this.lowerBound = lower;
    120            this.upperBound = upper;
    121            this.innerRadius = 0.48;
    122            this.outerRadius = 0.52;
    123            this.paint = paint;
    124        }
    125
    126        /**
    127         * Returns the scale index.
    128         *
    129         * @return The scale index.
    130         *
    131         * @see #setScaleIndex(int)
    132         */
    133        public int getScaleIndex() {
    134            return this.scaleIndex;
    135        }
    136
    137        /**
    138         * Sets the scale index and sends a {@link DialLayerChangeEvent} to all
    139         * registered listeners.
    140         *
    141         * @param index  the scale index.
    142         *
    143         * @see #getScaleIndex()
    144         */
    145        public void setScaleIndex(int index) {
    146            this.scaleIndex = index;
    147            notifyListeners(new DialLayerChangeEvent(this));
    148        }
    149
    150        /**
    151         * Returns the lower bound (a data value) of the dial range.
    152         *
    153         * @return The lower bound of the dial range.
    154         *
    155         * @see #setLowerBound(double)
    156         */
    157        public double getLowerBound() {
    158            return this.lowerBound;
    159        }
    160
    161        /**
    162         * Sets the lower bound of the dial range and sends a
    163         * {@link DialLayerChangeEvent} to all registered listeners.
    164         *
    165         * @param bound  the lower bound.
    166         *
    167         * @see #getLowerBound()
    168         */
    169        public void setLowerBound(double bound) {
    170            if (bound >= this.upperBound) {
    171                throw new IllegalArgumentException(
    172                        "Lower bound must be less than upper bound.");
    173            }
    174            this.lowerBound = bound;
    175            notifyListeners(new DialLayerChangeEvent(this));
    176        }
    177
    178        /**
    179         * Returns the upper bound of the dial range.
    180         *
    181         * @return The upper bound.
    182         *
    183         * @see #setUpperBound(double)
    184         */
    185        public double getUpperBound() {
    186            return this.upperBound;
    187        }
    188
    189        /**
    190         * Sets the upper bound of the dial range and sends a
    191         * {@link DialLayerChangeEvent} to all registered listeners.
    192         *
    193         * @param bound  the upper bound.
    194         *
    195         * @see #getUpperBound()
    196         */
    197        public void setUpperBound(double bound) {
    198            if (bound <= this.lowerBound) {
    199                throw new IllegalArgumentException(
    200                        "Lower bound must be less than upper bound.");
    201            }
    202            this.upperBound = bound;
    203            notifyListeners(new DialLayerChangeEvent(this));
    204        }
    205
    206        /**
    207         * Sets the bounds for the range and sends a {@link DialLayerChangeEvent}
    208         * to all registered listeners.
    209         *
    210         * @param lower  the lower bound.
    211         * @param upper  the upper bound.
    212         */
    213        public void setBounds(double lower, double upper) {
    214            if (lower >= upper) {
    215                throw new IllegalArgumentException(
    216                        "Lower must be less than upper.");
    217            }
    218            this.lowerBound = lower;
    219            this.upperBound = upper;
    220            notifyListeners(new DialLayerChangeEvent(this));
    221        }
    222
    223        /**
    224         * Returns the paint used to highlight the range.
    225         *
    226         * @return The paint (never <code>null</code>).
    227         *
    228         * @see #setPaint(Paint)
    229         */
    230        public Paint getPaint() {
    231            return this.paint;
    232        }
    233
    234        /**
    235         * Sets the paint used to highlight the range and sends a
    236         * {@link DialLayerChangeEvent} to all registered listeners.
    237         *
    238         * @param paint  the paint (<code>null</code> not permitted).
    239         *
    240         * @see #getPaint()
    241         */
    242        public void setPaint(Paint paint) {
    243            if (paint == null) {
    244                throw new IllegalArgumentException("Null 'paint' argument.");
    245            }
    246            this.paint = paint;
    247            notifyListeners(new DialLayerChangeEvent(this));
    248        }
    249
    250        /**
    251         * Returns the inner radius.
    252         *
    253         * @return The inner radius.
    254         *
    255         * @see #setInnerRadius(double)
    256         */
    257        public double getInnerRadius() {
    258            return this.innerRadius;
    259        }
    260
    261        /**
    262         * Sets the inner radius and sends a {@link DialLayerChangeEvent} to all
    263         * registered listeners.
    264         *
    265         * @param radius  the radius.
    266         *
    267         * @see #getInnerRadius()
    268         */
    269        public void setInnerRadius(double radius) {
    270            this.innerRadius = radius;
    271            notifyListeners(new DialLayerChangeEvent(this));
    272        }
    273
    274        /**
    275         * Returns the outer radius.
    276         *
    277         * @return The outer radius.
    278         *
    279         * @see #setOuterRadius(double)
    280         */
    281        public double getOuterRadius() {
    282            return this.outerRadius;
    283        }
    284
    285        /**
    286         * Sets the outer radius and sends a {@link DialLayerChangeEvent} to all
    287         * registered listeners.
    288         *
    289         * @param radius  the radius.
    290         *
    291         * @see #getOuterRadius()
    292         */
    293        public void setOuterRadius(double radius) {
    294            this.outerRadius = radius;
    295            notifyListeners(new DialLayerChangeEvent(this));
    296        }
    297
    298        /**
    299         * Returns <code>true</code> to indicate that this layer should be
    300         * clipped within the dial window.
    301         *
    302         * @return <code>true</code>.
    303         */
    304        public boolean isClippedToWindow() {
    305            return true;
    306        }
    307
    308        /**
    309         * Draws the range.
    310         *
    311         * @param g2  the graphics target.
    312         * @param plot  the plot.
    313         * @param frame  the dial's reference frame (in Java2D space).
    314         * @param view  the dial's view rectangle (in Java2D space).
    315         */
    316        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
    317                Rectangle2D view) {
    318
    319            Rectangle2D arcRectInner = DialPlot.rectangleByRadius(frame,
    320                    this.innerRadius, this.innerRadius);
    321            Rectangle2D arcRectOuter = DialPlot.rectangleByRadius(frame,
    322                    this.outerRadius, this.outerRadius);
    323
    324            DialScale scale = plot.getScale(this.scaleIndex);
    325            if (scale == null) {
    326                throw new RuntimeException("No scale for scaleIndex = "
    327                        + this.scaleIndex);
    328            }
    329            double angleMin = scale.valueToAngle(this.lowerBound);
    330            double angleMax = scale.valueToAngle(this.upperBound);
    331
    332            Arc2D arcInner = new Arc2D.Double(arcRectInner, angleMin,
    333                    angleMax - angleMin, Arc2D.OPEN);
    334            Arc2D arcOuter = new Arc2D.Double(arcRectOuter, angleMax,
    335                    angleMin - angleMax, Arc2D.OPEN);
    336
    337            g2.setPaint(this.paint);
    338            g2.setStroke(new BasicStroke(2.0f));
    339            g2.draw(arcInner);
    340            g2.draw(arcOuter);
    341        }
    342
    343        /**
    344         * Tests this instance for equality with an arbitrary object.
    345         *
    346         * @param obj  the object (<code>null</code> permitted).
    347         *
    348         * @return A boolean.
    349         */
    350        public boolean equals(Object obj) {
    351            if (obj == this) {
    352                return true;
    353            }
    354            if (!(obj instanceof StandardDialRange)) {
    355                return false;
    356            }
    357            StandardDialRange that = (StandardDialRange) obj;
    358            if (this.scaleIndex != that.scaleIndex) {
    359                return false;
    360            }
    361            if (this.lowerBound != that.lowerBound) {
    362                return false;
    363            }
    364            if (this.upperBound != that.upperBound) {
    365                return false;
    366            }
    367            if (!PaintUtilities.equal(this.paint, that.paint)) {
    368                return false;
    369            }
    370            if (this.innerRadius != that.innerRadius) {
    371                return false;
    372            }
    373            if (this.outerRadius != that.outerRadius) {
    374                return false;
    375            }
    376            return super.equals(obj);
    377        }
    378
    379        /**
    380         * Returns a hash code for this instance.
    381         *
    382         * @return The hash code.
    383         */
    384        public int hashCode() {
    385            int result = 193;
    386            long temp = Double.doubleToLongBits(this.lowerBound);
    387            result = 37 * result + (int) (temp ^ (temp >>> 32));
    388            temp = Double.doubleToLongBits(this.upperBound);
    389            result = 37 * result + (int) (temp ^ (temp >>> 32));
    390            temp = Double.doubleToLongBits(this.innerRadius);
    391            result = 37 * result + (int) (temp ^ (temp >>> 32));
    392            temp = Double.doubleToLongBits(this.outerRadius);
    393            result = 37 * result + (int) (temp ^ (temp >>> 32));
    394            result = 37 * result + HashUtilities.hashCodeForPaint(this.paint);
    395            return result;
    396        }
    397
    398        /**
    399         * Returns a clone of this instance.
    400         *
    401         * @return A clone.
    402         *
    403         * @throws CloneNotSupportedException if any of the attributes of this
    404         *     instance cannot be cloned.
    405         */
    406        public Object clone() throws CloneNotSupportedException {
    407            return super.clone();
    408        }
    409
    410        /**
    411         * Provides serialization support.
    412         *
    413         * @param stream  the output stream.
    414         *
    415         * @throws IOException  if there is an I/O error.
    416         */
    417        private void writeObject(ObjectOutputStream stream) throws IOException {
    418            stream.defaultWriteObject();
    419            SerialUtilities.writePaint(this.paint, stream);
    420        }
    421
    422        /**
    423         * Provides serialization support.
    424         *
    425         * @param stream  the input stream.
    426         *
    427         * @throws IOException  if there is an I/O error.
    428         * @throws ClassNotFoundException  if there is a classpath problem.
    429         */
    430        private void readObject(ObjectInputStream stream)
    431                throws IOException, ClassNotFoundException {
    432            stream.defaultReadObject();
    433            this.paint = SerialUtilities.readPaint(stream);
    434        }
    435
    436    }