    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            fireChangeEvent();
    484        }
    485
    486        /**
    487         * Returns the lower bound for the thermometer.  The data value can be set
    488         * lower than this, but it will not be shown in the thermometer.
    489         *
    490         * @return The lower bound.
    491         *
    492         * @see #setLowerBound(double)
    493         */
    494        public double getLowerBound() {
    495            return this.lowerBound;
    496        }
    497
    498        /**
    499         * Sets the lower bound for the thermometer.
    500         *
    501         * @param lower the lower bound.
    502         *
    503         * @see #getLowerBound()
    504         */
    505        public void setLowerBound(double lower) {
    506            this.lowerBound = lower;
    507            setAxisRange();
    508        }
    509
    510        /**
    511         * Returns the upper bound for the thermometer.  The data value can be set
    512         * higher than this, but it will not be shown in the thermometer.
    513         *
    514         * @return The upper bound.
    515         *
    516         * @see #setUpperBound(double)
    517         */
    518        public double getUpperBound() {
    519            return this.upperBound;
    520        }
    521
    522        /**
    523         * Sets the upper bound for the thermometer.
    524         *
    525         * @param upper the upper bound.
    526         *
    527         * @see #getUpperBound()
    528         */
    529        public void setUpperBound(double upper) {
    530            this.upperBound = upper;
    531            setAxisRange();
    532        }
    533
    534        /**
    535         * Sets the lower and upper bounds for the thermometer.
    536         *
    537         * @param lower  the lower bound.
    538         * @param upper  the upper bound.
    539         */
    540        public void setRange(double lower, double upper) {
    541            this.lowerBound = lower;
    542            this.upperBound = upper;
    543            setAxisRange();
    544        }
    545
    546        /**
    547         * Returns the padding for the thermometer.  This is the space inside the
    548         * plot area.
    549         *
    550         * @return The padding (never <code>null</code>).
    551         *
    552         * @see #setPadding(RectangleInsets)
    553         */
    554        public RectangleInsets getPadding() {
    555            return this.padding;
    556        }
    557
    558        /**
    559         * Sets the padding for the thermometer and sends a {@link PlotChangeEvent}
    560         * to all registered listeners.
    561         *
    562         * @param padding  the padding (<code>null</code> not permitted).
    563         *
    564         * @see #getPadding()
    565         */
    566        public void setPadding(RectangleInsets padding) {
    567            if (padding == null) {
    568                throw new IllegalArgumentException("Null 'padding' argument.");
    569            }
    570            this.padding = padding;
    571            fireChangeEvent();
    572        }
    573
    574        /**
    575         * Returns the stroke used to draw the thermometer outline.
    576         *
    577         * @return The stroke (never <code>null</code>).
    578         *
    579         * @see #setThermometerStroke(Stroke)
    580         * @see #getThermometerPaint()
    581         */
    582        public Stroke getThermometerStroke() {
    583            return this.thermometerStroke;
    584        }
    585
    586        /**
    587         * Sets the stroke used to draw the thermometer outline and sends a
    588         * {@link PlotChangeEvent} to all registered listeners.
    589         *
    590         * @param s  the new stroke (<code>null</code> ignored).
    591         *
    592         * @see #getThermometerStroke()
    593         */
    594        public void setThermometerStroke(Stroke s) {
    595            if (s != null) {
    596                this.thermometerStroke = s;
    597                fireChangeEvent();
    598            }
    599        }
    600
    601        /**
    602         * Returns the paint used to draw the thermometer outline.
    603         *
    604         * @return The paint (never <code>null</code>).
    605         *
    606         * @see #setThermometerPaint(Paint)
    607         * @see #getThermometerStroke()
    608         */
    609        public Paint getThermometerPaint() {
    610            return this.thermometerPaint;
    611        }
    612
    613        /**
    614         * Sets the paint used to draw the thermometer outline and sends a
    615         * {@link PlotChangeEvent} to all registered listeners.
    616         *
    617         * @param paint  the new paint (<code>null</code> ignored).
    618         *
    619         * @see #getThermometerPaint()
    620         */
    621        public void setThermometerPaint(Paint paint) {
    622            if (paint != null) {
    623                this.thermometerPaint = paint;
    624                fireChangeEvent();
    625            }
    626        }
    627
    628        /**
    629         * Returns a code indicating the unit display type.  This is one of
    630         * {@link #UNITS_NONE}, {@link #UNITS_FAHRENHEIT}, {@link #UNITS_CELCIUS}
    631         * and {@link #UNITS_KELVIN}.
    632         *
    633         * @return The units type.
    634         *
    635         * @see #setUnits(int)
    636         */
    637        public int getUnits() {
    638            return this.units;
    639        }
    640
    641        /**
    642         * Sets the units to be displayed in the thermometer. Use one of the
    643         * following constants:
    644         *
    645         * <ul>
    646         * <li>UNITS_NONE : no units displayed.</li>
    647         * <li>UNITS_FAHRENHEIT : units displayed in Fahrenheit.</li>
    648         * <li>UNITS_CELCIUS : units displayed in Celcius.</li>
    649         * <li>UNITS_KELVIN : units displayed in Kelvin.</li>
    650         * </ul>
    651         *
    652         * @param u  the new unit type.
    653         *
    654         * @see #getUnits()
    655         */
    656        public void setUnits(int u) {
    657            if ((u >= 0) && (u < UNITS.length)) {
    658                if (this.units != u) {
    659                    this.units = u;
    660                    fireChangeEvent();
    661                }
    662            }
    663        }
    664
    665        /**
    666         * Sets the unit type.
    667         *
    668         * @param u  the unit type (<code>null</code> ignored).
    669         *
    670         * @deprecated Use setUnits(int) instead.  Deprecated as of version 1.0.6,
    671         *     because this method is a little obscure and redundant anyway.
    672         */
    673        public void setUnits(String u) {
    674            if (u == null) {
    675                return;
    676            }
    677
    678            u = u.toUpperCase().trim();
    679            for (int i = 0; i < UNITS.length; ++i) {
    680                if (u.equals(UNITS[i].toUpperCase().trim())) {
    681                    setUnits(i);
    682                    i = UNITS.length;
    683                }
    684            }
    685        }
    686
    687        /**
    688         * Returns a code indicating the location at which the value label is
    689         * displayed.
    690         *
    691         * @return The location (one of {@link #NONE}, {@link #RIGHT},
    692         *         {@link #LEFT} and {@link #BULB}.).
    693         */
    694        public int getValueLocation() {
    695            return this.valueLocation;
    696        }
    697
    698        /**
    699         * Sets the location at which the current value is displayed and sends a
    700         * {@link PlotChangeEvent} to all registered listeners.
    701         * <P>
    702         * The location can be one of the constants:
    703         * <code>NONE</code>,
    704         * <code>RIGHT</code>
    705         * <code>LEFT</code> and
    706         * <code>BULB</code>.
    707         *
    708         * @param location  the location.
    709         */
    710        public void setValueLocation(int location) {
    711            if ((location >= 0) && (location < 4)) {
    712                this.valueLocation = location;
    713                fireChangeEvent();
    714            }
    715            else {
    716                throw new IllegalArgumentException("Location not recognised.");
    717            }
    718        }
    719
    720        /**
    721         * Returns the axis location.
    722         *
    723         * @return The location (one of {@link #NONE}, {@link #LEFT} and
    724         *         {@link #RIGHT}).
    725         *
    726         * @see #setAxisLocation(int)
    727         */
    728        public int getAxisLocation() {
    729            return this.axisLocation;
    730        }
    731
    732        /**
    733         * Sets the location at which the axis is displayed relative to the
    734         * thermometer, and sends a {@link PlotChangeEvent} to all registered
    735         * listeners.
    736         *
    737         * @param location  the location (one of {@link #NONE}, {@link #LEFT} and
    738         *         {@link #RIGHT}).
    739         *
    740         * @see #getAxisLocation()
    741         */
    742        public void setAxisLocation(int location) {
    743            if ((location >= 0) && (location < 3)) {
    744                this.axisLocation = location;
    745                fireChangeEvent();
    746            }
    747            else {
    748                throw new IllegalArgumentException("Location not recognised.");
    749            }
    750        }
    751
    752        /**
    753         * Gets the font used to display the current value.
    754         *
    755         * @return The font.
    756         *
    757         * @see #setValueFont(Font)
    758         */
    759        public Font getValueFont() {
    760            return this.valueFont;
    761        }
    762
    763        /**
    764         * Sets the font used to display the current value.
    765         *
    766         * @param f  the new font (<code>null</code> not permitted).
    767         *
    768         * @see #getValueFont()
    769         */
    770        public void setValueFont(Font f) {
    771            if (f == null) {
    772                throw new IllegalArgumentException("Null 'font' argument.");
    773            }
    774            if (!this.valueFont.equals(f)) {
    775                this.valueFont = f;
    776                fireChangeEvent();
    777            }
    778        }
    779
    780        /**
    781         * Gets the paint used to display the current value.
    782        *
    783         * @return The paint.
    784         *
    785         * @see #setValuePaint(Paint)
    786         */
    787        public Paint getValuePaint() {
    788            return this.valuePaint;
    789        }
    790
    791        /**
    792         * Sets the paint used to display the current value and sends a
    793         * {@link PlotChangeEvent} to all registered listeners.
    794         *
    795         * @param paint  the new paint (<code>null</code> not permitted).
    796         *
    797         * @see #getValuePaint()
    798         */
    799        public void setValuePaint(Paint paint) {
    800            if (paint == null) {
    801                throw new IllegalArgumentException("Null 'paint' argument.");
    802            }
    803            if (!this.valuePaint.equals(paint)) {
    804                this.valuePaint = paint;
    805                fireChangeEvent();
    806            }
    807        }
    808
    809        // FIXME: No getValueFormat() method?
    810
    811        /**
    812         * Sets the formatter for the value label and sends a
    813         * {@link PlotChangeEvent} to all registered listeners.
    814         *
    815         * @param formatter  the new formatter (<code>null</code> not permitted).
    816         */
    817        public void setValueFormat(NumberFormat formatter) {
    818            if (formatter == null) {
    819                throw new IllegalArgumentException("Null 'formatter' argument.");
    820            }
    821            this.valueFormat = formatter;
    822            fireChangeEvent();
    823        }
    824
    825        /**
    826         * Returns the default mercury paint.
    827         *
    828         * @return The paint (never <code>null</code>).
    829         *
    830         * @see #setMercuryPaint(Paint)
    831         */
    832        public Paint getMercuryPaint() {
    833            return this.mercuryPaint;
    834        }
    835
    836        /**
    837         * Sets the default mercury paint and sends a {@link PlotChangeEvent} to
    838         * all registered listeners.
    839         *
    840         * @param paint  the new paint (<code>null</code> not permitted).
    841         *
    842         * @see #getMercuryPaint()
    843         */
    844        public void setMercuryPaint(Paint paint) {
    845            if (paint == null) {
    846                throw new IllegalArgumentException("Null 'paint' argument.");
    847            }
    848            this.mercuryPaint = paint;
    849            fireChangeEvent();
    850        }
    851
    852        /**
    853         * Returns the flag that controls whether not value lines are displayed.
    854         *
    855         * @return The flag.
    856         *
    857         * @see #setShowValueLines(boolean)
    858         *
    859         * @deprecated This flag doesn't do anything useful/visible.  Deprecated
    860         *     as of version 1.0.6.
    861         */
    862        public boolean getShowValueLines() {
    863            return this.showValueLines;
    864        }
    865
    866        /**
    867         * Sets the display as to whether to show value lines in the output.
    868         *
    869         * @param b Whether to show value lines in the thermometer
    870         *
    871         * @see #getShowValueLines()
    872         *
    873         * @deprecated This flag doesn't do anything useful/visible.  Deprecated
    874         *     as of version 1.0.6.
    875         */
    876        public void setShowValueLines(boolean b) {
    877            this.showValueLines = b;
    878            fireChangeEvent();
    879        }
    880
    881        /**
    882         * Sets information for a particular range.
    883         *
    884         * @param range  the range to specify information about.
    885         * @param low  the low value for the range
    886         * @param hi  the high value for the range
    887         */
    888        public void setSubrangeInfo(int range, double low, double hi) {
    889            setSubrangeInfo(range, low, hi, low, hi);
    890        }
    891
    892        /**
    893         * Sets the subrangeInfo attribute of the ThermometerPlot object
    894         *
    895         * @param range  the new rangeInfo value.
    896         * @param rangeLow  the new rangeInfo value
    897         * @param rangeHigh  the new rangeInfo value
    898         * @param displayLow  the new rangeInfo value
    899         * @param displayHigh  the new rangeInfo value
    900         */
    901        public void setSubrangeInfo(int range,
    902                                    double rangeLow, double rangeHigh,
    903                                    double displayLow, double displayHigh) {
    904
    905            if ((range >= 0) && (range < 3)) {
    906                setSubrange(range, rangeLow, rangeHigh);
    907                setDisplayRange(range, displayLow, displayHigh);
    908                setAxisRange();
    909                fireChangeEvent();
    910            }
    911
    912        }
    913
    914        /**
    915         * Sets the bounds for a subrange.
    916         *
    917         * @param range  the range type.
    918         * @param low  the low value.
    919         * @param high  the high value.
    920         */
    921        public void setSubrange(int range, double low, double high) {
    922            if ((range >= 0) && (range < 3)) {
    923                this.subrangeInfo[range][RANGE_HIGH] = high;
    924                this.subrangeInfo[range][RANGE_LOW] = low;
    925            }
    926        }
    927
    928        /**
    929         * Sets the displayed bounds for a sub range.
    930         *
    931         * @param range  the range type.
    932         * @param low  the low value.
    933         * @param high  the high value.
    934         */
    935        public void setDisplayRange(int range, double low, double high) {
    936
    937            if ((range >= 0) && (range < this.subrangeInfo.length)
    938                && isValidNumber(high) && isValidNumber(low)) {
    939
    940                if (high > low) {
    941                    this.subrangeInfo[range][DISPLAY_HIGH] = high;
    942                    this.subrangeInfo[range][DISPLAY_LOW] = low;
    943                }
    944                else {
    945                    this.subrangeInfo[range][DISPLAY_HIGH] = low;
    946                    this.subrangeInfo[range][DISPLAY_LOW] = high;
    947                }
    948
    949            }
    950
    951        }
    952
    953        /**
    954         * Gets the paint used for a particular subrange.
    955         *
    956         * @param range  the range (.
    957         *
    958         * @return The paint.
    959         *
    960         * @see #setSubrangePaint(int, Paint)
    961         */
    962        public Paint getSubrangePaint(int range) {
    963            if ((range >= 0) && (range < this.subrangePaint.length)) {
    964                return this.subrangePaint[range];
    965            }
    966            else {
    967                return this.mercuryPaint;
    968            }
    969        }
    970
    971        /**
    972         * Sets the paint to be used for a subrange and sends a
    973         * {@link PlotChangeEvent} to all registered listeners.
    974         *
    975         * @param range  the range (0, 1 or 2).
    976         * @param paint  the paint to be applied (<code>null</code> not permitted).
    977         *
    978         * @see #getSubrangePaint(int)
    979         */
    980        public void setSubrangePaint(int range, Paint paint) {
    981            if ((range >= 0)
    982                    && (range < this.subrangePaint.length) && (paint != null)) {
    983                this.subrangePaint[range] = paint;
    984                fireChangeEvent();
    985            }
    986        }
    987
    988        /**
    989         * Returns a flag that controls whether or not the thermometer axis zooms
    990         * to display the subrange within which the data value falls.
    991         *
    992         * @return The flag.
    993         */
    994        public boolean getFollowDataInSubranges() {
    995            return this.followDataInSubranges;
    996        }
    997
    998        /**
    999         * Sets the flag that controls whether or not the thermometer axis zooms
    1000         * to display the subrange within which the data value falls.
    1001         *
    1002         * @param flag  the flag.
    1003         */
    1004        public void setFollowDataInSubranges(boolean flag) {
    1005            this.followDataInSubranges = flag;
    1006            fireChangeEvent();
    1007        }
    1008
    1009        /**
    1010         * Returns a flag that controls whether or not the mercury color changes
    1011         * for each subrange.
    1012         *
    1013         * @return The flag.
    1014         *
    1015         * @see #setUseSubrangePaint(boolean)
    1016         */
    1017        public boolean getUseSubrangePaint() {
    1018            return this.useSubrangePaint;
    1019        }
    1020
    1021        /**
    1022         * Sets the range colour change option.
    1023         *
    1024         * @param flag the new range colour change option
    1025         *
    1026         * @see #getUseSubrangePaint()
    1027         */
    1028        public void setUseSubrangePaint(boolean flag) {
    1029            this.useSubrangePaint = flag;
    1030            fireChangeEvent();
    1031        }
    1032
    1033        /**
    1034         * Returns the bulb radius, in Java2D units.
    1035
    1036         * @return The bulb radius.
    1037         *
    1038         * @since 1.0.7
    1039         */
    1040        public int getBulbRadius() {
    1041            return this.bulbRadius;
    1042        }
    1043
    1044        /**
    1045         * Sets the bulb radius (in Java2D units) and sends a
    1046         * {@link PlotChangeEvent} to all registered listeners.
    1047         *
    1048         * @param r  the new radius (in Java2D units).
    1049         *
    1050         * @see #getBulbRadius()
    1051         *
    1052         * @since 1.0.7
    1053         */
    1054        public void setBulbRadius(int r) {
    1055            this.bulbRadius = r;
    1056            fireChangeEvent();
    1057        }
    1058
    1059        /**
    1060         * Returns the bulb diameter, which is always twice the value returned
    1061         * by {@link #getBulbRadius()}.
    1062         *
    1063         * @return The bulb diameter.
    1064         *
    1065         * @since 1.0.7
    1066         */
    1067        public int getBulbDiameter() {
    1068            return getBulbRadius() * 2;
    1069        }
    1070
    1071        /**
    1072         * Returns the column radius, in Java2D units.
    1073         *
    1074         * @return The column radius.
    1075         *
    1076         * @see #setColumnRadius(int)
    1077         *
    1078         * @since 1.0.7
    1079         */
    1080        public int getColumnRadius() {
    1081            return this.columnRadius;
    1082        }
    1083
    1084        /**
    1085         * Sets the column radius (in Java2D units) and sends a
    1086         * {@link PlotChangeEvent} to all registered listeners.
    1087         *
    1088         * @param r  the new radius.
    1089         *
    1090         * @see #getColumnRadius()
    1091         *
    1092         * @since 1.0.7
    1093         */
    1094        public void setColumnRadius(int r) {
    1095            this.columnRadius = r;
    1096            fireChangeEvent();
    1097        }
    1098
    1099        /**
    1100         * Returns the column diameter, which is always twice the value returned
    1101         * by {@link #getColumnRadius()}.
    1102         *
    1103         * @return The column diameter.
    1104         *
    1105         * @since 1.0.7
    1106         */
    1107        public int getColumnDiameter() {
    1108            return getColumnRadius() * 2;
    1109        }
    1110
    1111        /**
    1112         * Returns the gap, in Java2D units, between the two outlines that
    1113         * represent the thermometer.
    1114         *
    1115         * @return The gap.
    1116         *
    1117         * @see #setGap(int)
    1118         *
    1119         * @since 1.0.7
    1120         */
    1121        public int getGap() {
    1122            return this.gap;
    1123        }
    1124
    1125        /**
    1126         * Sets the gap (in Java2D units) between the two outlines that represent
    1127         * the thermometer, and sends a {@link PlotChangeEvent} to all registered
    1128         * listeners.
    1129         *
    1130         * @param gap  the new gap.
    1131         *
    1132         * @see #getGap()
    1133         *
    1134         * @since 1.0.7
    1135         */
    1136        public void setGap(int gap) {
    1137            this.gap = gap;
    1138            fireChangeEvent();
    1139        }
    1140
    1141        /**
    1142         * Draws the plot on a Java 2D graphics device (such as the screen or a
    1143         * printer).
    1144         *
    1145         * @param g2  the graphics device.
    1146         * @param area  the area within which the plot should be drawn.
    1147         * @param anchor  the anchor point (<code>null</code> permitted).
    1148         * @param parentState  the state from the parent plot, if there is one.
    1149         * @param info  collects info about the drawing.
    1150         */
    1151        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
    1152                         PlotState parentState,
    1153                         PlotRenderingInfo info) {
    1154
    1155            RoundRectangle2D outerStem = new RoundRectangle2D.Double();
    1156            RoundRectangle2D innerStem = new RoundRectangle2D.Double();
    1157            RoundRectangle2D mercuryStem = new RoundRectangle2D.Double();
    1158            Ellipse2D outerBulb = new Ellipse2D.Double();
    1159            Ellipse2D innerBulb = new Ellipse2D.Double();
    1160            String temp = null;
    1161            FontMetrics metrics = null;
    1162            if (info != null) {
    1163                info.setPlotArea(area);
    1164            }
    1165
    1166            // adjust for insets...
    1167            RectangleInsets insets = getInsets();
    1168            insets.trim(area);
    1169            drawBackground(g2, area);
    1170
    1171            // adjust for padding...
    1172            Rectangle2D interior = (Rectangle2D) area.clone();
    1173            this.padding.trim(interior);
    1174            int midX = (int) (interior.getX() + (interior.getWidth() / 2));
    1175            int midY = (int) (interior.getY() + (interior.getHeight() / 2));
    1176            int stemTop = (int) (interior.getMinY() + getBulbRadius());
    1177            int stemBottom = (int) (interior.getMaxY() - getBulbDiameter());
    1178            Rectangle2D dataArea = new Rectangle2D.Double(midX - getColumnRadius(),
    1179                    stemTop, getColumnRadius(), stemBottom - stemTop);
    1180
    1181            outerBulb.setFrame(midX - getBulbRadius(), stemBottom,
    1182                    getBulbDiameter(), getBulbDiameter());
    1183
    1184            outerStem.setRoundRect(midX - getColumnRadius(), interior.getMinY(),
    1185                    getColumnDiameter(), stemBottom + getBulbDiameter() - stemTop,
    1186                    getColumnDiameter(), getColumnDiameter());
    1187
    1188            Area outerThermometer = new Area(outerBulb);
    1189            Area tempArea = new Area(outerStem);
    1190            outerThermometer.add(tempArea);
    1191
    1192            innerBulb.setFrame(midX - getBulbRadius() + getGap(), stemBottom
    1193                    + getGap(), getBulbDiameter() - getGap() * 2, getBulbDiameter()
    1194                    - getGap() * 2);
    1195
    1196            innerStem.setRoundRect(midX - getColumnRadius() + getGap(),
    1197                    interior.getMinY() + getGap(), getColumnDiameter()
    1198                    - getGap() * 2, stemBottom + getBulbDiameter() - getGap() * 2
    1199                    - stemTop, getColumnDiameter() - getGap() * 2,
    1200                    getColumnDiameter() - getGap() * 2);
    1201
    1202            Area innerThermometer = new Area(innerBulb);
    1203            tempArea = new Area(innerStem);
    1204            innerThermometer.add(tempArea);
    1205
    1206            if ((this.dataset != null) && (this.dataset.getValue() != null)) {
    1207                double current = this.dataset.getValue().doubleValue();
    1208                double ds = this.rangeAxis.valueToJava2D(current, dataArea,
    1209                        RectangleEdge.LEFT);
    1210
    1211                int i = getColumnDiameter() - getGap() * 2; // already calculated
    1212                int j = getColumnRadius() - getGap(); // already calculated
    1213                int l = (i / 2);
    1214                int k = (int) Math.round(ds);
    1215                if (k < (getGap() + interior.getMinY())) {
    1216                    k = (int) (getGap() + interior.getMinY());
    1217                    l = getBulbRadius();
    1218                }
    1219
    1220                Area mercury = new Area(innerBulb);
    1221
    1222                if (k < (stemBottom + getBulbRadius())) {
    1223                    mercuryStem.setRoundRect(midX - j, k, i,
    1224                            (stemBottom + getBulbRadius()) - k, l, l);
    1225                    tempArea = new Area(mercuryStem);
    1226                    mercury.add(tempArea);
    1227                }
    1228
    1229                g2.setPaint(getCurrentPaint());
    1230                g2.fill(mercury);
    1231
    1232                // draw range indicators...
    1233                if (this.subrangeIndicatorsVisible) {
    1234                    g2.setStroke(this.subrangeIndicatorStroke);
    1235                    Range range = this.rangeAxis.getRange();
    1236
    1237                    // draw start of normal range
    1238                    double value = this.subrangeInfo[NORMAL][RANGE_LOW];
    1239                    if (range.contains(value)) {
    1240                        double x = midX + getColumnRadius() + 2;
    1241                        double y = this.rangeAxis.valueToJava2D(value, dataArea,
    1242                                RectangleEdge.LEFT);
    1243                        Line2D line = new Line2D.Double(x, y, x + 10, y);
    1244                        g2.setPaint(this.subrangePaint[NORMAL]);
    1245                        g2.draw(line);
    1246                    }
    1247
    1248                    // draw start of warning range
    1249                    value = this.subrangeInfo[WARNING][RANGE_LOW];
    1250                    if (range.contains(value)) {
    1251                        double x = midX + getColumnRadius() + 2;
    1252                        double y = this.rangeAxis.valueToJava2D(value, dataArea,
    1253                                RectangleEdge.LEFT);
    1254                        Line2D line = new Line2D.Double(x, y, x + 10, y);
    1255                        g2.setPaint(this.subrangePaint[WARNING]);
    1256                        g2.draw(line);
    1257                    }
    1258
    1259                    // draw start of critical range
    1260                    value = this.subrangeInfo[CRITICAL][RANGE_LOW];
    1261                    if (range.contains(value)) {
    1262                        double x = midX + getColumnRadius() + 2;
    1263                        double y = this.rangeAxis.valueToJava2D(value, dataArea,
    1264                                RectangleEdge.LEFT);
    1265                        Line2D line = new Line2D.Double(x, y, x + 10, y);
    1266                        g2.setPaint(this.subrangePaint[CRITICAL]);
    1267                        g2.draw(line);
    1268                    }
    1269                }
    1270
    1271                // draw the axis...
    1272                if ((this.rangeAxis != null) && (this.axisLocation != NONE)) {
    1273                    int drawWidth = AXIS_GAP;
    1274                    if (this.showValueLines) {
    1275                        drawWidth += getColumnDiameter();
    1276                    }
    1277                    Rectangle2D drawArea;
    1278                    double cursor = 0;
    1279
    1280                    switch (this.axisLocation) {
    1281                        case RIGHT:
    1282                            cursor = midX + getColumnRadius();
    1283                            drawArea = new Rectangle2D.Double(cursor,
    1284                                    stemTop, drawWidth, (stemBottom - stemTop + 1));
    1285                            this.rangeAxis.draw(g2, cursor, area, drawArea,
    1286                                    RectangleEdge.RIGHT, null);
    1287                            break;
    1288
    1289                        case LEFT:
    1290                        default:
    1291                            //cursor = midX - COLUMN_RADIUS - AXIS_GAP;
    1292                            cursor = midX - getColumnRadius();
    1293                            drawArea = new Rectangle2D.Double(cursor, stemTop,
    1294                                    drawWidth, (stemBottom - stemTop + 1));
    1295                            this.rangeAxis.draw(g2, cursor, area, drawArea,
    1296                                    RectangleEdge.LEFT, null);
    1297                            break;
    1298                    }
    1299
    1300                }
    1301
    1302                // draw text value on screen
    1303                g2.setFont(this.valueFont);
    1304                g2.setPaint(this.valuePaint);
    1305                metrics = g2.getFontMetrics();
    1306                switch (this.valueLocation) {
    1307                    case RIGHT:
    1308                        g2.drawString(this.valueFormat.format(current),
    1309                                midX + getColumnRadius() + getGap(), midY);
    1310                        break;
    1311                    case LEFT:
    1312                        String valueString = this.valueFormat.format(current);
    1313                        int stringWidth = metrics.stringWidth(valueString);
    1314                        g2.drawString(valueString, midX - getColumnRadius()
    1315                                - getGap() - stringWidth, midY);
    1316                        break;
    1317                    case BULB:
    1318                        temp = this.valueFormat.format(current);
    1319                        i = metrics.stringWidth(temp) / 2;
    1320                        g2.drawString(temp, midX - i,
    1321                                stemBottom + getBulbRadius() + getGap());
    1322                        break;
    1323                    default:
    1324                }
    1325                /***/
    1326            }
    1327
    1328            g2.setPaint(this.thermometerPaint);
    1329            g2.setFont(this.valueFont);
    1330
    1331            //  draw units indicator
    1332            metrics = g2.getFontMetrics();
    1333            int tickX1 = midX - getColumnRadius() - getGap() * 2
    1334                         - metrics.stringWidth(UNITS[this.units]);
    1335            if (tickX1 > area.getMinX()) {
    1336                g2.drawString(UNITS[this.units], tickX1,
    1337                        (int) (area.getMinY() + 20));
    1338            }
    1339
    1340            // draw thermometer outline
    1341            g2.setStroke(this.thermometerStroke);
    1342            g2.draw(outerThermometer);
    1343            g2.draw(innerThermometer);
    1344
    1345            drawOutline(g2, area);
    1346        }
    1347
    1348        /**
    1349         * A zoom method that does nothing.  Plots are required to support the
    1350         * zoom operation.  In the case of a thermometer chart, it doesn't make
    1351         * sense to zoom in or out, so the method is empty.
    1352         *
    1353         * @param percent  the zoom percentage.
    1354         */
    1355        public void zoom(double percent) {
    1356            // intentionally blank
    1357       }
    1358
    1359        /**
    1360         * Returns a short string describing the type of plot.
    1361         *
    1362         * @return A short string describing the type of plot.
    1363         */
    1364        public String getPlotType() {
    1365            return localizationResources.getString("Thermometer_Plot");
    1366        }
    1367
    1368        /**
    1369         * Checks to see if a new value means the axis range needs adjusting.
    1370         *
    1371         * @param event  the dataset change event.
    1372         */
    1373        public void datasetChanged(DatasetChangeEvent event) {
    1374            if (this.dataset != null) {
    1375                Number vn = this.dataset.getValue();
    1376                if (vn != null) {
    1377                    double value = vn.doubleValue();
    1378                    if (inSubrange(NORMAL, value)) {
    1379                        this.subrange = NORMAL;
    1380                    }
    1381                    else if (inSubrange(WARNING, value)) {
    1382                       this.subrange = WARNING;
    1383                    }
    1384                    else if (inSubrange(CRITICAL, value)) {
    1385                        this.subrange = CRITICAL;
    1386                    }
    1387                    else {
    1388                        this.subrange = -1;
    1389                    }
    1390                    setAxisRange();
    1391                }
    1392            }
    1393            super.datasetChanged(event);
    1394        }
    1395
    1396        /**
    1397         * Returns the minimum value in either the domain or the range, whichever
    1398         * is displayed against the vertical axis for the particular type of plot
    1399         * implementing this interface.
    1400         *
    1401         * @return The minimum value in either the domain or the range.
    1402         *
    1403         * @deprecated This method is not used.  Officially deprecated in version
    1404         *         1.0.6.
    1405         */
    1406        public Number getMinimumVerticalDataValue() {
    1407            return new Double(this.lowerBound);
    1408        }
    1409
    1410        /**
    1411         * Returns the maximum value in either the domain or the range, whichever
    1412         * is displayed against the vertical axis for the particular type of plot
    1413         * implementing this interface.
    1414         *
    1415         * @return The maximum value in either the domain or the range
    1416         *
    1417         * @deprecated This method is not used.  Officially deprecated in version
    1418         *         1.0.6.
    1419         */
    1420        public Number getMaximumVerticalDataValue() {
    1421            return new Double(this.upperBound);
    1422        }
    1423
    1424        /**
    1425         * Returns the data range.
    1426         *
    1427         * @param axis  the axis.
    1428         *
    1429         * @return The range of data displayed.
    1430         */
    1431        public Range getDataRange(ValueAxis axis) {
    1432           return new Range(this.lowerBound, this.upperBound);
    1433        }
    1434
    1435        /**
    1436         * Sets the axis range to the current values in the rangeInfo array.
    1437         */
    1438        protected void setAxisRange() {
    1439            if ((this.subrange >= 0) && (this.followDataInSubranges)) {
    1440                this.rangeAxis.setRange(
    1441                        new Range(this.subrangeInfo[this.subrange][DISPLAY_LOW],
    1442                        this.subrangeInfo[this.subrange][DISPLAY_HIGH]));
    1443            }
    1444            else {
    1445                this.rangeAxis.setRange(this.lowerBound, this.upperBound);
    1446            }
    1447        }
    1448
    1449        /**
    1450         * Returns the legend items for the plot.
    1451         *
    1452         * @return <code>null</code>.
    1453         */
    1454        public LegendItemCollection getLegendItems() {
    1455            return null;
    1456        }
    1457
    1458        /**
    1459         * Returns the orientation of the plot.
    1460         *
    1461         * @return The orientation (always {@link PlotOrientation#VERTICAL}).
    1462         */
    1463        public PlotOrientation getOrientation() {
    1464            return PlotOrientation.VERTICAL;
    1465        }
    1466
    1467        /**
    1468         * Determine whether a number is valid and finite.
    1469         *
    1470         * @param d  the number to be tested.
    1471         *
    1472         * @return <code>true</code> if the number is valid and finite, and
    1473         *         <code>false</code> otherwise.
    1474         */
    1475        protected static boolean isValidNumber(double d) {
    1476            return (!(Double.isNaN(d) || Double.isInfinite(d)));
    1477        }
    1478
    1479        /**
    1480         * Returns true if the value is in the specified range, and false otherwise.
    1481         *
    1482         * @param subrange  the subrange.
    1483         * @param value  the value to check.
    1484         *
    1485         * @return A boolean.
    1486         */
    1487        private boolean inSubrange(int subrange, double value) {
    1488            return (value > this.subrangeInfo[subrange][RANGE_LOW]
    1489                && value <= this.subrangeInfo[subrange][RANGE_HIGH]);
    1490        }
    1491
    1492        /**
    1493         * Returns the mercury paint corresponding to the current data value.
    1494         * Called from the {@link #draw(Graphics2D, Rectangle2D, Point2D,
    1495         * PlotState, PlotRenderingInfo)} method.
    1496         *
    1497         * @return The paint (never <code>null</code>).
    1498         */
    1499        private Paint getCurrentPaint() {
    1500            Paint result = this.mercuryPaint;
    1501            if (this.useSubrangePaint) {
    1502                double value = this.dataset.getValue().doubleValue();
    1503                if (inSubrange(NORMAL, value)) {
    1504                    result = this.subrangePaint[NORMAL];
    1505                }
    1506                else if (inSubrange(WARNING, value)) {
    1507                    result = this.subrangePaint[WARNING];
    1508                }
    1509                else if (inSubrange(CRITICAL, value)) {
    1510                    result = this.subrangePaint[CRITICAL];
    1511                }
    1512            }
    1513            return result;
    1514        }
    1515
    1516        /**
    1517         * Tests this plot for equality with another object.  The plot's dataset
    1518         * is not considered in the test.
    1519         *
    1520         * @param obj  the object (<code>null</code> permitted).
    1521         *
    1522         * @return <code>true</code> or <code>false</code>.
    1523         */
    1524        public boolean equals(Object obj) {
    1525            if (obj == this) {
    1526                return true;
    1527            }
    1528            if (!(obj instanceof ThermometerPlot)) {
    1529                return false;
    1530            }
    1531            ThermometerPlot that = (ThermometerPlot) obj;
    1532            if (!super.equals(obj)) {
    1533                return false;
    1534            }
    1535            if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
    1536                return false;
    1537            }
    1538            if (this.axisLocation != that.axisLocation) {
    1539                return false;
    1540            }
    1541            if (this.lowerBound != that.lowerBound) {
    1542                return false;
    1543            }
    1544            if (this.upperBound != that.upperBound) {
    1545                return false;
    1546            }
    1547            if (!ObjectUtilities.equal(this.padding, that.padding)) {
    1548                return false;
    1549            }
    1550            if (!ObjectUtilities.equal(this.thermometerStroke,
    1551                    that.thermometerStroke)) {
    1552                return false;
    1553            }
    1554            if (!PaintUtilities.equal(this.thermometerPaint,
    1555                    that.thermometerPaint)) {
    1556                return false;
    1557            }
    1558            if (this.units != that.units) {
    1559                return false;
    1560            }
    1561            if (this.valueLocation != that.valueLocation) {
    1562                return false;
    1563            }
    1564            if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) {
    1565                return false;
    1566            }
    1567            if (!PaintUtilities.equal(this.valuePaint, that.valuePaint)) {
    1568                return false;
    1569            }
    1570            if (!ObjectUtilities.equal(this.valueFormat, that.valueFormat)) {
    1571                return false;
    1572            }
    1573            if (!PaintUtilities.equal(this.mercuryPaint, that.mercuryPaint)) {
    1574                return false;
    1575            }
    1576            if (this.showValueLines != that.showValueLines) {
    1577                return false;
    1578            }
    1579            if (this.subrange != that.subrange) {
    1580                return false;
    1581            }
    1582            if (this.followDataInSubranges != that.followDataInSubranges) {
    1583                return false;
    1584            }
    1585            if (!equal(this.subrangeInfo, that.subrangeInfo)) {
    1586                return false;
    1587            }
    1588            if (this.useSubrangePaint != that.useSubrangePaint) {
    1589                return false;
    1590            }
    1591            if (this.bulbRadius != that.bulbRadius) {
    1592                return false;
    1593            }
    1594            if (this.columnRadius != that.columnRadius) {
    1595                return false;
    1596            }
    1597            if (this.gap != that.gap) {
    1598                return false;
    1599            }
    1600            for (int i = 0; i < this.subrangePaint.length; i++) {
    1601                if (!PaintUtilities.equal(this.subrangePaint[i],
    1602                        that.subrangePaint[i])) {
    1603                    return false;
    1604                }
    1605            }
    1606            return true;
    1607        }
    1608
    1609        /**
    1610         * Tests two double[][] arrays for equality.
    1611         *
    1612         * @param array1  the first array (<code>null</code> permitted).
    1613         * @param array2  the second arrray (<code>null</code> permitted).
    1614         *
    1615         * @return A boolean.
    1616         */
    1617        private static boolean equal(double[][] array1, double[][] array2) {
    1618            if (array1 == null) {
    1619                return (array2 == null);
    1620            }
    1621            if (array2 == null) {
    1622                return false;
    1623            }
    1624            if (array1.length != array2.length) {
    1625                return false;
    1626            }
    1627            for (int i = 0; i < array1.length; i++) {
    1628                if (!Arrays.equals(array1[i], array2[i])) {
    1629                    return false;
    1630                }
    1631            }
    1632            return true;
    1633        }
    1634
    1635        /**
    1636         * Returns a clone of the plot.
    1637         *
    1638         * @return A clone.
    1639         *
    1640         * @throws CloneNotSupportedException  if the plot cannot be cloned.
    1641         */
    1642        public Object clone() throws CloneNotSupportedException {
    1643
    1644            ThermometerPlot clone = (ThermometerPlot) super.clone();
    1645
    1646            if (clone.dataset != null) {
    1647                clone.dataset.addChangeListener(clone);
    1648            }
    1649            clone.rangeAxis = (ValueAxis) ObjectUtilities.clone(this.rangeAxis);
    1650            if (clone.rangeAxis != null) {
    1651                clone.rangeAxis.setPlot(clone);
    1652                clone.rangeAxis.addChangeListener(clone);
    1653            }
    1654            clone.valueFormat = (NumberFormat) this.valueFormat.clone();
    1655            clone.subrangePaint = (Paint[]) this.subrangePaint.clone();
    1656
    1657            return clone;
    1658
    1659        }
    1660
    1661        /**
    1662         * Provides serialization support.
    1663         *
    1664         * @param stream  the output stream.
    1665         *
    1666         * @throws IOException  if there is an I/O error.
    1667         */
    1668        private void writeObject(ObjectOutputStream stream) throws IOException {
    1669            stream.defaultWriteObject();
    1670            SerialUtilities.writeStroke(this.thermometerStroke, stream);
    1671            SerialUtilities.writePaint(this.thermometerPaint, stream);
    1672            SerialUtilities.writePaint(this.valuePaint, stream);
    1673            SerialUtilities.writePaint(this.mercuryPaint, stream);
    1674            SerialUtilities.writeStroke(this.subrangeIndicatorStroke, stream);
    1675            SerialUtilities.writeStroke(this.rangeIndicatorStroke, stream);
    1676            for (int i = 0; i < 3; i++) {
    1677                SerialUtilities.writePaint(this.subrangePaint[i], stream);
    1678            }
    1679        }
    1680
    1681        /**
    1682         * Provides serialization support.
    1683         *
    1684         * @param stream  the input stream.
    1685         *
    1686         * @throws IOException  if there is an I/O error.
    1687         * @throws ClassNotFoundException  if there is a classpath problem.
    1688         */
    1689        private void readObject(ObjectInputStream stream) throws IOException,
    1690                ClassNotFoundException {
    1691            stream.defaultReadObject();
    1692            this.thermometerStroke = SerialUtilities.readStroke(stream);
    1693            this.thermometerPaint = SerialUtilities.readPaint(stream);
    1694            this.valuePaint = SerialUtilities.readPaint(stream);
    1695            this.mercuryPaint = SerialUtilities.readPaint(stream);
    1696            this.subrangeIndicatorStroke = SerialUtilities.readStroke(stream);
    1697            this.rangeIndicatorStroke = SerialUtilities.readStroke(stream);
    1698            this.subrangePaint = new Paint[3];
    1699            for (int i = 0; i < 3; i++) {
    1700                this.subrangePaint[i] = SerialUtilities.readPaint(stream);
    1701            }
    1702            if (this.rangeAxis != null) {
    1703                this.rangeAxis.addChangeListener(this);
    1704            }
    1705        }
    1706
    1707        /**
    1708         * Multiplies the range on the domain axis/axes by the specified factor.
    1709         *
    1710         * @param factor  the zoom factor.
    1711         * @param state  the plot state.
    1712         * @param source  the source point.
    1713         */
    1714        public void zoomDomainAxes(double factor, PlotRenderingInfo state,
    1715                                   Point2D source) {
    1716            // no domain axis to zoom
    1717        }
    1718
    1719        /**
    1720         * Multiplies the range on the domain axis/axes by the specified factor.
    1721         *
    1722         * @param factor  the zoom factor.
    1723         * @param state  the plot state.
    1724         * @param source  the source point.
    1725         * @param useAnchor  a flag that controls whether or not the source point
    1726         *         is used for the zoom anchor.
    1727         *
    1728         * @since 1.0.7
    1729         */
    1730        public void zoomDomainAxes(double factor, PlotRenderingInfo state,
    1731                                   Point2D source, boolean useAnchor) {
    1732            // no domain axis to zoom
    1733        }
    1734
    1735        /**
    1736         * Multiplies the range on the range axis/axes by the specified factor.
    1737         *
    1738         * @param factor  the zoom factor.
    1739         * @param state  the plot state.
    1740         * @param source  the source point.
    1741         */
    1742        public void zoomRangeAxes(double factor, PlotRenderingInfo state,
    1743                                  Point2D source) {
    1744            this.rangeAxis.resizeRange(factor);
    1745        }
    1746
    1747        /**
    1748         * Multiplies the range on the range axis/axes by the specified factor.
    1749         *
    1750         * @param factor  the zoom factor.
    1751         * @param state  the plot state.
    1752         * @param source  the source point.
    1753         * @param useAnchor  a flag that controls whether or not the source point
    1754         *         is used for the zoom anchor.
    1755         *
    1756         * @since 1.0.7
    1757         */
    1758        public void zoomRangeAxes(double factor, PlotRenderingInfo state,
    1759                                  Point2D source, boolean useAnchor) {
    1760            double anchorY = this.getRangeAxis().java2DToValue(source.getY(),
    1761                    state.getDataArea(), RectangleEdge.LEFT);
    1762            this.rangeAxis.resizeRange(factor, anchorY);
    1763        }
    1764
    1765        /**
    1766         * This method does nothing.
    1767         *
    1768         * @param lowerPercent  the lower percent.
    1769         * @param upperPercent  the upper percent.
    1770         * @param state  the plot state.
    1771         * @param source  the source point.
    1772         */
    1773        public void zoomDomainAxes(double lowerPercent, double upperPercent,
    1774                                   PlotRenderingInfo state, Point2D source) {
    1775            // no domain axis to zoom
    1776        }
    1777
    1778        /**
    1779         * Zooms the range axes.
    1780         *
    1781         * @param lowerPercent  the lower percent.
    1782         * @param upperPercent  the upper percent.
    1783         * @param state  the plot state.
    1784         * @param source  the source point.
    1785         */
    1786        public void zoomRangeAxes(double lowerPercent, double upperPercent,
    1787                                  PlotRenderingInfo state, Point2D source) {
    1788            this.rangeAxis.zoomRange(lowerPercent, upperPercent);
    1789        }
    1790
    1791        /**
    1792         * Returns <code>false</code>.
    1793         *
    1794         * @return A boolean.
    1795         */
    1796        public boolean isDomainZoomable() {
    1797            return false;
    1798        }
    1799
    1800        /**
    1801         * Returns <code>true</code>.
    1802         *
    1803         * @return A boolean.
    1804         */
    1805        public boolean isRangeZoomable() {
    1806            return true;
    1807        }
    1808
    1809    }