Frames | No Frames |
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: }