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