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