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