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