KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > chart > axis > ValueAxis


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
6  *
7  * Project Info: http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this library; if not, write to the Free Software Foundation,
21  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22  *
23  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
24  * in the United States and other countries.]
25  *
26  * --------------
27  * ValueAxis.java
28  * --------------
29  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
30  *
31  * Original Author: David Gilbert (for Object Refinery Limited);
32  * Contributor(s): Jonathan Nash;
33  * Nicolas Brodu (for Astrium and EADS Corporate Research
34  * Center);
35  *
36  * $Id: ValueAxis.java,v 1.10 2005/05/19 13:58:11 mungady Exp $
37  *
38  * Changes (from 18-Sep-2001)
39  * --------------------------
40  * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
41  * 23-Nov-2001 : Overhauled standard tick unit code (DG);
42  * 04-Dec-2001 : Changed constructors to protected, and tidied up default
43  * values (DG);
44  * 12-Dec-2001 : Fixed vertical gridlines bug (DG);
45  * 16-Jan-2002 : Added an optional crosshair, based on the implementation by
46  * Jonathan Nash (DG);
47  * 23-Jan-2002 : Moved the minimum and maximum values to here from NumberAxis,
48  * and changed the type from Number to double (DG);
49  * 25-Feb-2002 : Added default value for autoRange. Changed autoAdjustRange
50  * from public to protected. Updated import statements (DG);
51  * 23-Apr-2002 : Added setRange() method (DG);
52  * 29-Apr-2002 : Added range adjustment methods (DG);
53  * 13-Jun-2002 : Modified setCrosshairValue() to notify listeners only when the
54  * crosshairs are visible, to avoid unnecessary repaints, as
55  * suggested by Kees Kuip (DG);
56  * 25-Jul-2002 : Moved lower and upper margin attributes from the NumberAxis
57  * class (DG);
58  * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
59  * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
60  * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG);
61  * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
62  * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
63  * 27-Nov-2002 : Moved the 'inverted' attributed from NumberAxis to
64  * ValueAxis (DG);
65  * 03-Jan-2003 : Small fix to ensure auto-range minimum is observed
66  * immediately (DG);
67  * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
68  * 20-Jan-2003 : Replaced monolithic constructor (DG);
69  * 26-Mar-2003 : Implemented Serializable (DG);
70  * 09-May-2003 : Added AxisLocation parameter to translation methods (DG);
71  * 13-Aug-2003 : Implemented Cloneable (DG);
72  * 01-Sep-2003 : Fixed bug 793167 (setMaximumAxisValue exception) (DG);
73  * 02-Sep-2003 : Fixed bug 795366 (zooming on inverted axes) (DG);
74  * 08-Sep-2003 : Completed Serialization support (NB);
75  * 08-Sep-2003 : Renamed get/setMinimumValue --> get/setLowerBound,
76  * and get/setMaximumValue --> get/setUpperBound (DG);
77  * 27-Oct-2003 : Changed DEFAULT_AUTO_RANGE_MINIMUM_SIZE value - see bug ID
78  * 829606 (DG);
79  * 07-Nov-2003 : Changes to tick mechanism (DG);
80  * 06-Jan-2004 : Moved axis line attributes to Axis class (DG);
81  * 21-Jan-2004 : Removed redundant axisLineVisible attribute. Renamed
82  * translateJava2DToValue --> java2DToValue, and
83  * translateValueToJava2D --> valueToJava2D (DG);
84  * 23-Jan-2004 : Fixed setAxisLinePaint() and setAxisLineStroke() which had no
85  * effect (andreas.gawecki@coremedia.com);
86  * 07-Apr-2004 : Changed text bounds calculation (DG);
87  * 26-Apr-2004 : Added getter/setter methods for arrow shapes (DG);
88  * 18-May-2004 : Added methods to set axis range *including* current
89  * margins (DG);
90  * 02-Jun-2004 : Fixed bug in setRangeWithMargins() method (DG);
91  * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
92  * --> TextUtilities (DG);
93  * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
94  * release (DG);
95  * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
96  *
97  */

