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