Plugin UnivBristol by Frederic Francois
[myslice.git] / plugins / univbristopo / templates / spillout
1 //$("#univbris_welcome").hide();
2           //jQuery("#univbris_flowspace_selection").show();
3
4           /* d3 data */
5           var nIslands=[], //replace
6               pad = 5,
7               width = parseInt($("#topologyContainer").css("width")), /* Obtain width from container */
8               height = 400;
9
10           //Islands area generation
11           var p = width/height
12               ny = Math.sqrt(nIslands/p),
13               nx = Math.ceil(p*ny),
14               ny = Math.ceil(ny),
15               foci = [];
16
17           if (nx*ny > nIslands){
18                 if (nx > ny && (nx-1)*ny >= nIslands)
19                 nx--;
20                 else if( nx <= ny && nx*(ny-1) >= nIslands)
21                 ny--;
22           }
23
24           var aw = Math.floor((width-(nx+1)*pad)/nx),
25               ah = Math.floor((height-(ny+1)*pad)/ny);
26
27           for (i=0; i<nIslands; i++){
28                 tx0 = pad + (pad + aw)*(Math.floor(i%nx));
29                 ty0 = pad + (pad + ah)*(Math.floor(i/nx));
30                 foci[i] = {x0: tx0, x1: tx0 + aw, y0: ty0, y1: ty0 + ah};
31           }
32
33           function randomXToY(minVal,maxVal,floatVal){
34                 var randVal = minVal+(Math.random()*(maxVal-minVal));
35                 return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
36           };
37
38           
39           function pEllipse(set){
40                 function getDistance(p){
41                         return Math.pow((p[0]-cx),2)/Math.pow(rx,2) + Math.pow((p[1]-cy),2)/Math.pow(ry,2);
42                 };
43
44                 d = 2;
45
46                 try {
47                         cx = (set.x1 + set.x0)/2;
48                         rx = ((set.x1 - set.x0)/2 + (set.y1 - set.y0)/2)/2;
49                         cy = (set.y1 + set.y0)/2;
50                         ry = (set.y1 - set.y0)/2;
51         
52                         while (d>1) {
53                                 p = [randomXToY(set.x0, set.x1), randomXToY(set.y0, set.y1) ];
54                                 d = getDistance(p);     
55                         }
56                 } catch(err) {
57                         p = [];
58                 }
59
60                 return p;
61           };
62
63           var epoints= [];
64           for (i=0;i<d3_nodes.length;i++){
65                 epoints.push(d3_nodes[i]['group']);
66
67           };
68           
69           var data = {};
70
71           var nodes=[];
72           
73
74           for (i=0;i<d3_nodes.length;i++){
75                 var node={};
76                 node['nodeValue']=d3_nodes[i].value;
77                 node['nodeName']=d3_nodes[i].name;
78                 node['image']=d3_nodes[i].image;
79                 node['color']= "";
80                 node['group']=d3_nodes[i].group;
81                 node['location']=d3_nodes[i].location;
82                 node['description']= d3_nodes[i].description;
83                 node['fixed']= false;
84                 node['radius']= 10;
85                 node['available']= d3_nodes[i].available;
86                 nodes.push(node);
87           };
88
89           data["nodes"]=nodes;
90
91           var links=[];
92           for (i=0;i<d3_links.length;i++){
93                 var link={};
94                 link['source']= d3_links[i].source;
95                 link['target']= d3_links[i].target;
96                 link['value']= d3_links[i].value;
97                 links.push(link);
98           }
99
100          data["links"]=links;
101         },
102         
103         function getLinkStyle(d, attr){
104                 rsc_ids = d.value.split("-");
105                 if(attr=="click"){
106                         id0 = rsc_ids[0];
107                         id1 = rsc_ids[1];
108                         if (id1.indexOf("eth") != -1){
109                              $(":checkbox#"+id0).click();
110                         }
111                         else{           
112                         if((! $(":checkbox#"+id0+":checked").length && ! $(":checkbox#"+id1+":checked").length) || ($(":checkbox#"+id0+":checked").length && $(":checkbox#"+id1+":checked").length)){
113                                 $(":checkbox#"+id0).click();
114                                 $(":checkbox#"+id1).click();
115                         }
116                         else if ($(":checkbox#"+id0+":checked").length){
117                                 $(":checkbox#"+id1).click();
118                         }
119                         else{
120                                 $(":checkbox#"+id0).click();    
121                         }
122                         }
123                 }else if(attr=="mouseover"){
124                         var values = {stroke: "#00BFFF", strokewidth: "2" };
125                         return values;
126                 }else{
127                         if( ($(":checkbox#"+rsc_ids[0]+":checked").length && rsc_ids[1].indexOf("eth") != -1) ||($(":checkbox#"+rsc_ids[0]+":checked").length && $(":checkbox#"+rsc_ids[1]+":checked").length)){
128                                 if (attr == "stroke")
129                                         return "#666";
130                                 else
131                                         return 2;
132                         }else{
133                                 if (attr == "stroke")
134                                         return "#ccc";
135                                 else
136                                         return 2;
137                         }
138                 }
139         };
140
141         function getBaseNodeColor(d){
142                 var group = 0;
143                 group = d.group;
144                 return d3.rgb(d.color.toString().toString()).darker(.15*group);
145         };
146
147         function getNodeCircleStyle(d, attr){
148                 selected_len = $(":checkbox:checked.node_id_"+d.nodeValue).length;
149                 all_len = $(":checkbox:.node_id_"+d.nodeValue).length;
150                 selected_server = $(".server_node_"+d.nodeValue+".connected").length;
151                 if(attr == "drag"){
152                         return d3.rgb(d.color.toString()).brighter(5);
153                 }else if(attr=="click"){
154                          if(selected_len == all_len) {
155                                 $(":checkbox:checked.node_id_"+d.nodeValue).click();
156                                 return getBaseNodeColor(d);
157                          }else{
158                                 $(":checkbox:not(:checked).node_id_"+d.nodeValue).click();
159                                 return d3.rgb(d.color.toString()).darker(5);
160                         }
161                 }else if (attr == "dragstop" && selected_len == 0){
162                         return getBaseNodeColor(d);
163                 }else{
164                                 if (attr == "fill"){
165                                         return getBaseNodeColor(d);
166                                 } else{
167                                         if (selected_len != 0){
168                                                 return d3.rgb(d.color.toString()).darker(5);
169                                         }else{
170                                                 return getBaseNodeColor(d);
171                                         }
172                                 }
173                 }
174         };
175
176         function getNodesIsland(d){
177                 var nNodes = 0;
178                 data.nodes.forEach(function(o, i){
179                    if(o.group == d){
180                         nNodes++;
181                    }
182                 });
183             return nNodes;
184         }
185
186         cur_zoom = 1;
187         zoom_in_active = false;
188         zoom_out_active = false;
189
190         
191         function zoomIn(zoom){
192             if(zoom_in_active == false && zoom_out_active == false){
193                 $("#link_zoom_in img").css("background-color", "#666");
194                 cur_zoom = cur_zoom + zoom;
195                 zoom_in_active = true;
196             }
197
198             else if(zoom_in_active == true){
199                 cur_zoom = cur_zoom - zoom;
200                 $("#link_zoom_in img").css("background-color", "");
201                 $("#target, svg, g").css("cursor", "move");
202                 zoom_in_active = false;
203             }
204
205             else{
206                 cur_zoom = cur_zoom + zoom + zoom;
207                 $("#link_zoom_out img").css("background-color", "");
208                 $("#link_zoom_in img").css("background-color", "#666");
209                 zoom_out_active = false;
210                 zoom_in_active = true;
211             }
212         };
213
214         
215         function zoomOut(zoom){
216             if(zoom_out_active == false && zoom_in_active == false){
217                 if((cur_zoom - zoom) >0){
218                     $("#link_zoom_out img").css("background-color", "#666");
219                     cur_zoom = cur_zoom - zoom;
220                     zoom_out_active = true;
221                 }
222                 else{
223                     $("#target, svg, g").css("cursor", "move");
224                 }
225             }   
226             else if(zoom_out_active == true){
227                 cur_zoom = cur_zoom + zoom;
228                 $("#link_zoom_out img").css("background-color", "");
229                 $("#target, svg, g").css("cursor", "move");
230                 zoom_out_active = false;
231             }
232
233             else{
234                 cur_zoom = cur_zoom - zoom;
235                 zoom_out_active = false;
236                 $("#link_zoom_in img").css("background-color", "");
237                 if((cur_zoom - zoom) > 0){
238                     $("#link_zoom_out img").css("background-color", "#666");
239                     zoom_out_active = true;
240                 }
241                 else{
242                     $("#target, svg, g").css("cursor", "move");
243                 }
244             }
245         };
246
247         function zoomReset(){
248             cur_zoom = 0.99;
249             posx = 0;
250             posy = 0;
251             return redraw();
252         };
253
254         function click(){
255             if (zoom_in_active == true || zoom_out_active == true){
256                 var mouseClick = d3.mouse(this);
257                 _x = -mouseClick[0]/2;
258                 _y = -mouseClick[1]/2;
259                 if(zoom_out_active == true){
260                     _x = -_x/2;
261                     _y = -_y/2;
262                 }
263                 posx += _x;
264                 posy += _y;
265                 zoom_in_active = false;
266                 zoom_out_active = false;
267                 return redraw();
268             }
269         };
270
271         function redraw() {
272                 //  trans=[(Math.round(width/cur_zoom) - width)/2, (Math.round(height/cur_zoom) - height)/2];
273                     trans = [posx, posy];
274                     svg.transition()
275                        .duration(500)
276                        .attr('x', function(d){ return d.x; })
277                        .attr('y', function(d){ return d.y; })
278                        .attr("transform", 
279                             "translate(" + trans +")"
280                             + "scale(" + cur_zoom + ")");
281                    $("#link_zoom_in img").css("background-color","");
282                    $("#link_zoom_out img").css("background-color","");
283                    $("#target, svg, g").css("cursor", "move"); 
284         };
285
286         //Global position of the canvas
287         var posx = 0;
288         var posy = 0;
289
290         /* Translation - bound to drag behavior */
291         dragMap = function(d) {
292         //No drag while zoom option active
293            if(zoom_in_active == false && zoom_out_active == false){
294               posx += d3.event.dx;
295               posy += d3.event.dy;
296               svg.attr('x', function(d) { return d.x; })
297                  .attr('y', function(d) { return d.y; })
298                  .attr("transform", "translate(" + posx + "," + posy + ") scale (" + cur_zoom + ")");
299            }
300         };
301
302         
303         /* Instantiation General parameters*/
304         var  padding = 6,
305              color = d3.scale.category10().domain(d3.range(nIslands)),
306              radius = d3.scale.sqrt().range([0, 12]);
307
308         var svg = d3.select("#target")
309             .on("click", click)
310             .append("svg")
311                 .attr("pointer-events", "all")
312                 .attr("width", width)
313                 .attr("height", height)
314                 .datum({x: 0, y: 0})
315                 .call(d3.behavior.drag().on("drag", dragMap))
316             .append("svg:g").on("zoom", redraw)
317                 .attr("cursor", "move")       
318         
319         // DATA VARIABLES
320         var nodes = data.nodes;
321         // Set color for each node
322         nodes.forEach(function(d) {
323             if (d.available != "False") {
324                 d.color = color(d.group%nIslands);
325             } else {
326                 d.color = "#CCC";
327             }
328         });
329
330         var links = data.links;
331
332         // Modified version (Carolina)
333         var force = d3.layout.force()
334             .gravity(1/(2*nIslands))
335             .distance(200/nIslands)
336             .friction(0.6)
337             .size([width, height])
338             .nodes(nodes)
339             .links(links)
340             .start();
341
342         var EMPTY_ISLAND = "Island with no resources";
343
344         //XXX:Very ugly, needs improvement
345         var islandsLocs = []
346         for (i = 0; i< nIslands; i++){
347                 islandsLocs[i] = EMPTY_ISLAND;
348         }
349
350         nodeInitialPos = []
351         for (i = 0; i< nodes.length; i++){
352                 nodeInitialPos[i] = [nodes[i].x, nodes[i].y];
353                 if (islandsLocs[nodes[i].group] == EMPTY_ISLAND){
354                         islandsLocs[nodes[i].group] = nodes[i].location;
355                 }
356         }
357
358         var dataislands = []
359         for (i = 0; i< nIslands; i++){
360                 dataislands[i] = {rx: (aw/2 + ah/2)/2, ry: ah/2, cx:(foci[i].x0+foci[i].x1)/2, cy:(foci[i].y0+foci[i].y1)/2, group: i, location: islandsLocs[i]};
361         }
362
363         var islands = svg.selectAll(".island")
364             .data(dataislands)
365           .enter().append("g")
366                 .attr("class", "island")
367
368         var iellipses = islands.append("ellipse")
369             .attr("rx", function(d) { return d.rx; })
370             .attr("ry", function(d) { return d.ry; })
371             .attr("cx",function(d) { return d.cx; })
372             .attr("cy", function(d) { return d.cy; })
373             .style("fill", function(d) { return color(d.group%nIslands); })
374             .style("stroke", function(d) { return color(d.group%nIslands);}) 
375             .style("opacity", 0.3)
376             .style("stroke-opacity", 0.7)
377
378         var ilabels = islands.append("text")
379               .attr("text-anchor", "middle")
380               .attr("y", function(d){ return d.cy + d.ry*0.9})
381               .attr("x", function(d){ return d.cx})
382               .attr("font-color", function(d) { return d3.rgb(color(d.group%nIslands)).darker(5); })
383               .style("opacity",1)
384               .style("cursor", "default")
385               .text(function(d) { return d.location });
386
387         //First ellipse animation on startup
388         animate();
389
390         var link = svg.selectAll(".link")
391             .data(links)
392           .enter().append("line")
393             .attr("class", "link");
394
395         link.on("click", function(d) {
396                         d3.select(this).style("stroke", function(d){return getLinkStyle(d, "click");})});
397
398         var node = svg.selectAll(".node")
399             .data(nodes)
400           .enter().append("g")
401             .attr("class", "node")
402             .call(force.drag)
403             .call(d3.behavior.drag()
404             .on("dragstart", function(d, i, e) {
405                     d.fixed = false;
406                     force.stop();
407             })
408             .on("drag", function(d, i) {
409                     d.px += d3.event.dx;
410                     d.py += d3.event.dy;
411                     d.x += d3.event.dx;
412                     d.y += d3.event.dy;
413                     d3.select(this).selectAll("circle").style("stroke", function(d){return getNodeCircleStyle(d, "drag");}) 
414                     tick();
415             })
416             .on("dragend", function(d, i) {
417                   //  d.fixed = true; // of course set the node to fixed so the force does not include the node in its auto positioning stuff //with the force.stop() it won't autopositioning, allowing to regroup the nodes
418                     d3.select(this).selectAll("circle").style("stroke", function(d){return getNodeCircleStyle(d, "dragstop");})
419                     tick();
420                     force.stop();
421             })
422             );
423
424         
425         node.append("circle")
426             .attr("r", function(d) { return d.radius; })
427             .style("stroke", function(d){return getNodeCircleStyle(d, "stroke");})
428             .style("fill", function(d){return getNodeCircleStyle(d, "fill");});
429
430         node.append("image")
431             .attr("xlink:href", function (d) { return d.image; })
432             .attr("x", -8)
433             .attr("y", -8)
434             .attr("width", 16)
435             .attr("height", 16)
436             .attr("opacity", function(d) { return d.available=="False"?0.8:1; })
437
438         node.append("text")
439             .attr("dx", 12)
440             .attr("dy", ".35em")
441             .text(function(d) { return d.name });
442
443         node.on("mouseover", function (d, i){
444                         //tooltip.show(get_node_info_formated(d));
445                         // Ugly hack to decode HTML
446                         tooltip.show($('<div/>').html(d.description).text());
447                         $("#selected_node_info").html("Selected " + d.type + ": " + d.nodeName + " at " + d.location);
448                         $("#selected_node_info").css("background-color", d.color );
449                         $("#selected_node_info").css("text-shadow", "-3px 2px 4px #eee");
450                         $("#selected_node_info").show();
451                 }
452                 )
453                 .on("mouseout", function() {
454                                 $("#selected_node_info").css("background-color", "#ebf5ff");
455                                 //$("#selected_node_info").hide()
456                                 tooltip.hide();
457                         })
458                 .on("click", function(d) {
459                     /* Only available AMs can be selected */
460                     if (d.available != "False") {
461                         //checkTopologyLoops(d);
462                         d3.select(this).selectAll("circle").style("stroke", function(d){return getNodeCircleStyle(d, "click");});
463                     }
464                 })
465
466         //Number of nodes in each Island
467         var qNodes = [];
468         for(i=0; i<nIslands; i++){
469            qNodes[i] = getNodesIsland(i);
470         } 
471         var grav = 0.008 * nx * ny;
472
473         force.on("tick", function(e){
474             var k = grav * e.alpha;
475             var node_groups = {}
476
477             // Adjust K colliding factor
478             $.each(data.nodes, function(i, n){
479                 if (node_groups[n.group] == undefined) {
480                     node_groups[n.group] = 1;
481                 } else {
482                     node_groups[n.group] += 1;
483                 }
484             });
485
486             data.nodes.forEach(function(o, i){
487                 var fact = 1;
488                 // Dumb hack: limit expansion through #nodes
489                 if (node_groups[o.group] >= 10) {
490                     fact = Math.floor(node_groups[o.group]/10);
491                 } else {
492                     fact = o.group+1;
493                 }
494                 // Carolina: when there are few nodes, avoid them to stay too far away from the center
495                 // AND when there are so many nodes avoid them to stay too near each others 
496                 if(qNodes[o.group] < 5){
497                     fact = fact * 2;
498                 } else {
499                     fact = fact/2;
500                 }
501                 try {
502                     o.y += k * fact * qNodes[o.group] * ((dataislands[o.group].cy) - o.y);
503                     o.x += k * fact * qNodes[o.group] * ((dataislands[o.group].cx) - o.x);
504                 } catch (err) {
505                 }
506             });
507
508             node.each(collide(.5));
509             node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")";})
510                 .attr("cx", function(d) { return d.x; })
511                 .attr("cy", function(d) { return d.y; });
512
513             link.attr("x1", function(d) { return d.source.x; })
514                 .attr("y1", function(d) { return d.source.y; })
515                 .attr("x2", function(d) { return d.target.x; })
516                 .attr("y2", function(d) { return d.target.y; });
517         });
518
519         function redrawNodes(){
520                 node.selectAll("circle").style("stroke", function(d){return getNodeCircleStyle(d, "stroke");});
521                 link.style("stroke", function(d) {return getLinkStyle(d, "stroke");});
522                 link.style("stroke-width", function(d) {return getLinkStyle(d, "stroke-width");});
523         }
524
525         function tick() {
526           
527           link.attr("x1", function(d) { return d.source.x; })
528               .attr("y1", function(d) { return d.source.y; })
529               .attr("x2", function(d) { return d.target.x; })
530               .attr("y2", function(d) { return d.target.y; });
531           
532           node.each(collide(.5));
533           node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
534               .attr("cx", d.x)
535               .attr("cy", d.y);
536         };
537
538         function animate(){
539
540         iellipses.attr("rx", function(d) { return d.rx; })
541             .style("display","block")
542             .attr("ry", function(d) { return d.ry; })
543             .attr("cx",function(d) { return d.cx; })
544             .attr("cy", function(d) { return d.cy; })
545             .style("fill", function(d) { return color(d.group%nIslands); })
546             .style("stroke", function(d) { return color(d.group%nIslands);}) 
547             .style("opacity", 0.3)
548             .style("stroke-opacity", 0.7)
549
550         ilabels.attr("text-anchor", "middle")
551               .attr("y", function(d){ return d.cy + d.ry*0.9})
552               .attr("x", function(d){ return d.cx})
553               .attr("font-color", function(d) { return d3.rgb(color(d.group%nIslands)).darker(5); })
554               .style("opacity",1)
555               .text(function(d) { return d.location });
556
557         iellipses.transition()
558                         .style("stroke-width",3)
559                         .style("stroke", function(d) { return d3.rgb(color(d.group%nIslands)).brighter(10);})
560                         .duration(1500)
561         iellipses.transition()
562                    .delay(1500)
563                    .style("opacity",0)
564                    .duration(3000)
565         ilabels.transition()
566                    .delay(1500)
567                    .style("opacity",0)
568                    .duration(3000);
569         }
570
571         function regroup(){
572                 force.resume();
573                 animate();
574         }
575
576         function collide(alpha) {
577           var quadtree = d3.geom.quadtree(nodes);
578           return function(d) {
579             var r = d.radius + radius.domain()[1] + padding,
580                 nx1 = d.x - r,
581                 nx2 = d.x + r,
582                 ny1 = d.y - r,
583                 ny2 = d.y + r;
584             quadtree.visit(function(quad, x1, y1, x2, y2) {
585               if (quad.point && (quad.point !== d)) {
586                 var x = d.x - quad.point.x,
587                     y = d.y - quad.point.y,
588                     l = Math.sqrt(x * x + y * y),
589                     r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding;
590                 if (l < r) {
591                   l = (l - r) / l * alpha;
592                   d.x -= x *= l;
593                   d.y -= y *= l;
594                   quad.point.x += x;
595                   quad.point.y += y;
596                 }
597               }
598               return x1 > nx2
599                   || x2 < nx1
600                   || y1 > ny2
601                   || y2 < ny1;
602             });
603           };
604         };
605
606         $("#link_zoom_in").click(function(){
607           $("#target, svg, g").css("cursor", "url({%url img_media 'zoomin.png' %}),auto");
608         });
609
610         $("#link_zoom_out").click(function(){
611           $("#target, svg, g").css("cursor", "url({%url img_media 'zoomout.png' %}),auto" );
612         });
613