98
99 package org.jfree.chart.axis;
100
101 import java.awt.Font JavaDoc;
102 import java.awt.FontMetrics JavaDoc;
103 import java.awt.Graphics2D JavaDoc;
104 import java.awt.Polygon JavaDoc;
105 import java.awt.Shape JavaDoc;
106 import java.awt.font.LineMetrics JavaDoc;
107 import java.awt.geom.AffineTransform JavaDoc;
108 import java.awt.geom.Line2D JavaDoc;
109 import java.awt.geom.Rectangle2D JavaDoc;
110 import java.io.IOException JavaDoc;
111 import java.io.ObjectInputStream JavaDoc;
112 import java.io.ObjectOutputStream JavaDoc;
113 import java.io.Serializable JavaDoc;
114 import java.util.Iterator JavaDoc;
115 import java.util.List JavaDoc;
116
117 import org.jfree.chart.event.AxisChangeEvent;
118 import org.jfree.chart.plot.Plot;
119 import org.jfree.data.Range;
120 import org.jfree.io.SerialUtilities;
121 import org.jfree.text.TextUtilities;
122 import org.jfree.ui.RectangleEdge;
123 import org.jfree.ui.RectangleInsets;
124 import org.jfree.util.ObjectUtilities;
125 import org.jfree.util.PublicCloneable;
126
127 /**
128  * The base class for axes that display value data, where values are measured
129  * using the <code>double</code> primitive. The two key subclasses are
130  * {@link DateAxis} and {@link NumberAxis}.
131  */

