KickJava   Java API By Example, From Geeks To Geeks.

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


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2006, 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
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22  * USA.
23  *
24  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25  * in the United States and other countries.]
26  *
27  * ---------------
28  * PeriodAxis.java
29  * ---------------
30  * (C) Copyright 2004-2006, by Object Refinery Limited and Contributors.
31  *
32  * Original Author: David Gilbert (for Object Refinery Limited);
33  * Contributor(s): -;
34  *
35  * $Id: PeriodAxis.java,v 1.16.2.5 2006/10/06 14:00:06 mungady Exp $
36  *
37  * Changes
38  * -------
39  * 01-Jun-2004 : Version 1 (DG);
40  * 16-Sep-2004 : Fixed bug in equals() method, added clone() method and
41  * PublicCloneable interface (DG);
42  * 25-Nov-2004 : Updates to support major and minor tick marks (DG);
43  * 25-Feb-2005 : Fixed some tick mark bugs (DG);
44  * 15-Apr-2005 : Fixed some more tick mark bugs (DG);
45  * 26-Apr-2005 : Removed LOGGER (DG);
46  * 16-Jun-2005 : Fixed zooming (DG);
47  * 15-Sep-2005 : Changed configure() method to check autoRange flag,
48  * and added ticks to state (DG);
49  * ------------- JFREECHART 1.0.x ---------------------------------------------
50  * 06-Oct-2006 : Updated for deprecations in RegularTimePeriod and
51  * subclasses (DG);
52  *
53  */

54
55 package org.jfree.chart.axis;
56
57 import java.awt.BasicStroke JavaDoc;
58 import java.awt.Color JavaDoc;
59 import java.awt.FontMetrics JavaDoc;
60 import java.awt.Graphics2D JavaDoc;
61 import java.awt.Paint JavaDoc;
62 import java.awt.Stroke JavaDoc;
63 import java.awt.geom.Line2D JavaDoc;
64 import java.awt.geom.Rectangle2D JavaDoc;
65 import java.io.IOException JavaDoc;
66 import java.io.ObjectInputStream JavaDoc;
67 import java.io.ObjectOutputStream JavaDoc;
68 import java.io.Serializable JavaDoc;
69 import java.lang.reflect.Constructor JavaDoc;
70 import java.text.DateFormat JavaDoc;
71 import java.text.SimpleDateFormat JavaDoc;
72 import java.util.ArrayList JavaDoc;
73 import java.util.Arrays JavaDoc;
74 import java.util.Calendar JavaDoc;
75 import java.util.Collections JavaDoc;
76 import java.util.Date JavaDoc;
77 import java.util.List JavaDoc;
78 import java.util.TimeZone JavaDoc;
79
80 import org.jfree.chart.event.AxisChangeEvent;
81 import org.jfree.chart.plot.Plot;
82 import org.jfree.chart.plot.PlotRenderingInfo;
83 import org.jfree.chart.plot.ValueAxisPlot;
84 import org.jfree.data.Range;
85 import org.jfree.data.time.Day;
86 import org.jfree.data.time.Month;
87 import org.jfree.data.time.RegularTimePeriod;
88 import org.jfree.data.time.Year;
89 import org.jfree.io.SerialUtilities;
90 import org.jfree.text.TextUtilities;
91 import org.jfree.ui.RectangleEdge;
92 import org.jfree.ui.TextAnchor;
93 import org.jfree.util.PublicCloneable;
94
95 /**
96  * An axis that displays a date scale based on a
97  * {@link org.jfree.data.time.RegularTimePeriod}. This axis works when
98  * displayed across the bottom or top of a plot, but is broken for display at
99  * the left or right of charts.
100  */

