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