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