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