KickJava   Java API By Example, From Geeks To Geeks.

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


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  * DateAxis.java
28  * -------------
29  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
30  *
31  * Original Author: David Gilbert;
32  * Contributor(s): Jonathan Nash;
33  * David Li;
34  * Michael Rauch;
35  * Bill Kelemen;
36  * Pawel Pabis;
37  *
38  * $Id: DateAxis.java,v 1.17 2005/05/19 13:58:11 mungady Exp $
39  *
40  * Changes (from 23-Jun-2001)
41  * --------------------------
42  * 23-Jun-2001 : Modified to work with null data source (DG);
43  * 18-Sep-2001 : Updated header (DG);
44  * 27-Nov-2001 : Changed constructors from public to protected, updated Javadoc
45  * comments (DG);
46  * 16-Jan-2002 : Added an optional crosshair, based on the implementation by
47  * Jonathan Nash (DG);
48  * 26-Feb-2002 : Updated import statements (DG);
49  * 22-Apr-2002 : Added a setRange() method (DG);
50  * 25-Jun-2002 : Removed redundant local variable (DG);
51  * 25-Jul-2002 : Changed order of parameters in ValueAxis constructor (DG);
52  * 21-Aug-2002 : The setTickUnit() method now turns off auto-tick unit
53  * selection (fix for bug id 528885) (DG);
54  * 05-Sep-2002 : Updated the constructors to reflect changes in the Axis
55  * class (DG);
56  * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG);
57  * 25-Sep-2002 : Added new setRange() methods, and deprecated
58  * setAxisRange() (DG);
59  * 04-Oct-2002 : Changed auto tick selection to parallel number axis
60  * classes (DG);
61  * 24-Oct-2002 : Added a date format override (DG);
62  * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
63  * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double, moved
64  * crosshair settings to the plot (DG);
65  * 15-Jan-2003 : Removed anchor date (DG);
66  * 20-Jan-2003 : Removed unnecessary constructors (DG);
67  * 26-Mar-2003 : Implemented Serializable (DG);
68  * 02-May-2003 : Added additional units to createStandardDateTickUnits()
69  * method, as suggested by mhilpert in bug report 723187 (DG);
70  * 13-May-2003 : Merged HorizontalDateAxis and VerticalDateAxis (DG);
71  * 24-May-2003 : Added support for underlying timeline for
72  * SegmentedTimeline (BK);
73  * 16-Jul-2003 : Applied patch from Pawel Pabis to fix overlapping dates (DG);
74  * 22-Jul-2003 : Applied patch from Pawel Pabis for monthly ticks (DG);
75  * 25-Jul-2003 : Fixed bug 777561 and 777586 (DG);
76  * 13-Aug-2003 : Implemented Cloneable and added equals() method (DG);
77  * 02-Sep-2003 : Fixes for bug report 790506 (DG);
78  * 04-Sep-2003 : Fixed tick label alignment when axis appears at the top (DG);
79  * 10-Sep-2003 : Fixes for segmented timeline (DG);
80  * 17-Sep-2003 : Fixed a layout bug when multiple domain axes are used (DG);
81  * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
82  * 07-Nov-2003 : Modified to use new tick classes (DG);
83  * 12-Nov-2003 : Modified tick labelling to use roll unit from DateTickUnit
84  * when a calculated tick value is hidden (which can occur in
85  * segmented date axes) (DG);
86  * 24-Nov-2003 : Fixed some problems with the auto tick unit selection, and
87  * fixed bug 846277 (labels missing for inverted axis) (DG);
88  * 30-Dec-2003 : Fixed bug in refreshTicksHorizontal() when start of time unit
89  * (ex. 1st of month) was hidden, causing infinite loop (BK);
90  * 13-Jan-2004 : Fixed bug in previousStandardDate() method (fix by Richard
91  * Wardle) (DG);
92  * 21-Jan-2004 : Renamed translateJava2DToValue --> java2DToValue, and
93  * translateValueToJava2D --> valueToJava2D (DG);
94  * 12-Mar-2004 : Fixed bug where date format override is ignored for vertical
95  * axis (DG);
96  * 16-Mar-2004 : Added plotState to draw() method (DG);
97  * 07-Apr-2004 : Changed string width calculation (DG);
98  * 21-Apr-2004 : Fixed bug in estimateMaximumTickLabelWidth() method (bug id
99  * 939148) (DG);
100  * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
101  * release (DG);
102  * 13-Jan-2005 : Fixed bug (see
103  * http://www.jfree.org/forum/viewtopic.php?t=11330) (DG);
104  * 21-Apr-2005 : Replaced Insets with RectangleInsets, removed redundant
105  * argument from selectAutoTickUnit() (DG);
106  *
107  */

108
109 package org.jfree.chart.axis;
110
111 import java.awt.Font JavaDoc;
112 import java.awt.FontMetrics JavaDoc;
113 import java.awt.Graphics2D JavaDoc;
114 import java.awt.font.FontRenderContext JavaDoc;
115 import java.awt.font.LineMetrics JavaDoc;
116 import java.awt.geom.Rectangle2D JavaDoc;
117 import java.io.Serializable JavaDoc;
118 import java.text.DateFormat JavaDoc;
119 import java.text.SimpleDateFormat JavaDoc;
120 import java.util.Calendar JavaDoc;
121 import java.util.Date JavaDoc;
122 import java.util.List JavaDoc;
123 import java.util.TimeZone JavaDoc;
124
125 import org.jfree.chart.event.AxisChangeEvent;
126 import org.jfree.chart.plot.Plot;
127 import org.jfree.chart.plot.PlotRenderingInfo;
128 import org.jfree.chart.plot.ValueAxisPlot;
129 import org.jfree.data.Range;
130 import org.jfree.data.time.DateRange;
131 import org.jfree.data.time.Month;
132 import org.jfree.data.time.RegularTimePeriod;
133 import org.jfree.data.time.Year;
134 import org.jfree.ui.RectangleEdge;
135 import org.jfree.ui.RectangleInsets;
136 import org.jfree.ui.TextAnchor;
137 import org.jfree.util.ObjectUtilities;
138
139 /**
140  * The base class for axes that display dates. You will find it easier to
141  * understand how this axis works if you bear in mind that it really
142  * displays/measures integer (or long) data, where the integers are
143  * milliseconds since midnight, 1-Jan-1970. When displaying tick labels, the
144  * millisecond values are converted back to dates using a
145  * <code>DateFormat</code> instance.
146  * <P>
147  * You can also create a {@link org.jfree.chart.axis.Timeline} and supply in
148  * the constructor to create an axis that only contains certain domain values.
149  * For example, this allows you to create a date axis that only contains
150  * working days.
151  */

152 public class DateAxis extends ValueAxis implements Cloneable JavaDoc, Serializable JavaDoc {
153
154     /** For serialization. */
155     private static final long serialVersionUID = -1013460999649007604L;
156     
157     /** The default axis range. */
158     public static final DateRange DEFAULT_DATE_RANGE = new DateRange();
159
160     /** The default minimum auto range size. */
161     public static final double
162         DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS = 2.0;
163
164     /** The default date tick unit. */
165     public static final DateTickUnit DEFAULT_DATE_TICK_UNIT
166         = new DateTickUnit(DateTickUnit.DAY, 1, new SimpleDateFormat JavaDoc());
167
168     /** The default anchor date. */
169     public static final Date JavaDoc DEFAULT_ANCHOR_DATE = new Date JavaDoc();
170
171     /** The current tick unit. */
172     private DateTickUnit tickUnit;
173
174     /** The override date format. */
175     private DateFormat JavaDoc dateFormatOverride;
176
177     /**
178      * Tick marks can be displayed at the start or the middle of the time
179      * period.
180      */

181     private DateTickMarkPosition tickMarkPosition = DateTickMarkPosition.START;
182
183     /**
184      * A timeline that includes all milliseconds (as defined by
185      * <code>java.util.Date</code>) in the real time line.
186      */

187     private static class DefaultTimeline implements Timeline, Serializable JavaDoc {
188
189         /**
190          * Converts a millisecond into a timeline value.
191          *
192          * @param millisecond the millisecond.
193          *
194          * @return The timeline value.
195          */

196         public long toTimelineValue(long millisecond) {
197             return millisecond;
198         }
199
200         /**
201          * Converts a date into a timeline value.
202          *
203          * @param date the domain value.
204          *
205          * @return The timeline value.
206          */

207         public long toTimelineValue(Date JavaDoc date) {
208             return date.getTime();
209         }
210
211         /**
212          * Converts a timeline value into a millisecond (as encoded by
213          * <code>java.util.Date</code>).
214          *
215          * @param value the value.
216          *
217          * @return The millisecond.
218          */

219         public long toMillisecond(long value) {
220             return value;
221         }
222
223         /**
224          * Returns <code>true</code> if the timeline includes the specified
225          * domain value.
226          *
227          * @param millisecond the millisecond.
228          *
229          * @return <code>true</code>.
230          */

231         public boolean containsDomainValue(long millisecond) {
232             return true;
233         }
234
235         /**
236          * Returns <code>true</code> if the timeline includes the specified
237          * domain value.
238          *
239          * @param date the date.
240          *
241          * @return <code>true</code>.
242          */

243         public boolean containsDomainValue(Date JavaDoc date) {
244             return true;
245         }
246
247         /**
248          * Returns <code>true</code> if the timeline includes the specified
249          * domain value range.
250          *
251          * @param from the start value.
252          * @param to the end value.
253          *
254          * @return <code>true</code>.
255          */

256         public boolean containsDomainRange(long from, long to) {
257             return true;
258         }
259
260         /**
261          * Returns <code>true</code> if the timeline includes the specified
262          * domain value range.
263          *
264          * @param from the start date.
265          * @param to the end date.
266          *
267          * @return <code>true</code>.
268          */

269         public boolean containsDomainRange(Date JavaDoc from, Date JavaDoc to) {
270             return true;
271         }
272
273         /**
274          * Tests an object for equality with this instance.
275          *
276          * @param object the object.
277          *
278          * @return A boolean.
279          */

280         public boolean equals(Object JavaDoc object) {
281
282             if (object == null) {
283                 return false;
284             }
285
286             if (object == this) {
287                 return true;
288             }
289
290             if (object instanceof DefaultTimeline) {
291                 return true;
292             }
293
294             return false;
295
296         }
297     }
298
299     /** A static default timeline shared by all standard DateAxis */
300     private static final Timeline DEFAULT_TIMELINE = new DefaultTimeline();
301
302     /** The time zone for the axis. */
303     private TimeZone JavaDoc timeZone;
304     
305     /** Our underlying timeline. */
306     private Timeline timeline;
307
308     /**
309      * Creates a date axis with no label.
310      */

311     public DateAxis() {
312         this(null);
313     }
314
315     /**
316      * Creates a date axis with the specified label.
317      *
318      * @param label the axis label (<code>null</code> permitted).
319      */

320     public DateAxis(String JavaDoc label) {
321         this(label, TimeZone.getDefault());
322     }
323
324     /**
325      * Creates a date axis. A timeline is specified for the axis. This allows
326      * special transformations to occur between a domain of values and the
327      * values included in the axis.
328      *
329      * @see org.jfree.chart.axis.SegmentedTimeline
330      *
331      * @param label the axis label (<code>null</code> permitted).
332      * @param zone the time zone.
333      */

334     public DateAxis(String JavaDoc label, TimeZone JavaDoc zone) {
335         super(label, DateAxis.createStandardDateTickUnits(zone));
336         setTickUnit(DateAxis.DEFAULT_DATE_TICK_UNIT, false, false);
337         setAutoRangeMinimumSize(
338             DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS
339         );
340         setRange(DEFAULT_DATE_RANGE, false, false);
341         this.dateFormatOverride = null;
342         this.timeZone = zone;
343         this.timeline = DEFAULT_TIMELINE;
344     }
345
346     /**
347      * Returns the underlying timeline used by this axis.
348      *
349      * @return The timeline.
350      */

351     public Timeline getTimeline() {
352         return this.timeline;
353     }
354
355     /**
356      * Sets the underlying timeline to use for this axis.
357      * <P>
358      * If the timeline is changed, an {@link AxisChangeEvent} is sent to all
359      * registered listeners.
360      *
361      * @param timeline the timeline.
362      */

363     public void setTimeline(Timeline timeline) {
364         if (this.timeline != timeline) {
365             this.timeline = timeline;
366             notifyListeners(new AxisChangeEvent(this));
367         }
368     }
369
370     /**
371      * Returns the tick unit for the axis.
372      *
373      * @return The tick unit (possibly <code>null</code>).
374      */

375     public DateTickUnit getTickUnit() {
376         return this.tickUnit;
377     }
378
379     /**
380      * Sets the tick unit for the axis. The auto-tick-unit-selection flag is
381      * set to <code>false</code>, and registered listeners are notified that
382      * the axis has been changed.
383      *
384      * @param unit the tick unit.
385      */

386     public void setTickUnit(DateTickUnit unit) {
387         setTickUnit(unit, true, true);
388     }
389
390     /**
391      * Sets the tick unit attribute without any other side effects.
392      *
393      * @param unit the new tick unit.
394      * @param notify notify registered listeners?
395      * @param turnOffAutoSelection turn off auto selection?
396      */

397     public void setTickUnit(DateTickUnit unit, boolean notify,
398                             boolean turnOffAutoSelection) {
399
400         this.tickUnit = unit;
401         if (turnOffAutoSelection) {
402             setAutoTickUnitSelection(false, false);
403         }
404         if (notify) {
405             notifyListeners(new AxisChangeEvent(this));
406         }
407
408     }
409
410     /**
411      * Returns the date format override. If this is non-null, then it will be
412      * used to format the dates on the axis.
413      *
414      * @return The formatter (possibly <code>null</code>).
415      */

416     public DateFormat JavaDoc getDateFormatOverride() {
417         return this.dateFormatOverride;
418     }
419
420     /**
421      * Sets the date format override. If this is non-null, then it will be
422      * used to format the dates on the axis.
423      *
424      * @param formatter the date formatter (<code>null</code> permitted).
425      */

426     public void setDateFormatOverride(DateFormat JavaDoc formatter) {
427         this.dateFormatOverride = formatter;
428         notifyListeners(new AxisChangeEvent(this));
429     }
430
431     /**
432      * Sets the upper and lower bounds for the axis and sends an
433      * {@link AxisChangeEvent} to all registered listeners. As a side-effect,
434      * the auto-range flag is set to false.
435      *
436      * @param range the new range (<code>null</code> not permitted).
437      */

438     public void setRange(Range range) {
439         setRange(range, true, true);
440     }
441
442     /**
443      * Sets the range for the axis, if requested, sends an
444      * {@link AxisChangeEvent} to all registered listeners. As a side-effect,
445      * the auto-range flag is set to <code>false</code> (optional).
446      *
447      * @param range the range (<code>null</code> not permitted).
448      * @param turnOffAutoRange a flag that controls whether or not the auto
449      * range is turned off.
450      * @param notify a flag that controls whether or not listeners are
451      * notified.
452      */

453     public void setRange(Range range, boolean turnOffAutoRange,
454                          boolean notify) {
455         if (range == null) {
456             throw new IllegalArgumentException JavaDoc("Null 'range' argument.");
457         }
458         // usually the range will be a DateRange, but if it isn't do a
459
// conversion...
460
if (!(range instanceof DateRange)) {
461             range = new DateRange(range);
462         }
463         super.setRange(range, turnOffAutoRange, notify);
464     }
465
466     /**
467      * Sets the axis range and sends an {@link AxisChangeEvent} to all
468      * registered listeners.
469      *
470      * @param lower the lower bound for the axis.
471      * @param upper the upper bound for the axis.
472      */

473     public void setRange(Date JavaDoc lower, Date JavaDoc upper) {
474         if (lower.getTime() >= upper.getTime()) {
475             throw new IllegalArgumentException JavaDoc("Requires 'lower' < 'upper'.");
476         }
477         setRange(new DateRange(lower, upper));
478     }
479
480     /**
481      * Sets the axis range and sends an {@link AxisChangeEvent} to all
482      * registered listeners.
483      *
484      * @param lower the lower bound for the axis.
485      * @param upper the upper bound for the axis.
486      */

487     public void setRange(double lower, double upper) {
488         if (lower >= upper) {
489             throw new IllegalArgumentException JavaDoc("Requires 'lower' < 'upper'.");
490         }
491         setRange(new DateRange(lower, upper));
492     }
493
494     /**
495      * Returns the earliest date visible on the axis.
496      *
497      * @return The date.
498      */

499     public Date JavaDoc getMinimumDate() {
500
501         Date JavaDoc result = null;
502
503         Range range = getRange();
504         if (range instanceof DateRange) {
505             DateRange r = (DateRange) range;
506             result = r.getLowerDate();
507         }
508         else {
509             result = new Date JavaDoc((long) range.getLowerBound());
510         }
511
512         return result;
513
514     }
515
516     /**
517      * Sets the minimum date visible on the axis and sends an
518      * {@link AxisChangeEvent} to all registered listeners.
519      *
520      * @param date the date (<code>null</code> not permitted).
521      */

522     public void setMinimumDate(Date JavaDoc date) {
523         setRange(new DateRange(date, getMaximumDate()), true, false);
524         notifyListeners(new AxisChangeEvent(this));
525     }
526
527     /**
528      * Returns the latest date visible on the axis.
529      *
530      * @return The date.
531      */

532     public Date JavaDoc getMaximumDate() {
533
534         Date JavaDoc result = null;
535         Range range = getRange();
536         if (range instanceof DateRange) {
537             DateRange r = (DateRange) range;
538             result = r.getUpperDate();
539         }
540         else {
541             result = new Date JavaDoc((long) range.getUpperBound());
542         }
543         return result;
544
545     }
546
547     /**
548      * Sets the maximum date visible on the axis. An {@link AxisChangeEvent}
549      * is sent to all registered listeners.
550      *
551      * @param maximumDate the date (<code>null</code> not permitted).
552      */

553     public void setMaximumDate(Date JavaDoc maximumDate) {
554         setRange(new DateRange(getMinimumDate(), maximumDate), true, false);
555         notifyListeners(new AxisChangeEvent(this));
556     }
557
558     /**
559      * Returns the tick mark position (start, middle or end of the time period).
560      *
561      * @return The position (never <code>null</code>).
562      */

563     public DateTickMarkPosition getTickMarkPosition() {
564         return this.tickMarkPosition;
565     }
566
567     /**
568      * Sets the tick mark position (start, middle or end of the time period)
569      * and sends an {@link AxisChangeEvent} to all registered listeners.
570      *
571      * @param position the position (<code>null</code> not permitted).
572      */

573     public void setTickMarkPosition(DateTickMarkPosition position) {
574         if (position == null) {
575             throw new IllegalArgumentException JavaDoc("Null 'position' argument.");
576         }
577         this.tickMarkPosition = position;
578         notifyListeners(new AxisChangeEvent(this));
579     }
580
581     /**
582      * Configures the axis to work with the specified plot. If the axis has
583      * auto-scaling, then sets the maximum and minimum values.
584      */

585     public void configure() {
586         if (isAutoRange()) {
587             autoAdjustRange();
588         }
589     }
590
591     /**
592      * Returns <code>true</code> if the axis hides this value, and
593      * <code>false</code> otherwise.
594      *
595      * @param millis the data value.
596      *
597      * @return A value.
598      */

599     public boolean isHiddenValue(long millis) {
600         return (!this.timeline.containsDomainValue(new Date JavaDoc(millis)));
601     }
602
603     /**
604      * Translates the data value to the display coordinates (Java 2D User Space)
605      * of the chart.
606      *
607      * @param value the date to be plotted.
608      * @param area the rectangle (in Java2D space) where the data is to be
609      * plotted.
610      * @param edge the axis location.
611      *
612      * @return The coordinate corresponding to the supplied data value.
613      */

614     public double valueToJava2D(double value, Rectangle2D JavaDoc area,
615                                 RectangleEdge edge) {
616         
617         value = this.timeline.toTimelineValue((long) value);
618
619         DateRange range = (DateRange) getRange();
620         double axisMin = this.timeline.toTimelineValue(range.getLowerDate());
621         double axisMax = this.timeline.toTimelineValue(range.getUpperDate());
622         double result = 0.0;
623         if (RectangleEdge.isTopOrBottom(edge)) {
624             double minX = area.getX();
625             double maxX = area.getMaxX();
626             if (isInverted()) {
627                 result = maxX + ((value - axisMin) / (axisMax - axisMin))
628                          * (minX - maxX);
629             }
630             else {
631                 result = minX + ((value - axisMin) / (axisMax - axisMin))
632                          * (maxX - minX);
633             }
634         }
635         else if (RectangleEdge.isLeftOrRight(edge)) {
636             double minY = area.getMinY();
637             double maxY = area.getMaxY();
638             if (isInverted()) {
639                 result = minY + (((value - axisMin) / (axisMax - axisMin))
640                          * (maxY - minY));
641             }
642             else {
643                 result = maxY - (((value - axisMin) / (axisMax - axisMin))
644                          * (maxY - minY));
645             }
646         }
647         return result;
648
649     }
650
651     /**
652      * Translates a date to Java2D coordinates, based on the range displayed by
653      * this axis for the specified data area.
654      *
655      * @param date the date.
656      * @param area the rectangle (in Java2D space) where the data is to be
657      * plotted.
658      * @param edge the axis location.
659      *
660      * @return The coordinate corresponding to the supplied date.
661      */

662     public double dateToJava2D(Date JavaDoc date, Rectangle2D JavaDoc area,
663                                RectangleEdge edge) {
664         double value = date.getTime();
665         return valueToJava2D(value, area, edge);
666     }
667
668     /**
669      * Translates a Java2D coordinate into the corresponding data value. To
670      * perform this translation, you need to know the area used for plotting
671      * data, and which edge the axis is located on.
672      *
673      * @param java2DValue the coordinate in Java2D space.
674      * @param area the rectangle (in Java2D space) where the data is to be
675      * plotted.
676      * @param edge the axis location.
677      *
678      * @return A data value.
679      */

680     public double java2DToValue(double java2DValue, Rectangle2D JavaDoc area,
681                                 RectangleEdge edge) {
682         
683         DateRange range = (DateRange) getRange();
684         double axisMin = this.timeline.toTimelineValue(range.getLowerDate());
685         double axisMax = this.timeline.toTimelineValue(range.getUpperDate());
686
687         double min = 0.0;
688         double max = 0.0;
689         if (RectangleEdge.isTopOrBottom(edge)) {
690             min = area.getX();
691             max = area.getMaxX();
692         }
693         else if (RectangleEdge.isLeftOrRight(edge)) {
694             min = area.getMaxY();
695             max = area.getY();
696         }
697
698         double result;
699         if (isInverted()) {
700              result = axisMax - ((java2DValue - min) / (max - min)
701                       * (axisMax - axisMin));
702         }
703         else {
704              result = axisMin + ((java2DValue - min) / (max - min)
705                       * (axisMax - axisMin));
706         }
707
708         return this.timeline.toMillisecond((long) result);
709     }
710
711     /**
712      * Calculates the value of the lowest visible tick on the axis.
713      *
714      * @param unit date unit to use.
715      *
716      * @return The value of the lowest visible tick on the axis.
717      */

718     public Date JavaDoc calculateLowestVisibleTickValue(DateTickUnit unit) {
719         return nextStandardDate(getMinimumDate(), unit);
720     }
721
722     /**
723      * Calculates the value of the highest visible tick on the axis.
724      *
725      * @param unit date unit to use.
726      *
727      * @return The value of the highest visible tick on the axis.
728      */

729     public Date JavaDoc calculateHighestVisibleTickValue(DateTickUnit unit) {
730         return previousStandardDate(getMaximumDate(), unit);
731     }
732
733     /**
734      * Returns the previous "standard" date, for a given date and tick unit.
735      *
736      * @param date the reference date.
737      * @param unit the tick unit.
738      *
739      * @return The previous "standard" date.
740      */

741     protected Date JavaDoc previousStandardDate(Date JavaDoc date, DateTickUnit unit) {
742
743         int milliseconds;
744         int seconds;
745         int minutes;
746         int hours;
747         int days;
748         int months;
749         int years;
750
751         Calendar JavaDoc calendar = Calendar.getInstance(this.timeZone);
752         calendar.setTime(date);
753         int count = unit.getCount();
754         int current = calendar.get(unit.getCalendarField());
755         int value = count * (current / count);
756
757         switch (unit.getUnit()) {
758
759             case (DateTickUnit.MILLISECOND) :
760                 years = calendar.get(Calendar.YEAR);
761                 months = calendar.get(Calendar.MONTH);
762                 days = calendar.get(Calendar.DATE);
763                 hours = calendar.get(Calendar.HOUR_OF_DAY);
764                 minutes = calendar.get(Calendar.MINUTE);
765                 seconds = calendar.get(Calendar.SECOND);
766                 calendar.set(years, months, days, hours, minutes, seconds);
767                 calendar.set(Calendar.MILLISECOND, value);
768                 return calendar.getTime();
769
770             case (DateTickUnit.SECOND) :
771                 years = calendar.get(Calendar.YEAR);
772                 months = calendar.get(Calendar.MONTH);
773                 days = calendar.get(Calendar.DATE);
774                 hours = calendar.get(Calendar.HOUR_OF_DAY);
775                 minutes = calendar.get(Calendar.MINUTE);
776                 if (this.tickMarkPosition == DateTickMarkPosition.START) {
777                     milliseconds = 0;
778                 }
779                 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
780                     milliseconds = 500;
781                 }
782                 else {
783                     milliseconds = 999;
784                 }
785                 calendar.set(Calendar.MILLISECOND, milliseconds);
786                 calendar.set(years, months, days, hours, minutes, value);
787                 return calendar.getTime();
788
789             case (DateTickUnit.MINUTE) :
790                 years = calendar.get(Calendar.YEAR);
791                 months = calendar.get(Calendar.MONTH);
792                 days = calendar.get(Calendar.DATE);
793                 hours = calendar.get(Calendar.HOUR_OF_DAY);
794                 if (this.tickMarkPosition == DateTickMarkPosition.START) {
795                     seconds = 0;
796                 }
797                 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
798                     seconds = 30;
799                 }
800                 else {
801                     seconds = 59;
802                 }
803                 calendar.clear(Calendar.MILLISECOND);
804                 calendar.set(years, months, days, hours, value, seconds);
805                 return calendar.getTime();
806
807             case (DateTickUnit.HOUR) :
808                 years = calendar.get(Calendar.YEAR);
809                 months = calendar.get(Calendar.MONTH);
810                 days = calendar.get(Calendar.DATE);
811                 if (this.tickMarkPosition == DateTickMarkPosition.START) {
812                     minutes = 0;
813                     seconds = 0;
814                 }
815                 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
816                     minutes = 30;
817                     seconds = 0;
818                 }
819                 else {
820                     minutes = 59;
821                     seconds = 59;
822                 }
823                 calendar.clear(Calendar.MILLISECOND);
824                 calendar.set(years, months, days, value, minutes, seconds);
825                 return calendar.getTime();
826
827             case (DateTickUnit.DAY) :
828                 years = calendar.get(Calendar.YEAR);
829                 months = calendar.get(Calendar.MONTH);
830                 if (this.tickMarkPosition == DateTickMarkPosition.START) {
831                     hours = 0;
832                     minutes = 0;
833                     seconds = 0;
834                 }
835                 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
836                     hours = 12;
837                     minutes = 0;
838                     seconds = 0;
839                 }
840                 else {
841                     hours = 23;
842                     minutes = 59;
843                     seconds = 59;
844                 }
845                 calendar.clear(Calendar.MILLISECOND);
846                 calendar.set(years, months, value, hours, 0, 0);
847                 // long result = calendar.getTimeInMillis();
848
// won't work with JDK 1.3
849
long result = calendar.getTime().getTime();
850                 if (result > date.getTime()) {
851                     calendar.set(years, months, value - 1, hours, 0, 0);
852                 }
853                 return calendar.getTime();
854
855             case (DateTickUnit.MONTH) :
856                 years = calendar.get(Calendar.YEAR);
857                 calendar.clear(Calendar.MILLISECOND);
858                 calendar.set(years, value, 1, 0, 0, 0);
859                 Month month = new Month(calendar.getTime());
860                 Date JavaDoc standardDate = calculateDateForPosition(
861                     month, this.tickMarkPosition
862                 );
863                 long millis = standardDate.getTime();
864                 if (millis > date.getTime()) {
865                     month = (Month) month.previous();
866                     standardDate = calculateDateForPosition(
867                         month, this.tickMarkPosition
868                     );
869                 }
870                 return standardDate;
871
872             case(DateTickUnit.YEAR) :
873                 if (this.tickMarkPosition == DateTickMarkPosition.START) {
874                     months = 0;
875                     days = 1;
876                 }
877                 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
878                     months = 6;
879                     days = 1;
880                 }
881                 else {
882                     months = 11;
883                     days = 31;
884                 }
885                 calendar.clear(Calendar.MILLISECOND);
886                 calendar.set(value, months, days, 0, 0, 0);
887                 return calendar.getTime();
888
889             default: return null;
890
891         }
892
893     }
894
895     /**
896      * Returns a {@link java.util.Date} corresponding to the specified position
897      * within a {@link RegularTimePeriod}.
898      *
899      * @param period the period.
900      * @param position the position (<code>null</code> not permitted).
901      *
902      * @return A date.
903      */

