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