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