904     private Date JavaDoc calculateDateForPosition(RegularTimePeriod period,
905                                           DateTickMarkPosition position) {
906         
907         if (position == null) {
908             throw new IllegalArgumentException JavaDoc("Null 'position' argument.");
909         }
910         Date JavaDoc result = null;
911         if (position == DateTickMarkPosition.START) {
912             result = new Date JavaDoc(period.getFirstMillisecond());
913         }
914         else if (position == DateTickMarkPosition.MIDDLE) {
915             result = new Date JavaDoc(period.getMiddleMillisecond());
916         }
917         else if (position == DateTickMarkPosition.END) {
918             result = new Date JavaDoc(period.getLastMillisecond());
919         }
920         return result;
921
922     }
923
924     /**
925      * Returns the first "standard" date (based on the specified field and
926      * units).
927      *
928      * @param date the reference date.
929      * @param unit the date tick unit.
930      *
931      * @return The next "standard" date.
932      */

933     protected Date JavaDoc nextStandardDate(Date JavaDoc date, DateTickUnit unit) {
934
935         Date JavaDoc previous = previousStandardDate(date, unit);
936         Calendar JavaDoc calendar = Calendar.getInstance();
937         calendar.setTime(previous);
938         calendar.add(unit.getCalendarField(), unit.getCount());
939         return calendar.getTime();
940
941     }
942
943     /**
944      * Returns a collection of standard date tick units that uses the default
945      * time zone. This collection will be used by default, but you are free
946      * to create your own collection if you want to (see the
947      * {@link ValueAxis#setStandardTickUnits(TickUnitSource)} method inherited
948      * from the {@link ValueAxis} class).
949      *
950      * @return A collection of standard date tick units.
951      */

