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