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    }