952     public static TickUnitSource createStandardDateTickUnits() {
953         return createStandardDateTickUnits(TimeZone.getDefault());
954     }
955
956     /**
957      * Returns a collection of standard date tick units. This collection will
958      * be used by default, but you are free to create your own collection if
959      * you want to (see the
960      * {@link ValueAxis#setStandardTickUnits(TickUnitSource)} method inherited
961      * from the {@link ValueAxis} class).
962      *
963      * @param zone the time zone (<code>null</code> not permitted).
964      *
965      * @return A collection of standard date tick units.
966      */

967     public static TickUnitSource createStandardDateTickUnits(TimeZone JavaDoc zone) {
968
969         if (zone == null) {
970             throw new IllegalArgumentException JavaDoc("Null 'zone' argument.");
971         }
972         TickUnits units = new TickUnits();
973
974         // date formatters
975
DateFormat JavaDoc f1 = new SimpleDateFormat JavaDoc("HH:mm:ss.SSS");
976         DateFormat JavaDoc f2 = new SimpleDateFormat JavaDoc("HH:mm:ss");
977         DateFormat JavaDoc f3 = new SimpleDateFormat JavaDoc("HH:mm");
978         DateFormat JavaDoc f4 = new SimpleDateFormat JavaDoc("d-MMM, HH:mm");
979         DateFormat JavaDoc f5 = new SimpleDateFormat JavaDoc("d-MMM");
980         DateFormat JavaDoc f6 = new SimpleDateFormat JavaDoc("MMM-yyyy");
981         DateFormat JavaDoc f7 = new SimpleDateFormat JavaDoc("yyyy");
982         
983         f1.setTimeZone(zone);
984         f2.setTimeZone(zone);
985         f3.setTimeZone(zone);
986         f4.setTimeZone(zone);
987         f5.setTimeZone(zone);
988         f6.setTimeZone(zone);
989         f7.setTimeZone(zone);
990         
991         // milliseconds
992
units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 1, f1));
993         units.add(
994             new DateTickUnit(
995                 DateTickUnit.MILLISECOND, 5, DateTickUnit.MILLISECOND, 1, f1
996             )
997         );
998         units.add(
999             new DateTickUnit(
1000                DateTickUnit.MILLISECOND, 10, DateTickUnit.MILLISECOND, 1, f1
1001            )
1002        );
1003        units.add(
1004            new DateTickUnit(
1005                DateTickUnit.MILLISECOND, 25, DateTickUnit.MILLISECOND, 5, f1
1006            )
1007        );
1008        units.add(
1009            new DateTickUnit(
1010                DateTickUnit.MILLISECOND, 50, DateTickUnit.MILLISECOND, 10, f1
1011            )
1012        );
1013        units.add(
1014            new DateTickUnit(
1015                DateTickUnit.MILLISECOND, 100, DateTickUnit.MILLISECOND, 10, f1
1016            )
1017        );
1018        units.add(
1019            new DateTickUnit(
1020                DateTickUnit.MILLISECOND, 250, DateTickUnit.MILLISECOND, 10, f1
1021            )
1022        );
1023        units.add(
1024            new DateTickUnit(
1025                DateTickUnit.MILLISECOND, 500, DateTickUnit.MILLISECOND, 50, f1
1026            )
1027        );
1028
1029        // seconds
1030
units.add(
1031            new DateTickUnit(
1032                DateTickUnit.SECOND, 1, DateTickUnit.MILLISECOND, 50, f2
1033            )
1034        );
1035        units.add(
1036            new DateTickUnit(
1037                DateTickUnit.SECOND, 5, DateTickUnit.SECOND, 1, f2
1038            )
1039        );
1040        units.add(
1041            new DateTickUnit(
1042                DateTickUnit.SECOND, 10, DateTickUnit.SECOND, 1, f2
1043            )
1044        );
1045        units.add(
1046            new DateTickUnit(
1047                DateTickUnit.SECOND, 30, DateTickUnit.SECOND, 5, f2
1048            )
1049        );
1050
1051        // minutes
1052
units.add(
1053            new DateTickUnit(DateTickUnit.MINUTE, 1, DateTickUnit.SECOND, 5, f3)
1054        );
1055        units.add(
1056            new DateTickUnit(
1057                DateTickUnit.MINUTE, 2, DateTickUnit.SECOND, 10, f3
1058            )
1059        );
1060        units.add(
1061            new DateTickUnit(DateTickUnit.MINUTE, 5, DateTickUnit.MINUTE, 1, f3)
1062        );
1063        units.add(
1064            new DateTickUnit(
1065                DateTickUnit.MINUTE, 10, DateTickUnit.MINUTE, 1, f3
1066            )
1067        );
1068        units.add(
1069            new DateTickUnit(
1070                DateTickUnit.MINUTE, 15, DateTickUnit.MINUTE, 5, f3
1071            )
1072        );
1073        units.add(
1074            new DateTickUnit(
1075                DateTickUnit.MINUTE, 20, DateTickUnit.MINUTE, 5, f3
1076            )
1077        );
1078        units.add(
1079            new DateTickUnit(
1080                DateTickUnit.MINUTE, 30, DateTickUnit.MINUTE, 5, f3
1081            )
1082        );
1083
1084        // hours
1085
units.add(
1086            new DateTickUnit(DateTickUnit.HOUR, 1, DateTickUnit.MINUTE, 5, f3)
1087        );
1088        units.add(
1089            new DateTickUnit(DateTickUnit.HOUR, 2, DateTickUnit.MINUTE, 10, f3)
1090        );
1091        units.add(
1092            new DateTickUnit(DateTickUnit.HOUR, 4, DateTickUnit.MINUTE, 30, f3)
1093        );
1094        units.add(
1095            new DateTickUnit(DateTickUnit.HOUR, 6, DateTickUnit.HOUR, 1, f3)
1096        );
1097        units.add(
1098            new DateTickUnit(DateTickUnit.HOUR, 12, DateTickUnit.HOUR, 1, f4)
1099        );
1100
1101        // days
1102
units.add(
1103            new DateTickUnit(DateTickUnit.DAY, 1, DateTickUnit.HOUR, 1, f5)
1104        );
1105        units.add(
1106            new DateTickUnit(DateTickUnit.DAY, 2, DateTickUnit.HOUR, 1, f5)
1107        );
1108        units.add(
1109            new DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.DAY, 1, f5)
1110        );
1111        units.add(
1112            new DateTickUnit(DateTickUnit.DAY, 15, DateTickUnit.DAY, 1, f5)
1113        );
1114
1115        // months
1116
units.add(
1117            new DateTickUnit(DateTickUnit.MONTH, 1, DateTickUnit.DAY, 1, f6)
1118        );
1119        units.add(
1120            new DateTickUnit(DateTickUnit.MONTH, 2, DateTickUnit.DAY, 1, f6)
1121        );
1122        units.add(
1123            new DateTickUnit(DateTickUnit.MONTH, 3, DateTickUnit.MONTH, 1, f6)
1124        );
1125        units.add(
1126            new DateTickUnit(DateTickUnit.MONTH, 4, DateTickUnit.MONTH, 1, f6)
1127        );
1128        units.add(
1129            new DateTickUnit(DateTickUnit.MONTH, 6, DateTickUnit.MONTH, 1, f6)
1130        );
1131
1132        // years
1133
units.add(
1134            new DateTickUnit(DateTickUnit.YEAR, 1, DateTickUnit.MONTH, 1, f7)
1135        );
1136        units.add(
1137            new DateTickUnit(DateTickUnit.YEAR, 2, DateTickUnit.MONTH, 3, f7)
1138        );
1139        units.add(
1140            new DateTickUnit(DateTickUnit.YEAR, 5, DateTickUnit.YEAR, 1, f7)
1141        );
1142        units.add(
1143            new DateTickUnit(DateTickUnit.YEAR, 10, DateTickUnit.YEAR, 1, f7)
1144        );
1145        units.add(
1146            new DateTickUnit(DateTickUnit.YEAR, 25, DateTickUnit.YEAR, 5, f7)
1147        );
1148        units.add(
1149            new DateTickUnit(DateTickUnit.YEAR, 50, DateTickUnit.YEAR, 10, f7)
1150        );
1151        units.add(
1152            new DateTickUnit(DateTickUnit.YEAR, 100, DateTickUnit.YEAR, 20, f7)
1153        );
1154
1155        return units;
1156
1157    }
1158
1159    /**
1160     * Rescales the axis to ensure that all data is visible.
1161     */

