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