001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2020, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * --------------------
028 * LogarithmicAxis.java
029 * --------------------
030 * (C) Copyright 2000-2020, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  Michael Duffy / Eric Thomas;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *                   David M. O'Donnell;
035 *                   Scott Sams;
036 *                   Sergei Ivanov;
037 *
038 */
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     * @since 1.0.5
283     * @see #switchedLog10(double)
284     */
285    public double switchedPow10(double val) {
286        return this.smallLogFlag ? Math.pow(10.0, val) : adjustedPow10(val);
287    }
288
289    /**
290     * Returns an adjusted log10 value for graphing purposes.  The first
291     * adjustment is that negative values are changed to positive during
292     * the calculations, and then the answer is negated at the end.  The
293     * second is that, for values less than 10, an increasingly large
294     * (0 to 1) scaling factor is added such that at 0 the value is
295     * adjusted to 1, resulting in a returned result of 0.
296     *
297     * @param val  value for which log10 should be calculated.
298     *
299     * @return An adjusted log<sub>10</sub>(val).
300     *
301     * @see #adjustedPow10(double)
302     */
303    public double adjustedLog10(double val) {
304        boolean negFlag = (val < 0.0);
305        if (negFlag) {
306            val = -val;          // if negative then set flag and make positive
307        }
308        if (val < 10.0) {                // if < 10 then
309            val += (10.0 - val) / 10.0;  //increase so 0 translates to 0
310        }
311        //return value; negate if original value was negative:
312        double res = Math.log(val) / LOG10_VALUE;
313        return negFlag ? (-res) : res;
314    }
315
316    /**
317     * Returns an adjusted power of 10 value for graphing purposes.  The first
318     * adjustment is that negative values are changed to positive during
319     * the calculations, and then the answer is negated at the end.  The
320     * second is that, for values less than 1, a progressive logarithmic
321     * offset is subtracted such that at 0 the returned result is also 0.
322     *
323     * @param val  value for which power of 10 should be calculated.
324     *
325     * @return An adjusted 10<sup>val</sup>.
326     *
327     * @since 1.0.5
328     * @see #adjustedLog10(double)
329     */
330    public double adjustedPow10(double val) {
331        boolean negFlag = (val < 0.0);
332        if (negFlag) {
333            val = -val; // if negative then set flag and make positive
334        }
335        double res;
336        if (val < 1.0) {
337            res = (Math.pow(10, val + 1.0) - 10.0) / 9.0; //invert adjustLog10
338        }
339        else {
340            res = Math.pow(10, val);
341        }
342        return negFlag ? (-res) : res;
343    }
344
345    /**
346     * Returns the largest (closest to positive infinity) double value that is
347     * not greater than the argument, is equal to a mathematical integer and
348     * satisfying the condition that log base 10 of the value is an integer
349     * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).
350     *
351     * @param lower a double value below which a floor will be calcualted.
352     *
353     * @return 10<sup>N</sup> with N .. { 1 ... }
354     */
355    protected double computeLogFloor(double lower) {
356
357        double logFloor;
358        if (this.allowNegativesFlag) {
359            //negative values are allowed
360            if (lower > 10.0) {   //parameter value is > 10
361                // The Math.log() function is based on e not 10.
362                logFloor = Math.log(lower) / LOG10_VALUE;
363                logFloor = Math.floor(logFloor);
364                logFloor = Math.pow(10, logFloor);
365            }
366            else if (lower < -10.0) {   //parameter value is < -10
367                //calculate log using positive value:
368                logFloor = Math.log(-lower) / LOG10_VALUE;
369                //calculate floor using negative value:
370                logFloor = Math.floor(-logFloor);
371                //calculate power using positive value; then negate
372                logFloor = -Math.pow(10, -logFloor);
373            }
374            else {
375                //parameter value is -10 > val < 10
376                logFloor = Math.floor(lower);   //use as-is
377            }
378        }
379        else {
380            //negative values not allowed
381            if (lower > 0.0) {   //parameter value is > 0
382                // The Math.log() function is based on e not 10.
383                logFloor = Math.log(lower) / LOG10_VALUE;
384                logFloor = Math.floor(logFloor);
385                logFloor = Math.pow(10, logFloor);
386            }
387            else {
388                //parameter value is <= 0
389                logFloor = Math.floor(lower);   //use as-is
390            }
391        }
392        return logFloor;
393    }
394
395    /**
396     * Returns the smallest (closest to negative infinity) double value that is
397     * not less than the argument, is equal to a mathematical integer and
398     * satisfying the condition that log base 10 of the value is an integer
399     * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).
400     *
401     * @param upper a double value above which a ceiling will be calcualted.
402     *
403     * @return 10<sup>N</sup> with N .. { 1 ... }
404     */
405    protected double computeLogCeil(double upper) {
406
407        double logCeil;
408        if (this.allowNegativesFlag) {
409            //negative values are allowed
410            if (upper > 10.0) {
411                //parameter value is > 10
412                // The Math.log() function is based on e not 10.
413                logCeil = Math.log(upper) / LOG10_VALUE;
414                logCeil = Math.ceil(logCeil);
415                logCeil = Math.pow(10, logCeil);
416            }
417            else if (upper < -10.0) {
418                //parameter value is < -10
419                //calculate log using positive value:
420                logCeil = Math.log(-upper) / LOG10_VALUE;
421                //calculate ceil using negative value:
422                logCeil = Math.ceil(-logCeil);
423                //calculate power using positive value; then negate
424                logCeil = -Math.pow(10, -logCeil);
425            }
426            else {
427               //parameter value is -10 > val < 10
428               logCeil = Math.ceil(upper);     //use as-is
429            }
430        }
431        else {
432            //negative values not allowed
433            if (upper > 0.0) {
434                //parameter value is > 0
435                // The Math.log() function is based on e not 10.
436                logCeil = Math.log(upper) / LOG10_VALUE;
437                logCeil = Math.ceil(logCeil);
438                logCeil = Math.pow(10, logCeil);
439            }
440            else {
441                //parameter value is <= 0
442                logCeil = Math.ceil(upper);     //use as-is
443            }
444        }
445        return logCeil;
446    }
447
448    /**
449     * Rescales the axis to ensure that all data is visible.
450     */
451    @Override
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 = getDefaultAutoRange();
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("Values less than or equal to "
476                            + "zero not allowed with logarithmic axis");
477                }
478            }
479
480            //apply lower margin by decreasing lower bound:
481            final double lowerMargin;
482            if (lower > 0.0 && (lowerMargin = getLowerMargin()) > 0.0) {
483                   //lower bound and margin OK; get log10 of lower bound
484                final double logLower = (Math.log(lower) / LOG10_VALUE);
485                double logAbs;      //get absolute value of log10 value
486                if ((logAbs = Math.abs(logLower)) < 1.0) {
487                    logAbs = 1.0;     //if less than 1.0 then make it 1.0
488                }              //subtract out margin and get exponential value:
489                lower = Math.pow(10, (logLower - (logAbs * lowerMargin)));
490            }
491
492            //if flag then change to log version of lowest value
493            // to make range begin at a 10^n value:
494            if (this.autoRangeNextLogFlag) {
495                lower = computeLogFloor(lower);
496            }
497
498            if (!this.allowNegativesFlag && lower >= 0.0
499                    && lower < SMALL_LOG_VALUE) {
500                //negatives not allowed and lower range bound is zero
501                lower = r.getLowerBound();    //use data range bound instead
502            }
503
504            double upper = r.getUpperBound();
505
506             //apply upper margin by increasing upper bound:
507            final double upperMargin;
508            if (upper > 0.0 && (upperMargin = getUpperMargin()) > 0.0) {
509                   //upper bound and margin OK; get log10 of upper bound
510                final double logUpper = (Math.log(upper) / LOG10_VALUE);
511                double logAbs;      //get absolute value of log10 value
512                if ((logAbs = Math.abs(logUpper)) < 1.0) {
513                    logAbs = 1.0;     //if less than 1.0 then make it 1.0
514                }              //add in margin and get exponential value:
515                upper = Math.pow(10, (logUpper + (logAbs * upperMargin)));
516            }
517
518            if (!this.allowNegativesFlag && upper < 1.0 && upper > 0.0
519                    && lower > 0.0) {
520                //negatives not allowed and upper bound between 0 & 1
521                //round up to nearest significant digit for bound:
522                //get negative exponent:
523                double expVal = Math.log(upper) / LOG10_VALUE;
524                expVal = Math.ceil(-expVal + 0.001); //get positive exponent
525                expVal = Math.pow(10, expVal);      //create multiplier value
526                //multiply, round up, and divide for bound value:
527                upper = (expVal > 0.0) ? Math.ceil(upper * expVal) / expVal
528                    : Math.ceil(upper);
529            }
530            else {
531                //negatives allowed or upper bound not between 0 & 1
532                //if flag then change to log version of highest value to
533                // make range begin at a 10^n value; else use nearest int
534                upper = (this.autoRangeNextLogFlag) ? computeLogCeil(upper)
535                    : Math.ceil(upper);
536            }
537            // ensure the autorange is at least <minRange> in size...
538            double minRange = getAutoRangeMinimumSize();
539            if (upper - lower < minRange) {
540                upper = (upper + lower + minRange) / 2;
541                lower = (upper + lower - minRange) / 2;
542                //if autorange still below minimum then adjust by 1%
543                // (can be needed when minRange is very small):
544                if (upper - lower < minRange) {
545                    double absUpper = Math.abs(upper);
546                    //need to account for case where upper==0.0
547                    double adjVal = (absUpper > SMALL_LOG_VALUE) ? absUpper
548                        / 100.0 : 0.01;
549                    upper = (upper + lower + adjVal) / 2;
550                    lower = (upper + lower - adjVal) / 2;
551                }
552            }
553
554            setRange(new Range(lower, upper), false, false);
555            setupSmallLogFlag();       //setup flag based on bounds values
556        }
557    }
558
559    /**
560     * Converts a data value to a coordinate in Java2D space, assuming that
561     * the axis runs along one edge of the specified plotArea.
562     * Note that it is possible for the coordinate to fall outside the
563     * plotArea.
564     *
565     * @param value  the data value.
566     * @param plotArea  the area for plotting the data.
567     * @param edge  the axis location.
568     *
569     * @return The Java2D coordinate.
570     */
571    @Override
572    public double valueToJava2D(double value, Rectangle2D plotArea,
573                                RectangleEdge edge) {
574
575        Range range = getRange();
576        double axisMin = switchedLog10(range.getLowerBound());
577        double axisMax = switchedLog10(range.getUpperBound());
578
579        double min = 0.0;
580        double max = 0.0;
581        if (RectangleEdge.isTopOrBottom(edge)) {
582            min = plotArea.getMinX();
583            max = plotArea.getMaxX();
584        }
585        else if (RectangleEdge.isLeftOrRight(edge)) {
586            min = plotArea.getMaxY();
587            max = plotArea.getMinY();
588        }
589
590        value = switchedLog10(value);
591
592        if (isInverted()) {
593            return max - (((value - axisMin) / (axisMax - axisMin))
594                    * (max - min));
595        }
596        else {
597            return min + (((value - axisMin) / (axisMax - axisMin))
598                    * (max - min));
599        }
600
601    }
602
603    /**
604     * Converts a coordinate in Java2D space to the corresponding data
605     * value, assuming that the axis runs along one edge of the specified
606     * plotArea.
607     *
608     * @param java2DValue  the coordinate in Java2D space.
609     * @param plotArea  the area in which the data is plotted.
610     * @param edge  the axis location.
611     *
612     * @return The data value.
613     */
614    @Override
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 switchedPow10(axisMax - ((java2DValue - plotMin)
635                    / (plotMax - plotMin)) * (axisMax - axisMin));
636        }
637        else {
638            return switchedPow10(axisMin + ((java2DValue - plotMin)
639                    / (plotMax - plotMin)) * (axisMax - axisMin));
640        }
641    }
642
643    /**
644     * Zooms in on the current range.
645     *
646     * @param lowerPercent  the new lower bound.
647     * @param upperPercent  the new upper bound.
648     */
649    @Override
650    public void zoomRange(double lowerPercent, double upperPercent) {
651        double startLog = switchedLog10(getRange().getLowerBound());
652        double lengthLog = switchedLog10(getRange().getUpperBound()) - startLog;
653        Range adjusted;
654
655        if (isInverted()) {
656            adjusted = new Range(
657                    switchedPow10(startLog + (lengthLog * (1 - upperPercent))),
658                    switchedPow10(startLog + (lengthLog * (1 - lowerPercent))));
659        }
660        else {
661            adjusted = new Range(
662                    switchedPow10(startLog + (lengthLog * lowerPercent)),
663                    switchedPow10(startLog + (lengthLog * upperPercent)));
664        }
665
666        setRange(adjusted);
667    }
668
669    /**
670     * Calculates the positions of the tick labels for the axis, storing the
671     * results in the tick label list (ready for drawing).
672     *
673     * @param g2  the graphics device.
674     * @param dataArea  the area in which the plot should be drawn.
675     * @param edge  the location of the axis.
676     *
677     * @return A list of ticks.
678     */
679    @Override
680    protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea,
681            RectangleEdge edge) {
682
683        List ticks = new java.util.ArrayList();
684        Range range = getRange();
685
686        //get lower bound value:
687        double lowerBoundVal = range.getLowerBound();
688              //if small log values and lower bound value too small
689              // then set to a small value (don't allow <= 0):
690        if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {
691            lowerBoundVal = SMALL_LOG_VALUE;
692        }
693
694        //get upper bound value
695        double upperBoundVal = range.getUpperBound();
696
697        //get log10 version of lower bound and round to integer:
698        int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));
699        //get log10 version of upper bound and round to integer:
700        int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));
701
702        if (iBegCount == iEndCount && iBegCount > 0
703                && Math.pow(10, iBegCount) > lowerBoundVal) {
704              //only 1 power of 10 value, it's > 0 and its resulting
705              // tick value will be larger than lower bound of data
706            --iBegCount;       //decrement to generate more ticks
707        }
708
709        double currentTickValue;
710        String tickLabel;
711        boolean zeroTickFlag = false;
712        for (int i = iBegCount; i <= iEndCount; i++) {
713            //for each power of 10 value; create ten ticks
714            for (int j = 0; j < 10; ++j) {
715                //for each tick to be displayed
716                if (this.smallLogFlag) {
717                    //small log values in use; create numeric value for tick
718                    currentTickValue = Math.pow(10, i) + (Math.pow(10, i) * j);
719                    if (this.expTickLabelsFlag
720                        || (i < 0 && currentTickValue > 0.0
721                        && currentTickValue < 1.0)) {
722                        //showing "1e#"-style ticks or negative exponent
723                        // generating tick value between 0 & 1; show fewer
724                        if (j == 0 || (i > -4 && j < 2)
725                                   || currentTickValue >= upperBoundVal) {
726                          //first tick of series, or not too small a value and
727                          // one of first 3 ticks, or last tick to be displayed
728                            // set exact number of fractional digits to be shown
729                            // (no effect if showing "1e#"-style ticks):
730                            this.numberFormatterObj
731                                .setMaximumFractionDigits(-i);
732                               //create tick label (force use of fmt obj):
733                            tickLabel = makeTickLabel(currentTickValue, true);
734                        }
735                        else {    //no tick label to be shown
736                            tickLabel = "";
737                        }
738                    }
739                    else {     //tick value not between 0 & 1
740                               //show tick label if it's the first or last in
741                               // the set, or if it's 1-5; beyond that show
742                               // fewer as the values get larger:
743                        tickLabel = (j < 1 || (i < 1 && j < 5) || (j < 4 - i)
744                                         || currentTickValue >= upperBoundVal)
745                                         ? makeTickLabel(currentTickValue) : "";
746                    }
747                }
748                else { //not small log values in use; allow for values <= 0
749                    if (zeroTickFlag) {   //if did zero tick last iter then
750                        --j;              //decrement to do 1.0 tick now
751                    }     //calculate power-of-ten value for tick:
752                    currentTickValue = (i >= 0)
753                        ? Math.pow(10, i) + (Math.pow(10, i) * j)
754                        : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j));
755                    if (!zeroTickFlag) {  // did not do zero tick last iteration
756                        if (Math.abs(currentTickValue - 1.0) < 0.0001
757                            && lowerBoundVal <= 0.0 && upperBoundVal >= 0.0) {
758                            //tick value is 1.0 and 0.0 is within data range
759                            currentTickValue = 0.0;     //set tick value to zero
760                            zeroTickFlag = true;        //indicate zero tick
761                        }
762                    }
763                    else {     //did zero tick last iteration
764                        zeroTickFlag = false;         //clear flag
765                    }               //create tick label string:
766                               //show tick label if "1e#"-style and it's one
767                               // of the first two, if it's the first or last
768                               // in the set, or if it's 1-5; beyond that
769                               // show fewer as the values get larger:
770                    tickLabel = ((this.expTickLabelsFlag && j < 2)
771                                || j < 1
772                                || (i < 1 && j < 5) || (j < 4 - i)
773                                || currentTickValue >= upperBoundVal)
774                                   ? makeTickLabel(currentTickValue) : "";
775                }
776
777                if (currentTickValue > upperBoundVal) {
778                    return ticks;   // if past highest data value then exit
779                                    // method
780                }
781
782                if (currentTickValue >= lowerBoundVal - SMALL_LOG_VALUE) {
783                    //tick value not below lowest data value
784                    TextAnchor anchor;
785                    TextAnchor rotationAnchor;
786                    double angle = 0.0;
787                    if (isVerticalTickLabels()) {
788                        anchor = TextAnchor.CENTER_RIGHT;
789                        rotationAnchor = TextAnchor.CENTER_RIGHT;
790                        if (edge == RectangleEdge.TOP) {
791                            angle = Math.PI / 2.0;
792                        }
793                        else {
794                            angle = -Math.PI / 2.0;
795                        }
796                    }
797                    else {
798                        if (edge == RectangleEdge.TOP) {
799                            anchor = TextAnchor.BOTTOM_CENTER;
800                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
801                        }
802                        else {
803                            anchor = TextAnchor.TOP_CENTER;
804                            rotationAnchor = TextAnchor.TOP_CENTER;
805                        }
806                    }
807
808                    Tick tick = new NumberTick(currentTickValue,
809                            tickLabel, anchor, rotationAnchor, angle);
810                    ticks.add(tick);
811                }
812            }
813        }
814        return ticks;
815
816    }
817
818    /**
819     * Calculates the positions of the tick labels for the axis, storing the
820     * results in the tick label list (ready for drawing).
821     *
822     * @param g2  the graphics device.
823     * @param dataArea  the area in which the plot should be drawn.
824     * @param edge  the location of the axis.
825     *
826     * @return A list of ticks.
827     */
828    @Override
829    protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea,
830            RectangleEdge edge) {
831
832        List ticks = new java.util.ArrayList();
833
834        //get lower bound value:
835        double lowerBoundVal = getRange().getLowerBound();
836        //if small log values and lower bound value too small
837        // then set to a small value (don't allow <= 0):
838        if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {
839            lowerBoundVal = SMALL_LOG_VALUE;
840        }
841        //get upper bound value
842        double upperBoundVal = getRange().getUpperBound();
843
844        //get log10 version of lower bound and round to integer:
845        int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));
846        //get log10 version of upper bound and round to integer:
847        int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));
848
849        if (iBegCount == iEndCount && iBegCount > 0
850                && Math.pow(10, iBegCount) > lowerBoundVal) {
851              //only 1 power of 10 value, it's > 0 and its resulting
852              // tick value will be larger than lower bound of data
853            --iBegCount;       //decrement to generate more ticks
854        }
855
856        double tickVal;
857        String tickLabel;
858        boolean zeroTickFlag = false;
859        for (int i = iBegCount; i <= iEndCount; i++) {
860            //for each tick with a label to be displayed
861            int jEndCount = 10;
862            if (i == iEndCount) {
863                jEndCount = 1;
864            }
865
866            for (int j = 0; j < jEndCount; j++) {
867                //for each tick to be displayed
868                if (this.smallLogFlag) {
869                    //small log values in use
870                    tickVal = Math.pow(10, i) + (Math.pow(10, i) * j);
871                    if (j == 0) {
872                        //first tick of group; create label text
873                        if (this.log10TickLabelsFlag) {
874                            //if flag then
875                            tickLabel = "10^" + i;   //create "log10"-type label
876                        }
877                        else {    //not "log10"-type label
878                            if (this.expTickLabelsFlag) {
879                                //if flag then
880                                tickLabel = "1e" + i;  //create "1e#"-type label
881                            }
882                            else {    //not "1e#"-type label
883                                if (i >= 0) {   // if positive exponent then
884                                                // make integer
885                                    NumberFormat format
886                                        = getNumberFormatOverride();
887                                    if (format != null) {
888                                        tickLabel = format.format(tickVal);
889                                    }
890                                    else {
891                                        tickLabel = Long.toString((long)
892                                                Math.rint(tickVal));
893                                    }
894                                }
895                                else {
896                                    //negative exponent; create fractional value
897                                    //set exact number of fractional digits to
898                                    // be shown:
899                                    this.numberFormatterObj
900                                        .setMaximumFractionDigits(-i);
901                                    //create tick label:
902                                    tickLabel = this.numberFormatterObj.format(
903                                            tickVal);
904                                }
905                            }
906                        }
907                    }
908                    else {   //not first tick to be displayed
909                        tickLabel = "";     //no tick label
910                    }
911                }
912                else { //not small log values in use; allow for values <= 0
913                    if (zeroTickFlag) {      //if did zero tick last iter then
914                        --j;
915                    }               //decrement to do 1.0 tick now
916                    tickVal = (i >= 0) ? Math.pow(10, i) + (Math.pow(10, i) * j)
917                             : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j));
918                    if (j == 0) {  //first tick of group
919                        if (!zeroTickFlag) {     // did not do zero tick last
920                                                 // iteration
921                            if (i > iBegCount && i < iEndCount
922                                    && Math.abs(tickVal - 1.0) < 0.0001) {
923                                // not first or last tick on graph and value
924                                // is 1.0
925                                tickVal = 0.0;        //change value to 0.0
926                                zeroTickFlag = true;  //indicate zero tick
927                                tickLabel = "0";      //create label for tick
928                            }
929                            else {
930                                //first or last tick on graph or value is 1.0
931                                //create label for tick:
932                                if (this.log10TickLabelsFlag) {
933                                       //create "log10"-type label
934                                    tickLabel = (((i < 0) ? "-" : "")
935                                            + "10^" + Math.abs(i));
936                                }
937                                else {
938                                    if (this.expTickLabelsFlag) {
939                                           //create "1e#"-type label
940                                        tickLabel = (((i < 0) ? "-" : "")
941                                                + "1e" + Math.abs(i));
942                                    }
943                                    else {
944                                        NumberFormat format
945                                            = getNumberFormatOverride();
946                                        if (format != null) {
947                                            tickLabel = format.format(tickVal);
948                                        }
949                                        else {
950                                            tickLabel =  Long.toString(
951                                                    (long) Math.rint(tickVal));
952                                        }
953                                    }
954                                }
955                            }
956                        }
957                        else {     // did zero tick last iteration
958                            tickLabel = "";         //no label
959                            zeroTickFlag = false;   //clear flag
960                        }
961                    }
962                    else {       // not first tick of group
963                        tickLabel = "";           //no label
964                        zeroTickFlag = false;     //make sure flag cleared
965                    }
966                }
967
968                if (tickVal > upperBoundVal) {
969                    return ticks;  //if past highest data value then exit method
970                }
971
972                if (tickVal >= lowerBoundVal - SMALL_LOG_VALUE) {
973                    //tick value not below lowest data value
974                    TextAnchor anchor;
975                    TextAnchor rotationAnchor;
976                    double angle = 0.0;
977                    if (isVerticalTickLabels()) {
978                        if (edge == RectangleEdge.LEFT) {
979                            anchor = TextAnchor.BOTTOM_CENTER;
980                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
981                            angle = -Math.PI / 2.0;
982                        }
983                        else {
984                            anchor = TextAnchor.BOTTOM_CENTER;
985                            rotationAnchor = TextAnchor.BOTTOM_CENTER;
986                            angle = Math.PI / 2.0;
987                        }
988                    }
989                    else {
990                        if (edge == RectangleEdge.LEFT) {
991                            anchor = TextAnchor.CENTER_RIGHT;
992                            rotationAnchor = TextAnchor.CENTER_RIGHT;
993                        }
994                        else {
995                            anchor = TextAnchor.CENTER_LEFT;
996                            rotationAnchor = TextAnchor.CENTER_LEFT;
997                        }
998                    }
999                    //create tick object and add to list:
1000                    ticks.add(new NumberTick(tickVal, tickLabel,
1001                            anchor, rotationAnchor, angle));
1002                }
1003            }
1004        }
1005        return ticks;
1006    }
1007
1008    /**
1009     * Converts the given value to a tick label string.
1010     *
1011     * @param val the value to convert.
1012     * @param forceFmtFlag true to force the number-formatter object
1013     * to be used.
1014     *
1015     * @return The tick label string.
1016     */
1017    protected String makeTickLabel(double val, boolean forceFmtFlag) {
1018        if (this.expTickLabelsFlag || forceFmtFlag) {
1019            //using exponents or force-formatter flag is set
1020            // (convert 'E' to lower-case 'e'):
1021            return this.numberFormatterObj.format(val).toLowerCase();
1022        }
1023        return getTickUnit().valueToString(val);
1024    }
1025
1026    /**
1027     * Converts the given value to a tick label string.
1028     * @param val the value to convert.
1029     *
1030     * @return The tick label string.
1031     */
1032    protected String makeTickLabel(double val) {
1033        return makeTickLabel(val, false);
1034    }
1035
1036}