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