1 /* ---------------------------------------------------------------------------
2 (c) Telef�nica I+D, 2013
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.
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.
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 -------------------------------------------------------------------------- */
20 // For MSIE < 9, forget it
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";
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.
38 // Highlight a movie in the graph. It is a closure within the d3.json() call.
39 var selectNode = undefined;
41 // Change status of a panel from visible to hidden or viceversa
42 var toggleDiv = undefined;
44 // Clear all help boxes and select a movie in network and in movie details panel
45 var clearAndSelect = undefined;
48 // The call to set a zoom value -- currently unused
49 // (zoom is set via standard mouse-based zooming)
50 var zoomCall = undefined;
53 // -------------------------------------------------------------------
55 // Do the stuff -- to be called after D3.js has loaded
56 function D3ok(Network) {
60 d3.select("#graph").remove("svg:svg");
67 // Variables keeping graph state
68 var activeMovie = undefined;
69 var currentOffset = { x : 0, y : 0 };
70 var currentZoom = 1.0;
73 var xScale = d3.scale.linear()
76 var yScale = d3.scale.linear()
79 var zoomScale = d3.scale.linear()
84 /* .......................................................................... */
86 // The D3.js force-directed layout
87 var force = d3.layout.force()
89 .size( [WIDTH, HEIGHT] )
90 .linkStrength( function(d,idx) { return d.weight; } );
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')
96 .attr("height", HEIGHT)
98 .attr("viewBox", "0 0 " + WIDTH + " " + HEIGHT )
99 .attr("preserveAspectRatio", "xMidYMid meet");
101 // Movie panel: the div into which the movie details info will be written
102 nodeInfoDiv = d3.select("#nodeInfo");
104 /* ....................................................................... */
106 // Get the current size & offset of the browser's viewport window
107 function getViewportSize( w ) {
109 if( w.innerWidth != null )
110 return { w: w.innerWidth,
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 };
121 return { w: d.body.clientWidth,
122 h: d.body.clientHeight,
123 x: d.body.scrollLeft,
124 y: d.body.scrollTop};
129 function getQStringParameterByName(name) {
130 var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
131 return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
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
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 );
148 /* Clear all help boxes and select a movie in the network and in the
151 clearAndSelect = function (id) {
152 toggleDiv('faq','off');
153 toggleDiv('help','off');
154 selectNode(id,true); // we use here the selectNode() closure
158 /* Compose the content for the panel with movie details.
159 Parameters: the node data, and the array containing all nodes
161 function getnodeInfo( n, nodeArray ) {
163 // $.each(n, function(key, element) {
164 // alert('key: ' + key + '\n' + 'value: ' + element);
166 info = '<div id="cover">';
168 info += '<img class="cover" height="300" src="' + n.cover + '" title="' + n.label + '"/>';
170 info += '<div class=t style="float: right">' + n.title + '</div>';
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);"/>';
175 info += '<br/></div><div style="clear: both;">'
177 info += '<div class=f><span class=l>IP version</span>: <span class=g>'
178 + n.ip_version + '</span></div>';
180 info += '<div class=f><span class=l>Node Type</span>: <span class=d>'
181 + n.select_type + '</span></div>';
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>';
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>]'
201 // *************************************************************************
204 ////////// 'movie-network-25-7-3.json',
205 ////////// function(data) {
207 // Declare the variables pointing to the node & link arrays
208 var nodeArray = data.nodes;
209 var linkArray = data.links;
213 Math.min.apply( null, linkArray.map( function(n) {return n.weight;} ) );
215 Math.max.apply( null, linkArray.map( function(n) {return n.weight;} ) );
217 // Add the node & link arrays to the layout, and start it
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
228 var edge_width = d3.scale.pow().exponent(8)
229 .domain( [minLinkWeight,maxLinkWeight] )
233 /* Add drag & zoom behaviours */
234 svg.call( d3.behavior.drag()
235 .on("drag",dragmove) );
236 svg.call( d3.behavior.zoom()
240 .on("zoom", doZoom) );
242 // ------- Create the elements of the layout (links and nodes) ------
244 var networkGraph = svg.append('svg:g').attr('class','grpParent');
246 // links: simple lines
247 var graphLinks = networkGraph.append('svg:g').attr('class','grp gLinks')
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");
254 // nodes: an SVG circle
255 var graphNodes = networkGraph.append('svg:g').attr('class','grp gNodes')
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); } );
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');
276 shadows = graphLabels.append('svg:text')
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; } );
284 labels = graphLabels.append('svg:text')
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; } );
293 /* --------------------------------------------------------------------- */
294 /* Select/unselect a node in the network graph.
296 - node: data for the node to be changed,
297 - on: true/false to show/hide the node
299 function highlightGraphNode( node, on )
301 //if( d3.event.shiftKey ) on = false; // for debugging
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 );
309 // locate the SVG nodes: circle & label group
310 circle = d3.select( '#c' + node.index );
311 label = d3.select( '#l' + node.index );
313 // activate/deactivate the node itself
315 .classed( 'main', on );
317 .classed( 'on', on || currentZoom >= SHOW_THRESHOLD );
318 label.selectAll('text')
319 .classed( 'main', on );
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 );
330 // set the value for the current active movie
331 activeMovie = on ? node.index : undefined;
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.
339 - new_idx: index of the movie to show
340 - doMoveTo: boolean to indicate if the graph should be centered
343 selectNode = function( new_idx, doMoveTo ) {
345 // do we want to center the graph on the node?
346 doMoveTo = doMoveTo || false;
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' );
355 // Now highlight the graph node and show its movie panel
356 highlightGraphNode( nodeArray[new_idx], true );
357 showMoviePanel( nodeArray[new_idx] );
361 /* --------------------------------------------------------------------- */
362 /* Show the movie details panel for a given node
364 function showMoviePanel( node ) {
365 // Fill it and display the panel
367 .html( getnodeInfo(node,nodeArray) )
368 .attr("class","panel_on");
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
380 function repositionGraph( off, z, mode ) {
382 // do we want to do a transition?
383 var doTr = (mode == 'move');
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')
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;
398 // zoom: get new value of zoom
399 if( z === undefined ) {
401 return; // no zoom, no tick, we don't need to go further
408 e = doTr ? graphLinks.transition().select_comm_proto(500) : graphLinks;
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); });
416 n = doTr ? graphNodes.transition().select_comm_proto(500) : graphNodes;
418 .attr("transform", function(d) { return "translate("
419 +z*d.x+","+z*d.y+")" } );
421 l = doTr ? graphLabels.transition().select_comm_proto(500) : graphLabels;
423 .attr("transform", function(d) { return "translate("
424 +z*d.x+","+z*d.y+")" } );
428 /* --------------------------------------------------------------------- */
431 function dragmove(d) {
432 offset = { x : currentOffset.x + d3.event.dx,
433 y : currentOffset.y + d3.event.dy };
434 repositionGraph( offset, undefined, 'drag' );
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)
443 function doZoom( increment ) {
444 newZoom = increment === undefined ? d3.event.scale
445 : zoomScale(currentZoom+increment);
446 if( currentZoom == newZoom )
447 return; // no zoom change
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);
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;
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) };
465 // Reposition the graph
466 repositionGraph( newOffset, newZoom, "zoom" );
469 zoomCall = doZoom; // unused, so far
471 /* --------------------------------------------------------------------- */
473 /* process events from the force-directed graph */
474 force.on("tick", function() {
475 repositionGraph(undefined,undefined,'tick');
478 /* A small hack to start the graph with a movie pre-selected */
479 mid = getQStringParameterByName('id')
481 clearAndSelect( mid );
484 //////////////////////////////////////////);
488 function testme( data2 ) {