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