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