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