View Javadoc

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.Font;
28  import java.awt.Paint;
29  import java.io.File;
30  import java.io.InputStream;
31  import java.net.MalformedURLException;
32  import java.net.URL;
33  import java.util.ArrayList;
34  import java.util.List;
35  
36  import org.jrobin.core.RrdException;
37  import org.jrobin.core.Util;
38  import org.jrobin.data.Plottable;
39  
40  /**
41   * Class which should be used to define new JRobin graph. Once constructed and populated with data
42   * object of this class should be passed to the constructor of the {@link RrdGraph} class which
43   * will actually create the graph.
44   * <p/>
45   * The text printed below the actual graph can be formated by appending
46   * special escaped characters at the end of a text. When ever such a
47   * character occurs, all pending text is pushed onto the graph according to
48   * the character specified.
49   * <p/>
50   * Valid markers are: \j for justified, \l for left aligned, \r for right
51   * aligned and \c for centered.
52   * <p/>
53   * Normally there are two space characters inserted between every two
54   * items printed into the graph. The space following a string can be
55   * suppressed by putting a \g at the end of the string. The \g also squashes
56   * any space inside the string if it is at the very end of the string.
57   * This can be used in connection with %s to suppress empty unit strings.
58   * <p/>
59   * A special case is COMMENT:\s this inserts some additional vertical
60   * space before placing the next row of legends.
61   * <p/>
62   * When text has to be formated without special instructions from your
63   * side, RRDTool will automatically justify the text as soon as one string
64   * goes over the right edge. If you want to prevent the justification
65   * without forcing a newline, you can use the special tag \J at the end of
66   * the string to disable the auto justification.
67   */
68  public class RrdGraphDef implements RrdGraphConstants {
69  	boolean poolUsed = false; // ok
70  	boolean antiAliasing = false; // ok
71  	String filename = RrdGraphConstants.IN_MEMORY_IMAGE; // ok
72  	long startTime, endTime; // ok
73  	TimeAxisSetting timeAxisSetting = null; // ok
74  	ValueAxisSetting valueAxisSetting = null; // ok
75  	boolean altYGrid = false; // ok
76  	boolean noMinorGrid = false; // ok
77  	boolean altYMrtg = false; // ok
78  	boolean altAutoscale = false; // ok
79  	boolean altAutoscaleMax = false; // ok
80  	int unitsExponent = Integer.MAX_VALUE; // ok
81  	int unitsLength = DEFAULT_UNITS_LENGTH; // ok
82  	String verticalLabel = null; // ok
83  	int width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT; // ok
84  	boolean interlaced = false; // ok
85  	String imageInfo = null; // ok
86  	String imageFormat = DEFAULT_IMAGE_FORMAT; // ok
87  	float imageQuality = DEFAULT_IMAGE_QUALITY; // ok
88  	String backgroundImage = null; // ok
89  	String overlayImage = null; // ok
90  	String unit = null; // ok
91  	String signature = "Created with JRobin"; // ok
92  	boolean lazy = false; // ok
93  	double minValue = Double.NaN; // ok
94  	double maxValue = Double.NaN; // ok
95  	boolean rigid = false; // ok
96  	double base = DEFAULT_BASE;  // ok
97  	boolean logarithmic = false; // ok
98  	Paint[] colors = new Paint[] {
99  			// ok
100 			DEFAULT_CANVAS_COLOR,
101 			DEFAULT_BACK_COLOR,
102 			DEFAULT_SHADEA_COLOR,
103 			DEFAULT_SHADEB_COLOR,
104 			DEFAULT_GRID_COLOR,
105 			DEFAULT_MGRID_COLOR,
106 			DEFAULT_FONT_COLOR,
107 			DEFAULT_FRAME_COLOR,
108 			DEFAULT_ARROW_COLOR
109 	};
110 	boolean noLegend = false; // ok
111 	boolean onlyGraph = false; // ok
112 	boolean forceRulesLegend = false; // ok
113 	String title = null; // ok
114 	long step = 0; // ok
115 	protected Font smallFont;
116 	protected Font largeFont;
117 	boolean drawXGrid = true; // ok
118 	boolean drawYGrid = true; // ok
119 	int firstDayOfWeek = FIRST_DAY_OF_WEEK; // ok
120 	boolean showSignature = true;
121 	File fontDir = null;
122 	
123 	List<Source> sources = new ArrayList<Source>();
124 	List<CommentText> comments = new ArrayList<CommentText>();
125 	List<PlotElement> plotElements = new ArrayList<PlotElement>();
126 
127 	/**
128 	 * Creates RrdGraphDef object and sets default time span (default ending time is 'now',
129 	 * default starting time is 'end-1day'.
130 	 */
131 	public RrdGraphDef() {
132 		try {
133 			setTimeSpan(Util.getTimestamps(DEFAULT_START, DEFAULT_END));
134 		} catch (RrdException e) {
135 			throw new RuntimeException(e);
136 		}
137 		
138 		String fontdirProperty = System.getProperty("jrobin.fontdir");
139 		if (fontdirProperty != null && fontdirProperty.length() != 0) {
140 			fontDir = new File(fontdirProperty);
141 		}
142 		
143 		// smallFont = this.getFontFromResourceName(RrdGraphConstants.DEFAULT_MONOSPACE_FONT_FILE).deriveFont(10);
144 		// largeFont = this.getFontFromResourceName(RrdGraphConstants.DEFAULT_MONOSPACE_FONT_FILE).deriveFont(12);
145 		smallFont = new Font(DEFAULT_FONT_NAME, Font.PLAIN, 10);
146 		largeFont = new Font(DEFAULT_FONT_NAME, Font.BOLD, 12);
147 	}
148 
149 	protected Font getFontFromResourceName(String name) {
150 		Font font = null;
151 		Exception exception = null;
152 		URL file = null;
153 		
154 		if (fontDir != null) {
155 			try {
156 				file = new URL("file://" + new File(fontDir, name).getAbsolutePath());
157 			} catch (MalformedURLException e) {
158 				// fall through to the jar
159 				exception = e;
160 			}
161 		}
162 		if (file == null) {
163 			file = this.getClass().getResource(name);
164 		}
165 		
166 		if (file != null) {
167 			// System.err.println("Found a font URL: " + file.toExternalForm());
168 			try {
169 				InputStream fontStream = file.openStream();
170 				font = Font.createFont(Font.TRUETYPE_FONT, fontStream);
171 				fontStream.close();
172 			} catch (Exception e) {
173 				exception = e;
174 			}
175 		} else {
176 			// we can't find our fonts, fall back to the system font
177 			System.err.println("An error occurred loading the font '" + name + "'.  Falling back to the default.");
178 			if (exception != null) {
179 				System.err.println(exception.getLocalizedMessage());
180 			}
181 			font = new Font(DEFAULT_FONT_NAME, Font.PLAIN, 10);
182 		}
183 
184 		if (font == null) {
185 			font = new Font(null, Font.PLAIN, 10);
186 		}
187 		return font;
188 	}
189 	
190 	/**
191 	 * Sets the signature string that runs along the right-side of the graph.
192 	 * Defaults to "Created with JRobin".
193 	 * 
194 	 * @param signature the string to print
195 	 */
196 	public void setSignature(String signature) {
197 		this.signature = signature;
198 	}
199 
200 	/**
201 	 * Gets the signature string that runs along the right-side of the graph.
202 	 * @return the signature string
203 	 */
204 	public String getSignature() {
205 		return this.signature;
206 	}
207 
208 	/**
209 	 * Sets the time when the graph should begin. Time in seconds since epoch
210 	 * (1970-01-01) is required. Negative numbers are relative to the current time.
211 	 *
212 	 * @param time Starting time for the graph in seconds since epoch
213 	 */
214 	public void setStartTime(long time) {
215 		this.startTime = time;
216 		if (time <= 0) {
217 			this.startTime += Util.getTime();
218 		}
219 	}
220 
221 	/**
222 	 * Sets the time when the graph should end. Time in seconds since epoch
223 	 * (1970-01-01) is required. Negative numbers are relative to the current time.
224 	 *
225 	 * @param time Ending time for the graph in seconds since epoch
226 	 */
227 	public void setEndTime(long time) {
228 		this.endTime = time;
229 		if (time <= 0) {
230 			this.endTime += Util.getTime();
231 		}
232 	}
233 
234 	/**
235 	 * Sets starting and ending time for the for the graph. Timestamps in seconds since epoch are
236 	 * required. Negative numbers are relative to the current time.
237 	 *
238 	 * @param startTime Starting time in seconds since epoch
239 	 * @param endTime   Ending time in seconds since epoch
240 	 */
241 	public void setTimeSpan(long startTime, long endTime) {
242 		setStartTime(startTime);
243 		setEndTime(endTime);
244 	}
245 
246 	/**
247 	 * Sets starting and ending time for the for the graph. Timestamps in seconds since epoch are
248 	 * required.
249 	 *
250 	 * @param timestamps Array of timestamps. The first array item will be chosen for the starting
251 	 *                   timestamp. The last array item will be chosen for the ending timestamp.
252 	 */
253 	public void setTimeSpan(long[] timestamps) {
254 		setTimeSpan(timestamps[0], timestamps[timestamps.length - 1]);
255 	}
256 
257 	/**
258 	 * Sets RrdDbPool usage policy (defaults to true). If set to true,
259 	 * {@link org.jrobin.core.RrdDbPool RrdDbPool} will be used to
260 	 * access individual RRD files. If set to false, RRD files will be accessed directly.
261 	 *
262 	 * @param poolUsed true, if RrdDbPool class should be used. False otherwise.
263 	 */
264 	public void setPoolUsed(boolean poolUsed) {
265 		this.poolUsed = poolUsed;
266 	}
267 
268 	/**
269 	 * Sets the name of the graph to generate. Since JRobin outputs GIFs, PNGs,
270 	 * and JPEGs it's recommended that the filename end in either .gif,
271 	 * .png or .jpg. JRobin does not enforce this, however. If the filename is
272 	 * set to '-' the image will be created only in memory (no file will be created).
273 	 * PNG and GIF formats are recommended but JPEGs should be avoided.
274 	 *
275 	 * @param filename Path to the image file
276 	 */
277 	public void setFilename(String filename) {
278 		this.filename = filename;
279 	}
280 
281 	/**
282 	 * Configures x-axis grid and labels. The x-axis label is quite complex to configure.
283 	 * So if you don't have very special needs, you can rely on the autoconfiguration to
284 	 * get this right.
285 	 * <p/>
286 	 * Otherwise, you have to configure three elements making up the x-axis labels
287 	 * and grid. The base grid, the major grid and the labels.
288 	 * The configuration is based on the idea that you first specify a well
289 	 * known amount of time and then say how many times
290 	 * it has to pass between each minor/major grid line or label. For the label
291 	 * you have to define two additional items: The precision of the label
292 	 * in seconds and the format used to generate the text
293 	 * of the label.
294 	 * <p/>
295 	 * For example, if you wanted a graph with a base grid every 10 minutes and a major
296 	 * one every hour, with labels every hour you would use the following
297 	 * x-axis definition.
298 	 * <p/>
299 	 * <pre>
300 	 * setTimeAxis(RrdGraphConstants.MINUTE, 10,
301 	 *             RrdGraphConstants.HOUR, 1,
302 	 *             RrdGraphConstants.HOUR, 1,
303 	 *             0, "%H:%M")
304 	 * </pre>
305 	 * <p/>
306 	 * The precision in this example is 0 because the %X format is exact.
307 	 * If the label was the name of the day, we would have had a precision
308 	 * of 24 hours, because when you say something like 'Monday' you mean
309 	 * the whole day and not Monday morning 00:00. Thus the label should
310 	 * be positioned at noon. By defining a precision of 24 hours or
311 	 * rather 86400 seconds, you make sure that this happens.
312 	 *
313 	 * @param minorUnit		Minor grid unit. Minor grid, major grid and label units
314 	 *                         can be one of the following constants defined in
315 	 *                         {@link RrdGraphConstants}: {@link RrdGraphConstants#SECOND SECOND},
316 	 *                         {@link RrdGraphConstants#MINUTE MINUTE}, {@link RrdGraphConstants#HOUR HOUR},
317 	 *                         {@link RrdGraphConstants#DAY DAY}, {@link RrdGraphConstants#WEEK WEEK},
318 	 *                         {@link RrdGraphConstants#MONTH MONTH}, {@link RrdGraphConstants#YEAR YEAR}.
319 	 * @param minorUnitCount   Number of minor grid units between minor grid lines.
320 	 * @param majorUnit		Major grid unit.
321 	 * @param majorUnitCount   Number of major grid units between major grid lines.
322 	 * @param labelUnit		Label unit.
323 	 * @param labelUnitCount   Number of label units between labels.
324 	 * @param labelSpan		Label precision
325 	 * @param simpleDateFormat Date format (SimpleDateFormat pattern of strftime-like pattern)
326 	 */
327 	public void setTimeAxis(int minorUnit, int minorUnitCount, int majorUnit, int majorUnitCount,
328 							int labelUnit, int labelUnitCount, int labelSpan, String simpleDateFormat) {
329 		timeAxisSetting = new TimeAxisSetting(minorUnit, minorUnitCount, majorUnit, majorUnitCount,
330 				labelUnit, labelUnitCount, labelSpan, simpleDateFormat);
331 	}
332 
333 	/**
334 	 * Sets vertical axis grid and labels. Makes vertical grid lines appear
335 	 * at gridStep interval. Every labelFactor*gridStep, a major grid line is printed,
336 	 * along with label showing the value of the grid line.
337 	 *
338 	 * @param gridStep	Minor grid step
339 	 * @param labelFactor Specifies how many minor minor grid steps will appear between labels
340 	 *                    (major grid lines)
341 	 */
342 	public void setValueAxis(double gridStep, int labelFactor) {
343 		valueAxisSetting = new ValueAxisSetting(gridStep, labelFactor);
344 	}
345 
346 	/**
347 	 * Places Y grid dynamically based on graph Y range. Algorithm ensures
348 	 * that you always have grid, that there are enough but not too many
349 	 * grid lines and the grid is metric. That is grid lines are placed
350 	 * every 1, 2, 5 or 10 units.
351 	 *
352 	 * @param altYGrid true, if Y grid should be calculated dynamically (defaults to false)
353 	 */
354 	public void setAltYGrid(boolean altYGrid) {
355 		this.altYGrid = altYGrid;
356 	}
357 
358 	/**
359 	 * Use this method to turn off minor grid lines (printed by default)
360 	 *
361 	 * @param noMinorGrid true, to turn off, false to turn on (default)
362 	 */
363 	public void setNoMinorGrid(boolean noMinorGrid) {
364 		this.noMinorGrid = noMinorGrid;
365 	}
366 
367 	/**
368 	 * Use this method to request MRTG-like graph (false by default)
369 	 *
370 	 * @param altYMrtg true, to create MRTG-like graph, false otherwise (default)
371 	 */
372 	public void setAltYMrtg(boolean altYMrtg) {
373 		this.altYMrtg = altYMrtg;
374 	}
375 
376 	/**
377 	 * Computes Y range based on function absolute minimum and maximum
378 	 * values. Default algorithm uses predefined set of ranges.  This is
379 	 * good in many cases but it fails miserably when you need to graph
380 	 * something like 260 + 0.001 * sin(x). Default algorithm will use Y
381 	 * range from 250 to 300 and on the graph you will see almost straight
382 	 * line. With --alt-autoscale Y range will be from slightly less the
383 	 * 260 - 0.001 to slightly more then 260 + 0.001 and periodic behavior
384 	 * will be seen.
385 	 *
386 	 * @param altAutoscale true to request alternative autoscaling, false otherwise
387 	 *                     (default).
388 	 */
389 	public void setAltAutoscale(boolean altAutoscale) {
390 		this.altAutoscale = altAutoscale;
391 	}
392 
393 	/**
394 	 * Computes Y range based on function absolute minimum and maximum
395 	 * values. Where setAltAutoscale(true) will modify both the absolute maximum AND
396 	 * minimum values, this option will only affect the maximum value. The
397 	 * minimum value, if not defined elsewhere, will be 0. This
398 	 * option can be useful when graphing router traffic when the WAN line
399 	 * uses compression, and thus the throughput may be higher than the
400 	 * WAN line speed.
401 	 *
402 	 * @param altAutoscaleMax true to request alternative autoscaling, false
403 	 *                        otherwise (default)
404 	 */
405 	public void setAltAutoscaleMax(boolean altAutoscaleMax) {
406 		this.altAutoscaleMax = altAutoscaleMax;
407 	}
408 
409 	/**
410 	 * Sets the 10**unitsExponent scaling of the y-axis values. Normally
411 	 * values will be scaled to the appropriate units (k, M, etc.). However
412 	 * you may wish to display units always in k (Kilo, 10e3) even if
413 	 * the data is in the M (Mega, 10e6) range for instance.  Value should
414 	 * be an integer which is a multiple of 3 between -18 and 18, inclu-
415 	 * sive. It is the exponent on the units you which to use.  For example,
416 	 * use 3 to display the y-axis values in k (Kilo, 10e3, thou-
417 	 * sands), use -6 to display the y-axis values in u (Micro, 10e-6,
418 	 * millionths). Use a value of 0 to prevent any scaling of the y-axis
419 	 * values.
420 	 *
421 	 * @param unitsExponent
422 	 */
423 	public void setUnitsExponent(int unitsExponent) {
424 		this.unitsExponent = unitsExponent;
425 	}
426 
427 	/**
428 	 * Sets the character width on the left side of the graph for
429 	 * y-axis values.
430 	 *
431 	 * @param unitsLength Number of characters on the left side of the graphs
432 	 *                    reserved for vertical axis labels.
433 	 */
434 	public void setUnitsLength(int unitsLength) {
435 		this.unitsLength = unitsLength;
436 	}
437 
438 	/**
439 	 * Sets vertical label on the left side of the graph. This is normally used
440 	 * to specify the units used.
441 	 *
442 	 * @param verticalLabel Vertical axis label
443 	 */
444 	public void setVerticalLabel(String verticalLabel) {
445 		this.verticalLabel = verticalLabel;
446 	}
447 
448 	/**
449 	 * Sets width of the drawing area within the graph. This affects the total
450 	 * size of the image.
451 	 *
452 	 * @param width Width of the drawing area.
453 	 */
454 	public void setWidth(int width) {
455 		this.width = width;
456 	}
457 
458 	/**
459 	 * Sets height of the drawing area within the graph. This affects the total
460 	 * size of the image.
461 	 *
462 	 * @param height Height of the drawing area.
463 	 */
464 	public void setHeight(int height) {
465 		this.height = height;
466 	}
467 
468 	/**
469 	 * Creates interlaced GIF image (currently not supported,
470 	 * method is present only for RRDTool comaptibility).
471 	 *
472 	 * @param interlaced true, if GIF image should be interlaced.
473 	 */
474 	public void setInterlaced(boolean interlaced) {
475 		this.interlaced = interlaced;
476 	}
477 
478 	/**
479 	 * Creates additional image information.
480 	 * After the image has been created, the graph function uses imageInfo
481 	 * format string (printf-like) to create output similar to
482 	 * the {@link #print(String, String, String)} function.
483 	 * The format string is supplied with the following parameters:
484 	 * filename, xsize and ysize (in that particular order).
485 	 * <p/>
486 	 * For example, in order to generate an IMG tag
487 	 * suitable for including the graph into a web page, the command
488 	 * would look like this:
489 	 * <pre>
490 	 * setImageInfo(&quot;&lt;IMG SRC='/img/%s' WIDTH='%d' HEIGHT='%d' ALT='Demo'&gt;&quot;);
491 	 * </pre>
492 	 *
493 	 * @param imageInfo Image info format. Use %s placeholder for filename, %d placeholder for
494 	 *                  image width and height.
495 	 */
496 	public void setImageInfo(String imageInfo) {
497 		this.imageInfo = imageInfo;
498 	}
499 
500 	/**
501 	 * Sets image format.
502 	 *
503 	 * @param imageFormat "PNG", "GIF" or "JPG".
504 	 */
505 	public void setImageFormat(String imageFormat) {
506 		this.imageFormat = imageFormat;
507 	}
508 
509 	/**
510 	 * Sets background image - currently, only PNG images can be used as background.
511 	 *
512 	 * @param backgroundImage Path to background image
513 	 */
514 	public void setBackgroundImage(String backgroundImage) {
515 		this.backgroundImage = backgroundImage;
516 	}
517 
518 	/**
519 	 * Sets overlay image - currently, only PNG images can be used as overlay. Overlay image is
520 	 * printed on the top of the image, once it is completely created.
521 	 *
522 	 * @param overlayImage Path to overlay image
523 	 */
524 	public void setOverlayImage(String overlayImage) {
525 		this.overlayImage = overlayImage;
526 	}
527 
528 	/**
529 	 * Sets unit to be displayed on y axis. It is wise to use only short units on graph, however.
530 	 *
531 	 * @param unit Unit description
532 	 */
533 	public void setUnit(String unit) {
534 		this.unit = unit;
535 	}
536 
537 	/**
538 	 * Creates graph only if the current graph is out of date or not existent.
539 	 *
540 	 * @param lazy true, if graph should be 'lazy', false otherwise (defualt)
541 	 */
542 	public void setLazy(boolean lazy) {
543 		this.lazy = lazy;
544 	}
545 
546 	/**
547 	 * Sets the lower limit of a graph. But rather, this is the
548 	 * maximum lower bound of a graph. For example, the value -100 will
549 	 * result in a graph that has a lower limit of -100 or less.  Use this
550 	 * method to expand graphs down.
551 	 *
552 	 * @param minValue Minimal value displayed on the graph
553 	 */
554 	public void setMinValue(double minValue) {
555 		this.minValue = minValue;
556 	}
557 
558 	/**
559 	 * Defines the value normally located at the upper border of the
560 	 * graph. If the graph contains higher values, the upper border will
561 	 * move upwards to accommodate these values as well.
562 	 * <p/>
563 	 * If you want to define an upper-limit which will not move in any
564 	 * event you have to use {@link #setRigid(boolean)} method as well.
565 	 *
566 	 * @param maxValue Maximal value displayed on the graph.
567 	 */
568 	public void setMaxValue(double maxValue) {
569 		this.maxValue = maxValue;
570 	}
571 
572 	/**
573 	 * Sets rigid boundaries mode. Normally JRObin will automatically expand
574 	 * the lower and upper limit if the graph contains a value outside the
575 	 * valid range. With the <code>true</code> argument you can disable this behavior.
576 	 *
577 	 * @param rigid true if uper and lower limits should not be expanded to accomodate
578 	 *              values outside of the specified range. False otherwise (default).
579 	 */
580 	public void setRigid(boolean rigid) {
581 		this.rigid = rigid;
582 	}
583 
584 	/**
585 	 * Sets default base for magnitude scaling. If you are graphing memory
586 	 * (and NOT network traffic) this switch should be set to 1024 so that 1Kb is 1024 byte.
587 	 * For traffic measurement, 1 kb/s is 1000 b/s.
588 	 *
589 	 * @param base Base value (defaults to 1000.0)
590 	 */
591 	public void setBase(double base) {
592 		this.base = base;
593 	}
594 
595 	/**
596 	 * Sets logarithmic y-axis scaling.
597 	 *
598 	 * @param logarithmic true, for logarithmic scaling, false otherwise (default).
599 	 */
600 	public void setLogarithmic(boolean logarithmic) {
601 		this.logarithmic = logarithmic;
602 	}
603 
604 	/**
605      * Overrides the colors for the standard elements of the graph. The colorTag
606      * must be one of the following constants defined in the
607      * {@link RrdGraphConstants}:
608      * {@link RrdGraphConstants#COLOR_BACK COLOR_BACK} background,
609      * {@link RrdGraphConstants#COLOR_CANVAS COLOR_CANVAS} canvas,
610      * {@link RrdGraphConstants#COLOR_SHADEA COLOR_SHADEA} left/top border,
611      * {@link RrdGraphConstants#COLOR_SHADEB COLOR_SHADEB} right/bottom border,
612      * {@link RrdGraphConstants#COLOR_GRID COLOR_GRID} major grid,
613      * {@link RrdGraphConstants#COLOR_MGRID COLOR_MGRID} minor grid,
614      * {@link RrdGraphConstants#COLOR_FONT COLOR_FONT} font,
615      * {@link RrdGraphConstants#COLOR_FRAME COLOR_FRAME} axis of the graph,
616      * {@link RrdGraphConstants#COLOR_ARROW COLOR_ARROW} arrow. This method can
617      * be called multiple times to set several colors.
618      * 
619      * @param colorTag
620      *            Color tag, as explained above.
621      * @param color
622      *            Any color (paint) you like
623      * @throws RrdException
624      *             Thrown if invalid colorTag is supplied.
625      */
626     public void setColor(int colorTag, Paint color) throws RrdException {
627         if (colorTag >= 0 && colorTag < colors.length) {
628             colors[colorTag] = color;
629         } else {
630             throw new RrdException("Invalid color index specified: " + colorTag);
631         }
632     }
633 
634 	/**
635 	 * Overrides the colors for the standard elements of the graph by element name.
636 	 * See {@link #setColor(int, java.awt.Paint)} for full explanation.
637 	 *
638 	 * @param colorName One of the following strings: "BACK", "CANVAS", "SHADEA", "SHADEB",
639 	 *                  "GRID", "MGRID", "FONT", "FRAME", "ARROW"
640 	 * @param color	 Any color (paint) you like
641 	 * @throws RrdException Thrown if invalid element name is supplied.
642 	 */
643 	public void setColor(String colorName, Paint color) throws RrdException {
644 		setColor(getColorTagByName(colorName), color);
645 	}
646 
647 	private static int getColorTagByName(String colorName) throws RrdException {
648 		for (int i = 0; i < COLOR_NAMES.length; i++) {
649 			if (COLOR_NAMES[i].equalsIgnoreCase(colorName)) {
650 				return i;
651 			}
652 		}
653 		throw new RrdException("Unknown color name specified: " + colorName);
654 	}
655 
656 	/**
657 	 * Suppress generation of legend, only render the graph.
658 	 *
659 	 * @param noLegend true if graph legend should be omitted. False otherwise (default).
660 	 */
661 	public void setNoLegend(boolean noLegend) {
662 		this.noLegend = noLegend;
663 	}
664 
665 	/**
666 	 * Suppresses anything but the graph, works only for height < 64.
667 	 *
668 	 * @param onlyGraph true if only graph should be created, false otherwise (default).
669 	 */
670 	public void setOnlyGraph(boolean onlyGraph) {
671 		this.onlyGraph = onlyGraph;
672 	}
673 
674 	/**
675 	 * Force the generation of HRULE and VRULE legend even if those HRULE
676 	 * or VRULE will not be drawn because out of graph boundaries.
677 	 *
678 	 * @param forceRulesLegend true if rule legend should be always printed,
679 	 *                         false otherwise (default).
680 	 */
681 	public void setForceRulesLegend(boolean forceRulesLegend) {
682 		this.forceRulesLegend = forceRulesLegend;
683 	}
684 
685 	/**
686 	 * Defines a title to be written into the graph.
687 	 *
688 	 * @param title Graph title.
689 	 */
690 	public void setTitle(String title) {
691 		this.title = title;
692 	}
693 
694 	/**
695 	 * Suggests which time step should be used by JRobin while processing data from RRD files.
696 	 *
697 	 * @param step Desired time step (don't use this method if you don't know what you're doing).
698 	 */
699 	public void setStep(long step) {
700 		this.step = step;
701 	}
702 
703 	/**
704 	 * Get the default small font for graphing.
705 	 * @return the font
706 	 */
707 	public Font getSmallFont() {
708 		return this.smallFont;
709 	}
710 
711 	/**
712 	 * Get the default large font for graphing.
713 	 * @return the font
714 	 */
715 	public Font getLargeFont() {
716 		return this.largeFont;
717 	}
718 	
719 
720 	/**
721 	 * Sets default font for graphing. Note that JRobin will behave unpredictably if proportional
722 	 * font is selected.
723 	 *
724 	 * @param smallFont Default font for graphing. Use only monospaced fonts.
725 	 */
726 	public void setSmallFont(Font smallFont) {
727 		this.smallFont = smallFont;
728 	}
729 
730 	/**
731 	 * Sets title font.
732 	 *
733 	 * @param largeFont Font to be used for graph title.
734 	 */
735 	public void setLargeFont(Font largeFont) {
736 		this.largeFont = largeFont;
737 	}
738 
739 	/**
740 	 * Defines virtual datasource. This datasource can then be used
741 	 * in other methods like {@link #datasource(String, String)} or
742 	 * {@link #gprint(String, String, String)}.
743 	 *
744 	 * @param name	  Source name
745 	 * @param rrdPath   Path to RRD file
746 	 * @param dsName	Datasource name in the specified RRD file
747 	 * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST)
748 	 */
749 	public void datasource(String name, String rrdPath, String dsName, String consolFun) {
750 		sources.add(new Def(name, rrdPath, dsName, consolFun));
751 	}
752 
753 	/**
754 	 * Defines virtual datasource. This datasource can then be used
755 	 * in other methods like {@link #datasource(String, String)} or
756 	 * {@link #gprint(String, String, String)}.
757 	 *
758 	 * @param name	  Source name
759 	 * @param rrdPath   Path to RRD file
760 	 * @param dsName	Datasource name in the specified RRD file
761 	 * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST)
762 	 * @param backend   Backend to be used while fetching data from a RRD file.
763 	 */
764 	public void datasource(String name, String rrdPath, String dsName, String consolFun, String backend) {
765 		sources.add(new Def(name, rrdPath, dsName, consolFun, backend));
766 	}
767 
768 	/**
769 	 * Create a new virtual datasource by evaluating a mathematical
770 	 * expression, specified in Reverse Polish Notation (RPN).
771 	 *
772 	 * @param name		  Source name
773 	 * @param rpnExpression RPN expression.
774 	 */
775 	public void datasource(String name, String rpnExpression) {
776 		sources.add(new CDef(name, rpnExpression));
777 	}
778 
779 	/**
780 	 * Creates a new (static) virtual datasource. The value of the datasource is constant. This value is
781 	 * evaluated by applying the given consolidation function to another virtual datasource.
782 	 *
783 	 * @param name	  Source name
784 	 * @param defName   Other source name
785 	 * @param consolFun Consolidation function to be applied to other datasource.
786 	 */
787 	public void datasource(String name, String defName, String consolFun) {
788 		sources.add(new SDef(name, defName, consolFun));
789 	}
790 
791 	/**
792 	 * Creates a new (plottable) datasource. Datasource values are obtained from the given plottable
793 	 * object.
794 	 *
795 	 * @param name	  Source name.
796 	 * @param plottable Plottable object.
797 	 */
798 	public void datasource(String name, Plottable plottable) {
799 		sources.add(new PDef(name, plottable));
800 	}
801 
802 	/**
803 	 * Calculates the chosen consolidation function CF over the given datasource
804 	 * and creates the result by using the given format string.  In
805 	 * the format string there should be a '%[l]f', '%[l]g' or '%[l]e' marker in
806 	 * the place where the number should be printed.
807 	 * <p/>
808 	 * If an additional '%s' is found AFTER the marker, the value will be
809 	 * scaled and an appropriate SI magnitude unit will be printed in
810 	 * place of the '%s' marker. The scaling will take the '--base' argument
811 	 * into consideration!
812 	 * <p/>
813 	 * If a '%S' is used instead of a '%s', then instead of calculating
814 	 * the appropriate SI magnitude unit for this value, the previously
815 	 * calculated SI magnitude unit will be used.  This is useful if you
816 	 * want all the values in a print statement to have the same SI magnitude
817 	 * unit.  If there was no previous SI magnitude calculation made,
818 	 * then '%S' behaves like a '%s', unless the value is 0, in which case
819 	 * it does not remember a SI magnitude unit and a SI magnitude unit
820 	 * will only be calculated when the next '%s' is seen or the next '%S'
821 	 * for a non-zero value.
822 	 * <p/>
823 	 * Print results are collected in the {@link RrdGraphInfo} object which is retrieved
824 	 * from the {@link RrdGraph object} once the graph is created.
825 	 *
826 	 * @param srcName   Virtual source name
827 	 * @param consolFun Consolidation function to be applied to the source
828 	 * @param format	Format string (like "average = %10.3f %s")
829 	 */
830 	public void print(String srcName, String consolFun, String format) {
831 		comments.add(new PrintText(srcName, consolFun, format, false));
832 	}
833 
834 	/**
835 	 * This method does basically the same thing as {@link #print(String, String, String)},
836 	 * but the result is printed on the graph itself, below the chart area.
837 	 *
838 	 * @param srcName   Virtual source name
839 	 * @param consolFun Consolidation function to be applied to the source
840 	 * @param format	Format string (like "average = %10.3f %s")
841 	 */
842 	public void gprint(String srcName, String consolFun, String format) {
843 		comments.add(new PrintText(srcName, consolFun, format, true));
844 	}
845 
846 	/**
847 	 * Comment to be printed on the graph.
848 	 *
849 	 * @param text Comment text
850 	 */
851 	public void comment(String text) {
852 		comments.add(new CommentText(text));
853 	}
854 
855 	/**
856 	 * Draws a horizontal rule into the graph and optionally adds a legend
857 	 *
858 	 * @param value  Position of the rule
859 	 * @param color  Rule color
860 	 * @param legend Legend text. If null, legend text will be omitted.
861 	 */
862 	public void hrule(double value, Paint color, String legend) {
863 		hrule(value, color, legend, 1.0F);
864 	}
865 
866 	/**
867 	 * Draws a horizontal rule into the graph and optionally adds a legend
868 	 *
869 	 * @param value  Position of the rule
870 	 * @param color  Rule color
871 	 * @param legend Legend text. If null, legend text will be omitted.
872 	 * @param width  Rule width
873 	 */
874 	public void hrule(double value, Paint color, String legend, float width) {
875 		LegendText legendText = new LegendText(color, legend);
876 		comments.add(legendText);
877 		plotElements.add(new HRule(value, color, legendText, width));
878 	}
879 
880 	/**
881 	 * Draws a vertical rule into the graph and optionally adds a legend
882 	 *
883 	 * @param timestamp Position of the rule (seconds since epoch)
884 	 * @param color	 Rule color
885 	 * @param legend	Legend text. Use null to omit the text.
886 	 */
887 	public void vrule(long timestamp, Paint color, String legend) {
888 		vrule(timestamp, color, legend, 1.0F);
889 	}
890 
891 	/**
892 	 * Draws a vertical rule into the graph and optionally adds a legend
893 	 *
894 	 * @param timestamp Position of the rule (seconds since epoch)
895 	 * @param color	 Rule color
896 	 * @param legend	Legend text. Use null to omit the text.
897 	 * @param width	 Rule width
898 	 */
899 	public void vrule(long timestamp, Paint color, String legend, float width) {
900 		LegendText legendText = new LegendText(color, legend);
901 		comments.add(legendText);
902 		plotElements.add(new VRule(timestamp, color, legendText, width));
903 	}
904 
905 	/**
906 	 * Plots requested data as a line, using the color and the line width specified.
907 	 *
908 	 * @param srcName Virtual source name
909 	 * @param color   Line color
910 	 * @param legend  Legend text
911 	 * @param width   Line width (default: 1.0F)
912 	 */
913 	public void line(String srcName, Paint color, String legend, float width) {
914 		LegendText legendText = new LegendText(color, legend);
915 		comments.add(legendText);
916 		plotElements.add(new Line(srcName, color, width));
917 	}
918 
919 	/**
920 	 * Plots requested data as a line, using the color specified. Line width is assumed to be
921 	 * 1.0F.
922 	 *
923 	 * @param srcName Virtual source name
924 	 * @param color   Line color
925 	 * @param legend  Legend text
926 	 */
927 	public void line(String srcName, Paint color, String legend) {
928 		line(srcName, color, legend, 1F);
929 	}
930 
931 	/**
932      * Plots requested data in the form of the filled area starting from zero,
933      * using the color specified.
934      * 
935      * @param srcName
936      *            Virtual source name.
937      * @param color
938      *            Color of the filled area.
939      * @param legend
940      *            Legend text.
941      */
942     public void area(String srcName, Paint color, String legend) {
943         area(srcName, color);
944         if (legend.length() > 0) {
945             LegendText legendText = new LegendText(color, legend);
946             comments.add(legendText);
947         }
948     }
949 
950     /**
951      * Plots requested data in the form of the filled area starting from zero,
952      * using the color specified.
953      * 
954      * @param srcName
955      *            Virtual source name.
956      * @param color
957      *            Color of the filled area.
958      */
959     public void area(String srcName, Paint color) {
960         plotElements.add(new Area(srcName, color));
961     }
962 	
963 	/**
964 	 * Does the same as {@link #line(String, java.awt.Paint, String)},
965 	 * but the graph gets stacked on top of the
966 	 * previous LINE, AREA or STACK graph. Depending on the type of the
967 	 * previous graph, the STACK will be either a LINE or an AREA.  This
968 	 * obviously implies that the first STACK must be preceded by an AREA
969 	 * or LINE.
970 	 * <p/>
971 	 * Note, that when you STACK onto *UNKNOWN* data, JRobin will not
972 	 * draw any graphics ... *UNKNOWN* is not zero.
973 	 *
974 	 * @param srcName Virtual source name
975 	 * @param color   Stacked graph color
976 	 * @param legend  Legend text
977 	 * @throws RrdException Thrown if this STACK has no previously defined AREA, STACK or LINE
978 	 *                      graph bellow it.
979 	 */
980 	public void stack(String srcName, Paint color, String legend) throws RrdException {
981 		// find parent AREA or LINE
982 		SourcedPlotElement parent = null;
983 		for (int i = plotElements.size() - 1; i >= 0; i--) {
984 			PlotElement plotElement = plotElements.get(i);
985 			if (plotElement instanceof SourcedPlotElement) {
986 				parent = (SourcedPlotElement) plotElement;
987 				break;
988 			}
989 		}
990 		if (parent == null) {
991 			throw new RrdException("You have to stack graph onto something (line or area)");
992 		}
993 		else {
994 			LegendText legendText = new LegendText(color, legend);
995 			comments.add(legendText);
996 			plotElements.add(new Stack(parent, srcName, color));
997 		}
998 	}
999 
1000 	/**
1001 	 * Sets visibility of the X-axis grid.
1002 	 *
1003 	 * @param drawXGrid True if X-axis grid should be created (default), false otherwise.
1004 	 */
1005 	public void setDrawXGrid(boolean drawXGrid) {
1006 		this.drawXGrid = drawXGrid;
1007 	}
1008 
1009 	/**
1010 	 * Sets visibility of the Y-axis grid.
1011 	 *
1012 	 * @param drawYGrid True if Y-axis grid should be created (default), false otherwise.
1013 	 */
1014 	public void setDrawYGrid(boolean drawYGrid) {
1015 		this.drawYGrid = drawYGrid;
1016 	}
1017 
1018 	/**
1019 	 * Sets image quality. Relevant only for JPEG images.
1020 	 *
1021 	 * @param imageQuality (0F=worst, 1F=best).
1022 	 */
1023 	public void setImageQuality(float imageQuality) {
1024 		this.imageQuality = imageQuality;
1025 	}
1026 
1027 	/**
1028 	 * Controls if the chart area of the image should be antialiased or not.
1029 	 *
1030 	 * @param antiAliasing use true to turn antialiasing on, false to turn it off (default)
1031 	 */
1032 	public void setAntiAliasing(boolean antiAliasing) {
1033 		this.antiAliasing = antiAliasing;
1034 	}
1035 
1036 	/**
1037 	 * Shows or hides graph signature (gator) in the top right corner of the graph
1038 	 *
1039 	 * @param showSignature true, if signature should be seen (default), false otherwise
1040 	 */
1041 	public void setShowSignature(boolean showSignature) {
1042 		this.showSignature = showSignature;
1043 	}
1044 
1045 	/**
1046 	 * Sets first day of the week.
1047 	 *
1048 	 * @param firstDayOfWeek One of the following constants:
1049 	 *                       {@link RrdGraphConstants#MONDAY MONDAY},
1050 	 *                       {@link RrdGraphConstants#TUESDAY TUESDAY},
1051 	 *                       {@link RrdGraphConstants#WEDNESDAY WEDNESDAY},
1052 	 *                       {@link RrdGraphConstants#THURSDAY THURSDAY},
1053 	 *                       {@link RrdGraphConstants#FRIDAY FRIDAY},
1054 	 *                       {@link RrdGraphConstants#SATURDAY SATURDAY},
1055 	 *                       {@link RrdGraphConstants#SUNDAY SUNDAY}
1056 	 */
1057 	public void setFirstDayOfWeek(int firstDayOfWeek) {
1058 		this.firstDayOfWeek = firstDayOfWeek;
1059 	}
1060 
1061 	// helper methods
1062 
1063 	int printStatementCount() {
1064 		int count = 0;
1065 		for (CommentText comment : comments) {
1066 			if (comment instanceof PrintText) {
1067 				if (comment.isPrint()) {
1068 					count++;
1069 				}
1070 			}
1071 		}
1072 		return count;
1073 	}
1074 
1075 	boolean shouldPlot() {
1076 		if (plotElements.size() > 0) {
1077 			return true;
1078 		}
1079 		for (CommentText comment : comments) {
1080 			if (comment.isValidGraphElement()) {
1081 				return true;
1082 			}
1083 		}
1084 		return false;
1085 	}
1086 }