001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2011, 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     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025     * Other names may be trademarks of their respective owners.]
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 
648         * showing and sends an {@link AxisChangeEvent} to all registered
649         * listeners.
650         *
651         * @param flag  the flag.
652         *
653         * @see #isMinorTickMarksVisible()
654         *
655         * @since 1.0.12
656         */
657        public void setMinorTickMarksVisible(boolean flag) {
658            if (flag != this.minorTickMarksVisible) {
659                this.minorTickMarksVisible = flag;
660                fireChangeEvent();
661            }
662        }
663    
664        /**
665         * Returns the font used for the tick labels (if showing).
666         *
667         * @return The font (never <code>null</code>).
668         *
669         * @see #setTickLabelFont(Font)
670         */
671        public Font getTickLabelFont() {
672            return this.tickLabelFont;
673        }
674    
675        /**
676         * Sets the font for the tick labels and sends an {@link AxisChangeEvent}
677         * to all registered listeners.
678         *
679         * @param font  the font (<code>null</code> not allowed).
680         *
681         * @see #getTickLabelFont()
682         */
683        public void setTickLabelFont(Font font) {
684    
685            if (font == null) {
686                throw new IllegalArgumentException("Null 'font' argument.");
687            }
688    
689            if (!this.tickLabelFont.equals(font)) {
690                this.tickLabelFont = font;
691                fireChangeEvent();
692            }
693    
694        }
695    
696        /**
697         * Returns the color/shade used for the tick labels.
698         *
699         * @return The paint used for the tick labels.
700         *
701         * @see #setTickLabelPaint(Paint)
702         */
703        public Paint getTickLabelPaint() {
704            return this.tickLabelPaint;
705        }
706    
707        /**
708         * Sets the paint used to draw tick labels (if they are showing) and
709         * sends an {@link AxisChangeEvent} to all registered listeners.
710         *
711         * @param paint  the paint (<code>null</code> not permitted).
712         *
713         * @see #getTickLabelPaint()
714         */
715        public void setTickLabelPaint(Paint paint) {
716            if (paint == null) {
717                throw new IllegalArgumentException("Null 'paint' argument.");
718            }
719            this.tickLabelPaint = paint;
720            fireChangeEvent();
721        }
722    
723        /**
724         * Returns the insets for the tick labels.
725         *
726         * @return The insets (never <code>null</code>).
727         *
728         * @see #setTickLabelInsets(RectangleInsets)
729         */
730        public RectangleInsets getTickLabelInsets() {
731            return this.tickLabelInsets;
732        }
733    
734        /**
735         * Sets the insets for the tick labels and sends an {@link AxisChangeEvent}
736         * to all registered listeners.
737         *
738         * @param insets  the insets (<code>null</code> not permitted).
739         *
740         * @see #getTickLabelInsets()
741         */
742        public void setTickLabelInsets(RectangleInsets insets) {
743            if (insets == null) {
744                throw new IllegalArgumentException("Null 'insets' argument.");
745            }
746            if (!this.tickLabelInsets.equals(insets)) {
747                this.tickLabelInsets = insets;
748                fireChangeEvent();
749            }
750        }
751    
752        /**
753         * Returns the flag that indicates whether or not the tick marks are
754         * showing.
755         *
756         * @return The flag that indicates whether or not the tick marks are
757         *         showing.
758         *
759         * @see #setTickMarksVisible(boolean)
760         */
761        public boolean isTickMarksVisible() {
762            return this.tickMarksVisible;
763        }
764    
765        /**
766         * Sets the flag that indicates whether or not the tick marks are showing
767         * and sends an {@link AxisChangeEvent} to all registered listeners.
768         *
769         * @param flag  the flag.
770         *
771         * @see #isTickMarksVisible()
772         */
773        public void setTickMarksVisible(boolean flag) {
774            if (flag != this.tickMarksVisible) {
775                this.tickMarksVisible = flag;
776                fireChangeEvent();
777            }
778        }
779    
780        /**
781         * Returns the inside length of the tick marks.
782         *
783         * @return The length.
784         *
785         * @see #getTickMarkOutsideLength()
786         * @see #setTickMarkInsideLength(float)
787         */
788        public float getTickMarkInsideLength() {
789            return this.tickMarkInsideLength;
790        }
791    
792        /**
793         * Sets the inside length of the tick marks and sends
794         * an {@link AxisChangeEvent} to all registered listeners.
795         *
796         * @param length  the new length.
797         *
798         * @see #getTickMarkInsideLength()
799         */
800        public void setTickMarkInsideLength(float length) {
801            this.tickMarkInsideLength = length;
802            fireChangeEvent();
803        }
804    
805        /**
806         * Returns the outside length of the tick marks.
807         *
808         * @return The length.
809         *
810         * @see #getTickMarkInsideLength()
811         * @see #setTickMarkOutsideLength(float)
812         */
813        public float getTickMarkOutsideLength() {
814            return this.tickMarkOutsideLength;
815        }
816    
817        /**
818         * Sets the outside length of the tick marks and sends
819         * an {@link AxisChangeEvent} to all registered listeners.
820         *
821         * @param length  the new length.
822         *
823         * @see #getTickMarkInsideLength()
824         */
825        public void setTickMarkOutsideLength(float length) {
826            this.tickMarkOutsideLength = length;
827            fireChangeEvent();
828        }
829    
830        /**
831         * Returns the stroke used to draw tick marks.
832         *
833         * @return The stroke (never <code>null</code>).
834         *
835         * @see #setTickMarkStroke(Stroke)
836         */
837        public Stroke getTickMarkStroke() {
838            return this.tickMarkStroke;
839        }
840    
841        /**
842         * Sets the stroke used to draw tick marks and sends
843         * an {@link AxisChangeEvent} to all registered listeners.
844         *
845         * @param stroke  the stroke (<code>null</code> not permitted).
846         *
847         * @see #getTickMarkStroke()
848         */
849        public void setTickMarkStroke(Stroke stroke) {
850            if (stroke == null) {
851                throw new IllegalArgumentException("Null 'stroke' argument.");
852            }
853            if (!this.tickMarkStroke.equals(stroke)) {
854                this.tickMarkStroke = stroke;
855                fireChangeEvent();
856            }
857        }
858    
859        /**
860         * Returns the paint used to draw tick marks (if they are showing).
861         *
862         * @return The paint (never <code>null</code>).
863         *
864         * @see #setTickMarkPaint(Paint)
865         */
866        public Paint getTickMarkPaint() {
867            return this.tickMarkPaint;
868        }
869    
870        /**
871         * Sets the paint used to draw tick marks and sends an
872         * {@link AxisChangeEvent} to all registered listeners.
873         *
874         * @param paint  the paint (<code>null</code> not permitted).
875         *
876         * @see #getTickMarkPaint()
877         */
878        public void setTickMarkPaint(Paint paint) {
879            if (paint == null) {
880                throw new IllegalArgumentException("Null 'paint' argument.");
881            }
882            this.tickMarkPaint = paint;
883            fireChangeEvent();
884        }
885    
886        /**
887         * Returns the inside length of the minor tick marks.
888         *
889         * @return The length.
890         *
891         * @see #getMinorTickMarkOutsideLength()
892         * @see #setMinorTickMarkInsideLength(float)
893         *
894         * @since 1.0.12
895         */
896        public float getMinorTickMarkInsideLength() {
897            return this.minorTickMarkInsideLength;
898        }
899    
900        /**
901         * Sets the inside length of the minor tick marks and sends
902         * an {@link AxisChangeEvent} to all registered listeners.
903         *
904         * @param length  the new length.
905         *
906         * @see #getMinorTickMarkInsideLength()
907         *
908         * @since 1.0.12
909         */
910        public void setMinorTickMarkInsideLength(float length) {
911            this.minorTickMarkInsideLength = length;
912            fireChangeEvent();
913        }
914    
915        /**
916         * Returns the outside length of the minor tick marks.
917         *
918         * @return The length.
919         *
920         * @see #getMinorTickMarkInsideLength()
921         * @see #setMinorTickMarkOutsideLength(float)
922         *
923         * @since 1.0.12
924         */
925        public float getMinorTickMarkOutsideLength() {
926            return this.minorTickMarkOutsideLength;
927        }
928    
929        /**
930         * Sets the outside length of the minor tick marks and sends
931         * an {@link AxisChangeEvent} to all registered listeners.
932         *
933         * @param length  the new length.
934         *
935         * @see #getMinorTickMarkInsideLength()
936         *
937         * @since 1.0.12
938         */
939        public void setMinorTickMarkOutsideLength(float length) {
940            this.minorTickMarkOutsideLength = length;
941            fireChangeEvent();
942        }
943    
944        /**
945         * Returns the plot that the axis is assigned to.  This method will return
946         * <code>null</code> if the axis is not currently assigned to a plot.
947         *
948         * @return The plot that the axis is assigned to (possibly
949         *         <code>null</code>).
950         *
951         * @see #setPlot(Plot)
952         */
953        public Plot getPlot() {
954            return this.plot;
955        }
956    
957        /**
958         * Sets a reference to the plot that the axis is assigned to.
959         * <P>
960         * This method is used internally, you shouldn't need to call it yourself.
961         *
962         * @param plot  the plot.
963         *
964         * @see #getPlot()
965         */
966        public void setPlot(Plot plot) {
967            this.plot = plot;
968            configure();
969        }
970    
971        /**
972         * Returns the fixed dimension for the axis.
973         *
974         * @return The fixed dimension.
975         *
976         * @see #setFixedDimension(double)
977         */
978        public double getFixedDimension() {
979            return this.fixedDimension;
980        }
981    
982        /**
983         * Sets the fixed dimension for the axis.
984         * <P>
985         * This is used when combining more than one plot on a chart.  In this case,
986         * there may be several axes that need to have the same height or width so
987         * that they are aligned.  This method is used to fix a dimension for the
988         * axis (the context determines whether the dimension is horizontal or
989         * vertical).
990         *
991         * @param dimension  the fixed dimension.
992         *
993         * @see #getFixedDimension()
994         */
995        public void setFixedDimension(double dimension) {
996            this.fixedDimension = dimension;
997        }
998    
999        /**
1000         * Configures the axis to work with the current plot.  Override this method
1001         * to perform any special processing (such as auto-rescaling).
1002         */
1003        public abstract void configure();
1004    
1005        /**
1006         * Estimates the space (height or width) required to draw the axis.
1007         *
1008         * @param g2  the graphics device.
1009         * @param plot  the plot that the axis belongs to.
1010         * @param plotArea  the area within which the plot (including axes) should
1011         *                  be drawn.
1012         * @param edge  the axis location.
1013         * @param space  space already reserved.
1014         *
1015         * @return The space required to draw the axis (including pre-reserved
1016         *         space).
1017         */
1018        public abstract AxisSpace reserveSpace(Graphics2D g2, Plot plot,
1019                                               Rectangle2D plotArea,
1020                                               RectangleEdge edge,
1021                                               AxisSpace space);
1022    
1023        /**
1024         * Draws the axis on a Java 2D graphics device (such as the screen or a
1025         * printer).
1026         *
1027         * @param g2  the graphics device (<code>null</code> not permitted).
1028         * @param cursor  the cursor location (determines where to draw the axis).
1029         * @param plotArea  the area within which the axes and plot should be drawn.
1030         * @param dataArea  the area within which the data should be drawn.
1031         * @param edge  the axis location (<code>null</code> not permitted).
1032         * @param plotState  collects information about the plot
1033         *                   (<code>null</code> permitted).
1034         *
1035         * @return The axis state (never <code>null</code>).
1036         */
1037        public abstract AxisState draw(Graphics2D g2,
1038                                       double cursor,
1039                                       Rectangle2D plotArea,
1040                                       Rectangle2D dataArea,
1041                                       RectangleEdge edge,
1042                                       PlotRenderingInfo plotState);
1043    
1044        /**
1045         * Calculates the positions of the ticks for the axis, storing the results
1046         * in the tick list (ready for drawing).
1047         *
1048         * @param g2  the graphics device.
1049         * @param state  the axis state.
1050         * @param dataArea  the area inside the axes.
1051         * @param edge  the edge on which the axis is located.
1052         *
1053         * @return The list of ticks.
1054         */
1055        public abstract List refreshTicks(Graphics2D g2, AxisState state,
1056                Rectangle2D dataArea, RectangleEdge edge);
1057    
1058        /**
1059         * Created an entity for the axis.
1060         *
1061         * @param cursor  the initial cursor value.
1062         * @param state  the axis state after completion of the drawing with a
1063         *     possibly updated cursor position.
1064         * @param dataArea  the data area.
1065         * @param edge  the edge.
1066         * @param plotState  the PlotRenderingInfo from which a reference to the
1067         *     entity collection can be obtained.
1068         *
1069         * @since 1.0.13
1070         */
1071        protected void createAndAddEntity(double cursor, AxisState state,
1072                Rectangle2D dataArea, RectangleEdge edge,
1073                PlotRenderingInfo plotState) {
1074    
1075            if (plotState == null || plotState.getOwner() == null) {
1076                return;  // no need to create entity if we can“t save it anyways...
1077            }
1078            Rectangle2D hotspot = null;
1079            if (edge.equals(RectangleEdge.TOP)) {
1080                hotspot = new Rectangle2D.Double(dataArea.getX(),
1081                        state.getCursor(), dataArea.getWidth(),
1082                        cursor - state.getCursor());
1083            }
1084            else if (edge.equals(RectangleEdge.BOTTOM)) {
1085                hotspot = new Rectangle2D.Double(dataArea.getX(), cursor,
1086                        dataArea.getWidth(), state.getCursor() - cursor);
1087            }
1088            else if (edge.equals(RectangleEdge.LEFT)) {
1089                hotspot = new Rectangle2D.Double(state.getCursor(),
1090                        dataArea.getY(), cursor - state.getCursor(),
1091                        dataArea.getHeight());
1092            }
1093            else if (edge.equals(RectangleEdge.RIGHT)) {
1094                hotspot = new Rectangle2D.Double(cursor, dataArea.getY(),
1095                        state.getCursor() - cursor, dataArea.getHeight());
1096            }
1097            EntityCollection e = plotState.getOwner().getEntityCollection();
1098            if (e != null) {
1099                e.add(new AxisEntity(hotspot, this));
1100            }
1101        }
1102    
1103        /**
1104         * Registers an object for notification of changes to the axis.
1105         *
1106         * @param listener  the object that is being registered.
1107         *
1108         * @see #removeChangeListener(AxisChangeListener)
1109         */
1110        public void addChangeListener(AxisChangeListener listener) {
1111            this.listenerList.add(AxisChangeListener.class, listener);
1112        }
1113    
1114        /**
1115         * Deregisters an object for notification of changes to the axis.
1116         *
1117         * @param listener  the object to deregister.
1118         *
1119         * @see #addChangeListener(AxisChangeListener)
1120         */
1121        public void removeChangeListener(AxisChangeListener listener) {
1122            this.listenerList.remove(AxisChangeListener.class, listener);
1123        }
1124    
1125        /**
1126         * Returns <code>true</code> if the specified object is registered with
1127         * the dataset as a listener.  Most applications won't need to call this
1128         * method, it exists mainly for use by unit testing code.
1129         *
1130         * @param listener  the listener.
1131         *
1132         * @return A boolean.
1133         */
1134        public boolean hasListener(EventListener listener) {
1135            List list = Arrays.asList(this.listenerList.getListenerList());
1136            return list.contains(listener);
1137        }
1138    
1139        /**
1140         * Notifies all registered listeners that the axis has changed.
1141         * The AxisChangeEvent provides information about the change.
1142         *
1143         * @param event  information about the change to the axis.
1144         */
1145        protected void notifyListeners(AxisChangeEvent event) {
1146            Object[] listeners = this.listenerList.getListenerList();
1147            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1148                if (listeners[i] == AxisChangeListener.class) {
1149                    ((AxisChangeListener) listeners[i + 1]).axisChanged(event);
1150                }
1151            }
1152        }
1153    
1154        /**
1155         * Sends an {@link AxisChangeEvent} to all registered listeners.
1156         *
1157         * @since 1.0.12
1158         */
1159        protected void fireChangeEvent() {
1160            notifyListeners(new AxisChangeEvent(this));
1161        }
1162    
1163        /**
1164         * Returns a rectangle that encloses the axis label.  This is typically
1165         * used for layout purposes (it gives the maximum dimensions of the label).
1166         *
1167         * @param g2  the graphics device.
1168         * @param edge  the edge of the plot area along which the axis is measuring.
1169         *
1170         * @return The enclosing rectangle.
1171         */
1172        protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) {
1173    
1174            Rectangle2D result = new Rectangle2D.Double();
1175            String axisLabel = getLabel();
1176            if (axisLabel != null && !axisLabel.equals("")) {
1177                FontMetrics fm = g2.getFontMetrics(getLabelFont());
1178                Rectangle2D bounds = TextUtilities.getTextBounds(axisLabel, g2, fm);
1179                RectangleInsets insets = getLabelInsets();
1180                bounds = insets.createOutsetRectangle(bounds);
1181                double angle = getLabelAngle();
1182                if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) {
1183                    angle = angle - Math.PI / 2.0;
1184                }
1185                double x = bounds.getCenterX();
1186                double y = bounds.getCenterY();
1187                AffineTransform transformer
1188                    = AffineTransform.getRotateInstance(angle, x, y);
1189                Shape labelBounds = transformer.createTransformedShape(bounds);
1190                result = labelBounds.getBounds2D();
1191            }
1192    
1193            return result;
1194    
1195        }
1196    
1197        /**
1198         * Draws the axis label.
1199         *
1200         * @param label  the label text.
1201         * @param g2  the graphics device.
1202         * @param plotArea  the plot area.
1203         * @param dataArea  the area inside the axes.
1204         * @param edge  the location of the axis.
1205         * @param state  the axis state (<code>null</code> not permitted).
1206         *
1207         * @return Information about the axis.
1208         */
1209        protected AxisState drawLabel(String label, Graphics2D g2,
1210                Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge,
1211                AxisState state) {
1212    
1213            // it is unlikely that 'state' will be null, but check anyway...
1214            if (state == null) {
1215                throw new IllegalArgumentException("Null 'state' argument.");
1216            }
1217    
1218            if ((label == null) || (label.equals(""))) {
1219                return state;
1220            }
1221    
1222            Font font = getLabelFont();
1223            RectangleInsets insets = getLabelInsets();
1224            g2.setFont(font);
1225            g2.setPaint(getLabelPaint());
1226            FontMetrics fm = g2.getFontMetrics();
1227            Rectangle2D labelBounds = TextUtilities.getTextBounds(label, g2, fm);
1228    
1229            if (edge == RectangleEdge.TOP) {
1230                AffineTransform t = AffineTransform.getRotateInstance(
1231                        getLabelAngle(), labelBounds.getCenterX(),
1232                        labelBounds.getCenterY());
1233                Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
1234                labelBounds = rotatedLabelBounds.getBounds2D();
1235                double labelx = dataArea.getCenterX();
1236                double labely = state.getCursor() - insets.getBottom()
1237                                - labelBounds.getHeight() / 2.0;
1238                TextUtilities.drawRotatedString(label, g2, (float) labelx,
1239                        (float) labely, TextAnchor.CENTER, getLabelAngle(),
1240                        TextAnchor.CENTER);
1241                state.cursorUp(insets.getTop() + labelBounds.getHeight()
1242                        + insets.getBottom());
1243            }
1244            else if (edge == RectangleEdge.BOTTOM) {
1245                AffineTransform t = AffineTransform.getRotateInstance(
1246                        getLabelAngle(), labelBounds.getCenterX(),
1247                        labelBounds.getCenterY());
1248                Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
1249                labelBounds = rotatedLabelBounds.getBounds2D();
1250                double labelx = dataArea.getCenterX();
1251                double labely = state.getCursor()
1252                                + insets.getTop() + labelBounds.getHeight() / 2.0;
1253                TextUtilities.drawRotatedString(label, g2, (float) labelx,
1254                        (float) labely, TextAnchor.CENTER, getLabelAngle(),
1255                        TextAnchor.CENTER);
1256                state.cursorDown(insets.getTop() + labelBounds.getHeight()
1257                        + insets.getBottom());
1258            }
1259            else if (edge == RectangleEdge.LEFT) {
1260                AffineTransform t = AffineTransform.getRotateInstance(
1261                        getLabelAngle() - Math.PI / 2.0, labelBounds.getCenterX(),
1262                        labelBounds.getCenterY());
1263                Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
1264                labelBounds = rotatedLabelBounds.getBounds2D();
1265                double labelx = state.getCursor()
1266                                - insets.getRight() - labelBounds.getWidth() / 2.0;
1267                double labely = dataArea.getCenterY();
1268                TextUtilities.drawRotatedString(label, g2, (float) labelx,
1269                        (float) labely, TextAnchor.CENTER,
1270                        getLabelAngle() - Math.PI / 2.0, TextAnchor.CENTER);
1271                state.cursorLeft(insets.getLeft() + labelBounds.getWidth()
1272                        + insets.getRight());
1273            }
1274            else if (edge == RectangleEdge.RIGHT) {
1275    
1276                AffineTransform t = AffineTransform.getRotateInstance(
1277                        getLabelAngle() + Math.PI / 2.0,
1278                        labelBounds.getCenterX(), labelBounds.getCenterY());
1279                Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
1280                labelBounds = rotatedLabelBounds.getBounds2D();
1281                double labelx = state.getCursor()
1282                                + insets.getLeft() + labelBounds.getWidth() / 2.0;
1283                double labely = dataArea.getY() + dataArea.getHeight() / 2.0;
1284                TextUtilities.drawRotatedString(label, g2, (float) labelx,
1285                        (float) labely, TextAnchor.CENTER,
1286                        getLabelAngle() + Math.PI / 2.0, TextAnchor.CENTER);
1287                state.cursorRight(insets.getLeft() + labelBounds.getWidth()
1288                        + insets.getRight());
1289    
1290            }
1291    
1292            return state;
1293    
1294        }
1295    
1296        /**
1297         * Draws an axis line at the current cursor position and edge.
1298         *
1299         * @param g2  the graphics device.
1300         * @param cursor  the cursor position.
1301         * @param dataArea  the data area.
1302         * @param edge  the edge.
1303         */
1304        protected void drawAxisLine(Graphics2D g2, double cursor,
1305                Rectangle2D dataArea, RectangleEdge edge) {
1306    
1307            Line2D axisLine = null;
1308            if (edge == RectangleEdge.TOP) {
1309                axisLine = new Line2D.Double(dataArea.getX(), cursor,
1310                        dataArea.getMaxX(), cursor);
1311            }
1312            else if (edge == RectangleEdge.BOTTOM) {
1313                axisLine = new Line2D.Double(dataArea.getX(), cursor,
1314                        dataArea.getMaxX(), cursor);
1315            }
1316            else if (edge == RectangleEdge.LEFT) {
1317                axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
1318                        dataArea.getMaxY());
1319            }
1320            else if (edge == RectangleEdge.RIGHT) {
1321                axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
1322                        dataArea.getMaxY());
1323            }
1324            g2.setPaint(this.axisLinePaint);
1325            g2.setStroke(this.axisLineStroke);
1326            g2.draw(axisLine);
1327    
1328        }
1329    
1330        /**
1331         * Returns a clone of the axis.
1332         *
1333         * @return A clone.
1334         *
1335         * @throws CloneNotSupportedException if some component of the axis does
1336         *         not support cloning.
1337         */
1338        public Object clone() throws CloneNotSupportedException {
1339            Axis clone = (Axis) super.clone();
1340            // It's up to the plot which clones up to restore the correct references
1341            clone.plot = null;
1342            clone.listenerList = new EventListenerList();
1343            return clone;
1344        }
1345    
1346        /**
1347         * Tests this axis for equality with another object.
1348         *
1349         * @param obj  the object (<code>null</code> permitted).
1350         *
1351         * @return <code>true</code> or <code>false</code>.
1352         */
1353        public boolean equals(Object obj) {
1354            if (obj == this) {
1355                return true;
1356            }
1357            if (!(obj instanceof Axis)) {
1358                return false;
1359            }
1360            Axis that = (Axis) obj;
1361            if (this.visible != that.visible) {
1362                return false;
1363            }
1364            if (!ObjectUtilities.equal(this.label, that.label)) {
1365                return false;
1366            }
1367            if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
1368                return false;
1369            }
1370            if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) {
1371                return false;
1372            }
1373            if (!ObjectUtilities.equal(this.labelInsets, that.labelInsets)) {
1374                return false;
1375            }
1376            if (this.labelAngle != that.labelAngle) {
1377                return false;
1378            }
1379            if (this.axisLineVisible != that.axisLineVisible) {
1380                return false;
1381            }
1382            if (!ObjectUtilities.equal(this.axisLineStroke, that.axisLineStroke)) {
1383                return false;
1384            }
1385            if (!PaintUtilities.equal(this.axisLinePaint, that.axisLinePaint)) {
1386                return false;
1387            }
1388            if (this.tickLabelsVisible != that.tickLabelsVisible) {
1389                return false;
1390            }
1391            if (!ObjectUtilities.equal(this.tickLabelFont, that.tickLabelFont)) {
1392                return false;
1393            }
1394            if (!PaintUtilities.equal(this.tickLabelPaint, that.tickLabelPaint)) {
1395                return false;
1396            }
1397            if (!ObjectUtilities.equal(
1398                this.tickLabelInsets, that.tickLabelInsets
1399            )) {
1400                return false;
1401            }
1402            if (this.tickMarksVisible != that.tickMarksVisible) {
1403                return false;
1404            }
1405            if (this.tickMarkInsideLength != that.tickMarkInsideLength) {
1406                return false;
1407            }
1408            if (this.tickMarkOutsideLength != that.tickMarkOutsideLength) {
1409                return false;
1410            }
1411            if (!PaintUtilities.equal(this.tickMarkPaint, that.tickMarkPaint)) {
1412                return false;
1413            }
1414            if (!ObjectUtilities.equal(this.tickMarkStroke, that.tickMarkStroke)) {
1415                return false;
1416            }
1417            if (this.minorTickMarksVisible != that.minorTickMarksVisible) {
1418                return false;
1419            }
1420            if (this.minorTickMarkInsideLength != that.minorTickMarkInsideLength) {
1421                return false;
1422            }
1423            if (this.minorTickMarkOutsideLength
1424                    != that.minorTickMarkOutsideLength) {
1425                return false;
1426            }
1427            if (this.fixedDimension != that.fixedDimension) {
1428                return false;
1429            }
1430            return true;
1431        }
1432    
1433        /**
1434         * Provides serialization support.
1435         *
1436         * @param stream  the output stream.
1437         *
1438         * @throws IOException  if there is an I/O error.
1439         */
1440        private void writeObject(ObjectOutputStream stream) throws IOException {
1441            stream.defaultWriteObject();
1442            SerialUtilities.writePaint(this.labelPaint, stream);
1443            SerialUtilities.writePaint(this.tickLabelPaint, stream);
1444            SerialUtilities.writeStroke(this.axisLineStroke, stream);
1445            SerialUtilities.writePaint(this.axisLinePaint, stream);
1446            SerialUtilities.writeStroke(this.tickMarkStroke, stream);
1447            SerialUtilities.writePaint(this.tickMarkPaint, stream);
1448        }
1449    
1450        /**
1451         * Provides serialization support.
1452         *
1453         * @param stream  the input stream.
1454         *
1455         * @throws IOException  if there is an I/O error.
1456         * @throws ClassNotFoundException  if there is a classpath problem.
1457         */
1458        private void readObject(ObjectInputStream stream)
1459            throws IOException, ClassNotFoundException {
1460            stream.defaultReadObject();
1461            this.labelPaint = SerialUtilities.readPaint(stream);
1462            this.tickLabelPaint = SerialUtilities.readPaint(stream);
1463            this.axisLineStroke = SerialUtilities.readStroke(stream);
1464            this.axisLinePaint = SerialUtilities.readPaint(stream);
1465            this.tickMarkStroke = SerialUtilities.readStroke(stream);
1466            this.tickMarkPaint = SerialUtilities.readPaint(stream);
1467            this.listenerList = new EventListenerList();
1468        }
1469    
1470    }