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