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