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