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