aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/js
diff options
context:
space:
mode:
authorPim van den Berg2013-05-10 21:36:54 +0200
committerPim van den Berg2013-05-10 21:36:54 +0200
commit0163faefef02207ad0ea3330af688103633293ff (patch)
treec407fca93a3418af59c4c7133327f5bc1630fba5 /js
parentMerge pull request #5 from mce35/nut (diff)
downloadapt-panopticon_cgp-0163faefef02207ad0ea3330af688103633293ff.zip
apt-panopticon_cgp-0163faefef02207ad0ea3330af688103633293ff.tar.gz
apt-panopticon_cgp-0163faefef02207ad0ea3330af688103633293ff.tar.bz2
apt-panopticon_cgp-0163faefef02207ad0ea3330af688103633293ff.tar.xz
import js directory from jsrrdgraph for client side graph rendering
Source: https://github.com/manuelluis/jsrrdgraph@276b880
Diffstat (limited to 'js')
-rw-r--r--js/Color.js76
-rw-r--r--js/RrdCmdLine.js513
-rw-r--r--js/RrdDataFile.js129
-rw-r--r--js/RrdGfxCanvas.js254
-rw-r--r--js/RrdGfxPdf.js1014
-rw-r--r--js/RrdGfxSvg.js250
-rw-r--r--js/RrdGraph.js2914
-rw-r--r--js/RrdJson.js588
-rw-r--r--js/RrdRpn.js616
-rw-r--r--js/RrdTime.js621
-rw-r--r--js/base64.js143
-rw-r--r--js/binaryXHR.js234
-rw-r--r--js/rrdFile.js408
-rw-r--r--js/sprintf.js78
-rw-r--r--js/strftime.js146
15 files changed, 7984 insertions, 0 deletions
diff --git a/js/Color.js b/js/Color.js
new file mode 100644
index 0000000..e294f65
--- /dev/null
+++ b/js/Color.js
@@ -0,0 +1,76 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16 *
17 **/
18
19"use strict";
20
21/**
22 * ColorError
23 * @constructor
24 */
25var ColorError = function (message)
26{
27 this.prototype = Error.prototype;
28 this.name = "ColorError";
29 this.message = (message) ? message : "Error";
30};
31
32/**
33 * Color
34 * @constructor
35 */
36function Color(str)
37{
38 var bits;
39
40 this.r = 0;
41 this.g = 0;
42 this.b = 0;
43 this.a = 1.0;
44
45 if ((bits = /^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/.exec(str))) {
46 this.r = parseInt(bits[1]+bits[1], 16);
47 this.g = parseInt(bits[2]+bits[2], 16);
48 this.b = parseInt(bits[3]+bits[3], 16);
49 } else if ((bits = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(str))) {
50 this.r = parseInt(bits[1], 16);
51 this.g = parseInt(bits[2], 16);
52 this.b = parseInt(bits[3], 16);
53 } else if ((bits = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(str))) {
54 this.r = parseInt(bits[1], 16);
55 this.g = parseInt(bits[2], 16);
56 this.b = parseInt(bits[3], 16);
57 this.a = parseInt(bits[4], 16)/255;
58 } else if ((bits = /^rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\)$/.exec(str))) {
59 this.r = parseInt(bits[1], 10);
60 this.g = parseInt(bits[2], 10);
61 this.b = parseInt(bits[3], 10);
62 } else if ((bits = /^rgba\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([0-9.]+)\)$/.exec(str))) {
63 this.r = parseInt(bits[1], 10);
64 this.g = parseInt(bits[2], 10);
65 this.b = parseInt(bits[3], 10);
66 this.a = parseFloat(bits[4], 10);
67 } else {
68 throw new ColorError("Unknow color format '"+str+"'");
69 }
70};
71
72Color.prototype.torgba = function ()
73{
74 return 'rgba('+this.r+','+this.g+','+this.b+','+this.a+')';
75};
76
diff --git a/js/RrdCmdLine.js b/js/RrdCmdLine.js
new file mode 100644
index 0000000..fea0753
--- /dev/null
+++ b/js/RrdCmdLine.js
@@ -0,0 +1,513 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 *
18 * Manuel Sanmartin <manuel.luis at gmail.com>
19 **/
20
21"use strict";
22
23/**
24 * RrdCmdLine
25 * @constructor
26 */
27var RrdCmdLine = function() {
28 // if (arguments.lenght === 3) // XXX
29 this.init.apply(this, arguments);
30};
31
32RrdCmdLine.prototype = {
33 graph: null,
34
35 init: function (gfx, fetch, line)
36 {
37 this.graph = new RrdGraph(gfx, fetch);
38 this.cmdline(line);
39 },
40 cmdline: function(line) // FIXME
41 {
42 var i = 0;
43 line = line.replace(/\n/g," ");
44 var lines = line.match(/[^" ]+|"[^"]+"/g);
45 var len = lines.length;
46
47 while (i < len) {
48 var arg = lines[i];
49 if (arg.charAt(0) === '"' && arg.charAt(arg.length-1) === '"')
50 arg = arg.substr(1,arg.length-2);
51 if (/^LINE[0-9.]+:/.test(arg)) {
52 this.parse_line(arg);
53 } else if (/^AREA:/.test(arg)) {
54 this.parse_area(arg);
55 } else if (/^DEF:/.test(arg)) {
56 this.parse_def(arg);
57 } else if (/^CDEF:/.test(arg)) {
58 this.parse_cdef(arg);
59 } else if (/^VDEF:/.test(arg)) {
60 this.parse_vdef(arg);
61 } else if (/^GPRINT:/.test(arg)) {
62 this.parse_gprint(arg);
63 } else if (/^COMMENT:/.test(arg)) {
64 this.parse_comment(arg);
65 } else if (/^VRULE:/.test(arg)) {
66 this.parse_vrule(arg);
67 } else if (/^HRULE:/.test(arg)) {
68 this.parse_hrule(arg);
69 } else if (/^TICK:/.test(arg)) {
70 this.parse_tick(arg);
71 } else if (/^TEXTALIGN:/.test(arg)) {
72 this.parse_textaling(arg);
73 } else if (/^SHIFT:/.test(arg)) {
74 this.parse_shift(arg);
75 } else if (arg.charAt(0) === '-') {
76 var strip = 1;
77 if (arg.length > 1 && arg.charAt(1) === '-') {
78 strip = 2;
79 }
80 var option = arg.substr(strip);
81 var value = undefined;
82
83 if (option.indexOf('=') !== -1) {
84 var index = option.indexOf('=');
85 value = option.substr(index+1);
86 option = option.substr(0,index);
87 } else if (i+1 < len) {
88 if (lines[i+1].charAt(0) !== '-' &&
89 !/^"?LINE[0-9.]+:/.test(lines[i+1]) &&
90 !/^"?AREA:/.test(lines[i+1]) &&
91 !/^"?DEF:/.test(lines[i+1]) &&
92 !/^"?CDEF:/.test(lines[i+1]) &&
93 !/^"?VDEF:/.test(lines[i+1]) &&
94 !/^"?GPRINT:/.test(lines[i+1]) &&
95 !/^"?COMMENT:/.test(lines[i+1]) &&
96 !/^"?HRULE:/.test(lines[i+1]) &&
97 !/^"?VRULE:/.test(lines[i+1]) &&
98 !/^"?TICK:/.test(lines[i+1]) &&
99 !/^"?TEXTALING:/.test(lines[i+1]) &&
100 !/^"?SHIFT:/.test(lines[i+1])
101 ) {
102 i++;
103 if (lines[i].charAt(0) === '"' && lines[i].charAt(lines[i].length-1) === '"')
104 value = lines[i].substr(1,lines[i].length-2);
105 else
106 value = lines[i];
107 }
108 }
109 this.set_option(option, value);
110 } else {
111 throw "Unknow argument: "+arg;
112 }
113 i++;
114 }
115 var start_end = RrdTime.proc_start_end(this.graph.start_t, this.graph.end_t); // FIXME here?
116 this.graph.start = start_end[0];
117 this.graph.end = start_end[1];
118 },
119 set_option: function(option, value)
120 {
121 switch(option) {
122 case 'alt-autoscale':
123 case 'A':
124 this.graph.alt_autoscale = true;
125 break;
126 case 'base':
127 case 'b':
128 this.graph.base = parseInt(value, 10);
129 if (this.graph.base !== 1000 && this.graph.base !== 1024)
130 throw 'the only sensible value for base apart from 1000 is 1024';
131 break;
132 case 'color':
133 case 'c':
134 var index = value.indexOf('#');
135 if (index === -1)
136 throw "invalid color def format";
137 var name = value.substr(0,index);
138 if (!this.graph.GRC[name])
139 throw "invalid color name '"+name+"'"
140 this.graph.GRC[name] = value.substr(index); // FIXME check color
141 break;
142 case 'full-size-mode':
143 case 'D':
144 this.graph.full_size_mode = true;
145 break;
146 case 'slope-mode':
147 case 'E':
148 this.graph.slopemode = true;
149 break;
150 case 'end':
151 case 'e':
152 this.graph.end_t = new RrdTime(value);
153// this.graph.end = parseInt(value, 10);
154 break;
155 case 'force-rules-legend':
156 case 'F':
157 this.graph.force_rules_legend = true;
158 break;
159 case 'imginfo':
160 case 'f':
161 // im->imginfo = optarg;
162 break;
163 case 'graph-render-mode':
164 case 'G':
165 // im->graph_antialias
166 break;
167 case 'no-legend':
168 case 'g':
169 this.graph.no_legend = true;
170 break;
171 case 'height':
172 case 'h':
173 this.graph.ysize = parseInt(value, 10);
174 break;
175 case 'no-minor':
176 case 'I':
177 this.graph.no_minor = false;
178 break;
179 case 'interlaced':
180 case 'i':
181 break;
182 case 'alt-autoscale-min':
183 case 'J':
184 this.graph.alt_autoscale_min = true;
185 break;
186 case 'only-graph':
187 case 'j':
188 this.graph.only_graph = true;
189 break;
190 case 'units-length':
191 case 'L':
192 this.graph.unitslength = parseInt(value, 10);
193 this.graph.forceleftspace = true;
194 break;
195 case 'lower-limit':
196 case 'l':
197 this.graph.setminval = parseFloat(value)
198 break;
199 case 'alt-autoscale-max':
200 case 'M':
201 this.graph.alt_autoscale_max = true;
202 break;
203 case 'zoom':
204 case 'm':
205 this.graph.zoom = parseFloat(value);
206 if (this.graph.zoom <= 0.0)
207 throw "zoom factor must be > 0";
208 break;
209 case 'no-gridfit':
210 case 'N':
211 this.graph.gridfit = true;
212 break;
213 case 'font':
214 case 'n':
215 var args = value.split(':');
216 if (args.length !== 3)
217 throw "invalid text property format";
218 if (!this.graph.TEXT[args[0]])
219 throw "invalid fonttag '"+args[0]+"'"
220 if (args[1] > 0)
221 this.graph.TEXT[args[0]].size = args[1];
222 if (args[2])
223 this.graph.TEXT[args[0]].font = args[2];
224 break;
225 case 'logarithmic':
226 case 'o':
227 this.graph.logarithmic = true;
228 break;
229 case 'pango-markup':
230 case 'P':
231 // im->with_markup = 1;
232 break;
233 case 'font-render-mode':
234 case 'R':
235 // im->font_options: normal light mono
236 break;
237 case 'rigid':
238 case 'r':
239 this.graph.rigid = true;
240 break;
241 case 'step':
242 this.graph.step = parseInt(value, 10);
243 break;
244 case 'start':
245 case 's':
246 this.graph.start_t = new RrdTime(value);
247 //this.graph.start = parseInt(value, 10);
248 break;
249 case 'tabwidth':
250 case 'T':
251 this.graph.tabwidth = parseFloat(value);
252 break;
253 case 'title':
254 case 't':
255 this.graph.title = value;
256 break;
257 case 'upper-limit':
258 case 'u':
259 this.graph.setmaxval = parseFloat(value);
260 break;
261 case 'vertical-label':
262 case 'v':
263 this.graph.ylegend = value;
264 break;
265 case 'watermark':
266 case 'W':
267 this.graph.watermark = value;
268 break;
269 case 'width':
270 case 'w':
271 this.graph.xsize = parseInt(value, 10);
272 if (this.graph.xsize < 10)
273 throw "width below 10 pixels";
274 break;
275 case 'units-exponent':
276 case 'X':
277 this.graph.unitsexponent = parseInt(value, 10);
278 break;
279 case 'x-grid':
280 case 'x':
281 if (value === 'none') {
282 this.graph.draw_x_grid = false;
283 } else {
284 var args = value.split(':');
285 if (args.length !== 8)
286 throw "invalid x-grid format";
287 this.graph.xlab_user.gridtm = this.graph.tmt_conv(args[0]);
288 if (this.graph.xlab_user.gridtm < 0)
289 throw "unknown keyword "+args[0];
290 this.graph.xlab_user.gridst = parseInt(args[1], 10);
291 this.graph.xlab_user.mgridtm = this.graph.tmt_conv(args[2]);
292 if (this.graph.xlab_user.mgridtm < 2)
293 throw "unknown keyword "+args[2];
294 this.graph.xlab_user.mgridst = parseInt(args[3], 10);
295 this.graph.xlab_user.labtm = this.graph.tmt_conv(args[4]);
296 if (this.graph.xlab_user.labtm < 0)
297 throw "unknown keyword "+args[4];
298 this.graph.xlab_user.labst = parseInt(args[5], 10);
299 this.graph.xlab_user.precis = parseInt(args[6], 10);
300 this.graph.xlab_user.minsec = 1;
301 this.graph.xlab_form = args[7]; // FIXME : ? join(:)
302 this.graph.xlab_user.stst = this.graph.xlab_form;
303 }
304 break;
305 case 'alt-y-grid':
306 case 'Y':
307 this.graph.alt_ygrid = true;
308 break;
309 case 'y-grid':
310 case 'y':
311 if (value === 'none') {
312 this.graph.draw_y_grid = false;
313 } else {
314 var index = value.indexOf(':');
315 if (index === -1)
316 throw "invalid y-grid format";
317 this.graph.ygridstep = parseFloat(value.substr(0,index));
318 if (this.graph.ygridstep <= 0)
319 throw "grid step must be > 0";
320 this.graph.ylabfact = parseInt(value.substr(index+1), 10);
321 if (this.graph.ylabfact < 1)
322 throw "label factor must be > 0";
323 }
324 break;
325 case 'lazy':
326 case 'z':
327 this.graph.lazy = 1;
328 break;
329 case 'units':
330 if (this.graph.force_units)
331 throw "--units can only be used once!";
332 if (value === 'si')
333 this.graph.force_units_si = true;
334 else
335 throw "invalid argument for --units: "+value;
336 break;
337 case 'alt-y-mrtg':
338 break;
339 case 'disable-rrdtool-tag':
340 this.graph.no_rrdtool_tag = true;
341 break;
342 case 'right-axis':
343 var index = value.indexOf(':');
344 if (index === -1)
345 throw "invalid right-axis format expected scale:shift";
346 this.graph.second_axis_scale = parseFloat(value.substr(0,index));
347 if(this.graph.second_axis_scale === 0)
348 throw "the second_axis_scale must not be 0";
349 this.graph.second_axis_shift = parseFloat(value.substr(index+1));
350 break;
351 case 'right-axis-label':
352 this.graph.second_axis_legend = value;
353 break;
354 case 'right-axis-format':
355 this.graph.second_axis_format = value;
356 break;
357 case 'legend-position':
358 if (value === "north") {
359 this.graph.legendposition = this.graph.LEGEND_POS.NORTH;
360 } else if (value === "west") {
361 this.graph.legendposition = this.graph.LEGEND_POS.WEST;
362 } else if (value === "south") {
363 this.graph.legendposition = this.graph.LEGEND_POS.SOUTH;
364 } else if (value === "east") {
365 this.graph.legendposition = this.graph.LEGEND_POS.EAST;
366 } else {
367 throw "unknown legend-position '"+value+"'";
368 }
369 break;
370 case 'legend-direction':
371 if (value === "topdown") {
372 this.graph.legenddirection = this.graph.LEGEND_DIR.TOP_DOWN;
373 } else if (value === "bottomup") {
374 this.graph.legenddirection = this.graph.LEGEND_DIR.BOTTOM_UP;
375 } else {
376 throw "unknown legend-position '"+value+"'";
377 }
378 break;
379 case 'border':
380 this.graph.draw_3d_border = parseInt(value, 10);
381 break;
382 case 'grid-dash':
383 var index = value.indexOf(':');
384 if (index === -1)
385 throw "expected grid-dash format float:float";
386 this.graph.grid_dash_on = parseFloat(value.substr(0,index));
387 this.graph.grid_dash_off = parseFloat(value.substr(index+1));
388 break;
389 case 'dynamic-labels':
390 this.graph.dynamic_labels = true;
391 break;
392 default:
393 throw 'Unknow option "'+option+'"';
394 }
395
396 },
397 // DEF:<vname>=<rrdfile>:<ds-name>:<CF>[:step=<step>][:start=<time>][:end=<time>][:reduce=<CF>]
398 parse_def: function (line)
399 {
400 var args = line.split(/:/);
401 var n=1;
402 var vnames = args[n++].split('=');
403 var vname = vnames[0];
404 var rrdfile = vnames[1];
405 var name = args[n++];
406 var cf = args[n++];
407 var step = undefined;
408 var reduce = undefined;
409 var start = undefined;
410 var end = undefined;
411 if (args.length > n) {
412 for (var j = n, xlen = args.length ; j < xlen ; j++) {
413 var opts = args[j].split("=");
414 if (opts[0] === "step") step = opts[1];
415 if (opts[0] === "reduce") reduce = opts[1]
416 if (opts[0] === "start") start = opts[1];
417 if (opts[0] === "end") end = opts[1];
418 }
419 }
420 this.graph.gdes_add_def(vname, rrdfile, name, cf, step, start, end, reduce)
421 },
422 // CDEF:vname=RPN expression
423 parse_cdef: function (line)
424 {
425 var args = line.split(/:|=/);
426 this.graph.gdes_add_cdef(args[1], args[2]);
427 },
428 // VDEF:vname=RPN expression
429 parse_vdef: function (line)
430 {
431 var args = line.split(/:|=/);
432 this.graph.gdes_add_vdef(args[1], args[2]);
433 },
434 // SHIFT:vname:offset
435 parse_shift: function (line)
436 {
437 var args = line.split(':');
438 this.graph.gdes_add_shift(args[1], args[2]);
439 },
440 // LINE[width]:value[#color][:[legend][:STACK]][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
441 parse_line: function (line)
442 {
443 var args = line.split(/#|:/);
444 var width = parseFloat(args[0].substr(4));
445 var stack = args[4] === 'STACK' ? true : undefined;
446 var color = this.graph.parse_color(args[2]);
447 this.graph.gdes_add_line(width, args[1], this.graph.color2rgba(color), args[3], stack);
448 },
449 // AREA:value[#color][:[legend][:STACK]]
450 parse_area: function (line)
451 {
452 var args = line.split(/#|:/);
453 var stack = args[3] === 'STACK' ? true : undefined;
454 var color = this.graph.parse_color(args[2]);
455 this.graph.gdes_add_area(args[1], this.graph.color2rgba(color), stack);
456 },
457 // TICK:vname#rrggbb[aa][:fraction[:legend]]
458 parse_tick: function (line)
459 {
460 var args = line.split(/:|#/);
461 var color = this.graph.parse_color(args[2]);
462 this.graph.gdes_add_tick(args[1], this.graph.color2rgba(color), args[3], args[4]);
463 },
464 // GPRINT:vname:format
465 parse_gprint: function(line)
466 {
467 var args = line.split(':');
468 var strftime = false;
469 var vname = args[1];
470 var cf = args[2];
471 var format = "";
472 if (args.length > 3) {
473 var m=0;
474 for (var j = 3, xlen = args.length ; j < xlen ; j++) {
475 if (args[j] === 'strftime') {
476 strftime = true;
477 } else {
478 if (m>0) {
479 format = format + ':'+ args[j];
480 } else {
481 format = args[j];
482 }
483 m++;
484 }
485 }
486 }
487 this.graph.gdes_add_gprint(vname, cf, format, strftime);
488 },
489 //COMMENT:text
490 parse_comment: function (line)
491 {
492 var index = line.indexOf(':');
493 this.graph.gdes_add_comment(line.substr(index+1));
494 },
495 // TEXTALIGN:{left|right|justified|center}
496 parse_textaling: function (line)
497 {
498 var index = line.indexOf(':');
499 this.graph.gdes_add_textaling(line.substr(index+1));
500 },
501 // VRULE:time#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
502 parse_vrule: function (line)
503 {
504 var args = line.split(/:|#/);
505 this.graph.gdes_add_vrule(args[1], '#'+args[2], args[3]);
506 },
507 // HRULE:value#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
508 parse_hrule: function (line)
509 {
510 var args = line.split(/:|#/);
511 this.graph.gdes_add_hrule(args[1], '#'+args[2], args[3]);
512 }
513};
diff --git a/js/RrdDataFile.js b/js/RrdDataFile.js
new file mode 100644
index 0000000..ae4dcff
--- /dev/null
+++ b/js/RrdDataFile.js
@@ -0,0 +1,129 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 *
18 * Manuel Sanmartin <manuel.luis at gmail.com>
19 **/
20
21"use strict";
22
23/**
24 * RrdDataFile
25 * @constructor
26 */
27var RrdDataFile = function() {
28 this.init.apply(this, arguments);
29};
30
31RrdDataFile.prototype = {
32 rrdfiles: null,
33
34 init: function()
35 {
36 this.rrdfiles = {};
37 },
38 fetch: function(gdp, ft_step)
39 {
40 var cal_start, cal_end;
41 var best_full_rra = 0, best_part_rra = 0, chosen_rra = 0;
42 var best_full_step_diff = 0, best_part_step_diff = 0, tmp_step_diff = 0, tmp_match = 0, best_match = 0;
43 var full_match, rra_base;
44 var first_full = 1;
45 var first_part = 1;
46 var rrd;
47 var data_ptr;
48 var rows;
49
50 if (gdp.rrd in this.rrdfiles) {
51 rrd = this.rrdfiles[gdp.rrd];
52 } else {
53 var bf = FetchBinaryURL(gdp.rrd);
54 rrd = new RRDFile(bf);
55 this.rrdfiles[gdp.rrd] = rrd;
56 }
57
58 var cf_idx = gdp.cf;
59 var ds_cnt = rrd.getNrDSs();
60 var rra_cnt = rrd.getNrRRAs();
61
62 for (var i = 0; i < ds_cnt; i++)
63 gdp.ds_namv[i] = rrd.rrd_header.getDSbyIdx(i).getName();
64
65 for (var i = 0; i < rra_cnt; i++) {
66 var rra = rrd.getRRAInfo(i);
67 if (RrdGraphDesc.cf_conv(rra.getCFName()) === cf_idx) {
68 cal_end = (rrd.getLastUpdate() - (rrd.getLastUpdate() % (rra.getPdpPerRow() * rra.pdp_step)));
69 cal_start = (cal_end - (rra.getPdpPerRow() * rra.row_cnt * rra.pdp_step));
70 full_match = gdp.end - gdp.start;
71
72 tmp_step_diff = Math.abs(ft_step - (rrd.getMinStep() * rra.pdp_cnt));
73 if (cal_start <= gdp.start) {
74 if (first_full || (tmp_step_diff < best_full_step_diff)) {
75 first_full = 0;
76 best_full_step_diff = tmp_step_diff;
77 best_full_rra = i;
78 }
79 } else {
80 tmp_match = full_match;
81 if (cal_start > gdp.start) tmp_match -= (cal_start - gdp.start);
82 if (first_part || (best_match < tmp_match) || (best_match === tmp_match && tmp_step_diff < best_part_step_diff)) {
83 first_part = 0;
84 best_match = tmp_match;
85 best_part_step_diff = tmp_step_diff;
86 best_part_rra = i;
87 }
88 }
89 }
90 }
91
92 if (first_full === 0) chosen_rra = best_full_rra;
93 else if (first_part === 0) chosen_rra = best_part_rra;
94 else throw "the RRD does not contain an RRA matching the chosen CF";
95
96 var rra_info = rrd.getRRAInfo(chosen_rra);
97 var rra = rrd.getRRA(chosen_rra);
98
99 ft_step = rrd.rrd_header.pdp_step * rra_info.getPdpPerRow();
100 gdp.start -= (gdp.start % ft_step);
101 gdp.end += (ft_step - gdp.end % ft_step);
102 rows = (gdp.end - gdp.start) / ft_step + 1;
103
104 gdp.ds_cnt = ds_cnt;
105 data_ptr = 0;
106
107 var rra_end_time = (rrd.getLastUpdate() - (rrd.getLastUpdate() % ft_step));
108 var rra_start_time = (rra_end_time - (ft_step * (rra_info.row_cnt - 1)));
109 /* here's an error by one if we don't be careful */
110 var start_offset = (gdp.start + ft_step - rra_start_time) / ft_step;
111 var end_offset = (rra_end_time - gdp.end) / ft_step;
112
113 gdp.data = [];
114
115 for (i = start_offset; i < rra.row_cnt - end_offset; i++) {
116 if (i < 0) {
117 for (var ii = 0; ii < ds_cnt; ii++)
118 gdp.data[data_ptr++] = Number.NaN;
119 } else if (i >= rra.row_cnt) {
120 for (var ii = 0; ii < ds_cnt; ii++)
121 gdp.data[data_ptr++] = Number.NaN;
122 } else {
123 for (var ii = 0; ii < ds_cnt; ii++)
124 gdp.data[data_ptr++] = rra.getEl(i, ii);
125 }
126 }
127 return ft_step;
128 }
129};
diff --git a/js/RrdGfxCanvas.js b/js/RrdGfxCanvas.js
new file mode 100644
index 0000000..070b806
--- /dev/null
+++ b/js/RrdGfxCanvas.js
@@ -0,0 +1,254 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 *
18 * Manuel Sanmartin <manuel.luis at gmail.com>
19 **/
20
21"use strict";
22
23/**
24 * RrdGfxCanvas
25 * @constructor
26 */
27var RrdGfxCanvas = function(canvasId)
28{
29 this.canvas = document.getElementById(canvasId);
30 this.ctx = this.canvas.getContext('2d');
31};
32
33RrdGfxCanvas.prototype.size = function (width, height)
34{
35 this.canvas.width = width;
36 this.canvas.height = height;
37};
38
39RrdGfxCanvas.prototype.set_dash = function (dashes, n, offset)
40{
41
42};
43
44RrdGfxCanvas.prototype.line = function (X0, Y0, X1, Y1, width, color)
45{
46 X0 = Math.round(X0);
47 Y0 = Math.round(Y0);
48 X1 = Math.round(X1);
49 Y1 = Math.round(Y1);
50
51 if (Y0 === Y1) {
52 Y0 += 0.5;
53 Y1 += 0.5;
54 } else if (X0 === X1) {
55 X0 += 0.5;
56 X1 += 0.5;
57 }
58 this.ctx.save();
59 this.ctx.lineWidth = width;
60 this.ctx.strokeStyle = color
61 this.ctx.beginPath();
62 this.ctx.moveTo(X0, Y0);
63 this.ctx.lineTo(X1, Y1);
64 this.ctx.stroke();
65 this.ctx.restore();
66};
67
68RrdGfxCanvas.prototype.dashed_line = function (X0, Y0, X1, Y1, width, color, dash_on, dash_off)
69{
70 X0 = Math.round(X0);
71 Y0 = Math.round(Y0);
72 X1 = Math.round(X1);
73 Y1 = Math.round(Y1);
74
75 this.ctx.save();
76 this.ctx.lineWidth = width;
77 this.ctx.strokeStyle = color;
78
79 this.ctx.beginPath();
80 if (Y0 === Y1) {
81 Y0 += 0.5;
82 Y1 += 0.5;
83 if (X0 > X1) {
84 var swap = X0;
85 X0 = X1;
86 X1 = swap;
87 }
88 this.ctx.moveTo(X0, Y0);
89 var n=0;
90 while(X0<=X1) {
91 if (n%2 === 1) {
92 X0 += dash_on;
93 this.ctx.lineTo(X0, Y0);
94 } else {
95 X0 += dash_off;
96 this.ctx.moveTo(X0, Y0);
97 }
98 n++;
99 }
100 } else if (X0 === X1) {
101 X0 += 0.5;
102 X1 += 0.5;
103 if (Y0 > Y1) {
104 var swap = Y0;
105 Y0 = Y1;
106 Y1 = swap;
107 }
108 this.ctx.moveTo(X0, Y0);
109 var n=0;
110 while(Y0<=Y1) {
111 if (n%2 === 1) {
112 Y0 += dash_on;
113 this.ctx.lineTo(X0, Y0);
114 } else {
115 Y0 += dash_off;
116 this.ctx.moveTo(X0, Y0);
117 }
118 n++;
119 }
120
121 } else {
122 this.ctx.moveTo(X0, Y0);
123 this.ctx.lineTo(X1, Y1);
124 }
125 this.ctx.stroke();
126 this.ctx.restore();
127};
128
129RrdGfxCanvas.prototype.rectangle = function (X0, Y0, X1, Y1, width, style)
130{
131 X0 = Math.round(X0)+0.5;
132 X1 = Math.round(X1)+0.5;
133 Y0 = Math.round(Y0)+0.5;
134 Y1 = Math.round(Y1)+0.5;
135
136 this.ctx.save();
137 this.ctx.beginPath();
138 this.ctx.lineWidth = width;
139 this.ctx.moveTo(X0, Y0);
140 this.ctx.lineTo(X1, Y0);
141 this.ctx.lineTo(X1, Y1);
142 this.ctx.lineTo(X0, Y1);
143 this.ctx.closePath();
144 this.ctx.strokeStyle = style;
145 this.ctx.stroke();
146 this.ctx.restore();
147};
148
149RrdGfxCanvas.prototype.new_area = function (X0, Y0, X1, Y1, X2, Y2, color)
150{
151 X0 = Math.round(X0)+0.5;
152 Y0 = Math.round(Y0)+0.5;
153 X1 = Math.round(X1)+0.5;
154 Y1 = Math.round(Y1)+0.5;
155 X2 = Math.round(X2)+0.5;
156 Y2 = Math.round(Y2)+0.5;
157 this.ctx.fillStyle = color;
158 this.ctx.beginPath();
159 this.ctx.moveTo(X0, Y0);
160 this.ctx.lineTo(X1, Y1);
161 this.ctx.lineTo(X2, Y2);
162};
163
164RrdGfxCanvas.prototype.add_point = function (x, y)
165{
166 x = Math.round(x)+0.5;
167 y = Math.round(y)+0.5;
168 this.ctx.lineTo(x, y);
169};
170
171RrdGfxCanvas.prototype.close_path = function ()
172{
173 this.ctx.closePath();
174 this.ctx.fill();
175};
176
177RrdGfxCanvas.prototype.stroke_begin = function (width, style)
178{
179 this.ctx.save();
180 this.ctx.beginPath();
181 this.ctx.lineWidth = width;
182 this.ctx.strokeStyle = style;
183 this.ctx.lineCap = 'round';
184 this.ctx.round = 'round';
185};
186
187RrdGfxCanvas.prototype.stroke_end = function ()
188{
189 this.ctx.stroke();
190 this.ctx.restore();
191};
192
193RrdGfxCanvas.prototype.moveTo = function (x,y)
194{
195 x = Math.round(x)+0.5;
196 y = Math.round(y)+0.5;
197 this.ctx.moveTo(x, y);
198};
199
200RrdGfxCanvas.prototype.lineTo = function (x,y)
201{
202 x = Math.round(x)+0.5;
203 y = Math.round(y)+0.5;
204 this.ctx.lineTo(x, y)
205};
206
207RrdGfxCanvas.prototype.text = function (x, y, color, font, tabwidth, angle, h_align, v_align, text)
208{
209 x = Math.round(x);
210 y = Math.round(y);
211
212 this.ctx.save();
213 this.ctx.font = font.size+'px '+"'"+font.font+"'";
214
215 switch (h_align) {
216 case RrdGraph.GFX_H_LEFT:
217 this.ctx.textAlign = 'left';
218 break;
219 case RrdGraph.GFX_H_RIGHT:
220 this.ctx.textAlign = 'right';
221 break;
222 case RrdGraph.GFX_H_CENTER:
223 this.ctx.textAlign = 'center';
224 break;
225 }
226
227 switch (v_align) {
228 case RrdGraph.GFX_V_TOP:
229 this.ctx.textBaseline = 'top';
230 break;
231 case RrdGraph.GFX_V_BOTTOM:
232 this.ctx.textBaseline = 'bottom';
233 break;
234 case RrdGraph.GFX_V_CENTER:
235 this.ctx.textBaseline = 'middle';
236 break;
237 }
238
239 this.ctx.fillStyle = color;
240 this.ctx.translate(x,y);
241 this.ctx.rotate(-angle*Math.PI/180.0);
242 this.ctx.fillText(text, 0, 0);
243 this.ctx.restore();
244};
245
246RrdGfxCanvas.prototype.get_text_width = function(start, font, tabwidth, text)
247{
248 this.ctx.save();
249 this.ctx.font = font.size+"px "+font.font;
250 var width = this.ctx.measureText(text);
251 this.ctx.restore();
252 return width.width;
253};
254
diff --git a/js/RrdGfxPdf.js b/js/RrdGfxPdf.js
new file mode 100644
index 0000000..9203020
--- /dev/null
+++ b/js/RrdGfxPdf.js
@@ -0,0 +1,1014 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 **/
18/*******************************************************************************
19* FPDF *
20* *
21* Version: 1.7 *
22* Date: 2011-06-18 *
23* Author: Olivier PLATHEY *
24*******************************************************************************/
25
26// define('FPDF_VERSION','1.7');
27
28"use strict";
29
30/**
31 * RrdGfxPdf
32 * @constructor
33 */
34var RrdGfxPdf = function (orientation, unit, size)
35{
36 if (orientation === undefined)
37 orientation='P';
38 if (unit === undefined)
39 unit='mm';
40 if (size === undefined)
41 size='A4';
42
43 this.lMargin = 0; // left margin
44 this.tMargin = 0; // top margin
45 this.rMargin = 0; // right margin
46 this.bMargin = 0; // page break margin
47 this.cMargin = 0; // cell margin
48
49 this.x = 0; // current position in user unit
50 this.y = 0;
51
52 this.ZoomMode = null; // zoom display mode
53 this.LayoutMode = null; // layout display mode
54 this.title = null; // title
55 this.subject = null; // subject
56 this.author = null; // author
57 this.keywords = null; // keywords
58 this.creator = null; // creator
59
60 // Initialization of properties
61 this.page = 0; // current page number
62 this.offsets = []; // array of object offsets
63 this.n = 2; // current object number
64 this.buffer = ''; // buffer holding in-memory PDF
65 this.pages = []; // array containing pages
66 this.PageSizes = []; // used for pages with non default sizes or orientations
67 this.state = 0; // current document state
68 this.fonts = {}; // array of used fonts
69 this.diffs = []; // array of encoding differences
70 this.FontFamily = ''; // current font family
71 this.FontStyle = ''; // current font style
72 this.FontSizePt = 12; // current font size in points
73 this.FontSize = this.FontSizePt/this.k;
74 this.DrawColor = '0 G'; // commands for drawing color
75 this.FillColor = '0 g'; // commands for filling color
76 this.TextColor = '0 g'; // commands for text color
77 this.ColorFlag = false; // indicates whether fill and text colors are different
78 this.ws = 0; // word spacing
79
80 // Core fonts
81 this.CoreFonts = ['courier', 'helvetica', 'times', 'symbol', 'zapfdingbats'];
82 // Scale factor (number of points in user unit)
83 if(unit === 'pt')
84 this.k = 1;
85 else if(unit === 'mm')
86 this.k = 72/25.4;
87 else if(unit === 'cm')
88 this.k = 72/2.54;
89 else if(unit === 'in')
90 this.k = 72;
91 else
92 throw 'Incorrect unit: '+unit;
93 // Page sizes
94 this.StdPageSizes = {
95 'a3': [841.89 , 1190.55],
96 'a4': [595.28 , 841.89],
97 'a5': [420.94 , 595.28],
98 'letter': [612 , 792],
99 'legal': [612 , 1008]
100 };
101
102 size = this._getpagesize(size);
103 this.DefPageSize = size;
104 this.CurPageSize = size;
105 // Page orientation
106 orientation = orientation.toLowerCase();
107 if(orientation=='p' || orientation=='portrait') {
108 this.DefOrientation = 'P';
109 this.w = size[0];
110 this.h = size[1];
111 } else if(orientation=='l' || orientation=='landscape') {
112 this.DefOrientation = 'L';
113 this.w = size[1];
114 this.h = size[0];
115 } else {
116 throw 'Incorrect orientation: '+orientation;
117 }
118 this.CurOrientation = this.DefOrientation;
119 this.wPt = this.w*this.k;
120 this.hPt = this.h*this.k;
121 // Page margins (1 cm)
122 var margin = 28.35/this.k;
123 this.SetMargins(margin,margin);
124 // Interior cell margin (1 mm)
125 this.cMargin = margin/10;
126 // Line width (0.2 mm)
127 this.LineWidth = .567/this.k;
128 // Default display mode
129 this.SetDisplayMode('default');
130 // Set default PDF version number
131 this.PDFVersion = '1.3';
132};
133
134RrdGfxPdf.CORE_FONTS= {
135 'courierBI': {name: 'Courier-BoldOblique', up: -100, ut: 50, cw: [600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600] },
136 'courierB': {name: 'Courier-Bold', up: -100, ut: 50, cw: [600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600] },
137 'courierI': {name: 'Courier-Oblique', up: -100, ut: 50, cw: [600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600] },
138 'courier': {name: 'Courier', up: -100, ut: 50, cw: [600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600] },
139 'helveticaBI': {name: 'Helvetica-BoldOblique', up: -100, ut: 50, cw: [ 278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, 278,278,278,278,278,278,278,278,278,278,278,333,474,556,556,889,722,238,333,333,389,584, 278,333,278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,975,722, 722,722,722,667,611,778,722,278,556,722,611,833,722,778,667,778,722,667,611,722,667,944, 667,667,611,333,278,333,584,556,333,556,611,556,611,556,333,611,611,278,278,556,278,889, 611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,350,556,350,278,556, 500,1000,556,556,333,1000,667,333,1000,350,611,350,350,278,278,500,500,350,556,1000,333,1000, 556,333,944,350,500,667,278,333,556,556,556,556,280,556,333,737,370,556,584,333,737,333, 400,584,333,333,333,611,556,278,333,333,365,556,834,834,834,611,722,722,722,722,722,722, 1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722, 722,667,667,611,556,556,556,556,556,556,889,556,556,556,556,556,278,278,278,278,611,611, 611,611,611,611,611,584,611,611,611,611,611,556,611,556] },
140 'helveticaB': {name: 'Helvetica-Bold', up: -100, ut: 50, cw: [ 278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, 278,278,278,278,278,278,278,278,278,278,278,333,474,556,556,889,722,238,333,333,389,584, 278,333,278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,975,722, 722,722,722,667,611,778,722,278,556,722,611,833,722,778,667,778,722,667,611,722,667,944, 667,667,611,333,278,333,584,556,333,556,611,556,611,556,333,611,611,278,278,556,278,889, 611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,350,556,350,278,556, 500,1000,556,556,333,1000,667,333,1000,350,611,350,350,278,278,500,500,350,556,1000,333,1000, 556,333,944,350,500,667,278,333,556,556,556,556,280,556,333,737,370,556,584,333,737,333, 400,584,333,333,333,611,556,278,333,333,365,556,834,834,834,611,722,722,722,722,722,722, 1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722, 722,667,667,611,556,556,556,556,556,556,889,556,556,556,556,556,278,278,278,278,611,611, 611,611,611,611,611,584,611,611,611,611,611,556,611,556] },
141 'helveticaI': {name: 'Helvetica-Oblique', up: -100, ut: 50, cw: [ 278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, 278,278,278,278,278,278,278,278,278,278,278,278,355,556,556,889,667,191,333,333,389,584, 278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667, 667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944, 667,667,611,278,278,278,469,556,333,556,556,500,556,556,278,556,556,222,222,500,222,833, 556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,350,556,350,222,556, 333,1000,556,556,333,1000,667,333,1000,350,611,350,350,222,222,333,333,350,556,1000,333,1000, 500,333,944,350,500,667,278,333,556,556,556,556,260,556,333,737,370,556,584,333,737,333, 400,584,333,333,333,556,537,278,333,333,365,556,834,834,834,611,667,667,667,667,667,667, 1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722, 722,667,667,611,556,556,556,556,556,556,889,500,556,556,556,556,278,278,278,278,556,556, 556,556,556,556,556,584,611,556,556,556,556,500,556,500] },
142 'helvetica': {name: 'Helvetica', up: -100, ut: 50, cw: [ 278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, 278,278,278,278,278,278,278,278,278,278,278,278,355,556,556,889,667,191,333,333,389,584, 278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667, 667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944, 667,667,611,278,278,278,469,556,333,556,556,500,556,556,278,556,556,222,222,500,222,833, 556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,350,556,350,222,556, 333,1000,556,556,333,1000,667,333,1000,350,611,350,350,222,222,333,333,350,556,1000,333,1000, 500,333,944,350,500,667,278,333,556,556,556,556,260,556,333,737,370,556,584,333,737,333, 400,584,333,333,333,556,537,278,333,333,365,556,834,834,834,611,667,667,667,667,667,667, 1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722, 722,667,667,611,556,556,556,556,556,556,889,500,556,556,556,556,278,278,278,278,556,556, 556,556,556,556,556,584,611,556,556,556,556,500,556,500] },
143 'timesBI': {name: 'Times-BoldItalic', up: -100, ut: 50, cw: [ 250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250, 250,250,250,250,250,250,250,250,250,250,250,389,555,500,500,833,778,278,333,333,500,570, 250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,832,667, 667,667,722,667,667,722,778,389,500,667,611,889,722,722,611,722,667,556,611,722,667,889, 667,611,611,333,278,333,570,500,333,500,500,444,500,444,333,500,556,278,278,500,278,778, 556,500,500,500,389,389,278,556,444,667,500,444,389,348,220,348,570,350,500,350,333,500, 500,1000,500,500,333,1000,556,333,944,350,611,350,350,333,333,500,500,350,500,1000,333,1000, 389,333,722,350,389,611,250,389,500,500,500,500,220,500,333,747,266,500,606,333,747,333, 400,570,300,300,333,576,500,250,333,300,300,500,750,750,750,500,667,667,667,667,667,667, 944,667,667,667,667,667,389,389,389,389,722,722,722,722,722,722,722,570,722,722,722,722, 722,611,611,500,500,500,500,500,500,500,722,444,444,444,444,444,278,278,278,278,500,556, 500,500,500,500,500,570,500,556,556,556,556,444,500,444] },
144 'timesB': {name: 'Times-Bold', up: -100, ut: 50, cw: [ 250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250, 250,250,250,250,250,250,250,250,250,250,250,333,555,500,500,1000,833,278,333,333,500,570, 250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,930,722, 667,722,722,667,611,778,778,389,500,778,667,944,722,778,611,778,722,556,667,722,722,1000, 722,722,667,333,278,333,581,500,333,500,556,444,556,444,333,500,556,278,333,556,278,833, 556,500,556,556,444,389,333,556,500,722,500,500,444,394,220,394,520,350,500,350,333,500, 500,1000,500,500,333,1000,556,333,1000,350,667,350,350,333,333,500,500,350,500,1000,333,1000, 389,333,722,350,444,722,250,333,500,500,500,500,220,500,333,747,300,500,570,333,747,333, 400,570,300,300,333,556,540,250,333,300,330,500,750,750,750,500,722,722,722,722,722,722, 1000,722,667,667,667,667,389,389,389,389,722,722,778,778,778,778,778,570,778,722,722,722, 722,722,611,556,500,500,500,500,500,500,722,444,444,444,444,444,278,278,278,278,500,556, 500,500,500,500,500,570,500,556,556,556,556,500,556,500] },
145 'timesI': {name: 'Times-Italic', up: -100, ut: 50, cw: [ 250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250, 250,250,250,250,250,250,250,250,250,250,250,333,420,500,500,833,778,214,333,333,500,675, 250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,675,675,675,500,920,611, 611,667,722,611,611,722,722,333,444,667,556,833,667,722,611,722,611,500,556,722,611,833, 611,556,556,389,278,389,422,500,333,500,500,444,500,444,278,500,500,278,278,444,278,722, 500,500,500,500,389,389,278,500,444,667,444,444,389,400,275,400,541,350,500,350,333,500, 556,889,500,500,333,1000,500,333,944,350,556,350,350,333,333,556,556,350,500,889,333,980, 389,333,667,350,389,556,250,389,500,500,500,500,275,500,333,760,276,500,675,333,760,333, 400,675,300,300,333,500,523,250,333,300,310,500,750,750,750,500,611,611,611,611,611,611, 889,667,611,611,611,611,333,333,333,333,722,667,722,722,722,722,722,675,722,722,722,722, 722,556,611,500,500,500,500,500,500,500,667,444,444,444,444,444,278,278,278,278,500,500, 500,500,500,500,500,675,500,500,500,500,500,444,500,444] },
146 'times': {name: 'Times-Roman', up: -100, ut: 50, cw: [ 250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250, 250,250,250,250,250,250,250,250,250,250,250,333,408,500,500,833,778,180,333,333,500,564, 250,333,250,278,500,500,500,500,500,500,500,500,500,500,278,278,564,564,564,444,921,722, 667,667,722,611,556,722,722,333,389,722,611,889,722,722,556,722,667,556,611,722,722,944, 722,722,611,333,278,333,469,500,333,444,500,444,500,444,333,500,500,278,278,500,278,778, 500,500,500,500,333,389,278,500,500,722,500,500,444,480,200,480,541,350,500,350,333,500, 444,1000,500,500,333,1000,556,333,889,350,611,350,350,333,333,444,444,350,500,1000,333,980, 389,333,722,350,444,722,250,333,500,500,500,500,200,500,333,760,276,500,564,333,760,333, 400,564,300,300,333,500,453,250,333,300,310,500,750,750,750,444,722,722,722,722,722,722, 889,667,611,611,611,611,333,333,333,333,722,722,722,722,722,722,722,564,722,722,722,722, 722,722,556,500,444,444,444,444,444,444,667,444,444,444,444,444,278,278,278,278,500,500, 500,500,500,500,500,564,500,500,500,500,500,500,500,500] },
147 'symbol': {name: 'Symbol', up: -100, ut: 50, cw: [ 250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250, 250,250,250,250,250,250,250,250,250,250,250,333,713,500,549,833,778,439,333,333,500,549, 250,549,250,278,500,500,500,500,500,500,500,500,500,500,278,278,549,549,549,444,549,722, 667,722,612,611,763,603,722,333,631,722,686,889,722,722,768,741,556,592,611,690,439,768, 645,795,611,333,863,333,658,500,500,631,549,549,494,439,521,411,603,329,603,549,549,576, 521,549,549,521,549,603,439,576,713,686,493,686,494,480,200,480,549,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,750,620,247,549,167,713,500,753,753,753,753,1042,987,603,987,603, 400,549,411,549,549,713,494,460,549,549,549,549,1000,603,1000,658,823,686,795,987,768,768, 823,768,768,713,713,713,713,713,713,713,768,713,790,790,890,823,549,250,713,603,603,1042, 987,603,987,603,494,329,790,790,786,713,384,384,384,384,384,384,494,494,494,494,0,329,274,686,686,686,384,384,384,384,384,384,494,494,494,0] },
148 'zapfdingbats': {name: 'ZapfDingbats', up: -100, ut: 50, cw: [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,974,961,974,980,719,789,790,791,690,960,939, 549,855,911,933,911,945,974,755,846,762,761,571,677,763,760,759,754,494,552,537,577,692, 786,788,788,790,793,794,816,823,789,841,823,833,816,831,923,744,723,749,790,792,695,776, 768,792,759,707,708,682,701,826,815,789,789,707,687,696,689,786,787,713,791,785,791,873, 761,762,762,759,759,892,892,788,784,438,138,277,415,392,392,668,668,0,390,390,317,317, 276,276,509,509,410,410,234,234,334,334,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,732,544,544,910,667,760,760,776,595,694,626,788,788,788,788, 788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788, 788,788,788,788,788,788,788,788,788,788,788,788,788,788,894,838,1016,458,748,924,748,918, 927,928,928,834,873,828,924,924,917,930,931,463,883,836,836,867,867,696,696,874,0,874, 760,946,771,865,771,888,967,888,831,873,927,970,918,0] }
149};
150
151RrdGfxPdf.prototype.parse_color = function(str)
152{
153 var bits;
154 if ((bits = /^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/.exec(str))) {
155 return [parseInt(bits[1]+bits[1], 16), parseInt(bits[2]+bits[2], 16), parseInt(bits[3]+bits[3], 16), 1.0];
156 } else if ((bits = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(str))) {
157 return [parseInt(bits[1], 16), parseInt(bits[2], 16), parseInt(bits[3], 16), 1.0];
158 } else if ((bits = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(str))) {
159 return [parseInt(bits[1], 16), parseInt(bits[2], 16), parseInt(bits[3], 16), parseInt(bits[4], 16)/255];
160 } else if ((bits = /^rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\)$/.exec(str))) {
161 return [parseInt(bits[1], 10), parseInt(bits[2], 10), parseInt(bits[3], 10), 1.0];
162 } else if ((bits = /^rgba\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([0-9.]+)\)$/.exec(str))) {
163 return [parseInt(bits[1], 10), parseInt(bits[2], 10), parseInt(bits[3], 10), parseFloat(bits[4], 10)];
164 } else {
165 throw "Unknow color format '"+str+"'";
166 }
167};
168
169RrdGfxPdf.prototype.size = function (width, height)
170{
171 var size = [height, width];
172 this.DefPageSize = size;
173 this.CurPageSize = size;
174 this.DefOrientation = 'L';
175 this.w = size[1];
176 this.h = size[0];
177 this.CurOrientation = this.DefOrientation;
178 this.wPt = this.w*this.k;
179 this.hPt = this.h*this.k;
180 this.AddPage();
181};
182
183RrdGfxPdf.prototype.set_dash = function (dashes, n, offset)
184{
185};
186
187RrdGfxPdf.prototype.line = function (X0, Y0, X1, Y1, width, color)
188{
189 this._save();
190 this._setLineWidth(width);
191 var rgba = this.parse_color(color);
192 this._setDrawColor(rgba[0], rgba[1], rgba[2]);
193 this._moveTo(X0, Y0);
194 this._lineTo(X1, Y1);
195 this._stroke();
196 this._restore();
197};
198
199RrdGfxPdf.prototype.dashed_line = function (X0, Y0, X1, Y1, width, color, dash_on, dash_off)
200{
201 this._save();
202 this._setLineWidth(width);
203 var rgba = this.parse_color(color);
204 this._setDrawColor(rgba[0], rgba[1], rgba[2]);
205 this._out('['+(dash_on*this.k)+' '+(dash_off*this.k)+'] 0 d');
206 this._moveTo(X0, Y0);
207 this._lineTo(X1, Y1);
208 this._stroke();
209 this._restore();
210};
211
212RrdGfxPdf.prototype.rectangle = function (X0, Y0, X1, Y1, width, style)
213{
214 this._save();
215 this._setLineWidth(width);
216 var rgba = this.parse_color(style);
217 this._setDrawColor(rgba[0], rgba[1], rgba[2]);
218 this._moveTo(X0, Y0);
219 this._lineTo(X1, Y0);
220 this._lineTo(X1, Y1);
221 this._lineTo(X0, Y1);
222 this._closePath();
223 this._stroke();
224 this._restore();
225};
226
227RrdGfxPdf.prototype.new_area = function (X0, Y0, X1, Y1, X2, Y2, color)
228{
229 var rgba = this.parse_color(color);
230 this._setFillColor(rgba[0], rgba[1], rgba[2]);
231 this._moveTo(X0, Y0);
232 this._lineTo(X1, Y1);
233 this._lineTo(X2, Y2);
234};
235
236RrdGfxPdf.prototype.add_point = function (x, y)
237{
238 this._lineTo(x, y);
239};
240
241RrdGfxPdf.prototype.close_path = function ()
242{
243 this._closePath();
244 this._fill();
245};
246
247RrdGfxPdf.prototype.stroke_begin = function (width, style)
248{
249 this._save();
250 this._setLineWidth(width);
251 var rgba = this.parse_color(style);
252 this._setDrawColor(rgba[0], rgba[1], rgba[2]);
253 this._out('0 J'); // line cap
254 this._out('0 j'); // line join
255};
256
257RrdGfxPdf.prototype.stroke_end = function ()
258{
259 this._stroke();
260 this._restore();
261};
262
263RrdGfxPdf.prototype.moveTo = function (x,y)
264{
265 this._moveTo(x, y);
266};
267
268RrdGfxPdf.prototype.lineTo = function (x,y)
269{
270 this._lineTo(x, y)
271};
272
273RrdGfxPdf.prototype.text = function (x, y, color, font, tabwidth, angle, h_align, v_align, text)
274{
275 this._save();
276 this._setFont('courier', '', font.size*this.k);
277
278 var width = this._getStringWidth('courier', '', font.size*this.k, text);
279 var height = font.size;
280/*
281 this._moveTo(x,y-5); this._lineTo(x,y+5);
282 this._moveTo(x-5,y); this._lineTo(x+5,y);
283 this._moveTo(x+width,y-height-5); this._lineTo(x+width,y-height+5);
284 this._moveTo(x+width-5,y-height); this._lineTo(x+width+5,y-height);
285 this._stroke();
286*/
287 switch (h_align) {
288 case RrdGraph.GFX_H_LEFT:
289 if (angle == -90) {
290 x = x-height/2;
291 }
292 break;
293 case RrdGraph.GFX_H_RIGHT:
294 x = x-width;
295 break;
296 case RrdGraph.GFX_H_CENTER:
297 if (angle != 90) {
298 x = x-width/2;
299 }
300 break;
301 }
302
303 switch (v_align) {
304 case RrdGraph.GFX_V_TOP:
305 if (angle != -90) {
306 y = y + height/2;
307 }
308 break;
309 case RrdGraph.GFX_V_BOTTOM:
310 y = y - height/3;
311 break;
312 case RrdGraph.GFX_V_CENTER:
313 if (angle == 90) {
314 y = y + width/2;
315 } else {
316 y = y + height/4;
317 }
318 break;
319 }
320
321 x = x*this.k;
322 y = (this.h-y)*this.k
323
324 var tm = [];
325 tm[0] = Math.cos(angle*Math.PI/180.0);
326 tm[1] = Math.sin(angle*Math.PI/180.0);
327 tm[2] = -tm[1];
328 tm[3] = tm[0];
329
330 tm[4] = x + (tm[1] * y) - (tm[0] * x);
331 tm[5] = y - (tm[0] * y) - (tm[1] * x);
332
333 var rgba = this.parse_color(color);
334 this._save();
335 this._out('BT');
336 this._out(sprintf('%.3F %.3F %.3F rg', rgba[0]/255,rgba[1]/255,rgba[2]/255));
337 this._out(sprintf('%.2F %.2F Td', x, y));
338 this._out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', tm[0], tm[1], tm[2], tm[3], tm[4], tm[5]));
339 this._out(sprintf('(%s) Tj',this._escape(text)));
340 this._out('ET');
341 this._restore();
342};
343
344RrdGfxPdf.prototype.get_text_width = function(start, font, tabwidth, text)
345{
346 var width = this._getStringWidth('courier', '', font.size*this.k, text);
347 return width;
348};
349
350/**** Public methods *****/
351
352RrdGfxPdf.prototype.SetMargins = function(left, top, right)
353{
354 if (right === undefined)
355 right = null;
356 // Set left, top and right margins
357 this.lMargin = left;
358 this.tMargin = top;
359 if(right===null)
360 right = left;
361 this.rMargin = right;
362};
363
364RrdGfxPdf.prototype.SetLeftMargin = function(margin)
365{
366 // Set left margin
367 this.lMargin = margin;
368 if(this.page>0 && this.x<margin)
369 this.x = margin;
370};
371
372RrdGfxPdf.prototype.SetTopMargin = function(margin)
373{
374 // Set top margin
375 this.tMargin = margin;
376};
377
378RrdGfxPdf.prototype.SetRightMargin = function(margin)
379{
380 // Set right margin
381 this.rMargin = margin;
382};
383
384RrdGfxPdf.prototype.SetDisplayMode = function(zoom, layout)
385{
386 // Set display mode in viewer
387 if(zoom === 'fullpage' || zoom === 'fullwidth' || zoom === 'real' || zoom == 'default' || !(typeof zoom === "string"))
388 this.ZoomMode = zoom;
389 else
390 throw 'Incorrect zoom display mode: '+zoom;
391
392 if(layout === undefined) {
393 this.LayoutMode = 'default';
394 } else if(layout === 'single' || layout === 'continuous' || layout === 'two' || layout === 'default') {
395 this.LayoutMode = layout;
396 } else {
397 throw 'Incorrect layout display mode: '+layout;
398 }
399};
400
401RrdGfxPdf.prototype.SetTitle = function(title)
402{
403 // Title of document
404 this.title = title;
405};
406
407RrdGfxPdf.prototype.SetSubject = function(subject)
408{
409 // Subject of document
410 this.subject = subject;
411};
412
413RrdGfxPdf.prototype.SetAuthor = function(author)
414{
415 // Author of document
416 this.author = author;
417};
418
419RrdGfxPdf.prototype.SetKeywords = function(keywords)
420{
421 // Keywords of document
422 this.keywords = keywords;
423};
424
425RrdGfxPdf.prototype.SetCreator = function(creator)
426{
427 // Creator of document
428 this.creator = creator;
429};
430
431RrdGfxPdf.prototype.Open = function()
432{
433 // Begin document
434 this.state = 1;
435};
436
437RrdGfxPdf.prototype.Close = function()
438{
439 // Terminate document
440 if(this.state==3)
441 return;
442 if(this.page==0)
443 this.AddPage();
444 // Close page
445 this._endpage();
446 // Close document
447 this._enddoc();
448};
449
450RrdGfxPdf.prototype.AddPage = function(orientation, size)
451{
452 if (orientation === undefined) orientation='';
453 if (size === undefined) size='';
454
455 // Start a new page
456 if(this.state==0)
457 this.Open();
458
459 var family = this.FontFamily;
460 var style = this.FontStyle;
461 var fontsize = this.FontSizePt;
462 var lw = this.LineWidth;
463 var dc = this.DrawColor;
464 var fc = this.FillColor;
465 var tc = this.TextColor;
466 var cf = this.ColorFlag;
467
468 if(this.page>0)
469 {
470 // Close page
471 this._endpage();
472 }
473 // Start new page
474 this._beginpage(orientation,size);
475 // Set line cap style to square
476 this._out('2 J');
477 // Set line width
478 this.LineWidth = lw;
479 this._out(sprintf('%.2F w',lw*this.k));
480 // Set font
481 if(family)
482 this._setFont(family,style,fontsize);
483 // Set colors
484 this.DrawColor = dc;
485 if(dc!='0 G')
486 this._out(dc);
487 this.FillColor = fc;
488 if(fc!='0 g')
489 this._out(fc);
490 this.TextColor = tc;
491 this.ColorFlag = cf;
492 // Restore line width
493 if(this.LineWidth!=lw)
494 {
495 this.LineWidth = lw;
496 this._out(sprintf('%.2F w',lw*this.k));
497 }
498 // Restore font
499 if(family)
500 this._setFont(family,style,fontsize);
501 // Restore colors
502 if(this.DrawColor!=dc)
503 {
504 this.DrawColor = dc;
505 this._out(dc);
506 }
507 if(this.FillColor!=fc)
508 {
509 this.FillColor = fc;
510 this._out(fc);
511 }
512 this.TextColor = tc;
513 this.ColorFlag = cf;
514};
515
516RrdGfxPdf.prototype.PageNo = function()
517{
518 // Get current page number
519 return this.page;
520};
521
522RrdGfxPdf.prototype._setDrawColor = function(r, g, b)
523{
524 if (g === undefined) g=null;
525 if (b === undefined) b=null;
526 // Set color for all stroking operations
527 if((r==0 && g==0 && b==0) || g===null)
528 this.DrawColor = sprintf('%.3F G',r/255);
529 else
530 this.DrawColor = sprintf('%.3F %.3F %.3F RG',r/255,g/255,b/255);
531 if(this.page>0)
532 this._out(this.DrawColor);
533};
534
535RrdGfxPdf.prototype._setFillColor = function(r, g, b)
536{
537 if (g === undefined) g=null;
538 if (b === undefined) b=null;
539 // Set color for all filling operations
540 if((r==0 && g==0 && b==0) || g===null)
541 this.FillColor = sprintf('%.3F g',r/255);
542 else
543 this.FillColor = sprintf('%.3F %.3F %.3F rg',r/255,g/255,b/255);
544 this.ColorFlag = (this.FillColor!=this.TextColor);
545 if(this.page>0)
546 this._out(this.FillColor);
547};
548
549RrdGfxPdf.prototype._setTextColor = function(r, g, b)
550{
551 if (g === undefined) g=null;
552 if (b === undefined) b=null;
553 // Set color for text
554 if((r==0 && g==0 && b==0) || g===null)
555 this.TextColor = sprintf('%.3F g',r/255);
556 else
557 this.TextColor = sprintf('%.3F %.3F %.3F rg',r/255,g/255,b/255);
558 this.ColorFlag = (this.FillColor!=this.TextColor);
559};
560
561RrdGfxPdf.prototype._getStringWidth = function(family, style, size, s)
562{
563 if (style === undefined) style = '';
564 if (size === undefined) size = 0;
565 // Select a font; size given in points
566
567 if(family=='') family = this.FontFamily;
568 else family = family.toLowerCase();
569
570 style = style.toUpperCase();
571 if(style=='IB') style = 'BI';
572
573 if(size==0) size = this.FontSizePt;
574
575 // Test if font is already loaded
576 var fontkey = family+style;
577 if(!(fontkey in this.fonts)) {
578 // Test if one of the core fonts
579 if(family=='arial') family = 'helvetica';
580 if(family=='symbol' || family=='zapfdingbats') style = '';
581 fontkey = family+style;
582
583 if (!(fontkey in this.fonts))
584 this.AddFont(family, style);
585 }
586 // Select it
587 size = size/this.k;
588 var cw = this.fonts[fontkey].cw;
589 var w = 0;
590 var l = s.length;
591 for(var i=0; i<l; i++) {
592 w += cw[s.charCodeAt(i)];
593 }
594 return w*size/1000;
595};
596
597RrdGfxPdf.prototype._setLineWidth = function(width)
598{
599 // Set line width
600 this.LineWidth = width;
601 if(this.page>0)
602 this._out(sprintf('%.2F w',width*this.k));
603};
604
605RrdGfxPdf.prototype._moveTo = function(x, y)
606{
607 this._out(sprintf('%.2F %.2F m',x*this.k,(this.h-y)*this.k));
608};
609
610RrdGfxPdf.prototype._lineTo = function(x, y)
611{
612 this._out(sprintf('%.2F %.2F l',x*this.k,(this.h-y)*this.k));
613};
614
615RrdGfxPdf.prototype._stroke = function()
616{
617 this._out('S');
618};
619
620RrdGfxPdf.prototype._save = function()
621{
622 this._out('q');
623};
624
625RrdGfxPdf.prototype._restore = function()
626{
627 this._out('Q');
628};
629
630RrdGfxPdf.prototype._closePath = function()
631{
632 this._out('h');
633};
634
635RrdGfxPdf.prototype._fill = function()
636{
637 this._out('f');
638};
639
640RrdGfxPdf.prototype._line = function(x1, y1, x2, y2)
641{
642 // Draw a line
643 this._out(sprintf('%.2F %.2F m %.2F %.2F l S',x1*this.k,(this.h-y1)*this.k,x2*this.k,(this.h-y2)*this.k));
644};
645
646RrdGfxPdf.prototype._rect = function(x, y, w, h, style)
647{
648 var op;
649 // Draw a rectangle
650 if(style=='F')
651 op = 'f';
652 else if(style=='FD' || style=='DF')
653 op = 'B';
654 else
655 op = 'S';
656 this._out(sprintf('%.2F %.2F %.2F %.2F re %s',x*this.k,(this.h-y)*this.k,w*this.k,-h*this.k,op));
657};
658
659RrdGfxPdf.prototype.AddFont = function (family, style, file)
660{
661 if (style === undefined) style = '';
662
663 if(family=='') family = this.FontFamily;
664 else family = family.toLowerCase();
665
666 style = style.toUpperCase();
667 if(style=='IB') style = 'BI';
668
669 var fontkey = family+style;
670 if(fontkey in this.fonts)
671 return;
672
673 if(fontkey in RrdGfxPdf.CORE_FONTS){
674 var font = RrdGfxPdf.CORE_FONTS[fontkey];
675 this.fonts[fontkey] = font;
676 var i=0;
677 for (var n in this.fonts) i++;
678 font['i'] = i;
679 } else {
680 throw 'Undefined font: '+family+' '+style;
681 }
682};
683
684RrdGfxPdf.prototype._setFont = function(family, style, size)
685{
686 if (style === undefined) style = '';
687 if (size === undefined) size = 0;
688 // Select a font; size given in points
689
690 if(family=='') family = this.FontFamily;
691 else family = family.toLowerCase();
692
693 style = style.toUpperCase();
694 if(style=='IB') style = 'BI';
695
696 if(size==0) size = this.FontSizePt;
697
698 // Test if font is already selected
699 //if(this.FontFamily==family && this.FontStyle==style && this.FontSizePt==size)
700 // return;
701
702 // Test if font is already loaded
703 var fontkey = family+style;
704 if(!(fontkey in this.fonts)) {
705 // Test if one of the core fonts
706 if(family=='arial') family = 'helvetica';
707 if(family=='symbol' || family=='zapfdingbats') style = '';
708 fontkey = family+style;
709
710 if (!(fontkey in this.fonts))
711 this.AddFont(family, style);
712 }
713 // Select it
714 this.FontFamily = family;
715 this.FontStyle = style;
716 this.FontSizePt = size;
717 this.FontSize = size/this.k;
718 this.CurrentFont = this.fonts[fontkey];
719 if(this.page>0)
720 this._out(sprintf('BT /F%d %.2F Tf ET',this.CurrentFont['i'],this.FontSizePt)); // FIXME i
721};
722
723RrdGfxPdf.prototype._setFontSize = function(size)
724{
725 // Set font size in points
726 //if(this.FontSizePt==size)
727 // return;
728 this.FontSizePt = size;
729 this.FontSize = size/this.k;
730 if(this.page>0)
731 this._out(sprintf('BT /F%d %.2F Tf ET',this.CurrentFont['i'],this.FontSizePt));
732};
733
734RrdGfxPdf.prototype._text = function(x, y, txt)
735{
736 // Output a string
737 var s = sprintf('BT %.2F %.2F Td (%s) Tj ET',x*this.k,(this.h-y)*this.k,this._escape(txt));
738 if(this.ColorFlag)
739 s = 'q '+this.TextColor+' '+s+' Q';
740 this._out(s);
741};
742
743RrdGfxPdf.prototype.output = function()
744{
745 // Output PDF to some destination
746 if(this.state<3)
747 this.Close();
748 document.location.href = 'data:application/pdf;base64,' + Base64.encode(this.buffer);
749 //return this.buffer;
750};
751
752RrdGfxPdf.prototype._getpagesize = function(size) // FIXME
753{
754 if(typeof size === "string" ) {
755 size = size.toLowerCase();
756 if(!(size in this.StdPageSizes))
757 throw 'Unknown page size: '+size;
758 var a = this.StdPageSizes[size];
759 return [a[0]/this.k, a[1]/this.k];
760 } else {
761 if(size[0]>size[1]) {
762 return [size[1], size[0]];
763 } else {
764 return size;
765 }
766 }
767};
768
769RrdGfxPdf.prototype._beginpage = function(orientation, size)
770{
771 this.page++;
772 this.pages[this.page] = '';
773 this.state = 2;
774 this.x = this.lMargin;
775 this.y = this.tMargin;
776 this.FontFamily = '';
777 // Check page size and orientation
778 if(orientation=='') orientation = this.DefOrientation;
779 else orientation = strtoupper(orientation[0]);
780
781 if(size=='') size = this.DefPageSize;
782 else size = this._getpagesize(size);
783
784 if(orientation!=this.CurOrientation || size[0]!=this.CurPageSize[0] || size[1]!=this.CurPageSize[1])
785 {
786 // New size or orientation
787 if(orientation=='P') {
788 this.w = size[0];
789 this.h = size[1];
790 } else {
791 this.w = size[1];
792 this.h = size[0];
793 }
794 this.wPt = this.w*this.k;
795 this.hPt = this.h*this.k;
796 this.CurOrientation = orientation;
797 this.CurPageSize = size;
798 }
799 if(orientation!=this.DefOrientation || size[0]!=this.DefPageSize[0] || size[1]!=this.DefPageSize[1])
800 this.PageSizes[this.page] = [this.wPt, this.hPt];
801};
802
803RrdGfxPdf.prototype._endpage = function()
804{
805 this.state = 1;
806};
807
808RrdGfxPdf.prototype._escape = function(s) // FIXME
809{
810 // Escape special characters in strings
811 //s = str_replace('\\','\\\\',s);
812 //s = str_replace('(','\\(',s);
813 //s = str_replace(')','\\)',s);
814 //s = str_replace("\r",'\\r',s);
815 return s.replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
816};
817
818RrdGfxPdf.prototype._textstring = function(s)
819{
820 // Format a text string
821 return '('+this._escape(s)+')';
822};
823
824RrdGfxPdf.prototype._newobj = function()
825{
826 // Begin a new object
827 this.n++;
828 this.offsets[this.n] = this.buffer.length;
829 this._out(this.n+' 0 obj');
830};
831
832RrdGfxPdf.prototype._putstream = function(s)
833{
834 this._out('stream');
835 this._out(s);
836 this._out('endstream');
837},
838
839RrdGfxPdf.prototype._out = function(s)
840{
841// Add a line to the document
842 if(this.state==2)
843 this.pages[this.page] += s+"\n";
844 else
845 this.buffer += s+"\n";
846};
847
848RrdGfxPdf.prototype._putpages = function()
849{
850 var wPt, hPt;
851 var nb = this.page;
852 if(this.DefOrientation=='P') {
853 wPt = this.DefPageSize[0]*this.k;
854 hPt = this.DefPageSize[1]*this.k;
855 } else {
856 wPt = this.DefPageSize[1]*this.k;
857 hPt = this.DefPageSize[0]*this.k;
858 }
859 for(var n=1;n<=nb;n++)
860 {
861 // Page
862 this._newobj();
863 this._out('<</Type /Page');
864 this._out('/Parent 1 0 R');
865 if(this.PageSizes[n] !== undefined)
866 this._out(sprintf('/MediaBox [0 0 %.2F %.2F]',this.PageSizes[n][0],this.PageSizes[n][1]));
867 this._out('/Resources 2 0 R');
868 if(this.PDFVersion>'1.3')
869 this._out('/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>');
870 this._out('/Contents '+(this.n+1)+' 0 R>>');
871 this._out('endobj');
872 // Page content
873 this._newobj();
874 this._out('<</Length '+this.pages[n].length+'>>');
875 this._putstream(this.pages[n]);
876 this._out('endobj');
877 }
878 // Pages root
879 this.offsets[1] = this.buffer.length;
880 this._out('1 0 obj');
881 this._out('<</Type /Pages');
882 var kids = '/Kids [';
883 for(var i=0;i<nb;i++)
884 kids += (3+2*i)+' 0 R ';
885 this._out(kids+']');
886 this._out('/Count '+nb);
887 this._out(sprintf('/MediaBox [0 0 %.2F %.2F]',wPt,hPt));
888 this._out('>>');
889 this._out('endobj');
890};
891
892RrdGfxPdf.prototype._putfonts = function()
893{
894 var nf = this.n;
895 for(var diff in this.diffs) {
896 // Encodings
897 this._newobj();
898 this._out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+diff+']>>');
899 this._out('endobj');
900 }
901 for(var font in this.fonts) { // FIXME
902 // Font objects
903 this.fonts[font]['n'] = this.n+1;
904 var name = this.fonts[font]['name'];
905 // Core font
906 this._newobj();
907 this._out('<</Type /Font');
908 this._out('/BaseFont /'+name);
909 this._out('/Subtype /Type1');
910 if(name!='Symbol' && name!='ZapfDingbats')
911 this._out('/Encoding /WinAnsiEncoding');
912 this._out('>>');
913 this._out('endobj');
914 }
915};
916
917RrdGfxPdf.prototype._putresourcedict = function()
918{
919 this._out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
920 this._out('/Font <<');
921 for(var font in this.fonts)
922 this._out('/F'+this.fonts[font]['i']+' '+this.fonts[font]['n']+' 0 R');
923 this._out('>>');
924 this._out('/XObject <<');
925 this._out('>>');
926};
927
928RrdGfxPdf.prototype._putresources = function()
929{
930 this._putfonts();
931 // Resource dictionary
932 this.offsets[2] = this.buffer.length;
933 this._out('2 0 obj');
934 this._out('<<');
935 this._putresourcedict();
936 this._out('>>');
937 this._out('endobj');
938};
939
940RrdGfxPdf.prototype._putinfo = function()
941{
942 // this._out('/Producer '+this._textstring('FPDF '+FPDF_VERSION)); FIXME
943 if(this.title != null)
944 this._out('/Title '+this._textstring(this.title));
945 if(this.subject != null)
946 this._out('/Subject '+this._textstring(this.subject));
947 if(this.author != null)
948 this._out('/Author '+this._textstring(this.author));
949 if(this.keywords != null)
950 this._out('/Keywords '+this._textstring(this.keywords));
951 if(this.creator != null)
952 this._out('/Creator '+this._textstring(this.creator));
953 // this._out('/CreationDate '+this._textstring('D:'+date('YmdHis'))); // FIXME
954};
955
956RrdGfxPdf.prototype._putcatalog = function()
957{
958 this._out('/Type /Catalog');
959 this._out('/Pages 1 0 R');
960
961 if(this.ZoomMode=='fullpage')
962 this._out('/OpenAction [3 0 R /Fit]');
963 else if(this.ZoomMode=='fullwidth')
964 this._out('/OpenAction [3 0 R /FitH null]');
965 else if(this.ZoomMode=='real')
966 this._out('/OpenAction [3 0 R /XYZ null null 1]');
967 else if(!(typeof this.ZoomMode === 'string'))
968 this._out('/OpenAction [3 0 R /XYZ null null '+sprintf('%.2F',this.ZoomMode/100)+']');
969
970 if(this.LayoutMode=='single')
971 this._out('/PageLayout /SinglePage');
972 else if(this.LayoutMode=='continuous')
973 this._out('/PageLayout /OneColumn');
974 else if(this.LayoutMode=='two')
975 this._out('/PageLayout /TwoColumnLeft');
976};
977
978RrdGfxPdf.prototype._enddoc = function()
979{
980 this._out('%PDF-'+this.PDFVersion);
981 this._putpages();
982 this._putresources();
983 // Info
984 this._newobj();
985 this._out('<<');
986 this._putinfo();
987 this._out('>>');
988 this._out('endobj');
989 // Catalog
990 this._newobj();
991 this._out('<<');
992 this._putcatalog();
993 this._out('>>');
994 this._out('endobj');
995 // Cross-ref
996 var o = this.buffer.length;
997 this._out('xref');
998 this._out('0 '+(this.n+1));
999 this._out('0000000000 65535 f ');
1000 for(var i=1;i<=this.n;i++)
1001 this._out(sprintf('%010d 00000 n ',this.offsets[i]));
1002 // Trailer
1003 this._out('trailer');
1004 this._out('<<');
1005 this._out('/Size '+(this.n+1));
1006 this._out('/Root '+this.n+' 0 R');
1007 this._out('/Info '+(this.n-1)+' 0 R');
1008 this._out('>>');
1009 this._out('startxref');
1010 this._out(o);
1011 this._out('%%EOF');
1012 this.state = 3;
1013};
1014
diff --git a/js/RrdGfxSvg.js b/js/RrdGfxSvg.js
new file mode 100644
index 0000000..2bdc1b9
--- /dev/null
+++ b/js/RrdGfxSvg.js
@@ -0,0 +1,250 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 *
18 * Manuel Sanmartin <manuel.luis at gmail.com>
19 **/
20
21"use strict";
22
23/**
24 * RrdGfxSvg
25 * @constructor
26 */
27var RrdGfxSvg = function(svgId) {
28 this.svg = document.getElementById(svgId)
29 this.svgns = "http://www.w3.org/2000/svg";
30 this.xmlns = "http://www.w3.org/XML/1998/namespace";
31 this.path = null;
32 this.path_color = null;
33 this.path_width = null;
34};
35
36RrdGfxSvg.prototype.size = function (width, height)
37{
38 while(this.svg.lastChild)
39 this.svg.removeChild(this.svg.lastChild);
40
41 this.svg.setAttribute("width", width+"px");
42 this.svg.setAttribute("height", height+"px");
43 this.svg.setAttribute("viewBox", "0 0 "+width+" "+height);
44};
45
46RrdGfxSvg.prototype.set_dash = function (dashes, n, offset)
47{
48};
49
50RrdGfxSvg.prototype.line = function (X0, Y0, X1, Y1, width, color)
51{
52 var shape = document.createElementNS(this.svgns, "line");
53
54 X0 = Math.round(X0)+0.5;
55 Y0 = Math.round(Y0)+0.5;
56 X1 = Math.round(X1)+0.5;
57 Y1 = Math.round(Y1)+0.5;
58
59 shape.setAttributeNS(null, "x1", X0);
60 shape.setAttributeNS(null, "y1", Y0);
61 shape.setAttributeNS(null, "x2", X1);
62 shape.setAttributeNS(null, "y2", Y1);
63 shape.setAttributeNS(null, "stroke-width", width);
64 shape.setAttributeNS(null, "stroke", color);
65
66 this.svg.appendChild(shape);
67};
68
69RrdGfxSvg.prototype.dashed_line = function (X0, Y0, X1, Y1, width, color, dash_on, dash_off)
70{
71 var shape = document.createElementNS(this.svgns, "line");
72
73 X0 = Math.round(X0)+0.5;
74 Y0 = Math.round(Y0)+0.5;
75 X1 = Math.round(X1)+0.5;
76 Y1 = Math.round(Y1)+0.5;
77
78 shape.setAttributeNS(null, "x1", X0);
79 shape.setAttributeNS(null, "y1", Y0);
80 shape.setAttributeNS(null, "x2", X1);
81 shape.setAttributeNS(null, "y2", Y1);
82 shape.setAttributeNS(null, "stroke-width", width);
83 shape.setAttributeNS(null, "stroke", color);
84 shape.setAttributeNS(null, "stroke-dasharray", dash_on+','+dash_off);
85
86 this.svg.appendChild(shape);
87};
88
89RrdGfxSvg.prototype.rectangle = function (X0, Y0, X1, Y1, width, style)
90{
91 var shape = document.createElementNS(this.svgns, "rect");
92
93 var rwidth = Math.abs(X1-X0);
94 var rheight = Math.abs(Y1-Y0);
95
96 shape.setAttributeNS(null, "x", Math.round(X0)+0.5);
97 shape.setAttributeNS(null, "y", Math.round(Y0-rheight)+0.5);
98 shape.setAttributeNS(null, "width", rwidth);
99 shape.setAttributeNS(null, "height", rheight);
100 shape.setAttributeNS(null, "stroke-width", width);
101 shape.setAttributeNS(null, "stroke", style);
102 shape.setAttributeNS(null, "fill", "none");
103
104 this.svg.appendChild(shape);
105};
106
107RrdGfxSvg.prototype.new_area = function (X0, Y0, X1, Y1, X2, Y2, color)
108{
109 X0 = Math.round(X0)+0.5;
110 Y0 = Math.round(Y0)+0.5;
111 X1 = Math.round(X1)+0.5;
112 Y1 = Math.round(Y1)+0.5;
113 X2 = Math.round(X2)+0.5;
114 Y2 = Math.round(Y2)+0.5;
115
116 this.path_color = color;
117 this.path = 'M'+X0+','+Y0;
118 this.path += ' L'+X1+','+Y1;
119 this.path += ' L'+X2+','+Y2;
120};
121
122RrdGfxSvg.prototype.add_point = function (x, y)
123{
124 x = Math.round(x)+0.5;
125 y = Math.round(y)+0.5;
126
127 this.path += ' L'+x+','+y;
128};
129
130RrdGfxSvg.prototype.close_path = function ()
131{
132 var shape = document.createElementNS(this.svgns, "path");
133
134 this.path += ' Z';
135
136 shape.setAttributeNS(null, "d", this.path);
137 shape.setAttributeNS(null, "fill", this.path_color);
138 shape.setAttributeNS(null, "stroke", 'none');
139
140 this.svg.appendChild(shape);
141};
142
143RrdGfxSvg.prototype.stroke_begin = function (width, style)
144{
145 this.path_width = width;
146 this.path_color = style;
147 this.path = '';
148};
149
150RrdGfxSvg.prototype.stroke_end = function ()
151{
152 var shape = document.createElementNS(this.svgns, "path");
153
154 shape.setAttributeNS(null, "d", this.path);
155 shape.setAttributeNS(null, "fill", 'none');
156 shape.setAttributeNS(null, "stroke", this.path_color);
157 shape.setAttributeNS(null, "stroke-width", this.path_width);
158 shape.setAttributeNS(null, "stroke-linecap", 'round');
159 shape.setAttributeNS(null, "stroke-linejoin", 'round');
160
161 this.svg.appendChild(shape);
162};
163
164RrdGfxSvg.prototype.moveTo = function (x,y)
165{
166 x = Math.round(x)+0.5;
167 y = Math.round(y)+0.5;
168
169 this.path += ' M'+x+','+y;
170};
171
172RrdGfxSvg.prototype.lineTo = function (x,y)
173{
174 x = Math.round(x)+0.5;
175 y = Math.round(y)+0.5;
176
177 this.path += ' L'+x+','+y;
178};
179
180RrdGfxSvg.prototype.text = function (x, y, color, font, tabwidth, angle, h_align, v_align, text)
181{
182 x = Math.round(x);
183 y = Math.round(y);
184
185 var svgtext = document.createElementNS(this.svgns, "text");
186
187 var data = document.createTextNode(text);
188
189 svgtext.setAttributeNS(null, "x", x);
190 svgtext.setAttributeNS(null, "y", y);
191 svgtext.setAttributeNS(null, "fill", color);
192 svgtext.setAttributeNS(null, "stroke", "none");
193 svgtext.setAttributeNS(null, "font-family", font.font);
194 svgtext.setAttributeNS(null, "font-size", font.size+"px");
195 svgtext.setAttributeNS(this.xmlns, "xml:space", "preserve");
196
197 angle=-angle;
198 svgtext.setAttributeNS(null, "transform", 'rotate('+angle+' '+x+','+y+')' );
199
200 switch (h_align) {
201 case RrdGraph.GFX_H_LEFT:
202 svgtext.setAttributeNS(null, "text-anchor", 'start');
203 break;
204 case RrdGraph.GFX_H_RIGHT:
205 svgtext.setAttributeNS(null, "text-anchor", 'end');
206 break;
207 case RrdGraph.GFX_H_CENTER:
208 svgtext.setAttributeNS(null, "text-anchor", 'middle');
209 break;
210 }
211 svgtext.appendChild(data);
212 this.svg.appendChild(svgtext);
213
214 var bbox = svgtext.getBBox();
215
216 switch (v_align) { // FIXME
217 case RrdGraph.GFX_V_TOP:
218 svgtext.setAttributeNS(null, "y", y+bbox.height/2);
219 break;
220 case RrdGraph.GFX_V_BOTTOM:
221 svgtext.setAttributeNS(null, "y", y-bbox.height/6);
222 break;
223 case RrdGraph.GFX_V_CENTER:
224 svgtext.setAttributeNS(null, "y", y+bbox.height/4);
225 break;
226 }
227};
228
229RrdGfxSvg.prototype.get_text_width = function(start, font, tabwidth, text)
230{
231 var svgtext = document.createElementNS(this.svgns, "text");
232 var data = document.createTextNode(text);
233 svgtext.setAttributeNS(null, "x", 0);
234 svgtext.setAttributeNS(null, "y", 0);
235 svgtext.setAttributeNS(null, "fill", 'none');
236 svgtext.setAttributeNS(null, "stroke", 'none');
237 svgtext.setAttributeNS(null, "font-family", font.font);
238 svgtext.setAttributeNS(null, "font-size", font.size+"px");
239 svgtext.setAttributeNS(this.xmlns, "xml:space", "preserve");
240 svgtext.appendChild(data);
241 this.svg.appendChild(svgtext);
242
243 var bbox = svgtext.getBBox();
244
245 svgtext.removeChild(data);
246 this.svg.removeChild(svgtext);
247
248 return bbox.width;
249};
250
diff --git a/js/RrdGraph.js b/js/RrdGraph.js
new file mode 100644
index 0000000..9b8e3db
--- /dev/null
+++ b/js/RrdGraph.js
@@ -0,0 +1,2914 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 * RRDtool 1.4.5 Copyright by Tobi Oetiker, 1997-2010
18 *
19 * Convert to javascript: Manuel Sanmartin <manuel.luis at gmail.com>
20 **/
21
22"use strict";
23
24/**
25 * RrdGraphDescError
26 * @constructor
27 */
28var RrdGraphDescError = function (message)
29{
30 this.prototype = Error.prototype;
31 this.name = "RrdGraphDescError";
32 this.message = (message) ? message : "Error";
33};
34
35/**
36 * RrdGraphDesc
37 * @constructor
38 */
39var RrdGraphDesc = function (graph)
40{
41 this.gf = null; /* graphing function */
42 this.stack = false; /* boolean */
43 this.debug = false; /* boolean */
44 this.vname = null; /* name of the variable */
45 this.vidx = Number.NaN; /* gdes reference */
46 this.rrd = null; /* name of the rrd_file containing data */
47 this.ds_nam = null; /* data source name */
48 this.ds = -1; /* data source number */
49 this.cf = RrdGraphDesc.CF_AVERAGE; /* consolidation function */
50 this.cf_reduce = RrdGraphDesc.CF_AVERAGE; /* consolidation function for reduce_data() */
51 this.col = null; /* graph color */
52 this.format = null; /* format for PRINT AND GPRINT */
53 this.legend = null; /* legend */
54 this.strftm = false; /* should the VDEF legend be formated with strftime */
55 this.leg_x = 0; /* location of legend */
56 this.leg_y = 0;
57 this.yrule = Number.NaN; /* value for y rule line and for VDEF */
58 this.xrule = 0; /* time for x rule line and for VDEF */
59 this.vf = null; /* instruction for VDEF function */
60 this.rpnp = null; /* instructions for CDEF function */
61
62 /* SHIFT implementation */
63 this.shidx = 0; /* gdes reference for offset (-1 --> constant) */
64 this.shval = 0; /* offset if shidx is -1 */
65 this.shift = 0; /* current shift applied */
66
67 /* description of data fetched for the graph element */
68 this.start = graph.start; /* timestaps for first and last data element */
69 this.end = graph.end;
70 this.start_orig = graph.start; /* timestaps for first and last data element */
71 this.end_orig = graph.end;
72 this.step = graph.step; /* time between samples */
73 this.step_orig = graph.step; /* time between samples */
74 this.ds_cnt = 0; /* how many data sources are there in the fetch */
75 this.data_first = 0; /* first pointer to this data */
76 this.ds_namv = []; /* name of datasources in the fetch. */
77 this.data = []; /* the raw data drawn from the rrd */
78 this.p_data = []; /* processed data, xsize elments */
79 this.linewidth = 0; /* linewideth */
80
81 /* dashed line stuff */
82 this.dash = false; /* boolean, draw dashed line? */
83 this.p_dashes = []; /* pointer do dash array which keeps the lengths of dashes */
84 this.ndash = false; /* number of dash segments */
85 this.offset = 0; /* dash offset along the line */
86
87 this.txtalign = 0; /* change default alignment strategy for text */
88
89 /** ** **/
90 var args = []; // FIXME other way ¿?
91 var type = arguments[1]
92 args[0] = arguments[0];
93 for(var i = 2; i < arguments.length; i++) args[i-1] = arguments[i];
94
95 switch (type) {
96 case RrdGraphDesc.GF_GPRINT:
97 this.gprint.apply(this, args);
98 break;
99 case RrdGraphDesc.GF_COMMENT:
100 this.comment.apply(this, args);
101 break;
102 case RrdGraphDesc.GF_HRULE:
103 this.hrule.apply(this, args);
104 break;
105 case RrdGraphDesc.GF_VRULE:
106 this.vrule.apply(this, args);
107 break;
108 case RrdGraphDesc.GF_LINE:
109 this.line.apply(this, args);
110 break;
111 case RrdGraphDesc.GF_AREA:
112 this.area.apply(this, args);
113 break;
114 case RrdGraphDesc.GF_TICK:
115 this.tick.apply(this, args);
116 break;
117 case RrdGraphDesc.GF_TEXTALIGN:
118 this.textaling.apply(this, args);
119 break;
120 case RrdGraphDesc.GF_DEF:
121 this.def.apply(this, args);
122 break;
123 case RrdGraphDesc.GF_CDEF:
124 this.cdef.apply(this, args);
125 break;
126 case RrdGraphDesc.GF_VDEF:
127 this.vdef.apply(this, args);
128 break;
129 case RrdGraphDesc.GF_SHIFT:
130 this.fshift.apply(this, args);
131 break;
132 }
133};
134
135RrdGraphDesc.GF_PRINT = 0;
136RrdGraphDesc.GF_GPRINT = 1;
137RrdGraphDesc.GF_COMMENT = 2;
138RrdGraphDesc.GF_HRULE = 3;
139RrdGraphDesc.GF_VRULE = 4;
140RrdGraphDesc.GF_LINE = 5;
141RrdGraphDesc.GF_AREA = 6;
142RrdGraphDesc.GF_STACK = 7;
143RrdGraphDesc.GF_TICK = 8;
144RrdGraphDesc.GF_TEXTALIGN = 9;
145RrdGraphDesc.GF_DEF = 10;
146RrdGraphDesc.GF_CDEF = 11;
147RrdGraphDesc.GF_VDEF = 12;
148RrdGraphDesc.GF_SHIFT = 13;
149RrdGraphDesc.GF_XPORT = 14;
150
151RrdGraphDesc.CF_AVERAGE = 0;
152RrdGraphDesc.CF_MINIMUM = 1;
153RrdGraphDesc.CF_MAXIMUM = 2;
154RrdGraphDesc.CF_LAST = 3;
155RrdGraphDesc.CF_HWPREDICT = 4;
156RrdGraphDesc.CF_SEASONAL = 5;
157RrdGraphDesc.CF_DEVPREDICT = 6;
158RrdGraphDesc.CF_DEVSEASONAL = 7;
159RrdGraphDesc.CF_FAILURES = 8;
160RrdGraphDesc.CF_MHWPREDICT = 9;
161
162RrdGraphDesc.TXA_LEFT = 0;
163RrdGraphDesc.TXA_RIGHT = 1;
164RrdGraphDesc.TXA_CENTER = 2;
165RrdGraphDesc.TXA_JUSTIFIED = 3;
166
167RrdGraphDesc.cf_conv = function (str)
168{
169 switch (str){
170 case 'AVERAGE': return RrdGraphDesc.CF_AVERAGE;
171 case 'MIN': return RrdGraphDesc.CF_MINIMUM;
172 case 'MAX': return RrdGraphDesc.CF_MAXIMUM;
173 case 'LAST': return RrdGraphDesc.CF_LAST;
174 case 'HWPREDICT': return RrdGraphDesc.CF_HWPREDICT;
175 case 'MHWPREDICT': return RrdGraphDesc.CF_MHWPREDICT;
176 case 'DEVPREDICT': return RrdGraphDesc.CF_DEVPREDICT;
177 case 'SEASONAL': return RrdGraphDesc.CF_SEASONAL;
178 case 'DEVSEASONAL': return RrdGraphDesc.CF_DEVSEASONAL;
179 case 'FAILURES': return RrdGraphDesc.CF_FAILURES;
180 }
181 return -1;
182};
183
184RrdGraphDesc.cf2str = function (cf)
185{
186 switch (cf){
187 case RrdGraphDesc.CF_AVERAGE: return 'AVERAGE';
188 case RrdGraphDesc.CF_MINIMUM: return 'MIN';
189 case RrdGraphDesc.CF_MAXIMUM: return 'MAX';
190 case RrdGraphDesc.CF_LAST: return 'LAST';
191 case RrdGraphDesc.CF_HWPREDICT: return 'HWPREDICT';
192 case RrdGraphDesc.CF_MHWPREDICT: return 'MHWPREDICT';
193 case RrdGraphDesc.CF_DEVPREDICT: return 'DEVPREDICT';
194 case RrdGraphDesc.CF_SEASONAL: return 'SEASONAL';
195 case RrdGraphDesc.CF_DEVSEASONAL: return 'DEVSEASONAL';
196 case RrdGraphDesc.CF_FAILURES: return 'FAILURES';
197 }
198 return '';
199};
200
201RrdGraphDesc.prototype.def = function (graph, vname, rrdfile, name, cf, step, start, end, reduce)
202{
203 var start_t = new RrdTime(this.start);
204 var end_t = new RrdTime(this.end);
205
206 this.gf = RrdGraphDesc.GF_DEF;
207 this.vname = vname;
208 this.vidx = graph.find_var(vname);
209 this.rrd = rrdfile;
210 this.ds_nam = name;
211 this.cf = RrdGraphDesc.cf_conv(cf);
212
213 if (step != undefined && step != null)
214 this.step = step;
215 if (start != undefined && start != null)
216 start_t = new RrdTime(start);
217 if (end != undefined && end != null)
218 end_t = new RrdTime(end);
219 if (reduce === undefined || reduce === null)
220 this.cf_reduce = this.cf; // ¿?
221 else
222 this.cf_reduce = RrdGraphDesc.cf_conv(reduce);
223 this.legend = '';
224
225 var start_end = RrdTime.proc_start_end(start_t, end_t); // FIXME here?
226 this.start = start_end[0];
227 this.end = start_end[1];
228 this.start_orig = start_end[0];
229 this.end_orig = start_end[1];
230};
231
232RrdGraphDesc.prototype.cdef = function (graph, vname, rpn)
233{
234 this.gf = RrdGraphDesc.GF_CDEF;
235 this.vname = vname;
236 this.vidx = graph.find_var(vname);
237 this.rpnp = new RrdRpn(rpn, graph.gdes);
238 this.legend = '';
239};
240
241RrdGraphDesc.prototype.vdef = function (graph, vname, rpn)
242{
243 this.gf = RrdGraphDesc.GF_VDEF;
244 this.vname = vname;
245
246 var index = rpn.indexOf(',');
247 var name = rpn.substring(0,index);
248 this.vidx = graph.find_var(name); // FIXME checks
249 if (graph.gdes[this.vidx].gf != RrdGraphDesc.GF_DEF && graph.gdes[this.vidx].gf != RrdGraphDesc.GF_CDEF) {
250 throw new RrdGraphDescError('variable "'+name+'" not DEF nor CDEF in VDEF.');
251 }
252 this.vf = new RrdVdef(rpn.substring(index+1));
253 this.legend = '';
254};
255
256RrdGraphDesc.prototype.fshift = function (graph, vname, offset)
257{
258 this.gf = RrdGraphDesc.GF_SHIFT;
259 this.vname = vname; // ¿?
260 this.vidx = graph.find_var(vname); // FIXME checks
261
262 if (graph.gdes[this.vidx].gf === RrdGraphDesc.GF_VDEF)
263 throw new RrdGraphDescError("Cannot shift a VDEF: '%s' in line '"+graph.gdes[this.vidx].vname+"'");
264 if (graph.gdes[this.vidx].gf !== RrdGraphDesc.GF_DEF && graph.gdes[this.vidx].gf !== RrdGraphDesc.GF_CDEF)
265 throw new RrdGraphDescError("Encountered unknown type variable '"+graph.gdes[this.vidx].vname+"'");
266
267 this.shidx = graph.find_var(offset);
268 if (this.shidx >= 0) {
269 if (graph.gdes[gdp.shidx].gf === RrdGraphDesc.GF_DEF || graph.gdes[gdp.shidx].gf === RrdGraphDesc.GF_CDEF)
270 throw new RrdGraphDescError("Offset cannot be a (C)DEF: '"+graph.gdes[gdp.shidx].gf+"'");
271 if (graph.gdes[gdp.shidx].gf !== RrdGraphDesc.GF_VDEF)
272 throw new RrdGraphDescError("Encountered unknown type variable '"+graph.gdes[gdp.shidx].vname+"'");
273 } else {
274 this.shval = parseInt(offset, 10); // FIXME check
275 this.shidx = -1;
276 }
277 this.legend = '';
278};
279
280RrdGraphDesc.prototype.line = function (graph, width, value, color, legend, stack)
281{
282 this.gf = RrdGraphDesc.GF_LINE;
283 this.vname = value;
284 this.vidx = graph.find_var(value);
285 this.linewidth = width;
286 this.col = color;
287 if (legend === undefined) this.legend = '';
288 else this.legend = ' '+legend;
289 if (stack === undefined) this.stack = false;
290 else this.stack = stack;
291 this.format = this.legend;
292};
293
294RrdGraphDesc.prototype.area = function (graph, value, color, legend, stack)
295{
296 this.gf = RrdGraphDesc.GF_AREA;
297 this.vname = value;
298 this.vidx = graph.find_var(value);
299 this.col = color;
300 if (legend === undefined) this.legend = '';
301 else this.legend = ' '+legend;
302 if (stack === undefined) this.stack = false;
303 else this.stack = stack;
304 this.format = this.legend;
305};
306
307RrdGraphDesc.prototype.tick = function (graph, vname, color, fraction, legend)
308{
309 this.gf = RrdGraphDesc.GF_TICK;
310 this.vname = vname;
311 this.vidx = graph.find_var(vname);
312 this.col = color;
313 if (fraction !== undefined)
314 this.yrule = fraction;
315 if (legend === undefined) this.legend = '';
316 else this.legend = ' '+legend;
317 this.format = this.legend;
318};
319
320RrdGraphDesc.prototype.gprint = function (graph, vname, cf, format, strftimefmt)
321{
322 this.gf = RrdGraphDesc.GF_GPRINT;
323 this.vname = vname;
324 this.vidx = graph.find_var(vname);
325 this.legend = '';
326 if (format === undefined) {
327 this.format = cf;
328 switch (graph.gdes[this.vidx].gf) {
329 case RrdGraphDesc.GF_DEF:
330 case RrdGraphDesc.GF_CDEF:
331 this.cf = graph.gdes[this.vidx].cf;
332 break;
333 case RrdGraphDesc.GF_VDEF:
334 break;
335 default:
336 throw new RrdGraphDescError("Encountered unknown type variable "+graph.gdes[this.vidx].vname);
337 }
338 } else {
339 this.cf = RrdGraphDesc.cf_conv(cf);
340 this.format = format;
341 }
342 if (graph.gdes[this.vidx].gf === RrdGraphDesc.GF_VDEF && strftimefmt === true)
343 this.strftm = true;
344};
345
346RrdGraphDesc.prototype.comment = function (graph, text)
347{
348 this.gf = RrdGraphDesc.GF_COMMENT;
349 this.vidx = -1;
350 this.legend = text;
351};
352
353RrdGraphDesc.prototype.textalign = function (graph, align)
354{
355 this.gf = RrdGraphDesc.GF_TEXTALIGN;
356 this.vidx = -1;
357 if (align === "left") {
358 this.txtalign = RrdGraphDesc.TXA_LEFT;
359 } else if (align === "right") {
360 this.txtalign = RrdGraphDesc.TXA_RIGHT;
361 } else if (align === "justified") {
362 this.txtalign = RrdGraphDesc.TXA_JUSTIFIED;
363 } else if (align === "center") {
364 this.txtalign = RrdGraphDesc.TXA_CENTER;
365 } else {
366 throw new RrdGraphDescError("Unknown alignement type '"+align+"'");
367 }
368};
369
370RrdGraphDesc.prototype.vrule = function (graph, time, color, legend)
371{
372 this.gf = RrdGraphDesc.GF_VRULE;
373 this.xrule = time;
374 this.col = color;
375 if (legend === undefined) this.legend = '';
376 else this.legend = ' '+legend;
377};
378
379RrdGraphDesc.prototype.hrule = function (graph, value, color, legend)
380{
381 this.gf = RrdGraphDesc.GF_HRULE;
382 this.yrule = value;
383 this.col = color;
384 if (legend === undefined) this.legend = '';
385 else this.legend = ' '+legend;
386};
387
388/**
389 * RrdVdefError
390 * @constructor
391 */
392var RrdVdefError = function (message)
393{
394 this.prototype = Error.prototype;
395 this.name = "RrdVdefError";
396 this.message = (message) ? message : "Error";
397};
398
399/**
400 * RrdVdef
401 * @constructor
402 */
403var RrdVdef = function(vname, str) /* parse */
404{
405 var param;
406 var func;
407 var n = 0;
408
409 this.expr = str;
410 this.op = null;
411 this.param = null;
412 this.val = null;
413 this.when = null;
414
415 var index = str.indexOf(',');
416 if (index != -1) {
417 param = parseFloat(str.substr(0,index));
418 func = str.substr(index+1);
419 } else {
420 param = Number.NaN;
421 func = str;
422 }
423
424 if (func === 'PERCENT') this.op = RrdVdef.VDEF_PERCENT;
425 else if (func === 'PERCENTNAN') this.op = RrdVdef.VDEF_PERCENTNAN;
426 else if (func === 'MAXIMUM') this.op = RrdVdef.VDEF_MAXIMUM;
427 else if (func === 'AVERAGE') this.op = RrdVdef.VDEF_AVERAGE;
428 else if (func === 'STDEV') this.op = RrdVdef.VDEF_STDEV;
429 else if (func === 'MINIMUM') this.op = RrdVdef.VDEF_MINIMUM;
430 else if (func === 'TOTAL') this.op = RrdVdef.VDEF_TOTAL;
431 else if (func === 'FIRST') this.op = RrdVdef.VDEF_FIRST;
432 else if (func === 'LAST') this.op = RrdVdef.VDEF_LAST;
433 else if (func === 'LSLSLOPE') this.op = RrdVdef.VDEF_LSLSLOPE;
434 else if (func === 'LSLINT') this.op = RrdVdef.VDEF_LSLINT;
435 else if (func === 'LSLCORREL') this.op = RrdVdef.VDEF_LSLCORREL;
436 else {
437 throw new RrdVdefError('Unknown function "'+func+'" in VDEF "'+vame+'"');
438 }
439
440 switch (this.op) {
441 case RrdVdef.VDEF_PERCENT:
442 case RrdVdef.VDEF_PERCENTNAN:
443 if (isNaN(param)) { /* no parameter given */
444 throw new RrdVdefError("Function '"+func+"' needs parameter in VDEF '"+vname+"'");
445 }
446 if (param >= 0.0 && param <= 100.0) {
447 this.param = param;
448 this.val = Number.NaN; /* undefined */
449 this.when = 0; /* undefined */
450 } else {
451 throw new RrdVdefError("Parameter '"+param+"' out of range in VDEF '"+vname+"'");
452 }
453 break;
454 case RrdVdef.VDEF_MAXIMUM:
455 case RrdVdef.VDEF_AVERAGE:
456 case RrdVdef.VDEF_STDEV:
457 case RrdVdef.VDEF_MINIMUM:
458 case RrdVdef.VDEF_TOTAL:
459 case RrdVdef.VDEF_FIRST:
460 case RrdVdef.VDEF_LAST:
461 case RrdVdef.VDEF_LSLSLOPE:
462 case RrdVdef.VDEF_LSLINT:
463 case RrdVdef.VDEF_LSLCORREL:
464 if (isNaN(param)) {
465 this.param = Number.NaN;
466 this.val = Number.NaN;
467 this.when = 0;
468 } else {
469 throw new RrdVdefError("Function '"+func+"' needs no parameter in VDEF '"+vname+"'");
470 }
471 break;
472 }
473};
474
475RrdVdef.VDEF_MAXIMUM = 0;
476RrdVdef.VDEF_MINIMUM = 1;
477RrdVdef.VDEF_AVERAGE = 2;
478RrdVdef.VDEF_STDEV = 3;
479RrdVdef.VDEF_PERCENT = 4;
480RrdVdef.VDEF_TOTAL = 5;
481RrdVdef.VDEF_FIRST = 6;
482RrdVdef.VDEF_LAST = 7;
483RrdVdef.VDEF_LSLSLOPE = 8;
484RrdVdef.VDEF_LSLINT = 9;
485RrdVdef.VDEF_LSLCORREL = 10;
486RrdVdef.VDEF_PERCENTNAN = 11;
487
488RrdVdef.prototype.vdef_percent_compar = function (a, b)
489{ /* Equality is not returned; this doesn't hurt except (maybe) for a little performance. */
490 /* NaN < -INF < finite_values < INF */
491 if (isNaN(a)) return -1;
492 if (isNaN(b)) return 1;
493 /* NaN doesn't reach this part so INF and -INF are extremes. The sign from isinf() is compatible with the sign we return */
494 if (!isFinite(a)) {
495 if (a === -Infinity) return -1;
496 else return 1;
497 }
498 if (!isFinite(b)) {
499 if (b === -Infinity) return -1;
500 else return 1;
501 }
502 /* If we reach this, both values must be finite */
503 if (a < b) return -1;
504 else return 1;
505};
506
507RrdVdef.prototype.calc = function(src)
508{
509 var data;
510 var step, steps;
511
512 data = src.data;
513 steps = (src.end - src.start) / src.step;
514
515 switch (this.op) {
516 case RrdVdef.VDEF_PERCENT:
517 var array = [];
518 var field;
519
520 for (step = 0; step < steps; step++) {
521 array[step] = data[step * src.ds_cnt];
522 }
523 array.sort(this.vdef_percent_compar);
524 field = Math.round((this.param * (steps - 1)) / 100.0);
525 this.val = array[field];
526 this.when = 0; /* no time component */
527 break;
528 case RrdVdef.VDEF_PERCENTNAN:
529 var array = [];
530 var field;
531
532 field=0;
533 for (step = 0; step < steps; step++) {
534 if (!isNaN(data[step * src.ds_cnt])) {
535 array[field] = data[step * src.ds_cnt];
536 }
537 }
538 array.sort(vdef_percent_compar);
539 field = Math.round(this.param * (field - 1) / 100.0);
540 his.val = array[field];
541 this.when = 0; /* no time component */
542 break;
543 case RrdVdef.VDEF_MAXIMUM:
544 step = 0;
545 while (step != steps && isNaN(data[step * src.ds_cnt])) step++;
546 if (step === steps) {
547 this.val = Number.NaN;
548 this.when = 0;
549 } else {
550 this.val = data[step * src.ds_cnt];
551 this.when = src.start + (step + 1) * src.step;
552 }
553 while (step != steps) {
554 if (isFinite(data[step * src.ds_cnt])) {
555 if (data[step * src.ds_cnt] > this.val) {
556 this.val = data[step * src.ds_cnt];
557 this.when = src.start + (step + 1) * src.step;
558 }
559 }
560 step++;
561 }
562 break;
563 case RrdVdef.VDEF_TOTAL:
564 case RrdVdef.VDEF_STDEV:
565 case RrdVdef.VDEF_AVERAGE:
566 var cnt = 0;
567 var sum = 0.0;
568 var average = 0.0;
569
570 for (step = 0; step < steps; step++) {
571 if (isFinite(data[step * src.ds_cnt])) {
572 sum += data[step * src.ds_cnt];
573 cnt++;
574 }
575 }
576
577 if (cnt) {
578 if (this.op === RrdVdef.VDEF_TOTAL) {
579 this.val = sum * src.step;
580 this.when = 0; /* no time component */
581 } else if (this.op === RrdVdef.VDEF_AVERAGE) {
582 this.val = sum / cnt;
583 this.when = 0; /* no time component */
584 } else {
585 average = sum / cnt;
586 sum = 0.0;
587 for (step = 0; step < steps; step++) {
588 if (isFinite(data[step * src.ds_cnt])) {
589 sum += Math.pow((data[step * src.ds_cnt] - average), 2.0);
590 }
591 }
592 this.val = Math.pow(sum / cnt, 0.5);
593 this.when = 0; /* no time component */
594 }
595 } else {
596 this.val = Number.NaN;
597 this.when = 0;
598 }
599 break;
600 case RrdVdef.VDEF_MINIMUM:
601 step = 0;
602 while (step != steps && isNaN(data[step * src.ds_cnt])) step++;
603 if (step === steps) {
604 this.val = Number.NaN;
605 this.when = 0;
606 } else {
607 this.val = data[step * src.ds_cnt];
608 this.when = src.start + (step + 1) * src.step;
609 }
610 while (step != steps) {
611 if (isFinite(data[step * src.ds_cnt])) {
612 if (data[step * src.ds_cnt] < this.val) {
613 this.val = data[step * src.ds_cnt];
614 this.when = src.start + (step + 1) * src.step;
615 }
616 }
617 step++;
618 }
619 break;
620 case RrdVdef.VDEF_FIRST:
621 step = 0;
622 while (step != steps && isNaN(data[step * src.ds_cnt])) step++;
623 if (step === steps) { /* all entries were NaN */
624 this.val = Number.NaN;
625 this.when = 0;
626 } else {
627 this.val = data[step * src.ds_cnt];
628 this.when = src.start + step * src.step;
629 }
630 break;
631 case RrdVdef.VDEF_LAST:
632 step = steps - 1;
633 while (step >= 0 && isNaN(data[step * src.ds_cnt])) step--;
634 if (step < 0) { /* all entries were NaN */
635 this.val = Number.NaN;
636 this.when = 0;
637 } else {
638 this.val = data[step * src.ds_cnt];
639 this.when = src.start + (step + 1) * src.step;
640 }
641 break;
642 case RrdVdef.VDEF_LSLSLOPE:
643 case RrdVdef.VDEF_LSLINT:
644 case RrdVdef.VDEF_LSLCORREL:
645 var cnt = 0;
646 var SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
647
648 SUMx = 0;
649 SUMy = 0;
650 SUMxy = 0;
651 SUMxx = 0;
652 SUMyy = 0;
653
654 for (step = 0; step < steps; step++) {
655 if (isFinite(data[step * src.ds_cnt])) {
656 cnt++;
657 SUMx += step;
658 SUMxx += step * step;
659 SUMxy += step * data[step * src.ds_cnt];
660 SUMy += data[step * src.ds_cnt];
661 SUMyy += data[step * src.ds_cnt] * data[step * src.ds_cnt];
662 }
663 }
664
665 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
666 y_intercept = (SUMy - slope * SUMx) / cnt;
667 correl = (SUMxy - (SUMx * SUMy) / cnt) / Math.sqrt((SUMxx - (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
668
669 if (cnt) {
670 if (this.op === RrdVdef.VDEF_LSLSLOPE) {
671 this.val = slope;
672 this.when = 0;
673 } else if (this.op === RrdVdef.VDEF_LSLINT) {
674 this.val = y_intercept;
675 this.when = 0;
676 } else if (this.op === RrdVdef.VDEF_LSLCORREL) {
677 this.val = correl;
678 this.when = 0;
679 }
680 } else {
681 this.val = Number.NaN;
682 this.when = 0;
683 }
684 break;
685 }
686 return 0;
687};
688
689/**
690 * RrdGraphError
691 * @constructor
692 */
693var RrdGraphError = function (message)
694{
695 this.prototype = Error.prototype;
696 this.name = "RrdGraphError";
697 this.message = (message) ? message : "Error";
698};
699
700/**
701 * RrdGraph
702 * @constructor
703 */
704var RrdGraph = function (gfx, data)
705{
706 this.gfx = gfx; /* graphics object */
707 this.data = data; /* fetch data object */
708
709 this.minval = Number.NaN; /* extreme values in the data */
710 this.maxval = Number.NaN;
711/* status information */
712//with_markup: 0,
713 this.xorigin = 0; /* where is (0,0) of the graph */
714 this.yorigin = 0;
715 this.xOriginTitle = 0; /* where is the origin of the title */
716 this.yOriginTitle = 0;
717 this.xOriginLegendY = 0; /* where is the origin of the y legend */
718 this.yOriginLegendY = 0;
719 this.xOriginLegendY2 = 0; /* where is the origin of the second y legend */
720 this.yOriginLegendY2 = 0;
721 this.xOriginLegend = 0; /* where is the origin of the legend */
722 this.yOriginLegend = 0;
723 this.ximg = 0; /* total size of the image */
724 this.yimg = 0;
725 this.legendwidth = 0; /* the calculated height and width of the legend */
726 this.legendheight = 0;
727 this.magfact = 1; /* numerical magnitude */
728 this.symbol = null; /* magnitude symbol for y-axis */
729 this.viewfactor = 1.0; /* how should the numbers on the y-axis be scaled for viewing ? */
730
731 this.base = 1000; /* 1000 or 1024 depending on what we graph */
732
733 this.start = 0; /* what time does the graph cover */
734 this.end = 0;
735
736 this.xlab_form = null; /* format for the label on the xaxis */
737/* public */
738 this.xsize = 400; /* graph area size in pixels */
739 this.ysize = 100;
740 this.zoom = 1;
741 this.grid_dash_on = 1;
742 this.grid_dash_off = 1;
743 this.second_axis_scale = 0; /* relative to the first axis (0 to disable) */
744 this.second_axis_shift = 0; /* how much is it shifted vs the first axis */
745 this.second_axis_legend = null; /* label to put on the seond axis */
746 this.second_axis_format = null; /* format for the numbers on the scond axis */
747 this.draw_x_grid = true; /* no x-grid at all */
748 this.draw_y_grid = true; /* no y-grid at all */
749 this.ygridstep = Number.NaN; /* user defined step for y grid */
750 this.ylabfact = 0; /* every how many y grid shall a label be written ? */
751 this.draw_3d_border = 2; /* size of border in pixels, 0 for off */
752 this.dynamic_labels = false;/* pick the label shape according to the line drawn */
753 this.ylegend = null; /* legend along the yaxis */
754 this.title = ''; /* title for graph */
755 this.watermark = null; /* watermark for graph */
756 this.tabwidth = 40; /* tabwdith */
757 this.step = 0; /* any preference for the default step ? */
758 this.setminval = Number.NaN; /* extreme values in the data */
759 this.setmaxval = Number.NaN;
760 this.rigid = false; /* do not expand range even with values outside */
761 this.gridfit = true; /* adjust y-axis range etc so all grindlines falls in integer pixel values */
762 this.lazy = 0; /* only update the image if there is reasonable probablility that the existing one is out of date */
763 this.legendposition = RrdGraph.LEGEND_POS_SOUTH; /* the position of the legend: north, west, south or east */
764 this.legenddirection = RrdGraph.LEGEND_DIR_TOP_DOWN; /* The direction of the legend topdown or bottomup */
765 this.logarithmic = false; /* scale the yaxis logarithmic */
766 this.force_scale_min = 0; /* Force a scale--min */
767 this.force_scale_max = 0; /* Force a scale--max */
768 this.unitsexponent = 9999; /* 10*exponent for units on y-asis */
769 this.unitslength = 6; /* width of the yaxis labels */
770 this.forceleftspace = false; /* do not kill the space to the left of the y-axis if there is no grid */
771 this.slopemode = false; /* connect the dots of the curve directly, not using a stair */
772 this.alt_ygrid = false; /* use alternative y grid algorithm */
773 this.alt_autoscale = false; /* use alternative algorithm to find lower and upper bounds */
774 this.alt_autoscale_min = false; /* use alternative algorithm to find lower bounds */
775 this.alt_autoscale_max = false; /* use alternative algorithm to find upper bounds */
776 this.no_legend = false; /* use no legend */
777 this.no_minor = false; /* Turn off minor gridlines */
778 this.only_graph = false; /* use only graph */
779 this.force_rules_legend = false; /* force printing of HRULE and VRULE legend */
780 this.force_units = false; /* mask for all FORCE_UNITS_* flags */
781 this.force_units_si = false; /* force use of SI units in Y axis (no effect in linear graph, SI instead of E in log graph) */
782 this.full_size_mode = false; /* -width and -height indicate the total size of the image */
783 this.no_rrdtool_tag = false; /* disable the rrdtool tag */
784
785 this.xlab_user = null;
786 this.ygrid_scale = null;
787
788 this.gdes = [];
789
790 this.ytr_pixie = 0;
791 this.xtr_pixie = 0;
792
793 this.AlmostEqualBuffer = new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT*2);
794 this.AlmostEqualInt = new Int32Array(this.AlmostEqualBuffer);
795 this.AlmostEqualFloat = new Float32Array(this.AlmostEqualBuffer);
796
797 this.DEFAULT_FONT = 'DejaVu Sans Mono'; //DejaVu Sans Mono ,Bitstream Vera Sans Mono,monospace,Courier', // pt -> pt=px*72/96
798 this.MGRIDWIDTH = 0.6;
799 this.GRIDWIDTH = 0.4;
800 this.YLEGEND_ANGLE = 90.0;
801
802 this.TEXT = {
803 DEFAULT: { size: 11, font: this.DEFAULT_FONT },
804 TITLE: { size: 12, font: this.DEFAULT_FONT },
805 AXIS: { size: 10, font: this.DEFAULT_FONT },
806 UNIT: { size: 11, font: this.DEFAULT_FONT },
807 LEGEND: { size: 11, font: this.DEFAULT_FONT },
808 WATERMARK: { size: 8, font: this.DEFAULT_FONT }
809 };
810
811 this.GRC = {
812 CANVAS: 'rgba(255, 255, 255, 1.0)',
813 BACK: 'rgba(242,242, 242, 1.0)',
814 SHADEA: 'rgba(207, 207, 207, 1.0)',
815 SHADEB: 'rgba(158, 158, 158, 1.0)',
816 GRID: 'rgba(143, 143, 143, 0.75)',
817 MGRID: 'rgba(222, 79, 79, 0.60)',
818 FONT: 'rgba(0, 0, 0, 1.0)',
819 ARROW: 'rgba(127, 31, 31, 1.0)',
820 AXIS: 'rgba(31, 31, 31, 1.0)',
821 FRAME: 'rgba(0, 0, 0, 1.0)'
822 };
823
824 this.xlab = [
825 {minsec: 0, length: 0, gridtm: RrdGraph.TMT_SECOND, gridst: 30, mgridtm: RrdGraph.TMT_MINUTE, mgridst: 5, labtm: RrdGraph.TMT_MINUTE, labst: 5, precis: 0, stst: '%H:%M' } ,
826 {minsec: 2, length: 0, gridtm: RrdGraph.TMT_MINUTE, gridst: 1, mgridtm: RrdGraph.TMT_MINUTE, mgridst: 5, labtm: RrdGraph.TMT_MINUTE, labst: 5, precis: 0, stst: '%H:%M' } ,
827 {minsec: 5, length: 0, gridtm: RrdGraph.TMT_MINUTE, gridst: 2, mgridtm: RrdGraph.TMT_MINUTE, mgridst: 10, labtm: RrdGraph.TMT_MINUTE, labst: 10, precis: 0, stst: '%H:%M' } ,
828 {minsec: 10, length: 0, gridtm: RrdGraph.TMT_MINUTE, gridst: 5,mgridtm: RrdGraph.TMT_MINUTE, mgridst: 20, labtm: RrdGraph.TMT_MINUTE, labst: 20, precis: 0, stst: '%H:%M' } ,
829 {minsec: 30, length: 0, gridtm: RrdGraph.TMT_MINUTE, gridst: 10, mgridtm: RrdGraph.TMT_HOUR, mgridst: 1, labtm: RrdGraph.TMT_HOUR, labst: 1, precis: 0, stst: '%H:%M' } ,
830 {minsec: 60, length: 0, gridtm: RrdGraph.TMT_MINUTE, gridst: 30, mgridtm: RrdGraph.TMT_HOUR, mgridst: 2, labtm: RrdGraph.TMT_HOUR, labst: 2, precis: 0, stst: '%H:%M' } ,
831 {minsec: 60, length: 24 * 3600, gridtm: RrdGraph.TMT_MINUTE, gridst: 30, mgridtm: RrdGraph.TMT_HOUR, mgridst: 2, labtm: RrdGraph.TMT_HOUR, labst: 6, precis: 0, stst: '%a %H:%M' } ,
832 {minsec: 180, length: 0, gridtm: RrdGraph.TMT_HOUR, gridst: 1, mgridtm: RrdGraph.TMT_HOUR, mgridst: 6, labtm: RrdGraph.TMT_HOUR, labst: 6, precis: 0, stst: '%H:%M' } ,
833 {minsec: 180, length: 24 * 3600, gridtm: RrdGraph.TMT_HOUR, gridst: 1, mgridtm: RrdGraph.TMT_HOUR, mgridst: 6, labtm: RrdGraph.TMT_HOUR, labst: 12, precis: 0, stst: '%a %H:%M' } ,
834 {minsec: 600, length: 0, gridtm: RrdGraph.TMT_HOUR, gridst: 6, mgridtm: RrdGraph.TMT_DAY, mgridst: 1, labtm: RrdGraph.TMT_DAY, labst: 1, precis: 24 * 3600, stst: '%a' } ,
835 {minsec: 1200, length: 0, gridtm: RrdGraph.TMT_HOUR, gridst: 6, mgridtm: RrdGraph.TMT_DAY, mgridst: 1, labtm: RrdGraph.TMT_DAY, labst: 1, precis: 24 * 3600, stst: '%d' } ,
836 {minsec: 1800, length: 0, gridtm: RrdGraph.TMT_HOUR, gridst: 12, mgridtm: RrdGraph.TMT_DAY, mgridst: 1, labtm: RrdGraph.TMT_DAY, labst: 2, precis: 24 * 3600, stst: '%a %d' },
837 {minsec: 2400, length: 0, gridtm: RrdGraph.TMT_HOUR, gridst: 12, mgridtm: RrdGraph.TMT_DAY, mgridst: 1, labtm: RrdGraph.TMT_DAY, labst: 2, precis: 24 * 3600,stst: '%a' },
838 {minsec: 3600, length: 0, gridtm: RrdGraph.TMT_DAY, gridst: 1, mgridtm: RrdGraph.TMT_WEEK, mgridst: 1, labtm: RrdGraph.TMT_WEEK, labst: 1, precis: 7 * 24 * 3600, stst: 'Week %V' },
839 {minsec: 3 * 3600, length: 0, gridtm: RrdGraph.TMT_WEEK, gridst: 1, mgridtm: RrdGraph.TMT_MONTH, mgridst: 1, labtm: RrdGraph.TMT_WEEK, labst: 2, precis: 7 * 24 * 3600, stst: 'Week %V' },
840 {minsec: 6 * 3600, length: 0, gridtm: RrdGraph.TMT_MONTH, gridst: 1, mgridtm: RrdGraph.TMT_MONTH, mgridst: 1, labtm: RrdGraph.TMT_MONTH, labst: 1, precis: 30 * 24 * 3600, stst: '%b' },
841 {minsec: 48 * 3600,length: 0, gridtm: RrdGraph.TMT_MONTH, gridst: 1, mgridtm: RrdGraph.TMT_MONTH, mgridst: 3, labtm: RrdGraph.TMT_MONTH, labst: 3, precis: 30 * 24 * 3600, stst: '%b' },
842 {minsec: 315360, length: 0, gridtm: RrdGraph.TMT_MONTH, gridst: 3, mgridtm: RrdGraph.TMT_YEAR, mgridst: 1, labtm: RrdGraph.TMT_YEAR, labst: 1, precis: 365 * 24 * 3600, stst: '%Y' },
843 {minsec: 10 * 24 * 3600 , length: 0, gridtm: RrdGraph.TMT_YEAR, gridst: 1, mgridtm: RrdGraph.TMT_YEAR, mgridst: 1, labtm: RrdGraph.TMT_YEAR, labst: 1, precis: 365 * 24 * 3600, stst: '%y' },
844 {minsec: -1, length: 0, gridtm: RrdGraph.TMT_MONTH, gridst: 0, mgridtm: RrdGraph.TMT_MONTH, mgridst: 0, labtm: RrdGraph.TMT_MONTH, labst: 0, precis: 0, stst: null }
845 ];
846
847 this.ylab = [
848 {grid: 0.1, lfac: [1, 2, 5, 10] } ,
849 {grid: 0.2, lfac: [1, 5, 10, 20] } ,
850 {grid: 0.5, lfac: [1, 2, 4, 10] } ,
851 {grid: 1.0, lfac: [1, 2, 5, 10] } ,
852 {grid: 2.0, lfac: [1, 5, 10, 20] } ,
853 {grid: 5.0, lfac: [1, 2, 4, 10] } ,
854 {grid: 10.0, lfac: [1, 2, 5, 10] } ,
855 {grid: 20.0, lfac: [1, 5, 10, 20] } ,
856 {grid: 50.0, lfac: [1, 2, 4, 10] } ,
857 {grid: 100.0, lfac: [1, 2, 5, 10] } ,
858 {grid: 200.0, lfac: [1, 5, 10, 20] } ,
859 {grid: 500.0, lfac: [1, 2, 4, 10] },
860 {grid: 0.0, lfac: [0, 0, 0, 0] }
861 ];
862
863 this.si_symbol = [
864 'y', /* 10e-24 Yocto */
865 'z', /* 10e-21 Zepto */
866 'a', /* 10e-18 Atto */
867 'f', /* 10e-15 Femto */
868 'p', /* 10e-12 Pico */
869 'n', /* 10e-9 Nano */
870 'u', /* 10e-6 Micro */
871 'm', /* 10e-3 Milli */
872 ' ', /* Base */
873 'k', /* 10e3 Kilo */
874 'M', /* 10e6 Mega */
875 'G', /* 10e9 Giga */
876 'T', /* 10e12 Tera */
877 'P', /* 10e15 Peta */
878 'E', /* 10e18 Exa */
879 'Z', /* 10e21 Zeta */
880 'Y' /* 10e24 Yotta */
881 ];
882 this.si_symbcenter = 8;
883
884 this.start_t = new RrdTime("end-24h");
885 this.end_t = new RrdTime("now");
886};
887
888RrdGraph.TMT_SECOND = 0;
889RrdGraph.TMT_MINUTE = 1;
890RrdGraph.TMT_HOUR = 2;
891RrdGraph.TMT_DAY = 3;
892RrdGraph.TMT_WEEK = 4;
893RrdGraph.TMT_MONTH = 5;
894RrdGraph.TMT_YEAR = 6;
895
896RrdGraph.GFX_H_LEFT = 1;
897RrdGraph.GFX_H_RIGHT = 2;
898RrdGraph.GFX_H_CENTER = 3;
899
900RrdGraph.GFX_V_TOP = 1;
901RrdGraph.GFX_V_BOTTOM = 2;
902RrdGraph.GFX_V_CENTER = 3;
903
904RrdGraph.LEGEND_POS_NORTH = 0;
905RrdGraph.LEGEND_POS_WEST = 1;
906RrdGraph.LEGEND_POS_SOUTH = 2;
907RrdGraph.LEGEND_POS_EAST = 3;
908
909RrdGraph.LEGEND_DIR_TOP_DOWN = 0;
910RrdGraph.LEGEND_DIR_BOTTOM_UP = 1;
911
912RrdGraph.prototype.set_default_font = function (name)
913{
914 for (var font in this.TEXT)
915 this.TEXT[font].font = name;
916};
917
918RrdGraph.prototype.parse_color = function(str)
919{
920 var bits;
921 if ((bits = /^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/.exec(str))) {
922 return [parseInt(bits[1]+bits[1], 16), parseInt(bits[2]+bits[2], 16), parseInt(bits[3]+bits[3], 16), 1.0];
923 } else if ((bits = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(str))) {
924 return [parseInt(bits[1], 16), parseInt(bits[2], 16), parseInt(bits[3], 16), 1.0];
925 } else if ((bits = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(str))) {
926 return [parseInt(bits[1], 16), parseInt(bits[2], 16), parseInt(bits[3], 16), parseInt(bits[4], 16)/255];
927 } else if ((bits = /^rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\)$/.exec(str))) {
928 return [parseInt(bits[1], 10), parseInt(bits[2], 10), parseInt(bits[3], 10), 1.0];
929 } else if ((bits = /^rgba\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([0-9.]+)\)$/.exec(str))) {
930 return [parseInt(bits[1], 10), parseInt(bits[2], 10), parseInt(bits[3], 10), parseFloat(bits[4], 10)];
931 } else {
932 throw new RrdGraphError("Unknow color format '"+str+"'");
933 }
934};
935
936RrdGraph.prototype.color2rgba = function (color)
937{
938 return 'rgba('+color[0]+','+color[1]+','+color[2]+','+color[3]+')';
939};
940
941RrdGraph.prototype.xtr = function (mytime)
942{
943 if (mytime === 0) {
944 this.xtr_pixie = this.xsize / (this.end - this.start);
945 return this.xorigin;
946 }
947 return this.xorigin + this.xtr_pixie * (mytime - this.start);
948};
949
950RrdGraph.prototype.ytr = function (value)
951{
952 var yval;
953
954 if (isNaN(value)) {
955 if (!this.logarithmic)
956 this.ytr_pixie = this.ysize / (this.maxval - this.minval);
957 else
958 this.ytr_pixie = this.ysize / ((Math.log(this.maxval)/Math.LN10) - (Math.log(this.minval)/Math.LN10));
959 yval = this.yorigin;
960 } else if (!this.logarithmic) {
961 yval = this.yorigin - this.ytr_pixie * (value - this.minval);
962 } else {
963 if (value < this.minval) {
964 yval = this.yorigin;
965 } else {
966 yval = this.yorigin - this.ytr_pixie * ((Math.log(value)/Math.LN10) - (Math.log(this.minval)/Math.LN10));
967 }
968 }
969 return yval;
970};
971
972RrdGraph.prototype.AlmostEqual2sComplement = function(A, B, maxUlps)
973{
974 this.AlmostEqualFloat[0] = A;
975 this.AlmostEqualFloat[1] = B;
976
977 var aInt = this.AlmostEqualInt[0];
978 if (aInt < 0) aInt = 0x80000000 - aInt;
979
980 var bInt = this.AlmostEqualInt[1];
981 if (bInt < 0) bInt = 0x80000000 - bInt;
982
983 var intDiff = Math.abs(aInt - bInt);
984
985 if (intDiff <= maxUlps)
986 return true;
987
988 return false;
989};
990
991RrdGraph.prototype.tmt2str = function (val)
992{
993 switch (val) {
994 case RrdGraph.TMT_SECOND: return 'sec';
995 case RrdGraph.TMT_MINUTE: return 'min';
996 case RrdGraph.TMT_HOUR: return 'hour';
997 case RrdGraph.TMT_DAY: return 'day';
998 case RrdGraph.TMT_WEEK: return 'week';
999 case RrdGraph.TMT_MONTH: return 'mon';
1000 case RrdGraph.TMT_YEAR: return 'year';
1001 }
1002 return val;
1003};
1004
1005RrdGraph.prototype.find_first_time = function(start, baseint, basestep)
1006{
1007 var date = new Date(start*1000);
1008
1009 switch (baseint) {
1010 case RrdGraph.TMT_SECOND:
1011 var sec = date.getSeconds();
1012 sec -= sec % basestep;
1013 date.setSeconds(sec);
1014 break;
1015 case RrdGraph.TMT_MINUTE:
1016 date.setSeconds(0);
1017 var min = date.getMinutes();
1018 min -= min % basestep;
1019 date.setMinutes(min);
1020 break;
1021 case RrdGraph.TMT_HOUR:
1022 date.setSeconds(0);
1023 date.setMinutes(0);
1024 var hour = date.getHours();
1025 hour -= hour % basestep;
1026 date.setHours(hour);
1027 break;
1028 case RrdGraph.TMT_DAY:
1029 date.setSeconds(0);
1030 date.setMinutes(0);
1031 date.setHours(0);
1032 break;
1033 case RrdGraph.TMT_WEEK:
1034 date.setSeconds(0);
1035 date.setMinutes(0);
1036 date.setHours(0);
1037 var mday = date.getDate();
1038 var wday = date.getDay();
1039 mday -= wday - 1; // FIXME find_first_weekday
1040 if (wday === 0) mday -= 7;// FIXME find_first_weekday
1041 date.setDate(mday);
1042 break;
1043 case RrdGraph.TMT_MONTH:
1044 date.setSeconds(0);
1045 date.setMinutes(0);
1046 date.setHours(0);
1047 date.setDate(1);
1048 var mon = date.getMonth();
1049 mon -= mon % basestep;
1050 date.setMonth(mon);
1051 break;
1052 case RrdGraph.TMT_YEAR:
1053 date.setSeconds(0);
1054 date.setMinutes(0);
1055 date.setHours(0);
1056 date.setDate(1);
1057 date.setMonth(0);
1058 var year = date.getFullYear()-1900;
1059 year -= (year + 1900) %basestep;
1060 date.setFullYear(year+1900);
1061 }
1062 return Math.round(date.getTime()/1000.0);
1063};
1064
1065RrdGraph.prototype.find_next_time = function(current, baseint, basestep)
1066{
1067 var date = new Date(current*1000);
1068 var limit = 2;
1069 var madetime;
1070
1071 switch (baseint) {
1072 case RrdGraph.TMT_SECOND: limit = 7200; break;
1073 case RrdGraph.TMT_MINUTE: limit = 120; break;
1074 case RrdGraph.TMT_HOUR: limit = 2; break;
1075 default: limit = 2; break;
1076 }
1077
1078 do {
1079 switch (baseint) {
1080 case RrdGraph.TMT_SECOND:
1081 date.setSeconds(date.getSeconds()+basestep);
1082 break;
1083 case RrdGraph.TMT_MINUTE:
1084 date.setMinutes(date.getMinutes()+basestep);
1085 break;
1086 case RrdGraph.TMT_HOUR:
1087 date.setHours(date.getHours()+basestep);
1088 break;
1089 case RrdGraph.TMT_DAY:
1090 date.setDate(date.getDate()+basestep);
1091 break;
1092 case RrdGraph.TMT_WEEK:
1093 date.setDate(date.getDate()+7*basestep);
1094 break;
1095 case RrdGraph.TMT_MONTH:
1096 date.setMonth(date.getMonth()+basestep);
1097 break;
1098 case RrdGraph.TMT_YEAR:
1099 date.setFullYear(date.getFullYear()+basestep);
1100 break;
1101 }
1102 madetime = Math.round(date.getTime()/1000.0);
1103 } while (madetime === -1 && limit-- >= 0); /* this is necessary to skip impossible times like the daylight saving time skips */ // FIXME ??
1104 return madetime;
1105};
1106
1107RrdGraph.prototype.print_calc = function()
1108{
1109 var validsteps;
1110 var printval;
1111 var tmvdef;
1112 var graphelement = 0;
1113 var max_ii;
1114 var magfact = -1;
1115 var si_symb = "";
1116 var percent_s;
1117 var prline_cnt = 0;
1118
1119 var now = Math.round((new Date()).getTime() / 1000);
1120
1121 for (var i = 0, gdes_c = this.gdes.length; i < gdes_c; i++) {
1122 var vidx = this.gdes[i].vidx;
1123 switch (this.gdes[i].gf) {
1124 case RrdGraphDesc.GF_PRINT:
1125 case RrdGraphDesc.GF_GPRINT:
1126 if (this.gdes[vidx].gf === RrdGraphDesc.GF_VDEF) { /* simply use vals */
1127 printval = this.gdes[vidx].vf.val;
1128 tmvdef = this.gdes[vidx].vf.when;
1129 // localtime_r(&this.gdes[vidx].vf.when, &tmvdef); // FIXME ?
1130 } else { /* need to calculate max,min,avg etcetera */
1131 max_ii = ((this.gdes[vidx].end - this.gdes[vidx].start) / this.gdes[vidx].step * this.gdes[vidx].ds_cnt);
1132 printval = Number.NaN;
1133 validsteps = 0;
1134
1135 for (var ii = this.gdes[vidx].ds; ii < max_ii; ii += this.gdes[vidx].ds_cnt) {
1136 if (!isFinite(this.gdes[vidx].data[ii])) continue;
1137 if (isNaN(printval)) {
1138 printval = this.gdes[vidx].data[ii];
1139 validsteps++;
1140 continue;
1141 }
1142 switch (this.gdes[i].cf) {
1143 case RrdGraphDesc.CF_HWPREDICT:
1144 case RrdGraphDesc.CF_MHWPREDICT:
1145 case RrdGraphDesc.CF_DEVPREDICT:
1146 case RrdGraphDesc.CF_DEVSEASONAL:
1147 case RrdGraphDesc.CF_SEASONAL:
1148 case RrdGraphDesc.CF_AVERAGE:
1149 validsteps++;
1150 printval += this.gdes[vidx].data[ii];
1151 break;
1152 case RrdGraphDesc.CF_MINIMUM:
1153 printval = Math.min(printval, this.gdes[vidx].data[ii]);
1154 break;
1155 case RrdGraphDesc.CF_FAILURES:
1156 case RrdGraphDesc.CF_MAXIMUM:
1157 printval = Math.max(printval, this.gdes[vidx].data[ii]);
1158 break;
1159 case RrdGraphDesc.CF_LAST:
1160 printval = this.gdes[vidx].data[ii];
1161 }
1162 }
1163 if (this.gdes[i].cf === RrdGraphDesc.CF_AVERAGE || this.gdes[i].cf > RrdGraphDesc.CF_LAST) {
1164 if (validsteps > 1) printval = (printval / validsteps);
1165 }
1166 } /* prepare printval */
1167
1168 if (!this.gdes[i].strftm && (percent_s = this.gdes[i].format.indexOf('%S')) != -1) {
1169 if (magfact < 0.0) {
1170 //[printval, si_symb, magfact] = this.auto_scale(printval, si_symb, magfact);
1171 var dummy = this.auto_scale(printval, si_symb, magfact); printval = dummy[0]; si_symb = dummy[1]; magfact = dummy[2];
1172 if (printval === 0.0) magfact = -1.0;
1173 } else {
1174 printval /= magfact;
1175 }
1176 this.gdes[i].format = this.gdes[i].format.substr(0, percent_s+1)+'s'+this.gdes[i].format.substr(percent_s+2);
1177 } else if (!this.gdes[i].strftm && this.gdes[i].format.indexOf('%s') != -1) {
1178 //[printval, si_symb, magfact] = this.auto_scale(printval, si_symb, magfact);
1179 var dummy = this.auto_scale(printval, si_symb, magfact); printval = dummy[0]; si_symb = dummy[1]; magfact = dummy[2];
1180 }
1181
1182 if (this.gdes[i].strftm) {
1183 this.gdes[i].legend = strftime(this.gdes[i].format, tmvdef);
1184 } else {
1185 this.gdes[i].legend = sprintf(this.gdes[i].format, printval, si_symb);
1186 }
1187 graphelement = 1;
1188 break;
1189 case RrdGraphDesc.GF_LINE:
1190 case RrdGraphDesc.GF_AREA:
1191 case RrdGraphDesc.GF_TICK:
1192 graphelement = 1;
1193 break;
1194 case RrdGraphDesc.GF_HRULE:
1195 if (isNaN(this.gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1196 this.gdes[i].yrule = this.gdes[vidx].vf.val;
1197 };
1198 graphelement = 1;
1199 break;
1200 case RrdGraphDesc.GF_VRULE:
1201 if (this.gdes[i].xrule === 0) { /* again ... the legend printer needs it */
1202 this.gdes[i].xrule = this.gdes[vidx].vf.when;
1203 };
1204 graphelement = 1;
1205 break;
1206 case RrdGraphDesc.GF_COMMENT:
1207 case RrdGraphDesc.GF_TEXTALIGN:
1208 case RrdGraphDesc.GF_DEF:
1209 case RrdGraphDesc.GF_CDEF:
1210 case RrdGraphDesc.GF_VDEF:
1211 case RrdGraphDesc.GF_SHIFT:
1212 case RrdGraphDesc.GF_XPORT:
1213 break;
1214 case RrdGraphDesc.GF_STACK:
1215 throw new RrdGraphError("STACK should already be turned into LINE or AREA here");
1216 break;
1217 }
1218 }
1219 return graphelement;
1220};
1221
1222RrdGraph.prototype.reduce_data = function(gdes, cur_step)
1223{
1224 var reduce_factor = Math.ceil(gdes.step / cur_step);
1225 var col, dst_row, row_cnt, start_offset, end_offset, skiprows = 0;
1226 var srcptr, dstptr;
1227
1228 gdes.step = cur_step * reduce_factor; /* set new step size for reduced data */
1229 dstptr = 0;
1230 srcptr = 0;
1231 row_cnt = (gdes.end - gdes.start) / cur_step;
1232
1233 end_offset = gdes.end % gdes.step;
1234 start_offset = gdes.start % gdes.step;
1235
1236 if (start_offset) {
1237 gdes.start = gdes.start - start_offset;
1238 skiprows = reduce_factor - start_offset / cur_step;
1239 srcptr += skiprows * gdes.ds_cnt;
1240 for (col = 0; col < gdes.ds_cnt; col++)
1241 gdes.data[dstptr++] = Number.NaN;
1242 row_cnt -= skiprows;
1243 }
1244
1245 if (end_offset) {
1246 gdes.end = gdes.end - end_offset + gdes.step;
1247 skiprows = end_offset / cur_step;
1248 row_cnt -= skiprows;
1249 }
1250
1251 if (row_cnt % reduce_factor) {
1252 throw new RrdGraphError("BUG in reduce_data(), SANITY CHECK: "+row_cnt+" rows cannot be reduced by "+reduce_factor);
1253 }
1254
1255 for (dst_row = 0; row_cnt >= reduce_factor; dst_row++) {
1256 for (col = 0; col < gdes.ds_cnt; col++) {
1257 var newval = Number.NaN;
1258 var validval = 0;
1259
1260 for (var i = 0; i < reduce_factor; i++) {
1261 if (isNaN(gdes.data[srcptr + i*gdes.ds_cnt + col])) continue;
1262 validval++;
1263 if (isNaN(newval)) {
1264 newval = gdes.data[srcptr + i * gdes.ds_cnt + col];
1265 } else {
1266 switch (gdes.cf_reduce) {
1267 case RrdGraphDesc.CF_HWPREDICT:
1268 case RrdGraphDesc.CF_MHWPREDICT:
1269 case RrdGraphDesc.CF_DEVSEASONAL:
1270 case RrdGraphDesc.CF_DEVPREDICT:
1271 case RrdGraphDesc.CF_SEASONAL:
1272 case RrdGraphDesc.CF_AVERAGE:
1273 newval += gdes.data[srcptr + i*gdes.ds_cnt + col];
1274 break;
1275 case RrdGraphDesc.CF_MINIMUM:
1276 newval = Math.min(newval, gdes.data[srcptr + i*gdes.ds_cnt + col]);
1277 break;
1278 case RrdGraphDesc.CF_FAILURES:
1279 /* an interval contains a failure if any subintervals contained a failure */
1280 case RrdGraphDesc.CF_MAXIMUM:
1281 newval = Math.max(newval, gdes.data[srcptr + i*gdes.ds_cnt + col]);
1282 break;
1283 case RrdGraphDesc.CF_LAST:
1284 newval = gdes.data[srcptr + i*gdes.ds_cnt + col];
1285 break;
1286 }
1287 }
1288 }
1289
1290 if (validval === 0) {
1291 newval = Number.NaN;
1292 } else {
1293 switch (gdes.cf_reduce) {
1294 case RrdGraphDesc.CF_HWPREDICT:
1295 case RrdGraphDesc.CF_MHWPREDICT:
1296 case RrdGraphDesc.CF_DEVSEASONAL:
1297 case RrdGraphDesc.CF_DEVPREDICT:
1298 case RrdGraphDesc.CF_SEASONAL:
1299 case RrdGraphDesc.CF_AVERAGE:
1300 newval /= validval;
1301 break;
1302 case RrdGraphDesc.CF_MINIMUM:
1303 case RrdGraphDesc.CF_FAILURES:
1304 case RrdGraphDesc.CF_MAXIMUM:
1305 case RrdGraphDesc.CF_LAST:
1306 break;
1307 }
1308 }
1309 gdes.data[dstptr++] = newval;
1310 }
1311
1312 srcptr += gdes.ds_cnt * reduce_factor;
1313 row_cnt -= reduce_factor;
1314 }
1315 if (end_offset) {
1316 for (col = 0; col < gdes.ds_cnt; col++)
1317 gdes.data[dstptr++] = Number.NaN;
1318 }
1319};
1320
1321RrdGraph.prototype.data_fetch = function()
1322{
1323 var skip;
1324
1325 for (var i = 0, gdes_c = this.gdes.length; i < gdes_c; i++) {
1326 if (this.gdes[i].gf != RrdGraphDesc.GF_DEF) continue;
1327
1328 skip = false;
1329 for (var ii = 0; ii < i; ii++) {
1330 if (this.gdes[ii].gf != RrdGraphDesc.GF_DEF) continue;
1331 if ((this.gdes[i].rrd === this.gdes[ii].rrd)
1332 && (this.gdes[i].cf === this.gdes[ii].cf)
1333 && (this.gdes[i].cf_reduce === this.gdes[ii].cf_reduce)
1334 && (this.gdes[i].start_orig === this.gdes[ii].start_orig)
1335 && (this.gdes[i].end_orig === this.gdes[ii].end_orig)
1336 && (this.gdes[i].step_orig === this.gdes[ii].step_orig)) {
1337 this.gdes[i].start = this.gdes[ii].start;
1338 this.gdes[i].end = this.gdes[ii].end;
1339 this.gdes[i].step = this.gdes[ii].step;
1340 this.gdes[i].ds_cnt = this.gdes[ii].ds_cnt;
1341 this.gdes[i].ds_namv = this.gdes[ii].ds_namv;
1342 this.gdes[i].data = this.gdes[ii].data;
1343 this.gdes[i].data_first = 0;
1344 skip = true;
1345 }
1346 if (skip) break;
1347 }
1348
1349 if (!skip) {
1350 var ft_step = this.gdes[i].step; /* ft_step will record what we got from fetch */
1351 ft_step = this.data.fetch(this.gdes[i], ft_step);
1352 if (ft_step < 0)
1353 return -1;
1354 this.gdes[i].data_first = 1;
1355// this.gdes[i].step = Math.max(this.gdes[i].step, this.step); // FIXME
1356 if (ft_step < this.gdes[i].step) {
1357 this.reduce_data(this.gdes[i], ft_step);
1358 } else {
1359 this.gdes[i].step = ft_step;
1360 }
1361 }
1362 /* lets see if the required data source is really there */
1363 for (var ii = 0; ii < this.gdes[i].ds_cnt; ii++) {
1364 if (this.gdes[i].ds_namv[ii] === this.gdes[i].ds_nam) {
1365 this.gdes[i].ds = ii;
1366 break;
1367 }
1368 }
1369
1370 if (this.gdes[i].ds === -1)
1371 throw new RrdGraphError("No DS called '"+this.gdes[i].ds_nam+"' in '"+this.gdes[i].rrd+"'");
1372 }
1373
1374 return 0;
1375};
1376
1377RrdGraph.prototype.lcd = function (num)
1378{
1379 var rest;
1380 for (var i = 0; num[i + 1] != 0; i++) {
1381 do {
1382 rest = num[i] % num[i + 1];
1383 num[i] = num[i + 1];
1384 num[i + 1] = rest;
1385 } while (rest != 0); // FIXME infinite loop ?
1386 num[i + 1] = num[i];
1387 }
1388 return num[i];
1389};
1390
1391RrdGraph.prototype.data_calc = function()
1392{
1393 var dataidx;
1394 var now;
1395 var rpnstack;
1396
1397 for (var gdi = 0, gdes_c = this.gdes.length; gdi < gdes_c; gdi++) {
1398 switch (this.gdes[gdi].gf) {
1399 case RrdGraphDesc.GF_XPORT:
1400 break;
1401 case RrdGraphDesc.GF_SHIFT:
1402 var vdp = this.gdes[this.gdes[gdi].vidx];
1403 /* remove current shift */
1404 vdp.start -= vdp.shift;
1405 vdp.end -= vdp.shift;
1406
1407 if (this.gdes[gdi].shidx >= 0) vdp.shift = this.gdes[this.gdes[gdi].shidx].vf.val;
1408 else vdp.shift = this.gdes[gdi].shval;
1409
1410 vdp.shift = (vdp.shift / vdp.step) * vdp.step;
1411
1412 vdp.start += vdp.shift;
1413 vdp.end += vdp.shift;
1414 break;
1415 case RrdGraphDesc.GF_VDEF:
1416 this.gdes[gdi].ds_cnt = 0;
1417 if (this.gdes[gdi].vf.calc(this.gdes[this.gdes[gdi].vidx]))
1418 throw new RrdGraphError("Error processing VDEF '"+this.gdes[gdi].vname+"'");
1419 break;
1420 case RrdGraphDesc.GF_CDEF:
1421 this.gdes[gdi].ds_cnt = 1;
1422 this.gdes[gdi].ds = 0;
1423 this.gdes[gdi].data_first = 1;
1424 this.gdes[gdi].start = 0;
1425 this.gdes[gdi].end = 0;
1426 var steparray = [];
1427 var stepcnt = 0;
1428 dataidx = -1;
1429
1430 var rpnp = this.gdes[gdi].rpnp.rpnp;
1431 for (var rpi = 0; rpnp[rpi].op != RrdRpn.OP_END; rpi++) {
1432 if (rpnp[rpi].op === RrdRpn.OP_VARIABLE || rpnp[rpi].op === RrdRpn.OP_PREV_OTHER) {
1433 var ptr = rpnp[rpi].ptr;
1434 if (this.gdes[ptr].ds_cnt === 0) { /* this is a VDEF data source */
1435 rpnp[rpi].val = this.gdes[ptr].vf.val;
1436 rpnp[rpi].op = RrdRpn.OP_NUMBER;
1437 } else { /* normal variables and PREF(variables) */
1438 ++stepcnt;
1439 steparray[stepcnt - 1] = this.gdes[ptr].step;
1440
1441 if (this.gdes[gdi].start < this.gdes[ptr].start)
1442 this.gdes[gdi].start = this.gdes[ptr].start;
1443 if (this.gdes[gdi].end === 0 || this.gdes[gdi].end > this.gdes[ptr].end)
1444 this.gdes[gdi].end = this.gdes[ptr].end;
1445
1446 rpnp[rpi].data = this.gdes[ptr].data;
1447 rpnp[rpi].pdata = this.gdes[ptr].ds;
1448 rpnp[rpi].step = this.gdes[ptr].step;
1449 rpnp[rpi].ds_cnt = this.gdes[ptr].ds_cnt;
1450 }
1451 }
1452 }
1453 /* move the data pointers to the correct period */
1454 for (var rpi = 0; rpnp[rpi].op != RrdRpn.OP_END; rpi++) {
1455 if (rpnp[rpi].op === RrdRpn.OP_VARIABLE || rpnp[rpi].op === RrdRpn.OP_PREV_OTHER) {
1456 var ptr = rpnp[rpi].ptr;
1457 var diff = this.gdes[gdi].start - this.gdes[ptr].start;
1458
1459 if (diff > 0) rpnp[rpi].pdata += (diff / this.gdes[ptr].step) * this.gdes[ptr].ds_cnt;
1460 }
1461 }
1462
1463 if (steparray === null) {
1464 throw new RrdGraphError("rpn expressions without DEF or CDEF variables are not supported");
1465 }
1466 steparray[stepcnt] = 0;
1467 this.gdes[gdi].step = this.lcd(steparray);
1468 this.gdes[gdi].data = [];
1469
1470 for (now = this.gdes[gdi].start + this.gdes[gdi].step; now <= this.gdes[gdi].end; now += this.gdes[gdi].step) {
1471 if (this.gdes[gdi].rpnp.calc(now, this.gdes[gdi].data, ++dataidx) === -1)
1472 return -1;
1473 }
1474 break;
1475 default:
1476 continue;
1477 }
1478 }
1479 return 0;
1480};
1481
1482RrdGraph.prototype.data_proc = function()
1483{
1484 var pixstep = (this.end - this.start) / this.xsize;
1485 var paintval;
1486 var minval = Number.NaN, maxval = Number.NaN;
1487 var gr_time;
1488
1489 /* memory for the processed data */
1490
1491 for (var i = 0, gdes_c = this.gdes.length; i < gdes_c; i++) {
1492 if ((this.gdes[i].gf === RrdGraphDesc.GF_LINE) || (this.gdes[i].gf === RrdGraphDesc.GF_AREA) || (this.gdes[i].gf === RrdGraphDesc.GF_TICK)) {
1493 this.gdes[i].p_data = [];
1494 }
1495 }
1496
1497 for (var i = 0; i < this.xsize; i++) { /* for each pixel */
1498 var vidx;
1499 gr_time = this.start + pixstep * i; /* time of the current step */
1500 paintval = 0.0;
1501
1502 for (var ii = 0 , gdes_c = this.gdes.length; ii < gdes_c; ii++) {
1503 var value;
1504 switch (this.gdes[ii].gf) {
1505 case RrdGraphDesc.GF_LINE:
1506 case RrdGraphDesc.GF_AREA:
1507 case RrdGraphDesc.GF_TICK:
1508 if (!this.gdes[ii].stack) paintval = 0.0;
1509 value = this.gdes[ii].yrule;
1510 if (isNaN(value) || (this.gdes[ii].gf === RrdGraphDesc.GF_TICK)) {
1511 vidx = this.gdes[ii].vidx;
1512 if (this.gdes[vidx].gf === RrdGraphDesc.GF_VDEF) {
1513 value = this.gdes[vidx].vf.val;
1514 } else if (gr_time >= this.gdes[vidx].start && gr_time < this.gdes[vidx].end) {
1515 value = this.gdes[vidx].data[Math.floor((gr_time - this.gdes[vidx].start) / this.gdes[vidx].step) * this.gdes[vidx].ds_cnt + this.gdes[vidx].ds];
1516 } else {
1517 value = Number.NaN;
1518 }
1519 }
1520 if (!isNaN(value)) {
1521 paintval += value;
1522 this.gdes[ii].p_data[i] = paintval;
1523 if (isFinite(paintval) && this.gdes[ii].gf != RrdGraphDesc.GF_TICK) {
1524 if ((isNaN(minval) || paintval < minval) && !(this.logarithmic && paintval <= 0.0))
1525 minval = paintval;
1526 if (isNaN(maxval) || paintval > maxval)
1527 maxval = paintval;
1528 }
1529 } else {
1530 this.gdes[ii].p_data[i] = Number.NaN;
1531 }
1532 break;
1533 case RrdGraphDesc.GF_STACK:
1534 throw new RrdGraphError("STACK should already be turned into LINE or AREA here");
1535 break;
1536 default:
1537 break;
1538 }
1539 }
1540 }
1541
1542 if (this.logarithmic) {
1543 if (isNaN(minval) || isNaN(maxval) || maxval <= 0) {
1544 minval = 0.0;
1545 maxval = 5.1;
1546 }
1547 if (minval <= 0) minval = maxval / 10e8;
1548 } else {
1549 if (isNaN(minval) || isNaN(maxval)) {
1550 minval = 0.0;
1551 maxval = 1.0;
1552 }
1553 }
1554
1555 if (isNaN(this.minval) || ((!this.rigid) && this.minval > minval)) {
1556 if (this.logarithmic) this.minval = minval / 2.0;
1557 else this.minval = minval;
1558 }
1559 if (isNaN(this.maxval) || (!this.rigid && this.maxval < maxval)) {
1560 if (this.logarithmic) this.maxval = maxval * 2.0;
1561 else this.maxval = maxval;
1562 }
1563
1564 if (this.minval > this.maxval) {
1565 if (this.minval > 0) this.minval = 0.99 * this.maxval;
1566 else this.minval = 1.01 * this.maxval;
1567 }
1568
1569 if (this.AlmostEqual2sComplement(this.minval, this.maxval, 4)) {
1570 if (this.maxval > 0) this.maxval *= 1.01;
1571 else this.maxval *= 0.99;
1572 if (this.AlmostEqual2sComplement(this.maxval, 0, 4)) this.maxval = 1.0;
1573 }
1574 return 0;
1575};
1576
1577RrdGraph.prototype.leg_place = function (calc_width)
1578{
1579 var interleg = this.TEXT.LEGEND.size * 1.5;
1580 var border = this.TEXT.LEGEND.size * 1.5;
1581 var fill = 0, fill_last;
1582 var legendwidth; // = this.ximg - 2 * border;
1583 var leg_c = 0;
1584 var leg_x = border;
1585 var leg_y = 0; //this.yimg;
1586 var leg_y_prev = 0; // this.yimg;
1587 var leg_cc;
1588 var glue = 0;
1589 var ii, mark = 0;
1590 var default_txtalign = RrdGraphDesc.TXA_JUSTIFIED; /*default line orientation */
1591 var legspace;
1592 var tab;
1593 var saved_legend;
1594
1595 if(calc_width) legendwidth = 0;
1596 else legendwidth = this.legendwidth - 2 * border;
1597
1598 if (!this.no_legend && !this.only_graph) {
1599 legspace = [];
1600 for (var i = 0 , gdes_c = this.gdes.length; i < gdes_c; i++) {
1601 var prt_fctn; /*special printfunctions */
1602 if(calc_width) saved_legend = this.gdes[i].legend;
1603 fill_last = fill;
1604 if (this.gdes[i].gf === RrdGraphDesc.GF_TEXTALIGN)
1605 default_txtalign = this.gdes[i].txtalign;
1606
1607 if (!this.force_rules_legend) {
1608 if (this.gdes[i].gf === RrdGraphDesc.GF_HRULE && (this.gdes[i].yrule < this.minval || this.gdes[i].yrule > this.maxval))
1609 this.gdes[i].legend = null;
1610 if (this.gdes[i].gf === RrdGraphDesc.GF_VRULE && (this.gdes[i].xrule < this.start || this.gdes[i].xrule > this.end))
1611 this.gdes[i].legend = null;
1612 }
1613 this.gdes[i].legend = this.gdes[i].legend.replace(/\\t/gi, "\t") /* turn \\t into tab */
1614
1615 leg_cc = this.gdes[i].legend.length;
1616 /* is there a controle code at the end of the legend string ? */
1617 if (leg_cc >= 2 && this.gdes[i].legend.charAt(leg_cc - 2) === '\\') {
1618 prt_fctn = this.gdes[i].legend.charAt(leg_cc - 1);
1619 leg_cc -= 2;
1620 this.gdes[i].legend = this.gdes[i].legend.substr(0,leg_cc);
1621 } else {
1622 prt_fctn = null;
1623 }
1624 /* only valid control codes */
1625 if (prt_fctn != 'l' && prt_fctn != 'n' && prt_fctn != 'r' && prt_fctn != 'j' && prt_fctn != 'c' &&
1626 prt_fctn != '.' && prt_fctn != 'u' && prt_fctn != 's' && prt_fctn != null && prt_fctn != 'g') {
1627 throw new RrdGraphError("Unknown control code at the end of "+this.gdes[i].legend+": "+prt_fctn);
1628 }
1629 /* \n -> \l */
1630 if (prt_fctn === 'n') prt_fctn = 'l';
1631 if (prt_fctn === '.') prt_fctn = '\0';
1632
1633 /* remove exess space from the end of the legend for \g */
1634 while (prt_fctn === 'g' && leg_cc > 0 && this.gdes[i].legend.charAt(leg_cc - 1) === ' ') {
1635 leg_cc--;
1636 this.gdes[i].legend = this.gdes[i].legend.substr(0,leg_cc);
1637 }
1638
1639 if (leg_cc != 0) {
1640 legspace[i] = (prt_fctn === 'g' ? 0 : interleg);
1641 if (fill > 0) fill += legspace[i];
1642 fill += this.gfx.get_text_width(fill + border, this.TEXT.LEGEND, this.tabwidth, this.gdes[i].legend);
1643 leg_c++;
1644 } else {
1645 legspace[i] = 0;
1646 }
1647 /* who said there was a special tag ... ? */
1648 if (prt_fctn === 'g') prt_fctn = null;
1649
1650 if (prt_fctn === null) {
1651 if(calc_width && (fill > legendwidth)) legendwidth = fill;
1652
1653 if (i === gdes_c - 1 || fill > legendwidth) {
1654 switch (default_txtalign) {
1655 case RrdGraphDesc.TXA_RIGHT:
1656 prt_fctn = 'r';
1657 break;
1658 case RrdGraphDesc.TXA_CENTER:
1659 prt_fctn = 'c';
1660 break;
1661 case RrdGraphDesc.TXA_JUSTIFIED:
1662 prt_fctn = 'j';
1663 break;
1664 default:
1665 prt_fctn = 'l';
1666 break;
1667 }
1668 }
1669 /* is it time to place the legends ? */
1670 if (fill > legendwidth) {
1671 if (leg_c > 1) { /* go back one */
1672 i--;
1673 fill = fill_last;
1674 leg_c--;
1675 }
1676 }
1677 if (leg_c === 1 && prt_fctn === 'j') {
1678 prt_fctn = 'l';
1679 }
1680 }
1681
1682 if (prt_fctn != null) {
1683 leg_x = border;
1684 if (leg_c >= 2 && prt_fctn === 'j') {
1685 glue = (legendwidth - fill) / (leg_c - 1);
1686 } else {
1687 glue = 0;
1688 }
1689 if (prt_fctn === 'c')
1690 leg_x = border + (legendwidth - fill) / 2.0;
1691 if (prt_fctn === 'r')
1692 leg_x = legendwidth - fill + border;
1693 for (ii = mark; ii <= i; ii++) {
1694 if (this.gdes[ii].legend === '') continue;
1695 this.gdes[ii].leg_x = leg_x;
1696 this.gdes[ii].leg_y = leg_y + border;
1697 leg_x += this.gfx.get_text_width(leg_x, this.TEXT.LEGEND, this.tabwidth, this.gdes[ii].legend) + legspace[ii] + glue;
1698 }
1699 leg_y_prev = leg_y;
1700 if (leg_x > border || prt_fctn === 's') leg_y += this.TEXT.LEGEND.size * 1.4;
1701 if (prt_fctn === 's') leg_y -= this.TEXT.LEGEND.size;
1702 if (prt_fctn === 'u') leg_y -= this.TEXT.LEGEND.size * 1.4;
1703
1704 if(calc_width && (fill > legendwidth)) legendwidth = fill;
1705 fill = 0;
1706 leg_c = 0;
1707 mark = ii;
1708 }
1709
1710 if(calc_width) this.gdes[i].legend = saved_legend;
1711 }
1712 if(calc_width) this.legendwidth = legendwidth + 2 * border;
1713 else this.legendheight = leg_y + border * 0.6; // FIXME 0.6 ??
1714 }
1715 return 0;
1716};
1717
1718RrdGraph.prototype.axis_paint = function()
1719{
1720 this.gfx.line(this.xorigin - 4, this.yorigin,
1721 this.xorigin + this.xsize + 4, this.yorigin,
1722 this.MGRIDWIDTH, this.GRC.AXIS);
1723
1724 this.gfx.line(this.xorigin, this.yorigin + 4,
1725 this.xorigin, this.yorigin - this.ysize - 4,
1726 this.MGRIDWIDTH, this.GRC.AXIS);
1727
1728 this.gfx.new_area(this.xorigin + this.xsize + 2, this.yorigin - 3,
1729 this.xorigin + this.xsize + 2,
1730 this.yorigin + 3, this.xorigin + this.xsize + 7, this.yorigin,
1731 this.GRC.ARROW);
1732 this.gfx.close_path();
1733
1734 this.gfx.new_area(this.xorigin - 3, this.yorigin - this.ysize - 2,
1735 this.xorigin + 3, this.yorigin - this.ysize - 2,
1736 this.xorigin, this.yorigin - this.ysize - 7,
1737 this.GRC.ARROW);
1738 this.gfx.close_path();
1739
1740 if (this.second_axis_scale != 0){
1741 this.gfx.line (this.xorigin+this.xsize,this.yorigin+4,
1742 this.xorigin+this.xsize,this.yorigin-this.ysize-4,
1743 MGRIDWIDTH, this.graph_col[this.GRC.AXIS]);
1744 this.gfx.new_area (this.xorigin+this.xsize-2, this.yorigin-this.ysize-2,
1745 this.xorigin+this.xsize+3, this.yorigin-this.ysize-2,
1746 this.xorigin+this.xsize, this.yorigin-this.ysize-7, /* LINEOFFSET */
1747 this.GRC.ARROW);
1748 this.gfx.close_path();
1749 }
1750};
1751
1752RrdGraph.prototype.frexp10 = function (x)
1753{
1754 var mnt;
1755 var iexp;
1756
1757 iexp = Math.floor(Math.log(Math.abs(x)) / Math.LN10);
1758 mnt = x / Math.pow(10.0, iexp);
1759 if (mnt >= 10.0) {
1760 iexp++;
1761 mnt = x / Math.pow(10.0, iexp);
1762 }
1763 return [mnt, iexp];
1764};
1765
1766RrdGraph.prototype.horizontal_log_grid = function ()
1767{
1768 var yloglab = [ [ 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ],
1769 [ 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ],
1770 [ 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0 ],
1771 [ 1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0 ],
1772 [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10. ],
1773 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] /* last line */
1774 ];
1775 var i, j, val_exp, min_exp;
1776 var nex; /* number of decades in data */
1777 var logscale; /* scale in logarithmic space */
1778 var exfrac = 1; /* decade spacing */
1779 var mid = -1; /* row in yloglab for major grid */
1780 var mspac; /* smallest major grid spacing (pixels) */
1781 var flab; /* first value in yloglab to use */
1782 var value, tmp, pre_value;
1783 var X0, X1, Y0;
1784 var graph_label;
1785
1786 nex = Math.log(this.maxval / this.minval)/Math.LN10;
1787 logscale = this.ysize / nex;
1788 /* major spacing for data with high dynamic range */
1789 while (logscale * exfrac < 2.3 * this.TEXT.LEGEND.size) { // FIXME 3 -> 2.34 ??
1790 if (exfrac === 1) exfrac = 2.3; // ??? 3 -> 2.34
1791 else exfrac += 2.3 ; // 3-> 2.34
1792 }
1793 /* major spacing for less dynamic data */
1794 do {
1795 mid++;
1796 for (i = 0; yloglab[mid][i + 1] < 10.0; i++) {};
1797 mspac = logscale * Math.log(10.0 / yloglab[mid][i])/Math.LN10;
1798 } while (mspac > 1.56 * this.TEXT.LEGEND.size && yloglab[mid][0] > 0); // FIXME 2->1.56 ??
1799 if (mid) mid--;
1800 /* find first value in yloglab */
1801 //for (flab = 0; yloglab[mid][flab] < 10 && this.frexp10(this.minval,tmp) > yloglab[mid][flab]; flab++);
1802 flab = -1;
1803 do {
1804 var ret;
1805 flab++;
1806 // [ret, tmp] = this.frexp10(this.minval);
1807 var dummy = this.frexp10(this.minval); ret = dummy[0]; tmp = dummy[1];
1808 } while (yloglab[mid][flab] < 10 && ret > yloglab[mid][flab]);
1809
1810 if (yloglab[mid][flab] === 10.0) {
1811 tmp += 1.0;
1812 flab = 0;
1813 }
1814
1815 val_exp = tmp;
1816 if (val_exp % exfrac) val_exp += Math.abs(-val_exp % exfrac);
1817 X0 = this.xorigin;
1818 X1 = this.xorigin + this.xsize;
1819
1820 /* draw grid */
1821 pre_value = Number.NAN;
1822
1823 while (1) {
1824 value = yloglab[mid][flab] * Math.pow(10.0, val_exp);
1825 if (this.AlmostEqual2sComplement(value, pre_value, 4)) // FIXME
1826 break; /* it seems we are not converging */
1827 pre_value = value;
1828 Y0 = this.ytr(value);
1829 if (Math.floor(Y0 + 0.5) <= this.yorigin - this.ysize)
1830 break;
1831 /* major grid line */
1832 this.gfx.line(X0 - 2, Y0, X0, Y0, this.MGRIDWIDTH, this.GRC.MGRID);
1833 this.gfx.line(X1, Y0, X1 + 2, Y0, this.MGRIDWIDTH, this.GRC.MGRID);
1834 this.gfx.dashed_line(X0 - 2, Y0, X1 + 2, Y0, this.MGRIDWIDTH, this.GRC.MGRID, this.grid_dash_on, this.grid_dash_off);
1835 /* label */
1836 if (this.force_units_si) {
1837 var scale;
1838 var pvalue;
1839 var symbol;
1840
1841 scale = Math.floor(val_exp / 3.0);
1842 if (value >= 1.0) pvalue = Math.pow(10.0, val_exp % 3);
1843 else pvalue = Math.pow(10.0, ((val_exp + 1) % 3) + 2);
1844 pvalue *= yloglab[mid][flab];
1845
1846 if (((scale + this.si_symbcenter) < this.si_symbol.length) && ((scale + this.si_symbcenter) >= 0))
1847 symbol = this.si_symbol[scale + this.si_symbcenter];
1848 else
1849 symbol = '?';
1850 graph_label = sprintf("%3.0f %s", pvalue, symbol);
1851 } else {
1852 graph_label = sprintf("%3.0e", value);
1853 }
1854 if (this.second_axis_scale != 0){
1855 var graph_label_right;
1856 var sval = value*this.second_axis_scale+this.second_axis_shift;
1857 if (!this.second_axis_format[0]){
1858 if (this.force_units_si) {
1859 var mfac = 1;
1860 var symb = '';
1861 //[sval, symb, mfac ] = this.auto_scale(sval, symb, mfac);
1862 var dummy = this.auto_scale(sval, symb, mfac); sval = dummy[0]; symb = dummy[1]; mfac = dummy[2];
1863 graph_label_right = sprintf("%4.0f %s", sval,symb);
1864 } else {
1865 graph_label_right = sprintf("%3.0e", sval);
1866 }
1867 } else {
1868 graph_label_right = sprintf(this.second_axis_format,sval,"");
1869 }
1870 this.gfx.text( X1+7, Y0, this.GRC.FONT, this.TEXT.AXIS, this.tabwidth,0.0, RrdGraph.GFX_H_LEFT, RrdGraph.GFX_V_CENTER, graph_label_right );
1871 }
1872
1873 this.gfx.text(X0 - this.TEXT.AXIS.size, Y0, this.GRC.FONT, this.TEXT.AXIS, this.tabwidth, 0.0, RrdGraph.GFX_H_RIGHT, RrdGraph.GFX_V_CENTER, graph_label);
1874 if (mid < 4 && exfrac === 1) { /* minor grid */
1875 if (flab === 0) { /* find first and last minor line behind current major line * i is the first line and j tha last */
1876 min_exp = val_exp - 1;
1877 for (i = 1; yloglab[mid][i] < 10.0; i++) {};
1878 i = yloglab[mid][i - 1] + 1;
1879 j = 10;
1880 } else {
1881 min_exp = val_exp;
1882 i = yloglab[mid][flab - 1] + 1;
1883 j = yloglab[mid][flab];
1884 }
1885 for (; i < j; i++) { /* draw minor lines below current major line */
1886 value = i * Math.pow(10.0, min_exp);
1887 if (value < this.minval) continue;
1888 Y0 = this.ytr(value);
1889 if (Math.floor(Y0 + 0.5) <= this.yorigin - this.ysize) break;
1890 this.gfx.line(X0 - 2, Y0, X0, Y0, this.GRIDWIDTH, this.GRC.GRID);
1891 this.gfx.line(X1, Y0, X1 + 2, Y0, this.GRIDWIDTH, this.GRC.GRID);
1892 this.gfx.dashed_line(X0 - 1, Y0, X1 + 1, Y0, this.GRIDWIDTH, this.GRC.GRID, this.grid_dash_on, this.grid_dash_off);
1893 }
1894 } else if (exfrac > 1) {
1895 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1896 value = Math.pow(10.0, i);
1897 if (value < this.minval) continue;
1898 Y0 = this.ytr(value);
1899 if (Math.floor(Y0 + 0.5) <= this.yorigin - this.ysize) break;
1900 this.gfx.line(X0 - 2, Y0, X0, Y0, this.GRIDWIDTH, this.GRC.GRID);
1901 this.gfx.line(X1, Y0, X1 + 2, Y0, this.GRIDWIDTH, this.GRC.GRID);
1902 this.gfx.dashed_line(X0 - 1, Y0, X1 + 1, Y0, this.GRIDWIDTH, this.GRC.GRID, this.grid_dash_on, this.grid_dash_off);
1903 }
1904 }
1905 if (yloglab[mid][++flab] === 10.0) { /* next decade */
1906 flab = 0;
1907 val_exp += exfrac;
1908 }
1909 }
1910 if (mid < 4 && exfrac === 1) { /* draw minor lines after highest major line */
1911 if (flab === 0) { /* find first and last minor line below current major line * i is the first line and j tha last */
1912 min_exp = val_exp - 1;
1913 for (i = 1; yloglab[mid][i] < 10.0; i++) {};
1914 i = yloglab[mid][i - 1] + 1;
1915 j = 10;
1916 } else {
1917 min_exp = val_exp;
1918 i = yloglab[mid][flab - 1] + 1;
1919 j = yloglab[mid][flab];
1920 }
1921 for (; i < j; i++) { /* draw minor lines below current major line */
1922 value = i * Math.pow(10.0, min_exp);
1923 if (value < this.minval) continue;
1924 Y0 = this.ytr(value);
1925 if (Math.floor(Y0 + 0.5) <= this.yorigin - this.ysize) break;
1926 this.gfx.line(X0 - 2, Y0, X0, Y0, this.GRIDWIDTH, this.GRC.GRID);
1927 this.gfx.line(X1, Y0, X1 + 2, Y0, this.GRIDWIDTH, this.GRC.GRID);
1928 this.gfx.dashed_line(X0 - 1, Y0, X1 + 1, Y0, this.GRIDWIDTH, this.GRC.GRID, this.grid_dash_on, this.grid_dash_off);
1929 }
1930 } else if (exfrac > 1) { /* fancy minor gridlines */
1931 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1932 value = Math.pow(10.0, i);
1933 if (value < this.minval) continue;
1934 Y0 = this.ytr(value);
1935 if (Math.floor(Y0 + 0.5) <= this.yorigin - this.ysize) break;
1936 this.gfx.line(X0 - 2, Y0, X0, Y0, this.GRIDWIDTH, this.GRC.GRID);
1937 this.gfx.line(X1, Y0, X1 + 2, Y0, this.GRIDWIDTH, this.GRC.GRID);
1938 this.gfx.dashed_line(X0 - 1, Y0, X1 + 1, Y0, this.GRIDWIDTH, this.GRC.GRID, this.grid_dash_on, this.grid_dash_off);
1939 }
1940 }
1941 return 1;
1942};
1943
1944RrdGraph.prototype.vertical_grid = function()
1945{
1946 var xlab_sel; /* which sort of label and grid ? */
1947 var ti, tilab, timajor;
1948 var factor;
1949 var graph_label;
1950 var X0, Y0, Y1; /* points for filled graph and more */
1951
1952 /* the type of time grid is determined by finding the number of seconds per pixel in the graph */
1953 if (this.xlab_user.minsec === -1) {
1954 factor = (this.end - this.start) / this.xsize;
1955 xlab_sel = 0;
1956
1957 while (this.xlab[xlab_sel + 1].minsec != -1 && this.xlab[xlab_sel + 1].minsec <= factor) xlab_sel++;
1958 if (xlab_sel === 0) xlab_sel=1; // FIXME XXX XXX xlab_sel == 0 ???
1959 while (this.xlab[xlab_sel - 1].minsec === this.xlab[xlab_sel].minsec && this.xlab[xlab_sel].length > (this.end - this.start)) xlab_sel--;
1960 this.xlab_user = this.xlab[xlab_sel];
1961
1962 }
1963 Y0 = this.yorigin;
1964 Y1 = this.yorigin - this.ysize;
1965
1966 if (!(this.no_minor)) {
1967 for ( ti = this.find_first_time(this.start, this.xlab_user.gridtm, this.xlab_user.gridst),
1968 timajor = this.find_first_time(this.start, this.xlab_user.mgridtm, this.xlab_user.mgridst);
1969 ti < this.end && ti != -1;
1970 ti = this.find_next_time(ti, this.xlab_user.gridtm, this.xlab_user.gridst)) {
1971 if (ti < this.start || ti > this.end) continue;
1972 while (timajor < ti && timajor != -1) timajor = this.find_next_time(timajor, this.xlab_user.mgridtm, this.xlab_user.mgridst);
1973 if (timajor === -1) break;
1974 if (ti === timajor) continue;
1975 X0 = this.xtr(ti);
1976 this.gfx.line(X0, Y1 - 2, X0, Y1, this.GRIDWIDTH, this.GRC.GRID);
1977 this.gfx.line(X0, Y0, X0, Y0 + 2, this.GRIDWIDTH, this.GRC.GRID);
1978 this.gfx.dashed_line(X0, Y0 + 1, X0, Y1 - 1, this.GRIDWIDTH, this.GRC.GRID, this.grid_dash_on, this.grid_dash_off);
1979 }
1980 }
1981
1982 for ( ti = this.find_first_time(this.start, this.xlab_user.mgridtm, this.xlab_user.mgridst);
1983 ti < this.end && ti != -1;
1984 ti = this.find_next_time(ti, this.xlab_user.mgridtm, this.xlab_user.mgridst)
1985 ) {
1986 if (ti < this.start || ti > this.end) continue;
1987 X0 = this.xtr(ti);
1988 this.gfx.line(X0, Y1 - 2, X0, Y1, this.MGRIDWIDTH, this.GRC.MGRID);
1989 this.gfx.line(X0, Y0, X0, Y0 + 3, this.MGRIDWIDTH, this.GRC.MGRID);
1990 this.gfx.dashed_line(X0, Y0 + 3, X0, Y1 - 2, this.MGRIDWIDTH,
1991 this.GRC.MGRID, this.grid_dash_on, this.grid_dash_off);
1992 }
1993
1994 for ( ti = this.find_first_time(this.start - this.xlab_user.precis / 2, this.xlab_user.labtm, this.xlab_user.labst);
1995 (ti <= this.end - this.xlab_user.precis / 2) && ti != -1;
1996 ti = this.find_next_time(ti, this.xlab_user.labtm, this.xlab_user.labst)
1997 ) {
1998 tilab = ti + this.xlab_user.precis / 2;
1999 if (tilab < this.start || tilab > this.end)
2000 continue;
2001 //localtime_r(&tilab, &tm); FIXME
2002 //strftime(graph_label, 99, this.xlab_user.stst, &tm);
2003 graph_label = strftime(this.xlab_user.stst, tilab);
2004 this.gfx.text(this.xtr(tilab), Y0 + 3, this.GRC.FONT,
2005 this.TEXT.AXIS, this.tabwidth, 0.0,
2006 RrdGraph.GFX_H_CENTER, RrdGraph.GFX_V_TOP, graph_label);
2007 }
2008};
2009
2010RrdGraph.prototype.auto_scale = function (value, symb_ptr, magfact)
2011{
2012 var sindex;
2013
2014 if (value === 0.0 || isNaN(value)) {
2015 sindex = 0;
2016 magfact = 1.0;
2017 } else {
2018 sindex = Math.floor((Math.log(Math.abs(value))/Math.LN10) / (Math.log(this.base)/Math.LN10));
2019 magfact = Math.pow(this.base, sindex);
2020 value /= magfact;
2021 }
2022 if (sindex <= this.si_symbcenter && sindex >= -this.si_symbcenter) {
2023 symb_ptr = this.si_symbol[sindex + this.si_symbcenter];
2024 } else {
2025 symb_ptr = '?';
2026 }
2027 return [value, symb_ptr, magfact];
2028};
2029
2030RrdGraph.prototype.si_unit = function()
2031{
2032 var digits;
2033 var viewdigits = 0;
2034
2035 digits = Math.floor(Math.log(Math.max(Math.abs(this.minval), Math.abs(this.maxval))) / Math.log(this.base));
2036
2037 if (this.unitsexponent != 9999) {
2038 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
2039 viewdigits = Math.floor(this.unitsexponent / 3);
2040 } else {
2041 viewdigits = digits;
2042 }
2043
2044 this.magfact = Math.pow(this.base, digits);
2045
2046 this.viewfactor = this.magfact / Math.pow(this.base, viewdigits);
2047
2048 if (((viewdigits + this.si_symbcenter) < this.si_symbol.length) && ((viewdigits + this.si_symbcenter) >= 0))
2049 this.symbol = this.si_symbol[viewdigits + this.si_symbcenter];
2050 else
2051 this.symbol = '?';
2052};
2053
2054RrdGraph.prototype.expand_range = function ()
2055{
2056 var sensiblevalues = [ 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 ];
2057 var scaled_min, scaled_max;
2058 var adj;
2059 var i;
2060
2061 if (isNaN(this.ygridstep)) {
2062 if (this.alt_autoscale) {
2063 var delt, fact;
2064
2065 delt = this.maxval - this.minval;
2066 adj = delt * 0.1;
2067 fact = 2.0 * Math.pow(10.0, Math.floor(Math.log(Math.max(Math.abs(this.minval), Math.abs(this.maxval)) / this.magfact)/Math.LN10) - 2);
2068 if (delt < fact) adj = (fact - delt) * 0.55;
2069 this.minval -= adj;
2070 this.maxval += adj;
2071 } else if (this.alt_autoscale_min) {
2072 adj = (this.maxval - this.minval) * 0.1;
2073 this.minval -= adj;
2074 } else if (this.alt_autoscale_max) {
2075 adj = (this.maxval - this.minval) * 0.1;
2076 this.maxval += adj;
2077 } else {
2078 scaled_min = this.minval / this.magfact;
2079 scaled_max = this.maxval / this.magfact;
2080
2081 for (i = 1; sensiblevalues[i] > 0; i++) {
2082 if (sensiblevalues[i - 1] >= scaled_min && sensiblevalues[i] <= scaled_min)
2083 this.minval = sensiblevalues[i] * this.magfact;
2084 if (-sensiblevalues[i - 1] <= scaled_min && -sensiblevalues[i] >= scaled_min)
2085 this.minval = -sensiblevalues[i - 1] * this.magfact;
2086 if (sensiblevalues[i - 1] >= scaled_max && sensiblevalues[i] <= scaled_max)
2087 this.maxval = sensiblevalues[i - 1] * this.magfact;
2088 if (-sensiblevalues[i - 1] <= scaled_max && -sensiblevalues[i] >= scaled_max)
2089 this.maxval = -sensiblevalues[i] * this.magfact;
2090 }
2091 }
2092 } else {
2093 this.minval = this.ylabfact * this.ygridstep * Math.floor(this.minval / (this.ylabfact * this.ygridstep));
2094 this.maxval = this.ylabfact * this.ygridstep * Math.ceil(this.maxval / (this.ylabfact * this.ygridstep));
2095 }
2096};
2097
2098RrdGraph.prototype.calc_horizontal_grid = function()
2099{
2100 var range;
2101 var scaledrange;
2102 var pixel, i;
2103 var gridind = 0;
2104 var decimals, fractionals;
2105
2106 this.ygrid_scale.labfact = 2;
2107 range = this.maxval - this.minval;
2108 scaledrange = range / this.magfact;
2109 if (isNaN(scaledrange) || !isFinite(scaledrange)) {
2110 return false;
2111 }
2112
2113 pixel = 1;
2114 if (isNaN(this.ygridstep)) {
2115 if (this.alt_ygrid) {
2116 decimals = Math.ceil(Math.log(Math.max(Math.abs(this.maxval), Math.abs(this.minval)) * this.viewfactor / this.magfact)/Math.LN10);
2117 if (decimals <= 0) decimals = 1;
2118 this.ygrid_scale.gridstep = Math.pow(10, Math.floor(Math.log(range * this.viewfactor / this.magfact)/Math.LN10)) / this.viewfactor * this.magfact;
2119 if (this.ygrid_scale.gridstep === 0) this.ygrid_scale.gridstep = 0.1;
2120 if (range / this.ygrid_scale.gridstep < 5 && this.ygrid_scale.gridstep >= 30)
2121 this.ygrid_scale.gridstep /= 10;
2122 if (range / this.ygrid_scale.gridstep > 15)
2123 this.ygrid_scale.gridstep *= 10;
2124 if (range / this.ygrid_scale.gridstep > 5) {
2125 this.ygrid_scale.labfact = 1;
2126 if (range / this.ygrid_scale.gridstep > 8 || this.ygrid_scale.gridstep < 1.8 * this.TEXT.AXIS.size) // 1.8
2127 this.ygrid_scale.labfact = 2;
2128 } else {
2129 this.ygrid_scale.gridstep /= 5;
2130 this.ygrid_scale.labfact = 5;
2131 }
2132
2133 fractionals = Math.floor(Math.log(this.ygrid_scale.gridstep * this.ygrid_scale.labfact * this.viewfactor / this.magfact)/Math.LN10);
2134 if (fractionals < 0) { /* small amplitude. */
2135 var len = decimals - fractionals + 1;
2136 if (this.unitslength < len + 2) this.unitslength = len + 2;
2137 this.ygrid_scale.labfmt = sprintf("%%%d.%df%s", len, -fractionals, (this.symbol != ' ' ? " %s" : ""));
2138 } else {
2139 var len = decimals + 1;
2140 if (this.unitslength < len + 2) this.unitslength = len + 2;
2141 this.ygrid_scale.labfmt = sprintf("%%%d.0f%s", len, (this.symbol != ' ' ? " %s" : ""));
2142 }
2143 } else { /* classic rrd grid */
2144 for (i = 0; this.ylab[i].grid > 0; i++) {
2145 pixel = this.ysize / (scaledrange / this.ylab[i].grid);
2146 gridind = i;
2147 if (pixel >= 5) break;
2148 }
2149 for (i = 0; i < 4; i++) {
2150 if (pixel * this.ylab[gridind].lfac[i] >= 1.8 * this.TEXT.AXIS.size) { // 1.8
2151 this.ygrid_scale.labfact = this.ylab[gridind].lfac[i];
2152 break;
2153 }
2154 }
2155 this.ygrid_scale.gridstep = this.ylab[gridind].grid * this.magfact;
2156 }
2157 } else {
2158 this.ygrid_scale.gridstep = this.ygridstep;
2159 this.ygrid_scale.labfact = this.ylabfact;
2160 }
2161 return true;
2162};
2163
2164RrdGraph.prototype.draw_horizontal_grid = function()
2165{
2166 var i;
2167 var scaledstep;
2168 var graph_label;
2169 var nlabels = 0;
2170 var X0 = this.xorigin;
2171 var X1 = this.xorigin + this.xsize;
2172 var sgrid = Math.round(this.minval / this.ygrid_scale.gridstep - 1);
2173 var egrid = Math.round(this.maxval / this.ygrid_scale.gridstep + 1);
2174 var MaxY;
2175 var second_axis_magfact = 0;
2176 var second_axis_symb = '';
2177 var Y0, YN;
2178 var sisym;
2179
2180 scaledstep = this.ygrid_scale.gridstep / this.magfact * this.viewfactor;
2181 MaxY = scaledstep * egrid;
2182 for (i = sgrid; i <= egrid; i++) {
2183 Y0 = this.ytr(this.ygrid_scale.gridstep * i);
2184 YN = this.ytr(this.ygrid_scale.gridstep * (i + 1));
2185 if (Math.floor(Y0 + 0.5) >= this.yorigin - this.ysize && Math.floor(Y0 + 0.5) <= this.yorigin) {
2186 if (i % this.ygrid_scale.labfact === 0 || (nlabels === 1 && (YN < this.yorigin - this.ysize || YN > this.yorigin))) {
2187 if (this.symbol === ' ') {
2188 if (this.alt_ygrid) {
2189 graph_label = sprintf(this.ygrid_scale.labfmt, scaledstep * i); // FIXME
2190 } else {
2191 if (MaxY < 10) {
2192 graph_label = sprintf("%4.1f", scaledstep * i);
2193 } else {
2194 graph_label = sprintf("%4.0f", scaledstep * i);
2195 }
2196 }
2197 } else {
2198 sisym = (i === 0 ? ' ' : this.symbol);
2199 if (this.alt_ygrid) {
2200 graph_label = sprintf(this.ygrid_scale.labfmt, scaledstep * i, sisym);
2201 } else {
2202 if (MaxY < 10) {
2203 graph_label = sprintf("%4.1f %s", scaledstep * i, sisym);
2204 } else {
2205 graph_label = sprintf("%4.0f %s", scaledstep * i, sisym);
2206 }
2207 }
2208 }
2209 nlabels++;
2210 if (this.second_axis_scale != 0){
2211 var graph_label_right;
2212 sval = this.ygrid_scale.gridstep*i*this.second_axis_scale+this.second_axis_shift;
2213 if (!this.second_axis_format){
2214 if (!second_axis_magfact){
2215 var dummy = this.ygrid_scale.gridstep*(sgrid+egrid)/2.0*this.second_axis_scale+this.second_axis_shift;
2216 //[dummy, second_axis_symb, second_axis_magfact ] = this.auto_scale(dummy,second_axis_symb,second_axis_magfact);
2217 dummy = this.auto_scale(dummy,second_axis_symb,second_axis_magfact); second_axis_symb = dummy[1]; second_axis_magfact=dummy[2];
2218 }
2219 sval /= second_axis_magfact;
2220 if(MaxY < 10) {
2221 graph_label_right = sprintf("%5.1f %s", sval, second_axis_symb);
2222 } else {
2223 graph_label_right = sprintf("%5.0f %s", sval, second_axis_symb);
2224 }
2225 } else {
2226 graph_label_right = sprintf(this.second_axis_format, sval);
2227 }
2228 this.gfx.text (X1+7, Y0, this.GRC.FONT, this.TEXT.AXIS, this.tabwidth, 0.0, RrdGraph.GFX_H_LEFT, RrdGraph.GFX_V_CENTER, graph_label_right );
2229 }
2230 this.gfx.text(X0 - this.TEXT.AXIS.size , Y0, this.GRC.FONT, this.TEXT.AXIS , this.tabwidth, 0.0, RrdGraph.GFX_H_RIGHT, RrdGraph.GFX_V_CENTER, graph_label);
2231 this.gfx.line(X0 - 2, Y0, X0, Y0, this.MGRIDWIDTH, this.GRC.MGRID);
2232 this.gfx.line(X1, Y0, X1 + 2, Y0, this.MGRIDWIDTH, this.GRC.MGRID);
2233 this.gfx.dashed_line(X0 - 2, Y0, X1 + 2, Y0, this.MGRIDWIDTH, this.GRC.MGRID, this.grid_dash_on, this.grid_dash_off);
2234 } else if (!this.no_minor) {
2235 this.gfx.line( X0 - 2, Y0, X0, Y0, this.GRIDWIDTH, this.GRC.GRID);
2236 this.gfx.line(X1, Y0, X1 + 2, Y0, this.GRIDWIDTH, this.GRC.GRID);
2237 this.gfx.dashed_line(X0 - 1, Y0, X1 + 1, Y0, this.GRIDWIDTH, this.GRC.GRID, this.grid_dash_on, this.grid_dash_off);
2238 }
2239 }
2240 }
2241 return 1;
2242};
2243
2244RrdGraph.prototype.grid_paint = function()
2245{
2246 var i;
2247 var res = 0;
2248 var X0, Y0;
2249
2250 if (this.draw_3d_border > 0) {
2251 i = this.draw_3d_border;
2252 this.gfx.new_area(0, this.yimg, i, this.yimg - i, i, i, this.GRC.SHADEA);
2253 this.gfx.add_point(this.ximg - i, i);
2254 this.gfx.add_point(this.ximg, 0);
2255 this.gfx.add_point(0, 0);
2256 this.gfx.close_path();
2257 this.gfx.new_area(i, this.yimg - i, this.ximg - i, this.yimg - i, this.ximg - i, i, this.GRC.SHADEB);
2258 this.gfx.add_point(this.ximg, 0);
2259 this.gfx.add_point(this.ximg, this.yimg);
2260 this.gfx.add_point(0, this.yimg);
2261 this.gfx.close_path();
2262 }
2263 if (this.draw_x_grid)
2264 this.vertical_grid();
2265 if (this.draw_y_grid) {
2266 if (this.logarithmic)
2267 res = this.horizontal_log_grid();
2268 else
2269 res = this.draw_horizontal_grid();
2270 /* dont draw horizontal grid if there is no min and max val */
2271 if (!res) {
2272 this.gfx.text(this.ximg / 2, (2 * this.yorigin - this.ysize) / 2,
2273 this.GRC.FONT, this.TEXT.AXIS,
2274 this.tabwidth, 0.0,
2275 RrdGraph.GFX_H_CENTER, RrdGraph.GFX_V_CENTER, 'No Data found');
2276 }
2277 }
2278
2279 /* yaxis unit description */
2280 if (this.ylegend){
2281 this.gfx.text(this.xOriginLegendY+10, this.yOriginLegendY,
2282 this.GRC.FONT, this.TEXT.UNIT, this.tabwidth, this.YLEGEND_ANGLE,
2283 RrdGraph.GFX_H_CENTER, RrdGraph.GFX_V_CENTER, this.ylegend);
2284
2285 }
2286 if (this.second_axis_legend){
2287 this.gfx.text(this.xOriginLegendY2+10, this.yOriginLegendY2,
2288 this.GRC.FONT, this.TEXT.UNIT, this.tabwidth, this.YLEGEND_ANGLE,
2289 RrdGraph.GFX_H_CENTER, RrdGraph.GFX_V_CENTER, this.second_axis_legend);
2290 }
2291
2292 /* graph title */
2293 this.gfx.text(this.xOriginTitle, this.yOriginTitle+6,
2294 this.GRC.FONT, this.TEXT.TITLE, this.tabwidth, 0.0, RrdGraph.GFX_H_CENTER, RrdGraph.GFX_V_TOP, this.title);
2295 /* rrdtool 'logo' */
2296 if (!this.no_rrdtool_tag){
2297 var color = this.parse_color(this.GRC.FONT);
2298 color[3] = 0.3;
2299 var water_color = this.color2rgba(color);
2300 var xpos = this.legendposition === RrdGraph.LEGEND_POS_EAST ? this.xOriginLegendY : this.ximg - 4;
2301 this.gfx.text(xpos, 5, water_color, this.TEXT.WATERMARK, this.tabwidth,
2302 -90, RrdGraph.GFX_H_LEFT, RrdGraph.GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2303 }
2304 /* graph watermark */
2305 if (this.watermark) {
2306 var color = this.parse_color(this.GRC.FONT)
2307 color[3] = 0.3;
2308 var water_color = this.color2rgba(color);
2309 this.gfx.text(this.ximg / 2, this.yimg - 6, water_color, this.TEXT.WATERMARK , this.tabwidth, 0,
2310 RrdGraph.GFX_H_CENTER, RrdGraph.GFX_V_BOTTOM, this.watermark);
2311 }
2312 /* graph labels */
2313 if (!(this.no_legend) && !(this.only_graph)) {
2314 for (var i = 0 , gdes_c = this.gdes.length; i < gdes_c; i++) {
2315 if (!this.gdes[i].legend) continue;
2316 X0 = this.xOriginLegend + this.gdes[i].leg_x;
2317 Y0 = this.legenddirection === RrdGraph.LEGEND_DIR_TOP_DOWN ? this.yOriginLegend + this.gdes[i].leg_y : this.yOriginLegend + this.legendheight - this.gdes[i].leg_y;
2318 this.gfx.text(X0, Y0, this.GRC.FONT, this.TEXT.LEGEND, this.tabwidth, 0.0, RrdGraph.GFX_H_LEFT, RrdGraph.GFX_V_BOTTOM, this.gdes[i].legend);
2319 if (this.gdes[i].gf != RrdGraphDesc.GF_PRINT && this.gdes[i].gf != RrdGraphDesc.GF_GPRINT && this.gdes[i].gf != RrdGraphDesc.GF_COMMENT) {
2320 var boxH, boxV;
2321 var X1, Y1;
2322
2323 boxH = this.gfx.get_text_width(0,this.TEXT.LEGEND, this.tabwidth, 'o') * 1.2;
2324 boxV = boxH;
2325
2326 Y0 -= boxV * 0.4;
2327
2328 if (this.dynamic_labels && this.gdes[i].gf === RrdGraphDesc.GF_HRULE) {
2329 this.gfx.line(X0, Y0 - boxV / 2, X0 + boxH, Y0 - boxV / 2, 1.0, this.gdes[i].col);
2330 } else if (this.dynamic_labels && this.gdes[i].gf === RrdGraphDesc.GF_VRULE) {
2331 this.gfx.line(X0 + boxH / 2, Y0, X0 + boxH / 2, Y0 - boxV, 1.0, this.gdes[i].col);
2332 } else if (this.dynamic_labels && this.gdes[i].gf === RrdGraphDesc.GF_LINE) {
2333 this.gfx.line(X0, Y0, X0 + boxH, Y0 - boxV, this.gdes[i].linewidth, this.gdes[i].col);
2334 } else {
2335 this.gfx.new_area(X0, Y0 - boxV, X0, Y0, X0 + boxH, Y0, this.GRC.BACK);
2336 this.gfx.add_point(X0 + boxH, Y0 - boxV);
2337 this.gfx.close_path();
2338 this.gfx.new_area(X0, Y0 - boxV, X0, Y0, X0 + boxH, Y0, this.gdes[i].col);
2339 this.gfx.add_point(X0 + boxH, Y0 - boxV);
2340 this.gfx.close_path();
2341 if (this.gdes[i].dash) this.gfx.set_dash([ 3.0 ], 1, 0.0);
2342 this.gfx.rectangle(X0, Y0, X0 + boxH, Y0 - boxV, 1.0, this.GRC.FRAME);
2343 }
2344 }
2345 }
2346 }
2347};
2348
2349RrdGraph.prototype.graph_size_location = function (elements)
2350{
2351 var Xvertical = 0;
2352 var Xvertical2 = 0;
2353 var Ytitle = 0;
2354 var Xylabel = 0;
2355 var Xmain = 0;
2356 var Ymain = 0;
2357 var Yxlabel = 0;
2358 var Xspacing = 15;
2359 var Yspacing = 15;
2360 var Ywatermark = 4;
2361
2362 if (this.only_graph) {
2363 this.xorigin = 0;
2364 this.ximg = this.xsize;
2365 this.yimg = this.ysize;
2366 this.yorigin = this.ysize;
2367 this.xtr(0);
2368 this.ytr(Number.NaN);
2369 return 0;
2370 }
2371
2372 if(this.watermark)
2373 Ywatermark = this.TEXT.WATERMARK.size * 1.5; // 2
2374 if(this.ylegend)
2375 Xvertical = this.TEXT.UNIT.size * 1.5; // 2
2376 if(this.second_axis_legend) {
2377 Xvertical2 = this.TEXT.UNIT.size * 1.5; // 2
2378 } else {
2379 Xvertical2 = Xspacing;
2380 }
2381
2382 if(this.title)
2383 Ytitle = this.TEXT.TITLE.size * 1.95 + 10; // 2.6
2384 else
2385 Ytitle = Yspacing;
2386
2387 if (elements) {
2388 if (this.draw_x_grid)
2389 Yxlabel = this.TEXT.AXIS.size * 1.35; // 2.5 1.87
2390 if (this.draw_y_grid || this.forceleftspace) // FIXME
2391 Xylabel = this.gfx.get_text_width(0, this.TEXT.AXIS, this.tabwidth, '0') * this.unitslength;
2392 }
2393 Xylabel += Xspacing;
2394 this.legendheight = 0;
2395 this.legendwidth = 0;
2396 if(!this.no_legend) {
2397 if(this.legendposition === RrdGraph.LEGEND_POS_WEST || this.legendposition === RrdGraph.LEGEND_POS_EAST){
2398 if (this.leg_place(1) === -1) return -1; // FIXME
2399 }
2400 }
2401
2402 if(this.full_size_mode) {
2403 this.ximg = this.xsize;
2404 this.yimg = this.ysize;
2405 Xmain = this.ximg;
2406 Ymain = this.yimg;
2407
2408 Xmain -= Xylabel;// + Xspacing;
2409 if((this.legendposition === RrdGraph.LEGEND_POS_WEST || this.legendposition === RrdGraph.LEGEND_POS_EAST) && !(this.no_legend) )
2410 Xmain -= this.legendwidth;// + Xspacing;
2411 if (this.second_axis_scale != 0) Xmain -= Xylabel;
2412 if (!(this.no_rrdtool_tag)) Xmain -= Xspacing;
2413
2414 Xmain -= Xvertical + Xvertical2;
2415
2416 if(Xmain < 1) Xmain = 1;
2417 this.xsize = Xmain;
2418
2419 if (!(this.no_legend)) {
2420 if(this.legendposition === RrdGraph.LEGEND_POS_NORTH || this.legendposition === RrdGraph.LEGEND_POS_SOUTH){
2421 this.legendwidth = this.ximg;
2422 if (this.leg_place(0) === -1) return -1;
2423 }
2424 }
2425
2426 if( (this.legendposition === RrdGraph.LEGEND_POS_NORTH || this.legendposition === RrdGraph.LEGEND_POS_SOUTH) && !(this.no_legend) )
2427 Ymain -= Yxlabel + this.legendheight;
2428 else Ymain -= Yxlabel;
2429
2430 Ymain -= Ytitle;
2431
2432 if (this.nolegened) Ymain -= 0.5*Yspacing;
2433 if (this.watermark) Ymain -= Ywatermark;
2434 if(Ymain < 1) Ymain = 1;
2435 this.ysize = Ymain;
2436 } else {
2437 if (elements) {
2438// Xmain = this.xsize; // + Xspacing;
2439 Xmain = this.xsize + Xspacing; //FIXME ???
2440 Ymain = this.ysize;
2441 }
2442 this.ximg = Xmain + Xylabel;
2443 if (!this.no_rrdtool_tag) this.ximg += Xspacing;
2444
2445 if( (this.legendposition === RrdGraph.LEGEND_POS_WEST || this.legendposition === RrdGraph.LEGEND_POS_EAST) && !this.no_legend )
2446 this.ximg += this.legendwidth;// + Xspacing;
2447 if (this.second_axis_scale != 0) this.ximg += Xylabel;
2448
2449 this.ximg += Xvertical + Xvertical2;
2450
2451 if (!(this.no_legend)) {
2452 if(this.legendposition === RrdGraph.LEGEND_POS_NORTH || this.legendposition === RrdGraph.LEGEND_POS_SOUTH){
2453 this.legendwidth = this.ximg;
2454 if (this.leg_place(0) === -1) return -1;
2455 }
2456 }
2457
2458 this.yimg = Ymain + Yxlabel;
2459 if( (this.legendposition === RrdGraph.LEGEND_POS_NORTH || this.legendposition === RrdGraph.LEGEND_POS_SOUTH) && !(this.no_legend) )
2460 this.yimg += this.legendheight;
2461
2462 if (Ytitle) this.yimg += Ytitle;
2463 else this.yimg += 1.5 * Yspacing;
2464
2465 if (this.no_legend) this.yimg += 0.5*Yspacing;
2466 if (this.watermark) this.yimg += Ywatermark;
2467 }
2468
2469
2470 if (!this.no_legend) {
2471 if(this.legendposition === RrdGraph.LEGEND_POS_WEST || this.legendposition === RrdGraph.LEGEND_POS_EAST){
2472 if (this.leg_place(0) === -1) return -1;
2473 }
2474 }
2475
2476 switch(this.legendposition){
2477 case RrdGraph.LEGEND_POS_NORTH:
2478 this.xOriginTitle = Math.round(this.xsize / 2);
2479 this.yOriginTitle = 0;
2480 this.xOriginLegend = 0;
2481 this.yOriginLegend = Math.round(Ytitle);
2482 this.xOriginLegendY = 0;
2483 this.yOriginLegendY = Math.round(Ytitle + this.legendheight + (Ymain / 2) + Yxlabel);
2484 this.xorigin = Math.round(Xvertical + Xylabel);
2485 this.yorigin = Math.round(Ytitle + this.legendheight + Ymain);
2486 this.xOriginLegendY2 = Math.round(Xvertical + Xylabel + Xmain);
2487 if (this.second_axis_scale != 0) this.xOriginLegendY2 += Xylabel;
2488 this.yOriginLegendY2 = Math.round(Ytitle + this.legendheight + (Ymain / 2) + Yxlabel);
2489 break;
2490 case RrdGraph.LEGEND_POS_WEST:
2491 this.xOriginTitle = Math.round(this.legendwidth + this.xsize / 2);
2492 this.yOriginTitle = 0;
2493 this.xOriginLegend = 0;
2494 this.yOriginLegend = Math.round(Ytitle);
2495 this.xOriginLegendY = Math.round(this.legendwidth);
2496 this.yOriginLegendY = Math.round(Ytitle + (Ymain / 2));
2497 this.xorigin = Math.round(this.legendwidth + Xvertical + Xylabel);
2498 this.yorigin = Math.round(Ytitle + Ymain);
2499 this.xOriginLegendY2 = Math.round(this.legendwidth + Xvertical + Xylabel + Xmain);
2500 if (this.second_axis_scale != 0) this.xOriginLegendY2 += Xylabel;
2501 this.yOriginLegendY2 = Math.round(Ytitle + (Ymain / 2));
2502 break;
2503 case RrdGraph.LEGEND_POS_SOUTH:
2504 this.xOriginTitle = Math.round(this.xsize / 2);
2505 this.yOriginTitle = 0;
2506 this.xOriginLegend = 0;
2507 this.yOriginLegend = Math.round(Ytitle + Ymain + Yxlabel);
2508 this.xOriginLegendY = 0;
2509 this.yOriginLegendY = Math.round(Ytitle + (Ymain / 2));
2510 this.xorigin = Math.round(Xvertical + Xylabel);
2511 this.yorigin = Math.round(Ytitle + Ymain);
2512 this.xOriginLegendY2 = Math.round(Xvertical + Xylabel + Xmain);
2513 if (this.second_axis_scale != 0) this.xOriginLegendY2 += Xylabel;
2514 this.yOriginLegendY2 = Math.round(Ytitle + (Ymain / 2));
2515 break;
2516 case RrdGraph.LEGEND_POS_EAST:
2517 this.xOriginTitle = Math.round(this.xsize / 2);
2518 this.yOriginTitle = 0;
2519 this.xOriginLegend = Math.round(Xvertical + Xylabel + Xmain + Xvertical2);
2520 if (this.second_axis_scale != 0) this.xOriginLegend += Xylabel;
2521 this.yOriginLegend = Math.round(Ytitle);
2522 this.xOriginLegendY = 0;
2523 this.yOriginLegendY = Math.round(Ytitle + (Ymain / 2));
2524 this.xorigin = Math.round(Xvertical + Xylabel);
2525 this.yorigin = Math.round(Ytitle + Ymain);
2526 this.xOriginLegendY2 = Math.round(Xvertical + Xylabel + Xmain);
2527 if (this.second_axis_scale != 0) this.xOriginLegendY2 += Xylabel;
2528 this.yOriginLegendY2 = Math.round(Ytitle + (Ymain / 2));
2529
2530 if (!this.no_rrdtool_tag){
2531 this.xOriginTitle += Xspacing;
2532 this.xOriginLegend += Xspacing;
2533 this.xOriginLegendY += Xspacing;
2534 this.xorigin += Xspacing;
2535 this.xOriginLegendY2 += Xspacing;
2536 }
2537 break;
2538 }
2539 this.xtr(0);
2540 this.ytr(Number.NaN);
2541 return 0;
2542};
2543
2544RrdGraph.prototype.graph_paint = function()
2545{
2546 if (this.logarithmic && this.minval <= 0)
2547 throw new RrdGraphError("for a logarithmic yaxis you must specify a lower-limit > 0");
2548
2549 //var start_end = RrdTime.proc_start_end(this.start_t, this.end_t);
2550 //this.start = start_end[0];
2551 //this.end = start_end[1];
2552
2553 if (this.start < 3600 * 24 * 365 * 10)
2554 throw new RrdGraphError("the first entry to fetch should be after 1980 ("+this.start+")");
2555
2556 if (this.end < this.start)
2557 throw new RrdGraphError("start ("+this.start+") should be less than end ("+this.end+")");
2558
2559//this.xlab_form = null
2560 this.xlab_user = { minsec: -1, length: 0, gridtm: 0, gridst: 0, mgridtm: 0, mgridst: 0, labtm: 0, labst: 0, precis: 0, stst: null };
2561 this.ygrid_scale = { gridstep: 0.0, labfact:0 , labfmt: null };
2562 this.minval = this.setminval;
2563 this.maxval = this.setmaxval;
2564
2565 this.step = Math.max(this.step, (this.end - this.start) / this.xsize);
2566
2567 for (var i = 0, gdes_c = this.gdes.length; i < gdes_c; i++) {
2568 this.gdes[i].step = 0; // FIXME 0?
2569 this.gdes[i].step_orig = this.step;
2570 this.gdes[i].start = this.start; // FIXME SHIFT
2571// this.gdes[i].start_orig = this.start;
2572 this.gdes[i].end = this.end; // FIXME SHIFT
2573// this.gdes[i].end_orig = this.end;
2574 }
2575
2576 var areazero = 0.0
2577 var lastgdes = null;
2578
2579 if (this.data_fetch() === -1)
2580 return -1;
2581 if (this.data_calc() === -1)
2582 return -1;
2583 var i = this.print_calc();
2584 if (i < 0)
2585 return -1;
2586 if (this.graph_size_location(i) === -1)
2587 return -1;
2588
2589 if (this.data_proc() === -1)
2590 return -1;
2591 if (!this.logarithmic)
2592 this.si_unit();
2593 if (!this.rigid && !this.logarithmic)
2594 this.expand_range();
2595
2596 if (this.magfact === 0) this.magfact =1; // FIXME logarithmic ¿?
2597
2598 if (!this.calc_horizontal_grid())
2599 return -1;
2600
2601 this.ytr(Number.NaN);
2602
2603 this.gfx.size(this.ximg, this.yimg);
2604
2605 this.gfx.new_area(0, 0, 0, this.yimg, this.ximg, this.yimg, this.GRC.BACK);
2606 this.gfx.add_point(this.ximg, 0);
2607 this.gfx.close_path();
2608
2609 this.gfx.new_area(this.xorigin, this.yorigin, this.xorigin + this.xsize,
2610 this.yorigin, this.xorigin + this.xsize, this.yorigin - this.ysize, this.GRC.CANVAS);
2611 this.gfx.add_point(this.xorigin, this.yorigin - this.ysize);
2612 this.gfx.close_path();
2613
2614//this.ctx.rect(this.xorigin, this.yorigin - this.ysize - 1.0, this.xsize, this.ysize + 2.0);
2615//this.ctx.clip();
2616
2617 if (this.minval > 0.0) areazero = this.minval;
2618 if (this.maxval < 0.0) areazero = this.maxval;
2619
2620 for (var i = 0, gdes_c = this.gdes.length; i < gdes_c; i++) {
2621 switch (this.gdes[i].gf) {
2622 case RrdGraphDesc.GF_CDEF:
2623 case RrdGraphDesc.GF_VDEF:
2624 case RrdGraphDesc.GF_DEF:
2625 case RrdGraphDesc.GF_PRINT:
2626 case RrdGraphDesc.GF_GPRINT:
2627 case RrdGraphDesc.GF_COMMENT:
2628 case RrdGraphDesc.GF_TEXTALIGN:
2629 case RrdGraphDesc.GF_HRULE:
2630 case RrdGraphDesc.GF_VRULE:
2631 case RrdGraphDesc.GF_XPORT:
2632 case RrdGraphDesc.GF_SHIFT:
2633 break;
2634 case RrdGraphDesc.GF_TICK:
2635 for (var ii = 0; ii < this.xsize; ii++) {
2636 if (!isNaN(this.gdes[i].p_data[ii]) && this.gdes[i].p_data[ii] != 0.0) {
2637 if (this.gdes[i].yrule > 0) {
2638 this.gfx.line(this.xorigin + ii, this.yorigin + 1.0,
2639 this.xorigin + ii, this.yorigin - this.gdes[i].yrule * this.ysize, 1.0, this.gdes[i].col);
2640 } else if (this.gdes[i].yrule < 0) {
2641 this.gfx.line(this.xorigin + ii, this.yorigin - this.ysize - 1.0,
2642 this.xorigin + ii, this.yorigin - this.ysize - this.gdes[i].yrule * this.ysize, 1.0, this.gdes[i].col);
2643 }
2644 }
2645 }
2646 break;
2647 case RrdGraphDesc.GF_LINE:
2648 case RrdGraphDesc.GF_AREA:
2649 var diffval = this.maxval - this.minval;
2650 var maxlimit = this.maxval + 9 * diffval;
2651 var minlimit = this.minval - 9 * diffval;
2652 for (var ii = 0; ii < this.xsize; ii++) {
2653 if (!isNaN(this.gdes[i].p_data[ii])) { // FIXME NaN < ???
2654 if (!isFinite(this.gdes[i].p_data[ii])) {
2655 if (this.gdes[i].p_data[ii] > 0) this.gdes[i].p_data[ii] = this.maxval;
2656 else this.gdes[i].p_data[ii] = this.minval;
2657 }
2658 if (this.gdes[i].p_data[ii] > maxlimit) this.gdes[i].p_data[ii] = maxlimit;
2659 if (this.gdes[i].p_data[ii] < minlimit) this.gdes[i].p_data[ii] = minlimit;
2660 }
2661 }
2662 var color = this.parse_color(this.gdes[i].col); // if (this.gdes[i].col.alpha != 0.0)
2663 if (color[3] != 0.0) {
2664 if (this.gdes[i].gf === RrdGraphDesc.GF_LINE) {
2665 var last_y = 0.0;
2666 var draw_on = false;
2667
2668 if (this.gdes[i].dash) this.gfx.set_dash(this.gdes[i].p_dashes, this.gdes[i].ndash, this.gdes[i].offset);
2669 this.gfx.stroke_begin(this.gdes[i].linewidth, this.gdes[i].col);
2670 for (var ii = 1; ii < this.xsize; ii++) {
2671 if (isNaN(this.gdes[i].p_data[ii]) || (this.slopemode && isNaN(this.gdes[i].p_data[ii - 1]))) {
2672 draw_on = false;
2673 continue;
2674 }
2675 if (!draw_on) {
2676 last_y = this.ytr(this.gdes[i].p_data[ii]);
2677 if (!this.slopemode) {
2678 var x = ii - 1 + this.xorigin;
2679 var y = last_y;
2680 this.gfx.moveTo(x, y);
2681 x = ii + this.xorigin;
2682 y = last_y;
2683 this.gfx.lineTo(x, y)
2684 } else {
2685 var x = ii - 1 + this.xorigin;
2686 var y = this.ytr(this.gdes[i].p_data[ii - 1]);
2687 this.gfx.moveTo(x, y);
2688 x = ii + this.xorigin;
2689 y = last_y;
2690 this.gfx.lineTo(x, y);
2691 }
2692 draw_on = true;
2693 } else {
2694 var x1 = ii + this.xorigin;
2695 var y1 = this.ytr(this.gdes[i].p_data[ii]);
2696
2697 if (!this.slopemode && !this.AlmostEqual2sComplement(y1, last_y, 4)) {
2698 var x = ii - 1 + this.xorigin;
2699 var y = y1;
2700
2701 this.gfx.lineTo(x, y);
2702 }
2703 last_y = y1;
2704 this.gfx.lineTo(x1, y1);
2705 }
2706 }
2707 this.gfx.stroke_end();
2708 } else {
2709 var idxI = -1;
2710 var foreY = [];
2711 var foreX = [];
2712 var backY = [];
2713 var backX = [];
2714 var drawem = false;
2715
2716 for (ii = 0; ii <= this.xsize; ii++) {
2717 var ybase, ytop;
2718
2719 if (idxI > 0 && (drawem || ii === this.xsize)) {
2720 var cntI = 1;
2721 var lastI = 0;
2722
2723 while (cntI < idxI && this.AlmostEqual2sComplement(foreY [lastI], foreY[cntI], 4) &&
2724 this.AlmostEqual2sComplement(foreY [lastI], foreY [cntI + 1], 4)) cntI++;
2725 this.gfx.new_area(backX[0], backY[0], foreX[0], foreY[0], foreX[cntI], foreY[cntI], this.gdes[i].col);
2726 while (cntI < idxI) {
2727 lastI = cntI;
2728 cntI++;
2729 while (cntI < idxI &&
2730 this.AlmostEqual2sComplement(foreY [lastI], foreY[cntI], 4) &&
2731 this.AlmostEqual2sComplement(foreY [lastI], foreY [cntI + 1], 4)) cntI++;
2732 this.gfx.add_point(foreX[cntI], foreY[cntI]);
2733 }
2734 this.gfx.add_point(backX[idxI], backY[idxI]);
2735 while (idxI > 1) {
2736 lastI = idxI;
2737 idxI--;
2738 while (idxI > 1 &&
2739 this.AlmostEqual2sComplement(backY [lastI], backY[idxI], 4) &&
2740 this.AlmostEqual2sComplement(backY [lastI], backY [idxI - 1], 4)) idxI--;
2741 this.gfx.add_point(backX[idxI], backY[idxI]);
2742 }
2743 idxI = -1;
2744 drawem = false;
2745 this.gfx.close_path();
2746 }
2747 if (drawem) {
2748 drawem = false;
2749 idxI = -1;
2750 }
2751 if (ii === this.xsize)
2752 break;
2753 if (!this.slopemode && ii === 0)
2754 continue;
2755 if (isNaN(this.gdes[i].p_data[ii])) {
2756 drawem = true;
2757 continue;
2758 }
2759 ytop = this.ytr(this.gdes[i].p_data[ii]);
2760 if (lastgdes && this.gdes[i].stack) ybase = this.ytr(lastgdes.p_data[ii]);
2761 else ybase = this.ytr(areazero);
2762 if (ybase === ytop) {
2763 drawem = true;
2764 continue;
2765 }
2766 if (ybase > ytop) {
2767 var extra = ytop;
2768 ytop = ybase;
2769 ybase = extra;
2770 }
2771 if (!this.slopemode) {
2772 backY[++idxI] = ybase - 0.2;
2773 backX[idxI] = ii + this.xorigin - 1;
2774 foreY[idxI] = ytop + 0.2;
2775 foreX[idxI] = ii + this.xorigin - 1;
2776 }
2777 backY[++idxI] = ybase - 0.2;
2778 backX[idxI] = ii + this.xorigin;
2779 foreY[idxI] = ytop + 0.2;
2780 foreX[idxI] = ii + this.xorigin;
2781 }
2782 }
2783 }
2784 /* if color != 0x0 */
2785 /* make sure we do not run into trouble when stacking on NaN */
2786 for (ii = 0; ii < this.xsize; ii++) {
2787 if (isNaN(this.gdes[i].p_data[ii])) {
2788 if (lastgdes && (this.gdes[i].stack)) this.gdes[i].p_data[ii] = lastgdes.p_data[ii];
2789 else this.gdes[i].p_data[ii] = areazero;
2790 }
2791 }
2792 lastgdes = this.gdes[i]; //lastgdes = &(this.gdes[i]);
2793 break;
2794 case RrdGraphDesc.GF_STACK:
2795 throw new RrdGraphError("STACK should already be turned into LINE or AREA here");
2796 break;
2797 }
2798 }
2799//cairo_reset_clip(this.cr);
2800 if (!this.only_graph)
2801 this.grid_paint();
2802 if (!this.only_graph)
2803 this.axis_paint();
2804 /* the RULES are the last thing to paint ... */
2805 for (var i = 0, gdes_c = this.gdes.length; i < gdes_c; i++) {
2806 switch (this.gdes[i].gf) {
2807 case RrdGraphDesc.GF_HRULE:
2808 if (this.gdes[i].yrule >= this.minval && this.gdes[i].yrule <= this.maxval) {
2809 if (this.gdes[i].dash) this.gfx.set_dash(this.gdes[i].p_dashes, this.gdes[i].ndash, this.gdes[i].offset);
2810 this.gfx.line(this.xorigin, this.ytr(this.gdes[i].yrule),
2811 this.xorigin + this.xsize, this.ytr(this.gdes[i].yrule), 1.0, this.gdes[i].col);
2812 }
2813 break;
2814 case RrdGraphDesc.GF_VRULE:
2815 if (this.gdes[i].xrule >= this.start && this.gdes[i].xrule <= this.end) {
2816 if (this.gdes[i].dash) this.gfx.set_dash(this.gdes[i].p_dashes, this.gdes[i].ndash, this.gdes[i].offset);
2817 this.gfx.line(this.xtr(this.gdes[i].xrule), this.yorigin,
2818 this.xtr(this.gdes[i].xrule), this.yorigin - this.ysize, 1.0, this.gdes[i].col);
2819 }
2820 break;
2821 default:
2822 break;
2823 }
2824 }
2825 return 0;
2826};
2827
2828RrdGraph.prototype.find_var = function(key)
2829{
2830 for (var ii = 0, gdes_c = this.gdes.length; ii < gdes_c; ii++) {
2831 if ((this.gdes[ii].gf === RrdGraphDesc.GF_DEF ||
2832 this.gdes[ii].gf === RrdGraphDesc.GF_VDEF ||
2833 this.gdes[ii].gf === RrdGraphDesc.GF_CDEF)
2834 && this.gdes[ii].vname === key) {
2835 return ii;
2836 }
2837 }
2838 return -1;
2839};
2840
2841RrdGraph.prototype.gdes_add_def = function (vname, rrdfile, name, cf, step, start, end, reduce)
2842{
2843 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_DEF, vname, rrdfile, name, cf, step, start, end, reduce));
2844};
2845
2846RrdGraph.prototype.gdes_add_cdef = function (vname, rpn)
2847{
2848 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_CDEF, vname, rpn));
2849};
2850
2851RrdGraph.prototype.gdes_add_vdef = function (vname, rpn)
2852{
2853 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_VDEF, vname, rpn));
2854};
2855
2856RrdGraph.prototype.gdes_add_shift = function (vname, offset)
2857{
2858 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_SHIFT, vname, offset));
2859};
2860
2861RrdGraph.prototype.gdes_add_line = function (width, value, color, legend, stack)
2862{
2863 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_LINE, width, value, color, legend, stack));
2864};
2865
2866RrdGraph.prototype.gdes_add_area = function (value, color, legend, stack)
2867{
2868 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_AREA, value, color, legend, stack));
2869};
2870
2871RrdGraph.prototype.gdes_add_tick = function (vname, color, fraction, legend)
2872{
2873 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_TICK, vname, color, fraction, legend));
2874};
2875
2876RrdGraph.prototype.gdes_add_gprint = function (vname, cf, format, strftimefmt)
2877{
2878 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_GPRINT, vname, cf, format, strftimefmt));
2879};
2880
2881RrdGraph.prototype.gdes_add_comment = function (text)
2882{
2883 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_COMMENT, text));
2884};
2885
2886RrdGraph.prototype.gdes_add_textalign = function (align)
2887{
2888 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_TEXTALING, align));
2889};
2890
2891RrdGraph.prototype.gdes_add_vrule = function (time, color, legend)
2892{
2893 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_VRULE, time, color, legend));
2894};
2895
2896RrdGraph.prototype.gdes_add_hrule = function (value, color, legend)
2897{
2898 this.gdes.push(new RrdGraphDesc(this, RrdGraphDesc.GF_HRULE, value, color, legend));
2899};
2900
2901RrdGraph.prototype.tmt_conv = function (str)
2902{
2903 switch (str) {
2904 case 'SECOND': return RrdGraph.TMT_SECOND;
2905 case 'MINUTE': return RrdGraph.TMT_MINUTE;
2906 case 'HOUR': return RrdGraph.TMT_HOUR;
2907 case 'DAY': return RrdGraph.TMT_DAY;
2908 case 'WEEK': return RrdGraph.TMT_WEEK;
2909 case 'MONTH': return RrdGraph.TMT_MONTH;
2910 case 'YEAR': return RrdGraph.TMT_YEAR;
2911 }
2912 return -1;
2913};
2914
diff --git a/js/RrdJson.js b/js/RrdJson.js
new file mode 100644
index 0000000..85b7f11
--- /dev/null
+++ b/js/RrdJson.js
@@ -0,0 +1,588 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 *
18 * Manuel Sanmartin <manuel.luis at gmail.com>
19 **/
20
21"use strict";
22
23/**
24 * RrdJson
25 * @constructor
26 */
27var RrdJson = function() {
28 if (arguments.length == 1) {
29 this.init1.apply(this, arguments);
30 } else if (arguments.length == 2) {
31 this.init2.apply(this, arguments);
32 } else if (arguments.length == 3) {
33 this.init3.apply(this, arguments);
34 }
35};
36
37RrdJson.prototype = {
38 graph: null,
39 json: null,
40
41 init1: function (rrdgraph)
42 {
43 this.graph = rrdgraph
44 },
45 init2: function (rrdgraph, jsonstr)
46 {
47 this.json = JSON.parse(jsonstr);
48 this.graph = rrdgraph
49 },
50 init3: function (gfx, fetch, jsonstr)
51 {
52 this.json = JSON.parse(jsonstr);
53 this.graph = new RrdGraph(gfx, fetch);
54 },
55 parse: function()
56 {
57 for (var option in this.json) {
58 switch(option) {
59 case 'alt_autoscale':
60 this.graph.alt_autoscale = this.json.alt_autoscale;
61 break;
62 case 'base':
63 this.graph.base = parseInt(this.json.base, 10);
64 if (this.graph.base !== 1000 && this.graph.base !== 1024)
65 throw 'the only sensible value for base apart from 1000 is 1024';
66 break;
67 case 'color':
68 for (var color in this.json.color) {
69 if (color in this.graph.GRC) {
70 this.graph.GRC[color] = this.json.color[color];
71 } else {
72 throw "invalid color name '"+name+"'";
73 }
74 }
75 break;
76 case 'full_size_mode':
77 this.graph.full_size_mode = this.json.full_size_mode;
78 break;
79 case 'slope_mode':
80 this.graph.slopemode = this.json.slope_mode;
81 break;
82 case 'end':
83 this.graph.end_t = new RrdTime(this.json.end);
84 break;
85 case 'force_rules_legend':
86 this.graph.force_rules_legend = this.json.force_rules_legend;
87 break;
88 case 'no_legend':
89 this.graph.no_legend = this.json.no_legend;
90 break;
91 case 'height':
92 this.graph.ysize = this.json.height;
93 break;
94 case 'no_minor':
95 this.graph.no_minor = this.json.no_minor;
96 break;
97 case 'alt_autoscale_min':
98 this.graph.alt_autoscale_min = this.json.alt_autoscale_min;
99 break;
100 case 'only_graph':
101 this.graph.only_graph = this.json.only_graph;
102 break;
103 case 'units_length':
104 this.graph.unitslength = this.json.units_length; // FIXME
105 this.graph.forceleftspace = true;
106 break;
107 case 'lower_limit':
108 if (this.json.lower_limit === null) this.graph.setminval = Number.NaN;
109 else this.graph.setminval = this.json.lower_limit;
110 break;
111 case 'alt_autoscale_max':
112 this.graph.alt_autoscale_max = this.json.alt_autoscale_max;
113 break;
114 case 'zoom':
115 this.graph.zoom = this.json.zoom;
116 if (this.graph.zoom <= 0.0)
117 throw "zoom factor must be > 0";
118 break;
119 case 'no_gridfit':
120 this.graph.gridfit = this.json.no_gridfit;
121 break;
122 case 'font':
123 for (var font in this.json.font) {
124 if (font in this.graph.TEXT) {
125 if (this.json.font[font].size !== undefined)
126 this.graph.TEXT[font].size = this.json.font[font].size;
127 if (this.json.font[font].font !== undefined)
128 this.graph.TEXT[font].font = this.json.font[font].font;
129 } else {
130 throw "invalid text property name";
131 }
132 }
133 break;
134 case 'logarithmic':
135 this.graph.logarithmic = this.json.logarithmic;
136 break;
137 case 'rigid':
138 this.graph.rigid = this.json.rigid;
139 break;
140 case 'step':
141 this.graph.step = this.json.step;
142 break;
143 case 'start':
144 this.graph.start_t = new RrdTime(this.json.start);
145 break;
146 case 'tabwidth':
147 this.graph.tabwidth = this.json.tabwidth;
148 break;
149 case 'title':
150 this.graph.title = this.json.title;
151 break;
152 case 'upper_limit':
153 if (this.json.upper_limit === null) this.graph.setmaxval = Number.NaN;
154 else this.graph.setmaxval = this.json.upper_limit;
155 break;
156 case 'vertical_label':
157 this.graph.ylegend = this.json.vertical_label;
158 break;
159 case 'watermark':
160 this.graph.watermark = this.json.watermark;
161 break;
162 case 'width':
163 this.graph.xsize = this.json.width;
164 if (this.graph.xsize < 10)
165 throw "width below 10 pixels";
166 break;
167 case 'units_exponent':
168 this.graph.unitsexponent = this.json.units_exponent;
169 break;
170 case 'x_grid':
171 break;
172 case 'alt_ygrid':
173 this.graph.alt_ygrid = this.json.alt_ygrid;
174 break;
175 case 'y_grid':
176 break;
177 case 'lazy':
178 this.graph.lazy = this.json.lazy;
179 break;
180 case 'units':
181 break;
182 case 'disable_rrdtool_tag':
183 this.graph.no_rrdtool_tag = this.json.disable_rrdtool_tag;
184 break;
185 case 'right_axis':
186 break;
187 case 'right_axis_label':
188 this.graph.second_axis_legend = this.json.right_axis_label;
189 break;
190 case 'right_axis_format':
191 this.graph.second_axis_format = this.json.right_axis_format;
192 break;
193 case 'legend_position':
194 if (this.json.legend_position === "north") {
195 this.graph.legendposition = this.graph.LEGEND_POS.NORTH;
196 } else if (this.json.legend_position === "west") {
197 this.graph.legendposition = this.graph.LEGEND_POS.WEST;
198 } else if (this.json.legend_position === "south") {
199 this.graph.legendposition = this.graph.LEGEND_POS.SOUTH;
200 } else if (this.json.legend_position === "east") {
201 this.graph.legendposition = this.graph.LEGEND_POS.EAST;
202 } else {
203 throw "unknown legend-position '"+value+"'";
204 }
205 break;
206 case 'legend_direction':
207 if (this.json.legend_direction === "topdown") {
208 this.graph.legenddirection = this.graph.LEGEND_DIR.TOP_DOWN;
209 } else if (this.json.legend_direction === "bottomup") {
210 this.graph.legenddirection = this.graph.LEGEND_DIR.BOTTOM_UP;
211 } else {
212 throw "unknown legend-position '"+value+"'";
213 }
214 break;
215 case 'border':
216 this.graph.draw_3d_border = this.json.border;
217 break;
218 case 'grid_dash':
219 if (this.json.grid_dash.length !== 2)
220 throw "expected grid-dash format float:float";
221 this.graph.grid_dash_on = this.json.grid_dash[0];
222 this.graph.grid_dash_off = this.json.grid_dash[1];
223 break;
224 case 'dynamic_labels':
225 this.graph.dynamic_labels = this.json.dynamic_labels;
226 break;
227 case 'gdes':
228 this.parse_gdes(this.json.gdes);
229 break;
230 default:
231 throw 'Unknow option "'+option+'"';
232 }
233 }
234 var start_end = RrdTime.proc_start_end(this.graph.start_t, this.graph.end_t); // FIXME here?
235 this.graph.start = start_end[0];
236 this.graph.end = start_end[1];
237 },
238 parse_gdes: function (gdes)
239 {
240 for (var i = 0, gdes_c = gdes.length; i < gdes_c; i++) {
241 switch (gdes[i].type) {
242// GPRINT:vname:format
243 case 'GPRINT':
244 this.graph.gdes_add_gprint(gdes[i].vname, gdes[i].cf, gdes[i].format, gdes[i].strftm);
245 break;
246// LINE[width]:value[#color][:[legend][:STACK]][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
247 case 'LINE':
248 this.graph.gdes_add_line(gdes[i].width, gdes[i].value, gdes[i].color, gdes[i].legend, gdes[i].stack);
249 break;
250// AREA:value[#color][:[legend][:STACK]]
251 case 'AREA':
252 this.graph.gdes_add_area(gdes[i].value, gdes[i].color, gdes[i].legend, gdes[i].stack);
253 break;
254// TICK:vname#rrggbb[aa][:fraction[:legend]]
255 case 'TICK':
256 this.graph.gdes_add_tick(gdes[i].vname, gdes[i].color, gdes[i].fraction, gdes[i].legend);
257 break;
258// HRULE:value#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
259 case 'HRULE':
260 this.graph.gdes_add_hrule(gdes[i].value, gdes[i].color, gdes[i].legend);
261 break;
262// VRULE:time#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
263 case 'VRULE':
264 this.graph.gdes_add_vrule(gdes[i].time, gdes[i].color, gdes[i].legend);
265 break;
266// COMMENT:text
267 case 'COMMENT':
268 this.graph.gdes_add_comment(gdes[i].legend);
269 break;
270// TEXTALIGN:{left|right|justified|center}
271 case 'TEXTALIGN':
272 switch (gdes[i].align) {
273 case 'left':
274 this.graph.gdes_add_textaling(RrdGraphDesc.TXA_LEFT);
275 break
276 case 'right':
277 this.graph.gdes_add_textaling(RrdGraphDesc.TXA_RIGHT);
278 break
279 case 'justified':
280 this.graph.gdes_add_textaling(RrdGraphDesc.TXA_JUSTIFIED);
281 break
282 case 'center':
283 this.graph.gdes_add_textaling(RrdGraphDesc.TXA_CENTER);
284 break
285 }
286 break;
287// DEF:<vname>=<rrdfile>:<ds-name>:<CF>[:step=<step>][:start=<time>][:end=<time>][:reduce=<CF>]
288 case 'DEF':
289 this.graph.gdes_add_def(gdes[i].vname, gdes[i].rrdfile, gdes[i].name, gdes[i].cf, gdes[i].step, gdes[i].start, gdes[i].end, gdes[i].reduce)
290 break;
291// CDEF:vname=RPN expression
292 case 'CDEF':
293 this.graph.gdes_add_cdef(gdes[i].vname, gdes[i].rpn);
294 break;
295// VDEF:vname=RPN expression
296 case 'VDEF':
297 this.graph.gdes_add_vdef(gdes[i].vname, gdes[i].rpn);
298 break;
299// SHIFT:vname:offset
300 case 'SHIFT':
301 this.graph.gdes_add_shift(gdes[i].vname, gdes[i].offset);
302 break;
303 }
304 }
305 },
306 dump: function(full)
307 {
308 this.json = {};
309
310 if (full === undefined) full = false;
311
312 if (this.graph.alt_autoscale != false || full)
313 this.json.alt_autoscale = this.graph.alt_autoscale;
314
315 if (this.graph.base != 1000 || full)
316 this.json.base = this.graph.base;
317
318 this.json.color = {};
319
320 if (this.graph.GRC.CANVAS != 'rgba(255, 255, 255, 1.0)' || full)
321 this.json.color.CANVAS = this.graph.GRC.CANVAS;
322 if (this.graph.GRC.BACK != 'rgba(242,242, 242, 1.0)' || full)
323 this.json.color.BACK = this.graph.GRC.BACK;
324 if (this.graph.GRC.SHADEA != 'rgba(207, 207, 207, 1.0)' || full)
325 this.json.color.SHADEA = this.graph.GRC.SHADEA;
326 if (this.graph.GRC.SHADEB != 'rgba(158, 158, 158, 1.0)' || full)
327 this.json.color.SHADEB = this.graph.GRC.SHADEB;
328 if (this.graph.GRC.GRID != 'rgba(143, 143, 143, 0.75)' || full)
329 this.json.color.GRID = this.graph.GRC.GRID;
330 if (this.graph.GRC.MGRID != 'rgba(222, 79, 79, 0.60)' || full)
331 this.json.color.MGRID = this.graph.GRC.MGRID;
332 if (this.graph.GRC.FONT != 'rgba(0, 0, 0, 1.0)' || full)
333 this.json.color.FONT = this.graph.GRC.FONT;
334 if (this.graph.GRC.ARROW != 'rgba(127, 31, 31, 1.0)' || full)
335 this.json.color.ARROW = this.graph.GRC.ARROW;
336 if (this.graph.GRC.AXIS != 'rgba(31, 31, 31, 1.0)' || full)
337 this.json.color.AXIS = this.graph.GRC.AXIS;
338 if (this.graph.GRC.FRAME != 'rgba(0, 0, 0, 1.0)' || full)
339 this.json.color.FRAME = this.graph.GRC.FRAME;
340
341 if (Object.keys(this.json.color) == 0) delete this.json.color;
342
343 if (this.graph.full_size_mode != false || full)
344 this.json.full_size_mode = this.graph.full_size_mode;
345
346 if (this.graph.slopemode != false || full)
347 this.json.slope_mode = this.graph.slopemode;
348
349 this.json.end = this.graph.end_t.tspec;
350 this.json.start = this.graph.start_t.tspec;
351
352 if (this.graph.force_rules_legend != false || full)
353 this.json.force_rules_legend = this.graph.force_rules_legend;
354
355 if (this.graph.no_legend != false || full)
356 this.json.no_legend = this.graph.no_legend;
357
358 this.json.width = this.graph.xsize;
359 this.json.height = this.graph.ysize;
360
361 if (this.graph.no_minor != false || full)
362 this.json.no_minor = this.graph.no_minor;
363
364 if (this.graph.alt_autoscale_min != false || full)
365 this.json.alt_autoscale_min = this.graph.alt_autoscale_min;
366
367 if (this.graph.only_graph != false || full)
368 this.json.only_graph = this.graph.only_graph;
369
370 if (this.graph.unitslength != 6 || full)
371 this.json.units_length = this.graph.unitslength;
372
373 if (!isNaN(this.graph.setminval) || full)
374 this.json.lower_limit = this.graph.setminval;
375
376 if (this.graph.alt_autoscale_max != false || full)
377 this.json.alt_autoscale_max = this.graph.alt_autoscale_max;
378
379 if (this.graph.zoom != 1 || full)
380 this.json.zoom = this.graph.zoom;
381
382 if (this.graph.gridfit != true || full)
383 this.json.no_gridfit = this.graph.gridfit;
384
385 this.json.font = {};
386 if (this.graph.TEXT.DEFAULT.size != 11 || this.graph.TEXT.LEGEND.font != this.graph.DEFAULT_FONT || full)
387 this.json.font.DEFAULT = { size: this.graph.TEXT.DEFAULT.size, font: this.graph.TEXT.DEFAULT.font};
388 if (this.graph.TEXT.TITLE.size != 12 || this.graph.TEXT.TITLE.font != this.graph.DEFAULT_FONT || full)
389 this.json.font.TITLE = { size: this.graph.TEXT.TITLE.size, font: this.graph.TEXT.TITLE.font};
390 if (this.graph.TEXT.AXIS.size != 10 || this.graph.TEXT.AXIS.font != this.graph.DEFAULT_FONT || full)
391 this.json.font.AXIS = { size: this.graph.TEXT.AXIS.size, font: this.graph.TEXT.AXIS.font};
392 if (this.graph.TEXT.UNIT.size != 11 || this.graph.TEXT.UNIT.font != this.graph.DEFAULT_FONT || full)
393 this.json.font.UNIT = { size: this.graph.TEXT.UNIT.size, font: this.graph.TEXT.UNIT.font};
394 if (this.graph.TEXT.LEGEND.size != 11 || this.graph.TEXT.LEGEND.font != this.graph.DEFAULT_FONT || full)
395 this.json.font.LEGEND = { size: this.graph.TEXT.LEGEND.size, font: this.graph.TEXT.LEGEND.font};
396 if (this.graph.TEXT.WATERMARK.size != 8 || this.graph.TEXT.WATERMARK.font != this.graph.DEFAULT_FONT || full)
397 this.json.font.WATERMARK = { size: this.graph.TEXT.WATERMARK.size, font: this.graph.TEXT.WATERMARK.font};
398
399 if (Object.keys(this.json.font) == 0) delete this.json.font;
400
401 if (this.graph.logarithmic != false || full)
402 this.json.logarithmic = this.graph.logarithmic;
403
404 if (this.graph.rigid != false || full)
405 this.json.rigid = this.graph.rigid;
406
407// this.json.step = this.graph.step; // FIXME
408
409 if (this.graph.tabwidth != 40 || full)
410 this.json.tabwidth = this.graph.tabwidth;
411
412 if (this.graph.title != '' || full)
413 this.json.title = this.graph.title;
414
415 if (!isNaN(this.graph.setmaxval) || full)
416 this.json.upper_limit = this.graph.setmaxval;
417
418 if (this.graph.ylegend != null || full)
419 this.json.vertical_label = this.graph.ylegend;
420
421 if (this.graph.watermark != null || full)
422 this.json.watermark = this.graph.watermark;
423
424 if (this.graph.unitsexponent != 9999 || full)
425 this.json.units_exponent = this.graph.unitsexponent;
426
427// this.json.x-grid = // FIXME
428
429 if (this.graph.alt_ygrid != false || full)
430 this.json.alt_ygrid = this.graph.alt_ygrid;
431
432// this.json.y_grid = // FIXME
433
434// this.json.lazy = this.graph.lazy;
435
436 if (this.graph.force_units_si != false || full)
437 this.json.units = 'si'; // FIXME
438
439 if (this.graph.no_rrdtool_tag != false || full)
440 this.json.disable_rrdtool_tag = this.graph.no_rrdtool_tag;
441
442// this.json.right_axis = FIXME
443
444 if (this.graph.second_axis_legend != null || full)
445 this.json.right_axis_label = this.graph.second_axis_legend;
446 if (this.graph.second_axis_format != null || full)
447 this.json.right_axis_format = this.graph.second_axis_format;
448
449// this.json.legendposition = this.graph.legendposition; // FIXME
450// this.json.legend-direction = this.graph.legenddirection; // FIXME
451
452 if (this.graph.draw_3d_border != 2 || full)
453 this.json.border = this.graph.draw_3d_border;
454
455 if (this.graph.grid_dash_on != 1 || this.graph.grid_dash_off != 1 || full)
456 this.json.grid_dash = [this.graph.grid_dash_on, this.graph.grid_dash_off]
457
458 if (this.graph.dynamic_labels != false || full)
459 this.json.dynamic_labels = this.graph.dynamic_labels;
460
461 this.json.gdes = [];
462 for (var i = 0, gdes_c = this.graph.gdes.length; i < gdes_c; i++) {
463 switch (this.graph.gdes[i].gf) {
464// GPRINT:vname:format
465 case RrdGraphDesc.GF_GPRINT:
466 this.json.gdes.push({
467 type: 'GPRINT',
468 vname: this.graph.gdes[i].vname,
469 cf: RrdGraphDesc.cf2str(this.graph.gdes[i].cf),
470 format: this.graph.gdes[i].format,
471 strftm: (this.graph.gdes[i].strftm === false ? undefined : this.graph.gdes[i].strftm) });
472 break;
473// LINE[width]:value[#color][:[legend][:STACK]][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
474 case RrdGraphDesc.GF_LINE:
475 this.json.gdes.push({
476 type: 'LINE',
477 width: this.graph.gdes[i].linewidth,
478 value: this.graph.gdes[i].vname,
479 color: this.graph.gdes[i].col,
480 legend: (this.graph.gdes[i].legend === '' ? undefined : this.graph.gdes[i].legend.substr(2)),
481 stack: (this.graph.gdes[i].stack === false ? undefined : this.graph.gdes[i].stack) });
482 break;
483// AREA:value[#color][:[legend][:STACK]]
484 case RrdGraphDesc.GF_AREA:
485 this.json.gdes.push({
486 type: 'AREA',
487 value: this.graph.gdes[i].vname,
488 color: this.graph.gdes[i].col,
489 legend: (this.graph.gdes[i].legend === '' ? undefined : this.graph.gdes[i].legend.substr(2)),
490 stack: (this.graph.gdes[i].stack === false ? undefined : this.graph.gdes[i].stack) });
491 break;
492// TICK:vname#rrggbb[aa][:fraction[:legend]]
493 case RrdGraphDesc.GF_TICK:
494 this.json.gdes.push({
495 type: 'TICK',
496 vname: this.graph.gdes[i].vname,
497 color: this.graph.gdes[i].col,
498 fraction: this.graph.gdes[i].yrule,
499 legend: (this.graph.gdes[i].legend === '' ? undefined : this.graph.gdes[i].legend.substr(2)) });
500 break;
501// HRULE:value#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
502 case RrdGraphDesc.GF_HRULE:
503 this.json.gdes.push({
504 type: 'HRULE',
505 value: this.graph.gdes[i].yrule,
506 color: this.graph.gdes[i].col,
507 legend: (this.graph.gdes[i].legend === '' ? undefined : this.graph.gdes[i].legend.substr(2)) });
508 break;
509// VRULE:time#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]]
510 case RrdGraphDesc.GF_VRULE:
511 this.json.gdes.push({
512 type: 'VRULE',
513 time: this.graph.gdes[i].xrule,
514 color: this.graph.gdes[i].col,
515 legend: (this.graph.gdes[i].legend === '' ? undefined : this.graph.gdes[i].legend.substr(2)) });
516 break;
517// COMMENT:text
518 case RrdGraphDesc.GF_COMMENT:
519 this.json.gdes.push({
520 type: 'COMMENT',
521 legend: this.graph.gdes[i].legend});
522 break;
523// TEXTALIGN:{left|right|justified|center}
524 case RrdGraphDesc.GF_TEXTALIGN:
525 var align = '';
526 switch (this.graph.gdes[i].txtalign) {
527 case RrdGraphDesc.TXA_LEFT:
528 align = 'left';
529 break
530 case RrdGraphDesc.TXA_RIGHT:
531 align = 'right';
532 break
533 case RrdGraphDesc.TXA_JUSTIFIED:
534 align = 'justified';
535 break
536 case RrdGraphDesc.TXA_CENTER:
537 align = 'center';
538 break
539 }
540
541 this.json.gdes.push({
542 type: 'TEXTALIGN',
543 align: align });
544 break;
545// DEF:<vname>=<rrdfile>:<ds-name>:<CF>[:step=<step>][:start=<time>][:end=<time>][:reduce=<CF>]
546 case RrdGraphDesc.GF_DEF:
547 this.json.gdes.push({
548 type: 'DEF',
549 vname: this.graph.gdes[i].vname,
550 rrdfile: this.graph.gdes[i].rrd,
551 name: this.graph.gdes[i].ds_nam,
552 cf: RrdGraphDesc.cf2str(this.graph.gdes[i].cf),
553// step: this.graph.gdes[i].step,
554 step: undefined,
555 start: undefined,
556 // start: this.graph.gdes[i].start, // FIXME
557 end: undefined,
558 // end: this.graph.gdes[i].end, // FIXME
559 // reduce: RrdGraphDesc.cf2str(this.graph.gdes[i].cf_reduce)
560 reduce: undefined
561 });
562
563 break;
564// CDEF:vname=RPN expression
565 case RrdGraphDesc.GF_CDEF:
566 this.json.gdes.push({
567 type: 'CDEF',
568 vname: this.graph.gdes[i].vname,
569 rpn: this.graph.gdes[i].rpnp.rpnexpr});
570 break;
571// VDEF:vname=RPN expression
572 case RrdGraphDesc.GF_VDEF:
573 this.json.gdes.push({
574 type: 'VDEF',
575 vname: this.graph.gdes[i].vname,
576 rpn: this.graph.gdes[this.graph.gdes[i].vidx].vname+','+this.graph.gdes[i].vf.expr});
577 break;
578// SHIFT:vname:offset
579 case RrdGraphDesc.GF_SHIFT:
580 this.json.gdes.push({
581 type: 'VDEF',
582 vname: this.graph.gdes[i].vname,
583 offset: this.shidx });
584 break;
585 }
586 }
587 }
588};
diff --git a/js/RrdRpn.js b/js/RrdRpn.js
new file mode 100644
index 0000000..cbb5e8f
--- /dev/null
+++ b/js/RrdRpn.js
@@ -0,0 +1,616 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 * RRDtool 1.4.5 Copyright by Tobi Oetiker, 1997-2010
18 *
19 * Convert to javascript: Manuel Sanmartin <manuel.luis at gmail.com>
20 **/
21
22"use strict";
23
24/**
25 * RrdRpnError
26 * @constructor
27 */
28var RrdRpnError = function (message)
29{
30 this.prototype = Error.prototype;
31 this.name = "RrdRpnError";
32 this.message = (message) ? message : "RPN stack underflow";
33};
34
35/**
36 * RrdRpn
37 * @constructor
38 */
39var RrdRpn = function (str_expr, gdes) /* parser */
40{
41 var steps = -1;
42 var expr;
43 var exprs = str_expr.split(',');
44
45 this.rpnexpr = str_expr;
46 this.rpnp = [];
47 this.rpnstack = null;
48
49 for(var i=0, len=exprs.length; i < len; i++) {
50 expr=exprs[i].toUpperCase();
51
52 steps++;
53 this.rpnp[steps] = {};
54
55 if (!isNaN(expr)) {
56 this.rpnp[steps].op = RrdRpn.OP_NUMBER;
57 this.rpnp[steps].val = parseFloat(expr);
58 }
59 else if (expr === '+') this.rpnp[steps].op = RrdRpn.OP_ADD;
60 else if (expr === '-') this.rpnp[steps].op = RrdRpn.OP_SUB;
61 else if (expr === '*') this.rpnp[steps].op = RrdRpn.OP_MUL;
62 else if (expr === '/') this.rpnp[steps].op = RrdRpn.OP_DIV;
63 else if (expr === '%') this.rpnp[steps].op = RrdRpn.OP_MOD;
64 else if (expr === 'SIN') this.rpnp[steps].op = RrdRpn.OP_SIN;
65 else if (expr === 'COS') this.rpnp[steps].op = RrdRpn.OP_COS;
66 else if (expr === 'LOG') this.rpnp[steps].op = RrdRpn.OP_LOG;
67 else if (expr === 'FLOOR') this.rpnp[steps].op = RrdRpn.OP_FLOOR;
68 else if (expr === 'CEIL') this.rpnp[steps].op = RrdRpn.OP_CEIL;
69 else if (expr === 'EXP') this.rpnp[steps].op = RrdRpn.OP_EXP;
70 else if (expr === 'DUP') this.rpnp[steps].op = RrdRpn.OP_DUP;
71 else if (expr === 'EXC') this.rpnp[steps].op = RrdRpn.OP_EXC;
72 else if (expr === 'POP') this.rpnp[steps].op = RrdRpn.OP_POP;
73 else if (expr === 'LTIME') this.rpnp[steps].op = RrdRpn.OP_LTIME;
74 else if (expr === 'LT') this.rpnp[steps].op = RrdRpn.OP_LT;
75 else if (expr === 'LE') this.rpnp[steps].op = RrdRpn.OP_LE;
76 else if (expr === 'GT') this.rpnp[steps].op = RrdRpn.OP_GT;
77 else if (expr === 'GE') this.rpnp[steps].op = RrdRpn.OP_GE;
78 else if (expr === 'EQ') this.rpnp[steps].op = RrdRpn.OP_EQ;
79 else if (expr === 'IF') this.rpnp[steps].op = RrdRpn.OP_IF;
80 else if (expr === 'MIN') this.rpnp[steps].op = RrdRpn.OP_MIN;
81 else if (expr === 'MAX') this.rpnp[steps].op = RrdRpn.OP_MAX;
82 else if (expr === 'LIMIT') this.rpnp[steps].op = RrdRpn.OP_LIMIT;
83 else if (expr === 'UNKN') this.rpnp[steps].op = RrdRpn.OP_UNKN;
84 else if (expr === 'UN') this.rpnp[steps].op = RrdRpn.OP_UN;
85 else if (expr === 'NEGINF') this.rpnp[steps].op = RrdRpn.OP_NEGINF;
86 else if (expr === 'NE') this.rpnp[steps].op = RrdRpn.OP_NE;
87 else if (expr === 'COUNT') this.rpnp[steps].op = RrdRpn.OP_COUNT;
88 else if (/PREV\([-_A-Za-z0-9]+\)/.test(expr)) {
89 var match = exprs[i].match(/PREV\(([-_A-Za-z0-9]+)\)/i);
90 if (match.length == 2) {
91 this.rpnp[steps].op = RrdRpn.OP_PREV_OTHER;
92 this.rpnp[steps].ptr = this.find_var(gdes, match[1]); // FIXME if -1
93 }
94 }
95 else if (expr === 'PREV') this.rpnp[steps].op = RrdRpn.OP_PREV;
96 else if (expr === 'INF') this.rpnp[steps].op = RrdRpn.OP_INF;
97 else if (expr === 'ISINF') this.rpnp[steps].op = RrdRpn.OP_ISINF;
98 else if (expr === 'NOW') this.rpnp[steps].op = RrdRpn.OP_NOW;
99 else if (expr === 'TIME') this.rpnp[steps].op = RrdRpn.OP_TIME;
100 else if (expr === 'ATAN2') this.rpnp[steps].op = RrdRpn.OP_ATAN2;
101 else if (expr === 'ATAN') this.rpnp[steps].op = RrdRpn.OP_ATAN;
102 else if (expr === 'SQRT') this.rpnp[steps].op = RrdRpn.OP_SQRT;
103 else if (expr === 'SORT') this.rpnp[steps].op = RrdRpn.OP_SORT;
104 else if (expr === 'REV') this.rpnp[steps].op = RrdRpn.OP_REV;
105 else if (expr === 'TREND') this.rpnp[steps].op = RrdRpn.OP_TREND;
106 else if (expr === 'TRENDNAN') this.rpnp[steps].op = RrdRpn.OP_TRENDNAN;
107 else if (expr === 'PREDICT') this.rpnp[steps].op = RrdRpn.OP_PREDICT;
108 else if (expr === 'PREDICTSIGMA') this.rpnp[steps].op = RrdRpn.OP_PREDICTSIGMA;
109 else if (expr === 'RAD2DEG') this.rpnp[steps].op = RrdRpn.OP_RAD2DEG;
110 else if (expr === 'DEG2RAD') this.rpnp[steps].op = RrdRpn.OP_DEG2RAD;
111 else if (expr === 'AVG') this.rpnp[steps].op = RrdRpn.OP_AVG;
112 else if (expr === 'ABS') this.rpnp[steps].op = RrdRpn.OP_ABS;
113 else if (expr === 'ADDNAN') this.rpnp[steps].op = RrdRpn.OP_ADDNAN;
114 else if (/[-_A-Za-z0-9]+/.test(expr)) {
115 this.rpnp[steps].ptr = this.find_var(gdes, exprs[i]); // FIXME if -1
116 this.rpnp[steps].op = RrdRpn.OP_VARIABLE;
117 } else {
118 return;
119 }
120 }
121 this.rpnp[steps + 1] = {};
122 this.rpnp[steps + 1].op = RrdRpn.OP_END;
123};
124
125RrdRpn.OP_NUMBER= 0;
126RrdRpn.OP_VARIABLE = 1;
127RrdRpn.OP_INF = 2;
128RrdRpn.OP_PREV = 3;
129RrdRpn.OP_NEGINF = 4;
130RrdRpn.OP_UNKN = 5;
131RrdRpn.OP_NOW = 6;
132RrdRpn.OP_TIME = 7;
133RrdRpn.OP_ADD = 8;
134RrdRpn.OP_MOD = 9;
135RrdRpn.OP_SUB = 10;
136RrdRpn.OP_MUL = 11;
137RrdRpn.OP_DIV = 12;
138RrdRpn.OP_SIN = 13;
139RrdRpn.OP_DUP = 14;
140RrdRpn.OP_EXC = 15;
141RrdRpn.OP_POP = 16;
142RrdRpn.OP_COS = 17;
143RrdRpn.OP_LOG = 18;
144RrdRpn.OP_EXP = 19;
145RrdRpn.OP_LT = 20;
146RrdRpn.OP_LE = 21;
147RrdRpn.OP_GT = 22;
148RrdRpn.OP_GE = 23;
149RrdRpn.OP_EQ = 24;
150RrdRpn.OP_IF = 25;
151RrdRpn.OP_MIN = 26;
152RrdRpn.OP_MAX = 27;
153RrdRpn.OP_LIMIT = 28;
154RrdRpn.OP_FLOOR = 29;
155RrdRpn.OP_CEIL = 30;
156RrdRpn.OP_UN = 31;
157RrdRpn.OP_END = 32;
158RrdRpn.OP_LTIME = 33;
159RrdRpn.OP_NE = 34;
160RrdRpn.OP_ISINF = 35;
161RrdRpn.OP_PREV_OTHER = 36;
162RrdRpn.OP_COUNT = 37;
163RrdRpn.OP_ATAN = 38;
164RrdRpn.OP_SQRT = 39;
165RrdRpn.OP_SORT = 40;
166RrdRpn.OP_REV = 41;
167RrdRpn.OP_TREND = 42;
168RrdRpn.OP_TRENDNAN = 43;
169RrdRpn.OP_ATAN2 = 44;
170RrdRpn.OP_RAD2DEG = 45;
171RrdRpn.OP_DEG2RAD = 46;
172RrdRpn.OP_PREDICT = 47;
173RrdRpn.OP_PREDICTSIGMA = 48;
174RrdRpn.OP_AVG = 49;
175RrdRpn.OP_ABS = 50;
176RrdRpn.OP_ADDNAN = 51 ;
177
178RrdRpn.prototype.find_var = function(gdes, key)
179{
180 for (var ii = 0, gdes_c = gdes.length; ii < gdes_c; ii++) {
181 if ((gdes[ii].gf == RrdGraphDesc.GF_DEF ||
182 gdes[ii].gf == RrdGraphDesc.GF_VDEF ||
183 gdes[ii].gf == RrdGraphDesc.GF_CDEF)
184 && gdes[ii].vname == key) {
185 return ii;
186 }
187 }
188 return -1;
189};
190
191RrdRpn.prototype.compare_double = function(x, y)
192{
193 var diff = x - y;
194 return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
195};
196
197RrdRpn.prototype.fmod = function (x, y)
198{
199 // http://kevin.vanzonneveld.net
200 // + original by: Onno Marsman
201 // + input by: Brett Zamir (http://brett-zamir.me)
202 // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
203 // * example 1: fmod(5.7, 1.3);
204 // * returns 1: 0.5
205 var tmp, tmp2, p = 0,
206 pY = 0,
207 l = 0.0,
208 l2 = 0.0;
209
210 tmp = x.toExponential().match(/^.\.?(.*)e(.+)$/);
211 p = parseInt(tmp[2], 10) - (tmp[1] + '').length;
212 tmp = y.toExponential().match(/^.\.?(.*)e(.+)$/);
213 pY = parseInt(tmp[2], 10) - (tmp[1] + '').length;
214
215 if (pY > p) p = pY;
216
217 tmp2 = (x % y);
218
219 if (p < -100 || p > 20) {
220 l = Math.round(Math.log(tmp2) / Math.log(10));
221 l2 = Math.pow(10, l);
222 return (tmp2 / l2).toFixed(l - p) * l2;
223 } else {
224 return parseFloat(tmp2.toFixed(-p));
225 }
226};
227
228RrdRpn.prototype.calc = function (data_idx, output, output_idx)
229{
230 var stptr = -1;
231
232 this.rpnstack = [];
233
234 for (var rpi = 0; this.rpnp[rpi].op != RrdRpn.OP_END; rpi++) {
235 switch (this.rpnp[rpi].op) {
236 case RrdRpn.OP_NUMBER:
237 this.rpnstack[++stptr] = this.rpnp[rpi].val;
238 break;
239 case RrdRpn.OP_VARIABLE:
240 case RrdRpn.OP_PREV_OTHER:
241 if (this.rpnp[rpi].ds_cnt == 0) {
242 throw new RrdRpnError("VDEF made it into rpn_calc... aborting");
243 } else {
244 if (this.rpnp[rpi].op == RrdRpn.OP_VARIABLE) {
245 this.rpnstack[++stptr] = this.rpnp[rpi].data[this.rpnp[rpi].pdata];
246 } else {
247 if ((output_idx) <= 0) this.rpnstack[++stptr] = Number.NaN;
248 else this.rpnstack[++stptr] = this.rpnp[rpi].data[this.rpnp[rpi].pdata - this.rpnp[rpi].ds_cnt];
249 }
250 if (data_idx % this.rpnp[rpi].step == 0) {
251 this.rpnp[rpi].pdata += this.rpnp[rpi].ds_cnt;
252 }
253 }
254 break;
255 case RrdRpn.OP_COUNT:
256 this.rpnstack[++stptr] = (output_idx + 1); /* Note: Counter starts at 1 */
257 break;
258 case RrdRpn.OP_PREV:
259 if ((output_idx) <= 0) this.rpnstack[++stptr] = Number.NaN;
260 else this.rpnstack[++stptr] = output[output_idx - 1];
261 break;
262 case RrdRpn.OP_UNKN:
263 this.rpnstack[++stptr] = Number.NaN;
264 break;
265 case RrdRpn.OP_INF:
266 this.rpnstack[++stptr] = Infinity;
267 break;
268 case RrdRpn.OP_NEGINF:
269 this.rpnstack[++stptr] = -Infinity;
270 break;
271 case RrdRpn.OP_NOW:
272 this.rpnstack[++stptr] = Math.round((new Date()).getTime() / 1000);
273 break;
274 case RrdRpn.OP_TIME:
275 this.rpnstack[++stptr] = data_idx;
276 break;
277 case RrdRpn.OP_LTIME:
278 var date = new Date(data_idx*1000); // FIXME XXX
279 this.rpnstack[++stptr] = date.getTimezoneOffset() * 60 + data_idx;
280 break;
281 case RrdRpn.OP_ADD:
282 if(stptr < 1) throw new RrdRpnError();
283 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] + this.rpnstack[stptr];
284 stptr--;
285 break;
286 case RrdRpn.OP_ADDNAN:
287 if(stptr < 1) throw new RrdRpnError();
288 if (isNaN(this.rpnstack[stptr - 1])) {
289 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
290 } else if (isNaN(this.rpnstack[stptr])) {
291 /* NOOP */
292 /* this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1]; */
293 } else {
294 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] + this.rpnstack[stptr];
295 }
296 stptr--;
297 break;
298 case RrdRpn.OP_SUB:
299 if(stptr < 1) throw new RrdRpnError();
300 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] - this.rpnstack[stptr];
301 stptr--;
302 break;
303 case RrdRpn.OP_MUL:
304 if(stptr < 1) throw new RrdRpnError();
305 this.rpnstack[stptr - 1] = (this.rpnstack[stptr - 1]) * (this.rpnstack[stptr]);
306 stptr--;
307 break;
308 case RrdRpn.OP_DIV:
309 if(stptr < 1) throw new RrdRpnError();
310 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] / this.rpnstack[stptr];
311 stptr--;
312 break;
313 case RrdRpn.OP_MOD:
314 if(stptr < 1) throw new RrdRpnError();
315 this.rpnstack[stptr - 1] = this.fmod(this.rpnstack[stptr - 1] , this.rpnstack[stptr]);
316 stptr--;
317 break;
318 case RrdRpn.OP_SIN:
319 if(stptr < 0) throw new RrdRpnError();
320 this.rpnstack[stptr] = Math.sin(this.rpnstack[stptr]);
321 break;
322 case RrdRpn.OP_ATAN:
323 if(stptr < 0) throw new RrdRpnError();
324 this.rpnstack[stptr] = Math.atan(this.rpnstack[stptr]);
325 break;
326 case RrdRpn.OP_RAD2DEG:
327 if(stptr < 0) throw new RrdRpnError();
328 this.rpnstack[stptr] = 57.29577951 * this.rpnstack[stptr];
329 break;
330 case RrdRpn.OP_DEG2RAD:
331 if(stptr < 0) throw new RrdRpnError();
332 this.rpnstack[stptr] = 0.0174532952 * this.rpnstack[stptr];
333 break;
334 case RrdRpn.OP_ATAN2:
335 if(stptr < 1) throw new RrdRpnError();
336 this.rpnstack[stptr - 1] = Math.atan2(this.rpnstack[stptr - 1], this.rpnstack[stptr]);
337 stptr--;
338 break;
339 case RrdRpn.OP_COS:
340 if(stptr < 0) throw new RrdRpnError();
341 this.rpnstack[stptr] = Math.cos(this.rpnstack[stptr]);
342 break;
343 case RrdRpn.OP_CEIL:
344 if(stptr < 0) throw new RrdRpnError();
345 this.rpnstack[stptr] = Math.ceil(this.rpnstack[stptr]);
346 break;
347 case RrdRpn.OP_FLOOR:
348 if(stptr < 0) throw new RrdRpnError();
349 this.rpnstack[stptr] = Math.floor(this.rpnstack[stptr]);
350 break;
351 case RrdRpn.OP_LOG:
352 if(stptr < 0) throw new RrdRpnError();
353 this.rpnstack[stptr] = Math.log(this.rpnstack[stptr]);
354 break;
355 case RrdRpn.OP_DUP:
356 if(stptr < 0) throw new RrdRpnError();
357 this.rpnstack[stptr + 1] = this.rpnstack[stptr];
358 stptr++;
359 break;
360 case RrdRpn.OP_POP:
361 if(stptr < 0) throw new RrdRpnError();
362 stptr--;
363 break;
364 case RrdRpn.OP_EXC:
365 if(stptr < 1) throw new RrdRpnError(); {
366 var dummy = this.rpnstack[stptr];
367 this.rpnstack[stptr] = this.rpnstack[stptr - 1];
368 this.rpnstack[stptr - 1] = dummy;
369 }
370 break;
371 case RrdRpn.OP_EXP:
372 if(stptr < 0) throw new RrdRpnError();
373 this.rpnstack[stptr] = Math.exp(this.rpnstack[stptr]);
374 break;
375 case RrdRpn.OP_LT:
376 if(stptr < 1) throw new RrdRpnError();
377 if (isNaN(this.rpnstack[stptr - 1])) {
378 } else if (isNaN(this.rpnstack[stptr])) {
379 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
380 } else {
381 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] < this.rpnstack[stptr] ? 1.0 : 0.0;
382 }
383 stptr--;
384 break;
385 case RrdRpn.OP_LE:
386 if(stptr < 1) throw new RrdRpnError();
387 if (isNaN(this.rpnstack[stptr - 1])) {
388 } else if (isNaN(this.rpnstack[stptr])) {
389 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
390 } else {
391 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] <= this.rpnstack[stptr] ? 1.0 : 0.0;
392 }
393 stptr--;
394 break;
395 case RrdRpn.OP_GT:
396 if(stptr < 1) throw new RrdRpnError();
397 if (isNaN(this.rpnstack[stptr - 1])) {
398 } else if (isNaN(this.rpnstack[stptr])) {
399 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
400 } else {
401 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] > this.rpnstack[stptr] ? 1.0 : 0.0;
402 }
403 stptr--;
404 break;
405 case RrdRpn.OP_GE:
406 if(stptr < 1) throw new RrdRpnError();
407 if (isNaN(this.rpnstack[stptr - 1])) {
408 } else if (isNaN(this.rpnstack[stptr])) {
409 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
410 } else {
411 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] >= this.rpnstack[stptr] ? 1.0 : 0.0;
412 }
413 stptr--;
414 break;
415 case RrdRpn.OP_NE:
416 if(stptr < 1) throw new RrdRpnError();
417 if (isNaN(this.rpnstack[stptr - 1])) {
418 } else if (isNaN(this.rpnstack[stptr])) {
419 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
420 } else {
421 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] == this.rpnstack[stptr] ? 0.0 : 1.0;
422 }
423 stptr--;
424 break;
425 case RrdRpn.OP_EQ:
426 if(stptr < 1) throw new RrdRpnError();
427 if (isNaN(this.rpnstack[stptr - 1])) {
428 } else if (isNaN(this.rpnstack[stptr])) {
429 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
430 } else {
431 this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] == this.rpnstack[stptr] ? 1.0 : 0.0;
432 }
433 stptr--;
434 break;
435 case RrdRpn.OP_IF:
436 if(stptr < 2) throw new RrdRpnError();
437 this.rpnstack[stptr - 2] = (isNaN(this.rpnstack[stptr - 2]) || this.rpnstack[stptr - 2] == 0.0) ? this.rpnstack[stptr] : this.rpnstack[stptr - 1];
438 stptr--;
439 stptr--;
440 break;
441 case RrdRpn.OP_MIN:
442 if(stptr < 1) throw new RrdRpnError();
443 if (isNaN(this.rpnstack[stptr - 1])) {
444 } else if (isNaN(this.rpnstack[stptr])) {
445 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
446 } else if (this.rpnstack[stptr - 1] > this.rpnstack[stptr]) {
447 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
448 }
449 stptr--;
450 break;
451 case RrdRpn.OP_MAX:
452 if(stptr < 1) throw new RrdRpnError();
453 if (isNaN(this.rpnstack[stptr - 1])) {
454 } else if (isNaN(this.rpnstack[stptr])) {
455 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
456 } else if (this.rpnstack[stptr - 1] < this.rpnstack[stptr]) {
457 this.rpnstack[stptr - 1] = this.rpnstack[stptr];
458 }
459 stptr--;
460 break;
461 case RrdRpn.OP_LIMIT:
462 if(stptr < 2) throw new RrdRpnError();
463 if (isNaN(this.rpnstack[stptr - 2])) {
464 } else if (isNaN(this.rpnstack[stptr - 1])) {
465 this.rpnstack[stptr - 2] = this.rpnstack[stptr - 1];
466 } else if (isNaN(this.rpnstack[stptr])) {
467 this.rpnstack[stptr - 2] = this.rpnstack[stptr];
468 } else if (this.rpnstack[stptr - 2] < this.rpnstack[stptr - 1]) {
469 this.rpnstack[stptr - 2] = Number.NaN;
470 } else if (this.rpnstack[stptr - 2] > this.rpnstack[stptr]) {
471 this.rpnstack[stptr - 2] = Number.NaN;
472 }
473 stptr -= 2;
474 break;
475 case RrdRpn.OP_UN:
476 if(stptr < 0) throw new RrdRpnError();
477 this.rpnstack[stptr] = isNaN(this.rpnstack[stptr]) ? 1.0 : 0.0;
478 break;
479 case RrdRpn.OP_ISINF:
480 if(stptr < 0) throw new RrdRpnError();
481 this.rpnstack[stptr] = isInfinite(this.rpnstack[stptr]) ? 1.0 : 0.0;
482 break;
483 case RrdRpn.OP_SQRT:
484 if(stptr < 0) throw new RrdRpnError();
485 this.rpnstack[stptr] = Math.sqrt(this.rpnstack[stptr]);
486 break;
487 case RrdRpn.OP_SORT:
488 if(stptr < 0) throw new RrdRpnError();
489 var spn = this.rpnstack[stptr--];
490 if(stptr < spn - 1) throw new RrdRpnError();
491 var array = this.rpnstack.slice(stptr - spn + 1, stptr +1);
492 array.sort(this.compare_double);
493 for (var i=stptr - spn + 1, ii=0; i < (stptr +1) ; i++, ii++)
494 this.rpnstack[i] = array[ii];
495 // qsort(this.rpnstack + stptr - spn + 1, spn, sizeof(double), rpn_compare_double);
496 break;
497 case RrdRpn.OP_REV:
498 if(stptr < 0) throw new RrdRpnError();
499 var spn = this.rpnstack[stptr--];
500 if(stptr < spn - 1) throw new RrdRpnError();
501 var array = this.rpnstack.slice(stptr - spn + 1, stptr +1);
502 array.reverse();
503 for (var i=stptr - spn + 1, ii=0; i < (stptr +1) ; i++, ii++)
504 this.rpnstack[i] = array[ii];
505 break;
506 case RrdRpn.OP_PREDICT:
507 case RrdRpn.OP_PREDICTSIGMA:
508 if(stptr < 2) throw new RrdRpnError();
509 var locstepsize = this.rpnstack[--stptr];
510 var shifts = this.rpnstack[--stptr];
511 if(stptr < shifts) throw new RrdRpnError();
512 if (shifts<0) stptr--;
513 else stptr-=shifts;
514 var val=Number.NaN;
515 var dsstep = this.rpnp[rpi - 1].step;
516 var dscount = this.rpnp[rpi - 1].ds_cnt;
517 var locstep = Math.ceil(locstepsize/dsstep);
518 var sum = 0;
519 var sum2 = 0;
520 var count = 0;
521 /* now loop for each position */
522 var doshifts=shifts;
523 if (shifts<0) doshifts=-shifts;
524 for(var loop=0;loop<doshifts;loop++) {
525 var shiftstep=1;
526 if (shifts<0) shiftstep = loop*this.rpnstack[stptr];
527 else shiftstep = this.rpnstack[stptr+loop];
528 if(shiftstep <0) {
529 throw new RrdRpnError("negative shift step not allowed: "+shiftstep);
530 }
531 shiftstep=Math.ceil(shiftstep/dsstep);
532 for(var i=0;i<=locstep;i++) {
533 var offset=shiftstep+i;
534 if ((offset>=0)&&(offset<output_idx)) {
535 val = this.rpnp[rpi - 1].data[-dscount * offset];
536 if (! isNaN(val)) {
537 sum+=val;
538 sum2+=val*val;
539 count++;
540 }
541 }
542 }
543 }
544 val=Number.NaN;
545 if (this.rpnp[rpi].op == RrdRpn.OP_PREDICT) {
546 if (count>0) val = sum/count;
547 } else {
548 if (count>1) {
549 val=count*sum2-sum*sum;
550 if (val<0) {
551 val=Number.NaN;
552 } else {
553 val=Math.sqrt(val/(count*(count-1.0)));
554 }
555 }
556 }
557 this.rpnstack[stptr] = val;
558 break;
559 case RrdRpn.OP_TREND:
560 case RrdRpn.OP_TRENDNAN:
561 if(stptr < 1) throw new RrdRpnError();
562 if ((rpi < 2) || (this.rpnp[rpi - 2].op != RrdRpn.OP_VARIABLE)) {
563 throw new RrdRpnError("malformed trend arguments");
564 } else {
565 var dur = this.rpnstack[stptr];
566 var step = this.rpnp[rpi - 2].step;
567
568 if (output_idx + 1 >= Math.ceil(dur / step)) {
569 var ignorenan = (this.rpnp[rpi].op == RrdRpn.OP_TREND);
570 var accum = 0.0;
571 var i = 0;
572 var count = 0;
573
574 do {
575 var val = this.rpnp[rpi - 2].data[this.rpnp[rpi - 2].ds_cnt * i--];
576 if (ignorenan || !isNaN(val)) {
577 accum += val;
578 ++count;
579 }
580 dur -= step;
581 } while (dur > 0);
582
583 this.rpnstack[--stptr] = (count == 0) ? Number.NaN : (accum / count);
584 } else this.rpnstack[--stptr] = Number.NaN;
585 }
586 break;
587 case RrdRpn.OP_AVG:
588 if(stptr < 0) throw new RrdRpnError();
589 var i = this.rpnstack[stptr--];
590 var sum = 0;
591 var count = 0;
592
593 if(stptr < i - 1) throw new RrdRpnError();
594 while (i > 0) {
595 var val = this.rpnstack[stptr--];
596 i--;
597 if (isNaN(val)) continue;
598 count++;
599 sum += val;
600 }
601 if (count > 0) this.rpnstack[++stptr] = sum / count;
602 else this.rpnstack[++stptr] = Number.NaN;
603 break;
604 case RrdRpn.OP_ABS:
605 if(stptr < 0) throw new RrdRpnError();
606 this.rpnstack[stptr] = fabs(this.rpnstack[stptr]);
607 break;
608 case RrdRpn.OP_END:
609 break;
610 }
611 }
612 if (stptr != 0) throw new RrdRpnError("RPN final stack size != 1");
613 output[output_idx] = this.rpnstack[0];
614 return 0;
615};
616
diff --git a/js/RrdTime.js b/js/RrdTime.js
new file mode 100644
index 0000000..95ea0ae
--- /dev/null
+++ b/js/RrdTime.js
@@ -0,0 +1,621 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 * RRDtool 1.4.5 Copyright by Tobi Oetiker, 1997-2010
18 *
19 * Convert to javascript: Manuel Sanmartin <manuel.luis at gmail.com>
20 **/
21
22"use strict";
23
24/**
25 * RrdTimeError
26 * @constructor
27 */
28var RrdTimeError = function (message)
29{
30 this.prototype = Error.prototype;
31 this.name = "RrdTimeError";
32 this.message = (message) ? message : "Error";
33};
34
35/**
36 * RrdTime
37 * @constructor
38 */
39var RrdTime = function(tspec) /* parser */
40{
41 var date = new Date();
42 var hr = 0;
43
44 this.tspec = tspec;
45
46 this.tokens = (tspec+'').match(/[0-9]+|[A-Za-z]+|[:.+-\/]/g);
47 this.toklen = this.tokens.length;
48 this.tokidx = 0;
49
50 this.token = null;
51 this.tokid = 0;
52
53 this.specials = RrdTime.VARIOUSWORDS;
54
55 /* establish the default time reference */
56 this.type = RrdTime.ABSOLUTE_TIME;
57 this.offset = 0;
58 this.tm_sec = date.getSeconds();
59 this.tm_min = date.getMinutes();
60 this.tm_hour = date.getHours();
61 this.tm_mday = date.getDate();
62 this.tm_mon = date.getMonth();
63 this.tm_year = date.getFullYear()-1900;
64 this.tm_wday = date.getDay();
65
66 this.gettok();
67 switch (this.tokid) {
68 case RrdTime.PLUS:
69 case RrdTime.MINUS:
70 break; /* jump to OFFSET-SPEC part */
71 case RrdTime.EPOCH:
72 this.type = RrdTime.RELATIVE_TO_EPOCH;
73 case RrdTime.START:
74 case RrdTime.END:
75 if (this.tokid === RrdTime.EPOCH)
76 this.type = RrdTime.RELATIVE_TO_START_TIME;
77 else
78 this.type = RrdTime.RELATIVE_TO_END_TIME;
79 this.tm_sec = 0;
80 this.tm_min = 0;
81 this.tm_hour = 0;
82 this.tm_mday = 0;
83 this.tm_mon = 0;
84 this.tm_year = 0;
85 case RrdTime.NOW:
86 var time_reference = this.tokid;
87 this.gettok();
88 if (this.tokid == RrdTime.PLUS || this.tokid == RrdTime.MINUS)
89 break;
90 if (time_reference != RrdTime.NOW) {
91 throw new RrdTimeError("'start' or 'end' MUST be followed by +|- offset");
92 } else if (this.tokid != RrdTime.EOF) {
93 throw new RrdTimeError("if 'now' is followed by a token it must be +|- offset");
94 }
95 break;
96 case RrdTime.NUMBER: /* Only absolute time specifications below */
97 var hour_sv = this.tm_hour;
98 var year_sv = this.tm_year;
99 this.tm_hour = 30;
100 this.tm_year = 30000;
101 this.tod();
102 this.day();
103 if (this.tm_hour == 30 && this.tm_year != 30000)
104 this.tod();
105 if (this.tm_hour == 30)
106 this.tm_hour = hour_sv;
107 if (this.tm_year == 30000)
108 this.tm_year = year_sv;
109 break;
110 case RrdTime.JAN:
111 case RrdTime.FEB:
112 case RrdTime.MAR:
113 case RrdTime.APR:
114 case RrdTime.MAY:
115 case RrdTime.JUN:
116 case RrdTime.JUL:
117 case RrdTime.AUG:
118 case RrdTime.SEP:
119 case RrdTime.OCT:
120 case RrdTime.NOV:
121 case RrdTime.DEC:
122 this.day();
123 if (this.tokid != RrdTime.NUMBER)
124 break;
125 this.tod();
126 break;
127 case RrdTime.TEATIME:
128 hr += 4;
129 case RrdTime.NOON:
130 hr += 12;
131 case RrdTime.MIDNIGHT:
132 this.tm_hour = hr;
133 this.tm_min = 0;
134 this.tm_sec = 0;
135 this.gettok();
136 this.day();
137 break;
138 default:
139 throw new RrdTimeError("unparsable time: "+this.token+" "+this.sct);
140 break;
141 } /* ugly case statement */
142
143 /*
144 * the OFFSET-SPEC part
145 * (NOTE, the sc_tokid was prefetched for us by the previous code)
146 */
147 if (this.tokid == RrdTime.PLUS || this.tokid == RrdTime.MINUS) {
148 this.specials = RrdTime.TIMEMULTIPLIERS; /* switch special words context */
149 while (this.tokid == RrdTime.PLUS || this.tokid == RrdTime.MINUS || this.tokid == RrdTime.NUMBER) {
150 if (this.tokid == RrdTime.NUMBER) {
151 this.plus_minus(-1);
152 } else {
153 this.plus_minus(this.tokid);
154 }
155 this.gettok(); /* We will get EOF eventually but that's OK, since token() will return us as many EOFs as needed */
156 }
157 }
158
159 /* now we should be at EOF */
160 if (this.tokid != RrdTime.EOF)
161 throw new RrdTimeError("unparsable trailing text: '..."+this.token+"'");
162// if (this.type == RrdTime.ABSOLUTE_TIME)
163// if (mktime(&ptv->tm) == -1) // FIXME ??
164// panic(e("the specified time is incorrect (out of range?)"));
165};
166
167RrdTime.EOF = -1;
168RrdTime.MIDNIGHT = 0;
169RrdTime.NOON = 1;
170RrdTime.TEATIME = 2;
171RrdTime.PM = 3;
172RrdTime.AM = 4;
173RrdTime.YESTERDAY = 5;
174RrdTime.TODAY = 6;
175RrdTime.TOMORROW = 7;
176RrdTime.NOW = 8;
177RrdTime.START = 9;
178RrdTime.END = 10;
179RrdTime.EPOCH = 11;
180RrdTime.SECONDS = 12;
181RrdTime.MINUTES = 13;
182RrdTime.HOURS = 14;
183RrdTime.DAYS = 15;
184RrdTime.WEEKS = 16;
185RrdTime.MONTHS = 17;
186RrdTime.YEARS = 18;
187RrdTime.MONTHS_MINUTES = 19;
188RrdTime.NUMBER = 20;
189RrdTime.PLUS = 21;
190RrdTime.MINUS = 22;
191RrdTime.DOT = 23;
192RrdTime.COLON = 24;
193RrdTime.SLASH = 25;
194RrdTime.ID = 26;
195RrdTime.JUNK = 27;
196RrdTime.JAN = 28;
197RrdTime.FEB = 29;
198RrdTime.MAR = 30;
199RrdTime.APR = 31;
200RrdTime.MAY = 32;
201RrdTime.JUN = 33;
202RrdTime.JUL = 34;
203RrdTime.AUG = 35;
204RrdTime.SEP = 36;
205RrdTime.OCT = 37;
206RrdTime.NOV = 38;
207RrdTime.DEC = 39;
208RrdTime.SUN = 40;
209RrdTime.MON = 41;
210RrdTime.TUE = 42;
211RrdTime.WED = 43;
212RrdTime.THU = 44;
213RrdTime.FRI = 45;
214RrdTime.SAT = 46;
215
216RrdTime.VARIOUSWORDS = {
217 "midnight": RrdTime.MIDNIGHT, /* 00:00:00 of today or tomorrow */
218 "noon": RrdTime.NOON, /* 12:00:00 of today or tomorrow */
219 "teatime": RrdTime.TEATIME, /* 16:00:00 of today or tomorrow */
220 "am": RrdTime.AM, /* morning times for 0-12 clock */
221 "pm": RrdTime.PM, /* evening times for 0-12 clock */
222 "tomorrow": RrdTime.TOMORROW,
223 "yesterday": RrdTime.YESTERDAY,
224 "today": RrdTime.TODAY,
225 "now": RrdTime.NOW,
226 "n": RrdTime.NOW,
227 "start": RrdTime.START,
228 "s": RrdTime.START,
229 "end": RrdTime.END,
230 "e": RrdTime.END,
231 "epoch": RrdTime.EPOCH,
232 "jan": RrdTime.JAN,
233 "feb": RrdTime.FEB,
234 "mar": RrdTime.MAR,
235 "apr": RrdTime.APR,
236 "may": RrdTime.MAY,
237 "jun": RrdTime.JUN,
238 "jul": RrdTime.JUL,
239 "aug": RrdTime.AUG,
240 "sep": RrdTime.SEP,
241 "oct": RrdTime.OCT,
242 "nov": RrdTime.NOV,
243 "dec": RrdTime.DEC,
244 "january": RrdTime.JAN,
245 "february": RrdTime.FEB,
246 "march": RrdTime.MAR,
247 "april": RrdTime.APR,
248// "may": RrdTime.MAY,
249 "june": RrdTime.JUN,
250 "july": RrdTime.JUL,
251 "august": RrdTime.AUG,
252 "september": RrdTime.SEP,
253 "october": RrdTime.OCT,
254 "november": RrdTime.NOV,
255 "december": RrdTime.DEC,
256 "sunday": RrdTime.SUN,
257 "sun": RrdTime.SUN,
258 "monday": RrdTime.MON,
259 "mon": RrdTime.MON,
260 "tuesday": RrdTime.TUE,
261 "tue": RrdTime.TUE,
262 "wednesday": RrdTime.WED,
263 "wed": RrdTime.WED,
264 "thursday": RrdTime.THU,
265 "thu": RrdTime.THU,
266 "friday": RrdTime.FRI,
267 "fri": RrdTime.FRI,
268 "saturday": RrdTime.SAT,
269 "sat": RrdTime.SAT
270};
271
272RrdTime.TIMEMULTIPLIERS = {
273 "second": RrdTime.SECONDS, /* seconds multiplier */
274 "seconds": RrdTime.SECONDS, /* (pluralized) */
275 "sec": RrdTime.SECONDS, /* (generic) */
276 "s": RrdTime.SECONDS, /* (short generic) */
277 "minute": RrdTime.MINUTES, /* minutes multiplier */
278 "minutes": RrdTime.MINUTES, /* (pluralized) */
279 "min": RrdTime.MINUTES, /* (generic) */
280 "m": RrdTime.MONTHS_MINUTES, /* (short generic) */
281 "hour": RrdTime.HOURS, /* hours ... */
282 "hours": RrdTime.HOURS, /* (pluralized) */
283 "hr": RrdTime.HOURS, /* (generic) */
284 "h": RrdTime.HOURS, /* (short generic) */
285 "day": RrdTime.DAYS, /* days ... */
286 "days": RrdTime.DAYS, /* (pluralized) */
287 "d": RrdTime.DAYS, /* (short generic) */
288 "week": RrdTime.WEEKS, /* week ... */
289 "weeks": RrdTime.WEEKS, /* (pluralized) */
290 "wk": RrdTime.WEEKS, /* (generic) */
291 "w": RrdTime.WEEKS, /* (short generic) */
292 "month": RrdTime.MONTHS, /* week ... */
293 "months": RrdTime.MONTHS, /* (pluralized) */
294 "mon": RrdTime.MONTHS, /* (generic) */
295 "year": RrdTime.YEARS, /* year ... */
296 "years": RrdTime.YEARS, /* (pluralized) */
297 "yr": RrdTime.YEARS, /* (generic) */
298 "y": RrdTime.YEARS /* (short generic) */
299};
300
301RrdTime.ABSOLUTE_TIME = 0;
302RrdTime.RELATIVE_TO_START_TIME = 1;
303RrdTime.RELATIVE_TO_END_TIME = 2;
304RrdTime.RELATIVE_TO_EPOCH = 3;
305
306RrdTime.prototype.gettok = function ()
307{
308 if (this.tokidx >= this.toklen) {
309 this.tokid = RrdTime.EOF;
310 } else {
311 this.token = this.tokens[this.tokidx];
312 this.tokidx++;
313 if (!isNaN(this.token)) {
314 this.tokid = RrdTime.NUMBER;
315 this.token = parseInt(this.token, 10);
316 } else if (this.token === ':') {
317 this.tokid = RrdTime.COLON;
318 } else if (this.token === '.') {
319 this.tokid = RrdTime.DOT;
320 } else if (this.token === '+') {
321 this.tokid = RrdTime.PLUS;
322 } else if (this.token === '/') {
323 this.tokid = RrdTime.SLASH;
324 } else if (this.token === '-') {
325 this.tokid = RrdTime.MINUS;
326 } else {
327 this.tokid = RrdTime.ID;
328 if (this.token in this.specials)
329 this.tokid = this.specials[this.token];
330 }
331 }
332 return this.tokid;
333};
334
335RrdTime.prototype.plus_minus = function (doop)
336{
337 var op = RrdTime.PLUS;
338 var prev_multiplier = -1;
339 var delta;
340
341 if (doop >= 0) {
342 op = doop;
343 if (this.gettok() != RrdTime.NUMBER)
344 throw new RrdTimeError("There should be number after '"+(op == RrdTime.PLUS ? '+' : '-')+"'");
345 prev_multiplier = -1; /* reset months-minutes guessing mechanics */
346 }
347 /* if doop is < 0 then we repeat the previous op with the prefetched number */
348 delta = this.token;
349 if (this.gettok() == RrdTime.MONTHS_MINUTES) {
350 /* hard job to guess what does that -5m means: -5mon or -5min? */
351 switch (prev_multiplier) {
352 case RrdTime.DAYS:
353 case RrdTime.WEEKS:
354 case RrdTime.MONTHS:
355 case RrdTime.YEARS:
356 this.tokid = RrdTime.MONTHS;
357 break;
358 case RrdTime.SECONDS:
359 case RrdTime.MINUTES:
360 case RrdTime.HOURS:
361 this.tokid = RrdTime.MINUTES;
362 break;
363 default:
364 if (delta < 6) /* it may be some other value but in the context of RRD who needs less than 6 min deltas? */
365 this.tokid = RrdTime.MONTHS;
366 else
367 this.tokid = RrdTime.MINUTES;
368 }
369 }
370 prev_multiplier = this.tokid;
371 switch (this.tokid) {
372 case RrdTime.YEARS:
373 this.tm_year += ( op == RrdTime.PLUS) ? delta : -delta;
374 return;
375 case RrdTime.MONTHS:
376 this.tm_mon += ( op == RrdTime.PLUS) ? delta : -delta;
377 return;
378 case RrdTime.WEEKS:
379 delta *= 7;
380 case RrdTime.DAYS:
381 this.tm_mday += ( op == RrdTime.PLUS) ? delta : -delta;
382 return;
383 case RrdTime.HOURS:
384 this.offset += (op == RrdTime.PLUS) ? delta * 60 * 60 : -delta * 60 * 60;
385 return;
386 case RrdTime.MINUTES:
387 this.offset += (op == RrdTime.PLUS) ? delta * 60 : -delta * 60;
388 return;
389 case RrdTime.SECONDS:
390 this.offset += (op == RrdTime.PLUS) ? delta : -delta;
391 return;
392 default: /*default unit is seconds */
393 this.offset += (op == RrdTime.PLUS) ? delta : -delta;
394 return;
395 }
396 throw new RrdTimeError("well-known time unit expected after "+delta);
397};
398
399RrdTime.prototype.tod = function() /* tod() computes the time of day (TIME-OF-DAY-SPEC) */
400{
401 var hour, minute = 0;
402 var tlen;
403 /* save token status in case we must abort */
404 var tokid_sv = this.tokid;
405
406 tlen = (this.token+"").length;
407 /* first pick out the time of day - we assume a HH (COLON|DOT) MM time */
408 if (tlen > 2)
409 return;
410 hour = this.token;
411 this.gettok();
412 if (this.tokid == RrdTime.SLASH || this.tokid == RrdTime.DOT) {
413 /* guess we are looking at a date */
414 this.tokid = tokid_sv;
415 this.token = hour;
416 return;
417 }
418 if (this.tokid == RrdTime.COLON) {
419 if (this.gettok() != RrdTime.NUMBER)
420 throw new RrdTimeError("Parsing HH:MM syntax, expecting MM as number, got none");
421 minute = this.token;
422 if (minute > 59)
423 throw new RrdTimeError("parsing HH:MM syntax, got MM = "+minute+" (>59!)");
424 this.gettok();
425 }
426 /* check if an AM or PM specifier was given */
427 if (this.tokid == RrdTime.AM || this.tokid == RrdTime.PM) {
428 if (hour > 12) {
429 throw new RrdTimeError("there cannot be more than 12 AM or PM hours");
430 }
431 if (this.tokid == RrdTime.PM) {
432 if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
433 hour += 12;
434 } else {
435 if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
436 hour = 0;
437 }
438 this.gettok();
439 } else if (hour > 23) {
440 /* guess it was not a time then ... */
441 this.tokid = tokid_sv;
442 this.token = hour;
443 return;
444 }
445 this.tm_hour = hour;
446 this.tm_min = minute;
447 this.tm_sec = 0;
448 if (this.tm_hour == 24) {
449 this.tm_hour = 0;
450 this.tm_mday++;
451 }
452};
453
454RrdTime.prototype.assign_date = function(mday, mon, year)
455{
456 if (year > 138) {
457 if (year > 1970) {
458 year -= 1900;
459 } else {
460 throw new RrdTimeError("invalid year "+year+" (should be either 00-99 or >1900)");
461 }
462 } else if (year >= 0 && year < 38) {
463 year += 100; /* Allow year 2000-2037 to be specified as */
464 }
465 /* 00-37 until the problem of 2038 year will */
466 /* arise for unices with 32-bit time_t :) */
467 if (year < 70)
468 throw new RrdTimeError("won't handle dates before epoch (01/01/1970), sorry");
469
470 this.tm_mday = mday;
471 this.tm_mon = mon;
472 this.tm_year = year;
473};
474
475RrdTime.prototype.day = function ()
476{
477 var mday = 0, wday, mon, year = this.tm_year;
478 var tlen;
479
480 switch (this.tokid) {
481 case RrdTime.YESTERDAY:
482 this.tm_mday--;
483 case RrdTime.TODAY:
484 this.gettok();
485 break;
486 case RrdTime.TOMORROW:
487 this.tm_mday++;
488 this.gettok();
489 break;
490 case RrdTime.JAN:
491 case RrdTime.FEB:
492 case RrdTime.MAR:
493 case RrdTime.APR:
494 case RrdTime.MAY:
495 case RrdTime.JUN:
496 case RrdTime.JUL:
497 case RrdTime.AUG:
498 case RrdTime.SEP:
499 case RrdTime.OCT:
500 case RrdTime.NOV:
501 case RrdTime.DEC:
502 mon = (this.tokid - RrdTime.JAN);
503 if (this.gettok() != RrdTime.NUMBER)
504 throw new RrdTimeError("the day of the month should follow month name");
505 mday = this.token;
506 if (this.gettok() == RrdTime.NUMBER) {
507 year = this.token;
508 this.gettok();
509 } else {
510 year = this.tm_year;
511 }
512 this.assign_date(mday, mon, year);
513 break;
514 case RrdTime.SUN:
515 case RrdTime.MON:
516 case RrdTime.TUE:
517 case RrdTime.WED:
518 case RrdTime.THU:
519 case RrdTime.FRI:
520 case RrdTime.SAT:
521 wday = (this.tokid - RrdTime.SUN);
522 this.tm_mday += (wday - this.tm_wday);
523 this.gettok();
524 break;
525 case RrdTime.NUMBER:
526 mon = this.token;
527 if (mon > 10 * 365 * 24 * 60 * 60) {
528 this.localtime(mon);
529 this.gettok();
530 break;
531 }
532 if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */
533 var str = this.token + '';
534 year = parseInt(str.substr(0,4),10);
535 mon = parseInt(str.substr(4,2),10);
536 mday = parseInt(str.substr(6,2),10);
537 this.gettok();
538 } else {
539 this.gettok();
540 if (mon <= 31 && (this.tokid == RrdTime.SLASH || this.tokid == RrdTime.DOT)) {
541 var sep = this.tokid;
542 if (this.gettok() != RrdTime.NUMBER)
543 throw new RrdTimeError("there should be "+(RrdTime.DOT ? "month" : "day")+" number after '"+(RrdTime.DOT ? '.' : '/')+"'");
544 mday = this.token;
545 if (this.gettok() == sep) {
546 if (this.gettok() != RrdTime.NUMBER)
547 throw new RrdTimeError("there should be year number after '"+(sep == RrdTime.DOT ? '.' : '/')+"'");
548 year = this.token;
549 this.gettok();
550 }
551 if (sep == RrdTime.DOT) {
552 var x = mday;
553 mday = mon;
554 mon = x;
555 }
556 }
557 }
558 mon--;
559 if (mon < 0 || mon > 11)
560 throw new RrdTimeError("did you really mean month "+(mon+1)+"?");
561 if (mday < 1 || mday > 31)
562 throw new RrdTimeError("I'm afraid that "+mday+" is not a valid day of the month");
563 this.assign_date(mday, mon, year);
564 break;
565 }
566};
567
568RrdTime.prototype.localtime = function (tm)
569{
570 var date = new Date(tm*1000);
571 this.tm_sec = date.getSeconds();
572 this.tm_min = date.getMinutes();
573 this.tm_hour = date.getHours();
574 this.tm_mday = date.getDate();
575 this.tm_mon = date.getMonth();
576 this.tm_year = date.getFullYear()-1900;
577 this.tm_wday = date.getDay();
578};
579
580RrdTime.prototype.mktime = function()
581{
582 var date = new Date(this.tm_year+1900, this.tm_mon, this.tm_mday, this.tm_hour, this.tm_min, this.tm_sec);
583 return Math.round(date.getTime()/1000.0);
584};
585
586RrdTime.proc_start_end = function(start_t, end_t)
587{
588 var start, end;
589
590 if (start_t.type == RrdTime.RELATIVE_TO_END_TIME && end_t.type == RrdTime.RELATIVE_TO_START_TIME)
591 throw new RrdTimeError("the start and end times cannot be specified relative to each other");
592 if (start_t.type == RrdTime.RELATIVE_TO_START_TIME)
593 throw new RrdTimeError("the start time cannot be specified relative to itself");
594 if (end_t.type == RrdTime.RELATIVE_TO_END_TIME)
595 throw new RrdTimeError("the end time cannot be specified relative to itself");
596
597 if (start_t.type == RrdTime.RELATIVE_TO_END_TIME) {
598 end = end_t.mktime() + end_t.offset;
599 var tmtmp = new Date(end*1000);
600 tmtmp.setDate(tmtmp.getDate()+start_t.tm_mday);
601 tmtmp.setMonth(tmtmp.getMonth()+start_t.tm_mon);
602 tmtmp.setFullYear(tmtmp.getFullYear()+start_t.tm_year);
603 start = Math.round(tmtmp.getTime()/1000.0) + start_t.offset;
604 } else {
605 start = start_t.mktime() + start_t.offset;
606 }
607
608 if (end_t.type == RrdTime.RELATIVE_TO_START_TIME) {
609 start = start_t.mktime() + start_t.offset;
610 var tmtmp = new Date(start*1000);
611 tmtmp.setDate(tmtmp.getDate()+end_t.tm_mday);
612 tmtmp.setMonth(tmtmp.getMonth()+end_t.tm_mon);
613 tmtmp.setFullYear(tmtmp.getFullYear()+end_t.tm_year);
614 end = Math.round(tmtmp.getTime()/1000.0) + end_t.offset;
615 } else {
616 end = end_t.mktime() + end_t.offset;
617 }
618
619 return [start, end];
620};
621
diff --git a/js/base64.js b/js/base64.js
new file mode 100644
index 0000000..7d9536a
--- /dev/null
+++ b/js/base64.js
@@ -0,0 +1,143 @@
1
2/**
3*
4* Base64 encode / decode
5* http://www.webtoolkit.info/
6*
7**/
8
9var Base64 = {
10
11 // private property
12 _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
13
14 // public method for encoding
15 encode : function (input) {
16 var output = "";
17 var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
18 var i = 0;
19
20 input = Base64._utf8_encode(input);
21
22 while (i < input.length) {
23
24 chr1 = input.charCodeAt(i++);
25 chr2 = input.charCodeAt(i++);
26 chr3 = input.charCodeAt(i++);
27
28 enc1 = chr1 >> 2;
29 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
30 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
31 enc4 = chr3 & 63;
32
33 if (isNaN(chr2)) {
34 enc3 = enc4 = 64;
35 } else if (isNaN(chr3)) {
36 enc4 = 64;
37 }
38
39 output = output +
40 this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
41 this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
42
43 }
44
45 return output;
46 },
47
48 // public method for decoding
49 decode : function (input) {
50 var output = "";
51 var chr1, chr2, chr3;
52 var enc1, enc2, enc3, enc4;
53 var i = 0;
54
55 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
56
57 while (i < input.length) {
58
59 enc1 = this._keyStr.indexOf(input.charAt(i++));
60 enc2 = this._keyStr.indexOf(input.charAt(i++));
61 enc3 = this._keyStr.indexOf(input.charAt(i++));
62 enc4 = this._keyStr.indexOf(input.charAt(i++));
63
64 chr1 = (enc1 << 2) | (enc2 >> 4);
65 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
66 chr3 = ((enc3 & 3) << 6) | enc4;
67
68 output = output + String.fromCharCode(chr1);
69
70 if (enc3 != 64) {
71 output = output + String.fromCharCode(chr2);
72 }
73 if (enc4 != 64) {
74 output = output + String.fromCharCode(chr3);
75 }
76
77 }
78
79 output = Base64._utf8_decode(output);
80
81 return output;
82
83 },
84
85 // private method for UTF-8 encoding
86 _utf8_encode : function (string) {
87 string = string.replace(/\r\n/g,"\n");
88 var utftext = "";
89
90 for (var n = 0; n < string.length; n++) {
91
92 var c = string.charCodeAt(n);
93
94 if (c < 128) {
95 utftext += String.fromCharCode(c);
96 }
97 else if((c > 127) && (c < 2048)) {
98 utftext += String.fromCharCode((c >> 6) | 192);
99 utftext += String.fromCharCode((c & 63) | 128);
100 }
101 else {
102 utftext += String.fromCharCode((c >> 12) | 224);
103 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
104 utftext += String.fromCharCode((c & 63) | 128);
105 }
106
107 }
108
109 return utftext;
110 },
111
112 // private method for UTF-8 decoding
113 _utf8_decode : function (utftext) {
114 var string = "";
115 var i = 0;
116 var c = c1 = c2 = 0;
117
118 while ( i < utftext.length ) {
119
120 c = utftext.charCodeAt(i);
121
122 if (c < 128) {
123 string += String.fromCharCode(c);
124 i++;
125 }
126 else if((c > 191) && (c < 224)) {
127 c2 = utftext.charCodeAt(i+1);
128 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
129 i += 2;
130 }
131 else {
132 c2 = utftext.charCodeAt(i+1);
133 c3 = utftext.charCodeAt(i+2);
134 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
135 i += 3;
136 }
137
138 }
139
140 return string;
141 }
142
143}
diff --git a/js/binaryXHR.js b/js/binaryXHR.js
new file mode 100644
index 0000000..d91ad72
--- /dev/null
+++ b/js/binaryXHR.js
@@ -0,0 +1,234 @@
1
2/*
3 * BinaryFile over XMLHttpRequest
4 * Part of the javascriptRRD package
5 * Copyright (c) 2009 Frank Wuerthwein, fkw@ucsd.edu
6 * MIT License [http://www.opensource.org/licenses/mit-license.php]
7 *
8 * Original repository: http://javascriptrrd.sourceforge.net/
9 *
10 * Based on:
11 * Binary Ajax 0.1.5
12 * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/
13 * MIT License [http://www.opensource.org/licenses/mit-license.php]
14 */
15
16// ============================================================
17// Exception class
18function InvalidBinaryFile(msg) {
19 this.message=msg;
20 this.name="Invalid BinaryFile";
21}
22
23// pretty print
24InvalidBinaryFile.prototype.toString = function() {
25 return this.name + ': "' + this.message + '"';
26}
27
28// =====================================================================
29// BinaryFile class
30// Allows access to element inside a binary stream
31function BinaryFile(strData, iDataOffset, iDataLength) {
32 var data = strData;
33 var dataOffset = iDataOffset || 0;
34 var dataLength = 0;
35 // added
36 var doubleMantExpHi=Math.pow(2,-28);
37 var doubleMantExpLo=Math.pow(2,-52);
38 var doubleMantExpFast=Math.pow(2,-20);
39
40 this.getRawData = function() {
41 return data;
42 }
43
44 if (typeof strData == "string") {
45 dataLength = iDataLength || data.length;
46
47 this.getByteAt = function(iOffset) {
48 return data.charCodeAt(iOffset + dataOffset) & 0xFF;
49 }
50 } else if (typeof strData == "unknown") {
51 dataLength = iDataLength || IEBinary_getLength(data);
52
53 this.getByteAt = function(iOffset) {
54 return IEBinary_getByteAt(data, iOffset + dataOffset);
55 }
56 } else {
57 throw new InvalidBinaryFile("Unsupported type " + (typeof strData));
58 }
59
60 this.getLength = function() {
61 return dataLength;
62 }
63
64 this.getSByteAt = function(iOffset) {
65 var iByte = this.getByteAt(iOffset);
66 if (iByte > 127)
67 return iByte - 256;
68 else
69 return iByte;
70 }
71
72 this.getShortAt = function(iOffset) {
73 var iShort = (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset)
74 if (iShort < 0) iShort += 65536;
75 return iShort;
76 }
77 this.getSShortAt = function(iOffset) {
78 var iUShort = this.getShortAt(iOffset);
79 if (iUShort > 32767)
80 return iUShort - 65536;
81 else
82 return iUShort;
83 }
84 this.getLongAt = function(iOffset) {
85 var iByte1 = this.getByteAt(iOffset),
86 iByte2 = this.getByteAt(iOffset + 1),
87 iByte3 = this.getByteAt(iOffset + 2),
88 iByte4 = this.getByteAt(iOffset + 3);
89
90 var iLong = (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
91 if (iLong < 0) iLong += 4294967296;
92 return iLong;
93 }
94 this.getSLongAt = function(iOffset) {
95 var iULong = this.getLongAt(iOffset);
96 if (iULong > 2147483647)
97 return iULong - 4294967296;
98 else
99 return iULong;
100 }
101 this.getStringAt = function(iOffset, iLength) {
102 var aStr = [];
103 for (var i=iOffset,j=0;i<iOffset+iLength;i++,j++) {
104 aStr[j] = String.fromCharCode(this.getByteAt(i));
105 }
106 return aStr.join("");
107 }
108
109 // Added
110 this.getCStringAt = function(iOffset, iMaxLength) {
111 var aStr = [];
112 for (var i=iOffset,j=0;(i<iOffset+iMaxLength) && (this.getByteAt(i)>0);i++,j++) {
113 aStr[j] = String.fromCharCode(this.getByteAt(i));
114 }
115 return aStr.join("");
116 }
117
118 // Added
119 this.getDoubleAt = function(iOffset) {
120 var iByte1 = this.getByteAt(iOffset),
121 iByte2 = this.getByteAt(iOffset + 1),
122 iByte3 = this.getByteAt(iOffset + 2),
123 iByte4 = this.getByteAt(iOffset + 3),
124 iByte5 = this.getByteAt(iOffset + 4),
125 iByte6 = this.getByteAt(iOffset + 5),
126 iByte7 = this.getByteAt(iOffset + 6),
127 iByte8 = this.getByteAt(iOffset + 7);
128 var iSign=iByte8 >> 7;
129 var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4);
130 var iMantHi=((((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5) << 8) + iByte4;
131 var iMantLo=((((iByte3) << 8) + iByte2) << 8) + iByte1;
132
133 if (iExpRaw==0) return 0.0;
134 if (iExpRaw==0x7ff) return undefined;
135
136 var iExp=(iExpRaw & 0x7FF)-1023;
137
138 var dDouble = ((iSign==1)?-1:1)*Math.pow(2,iExp)*(1.0 + iMantLo*doubleMantExpLo + iMantHi*doubleMantExpHi);
139 return dDouble;
140 }
141 // added
142 // Extracts only 4 bytes out of 8, loosing in precision (20 bit mantissa)
143 this.getFastDoubleAt = function(iOffset) {
144 var iByte5 = this.getByteAt(iOffset + 4),
145 iByte6 = this.getByteAt(iOffset + 5),
146 iByte7 = this.getByteAt(iOffset + 6),
147 iByte8 = this.getByteAt(iOffset + 7);
148 var iSign=iByte8 >> 7;
149 var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4);
150 var iMant=((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5;
151
152 if (iExpRaw==0) return 0.0;
153 if (iExpRaw==0x7ff) return undefined;
154
155 var iExp=(iExpRaw & 0x7FF)-1023;
156
157 var dDouble = ((iSign==1)?-1:1)*Math.pow(2,iExp)*(1.0 + iMant*doubleMantExpFast);
158 return dDouble;
159 }
160
161 this.getCharAt = function(iOffset) {
162 return String.fromCharCode(this.getByteAt(iOffset));
163 }
164}
165
166
167document.write(
168 "<script type='text/vbscript'>\r\n"
169 + "Function IEBinary_getByteAt(strBinary, iOffset)\r\n"
170 + " IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n"
171 + "End Function\r\n"
172 + "Function IEBinary_getLength(strBinary)\r\n"
173 + " IEBinary_getLength = LenB(strBinary)\r\n"
174 + "End Function\r\n"
175 + "</script>\r\n"
176);
177
178
179
180// ===============================================================
181// Load a binary file from the specified URL
182// Will return an object of type BinaryFile
183function FetchBinaryURL(url) {
184 var request = new XMLHttpRequest();
185 request.open("GET", url,false);
186 try {
187 request.overrideMimeType('text/plain; charset=x-user-defined');
188 } catch (err) {
189 // ignore any error, just to make both FF and IE work
190 }
191 request.send(null);
192
193 var response=request.responseBody;
194 if (response==undefined){ // responseBody is non standard, but the only way to make it work in IE
195 response=request.responseText;
196 }
197 var bf=new BinaryFile(response);
198 return bf;
199}
200
201
202// ===============================================================
203// Asyncronously load a binary file from the specified URL
204//
205// callback must be a function with one or two arguments:
206// - bf = an object of type BinaryFile
207// - optional argument object (used only if callback_arg not undefined)
208function FetchBinaryURLAsync(url, callback, callback_arg) {
209 var callback_wrapper = function() {
210 if(this.readyState == 4) {
211 var response=this.responseBody;
212 if (response==undefined){ // responseBody is non standard, but the only way to make it work in IE
213 response=this.responseText;
214 }
215 var bf=new BinaryFile(response);
216 if (callback_arg!=null) {
217 callback(bf,callback_arg);
218 } else {
219 callback(bf);
220 }
221 }
222 }
223
224 var request = new XMLHttpRequest();
225 request.onreadystatechange = callback_wrapper;
226 request.open("GET", url,true);
227 try {
228 request.overrideMimeType('text/plain; charset=x-user-defined');
229 } catch (err) {
230 // ignore any error, just to make both FF and IE work
231 }
232 request.send(null);
233 return request
234}
diff --git a/js/rrdFile.js b/js/rrdFile.js
new file mode 100644
index 0000000..870c88e
--- /dev/null
+++ b/js/rrdFile.js
@@ -0,0 +1,408 @@
1/*
2 * Client library for access to RRD archive files
3 * Part of the javascriptRRD package
4 * Copyright (c) 2009-2010 Frank Wuerthwein, fkw@ucsd.edu
5 * Igor Sfiligoi, isfiligoi@ucsd.edu
6 *
7 * Original repository: http://javascriptrrd.sourceforge.net/
8 *
9 * MIT License [http://www.opensource.org/licenses/mit-license.php]
10 *
11 */
12
13/*
14 *
15 * RRDTool has been developed and is maintained by
16 * Tobias Oether [http://oss.oetiker.ch/rrdtool/]
17 *
18 * This software can be used to read files produced by the RRDTool
19 * but has been developed independently.
20 *
21 * Limitations:
22 *
23 * This version of the module assumes RRD files created on linux
24 * with intel architecture and supports both 32 and 64 bit CPUs.
25 * All integers in RRD files are suppoes to fit in 32bit values.
26 *
27 * Only versions 3 and 4 of the RRD archive are supported.
28 *
29 * Only AVERAGE,MAXIMUM,MINIMUM and LAST consolidation functions are
30 * supported. For all others, the behaviour is at the moment undefined.
31 *
32 */
33
34/*
35 * Dependencies:
36 *
37 * The data provided to this module require an object of a class
38 * that implements the following methods:
39 * getByteAt(idx) - Return a 8 bit unsigned integer at offset idx
40 * getLongAt(idx) - Return a 32 bit unsigned integer at offset idx
41 * getDoubleAt(idx) - Return a double float at offset idx
42 * getFastDoubleAt(idx) - Similar to getDoubleAt but with less precision
43 * getCStringAt(idx,maxsize) - Return a string of at most maxsize characters
44 * that was 0-terminated in the source
45 *
46 * The BinaryFile from binaryXHR.js implements this interface.
47 *
48 */
49
50
51// ============================================================
52// Exception class
53function InvalidRRD(msg) {
54 this.message=msg;
55 this.name="Invalid RRD";
56}
57
58// pretty print
59InvalidRRD.prototype.toString = function() {
60 return this.name + ': "' + this.message + '"';
61}
62
63
64// ============================================================
65// RRD DS Info class
66function RRDDS(rrd_data,rrd_data_idx,my_idx) {
67 this.rrd_data=rrd_data;
68 this.rrd_data_idx=rrd_data_idx;
69 this.my_idx=my_idx;
70}
71
72RRDDS.prototype.getIdx = function() {
73 return this.my_idx;
74}
75RRDDS.prototype.getName = function() {
76 return this.rrd_data.getCStringAt(this.rrd_data_idx,20);
77}
78RRDDS.prototype.getType = function() {
79 return this.rrd_data.getCStringAt(this.rrd_data_idx+20,20);
80}
81RRDDS.prototype.getMin = function() {
82 return this.rrd_data.getDoubleAt(this.rrd_data_idx+48);
83}
84RRDDS.prototype.getMax = function() {
85 return this.rrd_data.getDoubleAt(this.rrd_data_idx+56);
86}
87
88
89// ============================================================
90// RRD RRA Info class
91function RRDRRAInfo(rrd_data,rra_def_idx,
92 rrd_align,row_cnt,pdp_step,my_idx) {
93 this.rrd_data=rrd_data;
94 this.rra_def_idx=rra_def_idx;
95 this.rrd_align=rrd_align;
96 this.row_cnt=row_cnt;
97 this.pdp_step=pdp_step;
98 this.my_idx=my_idx;
99}
100
101RRDRRAInfo.prototype.getIdx = function() {
102 return this.my_idx;
103}
104
105// Get number of rows
106RRDRRAInfo.prototype.getNrRows = function() {
107 return this.row_cnt;
108}
109
110// Get number of slots used for consolidation
111// Mostly for internal use
112RRDRRAInfo.prototype.getPdpPerRow = function() {
113 if (this.rrd_align==32)
114 return this.rrd_data.getLongAt(this.rra_def_idx+24,20);
115 else
116 return this.rrd_data.getLongAt(this.rra_def_idx+32,20);
117}
118
119// Get RRA step (expressed in seconds)
120RRDRRAInfo.prototype.getStep = function() {
121 return this.pdp_step*this.getPdpPerRow();
122}
123
124// Get consolidation function name
125RRDRRAInfo.prototype.getCFName = function() {
126 return this.rrd_data.getCStringAt(this.rra_def_idx,20);
127}
128
129
130// ============================================================
131// RRD RRA handling class
132function RRDRRA(rrd_data,rra_ptr_idx,
133 rra_info,
134 header_size,prev_row_cnts,ds_cnt) {
135 this.rrd_data=rrd_data;
136 this.rra_info=rra_info;
137 this.row_cnt=rra_info.row_cnt;
138 this.ds_cnt=ds_cnt;
139
140 var row_size=ds_cnt*8;
141
142 this.base_rrd_db_idx=header_size+prev_row_cnts*row_size;
143
144 // get imediately, since it will be needed often
145 this.cur_row=rrd_data.getLongAt(rra_ptr_idx);
146
147 // calculate idx relative to base_rrd_db_idx
148 // mostly used internally
149 this.calc_idx = function(row_idx,ds_idx) {
150 if ((row_idx>=0) && (row_idx<this.row_cnt)) {
151 if ((ds_idx>=0) && (ds_idx<ds_cnt)){
152 // it is round robin, starting from cur_row+1
153 var real_row_idx=row_idx+this.cur_row+1;
154 if (real_row_idx>=this.row_cnt) real_row_idx-=this.row_cnt;
155 return row_size*real_row_idx+ds_idx*8;
156 } else {
157 throw RangeError("DS idx ("+ row_idx +") out of range [0-" + ds_cnt +").");
158 }
159 } else {
160 throw RangeError("Row idx ("+ row_idx +") out of range [0-" + this.row_cnt +").");
161 }
162 }
163}
164
165RRDRRA.prototype.getIdx = function() {
166 return this.rra_info.getIdx();
167}
168
169// Get number of rows/columns
170RRDRRA.prototype.getNrRows = function() {
171 return this.row_cnt;
172}
173RRDRRA.prototype.getNrDSs = function() {
174 return this.ds_cnt;
175}
176
177// Get RRA step (expressed in seconds)
178RRDRRA.prototype.getStep = function() {
179 return this.rra_info.getStep();
180}
181
182// Get consolidation function name
183RRDRRA.prototype.getCFName = function() {
184 return this.rra_info.getCFName();
185}
186
187RRDRRA.prototype.getEl = function(row_idx,ds_idx) {
188 return this.rrd_data.getDoubleAt(this.base_rrd_db_idx+this.calc_idx(row_idx,ds_idx));
189}
190
191// Low precision version of getEl
192// Uses getFastDoubleAt
193RRDRRA.prototype.getElFast = function(row_idx,ds_idx) {
194 return this.rrd_data.getFastDoubleAt(this.base_rrd_db_idx+this.calc_idx(row_idx,ds_idx));
195}
196
197// ============================================================
198// RRD Header handling class
199function RRDHeader(rrd_data) {
200 this.rrd_data=rrd_data;
201 this.validate_rrd();
202 this.load_header();
203 this.calc_idxs();
204}
205
206// Internal, used for initialization
207RRDHeader.prototype.validate_rrd = function() {
208 if (this.rrd_data.getCStringAt(0,4)!=="RRD") throw new InvalidRRD("Wrong magic id.");
209
210 this.rrd_version=this.rrd_data.getCStringAt(4,5);
211 if ((this.rrd_version!=="0003")&&(this.rrd_version!=="0004")) {
212 throw new InvalidRRD("Unsupported RRD version "+this.rrd_version+".");
213 }
214
215 if (this.rrd_data.getDoubleAt(12)==8.642135e+130) {
216 this.rrd_align=32;
217 } else if (this.rrd_data.getDoubleAt(16)==8.642135e+130) {
218 this.rrd_align=64;
219 } else {
220 throw new InvalidRRD("Unsupported platform.");
221 }
222}
223
224// Internal, used for initialization
225RRDHeader.prototype.load_header = function() {
226 if (this.rrd_align==32) {
227 this.ds_cnt=this.rrd_data.getLongAt(20,false);
228 this.rra_cnt=this.rrd_data.getLongAt(24,false);
229 this.pdp_step=this.rrd_data.getLongAt(28,false);
230 // 8*10 unused values follow
231 this.top_header_size=112;
232 } else {
233 //get only the low 32 bits, the high 32 should always be 0
234 this.ds_cnt=this.rrd_data.getLongAt(24,false);
235 this.rra_cnt=this.rrd_data.getLongAt(32,false);
236 this.pdp_step=this.rrd_data.getLongAt(40,false);
237 // 8*10 unused values follow
238 this.top_header_size=128;
239 }
240}
241
242// Internal, used for initialization
243RRDHeader.prototype.calc_idxs = function() {
244 this.ds_def_idx=this.top_header_size;
245 // char ds_nam[20], char dst[20], unival par[10]
246 this.ds_el_size=120;
247
248 this.rra_def_idx=this.ds_def_idx+this.ds_el_size*this.ds_cnt;
249 // char cf_nam[20], uint row_cnt, uint pdp_cnt, unival par[10]
250 this.row_cnt_idx;
251 if (this.rrd_align==32) {
252 this.rra_def_el_size=108;
253 this.row_cnt_idx=20;
254 } else {
255 this.rra_def_el_size=120;
256 this.row_cnt_idx=24;
257 }
258
259 this.live_head_idx=this.rra_def_idx+this.rra_def_el_size*this.rra_cnt;
260 // time_t last_up, int last_up_usec
261 if (this.rrd_align==32) {
262 this.live_head_size=8;
263 } else {
264 this.live_head_size=16;
265 }
266
267 this.pdp_prep_idx=this.live_head_idx+this.live_head_size;
268 // char last_ds[30], unival scratch[10]
269 this.pdp_prep_el_size=112;
270
271 this.cdp_prep_idx=this.pdp_prep_idx+this.pdp_prep_el_size*this.ds_cnt;
272 // unival scratch[10]
273 this.cdp_prep_el_size=80;
274
275 this.rra_ptr_idx=this.cdp_prep_idx+this.cdp_prep_el_size*this.ds_cnt*this.rra_cnt;
276 // uint cur_row
277 if (this.rrd_align==32) {
278 this.rra_ptr_el_size=4;
279 } else {
280 this.rra_ptr_el_size=8;
281 }
282
283 this.header_size=this.rra_ptr_idx+this.rra_ptr_el_size*this.rra_cnt;
284}
285
286// Optional initialization
287// Read and calculate row counts
288RRDHeader.prototype.load_row_cnts = function() {
289 this.rra_def_row_cnts=[];
290 this.rra_def_row_cnt_sums=[]; // how many rows before me
291 for (var i=0; i<this.rra_cnt; i++) {
292 this.rra_def_row_cnts[i]=this.rrd_data.getLongAt(this.rra_def_idx+i*this.rra_def_el_size+this.row_cnt_idx,false);
293 if (i==0) {
294 this.rra_def_row_cnt_sums[i]=0;
295 } else {
296 this.rra_def_row_cnt_sums[i]=this.rra_def_row_cnt_sums[i-1]+this.rra_def_row_cnts[i-1];
297 }
298 }
299}
300
301// ---------------------------
302// Start of user functions
303
304RRDHeader.prototype.getMinStep = function() {
305 return this.pdp_step;
306}
307RRDHeader.prototype.getLastUpdate = function() {
308 return this.rrd_data.getLongAt(this.live_head_idx,false);
309}
310
311RRDHeader.prototype.getNrDSs = function() {
312 return this.ds_cnt;
313}
314RRDHeader.prototype.getDSNames = function() {
315 var ds_names=[]
316 for (var idx=0; idx<this.ds_cnt; idx++) {
317 var ds=this.getDSbyIdx(idx);
318 var ds_name=ds.getName()
319 ds_names.push(ds_name);
320 }
321 return ds_names;
322}
323RRDHeader.prototype.getDSbyIdx = function(idx) {
324 if ((idx>=0) && (idx<this.ds_cnt)) {
325 return new RRDDS(this.rrd_data,this.ds_def_idx+this.ds_el_size*idx,idx);
326 } else {
327 throw RangeError("DS idx ("+ idx +") out of range [0-" + this.ds_cnt +").");
328 }
329}
330RRDHeader.prototype.getDSbyName = function(name) {
331 for (var idx=0; idx<this.ds_cnt; idx++) {
332 var ds=this.getDSbyIdx(idx);
333 var ds_name=ds.getName()
334 if (ds_name==name)
335 return ds;
336 }
337 throw RangeError("DS name "+ name +" unknown.");
338}
339
340RRDHeader.prototype.getNrRRAs = function() {
341 return this.rra_cnt;
342}
343RRDHeader.prototype.getRRAInfo = function(idx) {
344 if ((idx>=0) && (idx<this.rra_cnt)) {
345 return new RRDRRAInfo(this.rrd_data,
346 this.rra_def_idx+idx*this.rra_def_el_size,
347 this.rrd_align,this.rra_def_row_cnts[idx],this.pdp_step,
348 idx);
349 } else {
350 throw RangeError("RRA idx ("+ idx +") out of range [0-" + this.rra_cnt +").");
351 }
352}
353
354// ============================================================
355// RRDFile class
356// Given a BinaryFile, gives access to the RRD archive fields
357//
358// Arguments:
359// bf must be an object compatible with the BinaryFile interface
360function RRDFile(bf) {
361 var rrd_data=bf
362
363 this.rrd_header=new RRDHeader(rrd_data);
364 this.rrd_header.load_row_cnts();
365
366 // ===================================
367 // Start of user functions
368
369 this.getMinStep = function() {
370 return this.rrd_header.getMinStep();
371 }
372 this.getLastUpdate = function() {
373 return this.rrd_header.getLastUpdate();
374 }
375
376 this.getNrDSs = function() {
377 return this.rrd_header.getNrDSs();
378 }
379 this.getDSNames = function() {
380 return this.rrd_header.getDSNames();
381 }
382 this.getDS = function(id) {
383 if (typeof id == "number") {
384 return this.rrd_header.getDSbyIdx(id);
385 } else {
386 return this.rrd_header.getDSbyName(id);
387 }
388 }
389
390 this.getNrRRAs = function() {
391 return this.rrd_header.getNrRRAs();
392 }
393
394 this.getRRAInfo = function(idx) {
395 return this.rrd_header.getRRAInfo(idx);
396 }
397
398 this.getRRA = function(idx) {
399 var rra_info=this.rrd_header.getRRAInfo(idx);
400 return new RRDRRA(rrd_data,
401 this.rrd_header.rra_ptr_idx+idx*this.rrd_header.rra_ptr_el_size,
402 rra_info,
403 this.rrd_header.header_size,
404 this.rrd_header.rra_def_row_cnt_sums[idx],
405 this.rrd_header.ds_cnt);
406 }
407
408}
diff --git a/js/sprintf.js b/js/sprintf.js
new file mode 100644
index 0000000..4d61fa2
--- /dev/null
+++ b/js/sprintf.js
@@ -0,0 +1,78 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 **/
18
19"use strict";
20
21function sprintf()
22{
23 var argc = 0;
24 var args = arguments;
25 var fmt = args[argc++];
26
27 function lpad (str, padString, length)
28 {
29 while (str.length < length)
30 str = padString + str;
31 return str;
32 };
33
34 function format (match, width, dot, precision, length, conversion)
35 {
36 if (match === '%%') return '%';
37
38 var value = args[argc++];
39 var prefix;
40
41 if (width === undefined)
42 width = 0;
43 else
44 width = +width;
45
46 if (precision === undefined)
47 precision = conversion == 'd' ? 0 : 6;
48 else
49 precision = +precision;
50
51 switch (conversion) {
52 case 's':
53 case 'c':
54 return value;
55 break;
56 case 'd':
57 return parseInt(value, 10);
58 break;
59 case 'e':
60 prefix = value < 0 ? '-' : '';
61 return lpad(prefix+Math.abs(value).toExponential(precision),' ',width);
62 break;
63 case 'F':
64 case 'f':
65 prefix = value < 0 ? '-' : '';
66 return lpad(prefix+Math.abs(value).toFixed(precision),' ',width);
67 break;
68 case 'g':
69 prefix = value < 0 ? '-' : '';
70 return lpad(prefix+Math.abs(value).toPrecision(precision),' ',width);
71 break;
72 default:
73 return match;
74 }
75
76 };
77 return fmt.replace(/%(\d+)?(\.(\d+))?(l?)([%scdfFeg])/g,format);
78};
diff --git a/js/strftime.js b/js/strftime.js
new file mode 100644
index 0000000..0933c27
--- /dev/null
+++ b/js/strftime.js
@@ -0,0 +1,146 @@
1/**
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU General Public License as published by the Free
5 * Software Foundation; either version 2 of the License, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
16
17 **/
18
19"use strict";
20
21function strftime (fmt, time)
22{
23 var d = new Date(time*1000);
24
25 var days = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
26 var fdays = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
27 var months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
28 var fmonths = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
29
30 function pad2 (number)
31 {
32 return (number < 10 ? '0' : '') + number
33 };
34
35 function pad3(number)
36 {
37 return (number < 10 ? '00' : number < 100 ? '0' : '') + number
38 };
39
40 function lpad (str, padString, length)
41 {
42 while (str.length < length)
43 str = padString + str;
44 return str;
45 };
46
47 function format(match, opt)
48 {
49 if (match === '%%') return '%';
50
51 switch (opt) {
52 case 'a':
53 return days[d.getDay()];
54 break;
55 case 'A':
56 return fdays[d.getDay()];
57 break;
58 case 'b':
59 return months[d.getMonth()];
60 break;
61 case 'B':
62 return fmonths[d.getMonth()];
63 break;
64 case 'c':
65 return d.toLocaleString();
66 break;
67 case 'd':
68 return pad2(d.getDate());
69 break;
70 case 'H':
71 return pad2(d.getHours());
72 break;
73 case 'I':
74 var hours = d.getHours()%12;
75 return pad2(hours === 0 ? 12 : hours);
76 break;
77 case 'j':
78 var d01 = new Date (d.getFullYear(), 0, 1);
79 return pad3(Math.ceil((d.getTime()-d01.getTime())/86400000)+1);
80 break;
81 case 'm':
82 return pad2(d.getMonth());
83 break;
84 case 'M':
85 return pad2(d.getMinutes());
86 break;
87 case 'p':
88 return d.getHours() >= 12 ? 'PM' : 'AM';
89 break;
90 case 's':
91 return pad2(d.getSeconds());
92 break;
93 case 'S':
94 return d.getTime()/1000;
95 break;
96 case 'u':
97 return d.getDay() === 0 ? 7 : d.getDay();
98 break;
99 case 'U':
100 var d01 = new Date(d.getFullYear(),0,1);
101 return pad2(Math.round((Math.ceil((d.getTime()-d01.getTime())/86400000)+1 + 6 - d.getDay())/7));
102 break;
103 case 'V':
104 var d01 = new Date(d.getFullYear(), 0, 1);
105 var w = Math.round((Math.ceil((d.getTime()-d01.getTime())/86400000)+1 + 7 - (d.getDay() === 0 ? 7 : d.getDay()))/7);
106 var d31 = new Date(d.getFullYear(), 11, 31);
107 if (d01.getDay() < 4 && d01.getDay() > 1) w++;
108 if (w === 53 && d31.getDay() < 4) {
109 w = 1;
110 } else if (w === 0) {
111 d31 = new Date(d.getFullYear()-1, 11, 31);
112 d01 = new Date(d31.getFullYear(), 0, 1);
113 w = Math.round((Math.ceil((d31.getTime()-d01.getTime())/86400000)+1 + 7 - (d31.getDay() === 0 ? 7 : d31.getDay()))/7);
114 if (d01.getDay() < 4 && d01.getDay() > 1) w++;
115 if (w === 53 && d31.getDay() < 4) w = 1;
116 }
117 return pad2(w);
118 break;
119 case 'w':
120 return d.getDay();
121 break;
122 case 'W':
123 var d01 = new Date(d.getFullYear(),0,1);
124 return pad2(Math.round((Math.ceil((d.getTime()-d01.getTime())/86400000)+1 + 7 - (d.getDay() === 0 ? 7 : d.getDay()))/7));
125 break;
126 case 'x':
127 return pad2(d.getDate())+'/'+pad2(d.getMonth())+'/'+d.getFullYear()
128 break;
129 case 'X':
130 return pad2(d.getHours())+':'+pad2(d.getMinutes())+':'+pad2(d.getSeconds());
131 break;
132 case 'y':
133 return pad2(d.getFullYear()%100);
134 break;
135 case 'Y':
136 return d.getFullYear();
137 break;
138 case 'Z':
139 return d.toString().replace(/^.*\(([^)]+)\)$/, '$1');
140 break;
141 default:
142 return match;
143 }
144 };
145 return fmt.replace(/%([aAbBcdHIjmMpsSUVwWxXyYZ%])/g, format);
146};