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     * ArcDialFrame.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 : Updated equals() (DG);
040     * 24-Oct-2007 : Added argument checks and API docs, and renamed
041     *               StandardDialFrame --> ArcDialFrame (DG);
042     *
043     */
044    
045    package org.jfree.chart.plot.dial;
046    
047    import java.awt.BasicStroke;
048    import java.awt.Color;
049    import java.awt.Graphics2D;
050    import java.awt.Paint;
051    import java.awt.Shape;
052    import java.awt.Stroke;
053    import java.awt.geom.Arc2D;
054    import java.awt.geom.Area;
055    import java.awt.geom.GeneralPath;
056    import java.awt.geom.Point2D;
057    import java.awt.geom.Rectangle2D;
058    import java.io.IOException;
059    import java.io.ObjectInputStream;
060    import java.io.ObjectOutputStream;
061    import java.io.Serializable;
062    
063    import org.jfree.chart.HashUtilities;
064    import org.jfree.io.SerialUtilities;
065    import org.jfree.util.PaintUtilities;
066    import org.jfree.util.PublicCloneable;
067    
068    /**
069     * A standard frame for the {@link DialPlot} class.
070     *
071     * @since 1.0.7
072     */
073    public class ArcDialFrame extends AbstractDialLayer implements DialFrame,
074            Cloneable, PublicCloneable, Serializable {
075    
076        /** For serialization. */
077        static final long serialVersionUID = -4089176959553523499L;
078    
079        /**
080         * The color used for the front of the panel.  This field is transient
081         * because it requires special handling for serialization.
082         */
083        private transient Paint backgroundPaint;
084    
085        /**
086         * The color used for the border around the window. This field is transient
087         * because it requires special handling for serialization.
088         */
089        private transient Paint foregroundPaint;
090    
091        /**
092         * The stroke for drawing the frame outline.  This field is transient
093         * because it requires special handling for serialization.
094         */
095        private transient Stroke stroke;
096    
097        /**
098         * The start angle.
099         */
100        private double startAngle;
101    
102        /**
103         * The end angle.
104         */
105        private double extent;
106    
107        /** The inner radius, relative to the framing rectangle. */
108        private double innerRadius;
109    
110        /** The outer radius, relative to the framing rectangle. */
111        private double outerRadius;
112    
113        /**
114         * Creates a new instance of <code>ArcDialFrame</code> that spans
115         * 180 degrees.
116         */
117        public ArcDialFrame() {
118            this(0, 180);
119        }
120    
121        /**
122         * Creates a new instance of <code>ArcDialFrame</code> that spans
123         * the arc specified.
124         *
125         * @param startAngle  the startAngle (in degrees).
126         * @param extent  the extent of the arc (in degrees, counter-clockwise).
127         */
128        public ArcDialFrame(double startAngle, double extent) {
129            this.backgroundPaint = Color.gray;
130            this.foregroundPaint = new Color(100, 100, 150);
131            this.stroke = new BasicStroke(2.0f);
132            this.innerRadius = 0.25;
133            this.outerRadius = 0.75;
134            this.startAngle = startAngle;
135            this.extent = extent;
136        }
137    
138        /**
139         * Returns the background paint (never <code>null</code>).
140         *
141         * @return The background paint.
142         *
143         * @see #setBackgroundPaint(Paint)
144         */
145        public Paint getBackgroundPaint() {
146            return this.backgroundPaint;
147        }
148    
149        /**
150         * Sets the background paint and sends a {@link DialLayerChangeEvent} to
151         * all registered listeners.
152         *
153         * @param paint  the paint (<code>null</code> not permitted).
154         *
155         * @see #getBackgroundPaint()
156         */
157        public void setBackgroundPaint(Paint paint) {
158            if (paint == null) {
159                throw new IllegalArgumentException("Null 'paint' argument.");
160            }
161            this.backgroundPaint = paint;
162            notifyListeners(new DialLayerChangeEvent(this));
163        }
164    
165        /**
166         * Returns the foreground paint.
167         *
168         * @return The foreground paint (never <code>null</code>).
169         *
170         * @see #setForegroundPaint(Paint)
171         */
172        public Paint getForegroundPaint() {
173            return this.foregroundPaint;
174        }
175    
176        /**
177         * Sets the foreground paint and sends a {@link DialLayerChangeEvent} to
178         * all registered listeners.
179         *
180         * @param paint  the paint (<code>null</code> not permitted).
181         *
182         * @see #getForegroundPaint()
183         */
184        public void setForegroundPaint(Paint paint) {
185            if (paint == null) {
186                throw new IllegalArgumentException("Null 'paint' argument.");
187            }
188            this.foregroundPaint = paint;
189            notifyListeners(new DialLayerChangeEvent(this));
190        }
191    
192        /**
193         * Returns the stroke.
194         *
195         * @return The stroke (never <code>null</code>).
196         *
197         * @see #setStroke(Stroke)
198         */
199        public Stroke getStroke() {
200            return this.stroke;
201        }
202    
203        /**
204         * Sets the stroke and sends a {@link DialLayerChangeEvent} to
205         * all registered listeners.
206         *
207         * @param stroke  the stroke (<code>null</code> not permitted).
208         *
209         * @see #getStroke()
210         */
211        public void setStroke(Stroke stroke) {
212            if (stroke == null) {
213                throw new IllegalArgumentException("Null 'stroke' argument.");
214            }
215            this.stroke = stroke;
216            notifyListeners(new DialLayerChangeEvent(this));
217        }
218    
219        /**
220         * Returns the inner radius, relative to the framing rectangle.
221         *
222         * @return The inner radius.
223         *
224         * @see #setInnerRadius(double)
225         */
226        public double getInnerRadius() {
227            return this.innerRadius;
228        }
229    
230        /**
231         * Sets the inner radius and sends a {@link DialLayerChangeEvent} to
232         * all registered listeners.
233         *
234         * @param radius  the inner radius.
235         *
236         * @see #getInnerRadius()
237         */
238        public void setInnerRadius(double radius) {
239            if (radius < 0.0) {
240                throw new IllegalArgumentException("Negative 'radius' argument.");
241            }
242            this.innerRadius = radius;
243            notifyListeners(new DialLayerChangeEvent(this));
244        }
245    
246        /**
247         * Returns the outer radius, relative to the framing rectangle.
248         *
249         * @return The outer radius.
250         *
251         * @see #setOuterRadius(double)
252         */
253        public double getOuterRadius() {
254            return this.outerRadius;
255        }
256    
257        /**
258         * Sets the outer radius and sends a {@link DialLayerChangeEvent} to
259         * all registered listeners.
260         *
261         * @param radius  the outer radius.
262         *
263         * @see #getOuterRadius()
264         */
265        public void setOuterRadius(double radius) {
266            if (radius < 0.0) {
267                throw new IllegalArgumentException("Negative 'radius' argument.");
268            }
269            this.outerRadius = radius;
270            notifyListeners(new DialLayerChangeEvent(this));
271        }
272    
273        /**
274         * Returns the start angle.
275         *
276         * @return The start angle.
277         *
278         * @see #setStartAngle(double)
279         */
280        public double getStartAngle() {
281            return this.startAngle;
282        }
283    
284        /**
285         * Sets the start angle and sends a {@link DialLayerChangeEvent} to
286         * all registered listeners.
287         *
288         * @param angle  the angle.
289         *
290         * @see #getStartAngle()
291         */
292        public void setStartAngle(double angle) {
293            this.startAngle = angle;
294            notifyListeners(new DialLayerChangeEvent(this));
295        }
296    
297        /**
298         * Returns the extent.
299         *
300         * @return The extent.
301         *
302         * @see #setExtent(double)
303         */
304        public double getExtent() {
305            return this.extent;
306        }
307    
308        /**
309         * Sets the extent and sends a {@link DialLayerChangeEvent} to
310         * all registered listeners.
311         *
312         * @param extent  the extent.
313         *
314         * @see #getExtent()
315         */
316        public void setExtent(double extent) {
317            this.extent = extent;
318            notifyListeners(new DialLayerChangeEvent(this));
319        }
320    
321        /**
322         * Returns the shape for the window for this dial.  Some dial layers will
323         * request that their drawing be clipped within this window.
324         *
325         * @param frame  the reference frame (<code>null</code> not permitted).
326         *
327         * @return The shape of the dial's window.
328         */
329        public Shape getWindow(Rectangle2D frame) {
330    
331            Rectangle2D innerFrame = DialPlot.rectangleByRadius(frame,
332                    this.innerRadius, this.innerRadius);
333            Rectangle2D outerFrame = DialPlot.rectangleByRadius(frame,
334                    this.outerRadius, this.outerRadius);
335            Arc2D inner = new Arc2D.Double(innerFrame, this.startAngle,
336                    this.extent, Arc2D.OPEN);
337            Arc2D outer = new Arc2D.Double(outerFrame, this.startAngle
338                    + this.extent, -this.extent, Arc2D.OPEN);
339            GeneralPath p = new GeneralPath();
340            Point2D point1 = inner.getStartPoint();
341            p.moveTo((float) point1.getX(), (float) point1.getY());
342            p.append(inner, true);
343            p.append(outer, true);
344            p.closePath();
345            return p;
346    
347        }
348    
349        /**
350         * Returns the outer window.
351         *
352         * @param frame  the frame.
353         *
354         * @return The outer window.
355         */
356        protected Shape getOuterWindow(Rectangle2D frame) {
357            double radiusMargin = 0.02;
358            double angleMargin = 1.5;
359            Rectangle2D innerFrame = DialPlot.rectangleByRadius(frame,
360                    this.innerRadius - radiusMargin, this.innerRadius
361                    - radiusMargin);
362            Rectangle2D outerFrame = DialPlot.rectangleByRadius(frame,
363                    this.outerRadius + radiusMargin, this.outerRadius
364                    + radiusMargin);
365            Arc2D inner = new Arc2D.Double(innerFrame, this.startAngle
366                    - angleMargin, this.extent + 2 * angleMargin, Arc2D.OPEN);
367            Arc2D outer = new Arc2D.Double(outerFrame, this.startAngle
368                    + angleMargin + this.extent, -this.extent - 2 * angleMargin,
369                    Arc2D.OPEN);
370            GeneralPath p = new GeneralPath();
371            Point2D point1 = inner.getStartPoint();
372            p.moveTo((float) point1.getX(), (float) point1.getY());
373            p.append(inner, true);
374            p.append(outer, true);
375            p.closePath();
376            return p;
377        }
378    
379        /**
380         * Draws the frame.
381         *
382         * @param g2  the graphics target.
383         * @param plot  the plot.
384         * @param frame  the dial's reference frame.
385         * @param view  the dial's view rectangle.
386         */
387        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
388                Rectangle2D view) {
389    
390            Shape window = getWindow(frame);
391            Shape outerWindow = getOuterWindow(frame);
392    
393            Area area1 = new Area(outerWindow);
394            Area area2 = new Area(window);
395            area1.subtract(area2);
396            g2.setPaint(Color.lightGray);
397            g2.fill(area1);
398    
399            g2.setStroke(this.stroke);
400            g2.setPaint(this.foregroundPaint);
401            g2.draw(window);
402            g2.draw(outerWindow);
403    
404        }
405    
406        /**
407         * Returns <code>false</code> to indicate that this dial layer is not
408         * clipped to the dial window.
409         *
410         * @return <code>false</code>.
411         */
412        public boolean isClippedToWindow() {
413            return false;
414        }
415    
416        /**
417         * Tests this instance for equality with an arbitrary object.
418         *
419         * @param obj  the object (<code>null</code> permitted).
420         *
421         * @return A boolean.
422         */
423        public boolean equals(Object obj) {
424            if (obj == this) {
425                return true;
426            }
427            if (!(obj instanceof ArcDialFrame)) {
428                return false;
429            }
430            ArcDialFrame that = (ArcDialFrame) obj;
431            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
432                return false;
433            }
434            if (!PaintUtilities.equal(this.foregroundPaint, that.foregroundPaint)) {
435                return false;
436            }
437            if (this.startAngle != that.startAngle) {
438                return false;
439            }
440            if (this.extent != that.extent) {
441                return false;
442            }
443            if (this.innerRadius != that.innerRadius) {
444                return false;
445            }
446            if (this.outerRadius != that.outerRadius) {
447                return false;
448            }
449            if (!this.stroke.equals(that.stroke)) {
450                return false;
451            }
452            return super.equals(obj);
453        }
454    
455        /**
456         * Returns a hash code for this instance.
457         *
458         * @return The hash code.
459         */
460        public int hashCode() {
461            int result = 193;
462            long temp = Double.doubleToLongBits(this.startAngle);
463            result = 37 * result + (int) (temp ^ (temp >>> 32));
464            temp = Double.doubleToLongBits(this.extent);
465            result = 37 * result + (int) (temp ^ (temp >>> 32));
466            temp = Double.doubleToLongBits(this.innerRadius);
467            result = 37 * result + (int) (temp ^ (temp >>> 32));
468            temp = Double.doubleToLongBits(this.outerRadius);
469            result = 37 * result + (int) (temp ^ (temp >>> 32));
470            result = 37 * result + HashUtilities.hashCodeForPaint(
471                    this.backgroundPaint);
472            result = 37 * result + HashUtilities.hashCodeForPaint(
473                    this.foregroundPaint);
474            result = 37 * result + this.stroke.hashCode();
475            return result;
476        }
477    
478        /**
479         * Returns a clone of this instance.
480         *
481         * @return A clone.
482         *
483         * @throws CloneNotSupportedException if any attribute of this instance
484         *     cannot be cloned.
485         */
486        public Object clone() throws CloneNotSupportedException {
487            return super.clone();
488        }
489    
490        /**
491         * Provides serialization support.
492         *
493         * @param stream  the output stream.
494         *
495         * @throws IOException  if there is an I/O error.
496         */
497        private void writeObject(ObjectOutputStream stream) throws IOException {
498            stream.defaultWriteObject();
499            SerialUtilities.writePaint(this.backgroundPaint, stream);
500            SerialUtilities.writePaint(this.foregroundPaint, stream);
501            SerialUtilities.writeStroke(this.stroke, stream);
502        }
503    
504        /**
505         * Provides serialization support.
506         *
507         * @param stream  the input stream.
508         *
509         * @throws IOException  if there is an I/O error.
510         * @throws ClassNotFoundException  if there is a classpath problem.
511         */
512        private void readObject(ObjectInputStream stream)
513                throws IOException, ClassNotFoundException {
514            stream.defaultReadObject();
515            this.backgroundPaint = SerialUtilities.readPaint(stream);
516            this.foregroundPaint = SerialUtilities.readPaint(stream);
517            this.stroke = SerialUtilities.readStroke(stream);
518        }
519    
520    }