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 *
036 * Changes
037 * -------
038 * 21-Aug-2001 : Added standard header, fixed DOS encoding problem (DG);
039 * 18-Sep-2001 : Updated header (DG);
040 * 07-Nov-2001 : Allow null axis labels (DG);
041 * : Added default font values (DG);
042 * 13-Nov-2001 : Modified the setPlot() method to check compatibility between
043 * the axis and the plot (DG);
044 * 30-Nov-2001 : Changed default font from "Arial" --> "SansSerif" (DG);
045 * 06-Dec-2001 : Allow null in setPlot() method (BK);
046 * 06-Mar-2002 : Added AxisConstants interface (DG);
047 * 23-Apr-2002 : Added a visible property. Moved drawVerticalString to
048 * RefineryUtilities. Added fixedDimension property for use in
049 * combined plots (DG);
050 * 25-Jun-2002 : Removed unnecessary imports (DG);
051 * 05-Sep-2002 : Added attribute for tick mark paint (DG);
052 * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG);
053 * 07-Nov-2002 : Added attributes to control the inside and outside length of
054 * the tick marks (DG);
055 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
056 * 18-Nov-2002 : Added axis location to refreshTicks() parameters (DG);
057 * 15-Jan-2003 : Removed monolithic constructor (DG);
058 * 17-Jan-2003 : Moved plot classes to separate package (DG);
059 * 26-Mar-2003 : Implemented Serializable (DG);
060 * 03-Jul-2003 : Modified reserveSpace method (DG);
061 * 13-Aug-2003 : Implemented Cloneable (DG);
062 * 11-Sep-2003 : Took care of listeners while cloning (NB);
063 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
064 * 06-Nov-2003 : Modified refreshTicks() signature (DG);
065 * 06-Jan-2004 : Added axis line attributes (DG);
066 * 16-Mar-2004 : Added plot state to draw() method (DG);
067 * 07-Apr-2004 : Modified text bounds calculation (DG);
068 * 18-May-2004 : Eliminated AxisConstants.java (DG);
069 * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities -->
070 * TextUtilities (DG);
071 * 04-Oct-2004 : Modified getLabelEnclosure() method to treat an empty String
072 * the same way as a null string - see bug 1026521 (DG);
073 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
074 * 26-Apr-2005 : Removed LOGGER (DG);
075 * 01-Jun-2005 : Added hasListener() method for unit testing (DG);
076 * 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
077 * ------------- JFREECHART 1.0.x ---------------------------------------------
078 * 22-Aug-2006 : API doc updates (DG);
079 * 06-Jun-2008 : Added setTickLabelInsets(RectangleInsets, boolean) (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 = new Font(
133 "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 = new Font("SansSerif",
153 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 setLabelInsets(insets, true);
430 }
431
432 /**
433 * Sets the insets for the axis label, and sends an {@link AxisChangeEvent}
434 * to all registered listeners.
435 *
436 * @param insets the insets (<code>null</code> not permitted).
437 * @param notify notify listeners?
438 *
439 * @since 1.0.10
440 */
441 public void setLabelInsets(RectangleInsets insets, boolean notify) {
442 if (insets == null) {
443 throw new IllegalArgumentException("Null 'insets' argument.");
444 }
445 if (!insets.equals(this.labelInsets)) {
446 this.labelInsets = insets;
447 if (notify) {
448 notifyListeners(new AxisChangeEvent(this));
449 }
450 }
451 }
452
453 /**
454 * Returns the angle of the axis label.
455 *
456 * @return The angle (in radians).
457 *
458 * @see #setLabelAngle(double)
459 */
460 public double getLabelAngle() {
461 return this.labelAngle;
462 }
463
464 /**
465 * Sets the angle for the label and sends an {@link AxisChangeEvent} to all
466 * registered listeners.
467 *
468 * @param angle the angle (in radians).
469 *
470 * @see #getLabelAngle()
471 */
472 public void setLabelAngle(double angle) {
473 this.labelAngle = angle;
474 notifyListeners(new AxisChangeEvent(this));
475 }
476
477 /**
478 * A flag that controls whether or not the axis line is drawn.
479 *
480 * @return A boolean.
481 *
482 * @see #getAxisLinePaint()
483 * @see #getAxisLineStroke()
484 * @see #setAxisLineVisible(boolean)
485 */
486 public boolean isAxisLineVisible() {
487 return this.axisLineVisible;
488 }
489
490 /**
491 * Sets a flag that controls whether or not the axis line is visible and
492 * sends an {@link AxisChangeEvent} to all registered listeners.
493 *
494 * @param visible the flag.
495 *
496 * @see #isAxisLineVisible()
497 * @see #setAxisLinePaint(Paint)
498 * @see #setAxisLineStroke(Stroke)
499 */
500 public void setAxisLineVisible(boolean visible) {
501 this.axisLineVisible = visible;
502 notifyListeners(new AxisChangeEvent(this));
503 }
504
505 /**
506 * Returns the paint used to draw the axis line.
507 *
508 * @return The paint (never <code>null</code>).
509 *
510 * @see #setAxisLinePaint(Paint)
511 */
512 public Paint getAxisLinePaint() {
513 return this.axisLinePaint;
514 }
515
516 /**
517 * Sets the paint used to draw the axis line and sends an
518 * {@link AxisChangeEvent} to all registered listeners.
519 *
520 * @param paint the paint (<code>null</code> not permitted).
521 *
522 * @see #getAxisLinePaint()
523 */
524 public void setAxisLinePaint(Paint paint) {
525 if (paint == null) {
526 throw new IllegalArgumentException("Null 'paint' argument.");
527 }
528 this.axisLinePaint = paint;
529 notifyListeners(new AxisChangeEvent(this));
530 }
531
532 /**
533 * Returns the stroke used to draw the axis line.
534 *
535 * @return The stroke (never <code>null</code>).
536 *
537 * @see #setAxisLineStroke(Stroke)
538 */
539 public Stroke getAxisLineStroke() {
540 return this.axisLineStroke;
541 }
542
543 /**
544 * Sets the stroke used to draw the axis line and sends an
545 * {@link AxisChangeEvent} to all registered listeners.
546 *
547 * @param stroke the stroke (<code>null</code> not permitted).
548 *
549 * @see #getAxisLineStroke()
550 */
551 public void setAxisLineStroke(Stroke stroke) {
552 if (stroke == null) {
553 throw new IllegalArgumentException("Null 'stroke' argument.");
554 }
555 this.axisLineStroke = stroke;
556 notifyListeners(new AxisChangeEvent(this));
557 }
558
559 /**
560 * Returns a flag indicating whether or not the tick labels are visible.
561 *
562 * @return The flag.
563 *
564 * @see #getTickLabelFont()
565 * @see #getTickLabelPaint()
566 * @see #setTickLabelsVisible(boolean)
567 */
568 public boolean isTickLabelsVisible() {
569 return this.tickLabelsVisible;
570 }
571
572 /**
573 * Sets the flag that determines whether or not the tick labels are
574 * visible and sends an {@link AxisChangeEvent} to all registered
575 * listeners.
576 *
577 * @param flag the flag.
578 *
579 * @see #isTickLabelsVisible()
580 * @see #setTickLabelFont(Font)
581 * @see #setTickLabelPaint(Paint)
582 */
583 public void setTickLabelsVisible(boolean flag) {
584
585 if (flag != this.tickLabelsVisible) {
586 this.tickLabelsVisible = flag;
587 notifyListeners(new AxisChangeEvent(this));
588 }
589
590 }
591
592 /**
593 * Returns the font used for the tick labels (if showing).
594 *
595 * @return The font (never <code>null</code>).
596 *
597 * @see #setTickLabelFont(Font)
598 */
599 public Font getTickLabelFont() {
600 return this.tickLabelFont;
601 }
602
603 /**
604 * Sets the font for the tick labels and sends an {@link AxisChangeEvent}
605 * to all registered listeners.
606 *
607 * @param font the font (<code>null</code> not allowed).
608 *
609 * @see #getTickLabelFont()
610 */
611 public void setTickLabelFont(Font font) {
612
613 if (font == null) {
614 throw new IllegalArgumentException("Null 'font' argument.");
615 }
616
617 if (!this.tickLabelFont.equals(font)) {
618 this.tickLabelFont = font;
619 notifyListeners(new AxisChangeEvent(this));
620 }
621
622 }
623
624 /**
625 * Returns the color/shade used for the tick labels.
626 *
627 * @return The paint used for the tick labels.
628 *
629 * @see #setTickLabelPaint(Paint)
630 */
631 public Paint getTickLabelPaint() {
632 return this.tickLabelPaint;
633 }
634
635 /**
636 * Sets the paint used to draw tick labels (if they are showing) and
637 * sends an {@link AxisChangeEvent} to all registered listeners.
638 *
639 * @param paint the paint (<code>null</code> not permitted).
640 *
641 * @see #getTickLabelPaint()
642 */
643 public void setTickLabelPaint(Paint paint) {
644 if (paint == null) {
645 throw new IllegalArgumentException("Null 'paint' argument.");
646 }
647 this.tickLabelPaint = paint;
648 notifyListeners(new AxisChangeEvent(this));
649 }
650
651 /**
652 * Returns the insets for the tick labels.
653 *
654 * @return The insets (never <code>null</code>).
655 *
656 * @see #setTickLabelInsets(RectangleInsets)
657 */
658 public RectangleInsets getTickLabelInsets() {
659 return this.tickLabelInsets;
660 }
661
662 /**
663 * Sets the insets for the tick labels and sends an {@link AxisChangeEvent}
664 * to all registered listeners.
665 *
666 * @param insets the insets (<code>null</code> not permitted).
667 *
668 * @see #getTickLabelInsets()
669 */
670 public void setTickLabelInsets(RectangleInsets insets) {
671 if (insets == null) {
672 throw new IllegalArgumentException("Null 'insets' argument.");
673 }
674 if (!this.tickLabelInsets.equals(insets)) {
675 this.tickLabelInsets = insets;
676 notifyListeners(new AxisChangeEvent(this));
677 }
678 }
679
680 /**
681 * Returns the flag that indicates whether or not the tick marks are
682 * showing.
683 *
684 * @return The flag that indicates whether or not the tick marks are
685 * showing.
686 *
687 * @see #setTickMarksVisible(boolean)
688 */
689 public boolean isTickMarksVisible() {
690 return this.tickMarksVisible;
691 }
692
693 /**
694 * Sets the flag that indicates whether or not the tick marks are showing
695 * and sends an {@link AxisChangeEvent} to all registered listeners.
696 *
697 * @param flag the flag.
698 *
699 * @see #isTickMarksVisible()
700 */
701 public void setTickMarksVisible(boolean flag) {
702 if (flag != this.tickMarksVisible) {
703 this.tickMarksVisible = flag;
704 notifyListeners(new AxisChangeEvent(this));
705 }
706 }
707
708 /**
709 * Returns the inside length of the tick marks.
710 *
711 * @return The length.
712 *
713 * @see #getTickMarkOutsideLength()
714 * @see #setTickMarkInsideLength(float)
715 */
716 public float getTickMarkInsideLength() {
717 return this.tickMarkInsideLength;
718 }
719
720 /**
721 * Sets the inside length of the tick marks and sends
722 * an {@link AxisChangeEvent} to all registered listeners.
723 *
724 * @param length the new length.
725 *
726 * @see #getTickMarkInsideLength()
727 */
728 public void setTickMarkInsideLength(float length) {
729 this.tickMarkInsideLength = length;
730 notifyListeners(new AxisChangeEvent(this));
731 }
732
733 /**
734 * Returns the outside length of the tick marks.
735 *
736 * @return The length.
737 *
738 * @see #getTickMarkInsideLength()
739 * @see #setTickMarkOutsideLength(float)
740 */
741 public float getTickMarkOutsideLength() {
742 return this.tickMarkOutsideLength;
743 }
744
745 /**
746 * Sets the outside length of the tick marks and sends
747 * an {@link AxisChangeEvent} to all registered listeners.
748 *
749 * @param length the new length.
750 *
751 * @see #getTickMarkInsideLength()
752 */
753 public void setTickMarkOutsideLength(float length) {
754 this.tickMarkOutsideLength = length;
755 notifyListeners(new AxisChangeEvent(this));
756 }
757
758 /**
759 * Returns the stroke used to draw tick marks.
760 *
761 * @return The stroke (never <code>null</code>).
762 *
763 * @see #setTickMarkStroke(Stroke)
764 */
765 public Stroke getTickMarkStroke() {
766 return this.tickMarkStroke;
767 }
768
769 /**
770 * Sets the stroke used to draw tick marks and sends
771 * an {@link AxisChangeEvent} to all registered listeners.
772 *
773 * @param stroke the stroke (<code>null</code> not permitted).
774 *
775 * @see #getTickMarkStroke()
776 */
777 public void setTickMarkStroke(Stroke stroke) {
778 if (stroke == null) {
779 throw new IllegalArgumentException("Null 'stroke' argument.");
780 }
781 if (!this.tickMarkStroke.equals(stroke)) {
782 this.tickMarkStroke = stroke;
783 notifyListeners(new AxisChangeEvent(this));
784 }
785 }
786
787 /**
788 * Returns the paint used to draw tick marks (if they are showing).
789 *
790 * @return The paint (never <code>null</code>).
791 *
792 * @see #setTickMarkPaint(Paint)
793 */
794 public Paint getTickMarkPaint() {
795 return this.tickMarkPaint;
796 }
797
798 /**
799 * Sets the paint used to draw tick marks and sends an
800 * {@link AxisChangeEvent} to all registered listeners.
801 *
802 * @param paint the paint (<code>null</code> not permitted).
803 *
804 * @see #getTickMarkPaint()
805 */
806 public void setTickMarkPaint(Paint paint) {
807 if (paint == null) {
808 throw new IllegalArgumentException("Null 'paint' argument.");
809 }
810 this.tickMarkPaint = paint;
811 notifyListeners(new AxisChangeEvent(this));
812 }
813
814 /**
815 * Returns the plot that the axis is assigned to. This method will return
816 * <code>null</code> if the axis is not currently assigned to a plot.
817 *
818 * @return The plot that the axis is assigned to (possibly
819 * <code>null</code>).
820 *
821 * @see #setPlot(Plot)
822 */
823 public Plot getPlot() {
824 return this.plot;
825 }
826
827 /**
828 * Sets a reference to the plot that the axis is assigned to.
829 * <P>
830 * This method is used internally, you shouldn't need to call it yourself.
831 *
832 * @param plot the plot.
833 *
834 * @see #getPlot()
835 */
836 public void setPlot(Plot plot) {
837 this.plot = plot;
838 configure();
839 }
840
841 /**
842 * Returns the fixed dimension for the axis.
843 *
844 * @return The fixed dimension.
845 *
846 * @see #setFixedDimension(double)
847 */
848 public double getFixedDimension() {
849 return this.fixedDimension;
850 }
851
852 /**
853 * Sets the fixed dimension for the axis.
854 * <P>
855 * This is used when combining more than one plot on a chart. In this case,
856 * there may be several axes that need to have the same height or width so
857 * that they are aligned. This method is used to fix a dimension for the
858 * axis (the context determines whether the dimension is horizontal or
859 * vertical).
860 *
861 * @param dimension the fixed dimension.
862 *
863 * @see #getFixedDimension()
864 */
865 public void setFixedDimension(double dimension) {
866 this.fixedDimension = dimension;
867 }
868
869 /**
870 * Configures the axis to work with the current plot. Override this method
871 * to perform any special processing (such as auto-rescaling).
872 */
873 public abstract void configure();
874
875 /**
876 * Estimates the space (height or width) required to draw the axis.
877 *
878 * @param g2 the graphics device.
879 * @param plot the plot that the axis belongs to.
880 * @param plotArea the area within which the plot (including axes) should
881 * be drawn.
882 * @param edge the axis location.
883 * @param space space already reserved.
884 *
885 * @return The space required to draw the axis (including pre-reserved
886 * space).
887 */
888 public abstract AxisSpace reserveSpace(Graphics2D g2, Plot plot,
889 Rectangle2D plotArea,
890 RectangleEdge edge,
891 AxisSpace space);
892
893 /**
894 * Draws the axis on a Java 2D graphics device (such as the screen or a
895 * printer).
896 *
897 * @param g2 the graphics device (<code>null</code> not permitted).
898 * @param cursor the cursor location (determines where to draw the axis).
899 * @param plotArea the area within which the axes and plot should be drawn.
900 * @param dataArea the area within which the data should be drawn.
901 * @param edge the axis location (<code>null</code> not permitted).
902 * @param plotState collects information about the plot
903 * (<code>null</code> permitted).
904 *
905 * @return The axis state (never <code>null</code>).
906 */
907 public abstract AxisState draw(Graphics2D g2,
908 double cursor,
909 Rectangle2D plotArea,
910 Rectangle2D dataArea,
911 RectangleEdge edge,
912 PlotRenderingInfo plotState);
913
914 /**
915 * Calculates the positions of the ticks for the axis, storing the results
916 * in the tick list (ready for drawing).
917 *
918 * @param g2 the graphics device.
919 * @param state the axis state.
920 * @param dataArea the area inside the axes.
921 * @param edge the edge on which the axis is located.
922 *
923 * @return The list of ticks.
924 */
925 public abstract List refreshTicks(Graphics2D g2,
926 AxisState state,
927 Rectangle2D dataArea,
928 RectangleEdge edge);
929
930 /**
931 * Registers an object for notification of changes to the axis.
932 *
933 * @param listener the object that is being registered.
934 *
935 * @see #removeChangeListener(AxisChangeListener)
936 */
937 public void addChangeListener(AxisChangeListener listener) {
938 this.listenerList.add(AxisChangeListener.class, listener);
939 }
940
941 /**
942 * Deregisters an object for notification of changes to the axis.
943 *
944 * @param listener the object to deregister.
945 *
946 * @see #addChangeListener(AxisChangeListener)
947 */
948 public void removeChangeListener(AxisChangeListener listener) {
949 this.listenerList.remove(AxisChangeListener.class, listener);
950 }
951
952 /**
953 * Returns <code>true</code> if the specified object is registered with
954 * the dataset as a listener. Most applications won't need to call this
955 * method, it exists mainly for use by unit testing code.
956 *
957 * @param listener the listener.
958 *
959 * @return A boolean.
960 */
961 public boolean hasListener(EventListener listener) {
962 List list = Arrays.asList(this.listenerList.getListenerList());
963 return list.contains(listener);
964 }
965
966 /**
967 * Notifies all registered listeners that the axis has changed.
968 * The AxisChangeEvent provides information about the change.
969 *
970 * @param event information about the change to the axis.
971 */
972 protected void notifyListeners(AxisChangeEvent event) {
973
974 Object[] listeners = this.listenerList.getListenerList();
975 for (int i = listeners.length - 2; i >= 0; i -= 2) {
976 if (listeners[i] == AxisChangeListener.class) {
977 ((AxisChangeListener) listeners[i + 1]).axisChanged(event);
978 }
979 }
980
981 }
982
983 /**
984 * Returns a rectangle that encloses the axis label. This is typically
985 * used for layout purposes (it gives the maximum dimensions of the label).
986 *
987 * @param g2 the graphics device.
988 * @param edge the edge of the plot area along which the axis is measuring.
989 *
990 * @return The enclosing rectangle.
991 */
992 protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) {
993
994 Rectangle2D result = new Rectangle2D.Double();
995 String axisLabel = getLabel();
996 if (axisLabel != null && !axisLabel.equals("")) {
997 FontMetrics fm = g2.getFontMetrics(getLabelFont());
998 Rectangle2D bounds = TextUtilities.getTextBounds(axisLabel, g2, fm);
999 RectangleInsets insets = getLabelInsets();
1000 bounds = insets.createOutsetRectangle(bounds);
1001 double angle = getLabelAngle();
1002 if (edge == RectangleEdge.LEFT || edge == RectangleEdge.RIGHT) {
1003 angle = angle - Math.PI / 2.0;
1004 }
1005 double x = bounds.getCenterX();
1006 double y = bounds.getCenterY();
1007 AffineTransform transformer
1008 = AffineTransform.getRotateInstance(angle, x, y);
1009 Shape labelBounds = transformer.createTransformedShape(bounds);
1010 result = labelBounds.getBounds2D();
1011 }
1012
1013 return result;
1014
1015 }
1016
1017 /**
1018 * Draws the axis label.
1019 *
1020 * @param label the label text.
1021 * @param g2 the graphics device.
1022 * @param plotArea the plot area.
1023 * @param dataArea the area inside the axes.
1024 * @param edge the location of the axis.
1025 * @param state the axis state (<code>null</code> not permitted).
1026 *
1027 * @return Information about the axis.
1028 */
1029 protected AxisState drawLabel(String label, Graphics2D g2,
1030 Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge,
1031 AxisState state) {
1032
1033 // it is unlikely that 'state' will be null, but check anyway...
1034 if (state == null) {
1035 throw new IllegalArgumentException("Null 'state' argument.");
1036 }
1037
1038 if ((label == null) || (label.equals(""))) {
1039 return state;
1040 }
1041
1042 Font font = getLabelFont();
1043 RectangleInsets insets = getLabelInsets();
1044 g2.setFont(font);
1045 g2.setPaint(getLabelPaint());
1046 FontMetrics fm = g2.getFontMetrics();
1047 Rectangle2D labelBounds = TextUtilities.getTextBounds(label, g2, fm);
1048
1049 if (edge == RectangleEdge.TOP) {
1050
1051 AffineTransform t = AffineTransform.getRotateInstance(
1052 getLabelAngle(), labelBounds.getCenterX(),
1053 labelBounds.getCenterY());
1054 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
1055 labelBounds = rotatedLabelBounds.getBounds2D();
1056 double labelx = dataArea.getCenterX();
1057 double labely = state.getCursor() - insets.getBottom()
1058 - labelBounds.getHeight() / 2.0;
1059 TextUtilities.drawRotatedString(label, g2, (float) labelx,
1060 (float) labely, TextAnchor.CENTER, getLabelAngle(),
1061 TextAnchor.CENTER);
1062 state.cursorUp(insets.getTop() + labelBounds.getHeight()
1063 + insets.getBottom());
1064
1065 }
1066 else if (edge == RectangleEdge.BOTTOM) {
1067
1068 AffineTransform t = AffineTransform.getRotateInstance(
1069 getLabelAngle(), labelBounds.getCenterX(),
1070 labelBounds.getCenterY());
1071 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
1072 labelBounds = rotatedLabelBounds.getBounds2D();
1073 double labelx = dataArea.getCenterX();
1074 double labely = state.getCursor()
1075 + insets.getTop() + labelBounds.getHeight() / 2.0;
1076 TextUtilities.drawRotatedString(label, g2, (float) labelx,
1077 (float) labely, TextAnchor.CENTER, getLabelAngle(),
1078 TextAnchor.CENTER);
1079 state.cursorDown(insets.getTop() + labelBounds.getHeight()
1080 + insets.getBottom());
1081
1082 }
1083 else if (edge == RectangleEdge.LEFT) {
1084
1085 AffineTransform t = AffineTransform.getRotateInstance(
1086 getLabelAngle() - Math.PI / 2.0, labelBounds.getCenterX(),
1087 labelBounds.getCenterY());
1088 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
1089 labelBounds = rotatedLabelBounds.getBounds2D();
1090 double labelx = state.getCursor()
1091 - insets.getRight() - labelBounds.getWidth() / 2.0;
1092 double labely = dataArea.getCenterY();
1093 TextUtilities.drawRotatedString(label, g2, (float) labelx,
1094 (float) labely, TextAnchor.CENTER,
1095 getLabelAngle() - Math.PI / 2.0, TextAnchor.CENTER);
1096 state.cursorLeft(insets.getLeft() + labelBounds.getWidth()
1097 + insets.getRight());
1098 }
1099 else if (edge == RectangleEdge.RIGHT) {
1100
1101 AffineTransform t = AffineTransform.getRotateInstance(
1102 getLabelAngle() + Math.PI / 2.0,
1103 labelBounds.getCenterX(), labelBounds.getCenterY());
1104 Shape rotatedLabelBounds = t.createTransformedShape(labelBounds);
1105 labelBounds = rotatedLabelBounds.getBounds2D();
1106 double labelx = state.getCursor()
1107 + insets.getLeft() + labelBounds.getWidth() / 2.0;
1108 double labely = dataArea.getY() + dataArea.getHeight() / 2.0;
1109 TextUtilities.drawRotatedString(label, g2, (float) labelx,
1110 (float) labely, TextAnchor.CENTER,
1111 getLabelAngle() + Math.PI / 2.0, TextAnchor.CENTER);
1112 state.cursorRight(insets.getLeft() + labelBounds.getWidth()
1113 + insets.getRight());
1114
1115 }
1116
1117 return state;
1118
1119 }
1120
1121 /**
1122 * Draws an axis line at the current cursor position and edge.
1123 *
1124 * @param g2 the graphics device.
1125 * @param cursor the cursor position.
1126 * @param dataArea the data area.
1127 * @param edge the edge.
1128 */
1129 protected void drawAxisLine(Graphics2D g2, double cursor,
1130 Rectangle2D dataArea, RectangleEdge edge) {
1131
1132 Line2D axisLine = null;
1133 if (edge == RectangleEdge.TOP) {
1134 axisLine = new Line2D.Double(dataArea.getX(), cursor,
1135 dataArea.getMaxX(), cursor);
1136 }
1137 else if (edge == RectangleEdge.BOTTOM) {
1138 axisLine = new Line2D.Double(dataArea.getX(), cursor,
1139 dataArea.getMaxX(), cursor);
1140 }
1141 else if (edge == RectangleEdge.LEFT) {
1142 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
1143 dataArea.getMaxY());
1144 }
1145 else if (edge == RectangleEdge.RIGHT) {
1146 axisLine = new Line2D.Double(cursor, dataArea.getY(), cursor,
1147 dataArea.getMaxY());
1148 }
1149 g2.setPaint(this.axisLinePaint);
1150 g2.setStroke(this.axisLineStroke);
1151 g2.draw(axisLine);
1152
1153 }
1154
1155 /**
1156 * Returns a clone of the axis.
1157 *
1158 * @return A clone.
1159 *
1160 * @throws CloneNotSupportedException if some component of the axis does
1161 * not support cloning.
1162 */
1163 public Object clone() throws CloneNotSupportedException {
1164 Axis clone = (Axis) super.clone();
1165 // It's up to the plot which clones up to restore the correct references
1166 clone.plot = null;
1167 clone.listenerList = new EventListenerList();
1168 return clone;
1169 }
1170
1171 /**
1172 * Tests this axis for equality with another object.
1173 *
1174 * @param obj the object (<code>null</code> permitted).
1175 *
1176 * @return <code>true</code> or <code>false</code>.
1177 */
1178 public boolean equals(Object obj) {
1179 if (obj == this) {
1180 return true;
1181 }
1182 if (!(obj instanceof Axis)) {
1183 return false;
1184 }
1185 Axis that = (Axis) obj;
1186 if (this.visible != that.visible) {
1187 return false;
1188 }
1189 if (!ObjectUtilities.equal(this.label, that.label)) {
1190 return false;
1191 }
1192 if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
1193 return false;
1194 }
1195 if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) {
1196 return false;
1197 }
1198 if (!ObjectUtilities.equal(this.labelInsets, that.labelInsets)) {
1199 return false;
1200 }
1201 if (this.labelAngle != that.labelAngle) {
1202 return false;
1203 }
1204 if (this.axisLineVisible != that.axisLineVisible) {
1205 return false;
1206 }
1207 if (!ObjectUtilities.equal(this.axisLineStroke, that.axisLineStroke)) {
1208 return false;
1209 }
1210 if (!PaintUtilities.equal(this.axisLinePaint, that.axisLinePaint)) {
1211 return false;
1212 }
1213 if (this.tickLabelsVisible != that.tickLabelsVisible) {
1214 return false;
1215 }
1216 if (!ObjectUtilities.equal(this.tickLabelFont, that.tickLabelFont)) {
1217 return false;
1218 }
1219 if (!PaintUtilities.equal(this.tickLabelPaint, that.tickLabelPaint)) {
1220 return false;
1221 }
1222 if (!ObjectUtilities.equal(
1223 this.tickLabelInsets, that.tickLabelInsets
1224 )) {
1225 return false;
1226 }
1227 if (this.tickMarksVisible != that.tickMarksVisible) {
1228 return false;
1229 }
1230 if (this.tickMarkInsideLength != that.tickMarkInsideLength) {
1231 return false;
1232 }
1233 if (this.tickMarkOutsideLength != that.tickMarkOutsideLength) {
1234 return false;
1235 }
1236 if (!PaintUtilities.equal(this.tickMarkPaint, that.tickMarkPaint)) {
1237 return false;
1238 }
1239 if (!ObjectUtilities.equal(this.tickMarkStroke, that.tickMarkStroke)) {
1240 return false;
1241 }
1242 if (this.fixedDimension != that.fixedDimension) {
1243 return false;
1244 }
1245 return true;
1246 }
1247
1248 /**
1249 * Provides serialization support.
1250 *
1251 * @param stream the output stream.
1252 *
1253 * @throws IOException if there is an I/O error.
1254 */
1255 private void writeObject(ObjectOutputStream stream) throws IOException {
1256 stream.defaultWriteObject();
1257 SerialUtilities.writePaint(this.labelPaint, stream);
1258 SerialUtilities.writePaint(this.tickLabelPaint, stream);
1259 SerialUtilities.writeStroke(this.axisLineStroke, stream);
1260 SerialUtilities.writePaint(this.axisLinePaint, stream);
1261 SerialUtilities.writeStroke(this.tickMarkStroke, stream);
1262 SerialUtilities.writePaint(this.tickMarkPaint, stream);
1263 }
1264
1265 /**
1266 * Provides serialization support.
1267 *
1268 * @param stream the input stream.
1269 *
1270 * @throws IOException if there is an I/O error.
1271 * @throws ClassNotFoundException if there is a classpath problem.
1272 */
1273 private void readObject(ObjectInputStream stream)
1274 throws IOException, ClassNotFoundException {
1275 stream.defaultReadObject();
1276 this.labelPaint = SerialUtilities.readPaint(stream);
1277 this.tickLabelPaint = SerialUtilities.readPaint(stream);
1278 this.axisLineStroke = SerialUtilities.readStroke(stream);
1279 this.axisLinePaint = SerialUtilities.readPaint(stream);
1280 this.tickMarkStroke = SerialUtilities.readStroke(stream);
1281 this.tickMarkPaint = SerialUtilities.readPaint(stream);
1282 this.listenerList = new EventListenerList();
1283 }
1284
1285 }