1 /* =========================================================== 2 * JFreeChart : a free chart library for the Java(tm) platform 3 * =========================================================== 4 * 5 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors. 6 * 7 * Project Info: http://www.jfree.org/jfreechart/index.html 8 * 9 * This library is free software; you can redistribute it and/or modify it 10 * under the terms of the GNU Lesser General Public License as published by 11 * the Free Software Foundation; either version 2.1 of the License, or 12 * (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, but 15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17 * License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this library; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22 * USA. 23 * 24 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 25 * Other names may be trademarks of their respective owners.] 26 * 27 * --------------- 28 * SymbolAxis.java 29 * --------------- 30 * (C) Copyright 2002-2013, by Anthony Boulestreau and Contributors. 31 * 32 * Original Author: Anthony Boulestreau; 33 * Contributor(s): David Gilbert (for Object Refinery Limited); 34 * 35 * 36 * Changes 37 * ------- 38 * 29-Mar-2002 : First version (AB); 39 * 19-Apr-2002 : Updated formatting and import statements (DG); 40 * 21-Jun-2002 : Make change to use the class TickUnit - remove valueToString() 41 * method and add SymbolicTickUnit (AB); 42 * 25-Jun-2002 : Removed redundant code (DG); 43 * 25-Jul-2002 : Changed order of parameters in ValueAxis constructor (DG); 44 * 05-Sep-2002 : Updated constructor to reflect changes in the Axis class (DG); 45 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG); 46 * 14-Feb-2003 : Added back missing constructor code (DG); 47 * 26-Mar-2003 : Implemented Serializable (DG); 48 * 14-May-2003 : Renamed HorizontalSymbolicAxis --> SymbolicAxis and merged in 49 * VerticalSymbolicAxis (DG); 50 * 12-Aug-2003 : Fixed bug where refreshTicks() method has different signature 51 * to super class (DG); 52 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG); 53 * 02-Nov-2003 : Added code to avoid overlapping labels (MR); 54 * 07-Nov-2003 : Modified to use new tick classes (DG); 55 * 18-Nov-2003 : Fixed bug where symbols are not being displayed on the 56 * axis (DG); 57 * 24-Nov-2003 : Added fix for gridlines on zooming (bug id 834643) (DG); 58 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG); 59 * 11-Mar-2004 : Modified the way the background grid color is being drawn, see 60 * this thread: 61 * http://www.jfree.org/phpBB2/viewtopic.php?p=22973 (DG); 62 * 16-Mar-2004 : Added plotState to draw() method (DG); 63 * 07-Apr-2004 : Modified string bounds calculation (DG); 64 * 28-Mar-2005 : Renamed autoRangeIncludesZero() --> getAutoRangeIncludesZero() 65 * and autoRangeStickyZero() --> getAutoRangeStickyZero() (DG); 66 * 05-Jul-2005 : Fixed signature on refreshTicks() method - see bug report 67 * 1232264 (DG); 68 * 06-Jul-2005 : Renamed SymbolicAxis --> SymbolAxis, added equals() method, 69 * renamed getSymbolicValue() --> getSymbols(), renamed 70 * symbolicGridPaint --> gridBandPaint, fixed serialization of 71 * gridBandPaint, renamed symbolicGridLinesVisible --> 72 * gridBandsVisible, eliminated symbolicGridLineList (DG); 73 * ------------- JFREECHART 1.0.x --------------------------------------------- 74 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG); 75 * 28-Feb-2007 : Fixed bug 1669302 (tick label overlap) (DG); 76 * 25-Jul-2007 : Added new field for alternate grid band paint (DG); 77 * 15-Aug-2008 : Use alternate grid band paint when drawing (DG); 78 * 02-Jul-2013 : Use ParamChecks (DG); 79 * 80 */ 81 82 package org.jfree.chart.axis; 83 84 import java.awt.BasicStroke; 85 import java.awt.Color; 86 import java.awt.Font; 87 import java.awt.Graphics2D; 88 import java.awt.Paint; 89 import java.awt.Shape; 90 import java.awt.Stroke; 91 import java.awt.geom.Rectangle2D; 92 import java.io.IOException; 93 import java.io.ObjectInputStream; 94 import java.io.ObjectOutputStream; 95 import java.io.Serializable; 96 import java.text.NumberFormat; 97 import java.util.Arrays; 98 import java.util.Iterator; 99 import java.util.List; 100 101 import org.jfree.chart.event.AxisChangeEvent; 102 import org.jfree.chart.plot.Plot; 103 import org.jfree.chart.plot.PlotRenderingInfo; 104 import org.jfree.chart.plot.ValueAxisPlot; 105 import org.jfree.chart.util.ParamChecks; 106 import org.jfree.data.Range; 107 import org.jfree.io.SerialUtilities; 108 import org.jfree.text.TextUtilities; 109 import org.jfree.ui.RectangleEdge; 110 import org.jfree.ui.TextAnchor; 111 import org.jfree.util.PaintUtilities; 112 113 /** 114 * A standard linear value axis that replaces integer values with symbols. 115 */ 116 public class SymbolAxis extends NumberAxis implements Serializable { 117 118 /** For serialization. */ 119 private static final long serialVersionUID = 7216330468770619716L; 120 121 /** The default grid band paint. */ 122 public static final Paint DEFAULT_GRID_BAND_PAINT 123 = new Color(232, 234, 232, 128); 124 125 /** 126 * The default paint for alternate grid bands. 127 * 128 * @since 1.0.7 129 */ 130 public static final Paint DEFAULT_GRID_BAND_ALTERNATE_PAINT 131 = new Color(0, 0, 0, 0); // transparent 132 133 /** The list of symbols to display instead of the numeric values. */ 134 private List symbols; 135 136 /** Flag that indicates whether or not grid bands are visible. */ 137 private boolean gridBandsVisible; 138 139 /** The paint used to color the grid bands (if the bands are visible). */ 140 private transient Paint gridBandPaint; 141 142 /** 143 * The paint used to fill the alternate grid bands. 144 * 145 * @since 1.0.7 146 */ 147 private transient Paint gridBandAlternatePaint; 148 149 /** 150 * Constructs a symbol axis, using default attribute values where 151 * necessary. 152 * 153 * @param label the axis label (<code>null</code> permitted). 154 * @param sv the list of symbols to display instead of the numeric 155 * values. 156 */ SymbolAxis(String label, String[] sv)157 public SymbolAxis(String label, String[] sv) { 158 super(label); 159 this.symbols = Arrays.asList(sv); 160 this.gridBandsVisible = true; 161 this.gridBandPaint = DEFAULT_GRID_BAND_PAINT; 162 this.gridBandAlternatePaint = DEFAULT_GRID_BAND_ALTERNATE_PAINT; 163 setAutoTickUnitSelection(false, false); 164 setAutoRangeStickyZero(false); 165 166 } 167 168 /** 169 * Returns an array of the symbols for the axis. 170 * 171 * @return The symbols. 172 */ getSymbols()173 public String[] getSymbols() { 174 String[] result = new String[this.symbols.size()]; 175 result = (String[]) this.symbols.toArray(result); 176 return result; 177 } 178 179 /** 180 * Returns <code>true</code> if the grid bands are showing, and 181 * <code>false</code> otherwise. 182 * 183 * @return <code>true</code> if the grid bands are showing, and 184 * <code>false</code> otherwise. 185 * 186 * @see #setGridBandsVisible(boolean) 187 */ isGridBandsVisible()188 public boolean isGridBandsVisible() { 189 return this.gridBandsVisible; 190 } 191 192 /** 193 * Sets the visibility of the grid bands and notifies registered 194 * listeners that the axis has been modified. 195 * 196 * @param flag the new setting. 197 * 198 * @see #isGridBandsVisible() 199 */ setGridBandsVisible(boolean flag)200 public void setGridBandsVisible(boolean flag) { 201 if (this.gridBandsVisible != flag) { 202 this.gridBandsVisible = flag; 203 fireChangeEvent(); 204 } 205 } 206 207 /** 208 * Returns the paint used to color the grid bands. 209 * 210 * @return The grid band paint (never <code>null</code>). 211 * 212 * @see #setGridBandPaint(Paint) 213 * @see #isGridBandsVisible() 214 */ getGridBandPaint()215 public Paint getGridBandPaint() { 216 return this.gridBandPaint; 217 } 218 219 /** 220 * Sets the grid band paint and sends an {@link AxisChangeEvent} to 221 * all registered listeners. 222 * 223 * @param paint the paint (<code>null</code> not permitted). 224 * 225 * @see #getGridBandPaint() 226 */ setGridBandPaint(Paint paint)227 public void setGridBandPaint(Paint paint) { 228 ParamChecks.nullNotPermitted(paint, "paint"); 229 this.gridBandPaint = paint; 230 fireChangeEvent(); 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 */ getGridBandAlternatePaint()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 */ setGridBandAlternatePaint(Paint paint)258 public void setGridBandAlternatePaint(Paint paint) { 259 ParamChecks.nullNotPermitted(paint, "paint"); 260 this.gridBandAlternatePaint = paint; 261 fireChangeEvent(); 262 } 263 264 /** 265 * This operation is not supported by this axis. 266 * 267 * @param g2 the graphics device. 268 * @param dataArea the area in which the plot and axes should be drawn. 269 * @param edge the edge along which the axis is drawn. 270 */ 271 @Override selectAutoTickUnit(Graphics2D g2, Rectangle2D dataArea, RectangleEdge edge)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 @Override draw(Graphics2D g2, double cursor, Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge, PlotRenderingInfo plotState)294 public AxisState draw(Graphics2D g2, double cursor, Rectangle2D plotArea, 295 Rectangle2D dataArea, RectangleEdge edge, 296 PlotRenderingInfo plotState) { 297 298 AxisState info = new AxisState(cursor); 299 if (isVisible()) { 300 info = super.draw(g2, cursor, plotArea, dataArea, edge, plotState); 301 } 302 if (this.gridBandsVisible) { 303 drawGridBands(g2, plotArea, dataArea, edge, info.getTicks()); 304 } 305 return info; 306 307 } 308 309 /** 310 * Draws the grid bands. Alternate bands are colored using 311 * <CODE>gridBandPaint</CODE> (<CODE>DEFAULT_GRID_BAND_PAINT</CODE> by 312 * default). 313 * 314 * @param g2 the graphics device. 315 * @param plotArea the area within which the chart should be drawn. 316 * @param dataArea the area within which the plot should be drawn (a 317 * subset of the drawArea). 318 * @param edge the axis location. 319 * @param ticks the ticks. 320 */ drawGridBands(Graphics2D g2, Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge, List ticks)321 protected void drawGridBands(Graphics2D g2, Rectangle2D plotArea, 322 Rectangle2D dataArea, RectangleEdge edge, List ticks) { 323 324 Shape savedClip = g2.getClip(); 325 g2.clip(dataArea); 326 if (RectangleEdge.isTopOrBottom(edge)) { 327 drawGridBandsHorizontal(g2, plotArea, dataArea, true, ticks); 328 } 329 else if (RectangleEdge.isLeftOrRight(edge)) { 330 drawGridBandsVertical(g2, plotArea, dataArea, true, ticks); 331 } 332 g2.setClip(savedClip); 333 334 } 335 336 /** 337 * Draws the grid bands for the axis when it is at the top or bottom of 338 * the plot. 339 * 340 * @param g2 the graphics device. 341 * @param plotArea the area within which the chart should be drawn. 342 * @param dataArea the area within which the plot should be drawn 343 * (a subset of the drawArea). 344 * @param firstGridBandIsDark True: the first grid band takes the 345 * color of <CODE>gridBandPaint</CODE>. 346 * False: the second grid band takes the 347 * color of <CODE>gridBandPaint</CODE>. 348 * @param ticks the ticks. 349 */ drawGridBandsHorizontal(Graphics2D g2, Rectangle2D plotArea, Rectangle2D dataArea, boolean firstGridBandIsDark, List ticks)350 protected void drawGridBandsHorizontal(Graphics2D g2, 351 Rectangle2D plotArea, Rectangle2D dataArea, 352 boolean firstGridBandIsDark, List ticks) { 353 354 boolean currentGridBandIsDark = firstGridBandIsDark; 355 double yy = dataArea.getY(); 356 double xx1, xx2; 357 358 //gets the outline stroke width of the plot 359 double outlineStrokeWidth; 360 if (getPlot().getOutlineStroke() != null) { 361 outlineStrokeWidth 362 = ((BasicStroke) getPlot().getOutlineStroke()).getLineWidth(); 363 } 364 else { 365 outlineStrokeWidth = 1d; 366 } 367 368 Iterator iterator = ticks.iterator(); 369 ValueTick tick; 370 Rectangle2D band; 371 while (iterator.hasNext()) { 372 tick = (ValueTick) iterator.next(); 373 xx1 = valueToJava2D(tick.getValue() - 0.5d, dataArea, 374 RectangleEdge.BOTTOM); 375 xx2 = valueToJava2D(tick.getValue() + 0.5d, dataArea, 376 RectangleEdge.BOTTOM); 377 if (currentGridBandIsDark) { 378 g2.setPaint(this.gridBandPaint); 379 } 380 else { 381 g2.setPaint(this.gridBandAlternatePaint); 382 } 383 band = new Rectangle2D.Double(xx1, yy + outlineStrokeWidth, 384 xx2 - xx1, dataArea.getMaxY() - yy - outlineStrokeWidth); 385 g2.fill(band); 386 currentGridBandIsDark = !currentGridBandIsDark; 387 } 388 g2.setPaintMode(); 389 } 390 391 /** 392 * Draws the grid bands for the axis when it is at the top or bottom of 393 * the plot. 394 * 395 * @param g2 the graphics device. 396 * @param drawArea the area within which the chart should be drawn. 397 * @param plotArea the area within which the plot should be drawn (a 398 * subset of the drawArea). 399 * @param firstGridBandIsDark True: the first grid band takes the 400 * color of <CODE>gridBandPaint</CODE>. 401 * False: the second grid band takes the 402 * color of <CODE>gridBandPaint</CODE>. 403 * @param ticks a list of ticks. 404 */ drawGridBandsVertical(Graphics2D g2, Rectangle2D drawArea, Rectangle2D plotArea, boolean firstGridBandIsDark, List ticks)405 protected void drawGridBandsVertical(Graphics2D g2, Rectangle2D drawArea, 406 Rectangle2D plotArea, boolean firstGridBandIsDark, List ticks) { 407 408 boolean currentGridBandIsDark = firstGridBandIsDark; 409 double xx = plotArea.getX(); 410 double yy1, yy2; 411 412 //gets the outline stroke width of the plot 413 double outlineStrokeWidth; 414 Stroke outlineStroke = getPlot().getOutlineStroke(); 415 if (outlineStroke != null && outlineStroke instanceof BasicStroke) { 416 outlineStrokeWidth = ((BasicStroke) outlineStroke).getLineWidth(); 417 } 418 else { 419 outlineStrokeWidth = 1d; 420 } 421 422 Iterator iterator = ticks.iterator(); 423 ValueTick tick; 424 Rectangle2D band; 425 while (iterator.hasNext()) { 426 tick = (ValueTick) iterator.next(); 427 yy1 = valueToJava2D(tick.getValue() + 0.5d, plotArea, 428 RectangleEdge.LEFT); 429 yy2 = valueToJava2D(tick.getValue() - 0.5d, plotArea, 430 RectangleEdge.LEFT); 431 if (currentGridBandIsDark) { 432 g2.setPaint(this.gridBandPaint); 433 } 434 else { 435 g2.setPaint(this.gridBandAlternatePaint); 436 } 437 band = new Rectangle2D.Double(xx + outlineStrokeWidth, yy1, 438 plotArea.getMaxX() - xx - outlineStrokeWidth, yy2 - yy1); 439 g2.fill(band); 440 currentGridBandIsDark = !currentGridBandIsDark; 441 } 442 g2.setPaintMode(); 443 } 444 445 /** 446 * Rescales the axis to ensure that all data is visible. 447 */ 448 @Override autoAdjustRange()449 protected void autoAdjustRange() { 450 451 Plot plot = getPlot(); 452 if (plot == null) { 453 return; // no plot, no data 454 } 455 456 if (plot instanceof ValueAxisPlot) { 457 458 // ensure that all the symbols are displayed 459 double upper = this.symbols.size() - 1; 460 double lower = 0; 461 double range = upper - lower; 462 463 // ensure the autorange is at least <minRange> in size... 464 double minRange = getAutoRangeMinimumSize(); 465 if (range < minRange) { 466 upper = (upper + lower + minRange) / 2; 467 lower = (upper + lower - minRange) / 2; 468 } 469 470 // this ensure that the grid bands will be displayed correctly. 471 double upperMargin = 0.5; 472 double lowerMargin = 0.5; 473 474 if (getAutoRangeIncludesZero()) { 475 if (getAutoRangeStickyZero()) { 476 if (upper <= 0.0) { 477 upper = 0.0; 478 } 479 else { 480 upper = upper + upperMargin; 481 } 482 if (lower >= 0.0) { 483 lower = 0.0; 484 } 485 else { 486 lower = lower - lowerMargin; 487 } 488 } 489 else { 490 upper = Math.max(0.0, upper + upperMargin); 491 lower = Math.min(0.0, lower - lowerMargin); 492 } 493 } 494 else { 495 if (getAutoRangeStickyZero()) { 496 if (upper <= 0.0) { 497 upper = Math.min(0.0, upper + upperMargin); 498 } 499 else { 500 upper = upper + upperMargin * range; 501 } 502 if (lower >= 0.0) { 503 lower = Math.max(0.0, lower - lowerMargin); 504 } 505 else { 506 lower = lower - lowerMargin; 507 } 508 } 509 else { 510 upper = upper + upperMargin; 511 lower = lower - lowerMargin; 512 } 513 } 514 515 setRange(new Range(lower, upper), false, false); 516 517 } 518 519 } 520 521 /** 522 * Calculates the positions of the tick labels for the axis, storing the 523 * results in the tick label list (ready for drawing). 524 * 525 * @param g2 the graphics device. 526 * @param state the axis state. 527 * @param dataArea the area in which the data should be drawn. 528 * @param edge the location of the axis. 529 * 530 * @return A list of ticks. 531 */ 532 @Override refreshTicks(Graphics2D g2, AxisState state, Rectangle2D dataArea, RectangleEdge edge)533 public List refreshTicks(Graphics2D g2, AxisState state, 534 Rectangle2D dataArea, RectangleEdge edge) { 535 List ticks = null; 536 if (RectangleEdge.isTopOrBottom(edge)) { 537 ticks = refreshTicksHorizontal(g2, dataArea, edge); 538 } 539 else if (RectangleEdge.isLeftOrRight(edge)) { 540 ticks = refreshTicksVertical(g2, dataArea, edge); 541 } 542 return ticks; 543 } 544 545 /** 546 * Calculates the positions of the tick labels for the axis, storing the 547 * results in the tick label list (ready for drawing). 548 * 549 * @param g2 the graphics device. 550 * @param dataArea the area in which the data should be drawn. 551 * @param edge the location of the axis. 552 * 553 * @return The ticks. 554 */ 555 @Override refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea, RectangleEdge edge)556 protected List refreshTicksHorizontal(Graphics2D g2, Rectangle2D dataArea, 557 RectangleEdge edge) { 558 559 List ticks = new java.util.ArrayList(); 560 561 Font tickLabelFont = getTickLabelFont(); 562 g2.setFont(tickLabelFont); 563 564 double size = getTickUnit().getSize(); 565 int count = calculateVisibleTickCount(); 566 double lowestTickValue = calculateLowestVisibleTickValue(); 567 568 double previousDrawnTickLabelPos = 0.0; 569 double previousDrawnTickLabelLength = 0.0; 570 571 if (count <= ValueAxis.MAXIMUM_TICK_COUNT) { 572 for (int i = 0; i < count; i++) { 573 double currentTickValue = lowestTickValue + (i * size); 574 double xx = valueToJava2D(currentTickValue, dataArea, edge); 575 String tickLabel; 576 NumberFormat formatter = getNumberFormatOverride(); 577 if (formatter != null) { 578 tickLabel = formatter.format(currentTickValue); 579 } 580 else { 581 tickLabel = valueToString(currentTickValue); 582 } 583 584 // avoid to draw overlapping tick labels 585 Rectangle2D bounds = TextUtilities.getTextBounds(tickLabel, g2, 586 g2.getFontMetrics()); 587 double tickLabelLength = isVerticalTickLabels() 588 ? bounds.getHeight() : bounds.getWidth(); 589 boolean tickLabelsOverlapping = false; 590 if (i > 0) { 591 double avgTickLabelLength = (previousDrawnTickLabelLength 592 + tickLabelLength) / 2.0; 593 if (Math.abs(xx - previousDrawnTickLabelPos) 594 < avgTickLabelLength) { 595 tickLabelsOverlapping = true; 596 } 597 } 598 if (tickLabelsOverlapping) { 599 tickLabel = ""; // don't draw this tick label 600 } 601 else { 602 // remember these values for next comparison 603 previousDrawnTickLabelPos = xx; 604 previousDrawnTickLabelLength = tickLabelLength; 605 } 606 607 TextAnchor anchor; 608 TextAnchor rotationAnchor; 609 double angle = 0.0; 610 if (isVerticalTickLabels()) { 611 anchor = TextAnchor.CENTER_RIGHT; 612 rotationAnchor = TextAnchor.CENTER_RIGHT; 613 if (edge == RectangleEdge.TOP) { 614 angle = Math.PI / 2.0; 615 } 616 else { 617 angle = -Math.PI / 2.0; 618 } 619 } 620 else { 621 if (edge == RectangleEdge.TOP) { 622 anchor = TextAnchor.BOTTOM_CENTER; 623 rotationAnchor = TextAnchor.BOTTOM_CENTER; 624 } 625 else { 626 anchor = TextAnchor.TOP_CENTER; 627 rotationAnchor = TextAnchor.TOP_CENTER; 628 } 629 } 630 Tick tick = new NumberTick(new Double(currentTickValue), 631 tickLabel, anchor, rotationAnchor, angle); 632 ticks.add(tick); 633 } 634 } 635 return ticks; 636 637 } 638 639 /** 640 * Calculates the positions of the tick labels for the axis, storing the 641 * results in the tick label list (ready for drawing). 642 * 643 * @param g2 the graphics device. 644 * @param dataArea the area in which the plot should be drawn. 645 * @param edge the location of the axis. 646 * 647 * @return The ticks. 648 */ 649 @Override refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea, RectangleEdge edge)650 protected List refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea, 651 RectangleEdge edge) { 652 653 List ticks = new java.util.ArrayList(); 654 655 Font tickLabelFont = getTickLabelFont(); 656 g2.setFont(tickLabelFont); 657 658 double size = getTickUnit().getSize(); 659 int count = calculateVisibleTickCount(); 660 double lowestTickValue = calculateLowestVisibleTickValue(); 661 662 double previousDrawnTickLabelPos = 0.0; 663 double previousDrawnTickLabelLength = 0.0; 664 665 if (count <= ValueAxis.MAXIMUM_TICK_COUNT) { 666 for (int i = 0; i < count; i++) { 667 double currentTickValue = lowestTickValue + (i * size); 668 double yy = valueToJava2D(currentTickValue, dataArea, edge); 669 String tickLabel; 670 NumberFormat formatter = getNumberFormatOverride(); 671 if (formatter != null) { 672 tickLabel = formatter.format(currentTickValue); 673 } 674 else { 675 tickLabel = valueToString(currentTickValue); 676 } 677 678 // avoid to draw overlapping tick labels 679 Rectangle2D bounds = TextUtilities.getTextBounds(tickLabel, g2, 680 g2.getFontMetrics()); 681 double tickLabelLength = isVerticalTickLabels() 682 ? bounds.getWidth() : bounds.getHeight(); 683 boolean tickLabelsOverlapping = false; 684 if (i > 0) { 685 double avgTickLabelLength = (previousDrawnTickLabelLength 686 + tickLabelLength) / 2.0; 687 if (Math.abs(yy - previousDrawnTickLabelPos) 688 < avgTickLabelLength) { 689 tickLabelsOverlapping = true; 690 } 691 } 692 if (tickLabelsOverlapping) { 693 tickLabel = ""; // don't draw this tick label 694 } 695 else { 696 // remember these values for next comparison 697 previousDrawnTickLabelPos = yy; 698 previousDrawnTickLabelLength = tickLabelLength; 699 } 700 701 TextAnchor anchor; 702 TextAnchor rotationAnchor; 703 double angle = 0.0; 704 if (isVerticalTickLabels()) { 705 anchor = TextAnchor.BOTTOM_CENTER; 706 rotationAnchor = TextAnchor.BOTTOM_CENTER; 707 if (edge == RectangleEdge.LEFT) { 708 angle = -Math.PI / 2.0; 709 } 710 else { 711 angle = Math.PI / 2.0; 712 } 713 } 714 else { 715 if (edge == RectangleEdge.LEFT) { 716 anchor = TextAnchor.CENTER_RIGHT; 717 rotationAnchor = TextAnchor.CENTER_RIGHT; 718 } 719 else { 720 anchor = TextAnchor.CENTER_LEFT; 721 rotationAnchor = TextAnchor.CENTER_LEFT; 722 } 723 } 724 Tick tick = new NumberTick(new Double(currentTickValue), 725 tickLabel, anchor, rotationAnchor, angle); 726 ticks.add(tick); 727 } 728 } 729 return ticks; 730 731 } 732 733 /** 734 * Converts a value to a string, using the list of symbols. 735 * 736 * @param value value to convert. 737 * 738 * @return The symbol. 739 */ valueToString(double value)740 public String valueToString(double value) { 741 String strToReturn; 742 try { 743 strToReturn = (String) this.symbols.get((int) value); 744 } 745 catch (IndexOutOfBoundsException ex) { 746 strToReturn = ""; 747 } 748 return strToReturn; 749 } 750 751 /** 752 * Tests this axis for equality with an arbitrary object. 753 * 754 * @param obj the object (<code>null</code> permitted). 755 * 756 * @return A boolean. 757 */ 758 @Override equals(Object obj)759 public boolean equals(Object obj) { 760 if (obj == this) { 761 return true; 762 } 763 if (!(obj instanceof SymbolAxis)) { 764 return false; 765 } 766 SymbolAxis that = (SymbolAxis) obj; 767 if (!this.symbols.equals(that.symbols)) { 768 return false; 769 } 770 if (this.gridBandsVisible != that.gridBandsVisible) { 771 return false; 772 } 773 if (!PaintUtilities.equal(this.gridBandPaint, that.gridBandPaint)) { 774 return false; 775 } 776 if (!PaintUtilities.equal(this.gridBandAlternatePaint, 777 that.gridBandAlternatePaint)) { 778 return false; 779 } 780 return super.equals(obj); 781 } 782 783 /** 784 * Provides serialization support. 785 * 786 * @param stream the output stream. 787 * 788 * @throws IOException if there is an I/O error. 789 */ writeObject(ObjectOutputStream stream)790 private void writeObject(ObjectOutputStream stream) throws IOException { 791 stream.defaultWriteObject(); 792 SerialUtilities.writePaint(this.gridBandPaint, stream); 793 SerialUtilities.writePaint(this.gridBandAlternatePaint, stream); 794 } 795 796 /** 797 * Provides serialization support. 798 * 799 * @param stream the input stream. 800 * 801 * @throws IOException if there is an I/O error. 802 * @throws ClassNotFoundException if there is a classpath problem. 803 */ readObject(ObjectInputStream stream)804 private void readObject(ObjectInputStream stream) 805 throws IOException, ClassNotFoundException { 806 stream.defaultReadObject(); 807 this.gridBandPaint = SerialUtilities.readPaint(stream); 808 this.gridBandAlternatePaint = SerialUtilities.readPaint(stream); 809 } 810 811 } 812