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    
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        /** 
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    }