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