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