1162    protected void autoAdjustRange() {
1163
1164        Plot plot = getPlot();
1165
1166        if (plot == null) {
1167            return; // no plot, no data
1168
}
1169
1170        if (plot instanceof ValueAxisPlot) {
1171            ValueAxisPlot vap = (ValueAxisPlot) plot;
1172
1173            Range r = vap.getDataRange(this);
1174            if (r == null) {
1175                if (this.timeline instanceof SegmentedTimeline) {
1176                    //Timeline hasn't method getStartTime()
1177
r = new DateRange(
1178                        ((SegmentedTimeline) this.timeline).getStartTime(),
1179                        ((SegmentedTimeline) this.timeline).getStartTime() + 1
1180                    );
1181                }
1182                else {
1183                    r = new DateRange();
1184                }
1185            }
1186
1187            long upper = this.timeline.toTimelineValue(
1188                (long) r.getUpperBound()
1189            );
1190            long lower;
1191            long fixedAutoRange = (long) getFixedAutoRange();
1192            if (fixedAutoRange > 0.0) {
1193                lower = upper - fixedAutoRange;
1194            }
1195            else {
1196                lower = this.timeline.toTimelineValue((long) r.getLowerBound());
1197                double range = upper - lower;
1198                long minRange = (long) getAutoRangeMinimumSize();
1199                if (range < minRange) {
1200                    long expand = (long) (minRange - range) / 2;
1201                    upper = upper + expand;
1202                    lower = lower - expand;
1203                }
1204                upper = upper + (long) (range * getUpperMargin());
1205                lower = lower - (long) (range * getLowerMargin());
1206            }
1207
1208            upper = this.timeline.toMillisecond(upper);
1209            lower = this.timeline.toMillisecond(lower);
1210            DateRange dr = new DateRange(new Date JavaDoc(lower), new Date JavaDoc(upper));
1211            setRange(dr, false, false);
1212        }
1213
1214    }
1215
1216    /**
1217     * Selects an appropriate tick value for the axis. The strategy is to
1218     * display as many ticks as possible (selected from an array of 'standard'
1219     * tick units) without the labels overlapping.
1220     *
1221     * @param g2 the graphics device.
1222     * @param dataArea the area defined by the axes.
1223     * @param edge the axis location.
1224     */

