    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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     * CompassPlot.java
    029     * ----------------
    030     * (C) Copyright 2002-2005, by the Australian Antarctic Division and
    031     * Contributors.
    032     *
    033     * Original Author:  Bryan Scott (for the Australian Antarctic Division);
    034     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    035     *                   Arnaud Lelievre;
    036     *
    037     * $Id: CompassPlot.java,v 1.11.2.3 2005/10/25 20:52:07 mungady Exp $
    038     *
    039     * Changes:
    040     * --------
    041     * 25-Sep-2002 : Version 1, contributed by Bryan Scott (DG);
    042     * 23-Jan-2003 : Removed one constructor (DG);
    043     * 26-Mar-2003 : Implemented Serializable (DG);
    044     * 27-Mar-2003 : Changed MeterDataset to ValueDataset (DG);
    045     * 21-Aug-2003 : Implemented Cloneable (DG);
    046     * 08-Sep-2003 : Added internationalization via use of properties
    047     *               resourceBundle (RFE 690236) (AL);
    048     * 09-Sep-2003 : Changed Color --> Paint (DG);
    049     * 15-Sep-2003 : Added null data value check (bug report 805009) (DG);
    050     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    051     * 16-Mar-2004 : Added support for revolutionDistance to enable support for
    052     *               other units than degrees.
    053     * 16-Mar-2004 : Enabled LongNeedle to rotate about center.
    054     * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
    055     * 17-Apr-2005 : Fixed bug in clone() method (DG);
    056     * 05-May-2005 : Updated draw() method parameters (DG);
    057     * 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
    058     * 16-Jun-2005 : Renamed getData() --> getDatasets() and
    059     *               addData() --> addDataset() (DG);
    060     *
    061     */
    062
    063    package org.jfree.chart.plot;
    064
    065    import java.awt.BasicStroke;
    066    import java.awt.Color;
    067    import java.awt.Font;
    068    import java.awt.Graphics2D;
    069    import java.awt.Paint;
    070    import java.awt.Polygon;
    071    import java.awt.Stroke;
    072    import java.awt.geom.Area;
    073    import java.awt.geom.Ellipse2D;
    074    import java.awt.geom.Point2D;
    075    import java.awt.geom.Rectangle2D;
    076    import java.io.Serializable;
    077    import java.util.Arrays;
    078    import java.util.ResourceBundle;
    079
    080    import org.jfree.chart.LegendItemCollection;
    081    import org.jfree.chart.event.PlotChangeEvent;
    082    import org.jfree.chart.needle.ArrowNeedle;
    083    import org.jfree.chart.needle.LineNeedle;
    084    import org.jfree.chart.needle.LongNeedle;
    085    import org.jfree.chart.needle.MeterNeedle;
    086    import org.jfree.chart.needle.MiddlePinNeedle;
    087    import org.jfree.chart.needle.PinNeedle;
    088    import org.jfree.chart.needle.PlumNeedle;
    089    import org.jfree.chart.needle.PointerNeedle;
    090    import org.jfree.chart.needle.ShipNeedle;
    091    import org.jfree.chart.needle.WindNeedle;
    092    import org.jfree.data.general.DefaultValueDataset;
    093    import org.jfree.data.general.ValueDataset;
    094    import org.jfree.ui.RectangleInsets;
    095    import org.jfree.util.ObjectUtilities;
    096    import org.jfree.util.PaintUtilities;
    097
    098    /**
    099     * A specialised plot that draws a compass to indicate a direction based on the
    100     * value from a {@link ValueDataset}.
    101     *
    102     * @author Bryan Scott
    103     */
    104    public class CompassPlot extends Plot implements Cloneable, Serializable {
    105
    106        /** For serialization. */
    107        private static final long serialVersionUID = 6924382802125527395L;
    108
    109        /** The default label font. */
    110        public static final Font DEFAULT_LABEL_FONT
    111            = new Font("SansSerif", Font.BOLD, 10);
    112
    113        /** A constant for the label type. */
    114        public static final int NO_LABELS = 0;
    115
    116        /** A constant for the label type. */
    117        public static final int VALUE_LABELS = 1;
    118
    119        /** The label type (NO_LABELS, VALUE_LABELS). */
    120        private int labelType;
    121
    122        /** The label font. */
    123        private Font labelFont;
    124
    125        /** A flag that controls whether or not a border is drawn. */
    126        private boolean drawBorder = false;
    127
    128        /** The rose highlight paint. */
    129        private Paint roseHighlightPaint = Color.black;
    130
    131        /** The rose paint. */
    132        private Paint rosePaint = Color.yellow;
    133
    134        /** The rose center paint. */
    135        private Paint roseCenterPaint = Color.white;
    136
    137        /** The compass font. */
    138        private Font compassFont = new Font("Arial", Font.PLAIN, 10);
    139
    140        /** A working shape. */
    141        private transient Ellipse2D circle1;
    142
    143        /** A working shape. */
    144        private transient Ellipse2D circle2;
    145
    146        /** A working area. */
    147        private transient Area a1;
    148
    149        /** A working area. */
    150        private transient Area a2;
    151
    152        /** A working shape. */
    153        private transient Rectangle2D rect1;
    154
    155        /** An array of value datasets. */
    156        private ValueDataset[] datasets = new ValueDataset[1];
    157
    158        /** An array of needles. */
    159        private MeterNeedle[] seriesNeedle = new MeterNeedle[1];
    160
    161        /** The resourceBundle for the localization. */
    162        protected static ResourceBundle localizationResources =
    163            ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
    164
    165        /** The count to complete one revolution.  Can be arbitaly set
    166         * For degrees (the default) it is 360, for radians this is 2*Pi, etc
    167         */
    168        protected double revolutionDistance = 360;
    169
    170        /**
    171         * Default constructor.
    172         */
    173        public CompassPlot() {
    174            this(new DefaultValueDataset());
    175        }
    176
    177        /**
    178         * Constructs a new compass plot.
    179         *
    180         * @param dataset  the dataset for the plot (<code>null</code> permitted).
    181         */
    182        public CompassPlot(ValueDataset dataset) {
    183            super();
    184            if (dataset != null) {
    185                this.datasets[0] = dataset;
    186                dataset.addChangeListener(this);
    187            }
    188            this.circle1 = new Ellipse2D.Double();
    189            this.circle2 = new Ellipse2D.Double();
    190            this.rect1   = new Rectangle2D.Double();
    191            setSeriesNeedle(0);
    192        }
    193
    194        /**
    195         * Returns the label type.  Defined by the constants: {@link #NO_LABELS}
    196         * and {@link #VALUE_LABELS}.
    197         *
    198         * @return The label type.
    199         */
    200        public int getLabelType() {
    201            return this.labelType;
    202        }
    203
    204        /**
    205         * Sets the label type (either {@link #NO_LABELS} or {@link #VALUE_LABELS}.
    206         *
    207         * @param type  the type.
    208         */
    209        public void setLabelType(int type) {
    210            if ((type != NO_LABELS) && (type != VALUE_LABELS)) {
    211                throw new IllegalArgumentException(
    212                    "MeterPlot.setLabelType(int): unrecognised type."
    213                );
    214            }
    215            if (this.labelType != type) {
    216                this.labelType = type;
    217                notifyListeners(new PlotChangeEvent(this));
    218            }
    219        }
    220
    221        /**
    222         * Returns the label font.
    223         *
    224         * @return The label font.
    225         */
    226        public Font getLabelFont() {
    227            return this.labelFont;
    228        }
    229
    230        /**
    231         * Sets the label font and sends a {@link PlotChangeEvent} to all
    232         * registered listeners.
    233         *
    234         * @param font  the new label font.
    235         */
    236        public void setLabelFont(Font font) {
    237            if (font == null) {
    238                throw new IllegalArgumentException("Null 'font' not allowed.");
    239            }
    240            this.labelFont = font;
    241            notifyListeners(new PlotChangeEvent(this));
    242        }
    243
    244        /**
    245         * Returns the paint used to fill the outer circle of the compass.
    246         *
    247         * @return The paint (never <code>null</code>).
    248         */
    249        public Paint getRosePaint() {
    250            return this.rosePaint;
    251        }
    252
    253        /**
    254         * Sets the paint used to fill the outer circle of the compass,
    255         * and sends a {@link PlotChangeEvent} to all registered listeners.
    256         *
    257         * @param paint  the paint (<code>null</code> not permitted).
    258         */
    259        public void setRosePaint(Paint paint) {
    260            if (paint == null) {
    261                throw new IllegalArgumentException("Null 'paint' argument.");
    262            }
    263            this.rosePaint = paint;
    264            notifyListeners(new PlotChangeEvent(this));
    265        }
    266
    267        /**
    268         * Returns the paint used to fill the inner background area of the
    269         * compass.
    270         *
    271         * @return The paint (never <code>null</code>).
    272         */
    273        public Paint getRoseCenterPaint() {
    274            return this.roseCenterPaint;
    275        }
    276
    277        /**
    278         * Sets the paint used to fill the inner background area of the compass,
    279         * and sends a {@link PlotChangeEvent} to all registered listeners.
    280         *
    281         * @param paint  the paint (<code>null</code> not permitted).
    282         */
    283        public void setRoseCenterPaint(Paint paint) {
    284            if (paint == null) {
    285                throw new IllegalArgumentException("Null 'paint' argument.");
    286            }
    287            this.roseCenterPaint = paint;
    288            notifyListeners(new PlotChangeEvent(this));
    289        }
    290
    291        /**
    292         * Returns the paint used to draw the circles, symbols and labels on the
    293         * compass.
    294         *
    295         * @return The paint (never <code>null</code>).
    296         */
    297        public Paint getRoseHighlightPaint() {
    298            return this.roseHighlightPaint;
    299        }
    300
    301        /**
    302         * Sets the paint used to draw the circles, symbols and labels of the
    303         * compass, and sends a {@link PlotChangeEvent} to all registered listeners.
    304         *
    305         * @param paint  the paint (<code>null</code> not permitted).
    306         */
    307        public void setRoseHighlightPaint(Paint paint) {
    308            if (paint == null) {
    309                throw new IllegalArgumentException("Null 'paint' argument.");
    310            }
    311            this.roseHighlightPaint = paint;
    312            notifyListeners(new PlotChangeEvent(this));
    313        }
    314
    315        /**
    316         * Returns a flag that controls whether or not a border is drawn.
    317         *
    318         * @return The flag.
    319         */
    320        public boolean getDrawBorder() {
    321            return this.drawBorder;
    322        }
    323
    324        /**
    325         * Sets a flag that controls whether or not a border is drawn.
    326         *
    327         * @param status  the flag status.
    328         */
    329        public void setDrawBorder(boolean status) {
    330            this.drawBorder = status;
    331        }
    332
    333        /**
    334         * Sets the series paint.
    335         *
    336         * @param series  the series index.
    337         * @param paint  the paint.
    338         */
    339        public void setSeriesPaint(int series, Paint paint) {
    340           // super.setSeriesPaint(series, paint);
    341            if ((series >= 0) && (series < this.seriesNeedle.length)) {
    342                this.seriesNeedle[series].setFillPaint(paint);
    343            }
    344        }
    345
    346        /**
    347         * Sets the series outline paint.
    348         *
    349         * @param series  the series index.
    350         * @param p  the paint.
    351         */
    352        public void setSeriesOutlinePaint(int series, Paint p) {
    353
    354            if ((series >= 0) && (series < this.seriesNeedle.length)) {
    355                this.seriesNeedle[series].setOutlinePaint(p);
    356            }
    357
    358        }
    359
    360        /**
    361         * Sets the series outline stroke.
    362         *
    363         * @param series  the series index.
    364         * @param stroke  the stroke.
    365         */
    366        public void setSeriesOutlineStroke(int series, Stroke stroke) {
    367
    368          if ((series >= 0) && (series < this.seriesNeedle.length)) {
    369            this.seriesNeedle[series].setOutlineStroke(stroke);
    370          }
    371
    372        }
    373
    374        /**
    375         * Sets the needle type.
    376         *
    377         * @param type  the type.
    378         */
    379        public void setSeriesNeedle(int type) {
    380            setSeriesNeedle(0, type);
    381        }
    382
    383        /**
    384         * Sets the needle for a series.  The needle type is one of the following:
    385         * <ul>
    386         * <li>0 = {@link ArrowNeedle};</li>
    387         * <li>1 = {@link LineNeedle};</li>
    388         * <li>2 = {@link LongNeedle};</li>
    389         * <li>3 = {@link PinNeedle};</li>
    390         * <li>4 = {@link PlumNeedle};</li>
    391         * <li>5 = {@link PointerNeedle};</li>
    392         * <li>6 = {@link ShipNeedle};</li>
    393         * <li>7 = {@link WindNeedle};</li>
    394         * <li>8 = {@link ArrowNeedle};</li>
    395         * <li>9 = {@link MiddlePinNeedle};</li>
    396         * </ul>
    397         * @param index  the series index.
    398         * @param type  the needle type.
    399         */
    400        public void setSeriesNeedle(int index, int type) {
    401            switch (type) {
    402                case 0:
    403                    setSeriesNeedle(index, new ArrowNeedle(true));
    404                    setSeriesPaint(index, Color.red);
    405                    this.seriesNeedle[index].setHighlightPaint(Color.white);
    406                    break;
    407                case 1:
    408                    setSeriesNeedle(index, new LineNeedle());
    409                    break;
    410                case 2:
    411                    MeterNeedle longNeedle = new LongNeedle();
    412                    longNeedle.setRotateY(0.5);
    413                    setSeriesNeedle(index, longNeedle);
    414                    break;
    415                case 3:
    416                    setSeriesNeedle(index, new PinNeedle());
    417                    break;
    418                case 4:
    419                    setSeriesNeedle(index, new PlumNeedle());
    420                    break;
    421                case 5:
    422                    setSeriesNeedle(index, new PointerNeedle());
    423                    break;
    424                case 6:
    425                    setSeriesPaint(index, null);
    426                    setSeriesOutlineStroke(index, new BasicStroke(3));
    427                    setSeriesNeedle(index, new ShipNeedle());
    428                    break;
    429                case 7:
    430                    setSeriesPaint(index, Color.blue);
    431                    setSeriesNeedle(index, new WindNeedle());
    432                    break;
    433                case 8:
    434                    setSeriesNeedle(index, new ArrowNeedle(true));
    435                    break;
    436                case 9:
    437                    setSeriesNeedle(index, new MiddlePinNeedle());
    438                    break;
    439
    440                default:
    441                    throw new IllegalArgumentException("Unrecognised type.");
    442            }
    443
    444        }
    445
    446        /**
    447         * Sets the needle for a series.
    448         *
    449         * @param index  the series index.
    450         * @param needle  the needle.
    451         */
    452        public void setSeriesNeedle(int index, MeterNeedle needle) {
    453
    454            if ((needle != null) && (index < this.seriesNeedle.length)) {
    455                this.seriesNeedle[index] = needle;
    456            }
    457            notifyListeners(new PlotChangeEvent(this));
    458
    459        }
    460
    461        /**
    462         * Returns the dataset.
    463         * <P>
    464         * Provided for convenience.
    465         *
    466         * @return The dataset for the plot, cast as a ValueDataset.
    467         */
    468        public ValueDataset[] getDatasets() {
    469            return this.datasets;
    470        }
    471
    472        /**
    473         * Adds a dataset to the compass.
    474         *
    475         * @param dataset  the new dataset.
    476         */
    477        public void addDataset(ValueDataset dataset) {
    478            addDataset(dataset, null);
    479        }
    480
    481        /**
    482         * Adds a dataset to the compass.
    483         *
    484         * @param dataset  the new dataset.
    485         * @param needle  the needle.
    486         */
    487        public void addDataset(ValueDataset dataset, MeterNeedle needle) {
    488
    489            if (dataset != null) {
    490                int i = this.datasets.length + 1;
    491                ValueDataset[] t = new ValueDataset[i];
    492                MeterNeedle[] p = new MeterNeedle[i];
    493                i = i - 2;
    494                for (; i >= 0; --i) {
    495                    t[i] = this.datasets[i];
    496                    p[i] = this.seriesNeedle[i];
    497                }
    498                i = this.datasets.length;
    499                t[i] = dataset;
    500                p[i] = ((needle != null) ? needle : p[i - 1]);
    501
    502                ValueDataset[] a = this.datasets;
    503                MeterNeedle[] b = this.seriesNeedle;
    504                this.datasets = t;
    505                this.seriesNeedle = p;
    506
    507                for (--i; i >= 0; --i) {
    508                    a[i] = null;
    509                    b[i] = null;
    510                }
    511                dataset.addChangeListener(this);
    512            }
    513        }
    514
    515        /**
    516         * Draws the plot on a Java 2D graphics device (such as the screen or a
    517         * printer).
    518         *
    519         * @param g2  the graphics device.
    520         * @param area  the area within which the plot should be drawn.
    521         * @param anchor  the anchor point (<code>null</code> permitted).
    522         * @param parentState  the state from the parent plot, if there is one.
    523         * @param info  collects info about the drawing.
    524         */
    525        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
    526                         PlotState parentState,
    527                         PlotRenderingInfo info) {
    528
    529            int outerRadius = 0;
    530            int innerRadius = 0;
    531            int x1, y1, x2, y2;
    532            double a;
    533
    534            if (info != null) {
    535                info.setPlotArea(area);
    536            }
    537
    538            // adjust for insets...
    539            RectangleInsets insets = getInsets();
    540            insets.trim(area);
    541
    542            // draw the background
    543            if (this.drawBorder) {
    544                drawBackground(g2, area);
    545            }
    546
    547            int midX = (int) (area.getWidth() / 2);
    548            int midY = (int) (area.getHeight() / 2);
    549            int radius = midX;
    550            if (midY < midX) {
    551                radius = midY;
    552            }
    553            --radius;
    554            int diameter = 2 * radius;
    555
    556            midX += (int) area.getMinX();
    557            midY += (int) area.getMinY();
    558
    559            this.circle1.setFrame(midX - radius, midY - radius, diameter, diameter);
    560            this.circle2.setFrame(
    561                midX - radius + 15, midY - radius + 15,
    562                diameter - 30, diameter - 30
    563            );
    564            g2.setPaint(this.rosePaint);
    565            this.a1 = new Area(this.circle1);
    566            this.a2 = new Area(this.circle2);
    567            this.a1.subtract(this.a2);
    568            g2.fill(this.a1);
    569
    570            g2.setPaint(this.roseCenterPaint);
    571            x1 = diameter - 30;
    572            g2.fillOval(midX - radius + 15, midY - radius + 15, x1, x1);
    573            g2.setPaint(this.roseHighlightPaint);
    574            g2.drawOval(midX - radius, midY - radius, diameter, diameter);
    575            x1 = diameter - 20;
    576            g2.drawOval(midX - radius + 10, midY - radius + 10, x1, x1);
    577            x1 = diameter - 30;
    578            g2.drawOval(midX - radius + 15, midY - radius + 15, x1, x1);
    579            x1 = diameter - 80;
    580            g2.drawOval(midX - radius + 40, midY - radius + 40, x1, x1);
    581
    582            outerRadius = radius - 20;
    583            innerRadius = radius - 32;
    584            for (int w = 0; w < 360; w += 15) {
    585                a = Math.toRadians(w);
    586                x1 = midX - ((int) (Math.sin(a) * innerRadius));
    587                x2 = midX - ((int) (Math.sin(a) * outerRadius));
    588                y1 = midY - ((int) (Math.cos(a) * innerRadius));
    589                y2 = midY - ((int) (Math.cos(a) * outerRadius));
    590                g2.drawLine(x1, y1, x2, y2);
    591            }
    592
    593            g2.setPaint(this.roseHighlightPaint);
    594            innerRadius = radius - 26;
    595            outerRadius = 7;
    596            for (int w = 45; w < 360; w += 90) {
    597                a = Math.toRadians(w);
    598                x1 = midX - ((int) (Math.sin(a) * innerRadius));
    599                y1 = midY - ((int) (Math.cos(a) * innerRadius));
    600                g2.fillOval(
    601                    x1 - outerRadius, y1 - outerRadius,
    602                    2 * outerRadius, 2 * outerRadius
    603                );
    604            }
    605
    606            /// Squares
    607            for (int w = 0; w < 360; w += 90) {
    608                a = Math.toRadians(w);
    609                x1 = midX - ((int) (Math.sin(a) * innerRadius));
    610                y1 = midY - ((int) (Math.cos(a) * innerRadius));
    611
    612                Polygon p = new Polygon();
    613                p.addPoint(x1 - outerRadius, y1);
    614                p.addPoint(x1, y1 + outerRadius);
    615                p.addPoint(x1 + outerRadius, y1);
    616                p.addPoint(x1, y1 - outerRadius);
    617                g2.fillPolygon(p);
    618            }
    619
    620            /// Draw N, S, E, W
    621            innerRadius = radius - 42;
    622            Font f = getCompassFont(radius);
    623            g2.setFont(f);
    624            g2.drawString("N", midX - 5, midY - innerRadius + f.getSize());
    625            g2.drawString("S", midX - 5, midY + innerRadius - 5);
    626            g2.drawString("W", midX - innerRadius + 5, midY + 5);
    627            g2.drawString("E", midX + innerRadius - f.getSize(), midY + 5);
    628
    629            // plot the data (unless the dataset is null)...
    630            y1 = radius / 2;
    631            x1 = radius / 6;
    632            Rectangle2D needleArea = new Rectangle2D.Double(
    633                (midX - x1), (midY - y1), (2 * x1), (2 * y1)
    634            );
    635            int x = this.seriesNeedle.length;
    636            int current = 0;
    637            double value = 0;
    638            int i = (this.datasets.length - 1);
    639            for (; i >= 0; --i) {
    640                ValueDataset data = this.datasets[i];
    641
    642                if (data != null && data.getValue() != null) {
    643                    value = (data.getValue().doubleValue())
    644                        % this.revolutionDistance;
    645                    value = value / this.revolutionDistance * 360;
    646                    current = i % x;
    647                    this.seriesNeedle[current].draw(g2, needleArea, value);
    648                }
    649            }
    650
    651            if (this.drawBorder) {
    652                drawOutline(g2, area);
    653            }
    654
    655        }
    656
    657        /**
    658         * Returns a short string describing the type of plot.
    659         *
    660         * @return A string describing the plot.
    661         */
    662        public String getPlotType() {
    663            return localizationResources.getString("Compass_Plot");
    664        }
    665
    666        /**
    667         * Returns the legend items for the plot.  For now, no legend is available
    668         * - this method returns null.
    669         *
    670         * @return The legend items.
    671         */
    672        public LegendItemCollection getLegendItems() {
    673            return null;
    674        }
    675
    676        /**
    677         * No zooming is implemented for compass plot, so this method is empty.
    678         *
    679         * @param percent  the zoom amount.
    680         */
    681        public void zoom(double percent) {
    682            // no zooming possible
    683        }
    684
    685        /**
    686         * Returns the font for the compass, adjusted for the size of the plot.
    687         *
    688         * @param radius the radius.
    689         *
    690         * @return The font.
    691         */
    692        protected Font getCompassFont(int radius) {
    693            float fontSize = radius / 10.0f;
    694            if (fontSize < 8) {
    695                fontSize = 8;
    696            }
    697            Font newFont = this.compassFont.deriveFont(fontSize);
    698            return newFont;
    699        }
    700
    701        /**
    702         * Tests an object for equality with this plot.
    703         *
    704         * @param obj  the object (<code>null</code> permitted).
    705         *
    706         * @return A boolean.
    707         */
    708        public boolean equals(Object obj) {
    709            if (obj == this) {
    710                return true;
    711            }
    712            if (!(obj instanceof CompassPlot)) {
    713                return false;
    714            }
    715            if (!super.equals(obj)) {
    716                return false;
    717            }
    718            CompassPlot that = (CompassPlot) obj;
    719            if (this.labelType != that.labelType) {
    720                return false;
    721            }
    722            if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
    723                return false;
    724            }
    725            if (this.drawBorder != that.drawBorder) {
    726                return false;
    727            }
    728            if (!PaintUtilities.equal(this.roseHighlightPaint,
    729                    that.roseHighlightPaint)) {
    730                return false;
    731            }
    732            if (!PaintUtilities.equal(this.rosePaint, that.rosePaint)) {
    733                return false;
    734            }
    735            if (!PaintUtilities.equal(this.roseCenterPaint,
    736                    that.roseCenterPaint)) {
    737                return false;
    738            }
    739            if (!ObjectUtilities.equal(this.compassFont, that.compassFont)) {
    740                return false;
    741            }
    742            if (!Arrays.equals(this.seriesNeedle, that.seriesNeedle)) {
    743                return false;
    744            }
    745            if (getRevolutionDistance() != that.getRevolutionDistance()) {
    746                return false;
    747            }
    748            return true;
    749
    750        }
    751
    752        /**
    753         * Returns a clone of the annotation.
    754         *
    755         * @return A clone.
    756         *
    757         * @throws CloneNotSupportedException  this class will not throw this
    758         *         exception, but subclasses (if any) might.
    759         */
    760        public Object clone() throws CloneNotSupportedException {
    761
    762            CompassPlot clone = (CompassPlot) super.clone();
    763            //private int labelType <-- primitive
    764            //private Font labelFont <-- immutable
    765            //private boolean drawBorder = false <-- primitive
    766            //private Color roseHighlightColour <-- immutable
    767            //private Color roseColour <-- immutable
    768            //private Color roseCenterColour <-- immutable
    769            //private Font compassFont <-- immutable
    770            if (this.circle1 != null) {
    771                clone.circle1 = (Ellipse2D) this.circle1.clone();
    772            }
    773            if (this.circle2 != null) {
    774                clone.circle2 = (Ellipse2D) this.circle2.clone();
    775            }
    776            if (this.a1 != null) {
    777                clone.a1 = (Area) this.a1.clone();
    778            }
    779            if (this.a2 != null) {
    780                clone.a2 = (Area) this.a2.clone();
    781            }
    782            if (this.rect1 != null) {
    783                clone.rect1 = (Rectangle2D) this.rect1.clone();
    784            }
    785            clone.datasets = (ValueDataset[]) this.datasets.clone();
    786            clone.seriesNeedle = (MeterNeedle[]) this.seriesNeedle.clone();
    787
    788            // clone share data sets => add the clone as listener to the dataset
    789            for (int i = 0; i < this.datasets.length; ++i) {
    790                if (clone.datasets[i] != null) {
    791                    clone.datasets[i].addChangeListener(clone);
    792                }
    793            }
    794            return clone;
    795
    796        }
    797
    798        /**
    799         * Sets the count to complete one revolution.  Can be arbitaly set
    800         * For degrees (the default) it is 360, for radians this is 2*Pi, etc
    801         *
    802         * @param size the count to complete one revolution.
    803         */
    804        public void setRevolutionDistance(double size) {
    805            if (size > 0) {
    806                this.revolutionDistance = size;
    807            }
    808        }
    809
    810        /**
    811         * Gets the count to complete one revolution.
    812         *
    813         * @return The count to complete one revolution
    814         */
    815        public double getRevolutionDistance() {
    816            return this.revolutionDistance;
    817        }
    818    }