001package bradleyross.opensource.jfreechart.helpers;
002import org.jfree.chart.axis.TickUnits;
003import org.jfree.chart.axis.DateTickUnit;
004import org.jfree.chart.axis.NumberTickUnit;
005import org.jfree.chart.JFreeChart;
006import org.jfree.chart.plot.XYPlot;
007import org.jfree.chart.axis.DateAxis;
008import org.jfree.chart.axis.AxisLocation;
009import org.jfree.chart.axis.DateTickMarkPosition;
010import java.util.Date;
011import java.util.Calendar;
012import java.util.GregorianCalendar;
013import java.text.SimpleDateFormat;
014import java.text.NumberFormat;
015@SuppressWarnings("deprecation")
016/**
017 * Create a new collection of tick units.
018 * 
019 * <p>This class contains methods that help to control the appearance
020 *    of the axis and the associated tick marks.</p>
021 * @author Bradley Ross
022 * @see TickUnits
023 * @see DateTickUnit
024 *
025 */
026public class TickUnitCollection 
027{
028        /** 
029         * Controls amount of diagnostic messages 
030         * @see #getDebugLevel()
031         * @see #setDebugLevel(int)
032         * */
033        protected int outerDebugLevel = 0;
034        /**
035         * Getter for outerDebugLevel
036         * @see #outerDebugLevel
037         * @return Value of outerDebugLevel
038         */
039        public int getDebugLevel()
040        { return outerDebugLevel; }
041        /** 
042         * Setter for outerDebugLevel.
043         * 
044         * @see #outerDebugLevel
045         * @param value Value for outerDebugLevel
046         */
047        public void setDebugLevel(int value)
048        { outerDebugLevel = value; }
049        /**
050         * Constructor allowing extra debugging aids.
051         * 
052         * @param value Value for outerDebugLevel
053         */
054        public TickUnitCollection(int value)
055        {
056                outerDebugLevel = value;
057                if (value > 0)
058                {
059                        System.out.println("Running constructor for TickUnitCollection");
060                }
061        }
062        /**
063         * Default constructor.
064         */
065        public TickUnitCollection()
066        { outerDebugLevel = 0; }
067        /**
068         * java.text.DateFormat class required to satisfy the
069         * formatting requirements of the JFreeChart package.
070         * 
071         * @author Bradley Ross
072         *
073         */
074        protected class MNFormat extends java.text.DateFormat
075        {
076                protected int debugLevel = 0;
077                public int getDebugLevel()
078                { return debugLevel; }
079                public void setDebugLevel(int value)
080                { debugLevel = value; }
081                /**
082                 * Default entry to satisfy serializable interface.
083                 */
084                private static final long serialVersionUID = 1L;
085                MNFormat()
086                { if (outerDebugLevel > 0) { setDebugLevel(outerDebugLevel); }}
087                /**
088                 * Returns  a StringBuffer containing N or M to represent
089                 * noon and midnight in the time axis of the chart.  For other times
090                 * of day, it returns a blank character.
091                 * <p>This method may not return null although it may
092                 *    return an empty StringBuffer.</p>
093                 * <p>The values of 11, 13, 23, and 1 were added to the tests
094                 *    for the value of the hour field to handle the case where
095                 *    Daylight Savings Time begins or ends in the reporting
096                 *    period.  In this case, the hour corresponding to the tick
097                 *    marks is shifted by an hour after the change.</p>
098                 * @param date Date to be used in selecting the character
099                 * @param toAppendTo string to which output is appended
100                 * @param fieldPosition TBD
101                 * @return Character N or M to indicate noon or midnight or a
102                 *         blank character to indicate other times.
103                 */
104                public StringBuffer format(java.util.Date date, StringBuffer toAppendTo,
105                                java.text.FieldPosition fieldPosition)
106                {
107                        if (debugLevel > 1)
108                        { 
109                                java.text.SimpleDateFormat format =
110                                        new java.text.SimpleDateFormat("dd-MMM-yyyy HH:mm:ss:SSS");
111                                System.out.println("MNFormat.format - Time for tick " +
112                                                format.format(date)); 
113                        }
114                        GregorianCalendar calendar = new GregorianCalendar();
115                        calendar.setTime(date);
116                        int hour = calendar.get(Calendar.HOUR_OF_DAY);
117                        int minute = calendar.get(Calendar.MINUTE);
118                        if ( minute >= 30)
119                        {
120                                calendar.set(Calendar.MINUTE, 0);
121                                calendar.add(Calendar.HOUR_OF_DAY, 1);
122                        }
123                        else
124                        {
125                                calendar.set(Calendar.MINUTE, 0);
126                        }
127                        hour = calendar.get(Calendar.HOUR_OF_DAY);
128                        if (hour == 23 | hour == 24 || hour == 00 | hour == 01)
129                        { 
130                                toAppendTo.append("M");
131                                return new StringBuffer("M"); }
132                        else if (hour == 11 | hour == 12 | hour == 13)
133                        { 
134                                toAppendTo.append("N");
135                                return new StringBuffer("N"); }
136                        else
137                        { return new StringBuffer(); }
138                }
139                /**
140                 * Not used in this class.
141                 * <p>This would normally be used to convert the character 
142                 *    representation into a java.util.Date object.  However,
143                 *    that is not possible given the nature of this class.  A
144                 *    dummy method that returns null is used to satisfy the 
145                 *    interface and superclass requirements.  Actually
146                 *    calling this method will cause an exception to be 
147                 *    raised.</p>
148                 */
149                public java.util.Date parse(String source, java.text.ParsePosition pos)
150                {
151                        return null;
152                }
153        }
154        /**
155         * Set up the time tick marks and text items for the charts so that the time
156         * axis has letters indicating noon and midnight as well as the day of week,
157         * day of month and month.
158     * <p>This version didn't work.  The version specifying start and end
159     *    times worked.</p>
160         * @param chart JFreeChart object to be modified.
161         */
162        public void setMNTickMarks (JFreeChart chart)
163        {
164                XYPlot plot = null;
165                try
166                {
167                        plot = chart.getXYPlot();
168                        DateAxis newAxis = new DateAxis();
169                        DateAxis oldAxis = (DateAxis) plot.getDomainAxis();
170                        plot.setDomainAxis(1, newAxis);
171                        oldAxis.setStandardTickUnits(getNewUnits());
172                        oldAxis.setDateFormatOverride(new MNFormat());
173                        newAxis.setAxisLinePaint(chart.getBackgroundPaint());
174                        /*
175                         * The default for setTickMarkPosition is to place the
176                         * tick mark at the end of the interval
177                         */
178                        oldAxis.setTickMarkPosition(DateTickMarkPosition.START);
179                        newAxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
180                        newAxis.setAutoTickUnitSelection(false);
181                        plot.setDomainAxisLocation(1, AxisLocation.BOTTOM_OR_RIGHT);
182                        DateAxis bottomAxis = new DateAxis();
183                        plot.setDomainAxis(2, bottomAxis);
184                        newAxis.setDateFormatOverride(new SimpleDateFormat("dd-MMM"));
185                        bottomAxis.setDateFormatOverride(new SimpleDateFormat("EEE"));
186                        bottomAxis.setStandardTickUnits(getDailyUnits());
187                        newAxis.setStandardTickUnits(getDailyUnits());
188                        bottomAxis.setAxisLinePaint(chart.getBackgroundPaint());
189                        bottomAxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
190                        bottomAxis.setAutoTickUnitSelection(false);
191                        plot.setDomainAxisLocation(2, AxisLocation.BOTTOM_OR_RIGHT);
192                }
193                catch (Exception e)
194                {
195                }
196        }       
197        
198        
199        /**
200         * Set up the time tick marks and text items for the charts so that the time
201         * axis has letters indicating noon and midnight as well as the day of week,
202         * day of month and month.
203         * <p>It is necessary to specify the starting and ending time so that the
204         *        three time based axis can be lined up correctly.  For the three axis
205         *    (month/day, day of week, noon/midnight), the following actions
206         *    are carried out.</p>
207         * <p>When I didn't set the upper/lower bounds of each domain axis and turn off
208         *    autoranging, only the first row appeared.</p>
209         * <ul>
210         * <li><p>setAutoRange(false) is used to turn off automatic adjustment of 
211         *        the axis range and the setLowerBound and setUpperBound methods
212         *        are used to set the time range.</p></li>
213         * <li><p>The color of the axis line is set to the background color so that
214         *        it becomes invisible.</p></li>
215         * <li><p>The setDateFormatOverride, setStandardTickUnits, 
216         *        setAutoTickUnitSelection, and
217         *        setTickMarkPosition methods are used to set the appearance
218         *        of the axis.</p></li>
219         * </ul>
220         * <p>There appears to be a problem with the JFreeChart software.  Unless I moved
221         *    the start to before the start of the first interval, the first interval
222         *    was reduced in length by an hour.  Moving the start time created an
223         *    initial interval that was two milliseconds in length.</p>
224         * @param chart JFreeChart object to be modified.
225         * @param startTimeIn Start of time domain range
226         * @param endTime End of time domain range
227         */
228        public void setMNTickMarks (JFreeChart chart, Date startTimeIn, Date endTime)
229        {
230                XYPlot plot = null;
231                try
232                {
233                        /*
234                         * I had to move the start of the reporting period before the start of the
235                         * first tick.  Otherwise, it reduced the size of the first interval
236                         * by one hour.
237                         * 
238                         * This appears to be a bug.
239                         */
240                        java.util.Date startTime = new java.util.Date(startTimeIn.getTime() - 2l);
241                        if (outerDebugLevel > 0)
242                        {
243                                java.text.SimpleDateFormat format =
244                                        new java.text.SimpleDateFormat ("dd-MMM-yyyy HH:mm:ss:SSS");
245                                System.out.println("Constructor for setMNTickMarks " +
246                                                format.format(startTime) + "    " +
247                                                format.format(endTime));
248                        }
249                        plot = chart.getXYPlot();
250                        DateAxis newAxis = new DateAxis();
251                        DateAxis oldAxis = (DateAxis) plot.getDomainAxis();
252                        newAxis.setAutoRange(false);
253                        oldAxis.setAutoRange(false);
254                        newAxis.setLowerBound(startTime.getTime());
255                        oldAxis.setLowerBound(startTime.getTime());
256                        newAxis.setUpperBound(endTime.getTime());
257                        oldAxis.setUpperBound(endTime.getTime());
258                        plot.setDomainAxis(1, newAxis);
259                        oldAxis.setStandardTickUnits(getHourlyUnits(6));
260                        oldAxis.setDateFormatOverride(new MNFormat());
261                        newAxis.setAxisLinePaint(chart.getBackgroundPaint());
262                        newAxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
263                        newAxis.setAutoTickUnitSelection(false);
264                        plot.setDomainAxisLocation(1, AxisLocation.BOTTOM_OR_RIGHT);
265                        DateAxis bottomAxis = new DateAxis();
266                        bottomAxis.setAutoRange(false);
267                        bottomAxis.setLowerBound(startTime.getTime());
268                        bottomAxis.setUpperBound(endTime.getTime());
269                        plot.setDomainAxis(2, bottomAxis);
270                        newAxis.setDateFormatOverride(new SimpleDateFormat("dd-MMM"));
271                        bottomAxis.setDateFormatOverride(new SimpleDateFormat("EEE"));
272                        bottomAxis.setStandardTickUnits(getDailyUnits());
273                        newAxis.setStandardTickUnits(getDailyUnits());
274                        bottomAxis.setAxisLinePaint(chart.getBackgroundPaint());
275                        bottomAxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
276                        bottomAxis.setAutoTickUnitSelection(false);
277                        plot.setDomainAxisLocation(2, AxisLocation.BOTTOM_OR_RIGHT);
278                }
279                catch (Exception e)
280                {
281                }
282        }
283        /**
284         * Creates tick marks at twelve hour intervals.
285         * <p>org.jfree.chart.axis.TickUnits implements the
286         *    org.jfree.chart.axis.TickUnitSource interface.</p>
287         * <p>org.jfree.chart.axis.DateTickUnit is a subclass
288         *    of the abstract class
289         *    org.jfree.chart.axis.TickUnit.</p>
290         * <p>According to section 9.4.8 of the manual, you use the
291         *    setStandardTickUnits method of the axis object
292         *    to specify the tick mark collection to be used.<br>
293         *    DateAxis.setStandardTickUnits(TickUnitSource)</p>
294         *    
295         * @return Object defining set of tick marks.
296         * @see org.jfree.chart.axis.TickUnitSource
297         * @see org.jfree.chart.axis.TickUnits
298         * @see org.jfree.chart.axis.TickUnit
299         * @see org.jfree.chart.axis.DateTickUnit
300         */
301        public TickUnits getNewUnits()
302        {
303                TickUnits newUnits = null;
304                newUnits = new TickUnits();
305                newUnits.add(new DateTickUnit(DateTickUnit.HOUR, 12));
306                return newUnits;
307        }
308        /**
309         * Set up tick marks at multiple hour intervals.  
310         * 
311         * <p>Recommended intervals are
312         * 2, 3, 4, 6, and 12 hours.  Other intervals may have a strange appearance.</p>
313         * @param hours Number of hours per tick mark
314         * @return Set of tick units
315         */
316        public TickUnits getHourlyUnits(int hours)
317        {
318                TickUnits newUnits = new TickUnits();
319                newUnits.add(new DateTickUnit(DateTickUnit.HOUR, hours));
320                return newUnits;
321        }
322        /**
323         * Creates tick marks at 24 hour (daily) intervals.
324         * <p>The constructor getDailyUnits() behaves the same
325         *    as getHourlyUnits(24).</p>
326         * @return Object defining tick marks
327         */
328        public TickUnits getDailyUnits()
329        {
330                TickUnits dailyUnits = new TickUnits();
331                dailyUnits.add(new DateTickUnit(DateTickUnit.HOUR, 24));
332                return dailyUnits;
333        }
334        /**
335         * Specify the distance between tick marks.
336         * <p>This allows the tick marks on a plot with
337         *    multiple range axis to be lined up to
338         *    provide a cleaner appearance.</p>
339         * @param size Distance between tick marks
340         * @return Set of tick units
341         */
342        public TickUnits getNumberUnits(double size)
343        {
344                TickUnits valueUnits = new TickUnits();
345                valueUnits.add(new NumberTickUnit(size));
346                return valueUnits;
347        }
348        /**
349         * Specify the distance between tick marks and the format
350         * to be used in presenting the values for the tick marks.
351         * <p>This allows the tick marks on a plot with
352         *    multiple range axis to be lined up to
353         *    provide a cleaner appearance.</p>
354         * @param size Distance between tick marks
355         * @param formatter Formatter to be used for values of tick marks
356         * @return Set of tick units
357         */
358        public TickUnits getNumberUnits(double size, NumberFormat formatter)
359        {
360                TickUnits valueUnits = new TickUnits();
361                valueUnits.add(new NumberTickUnit(size, formatter));
362                return valueUnits;
363        }               
364}