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