132 public abstract class ValueAxis extends Axis
133                                 implements Cloneable JavaDoc, PublicCloneable,
134                                            Serializable JavaDoc {
135
136     /** For serialization. */
137     private static final long serialVersionUID = 3698345477322391456L;
138     
139     /** The default axis range. */
140     public static final Range DEFAULT_RANGE = new Range(0.0, 1.0);
141
142     /** The default auto-range value. */
143     public static final boolean DEFAULT_AUTO_RANGE = true;
144
145     /** The default inverted flag setting. */
146     public static final boolean DEFAULT_INVERTED = false;
147
148     /** The default minimum auto range. */
149     public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001;
150
151     /** The default value for the lower margin (0.05 = 5%). */
152     public static final double DEFAULT_LOWER_MARGIN = 0.05;
153
154     /** The default value for the upper margin (0.05 = 5%). */
155     public static final double DEFAULT_UPPER_MARGIN = 0.05;
156
157     /** The default lower bound for the axis. */
158     public static final double DEFAULT_LOWER_BOUND = 0.0;
159
160     /** The default upper bound for the axis. */
161     public static final double DEFAULT_UPPER_BOUND = 1.0;
162
163     /** The default auto-tick-unit-selection value. */
164     public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true;
165
166     /** The maximum tick count. */
167     public static final int MAXIMUM_TICK_COUNT = 500;
168     
169     /**
170      * A flag that controls whether an arrow is drawn at the positive end of
171      * the axis line.
172      */

173     private boolean positiveArrowVisible;
174     
175     /**
176      * A flag that controls whether an arrow is drawn at the negative end of
177      * the axis line.
178      */

179     private boolean negativeArrowVisible;
180     
181     /** The shape used for an up arrow. */
182     private transient Shape JavaDoc upArrow;
183     
184     /** The shape used for a down arrow. */
185     private transient Shape JavaDoc downArrow;
186     
187     /** The shape used for a left arrow. */
188     private transient Shape JavaDoc leftArrow;
189     
190     /** The shape used for a right arrow. */
191     private transient Shape JavaDoc rightArrow;
192     
193     /** A flag that affects the orientation of the values on the axis. */
194     private boolean inverted;
195
196     /** The axis range. */
197     private Range range;
198
199     /**
200      * Flag that indicates whether the axis automatically scales to fit the
201      * chart data.
202      */

203     private boolean autoRange;
204
205     /** The minimum size for the 'auto' axis range (excluding margins). */
206     private double autoRangeMinimumSize;
207
208     /**
209      * The upper margin percentage. This indicates the amount by which the
210      * maximum axis value exceeds the maximum data value (as a percentage of
211      * the range on the axis) when the axis range is determined automatically.
212      */

213     private double upperMargin;
214
215     /**
216      * The lower margin. This is a percentage that indicates the amount by
217      * which the minimum axis value is "less than" the minimum data value when
218      * the axis range is determined automatically.
219      */

220     private double lowerMargin;
221
222     /**
223      * If this value is positive, the amount is subtracted from the maximum
224      * data value to determine the lower axis range. This can be used to
225      * provide a fixed "window" on dynamic data.
226      */

227     private double fixedAutoRange;
228
229     /**
230      * Flag that indicates whether or not the tick unit is selected
231      * automatically.
232      */

233     private boolean autoTickUnitSelection;
234
235     /** The standard tick units for the axis. */
236     private TickUnitSource standardTickUnits;
237
238     /** An index into an array of standard tick values. */
239     private int autoTickIndex;
240     
241     /** A flag indicating whether or not tick labels are rotated to vertical. */
242     private boolean verticalTickLabels;
243
244     /**
245      * Constructs a value axis.
246      *
247      * @param label the axis label.
248      * @param standardTickUnits the source for standard tick units
249      * (<code>null</code> permitted).
250      */

251     protected ValueAxis(String JavaDoc label, TickUnitSource standardTickUnits) {
252
253         super(label);
254
255         this.positiveArrowVisible = false;
256         this.negativeArrowVisible = false;
257
258         this.range = DEFAULT_RANGE;
259         this.autoRange = DEFAULT_AUTO_RANGE;
260
261         this.inverted = DEFAULT_INVERTED;
262         this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE;
263
264         this.lowerMargin = DEFAULT_LOWER_MARGIN;
265         this.upperMargin = DEFAULT_UPPER_MARGIN;
266
267         this.fixedAutoRange = 0.0;
268
269         this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION;
270         this.standardTickUnits = standardTickUnits;
271         
272         Polygon JavaDoc p1 = new Polygon JavaDoc();
273         p1.addPoint(0, 0);
274         p1.addPoint(-2, 2);
275         p1.addPoint(2, 2);
276         
277         this.upArrow = p1;
278
279         Polygon JavaDoc p2 = new Polygon JavaDoc();
280         p2.addPoint(0, 0);
281         p2.addPoint(-2, -2);
282         p2.addPoint(2, -2);
283
284         this.downArrow = p2;
285
286         Polygon JavaDoc p3 = new Polygon JavaDoc();
287         p3.addPoint(0, 0);
288         p3.addPoint(-2, -2);
289         p3.addPoint(-2, 2);
290         
291         this.rightArrow = p3;
292
293         Polygon JavaDoc p4 = new Polygon JavaDoc();
294         p4.addPoint(0, 0);
295         p4.addPoint(2, -2);
296         p4.addPoint(2, 2);
297
298         this.leftArrow = p4;
299         
300         this.verticalTickLabels = false;
301         
302     }
303
304     /**
305      * Returns <code>true</code> if the tick labels should be rotated (to
306      * vertical), and <code>false</code> otherwise.
307      *
308      * @return <code>true</code> or <code>false</code>.
309      */

310     public boolean isVerticalTickLabels() {
311         return this.verticalTickLabels;
312     }
313
314     /**
315      * Sets the flag that controls whether the tick labels are displayed
316      * vertically (that is, rotated 90 degrees from horizontal). If the flag
317      * is changed, an {@link AxisChangeEvent} is sent to all registered
318      * listeners.
319      *
320      * @param flag the flag.
321      */

322     public void setVerticalTickLabels(boolean flag) {
323         if (this.verticalTickLabels != flag) {
324             this.verticalTickLabels = flag;
325             notifyListeners(new AxisChangeEvent(this));
326         }
327     }
328
329     /**
330      * Returns a flag that controls whether or not the axis line has an arrow
331      * drawn that points in the positive direction for the axis.
332      *
333      * @return A boolean.
334      */

335     public boolean isPositiveArrowVisible() {
336         return this.positiveArrowVisible;
337     }
338     
339     /**
340      * Sets a flag that controls whether or not the axis lines has an arrow
341      * drawn that points in the positive direction for the axis, and sends an
342      * {@link AxisChangeEvent} to all registered listeners.
343      *
344      * @param visible the flag.
345      */

346     public void setPositiveArrowVisible(boolean visible) {
347         this.positiveArrowVisible = visible;
348         notifyListeners(new AxisChangeEvent(this));
349     }
350     
351     /**
352      * Returns a flag that controls whether or not the axis line has an arrow
353      * drawn that points in the negative direction for the axis.
354      *
355      * @return A boolean.
356      */

357     public boolean isNegativeArrowVisible() {
358         return this.negativeArrowVisible;
359     }
360     
361     /**
362      * Sets a flag that controls whether or not the axis lines has an arrow
363      * drawn that points in the negative direction for the axis, and sends an
364      * {@link AxisChangeEvent} to all registered listeners.
365      *
366      * @param visible the flag.
367      */

368     public void setNegativeArrowVisible(boolean visible) {
369         this.negativeArrowVisible = visible;
370         notifyListeners(new AxisChangeEvent(this));
371     }
372     
373     /**
374      * Returns a shape that can be displayed as an arrow pointing upwards at
375      * the end of an axis line.
376      *
377      * @return A shape (never <code>null</code>).
378      */

379     public Shape JavaDoc getUpArrow() {
380         return this.upArrow;
381     }
382     
383     /**
384      * Sets the shape that can be displayed as an arrow pointing upwards at
385      * the end of an axis line and sends an {@link AxisChangeEvent} to all
386      * registered listeners.
387      *
388      * @param arrow the arrow shape (<code>null</code> not permitted).
389      */

390     public void setUpArrow(Shape JavaDoc arrow) {
391         if (arrow == null) {
392             throw new IllegalArgumentException JavaDoc("Null 'arrow' argument.");
393         }
394         this.upArrow = arrow;
395         notifyListeners(new AxisChangeEvent(this));
396     }
397     
398     /**
399      * Returns a shape that can be displayed as an arrow pointing downwards at
400      * the end of an axis line.
401      *
402      * @return A shape (never <code>null</code>).
403      */

404     public Shape JavaDoc getDownArrow() {
405         return this.downArrow;
406     }
407     
408     /**
409      * Sets the shape that can be displayed as an arrow pointing downwards at
410      * the end of an axis line and sends an {@link AxisChangeEvent} to all
411      * registered listeners.
412      *
413      * @param arrow the arrow shape (<code>null</code> not permitted).
414      */

415     public void setDownArrow(Shape JavaDoc arrow) {
416         if (arrow == null) {
417             throw new IllegalArgumentException JavaDoc("Null 'arrow' argument.");
418         }
419         this.downArrow = arrow;
420         notifyListeners(new AxisChangeEvent(this));
421     }
422     
423     /**
424      * Returns a shape that can be displayed as an arrow pointing left at the
425      * end of an axis line.
426      *
427      * @return A shape (never <code>null</code>).
428      */

429     public Shape JavaDoc getLeftArrow() {
430         return this.leftArrow;
431     }
432     
433     /**
434      * Sets the shape that can be displayed as an arrow pointing left at the
435      * end of an axis line and sends an {@link AxisChangeEvent} to all
436      * registered listeners.
437      *
438      * @param arrow the arrow shape (<code>null</code> not permitted).
439      */

440     public void setLeftArrow(Shape JavaDoc arrow) {
441         if (arrow == null) {
442             throw new IllegalArgumentException JavaDoc("Null 'arrow' argument.");
443         }
444         this.leftArrow = arrow;
445         notifyListeners(new AxisChangeEvent(this));
446     }
447     
448     /**
449      * Returns a shape that can be displayed as an arrow pointing right at the
450      * end of an axis line.
451      *
452      * @return A shape (never <code>null</code>).
453      */

454     public Shape JavaDoc getRightArrow() {
455         return this.rightArrow;
456     }
457     
458     /**
459      * Sets the shape that can be displayed as an arrow pointing rightwards at
460      * the end of an axis line and sends an {@link AxisChangeEvent} to all
461      * registered listeners.
462      *
463      * @param arrow the arrow shape (<code>null</code> not permitted).
464      */

465     public void setRightArrow(Shape JavaDoc arrow) {
466         if (arrow == null) {
467             throw new IllegalArgumentException JavaDoc("Null 'arrow' argument.");
468         }
469         this.rightArrow = arrow;
470         notifyListeners(new AxisChangeEvent(this));
471     }
472     
473     /**
474      * Draws an axis line at the current cursor position and edge.
475      *
476      * @param g2 the graphics device.
477      * @param cursor the cursor position.
478      * @param dataArea the data area.
479      * @param edge the edge.
480      */

481     protected void drawAxisLine(Graphics2D JavaDoc g2, double cursor,
482                                 Rectangle2D JavaDoc dataArea, RectangleEdge edge) {
483         Line2D JavaDoc axisLine = null;
484         if (edge == RectangleEdge.TOP) {
485             axisLine = new Line2D.Double JavaDoc(
486                 dataArea.getX(), cursor, dataArea.getMaxX(), cursor
487             );
488         }
489         else if (edge == RectangleEdge.BOTTOM) {
490             axisLine = new Line2D.Double JavaDoc(
491                 dataArea.getX(), cursor, dataArea.getMaxX(), cursor
492             );
493         }
494         else if (edge == RectangleEdge.LEFT) {
495             axisLine = new Line2D.Double JavaDoc(
496                 cursor, dataArea.getY(), cursor, dataArea.getMaxY()
497             );
498         }
499         else if (edge == RectangleEdge.RIGHT) {
500             axisLine = new Line2D.Double JavaDoc(
501                 cursor, dataArea.getY(), cursor, dataArea.getMaxY()
502             );
503         }
504         g2.setPaint(getAxisLinePaint());
505         g2.setStroke(getAxisLineStroke());
506         g2.draw(axisLine);
507         
508         boolean drawUpOrRight = false;
509         boolean drawDownOrLeft = false;
510         if (this.positiveArrowVisible) {
511             if (this.inverted) {
512                 drawDownOrLeft = true;
513             }
514             else {
515                 drawUpOrRight = true;
516             }
517         }
518         if (this.negativeArrowVisible) {
519             if (this.inverted) {
520                 drawUpOrRight = true;
521             }
522             else {
523                 drawDownOrLeft = true;
524             }
525         }
526         if (drawUpOrRight) {
527             double x = 0.0;
528             double y = 0.0;
529             Shape JavaDoc arrow = null;
530             if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
531                 x = dataArea.getMaxX();
532                 y = cursor;
533                 arrow = this.rightArrow;
534             }
535             else if (edge == RectangleEdge.LEFT
536                     || edge == RectangleEdge.RIGHT) {
537                 x = cursor;
538                 y = dataArea.getMinY();
539                 arrow = this.upArrow;
540             }
541
542             // draw the arrow...
543
AffineTransform JavaDoc transformer = new AffineTransform JavaDoc();
544             transformer.setToTranslation(x, y);
545             Shape JavaDoc shape = transformer.createTransformedShape(arrow);
546             g2.fill(shape);
547             g2.draw(shape);
548         }
549         
550         if (drawDownOrLeft) {
551             double x = 0.0;
552             double y = 0.0;
553             Shape JavaDoc arrow = null;
554             if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
555                 x = dataArea.getMinX();
556                 y = cursor;
557                 arrow = this.leftArrow;
558             }
559             else if (edge == RectangleEdge.LEFT
560                     || edge == RectangleEdge.RIGHT) {
561                 x = cursor;
562                 y = dataArea.getMaxY();
563                 arrow = this.downArrow;
564             }
565
566             // draw the arrow...
567
AffineTransform JavaDoc transformer = new AffineTransform JavaDoc();
568             transformer.setToTranslation(x, y);
569             Shape JavaDoc shape = transformer.createTransformedShape(arrow);
570             g2.fill(shape);
571             g2.draw(shape);
572         }
573         
574     }
575     
576     /**
577      * Calculates the anchor point for a tick label.
578      *
579      * @param tick the tick.
580      * @param cursor the cursor.
581      * @param dataArea the data area.
582      * @param edge the edge on which the axis is drawn.
583      *
584      * @return The x and y coordinates of the anchor point.
585      */

