001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it
010     * under the terms of the GNU Lesser General Public License as published by
011     * the Free Software Foundation; either version 2.1 of the License, or
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022     * USA.
023     *
024     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025     * Other names may be trademarks of their respective owners.]
026     *
027     * --------------
028     * ValueAxis.java
029     * --------------
030     * (C) Copyright 2000-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()
716                            : getTickMarkOutsideLength();
717    
718                    double il = (tick.getTickType().equals(TickType.MINOR)) 
719                            ? getMinorTickMarkInsideLength()
720                            : getTickMarkInsideLength();
721    
722                    float xx = (float) valueToJava2D(tick.getValue(), dataArea,
723                            edge);
724                    Line2D mark = null;
725                    g2.setStroke(getTickMarkStroke());
726                    g2.setPaint(getTickMarkPaint());
727                    if (edge == RectangleEdge.LEFT) {
728                        mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx);
729                    }
730                    else if (edge == RectangleEdge.RIGHT) {
731                        mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx);
732                    }
733                    else if (edge == RectangleEdge.TOP) {
734                        mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il);
735                    }
736                    else if (edge == RectangleEdge.BOTTOM) {
737                        mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il);
738                    }
739                    g2.draw(mark);
740                }
741            }
742    
743            // need to work out the space used by the tick labels...
744            // so we can update the cursor...
745            double used = 0.0;
746            if (isTickLabelsVisible()) {
747                if (edge == RectangleEdge.LEFT) {
748                    used += findMaximumTickLabelWidth(ticks, g2, plotArea,
749                            isVerticalTickLabels());
750                    state.cursorLeft(used);
751                }
752                else if (edge == RectangleEdge.RIGHT) {
753                    used = findMaximumTickLabelWidth(ticks, g2, plotArea,
754                            isVerticalTickLabels());
755                    state.cursorRight(used);
756                }
757                else if (edge == RectangleEdge.TOP) {
758                    used = findMaximumTickLabelHeight(ticks, g2, plotArea,
759                            isVerticalTickLabels());
760                    state.cursorUp(used);
761                }
762                else if (edge == RectangleEdge.BOTTOM) {
763                    used = findMaximumTickLabelHeight(ticks, g2, plotArea,
764                            isVerticalTickLabels());
765                    state.cursorDown(used);
766                }
767            }
768    
769            return state;
770        }
771    
772        /**
773         * Returns the space required to draw the axis.
774         *
775         * @param g2  the graphics device.
776         * @param plot  the plot that the axis belongs to.
777         * @param plotArea  the area within which the plot should be drawn.
778         * @param edge  the axis location.
779         * @param space  the space already reserved (for other axes).
780         *
781         * @return The space required to draw the axis (including pre-reserved
782         *         space).
783         */
784        public AxisSpace reserveSpace(Graphics2D g2, Plot plot,
785                                      Rectangle2D plotArea,
786                                      RectangleEdge edge, AxisSpace space) {
787    
788            // create a new space object if one wasn't supplied...
789            if (space == null) {
790                space = new AxisSpace();
791            }
792    
793            // if the axis is not visible, no additional space is required...
794            if (!isVisible()) {
795                return space;
796            }
797    
798            // if the axis has a fixed dimension, return it...
799            double dimension = getFixedDimension();
800            if (dimension > 0.0) {
801                space.ensureAtLeast(dimension, edge);
802            }
803    
804            // calculate the max size of the tick labels (if visible)...
805            double tickLabelHeight = 0.0;
806            double tickLabelWidth = 0.0;
807            if (isTickLabelsVisible()) {
808                g2.setFont(getTickLabelFont());
809                List ticks = refreshTicks(g2, new AxisState(), plotArea, edge);
810                if (RectangleEdge.isTopOrBottom(edge)) {
811                    tickLabelHeight = findMaximumTickLabelHeight(ticks, g2,
812                            plotArea, isVerticalTickLabels());
813                }
814                else if (RectangleEdge.isLeftOrRight(edge)) {
815                    tickLabelWidth = findMaximumTickLabelWidth(ticks, g2, plotArea,
816                            isVerticalTickLabels());
817                }
818            }
819    
820            // get the axis label size and update the space object...
821            Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
822            double labelHeight = 0.0;
823            double labelWidth = 0.0;
824            if (RectangleEdge.isTopOrBottom(edge)) {
825                labelHeight = labelEnclosure.getHeight();
826                space.add(labelHeight + tickLabelHeight, edge);
827            }
828            else if (RectangleEdge.isLeftOrRight(edge)) {
829                labelWidth = labelEnclosure.getWidth();
830                space.add(labelWidth + tickLabelWidth, edge);
831            }
832    
833            return space;
834    
835        }
836    
837        /**
838         * A utility method for determining the height of the tallest tick label.
839         *
840         * @param ticks  the ticks.
841         * @param g2  the graphics device.
842         * @param drawArea  the area within which the plot and axes should be drawn.
843         * @param vertical  a flag that indicates whether or not the tick labels
844         *                  are 'vertical'.
845         *
846         * @return The height of the tallest tick label.
847         */
848        protected double findMaximumTickLabelHeight(List ticks,
849                                                    Graphics2D g2,
850                                                    Rectangle2D drawArea,
851                                                    boolean vertical) {
852    
853            RectangleInsets insets = getTickLabelInsets();
854            Font font = getTickLabelFont();
855            double maxHeight = 0.0;
856            if (vertical) {
857                FontMetrics fm = g2.getFontMetrics(font);
858                Iterator iterator = ticks.iterator();
859                while (iterator.hasNext()) {
860                    Tick tick = (Tick) iterator.next();
861                    Rectangle2D labelBounds = TextUtilities.getTextBounds(
862                            tick.getText(), g2, fm);
863                    if (labelBounds.getWidth() + insets.getTop()
864                            + insets.getBottom() > maxHeight) {
865                        maxHeight = labelBounds.getWidth()
866                                    + insets.getTop() + insets.getBottom();
867                    }
868                }
869            }
870            else {
871                LineMetrics metrics = font.getLineMetrics("ABCxyz",
872                        g2.getFontRenderContext());
873                maxHeight = metrics.getHeight()
874                            + insets.getTop() + insets.getBottom();
875            }
876            return maxHeight;
877    
878        }
879    
880        /**
881         * A utility method for determining the width of the widest tick label.
882         *
883         * @param ticks  the ticks.
884         * @param g2  the graphics device.
885         * @param drawArea  the area within which the plot and axes should be drawn.
886         * @param vertical  a flag that indicates whether or not the tick labels
887         *                  are 'vertical'.
888         *
889         * @return The width of the tallest tick label.
890         */
891        protected double findMaximumTickLabelWidth(List ticks,
892                                                   Graphics2D g2,
893                                                   Rectangle2D drawArea,
894                                                   boolean vertical) {
895    
896            RectangleInsets insets = getTickLabelInsets();
897            Font font = getTickLabelFont();
898            double maxWidth = 0.0;
899            if (!vertical) {
900                FontMetrics fm = g2.getFontMetrics(font);
901                Iterator iterator = ticks.iterator();
902                while (iterator.hasNext()) {
903                    Tick tick = (Tick) iterator.next();
904                    Rectangle2D labelBounds = TextUtilities.getTextBounds(
905                            tick.getText(), g2, fm);
906                    if (labelBounds.getWidth() + insets.getLeft()
907                            + insets.getRight() > maxWidth) {
908                        maxWidth = labelBounds.getWidth()
909                                   + insets.getLeft() + insets.getRight();
910                    }
911                }
912            }
913            else {
914                LineMetrics metrics = font.getLineMetrics("ABCxyz",
915                        g2.getFontRenderContext());
916                maxWidth = metrics.getHeight()
917                           + insets.getTop() + insets.getBottom();
918            }
919            return maxWidth;
920    
921        }
922    
923        /**
924         * Returns a flag that controls the direction of values on the axis.
925         * <P>
926         * For a regular axis, values increase from left to right (for a horizontal
927         * axis) and bottom to top (for a vertical axis).  When the axis is
928         * 'inverted', the values increase in the opposite direction.
929         *
930         * @return The flag.
931         *
932         * @see #setInverted(boolean)
933         */
934        public boolean isInverted() {
935            return this.inverted;
936        }
937    
938        /**
939         * Sets a flag that controls the direction of values on the axis, and
940         * notifies registered listeners that the axis has changed.
941         *
942         * @param flag  the flag.
943         *
944         * @see #isInverted()
945         */
946        public void setInverted(boolean flag) {
947    
948            if (this.inverted != flag) {
949                this.inverted = flag;
950                notifyListeners(new AxisChangeEvent(this));
951            }
952    
953        }
954    
955        /**
956         * Returns the flag that controls whether or not the axis range is
957         * automatically adjusted to fit the data values.
958         *
959         * @return The flag.
960         *
961         * @see #setAutoRange(boolean)
962         */
963        public boolean isAutoRange() {
964            return this.autoRange;
965        }
966    
967        /**
968         * Sets a flag that determines whether or not the axis range is
969         * automatically adjusted to fit the data, and notifies registered
970         * listeners that the axis has been modified.
971         *
972         * @param auto  the new value of the flag.
973         *
974         * @see #isAutoRange()
975         */
976        public void setAutoRange(boolean auto) {
977            setAutoRange(auto, true);
978        }
979    
980        /**
981         * Sets the auto range attribute.  If the <code>notify</code> flag is set,
982         * an {@link AxisChangeEvent} is sent to registered listeners.
983         *
984         * @param auto  the flag.
985         * @param notify  notify listeners?
986         *
987         * @see #isAutoRange()
988         */
989        protected void setAutoRange(boolean auto, boolean notify) {
990            if (this.autoRange != auto) {
991                this.autoRange = auto;
992                if (this.autoRange) {
993                    autoAdjustRange();
994                }
995                if (notify) {
996                    notifyListeners(new AxisChangeEvent(this));
997                }
998            }
999        }
1000    
1001        /**
1002         * Returns the minimum size allowed for the axis range when it is
1003         * automatically calculated.
1004         *
1005         * @return The minimum range.
1006         *
1007         * @see #setAutoRangeMinimumSize(double)
1008         */
1009        public double getAutoRangeMinimumSize() {
1010            return this.autoRangeMinimumSize;
1011        }
1012    
1013        /**
1014         * Sets the auto range minimum size and sends an {@link AxisChangeEvent}
1015         * to all registered listeners.
1016         *
1017         * @param size  the size.
1018         *
1019         * @see #getAutoRangeMinimumSize()
1020         */
1021        public void setAutoRangeMinimumSize(double size) {
1022            setAutoRangeMinimumSize(size, true);
1023        }
1024    
1025        /**
1026         * Sets the minimum size allowed for the axis range when it is
1027         * automatically calculated.
1028         * <p>
1029         * If requested, an {@link AxisChangeEvent} is forwarded to all registered
1030         * listeners.
1031         *
1032         * @param size  the new minimum.
1033         * @param notify  notify listeners?
1034         */
1035        public void setAutoRangeMinimumSize(double size, boolean notify) {
1036            if (size <= 0.0) {
1037                throw new IllegalArgumentException(
1038                    "NumberAxis.setAutoRangeMinimumSize(double): must be > 0.0.");
1039            }
1040            if (this.autoRangeMinimumSize != size) {
1041                this.autoRangeMinimumSize = size;
1042                if (this.autoRange) {
1043                    autoAdjustRange();
1044                }
1045                if (notify) {
1046                    notifyListeners(new AxisChangeEvent(this));
1047                }
1048            }
1049    
1050        }
1051    
1052        /**
1053         * Returns the default auto range.
1054         *
1055         * @return The default auto range (never <code>null</code>).
1056         *
1057         * @see #setDefaultAutoRange(Range)
1058         *
1059         * @since 1.0.5
1060         */
1061        public Range getDefaultAutoRange() {
1062            return this.defaultAutoRange;
1063        }
1064    
1065        /**
1066         * Sets the default auto range and sends an {@link AxisChangeEvent} to all
1067         * registered listeners.
1068         *
1069         * @param range  the range (<code>null</code> not permitted).
1070         *
1071         * @see #getDefaultAutoRange()
1072         *
1073         * @since 1.0.5
1074         */
1075        public void setDefaultAutoRange(Range range) {
1076            if (range == null) {
1077                throw new IllegalArgumentException("Null 'range' argument.");
1078            }
1079            this.defaultAutoRange = range;
1080            notifyListeners(new AxisChangeEvent(this));
1081        }
1082    
1083        /**
1084         * Returns the lower margin for the axis, expressed as a percentage of the
1085         * axis range.  This controls the space added to the lower end of the axis
1086         * when the axis range is automatically calculated (it is ignored when the
1087         * axis range is set explicitly). The default value is 0.05 (five percent).
1088         *
1089         * @return The lower margin.
1090         *
1091         * @see #setLowerMargin(double)
1092         */
1093        public double getLowerMargin() {
1094            return this.lowerMargin;
1095        }
1096    
1097        /**
1098         * Sets the lower margin for the axis (as a percentage of the axis range)
1099         * and sends an {@link AxisChangeEvent} to all registered listeners.  This
1100         * margin is added only when the axis range is auto-calculated - if you set
1101         * the axis range manually, the margin is ignored.
1102         *
1103         * @param margin  the margin percentage (for example, 0.05 is five percent).
1104         *
1105         * @see #getLowerMargin()
1106         * @see #setUpperMargin(double)
1107         */
1108        public void setLowerMargin(double margin) {
1109            this.lowerMargin = margin;
1110            if (isAutoRange()) {
1111                autoAdjustRange();
1112            }
1113            notifyListeners(new AxisChangeEvent(this));
1114        }
1115    
1116        /**
1117         * Returns the upper margin for the axis, expressed as a percentage of the
1118         * axis range.  This controls the space added to the lower end of the axis
1119         * when the axis range is automatically calculated (it is ignored when the
1120         * axis range is set explicitly). The default value is 0.05 (five percent).
1121         *
1122         * @return The upper margin.
1123         *
1124         * @see #setUpperMargin(double)
1125         */
1126        public double getUpperMargin() {
1127            return this.upperMargin;
1128        }
1129    
1130        /**
1131         * Sets the upper margin for the axis (as a percentage of the axis range)
1132         * and sends an {@link AxisChangeEvent} to all registered listeners.  This
1133         * margin is added only when the axis range is auto-calculated - if you set
1134         * the axis range manually, the margin is ignored.
1135         *
1136         * @param margin  the margin percentage (for example, 0.05 is five percent).
1137         *
1138         * @see #getLowerMargin()
1139         * @see #setLowerMargin(double)
1140         */
1141        public void setUpperMargin(double margin) {
1142            this.upperMargin = margin;
1143            if (isAutoRange()) {
1144                autoAdjustRange();
1145            }
1146            notifyListeners(new AxisChangeEvent(this));
1147        }
1148    
1149        /**
1150         * Returns the fixed auto range.
1151         *
1152         * @return The length.
1153         *
1154         * @see #setFixedAutoRange(double)
1155         */
1156        public double getFixedAutoRange() {
1157            return this.fixedAutoRange;
1158        }
1159    
1160        /**
1161         * Sets the fixed auto range for the axis.
1162         *
1163         * @param length  the range length.
1164         *
1165         * @see #getFixedAutoRange()
1166         */
1167        public void setFixedAutoRange(double length) {
1168            this.fixedAutoRange = length;
1169            if (isAutoRange()) {
1170                autoAdjustRange();
1171            }
1172            notifyListeners(new AxisChangeEvent(this));
1173        }
1174    
1175        /**
1176         * Returns the lower bound of the axis range.
1177         *
1178         * @return The lower bound.
1179         *
1180         * @see #setLowerBound(double)
1181         */
1182        public double getLowerBound() {
1183            return this.range.getLowerBound();
1184        }
1185    
1186        /**
1187         * Sets the lower bound for the axis range.  An {@link AxisChangeEvent} is
1188         * sent to all registered listeners.
1189         *
1190         * @param min  the new minimum.
1191         *
1192         * @see #getLowerBound()
1193         */
1194        public void setLowerBound(double min) {
1195            if (this.range.getUpperBound() > min) {
1196                setRange(new Range(min, this.range.getUpperBound()));
1197            }
1198            else {
1199                setRange(new Range(min, min + 1.0));
1200            }
1201        }
1202    
1203        /**
1204         * Returns the upper bound for the axis range.
1205         *
1206         * @return The upper bound.
1207         *
1208         * @see #setUpperBound(double)
1209         */
1210        public double getUpperBound() {
1211            return this.range.getUpperBound();
1212        }
1213    
1214        /**
1215         * Sets the upper bound for the axis range, and sends an
1216         * {@link AxisChangeEvent} to all registered listeners.
1217         *
1218         * @param max  the new maximum.
1219         *
1220         * @see #getUpperBound()
1221         */
1222        public void setUpperBound(double max) {
1223            if (this.range.getLowerBound() < max) {
1224                setRange(new Range(this.range.getLowerBound(), max));
1225            }
1226            else {
1227                setRange(max - 1.0, max);
1228            }
1229        }
1230    
1231        /**
1232         * Returns the range for the axis.
1233         *
1234         * @return The axis range (never <code>null</code>).
1235         *
1236         * @see #setRange(Range)
1237         */
1238        public Range getRange() {
1239            return this.range;
1240        }
1241    
1242        /**
1243         * Sets the range attribute and sends an {@link AxisChangeEvent} to all
1244         * registered listeners.  As a side-effect, the auto-range flag is set to
1245         * <code>false</code>.
1246         *
1247         * @param range  the range (<code>null</code> not permitted).
1248         *
1249         * @see #getRange()
1250         */
1251        public void setRange(Range range) {
1252            // defer argument checking
1253            setRange(range, true, true);
1254        }
1255    
1256        /**
1257         * Sets the range for the axis, if requested, sends an
1258         * {@link AxisChangeEvent} to all registered listeners.  As a side-effect,
1259         * the auto-range flag is set to <code>false</code> (optional).
1260         *
1261         * @param range  the range (<code>null</code> not permitted).
1262         * @param turnOffAutoRange  a flag that controls whether or not the auto
1263         *                          range is turned off.
1264         * @param notify  a flag that controls whether or not listeners are
1265         *                notified.
1266         *
1267         * @see #getRange()
1268         */
1269        public void setRange(Range range, boolean turnOffAutoRange,
1270                             boolean notify) {
1271            if (range == null) {
1272                throw new IllegalArgumentException("Null 'range' argument.");
1273            }
1274            if (turnOffAutoRange) {
1275                this.autoRange = false;
1276            }
1277            this.range = range;
1278            if (notify) {
1279                notifyListeners(new AxisChangeEvent(this));
1280            }
1281        }
1282    
1283        /**
1284         * Sets the axis range and sends an {@link AxisChangeEvent} to all
1285         * registered listeners.  As a side-effect, the auto-range flag is set to
1286         * <code>false</code>.
1287         *
1288         * @param lower  the lower axis limit.
1289         * @param upper  the upper axis limit.
1290         *
1291         * @see #getRange()
1292         * @see #setRange(Range)
1293         */
1294        public void setRange(double lower, double upper) {
1295            setRange(new Range(lower, upper));
1296        }
1297    
1298        /**
1299         * Sets the range for the axis (after first adding the current margins to
1300         * the specified range) and sends an {@link AxisChangeEvent} to all
1301         * registered listeners.
1302         *
1303         * @param range  the range (<code>null</code> not permitted).
1304         */
1305        public void setRangeWithMargins(Range range) {
1306            setRangeWithMargins(range, true, true);
1307        }
1308    
1309        /**
1310         * Sets the range for the axis after first adding the current margins to
1311         * the range and, if requested, sends an {@link AxisChangeEvent} to all
1312         * registered listeners.  As a side-effect, the auto-range flag is set to
1313         * <code>false</code> (optional).
1314         *
1315         * @param range  the range (excluding margins, <code>null</code> not
1316         *               permitted).
1317         * @param turnOffAutoRange  a flag that controls whether or not the auto
1318         *                          range is turned off.
1319         * @param notify  a flag that controls whether or not listeners are
1320         *                notified.
1321         */
1322        public void setRangeWithMargins(Range range, boolean turnOffAutoRange,
1323                                        boolean notify) {
1324            if (range == null) {
1325                throw new IllegalArgumentException("Null 'range' argument.");
1326            }
1327            setRange(Range.expand(range, getLowerMargin(), getUpperMargin()),
1328                    turnOffAutoRange, notify);
1329        }
1330    
1331        /**
1332         * Sets the axis range (after first adding the current margins to the
1333         * range) and sends an {@link AxisChangeEvent} to all registered listeners.
1334         * As a side-effect, the auto-range flag is set to <code>false</code>.
1335         *
1336         * @param lower  the lower axis limit.
1337         * @param upper  the upper axis limit.
1338         */
1339        public void setRangeWithMargins(double lower, double upper) {
1340            setRangeWithMargins(new Range(lower, upper));
1341        }
1342    
1343        /**
1344         * Sets the axis range, where the new range is 'size' in length, and
1345         * centered on 'value'.
1346         *
1347         * @param value  the central value.
1348         * @param length  the range length.
1349         */
1350        public void setRangeAboutValue(double value, double length) {
1351            setRange(new Range(value - length / 2, value + length / 2));
1352        }
1353    
1354        /**
1355         * Returns a flag indicating whether or not the tick unit is automatically
1356         * selected from a range of standard tick units.
1357         *
1358         * @return A flag indicating whether or not the tick unit is automatically
1359         *         selected.
1360         *
1361         * @see #setAutoTickUnitSelection(boolean)
1362         */
1363        public boolean isAutoTickUnitSelection() {
1364            return this.autoTickUnitSelection;
1365        }
1366    
1367        /**
1368         * Sets a flag indicating whether or not the tick unit is automatically
1369         * selected from a range of standard tick units.  If the flag is changed,
1370         * registered listeners are notified that the chart has changed.
1371         *
1372         * @param flag  the new value of the flag.
1373         *
1374         * @see #isAutoTickUnitSelection()
1375         */
1376        public void setAutoTickUnitSelection(boolean flag) {
1377            setAutoTickUnitSelection(flag, true);
1378        }
1379    
1380        /**
1381         * Sets a flag indicating whether or not the tick unit is automatically
1382         * selected from a range of standard tick units.
1383         *
1384         * @param flag  the new value of the flag.
1385         * @param notify  notify listeners?
1386         *
1387         * @see #isAutoTickUnitSelection()
1388         */
1389        public void setAutoTickUnitSelection(boolean flag, boolean notify) {
1390    
1391            if (this.autoTickUnitSelection != flag) {
1392                this.autoTickUnitSelection = flag;
1393                if (notify) {
1394                    notifyListeners(new AxisChangeEvent(this));
1395                }
1396            }
1397        }
1398    
1399        /**
1400         * Returns the source for obtaining standard tick units for the axis.
1401         *
1402         * @return The source (possibly <code>null</code>).
1403         *
1404         * @see #setStandardTickUnits(TickUnitSource)
1405         */
1406        public TickUnitSource getStandardTickUnits() {
1407            return this.standardTickUnits;
1408        }
1409    
1410        /**
1411         * Sets the source for obtaining standard tick units for the axis and sends
1412         * an {@link AxisChangeEvent} to all registered listeners.  The axis will
1413         * try to select the smallest tick unit from the source that does not cause
1414         * the tick labels to overlap (see also the
1415         * {@link #setAutoTickUnitSelection(boolean)} method.
1416         *
1417         * @param source  the source for standard tick units (<code>null</code>
1418         *                permitted).
1419         *
1420         * @see #getStandardTickUnits()
1421         */
1422        public void setStandardTickUnits(TickUnitSource source) {
1423            this.standardTickUnits = source;
1424            notifyListeners(new AxisChangeEvent(this));
1425        }
1426    
1427        /**
1428         * Returns the number of minor tick marks to display.
1429         *
1430         * @return The number of minor tick marks to display.
1431         *
1432         * @see #setMinorTickCount(int)
1433         *
1434         * @since 1.0.12
1435         */
1436        public int getMinorTickCount() {
1437            return this.minorTickCount;
1438        }
1439    
1440        /**
1441         * Sets the number of minor tick marks to display, and sends an
1442         * {@link AxisChangeEvent} to all registered listeners.
1443         *
1444         * @param count  the count.
1445         *
1446         * @see #getMinorTickCount()
1447         *
1448         * @since 1.0.12
1449         */
1450        public void setMinorTickCount(int count) {
1451            this.minorTickCount = count;
1452            notifyListeners(new AxisChangeEvent(this));
1453        }
1454    
1455        /**
1456         * Converts a data value to a coordinate in Java2D space, assuming that the
1457         * axis runs along one edge of the specified dataArea.
1458         * <p>
1459         * Note that it is possible for the coordinate to fall outside the area.
1460         *
1461         * @param value  the data value.
1462         * @param area  the area for plotting the data.
1463         * @param edge  the edge along which the axis lies.
1464         *
1465         * @return The Java2D coordinate.
1466         *
1467         * @see #java2DToValue(double, Rectangle2D, RectangleEdge)
1468         */
1469        public abstract double valueToJava2D(double value, Rectangle2D area,
1470                                             RectangleEdge edge);
1471    
1472        /**
1473         * Converts a length in data coordinates into the corresponding length in
1474         * Java2D coordinates.
1475         *
1476         * @param length  the length.
1477         * @param area  the plot area.
1478         * @param edge  the edge along which the axis lies.
1479         *
1480         * @return The length in Java2D coordinates.
1481         */
1482        public double lengthToJava2D(double length, Rectangle2D area,
1483                                     RectangleEdge edge) {
1484            double zero = valueToJava2D(0.0, area, edge);
1485            double l = valueToJava2D(length, area, edge);
1486            return Math.abs(l - zero);
1487        }
1488    
1489        /**
1490         * Converts a coordinate in Java2D space to the corresponding data value,
1491         * assuming that the axis runs along one edge of the specified dataArea.
1492         *
1493         * @param java2DValue  the coordinate in Java2D space.
1494         * @param area  the area in which the data is plotted.
1495         * @param edge  the edge along which the axis lies.
1496         *
1497         * @return The data value.
1498         *
1499         * @see #valueToJava2D(double, Rectangle2D, RectangleEdge)
1500         */
1501        public abstract double java2DToValue(double java2DValue,
1502                                             Rectangle2D area,
1503                                             RectangleEdge edge);
1504    
1505        /**
1506         * Automatically sets the axis range to fit the range of values in the
1507         * dataset.  Sometimes this can depend on the renderer used as well (for
1508         * example, the renderer may "stack" values, requiring an axis range
1509         * greater than otherwise necessary).
1510         */
1511        protected abstract void autoAdjustRange();
1512    
1513        /**
1514         * Centers the axis range about the specified value and sends an
1515         * {@link AxisChangeEvent} to all registered listeners.
1516         *
1517         * @param value  the center value.
1518         */
1519        public void centerRange(double value) {
1520    
1521            double central = this.range.getCentralValue();
1522            Range adjusted = new Range(this.range.getLowerBound() + value - central,
1523                    this.range.getUpperBound() + value - central);
1524            setRange(adjusted);
1525    
1526        }
1527    
1528        /**
1529         * Increases or decreases the axis range by the specified percentage about
1530         * the central value and sends an {@link AxisChangeEvent} to all registered
1531         * listeners.
1532         * <P>
1533         * To double the length of the axis range, use 200% (2.0).
1534         * To halve the length of the axis range, use 50% (0.5).
1535         *
1536         * @param percent  the resize factor.
1537         *
1538         * @see #resizeRange(double, double)
1539         */
1540        public void resizeRange(double percent) {
1541            resizeRange(percent, this.range.getCentralValue());
1542        }
1543    
1544        /**
1545         * Increases or decreases the axis range by the specified percentage about
1546         * the specified anchor value and sends an {@link AxisChangeEvent} to all
1547         * registered listeners.
1548         * <P>
1549         * To double the length of the axis range, use 200% (2.0).
1550         * To halve the length of the axis range, use 50% (0.5).
1551         *
1552         * @param percent  the resize factor.
1553         * @param anchorValue  the new central value after the resize.
1554         *
1555         * @see #resizeRange(double)
1556         */
1557        public void resizeRange(double percent, double anchorValue) {
1558            if (percent > 0.0) {
1559                double halfLength = this.range.getLength() * percent / 2;
1560                Range adjusted = new Range(anchorValue - halfLength,
1561                        anchorValue + halfLength);
1562                setRange(adjusted);
1563            }
1564            else {
1565                setAutoRange(true);
1566            }
1567        }
1568    
1569        /**
1570         * Increases or decreases the axis range by the specified percentage about
1571         * the specified anchor value and sends an {@link AxisChangeEvent} to all
1572         * registered listeners.
1573         * <P>
1574         * To double the length of the axis range, use 200% (2.0).
1575         * To halve the length of the axis range, use 50% (0.5).
1576         *
1577         * @param percent  the resize factor.
1578         * @param anchorValue  the new central value after the resize.
1579         *
1580         * @see #resizeRange(double)
1581         *
1582         * @since 1.0.13
1583         */
1584        public void resizeRange2(double percent, double anchorValue) {
1585            if (percent > 0.0) {
1586                double left = anchorValue - getLowerBound();
1587                double right = getUpperBound() - anchorValue;
1588                Range adjusted = new Range(anchorValue - left * percent,
1589                        anchorValue + right * percent);
1590                setRange(adjusted);
1591            }
1592            else {
1593                setAutoRange(true);
1594            }
1595        }
1596    
1597        /**
1598         * Zooms in on the current range.
1599         *
1600         * @param lowerPercent  the new lower bound.
1601         * @param upperPercent  the new upper bound.
1602         */
1603        public void zoomRange(double lowerPercent, double upperPercent) {
1604            double start = this.range.getLowerBound();
1605            double length = this.range.getLength();
1606            Range adjusted = null;
1607            if (isInverted()) {
1608                adjusted = new Range(start + (length * (1 - upperPercent)),
1609                                     start + (length * (1 - lowerPercent)));
1610            }
1611            else {
1612                adjusted = new Range(start + length * lowerPercent,
1613                        start + length * upperPercent);
1614            }
1615            setRange(adjusted);
1616        }
1617    
1618        /**
1619         * Slides the axis range by the specified percentage.
1620         *
1621         * @param percent  the percentage.
1622         *
1623         * @since 1.0.13
1624         */
1625        public void pan(double percent) {
1626            Range range = getRange();
1627            double length = range.getLength();
1628            double adj = length * percent;
1629            double lower = range.getLowerBound() + adj;
1630            double upper = range.getUpperBound() + adj;
1631            setRange(lower, upper);
1632        }
1633    
1634        /**
1635         * Returns the auto tick index.
1636         *
1637         * @return The auto tick index.
1638         *
1639         * @see #setAutoTickIndex(int)
1640         */
1641        protected int getAutoTickIndex() {
1642            return this.autoTickIndex;
1643        }
1644    
1645        /**
1646         * Sets the auto tick index.
1647         *
1648         * @param index  the new value.
1649         *
1650         * @see #getAutoTickIndex()
1651         */
1652        protected void setAutoTickIndex(int index) {
1653            this.autoTickIndex = index;
1654        }
1655    
1656        /**
1657         * Tests the axis for equality with an arbitrary object.
1658         *
1659         * @param obj  the object (<code>null</code> permitted).
1660         *
1661         * @return <code>true</code> or <code>false</code>.
1662         */
1663        public boolean equals(Object obj) {
1664            if (obj == this) {
1665                return true;
1666            }
1667            if (!(obj instanceof ValueAxis)) {
1668                return false;
1669            }
1670            ValueAxis that = (ValueAxis) obj;
1671            if (this.positiveArrowVisible != that.positiveArrowVisible) {
1672                return false;
1673            }
1674            if (this.negativeArrowVisible != that.negativeArrowVisible) {
1675                return false;
1676            }
1677            if (this.inverted != that.inverted) {
1678                return false;
1679            }
1680            // if autoRange is true, then the current range is irrelevant
1681            if (!this.autoRange && !ObjectUtilities.equal(this.range, that.range)) {
1682                return false;
1683            }
1684            if (this.autoRange != that.autoRange) {
1685                return false;
1686            }
1687            if (this.autoRangeMinimumSize != that.autoRangeMinimumSize) {
1688                return false;
1689            }
1690            if (!this.defaultAutoRange.equals(that.defaultAutoRange)) {
1691                return false;
1692            }
1693            if (this.upperMargin != that.upperMargin) {
1694                return false;
1695            }
1696            if (this.lowerMargin != that.lowerMargin) {
1697                return false;
1698            }
1699            if (this.fixedAutoRange != that.fixedAutoRange) {
1700                return false;
1701            }
1702            if (this.autoTickUnitSelection != that.autoTickUnitSelection) {
1703                return false;
1704            }
1705            if (!ObjectUtilities.equal(this.standardTickUnits,
1706                    that.standardTickUnits)) {
1707                return false;
1708            }
1709            if (this.verticalTickLabels != that.verticalTickLabels) {
1710                return false;
1711            }
1712            if (this.minorTickCount != that.minorTickCount) {
1713                return false;
1714            }
1715            return super.equals(obj);
1716        }
1717    
1718        /**
1719         * Returns a clone of the object.
1720         *
1721         * @return A clone.
1722         *
1723         * @throws CloneNotSupportedException if some component of the axis does
1724         *         not support cloning.
1725         */
1726        public Object clone() throws CloneNotSupportedException {
1727            ValueAxis clone = (ValueAxis) super.clone();
1728            return clone;
1729        }
1730    
1731        /**
1732         * Provides serialization support.
1733         *
1734         * @param stream  the output stream.
1735         *
1736         * @throws IOException  if there is an I/O error.
1737         */
1738        private void writeObject(ObjectOutputStream stream) throws IOException {
1739            stream.defaultWriteObject();
1740            SerialUtilities.writeShape(this.upArrow, stream);
1741            SerialUtilities.writeShape(this.downArrow, stream);
1742            SerialUtilities.writeShape(this.leftArrow, stream);
1743            SerialUtilities.writeShape(this.rightArrow, stream);
1744        }
1745    
1746        /**
1747         * Provides serialization support.
1748         *
1749         * @param stream  the input stream.
1750         *
1751         * @throws IOException  if there is an I/O error.
1752         * @throws ClassNotFoundException  if there is a classpath problem.
1753         */
1754        private void readObject(ObjectInputStream stream)
1755                throws IOException, ClassNotFoundException {
1756    
1757            stream.defaultReadObject();
1758            this.upArrow = SerialUtilities.readShape(stream);
1759            this.downArrow = SerialUtilities.readShape(stream);
1760            this.leftArrow = SerialUtilities.readShape(stream);
1761            this.rightArrow = SerialUtilities.readShape(stream);
1762        }
1763    
1764    }