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