586     protected float[] calculateAnchorPoint(ValueTick tick,
587                                            double cursor,
588                                            Rectangle2D JavaDoc dataArea,
589                                            RectangleEdge edge) {
590     
591         RectangleInsets insets = getTickLabelInsets();
592         float[] result = new float[2];
593         if (edge == RectangleEdge.TOP) {
594             result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
595             result[1] = (float) (cursor - insets.getBottom() - 2.0);
596         }
597         else if (edge == RectangleEdge.BOTTOM) {
598             result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
599             result[1] = (float) (cursor + insets.getTop() + 2.0);
600         }
601         else if (edge == RectangleEdge.LEFT) {
602             result[0] = (float) (cursor - insets.getLeft() - 2.0);
603             result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
604         }
605         else if (edge == RectangleEdge.RIGHT) {
606             result[0] = (float) (cursor + insets.getRight() + 2.0);
607             result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge);
608         }
609         return result;
610     }
611     
612     /**
613      * Draws the axis line, tick marks and tick mark labels.
614      *
615      * @param g2 the graphics device.
616      * @param cursor the cursor.
617      * @param plotArea the plot area.
618      * @param dataArea the data area.
619      * @param edge the edge that the axis is aligned with.
620      *
621      * @return The width or height used to draw the axis.
622      */

