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