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