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 * SymbolAxis.java 029 * --------------- 030 * (C) Copyright 2002-2008, by Anthony Boulestreau and Contributors. 031 * 032 * Original Author: Anthony Boulestreau; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * 036 * Changes 037 * ------- 038 * 29-Mar-2002 : First version (AB); 039 * 19-Apr-2002 : Updated formatting and import statements (DG); 040 * 21-Jun-2002 : Make change to use the class TickUnit - remove valueToString() 041 * method and add SymbolicTickUnit (AB); 042 * 25-Jun-2002 : Removed redundant code (DG); 043 * 25-Jul-2002 : Changed order of parameters in ValueAxis constructor (DG); 044 * 05-Sep-2002 : Updated constructor to reflect changes in the Axis class (DG); 045 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG); 046 * 14-Feb-2003 : Added back missing constructor code (DG); 047 * 26-Mar-2003 : Implemented Serializable (DG); 048 * 14-May-2003 : Renamed HorizontalSymbolicAxis --> SymbolicAxis and merged in 049 * VerticalSymbolicAxis (DG); 050 * 12-Aug-2003 : Fixed bug where refreshTicks() method has different signature 051 * to super class (DG); 052 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG); 053 * 02-Nov-2003 : Added code to avoid overlapping labels (MR); 054 * 07-Nov-2003 : Modified to use new tick classes (DG); 055 * 18-Nov-2003 : Fixed bug where symbols are not being displayed on the 056 * axis (DG); 057 * 24-Nov-2003 : Added fix for gridlines on zooming (bug id 834643) (DG); 058 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG); 059 * 11-Mar-2004 : Modified the way the background grid color is being drawn, see 060 * this thread: 061 * http://www.jfree.org/phpBB2/viewtopic.php?p=22973 (DG); 062 * 16-Mar-2004 : Added plotState to draw() method (DG); 063 * 07-Apr-2004 : Modified string bounds calculation (DG); 064 * 28-Mar-2005 : Renamed autoRangeIncludesZero() --> getAutoRangeIncludesZero() 065 * and autoRangeStickyZero() --> getAutoRangeStickyZero() (DG); 066 * 05-Jul-2005 : Fixed signature on refreshTicks() method - see bug report 067 * 1232264 (DG); 068 * 06-Jul-2005 : Renamed SymbolicAxis --> SymbolAxis, added equals() method, 069 * renamed getSymbolicValue() --> getSymbols(), renamed 070 * symbolicGridPaint --> gridBandPaint, fixed serialization of 071 * gridBandPaint, renamed symbolicGridLinesVisible --> 072 * gridBandsVisible, eliminated symbolicGridLineList (DG); 073 * ------------- JFREECHART 1.0.x --------------------------------------------- 074 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG); 075 * 28-Feb-2007 : Fixed bug 1669302 (tick label overlap) (DG); 076 * 25-Jul-2007 : Added new field for alternate grid band paint (DG); 077 * 15-Aug-2008 : Use alternate grid band paint when drawing (DG); 078 * 079 */ 080 081 package org.jfree.chart.axis; 082 083 import java.awt.BasicStroke; 084 import java.awt.Color; 085 import java.awt.Font; 086 import java.awt.Graphics2D; 087 import java.awt.Paint; 088 import java.awt.Shape; 089 import java.awt.Stroke; 090 import java.awt.geom.Rectangle2D; 091 import java.io.IOException; 092 import java.io.ObjectInputStream; 093 import java.io.ObjectOutputStream; 094 import java.io.Serializable; 095 import java.text.NumberFormat; 096 import java.util.Arrays; 097 import java.util.Iterator; 098 import java.util.List; 099 100 import org.jfree.chart.event.AxisChangeEvent; 101 import org.jfree.chart.plot.Plot; 102 import org.jfree.chart.plot.PlotRenderingInfo; 103 import org.jfree.chart.plot.ValueAxisPlot; 104 import org.jfree.data.Range; 105 import org.jfree.io.SerialUtilities; 106 import org.jfree.text.TextUtilities; 107 import org.jfree.ui.RectangleEdge; 108 import org.jfree.ui.TextAnchor; 109 import org.jfree.util.PaintUtilities; 110 111 /** 112 * A standard linear value axis that replaces integer values with symbols. 113 */ 114 public class SymbolAxis extends NumberAxis implements Serializable { 115 116 /** For serialization. */ 117 private static final long serialVersionUID = 7216330468770619716L; 118 119 /** The default grid band paint. */ 120 public static final Paint DEFAULT_GRID_BAND_PAINT 121 = new Color(232, 234, 232, 128); 122 123 /** 124 * The default paint for alternate grid bands. 125 * 126 * @since 1.0.7 127 */ 128 public static final Paint DEFAULT_GRID_BAND_ALTERNATE_PAINT 129 = new Color(0, 0, 0, 0); // transparent 130 131 /** The list of symbols to display instead of the numeric values. */ 132 private List symbols; 133 134 /** Flag that indicates whether or not grid bands are visible. */ 135 private boolean gridBandsVisible; 136 137 /** The paint used to color the grid bands (if the bands are visible). */ 138 private transient Paint gridBandPaint; 139 140 /** 141 * The paint used to fill the alternate grid bands. 142 * 143 * @since 1.0.7 144 */ 145 private transient Paint gridBandAlternatePaint; 146 147 /** 148 * Constructs a symbol axis, using default attribute values where 149 * necessary. 150 * 151 * @param label the axis label (<code>null</code> permitted). 152 * @param sv the list of symbols to display instead of the numeric 153 * values. 154 */ 155 public SymbolAxis(String label, String[] sv) { 156 super(label); 157 this.symbols = Arrays.asList(sv); 158 this.gridBandsVisible = true; 159 this.gridBandPaint = DEFAULT_GRID_BAND_PAINT; 160 this.gridBandAlternatePaint = DEFAULT_GRID_BAND_ALTERNATE_PAINT; 161 setAutoTickUnitSelection(false, false); 162 setAutoRangeStickyZero(false); 163 164 } 165 166 /** 167 * Returns an array of the symbols for the axis. 168 * 169 * @return The symbols. 170 */ 171 public String[] getSymbols() { 172 String[] result = new String[this.symbols.size()]; 173 result = (String[]) this.symbols.toArray(result); 174 return result; 175 } 176 177 /** 178 * Returns <code>true</code> if the grid bands are showing, and 179 * <code>false</code> otherwise. 180 * 181 * @return <code>true</code> if the grid bands are showing, and 182 * <code>false</code> otherwise. 183 * 184 * @see #setGridBandsVisible(boolean) 185 */ 186 public boolean isGridBandsVisible() { 187 return this.gridBandsVisible; 188 } 189 190 /** 191 * Sets the visibility of the grid bands and notifies registered 192 * listeners that the axis has been modified. 193 * 194 * @param flag the new setting. 195 * 196 * @see #isGridBandsVisible() 197 */ 198 public void setGridBandsVisible(boolean flag) { 199 if (this.gridBandsVisible != flag) { 200 this.gridBandsVisible = flag; 201 notifyListeners(new AxisChangeEvent(this)); 202 } 203 } 204 205 /** 206 * Returns the paint used to color the grid bands. 207 * 208 * @return The grid band paint (never <code>null</code>). 209 * 210 * @see #setGridBandPaint(Paint) 211 * @see #isGridBandsVisible() 212 */ 213 public Paint getGridBandPaint() { 214 return this.gridBandPaint; 215 } 216 217 /** 218 * Sets the grid band paint and sends an {@link AxisChangeEvent} to 219 * all registered listeners. 220 * 221 * @param paint the paint (<code>null</code> not permitted). 222 * 223 * @see #getGridBandPaint() 224 */ 225 public void setGridBandPaint(Paint paint) { 226 if (paint == null) { 227 throw new IllegalArgumentException("Null 'paint' argument."); 228 } 229 this.gridBandPaint = paint; 230 notifyListeners(new AxisChangeEvent(this)); 231 } 232 233 /** 234 * Returns the paint used for alternate grid bands. 235 * 236 * @return The paint (never <code>null</code>). 237 * 238 * @see #setGridBandAlternatePaint(Paint) 239 * @see #getGridBandPaint() 240 * 241 * @since 1.0.7 242 */ 243 public Paint getGridBandAlternatePaint() { 244 return this.gridBandAlternatePaint; 245 } 246 247 /** 248 * Sets the paint used for alternate grid bands and sends a 249 * {@link AxisChangeEvent} to all registered listeners. 250 * 251 * @param paint the paint (<code>null</code> not permitted). 252 * 253 * @see #getGridBandAlternatePaint() 254 * @see #setGridBandPaint(Paint) 255 * 256 * @since 1.0.7 257 */ 258 public void setGridBandAlternatePaint(Paint paint) { 259 if (paint == null) { 260 throw new IllegalArgumentException("Null 'paint' argument."); 261 } 262 this.gridBandAlternatePaint = paint; 263 notifyListeners(new AxisChangeEvent(this)); 264 } 265 266 /** 267 * This operation is not supported by this axis. 268 * 269 * @param g2 the graphics device. 270 * @param dataArea the area in which the plot and axes should be drawn. 271 * @param edge the edge along which the axis is drawn. 272 */ 273 protected void selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea, 274 RectangleEdge edge) { 275 throw new UnsupportedOperationException(); 276 } 277 278 /** 279 * Draws the axis on a Java 2D graphics device (such as the screen or a 280 * printer). 281 * 282 * @param g2 the graphics device (<code>null</code> not permitted). 283 * @param cursor the cursor location. 284 * @param plotArea the area within which the plot and axes should be drawn 285 * (<code>null</code> not permitted). 286 * @param dataArea the area within which the data should be drawn 287 * (<code>null</code> not permitted). 288 * @param edge the axis location (<code>null</code> not permitted). 289 * @param plotState collects information about the plot 290 * (<code>null</code> permitted). 291 * 292 * @return The axis state (never <code>null</code>). 293 */ 294 public AxisState draw(Graphics2D g2, 295 double cursor, 296 Rectangle2D plotArea, 297 Rectangle2D dataArea, 298 RectangleEdge edge, 299 PlotRenderingInfo plotState) { 300 301 AxisState info = new AxisState(cursor); 302 if (isVisible()) { 303 info = super.draw(g2, cursor, plotArea, dataArea, edge, plotState); 304 } 305 if (this.gridBandsVisible) { 306 drawGridBands(g2, plotArea, dataArea, edge, info.getTicks()); 307 } 308 return info; 309 310 } 311 312 /** 313 * Draws the grid bands. Alternate bands are colored using 314 * <CODE>gridBandPaint<CODE> (<CODE>DEFAULT_GRID_BAND_PAINT</CODE> by 315 * default). 316 * 317 * @param g2 the graphics device. 318 * @param plotArea the area within which the chart should be drawn. 319 * @param dataArea the area within which the plot should be drawn (a 320 * subset of the drawArea). 321 * @param edge the axis location. 322 * @param ticks the ticks. 323 */ 324 protected void drawGridBands(Graphics2D g2, 325 Rectangle2D plotArea, 326 Rectangle2D dataArea, 327 RectangleEdge edge, 328 List ticks) { 329 330 Shape savedClip = g2.getClip(); 331 g2.clip(dataArea); 332 if (RectangleEdge.isTopOrBottom(edge)) { 333 drawGridBandsHorizontal(g2, plotArea, dataArea, true, ticks); 334 } 335 else if (RectangleEdge.isLeftOrRight(edge)) { 336 drawGridBandsVertical(g2, plotArea, dataArea, true, ticks); 337 } 338 g2.setClip(savedClip); 339 340 } 341 342 /** 343 * Draws the grid bands for the axis when it is at the top or bottom of 344 * the plot. 345 * 346 * @param g2 the graphics device. 347 * @param plotArea the area within which the chart should be drawn. 348 * @param dataArea the area within which the plot should be drawn 349 * (a subset of the drawArea). 350 * @param firstGridBandIsDark True: the first grid band takes the 351 * color of <CODE>gridBandPaint<CODE>. 352 * False: the second grid band takes the 353 * color of <CODE>gridBandPaint<CODE>. 354 * @param ticks the ticks. 355 */ 356 protected void drawGridBandsHorizontal(Graphics2D g2, 357 Rectangle2D plotArea, 358 Rectangle2D dataArea, 359 boolean firstGridBandIsDark, 360 List ticks) { 361 362 boolean currentGridBandIsDark = firstGridBandIsDark; 363 double yy = dataArea.getY(); 364 double xx1, xx2; 365 366 //gets the outline stroke width of the plot 367 double outlineStrokeWidth; 368 if (getPlot().getOutlineStroke() != null) { 369 outlineStrokeWidth 370 = ((BasicStroke) getPlot().getOutlineStroke()).getLineWidth(); 371 } 372 else { 373 outlineStrokeWidth = 1d; 374 } 375 376 Iterator iterator = ticks.iterator(); 377 ValueTick tick; 378 Rectangle2D band; 379 while (iterator.hasNext()) { 380 tick = (ValueTick) iterator.next(); 381 xx1 = valueToJava2D(tick.getValue() - 0.5d, dataArea, 382 RectangleEdge.BOTTOM); 383 xx2 = valueToJava2D(tick.getValue() + 0.5d, dataArea, 384 RectangleEdge.BOTTOM); 385 if (currentGridBandIsDark) { 386 g2.setPaint(this.gridBandPaint); 387 } 388 else { 389 g2.setPaint(this.gridBandAlternatePaint); 390 } 391 band = new Rectangle2D.Double(xx1, yy + outlineStrokeWidth, 392 xx2 - xx1, dataArea.getMaxY() - yy - outlineStrokeWidth); 393 g2.fill(band); 394 currentGridBandIsDark = !currentGridBandIsDark; 395 } 396 g2.setPaintMode(); 397 } 398 399 /** 400 * Draws the grid bands for the axis when it is at the top or bottom of 401 * the plot. 402 * 403 * @param g2 the graphics device. 404 * @param drawArea the area within which the chart should be drawn. 405 * @param plotArea the area within which the plot should be drawn (a 406 * subset of the drawArea). 407 * @param firstGridBandIsDark True: the first grid band takes the 408 * color of <CODE>gridBandPaint<CODE>. 409 * False: the second grid band takes the 410 * color of <CODE>gridBandPaint<CODE>. 411 * @param ticks a list of ticks. 412 */ 413 protected void drawGridBandsVertical(Graphics2D g2, 414 Rectangle2D drawArea, 415 Rectangle2D plotArea, 416 boolean firstGridBandIsDark, 417 List ticks) { 418 419 boolean currentGridBandIsDark = firstGridBandIsDark; 420 double xx = plotArea.getX(); 421 double yy1, yy2; 422 423 //gets the outline stroke width of the plot 424 double outlineStrokeWidth; 425 Stroke outlineStroke = getPlot().getOutlineStroke(); 426 if (outlineStroke != null && outlineStroke instanceof BasicStroke) { 427 outlineStrokeWidth = ((BasicStroke) outlineStroke).getLineWidth(); 428 } 429 else { 430 outlineStrokeWidth = 1d; 431 } 432 433 Iterator iterator = ticks.iterator(); 434 ValueTick tick; 435 Rectangle2D band; 436 while (iterator.hasNext()) { 437 tick = (ValueTick) iterator.next(); 438 yy1 = valueToJava2D(tick.getValue() + 0.5d, plotArea, 439 RectangleEdge.LEFT); 440 yy2 = valueToJava2D(tick.getValue() - 0.5d, plotArea, 441 RectangleEdge.LEFT); 442 if (currentGridBandIsDark) { 443 g2.setPaint(this.gridBandPaint); 444 } 445 else { 446 g2.setPaint(this.gridBandAlternatePaint); 447 } 448 band = new Rectangle2D.Double(xx + outlineStrokeWidth, yy1, 449 plotArea.getMaxX() - xx - outlineStrokeWidth, yy2 - yy1); 450 g2.fill(band); 451 currentGridBandIsDark = !currentGridBandIsDark; 452 } 453 g2.setPaintMode(); 454 } 455 456 /** 457 * Rescales the axis to ensure that all data is visible. 458 */ 459 protected void autoAdjustRange() { 460 461 Plot plot = getPlot(); 462 if (plot == null) { 463 return; // no plot, no data 464 } 465 466 if (plot instanceof ValueAxisPlot) { 467 468 // ensure that all the symbols are displayed 469 double upper = this.symbols.size() - 1; 470 double lower = 0; 471 double range = upper - lower; 472 473 // ensure the autorange is at least <minRange> in size... 474 double minRange = getAutoRangeMinimumSize(); 475 if (range < minRange) { 476 upper = (upper + lower + minRange) / 2; 477 lower = (upper + lower - minRange) / 2; 478 } 479 480 // this ensure that the grid bands will be displayed correctly. 481 double upperMargin = 0.5; 482 double lowerMargin = 0.5; 483 484 if (getAutoRangeIncludesZero()) { 485 if (getAutoRangeStickyZero()) { 486 if (upper <= 0.0) { 487 upper = 0.0; 488 } 489 else { 490 upper = upper + upperMargin; 491 } 492 if (lower >= 0.0) { 493 lower = 0.0; 494 } 495 else { 496 lower = lower - lowerMargin; 497 } 498 } 499 else { 500 upper = Math.max(0.0, upper + upperMargin); 501 lower = Math.min(0.0, lower - lowerMargin); 502 } 503 } 504 else { 505 if (getAutoRangeStickyZero()) { 506 if (upper <= 0.0) { 507 upper = Math.min(0.0, upper + upperMargin); 508 } 509 else { 510 upper = upper + upperMargin * range; 511 } 512 if (lower >= 0.0) { 513 lower = Math.max(0.0, lower - lowerMargin); 514 } 515 else { 516 lower = lower - lowerMargin; 517 } 518 } 519 else { 520 upper = upper + upperMargin; 521 lower = lower - lowerMargin; 522 } 523 } 524 525 setRange(new Range(lower, upper), false, false); 526 527 } 528 529 } 530 531 /** 532 * Calculates the positions of the tick labels for the axis, storing the 533 * results in the tick label list (ready for drawing). 534 * 535 * @param g2 the graphics device. 536 * @param state the axis state. 537 * @param dataArea the area in which the data should be drawn. 538 * @param edge the location of the axis. 539 * 540 * @return A list of ticks. 541 */ 542 public List refreshTicks(Graphics2D g2, 543 AxisState state, 544 Rectangle2D dataArea, 545 RectangleEdge edge) { 546 List ticks = null; 547 if (RectangleEdge.isTopOrBottom(edge)) { 548 ticks = refreshTicksHorizontal(g2, dataArea, edge); 549 } 550 else if (RectangleEdge.isLeftOrRight(edge)) { 551 ticks = refreshTicksVertical(g2, dataArea, edge); 552 } 553 return ticks; 554 } 555 556 /** 557 * Calculates the positions of the tick labels for the axis, storing the 558 * results in the tick label list (ready for drawing). 559 * 560 * @param g2 the graphics device. 561 * @param dataArea the area in which the data should be drawn. 562 * @param edge the location of the axis. 563 * 564 * @return The ticks. 565 */ 566 protected List refreshTicksHorizontal(Graphics2D g2, 567 Rectangle2D dataArea, 568 RectangleEdge edge) { 569 570 List ticks = new java.util.ArrayList(); 571 572 Font tickLabelFont = getTickLabelFont(); 573 g2.setFont(tickLabelFont); 574 575 double size = getTickUnit().getSize(); 576 int count = calculateVisibleTickCount(); 577 double lowestTickValue = calculateLowestVisibleTickValue(); 578 579 double previousDrawnTickLabelPos = 0.0; 580 double previousDrawnTickLabelLength = 0.0; 581 582 if (count <= ValueAxis.MAXIMUM_TICK_COUNT) { 583 for (int i = 0; i < count; i++) { 584 double currentTickValue = lowestTickValue + (i * size); 585 double xx = valueToJava2D(currentTickValue, dataArea, edge); 586 String tickLabel; 587 NumberFormat formatter = getNumberFormatOverride(); 588 if (formatter != null) { 589 tickLabel = formatter.format(currentTickValue); 590 } 591 else { 592 tickLabel = valueToString(currentTickValue); 593 } 594 595 // avoid to draw overlapping tick labels 596 Rectangle2D bounds = TextUtilities.getTextBounds(tickLabel, g2, 597 g2.getFontMetrics()); 598 double tickLabelLength = isVerticalTickLabels() 599 ? bounds.getHeight() : bounds.getWidth(); 600 boolean tickLabelsOverlapping = false; 601 if (i > 0) { 602 double avgTickLabelLength = (previousDrawnTickLabelLength 603 + tickLabelLength) / 2.0; 604 if (Math.abs(xx - previousDrawnTickLabelPos) 605 < avgTickLabelLength) { 606 tickLabelsOverlapping = true; 607 } 608 } 609 if (tickLabelsOverlapping) { 610 tickLabel = ""; // don't draw this tick label 611 } 612 else { 613 // remember these values for next comparison 614 previousDrawnTickLabelPos = xx; 615 previousDrawnTickLabelLength = tickLabelLength; 616 } 617 618 TextAnchor anchor = null; 619 TextAnchor rotationAnchor = null; 620 double angle = 0.0; 621 if (isVerticalTickLabels()) { 622 anchor = TextAnchor.CENTER_RIGHT; 623 rotationAnchor = TextAnchor.CENTER_RIGHT; 624 if (edge == RectangleEdge.TOP) { 625 angle = Math.PI / 2.0; 626 } 627 else { 628 angle = -Math.PI / 2.0; 629 } 630 } 631 else { 632 if (edge == RectangleEdge.TOP) { 633 anchor = TextAnchor.BOTTOM_CENTER; 634 rotationAnchor = TextAnchor.BOTTOM_CENTER; 635 } 636 else { 637 anchor = TextAnchor.TOP_CENTER; 638 rotationAnchor = TextAnchor.TOP_CENTER; 639 } 640 } 641 Tick tick = new NumberTick(new Double(currentTickValue), 642 tickLabel, anchor, rotationAnchor, angle); 643 ticks.add(tick); 644 } 645 } 646 return ticks; 647 648 } 649 650 /** 651 * Calculates the positions of the tick labels for the axis, storing the 652 * results in the tick label list (ready for drawing). 653 * 654 * @param g2 the graphics device. 655 * @param dataArea the area in which the plot should be drawn. 656 * @param edge the location of the axis. 657 * 658 * @return The ticks. 659 */ 660 protected List refreshTicksVertical(Graphics2D g2, 661 Rectangle2D dataArea, 662 RectangleEdge edge) { 663 664 List ticks = new java.util.ArrayList(); 665 666 Font tickLabelFont = getTickLabelFont(); 667 g2.setFont(tickLabelFont); 668 669 double size = getTickUnit().getSize(); 670 int count = calculateVisibleTickCount(); 671 double lowestTickValue = calculateLowestVisibleTickValue(); 672 673 double previousDrawnTickLabelPos = 0.0; 674 double previousDrawnTickLabelLength = 0.0; 675 676 if (count <= ValueAxis.MAXIMUM_TICK_COUNT) { 677 for (int i = 0; i < count; i++) { 678 double currentTickValue = lowestTickValue + (i * size); 679 double yy = valueToJava2D(currentTickValue, dataArea, edge); 680 String tickLabel; 681 NumberFormat formatter = getNumberFormatOverride(); 682 if (formatter != null) { 683 tickLabel = formatter.format(currentTickValue); 684 } 685 else { 686 tickLabel = valueToString(currentTickValue); 687 } 688 689 // avoid to draw overlapping tick labels 690 Rectangle2D bounds = TextUtilities.getTextBounds(tickLabel, g2, 691 g2.getFontMetrics()); 692 double tickLabelLength = isVerticalTickLabels() 693 ? bounds.getWidth() : bounds.getHeight(); 694 boolean tickLabelsOverlapping = false; 695 if (i > 0) { 696 double avgTickLabelLength = (previousDrawnTickLabelLength 697 + tickLabelLength) / 2.0; 698 if (Math.abs(yy - previousDrawnTickLabelPos) 699 < avgTickLabelLength) { 700 tickLabelsOverlapping = true; 701 } 702 } 703 if (tickLabelsOverlapping) { 704 tickLabel = ""; // don't draw this tick label 705 } 706 else { 707 // remember these values for next comparison 708 previousDrawnTickLabelPos = yy; 709 previousDrawnTickLabelLength = tickLabelLength; 710 } 711 712 TextAnchor anchor = null; 713 TextAnchor rotationAnchor = null; 714 double angle = 0.0; 715 if (isVerticalTickLabels()) { 716 anchor = TextAnchor.BOTTOM_CENTER; 717 rotationAnchor = TextAnchor.BOTTOM_CENTER; 718 if (edge == RectangleEdge.LEFT) { 719 angle = -Math.PI / 2.0; 720 } 721 else { 722 angle = Math.PI / 2.0; 723 } 724 } 725 else { 726 if (edge == RectangleEdge.LEFT) { 727 anchor = TextAnchor.CENTER_RIGHT; 728 rotationAnchor = TextAnchor.CENTER_RIGHT; 729 } 730 else { 731 anchor = TextAnchor.CENTER_LEFT; 732 rotationAnchor = TextAnchor.CENTER_LEFT; 733 } 734 } 735 Tick tick = new NumberTick(new Double(currentTickValue), 736 tickLabel, anchor, rotationAnchor, angle); 737 ticks.add(tick); 738 } 739 } 740 return ticks; 741 742 } 743 744 /** 745 * Converts a value to a string, using the list of symbols. 746 * 747 * @param value value to convert. 748 * 749 * @return The symbol. 750 */ 751 public String valueToString(double value) { 752 String strToReturn; 753 try { 754 strToReturn = (String) this.symbols.get((int) value); 755 } 756 catch (IndexOutOfBoundsException ex) { 757 strToReturn = ""; 758 } 759 return strToReturn; 760 } 761 762 /** 763 * Tests this axis for equality with an arbitrary object. 764 * 765 * @param obj the object (<code>null</code> permitted). 766 * 767 * @return A boolean. 768 */ 769 public boolean equals(Object obj) { 770 if (obj == this) { 771 return true; 772 } 773 if (!(obj instanceof SymbolAxis)) { 774 return false; 775 } 776 SymbolAxis that = (SymbolAxis) obj; 777 if (!this.symbols.equals(that.symbols)) { 778 return false; 779 } 780 if (this.gridBandsVisible != that.gridBandsVisible) { 781 return false; 782 } 783 if (!PaintUtilities.equal(this.gridBandPaint, that.gridBandPaint)) { 784 return false; 785 } 786 if (!PaintUtilities.equal(this.gridBandAlternatePaint, 787 that.gridBandAlternatePaint)) { 788 return false; 789 } 790 return super.equals(obj); 791 } 792 793 /** 794 * Provides serialization support. 795 * 796 * @param stream the output stream. 797 * 798 * @throws IOException if there is an I/O error. 799 */ 800 private void writeObject(ObjectOutputStream stream) throws IOException { 801 stream.defaultWriteObject(); 802 SerialUtilities.writePaint(this.gridBandPaint, stream); 803 SerialUtilities.writePaint(this.gridBandAlternatePaint, stream); 804 } 805 806 /** 807 * Provides serialization support. 808 * 809 * @param stream the input stream. 810 * 811 * @throws IOException if there is an I/O error. 812 * @throws ClassNotFoundException if there is a classpath problem. 813 */ 814 private void readObject(ObjectInputStream stream) 815 throws IOException, ClassNotFoundException { 816 stream.defaultReadObject(); 817 this.gridBandPaint = SerialUtilities.readPaint(stream); 818 this.gridBandAlternatePaint = SerialUtilities.readPaint(stream); 819 } 820 821 }