623     protected AxisState drawTickMarksAndLabels(Graphics2D JavaDoc g2,
624                                                double cursor,
625                                                Rectangle2D JavaDoc plotArea,
626                                                Rectangle2D JavaDoc dataArea,
627                                                RectangleEdge edge) {
628                                               
629         AxisState state = new AxisState(cursor);
630
631         if (isAxisLineVisible()) {
632             drawAxisLine(g2, cursor, dataArea, edge);
633         }
634
635         double ol = getTickMarkOutsideLength();
636         double il = getTickMarkInsideLength();
637
638         List JavaDoc ticks = refreshTicks(g2, state, dataArea, edge);
639         state.setTicks(ticks);
640         g2.setFont(getTickLabelFont());
641         Iterator JavaDoc iterator = ticks.iterator();
642         while (iterator.hasNext()) {
643             ValueTick tick = (ValueTick) iterator.next();
644             if (isTickLabelsVisible()) {
645                 g2.setPaint(getTickLabelPaint());
646                 float[] anchorPoint = calculateAnchorPoint(
647                     tick, cursor, dataArea, edge
648                 );
649                 TextUtilities.drawRotatedString(
650                     tick.getText(), g2,
651                     anchorPoint[0], anchorPoint[1],
652                     tick.getTextAnchor(),
653                     tick.getAngle(),
654                     tick.getRotationAnchor()
655                 );
656             }
657
658             if (isTickMarksVisible()) {
659                 float xx = (float) valueToJava2D(
660                     tick.getValue(), dataArea, edge
661                 );
662                 Line2D JavaDoc mark = null;
663                 g2.setStroke(getTickMarkStroke());
664                 g2.setPaint(getTickMarkPaint());
665                 if (edge == RectangleEdge.LEFT) {
666                     mark = new Line2D.Double JavaDoc(cursor - ol, xx, cursor + il, xx);
667                 }
668                 else if (edge == RectangleEdge.RIGHT) {
669                     mark = new Line2D.Double JavaDoc(cursor + ol, xx, cursor - il, xx);
670                 }
671                 else if (edge == RectangleEdge.TOP) {
672                     mark = new Line2D.Double JavaDoc(xx, cursor - ol, xx, cursor + il);
673                 }
674                 else if (edge == RectangleEdge.BOTTOM) {
675                     mark = new Line2D.Double JavaDoc(xx, cursor + ol, xx, cursor - il);
676                 }
677                 g2.draw(mark);
678             }
679         }
680         
681         // need to work out the space used by the tick labels...
682
// so we can update the cursor...
683
double used = 0.0;
684         if (isTickLabelsVisible()) {
685             if (edge == RectangleEdge.LEFT) {
686                 used += findMaximumTickLabelWidth(
687                     ticks, g2, plotArea, isVerticalTickLabels()
688                 );
689                 state.cursorLeft(used);
690             }
691             else if (edge == RectangleEdge.RIGHT) {
692                 used = findMaximumTickLabelWidth(
693                     ticks, g2, plotArea, isVerticalTickLabels()
694                 );
695                 state.cursorRight(used);
696             }
697             else if (edge == RectangleEdge.TOP) {
698                 used = findMaximumTickLabelHeight(
699                     ticks, g2, plotArea, isVerticalTickLabels()
700                 );
701                 state.cursorUp(used);
702             }
703             else if (edge == RectangleEdge.BOTTOM) {
704                 used = findMaximumTickLabelHeight(
705                     ticks, g2, plotArea, isVerticalTickLabels()
706                 );
707                 state.cursorDown(used);
708             }
709         }
710        
711         return state;
712     }
713     
714     /**
715      * Returns the space required to draw the axis.
716      *
717      * @param g2 the graphics device.
718      * @param plot the plot that the axis belongs to.
719      * @param plotArea the area within which the plot should be drawn.
720      * @param edge the axis location.
721      * @param space the space already reserved (for other axes).
722      *
723      * @return The space required to draw the axis (including pre-reserved
724      * space).
725      */

