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 * ThermometerPlot.java 029 * -------------------- 030 * 031 * (C) Copyright 2000-2007, by Bryan Scott and Contributors. 032 * 033 * Original Author: Bryan Scott (based on MeterPlot by Hari). 034 * Contributor(s): David Gilbert (for Object Refinery Limited). 035 * Arnaud Lelievre; 036 * Julien Henry (see patch 1769088) (DG); 037 * 038 * Changes 039 * ------- 040 * 11-Apr-2002 : Version 1, contributed by Bryan Scott; 041 * 15-Apr-2002 : Changed to implement VerticalValuePlot; 042 * 29-Apr-2002 : Added getVerticalValueAxis() method (DG); 043 * 25-Jun-2002 : Removed redundant imports (DG); 044 * 17-Sep-2002 : Reviewed with Checkstyle utility (DG); 045 * 18-Sep-2002 : Extensive changes made to API, to iron out bugs and 046 * inconsistencies (DG); 047 * 13-Oct-2002 : Corrected error datasetChanged which would generate exceptions 048 * when value set to null (BRS). 049 * 23-Jan-2003 : Removed one constructor (DG); 050 * 26-Mar-2003 : Implemented Serializable (DG); 051 * 02-Jun-2003 : Removed test for compatible range axis (DG); 052 * 01-Jul-2003 : Added additional check in draw method to ensure value not 053 * null (BRS); 054 * 08-Sep-2003 : Added internationalization via use of properties 055 * resourceBundle (RFE 690236) (AL); 056 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 057 * 29-Sep-2003 : Updated draw to set value of cursor to non-zero and allow 058 * painting of axis. An incomplete fix and needs to be set for 059 * left or right drawing (BRS); 060 * 19-Nov-2003 : Added support for value labels to be displayed left of the 061 * thermometer 062 * 19-Nov-2003 : Improved axis drawing (now default axis does not draw axis line 063 * and is closer to the bulb). Added support for the positioning 064 * of the axis to the left or right of the bulb. (BRS); 065 * 03-Dec-2003 : Directly mapped deprecated setData()/getData() method to 066 * get/setDataset() (TM); 067 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG); 068 * 07-Apr-2004 : Changed string width calculation (DG); 069 * 12-Nov-2004 : Implemented the new Zoomable interface (DG); 070 * 06-Jan-2004 : Added getOrientation() method (DG); 071 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG); 072 * 29-Mar-2005 : Fixed equals() method (DG); 073 * 05-May-2005 : Updated draw() method parameters (DG); 074 * 09-Jun-2005 : Fixed more bugs in equals() method (DG); 075 * 10-Jun-2005 : Fixed minor bug in setDisplayRange() method (DG); 076 * ------------- JFREECHART 1.0.x --------------------------------------------- 077 * 14-Nov-2006 : Fixed margin when drawing (DG); 078 * 03-May-2007 : Fixed datasetChanged() to handle null dataset, added null 079 * argument check and event notification to setRangeAxis(), 080 * added null argument check to setPadding(), setValueFont(), 081 * setValuePaint(), setValueFormat() and setMercuryPaint(), 082 * deprecated get/setShowValueLines(), deprecated 083 * getMinimum/MaximumVerticalDataValue(), and fixed serialization 084 * bug (DG); 085 * 24-Sep-2007 : Implemented new methods in Zoomable interface (DG); 086 * 08-Oct-2007 : Added attributes for thermometer dimensions - see patch 1769088 087 * by Julien Henry (DG); 088 * 089 */ 090 091 package org.jfree.chart.plot; 092 093 import java.awt.BasicStroke; 094 import java.awt.Color; 095 import java.awt.Font; 096 import java.awt.FontMetrics; 097 import java.awt.Graphics2D; 098 import java.awt.Paint; 099 import java.awt.Stroke; 100 import java.awt.geom.Area; 101 import java.awt.geom.Ellipse2D; 102 import java.awt.geom.Line2D; 103 import java.awt.geom.Point2D; 104 import java.awt.geom.Rectangle2D; 105 import java.awt.geom.RoundRectangle2D; 106 import java.io.IOException; 107 import java.io.ObjectInputStream; 108 import java.io.ObjectOutputStream; 109 import java.io.Serializable; 110 import java.text.DecimalFormat; 111 import java.text.NumberFormat; 112 import java.util.Arrays; 113 import java.util.ResourceBundle; 114 115 import org.jfree.chart.LegendItemCollection; 116 import org.jfree.chart.axis.NumberAxis; 117 import org.jfree.chart.axis.ValueAxis; 118 import org.jfree.chart.event.PlotChangeEvent; 119 import org.jfree.data.Range; 120 import org.jfree.data.general.DatasetChangeEvent; 121 import org.jfree.data.general.DefaultValueDataset; 122 import org.jfree.data.general.ValueDataset; 123 import org.jfree.io.SerialUtilities; 124 import org.jfree.ui.RectangleEdge; 125 import org.jfree.ui.RectangleInsets; 126 import org.jfree.util.ObjectUtilities; 127 import org.jfree.util.PaintUtilities; 128 import org.jfree.util.UnitType; 129 130 /** 131 * A plot that displays a single value (from a {@link ValueDataset}) in a 132 * thermometer type display. 133 * <p> 134 * This plot supports a number of options: 135 * <ol> 136 * <li>three sub-ranges which could be viewed as 'Normal', 'Warning' 137 * and 'Critical' ranges.</li> 138 * <li>the thermometer can be run in two modes: 139 * <ul> 140 * <li>fixed range, or</li> 141 * <li>range adjusts to current sub-range.</li> 142 * </ul> 143 * </li> 144 * <li>settable units to be displayed.</li> 145 * <li>settable display location for the value text.</li> 146 * </ol> 147 */ 148 public class ThermometerPlot extends Plot implements ValueAxisPlot, 149 Zoomable, Cloneable, Serializable { 150 151 /** For serialization. */ 152 private static final long serialVersionUID = 4087093313147984390L; 153 154 /** A constant for unit type 'None'. */ 155 public static final int UNITS_NONE = 0; 156 157 /** A constant for unit type 'Fahrenheit'. */ 158 public static final int UNITS_FAHRENHEIT = 1; 159 160 /** A constant for unit type 'Celcius'. */ 161 public static final int UNITS_CELCIUS = 2; 162 163 /** A constant for unit type 'Kelvin'. */ 164 public static final int UNITS_KELVIN = 3; 165 166 /** A constant for the value label position (no label). */ 167 public static final int NONE = 0; 168 169 /** A constant for the value label position (right of the thermometer). */ 170 public static final int RIGHT = 1; 171 172 /** A constant for the value label position (left of the thermometer). */ 173 public static final int LEFT = 2; 174 175 /** A constant for the value label position (in the thermometer bulb). */ 176 public static final int BULB = 3; 177 178 /** A constant for the 'normal' range. */ 179 public static final int NORMAL = 0; 180 181 /** A constant for the 'warning' range. */ 182 public static final int WARNING = 1; 183 184 /** A constant for the 'critical' range. */ 185 public static final int CRITICAL = 2; 186 187 /** 188 * The bulb radius. 189 * 190 * @deprecated As of 1.0.7, use {@link #getBulbRadius()}. 191 */ 192 protected static final int BULB_RADIUS = 40; 193 194 /** 195 * The bulb diameter. 196 * 197 * @deprecated As of 1.0.7, use {@link #getBulbDiameter()}. 198 */ 199 protected static final int BULB_DIAMETER = BULB_RADIUS * 2; 200 201 /** 202 * The column radius. 203 * 204 * @deprecated As of 1.0.7, use {@link #getColumnRadius()}. 205 */ 206 protected static final int COLUMN_RADIUS = 20; 207 208 /** 209 * The column diameter. 210 * 211 * @deprecated As of 1.0.7, use {@link #getColumnDiameter()}. 212 */ 213 protected static final int COLUMN_DIAMETER = COLUMN_RADIUS * 2; 214 215 /** 216 * The gap radius. 217 * 218 * @deprecated As of 1.0.7, use {@link #getGap()}. 219 */ 220 protected static final int GAP_RADIUS = 5; 221 222 /** 223 * The gap diameter. 224 * 225 * @deprecated As of 1.0.7, use {@link #getGap()} times two. 226 */ 227 protected static final int GAP_DIAMETER = GAP_RADIUS * 2; 228 229 /** The axis gap. */ 230 protected static final int AXIS_GAP = 10; 231 232 /** The unit strings. */ 233 protected static final String[] UNITS = {"", "\u00B0F", "\u00B0C", 234 "\u00B0K"}; 235 236 /** Index for low value in subrangeInfo matrix. */ 237 protected static final int RANGE_LOW = 0; 238 239 /** Index for high value in subrangeInfo matrix. */ 240 protected static final int RANGE_HIGH = 1; 241 242 /** Index for display low value in subrangeInfo matrix. */ 243 protected static final int DISPLAY_LOW = 2; 244 245 /** Index for display high value in subrangeInfo matrix. */ 246 protected static final int DISPLAY_HIGH = 3; 247 248 /** The default lower bound. */ 249 protected static final double DEFAULT_LOWER_BOUND = 0.0; 250 251 /** The default upper bound. */ 252 protected static final double DEFAULT_UPPER_BOUND = 100.0; 253 254 /** 255 * The default bulb radius. 256 * 257 * @since 1.0.7 258 */ 259 protected static final int DEFAULT_BULB_RADIUS = 40; 260 261 /** 262 * The default column radius. 263 * 264 * @since 1.0.7 265 */ 266 protected static final int DEFAULT_COLUMN_RADIUS = 20; 267 268 /** 269 * The default gap between the outlines representing the thermometer. 270 * 271 * @since 1.0.7 272 */ 273 protected static final int DEFAULT_GAP = 5; 274 275 /** The dataset for the plot. */ 276 private ValueDataset dataset; 277 278 /** The range axis. */ 279 private ValueAxis rangeAxis; 280 281 /** The lower bound for the thermometer. */ 282 private double lowerBound = DEFAULT_LOWER_BOUND; 283 284 /** The upper bound for the thermometer. */ 285 private double upperBound = DEFAULT_UPPER_BOUND; 286 287 /** 288 * The value label position. 289 * 290 * @since 1.0.7 291 */ 292 private int bulbRadius = DEFAULT_BULB_RADIUS; 293 294 /** 295 * The column radius. 296 * 297 * @since 1.0.7 298 */ 299 private int columnRadius = DEFAULT_COLUMN_RADIUS; 300 301 /** 302 * The gap between the two outlines the represent the thermometer. 303 * 304 * @since 1.0.7 305 */ 306 private int gap = DEFAULT_GAP; 307 308 /** 309 * Blank space inside the plot area around the outside of the thermometer. 310 */ 311 private RectangleInsets padding; 312 313 /** Stroke for drawing the thermometer */ 314 private transient Stroke thermometerStroke = new BasicStroke(1.0f); 315 316 /** Paint for drawing the thermometer */ 317 private transient Paint thermometerPaint = Color.black; 318 319 /** The display units */ 320 private int units = UNITS_CELCIUS; 321 322 /** The value label position. */ 323 private int valueLocation = BULB; 324 325 /** The position of the axis **/ 326 private int axisLocation = LEFT; 327 328 /** The font to write the value in */ 329 private Font valueFont = new Font("SansSerif", Font.BOLD, 16); 330 331 /** Colour that the value is written in */ 332 private transient Paint valuePaint = Color.white; 333 334 /** Number format for the value */ 335 private NumberFormat valueFormat = new DecimalFormat(); 336 337 /** The default paint for the mercury in the thermometer. */ 338 private transient Paint mercuryPaint = Color.lightGray; 339 340 /** A flag that controls whether value lines are drawn. */ 341 private boolean showValueLines = false; 342 343 /** The display sub-range. */ 344 private int subrange = -1; 345 346 /** The start and end values for the subranges. */ 347 private double[][] subrangeInfo = { 348 {0.0, 50.0, 0.0, 50.0}, 349 {50.0, 75.0, 50.0, 75.0}, 350 {75.0, 100.0, 75.0, 100.0} 351 }; 352 353 /** 354 * A flag that controls whether or not the axis range adjusts to the 355 * sub-ranges. 356 */ 357 private boolean followDataInSubranges = false; 358 359 /** 360 * A flag that controls whether or not the mercury paint changes with 361 * the subranges. 362 */ 363 private boolean useSubrangePaint = true; 364 365 /** Paint for each range */ 366 private transient Paint[] subrangePaint = {Color.green, Color.orange, 367 Color.red}; 368 369 /** A flag that controls whether the sub-range indicators are visible. */ 370 private boolean subrangeIndicatorsVisible = true; 371 372 /** The stroke for the sub-range indicators. */ 373 private transient Stroke subrangeIndicatorStroke = new BasicStroke(2.0f); 374 375 /** The range indicator stroke. */ 376 private transient Stroke rangeIndicatorStroke = new BasicStroke(3.0f); 377 378 /** The resourceBundle for the localization. */ 379 protected static ResourceBundle localizationResources = 380 ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle"); 381 382 /** 383 * Creates a new thermometer plot. 384 */ 385 public ThermometerPlot() { 386 this(new DefaultValueDataset()); 387 } 388 389 /** 390 * Creates a new thermometer plot, using default attributes where necessary. 391 * 392 * @param dataset the data set. 393 */ 394 public ThermometerPlot(ValueDataset dataset) { 395 396 super(); 397 398 this.padding = new RectangleInsets(UnitType.RELATIVE, 0.05, 0.05, 0.05, 399 0.05); 400 this.dataset = dataset; 401 if (dataset != null) { 402 dataset.addChangeListener(this); 403 } 404 NumberAxis axis = new NumberAxis(null); 405 axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); 406 axis.setAxisLineVisible(false); 407 axis.setPlot(this); 408 axis.addChangeListener(this); 409 this.rangeAxis = axis; 410 setAxisRange(); 411 } 412 413 /** 414 * Returns the dataset for the plot. 415 * 416 * @return The dataset (possibly <code>null</code>). 417 * 418 * @see #setDataset(ValueDataset) 419 */ 420 public ValueDataset getDataset() { 421 return this.dataset; 422 } 423 424 /** 425 * Sets the dataset for the plot, replacing the existing dataset if there 426 * is one, and sends a {@link PlotChangeEvent} to all registered listeners. 427 * 428 * @param dataset the dataset (<code>null</code> permitted). 429 * 430 * @see #getDataset() 431 */ 432 public void setDataset(ValueDataset dataset) { 433 434 // if there is an existing dataset, remove the plot from the list 435 // of change listeners... 436 ValueDataset existing = this.dataset; 437 if (existing != null) { 438 existing.removeChangeListener(this); 439 } 440 441 // set the new dataset, and register the chart as a change listener... 442 this.dataset = dataset; 443 if (dataset != null) { 444 setDatasetGroup(dataset.getGroup()); 445 dataset.addChangeListener(this); 446 } 447 448 // send a dataset change event to self... 449 DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); 450 datasetChanged(event); 451 452 } 453 454 /** 455 * Returns the range axis. 456 * 457 * @return The range axis (never <code>null</code>). 458 * 459 * @see #setRangeAxis(ValueAxis) 460 */ 461 public ValueAxis getRangeAxis() { 462 return this.rangeAxis; 463 } 464 465 /** 466 * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to 467 * all registered listeners. 468 * 469 * @param axis the new axis (<code>null</code> not permitted). 470 * 471 * @see #getRangeAxis() 472 */ 473 public void setRangeAxis(ValueAxis axis) { 474 if (axis == null) { 475 throw new IllegalArgumentException("Null 'axis' argument."); 476 } 477 // plot is registered as a listener with the existing axis... 478 this.rangeAxis.removeChangeListener(this); 479 480 axis.setPlot(this); 481 axis.addChangeListener(this); 482 this.rangeAxis = axis; 483 fireChangeEvent(); 484 } 485 486 /** 487 * Returns the lower bound for the thermometer. The data value can be set 488 * lower than this, but it will not be shown in the thermometer. 489 * 490 * @return The lower bound. 491 * 492 * @see #setLowerBound(double) 493 */ 494 public double getLowerBound() { 495 return this.lowerBound; 496 } 497 498 /** 499 * Sets the lower bound for the thermometer. 500 * 501 * @param lower the lower bound. 502 * 503 * @see #getLowerBound() 504 */ 505 public void setLowerBound(double lower) { 506 this.lowerBound = lower; 507 setAxisRange(); 508 } 509 510 /** 511 * Returns the upper bound for the thermometer. The data value can be set 512 * higher than this, but it will not be shown in the thermometer. 513 * 514 * @return The upper bound. 515 * 516 * @see #setUpperBound(double) 517 */ 518 public double getUpperBound() { 519 return this.upperBound; 520 } 521 522 /** 523 * Sets the upper bound for the thermometer. 524 * 525 * @param upper the upper bound. 526 * 527 * @see #getUpperBound() 528 */ 529 public void setUpperBound(double upper) { 530 this.upperBound = upper; 531 setAxisRange(); 532 } 533 534 /** 535 * Sets the lower and upper bounds for the thermometer. 536 * 537 * @param lower the lower bound. 538 * @param upper the upper bound. 539 */ 540 public void setRange(double lower, double upper) { 541 this.lowerBound = lower; 542 this.upperBound = upper; 543 setAxisRange(); 544 } 545 546 /** 547 * Returns the padding for the thermometer. This is the space inside the 548 * plot area. 549 * 550 * @return The padding (never <code>null</code>). 551 * 552 * @see #setPadding(RectangleInsets) 553 */ 554 public RectangleInsets getPadding() { 555 return this.padding; 556 } 557 558 /** 559 * Sets the padding for the thermometer and sends a {@link PlotChangeEvent} 560 * to all registered listeners. 561 * 562 * @param padding the padding (<code>null</code> not permitted). 563 * 564 * @see #getPadding() 565 */ 566 public void setPadding(RectangleInsets padding) { 567 if (padding == null) { 568 throw new IllegalArgumentException("Null 'padding' argument."); 569 } 570 this.padding = padding; 571 fireChangeEvent(); 572 } 573 574 /** 575 * Returns the stroke used to draw the thermometer outline. 576 * 577 * @return The stroke (never <code>null</code>). 578 * 579 * @see #setThermometerStroke(Stroke) 580 * @see #getThermometerPaint() 581 */ 582 public Stroke getThermometerStroke() { 583 return this.thermometerStroke; 584 } 585 586 /** 587 * Sets the stroke used to draw the thermometer outline and sends a 588 * {@link PlotChangeEvent} to all registered listeners. 589 * 590 * @param s the new stroke (<code>null</code> ignored). 591 * 592 * @see #getThermometerStroke() 593 */ 594 public void setThermometerStroke(Stroke s) { 595 if (s != null) { 596 this.thermometerStroke = s; 597 fireChangeEvent(); 598 } 599 } 600 601 /** 602 * Returns the paint used to draw the thermometer outline. 603 * 604 * @return The paint (never <code>null</code>). 605 * 606 * @see #setThermometerPaint(Paint) 607 * @see #getThermometerStroke() 608 */ 609 public Paint getThermometerPaint() { 610 return this.thermometerPaint; 611 } 612 613 /** 614 * Sets the paint used to draw the thermometer outline and sends a 615 * {@link PlotChangeEvent} to all registered listeners. 616 * 617 * @param paint the new paint (<code>null</code> ignored). 618 * 619 * @see #getThermometerPaint() 620 */ 621 public void setThermometerPaint(Paint paint) { 622 if (paint != null) { 623 this.thermometerPaint = paint; 624 fireChangeEvent(); 625 } 626 } 627 628 /** 629 * Returns a code indicating the unit display type. This is one of 630 * {@link #UNITS_NONE}, {@link #UNITS_FAHRENHEIT}, {@link #UNITS_CELCIUS} 631 * and {@link #UNITS_KELVIN}. 632 * 633 * @return The units type. 634 * 635 * @see #setUnits(int) 636 */ 637 public int getUnits() { 638 return this.units; 639 } 640 641 /** 642 * Sets the units to be displayed in the thermometer. Use one of the 643 * following constants: 644 * 645 * <ul> 646 * <li>UNITS_NONE : no units displayed.</li> 647 * <li>UNITS_FAHRENHEIT : units displayed in Fahrenheit.</li> 648 * <li>UNITS_CELCIUS : units displayed in Celcius.</li> 649 * <li>UNITS_KELVIN : units displayed in Kelvin.</li> 650 * </ul> 651 * 652 * @param u the new unit type. 653 * 654 * @see #getUnits() 655 */ 656 public void setUnits(int u) { 657 if ((u >= 0) && (u < UNITS.length)) { 658 if (this.units != u) { 659 this.units = u; 660 fireChangeEvent(); 661 } 662 } 663 } 664 665 /** 666 * Sets the unit type. 667 * 668 * @param u the unit type (<code>null</code> ignored). 669 * 670 * @deprecated Use setUnits(int) instead. Deprecated as of version 1.0.6, 671 * because this method is a little obscure and redundant anyway. 672 */ 673 public void setUnits(String u) { 674 if (u == null) { 675 return; 676 } 677 678 u = u.toUpperCase().trim(); 679 for (int i = 0; i < UNITS.length; ++i) { 680 if (u.equals(UNITS[i].toUpperCase().trim())) { 681 setUnits(i); 682 i = UNITS.length; 683 } 684 } 685 } 686 687 /** 688 * Returns a code indicating the location at which the value label is 689 * displayed. 690 * 691 * @return The location (one of {@link #NONE}, {@link #RIGHT}, 692 * {@link #LEFT} and {@link #BULB}.). 693 */ 694 public int getValueLocation() { 695 return this.valueLocation; 696 } 697 698 /** 699 * Sets the location at which the current value is displayed and sends a 700 * {@link PlotChangeEvent} to all registered listeners. 701 * <P> 702 * The location can be one of the constants: 703 * <code>NONE</code>, 704 * <code>RIGHT</code> 705 * <code>LEFT</code> and 706 * <code>BULB</code>. 707 * 708 * @param location the location. 709 */ 710 public void setValueLocation(int location) { 711 if ((location >= 0) && (location < 4)) { 712 this.valueLocation = location; 713 fireChangeEvent(); 714 } 715 else { 716 throw new IllegalArgumentException("Location not recognised."); 717 } 718 } 719 720 /** 721 * Returns the axis location. 722 * 723 * @return The location (one of {@link #NONE}, {@link #LEFT} and 724 * {@link #RIGHT}). 725 * 726 * @see #setAxisLocation(int) 727 */ 728 public int getAxisLocation() { 729 return this.axisLocation; 730 } 731 732 /** 733 * Sets the location at which the axis is displayed relative to the 734 * thermometer, and sends a {@link PlotChangeEvent} to all registered 735 * listeners. 736 * 737 * @param location the location (one of {@link #NONE}, {@link #LEFT} and 738 * {@link #RIGHT}). 739 * 740 * @see #getAxisLocation() 741 */ 742 public void setAxisLocation(int location) { 743 if ((location >= 0) && (location < 3)) { 744 this.axisLocation = location; 745 fireChangeEvent(); 746 } 747 else { 748 throw new IllegalArgumentException("Location not recognised."); 749 } 750 } 751 752 /** 753 * Gets the font used to display the current value. 754 * 755 * @return The font. 756 * 757 * @see #setValueFont(Font) 758 */ 759 public Font getValueFont() { 760 return this.valueFont; 761 } 762 763 /** 764 * Sets the font used to display the current value. 765 * 766 * @param f the new font (<code>null</code> not permitted). 767 * 768 * @see #getValueFont() 769 */ 770 public void setValueFont(Font f) { 771 if (f == null) { 772 throw new IllegalArgumentException("Null 'font' argument."); 773 } 774 if (!this.valueFont.equals(f)) { 775 this.valueFont = f; 776 fireChangeEvent(); 777 } 778 } 779 780 /** 781 * Gets the paint used to display the current value. 782 * 783 * @return The paint. 784 * 785 * @see #setValuePaint(Paint) 786 */ 787 public Paint getValuePaint() { 788 return this.valuePaint; 789 } 790 791 /** 792 * Sets the paint used to display the current value and sends a 793 * {@link PlotChangeEvent} to all registered listeners. 794 * 795 * @param paint the new paint (<code>null</code> not permitted). 796 * 797 * @see #getValuePaint() 798 */ 799 public void setValuePaint(Paint paint) { 800 if (paint == null) { 801 throw new IllegalArgumentException("Null 'paint' argument."); 802 } 803 if (!this.valuePaint.equals(paint)) { 804 this.valuePaint = paint; 805 fireChangeEvent(); 806 } 807 } 808 809 // FIXME: No getValueFormat() method? 810 811 /** 812 * Sets the formatter for the value label and sends a 813 * {@link PlotChangeEvent} to all registered listeners. 814 * 815 * @param formatter the new formatter (<code>null</code> not permitted). 816 */ 817 public void setValueFormat(NumberFormat formatter) { 818 if (formatter == null) { 819 throw new IllegalArgumentException("Null 'formatter' argument."); 820 } 821 this.valueFormat = formatter; 822 fireChangeEvent(); 823 } 824 825 /** 826 * Returns the default mercury paint. 827 * 828 * @return The paint (never <code>null</code>). 829 * 830 * @see #setMercuryPaint(Paint) 831 */ 832 public Paint getMercuryPaint() { 833 return this.mercuryPaint; 834 } 835 836 /** 837 * Sets the default mercury paint and sends a {@link PlotChangeEvent} to 838 * all registered listeners. 839 * 840 * @param paint the new paint (<code>null</code> not permitted). 841 * 842 * @see #getMercuryPaint() 843 */ 844 public void setMercuryPaint(Paint paint) { 845 if (paint == null) { 846 throw new IllegalArgumentException("Null 'paint' argument."); 847 } 848 this.mercuryPaint = paint; 849 fireChangeEvent(); 850 } 851 852 /** 853 * Returns the flag that controls whether not value lines are displayed. 854 * 855 * @return The flag. 856 * 857 * @see #setShowValueLines(boolean) 858 * 859 * @deprecated This flag doesn't do anything useful/visible. Deprecated 860 * as of version 1.0.6. 861 */ 862 public boolean getShowValueLines() { 863 return this.showValueLines; 864 } 865 866 /** 867 * Sets the display as to whether to show value lines in the output. 868 * 869 * @param b Whether to show value lines in the thermometer 870 * 871 * @see #getShowValueLines() 872 * 873 * @deprecated This flag doesn't do anything useful/visible. Deprecated 874 * as of version 1.0.6. 875 */ 876 public void setShowValueLines(boolean b) { 877 this.showValueLines = b; 878 fireChangeEvent(); 879 } 880 881 /** 882 * Sets information for a particular range. 883 * 884 * @param range the range to specify information about. 885 * @param low the low value for the range 886 * @param hi the high value for the range 887 */ 888 public void setSubrangeInfo(int range, double low, double hi) { 889 setSubrangeInfo(range, low, hi, low, hi); 890 } 891 892 /** 893 * Sets the subrangeInfo attribute of the ThermometerPlot object 894 * 895 * @param range the new rangeInfo value. 896 * @param rangeLow the new rangeInfo value 897 * @param rangeHigh the new rangeInfo value 898 * @param displayLow the new rangeInfo value 899 * @param displayHigh the new rangeInfo value 900 */ 901 public void setSubrangeInfo(int range, 902 double rangeLow, double rangeHigh, 903 double displayLow, double displayHigh) { 904 905 if ((range >= 0) && (range < 3)) { 906 setSubrange(range, rangeLow, rangeHigh); 907 setDisplayRange(range, displayLow, displayHigh); 908 setAxisRange(); 909 fireChangeEvent(); 910 } 911 912 } 913 914 /** 915 * Sets the bounds for a subrange. 916 * 917 * @param range the range type. 918 * @param low the low value. 919 * @param high the high value. 920 */ 921 public void setSubrange(int range, double low, double high) { 922 if ((range >= 0) && (range < 3)) { 923 this.subrangeInfo[range][RANGE_HIGH] = high; 924 this.subrangeInfo[range][RANGE_LOW] = low; 925 } 926 } 927 928 /** 929 * Sets the displayed bounds for a sub range. 930 * 931 * @param range the range type. 932 * @param low the low value. 933 * @param high the high value. 934 */ 935 public void setDisplayRange(int range, double low, double high) { 936 937 if ((range >= 0) && (range < this.subrangeInfo.length) 938 && isValidNumber(high) && isValidNumber(low)) { 939 940 if (high > low) { 941 this.subrangeInfo[range][DISPLAY_HIGH] = high; 942 this.subrangeInfo[range][DISPLAY_LOW] = low; 943 } 944 else { 945 this.subrangeInfo[range][DISPLAY_HIGH] = low; 946 this.subrangeInfo[range][DISPLAY_LOW] = high; 947 } 948 949 } 950 951 } 952 953 /** 954 * Gets the paint used for a particular subrange. 955 * 956 * @param range the range (. 957 * 958 * @return The paint. 959 * 960 * @see #setSubrangePaint(int, Paint) 961 */ 962 public Paint getSubrangePaint(int range) { 963 if ((range >= 0) && (range < this.subrangePaint.length)) { 964 return this.subrangePaint[range]; 965 } 966 else { 967 return this.mercuryPaint; 968 } 969 } 970 971 /** 972 * Sets the paint to be used for a subrange and sends a 973 * {@link PlotChangeEvent} to all registered listeners. 974 * 975 * @param range the range (0, 1 or 2). 976 * @param paint the paint to be applied (<code>null</code> not permitted). 977 * 978 * @see #getSubrangePaint(int) 979 */ 980 public void setSubrangePaint(int range, Paint paint) { 981 if ((range >= 0) 982 && (range < this.subrangePaint.length) && (paint != null)) { 983 this.subrangePaint[range] = paint; 984 fireChangeEvent(); 985 } 986 } 987 988 /** 989 * Returns a flag that controls whether or not the thermometer axis zooms 990 * to display the subrange within which the data value falls. 991 * 992 * @return The flag. 993 */ 994 public boolean getFollowDataInSubranges() { 995 return this.followDataInSubranges; 996 } 997 998 /** 999 * Sets the flag that controls whether or not the thermometer axis zooms 1000 * to display the subrange within which the data value falls. 1001 * 1002 * @param flag the flag. 1003 */ 1004 public void setFollowDataInSubranges(boolean flag) { 1005 this.followDataInSubranges = flag; 1006 fireChangeEvent(); 1007 } 1008 1009 /** 1010 * Returns a flag that controls whether or not the mercury color changes 1011 * for each subrange. 1012 * 1013 * @return The flag. 1014 * 1015 * @see #setUseSubrangePaint(boolean) 1016 */ 1017 public boolean getUseSubrangePaint() { 1018 return this.useSubrangePaint; 1019 } 1020 1021 /** 1022 * Sets the range colour change option. 1023 * 1024 * @param flag the new range colour change option 1025 * 1026 * @see #getUseSubrangePaint() 1027 */ 1028 public void setUseSubrangePaint(boolean flag) { 1029 this.useSubrangePaint = flag; 1030 fireChangeEvent(); 1031 } 1032 1033 /** 1034 * Returns the bulb radius, in Java2D units. 1035 1036 * @return The bulb radius. 1037 * 1038 * @since 1.0.7 1039 */ 1040 public int getBulbRadius() { 1041 return this.bulbRadius; 1042 } 1043 1044 /** 1045 * Sets the bulb radius (in Java2D units) and sends a 1046 * {@link PlotChangeEvent} to all registered listeners. 1047 * 1048 * @param r the new radius (in Java2D units). 1049 * 1050 * @see #getBulbRadius() 1051 * 1052 * @since 1.0.7 1053 */ 1054 public void setBulbRadius(int r) { 1055 this.bulbRadius = r; 1056 fireChangeEvent(); 1057 } 1058 1059 /** 1060 * Returns the bulb diameter, which is always twice the value returned 1061 * by {@link #getBulbRadius()}. 1062 * 1063 * @return The bulb diameter. 1064 * 1065 * @since 1.0.7 1066 */ 1067 public int getBulbDiameter() { 1068 return getBulbRadius() * 2; 1069 } 1070 1071 /** 1072 * Returns the column radius, in Java2D units. 1073 * 1074 * @return The column radius. 1075 * 1076 * @see #setColumnRadius(int) 1077 * 1078 * @since 1.0.7 1079 */ 1080 public int getColumnRadius() { 1081 return this.columnRadius; 1082 } 1083 1084 /** 1085 * Sets the column radius (in Java2D units) and sends a 1086 * {@link PlotChangeEvent} to all registered listeners. 1087 * 1088 * @param r the new radius. 1089 * 1090 * @see #getColumnRadius() 1091 * 1092 * @since 1.0.7 1093 */ 1094 public void setColumnRadius(int r) { 1095 this.columnRadius = r; 1096 fireChangeEvent(); 1097 } 1098 1099 /** 1100 * Returns the column diameter, which is always twice the value returned 1101 * by {@link #getColumnRadius()}. 1102 * 1103 * @return The column diameter. 1104 * 1105 * @since 1.0.7 1106 */ 1107 public int getColumnDiameter() { 1108 return getColumnRadius() * 2; 1109 } 1110 1111 /** 1112 * Returns the gap, in Java2D units, between the two outlines that 1113 * represent the thermometer. 1114 * 1115 * @return The gap. 1116 * 1117 * @see #setGap(int) 1118 * 1119 * @since 1.0.7 1120 */ 1121 public int getGap() { 1122 return this.gap; 1123 } 1124 1125 /** 1126 * Sets the gap (in Java2D units) between the two outlines that represent 1127 * the thermometer, and sends a {@link PlotChangeEvent} to all registered 1128 * listeners. 1129 * 1130 * @param gap the new gap. 1131 * 1132 * @see #getGap() 1133 * 1134 * @since 1.0.7 1135 */ 1136 public void setGap(int gap) { 1137 this.gap = gap; 1138 fireChangeEvent(); 1139 } 1140 1141 /** 1142 * Draws the plot on a Java 2D graphics device (such as the screen or a 1143 * printer). 1144 * 1145 * @param g2 the graphics device. 1146 * @param area the area within which the plot should be drawn. 1147 * @param anchor the anchor point (<code>null</code> permitted). 1148 * @param parentState the state from the parent plot, if there is one. 1149 * @param info collects info about the drawing. 1150 */ 1151 public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 1152 PlotState parentState, 1153 PlotRenderingInfo info) { 1154 1155 RoundRectangle2D outerStem = new RoundRectangle2D.Double(); 1156 RoundRectangle2D innerStem = new RoundRectangle2D.Double(); 1157 RoundRectangle2D mercuryStem = new RoundRectangle2D.Double(); 1158 Ellipse2D outerBulb = new Ellipse2D.Double(); 1159 Ellipse2D innerBulb = new Ellipse2D.Double(); 1160 String temp = null; 1161 FontMetrics metrics = null; 1162 if (info != null) { 1163 info.setPlotArea(area); 1164 } 1165 1166 // adjust for insets... 1167 RectangleInsets insets = getInsets(); 1168 insets.trim(area); 1169 drawBackground(g2, area); 1170 1171 // adjust for padding... 1172 Rectangle2D interior = (Rectangle2D) area.clone(); 1173 this.padding.trim(interior); 1174 int midX = (int) (interior.getX() + (interior.getWidth() / 2)); 1175 int midY = (int) (interior.getY() + (interior.getHeight() / 2)); 1176 int stemTop = (int) (interior.getMinY() + getBulbRadius()); 1177 int stemBottom = (int) (interior.getMaxY() - getBulbDiameter()); 1178 Rectangle2D dataArea = new Rectangle2D.Double(midX - getColumnRadius(), 1179 stemTop, getColumnRadius(), stemBottom - stemTop); 1180 1181 outerBulb.setFrame(midX - getBulbRadius(), stemBottom, 1182 getBulbDiameter(), getBulbDiameter()); 1183 1184 outerStem.setRoundRect(midX - getColumnRadius(), interior.getMinY(), 1185 getColumnDiameter(), stemBottom + getBulbDiameter() - stemTop, 1186 getColumnDiameter(), getColumnDiameter()); 1187 1188 Area outerThermometer = new Area(outerBulb); 1189 Area tempArea = new Area(outerStem); 1190 outerThermometer.add(tempArea); 1191 1192 innerBulb.setFrame(midX - getBulbRadius() + getGap(), stemBottom 1193 + getGap(), getBulbDiameter() - getGap() * 2, getBulbDiameter() 1194 - getGap() * 2); 1195 1196 innerStem.setRoundRect(midX - getColumnRadius() + getGap(), 1197 interior.getMinY() + getGap(), getColumnDiameter() 1198 - getGap() * 2, stemBottom + getBulbDiameter() - getGap() * 2 1199 - stemTop, getColumnDiameter() - getGap() * 2, 1200 getColumnDiameter() - getGap() * 2); 1201 1202 Area innerThermometer = new Area(innerBulb); 1203 tempArea = new Area(innerStem); 1204 innerThermometer.add(tempArea); 1205 1206 if ((this.dataset != null) && (this.dataset.getValue() != null)) { 1207 double current = this.dataset.getValue().doubleValue(); 1208 double ds = this.rangeAxis.valueToJava2D(current, dataArea, 1209 RectangleEdge.LEFT); 1210 1211 int i = getColumnDiameter() - getGap() * 2; // already calculated 1212 int j = getColumnRadius() - getGap(); // already calculated 1213 int l = (i / 2); 1214 int k = (int) Math.round(ds); 1215 if (k < (getGap() + interior.getMinY())) { 1216 k = (int) (getGap() + interior.getMinY()); 1217 l = getBulbRadius(); 1218 } 1219 1220 Area mercury = new Area(innerBulb); 1221 1222 if (k < (stemBottom + getBulbRadius())) { 1223 mercuryStem.setRoundRect(midX - j, k, i, 1224 (stemBottom + getBulbRadius()) - k, l, l); 1225 tempArea = new Area(mercuryStem); 1226 mercury.add(tempArea); 1227 } 1228 1229 g2.setPaint(getCurrentPaint()); 1230 g2.fill(mercury); 1231 1232 // draw range indicators... 1233 if (this.subrangeIndicatorsVisible) { 1234 g2.setStroke(this.subrangeIndicatorStroke); 1235 Range range = this.rangeAxis.getRange(); 1236 1237 // draw start of normal range 1238 double value = this.subrangeInfo[NORMAL][RANGE_LOW]; 1239 if (range.contains(value)) { 1240 double x = midX + getColumnRadius() + 2; 1241 double y = this.rangeAxis.valueToJava2D(value, dataArea, 1242 RectangleEdge.LEFT); 1243 Line2D line = new Line2D.Double(x, y, x + 10, y); 1244 g2.setPaint(this.subrangePaint[NORMAL]); 1245 g2.draw(line); 1246 } 1247 1248 // draw start of warning range 1249 value = this.subrangeInfo[WARNING][RANGE_LOW]; 1250 if (range.contains(value)) { 1251 double x = midX + getColumnRadius() + 2; 1252 double y = this.rangeAxis.valueToJava2D(value, dataArea, 1253 RectangleEdge.LEFT); 1254 Line2D line = new Line2D.Double(x, y, x + 10, y); 1255 g2.setPaint(this.subrangePaint[WARNING]); 1256 g2.draw(line); 1257 } 1258 1259 // draw start of critical range 1260 value = this.subrangeInfo[CRITICAL][RANGE_LOW]; 1261 if (range.contains(value)) { 1262 double x = midX + getColumnRadius() + 2; 1263 double y = this.rangeAxis.valueToJava2D(value, dataArea, 1264 RectangleEdge.LEFT); 1265 Line2D line = new Line2D.Double(x, y, x + 10, y); 1266 g2.setPaint(this.subrangePaint[CRITICAL]); 1267 g2.draw(line); 1268 } 1269 } 1270 1271 // draw the axis... 1272 if ((this.rangeAxis != null) && (this.axisLocation != NONE)) { 1273 int drawWidth = AXIS_GAP; 1274 if (this.showValueLines) { 1275 drawWidth += getColumnDiameter(); 1276 } 1277 Rectangle2D drawArea; 1278 double cursor = 0; 1279 1280 switch (this.axisLocation) { 1281 case RIGHT: 1282 cursor = midX + getColumnRadius(); 1283 drawArea = new Rectangle2D.Double(cursor, 1284 stemTop, drawWidth, (stemBottom - stemTop + 1)); 1285 this.rangeAxis.draw(g2, cursor, area, drawArea, 1286 RectangleEdge.RIGHT, null); 1287 break; 1288 1289 case LEFT: 1290 default: 1291 //cursor = midX - COLUMN_RADIUS - AXIS_GAP; 1292 cursor = midX - getColumnRadius(); 1293 drawArea = new Rectangle2D.Double(cursor, stemTop, 1294 drawWidth, (stemBottom - stemTop + 1)); 1295 this.rangeAxis.draw(g2, cursor, area, drawArea, 1296 RectangleEdge.LEFT, null); 1297 break; 1298 } 1299 1300 } 1301 1302 // draw text value on screen 1303 g2.setFont(this.valueFont); 1304 g2.setPaint(this.valuePaint); 1305 metrics = g2.getFontMetrics(); 1306 switch (this.valueLocation) { 1307 case RIGHT: 1308 g2.drawString(this.valueFormat.format(current), 1309 midX + getColumnRadius() + getGap(), midY); 1310 break; 1311 case LEFT: 1312 String valueString = this.valueFormat.format(current); 1313 int stringWidth = metrics.stringWidth(valueString); 1314 g2.drawString(valueString, midX - getColumnRadius() 1315 - getGap() - stringWidth, midY); 1316 break; 1317 case BULB: 1318 temp = this.valueFormat.format(current); 1319 i = metrics.stringWidth(temp) / 2; 1320 g2.drawString(temp, midX - i, 1321 stemBottom + getBulbRadius() + getGap()); 1322 break; 1323 default: 1324 } 1325 /***/ 1326 } 1327 1328 g2.setPaint(this.thermometerPaint); 1329 g2.setFont(this.valueFont); 1330 1331 // draw units indicator 1332 metrics = g2.getFontMetrics(); 1333 int tickX1 = midX - getColumnRadius() - getGap() * 2 1334 - metrics.stringWidth(UNITS[this.units]); 1335 if (tickX1 > area.getMinX()) { 1336 g2.drawString(UNITS[this.units], tickX1, 1337 (int) (area.getMinY() + 20)); 1338 } 1339 1340 // draw thermometer outline 1341 g2.setStroke(this.thermometerStroke); 1342 g2.draw(outerThermometer); 1343 g2.draw(innerThermometer); 1344 1345 drawOutline(g2, area); 1346 } 1347 1348 /** 1349 * A zoom method that does nothing. Plots are required to support the 1350 * zoom operation. In the case of a thermometer chart, it doesn't make 1351 * sense to zoom in or out, so the method is empty. 1352 * 1353 * @param percent the zoom percentage. 1354 */ 1355 public void zoom(double percent) { 1356 // intentionally blank 1357 } 1358 1359 /** 1360 * Returns a short string describing the type of plot. 1361 * 1362 * @return A short string describing the type of plot. 1363 */ 1364 public String getPlotType() { 1365 return localizationResources.getString("Thermometer_Plot"); 1366 } 1367 1368 /** 1369 * Checks to see if a new value means the axis range needs adjusting. 1370 * 1371 * @param event the dataset change event. 1372 */ 1373 public void datasetChanged(DatasetChangeEvent event) { 1374 if (this.dataset != null) { 1375 Number vn = this.dataset.getValue(); 1376 if (vn != null) { 1377 double value = vn.doubleValue(); 1378 if (inSubrange(NORMAL, value)) { 1379 this.subrange = NORMAL; 1380 } 1381 else if (inSubrange(WARNING, value)) { 1382 this.subrange = WARNING; 1383 } 1384 else if (inSubrange(CRITICAL, value)) { 1385 this.subrange = CRITICAL; 1386 } 1387 else { 1388 this.subrange = -1; 1389 } 1390 setAxisRange(); 1391 } 1392 } 1393 super.datasetChanged(event); 1394 } 1395 1396 /** 1397 * Returns the minimum value in either the domain or the range, whichever 1398 * is displayed against the vertical axis for the particular type of plot 1399 * implementing this interface. 1400 * 1401 * @return The minimum value in either the domain or the range. 1402 * 1403 * @deprecated This method is not used. Officially deprecated in version 1404 * 1.0.6. 1405 */ 1406 public Number getMinimumVerticalDataValue() { 1407 return new Double(this.lowerBound); 1408 } 1409 1410 /** 1411 * Returns the maximum value in either the domain or the range, whichever 1412 * is displayed against the vertical axis for the particular type of plot 1413 * implementing this interface. 1414 * 1415 * @return The maximum value in either the domain or the range 1416 * 1417 * @deprecated This method is not used. Officially deprecated in version 1418 * 1.0.6. 1419 */ 1420 public Number getMaximumVerticalDataValue() { 1421 return new Double(this.upperBound); 1422 } 1423 1424 /** 1425 * Returns the data range. 1426 * 1427 * @param axis the axis. 1428 * 1429 * @return The range of data displayed. 1430 */ 1431 public Range getDataRange(ValueAxis axis) { 1432 return new Range(this.lowerBound, this.upperBound); 1433 } 1434 1435 /** 1436 * Sets the axis range to the current values in the rangeInfo array. 1437 */ 1438 protected void setAxisRange() { 1439 if ((this.subrange >= 0) && (this.followDataInSubranges)) { 1440 this.rangeAxis.setRange( 1441 new Range(this.subrangeInfo[this.subrange][DISPLAY_LOW], 1442 this.subrangeInfo[this.subrange][DISPLAY_HIGH])); 1443 } 1444 else { 1445 this.rangeAxis.setRange(this.lowerBound, this.upperBound); 1446 } 1447 } 1448 1449 /** 1450 * Returns the legend items for the plot. 1451 * 1452 * @return <code>null</code>. 1453 */ 1454 public LegendItemCollection getLegendItems() { 1455 return null; 1456 } 1457 1458 /** 1459 * Returns the orientation of the plot. 1460 * 1461 * @return The orientation (always {@link PlotOrientation#VERTICAL}). 1462 */ 1463 public PlotOrientation getOrientation() { 1464 return PlotOrientation.VERTICAL; 1465 } 1466 1467 /** 1468 * Determine whether a number is valid and finite. 1469 * 1470 * @param d the number to be tested. 1471 * 1472 * @return <code>true</code> if the number is valid and finite, and 1473 * <code>false</code> otherwise. 1474 */ 1475 protected static boolean isValidNumber(double d) { 1476 return (!(Double.isNaN(d) || Double.isInfinite(d))); 1477 } 1478 1479 /** 1480 * Returns true if the value is in the specified range, and false otherwise. 1481 * 1482 * @param subrange the subrange. 1483 * @param value the value to check. 1484 * 1485 * @return A boolean. 1486 */ 1487 private boolean inSubrange(int subrange, double value) { 1488 return (value > this.subrangeInfo[subrange][RANGE_LOW] 1489 && value <= this.subrangeInfo[subrange][RANGE_HIGH]); 1490 } 1491 1492 /** 1493 * Returns the mercury paint corresponding to the current data value. 1494 * Called from the {@link #draw(Graphics2D, Rectangle2D, Point2D, 1495 * PlotState, PlotRenderingInfo)} method. 1496 * 1497 * @return The paint (never <code>null</code>). 1498 */ 1499 private Paint getCurrentPaint() { 1500 Paint result = this.mercuryPaint; 1501 if (this.useSubrangePaint) { 1502 double value = this.dataset.getValue().doubleValue(); 1503 if (inSubrange(NORMAL, value)) { 1504 result = this.subrangePaint[NORMAL]; 1505 } 1506 else if (inSubrange(WARNING, value)) { 1507 result = this.subrangePaint[WARNING]; 1508 } 1509 else if (inSubrange(CRITICAL, value)) { 1510 result = this.subrangePaint[CRITICAL]; 1511 } 1512 } 1513 return result; 1514 } 1515 1516 /** 1517 * Tests this plot for equality with another object. The plot's dataset 1518 * is not considered in the test. 1519 * 1520 * @param obj the object (<code>null</code> permitted). 1521 * 1522 * @return <code>true</code> or <code>false</code>. 1523 */ 1524 public boolean equals(Object obj) { 1525 if (obj == this) { 1526 return true; 1527 } 1528 if (!(obj instanceof ThermometerPlot)) { 1529 return false; 1530 } 1531 ThermometerPlot that = (ThermometerPlot) obj; 1532 if (!super.equals(obj)) { 1533 return false; 1534 } 1535 if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) { 1536 return false; 1537 } 1538 if (this.axisLocation != that.axisLocation) { 1539 return false; 1540 } 1541 if (this.lowerBound != that.lowerBound) { 1542 return false; 1543 } 1544 if (this.upperBound != that.upperBound) { 1545 return false; 1546 } 1547 if (!ObjectUtilities.equal(this.padding, that.padding)) { 1548 return false; 1549 } 1550 if (!ObjectUtilities.equal(this.thermometerStroke, 1551 that.thermometerStroke)) { 1552 return false; 1553 } 1554 if (!PaintUtilities.equal(this.thermometerPaint, 1555 that.thermometerPaint)) { 1556 return false; 1557 } 1558 if (this.units != that.units) { 1559 return false; 1560 } 1561 if (this.valueLocation != that.valueLocation) { 1562 return false; 1563 } 1564 if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) { 1565 return false; 1566 } 1567 if (!PaintUtilities.equal(this.valuePaint, that.valuePaint)) { 1568 return false; 1569 } 1570 if (!ObjectUtilities.equal(this.valueFormat, that.valueFormat)) { 1571 return false; 1572 } 1573 if (!PaintUtilities.equal(this.mercuryPaint, that.mercuryPaint)) { 1574 return false; 1575 } 1576 if (this.showValueLines != that.showValueLines) { 1577 return false; 1578 } 1579 if (this.subrange != that.subrange) { 1580 return false; 1581 } 1582 if (this.followDataInSubranges != that.followDataInSubranges) { 1583 return false; 1584 } 1585 if (!equal(this.subrangeInfo, that.subrangeInfo)) { 1586 return false; 1587 } 1588 if (this.useSubrangePaint != that.useSubrangePaint) { 1589 return false; 1590 } 1591 if (this.bulbRadius != that.bulbRadius) { 1592 return false; 1593 } 1594 if (this.columnRadius != that.columnRadius) { 1595 return false; 1596 } 1597 if (this.gap != that.gap) { 1598 return false; 1599 } 1600 for (int i = 0; i < this.subrangePaint.length; i++) { 1601 if (!PaintUtilities.equal(this.subrangePaint[i], 1602 that.subrangePaint[i])) { 1603 return false; 1604 } 1605 } 1606 return true; 1607 } 1608 1609 /** 1610 * Tests two double[][] arrays for equality. 1611 * 1612 * @param array1 the first array (<code>null</code> permitted). 1613 * @param array2 the second arrray (<code>null</code> permitted). 1614 * 1615 * @return A boolean. 1616 */ 1617 private static boolean equal(double[][] array1, double[][] array2) { 1618 if (array1 == null) { 1619 return (array2 == null); 1620 } 1621 if (array2 == null) { 1622 return false; 1623 } 1624 if (array1.length != array2.length) { 1625 return false; 1626 } 1627 for (int i = 0; i < array1.length; i++) { 1628 if (!Arrays.equals(array1[i], array2[i])) { 1629 return false; 1630 } 1631 } 1632 return true; 1633 } 1634 1635 /** 1636 * Returns a clone of the plot. 1637 * 1638 * @return A clone. 1639 * 1640 * @throws CloneNotSupportedException if the plot cannot be cloned. 1641 */ 1642 public Object clone() throws CloneNotSupportedException { 1643 1644 ThermometerPlot clone = (ThermometerPlot) super.clone(); 1645 1646 if (clone.dataset != null) { 1647 clone.dataset.addChangeListener(clone); 1648 } 1649 clone.rangeAxis = (ValueAxis) ObjectUtilities.clone(this.rangeAxis); 1650 if (clone.rangeAxis != null) { 1651 clone.rangeAxis.setPlot(clone); 1652 clone.rangeAxis.addChangeListener(clone); 1653 } 1654 clone.valueFormat = (NumberFormat) this.valueFormat.clone(); 1655 clone.subrangePaint = (Paint[]) this.subrangePaint.clone(); 1656 1657 return clone; 1658 1659 } 1660 1661 /** 1662 * Provides serialization support. 1663 * 1664 * @param stream the output stream. 1665 * 1666 * @throws IOException if there is an I/O error. 1667 */ 1668 private void writeObject(ObjectOutputStream stream) throws IOException { 1669 stream.defaultWriteObject(); 1670 SerialUtilities.writeStroke(this.thermometerStroke, stream); 1671 SerialUtilities.writePaint(this.thermometerPaint, stream); 1672 SerialUtilities.writePaint(this.valuePaint, stream); 1673 SerialUtilities.writePaint(this.mercuryPaint, stream); 1674 SerialUtilities.writeStroke(this.subrangeIndicatorStroke, stream); 1675 SerialUtilities.writeStroke(this.rangeIndicatorStroke, stream); 1676 for (int i = 0; i < 3; i++) { 1677 SerialUtilities.writePaint(this.subrangePaint[i], stream); 1678 } 1679 } 1680 1681 /** 1682 * Provides serialization support. 1683 * 1684 * @param stream the input stream. 1685 * 1686 * @throws IOException if there is an I/O error. 1687 * @throws ClassNotFoundException if there is a classpath problem. 1688 */ 1689 private void readObject(ObjectInputStream stream) throws IOException, 1690 ClassNotFoundException { 1691 stream.defaultReadObject(); 1692 this.thermometerStroke = SerialUtilities.readStroke(stream); 1693 this.thermometerPaint = SerialUtilities.readPaint(stream); 1694 this.valuePaint = SerialUtilities.readPaint(stream); 1695 this.mercuryPaint = SerialUtilities.readPaint(stream); 1696 this.subrangeIndicatorStroke = SerialUtilities.readStroke(stream); 1697 this.rangeIndicatorStroke = SerialUtilities.readStroke(stream); 1698 this.subrangePaint = new Paint[3]; 1699 for (int i = 0; i < 3; i++) { 1700 this.subrangePaint[i] = SerialUtilities.readPaint(stream); 1701 } 1702 if (this.rangeAxis != null) { 1703 this.rangeAxis.addChangeListener(this); 1704 } 1705 } 1706 1707 /** 1708 * Multiplies the range on the domain axis/axes by the specified factor. 1709 * 1710 * @param factor the zoom factor. 1711 * @param state the plot state. 1712 * @param source the source point. 1713 */ 1714 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 1715 Point2D source) { 1716 // no domain axis to zoom 1717 } 1718 1719 /** 1720 * Multiplies the range on the domain axis/axes by the specified factor. 1721 * 1722 * @param factor the zoom factor. 1723 * @param state the plot state. 1724 * @param source the source point. 1725 * @param useAnchor a flag that controls whether or not the source point 1726 * is used for the zoom anchor. 1727 * 1728 * @since 1.0.7 1729 */ 1730 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 1731 Point2D source, boolean useAnchor) { 1732 // no domain axis to zoom 1733 } 1734 1735 /** 1736 * Multiplies the range on the range axis/axes by the specified factor. 1737 * 1738 * @param factor the zoom factor. 1739 * @param state the plot state. 1740 * @param source the source point. 1741 */ 1742 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 1743 Point2D source) { 1744 this.rangeAxis.resizeRange(factor); 1745 } 1746 1747 /** 1748 * Multiplies the range on the range axis/axes by the specified factor. 1749 * 1750 * @param factor the zoom factor. 1751 * @param state the plot state. 1752 * @param source the source point. 1753 * @param useAnchor a flag that controls whether or not the source point 1754 * is used for the zoom anchor. 1755 * 1756 * @since 1.0.7 1757 */ 1758 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 1759 Point2D source, boolean useAnchor) { 1760 double anchorY = this.getRangeAxis().java2DToValue(source.getY(), 1761 state.getDataArea(), RectangleEdge.LEFT); 1762 this.rangeAxis.resizeRange(factor, anchorY); 1763 } 1764 1765 /** 1766 * This method does nothing. 1767 * 1768 * @param lowerPercent the lower percent. 1769 * @param upperPercent the upper percent. 1770 * @param state the plot state. 1771 * @param source the source point. 1772 */ 1773 public void zoomDomainAxes(double lowerPercent, double upperPercent, 1774 PlotRenderingInfo state, Point2D source) { 1775 // no domain axis to zoom 1776 } 1777 1778 /** 1779 * Zooms the range axes. 1780 * 1781 * @param lowerPercent the lower percent. 1782 * @param upperPercent the upper percent. 1783 * @param state the plot state. 1784 * @param source the source point. 1785 */ 1786 public void zoomRangeAxes(double lowerPercent, double upperPercent, 1787 PlotRenderingInfo state, Point2D source) { 1788 this.rangeAxis.zoomRange(lowerPercent, upperPercent); 1789 } 1790 1791 /** 1792 * Returns <code>false</code>. 1793 * 1794 * @return A boolean. 1795 */ 1796 public boolean isDomainZoomable() { 1797 return false; 1798 } 1799 1800 /** 1801 * Returns <code>true</code>. 1802 * 1803 * @return A boolean. 1804 */ 1805 public boolean isRangeZoomable() { 1806 return true; 1807 } 1808 1809 }