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