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