X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=plugins%2Fmaddash%2Fstatic%2Fjs%2Fmaddash.js;fp=plugins%2Fmaddash%2Fstatic%2Fjs%2Fmaddash.js;h=110eec13155656a3d5c1c94d3753ea1b83c8cb72;hb=0feaf648c801631483b4889b7b00c1d6b01fb253;hp=0000000000000000000000000000000000000000;hpb=f7ed6e816b00fabaf57d2946638a988a3043c3f5;p=myslice.git diff --git a/plugins/maddash/static/js/maddash.js b/plugins/maddash/static/js/maddash.js new file mode 100644 index 00000000..110eec13 --- /dev/null +++ b/plugins/maddash/static/js/maddash.js @@ -0,0 +1,583 @@ +/** + * MyPlugin: MadDash + * Version: 0.1 + * Description: Template for writing new plugins and illustrating the different + * possibilities of the plugin API. + * This file is part of the Manifold project + * Requires: js/plugin.js + * URL: http://www.myslice.info + * Author: Jordan Augé + * Copyright: Copyright 2012-2013 UPMC Sorbonne Universités + * License: GPLv3 + */ +var SVG='http://www.w3.org/2000/svg'; +var instance = this; +var colorscale = d3.scale.category10().range(["green", "yellow", "red", "orange", "gray"]); + +/* XXX */ +/** + * Class: MaDDashGrid + * Description: Widget that displays grid of checks. Uses + * d3 and jQuery to draw the grid. + * Parameters: + * parentId: id string of the container element + * legendId: id string of the legend element + */ + +(function($){ + + var MadDash = Plugin.extend({ + + /** XXX to check + * @brief Plugin constructor + * @param options : an associative array of setting values + * @param element : + * @return : a jQuery collection of objects on which the plugin is + * applied, which allows to maintain chainability of calls + */ + init: function(options, element) { + // Call the parent constructor, see FAQ when forgotten + this._super(options, element); + + + /* Member variables */ + //this.canvas = this.id('canvas'); + this._legend = this.id('legend'); //legendId; + this._labels = Array( + 'un', 'deux', 'trois', 'quatre' + ) + + this._cellsize = 13; + this._cellpadding = 2; + this._text_block_size = 130; + + this._map_elements = {}; + this._num_elements = 0; + + this._max_width_elements = 50; + this._max_height_elements = 30; + + this._buffer_key_list = new Buffer(this._process_key_list, this); + this._buffer_records = new Buffer(this._process_records, this); + + /* Pointers */ + this._canvas = d3.select("#" + options.plugin_uuid + '__canvas') + .style("width", this._max_width_elements * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size) + .style("height", this._max_height_elements * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size) + this._left_element = null; + this._top_element = null; + this._row_element = Array(); + this._grid_element = null; + + + + /* Buffered input */ +// this._buffer = Array(); +// this._update_interval = 1000; /* ms to wait, 1000 = 1 second */ +// setInterval(function(){ +// var tmp = buffer; /* Switch the buffer out quickly so we aren't scrambled +// if you addToBuffer in the middle of this */ +// buffer = Array(); +// $thisdocument.getElementById(htmlId).innerHTML = tmp.join(""); +// //document.getElementById(htmlId).innerHTML = tmp.join(""); +// }, wait); +// +//addToBuffer = function(html){ +// buffer.push(html); +//}; + + /* Plugin events */ + + /* Setup query and record handlers */ + + // Explain this will allow query events to be handled + // What happens when we don't define some events ? + // Some can be less efficient + this.listen_query(options.query_uuid); + this.listen_query(options.query_all_uuid, 'all'); + + /* GUI setup and event binding */ + // call function + this._display_legends(); + this._init_top(); + this._init_left(); + this._init_grid(); + //this._test(); + + }, + + _test: function() + { + data = { + "name":"OWAMP", + "statusLabels":[ + "Loss is 0",null,"Loss is greater than 0","Unable to retrieve data","Check has not yet run" + ], + "lastUpdateTime":1385376538, + "rows":[ + {"name":"200.128.79.100","uri":"/maddash/grids/OWAMP/200.128.79.100"}, + {"name":"ata.larc.usp.br","uri":"/maddash/grids/OWAMP/ata.larc.usp.br"}, + {"name":"mon-lt.fibre.cin.ufpe.br","uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br"} + ], + "columnNames":[ + "200.128.79.100","ata.larc.usp.br","mon-lt.fibre.cin.ufpe.br" + ], + "checkNames": ["Loss","Loss Reverse"], + "grid": [ + [ + /* First line */ + null, + [{ + "message":" No one-way delay data returned for direction where src=200.128.79.100 dst=ata.larc.usp.br", + "status":3, + "prevCheckTime":1385376238, + "uri":"/maddash/grids/OWAMP/200.128.79.100/ata.larc.usp.br/Loss" + },{ + "message":" No one-way delay data returned for direction where src=ata.larc.usp.br dst=200.128.79.100", + "status":2,"prevCheckTime":1385376178, + "uri":"/maddash/grids/OWAMP/200.128.79.100/ata.larc.usp.br/Loss+Reverse" + }],[{ + "message":" Loss is 100.000% ", + "status":2, + "prevCheckTime":1385374877, + "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss" + },{ + "message":" Loss is 100.000% ", + "status":2, + "prevCheckTime":1385375498, + "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss+Reverse" + }] + ],[ + /* Second line */ + [{ + "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.", + "status":3, + "prevCheckTime":1385376037, + "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss" + }, { + "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.", + "status":3, + "prevCheckTime":1385376117, + "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss+Reverse" + }], + null, + [{ + "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.", + "status":3, + "prevCheckTime":1385376117, + "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss" + }, { + "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.", + "status":3, + "prevCheckTime":1385376017, + "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss+Reverse" + }] + ],[ + /* Third line */ + [{ + "message":" Loss is 100.000% ", + "status":2, + "prevCheckTime":1385376478, + "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss" + },{ + "message":" Loss is 100.000% ", + "status":2, + "prevCheckTime":1385375958, + "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss+Reverse" + }], [{ + "message":" No one-way delay data returned for direction where src=mon-lt.fibre.cin.ufpe.br dst=ata.larc.usp.br", + "status":3, + "prevCheckTime":1385376538, + "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss" + },{ + "message":" No one-way delay data returned for direction where src=ata.larc.usp.br dst=mon-lt.fibre.cin.ufpe.br", + "status":3, + "prevCheckTime":1385376358, + "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss+Reverse" + }], + null + ] + ] + } + this._render(data); + }, + + /* ------------------------------------------------------------------ */ + /* Accessors */ + /* ------------------------------------------------------------------ */ + + setClickHandler: function(f) + { + this._handleClick = f; + }, + + setCellSize: function(value) + { + this._cellSize = value; + }, + + setCellPadding: function(value) + { + this._cellPadding = value; + }, + + setTextBlockSize: function(value) + { + this._textBlockSize = value; + }, + + + + + /* PLUGIN EVENTS */ + // on_show like in querytable + + + /* GUI EVENTS */ + + // a function to bind events here: click change + // how to raise manifold events + + + /* GUI MANIPULATION */ + + /* XXX */ + + + /* TEMPLATES */ + + // see in the html template + // How to load a template, use of mustache + + /* QUERY HANDLERS */ + + // How to make sure the plugin is not desynchronized + // He should manifest its interest in filters, fields or records + // functions triggered only if the proper listen is done + + // no prefix + + on_filter_added: function(filter) + { + + }, + + // ... be sure to list all events here + + /* RECORD HANDLERS */ + on_all_new_record: function(record) + { + var key_value = record['hrn']; + if (!(this._map_elements.hasOwnProperty(key_value))) { + /* Add the key_value to the buffer to be drawn */ + this._buffer_key_list.add(key_value); + /* Assign coordinates */ + this._map_elements[key_value] = this._num_elements++; + } + /* Add the record to the buffer to be drawn */ + this._buffer_records.add(record); + }, + + /* INTERNAL FUNCTIONS */ + + _render: function(data) + { + //TODO: Set title + //d3.select("#dashboard_name").html(dashboard.name + " Dashboard"); + // XXX OLD XXX d3.select("#" + this.parent).html(""); + //this.elmt().html('') + + this.display_component(data); + }, + + _init_left: function() + { + var self = this; + this._left_element = this._canvas.append("div") + .attr("class", "gleft") + .style("overflow-y", "scroll") + .style("height", "400px") + .append("svg:svg") + .attr("width", self._text_block_size) + .attr("height", self._max_height_elements * (self._cellsize + 2*self._cellpadding) + 1000) + + }, + + _process_left: function(key_list) + { + var self = this; + + this._left_element = this._left_element + .selectAll(".rname") + .data(key_list) + .enter() + .append("g") + .attr("class", function(d,i){return "grow" + i}) + .attr("transform", function(d,i){return "translate(0,"+(i*(self._cellsize+2*self._cellpadding))+")"}) + + this._left_element.append("svg:rect") + .attr("class", function(d,i){return "grow" + i}) + .attr("x",0).attr("y",0) + .attr("width",this._text_block_size).attr("height",(this._cellsize+2*this._cellpadding)) + + this._left_element.append("svg:text") + .attr("class", "gtext") + .attr("transform", "translate("+ (this._text_block_size-5) +",0)") + .text(function(d,i){return d}) //strdata.rows[i].name}) + .attr("text-anchor", "end") + .attr("dy", "1.1em") + + // XXX Let's generate fake records to create all rows + var records = Array(); + for(var i = 0; i < key_list.length; i++) { + for(var j = 0; j < key_list.length; j++) { + records.push({ + 'source': key_list[i], + 'destination': key_list[j], + 'value': Math.floor(Math.random()*4) /* 0 1 2 3 */ + }); + } + } + + // Create the rows + this._row_element = this._grid_element.selectAll(".grow") + .data(key_list) + .enter() + .append("div") + .attr("class", function(d,i){return "grow grow" + i}) + .style("width", "100%") + .style("z-index", 1000) + // jordan + .style('position', 'absolute') +/* + var cells = this._row_element.selectAll('.gcell') + .data(records) //function(d,r){ return d.map(function(d,i){ return {celldata:d, row:r}}) }, function(d) { return d['source'] + '--' + d['destination']; }) + .enter() + .append("div") + .attr("class", "gcell") + .style("height", self._cellsize +"px") + .style("width", self._cellsize +"px") + .style("margin", (self._cellpadding) +"px") + + .on("mouseover", function(d,i){ + selected_row = d.row; // We could find the row from the _map_elements + selected_col = i; + if(d.celldata){ + d3.select(this).style("margin", (self._cellpadding-1) +"px"); + d3.select(this).classed("shadow", true); + } + this._canvas.selectAll(".gcol"+ i).classed("gactive", true); + this._canvas.selectAll(".grow"+ d.row).classed("gactive", true); + }) + .on("mouseout", function(d,i){ + // d3.select(this).classed("shadow", false); + // d3.select(this).style("background-color", color.brighter()); + d3.select(this).style("margin", (self._cellpadding) +"px"); + d3.select(this).classed("shadow", false); + this._canvas.selectAll(".gcol"+ i).classed("gactive", false); + this._canvas.selectAll(".grow"+ d.row).classed("gactive", false); + }) +*/ + }, + + _init_top: function() + { + var self = this; + this._top_element = this._canvas.append("div") + .attr("class", "gtop") + .style("margin-left", self._text_block_size + "px") + .style("float", "left") + .style("overflow-x", "scroll") + .style("width", "960px") + .append("svg:svg") + .attr("height", self._text_block_size) + .attr("width", self._max_width_elements * (self._cellsize + 2*self._cellpadding) + 90 + 1000) + + }, + + + _process_top: function(key_list) + { + var self = this; + + this._top_element = this._top_element + .selectAll(".rname") + .data(key_list) + .enter() + .append("g") + .attr("class", function(d,i){return "gcol" + i}) + .attr("transform", function(d,i){ return "translate("+(i*(self._cellsize+2*self._cellpadding))+",0)"}) + + this._top_element.append("svg:rect") + .attr("class", function(d,i){return "gcol" + i}) + .attr("x",0).attr("y",0) + .attr("transform", "rotate(45,0,"+ self._text_block_size +") translate (-0.5,3)") + .attr("height",self._text_block_size).attr("width",(self._cellsize+self._cellpadding)) + //.attr("transform", "rotate(35,"+ (this._cellsize+this._cellpadding)/2 + "," + this._text_block_size/2 + ")") + + + this._top_element.append("svg:text") + .attr("class", "gtext") + .attr("text-anchor", "start") + .attr("dy", "1.5em") + .attr("dx", "1em") + .attr("transform", "rotate(-45,0,"+ self._text_block_size +") translate(0,"+ (self._text_block_size-5) + ")") + .text(function(d,i){return d}) + + // Create the columns... + var cols = this._grid_element.selectAll(".gcol") + .data(key_list) + .enter() + .append("div") + .attr("class", function(d,i){return "gcol gcol" + i}) + .style("width", (self._cellsize+2*self._cellpadding) + "px") + .style("height", "100%") + .style("left", function(d,i){return (i*(self._cellsize+2*self._cellpadding)) + "px"}) + }, + + _process_key_list: function() + { + console.log("process key list"); + var key_list = this._buffer_key_list.get(); + this._process_top(key_list); + this._process_left(key_list); + console.log("process key list done"); + }, + + _display_legends: function() + { + // Color scale = the number of different statuses + colorscale.domain(d3.range(0, this._labels.length)); + + // Labels + var legendsdata = this._labels + .map(function(d,i){ return {label:d, color:colorscale(i)} }) + //.filter(function(d,i){return d.label === null ? false : true}) + + // Clear and redraw legend + d3.select("#"+this._legend).html("") + + var legends = d3.select("#"+this._legend) + .selectAll(".legend") + .data(legendsdata) + .enter() + .append("div").attr("class", "legend"); + legends.append("div") + .attr("class", "lsymbol") + .style("background", function(d,i){return d.color}) + .style("display", function(d,i){return d.label === null ? "none" : "block"}) + legends.append("div") + .attr("class", "ltext") + .text(function(d,i){return d.label}) + .style("display", function(d,i){return d.label === null ? "none" : "block"}) + }, + + _init_grid: function() + { + this._grid_element = this._canvas + .style("width", this._ncols * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size) + .append("div") + .attr("class", "ggrid") + // jordan + .style('top', '165px') + .style('left', '145px') + .style('float', 'none') + .style('width', '2000px') + .style('height', '1000px') + + }, + + _process_records: function() + { + var self = this; + var records = this._buffer_records.get(); + console.log("processing records"); + + // XXX Let's generate fake records instead... NxN + var _records = Array(); + for(var i = 0; i < records.length; i++) { + for(var j = 0; j < records.length; j++) { + if (Math.random() < 0.2) { /* one out of 5 */ + _records.push({ + 'source': records[i]['hrn'], + 'destination': records[j]['hrn'], + 'value': Math.floor(Math.random()*4) /* 0 1 2 3 */ + }) + } + } + } + + // + + // Rows and columns have been created + + var color = ""; + var selected_row = 0; + var selected_col = 0; + var cells = this._row_element.selectAll(".gcell") + /* Not sure to understand this part of the code */ + //uses data.grid initially... this is not good anymore + .data(_records, function(d) { return d['source'] + '--' + d['destination']; }) + .style("background", function(d,i){ + return colorscale(parseInt(d.value)); + }) + + // Associate a tooltip to each cell thanks to tipsy + /* + this.elmt().find(".gcell").each(function(i,d){ + var data = d3.select(this).data()[0]; + if(data.celldata!=null){ + var html = "
" + (data.celldata[0]? data.celldata[0].message : "") + "
" + (data.celldata[1]? data.celldata[1].message : "") + "
"; + $(this).tipsy({ + html :true, + opacity: 0.9, + title : function(){ + return html + }}) + } + }) + */ + + // This seems to create the coloured subcells + var temp = cells.selectAll(".gsubcell") + .data(function(d,i){return d.celldata===null? [] : d.celldata }) + .enter() + .append("div"); + temp + .style("height", this._cellsize/2 +"px") + .style("background", function(d,i){ + return colorscale(parseInt(d.status)); + }) + .on("click", function(d,i){ //CHANGE FROM PORTAL + var that = this; + if(d!=null && d.uri!=null && self.handleClick != null){ + self.handleClick(d); + } + }) + console.log("processing records done"); + }, + + display_component: function(data) + { + this._display_grid_container(data); + }, + + _handleClick: function(d) + { + var uri = d.uri; + $.getJSON(uri, function(data) { + var href = data['history'][0].returnParams.graphUrl.replace("https", "http"); + window.open( href, "Graph", "menubar=0,location=0,height=700,width=700" ); + }) + + } + + + }); + + /* Plugin registration */ + $.plugin('MadDash', MadDash); + + // TODO Here use cases for instanciating plugins in different ways like in the pastie. + +})(jQuery);