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 * LogAxis.java 029 * ------------ 030 * (C) Copyright 2006-2008, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Andrew Mickish (patch 1868745); 034 * 035 * Changes 036 * ------- 037 * 24-Aug-2006 : Version 1 (DG); 038 * 22-Mar-2007 : Use defaultAutoArrange attribute (DG); 039 * 02-Aug-2007 : Fixed zooming bug, added support for margins (DG); 040 * 14-Feb-2008 : Changed default minorTickCount to 9 - see bug report 041 * 1892419 (DG); 042 * 15-Feb-2008 : Applied a variation of patch 1868745 by Andrew Mickish to 043 * fix a labelling bug when the axis appears at the top or 044 * right of the chart (DG); 045 * 19-Mar-2008 : Applied patch 1902418 by Andrew Mickish to fix bug in tick 046 * labels for vertical axis (DG); 047 * 26-Mar-2008 : Changed createTickLabel() method from private to protected - 048 * see patch 1918209 by Andrew Mickish (DG); 049 * 050 */ 051 052 package org.jfree.chart.axis; 053 054 import java.awt.Font; 055 import java.awt.FontMetrics; 056 import java.awt.Graphics2D; 057 import java.awt.font.FontRenderContext; 058 import java.awt.font.LineMetrics; 059 import java.awt.geom.Rectangle2D; 060 import java.text.DecimalFormat; 061 import java.text.NumberFormat; 062 import java.util.ArrayList; 063 import java.util.List; 064 import java.util.Locale; 065 066 import org.jfree.chart.event.AxisChangeEvent; 067 import org.jfree.chart.plot.Plot; 068 import org.jfree.chart.plot.PlotRenderingInfo; 069 import org.jfree.chart.plot.ValueAxisPlot; 070 import org.jfree.data.Range; 071 import org.jfree.ui.RectangleEdge; 072 import org.jfree.ui.RectangleInsets; 073 import org.jfree.ui.TextAnchor; 074 075 /** 076 * A numerical axis that uses a logarithmic scale. The class is an 077 * alternative to the {@link LogarithmicAxis} class. 078 * 079 * @since 1.0.7 080 */ 081 public class LogAxis extends ValueAxis { 082 083 /** The logarithm base. */ 084 private double base = 10.0; 085 086 /** The logarithm of the base value - cached for performance. */ 087 private double baseLog = Math.log(10.0); 088 089 /** The smallest value permitted on the axis. */ 090 private double smallestValue = 1E-100; 091 092 /** The current tick unit. */ 093 private NumberTickUnit tickUnit; 094 095 /** The override number format. */ 096 private NumberFormat numberFormatOverride; 097 098 /** The number of minor ticks per major tick unit. */ 099 private int minorTickCount; 100 101 /** 102 * Creates a new <code>LogAxis</code> with no label. 103 */ 104 public LogAxis() { 105 this(null); 106 } 107 108 /** 109 * Creates a new <code>LogAxis</code> with the given label. 110 * 111 * @param label the axis label (<code>null</code> permitted). 112 */ 113 public LogAxis(String label) { 114 super(label, createLogTickUnits(Locale.getDefault())); 115 setDefaultAutoRange(new Range(0.01, 1.0)); 116 this.tickUnit = new NumberTickUnit(1.0, new DecimalFormat("0.#")); 117 this.minorTickCount = 9; 118 } 119 120 /** 121 * Returns the base for the logarithm calculation. 122 * 123 * @return The base for the logarithm calculation. 124 * 125 * @see #setBase(double) 126 */ 127 public double getBase() { 128 return this.base; 129 } 130 131 /** 132 * Sets the base for the logarithm calculation and sends an 133 * {@link AxisChangeEvent} to all registered listeners. 134 * 135 * @param base the base value (must be > 1.0). 136 * 137 * @see #getBase() 138 */ 139 public void setBase(double base) { 140 if (base <= 1.0) { 141 throw new IllegalArgumentException("Requires 'base' > 1.0."); 142 } 143 this.base = base; 144 this.baseLog = Math.log(base); 145 notifyListeners(new AxisChangeEvent(this)); 146 } 147 148 /** 149 * Returns the smallest value represented by the axis. 150 * 151 * @return The smallest value represented by the axis. 152 * 153 * @see #setSmallestValue(double) 154 */ 155 public double getSmallestValue() { 156 return this.smallestValue; 157 } 158 159 /** 160 * Sets the smallest value represented by the axis and sends an 161 * {@link AxisChangeEvent} to all registered listeners. 162 * 163 * @param value the value. 164 * 165 * @see #getSmallestValue() 166 */ 167 public void setSmallestValue(double value) { 168 if (value <= 0.0) { 169 throw new IllegalArgumentException("Requires 'value' > 0.0."); 170 } 171 this.smallestValue = value; 172 notifyListeners(new AxisChangeEvent(this)); 173 } 174 175 /** 176 * Returns the current tick unit. 177 * 178 * @return The current tick unit. 179 * 180 * @see #setTickUnit(NumberTickUnit) 181 */ 182 public NumberTickUnit getTickUnit() { 183 return this.tickUnit; 184 } 185 186 /** 187 * Sets the tick unit for the axis and sends an {@link AxisChangeEvent} to 188 * all registered listeners. A side effect of calling this method is that 189 * the "auto-select" feature for tick units is switched off (you can 190 * restore it using the {@link ValueAxis#setAutoTickUnitSelection(boolean)} 191 * method). 192 * 193 * @param unit the new tick unit (<code>null</code> not permitted). 194 * 195 * @see #getTickUnit() 196 */ 197 public void setTickUnit(NumberTickUnit unit) { 198 // defer argument checking... 199 setTickUnit(unit, true, true); 200 } 201 202 /** 203 * Sets the tick unit for the axis and, if requested, sends an 204 * {@link AxisChangeEvent} to all registered listeners. In addition, an 205 * option is provided to turn off the "auto-select" feature for tick units 206 * (you can restore it using the 207 * {@link ValueAxis#setAutoTickUnitSelection(boolean)} method). 208 * 209 * @param unit the new tick unit (<code>null</code> not permitted). 210 * @param notify notify listeners? 211 * @param turnOffAutoSelect turn off the auto-tick selection? 212 * 213 * @see #getTickUnit() 214 */ 215 public void setTickUnit(NumberTickUnit unit, boolean notify, 216 boolean turnOffAutoSelect) { 217 218 if (unit == null) { 219 throw new IllegalArgumentException("Null 'unit' argument."); 220 } 221 this.tickUnit = unit; 222 if (turnOffAutoSelect) { 223 setAutoTickUnitSelection(false, false); 224 } 225 if (notify) { 226 notifyListeners(new AxisChangeEvent(this)); 227 } 228 229 } 230 231 /** 232 * Returns the number format override. If this is non-null, then it will 233 * be used to format the numbers on the axis. 234 * 235 * @return The number formatter (possibly <code>null</code>). 236 * 237 * @see #setNumberFormatOverride(NumberFormat) 238 */ 239 public NumberFormat getNumberFormatOverride() { 240 return this.numberFormatOverride; 241 } 242 243 /** 244 * Sets the number format override. If this is non-null, then it will be 245 * used to format the numbers on the axis. 246 * 247 * @param formatter the number formatter (<code>null</code> permitted). 248 * 249 * @see #getNumberFormatOverride() 250 */ 251 public void setNumberFormatOverride(NumberFormat formatter) { 252 this.numberFormatOverride = formatter; 253 notifyListeners(new AxisChangeEvent(this)); 254 } 255 256 /** 257 * Returns the number of minor tick marks to display. 258 * 259 * @return The number of minor tick marks to display. 260 * 261 * @see #setMinorTickCount(int) 262 */ 263 public int getMinorTickCount() { 264 return this.minorTickCount; 265 } 266 267 /** 268 * Sets the number of minor tick marks to display, and sends an 269 * {@link AxisChangeEvent} to all registered listeners. 270 * 271 * @param count the count. 272 * 273 * @see #getMinorTickCount() 274 */ 275 public void setMinorTickCount(int count) { 276 if (count <= 0) { 277 throw new IllegalArgumentException("Requires 'count' > 0."); 278 } 279 this.minorTickCount = count; 280 notifyListeners(new AxisChangeEvent(this)); 281 } 282 283 /** 284 * Calculates the log of the given value, using the current base. 285 * 286 * @param value the value. 287 * 288 * @return The log of the given value. 289 * 290 * @see #calculateValue(double) 291 * @see #getBase() 292 */ 293 public double calculateLog(double value) { 294 return Math.log(value) / this.baseLog; 295 } 296 297 /** 298 * Calculates the value from a given log. 299 * 300 * @param log the log value (must be > 0.0). 301 * 302 * @return The value with the given log. 303 * 304 * @see #calculateLog(double) 305 * @see #getBase() 306 */ 307 public double calculateValue(double log) { 308 return Math.pow(this.base, log); 309 } 310 311 /** 312 * Converts a Java2D coordinate to an axis value, assuming that the 313 * axis covers the specified <code>edge</code> of the <code>area</code>. 314 * 315 * @param java2DValue the Java2D coordinate. 316 * @param area the area. 317 * @param edge the edge that the axis belongs to. 318 * 319 * @return A value along the axis scale. 320 */ 321 public double java2DToValue(double java2DValue, Rectangle2D area, 322 RectangleEdge edge) { 323 324 Range range = getRange(); 325 double axisMin = calculateLog(range.getLowerBound()); 326 double axisMax = calculateLog(range.getUpperBound()); 327 328 double min = 0.0; 329 double max = 0.0; 330 if (RectangleEdge.isTopOrBottom(edge)) { 331 min = area.getX(); 332 max = area.getMaxX(); 333 } 334 else if (RectangleEdge.isLeftOrRight(edge)) { 335 min = area.getMaxY(); 336 max = area.getY(); 337 } 338 double log = 0.0; 339 if (isInverted()) { 340 log = axisMax - (java2DValue - min) / (max - min) 341 * (axisMax - axisMin); 342 } 343 else { 344 log = axisMin + (java2DValue - min) / (max - min) 345 * (axisMax - axisMin); 346 } 347 return calculateValue(log); 348 } 349 350 /** 351 * Converts a value on the axis scale to a Java2D coordinate relative to 352 * the given <code>area</code>, based on the axis running along the 353 * specified <code>edge</code>. 354 * 355 * @param value the data value. 356 * @param area the area. 357 * @param edge the edge. 358 * 359 * @return The Java2D coordinate corresponding to <code>value</code>. 360 */ 361 public double valueToJava2D(double value, Rectangle2D area, 362 RectangleEdge edge) { 363 364 Range range = getRange(); 365 double axisMin = calculateLog(range.getLowerBound()); 366 double axisMax = calculateLog(range.getUpperBound()); 367 value = calculateLog(value); 368 369 double min = 0.0; 370 double max = 0.0; 371 if (RectangleEdge.isTopOrBottom(edge)) { 372 min = area.getX(); 373 max = area.getMaxX(); 374 } 375 else if (RectangleEdge.isLeftOrRight(edge)) { 376 max = area.getMinY(); 377 min = area.getMaxY(); 378 } 379 if (isInverted()) { 380 return max 381 - ((value - axisMin) / (axisMax - axisMin)) * (max - min); 382 } 383 else { 384 return min 385 + ((value - axisMin) / (axisMax - axisMin)) * (max - min); 386 } 387 } 388 389 /** 390 * Configures the axis. This method is typically called when an axis 391 * is assigned to a new plot. 392 */ 393 public void configure() { 394 if (isAutoRange()) { 395 autoAdjustRange(); 396 } 397 } 398 399 /** 400 * Adjusts the axis range to match the data range that the axis is 401 * required to display. 402 */ 403 protected void autoAdjustRange() { 404 Plot plot = getPlot(); 405 if (plot == null) { 406 return; // no plot, no data 407 } 408 409 if (plot instanceof ValueAxisPlot) { 410 ValueAxisPlot vap = (ValueAxisPlot) plot; 411 412 Range r = vap.getDataRange(this); 413 if (r == null) { 414 r = getDefaultAutoRange(); 415 } 416 417 double upper = r.getUpperBound(); 418 double lower = Math.max(r.getLowerBound(), this.smallestValue); 419 double range = upper - lower; 420 421 // if fixed auto range, then derive lower bound... 422 double fixedAutoRange = getFixedAutoRange(); 423 if (fixedAutoRange > 0.0) { 424 lower = Math.max(upper - fixedAutoRange, this.smallestValue); 425 } 426 else { 427 // ensure the autorange is at least <minRange> in size... 428 double minRange = getAutoRangeMinimumSize(); 429 if (range < minRange) { 430 double expand = (minRange - range) / 2; 431 upper = upper + expand; 432 lower = lower - expand; 433 } 434 435 // apply the margins - these should apply to the exponent range 436 double logUpper = calculateLog(upper); 437 double logLower = calculateLog(lower); 438 double logRange = logUpper - logLower; 439 logUpper = logUpper + getUpperMargin() * logRange; 440 logLower = logLower - getLowerMargin() * logRange; 441 upper = calculateValue(logUpper); 442 lower = calculateValue(logLower); 443 } 444 445 setRange(new Range(lower, upper), false, false); 446 } 447 448 } 449 450 /** 451 * Draws the axis on a Java 2D graphics device (such as the screen or a 452 * printer). 453 * 454 * @param g2 the graphics device (<code>null</code> not permitted). 455 * @param cursor the cursor location (determines where to draw the axis). 456 * @param plotArea the area within which the axes and plot should be drawn. 457 * @param dataArea the area within which the data should be drawn. 458 * @param edge the axis location (<code>null</code> not permitted). 459 * @param plotState collects information about the plot 460 * (<code>null</code> permitted). 461 * 462 * @return The axis state (never <code>null</code>). 463 */ 464 public AxisState draw(Graphics2D g2, double cursor, Rectangle2D plotArea, 465 Rectangle2D dataArea, RectangleEdge edge, 466 PlotRenderingInfo plotState) { 467 468 AxisState state = null; 469 // if the axis is not visible, don't draw it... 470 if (!isVisible()) { 471 state = new AxisState(cursor); 472 // even though the axis is not visible, we need ticks for the 473 // gridlines... 474 List ticks = refreshTicks(g2, state, dataArea, edge); 475 state.setTicks(ticks); 476 return state; 477 } 478 state = drawTickMarksAndLabels(g2, cursor, plotArea, dataArea, edge); 479 state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state); 480 return state; 481 } 482 483 /** 484 * Calculates the positions of the tick labels for the axis, storing the 485 * results in the tick label list (ready for drawing). 486 * 487 * @param g2 the graphics device. 488 * @param state the axis state. 489 * @param dataArea the area in which the plot should be drawn. 490 * @param edge the location of the axis. 491 * 492 * @return A list of ticks. 493 * 494 */ 495 public List refreshTicks(Graphics2D g2, AxisState state, 496 Rectangle2D dataArea, RectangleEdge edge) { 497 498 List result = new java.util.ArrayList(); 499 if (RectangleEdge.isTopOrBottom(edge)) { 500 result = refreshTicksHorizontal(g2, dataArea, edge); 501 } 502 else if (RectangleEdge.isLeftOrRight(edge)) { 503 result = refreshTicksVertical(g2, dataArea, edge); 504 } 505 return result; 506 507 } 508 509 /** 510 * Returns a list of ticks for an axis at the top or bottom of the chart. 511 * 512 * @param g2 the graphics device. 513 * @param dataArea the data area. 514 * @param edge the edge. 515 * 516 * @return A list of ticks. 517 */ 518 protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea, 519 RectangleEdge edge) { 520 521 Range range = getRange(); 522 List ticks = new ArrayList(); 523 Font tickLabelFont = getTickLabelFont(); 524 g2.setFont(tickLabelFont); 525 TextAnchor textAnchor; 526 if (edge == RectangleEdge.TOP) { 527 textAnchor = TextAnchor.BOTTOM_CENTER; 528 } 529 else { 530 textAnchor = TextAnchor.TOP_CENTER; 531 } 532 533 if (isAutoTickUnitSelection()) { 534 selectAutoTickUnit(g2, dataArea, edge); 535 } 536 double start = Math.floor(calculateLog(getLowerBound())); 537 double end = Math.ceil(calculateLog(getUpperBound())); 538 double current = start; 539 while (current <= end) { 540 double v = calculateValue(current); 541 if (range.contains(v)) { 542 ticks.add(new NumberTick(TickType.MAJOR, v, createTickLabel(v), 543 textAnchor, TextAnchor.CENTER, 0.0)); 544 } 545 // add minor ticks (for gridlines) 546 double next = Math.pow(this.base, current 547 + this.tickUnit.getSize()); 548 for (int i = 1; i < this.minorTickCount; i++) { 549 double minorV = v + i * ((next - v) / this.minorTickCount); 550 if (range.contains(minorV)) { 551 ticks.add(new NumberTick(TickType.MINOR, minorV, "", 552 textAnchor, TextAnchor.CENTER, 0.0)); 553 } 554 } 555 current = current + this.tickUnit.getSize(); 556 } 557 return ticks; 558 } 559 560 /** 561 * Returns a list of ticks for an axis at the left or right of the chart. 562 * 563 * @param g2 the graphics device. 564 * @param dataArea the data area. 565 * @param edge the edge. 566 * 567 * @return A list of ticks. 568 */ 569 protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea, 570 RectangleEdge edge) { 571 572 Range range = getRange(); 573 List ticks = new ArrayList(); 574 Font tickLabelFont = getTickLabelFont(); 575 g2.setFont(tickLabelFont); 576 TextAnchor textAnchor; 577 if (edge == RectangleEdge.RIGHT) { 578 textAnchor = TextAnchor.CENTER_LEFT; 579 } 580 else { 581 textAnchor = TextAnchor.CENTER_RIGHT; 582 } 583 584 if (isAutoTickUnitSelection()) { 585 selectAutoTickUnit(g2, dataArea, edge); 586 } 587 double start = Math.floor(calculateLog(getLowerBound())); 588 double end = Math.ceil(calculateLog(getUpperBound())); 589 double current = start; 590 while (current <= end) { 591 double v = calculateValue(current); 592 if (range.contains(v)) { 593 ticks.add(new NumberTick(TickType.MAJOR, v, createTickLabel(v), 594 textAnchor, TextAnchor.CENTER, 0.0)); 595 } 596 // add minor ticks (for gridlines) 597 double next = Math.pow(this.base, current 598 + this.tickUnit.getSize()); 599 for (int i = 1; i < this.minorTickCount; i++) { 600 double minorV = v + i * ((next - v) / this.minorTickCount); 601 if (range.contains(minorV)) { 602 ticks.add(new NumberTick(TickType.MINOR, minorV, "", 603 textAnchor, TextAnchor.CENTER, 0.0)); 604 } 605 } 606 current = current + this.tickUnit.getSize(); 607 } 608 return ticks; 609 } 610 611 /** 612 * Selects an appropriate tick value for the axis. The strategy is to 613 * display as many ticks as possible (selected from an array of 'standard' 614 * tick units) without the labels overlapping. 615 * 616 * @param g2 the graphics device. 617 * @param dataArea the area defined by the axes. 618 * @param edge the axis location. 619 * 620 * @since 1.0.7 621 */ 622 protected void selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea, 623 RectangleEdge edge) { 624 625 if (RectangleEdge.isTopOrBottom(edge)) { 626 selectHorizontalAutoTickUnit(g2, dataArea, edge); 627 } 628 else if (RectangleEdge.isLeftOrRight(edge)) { 629 selectVerticalAutoTickUnit(g2, dataArea, edge); 630 } 631 632 } 633 634 /** 635 * Selects an appropriate tick value for the axis. The strategy is to 636 * display as many ticks as possible (selected from an array of 'standard' 637 * tick units) without the labels overlapping. 638 * 639 * @param g2 the graphics device. 640 * @param dataArea the area defined by the axes. 641 * @param edge the axis location. 642 * 643 * @since 1.0.7 644 */ 645 protected void selectHorizontalAutoTickUnit(Graphics2D g2, 646 Rectangle2D dataArea, RectangleEdge edge) { 647 648 double tickLabelWidth = estimateMaximumTickLabelWidth(g2, 649 getTickUnit()); 650 651 // start with the current tick unit... 652 TickUnitSource tickUnits = getStandardTickUnits(); 653 TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit()); 654 double unit1Width = exponentLengthToJava2D(unit1.getSize(), dataArea, 655 edge); 656 657 // then extrapolate... 658 double guess = (tickLabelWidth / unit1Width) * unit1.getSize(); 659 660 NumberTickUnit unit2 = (NumberTickUnit) 661 tickUnits.getCeilingTickUnit(guess); 662 double unit2Width = exponentLengthToJava2D(unit2.getSize(), dataArea, 663 edge); 664 665 tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2); 666 if (tickLabelWidth > unit2Width) { 667 unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2); 668 } 669 670 setTickUnit(unit2, false, false); 671 672 } 673 674 /** 675 * Converts a length in data coordinates into the corresponding length in 676 * Java2D coordinates. 677 * 678 * @param length the length. 679 * @param area the plot area. 680 * @param edge the edge along which the axis lies. 681 * 682 * @return The length in Java2D coordinates. 683 * 684 * @since 1.0.7 685 */ 686 public double exponentLengthToJava2D(double length, Rectangle2D area, 687 RectangleEdge edge) { 688 double one = valueToJava2D(calculateValue(1.0), area, edge); 689 double l = valueToJava2D(calculateValue(length + 1.0), area, edge); 690 return Math.abs(l - one); 691 } 692 693 /** 694 * Selects an appropriate tick value for the axis. The strategy is to 695 * display as many ticks as possible (selected from an array of 'standard' 696 * tick units) without the labels overlapping. 697 * 698 * @param g2 the graphics device. 699 * @param dataArea the area in which the plot should be drawn. 700 * @param edge the axis location. 701 * 702 * @since 1.0.7 703 */ 704 protected void selectVerticalAutoTickUnit(Graphics2D g2, 705 Rectangle2D dataArea, 706 RectangleEdge edge) { 707 708 double tickLabelHeight = estimateMaximumTickLabelHeight(g2); 709 710 // start with the current tick unit... 711 TickUnitSource tickUnits = getStandardTickUnits(); 712 TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit()); 713 double unitHeight = exponentLengthToJava2D(unit1.getSize(), dataArea, 714 edge); 715 716 // then extrapolate... 717 double guess = (tickLabelHeight / unitHeight) * unit1.getSize(); 718 719 NumberTickUnit unit2 = (NumberTickUnit) 720 tickUnits.getCeilingTickUnit(guess); 721 double unit2Height = exponentLengthToJava2D(unit2.getSize(), dataArea, 722 edge); 723 724 tickLabelHeight = estimateMaximumTickLabelHeight(g2); 725 if (tickLabelHeight > unit2Height) { 726 unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2); 727 } 728 729 setTickUnit(unit2, false, false); 730 731 } 732 733 /** 734 * Estimates the maximum tick label height. 735 * 736 * @param g2 the graphics device. 737 * 738 * @return The maximum height. 739 * 740 * @since 1.0.7 741 */ 742 protected double estimateMaximumTickLabelHeight(Graphics2D g2) { 743 744 RectangleInsets tickLabelInsets = getTickLabelInsets(); 745 double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom(); 746 747 Font tickLabelFont = getTickLabelFont(); 748 FontRenderContext frc = g2.getFontRenderContext(); 749 result += tickLabelFont.getLineMetrics("123", frc).getHeight(); 750 return result; 751 752 } 753 754 /** 755 * Estimates the maximum width of the tick labels, assuming the specified 756 * tick unit is used. 757 * <P> 758 * Rather than computing the string bounds of every tick on the axis, we 759 * just look at two values: the lower bound and the upper bound for the 760 * axis. These two values will usually be representative. 761 * 762 * @param g2 the graphics device. 763 * @param unit the tick unit to use for calculation. 764 * 765 * @return The estimated maximum width of the tick labels. 766 * 767 * @since 1.0.7 768 */ 769 protected double estimateMaximumTickLabelWidth(Graphics2D g2, 770 TickUnit unit) { 771 772 RectangleInsets tickLabelInsets = getTickLabelInsets(); 773 double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight(); 774 775 if (isVerticalTickLabels()) { 776 // all tick labels have the same width (equal to the height of the 777 // font)... 778 FontRenderContext frc = g2.getFontRenderContext(); 779 LineMetrics lm = getTickLabelFont().getLineMetrics("0", frc); 780 result += lm.getHeight(); 781 } 782 else { 783 // look at lower and upper bounds... 784 FontMetrics fm = g2.getFontMetrics(getTickLabelFont()); 785 Range range = getRange(); 786 double lower = range.getLowerBound(); 787 double upper = range.getUpperBound(); 788 String lowerStr = ""; 789 String upperStr = ""; 790 NumberFormat formatter = getNumberFormatOverride(); 791 if (formatter != null) { 792 lowerStr = formatter.format(lower); 793 upperStr = formatter.format(upper); 794 } 795 else { 796 lowerStr = unit.valueToString(lower); 797 upperStr = unit.valueToString(upper); 798 } 799 double w1 = fm.stringWidth(lowerStr); 800 double w2 = fm.stringWidth(upperStr); 801 result += Math.max(w1, w2); 802 } 803 804 return result; 805 806 } 807 808 /** 809 * Zooms in on the current range. 810 * 811 * @param lowerPercent the new lower bound. 812 * @param upperPercent the new upper bound. 813 */ 814 public void zoomRange(double lowerPercent, double upperPercent) { 815 Range range = getRange(); 816 double start = range.getLowerBound(); 817 double end = range.getUpperBound(); 818 double log1 = calculateLog(start); 819 double log2 = calculateLog(end); 820 double length = log2 - log1; 821 Range adjusted = null; 822 if (isInverted()) { 823 double logA = log1 + length * (1 - upperPercent); 824 double logB = log1 + length * (1 - lowerPercent); 825 adjusted = new Range(calculateValue(logA), calculateValue(logB)); 826 } 827 else { 828 double logA = log1 + length * lowerPercent; 829 double logB = log1 + length * upperPercent; 830 adjusted = new Range(calculateValue(logA), calculateValue(logB)); 831 } 832 setRange(adjusted); 833 } 834 835 /** 836 * Creates a tick label for the specified value. Note that this method 837 * was 'private' prior to version 1.0.10. 838 * 839 * @param value the value. 840 * 841 * @return The label. 842 * 843 * @since 1.0.10 844 */ 845 protected String createTickLabel(double value) { 846 if (this.numberFormatOverride != null) { 847 return this.numberFormatOverride.format(value); 848 } 849 else { 850 return this.tickUnit.valueToString(value); 851 } 852 } 853 854 /** 855 * Tests this axis for equality with an arbitrary object. 856 * 857 * @param obj the object (<code>null</code> permitted). 858 * 859 * @return A boolean. 860 */ 861 public boolean equals(Object obj) { 862 if (obj == this) { 863 return true; 864 } 865 if (!(obj instanceof LogAxis)) { 866 return false; 867 } 868 LogAxis that = (LogAxis) obj; 869 if (this.base != that.base) { 870 return false; 871 } 872 if (this.smallestValue != that.smallestValue) { 873 return false; 874 } 875 if (this.minorTickCount != that.minorTickCount) { 876 return false; 877 } 878 return super.equals(obj); 879 } 880 881 /** 882 * Returns a hash code for this instance. 883 * 884 * @return A hash code. 885 */ 886 public int hashCode() { 887 int result = 193; 888 long temp = Double.doubleToLongBits(this.base); 889 result = 37 * result + (int) (temp ^ (temp >>> 32)); 890 result = 37 * result + this.minorTickCount; 891 temp = Double.doubleToLongBits(this.smallestValue); 892 result = 37 * result + (int) (temp ^ (temp >>> 32)); 893 if (this.numberFormatOverride != null) { 894 result = 37 * result + this.numberFormatOverride.hashCode(); 895 } 896 result = 37 * result + this.tickUnit.hashCode(); 897 return result; 898 } 899 900 /** 901 * Returns a collection of tick units for log (base 10) values. 902 * Uses a given Locale to create the DecimalFormats. 903 * 904 * @param locale the locale to use to represent Numbers. 905 * 906 * @return A collection of tick units for integer values. 907 * 908 * @since 1.0.7 909 */ 910 public static TickUnitSource createLogTickUnits(Locale locale) { 911 912 TickUnits units = new TickUnits(); 913 914 NumberFormat numberFormat = NumberFormat.getNumberInstance(locale); 915 916 units.add(new NumberTickUnit(1, numberFormat)); 917 units.add(new NumberTickUnit(2, numberFormat)); 918 units.add(new NumberTickUnit(5, numberFormat)); 919 units.add(new NumberTickUnit(10, numberFormat)); 920 units.add(new NumberTickUnit(20, numberFormat)); 921 units.add(new NumberTickUnit(50, numberFormat)); 922 units.add(new NumberTickUnit(100, numberFormat)); 923 units.add(new NumberTickUnit(200, numberFormat)); 924 units.add(new NumberTickUnit(500, numberFormat)); 925 units.add(new NumberTickUnit(1000, numberFormat)); 926 units.add(new NumberTickUnit(2000, numberFormat)); 927 units.add(new NumberTickUnit(5000, numberFormat)); 928 units.add(new NumberTickUnit(10000, numberFormat)); 929 units.add(new NumberTickUnit(20000, numberFormat)); 930 units.add(new NumberTickUnit(50000, numberFormat)); 931 units.add(new NumberTickUnit(100000, numberFormat)); 932 units.add(new NumberTickUnit(200000, numberFormat)); 933 units.add(new NumberTickUnit(500000, numberFormat)); 934 units.add(new NumberTickUnit(1000000, numberFormat)); 935 units.add(new NumberTickUnit(2000000, numberFormat)); 936 units.add(new NumberTickUnit(5000000, numberFormat)); 937 units.add(new NumberTickUnit(10000000, numberFormat)); 938 units.add(new NumberTickUnit(20000000, numberFormat)); 939 units.add(new NumberTickUnit(50000000, numberFormat)); 940 units.add(new NumberTickUnit(100000000, numberFormat)); 941 units.add(new NumberTickUnit(200000000, numberFormat)); 942 units.add(new NumberTickUnit(500000000, numberFormat)); 943 units.add(new NumberTickUnit(1000000000, numberFormat)); 944 units.add(new NumberTickUnit(2000000000, numberFormat)); 945 units.add(new NumberTickUnit(5000000000.0, numberFormat)); 946 units.add(new NumberTickUnit(10000000000.0, numberFormat)); 947 948 return units; 949 950 } 951 }