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
045package org.jfree.chart.plot.dial;
046
047import java.awt.BasicStroke;
048import java.awt.Color;
049import java.awt.Graphics2D;
050import java.awt.Paint;
051import java.awt.Shape;
052import java.awt.Stroke;
053import java.awt.geom.Arc2D;
054import java.awt.geom.Area;
055import java.awt.geom.GeneralPath;
056import java.awt.geom.Point2D;
057import java.awt.geom.Rectangle2D;
058import java.io.IOException;
059import java.io.ObjectInputStream;
060import java.io.ObjectOutputStream;
061import java.io.Serializable;
062
063import org.jfree.chart.HashUtilities;
064import org.jfree.io.SerialUtilities;
065import org.jfree.util.PaintUtilities;
066import org.jfree.util.PublicCloneable;
067
068/**
069 * A standard frame for the {@link DialPlot} class.
070 *
071 * @since 1.0.7
072 */
073public 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}