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