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