Input:
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PaintUtilities;
import org.jfree.util.UnitType;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.ResourceBundle;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.event.PlotChangeEvent;
import org.jfree.chart.util.ParamChecks;
import org.jfree.chart.util.ResourceBundleWrapper;
import org.jfree.data.Range;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.general.DefaultValueDataset;
import org.jfree.data.general.ValueDataset;
import org.jfree.io.SerialUtilities;

Context:
lib/jfreechart-1.0.19/source/org/jfree/chart/plot/ThermometerPlot.java
/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.]
 *
 * --------------------
 * ThermometerPlot.java
 * --------------------
 *
 * (C) Copyright 2000-2013, by Bryan Scott and Contributors.
 *
 * Original Author:  Bryan Scott (based on MeterPlot by Hari).
 * Contributor(s):   David Gilbert (for Object Refinery Limited).
 *                   Arnaud Lelievre;
 *                   Julien Henry (see patch 1769088) (DG);
 *
 * Changes
 * -------
 * 11-Apr-2002 : Version 1, contributed by Bryan Scott;
 * 15-Apr-2002 : Changed to implement VerticalValuePlot;
 * 29-Apr-2002 : Added getVerticalValueAxis() method (DG);
 * 25-Jun-2002 : Removed redundant imports (DG);
 * 17-Sep-2002 : Reviewed with Checkstyle utility (DG);
 * 18-Sep-2002 : Extensive changes made to API, to iron out bugs and
 *               inconsistencies (DG);
 * 13-Oct-2002 : Corrected error datasetChanged which would generate exceptions
 *               when value set to null (BRS).
 * 23-Jan-2003 : Removed one constructor (DG);
 * 26-Mar-2003 : Implemented Serializable (DG);
 * 02-Jun-2003 : Removed test for compatible range axis (DG);
 * 01-Jul-2003 : Added additional check in draw method to ensure value not
 *               null (BRS);
 * 08-Sep-2003 : Added internationalization via use of properties
 *               resourceBundle (RFE 690236) (AL);
 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
 * 29-Sep-2003 : Updated draw to set value of cursor to non-zero and allow
 *               painting of axis.  An incomplete fix and needs to be set for
 *               left or right drawing (BRS);
 * 19-Nov-2003 : Added support for value labels to be displayed left of the
 *               thermometer
 * 19-Nov-2003 : Improved axis drawing (now default axis does not draw axis line
 *               and is closer to the bulb).  Added support for the positioning
 *               of the axis to the left or right of the bulb. (BRS);
 * 03-Dec-2003 : Directly mapped deprecated setData()/getData() method to
 *               get/setDataset() (TM);
 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
 * 07-Apr-2004 : Changed string width calculation (DG);
 * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
 * 06-Jan-2004 : Added getOrientation() method (DG);
 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
 * 29-Mar-2005 : Fixed equals() method (DG);
 * 05-May-2005 : Updated draw() method parameters (DG);
 * 09-Jun-2005 : Fixed more bugs in equals() method (DG);
 * 10-Jun-2005 : Fixed minor bug in setDisplayRange() method (DG);
 * ------------- JFREECHART 1.0.x ---------------------------------------------
 * 14-Nov-2006 : Fixed margin when drawing (DG);
 * 03-May-2007 : Fixed datasetChanged() to handle null dataset, added null
 *               argument check and event notification to setRangeAxis(),
 *               added null argument check to setPadding(), setValueFont(),
 *               setValuePaint(), setValueFormat() and setMercuryPaint(),
 *               deprecated get/setShowValueLines(), deprecated
 *               getMinimum/MaximumVerticalDataValue(), and fixed serialization
 *               bug (DG);
 * 24-Sep-2007 : Implemented new methods in Zoomable interface (DG);
 * 08-Oct-2007 : Added attributes for thermometer dimensions - see patch 1769088
 *               by Julien Henry (DG);
 * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
 *               Jess Thrysoee (DG);
 * 02-Jul-2013 : Use ParamChecks (DG);
 *
 */

package org.jfree.chart.plot;

/**
 * A plot that displays a single value (from a {@link ValueDataset}) in a
 * thermometer type display.
 *

 * This plot supports a number of options:
 *

 *
three sub-ranges which could be viewed as 'Normal', 'Warning'
 *   and 'Critical' ranges.

 *
the thermometer can be run in two modes:
 *

 *
fixed range, or

 *
range adjusts to current sub-range.

 *

 *

 *
settable units to be displayed.

 *
settable display location for the value text.

 *

 */
