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