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