/* ===========================================================

 * JFreeChart : a free chart library for the Java(tm) platform

 * ===========================================================

 *

 * (C) Copyright 2000-2004, by Object Refinery Limited and Contributors.

 *

 * Project Info:  http://www.jfree.org/jfreechart/index.html

 *

 * This library is free software; you can redistribute it and/or modify it under the terms

 * of the GNU Lesser General Public License as published by the Free Software Foundation;

 * either version 2.1 of the License, or (at your option) any later version.

 *

 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;

 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 * See the GNU Lesser General Public License for more details.

 *

 * You should have received a copy of the GNU Lesser General Public License along with this

 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,

 * Boston, MA 02111-1307, USA.

 *

 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 

 * in the United States and other countries.]

 *

 * --------------------

 * LogarithmicAxis.java

 * --------------------

 * (C) Copyright 2000-2004, by Object Refinery Limited and Contributors.

 *

 * Original Author:  Michael Duffy / Eric Thomas;

 * Contributor(s):   David Gilbert (for Object Refinery Limited);

 *                   David M. O'Donnell;

 *                   Scott Sams;

 *

 * $Id: LogarithmicAxis.java,v 1.1 2004/08/31 14:30:23 mungady Exp $

 *

 * Changes

 * -------

 * 14-Mar-2002 : Version 1 contributed by Michael Duffy (DG);

 * 19-Apr-2002 : drawVerticalString(...) is now drawRotatedString(...) in RefineryUtilities (DG);

 * 23-Apr-2002 : Added a range property (DG);

 * 15-May-2002 : Modified to be able to deal with negative and zero values (via new

 *               'adjustedLog10()' method);  occurrences of "Math.log(10)" changed to "LOG10_VALUE";

 *               changed 'intValue()' to 'longValue()' in 'refreshTicks()' to fix label-text value

 *               out-of-range problem; removed 'draw()' method; added 'autoRangeMinimumSize' check;

 *               added 'log10TickLabelsFlag' parameter flag and implementation (ET);

 * 25-Jun-2002 : Removed redundant import (DG);

 * 25-Jul-2002 : Changed order of parameters in ValueAxis constructor (DG);

 * 16-Jul-2002 : Implemented support for plotting positive values arbitrarily

 *               close to zero (added 'allowNegativesFlag' flag) (ET).

 * 05-Sep-2002 : Updated constructor reflecting changes in the Axis class (DG);

 * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);

 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);

 * 22-Nov-2002 : Bug fixes from David M. O'Donnell (DG);

 * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);

 * 20-Jan-2003 : Removed unnecessary constructors (DG);

 * 26-Mar-2003 : Implemented Serializable (DG);

 * 08-May-2003 : Fixed plotting of datasets with lower==upper bounds when

 *               'minAutoRange' is very small; added 'strictValuesFlag'

 *               and default functionality of throwing a runtime exception

 *               if 'allowNegativesFlag' is false and any values are less

 *               than or equal to zero; added 'expTickLabelsFlag' and

 *               changed to use "1e#"-style tick labels by default

 *               ("10^n"-style tick labels still supported via 'set'

 *               method); improved generation of tick labels when range of

 *               values is small; changed to use 'NumberFormat.getInstance()'

 *               to create 'numberFormatterObj' (ET);

 * 14-May-2003 : Merged HorizontalLogarithmicAxis and VerticalLogarithmicAxis (DG);

 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);

 * 07-Nov-2003 : Modified to use new NumberTick class (DG);

 * 08-Apr-2004 : Use numberFormatOverride if set - see patch 930139 (DG);

 *

 */



package org.jfree.chart.axis;



import java.awt.Graphics2D;

import java.awt.geom.Rectangle2D;

import java.text.DecimalFormat;

import java.text.NumberFormat;

import java.util.List;



import org.jfree.chart.plot.Plot;

import org.jfree.chart.plot.ValueAxisPlot;

import org.jfree.data.Range;

import org.jfree.ui.RectangleEdge;

import org.jfree.ui.TextAnchor;



/**

 * A numerical axis that uses a logarithmic scale.

 *

 * @author Michael Duffy

 */

public class LogarithmicAxis extends NumberAxis {



    /** Useful constant for log(10). */

    public static final double LOG10_VALUE = Math.log(10.0);



    /** Smallest arbitrarily-close-to-zero value allowed. */

    public static final double SMALL_LOG_VALUE = 1e-100;



    /** Flag set true to allow negative values in data. */

    protected boolean allowNegativesFlag = false;



