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