reservation plugin - unbound request (unclean
[unfold.git] / portal / static / unbound_reservation_static / js / node-network.js
1 /* ---------------------------------------------------------------------------
2    (c) Telef�nica I+D, 2013
3    Author: Paulo Villegas
4
5    This script is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17    -------------------------------------------------------------------------- */
18
19
20 // For MSIE < 9, forget it
21 function D3notok() {
22   document.getElementById('sidepanel').style.visibility = 'hidden';
23   var nocontent = document.getElementById('nocontent');
24   nocontent.style.visibility = 'visible';
25   nocontent.style.pointerEvents = 'all';
26   var t = document.getElementsByTagName('body');
27   var body = document.getElementsByTagName('body')[0];
28   body.style.backgroundImage = "url('movie-network-screenshot-d.png')";
29   body.style.backgroundRepeat = "no-repeat";
30 }
31
32 // -------------------------------------------------------------------
33 // A number of forward declarations. These variables need to be defined since 
34 // they are attached to static code in HTML. But we cannot define them yet
35 // since they need D3.js stuff. So we put placeholders.
36
37
38 // Highlight a movie in the graph. It is a closure within the d3.json() call.
39 var selectNode = undefined;
40
41 // Change status of a panel from visible to hidden or viceversa
42 var toggleDiv = undefined;
43
44 // Clear all help boxes and select a movie in network and in movie details panel
45 var clearAndSelect = undefined;
46
47
48 // The call to set a zoom value -- currently unused
49 // (zoom is set via standard mouse-based zooming)
50 var zoomCall = undefined;
51
52
53 // -------------------------------------------------------------------
54
55 // Do the stuff -- to be called after D3.js has loaded
56 function D3ok(Network) {
57
58
59 $("#graph").empty();
60 d3.select("#graph").remove("svg:svg");
61
62   // Some constants
63   var WIDTH = 790,
64       HEIGHT = 957 
65       SHOW_THRESHOLD = 2.5;
66
67   // Variables keeping graph state
68   var activeMovie = undefined;
69   var currentOffset = { x : 0, y : 0 };
70   var currentZoom = 1.0;
71
72   // The D3.js scales
73   var xScale = d3.scale.linear()
74     .domain([0, WIDTH])
75     .range([0, WIDTH]);
76   var yScale = d3.scale.linear()
77     .domain([0, HEIGHT])
78     .range([0, HEIGHT]);
79   var zoomScale = d3.scale.linear()
80     .domain([1,6])
81     .range([1,6])
82     .clamp(true);
83
84 /* .......................................................................... */
85
86   // The D3.js force-directed layout
87   var force = d3.layout.force()
88     .charge(-320)
89     .size( [WIDTH, HEIGHT] )
90     .linkStrength( function(d,idx) { return d.weight; } );
91
92   // Add to the page the SVG element that will contain the movie network
93   var svg = d3.select("#netcanvas").append("svg:svg")
94     .attr('xmlns','http://www.w3.org/2000/svg')
95     .attr("width", WIDTH)
96     .attr("height", HEIGHT)
97     .attr("id","graph")
98     .attr("viewBox", "0 0 " + WIDTH + " " + HEIGHT )
99     .attr("preserveAspectRatio", "xMidYMid meet");
100
101   // Movie panel: the div into which the movie details info will be written
102   nodeInfoDiv = d3.select("#nodeInfo");
103
104   /* ....................................................................... */
105
106   // Get the current size & offset of the browser's viewport window
107   function getViewportSize( w ) {
108     var w = w || window;
109     if( w.innerWidth != null ) 
110       return { w: w.innerWidth, 
111                h: w.innerHeight,
112                x : w.pageXOffset,
113                y : w.pageYOffset };
114     var d = w.document;
115     if( document.compatMode == "CSS1Compat" )
116       return { w: d.documentElement.clientWidth,
117                h: d.documentElement.clientHeight,
118                x: d.documentElement.scrollLeft,
119                y: d.documentElement.scrollTop };
120     else
121       return { w: d.body.clientWidth, 
122                h: d.body.clientHeight,
123                x: d.body.scrollLeft,
124                y: d.body.scrollTop};
125   }
126
127
128
129   function getQStringParameterByName(name) {
130     var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
131     return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
132   }
133
134
135   /* Change status of a panel from visible to hidden or viceversa
136      id: identifier of the div to change
137      status: 'on' or 'off'. If not specified, the panel will toggle status
138   */
139   toggleDiv = function( id, status ) {
140     d = d3.select('div#'+id);
141     if( status === undefined )
142       status = d.attr('class') == 'panel_on' ? 'off' : 'on';
143     d.attr( 'class', 'panel_' + status );
144     return false;
145   }
146
147
148   /* Clear all help boxes and select a movie in the network and in the 
149      movie details panel
150   */
151   clearAndSelect = function (id) {
152     toggleDiv('faq','off'); 
153     toggleDiv('help','off'); 
154     selectNode(id,true);        // we use here the selectNode() closure
155   }
156
157
158   /* Compose the content for the panel with movie details.
159      Parameters: the node data, and the array containing all nodes
160   */
161   function getnodeInfo( n, nodeArray ) {
162         
163 //      $.each(n, function(key, element) {
164 //    alert('key: ' + key + '\n' + 'value: ' + element);
165 //});
166     info = '<div id="cover">';
167     if( n.cover )
168       info += '<img class="cover" height="300" src="' + n.cover + '" title="' + n.label + '"/>';
169     else
170       info += '<div class=t style="float: right">' + n.title + '</div>';
171     info +=
172     '<img src="../static/unbound_reservation_static/img/close.png" class="action" style="top: 0px;" title="close panel" onClick="toggleDiv(\'nodeInfo\');"/>' +
173     '<img src="../static/unbound_reservation_static/img/target-32.png" class="action" style="top: 280px;" title="center graph on node" onclick="selectNode('+n.index+',true);"/>';
174
175     info += '<br/></div><div style="clear: both;">'
176     if( n.ip_version )
177       info += '<div class=f><span class=l>IP version</span>: <span class=g>' 
178            + n.ip_version + '</span></div>';
179     if( n.select_type )
180       info += '<div class=f><span class=l>Node Type</span>: <span class=d>' 
181            + n.select_type + '</span></div>';
182     if( n.request_type )
183       info += '<div class=f><span class=l>Request Type</span>: <span class=c>' 
184            + n.request_type + '</span></div>';
185     if( n.select_comm_proto )
186       info += '<div class=f><span class=l>Image</span>: ' + n.select_image 
187            + '<span class=l style="margin-left:1em;">Communication Protocol</span>: ' 
188            + n.select_comm_proto + '</div>';
189     if( n.links ) {
190       info += '<div class=f><span class=l>Related to</span>: ';
191       n.links.forEach( function(idx) {
192         info += '[<a href="javascript:void(0);" onclick="selectNode('  
193              + idx + ',true);">' + nodeArray[idx].label + '</a>]'
194       });
195       info += '</div>';
196     }
197     return info;
198   }
199
200
201   // *************************************************************************
202
203 //////////  d3.json(
204 //////////    'movie-network-25-7-3.json',
205 //////////    function(data) {
206         var data=Network;
207     // Declare the variables pointing to the node & link arrays
208     var nodeArray = data.nodes;
209     var linkArray = data.links;
210     //alert(linkArray);
211         //alert(nodeArray);
212     minLinkWeight = 
213       Math.min.apply( null, linkArray.map( function(n) {return n.weight;} ) );
214     maxLinkWeight = 
215       Math.max.apply( null, linkArray.map( function(n) {return n.weight;} ) );
216
217     // Add the node & link arrays to the layout, and start it
218     force
219       .nodes(nodeArray)
220       .links(linkArray)
221       .start();
222
223     // A couple of scales for node radius & edge width
224     var node_size = d3.scale.linear()
225       .domain([5,10])   // we know score is in this domain
226       .range([1,16])
227       .clamp(true);
228     var edge_width = d3.scale.pow().exponent(8)
229       .domain( [minLinkWeight,maxLinkWeight] )
230       .range([1,3])
231       .clamp(true);
232
233     /* Add drag & zoom behaviours */
234     svg.call( d3.behavior.drag()
235               .on("drag",dragmove) );
236     svg.call( d3.behavior.zoom()
237               .x(xScale)
238               .y(yScale)
239               .scaleExtent([1, 6])
240               .on("zoom", doZoom) );
241
242     // ------- Create the elements of the layout (links and nodes) ------
243
244     var networkGraph = svg.append('svg:g').attr('class','grpParent');
245
246     // links: simple lines
247     var graphLinks = networkGraph.append('svg:g').attr('class','grp gLinks')
248       .selectAll("line")
249       .data(linkArray, function(d) {return d.source.id+'-'+d.target.id;} )
250       .enter().append("line")
251       .style('stroke-width', function(d) { return edge_width(d.weight);} )
252       .attr("class", "link");
253
254     // nodes: an SVG circle
255     var graphNodes = networkGraph.append('svg:g').attr('class','grp gNodes')
256       .selectAll("circle")
257       .data( nodeArray, function(d){return d.label} )
258       .enter().append("svg:circle")
259       .attr('id', function(d) { return "c" + d.index; } )
260       .attr('class', function(d) { return 'node level'+d.level;} )
261       .attr('r', function(d) { return node_size(d.score); } )
262       .attr('pointer-events', 'all')
263       //.on("click", function(d) { highlightGraphNode(d,true,this); } )    
264       .on("click", function(d) { showMoviePanel(d); } )
265       .on("mouseover", function(d) { highlightGraphNode(d,true,this);  } )
266       .on("mouseout",  function(d) { highlightGraphNode(d,false,this); } );
267
268     // labels: a group with two SVG text: a title and a shadow (as background)
269     var graphLabels = networkGraph.append('svg:g').attr('class','grp gLabel')
270       .selectAll("g.label")
271       .data( nodeArray, function(d){return d.label} )
272       .enter().append("svg:g")
273       .attr('id', function(d) { return "l" + d.index; } )
274       .attr('class','label');
275    
276     shadows = graphLabels.append('svg:text')
277       .attr('x','-2em')
278       .attr('y','-.3em')
279       .attr('pointer-events', 'none') // they go to the circle beneath
280       .attr('id', function(d) { return "lb" + d.index; } )
281       .attr('class','nshadow')
282       .text( function(d) { return d.label; } );
283
284     labels = graphLabels.append('svg:text')
285       .attr('x','-2em')
286       .attr('y','-.3em')
287       .attr('pointer-events', 'none') // they go to the circle beneath
288       .attr('id', function(d) { return "lf" + d.index; } )
289       .attr('class','nlabel')
290       .text( function(d) { return d.label; } );
291
292
293     /* --------------------------------------------------------------------- */
294     /* Select/unselect a node in the network graph.
295        Parameters are: 
296        - node: data for the node to be changed,  
297        - on: true/false to show/hide the node
298     */
299     function highlightGraphNode( node, on )
300     {
301       //if( d3.event.shiftKey ) on = false; // for debugging
302
303       // If we are to activate a movie, and there's already one active,
304       // first switch that one off
305       if( on && activeMovie !== undefined ) {
306         highlightGraphNode( nodeArray[activeMovie], false );
307       }
308
309       // locate the SVG nodes: circle & label group
310       circle = d3.select( '#c' + node.index );
311       label  = d3.select( '#l' + node.index );
312
313       // activate/deactivate the node itself
314       circle
315         .classed( 'main', on );
316       label
317         .classed( 'on', on || currentZoom >= SHOW_THRESHOLD );
318       label.selectAll('text')
319         .classed( 'main', on );
320
321       // activate all siblings
322       Object(node.links).forEach( function(id) {
323         d3.select("#c"+id).classed( 'sibling', on );
324         label = d3.select('#l'+id);
325         label.classed( 'on', on || currentZoom >= SHOW_THRESHOLD );
326         label.selectAll('text.nlabel')
327           .classed( 'sibling', on );
328       } );
329
330       // set the value for the current active movie
331       activeMovie = on ? node.index : undefined;
332     }
333
334
335     /* --------------------------------------------------------------------- */
336     /* Show the details panel for a movie AND highlight its node in 
337        the graph. Also called from outside the d3.json context.
338        Parameters:
339        - new_idx: index of the movie to show
340        - doMoveTo: boolean to indicate if the graph should be centered
341          on the movie
342     */
343     selectNode = function( new_idx, doMoveTo ) {
344                 
345       // do we want to center the graph on the node?
346       doMoveTo = doMoveTo || false;
347       if( doMoveTo ) {
348         s = getViewportSize();
349         width  = s.w<WIDTH ? s.w : WIDTH;
350         height = s.h<HEIGHT ? s.h : HEIGHT;
351         offset = { x : s.x + width/2  - nodeArray[new_idx].x*currentZoom,
352                    y : s.y + height/2 - nodeArray[new_idx].y*currentZoom };
353         repositionGraph( offset, undefined, 'move' );
354       }
355       // Now highlight the graph node and show its movie panel
356       highlightGraphNode( nodeArray[new_idx], true );
357       showMoviePanel( nodeArray[new_idx] );
358     }
359
360
361     /* --------------------------------------------------------------------- */
362     /* Show the movie details panel for a given node
363      */
364     function showMoviePanel( node ) {
365       // Fill it and display the panel
366       nodeInfoDiv
367         .html( getnodeInfo(node,nodeArray) )
368         .attr("class","panel_on");
369     }
370
371             
372     /* --------------------------------------------------------------------- */
373     /* Move all graph elements to its new positions. Triggered:
374        - on node repositioning (as result of a force-directed iteration)
375        - on translations (user is panning)
376        - on zoom changes (user is zooming)
377        - on explicit node highlight (user clicks in a movie panel link)
378        Set also the values keeping track of current offset & zoom values
379     */
380     function repositionGraph( off, z, mode ) {
381
382       // do we want to do a transition?
383       var doTr = (mode == 'move');
384
385       // drag: translate to new offset
386       if( off !== undefined &&
387           (off.x != currentOffset.x || off.y != currentOffset.y ) ) {
388         g = d3.select('g.grpParent')
389         
390         if( doTr )
391           g = g.transition().duration(500);
392         g.attr("transform", function(d) { return "translate("+
393                                           off.x+","+off.y+")" } );
394         currentOffset.x = off.x;
395         currentOffset.y = off.y;
396       }
397
398       // zoom: get new value of zoom
399       if( z === undefined ) {
400         if( mode != 'tick' )
401           return;       // no zoom, no tick, we don't need to go further
402         z = currentZoom;
403       }
404       else
405         currentZoom = z;
406
407       // move edges
408       e = doTr ? graphLinks.transition().select_comm_proto(500) : graphLinks;
409       e
410         .attr("x1", function(d) { return z*(d.source.x); })
411         .attr("y1", function(d) { return z*(d.source.y); })
412         .attr("x2", function(d) { return z*(d.target.x); })
413         .attr("y2", function(d) { return z*(d.target.y); });
414
415       // move nodes
416       n = doTr ? graphNodes.transition().select_comm_proto(500) : graphNodes;
417       n
418         .attr("transform", function(d) { return "translate("
419                                          +z*d.x+","+z*d.y+")" } );
420       // move labels
421       l = doTr ? graphLabels.transition().select_comm_proto(500) : graphLabels;
422       l
423         .attr("transform", function(d) { return "translate("
424                                          +z*d.x+","+z*d.y+")" } );
425     }
426            
427
428     /* --------------------------------------------------------------------- */
429     /* Perform drag
430      */
431     function dragmove(d) {
432       offset = { x : currentOffset.x + d3.event.dx,
433                  y : currentOffset.y + d3.event.dy };
434       repositionGraph( offset, undefined, 'drag' );
435     }
436
437
438     /* --------------------------------------------------------------------- */
439     /* Perform zoom. We do "semantic zoom", not geometric zoom
440      * (i.e. nodes do not change size, but get spread out or stretched
441      * together as zoom changes)
442      */
443     function doZoom( increment ) {
444       newZoom = increment === undefined ? d3.event.scale 
445                                         : zoomScale(currentZoom+increment);
446       if( currentZoom == newZoom )
447         return; // no zoom change
448
449       // See if we cross the 'show' threshold in either direction
450       if( currentZoom<SHOW_THRESHOLD && newZoom>=SHOW_THRESHOLD )
451         svg.selectAll("g.label").classed('on',true);
452       else if( currentZoom>=SHOW_THRESHOLD && newZoom<SHOW_THRESHOLD )
453         svg.selectAll("g.label").classed('on',false);
454
455       // See what is the current graph window size
456       s = getViewportSize();
457       width  = s.w<WIDTH  ? s.w : WIDTH;
458       height = s.h<HEIGHT ? s.h : HEIGHT;
459
460       // Compute the new offset, so that the graph center does not move
461       zoomRatio = newZoom/currentZoom;
462       newOffset = { x : currentOffset.x*zoomRatio + width/2*(1-zoomRatio),
463                     y : currentOffset.y*zoomRatio + height/2*(1-zoomRatio) };
464
465       // Reposition the graph
466       repositionGraph( newOffset, newZoom, "zoom" );
467     }
468
469     zoomCall = doZoom;  // unused, so far
470
471     /* --------------------------------------------------------------------- */
472
473     /* process events from the force-directed graph */
474     force.on("tick", function() {
475       repositionGraph(undefined,undefined,'tick');
476     });
477
478     /* A small hack to start the graph with a movie pre-selected */
479     mid = getQStringParameterByName('id')
480     if( mid != null )
481       clearAndSelect( mid );
482   ///////////}
483   
484   //////////////////////////////////////////);
485
486 } // end of D3ok()
487
488   function testme( data2 ) {
489         alert(data2);
490         
491         }
492