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