added draft maddash plugin
[myslice.git] / plugins / maddash / static / js / maddash.js
1 /**
2  * MyPlugin:    MadDash
3  * Version:     0.1
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
11  * License:     GPLv3
12  */
13 var SVG='http://www.w3.org/2000/svg';
14 var instance = this;
15 var colorscale = d3.scale.category10().range(["green", "yellow", "red", "orange", "gray"]);
16
17 /* XXX */
18 /**
19  * Class: MaDDashGrid
20  * Description: Widget that displays grid of checks. Uses
21  *   d3 and jQuery to draw the grid.
22  * Parameters:
23  *      parentId: id string of the container element
24  *      legendId: id string of the legend element
25  */
26
27 (function($){
28
29     var MadDash = Plugin.extend({
30
31         /** XXX to check
32          * @brief Plugin constructor
33          * @param options : an associative array of setting values
34          * @param element : 
35          * @return : a jQuery collection of objects on which the plugin is
36          *     applied, which allows to maintain chainability of calls
37          */
38         init: function(options, element) {
39             // Call the parent constructor, see FAQ when forgotten
40             this._super(options, element);
41
42
43             /* Member variables */
44             //this.canvas = this.id('canvas');
45             this._legend = this.id('legend'); //legendId;
46             this._labels = Array(
47                 'un', 'deux', 'trois', 'quatre'
48             )
49
50             this._cellsize = 13;
51             this._cellpadding = 2;
52             this._text_block_size = 130;
53
54             this._map_elements = {};
55             this._num_elements = 0;
56
57             this._max_width_elements  = 50;
58             this._max_height_elements = 30;
59
60             this._buffer_key_list = new Buffer(this._process_key_list, this);
61             this._buffer_records  = new Buffer(this._process_records, this);
62
63             /* Pointers */
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;
71             
72
73
74             /* Buffered input */
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 */
80 //                buffer = Array();
81 //                $thisdocument.getElementById(htmlId).innerHTML = tmp.join("");
82 //                //document.getElementById(htmlId).innerHTML = tmp.join("");
83 //            }, wait);
84 //
85 //addToBuffer = function(html){
86 //    buffer.push(html);
87 //};
88
89             /* Plugin events */
90
91             /* Setup query and record handlers */
92
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');
98
99             /* GUI setup and event binding */
100             // call function
101             this._display_legends();
102             this._init_top();
103             this._init_left();
104             this._init_grid();
105             //this._test();
106
107         },
108
109         _test: function()
110         {
111             data = {
112                 "name":"OWAMP",
113                 "statusLabels":[
114                     "Loss is 0",null,"Loss is greater than 0","Unable to retrieve data","Check has not yet run"
115                 ],
116                 "lastUpdateTime":1385376538,
117                 "rows":[
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"}
121                 ],
122                 "columnNames":[
123                     "200.128.79.100","ata.larc.usp.br","mon-lt.fibre.cin.ufpe.br"
124                 ],
125                 "checkNames": ["Loss","Loss Reverse"],
126                 "grid": [
127                     [
128                         /* First line */
129                         null,
130                         [{
131                             "message":" No one-way delay data returned for direction where src=200.128.79.100 dst=ata.larc.usp.br",
132                             "status":3,
133                             "prevCheckTime":1385376238,
134                             "uri":"/maddash/grids/OWAMP/200.128.79.100/ata.larc.usp.br/Loss"
135                         },{
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"
139                         }],[{
140                             "message":" Loss is 100.000% ",
141                             "status":2,
142                             "prevCheckTime":1385374877,
143                             "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss"
144                         },{
145                             "message":" Loss is 100.000% ",
146                             "status":2,
147                             "prevCheckTime":1385375498,
148                             "uri":"/maddash/grids/OWAMP/200.128.79.100/mon-lt.fibre.cin.ufpe.br/Loss+Reverse"
149                         }]
150                     ],[
151                         /* Second line */
152                         [{
153                             "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
154                             "status":3,
155                             "prevCheckTime":1385376037,
156                             "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss"
157                         }, {
158                             "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
159                             "status":3,
160                             "prevCheckTime":1385376117,
161                             "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/200.128.79.100/Loss+Reverse"
162                         }],
163                         null,
164                         [{
165                             "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
166                             "status":3,
167                             "prevCheckTime":1385376117,
168                             "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss"
169                         }, {
170                             "message":" Unable to contact MA. Please check that the MA is running and the URL is correct.",
171                             "status":3,
172                             "prevCheckTime":1385376017,
173                             "uri":"/maddash/grids/OWAMP/ata.larc.usp.br/mon-lt.fibre.cin.ufpe.br/Loss+Reverse"
174                         }]
175                     ],[
176                         /* Third line */
177                         [{
178                             "message":" Loss is 100.000% ",
179                             "status":2,
180                             "prevCheckTime":1385376478,
181                             "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss"
182                         },{
183                             "message":" Loss is 100.000% ",
184                             "status":2,
185                             "prevCheckTime":1385375958,
186                             "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/200.128.79.100/Loss+Reverse"
187                         }], [{
188                             "message":" No one-way delay data returned for direction where src=mon-lt.fibre.cin.ufpe.br dst=ata.larc.usp.br",
189                             "status":3,
190                             "prevCheckTime":1385376538,
191                             "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss"
192                         },{
193                             "message":" No one-way delay data returned for direction where src=ata.larc.usp.br dst=mon-lt.fibre.cin.ufpe.br",
194                             "status":3,
195                             "prevCheckTime":1385376358,
196                             "uri":"/maddash/grids/OWAMP/mon-lt.fibre.cin.ufpe.br/ata.larc.usp.br/Loss+Reverse"
197                         }],
198                         null
199                     ]
200                 ]
201             }
202             this._render(data);
203         },
204
205         /* ------------------------------------------------------------------ */
206         /* Accessors                                                          */
207         /* ------------------------------------------------------------------ */
208
209         setClickHandler: function(f)
210         {
211             this._handleClick = f;
212         },
213
214         setCellSize: function(value)
215         {
216             this._cellSize = value;
217         },
218
219         setCellPadding: function(value)
220         {
221             this._cellPadding = value;
222         },
223
224         setTextBlockSize: function(value)
225         {
226             this._textBlockSize = value;
227         },
228
229
230         
231
232         /* PLUGIN EVENTS */
233         // on_show like in querytable
234
235
236         /* GUI EVENTS */
237
238         // a function to bind events here: click change
239         // how to raise manifold events
240
241
242         /* GUI MANIPULATION */
243
244         /* XXX */
245
246
247         /* TEMPLATES */
248
249         // see in the html template
250         // How to load a template, use of mustache
251
252         /* QUERY HANDLERS */
253
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
257
258         // no prefix
259
260         on_filter_added: function(filter)
261         {
262
263         },
264
265         // ... be sure to list all events here
266
267         /* RECORD HANDLERS */
268         on_all_new_record: function(record)
269         {
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++;
276             }
277             /* Add the record to the buffer to be drawn */
278             this._buffer_records.add(record);
279         },
280
281         /* INTERNAL FUNCTIONS */
282
283         _render: function(data)
284         {
285             //TODO: Set title
286             //d3.select("#dashboard_name").html(dashboard.name + " Dashboard");
287             // XXX OLD XXX d3.select("#" + this.parent).html("");
288             //this.elmt().html('')
289
290             this.display_component(data);
291         },
292
293         _init_left: function()
294         {
295             var self = this;
296             this._left_element = this._canvas.append("div")
297               .attr("class", "gleft")
298                 .style("overflow-y", "scroll")
299                 .style("height", "400px")
300               .append("svg:svg")
301                 .attr("width", self._text_block_size)
302                 .attr("height", self._max_height_elements * (self._cellsize + 2*self._cellpadding) + 1000)
303
304         },
305
306         _process_left: function(key_list)
307         {
308             var self = this;
309
310             this._left_element = this._left_element
311               .selectAll(".rname")
312                 .data(key_list)
313                 .enter()
314                   .append("g")
315                   .attr("class", function(d,i){return "grow" + i})
316                   .attr("transform", function(d,i){return "translate(0,"+(i*(self._cellsize+2*self._cellpadding))+")"})
317       
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))
322       
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")
328               .attr("dy", "1.1em")
329
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++) {
334                     records.push({
335                         'source': key_list[i],
336                         'destination': key_list[j],
337                         'value': Math.floor(Math.random()*4) /* 0 1 2 3 */
338                     });
339                 }
340             }
341
342             // Create the rows
343             this._row_element = this._grid_element.selectAll(".grow")
344               .data(key_list)
345               .enter()
346                 .append("div")
347                 .attr("class", function(d,i){return "grow grow" + i})
348                 .style("width", "100%")
349                 .style("z-index", 1000)
350                 // jordan
351                 .style('position', 'absolute')
352 /*
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']; })
355                   .enter()
356                     .append("div")
357                     .attr("class", "gcell")
358                     .style("height", self._cellsize +"px")
359                     .style("width", self._cellsize +"px")
360                     .style("margin", (self._cellpadding) +"px")
361           
362                     .on("mouseover", function(d,i){
363                       selected_row = d.row; // We could find the row from the _map_elements
364                       selected_col = i;
365                       if(d.celldata){
366                         d3.select(this).style("margin", (self._cellpadding-1) +"px");
367                         d3.select(this).classed("shadow", true);
368                       }
369                       this._canvas.selectAll(".gcol"+ i).classed("gactive", true);
370                       this._canvas.selectAll(".grow"+ d.row).classed("gactive", true);
371                     })
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);
379                     })
380 */
381         },
382
383         _init_top: function()
384         {
385             var self = this;
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")
392                 .append("svg:svg")
393                     .attr("height", self._text_block_size)
394                     .attr("width", self._max_width_elements * (self._cellsize + 2*self._cellpadding) + 90 + 1000)
395
396         },
397
398
399         _process_top: function(key_list)
400         {
401             var self = this;
402
403             this._top_element = this._top_element
404                 .selectAll(".rname")
405                     .data(key_list)
406                     .enter()
407                         .append("g")
408                         .attr("class", function(d,i){return "gcol" + i})
409                         .attr("transform", function(d,i){ return "translate("+(i*(self._cellsize+2*self._cellpadding))+",0)"})
410
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 + ")")
417
418
419             this._top_element.append("svg:text")
420                 .attr("class", "gtext")
421                 .attr("text-anchor", "start")
422                 .attr("dy", "1.5em")
423                 .attr("dx", "1em")
424                 .attr("transform", "rotate(-45,0,"+ self._text_block_size +")  translate(0,"+ (self._text_block_size-5) + ")")
425                 .text(function(d,i){return d})
426
427             // Create the columns... 
428             var cols = this._grid_element.selectAll(".gcol")
429               .data(key_list)
430               .enter()
431                 .append("div")
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"})
436         },
437
438         _process_key_list: function()
439         {
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");
445         },
446
447         _display_legends: function()
448         {
449             // Color scale = the number of different statuses
450             colorscale.domain(d3.range(0, this._labels.length));
451
452             // Labels
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})
456
457             // Clear and redraw legend
458             d3.select("#"+this._legend).html("")
459
460             var legends = d3.select("#"+this._legend)
461                 .selectAll(".legend")
462                 .data(legendsdata)
463                 .enter()
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"})
473         },
474
475         _init_grid: function()
476         {
477             this._grid_element = this._canvas
478                 .style("width", this._ncols * (this._cellsize + 2*this._cellpadding) + 110 + this._text_block_size)
479                 .append("div")
480                     .attr("class", "ggrid")
481                     // jordan
482                     .style('top', '165px')
483                     .style('left', '145px')
484                     .style('float', 'none')
485                     .style('width', '2000px')
486                     .style('height', '1000px')
487             
488         },
489
490         _process_records: function()
491         {
492             var self = this;
493             var records = this._buffer_records.get();
494             console.log("processing records");
495
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 */
501                         _records.push({
502                             'source': records[i]['hrn'],
503                             'destination': records[j]['hrn'],
504                             'value': Math.floor(Math.random()*4) /* 0 1 2 3 */
505                         })
506                     }
507                 }
508             }
509
510             //
511
512             // Rows and columns have been created
513
514             var color = "";
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));
523               })
524
525             // Associate a tooltip to each cell thanks to tipsy
526             /*
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>";
531                 $(this).tipsy({
532                   html :true,
533                   opacity: 0.9,
534                   title : function(){
535                   return html
536                 }})
537               }
538             })
539             */
540       
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 })
544               .enter()
545                 .append("div");
546             temp
547               .style("height", this._cellsize/2 +"px")
548               .style("background", function(d,i){
549                 return colorscale(parseInt(d.status));
550               })
551               .on("click", function(d,i){ //CHANGE FROM PORTAL
552                   var that = this;
553                   if(d!=null && d.uri!=null && self.handleClick != null){
554                     self.handleClick(d);
555                   }
556                 })
557             console.log("processing records done");
558         },
559
560         display_component: function(data) 
561         {
562             this._display_grid_container(data);
563         },
564
565         _handleClick: function(d)
566         {
567             var uri = d.uri;
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" );
571              })
572
573         }
574
575
576     });
577
578     /* Plugin registration */
579     $.plugin('MadDash', MadDash);
580
581     // TODO Here use cases for instanciating plugins in different ways like in the pastie.
582
583 })(jQuery);