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