1 /* ============================================================
2  * JRobin : Pure java implementation of RRDTool's functionality
3  * ============================================================
4  *
5  * Project Info:  http://www.jrobin.org
6  * Project Lead:  Sasa Markovic (saxon@jrobin.org)
7  *
8  * Developers:    Sasa Markovic (saxon@jrobin.org)
9  *
10  *
11  * (C) Copyright 2003-2005, by Sasa Markovic.
12  *
13  * This library is free software; you can redistribute it and/or modify it under the terms
14  * of the GNU Lesser General Public License as published by the Free Software Foundation;
15  * either version 2.1 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
18  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19  * See the GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License along with this
22  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
23  * Boston, MA 02111-1307, USA.
24  */

25 package org.jrobin.graph;
26
27 import java.awt.*;
28 import java.text.SimpleDateFormat;
29 import java.util.Calendar;
30 import java.util.Date;
31 import java.util.Locale;
32
33 class TimeAxis implements RrdGraphConstants {
34     private static final TimeAxisSetting[] tickSettings = {
35             new TimeAxisSetting(0, SECOND, 30, MINUTE, 5, MINUTE, 5, 0, "HH:mm"),
36             new TimeAxisSetting(2, MINUTE, 1, MINUTE, 5, MINUTE, 5, 0, "HH:mm"),
37             new TimeAxisSetting(5, MINUTE, 2, MINUTE, 10, MINUTE, 10, 0, "HH:mm"),
38             new TimeAxisSetting(10, MINUTE, 5, MINUTE, 20, MINUTE, 20, 0, "HH:mm"),
39             new TimeAxisSetting(30, MINUTE, 10, HOUR, 1, HOUR, 1, 0, "HH:mm"),
40             new TimeAxisSetting(60, MINUTE, 30, HOUR, 2, HOUR, 2, 0, "HH:mm"),
41             new TimeAxisSetting(180, HOUR, 1, HOUR, 6, HOUR, 6, 0, "HH:mm"),
42             new TimeAxisSetting(600, HOUR, 6, DAY, 1, DAY, 1, 24 * 3600, "EEE"),
43             new TimeAxisSetting(1800, HOUR, 12, DAY, 1, DAY, 2, 24 * 3600, "EEE"),
44             new TimeAxisSetting(3600, DAY, 1, WEEK, 1, WEEK, 1, 7 * 24 * 3600, "'Week 'w"),
45             new TimeAxisSetting(3 * 3600, WEEK, 1, MONTH, 1, WEEK, 2, 7 * 24 * 3600, "'Week 'w"),
46             new TimeAxisSetting(6 * 3600, MONTH, 1, MONTH, 1, MONTH, 1, 30 * 24 * 3600, "MMM"),
47             new TimeAxisSetting(48 * 3600, MONTH, 1, MONTH, 3, MONTH, 3, 30 * 24 * 3600, "MMM"),
48             new TimeAxisSetting(10 * 24 * 3600, YEAR, 1, YEAR, 1, YEAR, 1, 365 * 24 * 3600, "yy"),
49             new TimeAxisSetting(-1, MONTH, 0, MONTH, 0, MONTH, 0, 0, "")
50     };
51
52     private TimeAxisSetting tickSetting;
53     private RrdGraph rrdGraph;
54     private double secPerPix;
55     private Calendar calendar;
56
57     TimeAxis(RrdGraph rrdGraph) {
58         this.rrdGraph = rrdGraph;
59         if (rrdGraph.im.xsize > 0) {
60             this.secPerPix = (rrdGraph.im.end - rrdGraph.im.start) / rrdGraph.im.xsize;
61         }
62         this.calendar = Calendar.getInstance(Locale.getDefault());
63         this.calendar.setFirstDayOfWeek(rrdGraph.gdef.firstDayOfWeek);
64     }
65
66     void draw() {
67         chooseTickSettings();
68         if (tickSetting == null) {
69             return;
70         }
71         drawMinor();
72         drawMajor();
73         drawLabels();
74     }
75
76     private void drawMinor() {
77         if (!rrdGraph.gdef.noMinorGrid) {
78             adjustStartingTime(tickSetting.minorUnit, tickSetting.minorUnitCount);
79             Paint color = rrdGraph.gdef.colors[COLOR_GRID];
80             int y0 = rrdGraph.im.yorigin, y1 = y0 - rrdGraph.im.ysize;
81             for (int status = getTimeShift(); status <= 0; status = getTimeShift()) {
82                 if (status == 0) {
83                     long time = calendar.getTime().getTime() / 1000L;
84                     int x = rrdGraph.mapper.xtr(time);
85                     rrdGraph.worker.drawLine(x, y0 - 1, x, y0 + 1, color, TICK_STROKE);
86                     rrdGraph.worker.drawLine(x, y0, x, y1, color, GRID_STROKE);
87                 }
88                 findNextTime(tickSetting.minorUnit, tickSetting.minorUnitCount);
89             }
90         }
91     }
92
93     private void drawMajor() {
94         adjustStartingTime(tickSetting.majorUnit, tickSetting.majorUnitCount);
95         Paint color = rrdGraph.gdef.colors[COLOR_MGRID];
96         int y0 = rrdGraph.im.yorigin, y1 = y0 - rrdGraph.im.ysize;
97         for (int status = getTimeShift(); status <= 0; status = getTimeShift()) {
98             if (status == 0) {
99                 long time = calendar.getTime().getTime() / 1000L;
100                 int x = rrdGraph.mapper.xtr(time);
101                 rrdGraph.worker.drawLine(x, y0 - 2, x, y0 + 2, color, TICK_STROKE);
102                 rrdGraph.worker.drawLine(x, y0, x, y1, color, GRID_STROKE);
103             }
104             findNextTime(tickSetting.majorUnit, tickSetting.majorUnitCount);
105         }
106     }
107
108     private void drawLabels() {
109         // escape strftime like format string
110         String labelFormat = tickSetting.format.replaceAll("([^%]|^)%([^%t])""$1%t$2");
111         Font font = rrdGraph.gdef.smallFont;
112         Paint color = rrdGraph.gdef.colors[COLOR_FONT];
113         adjustStartingTime(tickSetting.labelUnit, tickSetting.labelUnitCount);
114         int y = rrdGraph.im.yorigin + (int) rrdGraph.worker.getFontHeight(font) + 2;
115         for (int status = getTimeShift(); status <= 0; status = getTimeShift()) {
116             String label = formatLabel(labelFormat, calendar.getTime());
117             long time = calendar.getTime().getTime() / 1000L;
118             int x1 = rrdGraph.mapper.xtr(time);
119             int x2 = rrdGraph.mapper.xtr(time + tickSetting.labelSpan);
120             int labelWidth = (int) rrdGraph.worker.getStringWidth(label, font);
121             int x = x1 + (x2 - x1 - labelWidth) / 2;
122             if (x >= rrdGraph.im.xorigin && x + labelWidth <= rrdGraph.im.xorigin + rrdGraph.im.xsize) {
123                 rrdGraph.worker.drawString(label, x, y, font, color);
124             }
125             findNextTime(tickSetting.labelUnit, tickSetting.labelUnitCount);
126         }
127     }
128
129     private static String formatLabel(String format, Date date) {
130         if (format.contains("%")) {
131             // strftime like format string
132             return String.format(format, date);
133         }
134         else {
135             // simple date format
136             return new SimpleDateFormat(format).format(date);
137         }
138     }
139
140     private void findNextTime(int timeUnit, int timeUnitCount) {
141         switch (timeUnit) {
142             case SECOND:
143                 calendar.add(Calendar.SECOND, timeUnitCount);
144                 break;
145             case MINUTE:
146                 calendar.add(Calendar.MINUTE, timeUnitCount);
147                 break;
148             case HOUR:
149                 calendar.add(Calendar.HOUR_OF_DAY, timeUnitCount);
150                 break;
151             case DAY:
152                 calendar.add(Calendar.DAY_OF_MONTH, timeUnitCount);
153                 break;
154             case WEEK:
155                 calendar.add(Calendar.DAY_OF_MONTH, 7 * timeUnitCount);
156                 break;
157             case MONTH:
158                 calendar.add(Calendar.MONTH, timeUnitCount);
159                 break;
160             case YEAR:
161                 calendar.add(Calendar.YEAR, timeUnitCount);
162                 break;
163         }
164     }
165
166     private int getTimeShift() {
167         long time = calendar.getTime().getTime() / 1000L;
168         return (time < rrdGraph.im.start) ? -1 : (time > rrdGraph.im.end) ? +1 : 0;
169     }
170
171     private void adjustStartingTime(int timeUnit, int timeUnitCount) {
172         calendar.setTime(new Date(rrdGraph.im.start * 1000L));
173         switch (timeUnit) {
174             case SECOND:
175                 calendar.add(Calendar.SECOND, -(calendar.get(Calendar.SECOND) % timeUnitCount));
176                 break;
177             case MINUTE:
178                 calendar.set(Calendar.SECOND, 0);
179                 calendar.add(Calendar.MINUTE, -(calendar.get(Calendar.MINUTE) % timeUnitCount));
180                 break;
181             case HOUR:
182                 calendar.set(Calendar.SECOND, 0);
183                 calendar.set(Calendar.MINUTE, 0);
184                 calendar.add(Calendar.HOUR_OF_DAY, -(calendar.get(Calendar.HOUR_OF_DAY) % timeUnitCount));
185                 break;
186             case DAY:
187                 calendar.set(Calendar.SECOND, 0);
188                 calendar.set(Calendar.MINUTE, 0);
189                 calendar.set(Calendar.HOUR_OF_DAY, 0);
190                 break;
191             case WEEK:
192                 calendar.set(Calendar.SECOND, 0);
193                 calendar.set(Calendar.MINUTE, 0);
194                 calendar.set(Calendar.HOUR_OF_DAY, 0);
195                 int diffDays = calendar.get(Calendar.DAY_OF_WEEK) - calendar.getFirstDayOfWeek();
196                 if (diffDays < 0) {
197                     diffDays += 7;
198                 }
199                 calendar.add(Calendar.DAY_OF_MONTH, -diffDays);
200                 break;
201             case MONTH:
202                 calendar.set(Calendar.SECOND, 0);
203                 calendar.set(Calendar.MINUTE, 0);
204                 calendar.set(Calendar.HOUR_OF_DAY, 0);
205                 calendar.set(Calendar.DAY_OF_MONTH, 1);
206                 calendar.add(Calendar.MONTH, -(calendar.get(Calendar.MONTH) % timeUnitCount));
207                 break;
208             case YEAR:
209                 calendar.set(Calendar.SECOND, 0);
210                 calendar.set(Calendar.MINUTE, 0);
211                 calendar.set(Calendar.HOUR_OF_DAY, 0);
212                 calendar.set(Calendar.DAY_OF_MONTH, 1);
213                 calendar.set(Calendar.MONTH, 0);
214                 calendar.add(Calendar.YEAR, -(calendar.get(Calendar.YEAR) % timeUnitCount));
215                 break;
216         }
217     }
218
219
220     private void chooseTickSettings() {
221         if (rrdGraph.gdef.timeAxisSetting != null) {
222             tickSetting = new TimeAxisSetting(rrdGraph.gdef.timeAxisSetting);
223         }
224         else {
225             for (int i = 0; tickSettings[i].secPerPix >= 0 && secPerPix > tickSettings[i].secPerPix; i++) {
226                 tickSetting = tickSettings[i];
227             }
228         }
229     }
230
231 }
232