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.5 2007/01/17 11:59:42 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 * 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.Serializable; 077 import java.util.Arrays; 078 import java.util.ResourceBundle; 079 080 import org.jfree.chart.LegendItemCollection; 081 import org.jfree.chart.event.PlotChangeEvent; 082 import org.jfree.chart.needle.ArrowNeedle; 083 import org.jfree.chart.needle.LineNeedle; 084 import org.jfree.chart.needle.LongNeedle; 085 import org.jfree.chart.needle.MeterNeedle; 086 import org.jfree.chart.needle.MiddlePinNeedle; 087 import org.jfree.chart.needle.PinNeedle; 088 import org.jfree.chart.needle.PlumNeedle; 089 import org.jfree.chart.needle.PointerNeedle; 090 import org.jfree.chart.needle.ShipNeedle; 091 import org.jfree.chart.needle.WindNeedle; 092 import org.jfree.data.general.DefaultValueDataset; 093 import org.jfree.data.general.ValueDataset; 094 import org.jfree.ui.RectangleInsets; 095 import org.jfree.util.ObjectUtilities; 096 import org.jfree.util.PaintUtilities; 097 098 /** 099 * A specialised plot that draws a compass to indicate a direction based on the 100 * value from a {@link ValueDataset}. 101 */ 102 public class CompassPlot extends Plot implements Cloneable, Serializable { 103 104 /** For serialization. */ 105 private static final long serialVersionUID = 6924382802125527395L; 106 107 /** The default label font. */ 108 public static final Font DEFAULT_LABEL_FONT 109 = new Font("SansSerif", Font.BOLD, 10); 110 111 /** A constant for the label type. */ 112 public static final int NO_LABELS = 0; 113 114 /** A constant for the label type. */ 115 public static final int VALUE_LABELS = 1; 116 117 /** The label type (NO_LABELS, VALUE_LABELS). */ 118 private int labelType; 119 120 /** The label font. */ 121 private Font labelFont; 122 123 /** A flag that controls whether or not a border is drawn. */ 124 private boolean drawBorder = false; 125 126 /** The rose highlight paint. */ 127 private Paint roseHighlightPaint = Color.black; 128 129 /** The rose paint. */ 130 private Paint rosePaint = Color.yellow; 131 132 /** The rose center paint. */ 133 private Paint roseCenterPaint = Color.white; 134 135 /** The compass font. */ 136 private Font compassFont = new Font("Arial", Font.PLAIN, 10); 137 138 /** A working shape. */ 139 private transient Ellipse2D circle1; 140 141 /** A working shape. */ 142 private transient Ellipse2D circle2; 143 144 /** A working area. */ 145 private transient Area a1; 146 147 /** A working area. */ 148 private transient Area a2; 149 150 /** A working shape. */ 151 private transient Rectangle2D rect1; 152 153 /** An array of value datasets. */ 154 private ValueDataset[] datasets = new ValueDataset[1]; 155 156 /** An array of needles. */ 157 private MeterNeedle[] seriesNeedle = new MeterNeedle[1]; 158 159 /** The resourceBundle for the localization. */ 160 protected static ResourceBundle localizationResources = 161 ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle"); 162 163 /** The count to complete one revolution. Can be arbitaly set 164 * For degrees (the default) it is 360, for radians this is 2*Pi, etc 165 */ 166 protected double revolutionDistance = 360; 167 168 /** 169 * Default constructor. 170 */ 171 public CompassPlot() { 172 this(new DefaultValueDataset()); 173 } 174 175 /** 176 * Constructs a new compass plot. 177 * 178 * @param dataset the dataset for the plot (<code>null</code> permitted). 179 */ 180 public CompassPlot(ValueDataset dataset) { 181 super(); 182 if (dataset != null) { 183 this.datasets[0] = dataset; 184 dataset.addChangeListener(this); 185 } 186 this.circle1 = new Ellipse2D.Double(); 187 this.circle2 = new Ellipse2D.Double(); 188 this.rect1 = new Rectangle2D.Double(); 189 setSeriesNeedle(0); 190 } 191 192 /** 193 * Returns the label type. Defined by the constants: {@link #NO_LABELS} 194 * and {@link #VALUE_LABELS}. 195 * 196 * @return The label type. 197 * 198 * @see #setLabelType(int) 199 */ 200 public int getLabelType() { 201 // FIXME: this attribute is never used - deprecate? 202 return this.labelType; 203 } 204 205 /** 206 * Sets the label type (either {@link #NO_LABELS} or {@link #VALUE_LABELS}. 207 * 208 * @param type the type. 209 * 210 * @see #getLabelType() 211 */ 212 public void setLabelType(int type) { 213 // FIXME: this attribute is never used - deprecate? 214 if ((type != NO_LABELS) && (type != VALUE_LABELS)) { 215 throw new IllegalArgumentException( 216 "MeterPlot.setLabelType(int): unrecognised type." 217 ); 218 } 219 if (this.labelType != type) { 220 this.labelType = type; 221 notifyListeners(new PlotChangeEvent(this)); 222 } 223 } 224 225 /** 226 * Returns the label font. 227 * 228 * @return The label font. 229 * 230 * @see #setLabelFont(Font) 231 */ 232 public Font getLabelFont() { 233 // FIXME: this attribute is not used - deprecate? 234 return this.labelFont; 235 } 236 237 /** 238 * Sets the label font and sends a {@link PlotChangeEvent} to all 239 * registered listeners. 240 * 241 * @param font the new label font. 242 * 243 * @see #getLabelFont() 244 */ 245 public void setLabelFont(Font font) { 246 // FIXME: this attribute is not used - deprecate? 247 if (font == null) { 248 throw new IllegalArgumentException("Null 'font' not allowed."); 249 } 250 this.labelFont = font; 251 notifyListeners(new PlotChangeEvent(this)); 252 } 253 254 /** 255 * Returns the paint used to fill the outer circle of the compass. 256 * 257 * @return The paint (never <code>null</code>). 258 * 259 * @see #setRosePaint(Paint) 260 */ 261 public Paint getRosePaint() { 262 return this.rosePaint; 263 } 264 265 /** 266 * Sets the paint used to fill the outer circle of the compass, 267 * and sends a {@link PlotChangeEvent} to all registered listeners. 268 * 269 * @param paint the paint (<code>null</code> not permitted). 270 * 271 * @see #getRosePaint() 272 */ 273 public void setRosePaint(Paint paint) { 274 if (paint == null) { 275 throw new IllegalArgumentException("Null 'paint' argument."); 276 } 277 this.rosePaint = paint; 278 notifyListeners(new PlotChangeEvent(this)); 279 } 280 281 /** 282 * Returns the paint used to fill the inner background area of the 283 * compass. 284 * 285 * @return The paint (never <code>null</code>). 286 * 287 * @see #setRoseCenterPaint(Paint) 288 */ 289 public Paint getRoseCenterPaint() { 290 return this.roseCenterPaint; 291 } 292 293 /** 294 * Sets the paint used to fill the inner background area of the compass, 295 * and sends a {@link PlotChangeEvent} to all registered listeners. 296 * 297 * @param paint the paint (<code>null</code> not permitted). 298 * 299 * @see #getRoseCenterPaint() 300 */ 301 public void setRoseCenterPaint(Paint paint) { 302 if (paint == null) { 303 throw new IllegalArgumentException("Null 'paint' argument."); 304 } 305 this.roseCenterPaint = paint; 306 notifyListeners(new PlotChangeEvent(this)); 307 } 308 309 /** 310 * Returns the paint used to draw the circles, symbols and labels on the 311 * compass. 312 * 313 * @return The paint (never <code>null</code>). 314 * 315 * @see #setRoseHighlightPaint(Paint) 316 */ 317 public Paint getRoseHighlightPaint() { 318 return this.roseHighlightPaint; 319 } 320 321 /** 322 * Sets the paint used to draw the circles, symbols and labels of the 323 * compass, and sends a {@link PlotChangeEvent} to all registered listeners. 324 * 325 * @param paint the paint (<code>null</code> not permitted). 326 * 327 * @see #getRoseHighlightPaint() 328 */ 329 public void setRoseHighlightPaint(Paint paint) { 330 if (paint == null) { 331 throw new IllegalArgumentException("Null 'paint' argument."); 332 } 333 this.roseHighlightPaint = paint; 334 notifyListeners(new PlotChangeEvent(this)); 335 } 336 337 /** 338 * Returns a flag that controls whether or not a border is drawn. 339 * 340 * @return The flag. 341 * 342 * @see #setDrawBorder(boolean) 343 */ 344 public boolean getDrawBorder() { 345 return this.drawBorder; 346 } 347 348 /** 349 * Sets a flag that controls whether or not a border is drawn. 350 * 351 * @param status the flag status. 352 * 353 * @see #getDrawBorder() 354 */ 355 public void setDrawBorder(boolean status) { 356 // FIXME: this should trigger a plot change event 357 this.drawBorder = status; 358 } 359 360 /** 361 * Sets the series paint. 362 * 363 * @param series the series index. 364 * @param paint the paint. 365 * 366 * @see #setSeriesOutlinePaint(int, Paint) 367 */ 368 public void setSeriesPaint(int series, Paint paint) { 369 // super.setSeriesPaint(series, paint); 370 if ((series >= 0) && (series < this.seriesNeedle.length)) { 371 this.seriesNeedle[series].setFillPaint(paint); 372 } 373 } 374 375 /** 376 * Sets the series outline paint. 377 * 378 * @param series the series index. 379 * @param p the paint. 380 * 381 * @see #setSeriesPaint(int, Paint) 382 */ 383 public void setSeriesOutlinePaint(int series, Paint p) { 384 385 if ((series >= 0) && (series < this.seriesNeedle.length)) { 386 this.seriesNeedle[series].setOutlinePaint(p); 387 } 388 389 } 390 391 /** 392 * Sets the series outline stroke. 393 * 394 * @param series the series index. 395 * @param stroke the stroke. 396 * 397 * @see #setSeriesOutlinePaint(int, Paint) 398 */ 399 public void setSeriesOutlineStroke(int series, Stroke stroke) { 400 401 if ((series >= 0) && (series < this.seriesNeedle.length)) { 402 this.seriesNeedle[series].setOutlineStroke(stroke); 403 } 404 405 } 406 407 /** 408 * Sets the needle type. 409 * 410 * @param type the type. 411 * 412 * @see #setSeriesNeedle(int, int) 413 */ 414 public void setSeriesNeedle(int type) { 415 setSeriesNeedle(0, type); 416 } 417 418 /** 419 * Sets the needle for a series. The needle type is one of the following: 420 * <ul> 421 * <li>0 = {@link ArrowNeedle};</li> 422 * <li>1 = {@link LineNeedle};</li> 423 * <li>2 = {@link LongNeedle};</li> 424 * <li>3 = {@link PinNeedle};</li> 425 * <li>4 = {@link PlumNeedle};</li> 426 * <li>5 = {@link PointerNeedle};</li> 427 * <li>6 = {@link ShipNeedle};</li> 428 * <li>7 = {@link WindNeedle};</li> 429 * <li>8 = {@link ArrowNeedle};</li> 430 * <li>9 = {@link MiddlePinNeedle};</li> 431 * </ul> 432 * @param index the series index. 433 * @param type the needle type. 434 * 435 * @see #setSeriesNeedle(int) 436 */ 437 public void setSeriesNeedle(int index, int type) { 438 switch (type) { 439 case 0: 440 setSeriesNeedle(index, new ArrowNeedle(true)); 441 setSeriesPaint(index, Color.red); 442 this.seriesNeedle[index].setHighlightPaint(Color.white); 443 break; 444 case 1: 445 setSeriesNeedle(index, new LineNeedle()); 446 break; 447 case 2: 448 MeterNeedle longNeedle = new LongNeedle(); 449 longNeedle.setRotateY(0.5); 450 setSeriesNeedle(index, longNeedle); 451 break; 452 case 3: 453 setSeriesNeedle(index, new PinNeedle()); 454 break; 455 case 4: 456 setSeriesNeedle(index, new PlumNeedle()); 457 break; 458 case 5: 459 setSeriesNeedle(index, new PointerNeedle()); 460 break; 461 case 6: 462 setSeriesPaint(index, null); 463 setSeriesOutlineStroke(index, new BasicStroke(3)); 464 setSeriesNeedle(index, new ShipNeedle()); 465 break; 466 case 7: 467 setSeriesPaint(index, Color.blue); 468 setSeriesNeedle(index, new WindNeedle()); 469 break; 470 case 8: 471 setSeriesNeedle(index, new ArrowNeedle(true)); 472 break; 473 case 9: 474 setSeriesNeedle(index, new MiddlePinNeedle()); 475 break; 476 477 default: 478 throw new IllegalArgumentException("Unrecognised type."); 479 } 480 481 } 482 483 /** 484 * Sets the needle for a series. 485 * 486 * @param index the series index. 487 * @param needle the needle. 488 */ 489 public void setSeriesNeedle(int index, MeterNeedle needle) { 490 491 if ((needle != null) && (index < this.seriesNeedle.length)) { 492 this.seriesNeedle[index] = needle; 493 } 494 notifyListeners(new PlotChangeEvent(this)); 495 496 } 497 498 /** 499 * Returns an array of dataset references for the plot. 500 * 501 * @return The dataset for the plot, cast as a ValueDataset. 502 * 503 * @see #addDataset(ValueDataset) 504 */ 505 public ValueDataset[] getDatasets() { 506 return this.datasets; 507 } 508 509 /** 510 * Adds a dataset to the compass. 511 * 512 * @param dataset the new dataset (<code>null</code> ignored). 513 * 514 * @see #addDataset(ValueDataset, MeterNeedle) 515 */ 516 public void addDataset(ValueDataset dataset) { 517 addDataset(dataset, null); 518 } 519 520 /** 521 * Adds a dataset to the compass. 522 * 523 * @param dataset the new dataset (<code>null</code> ignored). 524 * @param needle the needle (<code>null</code> permitted). 525 */ 526 public void addDataset(ValueDataset dataset, MeterNeedle needle) { 527 528 if (dataset != null) { 529 int i = this.datasets.length + 1; 530 ValueDataset[] t = new ValueDataset[i]; 531 MeterNeedle[] p = new MeterNeedle[i]; 532 i = i - 2; 533 for (; i >= 0; --i) { 534 t[i] = this.datasets[i]; 535 p[i] = this.seriesNeedle[i]; 536 } 537 i = this.datasets.length; 538 t[i] = dataset; 539 p[i] = ((needle != null) ? needle : p[i - 1]); 540 541 ValueDataset[] a = this.datasets; 542 MeterNeedle[] b = this.seriesNeedle; 543 this.datasets = t; 544 this.seriesNeedle = p; 545 546 for (--i; i >= 0; --i) { 547 a[i] = null; 548 b[i] = null; 549 } 550 dataset.addChangeListener(this); 551 } 552 } 553 554 /** 555 * Draws the plot on a Java 2D graphics device (such as the screen or a 556 * printer). 557 * 558 * @param g2 the graphics device. 559 * @param area the area within which the plot should be drawn. 560 * @param anchor the anchor point (<code>null</code> permitted). 561 * @param parentState the state from the parent plot, if there is one. 562 * @param info collects info about the drawing. 563 */ 564 public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 565 PlotState parentState, 566 PlotRenderingInfo info) { 567 568 int outerRadius = 0; 569 int innerRadius = 0; 570 int x1, y1, x2, y2; 571 double a; 572 573 if (info != null) { 574 info.setPlotArea(area); 575 } 576 577 // adjust for insets... 578 RectangleInsets insets = getInsets(); 579 insets.trim(area); 580 581 // draw the background 582 if (this.drawBorder) { 583 drawBackground(g2, area); 584 } 585 586 int midX = (int) (area.getWidth() / 2); 587 int midY = (int) (area.getHeight() / 2); 588 int radius = midX; 589 if (midY < midX) { 590 radius = midY; 591 } 592 --radius; 593 int diameter = 2 * radius; 594 595 midX += (int) area.getMinX(); 596 midY += (int) area.getMinY(); 597 598 this.circle1.setFrame(midX - radius, midY - radius, diameter, diameter); 599 this.circle2.setFrame( 600 midX - radius + 15, midY - radius + 15, 601 diameter - 30, diameter - 30 602 ); 603 g2.setPaint(this.rosePaint); 604 this.a1 = new Area(this.circle1); 605 this.a2 = new Area(this.circle2); 606 this.a1.subtract(this.a2); 607 g2.fill(this.a1); 608 609 g2.setPaint(this.roseCenterPaint); 610 x1 = diameter - 30; 611 g2.fillOval(midX - radius + 15, midY - radius + 15, x1, x1); 612 g2.setPaint(this.roseHighlightPaint); 613 g2.drawOval(midX - radius, midY - radius, diameter, diameter); 614 x1 = diameter - 20; 615 g2.drawOval(midX - radius + 10, midY - radius + 10, x1, x1); 616 x1 = diameter - 30; 617 g2.drawOval(midX - radius + 15, midY - radius + 15, x1, x1); 618 x1 = diameter - 80; 619 g2.drawOval(midX - radius + 40, midY - radius + 40, x1, x1); 620 621 outerRadius = radius - 20; 622 innerRadius = radius - 32; 623 for (int w = 0; w < 360; w += 15) { 624 a = Math.toRadians(w); 625 x1 = midX - ((int) (Math.sin(a) * innerRadius)); 626 x2 = midX - ((int) (Math.sin(a) * outerRadius)); 627 y1 = midY - ((int) (Math.cos(a) * innerRadius)); 628 y2 = midY - ((int) (Math.cos(a) * outerRadius)); 629 g2.drawLine(x1, y1, x2, y2); 630 } 631 632 g2.setPaint(this.roseHighlightPaint); 633 innerRadius = radius - 26; 634 outerRadius = 7; 635 for (int w = 45; w < 360; w += 90) { 636 a = Math.toRadians(w); 637 x1 = midX - ((int) (Math.sin(a) * innerRadius)); 638 y1 = midY - ((int) (Math.cos(a) * innerRadius)); 639 g2.fillOval( 640 x1 - outerRadius, y1 - outerRadius, 641 2 * outerRadius, 2 * outerRadius 642 ); 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("N", midX - 5, midY - innerRadius + f.getSize()); 664 g2.drawString("S", midX - 5, midY + innerRadius - 5); 665 g2.drawString("W", midX - innerRadius + 5, midY + 5); 666 g2.drawString("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 = 0; 676 double value = 0; 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 public String getPlotType() { 702 return localizationResources.getString("Compass_Plot"); 703 } 704 705 /** 706 * Returns the legend items for the plot. For now, no legend is available 707 * - this method returns null. 708 * 709 * @return The legend items. 710 */ 711 public LegendItemCollection getLegendItems() { 712 return null; 713 } 714 715 /** 716 * No zooming is implemented for compass plot, so this method is empty. 717 * 718 * @param percent the zoom amount. 719 */ 720 public void zoom(double percent) { 721 // no zooming possible 722 } 723 724 /** 725 * Returns the font for the compass, adjusted for the size of the plot. 726 * 727 * @param radius the radius. 728 * 729 * @return The font. 730 */ 731 protected Font getCompassFont(int radius) { 732 float fontSize = radius / 10.0f; 733 if (fontSize < 8) { 734 fontSize = 8; 735 } 736 Font newFont = this.compassFont.deriveFont(fontSize); 737 return newFont; 738 } 739 740 /** 741 * Tests an object for equality with this plot. 742 * 743 * @param obj the object (<code>null</code> permitted). 744 * 745 * @return A boolean. 746 */ 747 public boolean equals(Object obj) { 748 if (obj == this) { 749 return true; 750 } 751 if (!(obj instanceof CompassPlot)) { 752 return false; 753 } 754 if (!super.equals(obj)) { 755 return false; 756 } 757 CompassPlot that = (CompassPlot) obj; 758 if (this.labelType != that.labelType) { 759 return false; 760 } 761 if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) { 762 return false; 763 } 764 if (this.drawBorder != that.drawBorder) { 765 return false; 766 } 767 if (!PaintUtilities.equal(this.roseHighlightPaint, 768 that.roseHighlightPaint)) { 769 return false; 770 } 771 if (!PaintUtilities.equal(this.rosePaint, that.rosePaint)) { 772 return false; 773 } 774 if (!PaintUtilities.equal(this.roseCenterPaint, 775 that.roseCenterPaint)) { 776 return false; 777 } 778 if (!ObjectUtilities.equal(this.compassFont, that.compassFont)) { 779 return false; 780 } 781 if (!Arrays.equals(this.seriesNeedle, that.seriesNeedle)) { 782 return false; 783 } 784 if (getRevolutionDistance() != that.getRevolutionDistance()) { 785 return false; 786 } 787 return true; 788 789 } 790 791 /** 792 * Returns a clone of the plot. 793 * 794 * @return A clone. 795 * 796 * @throws CloneNotSupportedException this class will not throw this 797 * exception, but subclasses (if any) might. 798 */ 799 public Object clone() throws CloneNotSupportedException { 800 801 CompassPlot clone = (CompassPlot) super.clone(); 802 if (this.circle1 != null) { 803 clone.circle1 = (Ellipse2D) this.circle1.clone(); 804 } 805 if (this.circle2 != null) { 806 clone.circle2 = (Ellipse2D) this.circle2.clone(); 807 } 808 if (this.a1 != null) { 809 clone.a1 = (Area) this.a1.clone(); 810 } 811 if (this.a2 != null) { 812 clone.a2 = (Area) this.a2.clone(); 813 } 814 if (this.rect1 != null) { 815 clone.rect1 = (Rectangle2D) this.rect1.clone(); 816 } 817 clone.datasets = (ValueDataset[]) this.datasets.clone(); 818 clone.seriesNeedle = (MeterNeedle[]) this.seriesNeedle.clone(); 819 820 // clone share data sets => add the clone as listener to the dataset 821 for (int i = 0; i < this.datasets.length; ++i) { 822 if (clone.datasets[i] != null) { 823 clone.datasets[i].addChangeListener(clone); 824 } 825 } 826 return clone; 827 828 } 829 830 /** 831 * Sets the count to complete one revolution. Can be arbitrarily set 832 * For degrees (the default) it is 360, for radians this is 2*Pi, etc 833 * 834 * @param size the count to complete one revolution. 835 * 836 * @see #getRevolutionDistance() 837 */ 838 public void setRevolutionDistance(double size) { 839 if (size > 0) { 840 this.revolutionDistance = size; 841 } 842 } 843 844 /** 845 * Gets the count to complete one revolution. 846 * 847 * @return The count to complete one revolution. 848 * 849 * @see #setRevolutionDistance(double) 850 */ 851 public double getRevolutionDistance() { 852 return this.revolutionDistance; 853 } 854 }