101 public class PeriodAxis extends ValueAxis
102                         implements Cloneable JavaDoc, PublicCloneable, Serializable JavaDoc {
103     
104     /** For serialization. */
105     private static final long serialVersionUID = 8353295532075872069L;
106     
107     /** The first time period in the overall range. */
108     private RegularTimePeriod first;
109     
110     /** The last time period in the overall range. */
111     private RegularTimePeriod last;
112     
113     /**
114      * The time zone used to convert 'first' and 'last' to absolute
115      * milliseconds.
116      */

117     private TimeZone JavaDoc timeZone;
118     
119     /**
120      * A calendar used for date manipulations in the current time zone.
121      */

122     private Calendar JavaDoc calendar;
123     
124     /**
125      * The {@link RegularTimePeriod} subclass used to automatically determine
126      * the axis range.
127      */

128     private Class JavaDoc autoRangeTimePeriodClass;
129     
130     /**
131      * Indicates the {@link RegularTimePeriod} subclass that is used to
132      * determine the spacing of the major tick marks.
133      */

134     private Class JavaDoc majorTickTimePeriodClass;
135     
136     /**
137      * A flag that indicates whether or not tick marks are visible for the
138      * axis.
139      */

140     private boolean minorTickMarksVisible;
141
142     /**
143      * Indicates the {@link RegularTimePeriod} subclass that is used to
144      * determine the spacing of the minor tick marks.
145      */

146     private Class JavaDoc minorTickTimePeriodClass;
147     
148     /** The length of the tick mark inside the data area (zero permitted). */
149     private float minorTickMarkInsideLength = 0.0f;
150
151     /** The length of the tick mark outside the data area (zero permitted). */
152     private float minorTickMarkOutsideLength = 2.0f;
153
154     /** The stroke used to draw tick marks. */
155     private transient Stroke JavaDoc minorTickMarkStroke = new BasicStroke JavaDoc(0.5f);
156
157     /** The paint used to draw tick marks. */
158     private transient Paint JavaDoc minorTickMarkPaint = Color.black;
159     
160     /** Info for each labelling band. */
161     private PeriodAxisLabelInfo[] labelInfo;
162
163     /**
164      * Creates a new axis.
165      *
166      * @param label the axis label.
167      */

168     public PeriodAxis(String JavaDoc label) {
169         this(label, new Day(), new Day());
170     }
171     
172     /**
173      * Creates a new axis.
174      *
175      * @param label the axis label (<code>null</code> permitted).
176      * @param first the first time period in the axis range
177      * (<code>null</code> not permitted).
178      * @param last the last time period in the axis range
179      * (<code>null</code> not permitted).
180      */

181     public PeriodAxis(String JavaDoc label,
182                       RegularTimePeriod first, RegularTimePeriod last) {
183         this(label, first, last, TimeZone.getDefault());
184     }
185     
186     /**
187      * Creates a new axis.
188      *
189      * @param label the axis label (<code>null</code> permitted).
190      * @param first the first time period in the axis range
191      * (<code>null</code> not permitted).
192      * @param last the last time period in the axis range
193      * (<code>null</code> not permitted).
194      * @param timeZone the time zone (<code>null</code> not permitted).
195      */

196     public PeriodAxis(String JavaDoc label,
197                       RegularTimePeriod first, RegularTimePeriod last,
198                       TimeZone JavaDoc timeZone) {
199         
200         super(label, null);
201         this.first = first;
202         this.last = last;
203         this.timeZone = timeZone;
204         this.calendar = Calendar.getInstance(timeZone);
205         this.autoRangeTimePeriodClass = first.getClass();
206         this.majorTickTimePeriodClass = first.getClass();
207         this.minorTickMarksVisible = false;
208         this.minorTickTimePeriodClass = RegularTimePeriod.downsize(
209                 this.majorTickTimePeriodClass);
210         setAutoRange(true);
211         this.labelInfo = new PeriodAxisLabelInfo[2];
212         this.labelInfo[0] = new PeriodAxisLabelInfo(Month.class,
213                 new SimpleDateFormat JavaDoc("MMM"));
214         this.labelInfo[1] = new PeriodAxisLabelInfo(Year.class,
215                 new SimpleDateFormat JavaDoc("yyyy"));
216         
217     }
218     
219     /**
220      * Returns the first time period in the axis range.
221      *
222      * @return The first time period (never <code>null</code>).
223      */

224     public RegularTimePeriod getFirst() {
225         return this.first;
226     }
227     
228     /**
229      * Sets the first time period in the axis range and sends an
230      * {@link AxisChangeEvent} to all registered listeners.
231      *
232      * @param first the time period (<code>null</code> not permitted).
233      */

234     public void setFirst(RegularTimePeriod first) {
235         if (first == null) {
236             throw new IllegalArgumentException JavaDoc("Null 'first' argument.");
237         }
238         this.first = first;
239         notifyListeners(new AxisChangeEvent(this));
240     }
241     
242     /**
243      * Returns the last time period in the axis range.
244      *
245      * @return The last time period (never <code>null</code>).
246      */

247     public RegularTimePeriod getLast() {
248         return this.last;
249     }
250     
251     /**
252      * Sets the last time period in the axis range and sends an
253      * {@link AxisChangeEvent} to all registered listeners.
254      *
255      * @param last the time period (<code>null</code> not permitted).
256      */

257     public void setLast(RegularTimePeriod last) {
258         if (last == null) {
259             throw new IllegalArgumentException JavaDoc("Null 'last' argument.");
260         }
261         this.last = last;
262         notifyListeners(new AxisChangeEvent(this));
263     }
264     
265     /**
266      * Returns the time zone used to convert the periods defining the axis
267      * range into absolute milliseconds.
268      *
269      * @return The time zone (never <code>null</code>).
270      */

271     public TimeZone JavaDoc getTimeZone() {
272         return this.timeZone;
273     }
274     
275     /**
276      * Sets the time zone that is used to convert the time periods into
277      * absolute milliseconds.
278      *
279      * @param zone the time zone (<code>null</code> not permitted).
280      */

281     public void setTimeZone(TimeZone JavaDoc zone) {
282         if (zone == null) {
283             throw new IllegalArgumentException JavaDoc("Null 'zone' argument.");
284         }
285         this.timeZone = zone;
286         this.calendar = Calendar.getInstance(zone);
287         notifyListeners(new AxisChangeEvent(this));
288     }
289     
290     /**
291      * Returns the class used to create the first and last time periods for
292      * the axis range when the auto-range flag is set to <code>true</code>.
293      *
294      * @return The class (never <code>null</code>).
295      */

296     public Class JavaDoc getAutoRangeTimePeriodClass() {
297         return this.autoRangeTimePeriodClass;
298     }
299     
300     /**
301      * Sets the class used to create the first and last time periods for the
302      * axis range when the auto-range flag is set to <code>true</code> and
303      * sends an {@link AxisChangeEvent} to all registered listeners.
304      *
305      * @param c the class (<code>null</code> not permitted).
306      */

307     public void setAutoRangeTimePeriodClass(Class JavaDoc c) {
308         if (c == null) {
309             throw new IllegalArgumentException JavaDoc("Null 'c' argument.");
310         }
311         this.autoRangeTimePeriodClass = c;
312         notifyListeners(new AxisChangeEvent(this));
313     }
314     
315     /**
316      * Returns the class that controls the spacing of the major tick marks.
317      *
318      * @return The class (never <code>null</code>).
319      */

320     public Class JavaDoc getMajorTickTimePeriodClass() {
321         return this.majorTickTimePeriodClass;
322     }
323     
324     /**
325      * Sets the class that controls the spacing of the major tick marks, and
326      * sends an {@link AxisChangeEvent} to all registered listeners.
327      *
328      * @param c the class (a subclass of {@link RegularTimePeriod} is
329      * expected).
330      */

331     public void setMajorTickTimePeriodClass(Class JavaDoc c) {
332         if (c == null) {
333             throw new IllegalArgumentException JavaDoc("Null 'c' argument.");
334         }
335         this.majorTickTimePeriodClass = c;
336         notifyListeners(new AxisChangeEvent(this));
337     }
338     
339     /**
340      * Returns the flag that controls whether or not minor tick marks
341      * are displayed for the axis.
342      *
343      * @return A boolean.
344      */

345     public boolean isMinorTickMarksVisible() {
346         return this.minorTickMarksVisible;
347     }
348     
349     /**
350      * Sets the flag that controls whether or not minor tick marks
351      * are displayed for the axis, and sends a {@link AxisChangeEvent}
352      * to all registered listeners.
353      *
354      * @param visible the flag.
355      */

356     public void setMinorTickMarksVisible(boolean visible) {
357         this.minorTickMarksVisible = visible;
358         notifyListeners(new AxisChangeEvent(this));
359     }
360     
361     /**
362      * Returns the class that controls the spacing of the minor tick marks.
363      *
364      * @return The class (never <code>null</code>).
365      */

366     public Class JavaDoc getMinorTickTimePeriodClass() {
367         return this.minorTickTimePeriodClass;
368     }
369     
370     /**
371      * Sets the class that controls the spacing of the minor tick marks, and
372      * sends an {@link AxisChangeEvent} to all registered listeners.
373      *
374      * @param c the class (a subclass of {@link RegularTimePeriod} is
375      * expected).
376      */

377     public void setMinorTickTimePeriodClass(Class JavaDoc c) {
378         if (c == null) {
379             throw new IllegalArgumentException JavaDoc("Null 'c' argument.");
380         }
381         this.minorTickTimePeriodClass = c;
382         notifyListeners(new AxisChangeEvent(this));
383     }
384     
385     /**
386      * Returns the stroke used to display minor tick marks, if they are
387      * visible.
388      *
389      * @return A stroke (never <code>null</code>).
390      */

391     public Stroke JavaDoc getMinorTickMarkStroke() {
392         return this.minorTickMarkStroke;
393     }
394     
395     /**
396      * Sets the stroke used to display minor tick marks, if they are
397      * visible, and sends a {@link AxisChangeEvent} to all registered
398      * listeners.
399      *
400      * @param stroke the stroke (<code>null</code> not permitted).
401      */

402     public void setMinorTickMarkStroke(Stroke JavaDoc stroke) {
403         if (stroke == null) {
404             throw new IllegalArgumentException JavaDoc("Null 'stroke' argument.");
405         }
406         this.minorTickMarkStroke = stroke;
407         notifyListeners(new AxisChangeEvent(this));
408     }
409     
410     /**
411      * Returns the paint used to display minor tick marks, if they are
412      * visible.
413      *
414      * @return A paint (never <code>null</code>).
415      */

416     public Paint JavaDoc getMinorTickMarkPaint() {
417         return this.minorTickMarkPaint;
418     }
419     
420     /**
421      * Sets the paint used to display minor tick marks, if they are
422      * visible, and sends a {@link AxisChangeEvent} to all registered
423      * listeners.
424      *
425      * @param paint the paint (<code>null</code> not permitted).
426      */

427     public void setMinorTickMarkPaint(Paint JavaDoc paint) {
428         if (paint == null) {
429             throw new IllegalArgumentException JavaDoc("Null 'paint' argument.");
430         }
431         this.minorTickMarkPaint = paint;
432         notifyListeners(new AxisChangeEvent(this));
433     }
434     
435     /**
436      * Returns the inside length for the minor tick marks.
437      *
438      * @return The length.
439      */

440     public float getMinorTickMarkInsideLength() {
441         return this.minorTickMarkInsideLength;
442     }
443     
444     /**
445      * Sets the inside length of the minor tick marks and sends an
446      * {@link AxisChangeEvent} to all registered listeners.
447      *
448      * @param length the length.
449      */

450     public void setMinorTickMarkInsideLength(float length) {
451         this.minorTickMarkInsideLength = length;
452         notifyListeners(new AxisChangeEvent(this));
453     }
454     
455     /**
456      * Returns the outside length for the minor tick marks.
457      *
458      * @return The length.
459      */

460     public float getMinorTickMarkOutsideLength() {
461         return this.minorTickMarkOutsideLength;
462     }
463     
464     /**
465      * Sets the outside length of the minor tick marks and sends an
466      * {@link AxisChangeEvent} to all registered listeners.
467      *
468      * @param length the length.
469      */

470     public void setMinorTickMarkOutsideLength(float length) {
471         this.minorTickMarkOutsideLength = length;
472         notifyListeners(new AxisChangeEvent(this));
473     }
474     
475     /**
476      * Returns an array of label info records.
477      *
478      * @return An array.
479      */

480     public PeriodAxisLabelInfo[] getLabelInfo() {
481         return this.labelInfo;
482     }
483     
484     /**
485      * Sets the array of label info records.
486      *
487      * @param info the info.
488      */

489     public void setLabelInfo(PeriodAxisLabelInfo[] info) {
490         this.labelInfo = info;
491     }
492     
493     /**
494      * Returns the range for the axis.
495      *
496      * @return The axis range (never <code>null</code>).
497      */

498     public Range getRange() {
499         // TODO: find a cleaner way to do this...
500
return new Range(this.first.getFirstMillisecond(this.calendar),
501                 this.last.getLastMillisecond(this.calendar));
502     }
503
504     /**
505      * Sets the range for the axis, if requested, sends an
506      * {@link AxisChangeEvent} to all registered listeners. As a side-effect,
507      * the auto-range flag is set to <code>false</code> (optional).
508      *
509      * @param range the range (<code>null</code> not permitted).
510      * @param turnOffAutoRange a flag that controls whether or not the auto
511      * range is turned off.
512      * @param notify a flag that controls whether or not listeners are
513      * notified.
514      */

515     public void setRange(Range range, boolean turnOffAutoRange,
516                          boolean notify) {
517         super.setRange(range, turnOffAutoRange, false);
518         long upper = Math.round(range.getUpperBound());
519         long lower = Math.round(range.getLowerBound());
520         this.first = createInstance(this.autoRangeTimePeriodClass,
521                 new Date JavaDoc(lower), this.timeZone);
522         this.last = createInstance(this.autoRangeTimePeriodClass,
523                 new Date JavaDoc(upper), this.timeZone);
524     }
525
526     /**
527      * Configures the axis to work with the current plot. Override this method
528      * to perform any special processing (such as auto-rescaling).
529      */

530     public void configure() {
531         if (this.isAutoRange()) {
532             autoAdjustRange();
533         }
534     }
535
536     /**
537      * Estimates the space (height or width) required to draw the axis.
538      *
539      * @param g2 the graphics device.
540      * @param plot the plot that the axis belongs to.
541      * @param plotArea the area within which the plot (including axes) should
542      * be drawn.
543      * @param edge the axis location.
544      * @param space space already reserved.
545      *
546      * @return The space required to draw the axis (including pre-reserved
547      * space).
548      */

549     public AxisSpace reserveSpace(Graphics2D JavaDoc g2, Plot plot,
550                                   Rectangle2D JavaDoc plotArea, RectangleEdge edge,
551                                   AxisSpace space) {
552         // create a new space object if one wasn't supplied...
553
if (space == null) {
554             space = new AxisSpace();
555         }
556         
557         // if the axis is not visible, no additional space is required...
558
if (!isVisible()) {
559             return space;
560         }
561
562         // if the axis has a fixed dimension, return it...
563
double dimension = getFixedDimension();
564         if (dimension > 0.0) {
565             space.ensureAtLeast(dimension, edge);
566         }
567         
568         // get the axis label size and update the space object...
569
Rectangle2D JavaDoc labelEnclosure = getLabelEnclosure(g2, edge);
570         double labelHeight = 0.0;
571         double labelWidth = 0.0;
572         double tickLabelBandsDimension = 0.0;
573         
574         for (int i = 0; i < this.labelInfo.length; i++) {
575             PeriodAxisLabelInfo info = this.labelInfo[i];
576             FontMetrics JavaDoc fm = g2.getFontMetrics(info.getLabelFont());
577             tickLabelBandsDimension
578                 += info.getPadding().extendHeight(fm.getHeight());
579         }
580         
581         if (RectangleEdge.isTopOrBottom(edge)) {
582             labelHeight = labelEnclosure.getHeight();
583             space.add(labelHeight + tickLabelBandsDimension, edge);
584         }
585         else if (RectangleEdge.isLeftOrRight(edge)) {
586             labelWidth = labelEnclosure.getWidth();
587             space.add(labelWidth + tickLabelBandsDimension, edge);
588         }
589
590         // add space for the outer tick labels, if any...
591
double tickMarkSpace = 0.0;
592         if (isTickMarksVisible()) {
593             tickMarkSpace = getTickMarkOutsideLength();
594         }
595         if (this.minorTickMarksVisible) {
596             tickMarkSpace = Math.max(tickMarkSpace,
597                     this.minorTickMarkOutsideLength);
598         }
599         space.add(tickMarkSpace, edge);
600         return space;
601     }
602
603     /**
604      * Draws the axis on a Java 2D graphics device (such as the screen or a
605      * printer).
606      *
607      * @param g2 the graphics device (<code>null</code> not permitted).
608      * @param cursor the cursor location (determines where to draw the axis).
609      * @param plotArea the area within which the axes and plot should be drawn.
610      * @param dataArea the area within which the data should be drawn.
611      * @param edge the axis location (<code>null</code> not permitted).
612      * @param plotState collects information about the plot
613      * (<code>null</code> permitted).
614      *
615      * @return The axis state (never <code>null</code>).
616      */

617     public AxisState draw(Graphics2D JavaDoc g2,
618                           double cursor,
619                           Rectangle2D JavaDoc plotArea,
620                           Rectangle2D JavaDoc dataArea,
621                           RectangleEdge edge,
622                           PlotRenderingInfo plotState) {
623         
624         AxisState axisState = new AxisState(cursor);
625         if (isAxisLineVisible()) {
626             drawAxisLine(g2, cursor, dataArea, edge);
627         }
628         drawTickMarks(g2, axisState, dataArea, edge);
629         for (int band = 0; band < this.labelInfo.length; band++) {
630             axisState = drawTickLabels(band, g2, axisState, dataArea, edge);
631         }
632         
633         // draw the axis label (note that 'state' is passed in *and*
634
// returned)...
635
axisState = drawLabel(getLabel(), g2, plotArea, dataArea, edge,
636                 axisState);
637         return axisState;
638         
639     }
640     
641     /**
642      * Draws the tick marks for the axis.
643      *
644      * @param g2 the graphics device.
645      * @param state the axis state.
646      * @param dataArea the data area.
647      * @param edge the edge.
648      */

649     protected void drawTickMarks(Graphics2D JavaDoc g2, AxisState state,
650                                  Rectangle2D JavaDoc dataArea,
651                                  RectangleEdge edge) {
652         if (RectangleEdge.isTopOrBottom(edge)) {
653             drawTickMarksHorizontal(g2, state, dataArea, edge);
654         }
655         else if (RectangleEdge.isLeftOrRight(edge)) {
656             drawTickMarksVertical(g2, state, dataArea, edge);
657         }
658     }
659     
660     /**
661      * Draws the major and minor tick marks for an axis that lies at the top or
662      * bottom of the plot.
663      *
664      * @param g2 the graphics device.
665      * @param state the axis state.
666      * @param dataArea the data area.
667      * @param edge the edge.
668      */

669     protected void drawTickMarksHorizontal(Graphics2D JavaDoc g2, AxisState state,
670                                            Rectangle2D JavaDoc dataArea,
671                                            RectangleEdge edge) {
672         List JavaDoc ticks = new ArrayList JavaDoc();
673         double x0 = dataArea.getX();
674         double y0 = state.getCursor();
675         double insideLength = getTickMarkInsideLength();
676         double outsideLength = getTickMarkOutsideLength();
677         RegularTimePeriod t = RegularTimePeriod.createInstance(
678                 this.majorTickTimePeriodClass, this.first.getStart(),
679                 getTimeZone());
680         long t0 = t.getFirstMillisecond(this.calendar);
681         Line2D JavaDoc inside = null;
682         Line2D JavaDoc outside = null;
683         long firstOnAxis = getFirst().getFirstMillisecond(this.calendar);
684         long lastOnAxis = getLast().getLastMillisecond(this.calendar);
685         while (t0 <= lastOnAxis) {
686             ticks.add(new NumberTick(new Double JavaDoc(t0), "", TextAnchor.CENTER,
687                     TextAnchor.CENTER, 0.0));
688             x0 = valueToJava2D(t0, dataArea, edge);
689             if (edge == RectangleEdge.TOP) {
690                 inside = new Line2D.Double JavaDoc(x0, y0, x0, y0 + insideLength);
691                 outside = new Line2D.Double JavaDoc(x0, y0, x0, y0 - outsideLength);
692             }
693             else if (edge == RectangleEdge.BOTTOM) {
694                 inside = new Line2D.Double JavaDoc(x0, y0, x0, y0 - insideLength);
695                 outside = new Line2D.Double JavaDoc(x0, y0, x0, y0 + outsideLength);
696             }
697             if (t0 > firstOnAxis) {
698                 g2.setPaint(getTickMarkPaint());
699                 g2.setStroke(getTickMarkStroke());
700                 g2.draw(inside);
701                 g2.draw(outside);
702             }
703             // draw minor tick marks
704
if (this.minorTickMarksVisible) {
705                 RegularTimePeriod tminor = RegularTimePeriod.createInstance(
706                         this.minorTickTimePeriodClass, new Date JavaDoc(t0),
707                         getTimeZone());
708                 long tt0 = tminor.getFirstMillisecond(this.calendar);
709                 while (tt0 < t.getLastMillisecond(this.calendar)
710                         && tt0 < lastOnAxis) {
711                     double xx0 = valueToJava2D(tt0, dataArea, edge);
712                     if (edge == RectangleEdge.TOP) {
713                         inside = new Line2D.Double JavaDoc(xx0, y0, xx0,
714                                 y0 + this.minorTickMarkInsideLength);
715                         outside = new Line2D.Double JavaDoc(xx0, y0, xx0,
716                                 y0 - this.minorTickMarkOutsideLength);
717                     }
718                     else if (edge == RectangleEdge.BOTTOM) {
719                         inside = new Line2D.Double JavaDoc(xx0, y0, xx0,
720                                 y0 - this.minorTickMarkInsideLength);
721                         outside = new Line2D.Double JavaDoc(xx0, y0, xx0,
722                                 y0 + this.minorTickMarkOutsideLength);
723                     }
724                     if (tt0 >= firstOnAxis) {
725                         g2.setPaint(this.minorTickMarkPaint);
726                         g2.setStroke(this.minorTickMarkStroke);
727                         g2.draw(inside);
728                         g2.draw(outside);
729                     }
730                     tminor = tminor.next();
731                     tt0 = tminor.getFirstMillisecond(this.calendar);
732                 }
733             }
734             t = t.next();
735             t0 = t.getFirstMillisecond(this.calendar);
736         }
737         if (edge == RectangleEdge.TOP) {
738             state.cursorUp(Math.max(outsideLength,
739                     this.minorTickMarkOutsideLength));
740         }
741         else if (edge == RectangleEdge.BOTTOM) {
742             state.cursorDown(Math.max(outsideLength,
743                     this.minorTickMarkOutsideLength));
744         }
745         state.setTicks(ticks);
746     }
747     
748     /**
749      * Draws the tick marks for a vertical axis.
750      *
751      * @param g2 the graphics device.
752      * @param state the axis state.
753      * @param dataArea the data area.
754      * @param edge the edge.
755      */

756     protected void drawTickMarksVertical(Graphics2D JavaDoc g2, AxisState state,
757                                          Rectangle2D JavaDoc dataArea,
758                                          RectangleEdge edge) {
759             
760     }
761     
762     /**
763      * Draws the tick labels for one "band" of time periods.
764      *
765      * @param band the band index (zero-based).
766      * @param g2 the graphics device.
767      * @param state the axis state.
768      * @param dataArea the data area.
769      * @param edge the edge where the axis is located.
770      *
771      * @return The updated axis state.
772      */

773     protected AxisState drawTickLabels(int band, Graphics2D JavaDoc g2, AxisState state,
774                                        Rectangle2D JavaDoc dataArea,
775                                        RectangleEdge edge) {
776
777         // work out the initial gap
778
double delta1 = 0.0;
779         FontMetrics JavaDoc fm = g2.getFontMetrics(this.labelInfo[band].getLabelFont());
780         if (edge == RectangleEdge.BOTTOM) {
781             delta1 = this.labelInfo[band].getPadding().calculateTopOutset(
782                     fm.getHeight());
783         }
784         else if (edge == RectangleEdge.TOP) {
785             delta1 = this.labelInfo[band].getPadding().calculateBottomOutset(
786                     fm.getHeight());
787         }
788         state.moveCursor(delta1, edge);
789         long axisMin = this.first.getFirstMillisecond(this.calendar);
790         long axisMax = this.last.getLastMillisecond(this.calendar);
791         g2.setFont(this.labelInfo[band].getLabelFont());
792         g2.setPaint(this.labelInfo[band].getLabelPaint());
793
794         // work out the number of periods to skip for labelling
795
RegularTimePeriod p1 = this.labelInfo[band].createInstance(
796                 new Date JavaDoc(axisMin), this.timeZone);
797         RegularTimePeriod p2 = this.labelInfo[band].createInstance(
798                 new Date JavaDoc(axisMax), this.timeZone);
799         String JavaDoc label1 = this.labelInfo[band].getDateFormat().format(
800                 new Date JavaDoc(p1.getMiddleMillisecond(this.calendar)));
801         String JavaDoc label2 = this.labelInfo[band].getDateFormat().format(
802                 new Date JavaDoc(p2.getMiddleMillisecond(this.calendar)));
803         Rectangle2D JavaDoc b1 = TextUtilities.getTextBounds(label1, g2,
804                 g2.getFontMetrics());
805         Rectangle2D JavaDoc b2 = TextUtilities.getTextBounds(label2, g2,
806                 g2.getFontMetrics());
807         double w = Math.max(b1.getWidth(), b2.getWidth());
808         long ww = Math.round(java2DToValue(dataArea.getX() + w + 5.0,
809                 dataArea, edge)) - axisMin;
810         long length = p1.getLastMillisecond(this.calendar)
811                       - p1.getFirstMillisecond(this.calendar);
812         int periods = (int) (ww / length) + 1;
813         
814         RegularTimePeriod p = this.labelInfo[band].createInstance(
815                 new Date JavaDoc(axisMin), this.timeZone);
816         Rectangle2D JavaDoc b = null;
817         long lastXX = 0L;
818         float y = (float) (state.getCursor());
819         TextAnchor anchor = TextAnchor.TOP_CENTER;
820         float yDelta = (float) b1.getHeight();
821         if (edge == RectangleEdge.TOP) {
822             anchor = TextAnchor.BOTTOM_CENTER;
823             yDelta = -yDelta;
824         }
825         while (p.getFirstMillisecond(this.calendar) <= axisMax) {
826             float x = (float) valueToJava2D(p.getMiddleMillisecond(
827                     this.calendar), dataArea, edge);
828             DateFormat JavaDoc df = this.labelInfo[band].getDateFormat();
829             String JavaDoc label = df.format(new Date JavaDoc(p.getMiddleMillisecond(
830                     this.calendar)));
831             long first = p.getFirstMillisecond(this.calendar);
832             long last = p.getLastMillisecond(this.calendar);
833             if (last > axisMax) {
834                 // this is the last period, but it is only partially visible
835
// so check that the label will fit before displaying it...
836
Rectangle2D JavaDoc bb = TextUtilities.getTextBounds(label, g2,
837                         g2.getFontMetrics());
838                 if ((x + bb.getWidth() / 2) > dataArea.getMaxX()) {
839                     float xstart = (float) valueToJava2D(Math.max(first,
840                             axisMin), dataArea, edge);
841                     if (bb.getWidth() < (dataArea.getMaxX() - xstart)) {
842                         x = ((float) dataArea.getMaxX() + xstart) / 2.0f;
843                     }
844                     else {
845                         label = null;
846                     }
847                 }
848             }
849             if (first < axisMin) {
850                 // this is the first period, but it is only partially visible
851
// so check that the label will fit before displaying it...
852
Rectangle2D JavaDoc bb = TextUtilities.getTextBounds(label, g2,
853                         g2.getFontMetrics());
854                 if ((x - bb.getWidth() / 2) < dataArea.getX()) {
855                     float xlast = (float) valueToJava2D(Math.min(last,
856                             axisMax), dataArea, edge);
857                     if (bb.getWidth() < (xlast - dataArea.getX())) {
858                         x = (xlast + (float) dataArea.getX()) / 2.0f;
859                     }
860                     else {
861                         label = null;
862                     }
863                 }
864                 
865             }
866             if (label != null) {
867                 g2.setPaint(this.labelInfo[band].getLabelPaint());
868                 b = TextUtilities.drawAlignedString(label, g2, x, y, anchor);
869             }
870             if (lastXX > 0L) {
871                 if (this.labelInfo[band].getDrawDividers()) {
872                     long nextXX = p.getFirstMillisecond(this.calendar);
873                     long mid = (lastXX + nextXX) / 2;
874                     float mid2d = (float) valueToJava2D(mid, dataArea, edge);
875                     g2.setStroke(this.labelInfo[band].getDividerStroke());
876                     g2.setPaint(this.labelInfo[band].getDividerPaint());
877                     g2.draw(new Line2D.Float JavaDoc(mid2d, y, mid2d, y + yDelta));
878                 }
879             }
880             lastXX = last;
881             for (int i = 0; i < periods; i++) {
882                 p = p.next();
883             }
884         }
885         double used = 0.0;
886         if (b != null) {
887             used = b.getHeight();
888             // work out the trailing gap
889
if (edge == RectangleEdge.BOTTOM) {
890                 used += this.labelInfo[band].getPadding().calculateBottomOutset(
891                         fm.getHeight());
892             }
893             else if (edge == RectangleEdge.TOP) {
894                 used += this.labelInfo[band].getPadding().calculateTopOutset(
895                         fm.getHeight());
896             }
897         }
898         state.moveCursor(used, edge);
899         return state;
900     }
901
902     /**
903      * Calculates the positions of the ticks for the axis, storing the results
904      * in the tick list (ready for drawing).
905      *
906      * @param g2 the graphics device.
907      * @param state the axis state.
908      * @param dataArea the area inside the axes.
909      * @param edge the edge on which the axis is located.
910      *
911      * @return The list of ticks.
912      */

913     public List JavaDoc refreshTicks(Graphics2D JavaDoc g2,
914                              AxisState state,
915                              Rectangle2D JavaDoc dataArea,
916                              RectangleEdge edge) {
917         return Collections.EMPTY_LIST;
918     }
919     
920     /**
921      * Converts a data value to a coordinate in Java2D space, assuming that the
922      * axis runs along one edge of the specified dataArea.
923      * <p>
924      * Note that it is possible for the coordinate to fall outside the area.
925      *
926      * @param value the data value.
927      * @param area the area for plotting the data.
928      * @param edge the edge along which the axis lies.
929      *
930      * @return The Java2D coordinate.
931      */

932     public double valueToJava2D(double value,
933                                 Rectangle2D JavaDoc area,
934                                 RectangleEdge edge) {
935         
936         double result = Double.NaN;
937         double axisMin = this.first.getFirstMillisecond(this.calendar);
938         double axisMax = this.last.getLastMillisecond(this.calendar);
939         if (RectangleEdge.isTopOrBottom(edge)) {
940             double minX = area.getX();
941             double maxX = area.getMaxX();
942             if (isInverted()) {
943                 result = maxX + ((value - axisMin) / (axisMax - axisMin))
944                          * (minX - maxX);
945             }
946             else {
947                 result = minX + ((value - axisMin) / (axisMax - axisMin))
948                          * (maxX - minX);
949             }
950         }
951         else if (RectangleEdge.isLeftOrRight(edge)) {
952             double minY = area.getMinY();
953             double maxY = area.getMaxY();
954             if (isInverted()) {
955                 result = minY + (((value - axisMin) / (axisMax - axisMin))
956                          * (maxY - minY));
957             }
958             else {
959                 result = maxY - (((value - axisMin) / (axisMax - axisMin))
960                          * (maxY - minY));
961             }
962         }
963         return result;
964         
965     }
966
967     /**
968      * Converts a coordinate in Java2D space to the corresponding data value,
969      * assuming that the axis runs along one edge of the specified dataArea.
970      *
971      * @param java2DValue the coordinate in Java2D space.
972      * @param area the area in which the data is plotted.
973      * @param edge the edge along which the axis lies.
974      *
975      * @return The data value.
976      */

977     public double java2DToValue(double java2DValue,
978                                 Rectangle2D JavaDoc area,
979                                 RectangleEdge edge) {
980
981         double result = Double.NaN;
982         double min = 0.0;
983         double max = 0.0;
984         double axisMin = this.first.getFirstMillisecond(this.calendar);
985         double axisMax = this.last.getLastMillisecond(this.calendar);
986         if (RectangleEdge.isTopOrBottom(edge)) {
987             min = area.getX();
988             max = area.getMaxX();
989         }
990         else if (RectangleEdge.isLeftOrRight(edge)) {
991             min = area.getMaxY();
992             max = area.getY();
993         }
994         if (isInverted()) {
995              result = axisMax - ((java2DValue - min) / (max - min)
996                       * (axisMax - axisMin));
997         }
998         else {
999              result = axisMin + ((java2DValue - min) / (max - min)
1000                      * (axisMax - axisMin));
1001        }
1002        return result;
1003    }
1004
1005    /**
1006     * Rescales the axis to ensure that all data is visible.
1007     */

1008    protected void autoAdjustRange() {
1009
1010        Plot plot = getPlot();
1011        if (plot == null) {
1012            return; // no plot, no data
1013
}
1014
1015        if (plot instanceof ValueAxisPlot) {
1016            ValueAxisPlot vap = (ValueAxisPlot) plot;
1017
1018            Range r = vap.getDataRange(this);
1019            if (r == null) {
1020                r = new Range(DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND);
1021            }
1022            
1023            long upper = Math.round(r.getUpperBound());
1024            long lower = Math.round(r.getLowerBound());
1025            this.first = createInstance(this.autoRangeTimePeriodClass,
1026                    new Date JavaDoc(lower), this.timeZone);
1027            this.last = createInstance(this.autoRangeTimePeriodClass,
1028                    new Date JavaDoc(upper), this.timeZone);
1029            setRange(r, false, false);
1030        }
1031
1032    }
1033    
1034    /**
1035     * Tests the axis for equality with an arbitrary object.
1036     *
1037     * @param obj the object (<code>null</code> permitted).
1038     *
1039     * @return A boolean.
1040     */

1041    public boolean equals(Object JavaDoc obj) {
1042        if (obj == this) {
1043            return true;
1044        }
1045        if (obj instanceof PeriodAxis && super.equals(obj)) {
1046            PeriodAxis that = (PeriodAxis) obj;
1047            if (!this.first.equals(that.first)) {
1048                return false;
1049            }
1050            if (!this.last.equals(that.last)) {
1051                return false;
1052            }
1053            if (!this.timeZone.equals(that.timeZone)) {
1054                return false;
1055            }
1056            if (!this.autoRangeTimePeriodClass.equals(
1057                    that.autoRangeTimePeriodClass)) {
1058                return false;
1059            }
1060            if (!(isMinorTickMarksVisible()
1061                    == that.isMinorTickMarksVisible())) {
1062                return false;
1063            }
1064            if (!this.majorTickTimePeriodClass.equals(
1065                    that.majorTickTimePeriodClass)) {
1066                return false;
1067            }
1068            if (!this.minorTickTimePeriodClass.equals(
1069                    that.minorTickTimePeriodClass)) {
1070                return false;
1071            }
1072            if (!this.minorTickMarkPaint.equals(that.minorTickMarkPaint)) {
1073                return false;
1074            }
1075            if (!this.minorTickMarkStroke.equals(that.minorTickMarkStroke)) {
1076                return false;
1077            }
1078            if (!Arrays.equals(this.labelInfo, that.labelInfo)) {
1079                return false;
1080            }
1081            return true;
1082        }
1083        return false;
1084    }
1085
1086    /**
1087     * Returns a hash code for this object.
1088     *
1089     * @return A hash code.
1090     */

1091    public int hashCode() {
1092        if (getLabel() != null) {
1093            return getLabel().hashCode();
1094        }
1095        else {
1096            return 0;
1097        }
1098    }
1099    
1100    /**
1101     * Returns a clone of the axis.
1102     *
1103     * @return A clone.
1104     *
1105     * @throws CloneNotSupportedException this class is cloneable, but
1106     * subclasses may not be.
1107     */

1108    public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
1109        PeriodAxis clone = (PeriodAxis) super.clone();
1110        clone.timeZone = (TimeZone JavaDoc) this.timeZone.clone();
1111        clone.labelInfo = new PeriodAxisLabelInfo[this.labelInfo.length];
1112        for (int i = 0; i < this.labelInfo.length; i++) {
1113            clone.labelInfo[i] = this.labelInfo[i]; // copy across references
1114
// to immutable objs
1115
}
1116        return clone;
1117    }
1118    
1119    /**
1120     * A utility method used to create a particular subclass of the
1121     * {@link RegularTimePeriod} class that includes the specified millisecond,
1122     * assuming the specified time zone.
1123     *
1124     * @param periodClass the class.
1125     * @param millisecond the time.
1126     * @param zone the time zone.
1127     *
1128     * @return The time period.
1129     */