public class ThermometerPlot extends Plot implements ValueAxisPlot,
        Zoomable, Cloneable, Serializable {

    /** For serialization. */
    private static final long serialVersionUID = 4087093313147984390L;

    /** A constant for unit type 'None'. */
    public static final int UNITS_NONE = 0;

    /** A constant for unit type 'Fahrenheit'. */
    public static final int UNITS_FAHRENHEIT = 1;

    /** A constant for unit type 'Celcius'. */
    public static final int UNITS_CELCIUS = 2;

    /** A constant for unit type 'Kelvin'. */
    public static final int UNITS_KELVIN = 3;

    /** A constant for the value label position (no label). */
    public static final int NONE = 0;

    /** A constant for the value label position (right of the thermometer). */
    public static final int RIGHT = 1;

    /** A constant for the value label position (left of the thermometer). */
    public static final int LEFT = 2;

    /** A constant for the value label position (in the thermometer bulb). */
    public static final int BULB = 3;

    /** A constant for the 'normal' range. */
    public static final int NORMAL = 0;

    /** A constant for the 'warning' range. */
    public static final int WARNING = 1;

    /** A constant for the 'critical' range. */
    public static final int CRITICAL = 2;

    /**
     * The bulb radius.
     *
     * @deprecated As of 1.0.7, use {@link #getBulbRadius()}.
     */
    protected static final int BULB_RADIUS = 40;

    /**
     * The bulb diameter.
     *
     * @deprecated As of 1.0.7, use {@link #getBulbDiameter()}.
     */
    protected static final int BULB_DIAMETER = BULB_RADIUS * 2;

    /**
     * The column radius.
     *
     * @deprecated As of 1.0.7, use {@link #getColumnRadius()}.
     */
    protected static final int COLUMN_RADIUS = 20;

    /**
     * The column diameter.
     *
     * @deprecated As of 1.0.7, use {@link #getColumnDiameter()}.
     */
    protected static final int COLUMN_DIAMETER = COLUMN_RADIUS * 2;

    /**
     * The gap radius.
     *
     * @deprecated As of 1.0.7, use {@link #getGap()}.
     */
    protected static final int GAP_RADIUS = 5;

    /**
     * The gap diameter.
     *
     * @deprecated As of 1.0.7, use {@link #getGap()} times two.
     */
    protected static final int GAP_DIAMETER = GAP_RADIUS * 2;

    /** The axis gap. */
    protected static final int AXIS_GAP = 10;

    /** The unit strings. */
    protected static final String[] UNITS = {"", "\u00B0F", "\u00B0C",
            "\u00B0K"};

    /** Index for low value in subrangeInfo matrix. */
    protected static final int RANGE_LOW = 0;

    /** Index for high value in subrangeInfo matrix. */
    protected static final int RANGE_HIGH = 1;

    /** Index for display low value in subrangeInfo matrix. */
    protected static final int DISPLAY_LOW = 2;

    /** Index for display high value in subrangeInfo matrix. */
    protected static final int DISPLAY_HIGH = 3;

    /** The default lower bound. */
    protected static final double DEFAULT_LOWER_BOUND = 0.0;

    /** The default upper bound. */
    protected static final double DEFAULT_UPPER_BOUND = 100.0;

    /**
     * The default bulb radius.
     *
     * @since 1.0.7
     */
    protected static final int DEFAULT_BULB_RADIUS = 40;

    /**
     * The default column radius.
     *
     * @since 1.0.7
     */
    protected static final int DEFAULT_COLUMN_RADIUS = 20;

    /**
     * The default gap between the outlines representing the thermometer.
     *
     * @since 1.0.7
     */
    protected static final int DEFAULT_GAP = 5;

    /** The dataset for the plot. */
    private ValueDataset dataset;

    /** The range axis. */
    private ValueAxis rangeAxis;

    /** The lower bound for the thermometer. */
    private double lowerBound = DEFAULT_LOWER_BOUND;

    /** The upper bound for the thermometer. */
    private double upperBound = DEFAULT_UPPER_BOUND;

    /**
     * The value label position.
     *
     * @since 1.0.7
     */
    private int bulbRadius = DEFAULT_BULB_RADIUS;

    /**
     * The column radius.
     *
     * @since 1.0.7
     */
    private int columnRadius = DEFAULT_COLUMN_RADIUS;

    /**
     * The gap between the two outlines the represent the thermometer.
     *
     * @since 1.0.7
     */

lib/jfreechart-1.0.19/source/org/jfree/data/general/DatasetChangeEvent.java
public class DatasetChangeEvent extends java.util.EventObject {

    /**
     * The dataset that generated the change event.
     */
    private Dataset dataset;

    /**
     * Constructs a new event.  The source is either the dataset or the
     * {@link org.jfree.chart.plot.Plot} class.  The dataset can be
     * null (in this case the source will be the
     * {@link org.jfree.chart.plot.Plot} class).
     *
     * @param source  the source of the event.
     * @param dataset  the dataset that generated the event (null
     *                 permitted).
     */
    public DatasetChangeEvent(Object source, Dataset dataset) {
        super(source);
        this.dataset = dataset;
    }

    /**
     * Returns the dataset that generated the event.  Note that the dataset
     * may be null since adding a null dataset to a
     * plot will generated a change event.
     *
     * @return The dataset (possibly null).
     */
    public Dataset getDataset() {
        return this.dataset;
    }

}

lib/jfreechart-1.0.19/source/org/jfree/data/Range.java
public strictfp class Range implements Serializable {

    /** For serialization. */
    private static final long serialVersionUID = -906333695431863380L;

    /** The lower bound of the range. */
    private double lower;

    /** The upper bound of the range. */
    private double upper;

    /**
     * Creates a new range.
     *
     * @param lower  the lower bound (must be <= upper bound).
     * @param upper  the upper bound (must be >= lower bound).
     */
    public Range(double lower, double upper) {
        if (lower > upper) {
            String msg = "Range(double, double): require lower (" + lower
                + ") <= upper (" + upper + ").";
            throw new IllegalArgumentException(msg);
        }
        this.lower = lower;
        this.upper = upper;
    }

    /**
     * Returns the lower bound for the range.
     *
     * @return The lower bound.
     */
    public double getLowerBound() {
        return this.lower;
    }

    /**
     * Returns the upper bound for the range.
     *
     * @return The upper bound.
     */
    public double getUpperBound() {
        return this.upper;
    }

    /**
     * Returns the length of the range.
     *
     * @return The length.
     */
    public double getLength() {
        return this.upper - this.lower;
    }

    /**
     * Returns the central value for the range.
     *
     * @return The central value.
     */
    public double getCentralValue() {
        return this.lower / 2.0 + this.upper / 2.0;
    }

    /**
     * Returns true if the range contains the specified value and
     * false otherwise.
     *
     * @param value  the value to lookup.
     *
     * @return true if the range contains the specified value.
     */
    public boolean contains(double value) {
        return (value >= this.lower && value <= this.upper);
    }

    /**
     * Returns true if the range intersects with the specified
     * range, and false otherwise.
     *
     * @param b0  the lower bound (should be <= b1).
     * @param b1  the upper bound (should be >= b0).
     *
     * @return A boolean.
     */
    public boolean intersects(double b0, double b1) {
        if (b0 <= this.lower) {
            return (b1 > this.lower);
        }
        else {
            return (b0 < this.upper && b1 >= b0);
        }
    }

    /**
     * Returns true if the range intersects with the specified
     * range, and false otherwise.
     *
     * @param range  another range (null not permitted).
     *
     * @return A boolean.
     *
     * @since 1.0.9
     */
    public boolean intersects(Range range) {
        return intersects(range.getLowerBound(), range.getUpperBound());
    }

    /**
     * Returns the value within the range that is closest to the specified
     * value.
     *
     * @param value  the value.
     *
     * @return The constrained value.
     */
    public double constrain(double value) {
        double result = value;
        if (!contains(value)) {
            if (value > this.upper) {
                result = this.upper;
            }
            else if (value < this.lower) {
                result = this.lower;
            }
        }
        return result;
    }

    /**
     * Creates a new range by combining two existing ranges.
     *

     * Note that:
     *

     *
either range can be null, in which case the other
     *       range is returned;

     *
if both ranges are null the return value is
     *       null.

     *

     *
     * @param range1  the first range (null permitted).
     * @param range2  the second range (null permitted).
     *
     * @return A new range (possibly null).
     */
    public static Range combine(Range range1, Range range2) {
        if (range1 == null) {
            return range2;
        }
        if (range2 == null) {
            return range1;
        }
        double l = Math.min(range1.getLowerBound(), range2.getLowerBound());
        double u = Math.max(range1.getUpperBound(), range2.getUpperBound());
        return new Range(l, u);
    }

    /**
     * Returns a new range that spans both range1 and
     * range2.  This method has a special handling to ignore
     * Double.NaN values.
     *
     * @param range1  the first range (null permitted).
     * @param range2  the second range (null permitted).
     *
     * @return A new range (possibly null).
     *
     * @since 1.0.15
     */
    public static Range combineIgnoringNaN(Range range1, Range range2) {
        if (range1 == null) {
            if (range2 != null && range2.isNaNRange()) {
                return null;
            }
            return range2;
        }
        if (range2 == null) {
            if (range1.isNaNRange()) {
                return null;
            }
            return range1;
        }
        double l = min(range1.getLowerBound(), range2.getLowerBound());
        double u = max(range1.getUpperBound(), range2.getUpperBound());
        if (Double.isNaN(l) && Double.isNaN(u)) {
            return null;
        }
        return new Range(l, u);
    }

    /**
     * Returns the minimum value.  If either value is NaN, the other value is
     * returned.  If both are NaN, NaN is returned.
     *
     * @param d1  value 1.
     * @param d2  value 2.
     *
     * @return The minimum of the two values.
     */
    private static double min(double d1, double d2) {
        if (Double.isNaN(d1)) {
            return d2;
        }
        if (Double.isNaN(d2)) {
            return d1;
        }
        return Math.min(d1, d2);
    }

    private static double max(double d1, double d2) {
        if (Double.isNaN(d1)) {
            return d2;
        }
        if (Double.isNaN(d2)) {
            return d1;
        }
        return Math.max(d1, d2);
    }

    /**
     * Returns a range that includes all the values in the specified
     * range AND the specified value.
     *
     * @param range  the range (null permitted).
     * @param value  the value that must be included.
     *
     * @return A range.
     *
     * @since 1.0.1
     */
    public static Range expandToInclude(Range range, double value) {
        if (range == null) {
            return new Range(value, value);
        }
        if (value < range.getLowerBound()) {
            return new Range(value, range.getUpperBound());
        }
        else if (value > range.getUpperBound()) {
            return new Range(range.getLowerBound(), value);
        }
        else {
            return range;
        }
    }

    /**
     * Creates a new range by adding margins to an existing range.
     *
     * @param range  the range (null not permitted).
     * @param lowerMargin  the lower margin (expressed as a percentage of the
     *                     range length).
     * @param upperMargin  the upper margin (expressed as a percentage of the
     *                     range length).
     *
     * @return The expanded range.
     */
    public static Range expand(Range range,
                               double lowerMargin, double upperMargin) {
        ParamChecks.nullNotPermitted(range, "range");
        double length = range.getLength();
        double lower = range.getLowerBound() - length * lowerMargin;
        double upper = range.getUpperBound() + length * upperMargin;
        if (lower > upper) {
            lower = lower / 2.0 + upper / 2.0;
            upper = lower;
        }
        return new Range(lower, upper);
    }

    /**
     * Shifts the range by the specified amount.
     *
     * @param base  the base range (null not permitted).
     * @param delta  the shift amount.
     *
     * @return A new range.
     */
    public static Range shift(Range base, double delta) {
        return shift(base, delta, false);
    }

    /**
     * Shifts the range by the specified amount.
     *
     * @param base  the base range (null not permitted).
     * @param delta  the shift amount.
     * @param allowZeroCrossing  a flag that determines whether or not the
     *                           bounds of the range are allowed to cross
     *                           zero after adjustment.
     *
     * @return A new range.
     */
    public static Range shift(Range base, double delta,
                              boolean allowZeroCrossing) {
        ParamChecks.nullNotPermitted(base, "base");
        if (allowZeroCrossing) {
            return new Range(base.getLowerBound() + delta,
                    base.getUpperBound() + delta);
        }
        else {
            return new Range(shiftWithNoZeroCrossing(base.getLowerBound(),
                    delta), shiftWithNoZeroCrossing(base.getUpperBound(),
                    delta));
        }
    }

    /**
     * Returns the given value adjusted by delta but
     * with a check to prevent the result from crossing 0.0.
     *
     * @param value  the value.
     * @param delta  the adjustment.
     *
     * @return The adjusted value.
     */
    private static double shiftWithNoZeroCrossing(double value, double delta) {
        if (value > 0.0) {
            return Math.max(value + delta, 0.0);
        }
        else if (value < 0.0) {
            return Math.min(value + delta, 0.0);
        }
        else {
            return value + delta;
        }
    }

    /**
     * Scales the range by the specified factor.
     *
     * @param base the base range (null not permitted).
     * @param factor the scaling factor (must be non-negative).
     *
     * @return A new range.
     *
     * @since 1.0.9
     */
    public static Range scale(Range base, double factor) {
        ParamChecks.nullNotPermitted(base, "base");
        if (factor < 0) {
            throw new IllegalArgumentException("Negative 'factor' argument.");
        }
        return new Range(base.getLowerBound() * factor,
                base.getUpperBound() * factor);
    }

    /**
     * Tests this object for equality with an arbitrary object.
     *
     * @param obj  the object to test against (null permitted).
     *
     * @return A boolean.
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Range)) {
            return false;
        }
        Range range = (Range) obj;
        if (!(this.lower == range.lower)) {
            return false;
        }
        if (!(this.upper == range.upper)) {
            return false;
        }
        return true;
    }

    /**
     * Returns true if both the lower and upper bounds are
     * Double.NaN, and false otherwise.
     *
     * @return A boolean.
     *
     * @since 1.0.18
     */
    public boolean isNaNRange() {
        return Double.isNaN(this.lower) && Double.isNaN(this.upper);
    }

    /**
     * Returns a hash code.
     *
     * @return A hash code.
     */
    @Override
    public int hashCode() {
        int result;
        long temp;
        temp = Double.doubleToLongBits(this.lower);
        result = (int) (temp ^ (temp >>> 32));
        temp = Double.doubleToLongBits(this.upper);
        result = 29 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    /**
     * Returns a string representation of this Range.
     *
     * @return A String "Range[lower,upper]" where lower=lower range and
     *         upper=upper range.
     */
    @Override
    public String toString() {
        return ("Range[" + this.lower + "," + this.upper + "]");
    }

}

lib/jfreechart-1.0.19/source/org/jfree/chart/axis/ValueAxis.java
public abstract class ValueAxis extends Axis
        implements Cloneable, PublicCloneable, Serializable {

    /** For serialization. */
    private static final long serialVersionUID = 3698345477322391456L;

    /** The default axis range. */
    public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);

    /** The default auto-range value. */
    public static final boolean DEFAULT_AUTO_RANGE = true;

    /** The default inverted flag setting. */
    public static final boolean DEFAULT_INVERTED = false;

    /** The default minimum auto range. */
    public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;

    /** The default value for the lower margin (0.05 = 5%). */
    public static final double DEFAULT_LOWER_MARGIN = 0.05;

    /** The default value for the upper margin (0.05 = 5%). */
    public static final double DEFAULT_UPPER_MARGIN = 0.05;

    /**
     * The default lower bound for the axis.
     *
     * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
     *     attribute (see {@link #getDefaultAutoRange()}).
     */
    public static final double DEFAULT_LOWER_BOUND = 0.0;

    /**
     * The default upper bound for the axis.
     *
     * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
     *     attribute (see {@link #getDefaultAutoRange()}).
     */
    public static final double DEFAULT_UPPER_BOUND = 1.0;

    /** The default auto-tick-unit-selection value. */
    public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;

    /** The maximum tick count. */
    public static final int MAXIMUM_TICK_COUNT = 500;

    /**
     * A flag that controls whether an arrow is drawn at the positive end of
     * the axis line.
     */
    private boolean positiveArrowVisible;

    /**
     * A flag that controls whether an arrow is drawn at the negative end of
     * the axis line.
     */
    private boolean negativeArrowVisible;

    /** The shape used for an up arrow. */
    private transient Shape upArrow;

    /** The shape used for a down arrow. */
    private transient Shape downArrow;

    /** The shape used for a left arrow. */
    private transient Shape leftArrow;

    /** The shape used for a right arrow. */
    private transient Shape rightArrow;

    /** A flag that affects the orientation of the values on the axis. */
    private boolean inverted;

    /** The axis range. */
    private Range range;

    /**
     * Flag that indicates whether the axis automatically scales to fit the
     * chart data.
     */
    private boolean autoRange;

    /** The minimum size for the 'auto' axis range (excluding margins). */
    private double autoRangeMinimumSize;

    /**
     * The default range is used when the dataset is empty and the axis needs
     * to determine the auto range.
     *
     * @since 1.0.5
     */
    private Range defaultAutoRange;

    /**
     * The upper margin percentage.  This indicates the amount by which the
     * maximum axis value exceeds the maximum data value (as a percentage of
     * the range on the axis) when the axis range is determined automatically.
     */
    private double upperMargin;

    /**
     * The lower margin.  This is a percentage that indicates the amount by
     * which the minimum axis value is "less than" the minimum data value when
     * the axis range is determined automatically.
     */
    private double lowerMargin;

    /**
     * If this value is positive, the amount is subtracted from the maximum
     * data value to determine the lower axis range.  This can be used to
     * provide a fixed "window" on dynamic data.
     */
    private double fixedAutoRange;

    /**
     * Flag that indicates whether or not the tick unit is selected
     * automatically.
     */
    private boolean autoTickUnitSelection;

    /** The standard tick units for the axis. */
    private TickUnitSource standardTickUnits;

    /** An index into an array of standard tick values. */
    private int autoTickIndex;

    /**
     * The number of minor ticks per major tick unit.  This is an override
     * field, if the value is > 0 it is used, otherwise the axis refers to the
     * minorTickCount in the current tickUnit.
     */
    private int minorTickCount;

    /** A flag indicating whether or not tick labels are rotated to vertical. */
    private boolean verticalTickLabels;

    /**
     * Constructs a value axis.
     *
     * @param label  the axis label (null permitted).
     * @param standardTickUnits  the source for standard tick units
     *                           (null permitted).
     */
    protected ValueAxis(String label, TickUnitSource standardTickUnits) {

        super(label);

        this.positiveArrowVisible = false;
        this.negativeArrowVisible = false;

        this.range = DEFAULT_RANGE;
        this.autoRange = DEFAULT_AUTO_RANGE;
        this.defaultAutoRange = DEFAULT_RANGE;

        this.inverted = DEFAULT_INVERTED;
        this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;

        this.lowerMargin = DEFAULT_LOWER_MARGIN;
        this.upperMargin = DEFAULT_UPPER_MARGIN;

        this.fixedAutoRange = 0.0;

        this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
        this.standardTickUnits = standardTickUnits;

        Polygon p1 = new Polygon();
        p1.addPoint(0, 0);
        p1.addPoint(-2, 2);
        p1.addPoint(2, 2);

        this.upArrow = p1;

        Polygon p2 = new Polygon();
        p2.addPoint(0, 0);
        p2.addPoint(-2, -2);
        p2.addPoint(2, -2);

        this.downArrow = p2;

        Polygon p3 = new Polygon();
        p3.addPoint(0, 0);
        p3.addPoint(-2, -2);
        p3.addPoint(-2, 2);

        this.rightArrow = p3;

        Polygon p4 = new Polygon();
        p4.addPoint(0, 0);
        p4.addPoint(2, -2);
        p4.addPoint(2, 2);

        this.leftArrow = p4;

        this.verticalTickLabels = false;
        this.minorTickCount = 0;

    }

    /**
     * Returns true if the tick labels should be rotated (to
     * vertical), and false otherwise.
     *
     * @return true or false.
     *
     * @see #setVerticalTickLabels(boolean)
     */
    public boolean isVerticalTickLabels() {
        return this.verticalTickLabels;
    }

    /**
     * Sets the flag that controls whether the tick labels are displayed
     * vertically (that is, rotated 90 degrees from horizontal).  If the flag
     * is changed, an {@link AxisChangeEvent} is sent to all registered
     * listeners.
     *
     * @param flag  the flag.
     *
     * @see #isVerticalTickLabels()
     */
    public void setVerticalTickLabels(boolean flag) {
        if (this.verticalTickLabels != flag) {
            this.verticalTickLabels = flag;
            fireChangeEvent();
        }
    }

    /**
     * Returns a flag that controls whether or not the axis line has an arrow
     * drawn that points in the positive direction for the axis.
     *
     * @return A boolean.
     *
     * @see #setPositiveArrowVisible(boolean)
     */
    public boolean isPositiveArrowVisible() {
        return this.positiveArrowVisible;
    }

    /**
     * Sets a flag that controls whether or not the axis lines has an arrow
     * drawn that points in the positive direction for the axis, and sends an
     * {@link AxisChangeEvent} to all registered listeners.
     *
     * @param visible  the flag.
     *
     * @see #isPositiveArrowVisible()
     */
    public void setPositiveArrowVisible(boolean visible) {
        this.positiveArrowVisible = visible;
        fireChangeEvent();
    }

    /**
     * Returns a flag that controls whether or not the axis line has an arrow
     * drawn that points in the negative direction for the axis.
     *
     * @return A boolean.
     *
     * @see #setNegativeArrowVisible(boolean)
     */
    public boolean isNegativeArrowVisible() {
        return this.negativeArrowVisible;
    }

    /**
     * Sets a flag that controls whether or not the axis lines has an arrow
     * drawn that points in the negative direction for the axis, and sends an
     * {@link AxisChangeEvent} to all registered listeners.
     *
     * @param visible  the flag.
     *
     * @see #setNegativeArrowVisible(boolean)
     */
    public void setNegativeArrowVisible(boolean visible) {
        this.negativeArrowVisible = visible;
        fireChangeEvent();
    }

    /**
     * Returns a shape that can be displayed as an arrow pointing upwards at
     * the end of an axis line.
     *
     * @return A shape (never null).
     *
     * @see #setUpArrow(Shape)
     */
    public Shape getUpArrow() {
        return this.upArrow;
    }

    /**
     * Sets the shape that can be displayed as an arrow pointing upwards at
     * the end of an axis line and sends an {@link AxisChangeEvent} to all
     * registered listeners.
     *
     * @param arrow  the arrow shape (null not permitted).
     *
     * @see #getUpArrow()
     */
    public void setUpArrow(Shape arrow) {
        ParamChecks.nullNotPermitted(arrow, "arrow");
        this.upArrow = arrow;
        fireChangeEvent();
    }

    /**
     * Returns a shape that can be displayed as an arrow pointing downwards at
     * the end of an axis line.
     *
     * @return A shape (never null).
     *
     * @see #setDownArrow(Shape)
     */
    public Shape getDownArrow() {
        return this.downArrow;
    }

    /**
     * Sets the shape that can be displayed as an arrow pointing downwards at
     * the end of an axis line and sends an {@link AxisChangeEvent} to all
     * registered listeners.
     *
     * @param arrow  the arrow shape (null not permitted).
     *
     * @see #getDownArrow()
     */
    public void setDownArrow(Shape arrow) {
        ParamChecks.nullNotPermitted(arrow, "arrow");
        this.downArrow = arrow;
        fireChangeEvent();
    }

    /**
     * Returns a shape that can be displayed as an arrow pointing left at the
     * end of an axis line.
     *
     * @return A shape (never null).
     *
     * @see #setLeftArrow(Shape)
     */
    public Shape getLeftArrow() {
        return this.leftArrow;
    }

    /**
     * Sets the shape that can be displayed as an arrow pointing left at the
     * end of an axis line and sends an {@link AxisChangeEvent} to all
     * registered listeners.
     *
     * @param arrow  the arrow shape (null not permitted).
     *
     * @see #getLeftArrow()
     */
    public void setLeftArrow(Shape arrow) {
        ParamChecks.nullNotPermitted(arrow, "arrow");
        this.leftArrow = arrow;
        fireChangeEvent();
    }

    /**
     * Returns a shape that can be displayed as an arrow pointing right at the
     * end of an axis line.
     *
     * @return A shape (never null).
     *
     * @see #setRightArrow(Shape)
     */
    public Shape getRightArrow() {
        return this.rightArrow;
    }

    /**
     * Sets the shape that can be displayed as an arrow pointing rightwards at
     * the end of an axis line and sends an {@link AxisChangeEvent} to all
     * registered listeners.
     *
     * @param arrow  the arrow shape (null not permitted).
     *
     * @see #getRightArrow()
     */
    public void setRightArrow(Shape arrow) {
        ParamChecks.nullNotPermitted(arrow, "arrow");
        this.rightArrow = arrow;
        fireChangeEvent();
    }

    /**
     * Draws an axis line at the current cursor position and edge.
     *
     * @param g2  the graphics device ({@code null} not permitted).
     * @param cursor  the cursor position.
     * @param dataArea  the data area.
     * @param edge  the edge.
     */
    @Override
    protected void drawAxisLine(Graphics2D g2, double cursor,
            Rectangle2D dataArea, RectangleEdge edge) {
        Line2D axisLine = null;
        double c = cursor;
        if (edge == RectangleEdge.TOP) {
            axisLine = new Line2D.Double(dataArea.getX(), c, dataArea.getMaxX(),
                    c);
        } else if (edge == RectangleEdge.BOTTOM) {
            axisLine = new Line2D.Double(dataArea.getX(), c, dataArea.getMaxX(),
                    c);
        } else if (edge == RectangleEdge.LEFT) {
            axisLine = new Line2D.Double(c, dataArea.getY(), c,
                    dataArea.getMaxY());
        } else if (edge == RectangleEdge.RIGHT) {
            axisLine = new Line2D.Double(c, dataArea.getY(), c,
                    dataArea.getMaxY());
        }
        g2.setPaint(getAxisLinePaint());
        g2.setStroke(getAxisLineStroke());
        Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                RenderingHints.VALUE_STROKE_NORMALIZE);
        g2.draw(axisLine);
        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved);

        boolean drawUpOrRight = false;
        boolean drawDownOrLeft = false;
        if (this.positiveArrowVisible) {
            if (this.inverted) {
                drawDownOrLeft = true;
            }
            else {
                drawUpOrRight = true;
            }
        }
        if (this.negativeArrowVisible) {
            if (this.inverted) {
                drawUpOrRight = true;
            } else {
                drawDownOrLeft = true;
            }
        }
        if (drawUpOrRight) {
            double x = 0.0;
            double y = 0.0;
            Shape arrow = null;
            if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
                x = dataArea.getMaxX();
                y = cursor;
                arrow = this.rightArrow;
            } else if (edge == RectangleEdge.LEFT
                    || edge == RectangleEdge.RIGHT) {
                x = cursor;
                y = dataArea.getMinY();
                arrow = this.upArrow;
            }

            // draw the arrow...
            AffineTransform transformer = new AffineTransform();
            transformer.setToTranslation(x, y);
            Shape shape = transformer.createTransformedShape(arrow);
            g2.fill(shape);
            g2.draw(shape);
        }

        if (drawDownOrLeft) {
            double x = 0.0;
            double y = 0.0;
            Shape arrow = null;
            if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
                x = dataArea.getMinX();
                y = cursor;
                arrow = this.leftArrow;
            } else if (edge == RectangleEdge.LEFT
                    || edge == RectangleEdge.RIGHT) {
                x = cursor;
                y = dataArea.getMaxY();
                arrow = this.downArrow;
            }

            // draw the arrow...
            AffineTransform transformer = new AffineTransform();
            transformer.setToTranslation(x, y);
            Shape shape = transformer.createTransformedShape(arrow);
            g2.fill(shape);
            g2.draw(shape);
        }

    }

    /**
     * Calculates the anchor point for a tick label.
     *
     * @param tick  the tick.
     * @param cursor  the cursor.
     * @param dataArea  the data area.
     * @param edge  the edge on which the axis is drawn.
     *
     * @return The x and y coordinates of the anchor point.
     */
    protected float[] calculateAnchorPoint(ValueTick tick, double cursor,
            Rectangle2D dataArea, RectangleEdge edge) {

        RectangleInsets insets = getTickLabelInsets();
        float[] result = new float[2];
        if (edge == RectangleEdge.TOP) {
            result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
            result[1] = (float) (cursor - insets.getBottom() - 2.0);
        }
        else if (edge == RectangleEdge.BOTTOM) {
            result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
            result[1] = (float) (cursor + insets.getTop() + 2.0);
        }
        else if (edge == RectangleEdge.LEFT) {
            result[0] = (float) (cursor - insets.getLeft() - 2.0);
            result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
        }
        else if (edge == RectangleEdge.RIGHT) {
            result[0] = (float) (cursor + insets.getRight() + 2.0);
            result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
        }
        return result;
    }

    /**
     * Draws the axis line, tick marks and tick mark labels.
     *
     * @param g2  the graphics device (null not permitted).
     * @param cursor  the cursor.
     * @param plotArea  the plot area (null not permitted).
     * @param dataArea  the data area (null not permitted).
     * @param edge  the edge that the axis is aligned with (null
     *     not permitted).
     *
     * @return The width or height used to draw the axis.
     */
    protected AxisState drawTickMarksAndLabels(Graphics2D g2,
            double cursor, Rectangle2D plotArea, Rectangle2D dataArea,
            RectangleEdge edge) {

        AxisState state = new AxisState(cursor);
        if (isAxisLineVisible()) {
            drawAxisLine(g2, cursor, dataArea, edge);
        }
        List ticks = refreshTicks(g2, state, dataArea, edge);
        state.setTicks(ticks);
        g2.setFont(getTickLabelFont());
        Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                RenderingHints.VALUE_STROKE_NORMALIZE);
        Iterator iterator = ticks.iterator();
        while (iterator.hasNext()) {
            ValueTick tick = (ValueTick) iterator.next();
            if (isTickLabelsVisible()) {
                g2.setPaint(getTickLabelPaint());
                float[] anchorPoint = calculateAnchorPoint(tick, cursor,
                        dataArea, edge);
                if (tick instanceof LogTick) {
                    LogTick lt = (LogTick) tick;
                    if (lt.getAttributedLabel() == null) {
                        continue;
                    }
                    AttrStringUtils.drawRotatedString(lt.getAttributedLabel(),
                            g2, anchorPoint[0], anchorPoint[1],
                            tick.getTextAnchor(), tick.getAngle(),
                            tick.getRotationAnchor());
                } else {
                    if (tick.getText() == null) {
                        continue;
                    }
                    TextUtilities.drawRotatedString(tick.getText(), g2,
                            anchorPoint[0], anchorPoint[1],
                            tick.getTextAnchor(), tick.getAngle(),
                            tick.getRotationAnchor());
                }
            }

            if ((isTickMarksVisible() && tick.getTickType().equals(
                    TickType.MAJOR)) || (isMinorTickMarksVisible()
                    && tick.getTickType().equals(TickType.MINOR))) {

                double ol = (tick.getTickType().equals(TickType.MINOR))
                        ? getMinorTickMarkOutsideLength()
                        : getTickMarkOutsideLength();

                double il = (tick.getTickType().equals(TickType.MINOR))
                        ? getMinorTickMarkInsideLength()
                        : getTickMarkInsideLength();

                float xx = (float) valueToJava2D(tick.getValue(), dataArea,
                        edge);
                Line2D mark = null;
                g2.setStroke(getTickMarkStroke());
                g2.setPaint(getTickMarkPaint());
                if (edge == RectangleEdge.LEFT) {
                    mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx);
                }
                else if (edge == RectangleEdge.RIGHT) {
                    mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx);
                }
                else if (edge == RectangleEdge.TOP) {
                    mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il);
                }
                else if (edge == RectangleEdge.BOTTOM) {
                    mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il);
                }
                g2.draw(mark);
            }
        }
        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved);

        // need to work out the space used by the tick labels...
        // so we can update the cursor...
        double used = 0.0;
        if (isTickLabelsVisible()) {
            if (edge == RectangleEdge.LEFT) {
                used += findMaximumTickLabelWidth(ticks, g2, plotArea,
                        isVerticalTickLabels());
                state.cursorLeft(used);
            } else if (edge == RectangleEdge.RIGHT) {
                used = findMaximumTickLabelWidth(ticks, g2, plotArea,
                        isVerticalTickLabels());
                state.cursorRight(used);
            } else if (edge == RectangleEdge.TOP) {
                used = findMaximumTickLabelHeight(ticks, g2, plotArea,
                        isVerticalTickLabels());
                state.cursorUp(used);
            } else if (edge == RectangleEdge.BOTTOM) {
                used = findMaximumTickLabelHeight(ticks, g2, plotArea,
                        isVerticalTickLabels());
                state.cursorDown(used);
            }
        }

        return state;
    }

    /**
     * Returns the space required to draw the axis.
     *
     * @param g2  the graphics device.
     * @param plot  the plot that the axis belongs to.
     * @param plotArea  the area within which the plot should be drawn.
     * @param edge  the axis location.
     * @param space  the space already reserved (for other axes).
     *
     * @return The space required to draw the axis (including pre-reserved
     *         space).
     */
    @Override
    public AxisSpace reserveSpace(Graphics2D g2, Plot plot,
            Rectangle2D plotArea, RectangleEdge edge, AxisSpace space) {

        // create a new space object if one wasn't supplied...
        if (space == null) {
            space = new AxisSpace();
        }

        // if the axis is not visible, no additional space is required...
        if (!isVisible()) {
            return space;
        }

        // if the axis has a fixed dimension, return it...
        double dimension = getFixedDimension();
        if (dimension > 0.0) {
            space.add(dimension, edge);
            return space;
        }

        // calculate the max size of the tick labels (if visible)...
        double tickLabelHeight = 0.0;
        double tickLabelWidth = 0.0;
        if (isTickLabelsVisible()) {
            g2.setFont(getTickLabelFont());
            List ticks = refreshTicks(g2, new AxisState(), plotArea, edge);
            if (RectangleEdge.isTopOrBottom(edge)) {
                tickLabelHeight = findMaximumTickLabelHeight(ticks, g2,
                        plotArea, isVerticalTickLabels());
            }
            else if (RectangleEdge.isLeftOrRight(edge)) {
                tickLabelWidth = findMaximumTickLabelWidth(ticks, g2, plotArea,
                        isVerticalTickLabels());
            }
        }

        // get the axis label size and update the space object...
        Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
        if (RectangleEdge.isTopOrBottom(edge)) {
            double labelHeight = labelEnclosure.getHeight();
            space.add(labelHeight + tickLabelHeight, edge);
        }
        else if (RectangleEdge.isLeftOrRight(edge)) {
            double labelWidth = labelEnclosure.getWidth();
            space.add(labelWidth + tickLabelWidth, edge);
        }

        return space;

    }

    /**
     * A utility method for determining the height of the tallest tick label.
     *
     * @param ticks  the ticks.
     * @param g2  the graphics device.
     * @param drawArea  the area within which the plot and axes should be drawn.
     * @param vertical  a flag that indicates whether or not the tick labels
     *                  are 'vertical'.
     *
     * @return The height of the tallest tick label.
     */
    protected double findMaximumTickLabelHeight(List ticks, Graphics2D g2,
            Rectangle2D drawArea, boolean vertical) {

        RectangleInsets insets = getTickLabelInsets();
        Font font = getTickLabelFont();
        g2.setFont(font);
        double maxHeight = 0.0;
        if (vertical) {
            FontMetrics fm = g2.getFontMetrics(font);
            Iterator iterator = ticks.iterator();
            while (iterator.hasNext()) {
                Tick tick = (Tick) iterator.next();
                Rectangle2D labelBounds = null;
                if (tick instanceof LogTick) {
                    LogTick lt = (LogTick) tick;
                    if (lt.getAttributedLabel() != null) {
                        labelBounds = AttrStringUtils.getTextBounds(
                                lt.getAttributedLabel(), g2);
                    }
                } else if (tick.getText() != null) {
                    labelBounds = TextUtilities.getTextBounds(
                            tick.getText(), g2, fm);
                }
                if (labelBounds != null && labelBounds.getWidth()
                        + insets.getTop() + insets.getBottom() > maxHeight) {
                    maxHeight = labelBounds.getWidth()
                                + insets.getTop() + insets.getBottom();
                }
            }
        } else {
            LineMetrics metrics = font.getLineMetrics("ABCxyz",
                    g2.getFontRenderContext());
            maxHeight = metrics.getHeight()
                        + insets.getTop() + insets.getBottom();
        }
        return maxHeight;

    }

    /**
     * A utility method for determining the width of the widest tick label.
     *
     * @param ticks  the ticks.
     * @param g2  the graphics device.
     * @param drawArea  the area within which the plot and axes should be drawn.
     * @param vertical  a flag that indicates whether or not the tick labels
     *                  are 'vertical'.
     *
     * @return The width of the tallest tick label.
     */
    protected double findMaximumTickLabelWidth(List ticks, Graphics2D g2,
            Rectangle2D drawArea, boolean vertical) {

        RectangleInsets insets = getTickLabelInsets();
        Font font = getTickLabelFont();
        double maxWidth = 0.0;
        if (!vertical) {
            FontMetrics fm = g2.getFontMetrics(font);
            Iterator iterator = ticks.iterator();
            while (iterator.hasNext()) {
                Tick tick = (Tick) iterator.next();
                Rectangle2D labelBounds = null;
                if (tick instanceof LogTick) {
                    LogTick lt = (LogTick) tick;
                    if (lt.getAttributedLabel() != null) {
                        labelBounds = AttrStringUtils.getTextBounds(
                                lt.getAttributedLabel(), g2);
                    }
                } else if (tick.getText() != null) {
                    labelBounds = TextUtilities.getTextBounds(tick.getText(),
                            g2, fm);
                }
                if (labelBounds != null
                        && labelBounds.getWidth() + insets.getLeft()
                        + insets.getRight() > maxWidth) {
                    maxWidth = labelBounds.getWidth()
                               + insets.getLeft() + insets.getRight();
                }
            }
        } else {
            LineMetrics metrics = font.getLineMetrics("ABCxyz",
                    g2.getFontRenderContext());
            maxWidth = metrics.getHeight()
                       + insets.getTop() + insets.getBottom();
        }
        return maxWidth;

    }

    /**
     * Returns a flag that controls the direction of values on the axis.
     *

     * For a regular axis, values increase from left to right (for a horizontal
     * axis) and bottom to top (for a vertical axis).  When the axis is
     * 'inverted', the values increase in the opposite direction.
     *
     * @return The flag.
     *
     * @see #setInverted(boolean)
     */
    public boolean isInverted() {
        return this.inverted;
    }

    /**
     * Sets a flag that controls the direction of values on the axis, and
     * notifies registered listeners that the axis has changed.
     *
     * @param flag  the flag.
     *
     * @see #isInverted()
     */
    public void setInverted(boolean flag) {
        if (this.inverted != flag) {
            this.inverted = flag;
            fireChangeEvent();
        }
    }

    /**
     * Returns the flag that controls whether or not the axis range is
     * automatically adjusted to fit the data values.
     *
     * @return The flag.
     *
     * @see #setAutoRange(boolean)
     */
    public boolean isAutoRange() {
        return this.autoRange;
    }

    /**
     * Sets a flag that determines whether or not the axis range is
     * automatically adjusted to fit the data, and notifies registered
     * listeners that the axis has been modified.
     *
     * @param auto  the new value of the flag.
     *
     * @see #isAutoRange()
     */
    public void setAutoRange(boolean auto) {
        setAutoRange(auto, true);
    }

    /**
     * Sets the auto range attribute.  If the notify flag is set,
     * an {@link AxisChangeEvent} is sent to registered listeners.
     *
     * @param auto  the flag.
     * @param notify  notify listeners?
     *
     * @see #isAutoRange()
     */
    protected void setAutoRange(boolean auto, boolean notify) {
        if (this.autoRange != auto) {
            this.autoRange = auto;
            if (this.autoRange) {
                autoAdjustRange();
            }
            if (notify) {
                fireChangeEvent();
            }
        }
    }

    /**
     * Returns the minimum size allowed for the axis range when it is
     * automatically calculated.
     *
     * @return The minimum range.
     *
     * @see #setAutoRangeMinimumSize(double)
     */
    public double getAutoRangeMinimumSize() {
        return this.autoRangeMinimumSize;
    }

    /**
     * Sets the auto range minimum size and sends an {@link AxisChangeEvent}
     * to all registered listeners.
     *
     * @param size  the size.
     *
     * @see #getAutoRangeMinimumSize()
     */
    public void setAutoRangeMinimumSize(double size) {
        setAutoRangeMinimumSize(size, true);
    }

    /**
     * Sets the minimum size allowed for the axis range when it is
     * automatically calculated.
     *

     * If requested, an {@link AxisChangeEvent} is forwarded to all registered
     * listeners.
     *
     * @param size  the new minimum.
     * @param notify  notify listeners?
     */
    public void setAutoRangeMinimumSize(double size, boolean notify) {
        if (size <= 0.0) {
            throw new IllegalArgumentException(
                "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
        }
        if (this.autoRangeMinimumSize != size) {
            this.autoRangeMinimumSize = size;
            if (this.autoRange) {
                autoAdjustRange();
            }
            if (notify) {
                fireChangeEvent();
            }
        }

    }

    /**
     * Returns the default auto range.
     *
     * @return The default auto range (never null).
     *
     * @see #setDefaultAutoRange(Range)
     *
     * @since 1.0.5
     */
    public Range getDefaultAutoRange() {
        return this.defaultAutoRange;
    }

    /**
     * Sets the default auto range and sends an {@link AxisChangeEvent} to all
     * registered listeners.
     *
     * @param range  the range (null not permitted).
     *
     * @see #getDefaultAutoRange()
     *
     * @since 1.0.5
     */
    public void setDefaultAutoRange(Range range) {
        ParamChecks.nullNotPermitted(range, "range");
        this.defaultAutoRange = range;
        fireChangeEvent();
    }

    /**
     * Returns the lower margin for the axis, expressed as a percentage of the
     * axis range.  This controls the space added to the lower end of the axis
     * when the axis range is automatically calculated (it is ignored when the
     * axis range is set explicitly). The default value is 0.05 (five percent).
     *
     * @return The lower margin.
     *
     * @see #setLowerMargin(double)
     */
    public double getLowerMargin() {
        return this.lowerMargin;
    }

    /**
     * Sets the lower margin for the axis (as a percentage of the axis range)
     * and sends an {@link AxisChangeEvent} to all registered listeners.  This
     * margin is added only when the axis range is auto-calculated - if you set
     * the axis range manually, the margin is ignored.
     *
     * @param margin  the margin percentage (for example, 0.05 is five percent).
     *
     * @see #getLowerMargin()
     * @see #setUpperMargin(double)
     */
    public void setLowerMargin(double margin) {
        this.lowerMargin = margin;
        if (isAutoRange()) {
            autoAdjustRange();
        }
        fireChangeEvent();
    }

    /**
     * Returns the upper margin for the axis, expressed as a percentage of the
     * axis range.  This controls the space added to the lower end of the axis
     * when the axis range is automatically calculated (it is ignored when the
     * axis range is set explicitly). The default value is 0.05 (five percent).
     *
     * @return The upper margin.
     *
     * @see #setUpperMargin(double)
     */
    public double getUpperMargin() {
        return this.upperMargin;
    }

    /**
     * Sets the upper margin for the axis (as a percentage of the axis range)
     * and sends an {@link AxisChangeEvent} to all registered listeners.  This
     * margin is added only when the axis range is auto-calculated - if you set
     * the axis range manually, the margin is ignored.
     *
     * @param margin  the margin percentage (for example, 0.05 is five percent).
     *
     * @see #getLowerMargin()
     * @see #setLowerMargin(double)
     */
    public void setUpperMargin(double margin) {
        this.upperMargin = margin;
        if (isAutoRange()) {
            autoAdjustRange();
        }
        fireChangeEvent();
    }

    /**
     * Returns the fixed auto range.
     *
     * @return The length.
     *
     * @see #setFixedAutoRange(double)
     */
    public double getFixedAutoRange() {
        return this.fixedAutoRange;
    }

    /**
     * Sets the fixed auto range for the axis.
     *
     * @param length  the range length.
     *
     * @see #getFixedAutoRange()
     */
    public void setFixedAutoRange(double length) {
        this.fixedAutoRange = length;
        if (isAutoRange()) {
            autoAdjustRange();
        }
        fireChangeEvent();
    }

    /**
     * Returns the lower bound of the axis range.
     *
     * @return The lower bound.
     *
     * @see #setLowerBound(double)
     */
    public double getLowerBound() {
        return this.range.getLowerBound();
    }

    /**
     * Sets the lower bound for the axis range.  An {@link AxisChangeEvent} is
     * sent to all registered listeners.
     *
     * @param min  the new minimum.
     *
     * @see #getLowerBound()
     */
    public void setLowerBound(double min) {
        if (this.range.getUpperBound() > min) {
            setRange(new Range(min, this.range.getUpperBound()));
        }
        else {
            setRange(new Range(min, min + 1.0));
        }
    }

    /**
     * Returns the upper bound for the axis range.
     *
     * @return The upper bound.
     *
     * @see #setUpperBound(double)
     */
    public double getUpperBound() {
        return this.range.getUpperBound();
    }

    /**
     * Sets the upper bound for the axis range, and sends an
     * {@link AxisChangeEvent} to all registered listeners.
     *
     * @param max  the new maximum.
     *
     * @see #getUpperBound()
     */
    public void setUpperBound(double max) {
        if (this.range.getLowerBound() < max) {
            setRange(new Range(this.range.getLowerBound(), max));
        }
        else {
            setRange(max - 1.0, max);
        }
    }

    /**
     * Returns the range for the axis.
     *
     * @return The axis range (never null).
     *
     * @see #setRange(Range)
     */
    public Range getRange() {
        return this.range;
    }

    /**
     * Sets the range for the axis and sends a change event to all registered
     * listeners.  As a side-effect, the auto-range flag is set to
     * false.
     *
     * @param range  the range (null not permitted).
     *
     * @see #getRange()
     */
    public void setRange(Range range) {
        // defer argument checking
        setRange(range, true, true);
    }

    /**
     * Sets the range for the axis and, if requested, sends a change event to
     * all registered listeners.  Furthermore, if turnOffAutoRange
     * is true, the auto-range flag is set to false
     * (normally when setting the axis range manually the caller expects that
     * range to remain in force).
     *
     * @param range  the range (null not permitted).
     * @param turnOffAutoRange  a flag that controls whether or not the auto
     *                          range is turned off.
     * @param notify  a flag that controls whether or not listeners are
     *                notified.
     *
     * @see #getRange()
     */
    public void setRange(Range range, boolean turnOffAutoRange,
            boolean notify) {
        ParamChecks.nullNotPermitted(range, "range");
        if (range.getLength() <= 0.0) {
            throw new IllegalArgumentException(
                    "A positive range length is required: " + range);
        }
        if (turnOffAutoRange) {
            this.autoRange = false;
        }
        this.range = range;
        if (notify) {
            fireChangeEvent();
        }
    }

    /**
     * Sets the range for the axis and sends a change event to all registered
     * listeners.  As a side-effect, the auto-range flag is set to
     * false.
     *
     * @param lower  the lower axis limit.
     * @param upper  the upper axis limit.
     *
     * @see #getRange()
     * @see #setRange(Range)
     */
    public void setRange(double lower, double upper) {
        setRange(new Range(lower, upper));
    }

    /**
     * Sets the range for the axis (after first adding the current margins to
     * the specified range) and sends an {@link AxisChangeEvent} to all
     * registered listeners.
     *
     * @param range  the range (null not permitted).
     */
    public void setRangeWithMargins(Range range) {
        setRangeWithMargins(range, true, true);
    }

    /**
     * Sets the range for the axis after first adding the current margins to
     * the range and, if requested, sends an {@link AxisChangeEvent} to all
     * registered listeners.  As a side-effect, the auto-range flag is set to
     * false (optional).
     *
     * @param range  the range (excluding margins, null not
     *               permitted).
     * @param turnOffAutoRange  a flag that controls whether or not the auto
     *                          range is turned off.
     * @param notify  a flag that controls whether or not listeners are
     *                notified.
     */
    public void setRangeWithMargins(Range range, boolean turnOffAutoRange,
                                    boolean notify) {
        ParamChecks.nullNotPermitted(range, "range");
        setRange(Range.expand(range, getLowerMargin(), getUpperMargin()),
                turnOffAutoRange, notify);
    }

    /**
     * Sets the axis range (after first adding the current margins to the
     * range) and sends an {@link AxisChangeEvent} to all registered listeners.
     * As a side-effect, the auto-range flag is set to false.
     *
     * @param lower  the lower axis limit.
     * @param upper  the upper axis limit.
     */
    public void setRangeWithMargins(double lower, double upper) {
        setRangeWithMargins(new Range(lower, upper));
    }

    /**
     * Sets the axis range, where the new range is 'size' in length, and
     * centered on 'value'.
     *
     * @param value  the central value.
     * @param length  the range length.
     */
    public void setRangeAboutValue(double value, double length) {
        setRange(new Range(value - length / 2, value + length / 2));
    }

    /**
     * Returns a flag indicating whether or not the tick unit is automatically
     * selected from a range of standard tick units.
     *
     * @return A flag indicating whether or not the tick unit is automatically
     *         selected.
     *
     * @see #setAutoTickUnitSelection(boolean)
     */
    public boolean isAutoTickUnitSelection() {
        return this.autoTickUnitSelection;
    }

    /**
     * Sets a flag indicating whether or not the tick unit is automatically
     * selected from a range of standard tick units.  If the flag is changed,
     * registered listeners are notified that the chart has changed.
     *
     * @param flag  the new value of the flag.
     *
     * @see #isAutoTickUnitSelection()
     */
    public void setAutoTickUnitSelection(boolean flag) {
        setAutoTickUnitSelection(flag, true);
    }

    /**
     * Sets a flag indicating whether or not the tick unit is automatically
     * selected from a range of standard tick units.
     *
     * @param flag  the new value of the flag.
     * @param notify  notify listeners?
     *
     * @see #isAutoTickUnitSelection()
     */
    public void setAutoTickUnitSelection(boolean flag, boolean notify) {

        if (this.autoTickUnitSelection != flag) {
            this.autoTickUnitSelection = flag;
            if (notify) {
                fireChangeEvent();
            }
        }
    }

    /**
     * Returns the source for obtaining standard tick units for the axis.
     *
     * @return The source (possibly null).
     *
     * @see #setStandardTickUnits(TickUnitSource)
     */
    public TickUnitSource getStandardTickUnits() {
        return this.standardTickUnits;
    }

    /**
     * Sets the source for obtaining standard tick units for the axis and sends
     * an {@link AxisChangeEvent} to all registered listeners.  The axis will
     * try to select the smallest tick unit from the source that does not cause
     * the tick labels to overlap (see also the
     * {@link #setAutoTickUnitSelection(boolean)} method.
     *
     * @param source  the source for standard tick units (null
     *                permitted).
     *
     * @see #getStandardTickUnits()
     */
    public void setStandardTickUnits(TickUnitSource source) {
        this.standardTickUnits = source;
        fireChangeEvent();
    }

    /**
     * Returns the number of minor tick marks to display.
     *
     * @return The number of minor tick marks to display.
     *
     * @see #setMinorTickCount(int)
     *
     * @since 1.0.12
     */
    public int getMinorTickCount() {
        return this.minorTickCount;
    }

    /**
     * Sets the number of minor tick marks to display, and sends an
     * {@link AxisChangeEvent} to all registered listeners.
     *
     * @param count  the count.
     *
     * @see #getMinorTickCount()
     *
     * @since 1.0.12
     */
    public void setMinorTickCount(int count) {
        this.minorTickCount = count;
        fireChangeEvent();
    }

    /**
     * Converts a data value to a coordinate in Java2D space, assuming that the
     * axis runs along one edge of the specified dataArea.
     *

     * Note that it is possible for the coordinate to fall outside the area.
     *
     * @param value  the data value.
     * @param area  the area for plotting the data.
     * @param edge  the edge along which the axis lies.
     *
     * @return The Java2D coordinate.
     *
     * @see #java2DToValue(double, Rectangle2D, RectangleEdge)
     */
    public abstract double valueToJava2D(double value, Rectangle2D area,
                                         RectangleEdge edge);

    /**
     * Converts a length in data coordinates into the corresponding length in
     * Java2D coordinates.
     *
     * @param length  the length.
     * @param area  the plot area.
     * @param edge  the edge along which the axis lies.
     *
     * @return The length in Java2D coordinates.
     */
    public double lengthToJava2D(double length, Rectangle2D area,
                                 RectangleEdge edge) {
        double zero = valueToJava2D(0.0, area, edge);
        double l = valueToJava2D(length, area, edge);
        return Math.abs(l - zero);
    }

    /**
     * Converts a coordinate in Java2D space to the corresponding data value,
     * assuming that the axis runs along one edge of the specified dataArea.
     *
     * @param java2DValue  the coordinate in Java2D space.
     * @param area  the area in which the data is plotted.
     * @param edge  the edge along which the axis lies.
     *
     * @return The data value.
     *
     * @see #valueToJava2D(double, Rectangle2D, RectangleEdge)
     */
    public abstract double java2DToValue(double java2DValue, Rectangle2D area,
            RectangleEdge edge);

    /**
     * Automatically sets the axis range to fit the range of values in the
     * dataset.  Sometimes this can depend on the renderer used as well (for
     * example, the renderer may "stack" values, requiring an axis range
     * greater than otherwise necessary).
     */
    protected abstract void autoAdjustRange();

    /**
     * Centers the axis range about the specified value and sends an
     * {@link AxisChangeEvent} to all registered listeners.
     *
     * @param value  the center value.
     */
    public void centerRange(double value) {
        double central = this.range.getCentralValue();
        Range adjusted = new Range(this.range.getLowerBound() + value - central,
                this.range.getUpperBound() + value - central);
        setRange(adjusted);
    }

    /**
     * Increases or decreases the axis range by the specified percentage about
     * the central value and sends an {@link AxisChangeEvent} to all registered
     * listeners.
     *

     * To double the length of the axis range, use 200% (2.0).
     * To halve the length of the axis range, use 50% (0.5).
     *
     * @param percent  the resize factor.
     *
     * @see #resizeRange(double, double)
     */
    public void resizeRange(double percent) {
        resizeRange(percent, this.range.getCentralValue());
    }

    /**
     * Increases or decreases the axis range by the specified percentage about
     * the specified anchor value and sends an {@link AxisChangeEvent} to all
     * registered listeners.
     *

     * To double the length of the axis range, use 200% (2.0).
     * To halve the length of the axis range, use 50% (0.5).
     *
     * @param percent  the resize factor.
     * @param anchorValue  the new central value after the resize.
     *
     * @see #resizeRange(double)
     */
    public void resizeRange(double percent, double anchorValue) {
        if (percent > 0.0) {
            double halfLength = this.range.getLength() * percent / 2;
            Range adjusted = new Range(anchorValue - halfLength,
                    anchorValue + halfLength);
            setRange(adjusted);
        }
        else {
            setAutoRange(true);
        }
    }

    /**
     * Increases or decreases the axis range by the specified percentage about
     * the specified anchor value and sends an {@link AxisChangeEvent} to all
     * registered listeners.
     *

     * To double the length of the axis range, use 200% (2.0).
     * To halve the length of the axis range, use 50% (0.5).
     *
     * @param percent  the resize factor.
     * @param anchorValue  the new central value after the resize.
     *
     * @see #resizeRange(double)
     *
     * @since 1.0.13
     */
    public void resizeRange2(double percent, double anchorValue) {
        if (percent > 0.0) {
            double left = anchorValue - getLowerBound();
            double right = getUpperBound() - anchorValue;
            Range adjusted = new Range(anchorValue - left * percent,
                    anchorValue + right * percent);
            setRange(adjusted);
        }
        else {
            setAutoRange(true);
        }
    }

    /**
     * Zooms in on the current range.
     *
     * @param lowerPercent  the new lower bound.
     * @param upperPercent  the new upper bound.
     */
    public void zoomRange(double lowerPercent, double upperPercent) {
        double start = this.range.getLowerBound();
        double length = this.range.getLength();
        double r0, r1;
        if (isInverted()) {
            r0 = start + (length * (1 - upperPercent));
            r1 = start + (length * (1 - lowerPercent));
        }
        else {
            r0 = start + length * lowerPercent;
            r1 = start + length * upperPercent;
        }
        if ((r1 > r0) && !Double.isInfinite(r1 - r0)) {
            setRange(new Range(r0, r1));
        }
    }

    /**
     * Slides the axis range by the specified percentage.
     *
     * @param percent  the percentage.
     *
     * @since 1.0.13
     */
    public void pan(double percent) {
        Range r = getRange();
        double length = range.getLength();
        double adj = length * percent;
        double lower = r.getLowerBound() + adj;
        double upper = r.getUpperBound() + adj;
        setRange(lower, upper);
    }

    /**
     * Returns the auto tick index.
     *
     * @return The auto tick index.
     *
     * @see #setAutoTickIndex(int)
     */
    protected int getAutoTickIndex() {
        return this.autoTickIndex;
    }

    /**
     * Sets the auto tick index.
     *
     * @param index  the new value.
     *
     * @see #getAutoTickIndex()
     */
    protected void setAutoTickIndex(int index) {
        this.autoTickIndex = index;
    }

    /**
     * Tests the axis for equality with an arbitrary object.
     *
     * @param obj  the object (null permitted).
     *
     * @return true or false.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof ValueAxis)) {
            return false;
        }
        ValueAxis that = (ValueAxis) obj;
        if (this.positiveArrowVisible != that.positiveArrowVisible) {
            return false;
        }
        if (this.negativeArrowVisible != that.negativeArrowVisible) {
            return false;
        }
        if (this.inverted != that.inverted) {
            return false;
        }
        // if autoRange is true, then the current range is irrelevant
        if (!this.autoRange && !ObjectUtilities.equal(this.range, that.range)) {
            return false;
        }
        if (this.autoRange != that.autoRange) {
            return false;
        }
        if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
            return false;
        }
        if (!this.defaultAutoRange.equals(that.defaultAutoRange)) {
            return false;
        }
        if (this.upperMargin != that.upperMargin) {
            return false;
        }
        if (this.lowerMargin != that.lowerMargin) {
            return false;
        }
        if (this.fixedAutoRange != that.fixedAutoRange) {
            return false;
        }
        if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
            return false;
        }
        if (!ObjectUtilities.equal(this.standardTickUnits,
                that.standardTickUnits)) {
            return false;
        }
        if (this.verticalTickLabels != that.verticalTickLabels) {
            return false;
        }
        if (this.minorTickCount != that.minorTickCount) {
            return false;
        }
        return super.equals(obj);
    }

    /**
     * Returns a clone of the object.
     *
     * @return A clone.
     *
     * @throws CloneNotSupportedException if some component of the axis does
     *         not support cloning.
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        ValueAxis clone = (ValueAxis) super.clone();
        return clone;
    }

    /**
     * Provides serialization support.
     *
     * @param stream  the output stream.
     *
     * @throws IOException  if there is an I/O error.
     */
    private void writeObject(ObjectOutputStream stream) throws IOException {
        stream.defaultWriteObject();
        SerialUtilities.writeShape(this.upArrow, stream);
        SerialUtilities.writeShape(this.downArrow, stream);
        SerialUtilities.writeShape(this.leftArrow, stream);
        SerialUtilities.writeShape(this.rightArrow, stream);
    }

    /**
     * Provides serialization support.
     *
     * @param stream  the input stream.
     *
     * @throws IOException  if there is an I/O error.
     * @throws ClassNotFoundException  if there is a classpath problem.
     */
    private void readObject(ObjectInputStream stream)
            throws IOException, ClassNotFoundException {

        stream.defaultReadObject();
        this.upArrow = SerialUtilities.readShape(stream);
        this.downArrow = SerialUtilities.readShape(stream);
        this.leftArrow = SerialUtilities.readShape(stream);
        this.rightArrow = SerialUtilities.readShape(stream);
    }

}

Answer: private int gap = DEFAULT_GAP;

<think>
The comment just before the placeholder describes the gap between the two outlines that represent the thermometer. In the ThermometerPlot class, after defining the static DEFAULT_GAP constant, the next logical line declares the instance variable that holds the actual gap value, initializing it with the default. Therefore the next line is the declaration of the gap field.
</think>
private int gap = DEFAULT_GAP;