    /** Flag set true make axis throw exception if any values are

      * <= 0 and 'allowNegativesFlag' is false. */

    protected boolean strictValuesFlag = true;



    /** Number formatter for generating numeric strings. */

    protected final NumberFormat numberFormatterObj = NumberFormat.getInstance();



    /** Flag set true for "1e#"-style tick labels. */

    protected boolean expTickLabelsFlag = false;



    /** Flag set true for "10^n"-style tick labels. */

    protected boolean log10TickLabelsFlag = true;



    /** Helper flag for log axis processing. */

    protected boolean smallLogFlag = false;



    /**

     * Creates a new axis.

     *

     * @param label  the axis label.

     */

    public LogarithmicAxis(String label) {

        super(label);

        setupNumberFmtObj();      //setup number formatter obj

    }



    /**

     * Sets the 'allowNegativesFlag' flag; true to allow negative values

     * in data, false to be able to plot positive values arbitrarily close to zero.

     *

     * @param flgVal  the new value of the flag.

     */

    public void setAllowNegativesFlag(boolean flgVal) {

        this.allowNegativesFlag = flgVal;

    }



    /**

     * Returns the 'allowNegativesFlag' flag; true to allow negative values

     * in data, false to be able to plot positive values arbitrarily close

     * to zero.

     *

     * @return the flag.

     */

    public boolean getAllowNegativesFlag() {

        return this.allowNegativesFlag;

    }



    /**

     * Sets the 'strictValuesFlag' flag; if true and 'allowNegativesFlag'

     * is false then this axis will throw a runtime exception if any of its

     * values are less than or equal to zero; if false then the axis will

     * adjust for values less than or equal to zero as needed.

     *

     * @param flgVal true for strict enforcement.

     */

    public void setStrictValuesFlag(boolean flgVal) {

        this.strictValuesFlag = flgVal;

    }



    /**

     * Returns the 'strictValuesFlag' flag; if true and 'allowNegativesFlag'

     * is false then this axis will throw a runtime exception if any of its

     * values are less than or equal to zero; if false then the axis will

     * adjust for values less than or equal to zero as needed.

     *

     * @return true if strict enforcement is enabled.

     */

    public boolean getStrictValuesFlag() {

        return this.strictValuesFlag;

    }



    /**

     * Sets the 'expTickLabelsFlag' flag.  If the 'log10TickLabelsFlag'

     * is false then this will set whether or not "1e#"-style tick labels

     * are used.  The default is to use "1e#"-style tick labels.

     *

     * @param flgVal true for "1e#"-style tick labels, false for

     * log10 or regular numeric tick labels.

     */

    public void setExpTickLabelsFlag(boolean flgVal) {

        this.expTickLabelsFlag = flgVal;

        setupNumberFmtObj();             //setup number formatter obj

    }



    /**

     * Returns the 'expTickLabelsFlag' flag.

     *

     * @return  true for "1e#"-style tick labels, false for log10 or

     * regular numeric tick labels.

     */

    public boolean getExpTickLabelsFlag() {

      return this.expTickLabelsFlag;

    }



    /**

     * Sets the 'log10TickLabelsFlag' flag.  The default value is false.

     *

     * @param flag true for "10^n"-style tick labels, false for "1e#"-style

     * or regular numeric tick labels.

     */

    public void setLog10TickLabelsFlag(boolean flag) {

        this.log10TickLabelsFlag = flag;

    }



    /**

     * Returns the 'log10TickLabelsFlag' flag.

     *

     * @return true for "10^n"-style tick labels, false for "1e#"-style

     * or regular numeric tick labels.

     */

    public boolean getLog10TickLabelsFlag() {

        return this.log10TickLabelsFlag;

    }



    /**

     * Overridden version that calls original and then sets up flag for

     * log axis processing.

     *

     * @param range  the new range.

     */

    public void setRange(Range range) {

        super.setRange(range);      // call parent method

        setupSmallLogFlag();        // setup flag based on bounds values

    }



    /**

     * Sets up flag for log axis processing.  Set true if negative values

     * not allowed and the lower bound is between 0 and 10.

     */

    protected void setupSmallLogFlag() {

        // set flag true if negative values not allowed and the

        // lower bound is between 0 and 10:

        final double lowerVal = getRange().getLowerBound();

        this.smallLogFlag = (!this.allowNegativesFlag && lowerVal < 10.0 && lowerVal > 0.0);

    }



    /**

     * Sets up the number formatter object according to the

     * 'expTickLabelsFlag' flag.

     */