1225    protected void selectAutoTickUnit(Graphics2D JavaDoc g2,
1226                                      Rectangle2D JavaDoc dataArea,
1227                                      RectangleEdge edge) {
1228
1229        if (RectangleEdge.isTopOrBottom(edge)) {
1230            selectHorizontalAutoTickUnit(g2, dataArea, edge);
1231        }
1232        else if (RectangleEdge.isLeftOrRight(edge)) {
1233            selectVerticalAutoTickUnit(g2, dataArea, edge);
1234        }
1235
1236    }
1237
1238    /**
1239     * Selects an appropriate tick size for the axis. The strategy is to
1240     * display as many ticks as possible (selected from a collection of
1241     * 'standard' tick units) without the labels overlapping.
1242     *
1243     * @param g2 the graphics device.
1244     * @param dataArea the area defined by the axes.
1245     * @param edge the axis location.
1246     */

1247    protected void selectHorizontalAutoTickUnit(Graphics2D JavaDoc g2,
1248                                                Rectangle2D JavaDoc dataArea,
1249                                                RectangleEdge edge) {
1250
1251        long shift = 0;
1252        if (this.timeline instanceof SegmentedTimeline) {
1253            shift = ((SegmentedTimeline) this.timeline).getStartTime();
1254        }
1255        double zero = valueToJava2D(shift + 0.0, dataArea, edge);
1256        double tickLabelWidth
1257            = estimateMaximumTickLabelWidth(g2, getTickUnit());
1258
1259        // start with the current tick unit...
1260
TickUnitSource tickUnits = getStandardTickUnits();
1261        TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
1262        double x1 = valueToJava2D(shift + unit1.getSize(), dataArea, edge);
1263        double unit1Width = Math.abs(x1 - zero);
1264
1265        // then extrapolate...
1266
double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
1267        DateTickUnit unit2 = (DateTickUnit) tickUnits.getCeilingTickUnit(guess);
1268        double x2 = valueToJava2D(shift + unit2.getSize(), dataArea, edge);
1269        double unit2Width = Math.abs(x2 - zero);
1270        tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
1271        if (tickLabelWidth > unit2Width) {
1272            unit2 = (DateTickUnit) tickUnits.getLargerTickUnit(unit2);
1273        }
1274        setTickUnit(unit2, false, false);
1275    }
1276    
1277    /**
1278     * Selects an appropriate tick size for the axis. The strategy is to
1279     * display as many ticks as possible (selected from a collection of
1280     * 'standard' tick units) without the labels overlapping.
1281     *
1282     * @param g2 the graphics device.
1283     * @param dataArea the area in which the plot should be drawn.
1284     * @param edge the axis location.
1285     */

