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