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