API Docs for:
Show:

File: view\PathView.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. * @class PathView
  12. * @module modules
  13. */
  14. var PathView=Backbone.View.extend({
  15.  
  16. /**
  17. * The initialization method of this object.
  18. * @method initialize
  19. * @param {Map} A map of parameters
  20. */
  21. initialize:function(){
  22. this.environment=this.options.environment;
  23. this.bgplay=this.environment.bgplay;
  24. this.fileRoot=this.environment.fileRoot;
  25. this.eventAggregator=this.environment.eventAggregator;
  26.  
  27. this.source=this.options.source;
  28. this.target=this.options.target;
  29. this.path=this.options.path;
  30. this.drawnArcs=[];
  31. this.paper=this.options.paper;
  32. this.key=this.source.id+"-"+this.target.id;
  33. this.visible=this.options.visible;
  34. this.graphView=this.options.graphView;
  35. this.beamWidth=((this.environment.config.graph.nodeHeight/100)*90);
  36.  
  37. this.eventAggregator.on("destroyAll", function(){
  38. this.destroyMe();
  39. },this);
  40.  
  41. this.eventAggregator.on('nodeMoved',function(node){
  42. if (this.svgPath!=null && this.path!=null && this.path.contains(node)){
  43. this.updateWithoutAnimation();
  44. }
  45. },this);
  46.  
  47. this.eventAggregator.on('checkPathPosition',function(node){ //This event forces all paths to redraw themselves
  48. if (this.svgPath!=null && this.path!=null){
  49. this.updateWithoutAnimation();
  50. }
  51. },this);
  52.  
  53. this.eventAggregator.on('firstPathDraw',function(node){
  54. this.firstDraw();
  55. },this);
  56.  
  57. this.source.on('curEventChange',function(event){
  58. if (event.get("target")==this.target){ //Each PathView is a subscriber of its source.
  59. this.visible=true;
  60. this.setVisibility();
  61.  
  62. // IMPORTANT!!! It is very important to apply first the precedent event of the current event.
  63. // For example would be impossible to apply a path change or a withdrawal to an non-existent path.
  64. // The user can jump to events temporally distant, in this case the sequence of events becomes senseless (i.e. a path change after a withdrawal).
  65. if (event.get("type")!="initialstate")
  66. this.restoreCondition(event);
  67.  
  68. this.updatePath(event);
  69. }
  70. },this);
  71. // The user can jump back in time, if the first event A for the source S happened at T1
  72. // and the user jumps to T2, where T2<T1, the curEvent changes but is Null!
  73. // In this case the 'curEventChange' will be not triggered, to prevent 'undefined', and will be replaced by
  74. // 'curEventNull' (containing the last event not null, in order to know what should be deleted).
  75. this.source.on('curEventNull',function(event){//Look! This receives the last not null event, not the current!
  76. if (event!=null && event.get("target")==this.target){//THIS IS NOT A WITHDRAWAL, there isn't any route to delete because it never existed!
  77. this.visible=false;
  78. this.setVisibility();
  79. }
  80. },this);
  81.  
  82. },
  83. updateAllEdgeInCommon:function($this){
  84. var path1,path2, n, tmpNode, nodes1, nodes2, length1, length2;
  85. var notCommonNodes=[];
  86. path1=$this.path;
  87. path2=$this.prevPath;
  88.  
  89. if (path2==null && path1!=null){
  90. notCommonNodes=path1.get("nodes");
  91. }else if (path1==null && path2==null){
  92. return;
  93. }else if (path1==null && path2!=null){
  94. notCommonNodes=path2.get("nodes");
  95. }else{
  96. nodes1=path1.get("nodes");
  97. length1=nodes1.length;
  98. for(n=length1;n--;){
  99. tmpNode=nodes1[n];
  100. if (!path2.contains(tmpNode)){
  101. notCommonNodes.push(tmpNode);
  102. }
  103. }
  104. nodes2=path2.get("nodes");
  105. length2=nodes2.length;
  106. for(n=length2;n--;){
  107. tmpNode=nodes2[n];
  108. if (!path1.contains(tmpNode)){
  109. notCommonNodes.push(tmpNode);
  110. }
  111. }
  112. }
  113. this.eventAggregator.trigger('arcDeviationRedrawRequired',notCommonNodes);
  114. },
  115. removeMyArcs:function(){
  116. for (var n=0;n<this.drawnArcs.length;n++){
  117. this.drawnArcs[n].drawn=false;
  118. }
  119. },
  120. restoreCondition:function(event){
  121. this.removeMyArcs();
  122. this.path=event.get("prevPath"); //The new state is the previous one
  123.  
  124. if (this.path!=null){
  125. this.svgPath.attr({path:this.computePathString(this.path.get("nodes"))});
  126. this.svgPath.toBack();
  127. }else{
  128. this.visible=false;
  129. this.setVisibility();
  130. }
  131. this.updateAllEdgeInCommon(this);
  132. this.prevPath=this.path;
  133. },
  134. getLineStrokeStyle:function(){
  135. return (this.static==true)?this.environment.config.graph.pathStaticStrokeDasharray:this.environment.config.graph.pathDefaultStrokeDasharray;
  136. },
  137. isEdgeDrawn:function(arcs){ //There is at least a drawn arc for this tree on this edge?
  138. var n;
  139. for (n= 0;n<arcs.length;n++){
  140. if (arcs[n].drawn==true && arcs[n].subTreeId==this.subTreeId)
  141. return arcs[n];
  142. }
  143. return false;
  144. },
  145. getMyArc:function(arcs){
  146. var n,myArc,length;
  147. length=arcs.length;
  148. for (n=length;n--;){
  149. if (arcs[n].key==this.key){
  150. myArc=arcs[n];
  151. break;
  152. }
  153. }
  154. return myArc;
  155. },
  156.  
  157. /**
  158. * This method manages the animation of a path involved in an event.
  159. * @method updatePath
  160. * @param {Object} An instance of Event
  161. */
  162. updatePath:function(event){
  163. this.path=event.get("path");
  164. switch (event.get("subType")) {
  165. case "withdrawal":
  166. this.animateRemove();
  167. break;
  168. case "announce":
  169. this.animateNewPath(event);
  170. break;
  171. case "pathchange":
  172. this.removeMyArcs();
  173. this.animatePathChange(event); //Animate the transition between two routes
  174. break;
  175. case "reannounce":
  176. this.animateMinorChanges(event); //Do something to highlight the involved path
  177. break;
  178. case "prepending":
  179. this.animateMinorChanges(event);
  180. break;
  181. default:
  182. this.updateWithoutAnimation();
  183. break;
  184.  
  185. }
  186. },
  187.  
  188. /**
  189. * This method updates the SVG representing the path without animating it.
  190. * @method updateWithoutAnimation
  191. */
  192. updateWithoutAnimation:function(){
  193. this.svgPath.attr({"path":this.computePathString(this.path.get("nodes"))});
  194. },
  195.  
  196. /**
  197. * This method updates the visibility of the SVG representing the path.
  198. * @method setVisibility
  199. */
  200. setVisibility:function(){
  201. if (this.visible!=true){
  202. this.svgPath.hide();
  203. }else{
  204. this.svgPath.show();
  205. }
  206. },
  207.  
  208. /**
  209. * This method executes the first draw of the SVG representing the path.
  210. * @method firstDraw
  211. */
  212. firstDraw:function(){
  213. var pathString;
  214.  
  215. pathString=this.computePathString(this.path.get("nodes"));
  216.  
  217. this.svgPath=this.paper.path(pathString)
  218. .attr({stroke:this.graphView.getPathColor(this), fill:"none",
  219. "stroke-width":this.environment.config.graph.pathWeight,
  220. "stroke-dasharray":this.getLineStrokeStyle()});
  221. this.path.view=this.svgPath;
  222. this.paper.graphSet.push(this.svgPath);
  223. this.setVisibility();
  224.  
  225. this.svgPath.toBack();
  226. $(this.svgPath.node).css("cursor","pointer");
  227. this.svgEventManager();
  228.  
  229. },
  230.  
  231. /**
  232. * This method manages the SVG events.
  233. * @method svgEventManager
  234. */
  235. svgEventManager:function(){
  236. var $this=this;
  237. this.svgPath.click(function(event){
  238. $this.eventAggregator.trigger("clickOnPath",$this);
  239. });
  240. this.svgPath.mouseover(function(){
  241. $this.eventAggregator.trigger("selectedPath", $this);
  242. });
  243. this.svgPath.mouseout(function(){
  244. $this.eventAggregator.trigger("infoPanelReleased", true);
  245. });
  246. },
  247.  
  248. /**
  249. * This method computes the SVG representing a dynamic path.
  250. * @method computeDynamicPathString
  251. * @param {Array} An array of nodes
  252. * @return {String} An SVG path
  253. */
  254. computeDynamicPathString:function(nodes){
  255. var pathString="";
  256. var n,orderedNodes,node1,node2,reversed;
  257. for (n=0;n<nodes.length-1;n++){
  258. node1=nodes[n];
  259. node2=nodes[n+1];
  260.  
  261. if (node1.id==node2.id) //It is a fake point, skip it!
  262. continue;
  263.  
  264. orderedNodes=this.graphView.graph.utils.absOrientation(node1,node2);
  265. reversed=(orderedNodes[0].id==node2.id);
  266. pathString+=this.getArcDeviationPathString(orderedNodes[0],orderedNodes[1],reversed);
  267. }
  268. return pathString;
  269. },
  270.  
  271. /**
  272. * This method computes the SVG representing a path.
  273. * @method computeNormalPathString
  274. * @param {Array} An array of nodes
  275. * @return {String} An SVG path
  276. */
  277. computeNormalPathString:function(nodes){
  278. var pathString="";
  279. for (var i=0;i<nodes.length;i++){
  280. var node=nodes[i];
  281. pathString+=(pathString=="")?"M":" L";
  282. pathString+=(node.view) ? node.view.x+" "+node.view.y : node.x+" "+node.y;
  283. }
  284. return pathString;
  285. },
  286.  
  287. /**
  288. * This method dispatches a computation of an SVG path to a more specific method.
  289. * @method computePathString
  290. * @param {Array} An array of nodes
  291. * @return {String} An SVG path
  292. */
  293. computePathString:function(nodes){
  294. var pathString="";
  295. if (this.visible==true){
  296. if (this.static==true){
  297. pathString=this.computeStaticPathString(nodes);
  298. }else{
  299. pathString=this.computeDynamicPathString(nodes);
  300. }
  301. }
  302. return pathString;
  303. },
  304.  
  305. /**
  306. * This method computes the SVG representing a static path.
  307. * @method computeStaticPathString
  308. * @param {Array} An array of nodes
  309. * @return {String} An SVG path
  310. */
  311. computeStaticPathString:function(nodes){
  312. var n,node1,node2,myArc,sameEdge,drawnEdge,orderedNodes,reversed, pathString;
  313. pathString="";
  314. for (n=0;n<nodes.length-1;n++){
  315. node1=nodes[n];
  316. node2=nodes[n+1];
  317.  
  318. orderedNodes=this.graphView.graph.utils.absOrientation(node1,node2);
  319. reversed=(orderedNodes[0].id==node2.id);
  320. sameEdge=this.graphView.graph.edges.get({vertexStart:orderedNodes[0].view, vertexStop:orderedNodes[1].view});
  321. drawnEdge=this.isEdgeDrawn(sameEdge);
  322.  
  323. if (drawnEdge==false || drawnEdge.key==this.key){
  324. myArc=this.getMyArc(sameEdge);
  325. pathString+=this.getArcDeviationPathString(orderedNodes[0],orderedNodes[1],reversed,myArc);
  326. }
  327. }
  328. return pathString;
  329. },
  330.  
  331. /**
  332. * This method returns a set of ordered arcs that must be drawn between a nodes pair.
  333. * @method getDrawnArcs
  334. * @param {Array} An array of arcs
  335. * @param {Object} The arc belonging to this PathView
  336. * @return {String} An SVG path
  337. */
  338. getDrawnArcs:function(sameEdge,myArc){
  339. var h,length;
  340. var drawnArcs=[];
  341. if (this.environment.config.graph.staticDeviation==false){
  342. length=sameEdge.length;
  343. for (h=0;h<length;h++){
  344. if (sameEdge[h].drawn==true){
  345. drawnArcs.push(sameEdge[h]);
  346. sameEdge[h].drawnPosition=drawnArcs.length-1; //Sets the drawn position (i.e. this arc is the 4th arc drawn for this edge)
  347. }
  348. }
  349. }else{
  350. myArc.drawnPosition= sameEdge.contains(myArc);
  351. return sameEdge;
  352. }
  353. return drawnArcs;
  354. },
  355.  
  356. /**
  357. * This method assigns to each arcs a deviation in order to avoid overlap between arcs belonging to different paths.
  358. * @method getArcDeviationPathString
  359. * @param {Object} An instance of Node
  360. * @param {Object} An instance of Node
  361. * @param {Boolean} True if the provided nodes are not sorted
  362. * @param {Object} The arc belonging to this PathView
  363. * @return {String} An SVG path
  364. */
  365. getArcDeviationPathString:function(node1,node2,reversed,myArc){
  366. var unitVector,leftVector,finalVector,newPosition1,newPosition2,sameEdge,pathAndInterline,half;
  367. sameEdge=this.graphView.graph.edges.get({vertexStart:node1.view, vertexStop:node2.view});
  368.  
  369. var myArc=myArc||this.getMyArc(sameEdge);
  370. myArc.drawn=true;
  371. this.drawnArcs.push(myArc);
  372.  
  373. sameEdge=this.getDrawnArcs(sameEdge,myArc);
  374.  
  375. if (this.environment.config.graph.patInterlineDistributed){
  376. pathAndInterline=this.environment.config.graph.pathWeight + (this.beamWidth/sameEdge.length);
  377. }else{
  378. pathAndInterline=this.environment.config.graph.pathWeight + this.environment.config.graph.pathInterline;
  379. }
  380.  
  381. if (this.environment.config.graph.arcsBeamMaxWidth && (pathAndInterline*sameEdge.length)>this.environment.config.graph.nodeHeight){
  382. pathAndInterline=this.environment.config.graph.nodeHeight/sameEdge.length;
  383. }
  384.  
  385. half=(sameEdge.length-1)/2;
  386.  
  387. myArc.deviation=((myArc.drawnPosition-half)*pathAndInterline);
  388.  
  389. unitVector=this.graphView.graph.utils.unitVector(node1.view,node2.view);
  390.  
  391. leftVector=this.graphView.graph.utils.leftUnitVector(unitVector);
  392.  
  393. leftVector=(reversed==true)?this.graphView.graph.utils.inverseVector(leftVector):leftVector;
  394.  
  395. finalVector=this.graphView.graph.utils.mulVectorForValue(leftVector,myArc.deviation);
  396.  
  397. newPosition1={x:node1.view.x+finalVector.x, y:node1.view.y+finalVector.y};
  398. newPosition2={x:node2.view.x+finalVector.x, y:node2.view.y+finalVector.y};
  399.  
  400. return this.computeNormalPathString([newPosition1,newPosition2]);
  401. },
  402.  
  403. /**
  404. * Provided two paths, this method pushes fake points in order to return equidimensional paths.
  405. * @method addFakePoints
  406. * @param {Object} An instance of Path (1)
  407. * @param {Object} An instance of Path (2)
  408. * @return {Array} An array of equidimensional path [path1,path2]
  409. */
  410. addFakePoints:function(realPath1,realPath2){
  411. var fakePath1=realPath1.slice(0);//clone
  412. var fakePath2=realPath2.slice(0);//clone
  413. var n;
  414. var fakePoints=realPath1.length-realPath2.length;
  415. var absFakePoints=Math.abs(fakePoints);
  416.  
  417. if (fakePoints>0){
  418. for (n=0;n<absFakePoints;n++)
  419. fakePath2.push(fakePath2[fakePath2.length-1]);
  420. }else if (fakePoints<0){
  421. for (n=0;n<absFakePoints;n++)
  422. fakePath1.push(fakePath1[fakePath1.length-1]);
  423. }
  424. return [fakePath1,fakePath2];
  425. },
  426.  
  427. /**
  428. * This method animates a path when it is going to be removed.
  429. * The SVG path blinks and disappears
  430. * @method animateRemove
  431. */
  432. animateRemove:function(){
  433. this.eventAggregator.trigger("graphAnimationComplete", false);
  434. var $this=this;
  435. var pathBold=10;
  436. var animation1=Raphael.animation({"stroke-width":pathBold});
  437. var animation2=Raphael.animation({"stroke-width":this.environment.config.graph.pathWeight});
  438. var n,sum=0;
  439. var delays=this.environment.config.graph.animationPathWithdrawalDelays;
  440.  
  441. for (n=0;n<delays.length-1;n++){
  442. sum+=delays[n];
  443. this.svgPath.animate(animation1.delay(sum));
  444.  
  445. sum+=delays[n+1];
  446. this.svgPath.animate(animation2.delay(sum));
  447. }
  448.  
  449. setTimeout(function(){
  450. $this.visible=false;
  451. $this.setVisibility();
  452. $this.updateAllEdgeInCommon($this);
  453. $this.eventAggregator.trigger("graphAnimationComplete", true);
  454. $this.removeMyArcs();
  455. },sum+delays[0]);
  456.  
  457. },
  458.  
  459. /**
  460. * This method animates a path when it is going to be involved in a minor change.
  461. * The SVG path blinks.
  462. * @method animateMinorChanges
  463. */
  464. animateMinorChanges:function(event){ //A single blink
  465. this.eventAggregator.trigger("graphAnimationComplete", false);
  466. var $this=this;
  467. var animation1, animation2, delays;
  468. var n,sum=0;
  469. this.visible=true;
  470. this.setVisibility();
  471. animation1=Raphael.animation({"stroke-width":$this.environment.config.graph.pathBold});
  472. animation2=Raphael.animation({"stroke-width":$this.environment.config.graph.pathWeight});
  473.  
  474. delays=$this.environment.config.graph.animationMinorChangesDelays;
  475.  
  476. for (n=0;n<delays.length-1;n++){
  477. sum+=delays[n];
  478. this.svgPath.animate(animation1.delay(sum));
  479.  
  480. sum+=delays[n+1];
  481. this.svgPath.animate(animation2.delay(sum));
  482. }
  483. setTimeout(function(){
  484. $this.updateAllEdgeInCommon($this);
  485. $this.eventAggregator.trigger("graphAnimationComplete", true);
  486. },sum+delays[0]);
  487. },
  488.  
  489. /**
  490. * This method animates a path that was just drawn.
  491. * @method animateNewPath
  492. */
  493. animateNewPath:function(event){
  494. var newPath=event.get("path");
  495. this.eventAggregator.trigger("graphAnimationComplete", false);
  496. this.visible=true;
  497. this.setVisibility();
  498. var i;
  499. var pathBold=this.environment.config.graph.pathBold;
  500. var incrementalPath=[];
  501. var $this=this;
  502. var nodes=newPath.get("nodes");
  503. var delay=this.environment.config.graph.animationPathInsertionDelay;
  504.  
  505. incrementalPath.push(nodes[0]);
  506. for (i=1;i<nodes.length;i++){
  507. incrementalPath.push(nodes[i]);
  508. this.svgPath.animate((Raphael.animation({"stroke-width":pathBold,
  509. path:this.computePathString(incrementalPath)})).delay(delay*(i-1)));
  510. }
  511.  
  512. setTimeout(function(){
  513. $this.svgPath.toBack();
  514. $this.svgPath.attr({"stroke-width":$this.environment.config.graph.pathWeight});
  515. $this.eventAggregator.trigger("graphAnimationComplete", true);
  516. $this.updateAllEdgeInCommon($this);
  517. $this.path=newPath;
  518. },delay*i);
  519. },
  520.  
  521. /**
  522. * This method animates a path when it is going to be involved in a change of its route.
  523. * @method animatePathChange
  524. */
  525. animatePathChange:function(event){//path change ANIMATION
  526. var newPath=event.get("path");
  527. var oldPath=event.get("prevPath");
  528. this.visible=true;
  529. this.setVisibility();
  530. this.eventAggregator.trigger("graphAnimationComplete", false);
  531. var $this=this;
  532. var pathString,fakePaths;
  533. fakePaths=this.addFakePoints(newPath.get("nodes"),oldPath.get("nodes"));
  534. pathString=this.computeNormalPathString(fakePaths[1]);
  535. this.svgPath.attr({path:pathString}); //Add the same number of points to the already drawn path
  536.  
  537. this.svgPath.animate({"path":this.computeNormalPathString(fakePaths[0])},$this.environment.config.graph.animationPathChangeDelay,function(){ //we use the normal pathString function
  538. $this.svgPath.toBack();
  539. $this.path=newPath;
  540. pathString=$this.computePathString(fakePaths[0]); //Redraw the path after the animation to assign the right deviation. We use the complex pathString function
  541. $this.svgPath.attr({path:pathString}); //Redraw
  542. $this.updateAllEdgeInCommon($this);
  543. $this.eventAggregator.trigger("graphAnimationComplete", true); //Morphing
  544. });
  545. },
  546.  
  547. /**
  548. * This method provides percentages about the stability of the source-target pair in terms of sets of nodes involved.
  549. * @method getStatistics
  550. */
  551. getStatistics:function(){
  552. if (this.statistics!=null)
  553. return this.statistics;
  554.  
  555. this.statistics="";
  556. var eventsInvolvingThisPath,pathChangesInvolvingThisPath,$this,totalTime,percentage,lastChange;
  557.  
  558. $this=this;
  559.  
  560. eventsInvolvingThisPath=this.source.get("events")[this.target.id];
  561. pathChangesInvolvingThisPath=eventsInvolvingThisPath.getFilteredSubTreeMapByValue(function(event){
  562. if (event.get("subType")=="pathchange")
  563. return true;
  564. return false;
  565. });
  566.  
  567.  
  568. totalTime=this.bgplay.get("endtimestamp") - this.bgplay.get("starttimestamp");
  569. lastChange=this.bgplay.get("starttimestamp");
  570. pathChangesInvolvingThisPath.forEach(function(event){
  571. if (event.get("subType")=="pathchange"){
  572. percentage=((event.get('instant').get('timestamp')-lastChange)/totalTime)*100;
  573. if (percentage>=1){
  574. lastChange=event.get('instant').get('timestamp');
  575. $this.statistics+=percentage.roundTo(2)+"%: "+event.get("path").toString()+" | ";
  576. }
  577. }
  578. });
  579.  
  580.  
  581. }
  582. });