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 org.jrobin.core.RrdException;
28 import org.jrobin.core.Util;
29 import org.jrobin.data.DataProcessor;
30
31 import javax.swing.*;
32 import java.awt.*;
33 import java.io.IOException;
34
35 /**
36  * Class which actually creates JRobin graphs (does the hard work).
37  */

38 public class RrdGraph implements RrdGraphConstants {
39     RrdGraphDef gdef;
40     ImageParameters im = new ImageParameters();
41     DataProcessor dproc;
42     ImageWorker worker;
43     Mapper mapper;
44     RrdGraphInfo info = new RrdGraphInfo();
45     private String signature;
46
47     /**
48      * Creates graph from the corresponding {@link RrdGraphDef} object.
49      *
50      * @param gdef Graph definition
51      * @throws IOException  Thrown in case of I/O error
52      * @throws RrdException Thrown in case of JRobin related error
53      */

54     public RrdGraph(RrdGraphDef gdef) throws IOException, RrdException {
55         this.gdef = gdef;
56         signature = gdef.getSignature();
57         worker = new ImageWorker(100, 100); // Dummy worker, just to start with something
58         try {
59             createGraph();
60         }
61         finally {
62             worker.dispose();
63             worker = null;
64             dproc = null;
65         }
66     }
67
68     /**
69      * Returns complete graph information in a single object.
70      *
71      * @return Graph information (width, height, filename, image bytes, etc...)
72      */

73     public RrdGraphInfo getRrdGraphInfo() {
74         return info;
75     }
76
77     private void createGraph() throws RrdException, IOException {
78         boolean lazy = lazyCheck();
79         if (!lazy || gdef.printStatementCount() != 0) {
80             fetchData();
81             resolveTextElements();
82             if (gdef.shouldPlot() && !lazy) {
83                 calculatePlotValues();
84                 findMinMaxValues();
85                 identifySiUnit();
86                 expandValueRange();
87                 removeOutOfRangeRules();
88                 initializeLimits();
89                 placeLegends();
90                 createImageWorker();
91                 drawBackground();
92                 drawData();
93                 drawGrid();
94                 drawAxis();
95                 drawText();
96                 drawLegend();
97                 drawRules();
98                 gator();
99                 drawOverlay();
100                 saveImage();
101             }
102         }
103         collectInfo();
104     }
105
106     private void collectInfo() {
107         info.filename = gdef.filename;
108         info.width = im.xgif;
109         info.height = im.ygif;
110         for (CommentText comment : gdef.comments) {
111             if (comment instanceof PrintText) {
112                 PrintText pt = (PrintText) comment;
113                 if (pt.isPrint()) {
114                     info.addPrintLine(pt.resolvedText);
115                 }
116             }
117         }
118         if (gdef.imageInfo != null) {
119             info.imgInfo = Util.sprintf(gdef.imageInfo, gdef.filename, im.xgif, im.ygif);
120         }
121     }
122
123     private void saveImage() throws IOException {
124         if (!gdef.filename.equals("-")) {
125             info.bytes = worker.saveImage(gdef.filename, gdef.imageFormat, gdef.imageQuality);
126         }
127         else {
128             info.bytes = worker.getImageBytes(gdef.imageFormat, gdef.imageQuality);
129         }
130     }
131
132     private void drawOverlay() throws IOException {
133         if (gdef.overlayImage != null) {
134             worker.loadImage(gdef.overlayImage);
135         }
136     }
137
138     private void gator() {
139         if (!gdef.onlyGraph && gdef.showSignature) {
140             Font font = gdef.getSmallFont().deriveFont(Font.PLAIN, 9);
141             int x = (int) (im.xgif - 2 - worker.getFontAscent(font));
142             int y = 4;
143             worker.transform(x, y, Math.PI / 2);
144             worker.drawString(signature, 0, 0, font, Color.LIGHT_GRAY);
145             worker.reset();
146         }
147     }
148
149     private void drawRules() {
150         worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
151         for (PlotElement pe : gdef.plotElements) {
152             if (pe instanceof HRule) {
153                 HRule hr = (HRule) pe;
154                 if (hr.value >= im.minval && hr.value <= im.maxval) {
155                     int y = mapper.ytr(hr.value);
156                     worker.drawLine(im.xorigin, y, im.xorigin + im.xsize, y, hr.color, new BasicStroke(hr.width));
157                 }
158             }
159             else if (pe instanceof VRule) {
160                 VRule vr = (VRule) pe;
161                 if (vr.timestamp >= im.start && vr.timestamp <= im.end) {
162                     int x = mapper.xtr(vr.timestamp);
163                     worker.drawLine(x, im.yorigin, x, im.yorigin - im.ysize, vr.color, new BasicStroke(vr.width));
164                 }
165             }
166         }
167         worker.reset();
168     }
169
170     private void drawText() {
171         if (!gdef.onlyGraph) {
172             if (gdef.title != null) {
173                 int x = im.xgif / 2 - (int) (worker.getStringWidth(gdef.title, gdef.largeFont) / 2);
174                 int y = PADDING_TOP + (int) worker.getFontAscent(gdef.largeFont);
175                 worker.drawString(gdef.title, x, y, gdef.largeFont, gdef.colors[COLOR_FONT]);
176             }
177             if (gdef.verticalLabel != null) {
178                 int x = PADDING_LEFT;
179                 int y = im.yorigin - im.ysize / 2 + (int) worker.getStringWidth(gdef.verticalLabel, gdef.getSmallFont()) / 2;
180                 int ascent = (int) worker.getFontAscent(gdef.smallFont);
181                 worker.transform(x, y, -Math.PI / 2);
182                 worker.drawString(gdef.verticalLabel, 0, ascent, gdef.smallFont, gdef.colors[COLOR_FONT]);
183                 worker.reset();
184             }
185         }
186     }
187
188     private void drawGrid() {
189         if (!gdef.onlyGraph) {
190             Paint shade1 = gdef.colors[COLOR_SHADEA], shade2 = gdef.colors[COLOR_SHADEB];
191             Stroke borderStroke = new BasicStroke(1);
192             worker.drawLine(0, 0, im.xgif - 1, 0, shade1, borderStroke);
193             worker.drawLine(1, 1, im.xgif - 2, 1, shade1, borderStroke);
194             worker.drawLine(0, 0, 0, im.ygif - 1, shade1, borderStroke);
195             worker.drawLine(1, 1, 1, im.ygif - 2, shade1, borderStroke);
196             worker.drawLine(im.xgif - 1, 0, im.xgif - 1, im.ygif - 1, shade2, borderStroke);
197             worker.drawLine(0, im.ygif - 1, im.xgif - 1, im.ygif - 1, shade2, borderStroke);
198             worker.drawLine(im.xgif - 2, 1, im.xgif - 2, im.ygif - 2, shade2, borderStroke);
199             worker.drawLine(1, im.ygif - 2, im.xgif - 2, im.ygif - 2, shade2, borderStroke);
200             if (gdef.drawXGrid) {
201                 new TimeAxis(this).draw();
202             }
203             if (gdef.drawYGrid) {
204                 boolean ok;
205                 if (gdef.altYMrtg) {
206                     ok = new ValueAxisMrtg(this).draw();
207                 }
208                 else if (gdef.logarithmic) {
209                     ok = new ValueAxisLogarithmic(this).draw();
210                 }
211                 else {
212                     ok = new ValueAxis(this).draw();
213                 }
214                 if (!ok) {
215                     String msg = "No Data Found";
216                     worker.drawString(msg,
217                             im.xgif / 2 - (int) worker.getStringWidth(msg, gdef.largeFont) / 2,
218                             (2 * im.yorigin - im.ysize) / 2,
219                             gdef.largeFont, gdef.colors[COLOR_FONT]);
220                 }
221             }
222         }
223     }
224
225     private void drawData() throws RrdException {
226         worker.setAntiAliasing(gdef.antiAliasing);
227         worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2);
228         double areazero = mapper.ytr((im.minval > 0.0) ? im.minval : (im.maxval < 0.0) ? im.maxval : 0.0);
229         double[] x = xtr(dproc.getTimestamps()), lastY = null;
230         // draw line, area and stack
231         for (PlotElement plotElement : gdef.plotElements) {
232             if (plotElement instanceof SourcedPlotElement) {
233                 SourcedPlotElement source = (SourcedPlotElement) plotElement;
234                 double[] y = ytr(source.getValues());
235                 if (source instanceof Line) {
236                     worker.drawPolyline(x, y, source.color, new BasicStroke(((Line) source).width));
237                 }
238                 else if (source instanceof Area) {
239                     worker.fillPolygon(x, areazero, y, source.color);
240                 }
241                 else if (source instanceof Stack) {
242                     Stack stack = (Stack) source;
243                     float width = stack.getParentLineWidth();
244                     if (width >= 0F) {
245                         // line
246                         worker.drawPolyline(x, y, stack.color, new BasicStroke(width));
247                     }
248                     else {
249                         // area
250                         worker.fillPolygon(x, lastY, y, stack.color);
251                         worker.drawPolyline(x, lastY, stack.getParentColor(), new BasicStroke(0));
252                     }
253                 }
254                 else {
255                     // should not be here
256                     throw new RrdException("Unknown plot source: " + source.getClass().getName());
257                 }
258                 lastY = y;
259             }
260         }
261         worker.reset();
262         worker.setAntiAliasing(false);
263     }
264
265     private void drawAxis() {
266         if (!gdef.onlyGraph) {
267             Paint gridColor = gdef.colors[COLOR_GRID];
268             Paint fontColor = gdef.colors[COLOR_FONT];
269             Paint arrowColor = gdef.colors[COLOR_ARROW];
270             Stroke stroke = new BasicStroke(1);
271             worker.drawLine(im.xorigin + im.xsize, im.yorigin, im.xorigin + im.xsize, im.yorigin - im.ysize,
272                     gridColor, stroke);
273             worker.drawLine(im.xorigin, im.yorigin - im.ysize, im.xorigin + im.xsize, im.yorigin - im.ysize,
274                     gridColor, stroke);
275             worker.drawLine(im.xorigin - 4, im.yorigin, im.xorigin + im.xsize + 4, im.yorigin,
276                     fontColor, stroke);
277             worker.drawLine(im.xorigin, im.yorigin, im.xorigin, im.yorigin - im.ysize,
278                     gridColor, stroke);
279             worker.drawLine(im.xorigin + im.xsize + 4, im.yorigin - 3, im.xorigin + im.xsize + 4, im.yorigin + 3,
280                     arrowColor, stroke);
281             worker.drawLine(im.xorigin + im.xsize + 4, im.yorigin - 3, im.xorigin + im.xsize + 9, im.yorigin,
282                     arrowColor, stroke);
283             worker.drawLine(im.xorigin + im.xsize + 4, im.yorigin + 3, im.xorigin + im.xsize + 9, im.yorigin,
284                     arrowColor, stroke);
285         }
286     }
287
288     private void drawBackground() throws IOException {
289         worker.fillRect(0, 0, im.xgif, im.ygif, gdef.colors[COLOR_BACK]);
290         if (gdef.backgroundImage != null) {
291             worker.loadImage(gdef.backgroundImage);
292         }
293         worker.fillRect(im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize, gdef.colors[COLOR_CANVAS]);
294     }
295
296     private void createImageWorker() {
297         worker.resize(im.xgif, im.ygif);
298     }
299
300     private void placeLegends() {
301         if (!gdef.noLegend && !gdef.onlyGraph) {
302             int border = (int) (getSmallFontCharWidth() * PADDING_LEGEND);
303             LegendComposer lc = new LegendComposer(this, border, im.ygif, im.xgif - 2 * border);
304             im.ygif = lc.placeComments() + PADDING_BOTTOM;
305         }
306     }
307
308     private void initializeLimits() throws RrdException {
309         im.xsize = gdef.width;
310         im.ysize = gdef.height;
311         im.unitslength = gdef.unitsLength;
312         if (gdef.onlyGraph) {
313             if (im.ysize > 64) {
314                 throw new RrdException("Cannot create graph only, height too big");
315             }
316             im.xorigin = 0;
317         }
318         else {
319             im.xorigin = (int) (PADDING_LEFT + im.unitslength * getSmallFontCharWidth());
320         }
321         if (gdef.verticalLabel != null) {
322             im.xorigin += getSmallFontHeight();
323         }
324         if (gdef.onlyGraph) {
325             im.yorigin = im.ysize;
326         }
327         else {
328             im.yorigin = PADDING_TOP + im.ysize;
329         }
330         mapper = new Mapper(this);
331         if (gdef.title != null) {
332             im.yorigin += getLargeFontHeight() + PADDING_TITLE;
333         }
334         if (gdef.onlyGraph) {
335             im.xgif = im.xsize;
336             im.ygif = im.yorigin;
337         }
338         else {
339             im.xgif = PADDING_RIGHT + im.xsize + im.xorigin;
340             im.ygif = im.yorigin + (int) (PADDING_PLOT * getSmallFontHeight());
341         }
342     }
343
344     private void removeOutOfRangeRules() {
345         for (PlotElement plotElement : gdef.plotElements) {
346             if (plotElement instanceof HRule) {
347                 ((HRule) plotElement).setLegendVisibility(im.minval, im.maxval, gdef.forceRulesLegend);
348             }
349             else if (plotElement instanceof VRule) {
350                 ((VRule) plotElement).setLegendVisibility(im.start, im.end, gdef.forceRulesLegend);
351             }
352         }
353     }
354
355     private void expandValueRange() {
356         im.ygridstep = (gdef.valueAxisSetting != null) ? gdef.valueAxisSetting.gridStep : Double.NaN;
357         im.ylabfact = (gdef.valueAxisSetting != null) ? gdef.valueAxisSetting.labelFactor : 0;
358         if (!gdef.rigid && !gdef.logarithmic) {
359             double sensiblevalues[] = {
360                     1000.0, 900.0, 800.0, 750.0, 700.0, 600.0, 500.0, 400.0, 300.0, 250.0, 200.0, 125.0, 100.0,
361                     90.0, 80.0, 75.0, 70.0, 60.0, 50.0, 40.0, 30.0, 25.0, 20.0, 10.0,
362                     9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.5, 3.0, 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
363                     0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
364             };
365             double scaled_min, scaled_max, adj;
366             if (Double.isNaN(im.ygridstep)) {
367                 if (gdef.altYMrtg) { /* mrtg */
368                     im.decimals = Math.ceil(Math.log10(Math.max(Math.abs(im.maxval), Math.abs(im.minval))));
369                     im.quadrant = 0;
370                     if (im.minval < 0) {
371                         im.quadrant = 2;
372                         if (im.maxval <= 0) {
373                             im.quadrant = 4;
374                         }
375                     }
376                     switch (im.quadrant) {
377                         case 2:
378                             im.scaledstep = Math.ceil(50 * Math.pow(10, -(im.decimals)) * Math.max(Math.abs(im.maxval),
379                                     Math.abs(im.minval))) * Math.pow(10, im.decimals - 2);
380                             scaled_min = -2 * im.scaledstep;
381                             scaled_max = 2 * im.scaledstep;
382                             break;
383                         case 4:
384                             im.scaledstep = Math.ceil(25 * Math.pow(10,
385                                     -(im.decimals)) * Math.abs(im.minval)) * Math.pow(10, im.decimals - 2);
386                             scaled_min = -4 * im.scaledstep;
387                             scaled_max = 0;
388                             break;
389                         default/* quadrant 0 */
390                             im.scaledstep = Math.ceil(25 * Math.pow(10, -(im.decimals)) * im.maxval) *
391                                     Math.pow(10, im.decimals - 2);
392                             scaled_min = 0;
393                             scaled_max = 4 * im.scaledstep;
394                             break;
395                     }
396                     im.minval = scaled_min;
397                     im.maxval = scaled_max;
398                 }
399                 else if (gdef.altAutoscale) {
400                     /* measure the amplitude of the function. Make sure that
401                        graph boundaries are slightly higher then max/min vals
402                        so we can see amplitude on the graph */

403                     double delt, fact;
404
405                     delt = im.maxval - im.minval;
406                     adj = delt * 0.1;
407                     fact = 2.0 * Math.pow(10.0,
408                             Math.floor(Math.log10(Math.max(Math.abs(im.minval), Math.abs(im.maxval)))) - 2);
409                     if (delt < fact) {
410                         adj = (fact - delt) * 0.55;
411                     }
412                     im.minval -= adj;
413                     im.maxval += adj;
414                 }
415                 else if (gdef.altAutoscaleMax) {
416                     /* measure the amplitude of the function. Make sure that
417                        graph boundaries are slightly higher than max vals
418                        so we can see amplitude on the graph */

419                     adj = (im.maxval - im.minval) * 0.1;
420                     im.maxval += adj;
421                 }
422                 else {
423                     scaled_min = im.minval / im.magfact;
424                     scaled_max = im.maxval / im.magfact;
425                     for (int i = 1; sensiblevalues[i] > 0; i++) {
426                         if (sensiblevalues[i - 1] >= scaled_min && sensiblevalues[i] <= scaled_min) {
427                             im.minval = sensiblevalues[i] * im.magfact;
428                         }
429                         if (-sensiblevalues[i - 1] <= scaled_min && -sensiblevalues[i] >= scaled_min) {
430                             im.minval = -sensiblevalues[i - 1] * im.magfact;
431                         }
432                         if (sensiblevalues[i - 1] >= scaled_max && sensiblevalues[i] <= scaled_max) {
433                             im.maxval = sensiblevalues[i - 1] * im.magfact;
434                         }
435                         if (-sensiblevalues[i - 1] <= scaled_max && -sensiblevalues[i] >= scaled_max) {
436                             im.maxval = -sensiblevalues[i] * im.magfact;
437                         }
438                     }
439                 }
440             }
441             else {
442                 im.minval = (double) im.ylabfact * im.ygridstep *
443                         Math.floor(im.minval / ((double) im.ylabfact * im.ygridstep));
444                 im.maxval = (double) im.ylabfact * im.ygridstep *
445                         Math.ceil(im.maxval / ((double) im.ylabfact * im.ygridstep));
446             }
447
448         }
449     }
450
451     private void identifySiUnit() {
452         im.unitsexponent = gdef.unitsExponent;
453         im.base = gdef.base;
454         if (!gdef.logarithmic) {
455             final char symbol[] = {'a', 'f', 'p', 'n', 'u', 'm', ' ', 'k', 'M', 'G', 'T', 'P', 'E'};
456             int symbcenter = 6;
457             double digits;
458             if (im.unitsexponent != Integer.MAX_VALUE) {
459                 digits = Math.floor(im.unitsexponent / 3);
460             }
461             else {
462                 digits = Math.floor(Math.log(Math.max(Math.abs(im.minval), Math.abs(im.maxval))) / Math.log(im.base));
463             }
464             im.magfact = Math.pow(im.base, digits);
465             if (((digits + symbcenter) < symbol.length) && ((digits + symbcenter) >= 0)) {
466                 im.symbol = symbol[(int) digits + symbcenter];
467             }
468             else {
469                 im.symbol = '?';
470             }
471         }
472     }
473
474     private void findMinMaxValues() {
475         double minval = Double.NaN, maxval = Double.NaN;
476         for (PlotElement pe : gdef.plotElements) {
477             if (pe instanceof SourcedPlotElement) {
478                 minval = Util.min(((SourcedPlotElement) pe).getMinValue(), minval);
479                 maxval = Util.max(((SourcedPlotElement) pe).getMaxValue(), maxval);
480             }
481         }
482         if (Double.isNaN(minval)) {
483             minval = 0D;
484         }
485         if (Double.isNaN(maxval)) {
486             maxval = 1D;
487         }
488         im.minval = gdef.minValue;
489         im.maxval = gdef.maxValue;
490         /* adjust min and max values */
491         if (Double.isNaN(im.minval) || ((!gdef.logarithmic && !gdef.rigid) && im.minval > minval)) {
492             im.minval = minval;
493         }
494         if (Double.isNaN(im.maxval) || (!gdef.rigid && im.maxval < maxval)) {
495             if (gdef.logarithmic) {
496                 im.maxval = maxval * 1.1;
497             }
498             else {
499                 im.maxval = maxval;
500             }
501         }
502         /* make sure min is smaller than max */
503         if (im.minval > im.maxval) {
504             im.minval = 0.99 * im.maxval;
505         }
506         /* make sure min and max are not equal */
507         if (im.minval == im.maxval) {
508             im.maxval *= 1.01;
509             if (!gdef.logarithmic) {
510                 im.minval *= 0.99;
511             }
512             /* make sure min and max are not both zero */
513             if (im.maxval == 0.0) {
514                 im.maxval = 1.0;
515             }
516         }
517     }
518
519     private void calculatePlotValues() throws RrdException {
520         for (PlotElement pe : gdef.plotElements) {
521             if (pe instanceof SourcedPlotElement) {
522                 ((SourcedPlotElement) pe).assignValues(dproc);
523             }
524         }
525     }
526
527     private void resolveTextElements() throws RrdException {
528         ValueScaler valueScaler = new ValueScaler(gdef.base);
529         for (CommentText comment : gdef.comments) {
530             comment.resolveText(dproc, valueScaler);
531         }
532     }
533
534     private void fetchData() throws RrdException, IOException {
535         dproc = new DataProcessor(gdef.startTime, gdef.endTime);
536         dproc.setPoolUsed(gdef.poolUsed);
537         if (gdef.step > 0) {
538             dproc.setStep(gdef.step);
539         }
540         for (Source src : gdef.sources) {
541             src.requestData(dproc);
542         }
543         dproc.processData();
544         //long[] t = dproc.getTimestamps();
545         //im.start = t[0];
546         //im.end = t[t.length - 1];
547         im.start = gdef.startTime;
548         im.end = gdef.endTime;
549     }
550
551     private boolean lazyCheck() {
552         // redraw if lazy option is not set or file does not exist
553         if (!gdef.lazy || !Util.fileExists(gdef.filename)) {
554             return false// 'false' means 'redraw'
555         }
556         // redraw if not enough time has passed
557         long secPerPixel = (gdef.endTime - gdef.startTime) / gdef.width;
558         long elapsed = Util.getTimestamp() - Util.getLastModified(gdef.filename);
559         return elapsed <= secPerPixel;
560     }
561
562     private void drawLegend() {
563         if (!gdef.onlyGraph && !gdef.noLegend) {
564             int ascent = (int) worker.getFontAscent(gdef.smallFont);
565             int box = (int) getBox(), boxSpace = (int) (getBoxSpace());
566             for (CommentText c : gdef.comments) {
567                 if (c.isValidGraphElement()) {
568                     int x = c.x, y = c.y + ascent;
569                     if (c instanceof LegendText) {
570                         // draw with BOX
571                         worker.fillRect(x, y - box, box, box, gdef.colors[COLOR_FRAME]);
572                         worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, ((LegendText) c).legendColor);
573                         worker.drawString(c.resolvedText, x + boxSpace, y, gdef.smallFont, gdef.colors[COLOR_FONT]);
574                     }
575                     else {
576                         worker.drawString(c.resolvedText, x, y, gdef.smallFont, gdef.colors[COLOR_FONT]);
577                     }
578                 }
579             }
580         }
581     }
582
583     // helper methods
584
585     double getSmallFontHeight() {
586         return worker.getFontHeight(gdef.smallFont);
587     }
588
589     private double getLargeFontHeight() {
590         return worker.getFontHeight(gdef.largeFont);
591     }
592
593     private double getSmallFontCharWidth() {
594         return worker.getStringWidth("a", gdef.smallFont);
595     }
596
597     double getInterlegendSpace() {
598         return getSmallFontCharWidth() * LEGEND_INTERSPACING;
599     }
600
601     double getLeading() {
602         return getSmallFontHeight() * LEGEND_LEADING;
603     }
604
605     double getSmallLeading() {
606         return getSmallFontHeight() * LEGEND_LEADING_SMALL;
607     }
608
609     double getBoxSpace() {
610         return Math.ceil(getSmallFontHeight() * LEGEND_BOX_SPACE);
611     }
612
613     private double getBox() {
614         return getSmallFontHeight() * LEGEND_BOX;
615     }
616
617     double[] xtr(long[] timestamps) {
618         /*
619         double[] timestampsDev = new double[timestamps.length];
620         for (int i = 0; i < timestamps.length; i++) {
621             timestampsDev[i] = mapper.xtr(timestamps[i]);
622         }
623         return timestampsDev;
624         */

625         double[] timestampsDev = new double[2 * timestamps.length - 1];
626         for (int i = 0, j = 0; i < timestamps.length; i += 1, j += 2) {
627             timestampsDev[j] = mapper.xtr(timestamps[i]);
628             if (i < timestamps.length - 1) {
629                 timestampsDev[j + 1] = timestampsDev[j];
630             }
631         }
632         return timestampsDev;
633     }
634
635     double[] ytr(double[] values) {
636         /*
637         double[] valuesDev = new double[values.length];
638         for (int i = 0; i < values.length; i++) {
639             if (Double.isNaN(values[i])) {
640                 valuesDev[i] = Double.NaN;
641             }
642             else {
643                 valuesDev[i] = mapper.ytr(values[i]);
644             }
645         }
646         return valuesDev;
647         */

648         double[] valuesDev = new double[2 * values.length - 1];
649         for (int i = 0, j = 0; i < values.length; i += 1, j += 2) {
650             if (Double.isNaN(values[i])) {
651                 valuesDev[j] = Double.NaN;
652             }
653             else {
654                 valuesDev[j] = mapper.ytr(values[i]);
655             }
656             if (j > 0) {
657                 valuesDev[j - 1] = valuesDev[j];
658             }
659         }
660         return valuesDev;
661     }
662
663     /**
664      * Renders this graph onto graphing device
665      *
666      * @param g Graphics handle
667      */

668     public void render(Graphics g) {
669         byte[] imageData = getRrdGraphInfo().getBytes();
670         ImageIcon image = new ImageIcon(imageData);
671         image.paintIcon(null, g, 0, 0);
672     }
673 }
674