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