1130    private RegularTimePeriod createInstance(Class JavaDoc periodClass,
1131                                             Date JavaDoc millisecond, TimeZone JavaDoc zone) {
1132        RegularTimePeriod result = null;
1133        try {
1134            Constructor JavaDoc c = periodClass.getDeclaredConstructor(new Class JavaDoc[] {
1135                    Date JavaDoc.class, TimeZone JavaDoc.class});
1136            result = (RegularTimePeriod) c.newInstance(new Object JavaDoc[] {
1137                    millisecond, zone});
1138        }
1139        catch (Exception JavaDoc e) {
1140            // do nothing
1141
}
1142        return result;
1143    }
1144    
1145    /**
1146     * Provides serialization support.
1147     *
1148     * @param stream the output stream.
1149     *
1150     * @throws IOException if there is an I/O error.
1151     */

1152    private void writeObject(ObjectOutputStream JavaDoc stream) throws IOException JavaDoc {
1153        stream.defaultWriteObject();
1154        SerialUtilities.writeStroke(this.minorTickMarkStroke, stream);
1155        SerialUtilities.writePaint(this.minorTickMarkPaint, stream);
1156    }
1157
1158    /**
1159     * Provides serialization support.
1160     *
1161     * @param stream the input stream.
1162     *
1163     * @throws IOException if there is an I/O error.
1164     * @throws ClassNotFoundException if there is a classpath problem.
1165     */

1166    private void readObject(ObjectInputStream JavaDoc stream)
1167        throws IOException JavaDoc, ClassNotFoundException JavaDoc {
1168        stream.defaultReadObject();
1169        this.minorTickMarkStroke = SerialUtilities.readStroke(stream);
1170        this.minorTickMarkPaint = SerialUtilities.readPaint(stream);
1171    }
1172
1173}
1174
Popular Tags