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.classname="maddash";
41 this._super(options, element);
44 /* Member variables */
45 //this.canvas = this.id('canvas');
46 this._legend = this.id('legend'); //legendId;
48 'un', 'deux', 'trois', 'quatre'
52 this._cellpadding = 2;
53 this._text_block_size = 130;
55 this._map_elements = {};
56 this._num_elements = 0;
58 this._max_width_elements = 50;
59 this._max_height_elements = 30;
61 this._buffer_key_list = new Buffer(this._process_key_list, this);
62 this._buffer_records = new Buffer(this._process_records, this);
65 this._canvas = d3.select("#" + options.plugin_uuid + '__canvas')
66 .style("width", this._max_width_elements * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
67 .style("height", this._max_height_elements * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
68 this._left_element = null;
69 this._top_element = null;
70 this._row_element = Array();
71 this._grid_element = null;
76 // this._buffer = Array();
77 // this._update_interval = 1000; /* ms to wait, 1000 = 1 second */
78 // setInterval(function(){
79 // var tmp = buffer; /* Switch the buffer out quickly so we aren't scrambled
80 // if you addToBuffer in the middle of this */
82 // $thisdocument.getElementById(htmlId).innerHTML = tmp.join("");
83 // //document.getElementById(htmlId).innerHTML = tmp.join("");
86 //addToBuffer = function(html){
92 /* Setup query and record handlers */
94 // Explain this will allow query events to be handled
95 // What happens when we don't define some events ?
96 // Some can be less efficient
97 this.listen_query(options.query_uuid);
98 this.listen_query(options.query_all_uuid, 'all');
100 /* GUI setup and event binding */
102 this._display_legends();
115 "Loss is 0",null,"Loss is greater than 0","Unable to retrieve data","Check has not yet run"
117 "lastUpdateTime":1385376538,
119 {"name":"200.128.79.100","uri":"/maddash/grids/OWAMP/200.128.79.100"},
120 {"name":"ata.larc.usp.br","uri":"/maddash/grids/OWAMP/ata.larc.usp.br"},
121 {"name":"mon-lt.fibre.cin.ufpe.br","uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br"}
124 "200.128.79.100","ata.larc.usp.br","mon-lt.fibre.cin.ufpe.br"
126 "checkNames": ["Loss","Loss Reverse"],
132 "message":" No one-way delay data returned for direction where src=200.128.79.100 dst=ata.larc.usp.br",
134 "prevCheckTime":1385376238,
135 "uri":"/maddash/grids/OWAMP/200.128.79.100/ata.larc.usp.br/Loss"
137 "message":" No one-way delay data returned for direction where src=ata.larc.usp.br dst=200.128.79.100",
138 "status":2,"prevCheckTime":1385376178,
139 "uri":"/maddash/grids/OWAMP/200.128.79.100/ata.larc.usp.br/Loss+Reverse"
141 "message":" Loss is 100.000% ",
143 "prevCheckTime":1385374877,
144 "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss"
146 "message":" Loss is 100.000% ",
148 "prevCheckTime":1385375498,
149 "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss+Reverse"
154 "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
156 "prevCheckTime":1385376037,
157 "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss"
159 "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
161 "prevCheckTime":1385376117,
162 "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss+Reverse"
166 "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
168 "prevCheckTime":1385376117,
169 "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss"
171 "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
173 "prevCheckTime":1385376017,
174 "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss+Reverse"
179 "message":" Loss is 100.000% ",
181 "prevCheckTime":1385376478,
182 "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss"
184 "message":" Loss is 100.000% ",
186 "prevCheckTime":1385375958,
187 "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss+Reverse"
189 "message":" No one-way delay data returned for direction where src=mon-lt.fibre.cin.ufpe.br dst=ata.larc.usp.br",
191 "prevCheckTime":1385376538,
192 "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss"
194 "message":" No one-way delay data returned for direction where src=ata.larc.usp.br dst=mon-lt.fibre.cin.ufpe.br",
196 "prevCheckTime":1385376358,
197 "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss+Reverse"
206 /* ------------------------------------------------------------------ */
208 /* ------------------------------------------------------------------ */
210 setClickHandler: function(f)
212 this._handleClick = f;
215 setCellSize: function(value)
217 this._cellSize = value;
220 setCellPadding: function(value)
222 this._cellPadding = value;
225 setTextBlockSize: function(value)
227 this._textBlockSize = value;
234 // on_show like in querytable
239 // a function to bind events here: click change
240 // how to raise manifold events
243 /* GUI MANIPULATION */
250 // see in the html template
251 // How to load a template, use of mustache
255 // How to make sure the plugin is not desynchronized
256 // He should manifest its interest in filters, fields or records
257 // functions triggered only if the proper listen is done
261 on_filter_added: function(filter)
266 // ... be sure to list all events here
268 /* RECORD HANDLERS */
269 on_all_new_record: function(record)
271 var key_value = record['hrn'];
272 if (!(this._map_elements.hasOwnProperty(key_value))) {
273 /* Add the key_value to the buffer to be drawn */
274 this._buffer_key_list.add(key_value);
275 /* Assign coordinates */
276 this._map_elements[key_value] = this._num_elements++;
278 /* Add the record to the buffer to be drawn */
279 this._buffer_records.add(record);
282 /* INTERNAL FUNCTIONS */
284 _render: function(data)
287 //d3.select("#dashboard_name").html(dashboard.name + " Dashboard");
288 // XXX OLD XXX d3.select("#" + this.parent).html("");
289 //this.elmt().html('')
291 this.display_component(data);
294 _init_left: function()
297 this._left_element = this._canvas.append("div")
298 .attr("class", "gleft")
299 .style("overflow-y", "scroll")
300 .style("height", "400px")
302 .attr("width", self._text_block_size)
303 .attr("height", self._max_height_elements * (self._cellsize + 2*self._cellpadding) + 1000)
307 _process_left: function(key_list)
311 this._left_element = this._left_element
316 .attr("class", function(d,i){return "grow" + i})
317 .attr("transform", function(d,i){return "translate(0,"+(i*(self._cellsize+2*self._cellpadding))+")"})
319 this._left_element.append("svg:rect")
320 .attr("class", function(d,i){return "grow" + i})
321 .attr("x",0).attr("y",0)
322 .attr("width",this._text_block_size).attr("height",(this._cellsize+2*this._cellpadding))
324 this._left_element.append("svg:text")
325 .attr("class", "gtext")
326 .attr("transform", "translate("+ (this._text_block_size-5) +",0)")
327 .text(function(d,i){return d}) //strdata.rows[i].name})
328 .attr("text-anchor", "end")
331 // XXX Let's generate fake records to create all rows
332 var records = Array();
333 for(var i = 0; i < key_list.length; i++) {
334 for(var j = 0; j < key_list.length; j++) {
336 'source': key_list[i],
337 'destination': key_list[j],
338 'value': Math.floor(Math.random()*4) /* 0 1 2 3 */
344 this._row_element = this._grid_element.selectAll(".grow")
348 .attr("class", function(d,i){return "grow grow" + i})
349 .style("width", "100%")
350 .style("z-index", 1000)
352 .style('position', 'absolute')
354 var cells = this._row_element.selectAll('.gcell')
355 .data(records) //function(d,r){ return d.map(function(d,i){ return {celldata:d, row:r}}) }, function(d) { return d['source'] + '--' + d['destination']; })
358 .attr("class", "gcell")
359 .style("height", self._cellsize +"px")
360 .style("width", self._cellsize +"px")
361 .style("margin", (self._cellpadding) +"px")
363 .on("mouseover", function(d,i){
364 selected_row = d.row; // We could find the row from the _map_elements
367 d3.select(this).style("margin", (self._cellpadding-1) +"px");
368 d3.select(this).classed("shadow", true);
370 this._canvas.selectAll(".gcol"+ i).classed("gactive", true);
371 this._canvas.selectAll(".grow"+ d.row).classed("gactive", true);
373 .on("mouseout", function(d,i){
374 // d3.select(this).classed("shadow", false);
375 // d3.select(this).style("background-color", color.brighter());
376 d3.select(this).style("margin", (self._cellpadding) +"px");
377 d3.select(this).classed("shadow", false);
378 this._canvas.selectAll(".gcol"+ i).classed("gactive", false);
379 this._canvas.selectAll(".grow"+ d.row).classed("gactive", false);
384 _init_top: function()
387 this._top_element = this._canvas.append("div")
388 .attr("class", "gtop")
389 .style("margin-left", self._text_block_size + "px")
390 .style("float", "left")
391 .style("overflow-x", "scroll")
392 .style("width", "960px")
394 .attr("height", self._text_block_size)
395 .attr("width", self._max_width_elements * (self._cellsize + 2*self._cellpadding) + 90 + 1000)
400 _process_top: function(key_list)
404 this._top_element = this._top_element
409 .attr("class", function(d,i){return "gcol" + i})
410 .attr("transform", function(d,i){ return "translate("+(i*(self._cellsize+2*self._cellpadding))+",0)"})
412 this._top_element.append("svg:rect")
413 .attr("class", function(d,i){return "gcol" + i})
414 .attr("x",0).attr("y",0)
415 .attr("transform", "rotate(45,0,"+ self._text_block_size +") translate (-0.5,3)")
416 .attr("height",self._text_block_size).attr("width",(self._cellsize+self._cellpadding))
417 //.attr("transform", "rotate(35,"+ (this._cellsize+this._cellpadding)/2 + "," + this._text_block_size/2 + ")")
420 this._top_element.append("svg:text")
421 .attr("class", "gtext")
422 .attr("text-anchor", "start")
425 .attr("transform", "rotate(-45,0,"+ self._text_block_size +") translate(0,"+ (self._text_block_size-5) + ")")
426 .text(function(d,i){return d})
428 // Create the columns...
429 var cols = this._grid_element.selectAll(".gcol")
433 .attr("class", function(d,i){return "gcol gcol" + i})
434 .style("width", (self._cellsize+2*self._cellpadding) + "px")
435 .style("height", "100%")
436 .style("left", function(d,i){return (i*(self._cellsize+2*self._cellpadding)) + "px"})
439 _process_key_list: function()
441 console.log("process key list");
442 var key_list = this._buffer_key_list.get();
443 this._process_top(key_list);
444 this._process_left(key_list);
445 console.log("process key list done");
448 _display_legends: function()
450 // Color scale = the number of different statuses
451 colorscale.domain(d3.range(0, this._labels.length));
454 var legendsdata = this._labels
455 .map(function(d,i){ return {label:d, color:colorscale(i)} })
456 //.filter(function(d,i){return d.label === null ? false : true})
458 // Clear and redraw legend
459 d3.select("#"+this._legend).html("")
461 var legends = d3.select("#"+this._legend)
462 .selectAll(".legend")
465 .append("div").attr("class", "legend");
466 legends.append("div")
467 .attr("class", "lsymbol")
468 .style("background", function(d,i){return d.color})
469 .style("display", function(d,i){return d.label === null ? "none" : "block"})
470 legends.append("div")
471 .attr("class", "ltext")
472 .text(function(d,i){return d.label})
473 .style("display", function(d,i){return d.label === null ? "none" : "block"})
476 _init_grid: function()
478 this._grid_element = this._canvas
479 .style("width", this._ncols * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
481 .attr("class", "ggrid")
483 .style('top', '165px')
484 .style('left', '145px')
485 .style('float', 'none')
486 .style('width', '2000px')
487 .style('height', '1000px')
491 _process_records: function()
494 var records = this._buffer_records.get();
495 console.log("processing records");
497 // XXX Let's generate fake records instead... NxN
498 var _records = Array();
499 for(var i = 0; i < records.length; i++) {
500 for(var j = 0; j < records.length; j++) {
501 if (Math.random() < 0.2) { /* one out of 5 */
503 'source': records[i]['hrn'],
504 'destination': records[j]['hrn'],
505 'value': Math.floor(Math.random()*4) /* 0 1 2 3 */
513 // Rows and columns have been created
516 var selected_row = 0;
517 var selected_col = 0;
518 var cells = this._row_element.selectAll(".gcell")
519 /* Not sure to understand this part of the code */
520 //uses data.grid initially... this is not good anymore
521 .data(_records, function(d) { return d['source'] + '--' + d['destination']; })
522 .style("background", function(d,i){
523 return colorscale(parseInt(d.value));
526 // Associate a tooltip to each cell thanks to tipsy
528 this.elmt().find(".gcell").each(function(i,d){
529 var data = d3.select(this).data()[0];
530 if(data.celldata!=null){
531 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>";
542 // This seems to create the coloured subcells
543 var temp = cells.selectAll(".gsubcell")
544 .data(function(d,i){return d.celldata===null? [] : d.celldata })
548 .style("height", this._cellsize/2 +"px")
549 .style("background", function(d,i){
550 return colorscale(parseInt(d.status));
552 .on("click", function(d,i){ //CHANGE FROM PORTAL
554 if(d!=null && d.uri!=null && self.handleClick != null){
558 console.log("processing records done");
561 display_component: function(data)
563 this._display_grid_container(data);
566 _handleClick: function(d)
569 $.getJSON(uri, function(data) {
570 var href = data['history'][0].returnParams.graphUrl.replace("https", "http");
571 window.open( href, "Graph", "menubar=0,location=0,height=700,width=700" );
579 /* Plugin registration */
580 $.plugin('MadDash', MadDash);
582 // TODO Here use cases for instanciating plugins in different ways like in the pastie.