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-2008, 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-2008, 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, Serializable {
 137: 
 138:     /** For serialization. */
 139:     private static final long serialVersionUID = 3698345477322391456L;
 140: 
 141:     /** The default axis range. */
 142:     public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
 143: 
 144:     /** The default auto-range value. */
 145:     public static final boolean DEFAULT_AUTO_RANGE = true;
 146: 
 147:     /** The default inverted flag setting. */
 148:     public static final boolean DEFAULT_INVERTED = false;
 149: 
 150:     /** The default minimum auto range. */
 151:     public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
 152: 
 153:     /** The default value for the lower margin (0.05 = 5%). */
 154:     public static final double DEFAULT_LOWER_MARGIN = 0.05;
 155: 
 156:     /** The default value for the upper margin (0.05 = 5%). */
 157:     public static final double DEFAULT_UPPER_MARGIN = 0.05;
 158: 
 159:     /**
 160:      * The default lower bound for the axis.
 161:      *
 162:      * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
 163:      *     attribute (see {@link #getDefaultAutoRange()}).
 164:      */
 165:     public static final double DEFAULT_LOWER_BOUND = 0.0;
 166: 
 167:     /**
 168:      * The default upper bound for the axis.
 169:      *
 170:      * @deprecated From 1.0.5 onwards, the axis defines a defaultRange
 171:      *     attribute (see {@link #getDefaultAutoRange()}).
 172:      */
 173:     public static final double DEFAULT_UPPER_BOUND = 1.0;
 174: 
 175:     /** The default auto-tick-unit-selection value. */
 176:     public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
 177: 
 178:     /** The maximum tick count. */
 179:     public static final int MAXIMUM_TICK_COUNT = 500;
 180: 
 181:     /**
 182:      * A flag that controls whether an arrow is drawn at the positive end of
 183:      * the axis line.
 184:      */
 185:     private boolean positiveArrowVisible;
 186: 
 187:     /**
 188:      * A flag that controls whether an arrow is drawn at the negative end of
 189:      * the axis line.
 190:      */
 191:     private boolean negativeArrowVisible;
 192: 
 193:     /** The shape used for an up arrow. */
 194:     private transient Shape upArrow;
 195: 
 196:     /** The shape used for a down arrow. */
 197:     private transient Shape downArrow;
 198: 
 199:     /** The shape used for a left arrow. */
 200:     private transient Shape leftArrow;
 201: 
 202:     /** The shape used for a right arrow. */
 203:     private transient Shape rightArrow;
 204: 
 205:     /** A flag that affects the orientation of the values on the axis. */
 206:     private boolean inverted;
 207: 
 208:     /** The axis range. */
 209:     private Range range;
 210: 
 211:     /**
 212:      * Flag that indicates whether the axis automatically scales to fit the
 213:      * chart data.
 214:      */
 215:     private boolean autoRange;
 216: 
 217:     /** The minimum size for the 'auto' axis range (excluding margins). */
 218:     private double autoRangeMinimumSize;
 219: 
 220:     /**
 221:      * The default range is used when the dataset is empty and the axis needs
 222:      * to determine the auto range.
 223:      *
 224:      * @since 1.0.5
 225:      */
 226:     private Range defaultAutoRange;
 227: 
 228:     /**
 229:      * The upper margin percentage.  This indicates the amount by which the
 230:      * maximum axis value exceeds the maximum data value (as a percentage of
 231:      * the range on the axis) when the axis range is determined automatically.
 232:      */
 233:     private double upperMargin;
 234: 
 235:     /**
 236:      * The lower margin.  This is a percentage that indicates the amount by
 237:      * which the minimum axis value is "less than" the minimum data value when
 238:      * the axis range is determined automatically.
 239:      */
 240:     private double lowerMargin;
 241: 
 242:     /**
 243:      * If this value is positive, the amount is subtracted from the maximum
 244:      * data value to determine the lower axis range.  This can be used to
 245:      * provide a fixed "window" on dynamic data.
 246:      */
 247:     private double fixedAutoRange;
 248: 
 249:     /**
 250:      * Flag that indicates whether or not the tick unit is selected
 251:      * automatically.
 252:      */
 253:     private boolean autoTickUnitSelection;
 254: 
 255:     /** The standard tick units for the axis. */
 256:     private TickUnitSource standardTickUnits;
 257: 
 258:     /** An index into an array of standard tick values. */
 259:     private int autoTickIndex;
 260: 
 261:     /** A flag indicating whether or not tick labels are rotated to vertical. */
 262:     private boolean verticalTickLabels;
 263: 
 264:     /**
 265:      * Constructs a value axis.
 266:      *
 267:      * @param label  the axis label (<code>null</code> permitted).
 268:      * @param standardTickUnits  the source for standard tick units
 269:      *                           (<code>null</code> permitted).
 270:      */
 271:     protected ValueAxis(String label, TickUnitSource standardTickUnits) {
 272: 
 273:         super(label);
 274: 
 275:         this.positiveArrowVisible = false;
 276:         this.negativeArrowVisible = false;
 277: 
 278:         this.range = DEFAULT_RANGE;
 279:         this.autoRange = DEFAULT_AUTO_RANGE;
 280:         this.defaultAutoRange = DEFAULT_RANGE;
 281: 
 282:         this.inverted = DEFAULT_INVERTED;
 283:         this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
 284: 
 285:         this.lowerMargin = DEFAULT_LOWER_MARGIN;
 286:         this.upperMargin = DEFAULT_UPPER_MARGIN;
 287: 
 288:         this.fixedAutoRange = 0.0;
 289: 
 290:         this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
 291:         this.standardTickUnits = standardTickUnits;
 292: 
 293:         Polygon p1 = new Polygon();
 294:         p1.addPoint(0, 0);
 295:         p1.addPoint(-2, 2);
 296:         p1.addPoint(2, 2);
 297: 
 298:         this.upArrow = p1;
 299: 
 300:         Polygon p2 = new Polygon();
 301:         p2.addPoint(0, 0);
 302:         p2.addPoint(-2, -2);
 303:         p2.addPoint(2, -2);
 304: 
 305:         this.downArrow = p2;
 306: 
 307:         Polygon p3 = new Polygon();
 308:         p3.addPoint(0, 0);
 309:         p3.addPoint(-2, -2);
 310:         p3.addPoint(-2, 2);
 311: 
 312:         this.rightArrow = p3;
 313: 
 314:         Polygon p4 = new Polygon();
 315:         p4.addPoint(0, 0);
 316:         p4.addPoint(2, -2);
 317:         p4.addPoint(2, 2);
 318: 
 319:         this.leftArrow = p4;
 320: 
 321:         this.verticalTickLabels = false;
 322: 
 323:     }
 324: 
 325:     /**
 326:      * Returns <code>true</code> if the tick labels should be rotated (to
 327:      * vertical), and <code>false</code> otherwise.
 328:      *
 329:      * @return <code>true</code> or <code>false</code>.
 330:      *
 331:      * @see #setVerticalTickLabels(boolean)
 332:      */
 333:     public boolean isVerticalTickLabels() {
 334:         return this.verticalTickLabels;
 335:     }
 336: 
 337:     /**
 338:      * Sets the flag that controls whether the tick labels are displayed
 339:      * vertically (that is, rotated 90 degrees from horizontal).  If the flag
 340:      * is changed, an {@link AxisChangeEvent} is sent to all registered
 341:      * listeners.
 342:      *
 343:      * @param flag  the flag.
 344:      *
 345:      * @see #isVerticalTickLabels()
 346:      */
 347:     public void setVerticalTickLabels(boolean flag) {
 348:         if (this.verticalTickLabels != flag) {
 349:             this.verticalTickLabels = flag;
 350:             notifyListeners(new AxisChangeEvent(this));
 351:         }
 352:     }
 353: 
 354:     /**
 355:      * Returns a flag that controls whether or not the axis line has an arrow
 356:      * drawn that points in the positive direction for the axis.
 357:      *
 358:      * @return A boolean.
 359:      *
 360:      * @see #setPositiveArrowVisible(boolean)
 361:      */
 362:     public boolean isPositiveArrowVisible() {
 363:         return this.positiveArrowVisible;
 364:     }
 365: 
 366:     /**
 367:      * Sets a flag that controls whether or not the axis lines has an arrow
 368:      * drawn that points in the positive direction for the axis, and sends an
 369:      * {@link AxisChangeEvent} to all registered listeners.
 370:      *
 371:      * @param visible  the flag.
 372:      *
 373:      * @see #isPositiveArrowVisible()
 374:      */
 375:     public void setPositiveArrowVisible(boolean visible) {
 376:         this.positiveArrowVisible = visible;
 377:         notifyListeners(new AxisChangeEvent(this));
 378:     }
 379: 
 380:     /**
 381:      * Returns a flag that controls whether or not the axis line has an arrow
 382:      * drawn that points in the negative direction for the axis.
 383:      *
 384:      * @return A boolean.
 385:      *
 386:      * @see #setNegativeArrowVisible(boolean)
 387:      */
 388:     public boolean isNegativeArrowVisible() {
 389:         return this.negativeArrowVisible;
 390:     }
 391: 
 392:     /**
 393:      * Sets a flag that controls whether or not the axis lines has an arrow
 394:      * drawn that points in the negative direction for the axis, and sends an
 395:      * {@link AxisChangeEvent} to all registered listeners.
 396:      *
 397:      * @param visible  the flag.
 398:      *
 399:      * @see #setNegativeArrowVisible(boolean)
 400:      */
 401:     public void setNegativeArrowVisible(boolean visible) {
 402:         this.negativeArrowVisible = visible;
 403:         notifyListeners(new AxisChangeEvent(this));
 404:     }
 405: 
 406:     /**
 407:      * Returns a shape that can be displayed as an arrow pointing upwards at
 408:      * the end of an axis line.
 409:      *
 410:      * @return A shape (never <code>null</code>).
 411:      *
 412:      * @see #setUpArrow(Shape)
 413:      */
 414:     public Shape getUpArrow() {
 415:         return this.upArrow;
 416:     }
 417: 
 418:     /**
 419:      * Sets the shape that can be displayed as an arrow pointing upwards at
 420:      * the end of an axis line and sends an {@link AxisChangeEvent} to all
 421:      * registered listeners.
 422:      *
 423:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 424:      *
 425:      * @see #getUpArrow()
 426:      */
 427:     public void setUpArrow(Shape arrow) {
 428:         if (arrow == null) {
 429:             throw new IllegalArgumentException("Null 'arrow' argument.");
 430:         }
 431:         this.upArrow = arrow;
 432:         notifyListeners(new AxisChangeEvent(this));
 433:     }
 434: 
 435:     /**
 436:      * Returns a shape that can be displayed as an arrow pointing downwards at
 437:      * the end of an axis line.
 438:      *
 439:      * @return A shape (never <code>null</code>).
 440:      *
 441:      * @see #setDownArrow(Shape)
 442:      */
 443:     public Shape getDownArrow() {
 444:         return this.downArrow;
 445:     }
 446: 
 447:     /**
 448:      * Sets the shape that can be displayed as an arrow pointing downwards at
 449:      * the end of an axis line and sends an {@link AxisChangeEvent} to all
 450:      * registered listeners.
 451:      *
 452:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 453:      *
 454:      * @see #getDownArrow()
 455:      */
 456:     public void setDownArrow(Shape arrow) {
 457:         if (arrow == null) {
 458:             throw new IllegalArgumentException("Null 'arrow' argument.");
 459:         }
 460:         this.downArrow = arrow;
 461:         notifyListeners(new AxisChangeEvent(this));
 462:     }
 463: 
 464:     /**
 465:      * Returns a shape that can be displayed as an arrow pointing left at the
 466:      * end of an axis line.
 467:      *
 468:      * @return A shape (never <code>null</code>).
 469:      *
 470:      * @see #setLeftArrow(Shape)
 471:      */
 472:     public Shape getLeftArrow() {
 473:         return this.leftArrow;
 474:     }
 475: 
 476:     /**
 477:      * Sets the shape that can be displayed as an arrow pointing left at the
 478:      * end of an axis line and sends an {@link AxisChangeEvent} to all
 479:      * registered listeners.
 480:      *
 481:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 482:      *
 483:      * @see #getLeftArrow()
 484:      */
 485:     public void setLeftArrow(Shape arrow) {
 486:         if (arrow == null) {
 487:             throw new IllegalArgumentException("Null 'arrow' argument.");
 488:         }
 489:         this.leftArrow = arrow;
 490:         notifyListeners(new AxisChangeEvent(this));
 491:     }
 492: 
 493:     /**
 494:      * Returns a shape that can be displayed as an arrow pointing right at the
 495:      * end of an axis line.
 496:      *
 497:      * @return A shape (never <code>null</code>).
 498:      *
 499:      * @see #setRightArrow(Shape)
 500:      */
 501:     public Shape getRightArrow() {
 502:         return this.rightArrow;
 503:     }
 504: 
 505:     /**
 506:      * Sets the shape that can be displayed as an arrow pointing rightwards at
 507:      * the end of an axis line and sends an {@link AxisChangeEvent} to all
 508:      * registered listeners.
 509:      *
 510:      * @param arrow  the arrow shape (<code>null</code> not permitted).
 511:      *
 512:      * @see #getRightArrow()
 513:      */
 514:     public void setRightArrow(Shape arrow) {
 515:         if (arrow == null) {
 516:             throw new IllegalArgumentException("Null 'arrow' argument.");
 517:         }
 518:         this.rightArrow = arrow;
 519:         notifyListeners(new AxisChangeEvent(this));
 520:     }
 521: 
 522:     /**
 523:      * Draws an axis line at the current cursor position and edge.
 524:      *
 525:      * @param g2  the graphics device.
 526:      * @param cursor  the cursor position.
 527:      * @param dataArea  the data area.
 528:      * @param edge  the edge.
 529:      */
 530:     protected void drawAxisLine(Graphics2D g2, double cursor,
 531:                                 Rectangle2D dataArea, RectangleEdge edge) {
 532:         Line2D axisLine = null;
 533:         if (edge == RectangleEdge.TOP) {
 534:             axisLine = new Line2D.Double(dataArea.getX(), cursor,
 535:                     dataArea.getMaxX(), cursor);
 536:         }
 537:         else if (edge == RectangleEdge.BOTTOM) {
 538:             axisLine = new Line2D.Double(dataArea.getX(), cursor,
 539:                     dataArea.getMaxX(), cursor);
 540:         }
 541:         else if (edge == RectangleEdge.LEFT) {
 542:             axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
 543:                     dataArea.getMaxY());
 544:         }
 545:         else if (edge == RectangleEdge.RIGHT) {
 546:             axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
 547:                     dataArea.getMaxY());
 548:         }
 549:         g2.setPaint(getAxisLinePaint());
 550:         g2.setStroke(getAxisLineStroke());
 551:         g2.draw(axisLine);
 552: 
 553:         boolean drawUpOrRight = false;
 554:         boolean drawDownOrLeft = false;
 555:         if (this.positiveArrowVisible) {
 556:             if (this.inverted) {
 557:                 drawDownOrLeft = true;
 558:             }
 559:             else {
 560:                 drawUpOrRight = true;
 561:             }
 562:         }
 563:         if (this.negativeArrowVisible) {
 564:             if (this.inverted) {
 565:                 drawUpOrRight = true;
 566:             }
 567:             else {
 568:                 drawDownOrLeft = true;
 569:             }
 570:         }
 571:         if (drawUpOrRight) {
 572:             double x = 0.0;
 573:             double y = 0.0;
 574:             Shape arrow = null;
 575:             if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
 576:                 x = dataArea.getMaxX();
 577:                 y = cursor;
 578:                 arrow = this.rightArrow;
 579:             }
 580:             else if (edge == RectangleEdge.LEFT
 581:                     || edge == RectangleEdge.RIGHT) {
 582:                 x = cursor;
 583:                 y = dataArea.getMinY();
 584:                 arrow = this.upArrow;
 585:             }
 586: 
 587:             // draw the arrow...
 588:             AffineTransform transformer = new AffineTransform();
 589:             transformer.setToTranslation(x, y);
 590:             Shape shape = transformer.createTransformedShape(arrow);
 591:             g2.fill(shape);
 592:             g2.draw(shape);
 593:         }
 594: 
 595:         if (drawDownOrLeft) {
 596:             double x = 0.0;
 597:             double y = 0.0;
 598:             Shape arrow = null;
 599:             if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
 600:                 x = dataArea.getMinX();
 601:                 y = cursor;
 602:                 arrow = this.leftArrow;
 603:             }
 604:             else if (edge == RectangleEdge.LEFT
 605:                     || edge == RectangleEdge.RIGHT) {
 606:                 x = cursor;
 607:                 y = dataArea.getMaxY();
 608:                 arrow = this.downArrow;
 609:             }
 610: 
 611:             // draw the arrow...
 612:             AffineTransform transformer = new AffineTransform();
 613:             transformer.setToTranslation(x, y);
 614:             Shape shape = transformer.createTransformedShape(arrow);
 615:             g2.fill(shape);
 616:             g2.draw(shape);
 617:         }
 618: 
 619:     }
 620: 
 621:     /**
 622:      * Calculates the anchor point for a tick label.
 623:      *
 624:      * @param tick  the tick.
 625:      * @param cursor  the cursor.
 626:      * @param dataArea  the data area.
 627:      * @param edge  the edge on which the axis is drawn.
 628:      *
 629:      * @return The x and y coordinates of the anchor point.
 630:      */
 631:     protected float[] calculateAnchorPoint(ValueTick tick,
 632:                                            double cursor,
 633:                                            Rectangle2D dataArea,
 634:                                            RectangleEdge edge) {
 635: 
 636:         RectangleInsets insets = getTickLabelInsets();
 637:         float[] result = new float[2];
 638:         if (edge == RectangleEdge.TOP) {
 639:             result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 640:             result[1] = (float) (cursor - insets.getBottom() - 2.0);
 641:         }
 642:         else if (edge == RectangleEdge.BOTTOM) {
 643:             result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 644:             result[1] = (float) (cursor + insets.getTop() + 2.0);
 645:         }
 646:         else if (edge == RectangleEdge.LEFT) {
 647:             result[0] = (float) (cursor - insets.getLeft() - 2.0);
 648:             result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 649:         }
 650:         else if (edge == RectangleEdge.RIGHT) {
 651:             result[0] = (float) (cursor + insets.getRight() + 2.0);
 652:             result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
 653:         }
 654:         return result;
 655:     }
 656: 
 657:     /**
 658:      * Draws the axis line, tick marks and tick mark labels.
 659:      *
 660:      * @param g2  the graphics device.
 661:      * @param cursor  the cursor.
 662:      * @param plotArea  the plot area.
 663:      * @param dataArea  the data area.
 664:      * @param edge  the edge that the axis is aligned with.
 665:      *
 666:      * @return The width or height used to draw the axis.
 667:      */
 668:     protected AxisState drawTickMarksAndLabels(Graphics2D g2,
 669:                                                double cursor,
 670:                                                Rectangle2D plotArea,
 671:                                                Rectangle2D dataArea,
 672:                                                RectangleEdge edge) {
 673: 
 674:         AxisState state = new AxisState(cursor);
 675: 
 676:         if (isAxisLineVisible()) {
 677:             drawAxisLine(g2, cursor, dataArea, edge);
 678:         }
 679: 
 680:         double ol = getTickMarkOutsideLength();
 681:         double il = getTickMarkInsideLength();
 682: 
 683:         List ticks = refreshTicks(g2, state, dataArea, edge);
 684:         state.setTicks(ticks);
 685:         g2.setFont(getTickLabelFont());
 686:         Iterator iterator = ticks.iterator();
 687:         while (iterator.hasNext()) {
 688:             ValueTick tick = (ValueTick) iterator.next();
 689:             if (isTickLabelsVisible()) {
 690:                 g2.setPaint(getTickLabelPaint());
 691:                 float[] anchorPoint = calculateAnchorPoint(tick, cursor,
 692:                         dataArea, edge);
 693:                 TextUtilities.drawRotatedString(tick.getText(), g2,
 694:                         anchorPoint[0], anchorPoint[1], tick.getTextAnchor(),
 695:                         tick.getAngle(), tick.getRotationAnchor());
 696:             }
 697: 
 698:             if (isTickMarksVisible() && tick.getTickType().equals(
 699:                     TickType.MAJOR)) {
 700:                 float xx = (float) valueToJava2D(tick.getValue(), dataArea,
 701:                         edge);
 702:                 Line2D mark = null;
 703:                 g2.setStroke(getTickMarkStroke());
 704:                 g2.setPaint(getTickMarkPaint());
 705:                 if (edge == RectangleEdge.LEFT) {
 706:                     mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx);
 707:                 }
 708:                 else if (edge == RectangleEdge.RIGHT) {
 709:                     mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx);
 710:                 }
 711:                 else if (edge == RectangleEdge.TOP) {
 712:                     mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il);
 713:                 }
 714:                 else if (edge == RectangleEdge.BOTTOM) {
 715:                     mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il);
 716:                 }
 717:                 g2.draw(mark);
 718:             }
 719:         }
 720: 
 721:         // need to work out the space used by the tick labels...
 722:         // so we can update the cursor...
 723:         double used = 0.0;
 724:         if (isTickLabelsVisible()) {
 725:             if (edge == RectangleEdge.LEFT) {
 726:                 used += findMaximumTickLabelWidth(ticks, g2, plotArea,
 727:                         isVerticalTickLabels());
 728:                 state.cursorLeft(used);
 729:             }
 730:             else if (edge == RectangleEdge.RIGHT) {
 731:                 used = findMaximumTickLabelWidth(ticks, g2, plotArea,
 732:                         isVerticalTickLabels());
 733:                 state.cursorRight(used);
 734:             }
 735:             else if (edge == RectangleEdge.TOP) {
 736:                 used = findMaximumTickLabelHeight(ticks, g2, plotArea,
 737:                         isVerticalTickLabels());
 738:                 state.cursorUp(used);
 739:             }
 740:             else if (edge == RectangleEdge.BOTTOM) {
 741:                 used = findMaximumTickLabelHeight(ticks, g2, plotArea,
 742:                         isVerticalTickLabels());
 743:                 state.cursorDown(used);
 744:             }
 745:         }
 746: 
 747:         return state;
 748:     }
 749: 
 750:     /**
 751:      * Returns the space required to draw the axis.
 752:      *
 753:      * @param g2  the graphics device.
 754:      * @param plot  the plot that the axis belongs to.
 755:      * @param plotArea  the area within which the plot should be drawn.
 756:      * @param edge  the axis location.
 757:      * @param space  the space already reserved (for other axes).
 758:      *
 759:      * @return The space required to draw the axis (including pre-reserved
 760:      *         space).
 761:      */
 762:     public AxisSpace reserveSpace(Graphics2D g2, Plot plot,
 763:                                   Rectangle2D plotArea,
 764:                                   RectangleEdge edge, AxisSpace space) {
 765: 
 766:         // create a new space object if one wasn't supplied...
 767:         if (space == null) {
 768:             space = new AxisSpace();
 769:         }
 770: 
 771:         // if the axis is not visible, no additional space is required...
 772:         if (!isVisible()) {
 773:             return space;
 774:         }
 775: 
 776:         // if the axis has a fixed dimension, return it...
 777:         double dimension = getFixedDimension();
 778:         if (dimension > 0.0) {
 779:             space.ensureAtLeast(dimension, edge);
 780:         }
 781: 
 782:         // calculate the max size of the tick labels (if visible)...
 783:         double tickLabelHeight = 0.0;
 784:         double tickLabelWidth = 0.0;
 785:         if (isTickLabelsVisible()) {
 786:             g2.setFont(getTickLabelFont());
 787:             List ticks = refreshTicks(g2, new AxisState(), plotArea, edge);
 788:             if (RectangleEdge.isTopOrBottom(edge)) {
 789:                 tickLabelHeight = findMaximumTickLabelHeight(ticks, g2,
 790:                         plotArea, isVerticalTickLabels());
 791:             }
 792:             else if (RectangleEdge.isLeftOrRight(edge)) {
 793:                 tickLabelWidth = findMaximumTickLabelWidth(ticks, g2, plotArea,
 794:                         isVerticalTickLabels());
 795:             }
 796:         }
 797: 
 798:         // get the axis label size and update the space object...
 799:         Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
 800:         double labelHeight = 0.0;
 801:         double labelWidth = 0.0;
 802:         if (RectangleEdge.isTopOrBottom(edge)) {
 803:             labelHeight = labelEnclosure.getHeight();
 804:             space.add(labelHeight + tickLabelHeight, edge);
 805:         }
 806:         else if (RectangleEdge.isLeftOrRight(edge)) {
 807:             labelWidth = labelEnclosure.getWidth();
 808:             space.add(labelWidth + tickLabelWidth, edge);
 809:         }
 810: 
 811:         return space;
 812: 
 813:     }
 814: 
 815:     /**
 816:      * A utility method for determining the height of the tallest tick label.
 817:      *
 818:      * @param ticks  the ticks.
 819:      * @param g2  the graphics device.
 820:      * @param drawArea  the area within which the plot and axes should be drawn.
 821:      * @param vertical  a flag that indicates whether or not the tick labels
 822:      *                  are 'vertical'.
 823:      *
 824:      * @return The height of the tallest tick label.
 825:      */
 826:     protected double findMaximumTickLabelHeight(List ticks,
 827:                                                 Graphics2D g2,
 828:                                                 Rectangle2D drawArea,
 829:                                                 boolean vertical) {
 830: 
 831:         RectangleInsets insets = getTickLabelInsets();
 832:         Font font = getTickLabelFont();
 833:         double maxHeight = 0.0;
 834:         if (vertical) {
 835:             FontMetrics fm = g2.getFontMetrics(font);
 836:             Iterator iterator = ticks.iterator();
 837:             while (iterator.hasNext()) {
 838:                 Tick tick = (Tick) iterator.next();
 839:                 Rectangle2D labelBounds = TextUtilities.getTextBounds(
 840:                         tick.getText(), g2, fm);
 841:                 if (labelBounds.getWidth() + insets.getTop()
 842:                         + insets.getBottom() > maxHeight) {
 843:                     maxHeight = labelBounds.getWidth()
 844:                                 + insets.getTop() + insets.getBottom();
 845:                 }
 846:             }
 847:         }
 848:         else {
 849:             LineMetrics metrics = font.getLineMetrics("ABCxyz",
 850:                     g2.getFontRenderContext());
 851:             maxHeight = metrics.getHeight()
 852:                         + insets.getTop() + insets.getBottom();
 853:         }
 854:         return maxHeight;
 855: 
 856:     }
 857: 
 858:     /**
 859:      * A utility method for determining the width of the widest tick label.
 860:      *
 861:      * @param ticks  the ticks.
 862:      * @param g2  the graphics device.
 863:      * @param drawArea  the area within which the plot and axes should be drawn.
 864:      * @param vertical  a flag that indicates whether or not the tick labels
 865:      *                  are 'vertical'.
 866:      *
 867:      * @return The width of the tallest tick label.
 868:      */
 869:     protected double findMaximumTickLabelWidth(List ticks,
 870:                                                Graphics2D g2,
 871:                                                Rectangle2D drawArea,
 872:                                                boolean vertical) {
 873: 
 874:         RectangleInsets insets = getTickLabelInsets();
 875:         Font font = getTickLabelFont();
 876:         double maxWidth = 0.0;
 877:         if (!vertical) {
 878:             FontMetrics fm = g2.getFontMetrics(font);
 879:             Iterator iterator = ticks.iterator();
 880:             while (iterator.hasNext()) {
 881:                 Tick tick = (Tick) iterator.next();
 882:                 Rectangle2D labelBounds = TextUtilities.getTextBounds(
 883:                         tick.getText(), g2, fm);
 884:                 if (labelBounds.getWidth() + insets.getLeft()
 885:                         + insets.getRight() > maxWidth) {
 886:                     maxWidth = labelBounds.getWidth()
 887:                                + insets.getLeft() + insets.getRight();
 888:                 }
 889:             }
 890:         }
 891:         else {
 892:             LineMetrics metrics = font.getLineMetrics("ABCxyz",
 893:                     g2.getFontRenderContext());
 894:             maxWidth = metrics.getHeight()
 895:                        + insets.getTop() + insets.getBottom();
 896:         }
 897:         return maxWidth;
 898: 
 899:     }
 900: 
 901:     /**
 902:      * Returns a flag that controls the direction of values on the axis.
 903:      * <P>
 904:      * For a regular axis, values increase from left to right (for a horizontal
 905:      * axis) and bottom to top (for a vertical axis).  When the axis is
 906:      * 'inverted', the values increase in the opposite direction.
 907:      *
 908:      * @return The flag.
 909:      *
 910:      * @see #setInverted(boolean)
 911:      */
 912:     public boolean isInverted() {
 913:         return this.inverted;
 914:     }
 915: 
 916:     /**
 917:      * Sets a flag that controls the direction of values on the axis, and
 918:      * notifies registered listeners that the axis has changed.
 919:      *
 920:      * @param flag  the flag.
 921:      *
 922:      * @see #isInverted()
 923:      */
 924:     public void setInverted(boolean flag) {
 925: 
 926:         if (this.inverted != flag) {
 927:             this.inverted = flag;
 928:             notifyListeners(new AxisChangeEvent(this));
 929:         }
 930: 
 931:     }
 932: 
 933:     /**
 934:      * Returns the flag that controls whether or not the axis range is
 935:      * automatically adjusted to fit the data values.
 936:      *
 937:      * @return The flag.
 938:      *
 939:      * @see #setAutoRange(boolean)
 940:      */
 941:     public boolean isAutoRange() {
 942:         return this.autoRange;
 943:     }
 944: 
 945:     /**
 946:      * Sets a flag that determines whether or not the axis range is
 947:      * automatically adjusted to fit the data, and notifies registered
 948:      * listeners that the axis has been modified.
 949:      *
 950:      * @param auto  the new value of the flag.
 951:      *
 952:      * @see #isAutoRange()
 953:      */
 954:     public void setAutoRange(boolean auto) {
 955:         setAutoRange(auto, true);
 956:     }
 957: 
 958:     /**
 959:      * Sets the auto range attribute.  If the <code>notify</code> flag is set,
 960:      * an {@link AxisChangeEvent} is sent to registered listeners.
 961:      *
 962:      * @param auto  the flag.
 963:      * @param notify  notify listeners?
 964:      *
 965:      * @see #isAutoRange()
 966:      */
 967:     protected void setAutoRange(boolean auto, boolean notify) {
 968:         if (this.autoRange != auto) {
 969:             this.autoRange = auto;
 970:             if (this.autoRange) {
 971:                 autoAdjustRange();
 972:             }
 973:             if (notify) {
 974:                 notifyListeners(new AxisChangeEvent(this));
 975:             }
 976:         }
 977:     }
 978: 
 979:     /**
 980:      * Returns the minimum size allowed for the axis range when it is
 981:      * automatically calculated.
 982:      *
 983:      * @return The minimum range.
 984:      *
 985:      * @see #setAutoRangeMinimumSize(double)
 986:      */
 987:     public double getAutoRangeMinimumSize() {
 988:         return this.autoRangeMinimumSize;
 989:     }
 990: 
 991:     /**
 992:      * Sets the auto range minimum size and sends an {@link AxisChangeEvent}
 993:      * to all registered listeners.
 994:      *
 995:      * @param size  the size.
 996:      *
 997:      * @see #getAutoRangeMinimumSize()
 998:      */
 999:     public void setAutoRangeMinimumSize(double size) {
1000:         setAutoRangeMinimumSize(size, true);
1001:     }
1002: 
1003:     /**
1004:      * Sets the minimum size allowed for the axis range when it is
1005:      * automatically calculated.
1006:      * <p>
1007:      * If requested, an {@link AxisChangeEvent} is forwarded to all registered
1008:      * listeners.
1009:      *
1010:      * @param size  the new minimum.
1011:      * @param notify  notify listeners?
1012:      */
1013:     public void setAutoRangeMinimumSize(double size, boolean notify) {
1014:         if (size <= 0.0) {
1015:             throw new IllegalArgumentException(
1016:                 "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
1017:         }
1018:         if (this.autoRangeMinimumSize != size) {
1019:             this.autoRangeMinimumSize = size;
1020:             if (this.autoRange) {
1021:                 autoAdjustRange();
1022:             }
1023:             if (notify) {
1024:                 notifyListeners(new AxisChangeEvent(this));
1025:             }
1026:         }
1027: 
1028:     }
1029: 
1030:     /**
1031:      * Returns the default auto range.
1032:      *
1033:      * @return The default auto range (never <code>null</code>).
1034:      *
1035:      * @see #setDefaultAutoRange(Range)
1036:      *
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:      * @see #resizeRange(double, double)
1489:      */
1490:     public void resizeRange(double percent) {
1491:         resizeRange(percent, this.range.getCentralValue());
1492:     }
1493: 
1494:     /**
1495:      * Increases or decreases the axis range by the specified percentage about
1496:      * the specified anchor value and sends an {@link AxisChangeEvent} to all
1497:      * registered listeners.
1498:      * <P>
1499:      * To double the length of the axis range, use 200% (2.0).
1500:      * To halve the length of the axis range, use 50% (0.5).
1501:      *
1502:      * @param percent  the resize factor.
1503:      * @param anchorValue  the new central value after the resize.
1504:      *
1505:      * @see #resizeRange(double)
1506:      */
1507:     public void resizeRange(double percent, double anchorValue) {
1508:         if (percent > 0.0) {
1509:             double halfLength = this.range.getLength() * percent / 2;
1510:             Range adjusted = new Range(anchorValue - halfLength,
1511:                     anchorValue + halfLength);
1512:             setRange(adjusted);
1513:         }
1514:         else {
1515:             setAutoRange(true);
1516:         }
1517:     }
1518: 
1519:     /**
1520:      * Zooms in on the current range.
1521:      *
1522:      * @param lowerPercent  the new lower bound.
1523:      * @param upperPercent  the new upper bound.
1524:      */
1525:     public void zoomRange(double lowerPercent, double upperPercent) {
1526:         double start = this.range.getLowerBound();
1527:         double length = this.range.getLength();
1528:         Range adjusted = null;
1529:         if (isInverted()) {
1530:             adjusted = new Range(start + (length * (1 - upperPercent)),
1531:                                  start + (length * (1 - lowerPercent)));
1532:         }
1533:         else {
1534:             adjusted = new Range(start + length * lowerPercent,
1535:                     start + length * upperPercent);
1536:         }
1537:         setRange(adjusted);
1538:     }
1539: 
1540:     /**
1541:      * Returns the auto tick index.
1542:      *
1543:      * @return The auto tick index.
1544:      *
1545:      * @see #setAutoTickIndex(int)
1546:      */
1547:     protected int getAutoTickIndex() {
1548:         return this.autoTickIndex;
1549:     }
1550: 
1551:     /**
1552:      * Sets the auto tick index.
1553:      *
1554:      * @param index  the new value.
1555:      *
1556:      * @see #getAutoTickIndex()
1557:      */
1558:     protected void setAutoTickIndex(int index) {
1559:         this.autoTickIndex = index;
1560:     }
1561: 
1562:     /**
1563:      * Tests the axis for equality with an arbitrary object.
1564:      *
1565:      * @param obj  the object (<code>null</code> permitted).
1566:      *
1567:      * @return <code>true</code> or <code>false</code>.
1568:      */
1569:     public boolean equals(Object obj) {
1570: 
1571:         if (obj == this) {
1572:             return true;
1573:         }
1574:         if (!(obj instanceof ValueAxis)) {
1575:             return false;
1576:         }
1577: 
1578:         ValueAxis that = (ValueAxis) obj;
1579: 
1580:         if (this.positiveArrowVisible != that.positiveArrowVisible) {
1581:             return false;
1582:         }
1583:         if (this.negativeArrowVisible != that.negativeArrowVisible) {
1584:             return false;
1585:         }
1586:         if (this.inverted != that.inverted) {
1587:             return false;
1588:         }
1589:         if (!ObjectUtilities.equal(this.range, that.range)) {
1590:             return false;
1591:         }
1592:         if (this.autoRange != that.autoRange) {
1593:             return false;
1594:         }
1595:         if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
1596:             return false;
1597:         }
1598:         if (!this.defaultAutoRange.equals(that.defaultAutoRange)) {
1599:             return false;
1600:         }
1601:         if (this.upperMargin != that.upperMargin) {
1602:             return false;
1603:         }
1604:         if (this.lowerMargin != that.lowerMargin) {
1605:             return false;
1606:         }
1607:         if (this.fixedAutoRange != that.fixedAutoRange) {
1608:             return false;
1609:         }
1610:         if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
1611:             return false;
1612:         }
1613:         if (!ObjectUtilities.equal(this.standardTickUnits,
1614:                 that.standardTickUnits)) {
1615:             return false;
1616:         }
1617:         if (this.verticalTickLabels != that.verticalTickLabels) {
1618:             return false;
1619:         }
1620: 
1621:         return super.equals(obj);
1622: 
1623:     }
1624: 
1625:     /**
1626:      * Returns a clone of the object.
1627:      *
1628:      * @return A clone.
1629:      *
1630:      * @throws CloneNotSupportedException if some component of the axis does
1631:      *         not support cloning.
1632:      */
1633:     public Object clone() throws CloneNotSupportedException {
1634:         ValueAxis clone = (ValueAxis) super.clone();
1635:         return clone;
1636:     }
1637: 
1638:     /**
1639:      * Provides serialization support.
1640:      *
1641:      * @param stream  the output stream.
1642:      *
1643:      * @throws IOException  if there is an I/O error.
1644:      */
1645:     private void writeObject(ObjectOutputStream stream) throws IOException {
1646:         stream.defaultWriteObject();
1647:         SerialUtilities.writeShape(this.upArrow, stream);
1648:         SerialUtilities.writeShape(this.downArrow, stream);
1649:         SerialUtilities.writeShape(this.leftArrow, stream);
1650:         SerialUtilities.writeShape(this.rightArrow, stream);
1651:     }
1652: 
1653:     /**
1654:      * Provides serialization support.
1655:      *
1656:      * @param stream  the input stream.
1657:      *
1658:      * @throws IOException  if there is an I/O error.
1659:      * @throws ClassNotFoundException  if there is a classpath problem.
1660:      */
1661:     private void readObject(ObjectInputStream stream)
1662:             throws IOException, ClassNotFoundException {
1663: 
1664:         stream.defaultReadObject();
1665:         this.upArrow = SerialUtilities.readShape(stream);
1666:         this.downArrow = SerialUtilities.readShape(stream);
1667:         this.leftArrow = SerialUtilities.readShape(stream);
1668:         this.rightArrow = SerialUtilities.readShape(stream);
1669: 
1670:     }
1671: 
1672: }