1286    protected void selectVerticalAutoTickUnit(Graphics2D JavaDoc g2,
1287                                              Rectangle2D JavaDoc dataArea,
1288                                              RectangleEdge edge) {
1289
1290        // start with the current tick unit...
1291
TickUnitSource tickUnits = getStandardTickUnits();
1292        double zero = valueToJava2D(0.0, dataArea, edge);
1293
1294        // start with a unit that is at least 1/10th of the axis length
1295
double estimate1 = getRange().getLength() / 10.0;
1296        DateTickUnit candidate1
1297            = (DateTickUnit) tickUnits.getCeilingTickUnit(estimate1);
1298        double labelHeight1 = estimateMaximumTickLabelHeight(g2, candidate1);
1299        double y1 = valueToJava2D(candidate1.getSize(), dataArea, edge);
1300        double candidate1UnitHeight = Math.abs(y1 - zero);
1301
1302        // now extrapolate based on label height and unit height...
1303
double estimate2
1304            = (labelHeight1 / candidate1UnitHeight) * candidate1.getSize();
1305        DateTickUnit candidate2
1306            = (DateTickUnit) tickUnits.getCeilingTickUnit(estimate2);
1307        double labelHeight2 = estimateMaximumTickLabelHeight(g2, candidate2);
1308        double y2 = valueToJava2D(candidate2.getSize(), dataArea, edge);
1309        double unit2Height = Math.abs(y2 - zero);
1310
1311       // make final selection...
1312
DateTickUnit finalUnit;
1313       if (labelHeight2 < unit2Height) {
1314           finalUnit = candidate2;
1315       }
1316       else {
1317           finalUnit = (DateTickUnit) tickUnits.getLargerTickUnit(candidate2);
1318       }
1319       setTickUnit(finalUnit, false, false);
1320
1321    }
1322
1323    /**
1324     * Estimates the maximum width of the tick labels, assuming the specified
1325     * tick unit is used.
1326     * <P>
1327     * Rather than computing the string bounds of every tick on the axis, we
1328     * just look at two values: the lower bound and the upper bound for the
1329     * axis. These two values will usually be representative.
1330     *
1331     * @param g2 the graphics device.
1332     * @param unit the tick unit to use for calculation.
1333     *
1334     * @return The estimated maximum width of the tick labels.
1335     */

