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 publihed 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:  * Changes
  38:  * -------
  39:  * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
  40:  * 23-Nov-2001 : Overhauled standard tick unit code (DG);
  41:  * 04-Dec-2001 : Changed constructors to protected, and tidied up default 
  42:  *               values (DG);
  43:  * 12-Dec-2001 : Fixed vertical gridlines bug (DG);
  44:  * 16-Jan-2002 : Added an optional crosshair, based on the implementation by 
  45:  *               Jonathan Nash (DG);
  46:  * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis, 
  47:  *               and changed the type from Number to double (DG);
  48:  * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange 
  49:  *               from public to protected. Updated import statements (DG);
  50:  * 23-Apr-2002 : Added setRange() method (DG);
  51:  * 29-Apr-2002 : Added range adjustment methods (DG);
  52:  * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the
  53:  *               crosshairs are visible, to avoid unnecessary repaints, as 
  54:  *               suggested by Kees Kuip (DG);
  55:  * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis 
  56:  *               class (DG);
  57:  * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
  58:  * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  59:  * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG);
  60:  * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
  61:  * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
  62:  * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to 
  63:  *               ValueAxis (DG);
  64:  * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed 
  65:  *               immediately (DG);
  66:  * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
  67:  * 20-Jan-2003 : Replaced monolithic constructor (DG);
  68:  * 26-Mar-2003 : Implemented Serializable (DG);
  69:  * 09-May-2003 : Added AxisLocation parameter to translation methods (DG);
  70:  * 13-Aug-2003 : Implemented Cloneable (DG);
  71:  * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG);
  72:  * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG);
  73:  * 08-Sep-2003 : Completed Serialization support (NB);
  74:  * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound,
  75:  *               and get/setMaximumValue --> get/setUpperBound (DG);
  76:  * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID 
  77:  *               829606 (DG);
  78:  * 07-Nov-2003 : Changes to tick mechanism (DG);
  79:  * 06-Jan-2004 : Moved axis line attributes to Axis class (DG);
  80:  * 21-Jan-2004 : Removed redundant axisLineVisible attribute.  Renamed 
  81:  *               translateJava2DToValue --> java2DToValue, and 
  82:  *               translateValueToJava2D --> valueToJava2D (DG); 
  83:  * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no 
  84:  *               effect (andreas.gawecki@coremedia.com);
  85:  * 07-Apr-2004 : Changed text bounds calculation (DG);
  86:  * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG);
  87:  * 18-May-2004 : Added methods to set axis range *including* current 
  88:  *               margins (DG);
  89:  * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG);
  90:  * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities 
  91:  *               --> TextUtilities (DG);
  92:  * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 
  93:  *               release (DG);
  94:  * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
  95:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  96:  * 10-Oct-2006 : Source reformatting (DG);
  97:  * 22-Mar-2007 : Added new defaultAutoRange attribute (DG);
  98:  * 02-Aug-2007 : Check for major tick when drawing label (DG);
  99:  *
 100:  */
 101: 
 102: package org.jfree.chart.axis;
 103: 
 104: import java.awt.Font;
 105: import java.awt.FontMetrics;
 106: import java.awt.Graphics2D;
 107: import java.awt.Polygon;
 108: import java.awt.Shape;
 109: import java.awt.font.LineMetrics;
 110: import java.awt.geom.AffineTransform;
 111: import java.awt.geom.Line2D;
 112: import java.awt.geom.Rectangle2D;
 113: import java.io.IOException;
 114: import java.io.ObjectInputStream;
 115: import java.io.ObjectOutputStream;
 116: import java.io.Serializable;
 117: import java.util.Iterator;
 118: import java.util.List;
 119: 
 120: import org.jfree.chart.event.AxisChangeEvent;
 121: import org.jfree.chart.plot.Plot;
 122: import org.jfree.data.Range;
 123: import org.jfree.io.SerialUtilities;
 124: import org.jfree.text.TextUtilities;
 125: import org.jfree.ui.RectangleEdge;
 126: import org.jfree.ui.RectangleInsets;
 127: import org.jfree.util.ObjectUtilities;
 128: import org.jfree.util.PublicCloneable;
 129: 
 130: /**
 131:  * The base class for axes that display value data, where values are measured 
 132:  * using the <code>double</code> primitive.  The two key subclasses are 
 133:  * {@link DateAxis} and {@link NumberAxis}.
 134:  */
 135: public abstract class ValueAxis extends Axis 
 136:                                 implements Cloneable, PublicCloneable, 
 137:                                            Serializable {
 138: 
 139:     /** For serialization. */
 140:     private static final long serialVersionUID = 3698345477322391456L;
 141:     
 142:     /** The default axis range. */
 143:     public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
 144: 
 145:     /** The default auto-range value. */
 146:     public static final boolean DEFAULT_AUTO_RANGE = true;
 147: 
 148:     /** The default inverted flag setting. */
 149:     public static final boolean DEFAULT_INVERTED = false;
 150: 
 151:     /** The default minimum auto range. */
 152:     public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
 153: 
 154:     /** The default value for the lower margin (0.05 = 5%). */
 155:     public static final double DEFAULT_LOWER_MARGIN = 0.05;
 156: 
 157:     /** The default value for the upper margin (0.05 = 5%). */
 158:     public static final double DEFAULT_UPPER_MARGIN = 0.05;
 159: 
 160:     /** 
 161:      * The default lower bound for the axis.
 162:      * 
 163:      * @deprecated From 1.0.5 onwards, the axis defines a defaultRange 
 164:      *     attribute (see {@link #getDefaultAutoRange()}).
 165:      */
 166:     public static final double DEFAULT_LOWER_BOUND = 0.0;
 167: 
 168:     /** 
 169:      * The default upper bound for the axis. 
 170:      * 
 171:      * @deprecated From 1.0.5 onwards, the axis defines a defaultRange 
 172:      *     attribute (see {@link #getDefaultAutoRange()}).
 173:      */
 174:     public static final double DEFAULT_UPPER_BOUND = 1.0;
 175: 
 176:     /** The default auto-tick-unit-selection value. */
 177:     public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
 178: 
 179:     /** The maximum tick count. */
 180:     public static final int MAXIMUM_TICK_COUNT = 500;
 181:     
 182:     /** 
 183:      * A flag that controls whether an arrow is drawn at the positive end of 
 184:      * the axis line. 
 185:      */
 186:     private boolean positiveArrowVisible;
 187:     
 188:     /** 
 189:      * A flag that controls whether an arrow is drawn at the negative end of 
 190:      * the axis line. 
 191:      */
 192:     private boolean negativeArrowVisible;
 193:     
 194:     /** The shape used for an up arrow. */
 195:     private transient Shape upArrow;
 196:     
 197:     /** The shape used for a down arrow. */
 198:     private transient Shape downArrow;
 199:     
 200:     /** The shape used for a left arrow. */
 201:     private transient Shape leftArrow;
 202:     
 203:     /** The shape used for a right arrow. */
 204:     private transient Shape rightArrow;
 205:     
 206:     /** A flag that affects the orientation of the values on the axis. */
 207:     private boolean inverted;
 208: 
 209:     /** The axis range. */
 210:     private Range range;
 211: 
 212:     /** 
 213:      * Flag that indicates whether the axis automatically scales to fit the 
 214:      * chart data. 
 215:      */
 216:     private boolean autoRange;
 217: 
 218:     /** The minimum size for the 'auto' axis range (excluding margins). */
 219:     private double autoRangeMinimumSize;
 220: 
 221:     /**
 222:      * The default range is used when the dataset is empty and the axis needs
 223:      * to determine the auto range.
 224:      * 
 225:      * @since 1.0.5
 226:      */
 227:     private Range defaultAutoRange;
 228:     
 229:     /**
 230:      * The upper margin percentage.  This indicates the amount by which the 
 231:      * maximum axis value exceeds the maximum data value (as a percentage of 
 232:      * the range on the axis) when the axis range is determined automatically.
 233:      */
 234:     private double upperMargin;
 235: 
 236:     /**
 237:      * The lower margin.  This is a percentage that indicates the amount by
 238:      * which the minimum axis value is "less than" the minimum data value when
 239:      * the axis range is determined automatically.
 240:      */
 241:     private double lowerMargin;
 242: 
 243:     /**
 244:      * If this value is positive, the amount is subtracted from the maximum
 245:      * data value to determine the lower axis range.  This can be used to
 246:      * provide a fixed "window" on dynamic data.
 247:      */
 248:     private double fixedAutoRange;
 249: 
 250:     /** 
 251:      * Flag that indicates whether or not the tick unit is selected 
 252:      * automatically. 
 253:      */
 254:     private boolean autoTickUnitSelection;
 255: 
 256:     /** The standard tick units for the axis. */
 257:     private TickUnitSource standardTickUnits;
 258: 
 259:     /** An index into an array of standard tick values. */
 260:     private int autoTickIndex;
 261:     
 262:     /** A flag indicating whether or not tick labels are rotated to vertical. */
 263:     private boolean verticalTickLabels;
 264: 
 265:     /**
 266:      * Constructs a value axis.
 267:      *
 268:      * @param label  the axis label (<code>null</code> permitted).
 269:      * @param standardTickUnits  the source for standard tick units 
 270:      *                           (<code>null</code> permitted).
 271:      */
 272:     protected ValueAxis(String label, TickUnitSource standardTickUnits) {
 273: 
 274:         super(label);
 275: 
 276:         this.positiveArrowVisible = false;
 277:         this.negativeArrowVisible = false;
 278: 
 279:         this.range = DEFAULT_RANGE;
 280:         this.autoRange = DEFAULT_AUTO_RANGE;
 281:         this.defaultAutoRange = DEFAULT_RANGE;
 282: 
 283:         this.inverted = DEFAULT_INVERTED;
 284:         this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
 285: 
 286:         this.lowerMargin = DEFAULT_LOWER_MARGIN;
 287:         this.upperMargin = DEFAULT_UPPER_MARGIN;
 288: 
 289:         this.fixedAutoRange = 0.0;
 290: 
 291:         this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
 292:         this.standardTickUnits = standardTickUnits;
 293:         
 294:         Polygon p1 = new Polygon();
 295:         p1.addPoint(0, 0);
 296:         p1.addPoint(-2, 2);
 297:         p1.addPoint(2, 2);
 298:         
 299:         this.upArrow = p1;
 300: 
 301:         Polygon p2 = new Polygon();
 302:         p2.addPoint(0, 0);
 303:         p2.addPoint(-2, -2);
 304:         p2.addPoint(2, -2);
 305: 
 306:         this.downArrow = p2;
 307: 
 308:         Polygon p3 = new Polygon();
 309:         p3.addPoint(0, 0);
 310:         p3.addPoint(-2, -2);
 311:         p3.addPoint(-2, 2);
 312:         
 313:         this.rightArrow = p3;
 314: 
 315:         Polygon p4 = new Polygon();
 316:         p4.addPoint(0, 0);
 317:         p4.addPoint(2, -2);
 318:         p4.addPoint(2, 2);
 319: 
 320:         this.leftArrow = p4;
 321:         
 322:         this.verticalTickLabels = false;
 323:         
 324:     }
 325: 
 326:     /**
 327:      * Returns <code>true</code> if the tick labels should be rotated (to 
 328:      * vertical), and <code>false</code> otherwise.
 329:      *
 330:      * @return <code>true</code> or <code>false</code>.
 331:      * 
 332:      * @see #setVerticalTickLabels(boolean)
 333:      */
 334:     public boolean isVerticalTickLabels() {
 335:         return this.verticalTickLabels;
 336:     }
 337: 
 338:     /**
 339:      * Sets the flag that controls whether the tick labels are displayed 
 340:      * vertically (that is, rotated 90 degrees from horizontal).  If the flag 
 341:      * is changed, an {@link AxisChangeEvent} is sent to all registered 
 342:      * listeners.
 343:      *
 344:      * @param flag  the flag.
 345:      * 
 346:      * @see #isVerticalTickLabels()
 347:      */
 348:     public void setVerticalTickLabels(boolean flag) {
 349:         if (this.verticalTickLabels != flag) {
 350:             this.verticalTickLabels = flag;
 351:             notifyListeners(new AxisChangeEvent(this));
 352:         }
 353:     }
 354: 
 355:     /**
 356:      * Returns a flag that controls whether or not the axis line has an arrow 
 357:      * drawn that points in the positive direction for the axis.
 358:      * 
 359:      * @return A boolean.
 360:      * 
 361:      * @see #setPositiveArrowVisible(boolean)
 362:      */
 363:     public boolean isPositiveArrowVisible() {
 364:         return this.positiveArrowVisible;
 365:     }
 366:     
 367:     /**
 368:      * Sets a flag that controls whether or not the axis lines has an arrow 
 369:      * drawn that points in the positive direction for the axis, and sends an 
 370:      * {@link AxisChangeEvent} to all registered listeners.
 371:      * 
 372:      * @param visible  the flag.
 373:      * 
 374:      * @see #isPositiveArrowVisible()
 375:      */
 376:     public void setPositiveArrowVisible(boolean visible) {
 377:         this.positiveArrowVisible = visible;
 378:         notifyListeners(new AxisChangeEvent(this));
 379:     }
 380:     
 381:     /**
 382:      * Returns a flag that controls whether or not the axis line has an arrow 
 383:      * drawn that points in the negative direction for the axis.
 384:      * 
 385:      * @return A boolean.
 386:      * 
 387:      * @see #setNegativeArrowVisible(boolean)
 388:      */
 389:     public boolean isNegativeArrowVisible() {
 390:         return this.negativeArrowVisible;
 391:     }
 392:     
 393:     /**
 394:      * Sets a flag that controls whether or not the axis lines has an arrow 
 395:      * drawn that points in the negative direction for the axis, and sends an 
 396:      * {@link AxisChangeEvent} to all registered listeners.
 397:      * 
 398:      * @param visible  the flag.
 399:      * 
 400:      * @see #setNegativeArrowVisible(boolean)
 401:      */
 402:     public void setNegativeArrowVisible(boolean visible) {
 403:         this.negativeArrowVisible = visible;
 404:         notifyListeners(new AxisChangeEvent(this));
 405:     }
 406:     
 407:     /**
 408:      * Returns a shape that can be displayed as an arrow pointing upwards at 
 409:      * the end of an axis line.
 410:      * 
 411:      * @return A shape (never <code>null</code>).
 412:      * 
 413:      * @see #setUpArrow(Shape)
 414:      */
 415:     public Shape getUpArrow() {
 416:         return this.upArrow;   
 417:     }
 418:     
 419:     /**
 420:      * Sets the shape that can be displayed as an arrow pointing upwards at 
 421:      * the end of an axis line and sends an {@link AxisChangeEvent} to all 
 422:      * registered listeners.
 423:      * 
 424:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 425:      * 
 426:      * @see #getUpArrow()
 427:      */
 428:     public void setUpArrow(Shape arrow) {
 429:         if (arrow == null) {
 430:             throw new IllegalArgumentException("Null 'arrow' argument.");   
 431:         }
 432:         this.upArrow = arrow;
 433:         notifyListeners(new AxisChangeEvent(this));
 434:     }
 435:     
 436:     /**
 437:      * Returns a shape that can be displayed as an arrow pointing downwards at 
 438:      * the end of an axis line.
 439:      * 
 440:      * @return A shape (never <code>null</code>).
 441:      * 
 442:      * @see #setDownArrow(Shape)
 443:      */
 444:     public Shape getDownArrow() {
 445:         return this.downArrow;   
 446:     }
 447:     
 448:     /**
 449:      * Sets the shape that can be displayed as an arrow pointing downwards at 
 450:      * the end of an axis line and sends an {@link AxisChangeEvent} to all 
 451:      * registered listeners.
 452:      * 
 453:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 454:      * 
 455:      * @see #getDownArrow()
 456:      */
 457:     public void setDownArrow(Shape arrow) {
 458:         if (arrow == null) {
 459:             throw new IllegalArgumentException("Null 'arrow' argument.");   
 460:         }
 461:         this.downArrow = arrow;
 462:         notifyListeners(new AxisChangeEvent(this));
 463:     }
 464:     
 465:     /**
 466:      * Returns a shape that can be displayed as an arrow pointing left at the 
 467:      * end of an axis line.
 468:      * 
 469:      * @return A shape (never <code>null</code>).
 470:      * 
 471:      * @see #setLeftArrow(Shape)
 472:      */
 473:     public Shape getLeftArrow() {
 474:         return this.leftArrow;   
 475:     }
 476:     
 477:     /**
 478:      * Sets the shape that can be displayed as an arrow pointing left at the 
 479:      * end of an axis line and sends an {@link AxisChangeEvent} to all 
 480:      * registered listeners.
 481:      * 
 482:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 483:      * 
 484:      * @see #getLeftArrow()
 485:      */
 486:     public void setLeftArrow(Shape arrow) {
 487:         if (arrow == null) {
 488:             throw new IllegalArgumentException("Null 'arrow' argument.");   
 489:         }
 490:         this.leftArrow = arrow;
 491:         notifyListeners(new AxisChangeEvent(this));
 492:     }
 493:     
 494:     /**
 495:      * Returns a shape that can be displayed as an arrow pointing right at the 
 496:      * end of an axis line.
 497:      * 
 498:      * @return A shape (never <code>null</code>).
 499:      * 
 500:      * @see #setRightArrow(Shape)
 501:      */
 502:     public Shape getRightArrow() {
 503:         return this.rightArrow;   
 504:     }
 505:     
 506:     /**
 507:      * Sets the shape that can be displayed as an arrow pointing rightwards at 
 508:      * the end of an axis line and sends an {@link AxisChangeEvent} to all 
 509:      * registered listeners.
 510:      * 
 511:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 512:      * 
 513:      * @see #getRightArrow()
 514:      */
 515:     public void setRightArrow(Shape arrow) {
 516:         if (arrow == null) {
 517:             throw new IllegalArgumentException("Null 'arrow' argument.");   
 518:         }
 519:         this.rightArrow = arrow;
 520:         notifyListeners(new AxisChangeEvent(this));
 521:     }
 522:     
 523:     /**
 524:      * Draws an axis line at the current cursor position and edge.
 525:      * 
 526:      * @param g2  the graphics device.
 527:      * @param cursor  the cursor position.
 528:      * @param dataArea  the data area.
 529:      * @param edge  the edge.
 530:      */
 531:     protected void drawAxisLine(Graphics2D g2, double cursor,
 532:                                 Rectangle2D dataArea, RectangleEdge edge) {
 533:         Line2D axisLine = null;
 534:         if (edge == RectangleEdge.TOP) {
 535:             axisLine = new Line2D.Double(dataArea.getX(), cursor, 
 536:                     dataArea.getMaxX(), cursor);  
 537:         }
 538:         else if (edge == RectangleEdge.BOTTOM) {
 539:             axisLine = new Line2D.Double(dataArea.getX(), cursor, 
 540:                     dataArea.getMaxX(), cursor);  
 541:         }
 542:         else if (edge == RectangleEdge.LEFT) {
 543:             axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 
 544:                     dataArea.getMaxY());  
 545:         }
 546:         else if (edge == RectangleEdge.RIGHT) {
 547:             axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor, 
 548:                     dataArea.getMaxY());  
 549:         }
 550:         g2.setPaint(getAxisLinePaint());
 551:         g2.setStroke(getAxisLineStroke());
 552:         g2.draw(axisLine);
 553:         
 554:         boolean drawUpOrRight = false;  
 555:         boolean drawDownOrLeft = false;
 556:         if (this.positiveArrowVisible) {
 557:             if (this.inverted) {
 558:                 drawDownOrLeft = true;   
 559:             }
 560:             else {
 561:                 drawUpOrRight = true;   
 562:             }
 563:         }
 564:         if (this.negativeArrowVisible) {
 565:             if (this.inverted) {
 566:                 drawUpOrRight = true;   
 567:             }
 568:             else {
 569:                 drawDownOrLeft = true;   
 570:             }
 571:         }
 572:         if (drawUpOrRight) {
 573:             double x = 0.0;
 574:             double y = 0.0;
 575:             Shape arrow = null;
 576:             if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
 577:                 x = dataArea.getMaxX();
 578:                 y = cursor;
 579:                 arrow = this.rightArrow; 
 580:             }
 581:             else if (edge == RectangleEdge.LEFT 
 582:                     || edge == RectangleEdge.RIGHT) {
 583:                 x = cursor;
 584:                 y = dataArea.getMinY();
 585:                 arrow = this.upArrow; 
 586:             }
 587: 
 588:             // draw the arrow...
 589:             AffineTransform transformer = new AffineTransform();
 590:             transformer.setToTranslation(x, y);
 591:             Shape shape = transformer.createTransformedShape(arrow);
 592:             g2.fill(shape);
 593:             g2.draw(shape);
 594:         }
 595:         
 596:         if (drawDownOrLeft) {
 597:             double x = 0.0;
 598:             double y = 0.0;
 599:             Shape arrow = null;
 600:             if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
 601:                 x = dataArea.getMinX();
 602:                 y = cursor;
 603:                 arrow = this.leftArrow; 
 604:             }
 605:             else if (edge == RectangleEdge.LEFT 
 606:                     || edge == RectangleEdge.RIGHT) {
 607:                 x = cursor;
 608:                 y = dataArea.getMaxY();
 609:                 arrow = this.downArrow; 
 610:             }
 611: 
 612:             // draw the arrow...
 613:             AffineTransform transformer = new AffineTransform();
 614:             transformer.setToTranslation(x, y);
 615:             Shape shape = transformer.createTransformedShape(arrow);
 616:             g2.fill(shape);
 617:             g2.draw(shape);
 618:         }
 619:         
 620:     }
 621:     
 622:     /**
 623:      * Calculates the anchor point for a tick label.
 624:      * 
 625:      * @param tick  the tick.
 626:      * @param cursor  the cursor.
 627:      * @param dataArea  the data area.
 628:      * @param edge  the edge on which the axis is drawn.
 629:      * 
 630:      * @return The x and y coordinates of the anchor point.
 631:      */
 632:     protected float[] calculateAnchorPoint(ValueTick tick, 
 633:                                            double cursor, 
 634:                                            Rectangle2D dataArea, 
 635:                                            RectangleEdge edge) {
 636:     
 637:         RectangleInsets insets = getTickLabelInsets();
 638:         float[] result = new float[2];
 639:         if (edge == RectangleEdge.TOP) {
 640:             result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 641:             result[1] = (float) (cursor - insets.getBottom() - 2.0);
 642:         }
 643:         else if (edge == RectangleEdge.BOTTOM) {
 644:             result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 645:             result[1] = (float) (cursor + insets.getTop() + 2.0); 
 646:         }
 647:         else if (edge == RectangleEdge.LEFT) {
 648:             result[0] = (float) (cursor - insets.getLeft() - 2.0);    
 649:             result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 650:         }
 651:         else if (edge == RectangleEdge.RIGHT) {
 652:             result[0] = (float) (cursor + insets.getRight() + 2.0);    
 653:             result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 654:         }
 655:         return result;
 656:     }
 657:     
 658:     /**
 659:      * Draws the axis line, tick marks and tick mark labels.
 660:      * 
 661:      * @param g2  the graphics device.
 662:      * @param cursor  the cursor.
 663:      * @param plotArea  the plot area.
 664:      * @param dataArea  the data area.
 665:      * @param edge  the edge that the axis is aligned with.
 666:      * 
 667:      * @return The width or height used to draw the axis.
 668:      */
 669:     protected AxisState drawTickMarksAndLabels(Graphics2D g2, 
 670:                                                double cursor,
 671:                                                Rectangle2D plotArea,
 672:                                                Rectangle2D dataArea, 
 673:                                                RectangleEdge edge) {
 674:                                               
 675:         AxisState state = new AxisState(cursor);
 676: 
 677:         if (isAxisLineVisible()) {
 678:             drawAxisLine(g2, cursor, dataArea, edge);
 679:         }
 680: 
 681:         double ol = getTickMarkOutsideLength();
 682:         double il = getTickMarkInsideLength();
 683: 
 684:         List ticks = refreshTicks(g2, state, dataArea, edge);
 685:         state.setTicks(ticks);
 686:         g2.setFont(getTickLabelFont());
 687:         Iterator iterator = ticks.iterator();
 688:         while (iterator.hasNext()) {
 689:             ValueTick tick = (ValueTick) iterator.next();
 690:             if (isTickLabelsVisible()) {
 691:                 g2.setPaint(getTickLabelPaint());
 692:                 float[] anchorPoint = calculateAnchorPoint(tick, cursor, 
 693:                         dataArea, edge);
 694:                 TextUtilities.drawRotatedString(tick.getText(), g2, 
 695:                         anchorPoint[0], anchorPoint[1], tick.getTextAnchor(), 
 696:                         tick.getAngle(), tick.getRotationAnchor());
 697:             }
 698: 
 699:             if (isTickMarksVisible() && tick.getTickType().equals(
 700:                     TickType.MAJOR)) {
 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:      *
1038:      * @since 1.0.5
1039:      */
1040:     public Range getDefaultAutoRange() {
1041:         return this.defaultAutoRange;
1042:     }
1043:     
1044:     /**
1045:      * Sets the default auto range and sends an {@link AxisChangeEvent} to all
1046:      * registered listeners.
1047:      * 
1048:      * @param range  the range (<code>null</code> not permitted).
1049:      * 
1050:      * @see #getDefaultAutoRange()
1051:      * 
1052:      * @since 1.0.5
1053:      */
1054:     public void setDefaultAutoRange(Range range) {
1055:         if (range == null) {
1056:             throw new IllegalArgumentException("Null 'range' argument.");
1057:         }
1058:         this.defaultAutoRange = range;
1059:         notifyListeners(new AxisChangeEvent(this));
1060:     }
1061: 
1062:     /**
1063:      * Returns the lower margin for the axis, expressed as a percentage of the 
1064:      * axis range.  This controls the space added to the lower end of the axis 
1065:      * when the axis range is automatically calculated (it is ignored when the 
1066:      * axis range is set explicitly). The default value is 0.05 (five percent).
1067:      *
1068:      * @return The lower margin.
1069:      *
1070:      * @see #setLowerMargin(double)
1071:      */
1072:     public double getLowerMargin() {
1073:         return this.lowerMargin;
1074:     }
1075: 
1076:     /**
1077:      * Sets the lower margin for the axis (as a percentage of the axis range) 
1078:      * and sends an {@link AxisChangeEvent} to all registered listeners.  This
1079:      * margin is added only when the axis range is auto-calculated - if you set 
1080:      * the axis range manually, the margin is ignored.
1081:      *
1082:      * @param margin  the margin percentage (for example, 0.05 is five percent).
1083:      *
1084:      * @see #getLowerMargin()
1085:      * @see #setUpperMargin(double)
1086:      */
1087:     public void setLowerMargin(double margin) {
1088:         this.lowerMargin = margin;
1089:         if (isAutoRange()) {
1090:             autoAdjustRange();
1091:         }
1092:         notifyListeners(new AxisChangeEvent(this));
1093:     }
1094: 
1095:     /**
1096:      * Returns the upper margin for the axis, expressed as a percentage of the 
1097:      * axis range.  This controls the space added to the lower end of the axis 
1098:      * when the axis range is automatically calculated (it is ignored when the 
1099:      * axis range is set explicitly). The default value is 0.05 (five percent).
1100:      *
1101:      * @return The upper margin.
1102:      *
1103:      * @see #setUpperMargin(double)
1104:      */
1105:     public double getUpperMargin() {
1106:         return this.upperMargin;
1107:     }
1108: 
1109:     /**
1110:      * Sets the upper margin for the axis (as a percentage of the axis range) 
1111:      * and sends an {@link AxisChangeEvent} to all registered listeners.  This 
1112:      * margin is added only when the axis range is auto-calculated - if you set
1113:      * the axis range manually, the margin is ignored.
1114:      *
1115:      * @param margin  the margin percentage (for example, 0.05 is five percent).
1116:      *
1117:      * @see #getLowerMargin()
1118:      * @see #setLowerMargin(double)
1119:      */
1120:     public void setUpperMargin(double margin) {
1121:         this.upperMargin = margin;
1122:         if (isAutoRange()) {
1123:             autoAdjustRange();
1124:         }
1125:         notifyListeners(new AxisChangeEvent(this));
1126:     }
1127: 
1128:     /**
1129:      * Returns the fixed auto range.
1130:      *
1131:      * @return The length.
1132:      * 
1133:      * @see #setFixedAutoRange(double)
1134:      */
1135:     public double getFixedAutoRange() {
1136:         return this.fixedAutoRange;
1137:     }
1138: 
1139:     /**
1140:      * Sets the fixed auto range for the axis.
1141:      *
1142:      * @param length  the range length.
1143:      * 
1144:      * @see #getFixedAutoRange()
1145:      */
1146:     public void setFixedAutoRange(double length) {
1147:         this.fixedAutoRange = length;
1148:         if (isAutoRange()) {
1149:             autoAdjustRange();
1150:         }
1151:         notifyListeners(new AxisChangeEvent(this));
1152:     }
1153: 
1154:     /**
1155:      * Returns the lower bound of the axis range.
1156:      *
1157:      * @return The lower bound.
1158:      * 
1159:      * @see #setLowerBound(double)
1160:      */
1161:     public double getLowerBound() {
1162:         return this.range.getLowerBound();
1163:     }
1164: 
1165:     /**
1166:      * Sets the lower bound for the axis range.  An {@link AxisChangeEvent} is 
1167:      * sent to all registered listeners.
1168:      *
1169:      * @param min  the new minimum.
1170:      * 
1171:      * @see #getLowerBound()
1172:      */
1173:     public void setLowerBound(double min) {
1174:         if (this.range.getUpperBound() > min) {
1175:             setRange(new Range(min, this.range.getUpperBound()));            
1176:         }
1177:         else {
1178:             setRange(new Range(min, min + 1.0));                        
1179:         }
1180:     }
1181: 
1182:     /**
1183:      * Returns the upper bound for the axis range.
1184:      *
1185:      * @return The upper bound.
1186:      * 
1187:      * @see #setUpperBound(double)
1188:      */
1189:     public double getUpperBound() {
1190:         return this.range.getUpperBound();
1191:     }
1192: 
1193:     /**
1194:      * Sets the upper bound for the axis range, and sends an 
1195:      * {@link AxisChangeEvent} to all registered listeners.
1196:      *
1197:      * @param max  the new maximum.
1198:      * 
1199:      * @see #getUpperBound()
1200:      */
1201:     public void setUpperBound(double max) {
1202:         if (this.range.getLowerBound() < max) {
1203:             setRange(new Range(this.range.getLowerBound(), max));
1204:         }
1205:         else {
1206:             setRange(max - 1.0, max);
1207:         }
1208:     }
1209: 
1210:     /**
1211:      * Returns the range for the axis.
1212:      *
1213:      * @return The axis range (never <code>null</code>).
1214:      * 
1215:      * @see #setRange(Range)
1216:      */
1217:     public Range getRange() {
1218:         return this.range;
1219:     }
1220: 
1221:     /**
1222:      * Sets the range attribute and sends an {@link AxisChangeEvent} to all 
1223:      * registered listeners.  As a side-effect, the auto-range flag is set to 
1224:      * <code>false</code>.
1225:      *
1226:      * @param range  the range (<code>null</code> not permitted).
1227:      * 
1228:      * @see #getRange()
1229:      */
1230:     public void setRange(Range range) {
1231:         // defer argument checking
1232:         setRange(range, true, true);
1233:     }
1234: 
1235:     /**
1236:      * Sets the range for the axis, if requested, sends an 
1237:      * {@link AxisChangeEvent} to all registered listeners.  As a side-effect, 
1238:      * the auto-range flag is set to <code>false</code> (optional).
1239:      *
1240:      * @param range  the range (<code>null</code> not permitted).
1241:      * @param turnOffAutoRange  a flag that controls whether or not the auto 
1242:      *                          range is turned off.         
1243:      * @param notify  a flag that controls whether or not listeners are 
1244:      *                notified.
1245:      *                
1246:      * @see #getRange()
1247:      */
1248:     public void setRange(Range range, boolean turnOffAutoRange, 
1249:                          boolean notify) {
1250:         if (range == null) {
1251:             throw new IllegalArgumentException("Null 'range' argument.");
1252:         }
1253:         if (turnOffAutoRange) {
1254:             this.autoRange = false;
1255:         }
1256:         this.range = range;
1257:         if (notify) {
1258:             notifyListeners(new AxisChangeEvent(this));
1259:         }
1260:     }
1261: 
1262:     /**
1263:      * Sets the axis range and sends an {@link AxisChangeEvent} to all 
1264:      * registered listeners.  As a side-effect, the auto-range flag is set to 
1265:      * <code>false</code>.
1266:      *
1267:      * @param lower  the lower axis limit.
1268:      * @param upper  the upper axis limit.
1269:      * 
1270:      * @see #getRange()
1271:      * @see #setRange(Range)
1272:      */
1273:     public void setRange(double lower, double upper) {
1274:         setRange(new Range(lower, upper));
1275:     }
1276:     
1277:     /**
1278:      * Sets the range for the axis (after first adding the current margins to 
1279:      * the specified range) and sends an {@link AxisChangeEvent} to all 
1280:      * registered listeners.
1281:      * 
1282:      * @param range  the range (<code>null</code> not permitted).
1283:      */
1284:     public void setRangeWithMargins(Range range) {
1285:         setRangeWithMargins(range, true, true);
1286:     }
1287: 
1288:     /**
1289:      * Sets the range for the axis after first adding the current margins to 
1290:      * the range and, if requested, sends an {@link AxisChangeEvent} to all 
1291:      * registered listeners.  As a side-effect, the auto-range flag is set to 
1292:      * <code>false</code> (optional).
1293:      *
1294:      * @param range  the range (excluding margins, <code>null</code> not 
1295:      *               permitted).
1296:      * @param turnOffAutoRange  a flag that controls whether or not the auto 
1297:      *                          range is turned off.
1298:      * @param notify  a flag that controls whether or not listeners are 
1299:      *                notified.
1300:      */
1301:     public void setRangeWithMargins(Range range, boolean turnOffAutoRange, 
1302:                                     boolean notify) {
1303:         if (range == null) {
1304:             throw new IllegalArgumentException("Null 'range' argument.");
1305:         }
1306:         setRange(Range.expand(range, getLowerMargin(), getUpperMargin()), 
1307:                 turnOffAutoRange, notify);
1308:     }
1309: 
1310:     /**
1311:      * Sets the axis range (after first adding the current margins to the 
1312:      * range) and sends an {@link AxisChangeEvent} to all registered listeners.
1313:      * As a side-effect, the auto-range flag is set to <code>false</code>.
1314:      *
1315:      * @param lower  the lower axis limit.
1316:      * @param upper  the upper axis limit.
1317:      */
1318:     public void setRangeWithMargins(double lower, double upper) {
1319:         setRangeWithMargins(new Range(lower, upper));
1320:     }
1321:     
1322:     /**
1323:      * Sets the axis range, where the new range is 'size' in length, and 
1324:      * centered on 'value'.
1325:      *
1326:      * @param value  the central value.
1327:      * @param length  the range length.
1328:      */
1329:     public void setRangeAboutValue(double value, double length) {
1330:         setRange(new Range(value - length / 2, value + length / 2));
1331:     }
1332: 
1333:     /**
1334:      * Returns a flag indicating whether or not the tick unit is automatically
1335:      * selected from a range of standard tick units.
1336:      *
1337:      * @return A flag indicating whether or not the tick unit is automatically
1338:      *         selected.
1339:      *         
1340:      * @see #setAutoTickUnitSelection(boolean)
1341:      */
1342:     public boolean isAutoTickUnitSelection() {
1343:         return this.autoTickUnitSelection;
1344:     }
1345: 
1346:     /**
1347:      * Sets a flag indicating whether or not the tick unit is automatically
1348:      * selected from a range of standard tick units.  If the flag is changed, 
1349:      * registered listeners are notified that the chart has changed.
1350:      *
1351:      * @param flag  the new value of the flag.
1352:      * 
1353:      * @see #isAutoTickUnitSelection()
1354:      */
1355:     public void setAutoTickUnitSelection(boolean flag) {
1356:         setAutoTickUnitSelection(flag, true);
1357:     }
1358: 
1359:     /**
1360:      * Sets a flag indicating whether or not the tick unit is automatically
1361:      * selected from a range of standard tick units.
1362:      *
1363:      * @param flag  the new value of the flag.
1364:      * @param notify  notify listeners?
1365:      * 
1366:      * @see #isAutoTickUnitSelection()
1367:      */
1368:     public void setAutoTickUnitSelection(boolean flag, boolean notify) {
1369: 
1370:         if (this.autoTickUnitSelection != flag) {
1371:             this.autoTickUnitSelection = flag;
1372:             if (notify) {
1373:                 notifyListeners(new AxisChangeEvent(this));
1374:             }
1375:         }
1376:     }
1377: 
1378:     /**
1379:      * Returns the source for obtaining standard tick units for the axis.
1380:      *
1381:      * @return The source (possibly <code>null</code>).
1382:      * 
1383:      * @see #setStandardTickUnits(TickUnitSource)
1384:      */
1385:     public TickUnitSource getStandardTickUnits() {
1386:         return this.standardTickUnits;
1387:     }
1388: 
1389:     /**
1390:      * Sets the source for obtaining standard tick units for the axis and sends
1391:      * an {@link AxisChangeEvent} to all registered listeners.  The axis will 
1392:      * try to select the smallest tick unit from the source that does not cause
1393:      * the tick labels to overlap (see also the 
1394:      * {@link #setAutoTickUnitSelection(boolean)} method.
1395:      *
1396:      * @param source  the source for standard tick units (<code>null</code> 
1397:      *                permitted).
1398:      *                
1399:      * @see #getStandardTickUnits()
1400:      */
1401:     public void setStandardTickUnits(TickUnitSource source) {
1402:         this.standardTickUnits = source;
1403:         notifyListeners(new AxisChangeEvent(this));
1404:     }
1405:     
1406:     /**
1407:      * Converts a data value to a coordinate in Java2D space, assuming that the
1408:      * axis runs along one edge of the specified dataArea.
1409:      * <p>
1410:      * Note that it is possible for the coordinate to fall outside the area.
1411:      *
1412:      * @param value  the data value.
1413:      * @param area  the area for plotting the data.
1414:      * @param edge  the edge along which the axis lies.
1415:      *
1416:      * @return The Java2D coordinate.
1417:      * 
1418:      * @see #java2DToValue(double, Rectangle2D, RectangleEdge)
1419:      */
1420:     public abstract double valueToJava2D(double value, Rectangle2D area, 
1421:                                          RectangleEdge edge);
1422:     
1423:     /**
1424:      * Converts a length in data coordinates into the corresponding length in 
1425:      * Java2D coordinates.
1426:      * 
1427:      * @param length  the length.
1428:      * @param area  the plot area.
1429:      * @param edge  the edge along which the axis lies.
1430:      * 
1431:      * @return The length in Java2D coordinates.
1432:      */
1433:     public double lengthToJava2D(double length, Rectangle2D area, 
1434:                                  RectangleEdge edge) {
1435:         double zero = valueToJava2D(0.0, area, edge);
1436:         double l = valueToJava2D(length, area, edge);
1437:         return Math.abs(l - zero);
1438:     }
1439: 
1440:     /**
1441:      * Converts a coordinate in Java2D space to the corresponding data value,
1442:      * assuming that the axis runs along one edge of the specified dataArea.
1443:      *
1444:      * @param java2DValue  the coordinate in Java2D space.
1445:      * @param area  the area in which the data is plotted.
1446:      * @param edge  the edge along which the axis lies.
1447:      *
1448:      * @return The data value.
1449:      * 
1450:      * @see #valueToJava2D(double, Rectangle2D, RectangleEdge)
1451:      */
1452:     public abstract double java2DToValue(double java2DValue,
1453:                                          Rectangle2D area,
1454:                                          RectangleEdge edge);
1455: 
1456:     /**
1457:      * Automatically sets the axis range to fit the range of values in the 
1458:      * dataset.  Sometimes this can depend on the renderer used as well (for 
1459:      * example, the renderer may "stack" values, requiring an axis range 
1460:      * greater than otherwise necessary).
1461:      */
1462:     protected abstract void autoAdjustRange();
1463: 
1464:     /**
1465:      * Centers the axis range about the specified value and sends an 
1466:      * {@link AxisChangeEvent} to all registered listeners.
1467:      *
1468:      * @param value  the center value.
1469:      */
1470:     public void centerRange(double value) {
1471: 
1472:         double central = this.range.getCentralValue();
1473:         Range adjusted = new Range(this.range.getLowerBound() + value - central,
1474:                 this.range.getUpperBound() + value - central);
1475:         setRange(adjusted);
1476: 
1477:     }
1478: 
1479:     /**
1480:      * Increases or decreases the axis range by the specified percentage about 
1481:      * the central value and sends an {@link AxisChangeEvent} to all registered
1482:      * listeners.
1483:      * <P>
1484:      * To double the length of the axis range, use 200% (2.0).
1485:      * To halve the length of the axis range, use 50% (0.5).
1486:      *
1487:      * @param percent  the resize factor.
1488:      * 
1489:      * @see #resizeRange(double, double)
1490:      */
1491:     public void resizeRange(double percent) {
1492:         resizeRange(percent, this.range.getCentralValue());
1493:     }
1494: 
1495:     /**
1496:      * Increases or decreases the axis range by the specified percentage about
1497:      * the specified anchor value and sends an {@link AxisChangeEvent} to all 
1498:      * registered listeners.
1499:      * <P>
1500:      * To double the length of the axis range, use 200% (2.0).
1501:      * To halve the length of the axis range, use 50% (0.5).
1502:      *
1503:      * @param percent  the resize factor.
1504:      * @param anchorValue  the new central value after the resize.
1505:      * 
1506:      * @see #resizeRange(double)
1507:      */
1508:     public void resizeRange(double percent, double anchorValue) {
1509:         if (percent > 0.0) {
1510:             double halfLength = this.range.getLength() * percent / 2;
1511:             Range adjusted = new Range(anchorValue - halfLength, 
1512:                     anchorValue + halfLength);
1513:             setRange(adjusted);
1514:         }
1515:         else {
1516:             setAutoRange(true);
1517:         }
1518:     }
1519:     
1520:     /**
1521:      * Zooms in on the current range.
1522:      * 
1523:      * @param lowerPercent  the new lower bound.
1524:      * @param upperPercent  the new upper bound.
1525:      */
1526:     public void zoomRange(double lowerPercent, double upperPercent) {
1527:         double start = this.range.getLowerBound();
1528:         double length = this.range.getLength();
1529:         Range adjusted = null;
1530:         if (isInverted()) {
1531:             adjusted = new Range(start + (length * (1 - upperPercent)), 
1532:                                  start + (length * (1 - lowerPercent))); 
1533:         }
1534:         else {
1535:             adjusted = new Range(start + length * lowerPercent, 
1536:                     start + length * upperPercent);
1537:         }
1538:         setRange(adjusted);
1539:     }
1540: 
1541:     /**
1542:      * Returns the auto tick index.
1543:      *
1544:      * @return The auto tick index.
1545:      * 
1546:      * @see #setAutoTickIndex(int)
1547:      */
1548:     protected int getAutoTickIndex() {
1549:         return this.autoTickIndex;
1550:     }
1551: 
1552:     /**
1553:      * Sets the auto tick index.
1554:      *
1555:      * @param index  the new value.
1556:      * 
1557:      * @see #getAutoTickIndex()
1558:      */
1559:     protected void setAutoTickIndex(int index) {
1560:         this.autoTickIndex = index;
1561:     }
1562: 
1563:     /**
1564:      * Tests the axis for equality with an arbitrary object.
1565:      *
1566:      * @param obj  the object (<code>null</code> permitted).
1567:      *
1568:      * @return <code>true</code> or <code>false</code>.
1569:      */
1570:     public boolean equals(Object obj) {
1571: 
1572:         if (obj == this) {
1573:             return true;
1574:         }
1575:         if (!(obj instanceof ValueAxis)) {
1576:             return false;
1577:         }
1578: 
1579:         ValueAxis that = (ValueAxis) obj;
1580:         
1581:         if (this.positiveArrowVisible != that.positiveArrowVisible) {
1582:             return false;
1583:         }
1584:         if (this.negativeArrowVisible != that.negativeArrowVisible) {
1585:             return false;
1586:         }
1587:         if (this.inverted != that.inverted) {
1588:             return false;
1589:         }
1590:         if (!ObjectUtilities.equal(this.range, that.range)) {
1591:             return false;
1592:         }
1593:         if (this.autoRange != that.autoRange) {
1594:             return false;
1595:         }
1596:         if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
1597:             return false;
1598:         }
1599:         if (!this.defaultAutoRange.equals(that.defaultAutoRange)) {
1600:             return false;
1601:         }
1602:         if (this.upperMargin != that.upperMargin) {
1603:             return false;
1604:         }
1605:         if (this.lowerMargin != that.lowerMargin) {
1606:             return false;
1607:         }
1608:         if (this.fixedAutoRange != that.fixedAutoRange) {
1609:             return false;
1610:         }
1611:         if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
1612:             return false;
1613:         }
1614:         if (!ObjectUtilities.equal(this.standardTickUnits, 
1615:                 that.standardTickUnits)) {
1616:             return false;
1617:         }
1618:         if (this.verticalTickLabels != that.verticalTickLabels) {
1619:             return false;
1620:         }
1621: 
1622:         return super.equals(obj);
1623: 
1624:     }
1625:     
1626:     /**
1627:      * Returns a clone of the object.
1628:      * 
1629:      * @return A clone.
1630:      * 
1631:      * @throws CloneNotSupportedException if some component of the axis does 
1632:      *         not support cloning.
1633:      */
1634:     public Object clone() throws CloneNotSupportedException {
1635:         ValueAxis clone = (ValueAxis) super.clone();
1636:         return clone;
1637:     }
1638:     
1639:     /**
1640:      * Provides serialization support.
1641:      *
1642:      * @param stream  the output stream.
1643:      *
1644:      * @throws IOException  if there is an I/O error.
1645:      */
1646:     private void writeObject(ObjectOutputStream stream) throws IOException {
1647:         stream.defaultWriteObject();
1648:         SerialUtilities.writeShape(this.upArrow, stream);
1649:         SerialUtilities.writeShape(this.downArrow, stream);
1650:         SerialUtilities.writeShape(this.leftArrow, stream);
1651:         SerialUtilities.writeShape(this.rightArrow, stream);
1652:     }
1653: 
1654:     /**
1655:      * Provides serialization support.
1656:      *
1657:      * @param stream  the input stream.
1658:      *
1659:      * @throws IOException  if there is an I/O error.
1660:      * @throws ClassNotFoundException  if there is a classpath problem.
1661:      */
1662:     private void readObject(ObjectInputStream stream) 
1663:             throws IOException, ClassNotFoundException {
1664: 
1665:         stream.defaultReadObject();
1666:         this.upArrow = SerialUtilities.readShape(stream);
1667:         this.downArrow = SerialUtilities.readShape(stream);
1668:         this.leftArrow = SerialUtilities.readShape(stream);
1669:         this.rightArrow = SerialUtilities.readShape(stream);
1670: 
1671:     }
1672:    
1673: }