    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 * StandardDialRange.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 * 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
    044package org.jfree.chart.plot.dial;
    045
    046import java.awt.BasicStroke;
    047import java.awt.Color;
    048import java.awt.Graphics2D;
    049import java.awt.Paint;
    050import java.awt.geom.Arc2D;
    051import java.awt.geom.Rectangle2D;
    052import java.io.IOException;
    053import java.io.ObjectInputStream;
    054import java.io.ObjectOutputStream;
    055import java.io.Serializable;
    056
    057import org.jfree.chart.HashUtilities;
    058import org.jfree.io.SerialUtilities;
    059import org.jfree.util.PaintUtilities;
    060import 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 */
    067public 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}