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