    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ---------
    028     * Axis.java
    029     * ---------
    030     * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Bill Kelemen; Nicolas Brodu
    034     *
    035     * $Id: Axis.java,v 1.11.2.4 2006/08/23 10:24:26 mungady Exp $
    036     *
    037     * Changes (from 21-Aug-2001)
    038     * --------------------------
    039     * 21-Aug-2001 : Added standard header, fixed DOS encoding problem (DG);
    040     * 18-Sep-2001 : Updated header (DG);
    041     * 07-Nov-2001 : Allow null axis labels (DG);
    042     *             : Added default font values (DG);
    043     * 13-Nov-2001 : Modified the setPlot() method to check compatibility between
    044     *               the axis and the plot (DG);
    045     * 30-Nov-2001 : Changed default font from "Arial" --> "SansSerif" (DG);
    046     * 06-Dec-2001 : Allow null in setPlot() method (BK);
    047     * 06-Mar-2002 : Added AxisConstants interface (DG);
    048     * 23-Apr-2002 : Added a visible property.  Moved drawVerticalString to
    049     *               RefineryUtilities.  Added fixedDimension property for use in
    050     *               combined plots (DG);
    051     * 25-Jun-2002 : Removed unnecessary imports (DG);
    052     * 05-Sep-2002 : Added attribute for tick mark paint (DG);
    053     * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG);
    054     * 07-Nov-2002 : Added attributes to control the inside and outside length of
    055     *               the tick marks (DG);
    056     * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
    057     * 18-Nov-2002 : Added axis location to refreshTicks() parameters (DG);
    058     * 15-Jan-2003 : Removed monolithic constructor (DG);
    059     * 17-Jan-2003 : Moved plot classes to separate package (DG);
    060     * 26-Mar-2003 : Implemented Serializable (DG);
    061     * 03-Jul-2003 : Modified reserveSpace method (DG);
    062     * 13-Aug-2003 : Implemented Cloneable (DG);
    063     * 11-Sep-2003 : Took care of listeners while cloning (NB);
    064     * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
    065     * 06-Nov-2003 : Modified refreshTicks() signature (DG);
    066     * 06-Jan-2004 : Added axis line attributes (DG);
    067     * 16-Mar-2004 : Added plot state to draw() method (DG);
    068     * 07-Apr-2004 : Modified text bounds calculation (DG);
    069     * 18-May-2004 : Eliminated AxisConstants.java (DG);
    070     * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities -->
    071     *               TextUtilities (DG);
    072     * 04-Oct-2004 : Modified getLabelEnclosure() method to treat an empty String
    073     *               the same way as a null string - see bug 1026521 (DG);
    074     * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
    075     * 26-Apr-2005 : Removed LOGGER (DG);
    076     * 01-Jun-2005 : Added hasListener() method for unit testing (DG);
    077     * 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
    078     * ------------- JFREECHART 1.0.0 ---------------------------------------------
    079     * 22-Aug-2006 : API doc updates (DG);
    080     *
    081     */
    082
    083    package org.jfree.chart.axis;
    084
    085    import java.awt.BasicStroke;
    086    import java.awt.Color;
    087    import java.awt.Font;
    088    import java.awt.FontMetrics;
    089    import java.awt.Graphics2D;
    090    import java.awt.Paint;
    091    import java.awt.Shape;
    092    import java.awt.Stroke;
    093    import java.awt.geom.AffineTransform;
    094    import java.awt.geom.Line2D;
    095    import java.awt.geom.Rectangle2D;
    096    import java.io.IOException;
    097    import java.io.ObjectInputStream;
    098    import java.io.ObjectOutputStream;
    099    import java.io.Serializable;
    100    import java.util.Arrays;
    101    import java.util.EventListener;
    102    import java.util.List;
    103
    104    import javax.swing.event.EventListenerList;
    105
    106    import org.jfree.chart.event.AxisChangeEvent;
    107    import org.jfree.chart.event.AxisChangeListener;
    108    import org.jfree.chart.plot.Plot;
    109    import org.jfree.chart.plot.PlotRenderingInfo;
    110    import org.jfree.io.SerialUtilities;
    111    import org.jfree.text.TextUtilities;
    112    import org.jfree.ui.RectangleEdge;
    113    import org.jfree.ui.RectangleInsets;
    114    import org.jfree.ui.TextAnchor;
    115    import org.jfree.util.ObjectUtilities;
    116    import org.jfree.util.PaintUtilities;
    117
    118    /**
    119     * The base class for all axes in JFreeChart.  Subclasses are divided into
    120     * those that display values ({@link ValueAxis}) and those that display
    121     * categories ({@link CategoryAxis}).
    122     */
    123    public abstract class Axis implements Cloneable, Serializable {
    124
    125        /** For serialization. */
    126        private static final long serialVersionUID = 7719289504573298271L;
    127
    128        /** The default axis visibility. */
    129        public static final boolean DEFAULT_AXIS_VISIBLE = true;
    130
    131        /** The default axis label font. */
    132        public static final Font DEFAULT_AXIS_LABEL_FONT
    133            = new Font("SansSerif", Font.PLAIN, 12);
    134
    135        /** The default axis label paint. */
    136        public static final Paint DEFAULT_AXIS_LABEL_PAINT = Color.black;
    137
    138        /** The default axis label insets. */
    139        public static final RectangleInsets DEFAULT_AXIS_LABEL_INSETS
    140            = new RectangleInsets(3.0, 3.0, 3.0, 3.0);
    141
    142        /** The default axis line paint. */
    143        public static final Paint DEFAULT_AXIS_LINE_PAINT = Color.gray;
    144
    145        /** The default axis line stroke. */
    146        public static final Stroke DEFAULT_AXIS_LINE_STROKE = new BasicStroke(1.0f);
    147
    148        /** The default tick labels visibility. */
    149        public static final boolean DEFAULT_TICK_LABELS_VISIBLE = true;
    150
    151        /** The default tick label font. */
    152        public static final Font DEFAULT_TICK_LABEL_FONT
    153            = new Font("SansSerif", Font.PLAIN, 10);
    154
    155        /** The default tick label paint. */
    156        public static final Paint DEFAULT_TICK_LABEL_PAINT = Color.black;
    157
    158        /** The default tick label insets. */
    159        public static final RectangleInsets DEFAULT_TICK_LABEL_INSETS
    160            = new RectangleInsets(2.0, 4.0, 2.0, 4.0);
    161
    162        /** The default tick marks visible. */
    163        public static final boolean DEFAULT_TICK_MARKS_VISIBLE = true;
    164
    165        /** The default tick stroke. */
    166        public static final Stroke DEFAULT_TICK_MARK_STROKE = new BasicStroke(1);
    167
    168        /** The default tick paint. */
    169        public static final Paint DEFAULT_TICK_MARK_PAINT = Color.gray;
    170
    171        /** The default tick mark inside length. */
    172        public static final float DEFAULT_TICK_MARK_INSIDE_LENGTH = 0.0f;
    173
    174        /** The default tick mark outside length. */
    175        public static final float DEFAULT_TICK_MARK_OUTSIDE_LENGTH = 2.0f;
    176
    177        /** A flag indicating whether or not the axis is visible. */
    178        private boolean visible;
    179
    180        /** The label for the axis. */
    181        private String label;
    182
    183        /** The font for displaying the axis label. */
    184        private Font labelFont;
    185
    186        /** The paint for drawing the axis label. */
    187        private transient Paint labelPaint;
    188
    189        /** The insets for the axis label. */
    190        private RectangleInsets labelInsets;
    191
    192        /** The label angle. */
    193        private double labelAngle;
    194
    195        /** A flag that controls whether or not the axis line is visible. */
    196        private boolean axisLineVisible;
    197
    198        /** The stroke used for the axis line. */
    199        private transient Stroke axisLineStroke;
    200
    201        /** The paint used for the axis line. */
    202        private transient Paint axisLinePaint;
    203
    204        /**
    205         * A flag that indicates whether or not tick labels are visible for the
    206         * axis.
    207         */
    208        private boolean tickLabelsVisible;
    209
    210        /** The font used to display the tick labels. */
    211        private Font tickLabelFont;
    212
    213        /** The color used to display the tick labels. */
    214        private transient Paint tickLabelPaint;
    215
    216        /** The blank space around each tick label. */
    217        private RectangleInsets tickLabelInsets;
    218
    219        /**
    220         * A flag that indicates whether or not tick marks are visible for the
    221         * axis.
    222         */
    223        private boolean tickMarksVisible;
    224
    225        /** The length of the tick mark inside the data area (zero permitted). */
    226        private float tickMarkInsideLength;
    227
    228        /** The length of the tick mark outside the data area (zero permitted). */
    229        private float tickMarkOutsideLength;
    230
    231        /** The stroke used to draw tick marks. */
    232        private transient Stroke tickMarkStroke;
    233
    234        /** The paint used to draw tick marks. */
    235        private transient Paint tickMarkPaint;
    236
    237        /** The fixed (horizontal or vertical) dimension for the axis. */
    238        private double fixedDimension;
    239
    240        /**
    241         * A reference back to the plot that the axis is assigned to (can be
    242         * <code>null</code>).
    243         */
    244        private transient Plot plot;
    245
    246        /** Storage for registered listeners. */
    247        private transient EventListenerList listenerList;
    248
    249        /**
    250         * Constructs an axis, using default values where necessary.
    251         *
    252         * @param label  the axis label (<code>null</code> permitted).
    253         */
    254        protected Axis(String label) {
    255
    256            this.label = label;
    257            this.visible = DEFAULT_AXIS_VISIBLE;
    258            this.labelFont = DEFAULT_AXIS_LABEL_FONT;
    259            this.labelPaint = DEFAULT_AXIS_LABEL_PAINT;
    260            this.labelInsets = DEFAULT_AXIS_LABEL_INSETS;
    261            this.labelAngle = 0.0;
    262
    263            this.axisLineVisible = true;
    264            this.axisLinePaint = DEFAULT_AXIS_LINE_PAINT;
    265            this.axisLineStroke = DEFAULT_AXIS_LINE_STROKE;
    266
    267            this.tickLabelsVisible = DEFAULT_TICK_LABELS_VISIBLE;
    268            this.tickLabelFont = DEFAULT_TICK_LABEL_FONT;
    269            this.tickLabelPaint = DEFAULT_TICK_LABEL_PAINT;
    270            this.tickLabelInsets = DEFAULT_TICK_LABEL_INSETS;
    271
    272            this.tickMarksVisible = DEFAULT_TICK_MARKS_VISIBLE;
    273            this.tickMarkStroke = DEFAULT_TICK_MARK_STROKE;
    274            this.tickMarkPaint = DEFAULT_TICK_MARK_PAINT;
    275            this.tickMarkInsideLength = DEFAULT_TICK_MARK_INSIDE_LENGTH;
    276            this.tickMarkOutsideLength = DEFAULT_TICK_MARK_OUTSIDE_LENGTH;
    277
    278            this.plot = null;
    279
    280            this.listenerList = new EventListenerList();
    281
    282        }
    283
    284        /**
    285         * Returns <code>true</code> if the axis is visible, and
    286         * <code>false</code> otherwise.
    287         *
    288         * @return A boolean.
    289         *
    290         * @see #setVisible(boolean)
    291         */
    292        public boolean isVisible() {
    293            return this.visible;
    294        }
    295
    296        /**
    297         * Sets a flag that controls whether or not the axis is visible and sends
    298         * an {@link AxisChangeEvent} to all registered listeners.
    299         *
    300         * @param flag  the flag.
    301         *
    302         * @see #isVisible()
    303         */
    304        public void setVisible(boolean flag) {
    305            if (flag != this.visible) {
    306                this.visible = flag;
    307                notifyListeners(new AxisChangeEvent(this));
    308            }
    309        }
    310
    311        /**
    312         * Returns the label for the axis.
    313         *
    314         * @return The label for the axis (<code>null</code> possible).
    315         *
    316         * @see #getLabelFont()
    317         * @see #getLabelPaint()
    318         * @see #setLabel(String)
    319         */
    320        public String getLabel() {
    321            return this.label;
    322        }
    323
    324        /**
    325         * Sets the label for the axis and sends an {@link AxisChangeEvent} to all
    326         * registered listeners.
    327         *
    328         * @param label  the new label (<code>null</code> permitted).
    329         *
    330         * @see #getLabel()
    331         * @see #setLabelFont(Font)
    332         * @see #setLabelPaint(Paint)
    333         */
    334        public void setLabel(String label) {
    335
    336            String existing = this.label;
    337            if (existing != null) {
    338                if (!existing.equals(label)) {
    339                    this.label = label;
    340                    notifyListeners(new AxisChangeEvent(this));
    341                }
    342            }
    343            else {
    344                if (label != null) {
    345                    this.label = label;
    346                    notifyListeners(new AxisChangeEvent(this));
    347                }
    348            }
    349
    350        }
    351
    352        /**
    353         * Returns the font for the axis label.
    354         *
    355         * @return The font (never <code>null</code>).
    356         *
    357         * @see #setLabelFont(Font)
    358         */
    359        public Font getLabelFont() {
    360            return this.labelFont;
    361        }
    362
    363        /**
    364         * Sets the font for the axis label and sends an {@link AxisChangeEvent}
    365         * to all registered listeners.
    366         *
    367         * @param font  the font (<code>null</code> not permitted).
    368         *
    369         * @see #getLabelFont()
    370         */
    371        public void setLabelFont(Font font) {
    372            if (font == null) {
    373                throw new IllegalArgumentException("Null 'font' argument.");
    374            }
    375            if (!this.labelFont.equals(font)) {
    376                this.labelFont = font;
    377                notifyListeners(new AxisChangeEvent(this));
    378            }
    379        }
    380
    381        /**
    382         * Returns the color/shade used to draw the axis label.
    383         *
    384         * @return The paint (never <code>null</code>).
    385         *
    386         * @see #setLabelPaint(Paint)
    387         */
    388        public Paint getLabelPaint() {
    389            return this.labelPaint;
    390        }
    391
    392        /**
    393         * Sets the paint used to draw the axis label and sends an
    394         * {@link AxisChangeEvent} to all registered listeners.
    395         *
    396         * @param paint  the paint (<code>null</code> not permitted).
    397         *
    398         * @see #getLabelPaint()
    399         */
    400        public void setLabelPaint(Paint paint) {
    401            if (paint == null) {
    402                throw new IllegalArgumentException("Null 'paint' argument.");
    403            }
    404            this.labelPaint = paint;
    405            notifyListeners(new AxisChangeEvent(this));
    406        }
    407
    408        /**
    409         * Returns the insets for the label (that is, the amount of blank space
    410         * that should be left around the label).
    411         *
    412         * @return The label insets (never <code>null</code>).
    413         *
    414         * @see #setLabelInsets(RectangleInsets)
    415         */
    416        public RectangleInsets getLabelInsets() {
    417            return this.labelInsets;
    418        }
    419
    420        /**
    421         * Sets the insets for the axis label, and sends an {@link AxisChangeEvent}
    422         * to all registered listeners.
    423         *
    424         * @param insets  the insets (<code>null</code> not permitted).
    425         *
    426         * @see #getLabelInsets()
    427         */
    428        public void setLabelInsets(RectangleInsets insets) {
    429            if (insets == null) {
    430                throw new IllegalArgumentException("Null 'insets' argument.");
    431            }
    432            if (!insets.equals(this.labelInsets)) {
    433                this.labelInsets = insets;
    434                notifyListeners(new AxisChangeEvent(this));
    435            }
    436        }
    437
    438        /**
    439         * Returns the angle of the axis label.
    440         *
    441         * @return The angle (in radians).
    442         *
    443         * @see #setLabelAngle(double)
    444         */
    445        public double getLabelAngle() {
    446            return this.labelAngle;
    447        }
    448
    449        /**
    450         * Sets the angle for the label and sends an {@link AxisChangeEvent} to all
    451         * registered listeners.
    452         *
    453         * @param angle  the angle (in radians).
    454         *
    455         * @see #getLabelAngle()
    456         */
    457        public void setLabelAngle(double angle) {
    458            this.labelAngle = angle;
    459            notifyListeners(new AxisChangeEvent(this));
    460        }
    461
    462        /**
    463         * A flag that controls whether or not the axis line is drawn.
    464         *
    465         * @return A boolean.
    466         *
    467         * @see #getAxisLinePaint()
    468         * @see #getAxisLineStroke()
    469         * @see #setAxisLineVisible(boolean)
    470         */
    471        public boolean isAxisLineVisible() {
    472            return this.axisLineVisible;
    473        }
    474
    475        /**
    476         * Sets a flag that controls whether or not the axis line is visible and
    477         * sends an {@link AxisChangeEvent} to all registered listeners.
    478         *
    479         * @param visible  the flag.
    480         *
    481         * @see #isAxisLineVisible()
    482         * @see #setAxisLinePaint(Paint)
    483         * @see #setAxisLineStroke(Stroke)
    484         */
    485        public void setAxisLineVisible(boolean visible) {
    486            this.axisLineVisible = visible;
    487            notifyListeners(new AxisChangeEvent(this));
    488        }
    489
    490        /**
    491         * Returns the paint used to draw the axis line.
    492         *
    493         * @return The paint (never <code>null</code>).
    494         *
    495         * @see #setAxisLinePaint(Paint)
    496         */
    497        public Paint getAxisLinePaint() {
    498            return this.axisLinePaint;
    499        }
    500
    501        /**
    502         * Sets the paint used to draw the axis line and sends an
    503         * {@link AxisChangeEvent} to all registered listeners.
    504         *
    505         * @param paint  the paint (<code>null</code> not permitted).
    506         *
    507         * @see #getAxisLinePaint()
    508         */
    509        public void setAxisLinePaint(Paint paint) {
    510            if (paint == null) {
    511                throw new IllegalArgumentException("Null 'paint' argument.");
    512            }
    513            this.axisLinePaint = paint;
    514            notifyListeners(new AxisChangeEvent(this));
    515        }
    516
    517        /**
    518         * Returns the stroke used to draw the axis line.
    519         *
    520         * @return The stroke (never <code>null</code>).
    521         *
    522         * @see #setAxisLineStroke(Stroke)
    523         */
    524        public Stroke getAxisLineStroke() {
    525            return this.axisLineStroke;
    526        }
    527
    528        /**
    529         * Sets the stroke used to draw the axis line and sends an
    530         * {@link AxisChangeEvent} to all registered listeners.
    531         *
    532         * @param stroke  the stroke (<code>null</code> not permitted).
    533         *
    534         * @see #getAxisLineStroke()
    535         */
    536        public void setAxisLineStroke(Stroke stroke) {
    537            if (stroke == null) {
    538                throw new IllegalArgumentException("Null 'stroke' argument.");
    539            }
    540            this.axisLineStroke = stroke;
    541            notifyListeners(new AxisChangeEvent(this));
    542        }
    543
    544        /**
    545         * Returns a flag indicating whether or not the tick labels are visible.
    546         *
    547         * @return The flag.
    548         *
    549         * @see #getTickLabelFont()
    550         * @see #getTickLabelPaint()
    551         * @see #setTickLabelsVisible(boolean)
    552         */
    553        public boolean isTickLabelsVisible() {
    554            return this.tickLabelsVisible;
    555        }
    556
    557        /**
    558         * Sets the flag that determines whether or not the tick labels are
    559         * visible and sends an {@link AxisChangeEvent} to all registered
    560         * listeners.
    561         *
    562         * @param flag  the flag.
    563         *
    564         * @see #isTickLabelsVisible()
    565         * @see #setTickLabelFont(Font)
    566         * @see #setTickLabelPaint(Paint)
    567         */
    568        public void setTickLabelsVisible(boolean flag) {
    569
    570            if (flag != this.tickLabelsVisible) {
    571                this.tickLabelsVisible = flag;
    572                notifyListeners(new AxisChangeEvent(this));
    573            }
    574
    575        }
    576
    577        /**
    578         * Returns the font used for the tick labels (if showing).
    579         *
    580         * @return The font (never <code>null</code>).
    581         *
    582         * @see #setTickLabelFont(Font)
    583         */
    584        public Font getTickLabelFont() {
    585            return this.tickLabelFont;
    586        }
    587
    588        /**
    589         * Sets the font for the tick labels and sends an {@link AxisChangeEvent}
    590         * to all registered listeners.
    591         *
    592         * @param font  the font (<code>null</code> not allowed).
    593         *
    594         * @see #getTickLabelFont()
    595         */
    596        public void setTickLabelFont(Font font) {
    597
    598            if (font == null) {
    599                throw new IllegalArgumentException("Null 'font' argument.");
    600            }
    601
    602            if (!this.tickLabelFont.equals(font)) {
    603                this.tickLabelFont = font;
    604                notifyListeners(new AxisChangeEvent(this));
    605            }
    606
    607        }
    608
    609        /**
    610         * Returns the color/shade used for the tick labels.
    611         *
    612         * @return The paint used for the tick labels.
    613         *
    614         * @see #setTickLabelPaint(Paint)
    615         */
    616        public Paint getTickLabelPaint() {
    617            return this.tickLabelPaint;
    618        }
    619
    620        /**
    621         * Sets the paint used to draw tick labels (if they are showing) and
    622         * sends an {@link AxisChangeEvent} to all registered listeners.
    623         *
    624         * @param paint  the paint (<code>null</code> not permitted).
    625         *
    626         * @see #getTickLabelPaint()
    627         */
    628        public void setTickLabelPaint(Paint paint) {
    629            if (paint == null) {
    630                throw new IllegalArgumentException("Null 'paint' argument.");
    631            }
    632            this.tickLabelPaint = paint;
    633            notifyListeners(new AxisChangeEvent(this));
    634        }
    635
    636        /**
    637         * Returns the insets for the tick labels.
    638         *
    639         * @return The insets (never <code>null</code>).
    640         *
    641         * @see #setTickLabelInsets(RectangleInsets)
    642         */
    643        public RectangleInsets getTickLabelInsets() {
    644            return this.tickLabelInsets;
    645        }
    646
    647        /**
    648         * Sets the insets for the tick labels and sends an {@link AxisChangeEvent}
    649         * to all registered listeners.
    650         *
    651         * @param insets  the insets (<code>null</code> not permitted).
    652         *
    653         * @see #getTickLabelInsets()
    654         */
    655        public void setTickLabelInsets(RectangleInsets insets) {
    656            if (insets == null) {
    657                throw new IllegalArgumentException("Null 'insets' argument.");
    658            }
    659            if (!this.tickLabelInsets.equals(insets)) {
    660                this.tickLabelInsets = insets;
    661                notifyListeners(new AxisChangeEvent(this));
    662            }
    663        }
    664
    665        /**
    666         * Returns the flag that indicates whether or not the tick marks are
    667         * showing.
    668         *
    669         * @return The flag that indicates whether or not the tick marks are
    670         *         showing.
    671         *
    672         * @see #setTickMarksVisible(boolean)
    673         */
    674        public boolean isTickMarksVisible() {
    675            return this.tickMarksVisible;
    676        }
    677
    678        /**
    679         * Sets the flag that indicates whether or not the tick marks are showing
    680         * and sends an {@link AxisChangeEvent} to all registered listeners.
    681         *
    682         * @param flag  the flag.
    683         *
    684         * @see #isTickMarksVisible()
    685         */
    686        public void setTickMarksVisible(boolean flag) {
    687            if (flag != this.tickMarksVisible) {
    688                this.tickMarksVisible = flag;
    689                notifyListeners(new AxisChangeEvent(this));
    690            }
    691        }
    692
    693        /**
    694         * Returns the inside length of the tick marks.
    695         *
    696         * @return The length.
    697         *
    698         * @see #getTickMarkOutsideLength()
    699         * @see #setTickMarkInsideLength(float)
    700         */
    701        public float getTickMarkInsideLength() {
    702            return this.tickMarkInsideLength;
    703        }
    704
    705        /**
    706         * Sets the inside length of the tick marks and sends
    707         * an {@link AxisChangeEvent} to all registered listeners.
    708         *
    709         * @param length  the new length.
    710         *
    711         * @see #getTickMarkInsideLength()
    712         */
    713        public void setTickMarkInsideLength(float length) {
    714            this.tickMarkInsideLength = length;
    715            notifyListeners(new AxisChangeEvent(this));
    716        }
    717
    718        /**
    719         * Returns the outside length of the tick marks.
    720         *
    721         * @return The length.
    722         *
    723         * @see #getTickMarkInsideLength()
    724         * @see #setTickMarkOutsideLength(float)
    725         */
    726        public float getTickMarkOutsideLength() {
    727            return this.tickMarkOutsideLength;
    728        }
    729
    730        /**
    731         * Sets the outside length of the tick marks and sends
    732         * an {@link AxisChangeEvent} to all registered listeners.
    733         *
    734         * @param length  the new length.
    735         *
    736         * @see #getTickMarkInsideLength()
    737         */
    738        public void setTickMarkOutsideLength(float length) {
    739            this.tickMarkOutsideLength = length;
    740            notifyListeners(new AxisChangeEvent(this));
    741        }
    742
    743        /**
    744         * Returns the stroke used to draw tick marks.
    745         *
    746         * @return The stroke (never <code>null</code>).
    747         *
    748         * @see #setTickMarkStroke(Stroke)
    749         */
    750        public Stroke getTickMarkStroke() {
    751            return this.tickMarkStroke;
    752        }
    753
    754        /**
    755         * Sets the stroke used to draw tick marks and sends
    756         * an {@link AxisChangeEvent} to all registered listeners.
    757         *
    758         * @param stroke  the stroke (<code>null</code> not permitted).
    759         *
    760         * @see #getTickMarkStroke()
    761         */
    762        public void setTickMarkStroke(Stroke stroke) {
    763            if (stroke == null) {
    764                throw new IllegalArgumentException("Null 'stroke' argument.");
    765            }
    766            if (!this.tickMarkStroke.equals(stroke)) {
    767                this.tickMarkStroke = stroke;
    768                notifyListeners(new AxisChangeEvent(this));
    769            }
    770        }
    771
    772        /**
    773         * Returns the paint used to draw tick marks (if they are showing).
    774         *
    775         * @return The paint (never <code>null</code>).
    776         *
    777         * @see #setTickMarkPaint(Paint)
    778         */
    779        public Paint getTickMarkPaint() {
    780            return this.tickMarkPaint;
    781        }
    782
    783        /**
    784         * Sets the paint used to draw tick marks and sends an
    785         * {@link AxisChangeEvent} to all registered listeners.
    786         *
    787         * @param paint  the paint (<code>null</code> not permitted).
    788         *
    789         * @see #getTickMarkPaint()
    790         */
    791        public void setTickMarkPaint(Paint paint) {
    792            if (paint == null) {
    793                throw new IllegalArgumentException("Null 'paint' argument.");
    794            }
    795            this.tickMarkPaint = paint;
    796            notifyListeners(new AxisChangeEvent(this));
    797        }
    798
    799        /**
    800         * Returns the plot that the axis is assigned to.  This method will return
    801         * <code>null</code> if the axis is not currently assigned to a plot.
    802         *
    803         * @return The plot that the axis is assigned to (possibly
    804         *         <code>null</code>).
    805         *
    806         * @see #setPlot(Plot)
    807         */
    808        public Plot getPlot() {
    809            return this.plot;
    810        }
    811
    812        /**
    813         * Sets a reference to the plot that the axis is assigned to.
    814         * <P>
    815         * This method is used internally, you shouldn't need to call it yourself.
    816         *
    817         * @param plot  the plot.
    818         *
    819         * @see #getPlot()
    820         */
    821        public void setPlot(Plot plot) {
    822            this.plot = plot;
    823            configure();
    824        }
    825
    826        /**
    827         * Returns the fixed dimension for the axis.
    828         *
    829         * @return The fixed dimension.
    830         *
    831         * @see #setFixedDimension(double)
    832         */
    833        public double getFixedDimension() {
    834            return this.fixedDimension;
    835        }
    836
    837        /**
    838         * Sets the fixed dimension for the axis.
    839         * <P>
    840         * This is used when combining more than one plot on a chart.  In this case,
    841         * there may be several axes that need to have the same height or width so
    842         * that they are aligned.  This method is used to fix a dimension for the
    843         * axis (the context determines whether the dimension is horizontal or
    844         * vertical).
    845         *
    846         * @param dimension  the fixed dimension.
    847         *
    848         * @see #getFixedDimension()
    849         */
    850        public void setFixedDimension(double dimension) {
    851            this.fixedDimension = dimension;
    852        }
    853
    854        /**
    855         * Configures the axis to work with the current plot.  Override this method
    856         * to perform any special processing (such as auto-rescaling).
    857         */
    858        public abstract void configure();
    859
    860        /**
    861         * Estimates the space (height or width) required to draw the axis.
    862         *
    863         * @param g2  the graphics device.
    864         * @param plot  the plot that the axis belongs to.
    865         * @param plotArea  the area within which the plot (including axes) should
    866         *                  be drawn.
    867         * @param edge  the axis location.
    868         * @param space  space already reserved.
    869         *
    870         * @return The space required to draw the axis (including pre-reserved
    871         *         space).
    872         */
    873        public abstract AxisSpace reserveSpace(Graphics2D g2, Plot plot,
    874                                               Rectangle2D plotArea,
    875                                               RectangleEdge edge,
    876                                               AxisSpace space);
    877
    878        /**
    879         * Draws the axis on a Java 2D graphics device (such as the screen or a
    880         * printer).
    881         *
    882         * @param g2  the graphics device (<code>null</code> not permitted).
    883         * @param cursor  the cursor location (determines where to draw the axis).
    884         * @param plotArea  the area within which the axes and plot should be drawn.
    885         * @param dataArea  the area within which the data should be drawn.
    886         * @param edge  the axis location (<code>null</code> not permitted).
    887         * @param plotState  collects information about the plot
    888         *                   (<code>null</code> permitted).
    889         *
    890         * @return The axis state (never <code>null</code>).
    891         */
    892        public abstract AxisState draw(Graphics2D g2,
    893                                       double cursor,
    894                                       Rectangle2D plotArea,
    895                                       Rectangle2D dataArea,
    896                                       RectangleEdge edge,
    897                                       PlotRenderingInfo plotState);
    898
    899        /**
    900         * Calculates the positions of the ticks for the axis, storing the results
    901         * in the tick list (ready for drawing).
    902         *
    903         * @param g2  the graphics device.
    904         * @param state  the axis state.
    905         * @param dataArea  the area inside the axes.
    906         * @param edge  the edge on which the axis is located.
    907         *
    908         * @return The list of ticks.
    909         */
    910        public abstract List refreshTicks(Graphics2D g2,
    911                                          AxisState state,
    912                                          Rectangle2D dataArea,
    913                                          RectangleEdge edge);
    914
    915        /**
    916         * Registers an object for notification of changes to the axis.
    917         *
    918         * @param listener  the object that is being registered.
    919         *
    920         * @see #removeChangeListener(AxisChangeListener)
    921         */
    922        public void addChangeListener(AxisChangeListener listener) {
    923            this.listenerList.add(AxisChangeListener.class, listener);
    924        }
    925
    926        /**
    927         * Deregisters an object for notification of changes to the axis.
    928         *
    929         * @param listener  the object to deregister.
    930         *
    931         * @see #addChangeListener(AxisChangeListener)
    932         */
    933        public void removeChangeListener(AxisChangeListener listener) {
    934            this.listenerList.remove(AxisChangeListener.class, listener);
    935        }
    936
    937        /**
    938         * Returns <code>true</code> if the specified object is registered with
    939         * the dataset as a listener.  Most applications won't need to call this
    940         * method, it exists mainly for use by unit testing code.
    941         *
    942         * @param listener  the listener.
    943         *
    944         * @return A boolean.
    945         */
    946        public boolean hasListener(EventListener listener) {
    947            List list = Arrays.asList(this.listenerList.getListenerList());
    948            return list.contains(listener);
    949        }
    950
    951        /**
    952         * Notifies all registered listeners that the axis has changed.
    953         * The AxisChangeEvent provides information about the change.
    954         *
    955         * @param event  information about the change to the axis.
    956         */
    957        protected void notifyListeners(AxisChangeEvent event) {
    958
    959            Object[] listeners = this.listenerList.getListenerList();
    960            for (int i = listeners.length - 2; i >= 0; i -= 2) {
    961                if (listeners[i] == AxisChangeListener.class) {
    962                    ((AxisChangeListener) listeners[i + 1]).axisChanged(event);
    963                }
    964            }
    965
    966        }
    967
    968        /**
    969         * Returns a rectangle that encloses the axis label.  This is typically
    970         * used for layout purposes (it gives the maximum dimensions of the label).
    971         *
    972         * @param g2  the graphics device.
    973         * @param edge  the edge of the plot area along which the axis is measuring.
    974         *
    975         * @return The enclosing rectangle.
    976         */
    977        protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) {
    978
    979            Rectangle2D result = new Rectangle2D.Double();
    980            String axisLabel = getLabel();
    981            if (axisLabel != null && !axisLabel.equals("")) {
    982                FontMetrics fm = g2.getFontMetrics(getLabelFont());
    983                Rectangle2D bounds = TextUtilities.getTextBounds(axisLabel, g2, fm);
    984                RectangleInsets insets = getLabelInsets();
    985                bounds = insets.createOutsetRectangle(bounds);
    986                double angle = getLabelAngle();
    987                if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) {
    988                    angle = angle - Math.PI / 2.0;
    989                }
    990                double x = bounds.getCenterX();
    991                double y = bounds.getCenterY();
    992                AffineTransform transformer
    993                    = AffineTransform.getRotateInstance(angle, x, y);
    994                Shape labelBounds = transformer.createTransformedShape(bounds);
    995                result = labelBounds.getBounds2D();
    996            }
    997
    998            return result;
    999
    1000        }
    1001
    1002        /**
    1003         * Draws the axis label.
    1004         *
    1005         * @param label  the label text.
    1006         * @param g2  the graphics device.
    1007         * @param plotArea  the plot area.
    1008         * @param dataArea  the area inside the axes.
    1009         * @param edge  the location of the axis.
    1010         * @param state  the axis state (<code>null</code> not permitted).
    1011         *
    1012         * @return Information about the axis.
    1013         */
    1014        protected AxisState drawLabel(String label,
    1015                                      Graphics2D g2,
    1016                                      Rectangle2D plotArea,
    1017                                      Rectangle2D dataArea,
    1018                                      RectangleEdge edge,
    1019                                      AxisState state) {
    1020
    1021            // it is unlikely that 'state' will be null, but check anyway...
    1022            if (state == null) {
    1023                throw new IllegalArgumentException("Null 'state' argument.");
    1024            }
    1025
    1026            if ((label == null) || (label.equals(""))) {
    1027                return state;
    1028            }
    1029
    1030            Font font = getLabelFont();
    1031            RectangleInsets insets = getLabelInsets();
    1032            g2.setFont(font);
    1033            g2.setPaint(getLabelPaint());
    1034            FontMetrics fm = g2.getFontMetrics();
    1035            Rectangle2D labelBounds = TextUtilities.getTextBounds(label, g2, fm);
    1036
    1037            if (edge == RectangleEdge.TOP) {
    1038
    1039                AffineTransform t = AffineTransform.getRotateInstance(
    1040                        getLabelAngle(), labelBounds.getCenterX(),
    1041                        labelBounds.getCenterY());
    1042                Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
    1043                labelBounds = rotatedLabelBounds.getBounds2D();
    1044                double labelx = dataArea.getCenterX();
    1045                double labely = state.getCursor() - insets.getBottom()
    1046                                - labelBounds.getHeight() / 2.0;
    1047                TextUtilities.drawRotatedString(label, g2, (float) labelx,
    1048                        (float) labely, TextAnchor.CENTER, getLabelAngle(),
    1049                        TextAnchor.CENTER);
    1050                state.cursorUp(insets.getTop() + labelBounds.getHeight()
    1051                        + insets.getBottom());
    1052
    1053            }
    1054            else if (edge == RectangleEdge.BOTTOM) {
    1055
    1056                AffineTransform t = AffineTransform.getRotateInstance(
    1057                        getLabelAngle(), labelBounds.getCenterX(),
    1058                        labelBounds.getCenterY());
    1059                Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
    1060                labelBounds = rotatedLabelBounds.getBounds2D();
    1061                double labelx = dataArea.getCenterX();
    1062                double labely = state.getCursor()
    1063                                + insets.getTop() + labelBounds.getHeight() / 2.0;
    1064                TextUtilities.drawRotatedString(label, g2, (float) labelx,
    1065                        (float) labely, TextAnchor.CENTER, getLabelAngle(),
    1066                        TextAnchor.CENTER);
    1067                state.cursorDown(insets.getTop() + labelBounds.getHeight()
    1068                        + insets.getBottom());
    1069
    1070            }
    1071            else if (edge == RectangleEdge.LEFT) {
    1072
    1073                AffineTransform t = AffineTransform.getRotateInstance(
    1074                        getLabelAngle() - Math.PI / 2.0, labelBounds.getCenterX(),
    1075                        labelBounds.getCenterY());
    1076                Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
    1077                labelBounds = rotatedLabelBounds.getBounds2D();
    1078                double labelx = state.getCursor()
    1079                                - insets.getRight() - labelBounds.getWidth() / 2.0;
    1080                double labely = dataArea.getCenterY();
    1081                TextUtilities.drawRotatedString(label, g2, (float) labelx,
    1082                        (float) labely, TextAnchor.CENTER,
    1083                        getLabelAngle() - Math.PI / 2.0, TextAnchor.CENTER);
    1084                state.cursorLeft(insets.getLeft() + labelBounds.getWidth()
    1085                        + insets.getRight());
    1086            }
    1087            else if (edge == RectangleEdge.RIGHT) {
    1088
    1089                AffineTransform t = AffineTransform.getRotateInstance(
    1090                        getLabelAngle() + Math.PI / 2.0,
    1091                        labelBounds.getCenterX(), labelBounds.getCenterY());
    1092                Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
    1093                labelBounds = rotatedLabelBounds.getBounds2D();
    1094                double labelx = state.getCursor()
    1095                                + insets.getLeft() + labelBounds.getWidth() / 2.0;
    1096                double labely = dataArea.getY() + dataArea.getHeight() / 2.0;
    1097                TextUtilities.drawRotatedString(label, g2, (float) labelx,
    1098                        (float) labely, TextAnchor.CENTER,
    1099                        getLabelAngle() + Math.PI / 2.0, TextAnchor.CENTER);
    1100                state.cursorRight(insets.getLeft() + labelBounds.getWidth()
    1101                        + insets.getRight());
    1102
    1103            }
    1104
    1105            return state;
    1106
    1107        }
    1108
    1109        /**
    1110         * Draws an axis line at the current cursor position and edge.
    1111         *
    1112         * @param g2  the graphics device.
    1113         * @param cursor  the cursor position.
    1114         * @param dataArea  the data area.
    1115         * @param edge  the edge.
    1116         */
    1117        protected void drawAxisLine(Graphics2D g2, double cursor,
    1118                Rectangle2D dataArea, RectangleEdge edge) {
    1119
    1120            Line2D axisLine = null;
    1121            if (edge == RectangleEdge.TOP) {
    1122                axisLine = new Line2D.Double(dataArea.getX(), cursor,
    1123                        dataArea.getMaxX(), cursor);
    1124            }
    1125            else if (edge == RectangleEdge.BOTTOM) {
    1126                axisLine = new Line2D.Double(dataArea.getX(), cursor,
    1127                        dataArea.getMaxX(), cursor);
    1128            }
    1129            else if (edge == RectangleEdge.LEFT) {
    1130                axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
    1131                        dataArea.getMaxY());
    1132            }
    1133            else if (edge == RectangleEdge.RIGHT) {
    1134                axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
    1135                        dataArea.getMaxY());
    1136            }
    1137            g2.setPaint(this.axisLinePaint);
    1138            g2.setStroke(this.axisLineStroke);
    1139            g2.draw(axisLine);
    1140
    1141        }
    1142
    1143        /**
    1144         * Returns a clone of the axis.
    1145         *
    1146         * @return A clone.
    1147         *
    1148         * @throws CloneNotSupportedException if some component of the axis does
    1149         *         not support cloning.
    1150         */
    1151        public Object clone() throws CloneNotSupportedException {
    1152            Axis clone = (Axis) super.clone();
    1153            // It's up to the plot which clones up to restore the correct references
    1154            clone.plot = null;
    1155            clone.listenerList = new EventListenerList();
    1156            return clone;
    1157        }
    1158
    1159        /**
    1160         * Tests this axis for equality with another object.
    1161         *
    1162         * @param obj  the object (<code>null</code> permitted).
    1163         *
    1164         * @return <code>true</code> or <code>false</code>.
    1165         */
    1166        public boolean equals(Object obj) {
    1167            if (obj == this) {
    1168                return true;
    1169            }
    1170            if (!(obj instanceof Axis)) {
    1171                return false;
    1172            }
    1173            Axis that = (Axis) obj;
    1174            if (this.visible != that.visible) {
    1175                return false;
    1176            }
    1177            if (!ObjectUtilities.equal(this.label, that.label)) {
    1178                return false;
    1179            }
    1180            if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
    1181                return false;
    1182            }
    1183            if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) {
    1184                return false;
    1185            }
    1186            if (!ObjectUtilities.equal(this.labelInsets, that.labelInsets)) {
    1187                return false;
    1188            }
    1189            if (this.labelAngle != that.labelAngle) {
    1190                return false;
    1191            }
    1192            if (this.axisLineVisible != that.axisLineVisible) {
    1193                return false;
    1194            }
    1195            if (!ObjectUtilities.equal(this.axisLineStroke, that.axisLineStroke)) {
    1196                return false;
    1197            }
    1198            if (!PaintUtilities.equal(this.axisLinePaint, that.axisLinePaint)) {
    1199                return false;
    1200            }
    1201            if (this.tickLabelsVisible != that.tickLabelsVisible) {
    1202                return false;
    1203            }
    1204            if (!ObjectUtilities.equal(this.tickLabelFont, that.tickLabelFont)) {
    1205                return false;
    1206            }
    1207            if (!PaintUtilities.equal(this.tickLabelPaint, that.tickLabelPaint)) {
    1208                return false;
    1209            }
    1210            if (!ObjectUtilities.equal(
    1211                this.tickLabelInsets, that.tickLabelInsets
    1212            )) {
    1213                return false;
    1214            }
    1215            if (this.tickMarksVisible != that.tickMarksVisible) {
    1216                return false;
    1217            }
    1218            if (this.tickMarkInsideLength != that.tickMarkInsideLength) {
    1219                return false;
    1220            }
    1221            if (this.tickMarkOutsideLength != that.tickMarkOutsideLength) {
    1222                return false;
    1223            }
    1224            if (!PaintUtilities.equal(this.tickMarkPaint, that.tickMarkPaint)) {
    1225                return false;
    1226            }
    1227            if (!ObjectUtilities.equal(this.tickMarkStroke, that.tickMarkStroke)) {
    1228                return false;
    1229            }
    1230            if (this.fixedDimension != that.fixedDimension) {
    1231                return false;
    1232            }
    1233            return true;
    1234        }
    1235
    1236        /**
    1237         * Provides serialization support.
    1238         *
    1239         * @param stream  the output stream.
    1240         *
    1241         * @throws IOException  if there is an I/O error.
    1242         */
    1243        private void writeObject(ObjectOutputStream stream) throws IOException {
    1244            stream.defaultWriteObject();
    1245            SerialUtilities.writePaint(this.labelPaint, stream);
    1246            SerialUtilities.writePaint(this.tickLabelPaint, stream);
    1247            SerialUtilities.writeStroke(this.axisLineStroke, stream);
    1248            SerialUtilities.writePaint(this.axisLinePaint, stream);
    1249            SerialUtilities.writeStroke(this.tickMarkStroke, stream);
    1250            SerialUtilities.writePaint(this.tickMarkPaint, stream);
    1251        }
    1252
    1253        /**
    1254         * Provides serialization support.
    1255         *
    1256         * @param stream  the input stream.
    1257         *
    1258         * @throws IOException  if there is an I/O error.
    1259         * @throws ClassNotFoundException  if there is a classpath problem.
    1260         */
    1261        private void readObject(ObjectInputStream stream)
    1262            throws IOException, ClassNotFoundException {
    1263            stream.defaultReadObject();
    1264            this.labelPaint = SerialUtilities.readPaint(stream);
    1265            this.tickLabelPaint = SerialUtilities.readPaint(stream);
    1266            this.axisLineStroke = SerialUtilities.readStroke(stream);
    1267            this.axisLinePaint = SerialUtilities.readPaint(stream);
    1268            this.tickMarkStroke = SerialUtilities.readStroke(stream);
    1269            this.tickMarkPaint = SerialUtilities.readPaint(stream);
    1270            this.listenerList = new EventListenerList();
    1271        }
    1272
    1273    }