    protected void setupNumberFmtObj() {

        if (this.numberFormatterObj instanceof DecimalFormat) {

            //setup for "1e#"-style tick labels or regular

            // numeric tick labels, depending on flag:

            ((DecimalFormat) this.numberFormatterObj).applyPattern(

                this.expTickLabelsFlag ? "0E0" : "0.###"

            );

        }

    }



    /**

     * Returns the log10 value, depending on if values between 0 and

     * 1 are being plotted.  If negative values are not allowed and

     * the lower bound is between 0 and 10 then a normal log is

     * returned; otherwise the returned value is adjusted if the

     * given value is less than 10.

     *

     * @param val the value.

     *

     * @return log<sub>10</sub>(val).

     */

    protected double switchedLog10(double val) {

        return this.smallLogFlag ? Math.log(val) / LOG10_VALUE : adjustedLog10(val);

    }



    /**

     * Returns an adjusted log10 value for graphing purposes.  The first

     * adjustment is that negative values are changed to positive during

     * the calculations, and then the answer is negated at the end.  The

     * second is that, for values less than 10, an increasingly large

     * (0 to 1) scaling factor is added such that at 0 the value is

     * adjusted to 1, resulting in a returned result of 0.

     *

     * @param val  value for which log10 should be calculated.

     *

     * @return an adjusted log<sub>10</sub>(val).

     */

    public double adjustedLog10(double val) {

        final boolean negFlag = (val < 0.0);

        if (negFlag) {

            val = -val;                  // if negative then set flag and make positive

        }

        if (val < 10.0) {                // if < 10 then

            val += (10.0 - val) / 10;    //increase so 0 translates to 0

        }

        //return value; negate if original value was negative:

        return negFlag ? -(Math.log(val) / LOG10_VALUE) : (Math.log(val) / LOG10_VALUE);

    }



    /**

     * Returns the largest (closest to positive infinity) double value that is

     * not greater than the argument, is equal to a mathematical integer and

     * satisfying the condition that log base 10 of the value is an integer

     * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).

     *

     * @param lower a double value below which a floor will be calcualted.

     *

     * @return 10<sup>N</sup> with N .. { 1 ... }

     */

    protected double computeLogFloor(double lower) {



        double logFloor;

        if (this.allowNegativesFlag) {

            //negative values are allowed

            if (lower > 10.0) {   //parameter value is > 10

                // The Math.log() function is based on e not 10.

                logFloor = Math.log(lower) / LOG10_VALUE;

                logFloor = Math.floor(logFloor);

                logFloor = Math.pow(10, logFloor);

            }

            else if (lower < -10.0) {   //parameter value is < -10

                //calculate log using positive value:

                logFloor = Math.log(-lower) / LOG10_VALUE;

                //calculate floor using negative value:

                logFloor = Math.floor(-logFloor);

                //calculate power using positive value; then negate

                logFloor = -Math.pow(10, -logFloor);

            }

            else {

                //parameter value is -10 > val < 10

                logFloor = Math.floor(lower);   //use as-is

            }

        }

        else {

            //negative values not allowed

            if (lower > 0.0) {   //parameter value is > 0

                // The Math.log() function is based on e not 10.

                logFloor = Math.log(lower) / LOG10_VALUE;

                logFloor = Math.floor(logFloor);

                logFloor = Math.pow(10, logFloor);

            }

            else {

                //parameter value is <= 0

                logFloor = Math.floor(lower);   //use as-is

            }

        }

        return logFloor;

    }



    /**

     * Returns the smallest (closest to negative infinity) double value that is

     * not less than the argument, is equal to a mathematical integer and

     * satisfying the condition that log base 10 of the value is an integer

     * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).

     *

     * @param upper a double value above which a ceiling will be calcualted.

     *

     * @return 10<sup>N</sup> with N .. { 1 ... }

     */

    protected double computeLogCeil(double upper) {



        double logCeil;

        if (this.allowNegativesFlag) {

            //negative values are allowed

            if (upper > 10.0) {

                //parameter value is > 10

                // The Math.log() function is based on e not 10.

                logCeil = Math.log(upper) / LOG10_VALUE;

                logCeil = Math.ceil(logCeil);

                logCeil = Math.pow(10, logCeil);

            }

            else if (upper < -10.0) {

                //parameter value is < -10

                //calculate log using positive value:

                logCeil = Math.log(-upper) / LOG10_VALUE;

                //calculate ceil using negative value:

                logCeil = Math.ceil(-logCeil);

                //calculate power using positive value; then negate

                logCeil = -Math.pow(10, -logCeil);

            }

            else {

               //parameter value is -10 > val < 10

               logCeil = Math.ceil(upper);     //use as-is

            }

        }

        else {

            //negative values not allowed

            if (upper > 0.0) {

                //parameter value is > 0

                // The Math.log() function is based on e not 10.

                logCeil = Math.log(upper) / LOG10_VALUE;

                logCeil = Math.ceil(logCeil);

                logCeil = Math.pow(10, logCeil);

            }

            else {

                //parameter value is <= 0

                logCeil = Math.ceil(upper);     //use as-is

            }

        }

        return logCeil;

    }



