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