Source for org.jfree.chart.axis.SymbolAxis

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