    /**

     * Rescales the axis to ensure that all data is visible.

     */

    public void autoAdjustRange() {



        Plot plot = getPlot();

        if (plot == null) {

            return;  // no plot, no data.

        }



        if (plot instanceof ValueAxisPlot) {

            ValueAxisPlot vap = (ValueAxisPlot) plot;



            double lower;

            Range r = vap.getDataRange(this);

            if (r == null) {

                   //no real data present

                r = new Range(DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND);

                lower = r.getLowerBound();    //get lower bound value

            }

            else {

                //actual data is present

                lower = r.getLowerBound();    //get lower bound value

                if (this.strictValuesFlag && !this.allowNegativesFlag && lower <= 0.0) {

                    //strict flag set, allow-negatives not set and values <= 0

                throw new RuntimeException("Values less than or equal to " 

                                           + "zero not allowed with logarithmic axis");

                }

            }



            //change to log version of lowest value to make range

            // begin at a 10^n value:

            lower = computeLogFloor(lower);



            if (!this.allowNegativesFlag && lower >= 0.0 && lower < SMALL_LOG_VALUE) {

                //negatives not allowed and lower range bound is zero

                lower = r.getLowerBound();    //use data range bound instead

            }



            double upper = r.getUpperBound();

            if (!this.allowNegativesFlag && upper < 1.0 && upper > 0.0 && lower > 0.0) {

                //negatives not allowed and upper bound between 0 & 1

                //round up to nearest significant digit for bound:

                //get negative exponent:

                double expVal = Math.log(upper) / LOG10_VALUE;

                expVal = Math.ceil(-expVal + 0.001); //get positive exponent

                expVal = Math.pow(10, expVal);      //create multiplier value

                //multiply, round up, and divide for bound value:

                upper = (expVal > 0.0) ? Math.ceil(upper * expVal) / expVal : Math.ceil(upper);

            }

            else {

                //negatives allowed or upper bound not between 0 & 1

                upper = computeLogCeil(upper);  //use nearest log value

            }

            // ensure the autorange is at least <minRange> in size...

            double minRange = getAutoRangeMinimumSize();

            if (upper - lower < minRange) {

                upper = (upper + lower + minRange) / 2;

                lower = (upper + lower - minRange) / 2;

                //if autorange still below minimum then adjust by 1%

                // (can be needed when minRange is very small):

                if (upper - lower < minRange) {

                    final double absUpper = Math.abs(upper);

                    //need to account for case where upper==0.0

                    final double adjVal = (absUpper > SMALL_LOG_VALUE) ? absUpper / 100.0 : 0.01;

                    upper = (upper + lower + adjVal) / 2;

                    lower = (upper + lower - adjVal) / 2;

                }

            }



            setRange(new Range(lower, upper), false, false);

            setupSmallLogFlag();       //setup flag based on bounds values

        }

    }



    /**

     * Converts a data value to a coordinate in Java2D space, assuming that

     * the axis runs along one edge of the specified plotArea.

     * Note that it is possible for the coordinate to fall outside the

     * plotArea.

     *

     * @param value  the data value.

     * @param plotArea  the area for plotting the data.

     * @param edge  the axis location.

     *

     * @return the Java2D coordinate.

     */

    public double valueToJava2D(double value, Rectangle2D plotArea, RectangleEdge edge) {



        Range range = getRange();

        double axisMin = switchedLog10(range.getLowerBound());

        double axisMax = switchedLog10(range.getUpperBound());



        double min = 0.0;

        double max = 0.0;

        if (RectangleEdge.isTopOrBottom(edge)) {

            min = plotArea.getMinX();

            max = plotArea.getMaxX();

        }

        else if (RectangleEdge.isLeftOrRight(edge)) {

            min = plotArea.getMaxY();

            max = plotArea.getMinY();

        }



        value = switchedLog10(value);



        if (isInverted()) {

            return max - (((value - axisMin) / (axisMax - axisMin)) * (max - min));

        }

        else {

            return min + (((value - axisMin) / (axisMax - axisMin)) * (max - min));

        }



    }



