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