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