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