001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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-present, by David Gilbert and Contributors. 031 * 032 * Original Author: Michael Duffy / Eric Thomas; 033 * Contributor(s): David Gilbert; 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 * @see #switchedLog10(double) 283 */ 284 public double switchedPow10(double val) { 285 return this.smallLogFlag ? Math.pow(10.0, val) : adjustedPow10(val); 286 } 287 288 /** 289 * Returns an adjusted log10 value for graphing purposes. The first 290 * adjustment is that negative values are changed to positive during 291 * the calculations, and then the answer is negated at the end. The 292 * second is that, for values less than 10, an increasingly large 293 * (0 to 1) scaling factor is added such that at 0 the value is 294 * adjusted to 1, resulting in a returned result of 0. 295 * 296 * @param val value for which log10 should be calculated. 297 * 298 * @return An adjusted log<sub>10</sub>(val). 299 * 300 * @see #adjustedPow10(double) 301 */ 302 public double adjustedLog10(double val) { 303 boolean negFlag = (val < 0.0); 304 if (negFlag) { 305 val = -val; // if negative then set flag and make positive 306 } 307 if (val < 10.0) { // if < 10 then 308 val += (10.0 - val) / 10.0; //increase so 0 translates to 0 309 } 310 //return value; negate if original value was negative: 311 double res = Math.log(val) / LOG10_VALUE; 312 return negFlag ? (-res) : res; 313 } 314 315 /** 316 * Returns an adjusted power of 10 value for graphing purposes. The first 317 * adjustment is that negative values are changed to positive during 318 * the calculations, and then the answer is negated at the end. The 319 * second is that, for values less than 1, a progressive logarithmic 320 * offset is subtracted such that at 0 the returned result is also 0. 321 * 322 * @param val value for which power of 10 should be calculated. 323 * 324 * @return An adjusted 10<sup>val</sup>. 325 * 326 * @see #adjustedLog10(double) 327 */ 328 public double adjustedPow10(double val) { 329 boolean negFlag = (val < 0.0); 330 if (negFlag) { 331 val = -val; // if negative then set flag and make positive 332 } 333 double res; 334 if (val < 1.0) { 335 res = (Math.pow(10, val + 1.0) - 10.0) / 9.0; //invert adjustLog10 336 } 337 else { 338 res = Math.pow(10, val); 339 } 340 return negFlag ? (-res) : res; 341 } 342 343 /** 344 * Returns the largest (closest to positive infinity) double value that is 345 * not greater than the argument, is equal to a mathematical integer and 346 * satisfying the condition that log base 10 of the value is an integer 347 * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.). 348 * 349 * @param lower a double value below which a floor will be calcualted. 350 * 351 * @return 10<sup>N</sup> with N .. { 1 ... } 352 */ 353 protected double computeLogFloor(double lower) { 354 355 double logFloor; 356 if (this.allowNegativesFlag) { 357 //negative values are allowed 358 if (lower > 10.0) { //parameter value is > 10 359 // The Math.log() function is based on e not 10. 360 logFloor = Math.log(lower) / LOG10_VALUE; 361 logFloor = Math.floor(logFloor); 362 logFloor = Math.pow(10, logFloor); 363 } 364 else if (lower < -10.0) { //parameter value is < -10 365 //calculate log using positive value: 366 logFloor = Math.log(-lower) / LOG10_VALUE; 367 //calculate floor using negative value: 368 logFloor = Math.floor(-logFloor); 369 //calculate power using positive value; then negate 370 logFloor = -Math.pow(10, -logFloor); 371 } 372 else { 373 //parameter value is -10 > val < 10 374 logFloor = Math.floor(lower); //use as-is 375 } 376 } 377 else { 378 //negative values not allowed 379 if (lower > 0.0) { //parameter value is > 0 380 // The Math.log() function is based on e not 10. 381 logFloor = Math.log(lower) / LOG10_VALUE; 382 logFloor = Math.floor(logFloor); 383 logFloor = Math.pow(10, logFloor); 384 } 385 else { 386 //parameter value is <= 0 387 logFloor = Math.floor(lower); //use as-is 388 } 389 } 390 return logFloor; 391 } 392 393 /** 394 * Returns the smallest (closest to negative infinity) double value that is 395 * not less than the argument, is equal to a mathematical integer and 396 * satisfying the condition that log base 10 of the value is an integer 397 * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.). 398 * 399 * @param upper a double value above which a ceiling will be calcualted. 400 * 401 * @return 10<sup>N</sup> with N .. { 1 ... } 402 */ 403 protected double computeLogCeil(double upper) { 404 405 double logCeil; 406 if (this.allowNegativesFlag) { 407 //negative values are allowed 408 if (upper > 10.0) { 409 //parameter value is > 10 410 // The Math.log() function is based on e not 10. 411 logCeil = Math.log(upper) / LOG10_VALUE; 412 logCeil = Math.ceil(logCeil); 413 logCeil = Math.pow(10, logCeil); 414 } 415 else if (upper < -10.0) { 416 //parameter value is < -10 417 //calculate log using positive value: 418 logCeil = Math.log(-upper) / LOG10_VALUE; 419 //calculate ceil using negative value: 420 logCeil = Math.ceil(-logCeil); 421 //calculate power using positive value; then negate 422 logCeil = -Math.pow(10, -logCeil); 423 } 424 else { 425 //parameter value is -10 > val < 10 426 logCeil = Math.ceil(upper); //use as-is 427 } 428 } 429 else { 430 //negative values not allowed 431 if (upper > 0.0) { 432 //parameter value is > 0 433 // The Math.log() function is based on e not 10. 434 logCeil = Math.log(upper) / LOG10_VALUE; 435 logCeil = Math.ceil(logCeil); 436 logCeil = Math.pow(10, logCeil); 437 } 438 else { 439 //parameter value is <= 0 440 logCeil = Math.ceil(upper); //use as-is 441 } 442 } 443 return logCeil; 444 } 445 446 /** 447 * Rescales the axis to ensure that all data is visible. 448 */ 449 @Override 450 public void autoAdjustRange() { 451 452 Plot plot = getPlot(); 453 if (plot == null) { 454 return; // no plot, no data. 455 } 456 457 if (plot instanceof ValueAxisPlot) { 458 ValueAxisPlot vap = (ValueAxisPlot) plot; 459 460 double lower; 461 Range r = vap.getDataRange(this); 462 if (r == null) { 463 //no real data present 464 r = getDefaultAutoRange(); 465 lower = r.getLowerBound(); //get lower bound value 466 } 467 else { 468 //actual data is present 469 lower = r.getLowerBound(); //get lower bound value 470 if (this.strictValuesFlag 471 && !this.allowNegativesFlag && lower <= 0.0) { 472 //strict flag set, allow-negatives not set and values <= 0 473 throw new RuntimeException("Values less than or equal to " 474 + "zero not allowed with logarithmic axis"); 475 } 476 } 477 478 //apply lower margin by decreasing lower bound: 479 final double lowerMargin; 480 if (lower > 0.0 && (lowerMargin = getLowerMargin()) > 0.0) { 481 //lower bound and margin OK; get log10 of lower bound 482 final double logLower = (Math.log(lower) / LOG10_VALUE); 483 double logAbs; //get absolute value of log10 value 484 if ((logAbs = Math.abs(logLower)) < 1.0) { 485 logAbs = 1.0; //if less than 1.0 then make it 1.0 486 } //subtract out margin and get exponential value: 487 lower = Math.pow(10, (logLower - (logAbs * lowerMargin))); 488 } 489 490 //if flag then change to log version of lowest value 491 // to make range begin at a 10^n value: 492 if (this.autoRangeNextLogFlag) { 493 lower = computeLogFloor(lower); 494 } 495 496 if (!this.allowNegativesFlag && lower >= 0.0 497 && lower < SMALL_LOG_VALUE) { 498 //negatives not allowed and lower range bound is zero 499 lower = r.getLowerBound(); //use data range bound instead 500 } 501 502 double upper = r.getUpperBound(); 503 504 //apply upper margin by increasing upper bound: 505 final double upperMargin; 506 if (upper > 0.0 && (upperMargin = getUpperMargin()) > 0.0) { 507 //upper bound and margin OK; get log10 of upper bound 508 final double logUpper = (Math.log(upper) / LOG10_VALUE); 509 double logAbs; //get absolute value of log10 value 510 if ((logAbs = Math.abs(logUpper)) < 1.0) { 511 logAbs = 1.0; //if less than 1.0 then make it 1.0 512 } //add in margin and get exponential value: 513 upper = Math.pow(10, (logUpper + (logAbs * upperMargin))); 514 } 515 516 if (!this.allowNegativesFlag && upper < 1.0 && upper > 0.0 517 && lower > 0.0) { 518 //negatives not allowed and upper bound between 0 & 1 519 //round up to nearest significant digit for bound: 520 //get negative exponent: 521 double expVal = Math.log(upper) / LOG10_VALUE; 522 expVal = Math.ceil(-expVal + 0.001); //get positive exponent 523 expVal = Math.pow(10, expVal); //create multiplier value 524 //multiply, round up, and divide for bound value: 525 upper = (expVal > 0.0) ? Math.ceil(upper * expVal) / expVal 526 : Math.ceil(upper); 527 } 528 else { 529 //negatives allowed or upper bound not between 0 & 1 530 //if flag then change to log version of highest value to 531 // make range begin at a 10^n value; else use nearest int 532 upper = (this.autoRangeNextLogFlag) ? computeLogCeil(upper) 533 : Math.ceil(upper); 534 } 535 // ensure the autorange is at least <minRange> in size... 536 double minRange = getAutoRangeMinimumSize(); 537 if (upper - lower < minRange) { 538 upper = (upper + lower + minRange) / 2; 539 lower = (upper + lower - minRange) / 2; 540 //if autorange still below minimum then adjust by 1% 541 // (can be needed when minRange is very small): 542 if (upper - lower < minRange) { 543 double absUpper = Math.abs(upper); 544 //need to account for case where upper==0.0 545 double adjVal = (absUpper > SMALL_LOG_VALUE) ? absUpper 546 / 100.0 : 0.01; 547 upper = (upper + lower + adjVal) / 2; 548 lower = (upper + lower - adjVal) / 2; 549 } 550 } 551 552 setRange(new Range(lower, upper), false, false); 553 setupSmallLogFlag(); //setup flag based on bounds values 554 } 555 } 556 557 /** 558 * Converts a data value to a coordinate in Java2D space, assuming that 559 * the axis runs along one edge of the specified plotArea. 560 * Note that it is possible for the coordinate to fall outside the 561 * plotArea. 562 * 563 * @param value the data value. 564 * @param plotArea the area for plotting the data. 565 * @param edge the axis location. 566 * 567 * @return The Java2D coordinate. 568 */ 569 @Override 570 public double valueToJava2D(double value, Rectangle2D plotArea, 571 RectangleEdge edge) { 572 573 Range range = getRange(); 574 double axisMin = switchedLog10(range.getLowerBound()); 575 double axisMax = switchedLog10(range.getUpperBound()); 576 577 double min = 0.0; 578 double max = 0.0; 579 if (RectangleEdge.isTopOrBottom(edge)) { 580 min = plotArea.getMinX(); 581 max = plotArea.getMaxX(); 582 } 583 else if (RectangleEdge.isLeftOrRight(edge)) { 584 min = plotArea.getMaxY(); 585 max = plotArea.getMinY(); 586 } 587 588 value = switchedLog10(value); 589 590 if (isInverted()) { 591 return max - (((value - axisMin) / (axisMax - axisMin)) 592 * (max - min)); 593 } 594 else { 595 return min + (((value - axisMin) / (axisMax - axisMin)) 596 * (max - min)); 597 } 598 599 } 600 601 /** 602 * Converts a coordinate in Java2D space to the corresponding data 603 * value, assuming that the axis runs along one edge of the specified 604 * plotArea. 605 * 606 * @param java2DValue the coordinate in Java2D space. 607 * @param plotArea the area in which the data is plotted. 608 * @param edge the axis location. 609 * 610 * @return The data value. 611 */ 612 @Override 613 public double java2DToValue(double java2DValue, Rectangle2D plotArea, 614 RectangleEdge edge) { 615 616 Range range = getRange(); 617 double axisMin = switchedLog10(range.getLowerBound()); 618 double axisMax = switchedLog10(range.getUpperBound()); 619 620 double plotMin = 0.0; 621 double plotMax = 0.0; 622 if (RectangleEdge.isTopOrBottom(edge)) { 623 plotMin = plotArea.getX(); 624 plotMax = plotArea.getMaxX(); 625 } 626 else if (RectangleEdge.isLeftOrRight(edge)) { 627 plotMin = plotArea.getMaxY(); 628 plotMax = plotArea.getMinY(); 629 } 630 631 if (isInverted()) { 632 return switchedPow10(axisMax - ((java2DValue - plotMin) 633 / (plotMax - plotMin)) * (axisMax - axisMin)); 634 } 635 else { 636 return switchedPow10(axisMin + ((java2DValue - plotMin) 637 / (plotMax - plotMin)) * (axisMax - axisMin)); 638 } 639 } 640 641 /** 642 * Zooms in on the current range. 643 * 644 * @param lowerPercent the new lower bound. 645 * @param upperPercent the new upper bound. 646 */ 647 @Override 648 public void zoomRange(double lowerPercent, double upperPercent) { 649 double startLog = switchedLog10(getRange().getLowerBound()); 650 double lengthLog = switchedLog10(getRange().getUpperBound()) - startLog; 651 Range adjusted; 652 653 if (isInverted()) { 654 adjusted = new Range( 655 switchedPow10(startLog + (lengthLog * (1 - upperPercent))), 656 switchedPow10(startLog + (lengthLog * (1 - lowerPercent)))); 657 } 658 else { 659 adjusted = new Range( 660 switchedPow10(startLog + (lengthLog * lowerPercent)), 661 switchedPow10(startLog + (lengthLog * upperPercent))); 662 } 663 664 setRange(adjusted); 665 } 666 667 /** 668 * Calculates the positions of the tick labels for the axis, storing the 669 * results in the tick label list (ready for drawing). 670 * 671 * @param g2 the graphics device. 672 * @param dataArea the area in which the plot should be drawn. 673 * @param edge the location of the axis. 674 * 675 * @return A list of ticks. 676 */ 677 @Override 678 protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea, 679 RectangleEdge edge) { 680 681 List ticks = new java.util.ArrayList(); 682 Range range = getRange(); 683 684 //get lower bound value: 685 double lowerBoundVal = range.getLowerBound(); 686 //if small log values and lower bound value too small 687 // then set to a small value (don't allow <= 0): 688 if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) { 689 lowerBoundVal = SMALL_LOG_VALUE; 690 } 691 692 //get upper bound value 693 double upperBoundVal = range.getUpperBound(); 694 695 //get log10 version of lower bound and round to integer: 696 int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal)); 697 //get log10 version of upper bound and round to integer: 698 int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal)); 699 700 if (iBegCount == iEndCount && iBegCount > 0 701 && Math.pow(10, iBegCount) > lowerBoundVal) { 702 //only 1 power of 10 value, it's > 0 and its resulting 703 // tick value will be larger than lower bound of data 704 --iBegCount; //decrement to generate more ticks 705 } 706 707 double currentTickValue; 708 String tickLabel; 709 boolean zeroTickFlag = false; 710 for (int i = iBegCount; i <= iEndCount; i++) { 711 //for each power of 10 value; create ten ticks 712 for (int j = 0; j < 10; ++j) { 713 //for each tick to be displayed 714 if (this.smallLogFlag) { 715 //small log values in use; create numeric value for tick 716 currentTickValue = Math.pow(10, i) + (Math.pow(10, i) * j); 717 if (this.expTickLabelsFlag 718 || (i < 0 && currentTickValue > 0.0 719 && currentTickValue < 1.0)) { 720 //showing "1e#"-style ticks or negative exponent 721 // generating tick value between 0 & 1; show fewer 722 if (j == 0 || (i > -4 && j < 2) 723 || currentTickValue >= upperBoundVal) { 724 //first tick of series, or not too small a value and 725 // one of first 3 ticks, or last tick to be displayed 726 // set exact number of fractional digits to be shown 727 // (no effect if showing "1e#"-style ticks): 728 this.numberFormatterObj 729 .setMaximumFractionDigits(-i); 730 //create tick label (force use of fmt obj): 731 tickLabel = makeTickLabel(currentTickValue, true); 732 } 733 else { //no tick label to be shown 734 tickLabel = ""; 735 } 736 } 737 else { //tick value not between 0 & 1 738 //show tick label if it's the first or last in 739 // the set, or if it's 1-5; beyond that show 740 // fewer as the values get larger: 741 tickLabel = (j < 1 || (i < 1 && j < 5) || (j < 4 - i) 742 || currentTickValue >= upperBoundVal) 743 ? makeTickLabel(currentTickValue) : ""; 744 } 745 } 746 else { //not small log values in use; allow for values <= 0 747 if (zeroTickFlag) { //if did zero tick last iter then 748 --j; //decrement to do 1.0 tick now 749 } //calculate power-of-ten value for tick: 750 currentTickValue = (i >= 0) 751 ? Math.pow(10, i) + (Math.pow(10, i) * j) 752 : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j)); 753 if (!zeroTickFlag) { // did not do zero tick last iteration 754 if (Math.abs(currentTickValue - 1.0) < 0.0001 755 && lowerBoundVal <= 0.0 && upperBoundVal >= 0.0) { 756 //tick value is 1.0 and 0.0 is within data range 757 currentTickValue = 0.0; //set tick value to zero 758 zeroTickFlag = true; //indicate zero tick 759 } 760 } 761 else { //did zero tick last iteration 762 zeroTickFlag = false; //clear flag 763 } //create tick label string: 764 //show tick label if "1e#"-style and it's one 765 // of the first two, if it's the first or last 766 // in the set, or if it's 1-5; beyond that 767 // show fewer as the values get larger: 768 tickLabel = ((this.expTickLabelsFlag && j < 2) 769 || j < 1 770 || (i < 1 && j < 5) || (j < 4 - i) 771 || currentTickValue >= upperBoundVal) 772 ? makeTickLabel(currentTickValue) : ""; 773 } 774 775 if (currentTickValue > upperBoundVal) { 776 return ticks; // if past highest data value then exit 777 // method 778 } 779 780 if (currentTickValue >= lowerBoundVal - SMALL_LOG_VALUE) { 781 //tick value not below lowest data value 782 TextAnchor anchor; 783 TextAnchor rotationAnchor; 784 double angle = 0.0; 785 if (isVerticalTickLabels()) { 786 anchor = TextAnchor.CENTER_RIGHT; 787 rotationAnchor = TextAnchor.CENTER_RIGHT; 788 if (edge == RectangleEdge.TOP) { 789 angle = Math.PI / 2.0; 790 } 791 else { 792 angle = -Math.PI / 2.0; 793 } 794 } 795 else { 796 if (edge == RectangleEdge.TOP) { 797 anchor = TextAnchor.BOTTOM_CENTER; 798 rotationAnchor = TextAnchor.BOTTOM_CENTER; 799 } 800 else { 801 anchor = TextAnchor.TOP_CENTER; 802 rotationAnchor = TextAnchor.TOP_CENTER; 803 } 804 } 805 806 Tick tick = new NumberTick(currentTickValue, 807 tickLabel, anchor, rotationAnchor, angle); 808 ticks.add(tick); 809 } 810 } 811 } 812 return ticks; 813 814 } 815 816 /** 817 * Calculates the positions of the tick labels for the axis, storing the 818 * results in the tick label list (ready for drawing). 819 * 820 * @param g2 the graphics device. 821 * @param dataArea the area in which the plot should be drawn. 822 * @param edge the location of the axis. 823 * 824 * @return A list of ticks. 825 */ 826 @Override 827 protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea, 828 RectangleEdge edge) { 829 830 List ticks = new java.util.ArrayList(); 831 832 //get lower bound value: 833 double lowerBoundVal = getRange().getLowerBound(); 834 //if small log values and lower bound value too small 835 // then set to a small value (don't allow <= 0): 836 if (this.smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) { 837 lowerBoundVal = SMALL_LOG_VALUE; 838 } 839 //get upper bound value 840 double upperBoundVal = getRange().getUpperBound(); 841 842 //get log10 version of lower bound and round to integer: 843 int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal)); 844 //get log10 version of upper bound and round to integer: 845 int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal)); 846 847 if (iBegCount == iEndCount && iBegCount > 0 848 && Math.pow(10, iBegCount) > lowerBoundVal) { 849 //only 1 power of 10 value, it's > 0 and its resulting 850 // tick value will be larger than lower bound of data 851 --iBegCount; //decrement to generate more ticks 852 } 853 854 double tickVal; 855 String tickLabel; 856 boolean zeroTickFlag = false; 857 for (int i = iBegCount; i <= iEndCount; i++) { 858 //for each tick with a label to be displayed 859 int jEndCount = 10; 860 if (i == iEndCount) { 861 jEndCount = 1; 862 } 863 864 for (int j = 0; j < jEndCount; j++) { 865 //for each tick to be displayed 866 if (this.smallLogFlag) { 867 //small log values in use 868 tickVal = Math.pow(10, i) + (Math.pow(10, i) * j); 869 if (j == 0) { 870 //first tick of group; create label text 871 if (this.log10TickLabelsFlag) { 872 //if flag then 873 tickLabel = "10^" + i; //create "log10"-type label 874 } 875 else { //not "log10"-type label 876 if (this.expTickLabelsFlag) { 877 //if flag then 878 tickLabel = "1e" + i; //create "1e#"-type label 879 } 880 else { //not "1e#"-type label 881 if (i >= 0) { // if positive exponent then 882 // make integer 883 NumberFormat format 884 = getNumberFormatOverride(); 885 if (format != null) { 886 tickLabel = format.format(tickVal); 887 } 888 else { 889 tickLabel = Long.toString((long) 890 Math.rint(tickVal)); 891 } 892 } 893 else { 894 //negative exponent; create fractional value 895 //set exact number of fractional digits to 896 // be shown: 897 this.numberFormatterObj 898 .setMaximumFractionDigits(-i); 899 //create tick label: 900 tickLabel = this.numberFormatterObj.format( 901 tickVal); 902 } 903 } 904 } 905 } 906 else { //not first tick to be displayed 907 tickLabel = ""; //no tick label 908 } 909 } 910 else { //not small log values in use; allow for values <= 0 911 if (zeroTickFlag) { //if did zero tick last iter then 912 --j; 913 } //decrement to do 1.0 tick now 914 tickVal = (i >= 0) ? Math.pow(10, i) + (Math.pow(10, i) * j) 915 : -(Math.pow(10, -i) - (Math.pow(10, -i - 1) * j)); 916 if (j == 0) { //first tick of group 917 if (!zeroTickFlag) { // did not do zero tick last 918 // iteration 919 if (i > iBegCount && i < iEndCount 920 && Math.abs(tickVal - 1.0) < 0.0001) { 921 // not first or last tick on graph and value 922 // is 1.0 923 tickVal = 0.0; //change value to 0.0 924 zeroTickFlag = true; //indicate zero tick 925 tickLabel = "0"; //create label for tick 926 } 927 else { 928 //first or last tick on graph or value is 1.0 929 //create label for tick: 930 if (this.log10TickLabelsFlag) { 931 //create "log10"-type label 932 tickLabel = (((i < 0) ? "-" : "") 933 + "10^" + Math.abs(i)); 934 } 935 else { 936 if (this.expTickLabelsFlag) { 937 //create "1e#"-type label 938 tickLabel = (((i < 0) ? "-" : "") 939 + "1e" + Math.abs(i)); 940 } 941 else { 942 NumberFormat format 943 = getNumberFormatOverride(); 944 if (format != null) { 945 tickLabel = format.format(tickVal); 946 } 947 else { 948 tickLabel = Long.toString( 949 (long) Math.rint(tickVal)); 950 } 951 } 952 } 953 } 954 } 955 else { // did zero tick last iteration 956 tickLabel = ""; //no label 957 zeroTickFlag = false; //clear flag 958 } 959 } 960 else { // not first tick of group 961 tickLabel = ""; //no label 962 zeroTickFlag = false; //make sure flag cleared 963 } 964 } 965 966 if (tickVal > upperBoundVal) { 967 return ticks; //if past highest data value then exit method 968 } 969 970 if (tickVal >= lowerBoundVal - SMALL_LOG_VALUE) { 971 //tick value not below lowest data value 972 TextAnchor anchor; 973 TextAnchor rotationAnchor; 974 double angle = 0.0; 975 if (isVerticalTickLabels()) { 976 if (edge == RectangleEdge.LEFT) { 977 anchor = TextAnchor.BOTTOM_CENTER; 978 rotationAnchor = TextAnchor.BOTTOM_CENTER; 979 angle = -Math.PI / 2.0; 980 } 981 else { 982 anchor = TextAnchor.BOTTOM_CENTER; 983 rotationAnchor = TextAnchor.BOTTOM_CENTER; 984 angle = Math.PI / 2.0; 985 } 986 } 987 else { 988 if (edge == RectangleEdge.LEFT) { 989 anchor = TextAnchor.CENTER_RIGHT; 990 rotationAnchor = TextAnchor.CENTER_RIGHT; 991 } 992 else { 993 anchor = TextAnchor.CENTER_LEFT; 994 rotationAnchor = TextAnchor.CENTER_LEFT; 995 } 996 } 997 //create tick object and add to list: 998 ticks.add(new NumberTick(tickVal, tickLabel, 999 anchor, rotationAnchor, angle)); 1000 } 1001 } 1002 } 1003 return ticks; 1004 } 1005 1006 /** 1007 * Converts the given value to a tick label string. 1008 * 1009 * @param val the value to convert. 1010 * @param forceFmtFlag true to force the number-formatter object 1011 * to be used. 1012 * 1013 * @return The tick label string. 1014 */ 1015 protected String makeTickLabel(double val, boolean forceFmtFlag) { 1016 if (this.expTickLabelsFlag || forceFmtFlag) { 1017 //using exponents or force-formatter flag is set 1018 // (convert 'E' to lower-case 'e'): 1019 return this.numberFormatterObj.format(val).toLowerCase(); 1020 } 1021 return getTickUnit().valueToString(val); 1022 } 1023 1024 /** 1025 * Converts the given value to a tick label string. 1026 * @param val the value to convert. 1027 * 1028 * @return The tick label string. 1029 */ 1030 protected String makeTickLabel(double val) { 1031 return makeTickLabel(val, false); 1032 } 1033 1034}