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