    /**

     * Converts a coordinate in Java2D space to the corresponding data

     * value, assuming that the axis runs along one edge of the specified plotArea.

     *

     * @param java2DValue  the coordinate in Java2D space.

     * @param plotArea  the area in which the data is plotted.

     * @param edge  the axis location.

     *

     * @return the data value.

     */

    public double java2DToValue(double java2DValue, Rectangle2D plotArea, RectangleEdge edge) {



        Range range = getRange();

        double axisMin = switchedLog10(range.getLowerBound());

        double axisMax = switchedLog10(range.getUpperBound());



        double plotMin = 0.0;

        double plotMax = 0.0;

        if (RectangleEdge.isTopOrBottom(edge)) {

            plotMin = plotArea.getX();

            plotMax = plotArea.getMaxX();

        }

        else if (RectangleEdge.isLeftOrRight(edge)) {

            plotMin = plotArea.getMaxY();

            plotMax = plotArea.getMinY();

        }



        if (isInverted()) {

            return Math.pow(

                10, axisMax - ((java2DValue - plotMin) / (plotMax - plotMin)) * (axisMax - axisMin)

            );

        }

        else {

            return Math.pow(

                10, axisMin + ((java2DValue - plotMin) / (plotMax - plotMin)) * (axisMax - axisMin)

            );

        }

    }



    /**

     * Calculates the positions of the tick labels for the axis, storing the results in the

     * tick label list (ready for drawing).

     *

     * @param g2  the graphics device.

     * @param state  the axis state.

     * @param drawArea  the area in which the plot and the axes should be drawn.

     * @param dataArea  the area in which the plot should be drawn.

     * @param edge  the location of the axis.

     * 

     * @return a list of ticks.

     */

    public List refreshTicks(Graphics2D g2, 

                             AxisState state,

                             Rectangle2D drawArea, 

                             Rectangle2D dataArea,

                             RectangleEdge edge) {



        List ticks = new java.util.ArrayList();

        if (RectangleEdge.isTopOrBottom(edge)) {

            ticks = refreshTicksHorizontal(g2, state.getCursor(), drawArea, dataArea, edge);

        }

        else if (RectangleEdge.isLeftOrRight(edge)) {

            ticks = refreshTicksVertical(g2, state.getCursor(), drawArea, dataArea, edge);

        }

        return ticks;



    }



    /**

     * Calculates the positions of the tick labels for the axis, storing the results in the

     * tick label list (ready for drawing).

     *

     * @param g2  the graphics device.

     * @param cursor  the cursor position.

     * @param drawArea  the area in which the plot and the axes should be drawn.

     * @param dataArea  the area in which the plot should be drawn.

     * @param edge  the location of the axis.

     * 

     * @return A list of ticks.

     */