726     public AxisSpace reserveSpace(Graphics2D JavaDoc g2, Plot plot,
727                                   Rectangle2D JavaDoc plotArea,
728                                   RectangleEdge edge, AxisSpace space) {
729
730         // create a new space object if one wasn't supplied...
731
if (space == null) {
732             space = new AxisSpace();
733         }
734         
735         // if the axis is not visible, no additional space is required...
736
if (!isVisible()) {
737             return space;
738         }
739
740         // if the axis has a fixed dimension, return it...
741
double dimension = getFixedDimension();
742         if (dimension > 0.0) {
743             space.ensureAtLeast(dimension, edge);
744         }
745
746         // calculate the max size of the tick labels (if visible)...
747
double tickLabelHeight = 0.0;
748         double tickLabelWidth = 0.0;
749         if (isTickLabelsVisible()) {
750             g2.setFont(getTickLabelFont());
751             List JavaDoc ticks = refreshTicks(g2, new AxisState(), plotArea, edge);
752             if (RectangleEdge.isTopOrBottom(edge)) {
753                 tickLabelHeight = findMaximumTickLabelHeight(
754                     ticks, g2, plotArea, isVerticalTickLabels()
755                 );
756             }
757             else if (RectangleEdge.isLeftOrRight(edge)) {
758                 tickLabelWidth = findMaximumTickLabelWidth(
759                     ticks, g2, plotArea, isVerticalTickLabels()
760                 );
761             }
762         }
763
764         // get the axis label size and update the space object...
765
Rectangle2D JavaDoc labelEnclosure = getLabelEnclosure(g2, edge);
766         double labelHeight = 0.0;
767         double labelWidth = 0.0;
768         if (RectangleEdge.isTopOrBottom(edge)) {
769             labelHeight = labelEnclosure.getHeight();
770             space.add(labelHeight + tickLabelHeight, edge);
771         }
772         else if (RectangleEdge.isLeftOrRight(edge)) {
773             labelWidth = labelEnclosure.getWidth();
774             space.add(labelWidth + tickLabelWidth, edge);
775         }
776
777         return space;
778
779     }
780
781     /**
782      * A utility method for determining the height of the tallest tick label.
783      *
784      * @param ticks the ticks.
785      * @param g2 the graphics device.
786      * @param drawArea the area within which the plot and axes should be drawn.
787      * @param vertical a flag that indicates whether or not the tick labels
788      * are 'vertical'.
789      *
790      * @return The height of the tallest tick label.
791      */

