Source for org.jfree.chart.axis.LogarithmicAxis

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