Source for org.jfree.chart.axis.ValueAxis

   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:  * ValueAxis.java
  29:  * --------------
  30:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Jonathan Nash;
  34:  *                   Nicolas Brodu (for Astrium and EADS Corporate Research 
  35:  *                   Center);
  36:  *
  37:  * $Id: ValueAxis.java,v 1.10.2.1 2005/10/25 20:37:34 mungady Exp $
  38:  *
  39:  * Changes (from 18-Sep-2001)
  40:  * --------------------------
  41:  * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
  42:  * 23-Nov-2001 : Overhauled standard tick unit code (DG);
  43:  * 04-Dec-2001 : Changed constructors to protected, and tidied up default 
  44:  *               values (DG);
  45:  * 12-Dec-2001 : Fixed vertical gridlines bug (DG);
  46:  * 16-Jan-2002 : Added an optional crosshair, based on the implementation by 
  47:  *               Jonathan Nash (DG);
  48:  * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis, 
  49:  *               and changed the type from Number to double (DG);
  50:  * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange 
  51:  *               from public to protected. Updated import statements (DG);
  52:  * 23-Apr-2002 : Added setRange() method (DG);
  53:  * 29-Apr-2002 : Added range adjustment methods (DG);
  54:  * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the
  55:  *               crosshairs are visible, to avoid unnecessary repaints, as 
  56:  *               suggested by Kees Kuip (DG);
  57:  * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis 
  58:  *               class (DG);
  59:  * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
  60:  * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  61:  * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG);
  62:  * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
  63:  * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
  64:  * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to 
  65:  *               ValueAxis (DG);
  66:  * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed 
  67:  *               immediately (DG);
  68:  * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
  69:  * 20-Jan-2003 : Replaced monolithic constructor (DG);
  70:  * 26-Mar-2003 : Implemented Serializable (DG);
  71:  * 09-May-2003 : Added AxisLocation parameter to translation methods (DG);
  72:  * 13-Aug-2003 : Implemented Cloneable (DG);
  73:  * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG);
  74:  * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG);
  75:  * 08-Sep-2003 : Completed Serialization support (NB);
  76:  * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound,
  77:  *               and get/setMaximumValue --> get/setUpperBound (DG);
  78:  * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID 
  79:  *               829606 (DG);
  80:  * 07-Nov-2003 : Changes to tick mechanism (DG);
  81:  * 06-Jan-2004 : Moved axis line attributes to Axis class (DG);
  82:  * 21-Jan-2004 : Removed redundant axisLineVisible attribute.  Renamed 
  83:  *               translateJava2DToValue --> java2DToValue, and 
  84:  *               translateValueToJava2D --> valueToJava2D (DG); 
  85:  * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no 
  86:  *               effect (andreas.gawecki@coremedia.com);
  87:  * 07-Apr-2004 : Changed text bounds calculation (DG);
  88:  * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG);
  89:  * 18-May-2004 : Added methods to set axis range *including* current 
  90:  *               margins (DG);
  91:  * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG);
  92:  * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities 
  93:  *               --> TextUtilities (DG);
  94:  * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 
  95:  *               release (DG);
  96:  * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
  97:  *
  98:  */
  99: 
 100: package org.jfree.chart.axis;
 101: 
 102: import java.awt.Font;
 103: import java.awt.FontMetrics;
 104: import java.awt.Graphics2D;
 105: import java.awt.Polygon;
 106: import java.awt.Shape;
 107: import java.awt.font.LineMetrics;
 108: import java.awt.geom.AffineTransform;
 109: import java.awt.geom.Line2D;
 110: import java.awt.geom.Rectangle2D;
 111: import java.io.IOException;
 112: import java.io.ObjectInputStream;
 113: import java.io.ObjectOutputStream;
 114: import java.io.Serializable;
 115: import java.util.Iterator;
 116: import java.util.List;
 117: 
 118: import org.jfree.chart.event.AxisChangeEvent;
 119: import org.jfree.chart.plot.Plot;
 120: import org.jfree.data.Range;
 121: import org.jfree.io.SerialUtilities;
 122: import org.jfree.text.TextUtilities;
 123: import org.jfree.ui.RectangleEdge;
 124: import org.jfree.ui.RectangleInsets;
 125: import org.jfree.util.ObjectUtilities;
 126: import org.jfree.util.PublicCloneable;
 127: 
 128: /**
 129:  * The base class for axes that display value data, where values are measured 
 130:  * using the <code>double</code> primitive.  The two key subclasses are 
 131:  * {@link DateAxis} and {@link NumberAxis}.
 132:  */
 133: public abstract class ValueAxis extends Axis 
 134:                                 implements Cloneable, PublicCloneable, 
 135:                                            Serializable {
 136: 
 137:     /** For serialization. */
 138:     private static final long serialVersionUID = 3698345477322391456L;
 139:     
 140:     /** The default axis range. */
 141:     public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
 142: 
 143:     /** The default auto-range value. */
 144:     public static final boolean DEFAULT_AUTO_RANGE = true;
 145: 
 146:     /** The default inverted flag setting. */
 147:     public static final boolean DEFAULT_INVERTED = false;
 148: 
 149:     /** The default minimum auto range. */
 150:     public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
 151: 
 152:     /** The default value for the lower margin (0.05 = 5%). */
 153:     public static final double DEFAULT_LOWER_MARGIN = 0.05;
 154: 
 155:     /** The default value for the upper margin (0.05 = 5%). */
 156:     public static final double DEFAULT_UPPER_MARGIN = 0.05;
 157: 
 158:     /** The default lower bound for the axis. */
 159:     public static final double DEFAULT_LOWER_BOUND = 0.0;
 160: 
 161:     /** The default upper bound for the axis. */
 162:     public static final double DEFAULT_UPPER_BOUND = 1.0;
 163: 
 164:     /** The default auto-tick-unit-selection value. */
 165:     public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
 166: 
 167:     /** The maximum tick count. */
 168:     public static final int MAXIMUM_TICK_COUNT = 500;
 169:     
 170:     /** 
 171:      * A flag that controls whether an arrow is drawn at the positive end of 
 172:      * the axis line. 
 173:      */
 174:     private boolean positiveArrowVisible;
 175:     
 176:     /** 
 177:      * A flag that controls whether an arrow is drawn at the negative end of 
 178:      * the axis line. 
 179:      */
 180:     private boolean negativeArrowVisible;
 181:     
 182:     /** The shape used for an up arrow. */
 183:     private transient Shape upArrow;
 184:     
 185:     /** The shape used for a down arrow. */
 186:     private transient Shape downArrow;
 187:     
 188:     /** The shape used for a left arrow. */
 189:     private transient Shape leftArrow;
 190:     
 191:     /** The shape used for a right arrow. */
 192:     private transient Shape rightArrow;
 193:     
 194:     /** A flag that affects the orientation of the values on the axis. */
 195:     private boolean inverted;
 196: 
 197:     /** The axis range. */
 198:     private Range range;
 199: 
 200:     /** 
 201:      * Flag that indicates whether the axis automatically scales to fit the 
 202:      * chart data. 
 203:      */
 204:     private boolean autoRange;
 205: 
 206:     /** The minimum size for the 'auto' axis range (excluding margins). */
 207:     private double autoRangeMinimumSize;
 208: 
 209:     /**
 210:      * The upper margin percentage.  This indicates the amount by which the 
 211:      * maximum axis value exceeds the maximum data value (as a percentage of 
 212:      * the range on the axis) when the axis range is determined automatically.
 213:      */
 214:     private double upperMargin;
 215: 
 216:     /**
 217:      * The lower margin.  This is a percentage that indicates the amount by
 218:      * which the minimum axis value is "less than" the minimum data value when
 219:      * the axis range is determined automatically.
 220:      */
 221:     private double lowerMargin;
 222: 
 223:     /**
 224:      * If this value is positive, the amount is subtracted from the maximum
 225:      * data value to determine the lower axis range.  This can be used to
 226:      * provide a fixed "window" on dynamic data.
 227:      */
 228:     private double fixedAutoRange;
 229: 
 230:     /** 
 231:      * Flag that indicates whether or not the tick unit is selected 
 232:      * automatically. 
 233:      */
 234:     private boolean autoTickUnitSelection;
 235: 
 236:     /** The standard tick units for the axis. */
 237:     private TickUnitSource standardTickUnits;
 238: 
 239:     /** An index into an array of standard tick values. */
 240:     private int autoTickIndex;
 241:     
 242:     /** A flag indicating whether or not tick labels are rotated to vertical. */
 243:     private boolean verticalTickLabels;
 244: 
 245:     /**
 246:      * Constructs a value axis.
 247:      *
 248:      * @param label  the axis label.
 249:      * @param standardTickUnits  the source for standard tick units 
 250:      *                           (<code>null</code> permitted).
 251:      */
 252:     protected ValueAxis(String label, TickUnitSource standardTickUnits) {
 253: 
 254:         super(label);
 255: 
 256:         this.positiveArrowVisible = false;
 257:         this.negativeArrowVisible = false;
 258: 
 259:         this.range = DEFAULT_RANGE;
 260:         this.autoRange = DEFAULT_AUTO_RANGE;
 261: 
 262:         this.inverted = DEFAULT_INVERTED;
 263:         this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
 264: 
 265:         this.lowerMargin = DEFAULT_LOWER_MARGIN;
 266:         this.upperMargin = DEFAULT_UPPER_MARGIN;
 267: 
 268:         this.fixedAutoRange = 0.0;
 269: 
 270:         this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
 271:         this.standardTickUnits = standardTickUnits;
 272:         
 273:         Polygon p1 = new Polygon();
 274:         p1.addPoint(0, 0);
 275:         p1.addPoint(-2, 2);
 276:         p1.addPoint(2, 2);
 277:         
 278:         this.upArrow = p1;
 279: 
 280:         Polygon p2 = new Polygon();
 281:         p2.addPoint(0, 0);
 282:         p2.addPoint(-2, -2);
 283:         p2.addPoint(2, -2);
 284: 
 285:         this.downArrow = p2;
 286: 
 287:         Polygon p3 = new Polygon();
 288:         p3.addPoint(0, 0);
 289:         p3.addPoint(-2, -2);
 290:         p3.addPoint(-2, 2);
 291:         
 292:         this.rightArrow = p3;
 293: 
 294:         Polygon p4 = new Polygon();
 295:         p4.addPoint(0, 0);
 296:         p4.addPoint(2, -2);
 297:         p4.addPoint(2, 2);
 298: 
 299:         this.leftArrow = p4;
 300:         
 301:         this.verticalTickLabels = false;
 302:         
 303:     }
 304: 
 305:     /**
 306:      * Returns <code>true</code> if the tick labels should be rotated (to 
 307:      * vertical), and <code>false</code> otherwise.
 308:      *
 309:      * @return <code>true</code> or <code>false</code>.
 310:      */
 311:     public boolean isVerticalTickLabels() {
 312:         return this.verticalTickLabels;
 313:     }
 314: 
 315:     /**
 316:      * Sets the flag that controls whether the tick labels are displayed 
 317:      * vertically (that is, rotated 90 degrees from horizontal).  If the flag 
 318:      * is changed, an {@link AxisChangeEvent} is sent to all registered 
 319:      * listeners.
 320:      *
 321:      * @param flag  the flag.
 322:      */
 323:     public void setVerticalTickLabels(boolean flag) {
 324:         if (this.verticalTickLabels != flag) {
 325:             this.verticalTickLabels = flag;
 326:             notifyListeners(new AxisChangeEvent(this));
 327:         }
 328:     }
 329: 
 330:     /**
 331:      * Returns a flag that controls whether or not the axis line has an arrow 
 332:      * drawn that points in the positive direction for the axis.
 333:      * 
 334:      * @return A boolean.
 335:      */
 336:     public boolean isPositiveArrowVisible() {
 337:         return this.positiveArrowVisible;
 338:     }
 339:     
 340:     /**
 341:      * Sets a flag that controls whether or not the axis lines has an arrow 
 342:      * drawn that points in the positive direction for the axis, and sends an 
 343:      * {@link AxisChangeEvent} to all registered listeners.
 344:      * 
 345:      * @param visible  the flag.
 346:      */
 347:     public void setPositiveArrowVisible(boolean visible) {
 348:         this.positiveArrowVisible = visible;
 349:         notifyListeners(new AxisChangeEvent(this));
 350:     }
 351:     
 352:     /**
 353:      * Returns a flag that controls whether or not the axis line has an arrow 
 354:      * drawn that points in the negative direction for the axis.
 355:      * 
 356:      * @return A boolean.
 357:      */
 358:     public boolean isNegativeArrowVisible() {
 359:         return this.negativeArrowVisible;
 360:     }
 361:     
 362:     /**
 363:      * Sets a flag that controls whether or not the axis lines has an arrow 
 364:      * drawn that points in the negative direction for the axis, and sends an 
 365:      * {@link AxisChangeEvent} to all registered listeners.
 366:      * 
 367:      * @param visible  the flag.
 368:      */
 369:     public void setNegativeArrowVisible(boolean visible) {
 370:         this.negativeArrowVisible = visible;
 371:         notifyListeners(new AxisChangeEvent(this));
 372:     }
 373:     
 374:     /**
 375:      * Returns a shape that can be displayed as an arrow pointing upwards at 
 376:      * the end of an axis line.
 377:      * 
 378:      * @return A shape (never <code>null</code>).
 379:      */
 380:     public Shape getUpArrow() {
 381:         return this.upArrow;   
 382:     }
 383:     
 384:     /**
 385:      * Sets the shape that can be displayed as an arrow pointing upwards at 
 386:      * the end of an axis line and sends an {@link AxisChangeEvent} to all 
 387:      * registered listeners.
 388:      * 
 389:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 390:      */
 391:     public void setUpArrow(Shape arrow) {
 392:         if (arrow == null) {
 393:             throw new IllegalArgumentException("Null 'arrow' argument.");   
 394:         }
 395:         this.upArrow = arrow;
 396:         notifyListeners(new AxisChangeEvent(this));
 397:     }
 398:     
 399:     /**
 400:      * Returns a shape that can be displayed as an arrow pointing downwards at 
 401:      * the end of an axis line.
 402:      * 
 403:      * @return A shape (never <code>null</code>).
 404:      */
 405:     public Shape getDownArrow() {
 406:         return this.downArrow;   
 407:     }
 408:     
 409:     /**
 410:      * Sets the shape that can be displayed as an arrow pointing downwards at 
 411:      * the end of an axis line and sends an {@link AxisChangeEvent} to all 
 412:      * registered listeners.
 413:      * 
 414:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 415:      */
 416:     public void setDownArrow(Shape arrow) {
 417:         if (arrow == null) {
 418:             throw new IllegalArgumentException("Null 'arrow' argument.");   
 419:         }
 420:         this.downArrow = arrow;
 421:         notifyListeners(new AxisChangeEvent(this));
 422:     }
 423:     
 424:     /**
 425:      * Returns a shape that can be displayed as an arrow pointing left at the 
 426:      * end of an axis line.
 427:      * 
 428:      * @return A shape (never <code>null</code>).
 429:      */
 430:     public Shape getLeftArrow() {
 431:         return this.leftArrow;   
 432:     }
 433:     
 434:     /**
 435:      * Sets the shape that can be displayed as an arrow pointing left at the 
 436:      * end of an axis line and sends an {@link AxisChangeEvent} to all 
 437:      * registered listeners.
 438:      * 
 439:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 440:      */
 441:     public void setLeftArrow(Shape arrow) {
 442:         if (arrow == null) {
 443:             throw new IllegalArgumentException("Null 'arrow' argument.");   
 444:         }
 445:         this.leftArrow = arrow;
 446:         notifyListeners(new AxisChangeEvent(this));
 447:     }
 448:     
 449:     /**
 450:      * Returns a shape that can be displayed as an arrow pointing right at the 
 451:      * end of an axis line.
 452:      * 
 453:      * @return A shape (never <code>null</code>).
 454:      */
 455:     public Shape getRightArrow() {
 456:         return this.rightArrow;   
 457:     }
 458:     
 459:     /**
 460:      * Sets the shape that can be displayed as an arrow pointing rightwards at 
 461:      * the end of an axis line and sends an {@link AxisChangeEvent} to all 
 462:      * registered listeners.
 463:      * 
 464:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 465:      */
 466:     public void setRightArrow(Shape arrow) {
 467:         if (arrow == null) {
 468:             throw new IllegalArgumentException("Null 'arrow' argument.");   
 469:         }
 470:         this.rightArrow = arrow;
 471:         notifyListeners(new AxisChangeEvent(this));
 472:     }
 473:     
 474:     /**
 475:      * Draws an axis line at the current cursor position and edge.
 476:      * 
 477:      * @param g2  the graphics device.
 478:      * @param cursor  the cursor position.
 479:      * @param dataArea  the data area.
 480:      * @param edge  the edge.
 481:      */
 482:     protected void drawAxisLine(Graphics2D g2, double cursor,
 483:                                 Rectangle2D dataArea, RectangleEdge edge) {
 484:         Line2D axisLine = null;
 485:         if (edge == RectangleEdge.TOP) {
 486:             axisLine = new Line2D.Double(
 487:                 dataArea.getX(), cursor, dataArea.getMaxX(), cursor
 488:             );  
 489:         }
 490:         else if (edge == RectangleEdge.BOTTOM) {
 491:             axisLine = new Line2D.Double(
 492:                 dataArea.getX(), cursor, dataArea.getMaxX(), cursor
 493:             );  
 494:         }
 495:         else if (edge == RectangleEdge.LEFT) {
 496:             axisLine = new Line2D.Double(
 497:                 cursor, dataArea.getY(), cursor, dataArea.getMaxY()
 498:             );  
 499:         }
 500:         else if (edge == RectangleEdge.RIGHT) {
 501:             axisLine = new Line2D.Double(
 502:                 cursor, dataArea.getY(), cursor, dataArea.getMaxY()
 503:             );  
 504:         }
 505:         g2.setPaint(getAxisLinePaint());
 506:         g2.setStroke(getAxisLineStroke());
 507:         g2.draw(axisLine);
 508:         
 509:         boolean drawUpOrRight = false;  
 510:         boolean drawDownOrLeft = false;
 511:         if (this.positiveArrowVisible) {
 512:             if (this.inverted) {
 513:                 drawDownOrLeft = true;   
 514:             }
 515:             else {
 516:                 drawUpOrRight = true;   
 517:             }
 518:         }
 519:         if (this.negativeArrowVisible) {
 520:             if (this.inverted) {
 521:                 drawUpOrRight = true;   
 522:             }
 523:             else {
 524:                 drawDownOrLeft = true;   
 525:             }
 526:         }
 527:         if (drawUpOrRight) {
 528:             double x = 0.0;
 529:             double y = 0.0;
 530:             Shape arrow = null;
 531:             if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
 532:                 x = dataArea.getMaxX();
 533:                 y = cursor;
 534:                 arrow = this.rightArrow; 
 535:             }
 536:             else if (edge == RectangleEdge.LEFT 
 537:                     || edge == RectangleEdge.RIGHT) {
 538:                 x = cursor;
 539:                 y = dataArea.getMinY();
 540:                 arrow = this.upArrow; 
 541:             }
 542: 
 543:             // draw the arrow...
 544:             AffineTransform transformer = new AffineTransform();
 545:             transformer.setToTranslation(x, y);
 546:             Shape shape = transformer.createTransformedShape(arrow);
 547:             g2.fill(shape);
 548:             g2.draw(shape);
 549:         }
 550:         
 551:         if (drawDownOrLeft) {
 552:             double x = 0.0;
 553:             double y = 0.0;
 554:             Shape arrow = null;
 555:             if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
 556:                 x = dataArea.getMinX();
 557:                 y = cursor;
 558:                 arrow = this.leftArrow; 
 559:             }
 560:             else if (edge == RectangleEdge.LEFT 
 561:                     || edge == RectangleEdge.RIGHT) {
 562:                 x = cursor;
 563:                 y = dataArea.getMaxY();
 564:                 arrow = this.downArrow; 
 565:             }
 566: 
 567:             // draw the arrow...
 568:             AffineTransform transformer = new AffineTransform();
 569:             transformer.setToTranslation(x, y);
 570:             Shape shape = transformer.createTransformedShape(arrow);
 571:             g2.fill(shape);
 572:             g2.draw(shape);
 573:         }
 574:         
 575:     }
 576:     
 577:     /**
 578:      * Calculates the anchor point for a tick label.
 579:      * 
 580:      * @param tick  the tick.
 581:      * @param cursor  the cursor.
 582:      * @param dataArea  the data area.
 583:      * @param edge  the edge on which the axis is drawn.
 584:      * 
 585:      * @return The x and y coordinates of the anchor point.
 586:      */
 587:     protected float[] calculateAnchorPoint(ValueTick tick, 
 588:                                            double cursor, 
 589:                                            Rectangle2D dataArea, 
 590:                                            RectangleEdge edge) {
 591:     
 592:         RectangleInsets insets = getTickLabelInsets();
 593:         float[] result = new float[2];
 594:         if (edge == RectangleEdge.TOP) {
 595:             result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 596:             result[1] = (float) (cursor - insets.getBottom() - 2.0);
 597:         }
 598:         else if (edge == RectangleEdge.BOTTOM) {
 599:             result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 600:             result[1] = (float) (cursor + insets.getTop() + 2.0); 
 601:         }
 602:         else if (edge == RectangleEdge.LEFT) {
 603:             result[0] = (float) (cursor - insets.getLeft() - 2.0);    
 604:             result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 605:         }
 606:         else if (edge == RectangleEdge.RIGHT) {
 607:             result[0] = (float) (cursor + insets.getRight() + 2.0);    
 608:             result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 609:         }
 610:         return result;
 611:     }
 612:     
 613:     /**
 614:      * Draws the axis line, tick marks and tick mark labels.
 615:      * 
 616:      * @param g2  the graphics device.
 617:      * @param cursor  the cursor.
 618:      * @param plotArea  the plot area.
 619:      * @param dataArea  the data area.
 620:      * @param edge  the edge that the axis is aligned with.
 621:      * 
 622:      * @return The width or height used to draw the axis.
 623:      */
 624:     protected AxisState drawTickMarksAndLabels(Graphics2D g2, 
 625:                                                double cursor,
 626:                                                Rectangle2D plotArea,
 627:                                                Rectangle2D dataArea, 
 628:                                                RectangleEdge edge) {
 629:                                               
 630:         AxisState state = new AxisState(cursor);
 631: 
 632:         if (isAxisLineVisible()) {
 633:             drawAxisLine(g2, cursor, dataArea, edge);
 634:         }
 635: 
 636:         double ol = getTickMarkOutsideLength();
 637:         double il = getTickMarkInsideLength();
 638: 
 639:         List ticks = refreshTicks(g2, state, dataArea, edge);
 640:         state.setTicks(ticks);
 641:         g2.setFont(getTickLabelFont());
 642:         Iterator iterator = ticks.iterator();
 643:         while (iterator.hasNext()) {
 644:             ValueTick tick = (ValueTick) iterator.next();
 645:             if (isTickLabelsVisible()) {
 646:                 g2.setPaint(getTickLabelPaint());
 647:                 float[] anchorPoint = calculateAnchorPoint(
 648:                     tick, cursor, dataArea, edge
 649:                 );
 650:                 TextUtilities.drawRotatedString(
 651:                     tick.getText(), g2, 
 652:                     anchorPoint[0], anchorPoint[1],
 653:                     tick.getTextAnchor(), 
 654:                     tick.getAngle(),
 655:                     tick.getRotationAnchor()
 656:                 );
 657:             }
 658: 
 659:             if (isTickMarksVisible()) {
 660:                 float xx = (float) valueToJava2D(
 661:                     tick.getValue(), dataArea, edge
 662:                 );
 663:                 Line2D mark = null;
 664:                 g2.setStroke(getTickMarkStroke());
 665:                 g2.setPaint(getTickMarkPaint());
 666:                 if (edge == RectangleEdge.LEFT) {
 667:                     mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx);
 668:                 }
 669:                 else if (edge == RectangleEdge.RIGHT) {
 670:                     mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx);
 671:                 }
 672:                 else if (edge == RectangleEdge.TOP) {
 673:                     mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il);
 674:                 }
 675:                 else if (edge == RectangleEdge.BOTTOM) {
 676:                     mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il);
 677:                 }
 678:                 g2.draw(mark);
 679:             }
 680:         }
 681:         
 682:         // need to work out the space used by the tick labels...
 683:         // so we can update the cursor...
 684:         double used = 0.0;
 685:         if (isTickLabelsVisible()) {
 686:             if (edge == RectangleEdge.LEFT) {
 687:                 used += findMaximumTickLabelWidth(
 688:                     ticks, g2, plotArea, isVerticalTickLabels()
 689:                 );  
 690:                 state.cursorLeft(used);      
 691:             }
 692:             else if (edge == RectangleEdge.RIGHT) {
 693:                 used = findMaximumTickLabelWidth(
 694:                     ticks, g2, plotArea, isVerticalTickLabels()
 695:                 );
 696:                 state.cursorRight(used);      
 697:             }
 698:             else if (edge == RectangleEdge.TOP) {
 699:                 used = findMaximumTickLabelHeight(
 700:                     ticks, g2, plotArea, isVerticalTickLabels()
 701:                 );
 702:                 state.cursorUp(used);
 703:             }
 704:             else if (edge == RectangleEdge.BOTTOM) {
 705:                 used = findMaximumTickLabelHeight(
 706:                     ticks, g2, plotArea, isVerticalTickLabels()
 707:                 );
 708:                 state.cursorDown(used);
 709:             }
 710:         }
 711:        
 712:         return state;
 713:     }
 714:     
 715:     /**
 716:      * Returns the space required to draw the axis.
 717:      *
 718:      * @param g2  the graphics device.
 719:      * @param plot  the plot that the axis belongs to.
 720:      * @param plotArea  the area within which the plot should be drawn.
 721:      * @param edge  the axis location.
 722:      * @param space  the space already reserved (for other axes).
 723:      *
 724:      * @return The space required to draw the axis (including pre-reserved 
 725:      *         space).
 726:      */
 727:     public AxisSpace reserveSpace(Graphics2D g2, Plot plot,
 728:                                   Rectangle2D plotArea, 
 729:                                   RectangleEdge edge, AxisSpace space) {
 730: 
 731:         // create a new space object if one wasn't supplied...
 732:         if (space == null) {
 733:             space = new AxisSpace();
 734:         }
 735:         
 736:         // if the axis is not visible, no additional space is required...
 737:         if (!isVisible()) {
 738:             return space;
 739:         }
 740: 
 741:         // if the axis has a fixed dimension, return it...
 742:         double dimension = getFixedDimension();
 743:         if (dimension > 0.0) {
 744:             space.ensureAtLeast(dimension, edge);
 745:         }
 746: 
 747:         // calculate the max size of the tick labels (if visible)...
 748:         double tickLabelHeight = 0.0;
 749:         double tickLabelWidth = 0.0;
 750:         if (isTickLabelsVisible()) {
 751:             g2.setFont(getTickLabelFont());
 752:             List ticks = refreshTicks(g2, new AxisState(), plotArea, edge);
 753:             if (RectangleEdge.isTopOrBottom(edge)) {
 754:                 tickLabelHeight = findMaximumTickLabelHeight(
 755:                     ticks, g2, plotArea, isVerticalTickLabels()
 756:                 );
 757:             }
 758:             else if (RectangleEdge.isLeftOrRight(edge)) {
 759:                 tickLabelWidth = findMaximumTickLabelWidth(
 760:                     ticks, g2, plotArea, isVerticalTickLabels()
 761:                 );
 762:             }
 763:         }
 764: 
 765:         // get the axis label size and update the space object...
 766:         Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
 767:         double labelHeight = 0.0;
 768:         double labelWidth = 0.0;
 769:         if (RectangleEdge.isTopOrBottom(edge)) {
 770:             labelHeight = labelEnclosure.getHeight();
 771:             space.add(labelHeight + tickLabelHeight, edge);
 772:         }
 773:         else if (RectangleEdge.isLeftOrRight(edge)) {
 774:             labelWidth = labelEnclosure.getWidth();
 775:             space.add(labelWidth + tickLabelWidth, edge);
 776:         }
 777: 
 778:         return space;
 779: 
 780:     }
 781: 
 782:     /**
 783:      * A utility method for determining the height of the tallest tick label.
 784:      *
 785:      * @param ticks  the ticks.
 786:      * @param g2  the graphics device.
 787:      * @param drawArea  the area within which the plot and axes should be drawn.
 788:      * @param vertical  a flag that indicates whether or not the tick labels 
 789:      *                  are 'vertical'.
 790:      *
 791:      * @return The height of the tallest tick label.
 792:      */
 793:     protected double findMaximumTickLabelHeight(List ticks,
 794:                                                 Graphics2D g2, 
 795:                                                 Rectangle2D drawArea, 
 796:                                                 boolean vertical) {
 797:                                                     
 798:         RectangleInsets insets = getTickLabelInsets();
 799:         Font font = getTickLabelFont();
 800:         double maxHeight = 0.0;
 801:         if (vertical) {
 802:             FontMetrics fm = g2.getFontMetrics(font);
 803:             Iterator iterator = ticks.iterator();
 804:             while (iterator.hasNext()) {
 805:                 Tick tick = (Tick) iterator.next();
 806:                 Rectangle2D labelBounds = TextUtilities.getTextBounds(
 807:                     tick.getText(), g2, fm
 808:                 );
 809:                 if (labelBounds.getWidth() + insets.getTop() 
 810:                         + insets.getBottom() > maxHeight) {
 811:                     maxHeight = labelBounds.getWidth() 
 812:                                 + insets.getTop() + insets.getBottom();
 813:                 }
 814:             }
 815:         }
 816:         else {
 817:             LineMetrics metrics = font.getLineMetrics(
 818:                 "ABCxyz", g2.getFontRenderContext()
 819:             );
 820:             maxHeight = metrics.getHeight() 
 821:                         + insets.getTop() + insets.getBottom();
 822:         }
 823:         return maxHeight;
 824:         
 825:     }
 826: 
 827:     /**
 828:      * A utility method for determining the width of the widest tick label.
 829:      *
 830:      * @param ticks  the ticks.
 831:      * @param g2  the graphics device.
 832:      * @param drawArea  the area within which the plot and axes should be drawn.
 833:      * @param vertical  a flag that indicates whether or not the tick labels 
 834:      *                  are 'vertical'.
 835:      *
 836:      * @return The width of the tallest tick label.
 837:      */
 838:     protected double findMaximumTickLabelWidth(List ticks, 
 839:                                                Graphics2D g2, 
 840:                                                Rectangle2D drawArea, 
 841:                                                boolean vertical) {
 842:                                                    
 843:         RectangleInsets insets = getTickLabelInsets();
 844:         Font font = getTickLabelFont();
 845:         double maxWidth = 0.0;
 846:         if (!vertical) {
 847:             FontMetrics fm = g2.getFontMetrics(font);
 848:             Iterator iterator = ticks.iterator();
 849:             while (iterator.hasNext()) {
 850:                 Tick tick = (Tick) iterator.next();
 851:                 Rectangle2D labelBounds = TextUtilities.getTextBounds(
 852:                     tick.getText(), g2, fm
 853:                 );
 854:                 if (labelBounds.getWidth() + insets.getLeft() 
 855:                         + insets.getRight() > maxWidth) {
 856:                     maxWidth = labelBounds.getWidth() 
 857:                                + insets.getLeft() + insets.getRight();
 858:                 }
 859:             }
 860:         }
 861:         else {
 862:             LineMetrics metrics = font.getLineMetrics(
 863:                 "ABCxyz", g2.getFontRenderContext()
 864:             );
 865:             maxWidth = metrics.getHeight() 
 866:                        + insets.getTop() + insets.getBottom();
 867:         }
 868:         return maxWidth;
 869:         
 870:     }
 871: 
 872:     /**
 873:      * Returns a flag that controls the direction of values on the axis.
 874:      * <P>
 875:      * For a regular axis, values increase from left to right (for a horizontal
 876:      * axis) and bottom to top (for a vertical axis).  When the axis is
 877:      * 'inverted', the values increase in the opposite direction.
 878:      *
 879:      * @return The flag.
 880:      */
 881:     public boolean isInverted() {
 882:         return this.inverted;
 883:     }
 884: 
 885:     /**
 886:      * Sets a flag that controls the direction of values on the axis, and
 887:      * notifies registered listeners that the axis has changed.
 888:      *
 889:      * @param flag  the flag.
 890:      */
 891:     public void setInverted(boolean flag) {
 892: 
 893:         if (this.inverted != flag) {
 894:             this.inverted = flag;
 895:             notifyListeners(new AxisChangeEvent(this));
 896:         }
 897: 
 898:     }
 899: 
 900:     /**
 901:      * Returns the flag that controls whether or not the axis range is 
 902:      * automatically adjusted to fit the data values.
 903:      *
 904:      * @return The flag.
 905:      */
 906:     public boolean isAutoRange() {
 907:         return this.autoRange;
 908:     }
 909: 
 910:     /**
 911:      * Sets a flag that determines whether or not the axis range is
 912:      * automatically adjusted to fit the data, and notifies registered
 913:      * listeners that the axis has been modified.
 914:      *
 915:      * @param auto  the new value of the flag.
 916:      */
 917:     public void setAutoRange(boolean auto) {
 918:         setAutoRange(auto, true);
 919:     }
 920: 
 921:     /**
 922:      * Sets the auto range attribute.  If the <code>notify</code> flag is set, 
 923:      * an {@link AxisChangeEvent} is sent to registered listeners.
 924:      *
 925:      * @param auto  the flag.
 926:      * @param notify  notify listeners?
 927:      */
 928:     protected void setAutoRange(boolean auto, boolean notify) {
 929:         if (this.autoRange != auto) {
 930:             this.autoRange = auto;
 931:             if (this.autoRange) {
 932:                 autoAdjustRange();
 933:             }
 934:             if (notify) {
 935:                 notifyListeners(new AxisChangeEvent(this));
 936:             }
 937:         }
 938:     }
 939: 
 940:     /**
 941:      * Returns the minimum size allowed for the axis range when it is 
 942:      * automatically calculated.
 943:      *
 944:      * @return The minimum range.
 945:      */
 946:     public double getAutoRangeMinimumSize() {
 947:         return this.autoRangeMinimumSize;
 948:     }
 949: 
 950:     /**
 951:      * Sets the auto range minimum size and sends an {@link AxisChangeEvent} 
 952:      * to all registered listeners.
 953:      *
 954:      * @param size  the size.
 955:      */
 956:     public void setAutoRangeMinimumSize(double size) {
 957:         setAutoRangeMinimumSize(size, true);
 958:     }
 959: 
 960:     /**
 961:      * Sets the minimum size allowed for the axis range when it is 
 962:      * automatically calculated.
 963:      * <p>
 964:      * If requested, an {@link AxisChangeEvent} is forwarded to all registered 
 965:      * listeners.
 966:      *
 967:      * @param size  the new minimum.
 968:      * @param notify  notify listeners?
 969:      */
 970:     public void setAutoRangeMinimumSize(double size, boolean notify) {
 971: 
 972:         // check argument...
 973:         if (size <= 0.0) {
 974:             throw new IllegalArgumentException(
 975:                 "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
 976:         }
 977: 
 978:         // make the change...
 979:         if (this.autoRangeMinimumSize != size) {
 980:             this.autoRangeMinimumSize = size;
 981:             if (this.autoRange) {
 982:                 autoAdjustRange();
 983:             }
 984:             if (notify) {
 985:                 notifyListeners(new AxisChangeEvent(this));
 986:             }
 987:         }
 988: 
 989:     }
 990: 
 991:     /**
 992:      * Returns the lower margin for the axis, expressed as a percentage of the 
 993:      * axis range.  This controls the space added to the lower end of the axis 
 994:      * when the axis range is automatically calculated (it is ignored when the 
 995:      * axis range is set explicitly). The default value is 0.05 (five percent).
 996:      *
 997:      * @return The lower margin.
 998:      */
 999:     public double getLowerMargin() {
1000:         return this.lowerMargin;
1001:     }
1002: 
1003:     /**
1004:      * Sets the lower margin for the axis (as a percentage of the axis range) 
1005:      * and sends an {@link AxisChangeEvent} to all registered listeners.  This
1006:      * margin is added only when the axis range is auto-calculated - if you set 
1007:      * the axis range manually, the margin is ignored.
1008:      *
1009:      * @param margin  the margin percentage (for example, 0.05 is five percent).
1010:      */
1011:     public void setLowerMargin(double margin) {
1012:         this.lowerMargin = margin;
1013:         if (isAutoRange()) {
1014:             autoAdjustRange();
1015:         }
1016:         notifyListeners(new AxisChangeEvent(this));
1017:     }
1018: 
1019:     /**
1020:      * Returns the upper margin for the axis, expressed as a percentage of the 
1021:      * axis range.  This controls the space added to the lower end of the axis 
1022:      * when the axis range is automatically calculated (it is ignored when the 
1023:      * axis range is set explicitly). The default value is 0.05 (five percent).
1024:      *
1025:      * @return The upper margin.
1026:      */
1027:     public double getUpperMargin() {
1028:         return this.upperMargin;
1029:     }
1030: 
1031:     /**
1032:      * Sets the upper margin for the axis (as a percentage of the axis range) 
1033:      * and sends an {@link AxisChangeEvent} to all registered listeners.  This 
1034:      * margin is added only when the axis range is auto-calculated - if you set
1035:      * the axis range manually, the margin is ignored.
1036:      *
1037:      * @param margin  the margin percentage (for example, 0.05 is five percent).
1038:      */
1039:     public void setUpperMargin(double margin) {
1040:         this.upperMargin = margin;
1041:         if (isAutoRange()) {
1042:             autoAdjustRange();
1043:         }
1044:         notifyListeners(new AxisChangeEvent(this));
1045:     }
1046: 
1047:     /**
1048:      * Returns the fixed auto range.
1049:      *
1050:      * @return The length.
1051:      */
1052:     public double getFixedAutoRange() {
1053:         return this.fixedAutoRange;
1054:     }
1055: 
1056:     /**
1057:      * Sets the fixed auto range for the axis.
1058:      *
1059:      * @param length  the range length.
1060:      */
1061:     public void setFixedAutoRange(double length) {
1062: 
1063:         this.fixedAutoRange = length;
1064:         notifyListeners(new AxisChangeEvent(this));
1065: 
1066:     }
1067: 
1068:     /**
1069:      * Returns the lower bound of the axis range.
1070:      *
1071:      * @return The lower bound.
1072:      */
1073:     public double getLowerBound() {
1074:         return this.range.getLowerBound();
1075:     }
1076: 
1077:     /**
1078:      * Sets the lower bound for the axis range.  An {@link AxisChangeEvent} is 
1079:      * sent to all registered listeners.
1080:      *
1081:      * @param min  the new minimum.
1082:      */
1083:     public void setLowerBound(double min) {
1084:         if (this.range.getUpperBound() > min) {
1085:             setRange(new Range(min, this.range.getUpperBound()));            
1086:         }
1087:         else {
1088:             setRange(new Range(min, min + 1.0));                        
1089:         }
1090:     }
1091: 
1092:     /**
1093:      * Returns the upper bound for the axis range.
1094:      *
1095:      * @return The upper bound.
1096:      */
1097:     public double getUpperBound() {
1098:         return this.range.getUpperBound();
1099:     }
1100: 
1101:     /**
1102:      * Sets the upper bound for the axis range.  An {@link AxisChangeEvent} is 
1103:      * sent to all registered listeners.
1104:      *
1105:      * @param max  the new maximum.
1106:      */
1107:     public void setUpperBound(double max) {
1108: 
1109:         if (this.range.getLowerBound() < max) {
1110:             setRange(new Range(this.range.getLowerBound(), max));
1111:         }
1112:         else {
1113:             setRange(max - 1.0, max);
1114:         }
1115: 
1116:     }
1117: 
1118:     /**
1119:      * Returns the range for the axis.
1120:      *
1121:      * @return The axis range (never <code>null</code>).
1122:      */
1123:     public Range getRange() {
1124:         return this.range;
1125:     }
1126: 
1127:     /**
1128:      * Sets the range attribute and sends an {@link AxisChangeEvent} to all 
1129:      * registered listeners.  As a side-effect, the auto-range flag is set to 
1130:      * <code>false</code>.
1131:      *
1132:      * @param range  the range (<code>null</code> not permitted).
1133:      */
1134:     public void setRange(Range range) {
1135:         // defer argument checking
1136:         setRange(range, true, true);
1137:     }
1138: 
1139:     /**
1140:      * Sets the range for the axis, if requested, sends an 
1141:      * {@link AxisChangeEvent} to all registered listeners.  As a side-effect, 
1142:      * the auto-range flag is set to <code>false</code> (optional).
1143:      *
1144:      * @param range  the range (<code>null</code> not permitted).
1145:      * @param turnOffAutoRange  a flag that controls whether or not the auto 
1146:      *                          range is turned off.         
1147:      * @param notify  a flag that controls whether or not listeners are 
1148:      *                notified.
1149:      */
1150:     public void setRange(Range range, boolean turnOffAutoRange, 
1151:                          boolean notify) {
1152:         if (range == null) {
1153:             throw new IllegalArgumentException("Null 'range' argument.");
1154:         }
1155:         if (turnOffAutoRange) {
1156:             this.autoRange = false;
1157:         }
1158:         this.range = range;
1159:         if (notify) {
1160:             notifyListeners(new AxisChangeEvent(this));
1161:         }
1162:     }
1163: 
1164:     /**
1165:      * Sets the axis range and sends an {@link AxisChangeEvent} to all 
1166:      * registered listeners.  As a side-effect, the auto-range flag is set to 
1167:      * <code>false</code>.
1168:      *
1169:      * @param lower  the lower axis limit.
1170:      * @param upper  the upper axis limit.
1171:      */
1172:     public void setRange(double lower, double upper) {
1173:         setRange(new Range(lower, upper));
1174:     }
1175:     
1176:     /**
1177:      * Sets the range for the axis (after first adding the current margins to 
1178:      * the specified range) and sends an {@link AxisChangeEvent} to all 
1179:      * registered listeners.
1180:      * 
1181:      * @param range  the range (<code>null</code> not permitted).
1182:      */
1183:     public void setRangeWithMargins(Range range) {
1184:         setRangeWithMargins(range, true, true);
1185:     }
1186: 
1187:     /**
1188:      * Sets the range for the axis after first adding the current margins to 
1189:      * the range and, if requested, sends an {@link AxisChangeEvent} to all 
1190:      * registered listeners.  As a side-effect, the auto-range flag is set to 
1191:      * <code>false</code> (optional).
1192:      *
1193:      * @param range  the range (excluding margins, <code>null</code> not 
1194:      *               permitted).
1195:      * @param turnOffAutoRange  a flag that controls whether or not the auto 
1196:      *                          range is turned off.
1197:      * @param notify  a flag that controls whether or not listeners are 
1198:      *                notified.
1199:      */
1200:     public void setRangeWithMargins(Range range, boolean turnOffAutoRange, 
1201:                                     boolean notify) {
1202:         if (range == null) {
1203:             throw new IllegalArgumentException("Null 'range' argument.");
1204:         }
1205:         setRange(
1206:             Range.expand(range, getLowerMargin(), getUpperMargin()), 
1207:             turnOffAutoRange, notify
1208:         );
1209:     }
1210: 
1211:     /**
1212:      * Sets the axis range (after first adding the current margins to the 
1213:      * range) and sends an {@link AxisChangeEvent} to all registered listeners.
1214:      * As a side-effect, the auto-range flag is set to <code>false</code>.
1215:      *
1216:      * @param lower  the lower axis limit.
1217:      * @param upper  the upper axis limit.
1218:      */
1219:     public void setRangeWithMargins(double lower, double upper) {
1220:         setRangeWithMargins(new Range(lower, upper));
1221:     }
1222:     
1223:     /**
1224:      * Sets the axis range, where the new range is 'size' in length, and 
1225:      * centered on 'value'.
1226:      *
1227:      * @param value  the central value.
1228:      * @param length  the range length.
1229:      */
1230:     public void setRangeAboutValue(double value, double length) {
1231:         setRange(new Range(value - length / 2, value + length / 2));
1232:     }
1233: 
1234:     /**
1235:      * Returns a flag indicating whether or not the tick unit is automatically
1236:      * selected from a range of standard tick units.
1237:      *
1238:      * @return A flag indicating whether or not the tick unit is automatically
1239:      *         selected.
1240:      */
1241:     public boolean isAutoTickUnitSelection() {
1242:         return this.autoTickUnitSelection;
1243:     }
1244: 
1245:     /**
1246:      * Sets a flag indicating whether or not the tick unit is automatically
1247:      * selected from a range of standard tick units.  If the flag is changed, 
1248:      * registered listeners are notified that the chart has changed.
1249:      *
1250:      * @param flag  the new value of the flag.
1251:      */
1252:     public void setAutoTickUnitSelection(boolean flag) {
1253:         setAutoTickUnitSelection(flag, true);
1254:     }
1255: 
1256:     /**
1257:      * Sets a flag indicating whether or not the tick unit is automatically
1258:      * selected from a range of standard tick units.
1259:      *
1260:      * @param flag  the new value of the flag.
1261:      * @param notify  notify listeners?
1262:      */
1263:     public void setAutoTickUnitSelection(boolean flag, boolean notify) {
1264: 
1265:         if (this.autoTickUnitSelection != flag) {
1266:             this.autoTickUnitSelection = flag;
1267:             if (notify) {
1268:                 notifyListeners(new AxisChangeEvent(this));
1269:             }
1270:         }
1271:     }
1272: 
1273:     /**
1274:      * Returns the source for obtaining standard tick units for the axis.
1275:      *
1276:      * @return The source (possibly <code>null</code>).
1277:      */
1278:     public TickUnitSource getStandardTickUnits() {
1279:         return this.standardTickUnits;
1280:     }
1281: 
1282:     /**
1283:      * Sets the source for obtaining standard tick units for the axis and sends
1284:      * an {@link AxisChangeEvent} to all registered listeners.  The axis will 
1285:      * try to select the smallest tick unit from the source that does not cause
1286:      * the tick labels to overlap (see also the 
1287:      * {@link #setAutoTickUnitSelection(boolean)} method.
1288:      *
1289:      * @param source  the source for standard tick units (<code>null</code> 
1290:      *                permitted).
1291:      */
1292:     public void setStandardTickUnits(TickUnitSource source) {
1293:         this.standardTickUnits = source;
1294:         notifyListeners(new AxisChangeEvent(this));
1295:     }
1296:     
1297:     /**
1298:      * Converts a data value to a coordinate in Java2D space, assuming that the
1299:      * axis runs along one edge of the specified dataArea.
1300:      * <p>
1301:      * Note that it is possible for the coordinate to fall outside the area.
1302:      *
1303:      * @param value  the data value.
1304:      * @param area  the area for plotting the data.
1305:      * @param edge  the edge along which the axis lies.
1306:      *
1307:      * @return The Java2D coordinate.
1308:      */
1309:     public abstract double valueToJava2D(double value, Rectangle2D area, 
1310:                                          RectangleEdge edge);
1311:     
1312:     /**
1313:      * Converts a length in data coordinates into the corresponding length in 
1314:      * Java2D coordinates.
1315:      * 
1316:      * @param length  the length.
1317:      * @param area  the plot area.
1318:      * @param edge  the edge along which the axis lies.
1319:      * 
1320:      * @return The length in Java2D coordinates.
1321:      */
1322:     public double lengthToJava2D(double length, Rectangle2D area, 
1323:                                  RectangleEdge edge) {
1324:         double zero = valueToJava2D(0.0, area, edge);
1325:         double l = valueToJava2D(length, area, edge);
1326:         return Math.abs(l - zero);
1327:     }
1328: 
1329:     /**
1330:      * Converts a coordinate in Java2D space to the corresponding data value,
1331:      * assuming that the axis runs along one edge of the specified dataArea.
1332:      *
1333:      * @param java2DValue  the coordinate in Java2D space.
1334:      * @param area  the area in which the data is plotted.
1335:      * @param edge  the edge along which the axis lies.
1336:      *
1337:      * @return The data value.
1338:      */
1339:     public abstract double java2DToValue(double java2DValue,
1340:                                          Rectangle2D area,
1341:                                          RectangleEdge edge);
1342: 
1343:     /**
1344:      * Automatically sets the axis range to fit the range of values in the 
1345:      * dataset.  Sometimes this can depend on the renderer used as well (for 
1346:      * example, the renderer may "stack" values, requiring an axis range 
1347:      * greater than otherwise necessary).
1348:      */
1349:     protected abstract void autoAdjustRange();
1350: 
1351:     /**
1352:      * Centers the axis range about the specified value and sends an 
1353:      * {@link AxisChangeEvent} to all registered listeners.
1354:      *
1355:      * @param value  the center value.
1356:      */
1357:     public void centerRange(double value) {
1358: 
1359:         double central = this.range.getCentralValue();
1360:         Range adjusted = new Range(
1361:             this.range.getLowerBound() + value - central,
1362:             this.range.getUpperBound() + value - central
1363:         );
1364:         setRange(adjusted);
1365: 
1366:     }
1367: 
1368:     /**
1369:      * Increases or decreases the axis range by the specified percentage about 
1370:      * the central value and sends an {@link AxisChangeEvent} to all registered
1371:      * listeners.
1372:      * <P>
1373:      * To double the length of the axis range, use 200% (2.0).
1374:      * To halve the length of the axis range, use 50% (0.5).
1375:      *
1376:      * @param percent  the resize factor.
1377:      */
1378:     public void resizeRange(double percent) {
1379:         resizeRange(percent, this.range.getCentralValue());
1380:     }
1381: 
1382:     /**
1383:      * Increases or decreases the axis range by the specified percentage about
1384:      * the specified anchor value and sends an {@link AxisChangeEvent} to all 
1385:      * registered listeners.
1386:      * <P>
1387:      * To double the length of the axis range, use 200% (2.0).
1388:      * To halve the length of the axis range, use 50% (0.5).
1389:      *
1390:      * @param percent  the resize factor.
1391:      * @param anchorValue  the new central value after the resize.
1392:      */
1393:     public void resizeRange(double percent, double anchorValue) {
1394: 
1395:         if (percent > 0.0) {
1396:             double halfLength = this.range.getLength() * percent / 2;
1397:             Range adjusted = new Range(
1398:                 anchorValue - halfLength, anchorValue + halfLength
1399:             );
1400:             setRange(adjusted);
1401:         }
1402:         else {
1403:             setAutoRange(true);
1404:         }
1405: 
1406:     }
1407:     
1408:     /**
1409:      * Zooms in on the current range.
1410:      * 
1411:      * @param lowerPercent  the new lower bound.
1412:      * @param upperPercent  the new upper bound.
1413:      */
1414:     public void zoomRange(double lowerPercent, double upperPercent) {
1415:         double start = this.range.getLowerBound();
1416:         double length = this.range.getLength();
1417:         Range adjusted = null;
1418:         if (isInverted()) {
1419:             adjusted = new Range(start + (length * (1 - upperPercent)), 
1420:                                  start + (length * (1 - lowerPercent))); 
1421:         }
1422:         else {
1423:             adjusted = new Range(
1424:                 start + length * lowerPercent, start + length * upperPercent
1425:             );
1426:         }
1427:         setRange(adjusted);
1428:     }
1429: 
1430:     /**
1431:      * Returns the auto tick index.
1432:      *
1433:      * @return The auto tick index.
1434:      */
1435:     protected int getAutoTickIndex() {
1436:         return this.autoTickIndex;
1437:     }
1438: 
1439:     /**
1440:      * Sets the auto tick index.
1441:      *
1442:      * @param index  the new value.
1443:      */
1444:     protected void setAutoTickIndex(int index) {
1445:         this.autoTickIndex = index;
1446:     }
1447: 
1448:     /**
1449:      * Tests the axis for equality with an arbitrary object.
1450:      *
1451:      * @param obj  the object (<code>null</code> permitted).
1452:      *
1453:      * @return <code>true</code> or <code>false</code>.
1454:      */
1455:     public boolean equals(Object obj) {
1456: 
1457:         if (obj == this) {
1458:             return true;
1459:         }
1460: 
1461:         if (!(obj instanceof ValueAxis)) {
1462:             return false;
1463:         }
1464:         if (!super.equals(obj)) {
1465:             return false;
1466:         }
1467:         ValueAxis that = (ValueAxis) obj;
1468: 
1469:         
1470:         if (this.positiveArrowVisible != that.positiveArrowVisible) {
1471:             return false;
1472:         }
1473:         if (this.negativeArrowVisible != that.negativeArrowVisible) {
1474:             return false;
1475:         }
1476:         if (this.inverted != that.inverted) {
1477:             return false;
1478:         }
1479:         if (!ObjectUtilities.equal(this.range, that.range)) {
1480:             return false;
1481:         }
1482:         if (this.autoRange != that.autoRange) {
1483:             return false;
1484:         }
1485:         if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
1486:             return false;
1487:         }
1488:         if (this.upperMargin != that.upperMargin) {
1489:             return false;
1490:         }
1491:         if (this.lowerMargin != that.lowerMargin) {
1492:             return false;
1493:         }
1494:         if (this.fixedAutoRange != that.fixedAutoRange) {
1495:             return false;
1496:         }
1497:         if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
1498:             return false;
1499:         }
1500:         if (!ObjectUtilities.equal(this.standardTickUnits, 
1501:                 that.standardTickUnits)) {
1502:             return false;
1503:         }
1504:         if (this.verticalTickLabels != that.verticalTickLabels) {
1505:             return false;
1506:         }
1507: 
1508:         return true;
1509: 
1510:     }
1511:     
1512:     /**
1513:      * Returns a clone of the object.
1514:      * 
1515:      * @return A clone.
1516:      * 
1517:      * @throws CloneNotSupportedException if some component of the axis does 
1518:      *         not support cloning.
1519:      */
1520:     public Object clone() throws CloneNotSupportedException {
1521:         ValueAxis clone = (ValueAxis) super.clone();
1522:         return clone;
1523:     }
1524:     
1525:     /**
1526:      * Provides serialization support.
1527:      *
1528:      * @param stream  the output stream.
1529:      *
1530:      * @throws IOException  if there is an I/O error.
1531:      */
1532:     private void writeObject(ObjectOutputStream stream) throws IOException {
1533: 
1534:         stream.defaultWriteObject();
1535:         SerialUtilities.writeShape(this.upArrow, stream);
1536:         SerialUtilities.writeShape(this.downArrow, stream);
1537:         SerialUtilities.writeShape(this.leftArrow, stream);
1538:         SerialUtilities.writeShape(this.rightArrow, stream);
1539: 
1540:     }
1541: 
1542:     /**
1543:      * Provides serialization support.
1544:      *
1545:      * @param stream  the input stream.
1546:      *
1547:      * @throws IOException  if there is an I/O error.
1548:      * @throws ClassNotFoundException  if there is a classpath problem.
1549:      */
1550:     private void readObject(ObjectInputStream stream) 
1551:         throws IOException, ClassNotFoundException {
1552: 
1553:         stream.defaultReadObject();
1554:         this.upArrow = SerialUtilities.readShape(stream);
1555:         this.downArrow = SerialUtilities.readShape(stream);
1556:         this.leftArrow = SerialUtilities.readShape(stream);
1557:         this.rightArrow = SerialUtilities.readShape(stream);
1558: 
1559:     }
1560:    
1561: }