Source for org.jfree.chart.plot.ThermometerPlot

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