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 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 100 * @param fieldPosition 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}