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 fireChangeEvent(); 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 fireChangeEvent(); 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 fireChangeEvent(); 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 fireChangeEvent(); 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 fireChangeEvent(); 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 fireChangeEvent(); 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 if ((needle != null) && (index < this.seriesNeedle.length)) { 497 this.seriesNeedle[index] = needle; 498 } 499 fireChangeEvent(); 500 } 501 502 /** 503 * Returns an array of dataset references for the plot. 504 * 505 * @return The dataset for the plot, cast as a ValueDataset. 506 * 507 * @see #addDataset(ValueDataset) 508 */ 509 public ValueDataset[] getDatasets() { 510 return this.datasets; 511 } 512 513 /** 514 * Adds a dataset to the compass. 515 * 516 * @param dataset the new dataset (<code>null</code> ignored). 517 * 518 * @see #addDataset(ValueDataset, MeterNeedle) 519 */ 520 public void addDataset(ValueDataset dataset) { 521 addDataset(dataset, null); 522 } 523 524 /** 525 * Adds a dataset to the compass. 526 * 527 * @param dataset the new dataset (<code>null</code> ignored). 528 * @param needle the needle (<code>null</code> permitted). 529 */ 530 public void addDataset(ValueDataset dataset, MeterNeedle needle) { 531 532 if (dataset != null) { 533 int i = this.datasets.length + 1; 534 ValueDataset[] t = new ValueDataset[i]; 535 MeterNeedle[] p = new MeterNeedle[i]; 536 i = i - 2; 537 for (; i >= 0; --i) { 538 t[i] = this.datasets[i]; 539 p[i] = this.seriesNeedle[i]; 540 } 541 i = this.datasets.length; 542 t[i] = dataset; 543 p[i] = ((needle != null) ? needle : p[i - 1]); 544 545 ValueDataset[] a = this.datasets; 546 MeterNeedle[] b = this.seriesNeedle; 547 this.datasets = t; 548 this.seriesNeedle = p; 549 550 for (--i; i >= 0; --i) { 551 a[i] = null; 552 b[i] = null; 553 } 554 dataset.addChangeListener(this); 555 } 556 } 557 558 /** 559 * Draws the plot on a Java 2D graphics device (such as the screen or a 560 * printer). 561 * 562 * @param g2 the graphics device. 563 * @param area the area within which the plot should be drawn. 564 * @param anchor the anchor point (<code>null</code> permitted). 565 * @param parentState the state from the parent plot, if there is one. 566 * @param info collects info about the drawing. 567 */ 568 public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 569 PlotState parentState, 570 PlotRenderingInfo info) { 571 572 int outerRadius = 0; 573 int innerRadius = 0; 574 int x1, y1, x2, y2; 575 double a; 576 577 if (info != null) { 578 info.setPlotArea(area); 579 } 580 581 // adjust for insets... 582 RectangleInsets insets = getInsets(); 583 insets.trim(area); 584 585 // draw the background 586 if (this.drawBorder) { 587 drawBackground(g2, area); 588 } 589 590 int midX = (int) (area.getWidth() / 2); 591 int midY = (int) (area.getHeight() / 2); 592 int radius = midX; 593 if (midY < midX) { 594 radius = midY; 595 } 596 --radius; 597 int diameter = 2 * radius; 598 599 midX += (int) area.getMinX(); 600 midY += (int) area.getMinY(); 601 602 this.circle1.setFrame(midX - radius, midY - radius, diameter, diameter); 603 this.circle2.setFrame( 604 midX - radius + 15, midY - radius + 15, 605 diameter - 30, diameter - 30 606 ); 607 g2.setPaint(this.rosePaint); 608 this.a1 = new Area(this.circle1); 609 this.a2 = new Area(this.circle2); 610 this.a1.subtract(this.a2); 611 g2.fill(this.a1); 612 613 g2.setPaint(this.roseCenterPaint); 614 x1 = diameter - 30; 615 g2.fillOval(midX - radius + 15, midY - radius + 15, x1, x1); 616 g2.setPaint(this.roseHighlightPaint); 617 g2.drawOval(midX - radius, midY - radius, diameter, diameter); 618 x1 = diameter - 20; 619 g2.drawOval(midX - radius + 10, midY - radius + 10, x1, x1); 620 x1 = diameter - 30; 621 g2.drawOval(midX - radius + 15, midY - radius + 15, x1, x1); 622 x1 = diameter - 80; 623 g2.drawOval(midX - radius + 40, midY - radius + 40, x1, x1); 624 625 outerRadius = radius - 20; 626 innerRadius = radius - 32; 627 for (int w = 0; w < 360; w += 15) { 628 a = Math.toRadians(w); 629 x1 = midX - ((int) (Math.sin(a) * innerRadius)); 630 x2 = midX - ((int) (Math.sin(a) * outerRadius)); 631 y1 = midY - ((int) (Math.cos(a) * innerRadius)); 632 y2 = midY - ((int) (Math.cos(a) * outerRadius)); 633 g2.drawLine(x1, y1, x2, y2); 634 } 635 636 g2.setPaint(this.roseHighlightPaint); 637 innerRadius = radius - 26; 638 outerRadius = 7; 639 for (int w = 45; w < 360; w += 90) { 640 a = Math.toRadians(w); 641 x1 = midX - ((int) (Math.sin(a) * innerRadius)); 642 y1 = midY - ((int) (Math.cos(a) * innerRadius)); 643 g2.fillOval(x1 - outerRadius, y1 - outerRadius, 2 * outerRadius, 644 2 * outerRadius); 645 } 646 647 /// Squares 648 for (int w = 0; w < 360; w += 90) { 649 a = Math.toRadians(w); 650 x1 = midX - ((int) (Math.sin(a) * innerRadius)); 651 y1 = midY - ((int) (Math.cos(a) * innerRadius)); 652 653 Polygon p = new Polygon(); 654 p.addPoint(x1 - outerRadius, y1); 655 p.addPoint(x1, y1 + outerRadius); 656 p.addPoint(x1 + outerRadius, y1); 657 p.addPoint(x1, y1 - outerRadius); 658 g2.fillPolygon(p); 659 } 660 661 /// Draw N, S, E, W 662 innerRadius = radius - 42; 663 Font f = getCompassFont(radius); 664 g2.setFont(f); 665 g2.drawString("N", midX - 5, midY - innerRadius + f.getSize()); 666 g2.drawString("S", midX - 5, midY + innerRadius - 5); 667 g2.drawString("W", midX - innerRadius + 5, midY + 5); 668 g2.drawString("E", midX + innerRadius - f.getSize(), midY + 5); 669 670 // plot the data (unless the dataset is null)... 671 y1 = radius / 2; 672 x1 = radius / 6; 673 Rectangle2D needleArea = new Rectangle2D.Double( 674 (midX - x1), (midY - y1), (2 * x1), (2 * y1) 675 ); 676 int x = this.seriesNeedle.length; 677 int current = 0; 678 double value = 0; 679 int i = (this.datasets.length - 1); 680 for (; i >= 0; --i) { 681 ValueDataset data = this.datasets[i]; 682 683 if (data != null && data.getValue() != null) { 684 value = (data.getValue().doubleValue()) 685 % this.revolutionDistance; 686 value = value / this.revolutionDistance * 360; 687 current = i % x; 688 this.seriesNeedle[current].draw(g2, needleArea, value); 689 } 690 } 691 692 if (this.drawBorder) { 693 drawOutline(g2, area); 694 } 695 696 } 697 698 /** 699 * Returns a short string describing the type of plot. 700 * 701 * @return A string describing the plot. 702 */ 703 public String getPlotType() { 704 return localizationResources.getString("Compass_Plot"); 705 } 706 707 /** 708 * Returns the legend items for the plot. For now, no legend is available 709 * - this method returns null. 710 * 711 * @return The legend items. 712 */ 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 public void zoom(double percent) { 723 // no zooming possible 724 } 725 726 /** 727 * Returns the font for the compass, adjusted for the size of the plot. 728 * 729 * @param radius the radius. 730 * 731 * @return The font. 732 */ 733 protected Font getCompassFont(int radius) { 734 float fontSize = radius / 10.0f; 735 if (fontSize < 8) { 736 fontSize = 8; 737 } 738 Font newFont = this.compassFont.deriveFont(fontSize); 739 return newFont; 740 } 741 742 /** 743 * Tests an object for equality with this plot. 744 * 745 * @param obj the object (<code>null</code> permitted). 746 * 747 * @return A boolean. 748 */ 749 public boolean equals(Object obj) { 750 if (obj == this) { 751 return true; 752 } 753 if (!(obj instanceof CompassPlot)) { 754 return false; 755 } 756 if (!super.equals(obj)) { 757 return false; 758 } 759 CompassPlot that = (CompassPlot) obj; 760 if (this.labelType != that.labelType) { 761 return false; 762 } 763 if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) { 764 return false; 765 } 766 if (this.drawBorder != that.drawBorder) { 767 return false; 768 } 769 if (!PaintUtilities.equal(this.roseHighlightPaint, 770 that.roseHighlightPaint)) { 771 return false; 772 } 773 if (!PaintUtilities.equal(this.rosePaint, that.rosePaint)) { 774 return false; 775 } 776 if (!PaintUtilities.equal(this.roseCenterPaint, 777 that.roseCenterPaint)) { 778 return false; 779 } 780 if (!ObjectUtilities.equal(this.compassFont, that.compassFont)) { 781 return false; 782 } 783 if (!Arrays.equals(this.seriesNeedle, that.seriesNeedle)) { 784 return false; 785 } 786 if (getRevolutionDistance() != that.getRevolutionDistance()) { 787 return false; 788 } 789 return true; 790 791 } 792 793 /** 794 * Returns a clone of the plot. 795 * 796 * @return A clone. 797 * 798 * @throws CloneNotSupportedException this class will not throw this 799 * exception, but subclasses (if any) might. 800 */ 801 public Object clone() throws CloneNotSupportedException { 802 803 CompassPlot clone = (CompassPlot) super.clone(); 804 if (this.circle1 != null) { 805 clone.circle1 = (Ellipse2D) this.circle1.clone(); 806 } 807 if (this.circle2 != null) { 808 clone.circle2 = (Ellipse2D) this.circle2.clone(); 809 } 810 if (this.a1 != null) { 811 clone.a1 = (Area) this.a1.clone(); 812 } 813 if (this.a2 != null) { 814 clone.a2 = (Area) this.a2.clone(); 815 } 816 if (this.rect1 != null) { 817 clone.rect1 = (Rectangle2D) this.rect1.clone(); 818 } 819 clone.datasets = (ValueDataset[]) this.datasets.clone(); 820 clone.seriesNeedle = (MeterNeedle[]) this.seriesNeedle.clone(); 821 822 // clone share data sets => add the clone as listener to the dataset 823 for (int i = 0; i < this.datasets.length; ++i) { 824 if (clone.datasets[i] != null) { 825 clone.datasets[i].addChangeListener(clone); 826 } 827 } 828 return clone; 829 830 } 831 832 /** 833 * Sets the count to complete one revolution. Can be arbitrarily set 834 * For degrees (the default) it is 360, for radians this is 2*Pi, etc 835 * 836 * @param size the count to complete one revolution. 837 * 838 * @see #getRevolutionDistance() 839 */ 840 public void setRevolutionDistance(double size) { 841 if (size > 0) { 842 this.revolutionDistance = size; 843 } 844 } 845 846 /** 847 * Gets the count to complete one revolution. 848 * 849 * @return The count to complete one revolution. 850 * 851 * @see #setRevolutionDistance(double) 852 */ 853 public double getRevolutionDistance() { 854 return this.revolutionDistance; 855 } 856 857 /** 858 * Provides serialization support. 859 * 860 * @param stream the output stream. 861 * 862 * @throws IOException if there is an I/O error. 863 */ 864 private void writeObject(ObjectOutputStream stream) throws IOException { 865 stream.defaultWriteObject(); 866 SerialUtilities.writePaint(this.rosePaint, stream); 867 SerialUtilities.writePaint(this.roseCenterPaint, stream); 868 SerialUtilities.writePaint(this.roseHighlightPaint, stream); 869 } 870 871 /** 872 * Provides serialization support. 873 * 874 * @param stream the input stream. 875 * 876 * @throws IOException if there is an I/O error. 877 * @throws ClassNotFoundException if there is a classpath problem. 878 */ 879 private void readObject(ObjectInputStream stream) 880 throws IOException, ClassNotFoundException { 881 stream.defaultReadObject(); 882 this.rosePaint = SerialUtilities.readPaint(stream); 883 this.roseCenterPaint = SerialUtilities.readPaint(stream); 884 this.roseHighlightPaint = SerialUtilities.readPaint(stream); 885 } 886 887 }