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