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