1336    private double estimateMaximumTickLabelWidth(Graphics2D JavaDoc g2,
1337                                                 DateTickUnit unit) {
1338
1339        RectangleInsets tickLabelInsets = getTickLabelInsets();
1340        double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();
1341
1342        Font JavaDoc tickLabelFont = getTickLabelFont();
1343        FontRenderContext JavaDoc frc = g2.getFontRenderContext();
1344        LineMetrics JavaDoc lm = tickLabelFont.getLineMetrics("ABCxyz", frc);
1345        if (isVerticalTickLabels()) {
1346            // all tick labels have the same width (equal to the height of
1347
// the font)...
1348
result += lm.getHeight();
1349        }
1350        else {
1351            // look at lower and upper bounds...
1352
DateRange range = (DateRange) getRange();
1353            Date JavaDoc lower = range.getLowerDate();
1354            Date JavaDoc upper = range.getUpperDate();
1355            String JavaDoc lowerStr = null;
1356            String JavaDoc upperStr = null;
1357            DateFormat JavaDoc formatter = getDateFormatOverride();
1358            if (formatter != null) {
1359                lowerStr = formatter.format(lower);
1360                upperStr = formatter.format(upper);
1361            }
1362            else {
1363                lowerStr = unit.dateToString(lower);
1364                upperStr = unit.dateToString(upper);
1365            }
1366            FontMetrics JavaDoc fm = g2.getFontMetrics(tickLabelFont);
1367            double w1 = fm.stringWidth(lowerStr);
1368            double w2 = fm.stringWidth(upperStr);
1369            result += Math.max(w1, w2);
1370        }
1371
1372        return result;
1373
1374    }
1375
1376    /**
1377     * Estimates the maximum width of the tick labels, assuming the specified
1378     * tick unit is used.
1379     * <P>
1380     * Rather than computing the string bounds of every tick on the axis, we
1381     * just look at two values: the lower bound and the upper bound for the
1382     * axis. These two values will usually be representative.
1383     *
1384     * @param g2 the graphics device.
1385     * @param unit the tick unit to use for calculation.
1386     *
1387     * @return The estimated maximum width of the tick labels.
1388     */

1389    private double estimateMaximumTickLabelHeight(Graphics2D JavaDoc g2,
1390                                                  DateTickUnit unit) {
1391
1392        RectangleInsets tickLabelInsets = getTickLabelInsets();
1393        double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();
1394
1395        Font JavaDoc tickLabelFont = getTickLabelFont();
1396        FontRenderContext JavaDoc frc = g2.getFontRenderContext();
1397        LineMetrics JavaDoc lm = tickLabelFont.getLineMetrics("ABCxyz", frc);
1398        if (!isVerticalTickLabels()) {
1399            // all tick labels have the same width (equal to the height of
1400
// the font)...
1401
result += lm.getHeight();
1402        }
1403        else {
1404            // look at lower and upper bounds...
1405
DateRange range = (DateRange) getRange();
1406            Date JavaDoc lower = range.getLowerDate();
1407            Date JavaDoc upper = range.getUpperDate();
1408            String JavaDoc lowerStr = null;
1409            String JavaDoc upperStr = null;
1410            DateFormat JavaDoc formatter = getDateFormatOverride();
1411            if (formatter != null) {
1412                lowerStr = formatter.format(lower);
1413                upperStr = formatter.format(upper);
1414            }
1415            else {
1416                lowerStr = unit.dateToString(lower);
1417                upperStr = unit.dateToString(upper);
1418            }
1419            FontMetrics JavaDoc fm = g2.getFontMetrics(tickLabelFont);
1420            double w1 = fm.stringWidth(lowerStr);
1421            double w2 = fm.stringWidth(upperStr);
1422            result += Math.max(w1, w2);
1423        }
1424
1425        return result;
1426
1427    }
1428
1429    /**
1430     * Calculates the positions of the tick labels for the axis, storing the
1431     * results in the tick label list (ready for drawing).
1432     *
1433     * @param g2 the graphics device.
1434     * @param state the axis state.
1435     * @param dataArea the area in which the plot should be drawn.
1436     * @param edge the location of the axis.
1437     *
1438     * @return A list of ticks.
1439     */

1440    public List JavaDoc refreshTicks(Graphics2D JavaDoc g2,
1441                             AxisState state,
1442                             Rectangle2D JavaDoc dataArea,
1443                             RectangleEdge edge) {
1444
1445        List JavaDoc result = null;
1446        if (RectangleEdge.isTopOrBottom(edge)) {
1447            result = refreshTicksHorizontal(g2, dataArea, edge);
1448        }
1449        else if (RectangleEdge.isLeftOrRight(edge)) {
1450            result = refreshTicksVertical(g2, dataArea, edge);
1451        }
1452        return result;
1453
1454    }
1455
1456    /**
1457     * Recalculates the ticks for the date axis.
1458     *
1459     * @param g2 the graphics device.
1460     * @param dataArea the area in which the data is to be drawn.
1461     * @param edge the location of the axis.
1462     *
1463     * @return A list of ticks.
1464     */

1465    protected List JavaDoc refreshTicksHorizontal(Graphics2D JavaDoc g2,
1466                                          Rectangle2D JavaDoc dataArea,
1467                                          RectangleEdge edge) {
1468
1469        List JavaDoc result = new java.util.ArrayList JavaDoc();
1470
1471        Font JavaDoc tickLabelFont = getTickLabelFont();
1472        g2.setFont(tickLabelFont);
1473
1474        if (isAutoTickUnitSelection()) {
1475            selectAutoTickUnit(g2, dataArea, edge);
1476        }
1477
1478        DateTickUnit unit = getTickUnit();
1479        Date JavaDoc tickDate = calculateLowestVisibleTickValue(unit);
1480        Date JavaDoc upperDate = getMaximumDate();
1481        // float lastX = Float.MIN_VALUE;
1482
while (tickDate.before(upperDate)) {
1483
1484            if (!isHiddenValue(tickDate.getTime())) {
1485                // work out the value, label and position
1486
String JavaDoc tickLabel;
1487                DateFormat JavaDoc formatter = getDateFormatOverride();
1488                if (formatter != null) {
1489                    tickLabel = formatter.format(tickDate);
1490                }
1491                else {
1492                    tickLabel = this.tickUnit.dateToString(tickDate);
1493                }
1494                TextAnchor anchor = null;
1495                TextAnchor rotationAnchor = null;
1496                double angle = 0.0;
1497                if (isVerticalTickLabels()) {
1498                    anchor = TextAnchor.CENTER_RIGHT;
1499                    rotationAnchor = TextAnchor.CENTER_RIGHT;
1500                    if (edge == RectangleEdge.TOP) {
1501                        angle = Math.PI / 2.0;
1502                    }
1503                    else {
1504                        angle = -Math.PI / 2.0;
1505                    }
1506                }
1507                else {
1508                    if (edge == RectangleEdge.TOP) {
1509                        anchor = TextAnchor.BOTTOM_CENTER;
1510                        rotationAnchor = TextAnchor.BOTTOM_CENTER;
1511                    }
1512                    else {
1513                        anchor = TextAnchor.TOP_CENTER;
1514                        rotationAnchor = TextAnchor.TOP_CENTER;
1515                    }
1516                }
1517
1518                Tick tick = new DateTick(
1519                    tickDate, tickLabel, anchor, rotationAnchor, angle
1520                );
1521                result.add(tick);
1522                tickDate = unit.addToDate(tickDate);
1523            }
1524            else {
1525                tickDate = unit.rollDate(tickDate);
1526                continue;
1527            }
1528
1529            // could add a flag to make the following correction optional...
1530
switch (unit.getUnit()) {
1531
1532                case (DateTickUnit.MILLISECOND) :
1533                case (DateTickUnit.SECOND) :
1534                case (DateTickUnit.MINUTE) :
1535                case (DateTickUnit.HOUR) :
1536                case (DateTickUnit.DAY) :
1537                    break;
1538                case (DateTickUnit.MONTH) :
1539                    tickDate = calculateDateForPosition(
1540                        new Month(tickDate), this.tickMarkPosition
1541                    );
1542                    break;
1543                case(DateTickUnit.YEAR) :
1544                    tickDate = calculateDateForPosition(
1545                        new Year(tickDate), this.tickMarkPosition
1546                    );
1547                    break;
1548
1549                default: break;
1550
1551            }
1552
1553        }
1554        return result;
1555
1556    }
1557
1558    /**
1559     * Recalculates the ticks for the date axis.
1560     *
1561     * @param g2 the graphics device.
1562     * @param dataArea the area in which the plot should be drawn.
1563     * @param edge the location of the axis.
1564     *
1565     * @return A list of ticks.
1566     */