    public List refreshTicksHorizontal(Graphics2D g2, 

                                       double cursor, 

                                       Rectangle2D drawArea, 

                                       Rectangle2D dataArea,

                                       RectangleEdge edge) {

        

        List ticks = new java.util.ArrayList();

//        FontMetrics fm = g2.getFontMetrics(getTickLabelFont());

        Range range = getRange();



        //get lower bound value:

        double lowerBoundVal = range.getLowerBound();

              //if small log values and lower bound value too small

              // then set to a small value (don't allow <= 0):

        if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {

            lowerBoundVal = SMALL_LOG_VALUE;

        }



        //get upper bound value

        final double upperBoundVal = range.getUpperBound();



        //get log10 version of lower bound and round to integer:

        int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));

        //get log10 version of upper bound and round to integer:

        final int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));



        if (iBegCount == iEndCount && iBegCount > 0 && Math.pow(10, iBegCount) > lowerBoundVal) {

              //only 1 power of 10 value, it's > 0 and its resulting

              // tick value will be larger than lower bound of data

          --iBegCount;       //decrement to generate more ticks

        }



        double currentTickValue;

        String tickLabel;

        boolean zeroTickFlag = false;

        for (int i = iBegCount; i <= iEndCount; i++) {

            //for each power of 10 value; create ten ticks

            for (int j = 0; j < 10; ++j) {

                //for each tick to be displayed

                if (this.smallLogFlag) {

                    //small log values in use; create numeric value for tick

                    currentTickValue = Math.pow(10, i) + (Math.pow(10, i) * j);

                    if (this.expTickLabelsFlag 

                        || (i < 0 && currentTickValue > 0.0 && currentTickValue < 1.0)) {

                        //showing "1e#"-style ticks or negative exponent

                        // generating tick value between 0 & 1; show fewer

                        if (j == 0 || (i > -4 && j < 2)

                                   || currentTickValue >= upperBoundVal) {

                          //first tick of series, or not too small a value and

                          // one of first 3 ticks, or last tick to be displayed

                               //set exact number of fractional digits to be shown

                               // (no effect if showing "1e#"-style ticks):

                            this.numberFormatterObj.setMaximumFractionDigits(-i);

                               //create tick label (force use of fmt obj):

                            tickLabel = makeTickLabel(currentTickValue, true);

                        }

                        else {    //no tick label to be shown

                            tickLabel = "";

                        }

                    }

                    else {     //tick value not between 0 & 1

                               //show tick label if it's the first or last in

                               // the set, or if it's 1-5; beyond that show

                               // fewer as the values get larger:

                        tickLabel = (j < 1 || (i < 1 && j < 5) || (j < 4 - i) 

                                           || currentTickValue >= upperBoundVal) 

                                           ? makeTickLabel(currentTickValue) : "";

                    }

                }

                else { //not small log values in use; allow for values <= 0

                    if (zeroTickFlag) {   //if did zero tick last iter then

                        --j;              //decrement to do 1.0 tick now

                    }     //calculate power-of-ten value for tick:

                    currentTickValue = (i >= 0)

                        ? Math.pow(10, i) + (Math.pow(10, i) * j)

                        : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j));

                    if (!zeroTickFlag) {    //did not do zero tick last iteration

                        if (Math.abs(currentTickValue - 1.0) < 0.0001

                            && lowerBoundVal <= 0.0 && upperBoundVal >= 0.0) {

                            //tick value is 1.0 and 0.0 is within data range

                            currentTickValue = 0.0;     //set tick value to zero

                            zeroTickFlag = true;        //indicate zero tick

                        }

                    }

                    else {     //did zero tick last iteration

                        zeroTickFlag = false;         //clear flag

                    }               //create tick label string:

                               //show tick label if "1e#"-style and it's one

                               // of the first two, if it's the first or last

                               // in the set, or if it's 1-5; beyond that

                               // show fewer as the values get larger:

                    tickLabel = ((this.expTickLabelsFlag && j < 2) 

                                || j < 1 

                                || (i < 1 && j < 5) || (j < 4 - i) 

                                || currentTickValue >= upperBoundVal) 

                                   ? makeTickLabel(currentTickValue) : "";

                }



                if (currentTickValue > upperBoundVal) {

                    return ticks;   //if past highest data value then exit method

                }



                if (currentTickValue >= lowerBoundVal - SMALL_LOG_VALUE) {

                    //tick value not below lowest data value

                    TextAnchor anchor = null;

                    TextAnchor rotationAnchor = null;

                    double angle = 0.0;

                    if (isVerticalTickLabels()) {

                        anchor = TextAnchor.CENTER_RIGHT;

                        rotationAnchor = TextAnchor.CENTER_RIGHT;

                        if (edge == RectangleEdge.TOP) {

                            angle = Math.PI / 2.0;

                        }

                        else {

                            angle = -Math.PI / 2.0;

                        }

                    }

                    else {

                        if (edge == RectangleEdge.TOP) {

                            anchor = TextAnchor.BOTTOM_CENTER;

                            rotationAnchor = TextAnchor.BOTTOM_CENTER;

                        }

                        else {

                            anchor = TextAnchor.TOP_CENTER;

                            rotationAnchor = TextAnchor.TOP_CENTER;

                        }

                    }



                    Tick tick = new NumberTick(

                        new Double(currentTickValue), tickLabel, anchor, rotationAnchor, angle

                    );

                    ticks.add(tick);

                }

            }

        }

        return ticks;



    }



    /**

     * Calculates the positions of the tick labels for the axis, storing the

     * results in the tick label list (ready for drawing).

     *

     * @param g2  the graphics device.

     * @param cursor  the cursor position.

     * @param plotArea  the area in which the plot and the axes should be drawn.

     * @param dataArea  the area in which the plot should be drawn.

     * @param edge  the axis location.

     * 

     * @return A list of ticks.

     */

    public List refreshTicksVertical(Graphics2D g2, double cursor,

                                     Rectangle2D plotArea, Rectangle2D dataArea,

                                     RectangleEdge edge) {



        List ticks = new java.util.ArrayList();

        //FontMetrics fm = g2.getFontMetrics(getTickLabelFont());



        //get lower bound value:

        double lowerBoundVal = getRange().getLowerBound();

        //if small log values and lower bound value too small

        // then set to a small value (don't allow <= 0):

        if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {

            lowerBoundVal = SMALL_LOG_VALUE;

        }

        //get upper bound value

        final double upperBoundVal = getRange().getUpperBound();



        //get log10 version of lower bound and round to integer:

        int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));

        //get log10 version of upper bound and round to integer:

        final int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));



        if (iBegCount == iEndCount && iBegCount > 0 && Math.pow(10, iBegCount) > lowerBoundVal) {

              //only 1 power of 10 value, it's > 0 and its resulting

              // tick value will be larger than lower bound of data

          --iBegCount;       //decrement to generate more ticks

        }



        double tickVal;

        String tickLabel;

        boolean zeroTickFlag = false;

        for (int i = iBegCount; i <= iEndCount; i++) {

            //for each tick with a label to be displayed

            int jEndCount = 10;

            if (i == iEndCount) {

                jEndCount = 1;

            }



            for (int j = 0; j < jEndCount; j++) {

                //for each tick to be displayed

                if (this.smallLogFlag) {

                    //small log values in use

                    tickVal = Math.pow(10, i) + (Math.pow(10, i) * j);

                    if (j == 0) {

                        //first tick of group; create label text

                        if (this.log10TickLabelsFlag) {

                            //if flag then

                            tickLabel = "10^" + i;      //create "log10"-type label

                        }

                        else {    //not "log10"-type label

                            if (this.expTickLabelsFlag) {

                                //if flag then

                                tickLabel = "1e" + i;   //create "1e#"-type label

                            }

                            else {    //not "1e#"-type label

                                if (i >= 0) {   //if positive exponent then make integer

                                    NumberFormat format = getNumberFormatOverride();

                                    if (format != null) {

                                        tickLabel = format.format(tickVal);   

                                    }

                                    else {

                                        tickLabel =  Long.toString((long) Math.rint(tickVal));   

                                    }

                                }

                                else {

                                    //negative exponent; create fractional value

                                    //set exact number of fractional digits to be shown:

                                    this.numberFormatterObj.setMaximumFractionDigits(-i);

                                    //create tick label:

                                    tickLabel = this.numberFormatterObj.format(tickVal);

                                }

                            }

                        }

                    }

                    else {   //not first tick to be displayed

                        tickLabel = "";     //no tick label

                    }

                }

                else { //not small log values in use; allow for values <= 0

                    if (zeroTickFlag) {      //if did zero tick last iter then

                        --j;

                    }               //decrement to do 1.0 tick now

                    tickVal = (i >= 0) ? Math.pow(10, i) + (Math.pow(10, i) * j)

                                       : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j));

                    if (j == 0) {  //first tick of group

                        if (!zeroTickFlag) {     //did not do zero tick last iteration

                            if (i > iBegCount && i < iEndCount

                                              && Math.abs(tickVal - 1.0) < 0.0001) {

                                //not first or last tick on graph and value is 1.0

                                tickVal = 0.0;        //change value to 0.0

                                zeroTickFlag = true;  //indicate zero tick

                                tickLabel = "0";      //create label for tick

                            }

                            else {

                                //first or last tick on graph or value is 1.0

                                //create label for tick:

                                if (this.log10TickLabelsFlag) {

                                       //create "log10"-type label

                                    tickLabel = (((i < 0) ? "-" : "") + "10^" + Math.abs(i));

                                }

                                else {

                                    if (this.expTickLabelsFlag) {

                                           //create "1e#"-type label

                                        tickLabel = (((i < 0) ? "-" : "") + "1e" + Math.abs(i));

                                    }

                                    else {

                                        NumberFormat format = getNumberFormatOverride();

                                        if (format != null) {

                                            tickLabel = format.format(tickVal);   

                                        }

                                        else {

                                            tickLabel =  Long.toString((long) Math.rint(tickVal));

                                        }

                                    }

                                }

                            }

                        }

                        else {     // did zero tick last iteration

                            tickLabel = "";         //no label

                            zeroTickFlag = false;   //clear flag

                        }

                    }

                    else {       // not first tick of group

                        tickLabel = "";           //no label

                        zeroTickFlag = false;     //make sure flag cleared

                    }

                }



                if (tickVal > upperBoundVal) {

                    return ticks;     //if past highest data value then exit method

                }



                if (tickVal >= lowerBoundVal - SMALL_LOG_VALUE) {

                    //tick value not below lowest data value

                    TextAnchor anchor = null;

                    TextAnchor rotationAnchor = null;

                    double angle = 0.0;

                    if (isVerticalTickLabels()) {

                        if (edge == RectangleEdge.LEFT) {

                            anchor = TextAnchor.BOTTOM_CENTER;

                            rotationAnchor = TextAnchor.BOTTOM_CENTER;

                            angle = -Math.PI / 2.0;

                        }

                        else {

                            anchor = TextAnchor.BOTTOM_CENTER;

                            rotationAnchor = TextAnchor.BOTTOM_CENTER;

                            angle = Math.PI / 2.0;

                        }

                    }

                    else {

                        if (edge == RectangleEdge.LEFT) {

                            anchor = TextAnchor.CENTER_RIGHT;

                            rotationAnchor = TextAnchor.CENTER_RIGHT;

                        }

                        else {

                            anchor = TextAnchor.CENTER_LEFT;

                            rotationAnchor = TextAnchor.CENTER_LEFT;

                        }

                    }

                    //create tick object and add to list:

                    ticks.add(

                        new NumberTick(

                            new Double(tickVal), tickLabel, anchor, rotationAnchor, angle

                        )

                    );



                }

            }

        }

        return ticks;

    }



    /**

     * Converts the given value to a tick label string.

     *

     * @param val the value to convert.

     * @param forceFmtFlag true to force the number-formatter object

     * to be used.

     *

     * @return The tick label string.

     */

    protected String makeTickLabel(double val, boolean forceFmtFlag) {

        if (this.expTickLabelsFlag || forceFmtFlag) {

            //using exponents or force-formatter flag is set

            // (convert 'E' to lower-case 'e'):

            return this.numberFormatterObj.format(val).toLowerCase();

        }

        return getTickUnit().valueToString(val);

    }



    /**

     * Converts the given value to a tick label string.

     * @param val the value to convert.

     *

     * @return The tick label string.

     */

    protected String makeTickLabel(double val) {

        return makeTickLabel(val, false);

    }

    

    //// DEPRECATED CODE //////////////////////////////////////////////////////////////////////////

    

    /**

     * Converts a data value to a coordinate in Java2D space, assuming that

     * the axis runs along one edge of the specified plotArea.

     * Note that it is possible for the coordinate to fall outside the

     * plotArea.

     *

     * @param value  the data value.

     * @param plotArea  the area for plotting the data.

     * @param edge  the axis location.

     *

     * @return the Java2D coordinate.

     * 

     * @deprecated Use valueToJava2D().

     */

    public double translateValueToJava2D(double value, Rectangle2D plotArea,

                                         RectangleEdge edge) {



        Range range = getRange();

        double axisMin = switchedLog10(range.getLowerBound());

        double axisMax = switchedLog10(range.getUpperBound());



        double min = 0.0;

        double max = 0.0;

        if (RectangleEdge.isTopOrBottom(edge)) {

            min = plotArea.getMinX();

            max = plotArea.getMaxX();

        }

        else if (RectangleEdge.isLeftOrRight(edge)) {

            min = plotArea.getMaxY();

            max = plotArea.getMinY();

        }



        value = switchedLog10(value);



        if (isInverted()) {

            return max - (((value - axisMin) / (axisMax - axisMin)) * (max - min));

        }

        else {

            return min + (((value - axisMin) / (axisMax - axisMin)) * (max - min));

        }



    }



    /**

     * Converts a coordinate in Java2D space to the corresponding data

     * value, assuming that the axis runs along one edge of the specified plotArea.

     *

     * @param java2DValue  the coordinate in Java2D space.

     * @param plotArea  the area in which the data is plotted.

     * @param edge  the axis location.

     *

     * @return the data value.

     * 

     * @deprecated Use java2DToValue().

     */

    public double translateJava2DToValue(double java2DValue, Rectangle2D plotArea,

                                         RectangleEdge edge) {



        Range range = getRange();

        double axisMin = switchedLog10(range.getLowerBound());

        double axisMax = switchedLog10(range.getUpperBound());



        double plotMin = 0.0;

        double plotMax = 0.0;

        if (RectangleEdge.isTopOrBottom(edge)) {

            plotMin = plotArea.getX();

            plotMax = plotArea.getMaxX();

        }

        else if (RectangleEdge.isLeftOrRight(edge)) {

            plotMin = plotArea.getMaxY();

            plotMax = plotArea.getMinY();

        }



        if (isInverted()) {

            return Math.pow(

                10, axisMax - ((java2DValue - plotMin) / (plotMax - plotMin)) * (axisMax - axisMin)

            );

        }

        else {

            return Math.pow(

                10, axisMin + ((java2DValue - plotMin) / (plotMax - plotMin)) * (axisMax - axisMin)

            );

        }

    }

    

}

