1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
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
49
50
51
52
53
54 public RrdGraph(RrdGraphDef gdef) throws IOException, RrdException {
55 this.gdef = gdef;
56 signature = gdef.getSignature();
57 worker = new ImageWorker(100, 100);
58 try {
59 createGraph();
60 }
61 finally {
62 worker.dispose();
63 worker = null;
64 dproc = null;
65 }
66 }
67
68
69
70
71
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
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
246 worker.drawPolyline(x, y, stack.color, new BasicStroke(width));
247 }
248 else {
249
250 worker.fillPolygon(x, lastY, y, stack.color);
251 worker.drawPolyline(x, lastY, stack.getParentColor(), new BasicStroke(0));
252 }
253 }
254 else {
255
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) {
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:
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
401
402
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
417
418
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
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
503 if (im.minval > im.maxval) {
504 im.minval = 0.99 * im.maxval;
505 }
506
507 if (im.minval == im.maxval) {
508 im.maxval *= 1.01;
509 if (!gdef.logarithmic) {
510 im.minval *= 0.99;
511 }
512
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
545
546
547 im.start = gdef.startTime;
548 im.end = gdef.endTime;
549 }
550
551 private boolean lazyCheck() {
552
553 if (!gdef.lazy || !Util.fileExists(gdef.filename)) {
554 return false;
555 }
556
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
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
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
620
621
622
623
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
638
639
640
641
642
643
644
645
646
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
665
666
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 }