1567    protected List JavaDoc refreshTicksVertical(Graphics2D JavaDoc g2,
1568                                        Rectangle2D JavaDoc dataArea,
1569                                        RectangleEdge edge) {
1570
1571        List JavaDoc result = new java.util.ArrayList JavaDoc();
1572
1573        Font JavaDoc tickLabelFont = getTickLabelFont();
1574        g2.setFont(tickLabelFont);
1575
1576        if (isAutoTickUnitSelection()) {
1577            selectAutoTickUnit(g2, dataArea, edge);
1578        }
1579        DateTickUnit unit = getTickUnit();
1580        Date JavaDoc tickDate = calculateLowestVisibleTickValue(unit);
1581        //Date upperDate = calculateHighestVisibleTickValue(unit);
1582
Date JavaDoc upperDate = getMaximumDate();
1583        while (tickDate.before(upperDate)) {
1584
1585            if (!isHiddenValue(tickDate.getTime())) {
1586                // work out the value, label and position
1587
String JavaDoc tickLabel;
1588                DateFormat JavaDoc formatter = getDateFormatOverride();
1589                if (formatter != null) {
1590                    tickLabel = formatter.format(tickDate);
1591                }
1592                else {
1593                    tickLabel = this.tickUnit.dateToString(tickDate);
1594                }
1595                TextAnchor anchor = null;
1596                TextAnchor rotationAnchor = null;
1597                double angle = 0.0;
1598                if (isVerticalTickLabels()) {
1599                    anchor = TextAnchor.BOTTOM_CENTER;
1600                    rotationAnchor = TextAnchor.BOTTOM_CENTER;
1601                    if (edge == RectangleEdge.LEFT) {
1602                        angle = -Math.PI / 2.0;
1603                    }
1604                    else {
1605                        angle = Math.PI / 2.0;
1606                    }
1607                }
1608                else {
1609                    if (edge == RectangleEdge.LEFT) {
1610                        anchor = TextAnchor.CENTER_RIGHT;
1611                        rotationAnchor = TextAnchor.CENTER_RIGHT;
1612                    }
1613                    else {
1614                        anchor = TextAnchor.CENTER_LEFT;
1615                        rotationAnchor = TextAnchor.CENTER_LEFT;
1616                    }
1617                }
1618
1619                Tick tick = new DateTick(
1620                    tickDate, tickLabel, anchor, rotationAnchor, angle
1621                );
1622                result.add(tick);
1623                tickDate = unit.addToDate(tickDate);
1624            }
1625            else {
1626                tickDate = unit.rollDate(tickDate);
1627            }
1628        }
1629        return result;
1630    }
1631
1632    /**
1633     * Draws the axis on a Java 2D graphics device (such as the screen or a
1634     * printer).
1635     *
1636     * @param g2 the graphics device (<code>null</code> not permitted).
1637     * @param cursor the cursor location.
1638     * @param plotArea the area within which the axes and data should be
1639     * drawn (<code>null</code> not permitted).
1640     * @param dataArea the area within which the data should be drawn
1641     * (<code>null</code> not permitted).
1642     * @param edge the location of the axis (<code>null</code> not permitted).
1643     * @param plotState collects information about the plot
1644     * (<code>null</code> permitted).
1645     *
1646     * @return The axis state (never <code>null</code>).
1647     */

1648    public AxisState draw(Graphics2D JavaDoc g2,
1649                          double cursor,
1650                          Rectangle2D JavaDoc plotArea,
1651                          Rectangle2D JavaDoc dataArea,
1652                          RectangleEdge edge,
1653                          PlotRenderingInfo plotState) {
1654
1655        // if the axis is not visible, don't draw it...
1656
if (!isVisible()) {
1657            AxisState state = new AxisState(cursor);
1658            // even though the axis is not visible, we need to refresh ticks in
1659
// case the grid is being drawn...
1660
List JavaDoc ticks = refreshTicks(g2, state, dataArea, edge);
1661            state.setTicks(ticks);
1662            return state;
1663        }
1664
1665        // draw the tick marks and labels...
1666
AxisState state = drawTickMarksAndLabels(
1667            g2, cursor, plotArea, dataArea, edge
1668        );
1669
1670        // draw the axis label (note that 'state' is passed in *and*
1671
// returned)...
1672
state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
1673
1674        return state;
1675
1676    }
1677
1678    /**
1679     * Zooms in on the current range.
1680     *
1681     * @param lowerPercent the new lower bound.
1682     * @param upperPercent the new upper bound.
1683     */

1684    public void zoomRange(double lowerPercent, double upperPercent) {
1685        double start = this.timeline.toTimelineValue(
1686            (long) getRange().getLowerBound()
1687        );
1688        double length = (this.timeline.toTimelineValue(
1689                (long) getRange().getUpperBound())
1690                - this.timeline.toTimelineValue(
1691                    (long) getRange().getLowerBound()
1692        ));
1693        Range adjusted = null;
1694        if (isInverted()) {
1695            adjusted = new DateRange(
1696                this.timeline.toMillisecond(
1697                    (long) (start + (length * (1 - upperPercent)))
1698                ),
1699                this.timeline.toMillisecond(
1700                    (long) (start + (length * (1 - lowerPercent)))
1701                )
1702            );
1703        }
1704        else {
1705            adjusted = new DateRange(this.timeline.toMillisecond(
1706                (long) (start + length * lowerPercent)),
1707                this.timeline.toMillisecond(
1708                    (long) (start + length * upperPercent)
1709                )
1710            );
1711        }
1712        setRange(adjusted);
1713    }
1714    
1715    /**
1716     * Tests an object for equality with this instance.
1717     *
1718     * @param obj the object to test.
1719     *
1720     * @return A boolean.
1721     */

1722    public boolean equals(Object JavaDoc obj) {
1723        if (obj == this) {
1724            return true;
1725        }
1726        if (!(obj instanceof DateAxis)) {
1727            return false;
1728        }
1729        DateAxis that = (DateAxis) obj;
1730        if (!ObjectUtilities.equal(this.tickUnit, that.tickUnit)) {
1731            return false;
1732        }
1733        if (!ObjectUtilities.equal(
1734            this.dateFormatOverride, that.dateFormatOverride)
1735        ) {
1736            return false;
1737        }
1738        if (!ObjectUtilities.equal(
1739            this.tickMarkPosition, that.tickMarkPosition
1740        )) {
1741            return false;
1742        }
1743        if (!ObjectUtilities.equal(this.timeline, that.timeline)) {
1744            return false;
1745        }
1746        return true;
1747    }
1748
1749    /**
1750     * Returns a hash code for this object.
1751     *
1752     * @return A hash code.
1753     */

1754    public int hashCode() {
1755        if (getLabel() != null) {
1756            return getLabel().hashCode();
1757        }
1758        else {
1759            return 0;
1760        }
1761    }
1762
1763    /**
1764     * Returns a clone of the object.
1765     *
1766     * @return A clone.
1767     *
1768     * @throws CloneNotSupportedException if some component of the axis does
1769     * not support cloning.
1770     */

1771    public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
1772
1773        DateAxis clone = (DateAxis) super.clone();
1774
1775        // 'dateTickUnit' is immutable : no need to clone
1776
if (this.dateFormatOverride != null) {
1777            clone.dateFormatOverride
1778                = (DateFormat JavaDoc) this.dateFormatOverride.clone();
1779        }
1780        // 'tickMarkPosition' is immutable : no need to clone
1781

1782        return clone;
1783
1784    }
1785            
1786}
1787
Popular Tags