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