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