    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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-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}