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 }