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