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     * 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    
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    }