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