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