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