001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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-2005, 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.1 2005/10/25 20:37:34 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 * 090 */ 091 092 package org.jfree.chart.axis; 093 094 import java.awt.Graphics2D; 095 import java.awt.geom.Rectangle2D; 096 import java.text.DecimalFormat; 097 import java.text.NumberFormat; 098 import java.util.List; 099 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 }