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