4 * Description: Template for writing new plugins and illustrating the different
5 * possibilities of the plugin API.
6 * This file is part of the Manifold project
7 * Requires: js/plugin.js
8 * URL: http://www.myslice.info
9 * Author: Jordan Augé <jordan.auge@lip6.fr>
10 * Copyright: Copyright 2012-2013 UPMC Sorbonne Universités
13 var SVG='http://www.w3.org/2000/svg';
15 var colorscale = d3.scale.category10().range(["green", "yellow", "red", "orange", "gray"]);
20 * Description: Widget that displays grid of checks. Uses
21 * d3 and jQuery to draw the grid.
23 * parentId: id string of the container element
24 * legendId: id string of the legend element
29 var MadDash = Plugin.extend({
32 * @brief Plugin constructor
33 * @param options : an associative array of setting values
35 * @return : a jQuery collection of objects on which the plugin is
36 * applied, which allows to maintain chainability of calls
38 init: function(options, element) {
39 // Call the parent constructor, see FAQ when forgotten
40 this._super(options, element);
43 /* Member variables */
44 //this.canvas = this.id('canvas');
45 this._legend = this.id('legend'); //legendId;
47 'un', 'deux', 'trois', 'quatre'
51 this._cellpadding = 2;
52 this._text_block_size = 130;
54 this._map_elements = {};
55 this._num_elements = 0;
57 this._max_width_elements = 50;
58 this._max_height_elements = 30;
60 this._buffer_key_list = new Buffer(this._process_key_list, this);
61 this._buffer_records = new Buffer(this._process_records, this);
64 this._canvas = d3.select("#" + options.plugin_uuid + '__canvas')
65 .style("width", this._max_width_elements * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
66 .style("height", this._max_height_elements * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
67 this._left_element = null;
68 this._top_element = null;
69 this._row_element = Array();
70 this._grid_element = null;
75 // this._buffer = Array();
76 // this._update_interval = 1000; /* ms to wait, 1000 = 1 second */
77 // setInterval(function(){
78 // var tmp = buffer; /* Switch the buffer out quickly so we aren't scrambled
79 // if you addToBuffer in the middle of this */
81 // $thisdocument.getElementById(htmlId).innerHTML = tmp.join("");
82 // //document.getElementById(htmlId).innerHTML = tmp.join("");
85 //addToBuffer = function(html){
91 /* Setup query and record handlers */
93 // Explain this will allow query events to be handled
94 // What happens when we don't define some events ?
95 // Some can be less efficient
96 this.listen_query(options.query_uuid);
97 this.listen_query(options.query_all_uuid, 'all');
99 /* GUI setup and event binding */
101 this._display_legends();
114 "Loss is 0",null,"Loss is greater than 0","Unable to retrieve data","Check has not yet run"
116 "lastUpdateTime":1385376538,
118 {"name":"200.128.79.100","uri":"/maddash/grids/OWAMP/200.128.79.100"},
119 {"name":"ata.larc.usp.br","uri":"/maddash/grids/OWAMP/ata.larc.usp.br"},
120 {"name":"mon-lt.fibre.cin.ufpe.br","uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br"}
123 "200.128.79.100","ata.larc.usp.br","mon-lt.fibre.cin.ufpe.br"
125 "checkNames": ["Loss","Loss Reverse"],
131 "message":" No one-way delay data returned for direction where src=200.128.79.100 dst=ata.larc.usp.br",
133 "prevCheckTime":1385376238,
134 "uri":"/maddash/grids/OWAMP/200.128.79.100/ata.larc.usp.br/Loss"
136 "message":" No one-way delay data returned for direction where src=ata.larc.usp.br dst=200.128.79.100",
137 "status":2,"prevCheckTime":1385376178,
138 "uri":"/maddash/grids/OWAMP/200.128.79.100/ata.larc.usp.br/Loss+Reverse"
140 "message":" Loss is 100.000% ",
142 "prevCheckTime":1385374877,
143 "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss"
145 "message":" Loss is 100.000% ",
147 "prevCheckTime":1385375498,
148 "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss+Reverse"
153 "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
155 "prevCheckTime":1385376037,
156 "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss"
158 "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
160 "prevCheckTime":1385376117,
161 "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss+Reverse"
165 "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
167 "prevCheckTime":1385376117,
168 "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss"
170 "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
172 "prevCheckTime":1385376017,
173 "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss+Reverse"
178 "message":" Loss is 100.000% ",
180 "prevCheckTime":1385376478,
181 "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss"
183 "message":" Loss is 100.000% ",
185 "prevCheckTime":1385375958,
186 "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss+Reverse"
188 "message":" No one-way delay data returned for direction where src=mon-lt.fibre.cin.ufpe.br dst=ata.larc.usp.br",
190 "prevCheckTime":1385376538,
191 "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss"
193 "message":" No one-way delay data returned for direction where src=ata.larc.usp.br dst=mon-lt.fibre.cin.ufpe.br",
195 "prevCheckTime":1385376358,
196 "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss+Reverse"
205 /* ------------------------------------------------------------------ */
207 /* ------------------------------------------------------------------ */
209 setClickHandler: function(f)
211 this._handleClick = f;
214 setCellSize: function(value)
216 this._cellSize = value;
219 setCellPadding: function(value)
221 this._cellPadding = value;
224 setTextBlockSize: function(value)
226 this._textBlockSize = value;
233 // on_show like in querytable
238 // a function to bind events here: click change
239 // how to raise manifold events
242 /* GUI MANIPULATION */
249 // see in the html template
250 // How to load a template, use of mustache
254 // How to make sure the plugin is not desynchronized
255 // He should manifest its interest in filters, fields or records
256 // functions triggered only if the proper listen is done
260 on_filter_added: function(filter)
265 // ... be sure to list all events here
267 /* RECORD HANDLERS */
268 on_all_new_record: function(record)
270 var key_value = record['hrn'];
271 if (!(this._map_elements.hasOwnProperty(key_value))) {
272 /* Add the key_value to the buffer to be drawn */
273 this._buffer_key_list.add(key_value);
274 /* Assign coordinates */
275 this._map_elements[key_value] = this._num_elements++;
277 /* Add the record to the buffer to be drawn */
278 this._buffer_records.add(record);
281 /* INTERNAL FUNCTIONS */
283 _render: function(data)
286 //d3.select("#dashboard_name").html(dashboard.name + " Dashboard");
287 // XXX OLD XXX d3.select("#" + this.parent).html("");
288 //this.elmt().html('')
290 this.display_component(data);
293 _init_left: function()
296 this._left_element = this._canvas.append("div")
297 .attr("class", "gleft")
298 .style("overflow-y", "scroll")
299 .style("height", "400px")
301 .attr("width", self._text_block_size)
302 .attr("height", self._max_height_elements * (self._cellsize + 2*self._cellpadding) + 1000)
306 _process_left: function(key_list)
310 this._left_element = this._left_element
315 .attr("class", function(d,i){return "grow" + i})
316 .attr("transform", function(d,i){return "translate(0,"+(i*(self._cellsize+2*self._cellpadding))+")"})
318 this._left_element.append("svg:rect")
319 .attr("class", function(d,i){return "grow" + i})
320 .attr("x",0).attr("y",0)
321 .attr("width",this._text_block_size).attr("height",(this._cellsize+2*this._cellpadding))
323 this._left_element.append("svg:text")
324 .attr("class", "gtext")
325 .attr("transform", "translate("+ (this._text_block_size-5) +",0)")
326 .text(function(d,i){return d}) //strdata.rows[i].name})
327 .attr("text-anchor", "end")
330 // XXX Let's generate fake records to create all rows
331 var records = Array();
332 for(var i = 0; i < key_list.length; i++) {
333 for(var j = 0; j < key_list.length; j++) {
335 'source': key_list[i],
336 'destination': key_list[j],
337 'value': Math.floor(Math.random()*4) /* 0 1 2 3 */
343 this._row_element = this._grid_element.selectAll(".grow")
347 .attr("class", function(d,i){return "grow grow" + i})
348 .style("width", "100%")
349 .style("z-index", 1000)
351 .style('position', 'absolute')
353 var cells = this._row_element.selectAll('.gcell')
354 .data(records) //function(d,r){ return d.map(function(d,i){ return {celldata:d, row:r}}) }, function(d) { return d['source'] + '--' + d['destination']; })
357 .attr("class", "gcell")
358 .style("height", self._cellsize +"px")
359 .style("width", self._cellsize +"px")
360 .style("margin", (self._cellpadding) +"px")
362 .on("mouseover", function(d,i){
363 selected_row = d.row; // We could find the row from the _map_elements
366 d3.select(this).style("margin", (self._cellpadding-1) +"px");
367 d3.select(this).classed("shadow", true);
369 this._canvas.selectAll(".gcol"+ i).classed("gactive", true);
370 this._canvas.selectAll(".grow"+ d.row).classed("gactive", true);
372 .on("mouseout", function(d,i){
373 // d3.select(this).classed("shadow", false);
374 // d3.select(this).style("background-color", color.brighter());
375 d3.select(this).style("margin", (self._cellpadding) +"px");
376 d3.select(this).classed("shadow", false);
377 this._canvas.selectAll(".gcol"+ i).classed("gactive", false);
378 this._canvas.selectAll(".grow"+ d.row).classed("gactive", false);
383 _init_top: function()
386 this._top_element = this._canvas.append("div")
387 .attr("class", "gtop")
388 .style("margin-left", self._text_block_size + "px")
389 .style("float", "left")
390 .style("overflow-x", "scroll")
391 .style("width", "960px")
393 .attr("height", self._text_block_size)
394 .attr("width", self._max_width_elements * (self._cellsize + 2*self._cellpadding) + 90 + 1000)
399 _process_top: function(key_list)
403 this._top_element = this._top_element
408 .attr("class", function(d,i){return "gcol" + i})
409 .attr("transform", function(d,i){ return "translate("+(i*(self._cellsize+2*self._cellpadding))+",0)"})
411 this._top_element.append("svg:rect")
412 .attr("class", function(d,i){return "gcol" + i})
413 .attr("x",0).attr("y",0)
414 .attr("transform", "rotate(45,0,"+ self._text_block_size +") translate (-0.5,3)")
415 .attr("height",self._text_block_size).attr("width",(self._cellsize+self._cellpadding))
416 //.attr("transform", "rotate(35,"+ (this._cellsize+this._cellpadding)/2 + "," + this._text_block_size/2 + ")")
419 this._top_element.append("svg:text")
420 .attr("class", "gtext")
421 .attr("text-anchor", "start")
424 .attr("transform", "rotate(-45,0,"+ self._text_block_size +") translate(0,"+ (self._text_block_size-5) + ")")
425 .text(function(d,i){return d})
427 // Create the columns...
428 var cols = this._grid_element.selectAll(".gcol")
432 .attr("class", function(d,i){return "gcol gcol" + i})
433 .style("width", (self._cellsize+2*self._cellpadding) + "px")
434 .style("height", "100%")
435 .style("left", function(d,i){return (i*(self._cellsize+2*self._cellpadding)) + "px"})
438 _process_key_list: function()
440 console.log("process key list");
441 var key_list = this._buffer_key_list.get();
442 this._process_top(key_list);
443 this._process_left(key_list);
444 console.log("process key list done");
447 _display_legends: function()
449 // Color scale = the number of different statuses
450 colorscale.domain(d3.range(0, this._labels.length));
453 var legendsdata = this._labels
454 .map(function(d,i){ return {label:d, color:colorscale(i)} })
455 //.filter(function(d,i){return d.label === null ? false : true})
457 // Clear and redraw legend
458 d3.select("#"+this._legend).html("")
460 var legends = d3.select("#"+this._legend)
461 .selectAll(".legend")
464 .append("div").attr("class", "legend");
465 legends.append("div")
466 .attr("class", "lsymbol")
467 .style("background", function(d,i){return d.color})
468 .style("display", function(d,i){return d.label === null ? "none" : "block"})
469 legends.append("div")
470 .attr("class", "ltext")
471 .text(function(d,i){return d.label})
472 .style("display", function(d,i){return d.label === null ? "none" : "block"})
475 _init_grid: function()
477 this._grid_element = this._canvas
478 .style("width", this._ncols * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
480 .attr("class", "ggrid")
482 .style('top', '165px')
483 .style('left', '145px')
484 .style('float', 'none')
485 .style('width', '2000px')
486 .style('height', '1000px')
490 _process_records: function()
493 var records = this._buffer_records.get();
494 console.log("processing records");
496 // XXX Let's generate fake records instead... NxN
497 var _records = Array();
498 for(var i = 0; i < records.length; i++) {
499 for(var j = 0; j < records.length; j++) {
500 if (Math.random() < 0.2) { /* one out of 5 */
502 'source': records[i]['hrn'],
503 'destination': records[j]['hrn'],
504 'value': Math.floor(Math.random()*4) /* 0 1 2 3 */
512 // Rows and columns have been created
515 var selected_row = 0;
516 var selected_col = 0;
517 var cells = this._row_element.selectAll(".gcell")
518 /* Not sure to understand this part of the code */
519 //uses data.grid initially... this is not good anymore
520 .data(_records, function(d) { return d['source'] + '--' + d['destination']; })
521 .style("background", function(d,i){
522 return colorscale(parseInt(d.value));
525 // Associate a tooltip to each cell thanks to tipsy
527 this.elmt().find(".gcell").each(function(i,d){
528 var data = d3.select(this).data()[0];
529 if(data.celldata!=null){
530 var html = "<div class='tooltip'><div class='top-tip'>" + (data.celldata[0]? data.celldata[0].message : "") + "</div><div class='bottom-tip'>" + (data.celldata[1]? data.celldata[1].message : "") + "</div></div>";
541 // This seems to create the coloured subcells
542 var temp = cells.selectAll(".gsubcell")
543 .data(function(d,i){return d.celldata===null? [] : d.celldata })
547 .style("height", this._cellsize/2 +"px")
548 .style("background", function(d,i){
549 return colorscale(parseInt(d.status));
551 .on("click", function(d,i){ //CHANGE FROM PORTAL
553 if(d!=null && d.uri!=null && self.handleClick != null){
557 console.log("processing records done");
560 display_component: function(data)
562 this._display_grid_container(data);
565 _handleClick: function(d)
568 $.getJSON(uri, function(data) {
569 var href = data['history'][0].returnParams.graphUrl.replace("https", "http");
570 window.open( href, "Graph", "menubar=0,location=0,height=700,width=700" );
578 /* Plugin registration */
579 $.plugin('MadDash', MadDash);
581 // TODO Here use cases for instanciating plugins in different ways like in the pastie.