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