API Docs for:
Show:

File: view\GraphView.js

  1. /*
  2. * BGPlay.js #9660
  3. * A web-based service for the visualization of the Internet routing
  4. *
  5. * Copyright (c) 2012 Roma Tre University and RIPE NCC
  6. *
  7. * See the file LICENSE.txt for copying permission.
  8. */
  9.  
  10. /**
  11. * GraphView provides the SVG graph.
  12. * Template: graph.html
  13. * @class GraphView
  14. * @module modules
  15. */
  16. var GraphView=Backbone.View.extend({
  17. events:function(){
  18. return {
  19. "touchstart .touchGraphEvents":"activateTouch"
  20. }
  21. },
  22.  
  23. /**
  24. * The initialization method of this object.
  25. * @method initialize
  26. * @param {Map} A map of parameters
  27. */
  28. initialize:function(){
  29. this.environment=this.options.environment;
  30. this.bgplay=this.environment.bgplay;
  31. this.fileRoot=this.environment.fileRoot;
  32. this.eventAggregator=this.environment.eventAggregator;
  33.  
  34. this.eventAggregator.trigger("moduleLoading", true);
  35. this.el=this.options.el;
  36. this.model=this.options.model;
  37. this.pathViews={};
  38. this.dom=$(this.el);
  39. this.width=this.dom.width();
  40. this.height=this.dom.height();
  41. this.subtrees=[];
  42. this.pathColorsDoublePrefixOne=this.environment.config.graph.pathColorsDoublePrefixOne;
  43. this.pathColorsDoublePrefixTwo=this.environment.config.graph.pathColorsDoublePrefixTwo;
  44. this.doublePath=[];
  45. this.arcDeviationRedrawRequired=[];
  46. this.uniquePathsCheck=[];
  47. this.staticPaths=[];
  48. this.selectedNodes=[];
  49. this.isMobile=isMobileBrowser();
  50. this.graphAnimationsOngoing=0;
  51. this.environment.GraphView=this;
  52.  
  53. this.eventAggregator.on("destroyAll", function(){
  54. this.destroyMe();
  55. },this);
  56.  
  57. this.graph=new BgplayGraph({parentDimensionX:this.width,parentDimensionY:this.height, environment:this.environment});
  58.  
  59. this.bgplay.on('change:cur_instant',function(){
  60. this.update();
  61. },this);
  62.  
  63. this.eventAggregator.on('allAnimationsCompleted', function(parameters){
  64. this.allConcurrentAnimationsCompleted();
  65. },this);
  66.  
  67. this.eventAggregator.on("graphAnimationComplete",function(value){
  68. this.graphAnimationsOngoing += (value) ? -1 : +1;
  69.  
  70. if (this.graphAnimationsOngoing==0){
  71. this.eventAggregator.trigger("allAnimationsCompleted",null);
  72. }
  73.  
  74. },this);
  75.  
  76.  
  77. this.eventAggregator.on('arcDeviationRedrawRequired', function(nodes){
  78. var $this=this;
  79. if (nodes!=null){
  80. nodes.forEach(function(node){
  81. if (!$this.arcDeviationRedrawRequired.contains(node)){
  82. $this.arcDeviationRedrawRequired.push(node);
  83. }
  84. });
  85. }
  86. },this);
  87.  
  88. this.render();
  89.  
  90. this.animation=false;
  91.  
  92. this.paper=new Raphael(this.nodeContainer[0], this.width,this.height);
  93. this.paper["node"]=this.nodeContainer;
  94. new paperAddOn(this.paper, true, true, true);
  95. this.svgGraph = this.paper.set();
  96. this.paper.graphSet=this.svgGraph;
  97.  
  98. var $this = this;
  99.  
  100. $(this.paper.node).dblclick(function(event){
  101. $this.selectedNodes=[];
  102. });
  103.  
  104. this.createAllNodes();
  105. this.createAllPaths();
  106.  
  107. this.computeSubTrees();
  108.  
  109. if (this.environment.config.graph.computeNodesPosition==true && this.environment.params.nodesPosition==null){
  110. this.graph.computePosition();
  111. this.eventAggregator.trigger("firstPathDraw"); //draw
  112. this.eventAggregator.trigger("updateNodesPosition"); //draw
  113. }else{
  114. this.eventAggregator.trigger("firstPathDraw"); //draw
  115. }
  116.  
  117.  
  118. makeUnselectable(this.nodeContainer[0]);
  119. log("Graph initialized.");
  120. this.eventAggregator.trigger("moduleLoading", false);
  121. },
  122.  
  123. /*
  124. * This method creates the pointers to the DOM elements.
  125. */
  126. getDomElements:function(){
  127. this.nodeContainer = this.dom.find('.bgplayNodeContainer');
  128. this.touchGraphEvents = this.dom.find('.touchGraphEvents')
  129. },
  130.  
  131. /**
  132. * This method draws this module (eg. inject the DOM and elements).
  133. * @method render
  134. */
  135. render:function(){
  136. parseTemplate('graph.html', this, this.el);
  137. this.getDomElements();
  138.  
  139. return this;
  140. },
  141.  
  142. /**
  143. * This method activates touch gestures on the graph preventing the propagation of them on the whole page.
  144. * @method activateTouch
  145. */
  146. activateTouch:function(event){
  147. event.preventDefault();
  148. this.paper.touchEnabled=!this.paper.touchEnabled;
  149. if (this.paper.touchEnabled==true){
  150. this.touchGraphEvents.attr('src',this.fileRoot+'view/html/img/touch_icon_enabled.png');
  151. }else{
  152. this.touchGraphEvents.attr('src',this.fileRoot+'view/html/img/touch_icon_disabled.png');
  153. }
  154. },
  155.  
  156. /**
  157. * This method is called during an animation.
  158. * Use this method if you want to make some changes to the entire graph.
  159. * Do not use this method to make changes to a particular vertex or edge,
  160. * use instead dedicated event-triggered methods in the relative views.
  161. * @method update
  162. */
  163. update:function(){
  164.  
  165. },
  166.  
  167. /**
  168. * This method will be called at the end of all the concurrent animation on the graph.
  169. * Use this method if you want to make some changes to the entire graph.
  170. * Do not use this method to make changes to a particular vertex or edge,
  171. * use instead dedicated event-triggered method in the relative views.
  172. * @method allConcurrentAnimationsCompleted
  173. */
  174. allConcurrentAnimationsCompleted:function(){
  175. var $this=this;
  176. this.arcDeviationRedrawRequired.forEach(function(node){
  177. $this.eventAggregator.trigger('nodeMoved',node);
  178. });
  179. this.arcDeviationRedrawRequired=[];
  180. },
  181.  
  182. /**
  183. * This method initializes all the NodeView needed to represent the nodes of the model layer.
  184. * @method createAllNodes
  185. */
  186. createAllNodes:function(){
  187. var $this=this;
  188. this.bgplay.get("nodes").forEach(function(node){
  189. $this.graph.addNode(new NodeView({model:node,paper:$this.paper,visible:true,graphView:$this, environment:$this.environment}));
  190. });
  191. },
  192.  
  193. /**
  194. * This method initializes a PathView object for each source-target pair of the model layer.
  195. * Hence a PathView represents the transition between a set of path objects of the model layer involving the same source-target pair.
  196. * PathView uses events to update itself.
  197. * @method createAllPaths
  198. */
  199. createAllPaths:function(){
  200. var $this=this;
  201. this.bgplay.get("sources").each(function(source){
  202. $.each(source.get("events"),function(key,tree){ //A tree for each target, almost always one
  203. var path,target,event;
  204. event=tree.first();
  205. path=event.get("path");
  206. target=event.get("target");
  207.  
  208. $this.pathViews[source.id+"-"+target.id] = new PathView({source:source,target:target,path:path,paper:$this.paper,visible:(event.get("type")=="initialstate"),graphView:$this, environment:$this.environment});//We instantiate a new PathView
  209.  
  210. tree.forEach(function(event){
  211. var path=event.get("path");
  212. if (path!=null){
  213. var target=path.get("target");
  214. var keyForUniquenessCheck=source.id+"-"+path.toString()+"-"+target.id;
  215. var keyForStaticCheck=source.id+"-"+target.id;
  216.  
  217. if ($this.uniquePathsCheck[keyForUniquenessCheck]==null){ //In this stage, we want to skip both null and duplicated paths in order to have only unique and valid paths
  218. $this.uniquePathsCheck[keyForUniquenessCheck]=true;
  219. $this.pathViews[keyForStaticCheck].static=($this.pathViews[keyForStaticCheck].static==null)?true:false;
  220. $this.graph.addPath(path);
  221. }
  222. }
  223. });
  224. });
  225. });
  226.  
  227. $.each(this.pathViews,function(key,element){
  228. if (element.static==true){
  229. $this.staticPaths.push(element.path);
  230. }
  231. });
  232. },
  233.  
  234. checkCycleOneWay:function(path1,path2){
  235. var n,node,iteration;
  236. var notCommon=false;
  237.  
  238. iteration=path1.length-1;
  239. for (n=1;n<=iteration;n++){
  240. node=path1[iteration-n]; //On-fly reverse
  241.  
  242. if (!path2.contains(node)){
  243.  
  244. notCommon=true;
  245. }else{
  246. if (notCommon){
  247. return true; //A common node after a notCommon node
  248. }
  249. }
  250. }
  251. return false; //There isn't a cycle (the worst case for this algorithm, all nodes were checked)
  252. },
  253.  
  254. /**
  255. * This method checks if there is a cycle between two paths in order to understand if they can be collapsed together.
  256. * @method thereIsCycle
  257. * @param {Object} An instance of Path
  258. * @param {Object} An instance of Path
  259. * @return {Boolean} True if there is a cycle
  260. */
  261. thereIsCycle:function(path1,path2){
  262. var nodes1,nodes2;
  263.  
  264. nodes1=path1.get("nodes");
  265. nodes2=path2.get("nodes");
  266.  
  267. //First fast check
  268. if (nodes1[nodes1.length-1].id!=nodes2[nodes2.length-1].id){
  269. return true; //Avoids checks between paths with different targets
  270. }
  271. return (this.checkCycleOneWay(nodes1,nodes2) || this.checkCycleOneWay(nodes2,nodes1)); //If the first check returns true, the second will not start
  272. },
  273.  
  274. getRedArrayOfColours:function(){
  275. if (!this.getRedArrayOfColoursCache){
  276. this.getRedArrayOfColoursCache=[];
  277.  
  278. var red, green, offset, secondColour_tmp, offset2, blue;
  279. red = 255;
  280. blue = 0;
  281. green = 0;
  282. offset=20;
  283. offset2=20;
  284. while (red>=0){
  285. red-=offset;
  286. secondColour_tmp=blue;
  287. this.getRedArrayOfColoursCache.push(("#" + red.toString(16) + green.toString(16) + secondColour_tmp.toString(16)).toUpperCase());
  288. while (secondColour_tmp<=255){
  289. secondColour_tmp+=offset2;
  290. this.getRedArrayOfColoursCache.push(("#" + red.toString(16) + green.toString(16) + secondColour_tmp.toString(16)).toUpperCase());
  291. }
  292.  
  293. }
  294. }
  295. return this.getRedArrayOfColoursCache;
  296. },
  297.  
  298. getGreenArrayOfColours:function(){
  299. if (!this.getGreenArrayOfColoursCache){
  300. this.getGreenArrayOfColoursCache=[];
  301.  
  302. var red, green, offset, secondColour_tmp, offset2, blue;
  303. red = 0;
  304. blue = 0;
  305. green=255;
  306. offset=20;
  307. offset2=20;
  308. while (green>=0){
  309. green-=offset;
  310. secondColour_tmp=blue;
  311. this.getGreenArrayOfColoursCache.push(("#" + red.toString(16) + green.toString(16) + secondColour_tmp.toString(16)).toUpperCase());
  312. while (secondColour_tmp<=255){
  313. secondColour_tmp+=offset2;
  314. this.getGreenArrayOfColoursCache.push(("#" + red.toString(16) + green.toString(16) + secondColour_tmp.toString(16)).toUpperCase());
  315. }
  316.  
  317. }
  318. }
  319. return this.getGreenArrayOfColoursCache;
  320. },
  321. getFromToColor:function(firstColour, secondColour){
  322. var green, offset, out, secondColour_tmp, offset2;
  323. green=0;
  324. offset=20;
  325. offset2=40
  326. out=[];
  327. while (firstColour>=0){
  328. firstColour-=offset;
  329. out.push(("#" + firstColour.toString(16) + green.toString(16) + secondColour.toString(16)).toUpperCase());
  330. secondColour_tmp=secondColour;
  331. while (secondColour_tmp>=0){
  332. secondColour_tmp-=offset2;
  333. out.push(("#" + firstColour.toString(16) + green.toString(16) + secondColour_tmp.toString(16)).toUpperCase());
  334. }
  335.  
  336. }
  337. return out;
  338. },
  339.  
  340. /**
  341. * This method returns a unique color for a given PathView.
  342. * The objective of this method is to provide unambiguous colours for the paths of the graph.
  343. * As a first approach, the returned colours are taken from an array declared in config.js.
  344. * The default array is generated using the CMC(l:c) colour algorithm.
  345. * As a second approach, when the array of colours ends, a random generation is used.
  346. * This second approach does not guarantee that the generated colours are distinguishable.
  347. * Therefore, tune the array of colours to prevent as much as possible the second approach.
  348. * @method getPathColor
  349. * @param {Object} An instance of PathView
  350. * @return {String} An hexadecimal color
  351. */
  352. getPathColor:function(pathView){
  353. var color;
  354. if (!this.colorRedTmp){
  355. this.colorRedTmp=['#8B8989','#8B6969','#BC8F8F','#C67171','#CD5555','#8E2323','#CD3333','#8B1A1A','#DB2929','#EE6363','#330000','#8B0000','#FF0000','#FF4040','#FFC1C1','#A02422','#F2473F','#CDB7B5','#FC1501','#FA8072','#D66F62','#8A3324','#FF5333','#8B3626','#FF7256','#F5785A','#8B4C39','#EE8262','#E9967A','#E04006','#EE4000','#8B5742','#B13E0F','#5C4033','#CD6839','#FF7D40','#DB9370','#F87531','#BD9178','#FF6103','#FF7722','#733D1A','#FF9955','#E9C2A6','#8B4513','#FFF5EE','#BC7642','#C76114','#EE8833','#8B7765','#F4A460','#B87333','#FFA54F','#CD7F32','#CC7722','#EE7600','#FFCC99','#B67C3D','#E38217','#9F703A','#EDC393','#DD7500','#8B7355','#ED9121','#FFEFDB','#CDAA7D','#C48E48','#EEC591','#FCE6C9','#8B8378','#8B795E','#DC8909','#AA6600','#FFDEAD','#EED6AF','#FFE4B5','#CDBA96','#FDF5E6','#EE9A00','#D5B77A','#FFAA00','#E8C782','#FEE5AC','#DAA520','#EEB422','#CD950C','#E6B426','#CDAB2D','#FFF8DC','#EEE8CD','#FEF1B5','#EEDC82','#8B8878','#EEDD82','#EEC900','#FBDB0C','#FFE303','#D6C537','#FFE600','#8B864E','#EEE685','#BDB76B','#FFFCCF','#7B7922','#8B8B83','#CDCDC1','#4F4F2F','#777733','#F5F5DC','#D9D919','#8B8B00','#EEEE00','#FFFFAA','#FFFFF0','#98A148','#AEBB51','#B3C95A','#FCFFF0','#668014','#54632C','#D4ED91','#A2C257','#79973F','#9ACD32','#DFFFA5','#A2CD5A','#ADFF2F'];
  356. this.colorNumberLeft=0;
  357. this.colorNumberRight=this.colorRedTmp.length;
  358. }
  359.  
  360. if (this.environment.config.graph.pathIncrementalColoringForTwoPrefixes==true && this.bgplay.getPrefixes().length==2){
  361. if (this.doublePath[0]==null){
  362. this.doublePath[0]=pathView.target;
  363. }else if (pathView.target!=this.doublePath[0] && this.doublePath[1]==null){
  364. this.doublePath[1]=pathView.target;
  365. }
  366. if (pathView.target==this.doublePath[0]){
  367. color=this.colorRedTmp[this.colorNumberLeft];
  368. this.colorNumberLeft+=1;
  369. }else if (pathView.target==this.doublePath[1]){
  370.  
  371. color=this.colorRedTmp[this.colorNumberRight];
  372. this.colorNumberRight-=1;
  373. }
  374.  
  375. }else{
  376. if (this.notUsedColor==null){
  377. this.notUsedColor=this.environment.config.graph.pathColors.slice(this.subtrees.length); //Initialize the array of colours
  378. }
  379.  
  380. if (pathView.static==true){
  381. color=this.environment.config.graph.pathColors[pathView.subTreeId]; //Dedicated color for static paths
  382. }else{
  383. color=this.notUsedColor.pop();
  384. }
  385. }
  386.  
  387. color=(color)?color:this.getRandomColor();
  388.  
  389. return color;
  390. },
  391.  
  392. getRandomColor:function(){
  393. var letters = '0123456789ABCDEF'.split('');
  394. var color = '#';
  395. for (var i = 0; i < 6; i++ ) {
  396. color += letters[Math.round(Math.random() * 15)];
  397. }
  398. return color;
  399. },
  400.  
  401. /**
  402. * The objective of this method is to identify a set of trees composed of static paths that can be collapsed
  403. * together and coloured with the same colour without introducing ambiguity.
  404. * An ambiguity is generated when there is a cycle between two paths.
  405. * @method computeSubTrees
  406. */
  407. computeSubTrees:function(){
  408. if (this.staticPaths.length==0)
  409. return;
  410. var n, i, tree, h, path1, path2, inThisTree,key;
  411. this.subtrees.push([this.staticPaths[0]]); //Initializes the first set (alias tree)
  412. this.pathViews[this.staticPaths[0].get("source").id+"-"+this.staticPaths[0].get("target").id].subTreeId=0;//The id of the subTree is the index of the array
  413.  
  414. for (h=1;h<this.staticPaths.length;h++){ //For each static path
  415. path1=this.staticPaths[h];
  416.  
  417. inThisTree=true;
  418.  
  419. for (n=0;n<this.subtrees.length;n++){ //Tries to insert the current static path in a set
  420. inThisTree=true;
  421.  
  422. tree=this.subtrees[n];
  423.  
  424. for (i=0;i<tree.length;i++){ //Checks if there is a cycle between the new path and the paths already in the set
  425. path2=tree[i]; //A path in the set
  426.  
  427. if (this.thereIsCycle(path1,path2)){ //There is a cycle between two paths in the same set
  428. inThisTree=false;
  429. break; //Skip to check the other paths in the same tree
  430. }
  431. }
  432.  
  433. if (inThisTree){ //If no checks generates a negative result then we can put this path in the current set
  434. this.pathViews[path1.get("source").id+"-"+path1.get("target").id].subTreeId=n;//The id of the subTree is the index of the array
  435. this.subtrees[n].push(path1);
  436. break; //Don't check in other trees
  437. }
  438. }
  439.  
  440. if (!inThisTree){
  441. this.pathViews[path1.get("source").id+"-"+path1.get("target").id].subTreeId=this.subtrees.length;
  442. this.subtrees.push([path1]);
  443. }
  444. }
  445. this.applyTreeAtEdges();
  446. },
  447.  
  448. applyTreeAtEdges:function(){
  449. var $this=this;
  450. this.graph.edges.forEach(function(edge){
  451. edge.subTreeId=$this.pathViews[edge.key].subTreeId;
  452. });
  453. }
  454.  
  455. });