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