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