001    /* ============================================================
002     * JRobin : Pure java implementation of RRDTool's functionality
003     * ============================================================
004     *
005     * Project Info:  http://www.jrobin.org
006     * Project Lead:  Sasa Markovic (saxon@jrobin.org)
007     *
008     * Developers:    Sasa Markovic (saxon@jrobin.org)
009     *
010     *
011     * (C) Copyright 2003-2005, by Sasa Markovic.
012     *
013     * This library is free software; you can redistribute it and/or modify it under the terms
014     * of the GNU Lesser General Public License as published by the Free Software Foundation;
015     * either version 2.1 of the License, or (at your option) any later version.
016     *
017     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019     * See the GNU Lesser General Public License for more details.
020     *
021     * You should have received a copy of the GNU Lesser General Public License along with this
022     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023     * Boston, MA 02111-1307, USA.
024     */
025    package org.jrobin.graph;
026    
027    import org.jrobin.core.RrdException;
028    import org.jrobin.core.Util;
029    import org.jrobin.data.DataProcessor;
030    
031    import javax.swing.*;
032    import java.awt.*;
033    import java.io.IOException;
034    
035    /**
036     * Class which actually creates JRobin graphs (does the hard work).
037     */
038    public class RrdGraph implements RrdGraphConstants {
039            RrdGraphDef gdef;
040            ImageParameters im = new ImageParameters();
041            DataProcessor dproc;
042            ImageWorker worker;
043            Mapper mapper;
044            RrdGraphInfo info = new RrdGraphInfo();
045            private String signature;
046    
047            /**
048             * Creates graph from the corresponding {@link RrdGraphDef} object.
049             *
050             * @param gdef Graph definition
051             * @throws IOException  Thrown in case of I/O error
052             * @throws RrdException Thrown in case of JRobin related error
053             */
054            public RrdGraph(RrdGraphDef gdef) throws IOException, RrdException {
055                    this.gdef = gdef;
056                    signature = gdef.getSignature();
057                    worker = new ImageWorker(100, 100); // Dummy worker, just to start with something
058                    try {
059                            createGraph();
060                    }
061                    finally {
062                            worker.dispose();
063                            worker = null;
064                            dproc = null;
065                    }
066            }
067    
068            /**
069             * Returns complete graph information in a single object.
070             *
071             * @return Graph information (width, height, filename, image bytes, etc...)
072             */
073            public RrdGraphInfo getRrdGraphInfo() {
074                    return info;
075            }
076    
077            private void createGraph() throws RrdException, IOException {
078                    boolean lazy = lazyCheck();
079                    if (!lazy || gdef.printStatementCount() != 0) {
080                            fetchData();
081                            resolveTextElements();
082                            if (gdef.shouldPlot() && !lazy) {
083                                    calculatePlotValues();
084                                    findMinMaxValues();
085                                    identifySiUnit();
086                                    expandValueRange();
087                                    removeOutOfRangeRules();
088                                    initializeLimits();
089                                    placeLegends();
090                                    createImageWorker();
091                                    drawBackground();
092                                    drawData();
093                                    drawGrid();
094                                    drawAxis();
095                                    drawText();
096                                    drawLegend();
097                                    drawRules();
098                                    gator();
099                                    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    }