package org.rrd4j.graph; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Paint; import java.awt.Stroke; import java.io.IOException; import javax.swing.ImageIcon; import org.rrd4j.core.Util; import org.rrd4j.data.DataProcessor; import org.rrd4j.graph.DownSampler.DataSet; /** * Class which actually creates Rrd4j graphs (does the hard work). */ public class RrdGraph implements RrdGraphConstants { private static final double[] SENSIBLE_VALUES = { 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, 90.0, 80.0, 75.0, 70.0, 60.0, 50.0, 40.0, 30.0, 25.0, 20.0, 10.0, 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, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1 }; private static final char[] SYMBOLS = {'a', 'f', 'p', 'n', 'ยต', 'm', ' ', 'k', 'M', 'G', 'T', 'P', 'E'}; final RrdGraphDef gdef; ImageParameters im = new ImageParameters(); DataProcessor dproc; ImageWorker worker; Mapper mapper; RrdGraphInfo info = new RrdGraphInfo(); private final String signature; /** * Creates graph from the corresponding {@link org.rrd4j.graph.RrdGraphDef} object. * * @param gdef Graph definition * @throws java.io.IOException Thrown in case of I/O error */ public RrdGraph(RrdGraphDef gdef) throws IOException { this.gdef = gdef; signature = gdef.getSignature(); worker = new ImageWorker(1, 1); // Dummy worker, just to start with something try { createGraph(); } finally { worker.dispose(); worker = null; dproc = null; } } /** * Returns complete graph information in a single object. * * @return Graph information (width, height, filename, image bytes, etc...) */ public RrdGraphInfo getRrdGraphInfo() { return info; } private void createGraph() throws IOException { boolean lazy = lazyCheck(); if (!lazy || gdef.printStatementCount() != 0) { fetchData(); resolveTextElements(); if (gdef.shouldPlot() && !lazy) { calculatePlotValues(); findMinMaxValues(); identifySiUnit(); expandValueRange(); removeOutOfRangeRules(); removeOutOfRangeSpans(); initializeLimits(); placeLegends(); createImageWorker(); drawBackground(); drawData(); drawGrid(); drawAxis(); drawText(); drawLegend(); drawRules(); drawSpans(); gator(); drawOverlay(); saveImage(); } } collectInfo(); } private void collectInfo() { info.filename = gdef.filename; info.width = im.xgif; info.height = im.ygif; for (CommentText comment : gdef.comments) { if (comment instanceof PrintText) { PrintText pt = (PrintText) comment; if (pt.isPrint()) { info.addPrintLine(pt.resolvedText); } } } if (gdef.imageInfo != null) { info.imgInfo = Util.sprintf(gdef.locale, gdef.imageInfo, gdef.filename, im.xgif, im.ygif); } } private void saveImage() throws IOException { if (!gdef.filename.equals("-")) { info.bytes = worker.saveImage(gdef.filename, gdef.imageFormat, gdef.imageQuality, gdef.interlaced); } else { info.bytes = worker.getImageBytes(gdef.imageFormat, gdef.imageQuality, gdef.interlaced); } } private void drawOverlay() throws IOException { if (gdef.overlayImage != null) { worker.loadImage(gdef.overlayImage); } } private void gator() { if (!gdef.onlyGraph && gdef.showSignature) { worker.setTextAntiAliasing(gdef.textAntiAliasing); Font font = gdef.getFont(FONTTAG_WATERMARK); int x = (int) (im.xgif - 2 - worker.getFontAscent(font)); int y = 4; worker.transform(x, y, Math.PI / 2); worker.drawString(signature, 0, 0, font, Color.LIGHT_GRAY); worker.reset(); worker.setTextAntiAliasing(false); } } private void drawRules() { worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2); for (PlotElement pe : gdef.plotElements) { if (pe instanceof HRule) { HRule hr = (HRule) pe; if (hr.value >= im.minval && hr.value <= im.maxval) { int y = mapper.ytr(hr.value); worker.drawLine(im.xorigin, y, im.xorigin + im.xsize, y, hr.color, hr.stroke); } } else if (pe instanceof VRule) { VRule vr = (VRule) pe; if (vr.timestamp >= im.start && vr.timestamp <= im.end) { int x = mapper.xtr(vr.timestamp); worker.drawLine(x, im.yorigin, x, im.yorigin - im.ysize, vr.color, vr.stroke); } } } worker.reset(); } private void drawSpans() { worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2); for (PlotElement pe : gdef.plotElements) { if (pe instanceof HSpan) { HSpan hr = (HSpan) pe; int ys = mapper.ytr(hr.start); int ye = mapper.ytr(hr.end); int height = ys - ye; worker.fillRect(im.xorigin, ys - height, im.xsize, height, hr.color); } else if (pe instanceof VSpan) { VSpan vr = (VSpan) pe; int xs = mapper.xtr(vr.start); int xe = mapper.xtr(vr.end); worker.fillRect(xs, im.yorigin - im.ysize, xe - xs, im.ysize, vr.color); } } worker.reset(); } private void drawText() { if (!gdef.onlyGraph) { worker.setTextAntiAliasing(gdef.textAntiAliasing); if (gdef.title != null) { int x = im.xgif / 2 - (int) (worker.getStringWidth(gdef.title, gdef.getFont(FONTTAG_TITLE)) / 2); int y = PADDING_TOP + (int) worker.getFontAscent(gdef.getFont(FONTTAG_TITLE)); worker.drawString(gdef.title, x, y, gdef.getFont(FONTTAG_TITLE), gdef.colors[COLOR_FONT]); } if (gdef.verticalLabel != null) { int x = PADDING_LEFT; int y = im.yorigin - im.ysize / 2 + (int) worker.getStringWidth(gdef.verticalLabel, gdef.getFont(FONTTAG_UNIT)) / 2; int ascent = (int) worker.getFontAscent(gdef.getFont(FONTTAG_UNIT)); worker.transform(x, y, -Math.PI / 2); worker.drawString(gdef.verticalLabel, 0, ascent, gdef.getFont(FONTTAG_UNIT), gdef.colors[COLOR_FONT]); worker.reset(); } worker.setTextAntiAliasing(false); } } private void drawGrid() { if (!gdef.onlyGraph) { worker.setTextAntiAliasing(gdef.textAntiAliasing); Paint shade1 = gdef.colors[COLOR_SHADEA], shade2 = gdef.colors[COLOR_SHADEB]; Stroke borderStroke = new BasicStroke(1); worker.drawLine(0, 0, im.xgif - 1, 0, shade1, borderStroke); worker.drawLine(1, 1, im.xgif - 2, 1, shade1, borderStroke); worker.drawLine(0, 0, 0, im.ygif - 1, shade1, borderStroke); worker.drawLine(1, 1, 1, im.ygif - 2, shade1, borderStroke); worker.drawLine(im.xgif - 1, 0, im.xgif - 1, im.ygif - 1, shade2, borderStroke); worker.drawLine(0, im.ygif - 1, im.xgif - 1, im.ygif - 1, shade2, borderStroke); worker.drawLine(im.xgif - 2, 1, im.xgif - 2, im.ygif - 2, shade2, borderStroke); worker.drawLine(1, im.ygif - 2, im.xgif - 2, im.ygif - 2, shade2, borderStroke); if (gdef.drawXGrid) { new TimeAxis(this).draw(); } if (gdef.drawYGrid) { boolean ok; if (gdef.altYMrtg) { ok = new ValueAxisMrtg(this).draw(); } else if (gdef.logarithmic) { ok = new ValueAxisLogarithmic(this).draw(); } else { ok = new ValueAxis(this).draw(); } if (!ok) { String msg = "No Data Found"; worker.drawString(msg, im.xgif / 2 - (int) worker.getStringWidth(msg, gdef.getFont(FONTTAG_TITLE)) / 2, (2 * im.yorigin - im.ysize) / 2, gdef.getFont(FONTTAG_TITLE), gdef.colors[COLOR_FONT]); } } worker.setTextAntiAliasing(false); } } private void drawData() { worker.setAntiAliasing(gdef.antiAliasing); worker.clip(im.xorigin, im.yorigin - gdef.height - 1, gdef.width, gdef.height + 2); double areazero = mapper.ytr((im.minval > 0.0) ? im.minval : (im.maxval < 0.0) ? im.maxval : 0.0); double[] x = gdef.downsampler == null ? xtr(dproc.getTimestamps()) : null; double[] lastY = null; // draw line, area and stack for (PlotElement plotElement : gdef.plotElements) { if (plotElement instanceof SourcedPlotElement) { SourcedPlotElement source = (SourcedPlotElement) plotElement; double[] y; if (gdef.downsampler != null) { DataSet set = gdef.downsampler.downsize(dproc.getTimestamps(), source.getValues()); x = xtr(set.timestamps); y = ytr(set.values); } else { y = ytr(source.getValues()); } if (Line.class.isAssignableFrom(source.getClass())) { worker.drawPolyline(x, y, source.color, ((Line)source).stroke ); } else if (Area.class.isAssignableFrom(source.getClass())) { if(source.parent == null) { worker.fillPolygon(x, areazero, y, source.color); } else { worker.fillPolygon(x, lastY, y, source.color); worker.drawPolyline(x, lastY, source.getParentColor(), new BasicStroke(0)); } } else if (source instanceof Stack) { Stack stack = (Stack) source; float width = stack.getParentLineWidth(); if (width >= 0F) { // line worker.drawPolyline(x, y, stack.color, new BasicStroke(width)); } else { // area worker.fillPolygon(x, lastY, y, stack.color); worker.drawPolyline(x, lastY, stack.getParentColor(), new BasicStroke(0)); } } else { // should not be here throw new IllegalStateException("Unknown plot source: " + source.getClass().getName()); } lastY = y; } } worker.reset(); worker.setAntiAliasing(false); } private void drawAxis() { if (!gdef.onlyGraph) { Paint gridColor = gdef.colors[COLOR_GRID]; Paint xaxisColor = gdef.colors[COLOR_XAXIS]; Paint yaxisColor = gdef.colors[COLOR_YAXIS]; Paint arrowColor = gdef.colors[COLOR_ARROW]; Stroke stroke = new BasicStroke(1); worker.drawLine(im.xorigin + im.xsize, im.yorigin, im.xorigin + im.xsize, im.yorigin - im.ysize, gridColor, stroke); worker.drawLine(im.xorigin, im.yorigin - im.ysize, im.xorigin + im.xsize, im.yorigin - im.ysize, gridColor, stroke); worker.drawLine(im.xorigin - 4, im.yorigin, im.xorigin + im.xsize + 4, im.yorigin, xaxisColor, stroke); worker.drawLine(im.xorigin, im.yorigin + 4, im.xorigin, im.yorigin - im.ysize - 4, yaxisColor, stroke); //Do X axis arrow double[] Xarrow_x = { im.xorigin + im.xsize + 4, im.xorigin + im.xsize + 9, im.xorigin + im.xsize + 4, }; double[] Xarrow_y = { im.yorigin - 3, im.yorigin + 0, im.yorigin + 3, }; worker.fillPolygon(Xarrow_x, im.yorigin + 3, Xarrow_y, arrowColor); //Do y axis arrow double[] Yarrow_x = { im.xorigin - 3, im.xorigin, im.xorigin + 3, }; double[] Yarrow_y = { im.yorigin - im.ysize - 4, im.yorigin - im.ysize - 9, im.yorigin - im.ysize - 4, }; worker.fillPolygon(Yarrow_x, im.yorigin - im.ysize - 4, Yarrow_y, arrowColor); } } private void drawBackground() throws IOException { worker.fillRect(0, 0, im.xgif, im.ygif, gdef.colors[COLOR_BACK]); if (gdef.backgroundImage != null) { worker.loadImage(gdef.backgroundImage); } worker.fillRect(im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize, gdef.colors[COLOR_CANVAS]); } private void createImageWorker() { worker.resize(im.xgif, im.ygif); } private void placeLegends() { if (!gdef.noLegend && !gdef.onlyGraph) { int border = (int) (getFontCharWidth(FontTag.LEGEND) * PADDING_LEGEND); LegendComposer lc = new LegendComposer(this, border, im.ygif, im.xgif - 2 * border); im.ygif = lc.placeComments() + PADDING_BOTTOM; } } private void initializeLimits() { im.xsize = gdef.width; im.ysize = gdef.height; im.unitslength = gdef.unitsLength; if (gdef.onlyGraph) { im.xorigin = 0; } else { im.xorigin = (int) (PADDING_LEFT + im.unitslength * getFontCharWidth(FontTag.UNIT)); } if (!gdef.onlyGraph && gdef.verticalLabel != null) { im.xorigin += getFontHeight(FONTTAG_UNIT); } if (gdef.onlyGraph) { im.yorigin = im.ysize; } else { im.yorigin = PADDING_TOP + im.ysize; } mapper = new Mapper(this); if (!gdef.onlyGraph && gdef.title != null) { im.yorigin += getFontHeight(FONTTAG_TITLE) + PADDING_TITLE; } if (gdef.onlyGraph) { im.xgif = im.xsize; im.ygif = im.yorigin; } else { im.xgif = PADDING_RIGHT + im.xsize + im.xorigin; im.ygif = im.yorigin + (int) (PADDING_PLOT * getFontHeight(FONTTAG_DEFAULT)); } } private void removeOutOfRangeRules() { for (PlotElement plotElement : gdef.plotElements) { if (plotElement instanceof HRule) { ((HRule) plotElement).setLegendVisibility(im.minval, im.maxval, gdef.forceRulesLegend); } else if (plotElement instanceof VRule) { ((VRule) plotElement).setLegendVisibility(im.start, im.end, gdef.forceRulesLegend); } } } private void removeOutOfRangeSpans() { for (PlotElement plotElement : gdef.plotElements) { if (plotElement instanceof HSpan) { ((HSpan) plotElement).setLegendVisibility(im.minval, im.maxval, gdef.forceRulesLegend); } else if (plotElement instanceof VSpan) { ((VSpan) plotElement).setLegendVisibility(im.start, im.end, gdef.forceRulesLegend); } } } private void expandValueRange() { im.ygridstep = (gdef.valueAxisSetting != null) ? gdef.valueAxisSetting.gridStep : Double.NaN; im.ylabfact = (gdef.valueAxisSetting != null) ? gdef.valueAxisSetting.labelFactor : 0; if (!gdef.rigid && !gdef.logarithmic) { double scaled_min, scaled_max, adj; if (Double.isNaN(im.ygridstep)) { if (gdef.altYMrtg) { /* mrtg */ im.decimals = Math.ceil(Math.log10(Math.max(Math.abs(im.maxval), Math.abs(im.minval)))); im.quadrant = 0; if (im.minval < 0) { im.quadrant = 2; if (im.maxval <= 0) { im.quadrant = 4; } } switch (im.quadrant) { case 2: im.scaledstep = Math.ceil(50 * Math.pow(10, -(im.decimals)) * Math.max(Math.abs(im.maxval), Math.abs(im.minval))) * Math.pow(10, im.decimals - 2); scaled_min = -2 * im.scaledstep; scaled_max = 2 * im.scaledstep; break; case 4: im.scaledstep = Math.ceil(25 * Math.pow(10, -(im.decimals)) * Math.abs(im.minval)) * Math.pow(10, im.decimals - 2); scaled_min = -4 * im.scaledstep; scaled_max = 0; break; default: /* quadrant 0 */ im.scaledstep = Math.ceil(25 * Math.pow(10, -(im.decimals)) * im.maxval) * Math.pow(10, im.decimals - 2); scaled_min = 0; scaled_max = 4 * im.scaledstep; break; } im.minval = scaled_min; im.maxval = scaled_max; } else if (gdef.altAutoscale || (gdef.altAutoscaleMin && gdef.altAutoscaleMax)) { /* measure the amplitude of the function. Make sure that graph boundaries are slightly higher then max/min vals so we can see amplitude on the graph */ double delt, fact; delt = im.maxval - im.minval; adj = delt * 0.1; fact = 2.0 * Math.pow(10.0, Math.floor(Math.log10(Math.max(Math.abs(im.minval), Math.abs(im.maxval)))) - 2); if (delt < fact) { adj = (fact - delt) * 0.55; } im.minval -= adj; im.maxval += adj; } else if (gdef.altAutoscaleMin) { /* measure the amplitude of the function. Make sure that graph boundaries are slightly lower than min vals so we can see amplitude on the graph */ adj = (im.maxval - im.minval) * 0.1; im.minval -= adj; } else if (gdef.altAutoscaleMax) { /* measure the amplitude of the function. Make sure that graph boundaries are slightly higher than max vals so we can see amplitude on the graph */ adj = (im.maxval - im.minval) * 0.1; im.maxval += adj; } else { scaled_min = im.minval / im.magfact; scaled_max = im.maxval / im.magfact; for (int i = 1; SENSIBLE_VALUES[i] > 0; i++) { if (SENSIBLE_VALUES[i - 1] >= scaled_min && SENSIBLE_VALUES[i] <= scaled_min) { im.minval = SENSIBLE_VALUES[i] * im.magfact; } if (-SENSIBLE_VALUES[i - 1] <= scaled_min && -SENSIBLE_VALUES[i] >= scaled_min) { im.minval = -SENSIBLE_VALUES[i - 1] * im.magfact; } if (SENSIBLE_VALUES[i - 1] >= scaled_max && SENSIBLE_VALUES[i] <= scaled_max) { im.maxval = SENSIBLE_VALUES[i - 1] * im.magfact; } if (-SENSIBLE_VALUES[i - 1] <= scaled_max && -SENSIBLE_VALUES[i] >= scaled_max) { im.maxval = -SENSIBLE_VALUES[i] * im.magfact; } } } } else { im.minval = (double) im.ylabfact * im.ygridstep * Math.floor(im.minval / ((double) im.ylabfact * im.ygridstep)); im.maxval = (double) im.ylabfact * im.ygridstep * Math.ceil(im.maxval / ((double) im.ylabfact * im.ygridstep)); } } } private void identifySiUnit() { im.unitsexponent = gdef.unitsExponent; im.base = gdef.base; if (!gdef.logarithmic) { int symbcenter = 6; double digits; if (im.unitsexponent != Integer.MAX_VALUE) { digits = Math.floor(im.unitsexponent / 3); } else { digits = Math.floor(Math.log(Math.max(Math.abs(im.minval), Math.abs(im.maxval))) / Math.log(im.base)); } im.magfact = Math.pow(im.base, digits); if (((digits + symbcenter) < SYMBOLS.length) && ((digits + symbcenter) >= 0)) { im.symbol = SYMBOLS[(int) digits + symbcenter]; } else { im.symbol = '?'; } } } private void findMinMaxValues() { double minval = Double.NaN, maxval = Double.NaN; for (PlotElement pe : gdef.plotElements) { if (pe instanceof SourcedPlotElement) { minval = Util.min(((SourcedPlotElement) pe).getMinValue(), minval); maxval = Util.max(((SourcedPlotElement) pe).getMaxValue(), maxval); } } if (Double.isNaN(minval)) { minval = 0D; } if (Double.isNaN(maxval)) { maxval = 1D; } im.minval = gdef.minValue; im.maxval = gdef.maxValue; /* adjust min and max values */ if (Double.isNaN(im.minval) || ((!gdef.logarithmic && !gdef.rigid) && im.minval > minval)) { im.minval = minval; } if (Double.isNaN(im.maxval) || (!gdef.rigid && im.maxval < maxval)) { if (gdef.logarithmic) { im.maxval = maxval * 1.1; } else { im.maxval = maxval; } } /* make sure min is smaller than max */ if (im.minval > im.maxval) { im.minval = 0.99 * im.maxval; } /* make sure min and max are not equal */ if (Math.abs(im.minval - im.maxval) < .0000001) { im.maxval *= 1.01; if (!gdef.logarithmic) { im.minval *= 0.99; } /* make sure min and max are not both zero */ if (im.maxval == 0.0) { im.maxval = 1.0; } } } private void calculatePlotValues() { for (PlotElement pe : gdef.plotElements) { if (pe instanceof SourcedPlotElement) { ((SourcedPlotElement) pe).assignValues(dproc); } } } private void resolveTextElements() { ValueScaler valueScaler = new ValueScaler(gdef.base); for (CommentText comment : gdef.comments) { comment.resolveText(gdef.locale, dproc, valueScaler); } } private void fetchData() throws IOException { dproc = new DataProcessor(gdef.startTime, gdef.endTime); dproc.setPoolUsed(gdef.poolUsed); dproc.setTimeZone(gdef.tz); if (gdef.step > 0) { dproc.setStep(gdef.step); dproc.setFetchRequestResolution(gdef.step); } for (Source src : gdef.sources) { src.requestData(dproc); } dproc.processData(); im.start = gdef.startTime; im.end = gdef.endTime; } private boolean lazyCheck() { // redraw if lazy option is not set or file does not exist if (!gdef.lazy || !Util.fileExists(gdef.filename)) { return false; // 'false' means 'redraw' } // redraw if not enough time has passed long secPerPixel = (gdef.endTime - gdef.startTime) / gdef.width; long elapsed = Util.getTimestamp() - Util.getLastModified(gdef.filename); return elapsed <= secPerPixel; } private void drawLegend() { if (!gdef.onlyGraph && !gdef.noLegend) { worker.setTextAntiAliasing(gdef.textAntiAliasing); int ascent = (int) worker.getFontAscent(gdef.getFont(FONTTAG_LEGEND)); int box = (int) getBox(), boxSpace = (int) (getBoxSpace()); for (CommentText c : gdef.comments) { if (c.isValidGraphElement()) { int x = c.x, y = c.y + ascent; if (c instanceof LegendText) { // draw with BOX worker.fillRect(x, y - box, box, box, gdef.colors[COLOR_FRAME]); worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, gdef.colors[COLOR_CANVAS]); worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, gdef.colors[COLOR_BACK]); worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, ((LegendText) c).legendColor); worker.drawString(c.resolvedText, x + boxSpace, y, gdef.getFont(FONTTAG_LEGEND), gdef.colors[COLOR_FONT]); } else { worker.drawString(c.resolvedText, x, y, gdef.getFont(FONTTAG_LEGEND), gdef.colors[COLOR_FONT]); } } } worker.setTextAntiAliasing(false); } } // helper methods double getFontHeight(FontTag fonttag) { return worker.getFontHeight(gdef.getFont(fonttag)); } double getFontCharWidth(FontTag fonttag) { return worker.getStringWidth("a", gdef.getFont(fonttag)); } @Deprecated double getSmallFontHeight() { return getFontHeight(FONTTAG_LEGEND); } double getTitleFontHeight() { return getFontHeight(FONTTAG_TITLE); } double getInterlegendSpace() { return getFontCharWidth(FONTTAG_LEGEND) * LEGEND_INTERSPACING; } double getLeading() { return getFontHeight(FONTTAG_LEGEND) * LEGEND_LEADING; } double getSmallLeading() { return getFontHeight(FONTTAG_LEGEND) * LEGEND_LEADING_SMALL; } double getBoxSpace() { return Math.ceil(getFontHeight(FONTTAG_LEGEND) * LEGEND_BOX_SPACE); } private double getBox() { return getFontHeight(FONTTAG_LEGEND) * LEGEND_BOX; } double[] xtr(long[] timestamps) { double[] timestampsDev = new double[2 * timestamps.length - 1]; for (int i = 0, j = 0; i < timestamps.length; i += 1, j += 2) { timestampsDev[j] = mapper.xtr(timestamps[i]); if (i < timestamps.length - 1) { timestampsDev[j + 1] = timestampsDev[j]; } } return timestampsDev; } double[] ytr(double[] values) { double[] valuesDev = new double[2 * values.length - 1]; for (int i = 0, j = 0; i < values.length; i += 1, j += 2) { if (Double.isNaN(values[i])) { valuesDev[j] = Double.NaN; } else { valuesDev[j] = mapper.ytr(values[i]); } if (j > 0) { valuesDev[j - 1] = valuesDev[j]; } } return valuesDev; } /** * Renders this graph onto graphing device * * @param g Graphics handle */ public void render(Graphics g) { byte[] imageData = getRrdGraphInfo().getBytes(); ImageIcon image = new ImageIcon(imageData); image.paintIcon(null, g, 0, 0); } }