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