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