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