/* * RRD graphing libraries, based on Flot * Part of the javascriptRRD package * Copyright (c) 2010 Frank Wuerthwein, fkw@ucsd.edu * Igor Sfiligoi, isfiligoi@ucsd.edu * * Original repository: http://javascriptrrd.sourceforge.net/ * * MIT License [http://www.opensource.org/licenses/mit-license.php] * */ /* * * Flot is a javascript plotting library developed and maintained by * Ole Laursen [http://code.google.com/p/flot/] * */ /* * Local dependencies: * rrdFlotSupport.py * * External dependencies: * [Flot]/jquery.py * [Flot]/jquery.flot.js * [Flot]/jquery.flot.selection.js */ /* graph_options defaults (see Flot docs for details) * { * legend: { position:"nw",noColumns:3}, * lines: { show:true }, * yaxis: { autoscaleMargin: 0.20}, * tooltip: true, * tooltipOpts: { content: "

%s

Value: %y.3" } * } * * ds_graph_options is a dictionary of DS_name, * with each element being a graph_option * The defaults for each element are * { * title: label or ds_name // this is what is displayed in the checkboxes * checked: first_ds_in_list? // boolean * label: title or ds_name // this is what is displayed in the legend * color: ds_index // see Flot docs for details * lines: { show:true } // see Flot docs for details * yaxis: 1 // can be 1 or 2 * stack: 'none' // other options are 'positive' and 'negative' * } * * //overwrites other defaults; mostly used for linking via the URL * rrdflot_defaults defaults (see Flot docs for details) * { * graph_only: false // If true, limit the display to the graph only * legend: "Top" //Starting location of legend. Options are: * // "Top","Bottom","TopRight","BottomRight","None". * num_cb_rows: 12 //How many rows of DS checkboxes per column. * use_element_buttons: false //To be used in conjunction with num_cb_rows: This option * // creates a button above every column, which selects * // every element in the column. * multi_ds: false //"true" appends the name of the aggregation function to the * // name of the DS element. * multi_rra: false //"true" appends the name of the RRA consolidation function (CF) * // (AVERAGE, MIN, MAX or LAST) to the name of the RRA. Useful * // for RRAs over the same interval with different CFs. * use_checked_DSs: false //Use the list checked_DSs below. * checked_DSs: [] //List of elements to be checked by default when graph is loaded. * // Overwrites graph options. * use_rra: false //Whether to use the rra index specified below. * rra: 0 //RRA (rra index in rrd) to be selected when graph is loaded. * use_windows: false //Whether to use the window zoom specifications below. * window_min: 0 //Sets minimum for window zoom. X-axis usually in unix time. * window_max: 0 //Sets maximum for window zoom. * graph_height: "300px" //Height of main graph. * graph_width: "500px" //Width of main graph. * scale_height: "110px" //Height of small scaler graph. * scale_width: "250px" //Width of small scaler graph. * timezone: local //timezone. * } */ var local_checked_DSs = []; var selected_rra = 0; var window_min=0; var window_max=0; var elem_group=null; var timezone_shift=0; function rrdFlot(html_id, rrd_file, graph_options, ds_graph_options, rrdflot_defaults) { this.html_id=html_id; this.rrd_file=rrd_file; this.graph_options=graph_options; if (rrdflot_defaults==null) { this.rrdflot_defaults=new Object(); // empty object, just not to be null } else { this.rrdflot_defaults=rrdflot_defaults; } if (ds_graph_options==null) { this.ds_graph_options=new Object(); // empty object, just not to be null } else { this.ds_graph_options=ds_graph_options; } this.selection_range=new rrdFlotSelection(); graph_info={}; this.createHTML(); this.populateRes(); this.populateDScb(); this.drawFlotGraph(); if (this.rrdflot_defaults.graph_only==true) { this.cleanHTMLCruft(); } } // =============================================== // Create the HTML tags needed to host the graphs rrdFlot.prototype.createHTML = function() { var rf_this=this; // use obj inside other functions var base_el=document.getElementById(this.html_id); this.res_id=this.html_id+"_res"; this.ds_cb_id=this.html_id+"_ds_cb"; this.graph_id=this.html_id+"_graph"; this.scale_id=this.html_id+"_scale"; this.legend_sel_id=this.html_id+"_legend_sel"; this.time_sel_id=this.html_id+"_time_sel"; this.elem_group_id=this.html_id+"_elem_group"; // First clean up anything in the element while (base_el.lastChild!=null) base_el.removeChild(base_el.lastChild); // Now create the layout var external_table=document.createElement("Table"); this.external_table=external_table; // Header two: resulution select and DS selection title var rowHeader=external_table.insertRow(-1); var cellRes=rowHeader.insertCell(-1); cellRes.colSpan=3; cellRes.appendChild(document.createTextNode("Resolution:")); var forRes=document.createElement("Select"); forRes.id=this.res_id; //forRes.onChange= this.callback_res_changed; forRes.onchange= function () {rf_this.callback_res_changed();}; cellRes.appendChild(forRes); var cellDSTitle=rowHeader.insertCell(-1); cellDSTitle.appendChild(document.createTextNode("Select elements to plot:")); // Graph row: main graph and DS selection block var rowGraph=external_table.insertRow(-1); var cellGraph=rowGraph.insertCell(-1); cellGraph.colSpan=3; var elGraph=document.createElement("Div"); if(this.rrdflot_defaults.graph_width!=null) { elGraph.style.width=this.rrdflot_defaults.graph_width; } else {elGraph.style.width="500px";} if(this.rrdflot_defaults.graph_height!=null) { elGraph.style.height=this.rrdflot_defaults.graph_height; } else {elGraph.style.height="300px";} elGraph.id=this.graph_id; cellGraph.appendChild(elGraph); var cellDScb=rowGraph.insertCell(-1); cellDScb.vAlign="top"; var formDScb=document.createElement("Form"); formDScb.id=this.ds_cb_id; formDScb.onchange= function () {rf_this.callback_ds_cb_changed();}; cellDScb.appendChild(formDScb); // Scale row: scaled down selection graph var rowScale=external_table.insertRow(-1); var cellScaleLegend=rowScale.insertCell(-1); cellScaleLegend.vAlign="top"; cellScaleLegend.appendChild(document.createTextNode("Legend:")); cellScaleLegend.appendChild(document.createElement('br')); var forScaleLegend=document.createElement("Select"); forScaleLegend.id=this.legend_sel_id; forScaleLegend.appendChild(new Option("Top","nw",this.rrdflot_defaults.legend=="Top",this.rrdflot_defaults.legend=="Top")); forScaleLegend.appendChild(new Option("Bottom","sw",this.rrdflot_defaults.legend=="Bottom",this.rrdflot_defaults.legend=="Bottom")); forScaleLegend.appendChild(new Option("TopRight","ne",this.rrdflot_defaults.legend=="TopRight",this.rrdflot_defaults.legend=="TopRight")); forScaleLegend.appendChild(new Option("BottomRight","se",this.rrdflot_defaults.legend=="BottomRight",this.rrdflot_defaults.legend=="BottomRight")); forScaleLegend.appendChild(new Option("None","None",this.rrdflot_defaults.legend=="None",this.rrdflot_defaults.legend=="None")); forScaleLegend.onchange= function () {rf_this.callback_legend_changed();}; cellScaleLegend.appendChild(forScaleLegend); cellScaleLegend.appendChild(document.createElement('br')); cellScaleLegend.appendChild(document.createTextNode("Timezone:")); cellScaleLegend.appendChild(document.createElement('br')); var timezone=document.createElement("Select"); timezone.id=this.time_sel_id; var timezones = ["+12","+11","+10","+9","+8","+7","+6","+5","+4","+3","+2","+1","0", "-1","-2","-3","-4","-5","-6","-7","-8","-9","-10","-11","-12"]; var tz_found=false; var true_tz; for(var j=0; j<24; j++) { if (Math.ceil(this.rrdflot_defaults.timezone)==Math.ceil(timezones[j])) { tz_found=true; true_tz=Math.ceil(this.rrdflot_defaults.timezone); break; } } if (!tz_found) { // the passed timezone does not make sense // find the local time var d= new Date(); true_tz=-Math.ceil(d.getTimezoneOffset()/60); } for(var j=0; j<24; j++) { timezone.appendChild(new Option(timezones[j],timezones[j],true_tz==Math.ceil(timezones[j]),true_tz==Math.ceil(timezones[j]))); } timezone.onchange= function () {rf_this.callback_timezone_changed();}; cellScaleLegend.appendChild(timezone); var cellScale=rowScale.insertCell(-1); cellScale.align="right"; var elScale=document.createElement("Div"); if(this.rrdflot_defaults.scale_width!=null) { elScale.style.width=this.rrdflot_defaults.scale_width; } else {elScale.style.width="250px";} if(this.rrdflot_defaults.scale_height!=null) { elScale.style.height=this.rrdflot_defaults.scale_height; } else {elScale.style.height="110px";} elScale.id=this.scale_id; cellScale.appendChild(elScale); var cellScaleReset=rowScale.insertCell(-1); cellScaleReset.vAlign="top"; cellScaleReset.appendChild(document.createTextNode(" ")); cellScaleReset.appendChild(document.createElement('br')); var elScaleReset=document.createElement("input"); elScaleReset.type = "button"; elScaleReset.value = "Reset selection"; elScaleReset.onclick = function () {rf_this.callback_scale_reset();} cellScaleReset.appendChild(elScaleReset); base_el.appendChild(external_table); }; // =============================================== // Remove all HTMl elements but the graph rrdFlot.prototype.cleanHTMLCruft = function() { var rf_this=this; // use obj inside other functions // delete top and bottom rows... graph is in the middle this.external_table.deleteRow(-1); this.external_table.deleteRow(0); var ds_el=document.getElementById(this.ds_cb_id); ds_el.removeChild(ds_el.lastChild); } // ====================================== // Populate RRA and RD info rrdFlot.prototype.populateRes = function() { var form_el=document.getElementById(this.res_id); // First clean up anything in the element while (form_el.lastChild!=null) form_el.removeChild(form_el.lastChild); // now populate with RRA info var nrRRAs=this.rrd_file.getNrRRAs(); for (var i=0; ithis.rrdflot_defaults.num_cb_rows) { //if only one column, no need for a button elem_group_number = (i/this.rrdflot_defaults.num_cb_rows)+1; var elGroupSelect = document.createElement("input"); elGroupSelect.type = "button"; elGroupSelect.value = "Group "+elem_group_number; elGroupSelect.onclick = (function(e) { //lambda function!! return function() {rf_this.callback_elem_group_changed(e);};})(elem_group_number); cell_el.appendChild(elGroupSelect); cell_el.appendChild(document.createElement('br')); //add space between the two } } else { //just make next element column cell_el=row_el.insertCell(-1); } } var ds=this.rrd_file.getDS(i); if (this.rrdflot_defaults.multi_ds) { //null==false in boolean ops var name=ds.getName()+"-"+ds.getType(); var name2=ds.getName(); } else {var name=ds.getName(); var name2=ds.getName();} var title=name; if(this.rrdflot_defaults.use_checked_DSs) { if(this.rrdflot_defaults.checked_DSs.length==0) { var checked=(i==0); // only first checked by default } else{checked=false;} } else {var checked=(i==0);} if (this.ds_graph_options[name]!=null) { var dgo=this.ds_graph_options[name]; if (dgo['title']!=null) { // if the user provided the title, use it title=dgo['title']; } else if (dgo['label']!=null) { // use label as a second choiceit title=dgo['label']; } // else leave the ds name if(this.rrdflot_defaults.use_checked_DSs) { if(this.rrdflot_defaults.checked_DSs.length==0) { // if the user provided the title, use it checked=dgo['checked']; } } else { if (dgo['checked']!=null) { checked=dgo['checked']; } } } if(this.rrdflot_defaults.use_checked_DSs) { if(this.rrdflot_defaults.checked_DSs==null) {continue;} for(var j=0;j0) { for (var i=0; i%s Value: %y.3" }, grid: { hoverable: true }, }; if (legend_id=="None") { // do nothing } else { graph_options.legend.show=true; graph_options.legend.position=legend_id; } if (this.graph_options!=null) { graph_options=populateGraphOptions(graph_options,this.graph_options); } if (graph_options.tooltip==false) { // avoid the need for the caller specify both graph_options.grid.hoverable=false; } if (this.selection_range.isSet()) { var selection_range=this.selection_range.getFlotRanges(); if(this.rrdflot_defaults.use_windows) { graph_options.xaxis.min = this.rrdflot_defaults.window_min; graph_options.xaxis.max = this.rrdflot_defaults.window_max; } else { graph_options.xaxis.min=selection_range.xaxis.from; graph_options.xaxis.max=selection_range.xaxis.to; } } else if(this.rrdflot_defaults.use_windows) { graph_options.xaxis.min = this.rrdflot_defaults.window_min; graph_options.xaxis.max = this.rrdflot_defaults.window_max; } else { graph_options.xaxis.min=flot_obj.min; graph_options.xaxis.max=flot_obj.max; } var scale_options = { legend: {show:false}, lines: {show:true}, xaxis: {mode: "time", min:flot_obj.min, max:flot_obj.max }, yaxis: graph_options.yaxis, selection: { mode: "x" }, }; //this.selection_range.selection_min=flot_obj.min; //this.selection_range.selection_max=flot_obj.max; var flot_data=flot_obj.data; var graph_data=this.selection_range.trim_flot_data(flot_data); var scale_data=flot_data; this.graph = $.plot($(graph_jq_id), graph_data, graph_options); this.scale = $.plot($(scale_jq_id), scale_data, scale_options); if(this.rrdflot_defaults.use_windows) { ranges = {}; ranges.xaxis = []; ranges.xaxis.from = this.rrdflot_defaults.window_min; ranges.xaxis.to = this.rrdflot_defaults.window_max; rf_this.scale.setSelection(ranges,true); window_min = ranges.xaxis.from; window_max = ranges.xaxis.to; } if (this.selection_range.isSet()) { this.scale.setSelection(this.selection_range.getFlotRanges(),true); //don't fire event, no need } // now connect the two $(graph_jq_id).unbind("plotselected"); // but first remove old function $(graph_jq_id).bind("plotselected", function (event, ranges) { // do the zooming rf_this.selection_range.setFromFlotRanges(ranges); graph_options.xaxis.min=ranges.xaxis.from; graph_options.xaxis.max=ranges.xaxis.to; window_min = ranges.xaxis.from; window_max = ranges.xaxis.to; rf_this.graph = $.plot($(graph_jq_id), rf_this.selection_range.trim_flot_data(flot_data), graph_options); // don't fire event on the scale to prevent eternal loop rf_this.scale.setSelection(ranges, true); //puts the transparent window on minigraph }); $(scale_jq_id).unbind("plotselected"); //same here $(scale_jq_id).bind("plotselected", function (event, ranges) { rf_this.graph.setSelection(ranges); }); // only the scale has a selection // so when that is cleared, redraw also the graph $(scale_jq_id).bind("plotunselected", function() { rf_this.selection_range.reset(); graph_options.xaxis.min=flot_obj.min; graph_options.xaxis.max=flot_obj.max; rf_this.graph = $.plot($(graph_jq_id), rf_this.selection_range.trim_flot_data(flot_data), graph_options); window_min = 0; window_max = 0; }); }; // callback functions that are called when one of the selections changes rrdFlot.prototype.callback_res_changed = function() { this.rrdflot_defaults.use_rra = false; this.drawFlotGraph(); }; rrdFlot.prototype.callback_ds_cb_changed = function() { this.drawFlotGraph(); }; rrdFlot.prototype.callback_scale_reset = function() { this.scale.clearSelection(); }; rrdFlot.prototype.callback_legend_changed = function() { this.drawFlotGraph(); }; rrdFlot.prototype.callback_timezone_changed = function() { this.drawFlotGraph(); }; rrdFlot.prototype.callback_elem_group_changed = function(num) { //,window_min,window_max) { var oCB=document.getElementById(this.ds_cb_id); var nrDSs=oCB.ds.length; if (oCB.ds.length>0) { for (var i=0; i