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