792     protected double findMaximumTickLabelHeight(List JavaDoc ticks,
793                                                 Graphics2D JavaDoc g2,
794                                                 Rectangle2D JavaDoc drawArea,
795                                                 boolean vertical) {
796                                                     
797         RectangleInsets insets = getTickLabelInsets();
798         Font JavaDoc font = getTickLabelFont();
799         double maxHeight = 0.0;
800         if (vertical) {
801             FontMetrics JavaDoc fm = g2.getFontMetrics(font);
802             Iterator JavaDoc iterator = ticks.iterator();
803             while (iterator.hasNext()) {
804                 Tick tick = (Tick) iterator.next();
805                 Rectangle2D JavaDoc labelBounds = TextUtilities.getTextBounds(
806                     tick.getText(), g2, fm
807                 );
808                 if (labelBounds.getWidth() + insets.getTop()
809                         + insets.getBottom() > maxHeight) {
810                     maxHeight = labelBounds.getWidth()
811                                 + insets.getTop() + insets.getBottom();
812                 }
813             }
814         }
815         else {
816             LineMetrics JavaDoc metrics = font.getLineMetrics(
817                 "ABCxyz", g2.getFontRenderContext()
818             );
819             maxHeight = metrics.getHeight()
820                         + insets.getTop() + insets.getBottom();
821         }
822         return maxHeight;
823         
824     }
825
826     /**
827      * A utility method for determining the width of the widest tick label.
828      *
829      * @param ticks the ticks.
830      * @param g2 the graphics device.
831      * @param drawArea the area within which the plot and axes should be drawn.
832      * @param vertical a flag that indicates whether or not the tick labels
833      * are 'vertical'.
834      *
835      * @return The width of the tallest tick label.
836      */

837     protected double findMaximumTickLabelWidth(List JavaDoc ticks,
838