    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 * LogarithmicAxis.java
    029 * --------------------
    030 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
    031 *
    032 * Original Author:  Michael Duffy / Eric Thomas;
    033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034 *                   David M. O'Donnell;
    035 *                   Scott Sams;
    036 *                   Sergei Ivanov;
    037 *
    038 * Changes
    039 * -------
    040 * 14-Mar-2002 : Version 1 contributed by Michael Duffy (DG);
    041 * 19-Apr-2002 : drawVerticalString() is now drawRotatedString() in
    042 *               RefineryUtilities (DG);
    043 * 23-Apr-2002 : Added a range property (DG);
    044 * 15-May-2002 : Modified to be able to deal with negative and zero values (via
    045 *               new 'adjustedLog10()' method);  occurrences of "Math.log(10)"
    046 *               changed to "LOG10_VALUE"; changed 'intValue()' to
    047 *               'longValue()' in 'refreshTicks()' to fix label-text value
    048 *               out-of-range problem; removed 'draw()' method; added
    049 *               'autoRangeMinimumSize' check; added 'log10TickLabelsFlag'
    050 *               parameter flag and implementation (ET);
    051 * 25-Jun-2002 : Removed redundant import (DG);
    052 * 25-Jul-2002 : Changed order of parameters in ValueAxis constructor (DG);
    053 * 16-Jul-2002 : Implemented support for plotting positive values arbitrarily
    054 *               close to zero (added 'allowNegativesFlag' flag) (ET).
    055 * 05-Sep-2002 : Updated constructor reflecting changes in the Axis class (DG);
    056 * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    057 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
    058 * 22-Nov-2002 : Bug fixes from David M. O'Donnell (DG);
    059 * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
    060 * 20-Jan-2003 : Removed unnecessary constructors (DG);
    061 * 26-Mar-2003 : Implemented Serializable (DG);
    062 * 08-May-2003 : Fixed plotting of datasets with lower==upper bounds when
    063 *               'minAutoRange' is very small; added 'strictValuesFlag'
    064 *               and default functionality of throwing a runtime exception
    065 *               if 'allowNegativesFlag' is false and any values are less
    066 *               than or equal to zero; added 'expTickLabelsFlag' and
    067 *               changed to use "1e#"-style tick labels by default
    068 *               ("10^n"-style tick labels still supported via 'set'
    069 *               method); improved generation of tick labels when range of
    070 *               values is small; changed to use 'NumberFormat.getInstance()'
    071 *               to create 'numberFormatterObj' (ET);
    072 * 14-May-2003 : Merged HorizontalLogarithmicAxis and
    073 *               VerticalLogarithmicAxis (DG);
    074 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
    075 * 07-Nov-2003 : Modified to use new NumberTick class (DG);
    076 * 08-Apr-2004 : Use numberFormatOverride if set - see patch 930139 (DG);
    077 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
    078 * 21-Apr-2005 : Added support for upper and lower margins; added
    079 *               get/setAutoRangeNextLogFlag() methods and changed
    080 *               default to 'autoRangeNextLogFlag'==false (ET);
    081 * 22-Apr-2005 : Removed refreshTicks() and fixed names and parameters for
    082 *               refreshHorizontalTicks() & refreshVerticalTicks();
    083 *               changed javadoc on setExpTickLabelsFlag() to specify
    084 *               proper default (ET);
    085 * 22-Apr-2005 : Renamed refreshHorizontalTicks --> refreshTicksHorizontal
    086 *               (and likewise the vertical version) for consistency with
    087 *               other axis classes (DG);
    088 * ------------- JFREECHART 1.0.x ---------------------------------------------
    089 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
    090 * 02-Mar-2007 : Applied patch 1671069 to fix zooming (DG);
    091 * 22-Mar-2007 : Use new defaultAutoRange attribute (DG);
    092 *
    093 */
    094
    095package org.jfree.chart.axis;
    096
    097import java.awt.Graphics2D;
    098import java.awt.geom.Rectangle2D;
    099import java.text.DecimalFormat;
    100import java.text.NumberFormat;
    101import java.util.List;
    102
    103import org.jfree.chart.plot.Plot;
    104import org.jfree.chart.plot.ValueAxisPlot;
    105import org.jfree.data.Range;
    106import org.jfree.ui.RectangleEdge;
    107import org.jfree.ui.TextAnchor;
    108
    109/**
    110 * A numerical axis that uses a logarithmic scale.
    111 */
    112public class LogarithmicAxis extends NumberAxis {
    113
    114    /** For serialization. */
    115    private static final long serialVersionUID = 2502918599004103054L;
    116
    117    /** Useful constant for log(10). */
    118    public static final double LOG10_VALUE = Math.log(10.0);
    119
    120    /** Smallest arbitrarily-close-to-zero value allowed. */
    121    public static final double SMALL_LOG_VALUE = 1e-100;
    122
    123    /** Flag set true to allow negative values in data. */
    124    protected boolean allowNegativesFlag = false;
    125
    126    /**
    127     * Flag set true make axis throw exception if any values are
    128     * <= 0 and 'allowNegativesFlag' is false.
    129     */
    130    protected boolean strictValuesFlag = true;
    131
    132    /** Number formatter for generating numeric strings. */
    133    protected final NumberFormat numberFormatterObj
    134        = NumberFormat.getInstance();
    135
    136    /** Flag set true for "1e#"-style tick labels. */
    137    protected boolean expTickLabelsFlag = false;
    138
    139    /** Flag set true for "10^n"-style tick labels. */
    140    protected boolean log10TickLabelsFlag = false;
    141
    142    /** True to make 'autoAdjustRange()' select "10^n" values. */
    143    protected boolean autoRangeNextLogFlag = false;
    144
    145    /** Helper flag for log axis processing. */
    146    protected boolean smallLogFlag = false;
    147
    148    /**
    149     * Creates a new axis.
    150     *
    151     * @param label  the axis label.
    152     */
    153    public LogarithmicAxis(String label) {
    154        super(label);
    155        setupNumberFmtObj();      //setup number formatter obj
    156    }
    157
    158    /**
    159     * Sets the 'allowNegativesFlag' flag; true to allow negative values
    160     * in data, false to be able to plot positive values arbitrarily close to
    161     * zero.
    162     *
    163     * @param flgVal  the new value of the flag.
    164     */
    165    public void setAllowNegativesFlag(boolean flgVal) {
    166        this.allowNegativesFlag = flgVal;
    167    }
    168
    169    /**
    170     * Returns the 'allowNegativesFlag' flag; true to allow negative values
    171     * in data, false to be able to plot positive values arbitrarily close
    172     * to zero.
    173     *
    174     * @return The flag.
    175     */
    176    public boolean getAllowNegativesFlag() {
    177        return this.allowNegativesFlag;
    178    }
    179
    180    /**
    181     * Sets the 'strictValuesFlag' flag; if true and 'allowNegativesFlag'
    182     * is false then this axis will throw a runtime exception if any of its
    183     * values are less than or equal to zero; if false then the axis will
    184     * adjust for values less than or equal to zero as needed.
    185     *
    186     * @param flgVal true for strict enforcement.
    187     */
    188    public void setStrictValuesFlag(boolean flgVal) {
    189        this.strictValuesFlag = flgVal;
    190    }
    191
    192    /**
    193     * Returns the 'strictValuesFlag' flag; if true and 'allowNegativesFlag'
    194     * is false then this axis will throw a runtime exception if any of its
    195     * values are less than or equal to zero; if false then the axis will
    196     * adjust for values less than or equal to zero as needed.
    197     *
    198     * @return <code>true</code> if strict enforcement is enabled.
    199     */
    200    public boolean getStrictValuesFlag() {
    201        return this.strictValuesFlag;
    202    }
    203
    204    /**
    205     * Sets the 'expTickLabelsFlag' flag.  If the 'log10TickLabelsFlag'
    206     * is false then this will set whether or not "1e#"-style tick labels
    207     * are used.  The default is to use regular numeric tick labels.
    208     *
    209     * @param flgVal true for "1e#"-style tick labels, false for
    210     * log10 or regular numeric tick labels.
    211     */
    212    public void setExpTickLabelsFlag(boolean flgVal) {
    213        this.expTickLabelsFlag = flgVal;
    214        setupNumberFmtObj();             //setup number formatter obj
    215    }
    216
    217    /**
    218     * Returns the 'expTickLabelsFlag' flag.
    219     *
    220     * @return <code>true</code> for "1e#"-style tick labels,
    221     *         <code>false</code> for log10 or regular numeric tick labels.
    222     */
    223    public boolean getExpTickLabelsFlag() {
    224      return this.expTickLabelsFlag;
    225    }
    226
    227    /**
    228     * Sets the 'log10TickLabelsFlag' flag.  The default value is false.
    229     *
    230     * @param flag true for "10^n"-style tick labels, false for "1e#"-style
    231     * or regular numeric tick labels.
    232     */
    233    public void setLog10TickLabelsFlag(boolean flag) {
    234        this.log10TickLabelsFlag = flag;
    235    }
    236
    237    /**
    238     * Returns the 'log10TickLabelsFlag' flag.
    239     *
    240     * @return <code>true</code> for "10^n"-style tick labels,
    241     *         <code>false</code> for "1e#"-style or regular numeric tick
    242     *         labels.
    243     */
    244    public boolean getLog10TickLabelsFlag() {
    245        return this.log10TickLabelsFlag;
    246    }
    247
    248    /**
    249     * Sets the 'autoRangeNextLogFlag' flag.  This determines whether or
    250     * not the 'autoAdjustRange()' method will select the next "10^n"
    251     * values when determining the upper and lower bounds.  The default
    252     * value is false.
    253     *
    254     * @param flag <code>true</code> to make the 'autoAdjustRange()'
    255     * method select the next "10^n" values, <code>false</code> to not.
    256     */
    257    public void setAutoRangeNextLogFlag(boolean flag) {
    258        this.autoRangeNextLogFlag = flag;
    259    }
    260
    261    /**
    262     * Returns the 'autoRangeNextLogFlag' flag.
    263     *
    264     * @return <code>true</code> if the 'autoAdjustRange()' method will
    265     * select the next "10^n" values, <code>false</code> if not.
    266     */
    267    public boolean getAutoRangeNextLogFlag() {
    268        return this.autoRangeNextLogFlag;
    269    }
    270
    271    /**
    272     * Overridden version that calls original and then sets up flag for
    273     * log axis processing.
    274     *
    275     * @param range  the new range.
    276     */
    277    public void setRange(Range range) {
    278        super.setRange(range);      // call parent method
    279        setupSmallLogFlag();        // setup flag based on bounds values
    280    }
    281
    282    /**
    283     * Sets up flag for log axis processing.  Set true if negative values
    284     * not allowed and the lower bound is between 0 and 10.
    285     */
    286    protected void setupSmallLogFlag() {
    287        // set flag true if negative values not allowed and the
    288        // lower bound is between 0 and 10:
    289        double lowerVal = getRange().getLowerBound();
    290        this.smallLogFlag = (!this.allowNegativesFlag && lowerVal < 10.0
    291                && lowerVal > 0.0);
    292    }
    293
    294    /**
    295     * Sets up the number formatter object according to the
    296     * 'expTickLabelsFlag' flag.
    297     */
    298    protected void setupNumberFmtObj() {
    299        if (this.numberFormatterObj instanceof DecimalFormat) {
    300            //setup for "1e#"-style tick labels or regular
    301            // numeric tick labels, depending on flag:
    302            ((DecimalFormat) this.numberFormatterObj).applyPattern(
    303                    this.expTickLabelsFlag ? "0E0" : "0.###");
    304        }
    305    }
    306
    307    /**
    308     * Returns the log10 value, depending on if values between 0 and
    309     * 1 are being plotted.  If negative values are not allowed and
    310     * the lower bound is between 0 and 10 then a normal log is
    311     * returned; otherwise the returned value is adjusted if the
    312     * given value is less than 10.
    313     *
    314     * @param val the value.
    315     *
    316     * @return log<sub>10</sub>(val).
    317     *
    318     * @see #switchedPow10(double)
    319     */
    320    protected double switchedLog10(double val) {
    321        return this.smallLogFlag ? Math.log(val)
    322                / LOG10_VALUE : adjustedLog10(val);
    323    }
    324
    325    /**
    326     * Returns a power of 10, depending on if values between 0 and
    327     * 1 are being plotted.  If negative values are not allowed and
    328     * the lower bound is between 0 and 10 then a normal power is
    329     * returned; otherwise the returned value is adjusted if the
    330     * given value is less than 1.
    331     *
    332     * @param val the value.
    333     *
    334     * @return 10<sup>val</sup>.
    335     *
    336     * @since 1.0.5
    337     * @see #switchedLog10(double)
    338     */
    339    public double switchedPow10(double val) {
    340        return this.smallLogFlag ? Math.pow(10.0, val) : adjustedPow10(val);
    341    }
    342
    343    /**
    344     * Returns an adjusted log10 value for graphing purposes.  The first
    345     * adjustment is that negative values are changed to positive during
    346     * the calculations, and then the answer is negated at the end.  The
    347     * second is that, for values less than 10, an increasingly large
    348     * (0 to 1) scaling factor is added such that at 0 the value is
    349     * adjusted to 1, resulting in a returned result of 0.
    350     *
    351     * @param val  value for which log10 should be calculated.
    352     *
    353     * @return An adjusted log<sub>10</sub>(val).
    354     *
    355     * @see #adjustedPow10(double)
    356     */
    357    public double adjustedLog10(double val) {
    358        boolean negFlag = (val < 0.0);
    359        if (negFlag) {
    360            val = -val;          // if negative then set flag and make positive
    361        }
    362        if (val < 10.0) {                // if < 10 then
    363            val += (10.0 - val) / 10.0;  //increase so 0 translates to 0
    364        }
    365        //return value; negate if original value was negative:
    366        double res = Math.log(val) / LOG10_VALUE;
    367        return negFlag ? (-res) : res;
    368    }
    369
    370    /**
    371     * Returns an adjusted power of 10 value for graphing purposes.  The first
    372     * adjustment is that negative values are changed to positive during
    373     * the calculations, and then the answer is negated at the end.  The
    374     * second is that, for values less than 1, a progressive logarithmic
    375     * offset is subtracted such that at 0 the returned result is also 0.
    376     *
    377     * @param val  value for which power of 10 should be calculated.
    378     *
    379     * @return An adjusted 10<sup>val</sup>.
    380     *
    381     * @since 1.0.5
    382     * @see #adjustedLog10(double)
    383     */
    384    public double adjustedPow10(double val) {
    385        boolean negFlag = (val < 0.0);
    386        if (negFlag) {
    387            val = -val; // if negative then set flag and make positive
    388        }
    389        double res;
    390        if (val < 1.0) {
    391            res = (Math.pow(10, val + 1.0) - 10.0) / 9.0; //invert adjustLog10
    392        }
    393        else {
    394            res = Math.pow(10, val);
    395        }
    396        return negFlag ? (-res) : res;
    397    }
    398
    399    /**
    400     * Returns the largest (closest to positive infinity) double value that is
    401     * not greater than the argument, is equal to a mathematical integer and
    402     * satisfying the condition that log base 10 of the value is an integer
    403     * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).
    404     *
    405     * @param lower a double value below which a floor will be calcualted.
    406     *
    407     * @return 10<sup>N</sup> with N .. { 1 ... }
    408     */
    409    protected double computeLogFloor(double lower) {
    410
    411        double logFloor;
    412        if (this.allowNegativesFlag) {
    413            //negative values are allowed
    414            if (lower > 10.0) {   //parameter value is > 10
    415                // The Math.log() function is based on e not 10.
    416                logFloor = Math.log(lower) / LOG10_VALUE;
    417                logFloor = Math.floor(logFloor);
    418                logFloor = Math.pow(10, logFloor);
    419            }
    420            else if (lower < -10.0) {   //parameter value is < -10
    421                //calculate log using positive value:
    422                logFloor = Math.log(-lower) / LOG10_VALUE;
    423                //calculate floor using negative value:
    424                logFloor = Math.floor(-logFloor);
    425                //calculate power using positive value; then negate
    426                logFloor = -Math.pow(10, -logFloor);
    427            }
    428            else {
    429                //parameter value is -10 > val < 10
    430                logFloor = Math.floor(lower);   //use as-is
    431            }
    432        }
    433        else {
    434            //negative values not allowed
    435            if (lower > 0.0) {   //parameter value is > 0
    436                // The Math.log() function is based on e not 10.
    437                logFloor = Math.log(lower) / LOG10_VALUE;
    438                logFloor = Math.floor(logFloor);
    439                logFloor = Math.pow(10, logFloor);
    440            }
    441            else {
    442                //parameter value is <= 0
    443                logFloor = Math.floor(lower);   //use as-is
    444            }
    445        }
    446        return logFloor;
    447    }
    448
    449    /**
    450     * Returns the smallest (closest to negative infinity) double value that is
    451     * not less than the argument, is equal to a mathematical integer and
    452     * satisfying the condition that log base 10 of the value is an integer
    453     * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).
    454     *
    455     * @param upper a double value above which a ceiling will be calcualted.
    456     *
    457     * @return 10<sup>N</sup> with N .. { 1 ... }
    458     */
    459    protected double computeLogCeil(double upper) {
    460
    461        double logCeil;
    462        if (this.allowNegativesFlag) {
    463            //negative values are allowed
    464            if (upper > 10.0) {
    465                //parameter value is > 10
    466                // The Math.log() function is based on e not 10.
    467                logCeil = Math.log(upper) / LOG10_VALUE;
    468                logCeil = Math.ceil(logCeil);
    469                logCeil = Math.pow(10, logCeil);
    470            }
    471            else if (upper < -10.0) {
    472                //parameter value is < -10
    473                //calculate log using positive value:
    474                logCeil = Math.log(-upper) / LOG10_VALUE;
    475                //calculate ceil using negative value:
    476                logCeil = Math.ceil(-logCeil);
    477                //calculate power using positive value; then negate
    478                logCeil = -Math.pow(10, -logCeil);
    479            }
    480            else {
    481               //parameter value is -10 > val < 10
    482               logCeil = Math.ceil(upper);     //use as-is
    483            }
    484        }
    485        else {
    486            //negative values not allowed
    487            if (upper > 0.0) {
    488                //parameter value is > 0
    489                // The Math.log() function is based on e not 10.
    490                logCeil = Math.log(upper) / LOG10_VALUE;
    491                logCeil = Math.ceil(logCeil);
    492                logCeil = Math.pow(10, logCeil);
    493            }
    494            else {
    495                //parameter value is <= 0
    496                logCeil = Math.ceil(upper);     //use as-is
    497            }
    498        }
    499        return logCeil;
    500    }
    501
    502    /**
    503     * Rescales the axis to ensure that all data is visible.
    504     */
    505    public void autoAdjustRange() {
    506
    507        Plot plot = getPlot();
    508        if (plot == null) {
    509            return;  // no plot, no data.
    510        }
    511
    512        if (plot instanceof ValueAxisPlot) {
    513            ValueAxisPlot vap = (ValueAxisPlot) plot;
    514
    515            double lower;
    516            Range r = vap.getDataRange(this);
    517            if (r == null) {
    518                   //no real data present
    519                r = getDefaultAutoRange();
    520                lower = r.getLowerBound();    //get lower bound value
    521            }
    522            else {
    523                //actual data is present
    524                lower = r.getLowerBound();    //get lower bound value
    525                if (this.strictValuesFlag
    526                        && !this.allowNegativesFlag && lower <= 0.0) {
    527                    //strict flag set, allow-negatives not set and values <= 0
    528                    throw new RuntimeException("Values less than or equal to "
    529                            + "zero not allowed with logarithmic axis");
    530                }
    531            }
    532
    533            //apply lower margin by decreasing lower bound:
    534            final double lowerMargin;
    535            if (lower > 0.0 && (lowerMargin = getLowerMargin()) > 0.0) {
    536                   //lower bound and margin OK; get log10 of lower bound
    537                final double logLower = (Math.log(lower) / LOG10_VALUE);
    538                double logAbs;      //get absolute value of log10 value
    539                if ((logAbs = Math.abs(logLower)) < 1.0) {
    540                    logAbs = 1.0;     //if less than 1.0 then make it 1.0
    541                }              //subtract out margin and get exponential value:
    542                lower = Math.pow(10, (logLower - (logAbs * lowerMargin)));
    543            }
    544
    545            //if flag then change to log version of lowest value
    546            // to make range begin at a 10^n value:
    547            if (this.autoRangeNextLogFlag) {
    548                lower = computeLogFloor(lower);
    549            }
    550
    551            if (!this.allowNegativesFlag && lower >= 0.0
    552                    && lower < SMALL_LOG_VALUE) {
    553                //negatives not allowed and lower range bound is zero
    554                lower = r.getLowerBound();    //use data range bound instead
    555            }
    556
    557            double upper = r.getUpperBound();
    558
    559             //apply upper margin by increasing upper bound:
    560            final double upperMargin;
    561            if (upper > 0.0 && (upperMargin = getUpperMargin()) > 0.0) {
    562                   //upper bound and margin OK; get log10 of upper bound
    563                final double logUpper = (Math.log(upper) / LOG10_VALUE);
    564                double logAbs;      //get absolute value of log10 value
    565                if ((logAbs = Math.abs(logUpper)) < 1.0) {
    566                    logAbs = 1.0;     //if less than 1.0 then make it 1.0
    567                }              //add in margin and get exponential value:
    568                upper = Math.pow(10, (logUpper + (logAbs * upperMargin)));
    569            }
    570
    571            if (!this.allowNegativesFlag && upper < 1.0 && upper > 0.0
    572                    && lower > 0.0) {
    573                //negatives not allowed and upper bound between 0 & 1
    574                //round up to nearest significant digit for bound:
    575                //get negative exponent:
    576                double expVal = Math.log(upper) / LOG10_VALUE;
    577                expVal = Math.ceil(-expVal + 0.001); //get positive exponent
    578                expVal = Math.pow(10, expVal);      //create multiplier value
    579                //multiply, round up, and divide for bound value:
    580                upper = (expVal > 0.0) ? Math.ceil(upper * expVal) / expVal
    581                    : Math.ceil(upper);
    582            }
    583            else {
    584                //negatives allowed or upper bound not between 0 & 1
    585                //if flag then change to log version of highest value to
    586                // make range begin at a 10^n value; else use nearest int
    587                upper = (this.autoRangeNextLogFlag) ? computeLogCeil(upper)
    588                    : Math.ceil(upper);
    589            }
    590            // ensure the autorange is at least <minRange> in size...
    591            double minRange = getAutoRangeMinimumSize();
    592            if (upper - lower < minRange) {
    593                upper = (upper + lower + minRange) / 2;
    594                lower = (upper + lower - minRange) / 2;
    595                //if autorange still below minimum then adjust by 1%
    596                // (can be needed when minRange is very small):
    597                if (upper - lower < minRange) {
    598                    double absUpper = Math.abs(upper);
    599                    //need to account for case where upper==0.0
    600                    double adjVal = (absUpper > SMALL_LOG_VALUE) ? absUpper
    601                        / 100.0 : 0.01;
    602                    upper = (upper + lower + adjVal) / 2;
    603                    lower = (upper + lower - adjVal) / 2;
    604                }
    605            }
    606
    607            setRange(new Range(lower, upper), false, false);
    608            setupSmallLogFlag();       //setup flag based on bounds values
    609        }
    610    }
    611
    612    /**
    613     * Converts a data value to a coordinate in Java2D space, assuming that
    614     * the axis runs along one edge of the specified plotArea.
    615     * Note that it is possible for the coordinate to fall outside the
    616     * plotArea.
    617     *
    618     * @param value  the data value.
    619     * @param plotArea  the area for plotting the data.
    620     * @param edge  the axis location.
    621     *
    622     * @return The Java2D coordinate.
    623     */
    624    public double valueToJava2D(double value, Rectangle2D plotArea,
    625                                RectangleEdge edge) {
    626
    627        Range range = getRange();
    628        double axisMin = switchedLog10(range.getLowerBound());
    629        double axisMax = switchedLog10(range.getUpperBound());
    630
    631        double min = 0.0;
    632        double max = 0.0;
    633        if (RectangleEdge.isTopOrBottom(edge)) {
    634            min = plotArea.getMinX();
    635            max = plotArea.getMaxX();
    636        }
    637        else if (RectangleEdge.isLeftOrRight(edge)) {
    638            min = plotArea.getMaxY();
    639            max = plotArea.getMinY();
    640        }
    641
    642        value = switchedLog10(value);
    643
    644        if (isInverted()) {
    645            return max - (((value - axisMin) / (axisMax - axisMin))
    646                    * (max - min));
    647        }
    648        else {
    649            return min + (((value - axisMin) / (axisMax - axisMin))
    650                    * (max - min));
    651        }
    652
    653    }
    654
    655    /**
    656     * Converts a coordinate in Java2D space to the corresponding data
    657     * value, assuming that the axis runs along one edge of the specified
    658     * plotArea.
    659     *
    660     * @param java2DValue  the coordinate in Java2D space.
    661     * @param plotArea  the area in which the data is plotted.
    662     * @param edge  the axis location.
    663     *
    664     * @return The data value.
    665     */
    666    public double java2DToValue(double java2DValue, Rectangle2D plotArea,
    667                                RectangleEdge edge) {
    668
    669        Range range = getRange();
    670        double axisMin = switchedLog10(range.getLowerBound());
    671        double axisMax = switchedLog10(range.getUpperBound());
    672
    673        double plotMin = 0.0;
    674        double plotMax = 0.0;
    675        if (RectangleEdge.isTopOrBottom(edge)) {
    676            plotMin = plotArea.getX();
    677            plotMax = plotArea.getMaxX();
    678        }
    679        else if (RectangleEdge.isLeftOrRight(edge)) {
    680            plotMin = plotArea.getMaxY();
    681            plotMax = plotArea.getMinY();
    682        }
    683
    684        if (isInverted()) {
    685            return switchedPow10(axisMax - ((java2DValue - plotMin)
    686                    / (plotMax - plotMin)) * (axisMax - axisMin));
    687        }
    688        else {
    689            return switchedPow10(axisMin + ((java2DValue - plotMin)
    690                    / (plotMax - plotMin)) * (axisMax - axisMin));
    691        }
    692    }
    693
    694    /**
    695     * Zooms in on the current range.
    696     *
    697     * @param lowerPercent  the new lower bound.
    698     * @param upperPercent  the new upper bound.
    699     */
    700    public void zoomRange(double lowerPercent, double upperPercent) {
    701        double startLog = switchedLog10(getRange().getLowerBound());
    702        double lengthLog = switchedLog10(getRange().getUpperBound()) - startLog;
    703        Range adjusted;
    704
    705        if (isInverted()) {
    706            adjusted = new Range(
    707                    switchedPow10(
    708                            startLog + (lengthLog * (1 - upperPercent))),
    709                    switchedPow10(
    710                            startLog + (lengthLog * (1 - lowerPercent))));
    711        }
    712        else {
    713            adjusted = new Range(
    714                    switchedPow10(startLog + (lengthLog * lowerPercent)),
    715                    switchedPow10(startLog + (lengthLog * upperPercent)));
    716        }
    717
    718        setRange(adjusted);
    719    }
    720
    721    /**
    722     * Calculates the positions of the tick labels for the axis, storing the
    723     * results in the tick label list (ready for drawing).
    724     *
    725     * @param g2  the graphics device.
    726     * @param dataArea  the area in which the plot should be drawn.
    727     * @param edge  the location of the axis.
    728     *
    729     * @return A list of ticks.
    730     */
    731    protected List refreshTicksHorizontal(Graphics2D g2,
    732                                          Rectangle2D dataArea,
    733                                          RectangleEdge edge) {
    734
    735        List ticks = new java.util.ArrayList();
    736        Range range = getRange();
    737
    738        //get lower bound value:
    739        double lowerBoundVal = range.getLowerBound();
    740              //if small log values and lower bound value too small
    741              // then set to a small value (don't allow <= 0):
    742        if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {
    743            lowerBoundVal = SMALL_LOG_VALUE;
    744        }
    745
    746        //get upper bound value
    747        double upperBoundVal = range.getUpperBound();
    748
    749        //get log10 version of lower bound and round to integer:
    750        int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));
    751        //get log10 version of upper bound and round to integer:
    752        int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));
    753
    754        if (iBegCount == iEndCount && iBegCount > 0
    755                && Math.pow(10, iBegCount) > lowerBoundVal) {
    756              //only 1 power of 10 value, it's > 0 and its resulting
    757              // tick value will be larger than lower bound of data
    758            --iBegCount;       //decrement to generate more ticks
    759        }
    760
    761        double currentTickValue;
    762        String tickLabel;
    763        boolean zeroTickFlag = false;
    764        for (int i = iBegCount; i <= iEndCount; i++) {
    765            //for each power of 10 value; create ten ticks
    766            for (int j = 0; j < 10; ++j) {
    767                //for each tick to be displayed
    768                if (this.smallLogFlag) {
    769                    //small log values in use; create numeric value for tick
    770                    currentTickValue = Math.pow(10, i) + (Math.pow(10, i) * j);
    771                    if (this.expTickLabelsFlag
    772                        || (i < 0 && currentTickValue > 0.0
    773                        && currentTickValue < 1.0)) {
    774                        //showing "1e#"-style ticks or negative exponent
    775                        // generating tick value between 0 & 1; show fewer
    776                        if (j == 0 || (i > -4 && j < 2)
    777                                   || currentTickValue >= upperBoundVal) {
    778                          //first tick of series, or not too small a value and
    779                          // one of first 3 ticks, or last tick to be displayed
    780                            // set exact number of fractional digits to be shown
    781                            // (no effect if showing "1e#"-style ticks):
    782                            this.numberFormatterObj
    783                                .setMaximumFractionDigits(-i);
    784                               //create tick label (force use of fmt obj):
    785                            tickLabel = makeTickLabel(currentTickValue, true);
    786                        }
    787                        else {    //no tick label to be shown
    788                            tickLabel = "";
    789                        }
    790                    }
    791                    else {     //tick value not between 0 & 1
    792                               //show tick label if it's the first or last in
    793                               // the set, or if it's 1-5; beyond that show
    794                               // fewer as the values get larger:
    795                        tickLabel = (j < 1 || (i < 1 && j < 5) || (j < 4 - i)
    796                                         || currentTickValue >= upperBoundVal)
    797                                         ? makeTickLabel(currentTickValue) : "";
    798                    }
    799                }
    800                else { //not small log values in use; allow for values <= 0
    801                    if (zeroTickFlag) {   //if did zero tick last iter then
    802                        --j;              //decrement to do 1.0 tick now
    803                    }     //calculate power-of-ten value for tick:
    804                    currentTickValue = (i >= 0)
    805                        ? Math.pow(10, i) + (Math.pow(10, i) * j)
    806                        : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j));
    807                    if (!zeroTickFlag) {  // did not do zero tick last iteration
    808                        if (Math.abs(currentTickValue - 1.0) < 0.0001
    809                            && lowerBoundVal <= 0.0 && upperBoundVal >= 0.0) {
    810                            //tick value is 1.0 and 0.0 is within data range
    811                            currentTickValue = 0.0;     //set tick value to zero
    812                            zeroTickFlag = true;        //indicate zero tick
    813                        }
    814                    }
    815                    else {     //did zero tick last iteration
    816                        zeroTickFlag = false;         //clear flag
    817                    }               //create tick label string:
    818                               //show tick label if "1e#"-style and it's one
    819                               // of the first two, if it's the first or last
    820                               // in the set, or if it's 1-5; beyond that
    821                               // show fewer as the values get larger:
    822                    tickLabel = ((this.expTickLabelsFlag && j < 2)
    823                                || j < 1
    824                                || (i < 1 && j < 5) || (j < 4 - i)
    825                                || currentTickValue >= upperBoundVal)
    826                                   ? makeTickLabel(currentTickValue) : "";
    827                }
    828
    829                if (currentTickValue > upperBoundVal) {
    830                    return ticks;   // if past highest data value then exit
    831                                    // method
    832                }
    833
    834                if (currentTickValue >= lowerBoundVal - SMALL_LOG_VALUE) {
    835                    //tick value not below lowest data value
    836                    TextAnchor anchor = null;
    837                    TextAnchor rotationAnchor = null;
    838                    double angle = 0.0;
    839                    if (isVerticalTickLabels()) {
    840                        anchor = TextAnchor.CENTER_RIGHT;
    841                        rotationAnchor = TextAnchor.CENTER_RIGHT;
    842                        if (edge == RectangleEdge.TOP) {
    843                            angle = Math.PI / 2.0;
    844                        }
    845                        else {
    846                            angle = -Math.PI / 2.0;
    847                        }
    848                    }
    849                    else {
    850                        if (edge == RectangleEdge.TOP) {
    851                            anchor = TextAnchor.BOTTOM_CENTER;
    852                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
    853                        }
    854                        else {
    855                            anchor = TextAnchor.TOP_CENTER;
    856                            rotationAnchor = TextAnchor.TOP_CENTER;
    857                        }
    858                    }
    859
    860                    Tick tick = new NumberTick(new Double(currentTickValue),
    861                            tickLabel, anchor, rotationAnchor, angle);
    862                    ticks.add(tick);
    863                }
    864            }
    865        }
    866        return ticks;
    867
    868    }
    869
    870    /**
    871     * Calculates the positions of the tick labels for the axis, storing the
    872     * results in the tick label list (ready for drawing).
    873     *
    874     * @param g2  the graphics device.
    875     * @param dataArea  the area in which the plot should be drawn.
    876     * @param edge  the location of the axis.
    877     *
    878     * @return A list of ticks.
    879     */
    880    protected List refreshTicksVertical(Graphics2D g2,
    881                                        Rectangle2D dataArea,
    882                                        RectangleEdge edge) {
    883
    884        List ticks = new java.util.ArrayList();
    885
    886        //get lower bound value:
    887        double lowerBoundVal = getRange().getLowerBound();
    888        //if small log values and lower bound value too small
    889        // then set to a small value (don't allow <= 0):
    890        if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {
    891            lowerBoundVal = SMALL_LOG_VALUE;
    892        }
    893        //get upper bound value
    894        double upperBoundVal = getRange().getUpperBound();
    895
    896        //get log10 version of lower bound and round to integer:
    897        int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));
    898        //get log10 version of upper bound and round to integer:
    899        int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));
    900
    901        if (iBegCount == iEndCount && iBegCount > 0
    902                && Math.pow(10, iBegCount) > lowerBoundVal) {
    903              //only 1 power of 10 value, it's > 0 and its resulting
    904              // tick value will be larger than lower bound of data
    905            --iBegCount;       //decrement to generate more ticks
    906        }
    907
    908        double tickVal;
    909        String tickLabel;
    910        boolean zeroTickFlag = false;
    911        for (int i = iBegCount; i <= iEndCount; i++) {
    912            //for each tick with a label to be displayed
    913            int jEndCount = 10;
    914            if (i == iEndCount) {
    915                jEndCount = 1;
    916            }
    917
    918            for (int j = 0; j < jEndCount; j++) {
    919                //for each tick to be displayed
    920                if (this.smallLogFlag) {
    921                    //small log values in use
    922                    tickVal = Math.pow(10, i) + (Math.pow(10, i) * j);
    923                    if (j == 0) {
    924                        //first tick of group; create label text
    925                        if (this.log10TickLabelsFlag) {
    926                            //if flag then
    927                            tickLabel = "10^" + i;   //create "log10"-type label
    928                        }
    929                        else {    //not "log10"-type label
    930                            if (this.expTickLabelsFlag) {
    931                                //if flag then
    932                                tickLabel = "1e" + i;  //create "1e#"-type label
    933                            }
    934                            else {    //not "1e#"-type label
    935                                if (i >= 0) {   // if positive exponent then
    936                                                // make integer
    937                                    NumberFormat format
    938                                        = getNumberFormatOverride();
    939                                    if (format != null) {
    940                                        tickLabel = format.format(tickVal);
    941                                    }
    942                                    else {
    943                                        tickLabel = Long.toString((long)
    944                                                Math.rint(tickVal));
    945                                    }
    946                                }
    947                                else {
    948                                    //negative exponent; create fractional value
    949                                    //set exact number of fractional digits to
    950                                    // be shown:
    951                                    this.numberFormatterObj
    952                                        .setMaximumFractionDigits(-i);
    953                                    //create tick label:
    954                                    tickLabel = this.numberFormatterObj.format(
    955                                            tickVal);
    956                                }
    957                            }
    958                        }
    959                    }
    960                    else {   //not first tick to be displayed
    961                        tickLabel = "";     //no tick label
    962                    }
    963                }
    964                else { //not small log values in use; allow for values <= 0
    965                    if (zeroTickFlag) {      //if did zero tick last iter then
    966                        --j;
    967                    }               //decrement to do 1.0 tick now
    968                    tickVal = (i >= 0) ? Math.pow(10, i) + (Math.pow(10, i) * j)
    969                             : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j));
    970                    if (j == 0) {  //first tick of group
    971                        if (!zeroTickFlag) {     // did not do zero tick last
    972                                                 // iteration
    973                            if (i > iBegCount && i < iEndCount
    974                                    && Math.abs(tickVal - 1.0) < 0.0001) {
    975                                // not first or last tick on graph and value
    976                                // is 1.0
    977                                tickVal = 0.0;        //change value to 0.0
    978                                zeroTickFlag = true;  //indicate zero tick
    979                                tickLabel = "0";      //create label for tick
    980                            }
    981                            else {
    982                                //first or last tick on graph or value is 1.0
    983                                //create label for tick:
    984                                if (this.log10TickLabelsFlag) {
    985                                       //create "log10"-type label
    986                                    tickLabel = (((i < 0) ? "-" : "")
    987                                            + "10^" + Math.abs(i));
    988                                }
    989                                else {
    990                                    if (this.expTickLabelsFlag) {
    991                                           //create "1e#"-type label
    992                                        tickLabel = (((i < 0) ? "-" : "")
    993                                                + "1e" + Math.abs(i));
    994                                    }
    995                                    else {
    996                                        NumberFormat format
    997                                            = getNumberFormatOverride();
    998                                        if (format != null) {
    999                                            tickLabel = format.format(tickVal);
    1000                                        }
    1001                                        else {
    1002                                            tickLabel =  Long.toString(
    1003                                                    (long) Math.rint(tickVal));
    1004                                        }
    1005                                    }
    1006                                }
    1007                            }
    1008                        }
    1009                        else {     // did zero tick last iteration
    1010                            tickLabel = "";         //no label
    1011                            zeroTickFlag = false;   //clear flag
    1012                        }
    1013                    }
    1014                    else {       // not first tick of group
    1015                        tickLabel = "";           //no label
    1016                        zeroTickFlag = false;     //make sure flag cleared
    1017                    }
    1018                }
    1019
    1020                if (tickVal > upperBoundVal) {
    1021                    return ticks;  //if past highest data value then exit method
    1022                }
    1023
    1024                if (tickVal >= lowerBoundVal - SMALL_LOG_VALUE) {
    1025                    //tick value not below lowest data value
    1026                    TextAnchor anchor = null;
    1027                    TextAnchor rotationAnchor = null;
    1028                    double angle = 0.0;
    1029                    if (isVerticalTickLabels()) {
    1030                        if (edge == RectangleEdge.LEFT) {
    1031                            anchor = TextAnchor.BOTTOM_CENTER;
    1032                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
    1033                            angle = -Math.PI / 2.0;
    1034                        }
    1035                        else {
    1036                            anchor = TextAnchor.BOTTOM_CENTER;
    1037                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
    1038                            angle = Math.PI / 2.0;
    1039                        }
    1040                    }
    1041                    else {
    1042                        if (edge == RectangleEdge.LEFT) {
    1043                            anchor = TextAnchor.CENTER_RIGHT;
    1044                            rotationAnchor = TextAnchor.CENTER_RIGHT;
    1045                        }
    1046                        else {
    1047                            anchor = TextAnchor.CENTER_LEFT;
    1048                            rotationAnchor = TextAnchor.CENTER_LEFT;
    1049                        }
    1050                    }
    1051                    //create tick object and add to list:
    1052                    ticks.add(new NumberTick(new Double(tickVal), tickLabel,
    1053                            anchor, rotationAnchor, angle));
    1054                }
    1055            }
    1056        }
    1057        return ticks;
    1058    }
    1059
    1060    /**
    1061     * Converts the given value to a tick label string.
    1062     *
    1063     * @param val the value to convert.
    1064     * @param forceFmtFlag true to force the number-formatter object
    1065     * to be used.
    1066     *
    1067     * @return The tick label string.
    1068     */
    1069    protected String makeTickLabel(double val, boolean forceFmtFlag) {
    1070        if (this.expTickLabelsFlag || forceFmtFlag) {
    1071            //using exponents or force-formatter flag is set
    1072            // (convert 'E' to lower-case 'e'):
    1073            return this.numberFormatterObj.format(val).toLowerCase();
    1074        }
    1075        return getTickUnit().valueToString(val);
    1076    }
    1077
    1078    /**
    1079     * Converts the given value to a tick label string.
    1080     * @param val the value to convert.
    1081     *
    1082     * @return The tick label string.
    1083     */
    1084    protected String makeTickLabel(double val) {
    1085        return makeTickLabel(val, false);
    1086    }
    1087
    1088}