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