1(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.cytoscape = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ 2 3/*! 4 5Cytoscape.js 2.7.12 (MIT licensed) 6 7Copyright (c) The Cytoscape Consortium 8 9Permission is hereby granted, free of charge, to any person obtaining a copy of 10this software and associated documentation files (the âSoftwareâ), to deal in 11the Software without restriction, including without limitation the rights to 12use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13of the Software, and to permit persons to whom the Software is furnished to do 14so, subject to the following conditions: 15 16The above copyright notice and this permission notice shall be included in all 17copies or substantial portions of the Software. 18 19THE SOFTWARE IS PROVIDED âAS ISâ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25SOFTWARE. 26 27*/ 28 29'use strict'; 30 31},{}],2:[function(_dereq_,module,exports){ 32'use strict'; 33 34var util = _dereq_( './util' ); 35var is = _dereq_( './is' ); 36var Promise = _dereq_( './promise' ); 37 38var Animation = function( target, opts, opts2 ){ 39 if( !(this instanceof Animation) ){ 40 return new Animation( target, opts, opts2 ); 41 } 42 43 var _p = this._private = util.extend( { 44 duration: 1000 45 }, opts, opts2 ); 46 47 _p.target = target; 48 _p.style = _p.style || _p.css; 49 _p.started = false; 50 _p.playing = false; 51 _p.hooked = false; 52 _p.applying = false; 53 _p.progress = 0; 54 _p.completes = []; 55 _p.frames = []; 56 57 if( _p.complete && is.fn( _p.complete ) ){ 58 _p.completes.push( _p.complete ); 59 } 60 61 // for future timeline/animations impl 62 this.length = 1; 63 this[0] = this; 64}; 65 66var anifn = Animation.prototype; 67 68util.extend( anifn, { 69 70 instanceString: function(){ return 'animation'; }, 71 72 hook: function(){ 73 var _p = this._private; 74 75 if( !_p.hooked ){ 76 // add to target's animation queue 77 var q; 78 var tAni = _p.target._private.animation; 79 if( _p.queue ){ 80 q = tAni.queue; 81 } else { 82 q = tAni.current; 83 } 84 q.push( this ); 85 86 // add to the animation loop pool 87 if( is.elementOrCollection( _p.target ) ){ 88 _p.target.cy().addToAnimationPool( _p.target ); 89 } 90 91 _p.hooked = true; 92 } 93 94 return this; 95 }, 96 97 play: function(){ 98 var _p = this._private; 99 100 // autorewind 101 if( _p.progress === 1 ){ 102 _p.progress = 0; 103 } 104 105 _p.playing = true; 106 _p.started = false; // needs to be started by animation loop 107 _p.stopped = false; 108 109 this.hook(); 110 111 // the animation loop will start the animation... 112 113 return this; 114 }, 115 116 playing: function(){ 117 return this._private.playing; 118 }, 119 120 apply: function(){ 121 var _p = this._private; 122 123 _p.applying = true; 124 _p.started = false; // needs to be started by animation loop 125 _p.stopped = false; 126 127 this.hook(); 128 129 // the animation loop will apply the animation at this progress 130 131 return this; 132 }, 133 134 applying: function(){ 135 return this._private.applying; 136 }, 137 138 pause: function(){ 139 var _p = this._private; 140 141 _p.playing = false;
142 _p.started = false; 143 144 return this; 145 }, 146 147 stop: function(){ 148 var _p = this._private; 149 150 _p.playing = false; 151 _p.started = false; 152 _p.stopped = true; // to be removed from animation queues 153 154 return this; 155 }, 156 157 rewind: function(){ 158 return this.progress( 0 ); 159 }, 160 161 fastforward: function(){ 162 return this.progress( 1 ); 163 }, 164 165 time: function( t ){ 166 var _p = this._private; 167 168 if( t === undefined ){ 169 return _p.progress * _p.duration; 170 } else { 171 return this.progress( t / _p.duration ); 172 } 173 }, 174 175 progress: function( p ){ 176 var _p = this._private; 177 var wasPlaying = _p.playing; 178 179 if( p === undefined ){ 180 return _p.progress; 181 } else { 182 if( wasPlaying ){ 183 this.pause(); 184 } 185 186 _p.progress = p; 187 _p.started = false; 188 189 if( wasPlaying ){ 190 this.play(); 191 } 192 } 193 194 return this; 195 }, 196 197 completed: function(){ 198 return this._private.progress === 1; 199 }, 200 201 reverse: function(){ 202 var _p = this._private; 203 var wasPlaying = _p.playing; 204 205 if( wasPlaying ){ 206 this.pause(); 207 } 208 209 _p.progress = 1 - _p.progress; 210 _p.started = false; 211 212 var swap = function( a, b ){ 213 var _pa = _p[ a ]; 214 215 _p[ a ] = _p[ b ]; 216 _p[ b ] = _pa; 217 }; 218 219 swap( 'zoom', 'startZoom' ); 220 swap( 'pan', 'startPan' ); 221 swap( 'position', 'startPosition' ); 222 223 // swap styles 224 for( var i = 0; i < _p.style.length; i++ ){ 225 var prop = _p.style[ i ]; 226 var name = prop.name; 227 var startStyleProp = _p.startStyle[ name ]; 228 229 _p.startStyle[ name ] = prop; 230 _p.style[ i ] = startStyleProp; 231 } 232 233 if( wasPlaying ){ 234 this.play(); 235 } 236 237 return this; 238 }, 239 240 promise: function( type ){ 241 var _p = this._private; 242 243 var arr; 244 245 switch( type ){ 246 case 'frame': 247 arr = _p.frames; 248 break; 249 default: 250 case 'complete': 251 case 'completed': 252 arr = _p.completes; 253 } 254 255 return new Promise( function( resolve, reject ){ 256 arr.push( function(){ 257 resolve(); 258 } ); 259 } ); 260 } 261 262} ); 263 264anifn.complete = anifn.completed; 265 266module.exports = Animation; 267 268},{"./is":83,"./promise":86,"./util":100}],3:[function(_dereq_,module,exports){ 269'use strict'; 270 271var is = _dereq_( '../../is' ); 272 273var elesfn = ({ 274 275 // Implemented from pseudocode from wikipedia 276 aStar: function( options ){ 277 var eles = this; 278 279 options = options || {}; 280 281 // Reconstructs the path from Start to End, acumulating the result in pathAcum 282 var reconstructPath = function( start, end, cameFromMap, pathAcum ){ 283 // Base case 284 if( start == end ){ 285 pathAcum.push( cy.getElementById( end ) ); 286 return pathAcum; 287 } 288 289 if( end in cameFromMap ){ 290 // We know which node is before the last one 291 var previous = cameFromMap[ end ]; 292 var previousEdge = cameFromEdge[ end ]; 293 294 pathAcum.push( cy.getElementById( end ) ); 295 pathAcum.push( cy.getElementById( previousEdge ) ); 296 297 298 return reconstructPath( start, 299 previous, 300 cameFromMap, 301 pathAcum ); 302 } 303 304 // We should not reach here! 305 return undefined; 306 }; 307 308 // Returns the index of the element in openSet which has minimum fScore 309 var findMin = function( openSet, fScore ){ 310 if( openSet.length === 0 ){ 311 // Should never be the case 312 return undefined; 313 } 314 var minPos = 0; 315 var tempScore = fScore[ openSet[0] ]; 316 for( var i = 1; i < openSet.length; i++ ){ 317 var s = fScore[ openSet[ i ] ]; 318 if( s < tempScore ){ 319 tempScore = s; 320 minPos = i; 321 } 322 } 323 return minPos; 324 }; 325 326 var cy = this._private.cy; 327 328 // root - mandatory! 329 if( options != null && options.root != null ){ 330 var source = is.string( options.root ) ? 331 // use it as a selector, e.g. "#rootID 332 this.filter( options.root )[0] : 333 options.root[0]; 334 } else { 335 return undefined; 336 } 337 338 // goal - mandatory! 339 if( options.goal != null ){ 340 var target = is.string( options.goal ) ? 341 // use it as a selector, e.g. "#goalID 342 this.filter( options.goal )[0] : 343 options.goal[0]; 344 } else { 345 return undefined; 346 } 347 348 // Heuristic function - optional 349 if( options.heuristic != null && is.fn( options.heuristic ) ){ 350 var heuristic = options.heuristic; 351 } else { 352 var heuristic = function(){ return 0; }; // use constant if unspecified 353 } 354 355 // Weight function - optional 356 if( options.weight != null && is.fn( options.weight ) ){ 357 var weightFn = options.weight; 358 } else { 359 // If not specified, assume each edge has equal weight (1) 360 var weightFn = function( e ){return 1;}; 361 } 362 363 // directed - optional 364 if( options.directed != null ){ 365 var directed = options.directed; 366 } else { 367 var directed = false;
368 } 369 370 var closedSet = []; 371 var openSet = [ source.id() ]; 372 var cameFrom = {}; 373 var cameFromEdge = {}; 374 var gScore = {}; 375 var fScore = {}; 376 377 gScore[ source.id() ] = 0; 378 fScore[ source.id() ] = heuristic( source ); 379 380 var edges = this.edges().stdFilter( function( e ){ return !e.isLoop(); } ); 381 var nodes = this.nodes(); 382 383 // Counter 384 var steps = 0; 385 386 // Main loop 387 while( openSet.length > 0 ){ 388 var minPos = findMin( openSet, fScore ); 389 var cMin = cy.getElementById( openSet[ minPos ] ); 390 steps++; 391 392 // If we've found our goal, then we are done 393 if( cMin.id() == target.id() ){ 394 var rPath = reconstructPath( source.id(), target.id(), cameFrom, [] ); 395 rPath.reverse(); 396 return { 397 found: true, 398 distance: gScore[ cMin.id() ], 399 path: eles.spawn( rPath ), 400 steps: steps 401 }; 402 } 403 404 // Add cMin to processed nodes 405 closedSet.push( cMin.id() ); 406 // Remove cMin from boundary nodes 407 openSet.splice( minPos, 1 ); 408 409 // Update scores for neighbors of cMin 410 // Take into account if graph is directed or not 411 var vwEdges = cMin.connectedEdges(); 412 if( directed ){ vwEdges = vwEdges.stdFilter( function( ele ){ return ele.data( 'source' ) === cMin.id(); } ); } 413 vwEdges = vwEdges.intersect( edges ); 414 415 for( var i = 0; i < vwEdges.length; i++ ){ 416 var e = vwEdges[ i ]; 417 var w = e.connectedNodes().stdFilter( function( n ){ return n.id() !== cMin.id(); } ).intersect( nodes ); 418 419 // if node is in closedSet, ignore it 420 if( closedSet.indexOf( w.id() ) != -1 ){ 421 continue; 422 } 423 424 // New tentative score for node w 425 var tempScore = gScore[ cMin.id() ] + weightFn.apply( e, [ e ] ); 426 427 // Update gScore for node w if: 428 // w not present in openSet 429 // OR 430 // tentative gScore is less than previous value 431 432 // w not in openSet 433 if( openSet.indexOf( w.id() ) == -1 ){ 434 gScore[ w.id() ] = tempScore; 435 fScore[ w.id() ] = tempScore + heuristic( w ); 436 openSet.push( w.id() ); // Add node to openSet 437 cameFrom[ w.id() ] = cMin.id(); 438 cameFromEdge[ w.id() ] = e.id(); 439 continue; 440 } 441 // w already in openSet, but with greater gScore 442 if( tempScore < gScore[ w.id() ] ){ 443 gScore[ w.id() ] = tempScore; 444 fScore[ w.id() ] = tempScore + heuristic( w ); 445 cameFrom[ w.id() ] = cMin.id(); 446 } 447 448 } // End of neighbors update 449 450 } // End of main loop 451 452 // If we've reached here, then we've not reached our goal 453 return { 454 found: false, 455 distance: undefined, 456 path: undefined, 457 steps: steps 458 }; 459 } 460 461}); // elesfn 462 463 464module.exports = elesfn; 465 466},{"../../is":83}],4:[function(_dereq_,module,exports){ 467'use strict'; 468 469var is = _dereq_( '../../is' ); 470var util = _dereq_( '../../util' ); 471 472var elesfn = ({ 473 474 // Implemented from pseudocode from wikipedia 475 bellmanFord: function( options ){ 476 var eles = this; 477 478 options = options || {}; 479 480 // Weight function - optional 481 if( options.weight != null && is.fn( options.weight ) ){ 482 var weightFn = options.weight; 483 } else { 484 // If not specified, assume each edge has equal weight (1) 485 var weightFn = function( e ){return 1;}; 486 } 487 488 // directed - optional 489 if( options.directed != null ){ 490 var directed = options.directed; 491 } else { 492 var directed = false; 493 } 494 495 // root - mandatory! 496 if( options.root != null ){ 497 if( is.string( options.root ) ){ 498 // use it as a selector, e.g. "#rootID 499 var source = this.filter( options.root )[0]; 500 } else { 501 var source = options.root[0]; 502 } 503 } else { 504 return undefined; 505 } 506 507 var cy = this._private.cy; 508 var edges = this.edges().stdFilter( function( e ){ return !e.isLoop(); } ); 509 var nodes = this.nodes(); 510 var numNodes = nodes.length; 511 512 // mapping: node id -> position in nodes array 513 var id2position = {}; 514 for( var i = 0; i < numNodes; i++ ){ 515 id2position[ nodes[ i ].id() ] = i; 516 } 517 518 // Initializations 519 var cost = []; 520 var predecessor = []; 521 var predEdge = []; 522 523 for( var i = 0; i < numNodes; i++ ){ 524 if( nodes[ i ].id() === source.id() ){ 525 cost[ i ] = 0; 526 } else { 527 cost[ i ] = Infinity; 528 } 529 predecessor[ i ] = undefined; 530 } 531 532 // Edges relaxation 533 var flag = false;
534 for( var i = 1; i < numNodes; i++ ){ 535 flag = false; 536 for( var e = 0; e < edges.length; e++ ){ 537 var sourceIndex = id2position[ edges[ e ].source().id() ]; 538 var targetIndex = id2position[ edges[ e ].target().id() ]; 539 var weight = weightFn.apply( edges[ e ], [ edges[ e ] ] ); 540 541 var temp = cost[ sourceIndex ] + weight; 542 if( temp < cost[ targetIndex ] ){ 543 cost[ targetIndex ] = temp; 544 predecessor[ targetIndex ] = sourceIndex; 545 predEdge[ targetIndex ] = edges[ e ]; 546 flag = true; 547 } 548 549 // If undirected graph, we need to take into account the 'reverse' edge 550 if( !directed ){ 551 var temp = cost[ targetIndex ] + weight; 552 if( temp < cost[ sourceIndex ] ){ 553 cost[ sourceIndex ] = temp; 554 predecessor[ sourceIndex ] = targetIndex; 555 predEdge[ sourceIndex ] = edges[ e ]; 556 flag = true; 557 } 558 } 559 } 560 561 if( !flag ){ 562 break; 563 } 564 } 565 566 if( flag ){ 567 // Check for negative weight cycles 568 for( var e = 0; e < edges.length; e++ ){ 569 var sourceIndex = id2position[ edges[ e ].source().id() ]; 570 var targetIndex = id2position[ edges[ e ].target().id() ]; 571 var weight = weightFn.apply( edges[ e ], [ edges[ e ] ] ); 572 573 if( cost[ sourceIndex ] + weight < cost[ targetIndex ] ){ 574 util.error( 'Graph contains a negative weight cycle for Bellman-Ford' ); 575 return { pathTo: undefined, 576 distanceTo: undefined, 577 hasNegativeWeightCycle: true}; 578 } 579 } 580 } 581 582 // Build result object 583 var position2id = []; 584 for( var i = 0; i < numNodes; i++ ){ 585 position2id.push( nodes[ i ].id() ); 586 } 587 588 589 var res = { 590 distanceTo: function( to ){ 591 if( is.string( to ) ){ 592 // to is a selector string 593 var toId = (cy.filter( to )[0]).id(); 594 } else { 595 // to is a node 596 var toId = to.id(); 597 } 598 599 return cost[ id2position[ toId ] ]; 600 }, 601 602 pathTo: function( to ){ 603 604 var reconstructPathAux = function( predecessor, fromPos, toPos, position2id, acumPath, predEdge ){ 605 for( ;; ){ 606 // Add toId to path 607 acumPath.push( cy.getElementById( position2id[ toPos ] ) ); 608 acumPath.push( predEdge[ toPos ] ); 609 610 if( fromPos === toPos ){ 611 // reached starting node 612 return acumPath; 613 } 614 615 // If no path exists, discart acumulated path and return undefined 616 var predPos = predecessor[ toPos ]; 617 if( typeof predPos === 'undefined' ){ 618 return undefined; 619 } 620 621 toPos = predPos; 622 } 623 624 }; 625 626 if( is.string( to ) ){ 627 // to is a selector string 628 var toId = (cy.filter( to )[0]).id(); 629 } else { 630 // to is a node 631 var toId = to.id(); 632 } 633 var path = []; 634 635 // This returns a reversed path 636 var res = reconstructPathAux( predecessor, 637 id2position[ source.id() ], 638 id2position[ toId ], 639 position2id, 640 path, 641 predEdge ); 642 643 // Get it in the correct order and return it 644 if( res != null ){ 645 res.reverse(); 646 } 647 648 return eles.spawn( res ); 649 }, 650 651 hasNegativeWeightCycle: false 652 }; 653 654 return res; 655 656 } // bellmanFord 657 658}); // elesfn 659 660module.exports = elesfn; 661 662},{"../../is":83,"../../util":100}],5:[function(_dereq_,module,exports){ 663'use strict'; 664 665var is = _dereq_( '../../is' ); 666var Heap = _dereq_( '../../heap' ); 667 668var elesfn = ({ 669 670 // Implemented from the algorithm in the paper "On Variants of Shortest-Path Betweenness Centrality and their Generic Computation" by Ulrik Brandes 671 betweennessCentrality: function( options ){ 672 options = options || {}; 673 674 // Weight - optional 675 var weighted, weightFn; 676 if( is.fn( options.weight ) ){ 677 weightFn = options.weight; 678 weighted = true; 679 } else { 680 weighted = false;
681 } 682 683 // Directed - default false 684 var directed = options.directed != null ? options.directed : false; 685 686 var cy = this._private.cy; 687 688 // starting 689 var V = this.nodes(); 690 var A = {}; 691 var _C = {}; 692 var max = 0; 693 var C = { 694 set: function( key, val ){ 695 _C[ key ] = val; 696 697 if( val > max ){ max = val; } 698 }, 699 700 get: function( key ){ return _C[ key ]; } 701 }; 702 703 // A contains the neighborhoods of every node 704 for( var i = 0; i < V.length; i++ ){ 705 var v = V[ i ]; 706 var vid = v.id(); 707 708 if( directed ){ 709 A[ vid ] = v.outgoers().nodes(); // get outgoers of every node 710 } else { 711 A[ vid ] = v.openNeighborhood().nodes(); // get neighbors of every node 712 } 713 714 C.set( vid, 0 ); 715 } 716 717 for( var s = 0; s < V.length; s++ ){ 718 var sid = V[s].id(); 719 var S = []; // stack 720 var P = {}; 721 var g = {}; 722 var d = {}; 723 var Q = new Heap(function( a, b ){ 724 return d[a] - d[b]; 725 }); // queue 726 727 // init dictionaries 728 for( var i = 0; i < V.length; i++ ){ 729 var vid = V[ i ].id(); 730 731 P[ vid ] = []; 732 g[ vid ] = 0; 733 d[ vid ] = Infinity; 734 } 735 736 g[ sid ] = 1; // sigma 737 d[ sid ] = 0; // distance to s 738 739 Q.push( sid ); 740 741 while( !Q.empty() ){ 742 var v = Q.pop(); 743 744 S.push( v ); 745 746 if( weighted ){ 747 for( var j = 0; j < A[v].length; j++ ){ 748 var w = A[v][j]; 749 var vEle = cy.getElementById( v ); 750 751 var edge; 752 if( vEle.edgesTo( w ).length > 0 ){ 753 edge = vEle.edgesTo( w )[0]; 754 } else { 755 edge = w.edgesTo( vEle )[0]; 756 } 757 758 var edgeWeight = weightFn.apply( edge, [ edge ] ); 759 760 w = w.id(); 761 762 if( d[w] > d[v] + edgeWeight ){ 763 d[w] = d[v] + edgeWeight; 764 765 if( Q.nodes.indexOf( w ) < 0 ){ //if w is not in Q 766 Q.push( w ); 767 } else { // update position if w is in Q 768 Q.updateItem( w ); 769 } 770 771 g[w] = 0; 772 P[w] = []; 773 } 774 775 if( d[w] == d[v] + edgeWeight ){ 776 g[w] = g[w] + g[v]; 777 P[w].push( v ); 778 } 779 } 780 } else { 781 for( var j = 0; j < A[v].length; j++ ){ 782 var w = A[v][j].id(); 783 784 if( d[w] == Infinity ){ 785 Q.push( w ); 786 787 d[w] = d[v] + 1; 788 } 789 790 if( d[w] == d[v] + 1 ){ 791 g[w] = g[w] + g[v]; 792 P[w].push( v ); 793 } 794 } 795 } 796 } 797 798 var e = {}; 799 for( var i = 0; i < V.length; i++ ){ 800 e[ V[ i ].id() ] = 0; 801 } 802 803 while( S.length > 0 ){ 804 var w = S.pop(); 805 806 for( var j = 0; j < P[w].length; j++ ){ 807 var v = P[w][j]; 808 809 e[v] = e[v] + (g[v] / g[w]) * (1 + e[w]); 810 811 if( w != V[s].id() ){ 812 C.set( w, C.get( w ) + e[w] ); 813 } 814 } 815 } 816 } 817 818 var ret = { 819 betweenness: function( node ){ 820 if( is.string( node ) ){ 821 var node = cy.filter( node ).id(); 822 } else { 823 var node = node.id(); 824 } 825 826 return C.get( node ); 827 }, 828 829 betweennessNormalized: function( node ){ 830 if ( max == 0 ) 831 return 0; 832 833 if( is.string( node ) ){ 834 var node = cy.filter( node ).id(); 835 } else { 836 var node = node.id(); 837 } 838 839 return C.get( node ) / max; 840 } 841 }; 842 843 // alias 844 ret.betweennessNormalised = ret.betweennessNormalized; 845 846 return ret; 847 } // betweennessCentrality 848 849}); // elesfn 850 851// nice, short mathemathical alias 852elesfn.bc = elesfn.betweennessCentrality; 853 854module.exports = elesfn; 855 856},{"../../heap":81,"../../is":83}],6:[function(_dereq_,module,exports){ 857'use strict'; 858 859var is = _dereq_( '../../is' ); 860 861var defineSearch = function( params ){ 862 params = { 863 bfs: params.bfs || !params.dfs, 864 dfs: params.dfs || !params.bfs 865 }; 866 867 // from pseudocode on wikipedia 868 return function searchFn( roots, fn, directed ){ 869 var options; 870 var std; 871 var thisArg; 872 if( is.plainObject( roots ) && !is.elementOrCollection( roots ) ){ 873 options = roots; 874 roots = options.roots || options.root; 875 fn = options.visit; 876 directed = options.directed; 877 std = options.std; 878 thisArg = options.thisArg; 879 } 880 881 directed = arguments.length === 2 && !is.fn( fn ) ? fn : directed; 882 fn = is.fn( fn ) ? fn : function(){}; 883 884 var cy = this._private.cy; 885 var v = roots = is.string( roots ) ? this.filter( roots ) : roots; 886 var Q = []; 887 var connectedNodes = []; 888 var connectedBy = {}; 889 var id2depth = {}; 890 var V = {}; 891 var j = 0; 892 var found; 893 var nodes = this.nodes(); 894 var edges = this.edges(); 895 896 // enqueue v 897 for( var i = 0; i < v.length; i++ ){ 898 if( v[ i ].isNode() ){ 899 Q.unshift( v[ i ] ); 900 901 if( params.bfs ){ 902 V[ v[ i ].id() ] = true; 903 904 connectedNodes.push( v[ i ] ); 905 } 906 907 id2depth[ v[ i ].id() ] = 0; 908 } 909 } 910 911 while( Q.length !== 0 ){ 912 var v = params.bfs ? Q.shift() : Q.pop(); 913 914 if( params.dfs ){ 915 if( V[ v.id() ] ){ continue; } 916
917 V[ v.id() ] = true; 918 919 connectedNodes.push( v ); 920 } 921 922 var depth = id2depth[ v.id() ]; 923 var prevEdge = connectedBy[ v.id() ]; 924 var prevNode = prevEdge == null ? undefined : prevEdge.connectedNodes().not( v )[0]; 925 var ret; 926 927 if( std ){ 928 ret = fn.call( thisArg, v, prevEdge, prevNode, j++, depth ); 929 } else { 930 ret = fn.call( v, j++, depth, v, prevEdge, prevNode ); 931 } 932 933 if( ret === true ){ 934 found = v; 935 break; 936 } 937 938 if( ret === false ){ 939 break; 940 } 941 942 var vwEdges = v.connectedEdges( directed ? function(){ return this.data( 'source' ) === v.id(); } : undefined ).intersect( edges ); 943 for( var i = 0; i < vwEdges.length; i++ ){ 944 var e = vwEdges[ i ]; 945 var w = e.connectedNodes( function(){ return this.id() !== v.id(); } ).intersect( nodes ); 946 947 if( w.length !== 0 && !V[ w.id() ] ){ 948 w = w[0]; 949 950 Q.push( w ); 951 952 if( params.bfs ){ 953 V[ w.id() ] = true; 954 955 connectedNodes.push( w ); 956 } 957 958 connectedBy[ w.id() ] = e; 959 960 id2depth[ w.id() ] = id2depth[ v.id() ] + 1; 961 } 962 } 963 964 } 965 966 var connectedEles = []; 967 968 for( var i = 0; i < connectedNodes.length; i++ ){ 969 var node = connectedNodes[ i ]; 970 var edge = connectedBy[ node.id() ]; 971 972 if( edge ){ 973 connectedEles.push( edge ); 974 } 975 976 connectedEles.push( node ); 977 } 978 979 return { 980 path: cy.collection( connectedEles, { unique: true } ), 981 found: cy.collection( found ) 982 }; 983 }; 984}; 985 986// search, spanning trees, etc 987var elesfn = ({ 988 breadthFirstSearch: defineSearch( { bfs: true } ), 989 depthFirstSearch: defineSearch( { dfs: true } ) 990}); 991 992// nice, short mathemathical alias 993elesfn.bfs = elesfn.breadthFirstSearch; 994elesfn.dfs = elesfn.depthFirstSearch; 995 996module.exports = elesfn; 997 998},{"../../is":83}],7:[function(_dereq_,module,exports){ 999'use strict'; 1000 1001var is = _dereq_( '../../is' ); 1002 1003var elesfn = ({ 1004 1005 closenessCentralityNormalized: function( options ){ 1006 options = options || {}; 1007 1008 var cy = this.cy(); 1009 1010 var harmonic = options.harmonic; 1011 if( harmonic === undefined ){ 1012 harmonic = true; 1013 } 1014 1015 var closenesses = {}; 1016 var maxCloseness = 0; 1017 var nodes = this.nodes(); 1018 var fw = this.floydWarshall( { weight: options.weight, directed: options.directed } ); 1019 1020 // Compute closeness for every node and find the maximum closeness 1021 for( var i = 0; i < nodes.length; i++ ){ 1022 var currCloseness = 0; 1023 for( var j = 0; j < nodes.length; j++ ){ 1024 if( i != j ){ 1025 var d = fw.distance( nodes[ i ], nodes[ j ] ); 1026 1027 if( harmonic ){ 1028 currCloseness += 1 / d; 1029 } else { 1030 currCloseness += d; 1031 } 1032 } 1033 } 1034 1035 if( !harmonic ){ 1036 currCloseness = 1 / currCloseness; 1037 } 1038 1039 if( maxCloseness < currCloseness ){ 1040 maxCloseness = currCloseness; 1041 } 1042 1043 closenesses[ nodes[ i ].id() ] = currCloseness; 1044 } 1045 1046 return { 1047 closeness: function( node ){ 1048 if( maxCloseness == 0 ){ return 0; } 1049 1050 if( is.string( node ) ){ 1051 // from is a selector string 1052 var node = (cy.filter( node )[0]).id(); 1053 } else { 1054 // from is a node 1055 var node = node.id(); 1056 } 1057 1058 return closenesses[ node ] / maxCloseness; 1059 } 1060 }; 1061 }, 1062 1063 // Implemented from pseudocode from wikipedia 1064 closenessCentrality: function( options ){ 1065 options = options || {}; 1066 1067 // root - mandatory! 1068 if( options.root != null ){ 1069 if( is.string( options.root ) ){ 1070 // use it as a selector, e.g. "#rootID 1071 var root = this.filter( options.root )[0]; 1072 } else { 1073 var root = options.root[0]; 1074 } 1075 } else { 1076 return undefined; 1077 } 1078 1079 // weight - optional 1080 if( options.weight != null && is.fn( options.weight ) ){ 1081 var weight = options.weight; 1082 } else { 1083 var weight = function(){return 1;}; 1084 } 1085 1086 // directed - optional 1087 if( options.directed != null && is.bool( options.directed ) ){ 1088 var directed = options.directed; 1089 } else { 1090 var directed = false;
1091 } 1092 1093 var harmonic = options.harmonic; 1094 if( harmonic === undefined ){ 1095 harmonic = true; 1096 } 1097 1098 // we need distance from this node to every other node 1099 var dijkstra = this.dijkstra( { 1100 root: root, 1101 weight: weight, 1102 directed: directed 1103 } ); 1104 var totalDistance = 0; 1105 1106 var nodes = this.nodes(); 1107 for( var i = 0; i < nodes.length; i++ ){ 1108 if( nodes[ i ].id() != root.id() ){ 1109 var d = dijkstra.distanceTo( nodes[ i ] ); 1110 1111 if( harmonic ){ 1112 totalDistance += 1 / d; 1113 } else { 1114 totalDistance += d; 1115 } 1116 } 1117 } 1118 1119 return harmonic ? totalDistance : 1 / totalDistance; 1120 } // closenessCentrality 1121 1122}); // elesfn 1123 1124// nice, short mathemathical alias 1125elesfn.cc = elesfn.closenessCentrality; 1126elesfn.ccn = elesfn.closenessCentralityNormalised = elesfn.closenessCentralityNormalized; 1127 1128module.exports = elesfn; 1129 1130},{"../../is":83}],8:[function(_dereq_,module,exports){ 1131'use strict'; 1132 1133var is = _dereq_( '../../is' ); 1134var util = _dereq_( '../../util' ); 1135 1136var elesfn = ({ 1137 1138 degreeCentralityNormalized: function( options ){ 1139 options = options || {}; 1140 1141 var cy = this.cy(); 1142 1143 // directed - optional 1144 if( options.directed != null ){ 1145 var directed = options.directed; 1146 } else { 1147 var directed = false; 1148 } 1149 1150 var nodes = this.nodes(); 1151 var numNodes = nodes.length; 1152 1153 if( !directed ){ 1154 var degrees = {}; 1155 var maxDegree = 0; 1156 1157 for( var i = 0; i < numNodes; i++ ){ 1158 var node = nodes[ i ]; 1159 // add current node to the current options object and call degreeCentrality 1160 var currDegree = this.degreeCentrality( util.extend( {}, options, {root: node} ) ); 1161 if( maxDegree < currDegree.degree ) 1162 maxDegree = currDegree.degree; 1163 1164 degrees[ node.id() ] = currDegree.degree; 1165 } 1166 1167 return { 1168 degree: function( node ){ 1169 if( maxDegree == 0 ) 1170 return 0; 1171 1172 if( is.string( node ) ){ 1173 // from is a selector string 1174 var node = (cy.filter( node )[0]).id(); 1175 } else { 1176 // from is a node 1177 var node = node.id(); 1178 } 1179 1180 return degrees[ node ] / maxDegree; 1181 } 1182 }; 1183 } else { 1184 var indegrees = {}; 1185 var outdegrees = {}; 1186 var maxIndegree = 0; 1187 var maxOutdegree = 0; 1188 1189 for( var i = 0; i < numNodes; i++ ){ 1190 var node = nodes[ i ]; 1191 // add current node to the current options object and call degreeCentrality 1192 var currDegree = this.degreeCentrality( util.extend( {}, options, {root: node} ) ); 1193 1194 if( maxIndegree < currDegree.indegree ) 1195 maxIndegree = currDegree.indegree; 1196 1197 if( maxOutdegree < currDegree.outdegree ) 1198 maxOutdegree = currDegree.outdegree; 1199 1200 indegrees[ node.id() ] = currDegree.indegree; 1201 outdegrees[ node.id() ] = currDegree.outdegree; 1202 } 1203 1204 return { 1205 indegree: function( node ){ 1206 if ( maxIndegree == 0 ) 1207 return 0; 1208 1209 if( is.string( node ) ){ 1210 // from is a selector string 1211 var node = (cy.filter( node )[0]).id(); 1212 } else { 1213 // from is a node 1214 var node = node.id(); 1215 } 1216 1217 return indegrees[ node ] / maxIndegree; 1218 }, 1219 outdegree: function( node ){ 1220 if ( maxOutdegree == 0 ) 1221 return 0; 1222 1223 if( is.string( node ) ){ 1224 // from is a selector string 1225 var node = (cy.filter( node )[0]).id(); 1226 } else { 1227 // from is a node 1228 var node = node.id(); 1229 } 1230 1231 return outdegrees[ node ] / maxOutdegree; 1232 } 1233 1234 }; 1235 } 1236 1237 }, // degreeCentralityNormalized 1238 1239 // Implemented from the algorithm in Opsahl's paper 1240 // "Node centrality in weighted networks: Generalizing degree and shortest paths" 1241 // check the heading 2 "Degree" 1242 degreeCentrality: function( options ){ 1243 options = options || {}; 1244 1245 var callingEles = this; 1246 1247 // root - mandatory! 1248 if( options != null && options.root != null ){ 1249 var root = is.string( options.root ) ? this.filter( options.root )[0] : options.root[0]; 1250 } else { 1251 return undefined; 1252 } 1253 1254 // weight - optional 1255 if( options.weight != null && is.fn( options.weight ) ){ 1256 var weightFn = options.weight; 1257 } else { 1258 // If not specified, assume each edge has equal weight (1) 1259 var weightFn = function( e ){ 1260 return 1; 1261 }; 1262 } 1263 1264 // directed - optional 1265 if( options.directed != null ){ 1266 var directed = options.directed; 1267 } else { 1268 var directed = false;
1269 } 1270 1271 // alpha - optional 1272 if( options.alpha != null && is.number( options.alpha ) ){ 1273 var alpha = options.alpha; 1274 } else { 1275 alpha = 0; 1276 } 1277 1278 1279 if( !directed ){ 1280 var connEdges = root.connectedEdges().intersection( callingEles ); 1281 var k = connEdges.length; 1282 var s = 0; 1283 1284 // Now, sum edge weights 1285 for( var i = 0; i < connEdges.length; i++ ){ 1286 var edge = connEdges[ i ]; 1287 s += weightFn.apply( edge, [ edge ] ); 1288 } 1289 1290 return { 1291 degree: Math.pow( k, 1 - alpha ) * Math.pow( s, alpha ) 1292 }; 1293 } else { 1294 var incoming = root.connectedEdges( 'edge[target = "' + root.id() + '"]' ).intersection( callingEles ); 1295 var outgoing = root.connectedEdges( 'edge[source = "' + root.id() + '"]' ).intersection( callingEles ); 1296 var k_in = incoming.length; 1297 var k_out = outgoing.length; 1298 var s_in = 0; 1299 var s_out = 0; 1300 1301 // Now, sum incoming edge weights 1302 for( var i = 0; i < incoming.length; i++ ){ 1303 var edge = incoming[ i ]; 1304 s_in += weightFn.apply( edge, [ edge ] ); 1305 } 1306 1307 // Now, sum outgoing edge weights 1308 for( var i = 0; i < outgoing.length; i++ ){ 1309 var edge = outgoing[ i ]; 1310 s_out += weightFn.apply( edge, [ edge ] ); 1311 } 1312 1313 return { 1314 indegree: Math.pow( k_in, 1 - alpha ) * Math.pow( s_in, alpha ), 1315 outdegree: Math.pow( k_out, 1 - alpha ) * Math.pow( s_out, alpha ) 1316 }; 1317 } 1318 } // degreeCentrality 1319 1320}); // elesfn 1321 1322// nice, short mathemathical alias 1323elesfn.dc = elesfn.degreeCentrality; 1324elesfn.dcn = elesfn.degreeCentralityNormalised = elesfn.degreeCentralityNormalized; 1325 1326module.exports = elesfn; 1327 1328},{"../../is":83,"../../util":100}],9:[function(_dereq_,module,exports){ 1329'use strict'; 1330 1331var is = _dereq_( '../../is' ); 1332var Heap = _dereq_( '../../heap' ); 1333 1334var elesfn = ({ 1335 1336 dijkstra: function( root, weightFn, directed ){ 1337 var options; 1338 if( is.plainObject( root ) && !is.elementOrCollection( root ) ){ 1339 options = root; 1340 root = options.root; 1341 weightFn = options.weight; 1342 directed = options.directed; 1343 } 1344 1345 var cy = this._private.cy; 1346 weightFn = is.fn( weightFn ) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1) 1347 1348 var source = is.string( root ) ? this.filter( root )[0] : root[0]; 1349 var dist = {}; 1350 var prev = {}; 1351 var knownDist = {}; 1352 1353 var edges = this.edges().filter( function(){ return !this.isLoop(); } ); 1354 var nodes = this.nodes(); 1355 1356 var getDist = function( node ){ 1357 return dist[ node.id() ]; 1358 }; 1359 1360 var setDist = function( node, d ){ 1361 dist[ node.id() ] = d; 1362 1363 Q.updateItem( node ); 1364 }; 1365 1366 var Q = new Heap( function( a, b ){ 1367 return getDist( a ) - getDist( b ); 1368 } ); 1369 1370 for( var i = 0; i < nodes.length; i++ ){ 1371 var node = nodes[ i ]; 1372 1373 dist[ node.id() ] = node.same( source ) ? 0 : Infinity; 1374 Q.push( node ); 1375 } 1376 1377 var distBetween = function( u, v ){ 1378 var uvs = ( directed ? u.edgesTo( v ) : u.edgesWith( v ) ).intersect( edges ); 1379 var smallestDistance = Infinity; 1380 var smallestEdge; 1381 1382 for( var i = 0; i < uvs.length; i++ ){ 1383 var edge = uvs[ i ]; 1384 var weight = weightFn.apply( edge, [ edge ] ); 1385 1386 if( weight < smallestDistance || !smallestEdge ){ 1387 smallestDistance = weight; 1388 smallestEdge = edge; 1389 } 1390 } 1391 1392 return { 1393 edge: smallestEdge, 1394 dist: smallestDistance 1395 }; 1396 }; 1397 1398 while( Q.size() > 0 ){ 1399 var u = Q.pop(); 1400 var smalletsDist = getDist( u ); 1401 var uid = u.id(); 1402 1403 knownDist[ uid ] = smalletsDist; 1404 1405 if( smalletsDist === Math.Infinite ){ 1406 break; 1407 } 1408 1409 var neighbors = u.neighborhood().intersect( nodes ); 1410 for( var i = 0; i < neighbors.length; i++ ){ 1411 var v = neighbors[ i ]; 1412 var vid = v.id(); 1413 var vDist = distBetween( u, v ); 1414 1415 var alt = smalletsDist + vDist.dist; 1416 1417 if( alt < getDist( v ) ){ 1418 setDist( v, alt ); 1419 1420 prev[ vid ] = { 1421 node: u, 1422 edge: vDist.edge 1423 }; 1424 } 1425 } // for 1426 } // while 1427 1428 return { 1429 distanceTo: function( node ){
1430 var target = is.string( node ) ? nodes.filter( node )[0] : node[0]; 1431 1432 return knownDist[ target.id() ]; 1433 }, 1434 1435 pathTo: function( node ){ 1436 var target = is.string( node ) ? nodes.filter( node )[0] : node[0]; 1437 var S = []; 1438 var u = target; 1439 1440 if( target.length > 0 ){ 1441 S.unshift( target ); 1442 1443 while( prev[ u.id() ] ){ 1444 var p = prev[ u.id() ]; 1445 1446 S.unshift( p.edge ); 1447 S.unshift( p.node ); 1448 1449 u = p.node; 1450 } 1451 } 1452 1453 return cy.collection( S ); 1454 } 1455 }; 1456 } 1457}); 1458 1459module.exports = elesfn; 1460 1461},{"../../heap":81,"../../is":83}],10:[function(_dereq_,module,exports){ 1462'use strict'; 1463 1464var is = _dereq_( '../../is' ); 1465 1466var elesfn = ({ 1467 1468 // Implemented from pseudocode from wikipedia 1469 floydWarshall: function( options ){ 1470 options = options || {}; 1471 1472 var cy = this.cy(); 1473 1474 // Weight function - optional 1475 if( options.weight != null && is.fn( options.weight ) ){ 1476 var weightFn = options.weight; 1477 } else { 1478 // If not specified, assume each edge has equal weight (1) 1479 var weightFn = function( e ){return 1;}; 1480 } 1481 1482 // directed - optional 1483 if( options.directed != null ){ 1484 var directed = options.directed; 1485 } else { 1486 var directed = false; 1487 } 1488 1489 var edges = this.edges().stdFilter( function( e ){ return !e.isLoop(); } ); 1490 var nodes = this.nodes(); 1491 var numNodes = nodes.length; 1492 1493 // mapping: node id -> position in nodes array 1494 var id2position = {}; 1495 for( var i = 0; i < numNodes; i++ ){ 1496 id2position[ nodes[ i ].id() ] = i; 1497 } 1498 1499 // Initialize distance matrix 1500 var dist = []; 1501 for( var i = 0; i < numNodes; i++ ){ 1502 var newRow = new Array( numNodes ); 1503 for( var j = 0; j < numNodes; j++ ){ 1504 if( i == j ){ 1505 newRow[ j ] = 0; 1506 } else { 1507 newRow[ j ] = Infinity; 1508 } 1509 } 1510 dist.push( newRow ); 1511 } 1512 1513 // Initialize matrix used for path reconstruction 1514 // Initialize distance matrix 1515 var next = []; 1516 var edgeNext = []; 1517 1518 var initMatrix = function( next ){ 1519 for( var i = 0; i < numNodes; i++ ){ 1520 var newRow = new Array( numNodes ); 1521 for( var j = 0; j < numNodes; j++ ){ 1522 newRow[ j ] = undefined; 1523 } 1524 next.push( newRow ); 1525 } 1526 }; 1527 1528 initMatrix( next ); 1529 initMatrix( edgeNext ); 1530 1531 // Process edges 1532 for( var i = 0; i < edges.length ; i++ ){ 1533 var sourceIndex = id2position[ edges[ i ].source().id() ]; 1534 var targetIndex = id2position[ edges[ i ].target().id() ]; 1535 var weight = weightFn.apply( edges[ i ], [ edges[ i ] ] ); 1536 1537 // Check if already process another edge between same 2 nodes 1538 if( dist[ sourceIndex ][ targetIndex ] > weight ){ 1539 dist[ sourceIndex ][ targetIndex ] = weight; 1540 next[ sourceIndex ][ targetIndex ] = targetIndex; 1541 edgeNext[ sourceIndex ][ targetIndex ] = edges[ i ]; 1542 } 1543 } 1544 1545 // If undirected graph, process 'reversed' edges 1546 if( !directed ){ 1547 for( var i = 0; i < edges.length ; i++ ){ 1548 var sourceIndex = id2position[ edges[ i ].target().id() ]; 1549 var targetIndex = id2position[ edges[ i ].source().id() ]; 1550 var weight = weightFn.apply( edges[ i ], [ edges[ i ] ] ); 1551 1552 // Check if already process another edge between same 2 nodes 1553 if( dist[ sourceIndex ][ targetIndex ] > weight ){ 1554 dist[ sourceIndex ][ targetIndex ] = weight; 1555 next[ sourceIndex ][ targetIndex ] = targetIndex; 1556 edgeNext[ sourceIndex ][ targetIndex ] = edges[ i ]; 1557 } 1558 } 1559 } 1560 1561 // Main loop 1562 for( var k = 0; k < numNodes; k++ ){ 1563 for( var i = 0; i < numNodes; i++ ){ 1564 for( var j = 0; j < numNodes; j++ ){ 1565 if( dist[ i ][ k ] + dist[ k ][ j ] < dist[ i ][ j ] ){ 1566 dist[ i ][ j ] = dist[ i ][ k ] + dist[ k ][ j ]; 1567 next[ i ][ j ] = next[ i ][ k ]; 1568 } 1569 } 1570 } 1571 } 1572 1573 // Build result object 1574 var position2id = []; 1575 for( var i = 0; i < numNodes; i++ ){
1576 position2id.push( nodes[ i ].id() ); 1577 } 1578 1579 var res = { 1580 distance: function( from, to ){ 1581 if( is.string( from ) ){ 1582 // from is a selector string 1583 var fromId = (cy.filter( from )[0]).id(); 1584 } else { 1585 // from is a node 1586 var fromId = from.id(); 1587 } 1588 1589 if( is.string( to ) ){ 1590 // to is a selector string 1591 var toId = (cy.filter( to )[0]).id(); 1592 } else { 1593 // to is a node 1594 var toId = to.id(); 1595 } 1596 1597 return dist[ id2position[ fromId ] ][ id2position[ toId ] ]; 1598 }, 1599 1600 path: function( from, to ){ 1601 var reconstructPathAux = function( from, to, next, position2id, edgeNext ){ 1602 if( from === to ){ 1603 return cy.getElementById( position2id[ from ] ); 1604 } 1605 if( next[ from ][ to ] === undefined ){ 1606 return undefined; 1607 } 1608 1609 var path = [ cy.getElementById( position2id[ from ] ) ]; 1610 var prev = from; 1611 while( from !== to ){ 1612 prev = from; 1613 from = next[ from ][ to ]; 1614 1615 var edge = edgeNext[ prev ][ from ]; 1616 path.push( edge ); 1617 1618 path.push( cy.getElementById( position2id[ from ] ) ); 1619 } 1620 return path; 1621 }; 1622 1623 if( is.string( from ) ){ 1624 // from is a selector string 1625 var fromId = (cy.filter( from )[0]).id(); 1626 } else { 1627 // from is a node 1628 var fromId = from.id(); 1629 } 1630 1631 if( is.string( to ) ){ 1632 // to is a selector string 1633 var toId = (cy.filter( to )[0]).id(); 1634 } else { 1635 // to is a node 1636 var toId = to.id(); 1637 } 1638 1639 var pathArr = reconstructPathAux( id2position[ fromId ], 1640 id2position[ toId ], 1641 next, 1642 position2id, 1643 edgeNext ); 1644 1645 return cy.collection( pathArr ); 1646 } 1647 }; 1648 1649 return res; 1650 1651 } // floydWarshall 1652 1653}); // elesfn 1654 1655module.exports = elesfn; 1656 1657},{"../../is":83}],11:[function(_dereq_,module,exports){ 1658'use strict'; 1659 1660var util = _dereq_( '../../util' ); 1661 1662var elesfn = {}; 1663 1664[ 1665 _dereq_( './bfs-dfs' ), 1666 _dereq_( './dijkstra' ), 1667 _dereq_( './kruskal' ), 1668 _dereq_( './a-star' ), 1669 _dereq_( './floyd-warshall' ), 1670 _dereq_( './bellman-ford' ), 1671 _dereq_( './kerger-stein' ), 1672 _dereq_( './page-rank' ), 1673 _dereq_( './degree-centrality' ), 1674 _dereq_( './closeness-centrality' ), 1675 _dereq_( './betweenness-centrality' ) 1676].forEach( function( props ){ 1677 util.extend( elesfn, props ); 1678} ); 1679 1680module.exports = elesfn; 1681 1682},{"../../util":100,"./a-star":3,"./bellman-ford":4,"./betweenness-centrality":5,"./bfs-dfs":6,"./closeness-centrality":7,"./degree-centrality":8,"./dijkstra":9,"./floyd-warshall":10,"./kerger-stein":12,"./kruskal":13,"./page-rank":14}],12:[function(_dereq_,module,exports){ 1683'use strict'; 1684 1685var util = _dereq_( '../../util' ); 1686 1687var elesfn = ({ 1688 1689 // Computes the minimum cut of an undirected graph 1690 // Returns the correct answer with high probability 1691 kargerStein: function( options ){ 1692 var eles = this; 1693 1694 options = options || {}; 1695 1696 // Function which colapses 2 (meta) nodes into one 1697 // Updates the remaining edge lists 1698 // Receives as a paramater the edge which causes the collapse 1699 var colapse = function( edgeIndex, nodeMap, remainingEdges ){ 1700 var edgeInfo = remainingEdges[ edgeIndex ]; 1701 var sourceIn = edgeInfo[1]; 1702 var targetIn = edgeInfo[2]; 1703 var partition1 = nodeMap[ sourceIn ]; 1704 var partition2 = nodeMap[ targetIn ]; 1705 1706 // Delete all edges between partition1 and partition2 1707 var newEdges = remainingEdges.filter( function( edge ){ 1708 if( nodeMap[ edge[1] ] === partition1 && nodeMap[ edge[2] ] === partition2 ){ 1709 return false; 1710 } 1711 if( nodeMap[ edge[1] ] === partition2 && nodeMap[ edge[2] ] === partition1 ){ 1712 return false; 1713 } 1714 return true; 1715 } ); 1716 1717 // All edges pointing to partition2 should now point to partition1 1718 for( var i = 0; i < newEdges.length; i++ ){ 1719 var edge = newEdges[ i ];
1720 if( edge[1] === partition2 ){ // Check source 1721 newEdges[ i ] = edge.slice( 0 ); 1722 newEdges[ i ][1] = partition1; 1723 } else if( edge[2] === partition2 ){ // Check target 1724 newEdges[ i ] = edge.slice( 0 ); 1725 newEdges[ i ][2] = partition1; 1726 } 1727 } 1728 1729 // Move all nodes from partition2 to partition1 1730 for( var i = 0; i < nodeMap.length; i++ ){ 1731 if( nodeMap[ i ] === partition2 ){ 1732 nodeMap[ i ] = partition1; 1733 } 1734 } 1735 1736 return newEdges; 1737 }; 1738 1739 1740 // Contracts a graph until we reach a certain number of meta nodes 1741 var contractUntil = function( metaNodeMap, 1742 remainingEdges, 1743 size, 1744 sizeLimit ){ 1745 // Stop condition 1746 if( size <= sizeLimit ){ 1747 return remainingEdges; 1748 } 1749 1750 // Choose an edge randomly 1751 var edgeIndex = Math.floor( (Math.random() * remainingEdges.length) ); 1752 1753 // Colapse graph based on edge 1754 var newEdges = colapse( edgeIndex, metaNodeMap, remainingEdges ); 1755 1756 return contractUntil( metaNodeMap, 1757 newEdges, 1758 size - 1, 1759 sizeLimit ); 1760 }; 1761 1762 var cy = this._private.cy; 1763 var edges = this.edges().stdFilter( function( e ){ return !e.isLoop(); } ); 1764 var nodes = this.nodes(); 1765 var numNodes = nodes.length; 1766 var numEdges = edges.length; 1767 var numIter = Math.ceil( Math.pow( Math.log( numNodes ) / Math.LN2, 2 ) ); 1768 var stopSize = Math.floor( numNodes / Math.sqrt( 2 ) ); 1769 1770 if( numNodes < 2 ){ 1771 util.error( 'At least 2 nodes are required for Karger-Stein algorithm' ); 1772 return undefined; 1773 } 1774 1775 // Create numerical identifiers for each node 1776 // mapping: node id -> position in nodes array 1777 // for reverse mapping, simply use nodes array 1778 var id2position = {}; 1779 for( var i = 0; i < numNodes; i++ ){ 1780 id2position[ nodes[ i ].id() ] = i; 1781 } 1782 1783 // Now store edge destination as indexes 1784 // Format for each edge (edge index, source node index, target node index) 1785 var edgeIndexes = []; 1786 for( var i = 0; i < numEdges; i++ ){ 1787 var e = edges[ i ]; 1788 edgeIndexes.push( [ i, id2position[ e.source().id() ], id2position[ e.target().id() ] ] ); 1789 } 1790 1791 // We will store the best cut found here 1792 var minCutSize = Infinity; 1793 var minCut; 1794 1795 // Initial meta node partition 1796 var originalMetaNode = []; 1797 for( var i = 0; i < numNodes; i++ ){ 1798 originalMetaNode.push( i ); 1799 } 1800 1801 // Main loop 1802 for( var iter = 0; iter <= numIter; iter++ ){ 1803 // Create new meta node partition 1804 var metaNodeMap = originalMetaNode.slice( 0 ); 1805 1806 // Contract until stop point (stopSize nodes) 1807 var edgesState = contractUntil( metaNodeMap, edgeIndexes, numNodes, stopSize ); 1808 1809 // Create a copy of the colapsed nodes state 1810 var metaNodeMap2 = metaNodeMap.slice( 0 ); 1811 1812 // Run 2 iterations starting in the stop state 1813 var res1 = contractUntil( metaNodeMap, edgesState, stopSize, 2 ); 1814 var res2 = contractUntil( metaNodeMap2, edgesState, stopSize, 2 ); 1815 1816 // Is any of the 2 results the best cut so far? 1817 if( res1.length <= res2.length && res1.length < minCutSize ){ 1818 minCutSize = res1.length; 1819 minCut = [ res1, metaNodeMap ]; 1820 } else if( res2.length <= res1.length && res2.length < minCutSize ){ 1821 minCutSize = res2.length; 1822 minCut = [ res2, metaNodeMap2 ]; 1823 } 1824 } // end of main loop 1825 1826 1827 // Construct result 1828 var resEdges = (minCut[0]).map( function( e ){ return edges[ e[0] ]; } ); 1829 var partition1 = []; 1830 var partition2 = []; 1831 1832 // traverse metaNodeMap for best cut 1833 var witnessNodePartition = minCut[1][0]; 1834 for( var i = 0; i < minCut[1].length; i++ ){ 1835 var partitionId = minCut[1][ i ]; 1836 if( partitionId === witnessNodePartition ){ 1837 partition1.push( nodes[ i ] ); 1838 } else {
1839 partition2.push( nodes[ i ] ); 1840 } 1841 } 1842 1843 var ret = { 1844 cut: eles.spawn( cy, resEdges ), 1845 partition1: eles.spawn( partition1 ), 1846 partition2: eles.spawn( partition2 ) 1847 }; 1848 1849 return ret; 1850 } 1851}); // elesfn 1852 1853 1854module.exports = elesfn; 1855 1856},{"../../util":100}],13:[function(_dereq_,module,exports){ 1857'use strict'; 1858 1859var is = _dereq_( '../../is' ); 1860 1861// search, spanning trees, etc 1862var elesfn = ({ 1863 1864 // kruskal's algorithm (finds min spanning tree, assuming undirected graph) 1865 // implemented from pseudocode from wikipedia 1866 kruskal: function( weightFn ){ 1867 var cy = this.cy(); 1868 1869 weightFn = is.fn( weightFn ) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1) 1870 1871 function findSet( ele ){ 1872 for( var i = 0; i < forest.length; i++ ){ 1873 var eles = forest[ i ]; 1874 1875 if( eles.anySame( ele ) ){ 1876 return { 1877 eles: eles, 1878 index: i 1879 }; 1880 } 1881 } 1882 } 1883 1884 var A = cy.collection( cy, [] ); 1885 var forest = []; 1886 var nodes = this.nodes(); 1887 1888 for( var i = 0; i < nodes.length; i++ ){ 1889 forest.push( nodes[ i ].collection() ); 1890 } 1891 1892 var edges = this.edges(); 1893 var S = edges.toArray().sort( function( a, b ){ 1894 var weightA = weightFn.call( a, a ); 1895 var weightB = weightFn.call( b, b ); 1896 1897 return weightA - weightB; 1898 } ); 1899 1900 for( var i = 0; i < S.length; i++ ){ 1901 var edge = S[ i ]; 1902 var u = edge.source()[0]; 1903 var v = edge.target()[0]; 1904 var setU = findSet( u ); 1905 var setV = findSet( v ); 1906 1907 if( setU.index !== setV.index ){ 1908 A = A.add( edge ); 1909 1910 // combine forests for u and v 1911 forest[ setU.index ] = setU.eles.add( setV.eles ); 1912 forest.splice( setV.index, 1 ); 1913 } 1914 } 1915 1916 return nodes.add( A ); 1917 } 1918}); 1919 1920module.exports = elesfn; 1921 1922},{"../../is":83}],14:[function(_dereq_,module,exports){ 1923'use strict'; 1924 1925var is = _dereq_( '../../is' ); 1926 1927var elesfn = ({ 1928 1929 pageRank: function( options ){ 1930 options = options || {}; 1931 1932 var normalizeVector = function( vector ){ 1933 var length = vector.length; 1934 1935 // First, get sum of all elements 1936 var total = 0; 1937 for( var i = 0; i < length; i++ ){ 1938 total += vector[ i ]; 1939 } 1940 1941 // Now, divide each by the sum of all elements 1942 for( var i = 0; i < length; i++ ){ 1943 vector[ i ] = vector[ i ] / total; 1944 } 1945 }; 1946 1947 // dampingFactor - optional 1948 if( options != null && 1949 options.dampingFactor != null ){ 1950 var dampingFactor = options.dampingFactor; 1951 } else { 1952 var dampingFactor = 0.8; // Default damping factor 1953 } 1954 1955 // desired precision - optional 1956 if( options != null && 1957 options.precision != null ){ 1958 var epsilon = options.precision; 1959 } else { 1960 var epsilon = 0.000001; // Default precision 1961 } 1962 1963 // Max number of iterations - optional 1964 if( options != null && 1965 options.iterations != null ){ 1966 var numIter = options.iterations; 1967 } else { 1968 var numIter = 200; // Default number of iterations 1969 } 1970 1971 // Weight function - optional 1972 if( options != null && 1973 options.weight != null && 1974 is.fn( options.weight ) ){ 1975 var weightFn = options.weight; 1976 } else { 1977 // If not specified, assume each edge has equal weight (1) 1978 var weightFn = function( e ){return 1;}; 1979 } 1980 1981 var cy = this._private.cy; 1982 var edges = this.edges().stdFilter( function( e ){ return !e.isLoop(); } ); 1983 var nodes = this.nodes(); 1984 var numNodes = nodes.length; 1985 var numEdges = edges.length; 1986 1987 // Create numerical identifiers for each node 1988 // mapping: node id -> position in nodes array 1989 // for reverse mapping, simply use nodes array 1990 var id2position = {}; 1991 for( var i = 0; i < numNodes; i++ ){ 1992 id2position[ nodes[ i ].id() ] = i; 1993 } 1994 1995 // Construct transposed adjacency matrix 1996 // First lets have a zeroed matrix of the right size 1997 // We'll also keep track of the sum of each column 1998 var matrix = []; 1999 var columnSum = []; 2000 var additionalProb = (1 - dampingFactor) / numNodes; 2001 2002 // Create null matric 2003 for( var i = 0; i < numNodes; i++ ){ 2004 var newRow = []; 2005 for( var j = 0; j < numNodes; j++ ){ 2006 newRow.push( 0.0 ); 2007 } 2008 matrix.push( newRow ); 2009 columnSum.push( 0.0 ); 2010 } 2011 2012 // Now, process edges 2013 for( var i = 0; i < numEdges; i++ ){ 2014 var edge = edges[ i ]; 2015 var s = id2position[ edge.source().id() ]; 2016 var t = id2position[ edge.target().id() ]; 2017 var w = weightFn.apply( edge, [ edge ] ); 2018 2019 // Update matrix 2020 matrix[ t ][ s ] += w; 2021 2022 // Update column sum 2023 columnSum[ s ] += w; 2024 } 2025 2026 // Add additional probability based on damping factor 2027 // Also, take into account columns that have sum = 0 2028 var p = 1.0 / numNodes + additionalProb; // Shorthand 2029 // Traverse matrix, column by column 2030 for( var j = 0; j < numNodes; j++ ){ 2031 if( columnSum[ j ] === 0 ){ 2032 // No 'links' out from node jth, assume equal probability for each possible node 2033 for( var i = 0; i < numNodes; i++ ){ 2034 matrix[ i ][ j ] = p; 2035 } 2036 } else { 2037 // Node jth has outgoing link, compute normalized probabilities 2038 for( var i = 0; i < numNodes; i++ ){ 2039 matrix[ i ][ j ] = matrix[ i ][ j ] / columnSum[ j ] + additionalProb; 2040 } 2041 } 2042 } 2043 2044 // Compute dominant eigenvector using power method 2045 var eigenvector = []; 2046 var nullVector = []; 2047 var previous; 2048 2049 // Start with a vector of all 1's 2050 // Also, initialize a null vector which will be used as shorthand 2051 for( var i = 0; i < numNodes; i++ ){ 2052 eigenvector.push( 1.0 ); 2053 nullVector.push( 0.0 ); 2054 } 2055 2056 for( var iter = 0; iter < numIter; iter++ ){ 2057 // New array with all 0's 2058 var temp = nullVector.slice( 0 ); 2059 2060 // Multiply matrix with previous result 2061 for( var i = 0; i < numNodes; i++ ){ 2062 for( var j = 0; j < numNodes; j++ ){ 2063 temp[ i ] += matrix[ i ][ j ] * eigenvector[ j ]; 2064 } 2065 } 2066 2067 normalizeVector( temp ); 2068 previous = eigenvector; 2069 eigenvector = temp; 2070 2071 var diff = 0; 2072 // Compute difference (squared module) of both vectors 2073 for( var i = 0; i < numNodes; i++ ){ 2074 diff += Math.pow( previous[ i ] - eigenvector[ i ], 2 ); 2075 } 2076 2077 // If difference is less than the desired threshold, stop iterating 2078 if( diff < epsilon ){ 2079 break; 2080 } 2081 } 2082 2083 // Construct result 2084 var res = { 2085 rank: function( node ){ 2086 if( is.string( node ) ){ 2087 // is a selector string
2088 var nodeId = (cy.filter( node )[0]).id(); 2089 } else { 2090 // is a node object 2091 var nodeId = node.id(); 2092 } 2093 return eigenvector[ id2position[ nodeId ] ]; 2094 } 2095 }; 2096 2097 2098 return res; 2099 } // pageRank 2100 2101}); // elesfn 2102 2103module.exports = elesfn; 2104 2105},{"../../is":83}],15:[function(_dereq_,module,exports){ 2106'use strict'; 2107 2108var define = _dereq_( '../define' ); 2109 2110var elesfn = ({ 2111 animate: define.animate(), 2112 animation: define.animation(), 2113 animated: define.animated(), 2114 clearQueue: define.clearQueue(), 2115 delay: define.delay(), 2116 delayAnimation: define.delayAnimation(), 2117 stop: define.stop() 2118}); 2119 2120module.exports = elesfn; 2121 2122},{"../define":44}],16:[function(_dereq_,module,exports){ 2123'use strict'; 2124 2125var util = _dereq_( '../util' ); 2126 2127var elesfn = ({ 2128 classes: function( classes ){ 2129 classes = classes.match( /\S+/g ) || []; 2130 var self = this; 2131 var changed = []; 2132 var classesMap = {}; 2133 2134 // fill in classes map 2135 for( var i = 0; i < classes.length; i++ ){ 2136 var cls = classes[ i ]; 2137 2138 classesMap[ cls ] = true; 2139 } 2140 2141 // check and update each ele 2142 for( var j = 0; j < self.length; j++ ){ 2143 var ele = self[ j ]; 2144 var _p = ele._private; 2145 var eleClasses = _p.classes; 2146 var changedEle = false; 2147 2148 // check if ele has all of the passed classes 2149 for( var i = 0; i < classes.length; i++ ){ 2150 var cls = classes[ i ]; 2151 var eleHasClass = eleClasses[ cls ]; 2152 2153 if( !eleHasClass ){ 2154 changedEle = true; 2155 break; 2156 } 2157 } 2158 2159 // check if ele has classes outside of those passed 2160 if( !changedEle ){ 2161 var classes = Object.keys( eleClasses ); 2162 2163 for( var i = 0; i < classes.length; i++ ){ 2164 var eleCls = classes[i]; 2165 var eleHasClass = eleClasses[ eleCls ]; 2166 var specdClass = classesMap[ eleCls ]; // i.e. this class is passed to the function 2167 2168 if( eleHasClass && !specdClass ){ 2169 changedEle = true; 2170 break; 2171 } 2172 } 2173 } 2174 2175 if( changedEle ){ 2176 _p.classes = util.copy( classesMap ); 2177 2178 changed.push( ele ); 2179 } 2180 } 2181 2182 // trigger update style on those eles that had class changes 2183 if( changed.length > 0 ){ 2184 this.spawn( changed ) 2185 .updateStyle() 2186 .trigger( 'class' ) 2187 ; 2188 } 2189 2190 return self; 2191 }, 2192 2193 addClass: function( classes ){ 2194 return this.toggleClass( classes, true ); 2195 }, 2196 2197 hasClass: function( className ){ 2198 var ele = this[0]; 2199 return ( ele != null && ele._private.classes[ className ] ) ? true : false; 2200 }, 2201 2202 toggleClass: function( classesStr, toggle ){ 2203 var classes = classesStr.match( /\S+/g ) || []; 2204 var self = this; 2205 var changed = []; // eles who had classes changed 2206 2207 for( var i = 0, il = self.length; i < il; i++ ){ 2208 var ele = self[ i ]; 2209 var changedEle = false; 2210 2211 for( var j = 0; j < classes.length; j++ ){ 2212 var cls = classes[ j ]; 2213 var eleClasses = ele._private.classes; 2214 var hasClass = eleClasses[ cls ]; 2215 var shouldAdd = toggle || (toggle === undefined && !hasClass); 2216 2217 if( shouldAdd ){ 2218 eleClasses[ cls ] = true; 2219 2220 if( !hasClass && !changedEle ){ 2221 changed.push( ele ); 2222 changedEle = true; 2223 } 2224 } else { // then remove 2225 eleClasses[ cls ] = false; 2226 2227 if( hasClass && !changedEle ){ 2228 changed.push( ele ); 2229 changedEle = true; 2230 } 2231 } 2232 2233 } // for j classes 2234 } // for i eles 2235 2236 // trigger update style on those eles that had class changes 2237 if( changed.length > 0 ){ 2238 this.spawn( changed ) 2239 .updateStyle() 2240 .trigger( 'class' ) 2241 ; 2242 } 2243 2244 return self; 2245 }, 2246 2247 removeClass: function( classes ){ 2248 return this.toggleClass( classes, false ); 2249 }, 2250 2251 flashClass: function( classes, duration ){ 2252 var self = this; 2253 2254 if( duration == null ){ 2255 duration = 250; 2256 } else if( duration === 0 ){ 2257 return self; // nothing to do really 2258 } 2259 2260 self.addClass( classes ); 2261 setTimeout( function(){ 2262 self.removeClass( classes ); 2263 }, duration ); 2264 2265 return self; 2266 } 2267}); 2268 2269module.exports = elesfn; 2270 2271},{"../util":100}],17:[function(_dereq_,module,exports){ 2272'use strict'; 2273 2274var elesfn = ({ 2275 allAre: function( selector ){ 2276 return this.filter( selector ).length === this.length; 2277 }, 2278 2279 is: function( selector ){ 2280 return this.filter( selector ).length > 0; 2281 }, 2282 2283 some: function( fn, thisArg ){ 2284 for( var i = 0; i < this.length; i++ ){ 2285 var ret = !thisArg ? fn( this[ i ], i, this ) : fn.apply( thisArg, [ this[ i ], i, this ] ); 2286 2287 if( ret ){ 2288 return true; 2289 } 2290 } 2291 2292 return false;
2293 }, 2294 2295 every: function( fn, thisArg ){ 2296 for( var i = 0; i < this.length; i++ ){ 2297 var ret = !thisArg ? fn( this[ i ], i, this ) : fn.apply( thisArg, [ this[ i ], i, this ] ); 2298 2299 if( !ret ){ 2300 return false; 2301 } 2302 } 2303 2304 return true; 2305 }, 2306 2307 same: function( collection ){ 2308 collection = this.cy().collection( collection ); 2309 2310 // cheap extra check 2311 if( this.length !== collection.length ){ 2312 return false; 2313 } 2314 2315 return this.intersect( collection ).length === this.length; 2316 }, 2317 2318 anySame: function( collection ){ 2319 collection = this.cy().collection( collection ); 2320 2321 return this.intersect( collection ).length > 0; 2322 }, 2323 2324 allAreNeighbors: function( collection ){ 2325 collection = this.cy().collection( collection ); 2326 2327 return this.neighborhood().intersect( collection ).length === collection.length; 2328 } 2329}); 2330 2331elesfn.allAreNeighbours = elesfn.allAreNeighbors; 2332 2333module.exports = elesfn; 2334 2335},{}],18:[function(_dereq_,module,exports){ 2336'use strict'; 2337 2338var elesfn = ({ 2339 parent: function( selector ){ 2340 var parents = []; 2341 var cy = this._private.cy; 2342 2343 for( var i = 0; i < this.length; i++ ){ 2344 var ele = this[ i ]; 2345 var parent = cy.getElementById( ele._private.data.parent ); 2346 2347 if( parent.size() > 0 ){ 2348 parents.push( parent ); 2349 } 2350 } 2351 2352 return this.spawn( parents, { unique: true } ).filter( selector ); 2353 }, 2354 2355 parents: function( selector ){ 2356 var parents = []; 2357 2358 var eles = this.parent(); 2359 while( eles.nonempty() ){ 2360 for( var i = 0; i < eles.length; i++ ){ 2361 var ele = eles[ i ]; 2362 parents.push( ele ); 2363 } 2364 2365 eles = eles.parent(); 2366 } 2367 2368 return this.spawn( parents, { unique: true } ).filter( selector ); 2369 }, 2370 2371 commonAncestors: function( selector ){ 2372 var ancestors; 2373 2374 for( var i = 0; i < this.length; i++ ){ 2375 var ele = this[ i ]; 2376 var parents = ele.parents(); 2377 2378 ancestors = ancestors || parents; 2379 2380 ancestors = ancestors.intersect( parents ); // current list must be common with current ele parents set 2381 } 2382 2383 return ancestors.filter( selector ); 2384 }, 2385 2386 orphans: function( selector ){ 2387 return this.stdFilter( function( ele ){ 2388 return ele.isNode() && ele.parent().empty(); 2389 } ).filter( selector ); 2390 }, 2391 2392 nonorphans: function( selector ){ 2393 return this.stdFilter( function( ele ){ 2394 return ele.isNode() && ele.parent().nonempty(); 2395 } ).filter( selector ); 2396 }, 2397 2398 children: function( selector ){ 2399 var children = []; 2400 2401 for( var i = 0; i < this.length; i++ ){ 2402 var ele = this[ i ]; 2403 children = children.concat( ele._private.children ); 2404 } 2405 2406 return this.spawn( children, { unique: true } ).filter( selector ); 2407 }, 2408 2409 siblings: function( selector ){ 2410 return this.parent().children().not( this ).filter( selector ); 2411 }, 2412 2413 isParent: function(){ 2414 var ele = this[0]; 2415 2416 if( ele ){ 2417 return ele._private.children.length !== 0; 2418 } 2419 }, 2420 2421 isChild: function(){ 2422 var ele = this[0]; 2423 2424 if( ele ){ 2425 return ele._private.data.parent !== undefined && ele.parent().length !== 0; 2426 } 2427 }, 2428 2429 descendants: function( selector ){ 2430 var elements = []; 2431 2432 function add( eles ){ 2433 for( var i = 0; i < eles.length; i++ ){ 2434 var ele = eles[ i ]; 2435 2436 elements.push( ele ); 2437 2438 if( ele.children().nonempty() ){ 2439 add( ele.children() ); 2440 } 2441 } 2442 } 2443 2444 add( this.children() ); 2445 2446 return this.spawn( elements, { unique: true } ).filter( selector ); 2447 } 2448}); 2449 2450// aliases 2451elesfn.ancestors = elesfn.parents; 2452 2453module.exports = elesfn; 2454 2455},{}],19:[function(_dereq_,module,exports){ 2456'use strict'; 2457 2458var define = _dereq_( '../define' ); 2459var fn, elesfn; 2460 2461fn = elesfn = ({ 2462 2463 data: define.data( { 2464 field: 'data', 2465 bindingEvent: 'data', 2466 allowBinding: true, 2467 allowSetting: true, 2468 settingEvent: 'data', 2469 settingTriggersEvent: true, 2470 triggerFnName: 'trigger', 2471 allowGetting: true, 2472 immutableKeys: { 2473 'id': true, 2474 'source': true, 2475 'target': true, 2476 'parent': true 2477 }, 2478 updateStyle: true 2479 } ), 2480 2481 removeData: define.removeData( { 2482 field: 'data', 2483 event: 'data', 2484 triggerFnName: 'trigger', 2485 triggerEvent: true, 2486 immutableKeys: { 2487 'id': true, 2488 'source': true, 2489 'target': true, 2490 'parent': true 2491 }, 2492 updateStyle: true 2493 } ), 2494 2495 scratch: define.data( { 2496 field: 'scratch', 2497 bindingEvent: 'scratch', 2498 allowBinding: true, 2499 allowSetting: true, 2500 settingEvent: 'scratch', 2501 settingTriggersEvent: true, 2502 triggerFnName: 'trigger', 2503 allowGetting: true, 2504 updateStyle: true 2505 } ), 2506 2507 removeScratch: define.removeData( { 2508 field: 'scratch', 2509 event: 'scratch', 2510 triggerFnName: 'trigger', 2511 triggerEvent: true, 2512 updateStyle: true 2513 } ), 2514 2515 rscratch: define.data( { 2516 field: 'rscratch', 2517 allowBinding: false, 2518 allowSetting: true, 2519 settingTriggersEvent: false, 2520 allowGetting: true 2521 } ), 2522 2523 removeRscratch: define.removeData( { 2524 field: 'rscratch', 2525 triggerEvent: false 2526 } ), 2527 2528 id: function(){ 2529 var ele = this[0]; 2530 2531 if( ele ){ 2532 return ele._private.data.id; 2533 } 2534 } 2535 2536}); 2537 2538// aliases 2539fn.attr = fn.data; 2540fn.removeAttr = fn.removeData; 2541 2542module.exports = elesfn; 2543 2544},{"../define":44}],20:[function(_dereq_,module,exports){ 2545'use strict'; 2546 2547var util = _dereq_( '../util' ); 2548 2549var elesfn = {}; 2550 2551function defineDegreeFunction( callback ){ 2552 return function( includeLoops ){ 2553 var self = this; 2554 2555 if( includeLoops === undefined ){ 2556 includeLoops = true; 2557 } 2558 2559 if( self.length === 0 ){ return; } 2560 2561 if( self.isNode() && !self.removed() ){ 2562 var degree = 0; 2563 var node = self[0]; 2564 var connectedEdges = node._private.edges; 2565 2566 for( var i = 0; i < connectedEdges.length; i++ ){ 2567 var edge = connectedEdges[ i ]; 2568 2569 if( !includeLoops && edge.isLoop() ){ 2570 continue; 2571 } 2572 2573 degree += callback( node, edge ); 2574 } 2575 2576 return degree; 2577 } else { 2578 return; 2579 } 2580 }; 2581} 2582 2583util.extend( elesfn, { 2584 degree: defineDegreeFunction( function( node, edge ){ 2585 if( edge.source().same( edge.target() ) ){ 2586 return 2; 2587 } else { 2588 return 1; 2589 } 2590 } ), 2591 2592 indegree: defineDegreeFunction( function( node, edge ){ 2593 if( edge.target().same( node ) ){ 2594 return 1; 2595 } else { 2596 return 0; 2597 } 2598 } ), 2599 2600 outdegree: defineDegreeFunction( function( node, edge ){ 2601 if( edge.source().same( node ) ){ 2602 return 1; 2603 } else { 2604 return 0; 2605 } 2606 } ) 2607} ); 2608 2609function defineDegreeBoundsFunction( degreeFn, callback ){ 2610 return function( includeLoops ){ 2611 var ret; 2612 var nodes = this.nodes(); 2613 2614 for( var i = 0; i < nodes.length; i++ ){ 2615 var ele = nodes[ i ]; 2616 var degree = ele[ degreeFn ]( includeLoops ); 2617 if( degree !== undefined && (ret === undefined || callback( degree, ret )) ){ 2618 ret = degree; 2619 } 2620 } 2621 2622 return ret; 2623 }; 2624} 2625 2626util.extend( elesfn, { 2627 minDegree: defineDegreeBoundsFunction( 'degree', function( degree, min ){ 2628 return degree < min; 2629 } ), 2630 2631 maxDegree: defineDegreeBoundsFunction( 'degree', function( degree, max ){ 2632 return degree > max; 2633 } ), 2634 2635 minIndegree: defineDegreeBoundsFunction( 'indegree', function( degree, min ){ 2636 return degree < min; 2637 } ), 2638 2639 maxIndegree: defineDegreeBoundsFunction( 'indegree', function( degree, max ){ 2640 return degree > max; 2641 } ), 2642 2643 minOutdegree: defineDegreeBoundsFunction( 'outdegree', function( degree, min ){ 2644 return degree < min; 2645 } ), 2646 2647 maxOutdegree: defineDegreeBoundsFunction( 'outdegree', function( degree, max ){ 2648 return degree > max; 2649 } ) 2650} ); 2651 2652util.extend( elesfn, { 2653 totalDegree: function( includeLoops ){ 2654 var total = 0; 2655 var nodes = this.nodes(); 2656 2657 for( var i = 0; i < nodes.length; i++ ){ 2658 total += nodes[ i ].degree( includeLoops ); 2659 } 2660 2661 return total; 2662 } 2663} ); 2664 2665module.exports = elesfn; 2666 2667},{"../util":100}],21:[function(_dereq_,module,exports){ 2668'use strict'; 2669 2670var define = _dereq_( '../define' ); 2671var is = _dereq_( '../is' ); 2672var util = _dereq_( '../util' ); 2673var math = _dereq_( '../math' ); 2674var fn, elesfn; 2675 2676fn = elesfn = ({ 2677 2678 position: define.data( { 2679 field: 'position', 2680 bindingEvent: 'position', 2681 allowBinding: true, 2682 allowSetting: true, 2683 settingEvent: 'position', 2684 settingTriggersEvent: true, 2685 triggerFnName: 'rtrigger', 2686 allowGetting: true, 2687 validKeys: [ 'x', 'y' ], 2688 onSet: function( eles ){ 2689 var updatedEles = eles.updateCompoundBounds(); 2690 updatedEles.rtrigger( 'position' ); 2691 }, 2692 canSet: function( ele ){ 2693 return !ele.locked() && !ele.isParent(); 2694 } 2695 } ), 2696 2697 // position but no notification to renderer 2698 silentPosition: define.data( { 2699 field: 'position', 2700 bindingEvent: 'position', 2701 allowBinding: false, 2702 allowSetting: true, 2703 settingEvent: 'position', 2704 settingTriggersEvent: false, 2705 triggerFnName: 'trigger', 2706 allowGetting: true, 2707 validKeys: [ 'x', 'y' ], 2708 onSet: function( eles ){ 2709 eles.updateCompoundBounds(); 2710 }, 2711 canSet: function( ele ){ 2712 return !ele.locked() && !ele.isParent(); 2713 } 2714 } ), 2715 2716 positions: function( pos, silent ){ 2717 if( is.plainObject( pos ) ){ 2718 this.position( pos ); 2719 2720 } else if( is.fn( pos ) ){ 2721 var fn = pos; 2722 2723 for( var i = 0; i < this.length; i++ ){ 2724 var ele = this[ i ]; 2725 2726 var pos = fn.apply( ele, [ i, ele ] ); 2727 2728 if( pos && !ele.locked() && !ele.isParent() ){ 2729 var elePos = ele._private.position; 2730 elePos.x = pos.x; 2731 elePos.y = pos.y; 2732 } 2733 } 2734 2735 var updatedEles = this.updateCompoundBounds(); 2736 var toTrigger = updatedEles.length > 0 ? this.add( updatedEles ) : this; 2737 2738 if( silent ){ 2739 toTrigger.trigger( 'position' ); 2740 } else { 2741 toTrigger.rtrigger( 'position' ); 2742 } 2743 } 2744 2745 return this; // chaining 2746 }, 2747 2748 silentPositions: function( pos ){ 2749 return this.positions( pos, true ); 2750 }, 2751 2752 // get/set the rendered (i.e. on screen) positon of the element 2753 renderedPosition: function( dim, val ){ 2754 var ele = this[0]; 2755 var cy = this.cy(); 2756 var zoom = cy.zoom(); 2757 var pan = cy.pan(); 2758 var rpos = is.plainObject( dim ) ? dim : undefined; 2759 var setting = rpos !== undefined || ( val !== undefined && is.string( dim ) ); 2760 2761 if( ele && ele.isNode() ){ // must have an element and must be a node to return position 2762 if( setting ){ 2763 for( var i = 0; i < this.length; i++ ){ 2764 var ele = this[ i ]; 2765 2766 if( val !== undefined ){ // set one dimension 2767 ele._private.position[ dim ] = ( val - pan[ dim ] ) / zoom; 2768 } else if( rpos !== undefined ){ // set whole position 2769 ele._private.position = { 2770 x: ( rpos.x - pan.x ) / zoom, 2771 y: ( rpos.y - pan.y ) / zoom 2772 }; 2773 } 2774 } 2775 2776 this.rtrigger( 'position' ); 2777 } else { // getting 2778 var pos = ele._private.position; 2779 rpos = { 2780 x: pos.x * zoom + pan.x, 2781 y: pos.y * zoom + pan.y 2782 }; 2783 2784 if( dim === undefined ){ // then return the whole rendered position 2785 return rpos; 2786 } else { // then return the specified dimension 2787 return rpos[ dim ]; 2788 } 2789 } 2790 } else if( !setting ){ 2791 return undefined; // for empty collection case 2792 } 2793 2794 return this; // chaining 2795 }, 2796 2797 // get/set the position relative to the parent 2798 relativePosition: function( dim, val ){ 2799 var ele = this[0]; 2800 var cy = this.cy(); 2801 var ppos = is.plainObject( dim ) ? dim : undefined; 2802 var setting = ppos !== undefined || ( val !== undefined && is.string( dim ) ); 2803 var hasCompoundNodes = cy.hasCompoundNodes(); 2804 2805 if( ele && ele.isNode() ){ // must have an element and must be a node to return position 2806 if( setting ){ 2807 for( var i = 0; i < this.length; i++ ){ 2808 var ele = this[ i ]; 2809 var parent = hasCompoundNodes ? ele.parent() : null; 2810 var hasParent = parent && parent.length > 0; 2811 var relativeToParent = hasParent; 2812 2813 if( hasParent ){ 2814 parent = parent[0]; 2815 } 2816 2817 var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 }; 2818 2819 if( val !== undefined ){ // set one dimension 2820 ele._private.position[ dim ] = val + origin[ dim ]; 2821 } else if( ppos !== undefined ){ // set whole position 2822 ele._private.position = { 2823 x: ppos.x + origin.x, 2824 y: ppos.y + origin.y 2825 }; 2826 } 2827 } 2828 2829 this.rtrigger( 'position' ); 2830 2831 } else { // getting 2832 var pos = ele._private.position; 2833 var parent = hasCompoundNodes ? ele.parent() : null; 2834 var hasParent = parent && parent.length > 0; 2835 var relativeToParent = hasParent; 2836 2837 if( hasParent ){ 2838 parent = parent[0]; 2839 } 2840 2841 var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 }; 2842 2843 ppos = { 2844 x: pos.x - origin.x, 2845 y: pos.y - origin.y 2846 }; 2847 2848 if( dim === undefined ){ // then return the whole rendered position 2849 return ppos; 2850 } else { // then return the specified dimension 2851 return ppos[ dim ]; 2852 } 2853 } 2854 } else if( !setting ){ 2855 return undefined; // for empty collection case 2856 } 2857 2858 return this; // chaining 2859 }, 2860 2861 renderedBoundingBox: function( options ){ 2862 var bb = this.boundingBox( options ); 2863 var cy = this.cy(); 2864 var zoom = cy.zoom(); 2865 var pan = cy.pan(); 2866 2867 var x1 = bb.x1 * zoom + pan.x; 2868 var x2 = bb.x2 * zoom + pan.x; 2869 var y1 = bb.y1 * zoom + pan.y;
2870 var y2 = bb.y2 * zoom + pan.y; 2871 2872 return { 2873 x1: x1, 2874 x2: x2, 2875 y1: y1, 2876 y2: y2, 2877 w: x2 - x1, 2878 h: y2 - y1 2879 }; 2880 }, 2881 2882 updateCompoundBounds: function(){ 2883 var cy = this.cy(); 2884 2885 // save cycles for non compound graphs or when style disabled 2886 if( !cy.styleEnabled() || !cy.hasCompoundNodes() ){ return cy.collection(); } 2887 2888 var updated = []; 2889 2890 function update( parent ){ 2891 if( !parent.isParent() ){ return; } 2892 2893 var _p = parent._private; 2894 var children = parent.children(); 2895 var includeLabels = parent.pstyle( 'compound-sizing-wrt-labels' ).value === 'include'; 2896 var bb = children.boundingBox( { 2897 includeLabels: includeLabels, 2898 includeShadows: false, 2899 includeOverlays: false, 2900 2901 // updating the compound bounds happens outside of the regular 2902 // cache cycle (i.e. before fired events) 2903 useCache: false 2904 } ); 2905 var padding = { 2906 top: parent.pstyle( 'padding-top' ).pfValue, 2907 bottom: parent.pstyle( 'padding-bottom' ).pfValue, 2908 left: parent.pstyle( 'padding-left' ).pfValue, 2909 right: parent.pstyle( 'padding-right' ).pfValue 2910 }; 2911 var pos = _p.position; 2912 2913 _p.autoWidth = bb.w; 2914 pos.x = (bb.x1 + bb.x2 - padding.left + padding.right) / 2; 2915 2916 _p.autoHeight = bb.h; 2917 pos.y = (bb.y1 + bb.y2 - padding.top + padding.bottom) / 2; 2918 2919 updated.push( parent ); 2920 } 2921 2922 // go up, level by level 2923 var eles = this; 2924 while( eles.nonempty() ){ 2925 2926 // update each parent node in this level 2927 for( var i = 0; i < eles.length; i++ ){ 2928 var ele = eles[ i ]; 2929 2930 update( ele ); 2931 } 2932 2933 // next level 2934 eles = eles.parent(); 2935 } 2936 2937 // return changed 2938 return this.spawn( updated ); 2939 } 2940}); 2941 2942var noninf = function( x ){ 2943 if( x === Infinity || x === -Infinity ){ 2944 return 0; 2945 } 2946 2947 return x; 2948}; 2949 2950var updateBounds = function( b, x1, y1, x2, y2 ){ 2951 // don't update with zero area boxes 2952 if( x2 - x1 === 0 || y2 - y1 === 0 ){ return; } 2953 2954 b.x1 = x1 < b.x1 ? x1 : b.x1; 2955 b.x2 = x2 > b.x2 ? x2 : b.x2; 2956 b.y1 = y1 < b.y1 ? y1 : b.y1; 2957 b.y2 = y2 > b.y2 ? y2 : b.y2; 2958}; 2959 2960var updateBoundsFromBox = function( b, b2 ){ 2961 return updateBounds( b, b2.x1, b2.y1, b2.x2, b2.y2 ); 2962}; 2963 2964var prefixedProperty = function( obj, field, prefix ){ 2965 return util.getPrefixedProperty( obj, field, prefix ); 2966}; 2967 2968var updateBoundsFromArrow = function( bounds, ele, prefix, options ){ 2969 var _p = ele._private; 2970 var rstyle = _p.rstyle; 2971 var halfArW = rstyle.arrowWidth / 2; 2972 var arrowType = ele.pstyle( prefix + '-arrow-shape' ).value; 2973 var x; 2974 var y; 2975 2976 if( arrowType !== 'none' ){ 2977 if( prefix === 'source' ){ 2978 x = rstyle.srcX; 2979 y = rstyle.srcY; 2980 } else if( prefix === 'target' ){ 2981 x = rstyle.tgtX; 2982 y = rstyle.tgtY; 2983 } else { 2984 x = rstyle.midX; 2985 y = rstyle.midY; 2986 } 2987 2988 updateBounds( bounds, x - halfArW, y - halfArW, x + halfArW, y + halfArW ); 2989 } 2990}; 2991 2992var updateBoundsFromLabel = function( bounds, ele, prefix, options ){ 2993 var prefixDash; 2994 2995 if( prefix ){ 2996 prefixDash = prefix + '-'; 2997 } else { 2998 prefixDash = ''; 2999 } 3000 3001 var _p = ele._private; 3002 var rstyle = _p.rstyle; 3003 var label = ele.pstyle( prefixDash + 'label' ).strValue; 3004 3005 if( label ){ 3006 var halign = ele.pstyle( 'text-halign' ); 3007 var valign = ele.pstyle( 'text-valign' ); 3008 var labelWidth = prefixedProperty( rstyle, 'labelWidth', prefix ); 3009 var labelHeight = prefixedProperty( rstyle, 'labelHeight', prefix ); 3010 var labelX = prefixedProperty( rstyle, 'labelX', prefix ); 3011 var labelY = prefixedProperty( rstyle, 'labelY', prefix ); 3012 var marginX = ele.pstyle( prefixDash + 'text-margin-x' ).pfValue; 3013 var marginY = ele.pstyle( prefixDash + 'text-margin-y' ).pfValue; 3014 var isEdge = ele.isEdge(); 3015 var rotation = ele.pstyle( prefixDash + 'text-rotation' ); 3016 var shadowR = ele.pstyle( 'text-shadow-blur' ).pfValue / 2; 3017 var shadowX = ele.pstyle( 'text-shadow-offset-x' ).pfValue; 3018 var shadowY = ele.pstyle( 'text-shadow-offset-y' ).pfValue; 3019 var shadowOpacity = ele.pstyle( 'text-shadow-opacity' ).value; 3020 var outlineWidth = ele.pstyle( 'text-outline-width' ).pfValue; 3021 var borderWidth = ele.pstyle( 'text-border-width' ).pfValue; 3022 var halfBorderWidth = borderWidth / 2; 3023 3024 var lh = labelHeight; 3025 var lw = labelWidth; 3026 var lw_2 = lw / 2; 3027 var lh_2 = lh / 2; 3028 var lx1, lx2, ly1, ly2; 3029 3030 if( isEdge ){ 3031 lx1 = labelX - lw_2; 3032 lx2 = labelX + lw_2; 3033 ly1 = labelY - lh_2; 3034 ly2 = labelY + lh_2; 3035 } else { 3036 switch( halign.value ){ 3037 case 'left': 3038 lx1 = labelX - lw; 3039 lx2 = labelX; 3040 break; 3041 3042 case 'center': 3043 lx1 = labelX - lw_2; 3044 lx2 = labelX + lw_2; 3045 break; 3046 3047 case 'right': 3048 lx1 = labelX; 3049 lx2 = labelX + lw; 3050 break; 3051 } 3052 3053 switch( valign.value ){ 3054 case 'top': 3055 ly1 = labelY - lh; 3056 ly2 = labelY; 3057 break; 3058 3059 case 'center': 3060 ly1 = labelY - lh_2; 3061 ly2 = labelY + lh_2; 3062 break; 3063 3064 case 'bottom': 3065 ly1 = labelY; 3066 ly2 = labelY + lh; 3067 break; 3068 } 3069 } 3070 3071 var isAutorotate = ( isEdge && rotation.strValue === 'autorotate' ); 3072 var isPfValue = ( rotation.pfValue != null && rotation.pfValue !== 0 ); 3073 3074 if( isAutorotate || isPfValue ){ 3075 var theta = isAutorotate ? prefixedProperty( _p.rstyle, 'labelAngle', prefix ) : rotation.pfValue; 3076 var cos = Math.cos( theta ); 3077 var sin = Math.sin( theta ); 3078 3079 var rotate = function( x, y ){ 3080 x = x - labelX; 3081 y = y - labelY; 3082 3083 return { 3084 x: x * cos - y * sin + labelX, 3085 y: x * sin + y * cos + labelY 3086 }; 3087 }; 3088 3089 var px1y1 = rotate( lx1, ly1 ); 3090 var px1y2 = rotate( lx1, ly2 ); 3091 var px2y1 = rotate( lx2, ly1 ); 3092 var px2y2 = rotate( lx2, ly2 ); 3093 3094 lx1 = Math.min( px1y1.x, px1y2.x, px2y1.x, px2y2.x ); 3095 lx2 = Math.max( px1y1.x, px1y2.x, px2y1.x, px2y2.x ); 3096 ly1 = Math.min( px1y1.y, px1y2.y, px2y1.y, px2y2.y ); 3097 ly2 = Math.max( px1y1.y, px1y2.y, px2y1.y, px2y2.y ); 3098 } 3099 3100 lx1 += marginX - Math.max( outlineWidth, halfBorderWidth ); 3101 lx2 += marginX + Math.max( outlineWidth, halfBorderWidth ); 3102 ly1 += marginY - Math.max( outlineWidth, halfBorderWidth ); 3103 ly2 += marginY + Math.max( outlineWidth, halfBorderWidth ); 3104 3105 updateBounds( bounds, lx1, ly1, lx2, ly2 ); 3106 3107 if( options.includeShadows && shadowOpacity > 0 ){ 3108 lx1 += - shadowR + shadowX; 3109 lx2 += + shadowR + shadowX; 3110 ly1 += - shadowR + shadowY; 3111 ly2 += + shadowR + shadowY; 3112 3113 updateBounds( bounds, lx1, ly1, lx2, ly2 ); 3114 } 3115 } 3116 3117 return bounds; 3118}; 3119 3120// get the bounding box of the elements (in raw model position) 3121var boundingBoxImpl = function( ele, options ){ 3122 var cy = ele._private.cy; 3123 var cy_p = cy._private; 3124 var styleEnabled = cy_p.styleEnabled; 3125 3126 var bounds = { 3127 x1: Infinity, 3128 y1: Infinity, 3129 x2: -Infinity, 3130 y2: -Infinity 3131 }; 3132 3133 var _p = ele._private; 3134 var display = styleEnabled ? ele.pstyle( 'display' ).value : 'element'; 3135 var isNode = ele.isNode(); 3136 var isEdge = ele.isEdge(); 3137 var ex1, ex2, ey1, ey2, x, y; 3138 var displayed = display !== 'none'; 3139
3140 if( displayed ){ 3141 var overlayOpacity = 0; 3142 var overlayPadding = 0; 3143 3144 if( styleEnabled && options.includeOverlays ){ 3145 overlayOpacity = ele.pstyle( 'overlay-opacity' ).value; 3146 3147 if( overlayOpacity !== 0 ){ 3148 overlayPadding = ele.pstyle( 'overlay-padding' ).value; 3149 } 3150 } 3151 3152 var w = 0; 3153 var wHalf = 0; 3154 3155 if( styleEnabled ){ 3156 w = ele.pstyle( 'width' ).pfValue; 3157 wHalf = w / 2; 3158 } 3159 3160 if( isNode && options.includeNodes ){ 3161 var pos = _p.position; 3162 x = pos.x; 3163 y = pos.y; 3164 var w = ele.outerWidth(); 3165 var halfW = w / 2; 3166 var h = ele.outerHeight(); 3167 var halfH = h / 2; 3168 3169 // handle node dimensions 3170 ///////////////////////// 3171 3172 ex1 = x - halfW - overlayPadding; 3173 ex2 = x + halfW + overlayPadding; 3174 ey1 = y - halfH - overlayPadding; 3175 ey2 = y + halfH + overlayPadding; 3176 3177 updateBounds( bounds, ex1, ey1, ex2, ey2 ); 3178 3179 } else if( isEdge && options.includeEdges ){ 3180 var rstyle = _p.rstyle || {}; 3181 3182 // handle edge dimensions (rough box estimate) 3183 ////////////////////////////////////////////// 3184 if( styleEnabled ){ 3185 ex1 = Math.min( rstyle.srcX, rstyle.midX, rstyle.tgtX ); 3186 ex2 = Math.max( rstyle.srcX, rstyle.midX, rstyle.tgtX ); 3187 ey1 = Math.min( rstyle.srcY, rstyle.midY, rstyle.tgtY ); 3188 ey2 = Math.max( rstyle.srcY, rstyle.midY, rstyle.tgtY ); 3189 3190 // take into account edge width 3191 ex1 -= wHalf; 3192 ex2 += wHalf; 3193 ey1 -= wHalf; 3194 ey2 += wHalf; 3195 3196 updateBounds( bounds, ex1, ey1, ex2, ey2 ); 3197 } 3198 3199 // precise haystacks 3200 //////////////////// 3201 if( styleEnabled && ele.pstyle( 'curve-style' ).strValue === 'haystack' ){ 3202 var hpts = rstyle.haystackPts; 3203 3204 ex1 = hpts[0].x; 3205 ey1 = hpts[0].y; 3206 ex2 = hpts[1].x; 3207 ey2 = hpts[1].y; 3208 3209 if( ex1 > ex2 ){ 3210 var temp = ex1; 3211 ex1 = ex2; 3212 ex2 = temp; 3213 } 3214 3215 if( ey1 > ey2 ){ 3216 var temp = ey1; 3217 ey1 = ey2; 3218 ey2 = temp; 3219 } 3220 3221 updateBounds( bounds, ex1 - wHalf, ey1 - wHalf, ex2 + wHalf, ey2 + wHalf ); 3222 3223 // handle points along edge 3224 /////////////////////////// 3225 } else { 3226 var pts = rstyle.bezierPts || rstyle.linePts || []; 3227 3228 for( var j = 0; j < pts.length; j++ ){ 3229 var pt = pts[ j ]; 3230 3231 ex1 = pt.x - wHalf; 3232 ex2 = pt.x + wHalf; 3233 ey1 = pt.y - wHalf; 3234 ey2 = pt.y + wHalf; 3235 3236 updateBounds( bounds, ex1, ey1, ex2, ey2 ); 3237 } 3238 3239 // fallback on source and target positions
3240 ////////////////////////////////////////// 3241 if( pts.length === 0 ){ 3242 var n1 = _p.source; 3243 var n1_p = n1._private; 3244 var n1pos = n1_p.position; 3245 3246 var n2 = _p.target; 3247 var n2_p = n2._private; 3248 var n2pos = n2_p.position; 3249 3250 ex1 = n1pos.x; 3251 ex2 = n2pos.x; 3252 ey1 = n1pos.y; 3253 ey2 = n2pos.y; 3254 3255 if( ex1 > ex2 ){ 3256 var temp = ex1; 3257 ex1 = ex2; 3258 ex2 = temp; 3259 } 3260 3261 if( ey1 > ey2 ){ 3262 var temp = ey1; 3263 ey1 = ey2; 3264 ey2 = temp; 3265 } 3266 3267 // take into account edge width 3268 ex1 -= wHalf; 3269 ex2 += wHalf; 3270 ey1 -= wHalf; 3271 ey2 += wHalf; 3272 3273 updateBounds( bounds, ex1, ey1, ex2, ey2 ); 3274 } 3275 } 3276 3277 } // edges 3278 3279 // shadow and overlay 3280 ///////////////////// 3281 3282 if( styleEnabled ){ 3283 3284 ex1 = bounds.x1; 3285 ex2 = bounds.x2; 3286 ey1 = bounds.y1; 3287 ey2 = bounds.y2; 3288 3289 if( options.includeShadows && ele.pstyle('shadow-opacity').value > 0 ){ 3290 var r = ele.pstyle('shadow-blur').pfValue / 2; 3291 var ox = ele.pstyle('shadow-offset-x').pfValue; 3292 var oy = ele.pstyle('shadow-offset-y').pfValue; 3293 3294 updateBounds( bounds, ex1 - r + ox, ey1 - r + oy, ex2 + r + ox, ey2 + r + oy ); 3295 } 3296 3297 updateBounds( bounds, ex1 - overlayPadding, ey1 - overlayPadding, ex2 + overlayPadding, ey2 + overlayPadding ); 3298 } 3299 3300 // handle edge arrow size 3301 ///////////////////////// 3302 3303 if( styleEnabled && options.includeEdges && isEdge ){ 3304 updateBoundsFromArrow( bounds, ele, 'mid-source', options ); 3305 updateBoundsFromArrow( bounds, ele, 'mid-target', options ); 3306 updateBoundsFromArrow( bounds, ele, 'source', options ); 3307 updateBoundsFromArrow( bounds, ele, 'target', options ); 3308 } 3309 3310 // handle label dimensions 3311 ////////////////////////// 3312 3313 if( styleEnabled && options.includeLabels ){ 3314 updateBoundsFromLabel( bounds, ele, null, options ); 3315 3316 if( isEdge ){ 3317 updateBoundsFromLabel( bounds, ele, 'source', options ); 3318 updateBoundsFromLabel( bounds, ele, 'target', options ); 3319 } 3320 } // style enabled for labels 3321 } // if displayed 3322 3323 bounds.x1 = noninf( bounds.x1 ); 3324 bounds.y1 = noninf( bounds.y1 ); 3325 bounds.x2 = noninf( bounds.x2 ); 3326 bounds.y2 = noninf( bounds.y2 ); 3327 bounds.w = noninf( bounds.x2 - bounds.x1 ); 3328 bounds.h = noninf( bounds.y2 - bounds.y1 ); 3329 3330 // expand bounds by 1 because antialiasing can increase the visual/effective size by 1 on all sides 3331 if( bounds.w > 0 && bounds.h > 0 && displayed ){ 3332 math.expandBoundingBox( bounds, 1 ); 3333 } 3334 3335 return bounds; 3336}; 3337 3338var tf = function( val ){ 3339 if( val ){ 3340 return 't'; 3341 } else { 3342 return 'f'; 3343 } 3344}; 3345 3346var getKey = function( opts ){ 3347 var key = ''; 3348 3349 key += tf( opts.incudeNodes ); 3350 key += tf( opts.includeEdges ); 3351 key += tf( opts.includeLabels ); 3352 key += tf( opts.includeShadows ); 3353 key += tf( opts.includeOverlays ); 3354 3355 return key; 3356}; 3357 3358var cachedBoundingBoxImpl = function( ele, opts ){ 3359 var _p = ele._private; 3360 var bb; 3361 var headless = ele.cy().headless(); 3362 var key = opts === defBbOpts ? defBbOptsKey : getKey( opts ); 3363 3364 if( !opts.useCache || headless || !_p.bbCache || !_p.bbCache[key] ){ 3365 bb = boundingBoxImpl( ele, opts ); 3366 3367 if( !headless ){ 3368 _p.bbCache = _p.bbCache || {}; 3369 _p.bbCache[key] = bb; 3370 } 3371 } else { 3372 bb = _p.bbCache[key]; 3373 } 3374 3375 return bb; 3376}; 3377 3378var defBbOpts = { 3379 includeNodes: true, 3380 includeEdges: true, 3381 includeLabels: true, 3382 includeShadows: true, 3383 includeOverlays: true, 3384 useCache: true 3385}; 3386 3387var defBbOptsKey = getKey( defBbOpts ); 3388 3389elesfn.recalculateRenderedStyle = function( useCache ){ 3390 var cy = this.cy(); 3391 var renderer = cy.renderer(); 3392 var styleEnabled = cy.styleEnabled(); 3393 3394 if( renderer && styleEnabled ){ 3395 renderer.recalculateRenderedStyle( this, useCache ); 3396 } 3397 3398 return this; 3399}; 3400 3401elesfn.boundingBox = function( options ){ 3402 // the main usecase is ele.boundingBox() for a single element with no/def options 3403 // specified s.t. the cache is used, so check for this case to make it faster by 3404 // avoiding the overhead of the rest of the function 3405 if( this.length === 1 && this[0]._private.bbCache && (options === undefined || options.useCache === undefined || options.useCache === true) ){ 3406 if( options === undefined ){ 3407 options = defBbOpts; 3408 } 3409 3410 return cachedBoundingBoxImpl( this[0], options ); 3411 } 3412 3413 var bounds = { 3414 x1: Infinity, 3415 y1: Infinity, 3416 x2: -Infinity, 3417 y2: -Infinity 3418 }; 3419 3420 options = options || util.staticEmptyObject(); 3421 3422 var opts = { 3423 includeNodes: util.default( options.includeNodes, defBbOpts.includeNodes ), 3424 includeEdges: util.default( options.includeEdges, defBbOpts.includeEdges ), 3425 includeLabels: util.default( options.includeLabels, defBbOpts.includeLabels ), 3426 includeShadows: util.default( options.includeShadows, defBbOpts.includeShadows ), 3427 includeOverlays: util.default( options.includeOverlays, defBbOpts.includeOverlays ), 3428 useCache: util.default( options.useCache, defBbOpts.useCache ) 3429 }; 3430 3431 var eles = this; 3432 var cy = eles.cy(); 3433 var styleEnabled = cy.styleEnabled(); 3434 3435 if( styleEnabled ){ 3436 this.recalculateRenderedStyle( opts.useCache ); 3437 } 3438 3439 for( var i = 0; i < eles.length; i++ ){ 3440 var ele = eles[i]; 3441 3442 if( styleEnabled && ele.isEdge() && ele.pstyle('curve-style').strValue === 'bezier' ){ 3443 ele.parallelEdges().recalculateRenderedStyle( opts.useCache ); // n.b. ele.parallelEdges() single is cached 3444 } 3445 3446 updateBoundsFromBox( bounds, cachedBoundingBoxImpl( ele, opts ) ); 3447 } 3448 3449 bounds.x1 = noninf( bounds.x1 ); 3450 bounds.y1 = noninf( bounds.y1 ); 3451 bounds.x2 = noninf( bounds.x2 ); 3452 bounds.y2 = noninf( bounds.y2 ); 3453 bounds.w = noninf( bounds.x2 - bounds.x1 ); 3454 bounds.h = noninf( bounds.y2 - bounds.y1 ); 3455 3456 return bounds; 3457}; 3458
3459var defineDimFns = function( opts ){ 3460 opts.uppercaseName = util.capitalize( opts.name ); 3461 opts.autoName = 'auto' + opts.uppercaseName; 3462 opts.labelName = 'label' + opts.uppercaseName; 3463 opts.outerName = 'outer' + opts.uppercaseName; 3464 opts.uppercaseOuterName = util.capitalize( opts.outerName ); 3465 3466 fn[ opts.name ] = function dimImpl(){ 3467 var ele = this[0]; 3468 var _p = ele._private; 3469 var cy = _p.cy; 3470 var styleEnabled = cy._private.styleEnabled; 3471 3472 if( ele ){ 3473 if( styleEnabled ){ 3474 if( ele.isParent() ){ 3475 return _p[ opts.autoName ] || 0; 3476 } 3477 3478 var d = ele.pstyle( opts.name ); 3479 3480 switch( d.strValue ){ 3481 case 'label': 3482 return _p.rstyle[ opts.labelName ] || 0; 3483 default: 3484 return d.pfValue; 3485 } 3486 } else { 3487 return 1; 3488 } 3489 } 3490 }; 3491 3492 fn[ 'outer' + opts.uppercaseName ] = function outerDimImpl(){ 3493 var ele = this[0]; 3494 var _p = ele._private; 3495 var cy = _p.cy; 3496 var styleEnabled = cy._private.styleEnabled; 3497 3498 if( ele ){ 3499 if( styleEnabled ){ 3500 var dim = ele[ opts.name ](); 3501 var border = ele.pstyle( 'border-width' ).pfValue; 3502 var padding = ele.pstyle( opts.paddings[0] ).pfValue + ele.pstyle( opts.paddings[1] ).pfValue; 3503 3504 return dim + border + padding; 3505 } else { 3506 return 1; 3507 } 3508 } 3509 }; 3510 3511 fn[ 'rendered' + opts.uppercaseName ] = function renderedDimImpl(){ 3512 var ele = this[0]; 3513 3514 if( ele ){ 3515 var d = ele[ opts.name ](); 3516 return d * this.cy().zoom(); 3517 } 3518 }; 3519 3520 fn[ 'rendered' + opts.uppercaseOuterName ] = function renderedOuterDimImpl(){ 3521 var ele = this[0]; 3522 3523 if( ele ){ 3524 var od = ele[ opts.outerName ](); 3525 return od * this.cy().zoom(); 3526 } 3527 }; 3528}; 3529 3530defineDimFns( { 3531 name: 'width', 3532 paddings: [ 'padding-left', 'padding-right' ] 3533} ); 3534 3535defineDimFns( { 3536 name: 'height', 3537 paddings: [ 'padding-top', 'padding-bottom' ] 3538} ); 3539 3540// aliases 3541fn.modelPosition = fn.point = fn.position; 3542fn.modelPositions = fn.points = fn.positions; 3543fn.renderedPoint = fn.renderedPosition; 3544fn.relativePoint = fn.relativePosition; 3545fn.boundingbox = fn.boundingBox; 3546fn.renderedBoundingbox = fn.renderedBoundingBox; 3547 3548module.exports = elesfn; 3549 3550},{"../define":44,"../is":83,"../math":85,"../util":100}],22:[function(_dereq_,module,exports){ 3551'use strict'; 3552 3553var util = _dereq_( '../util' ); 3554var is = _dereq_( '../is' ); 3555 3556// represents a node or an edge 3557var Element = function( cy, params, restore ){ 3558 var self = this; 3559 restore = (restore === undefined || restore ? true : false); 3560 3561 if( cy === undefined || params === undefined || !is.core( cy ) ){ 3562 util.error( 'An element must have a core reference and parameters set' ); 3563 return; 3564 } 3565 3566 var group = params.group; 3567 3568 // try to automatically infer the group if unspecified 3569 if( group == null ){ 3570 if( params.data && params.data.source != null && params.data.target != null ){ 3571 group = 'edges'; 3572 } else { 3573 group = 'nodes'; 3574 } 3575 } 3576 3577 // validate group 3578 if( group !== 'nodes' && group !== 'edges' ){ 3579 util.error( 'An element must be of type `nodes` or `edges`; you specified `' + group + '`' ); 3580 return; 3581 } 3582 3583 // make the element array-like, just like a collection 3584 this.length = 1; 3585 this[0] = this; 3586 3587 // NOTE: when something is added here, add also to ele.json() 3588 this._private = { 3589 cy: cy, 3590 single: true, // indicates this is an element 3591 data: params.data || {}, // data object 3592 position: params.position || {}, // (x, y) position pair
3593 autoWidth: undefined, // width and height of nodes calculated by the renderer when set to special 'auto' value 3594 autoHeight: undefined, 3595 listeners: [], // array of bound listeners 3596 group: group, // string; 'nodes' or 'edges' 3597 style: {}, // properties as set by the style 3598 rstyle: {}, // properties for style sent from the renderer to the core 3599 styleCxts: [], // applied style contexts from the styler 3600 removed: true, // whether it's inside the vis; true if removed (set true here since we call restore) 3601 selected: params.selected ? true : false, // whether it's selected 3602 selectable: params.selectable === undefined ? true : ( params.selectable ? true : false ), // whether it's selectable 3603 locked: params.locked ? true : false, // whether the element is locked (cannot be moved) 3604 grabbed: false, // whether the element is grabbed by the mouse; renderer sets this privately 3605 grabbable: params.grabbable === undefined ? true : ( params.grabbable ? true : false ), // whether the element can be grabbed 3606 active: false, // whether the element is active from user interaction 3607 classes: {}, // map ( className => true ) 3608 animation: { // object for currently-running animations 3609 current: [], 3610 queue: [] 3611 }, 3612 rscratch: {}, // object in which the renderer can store information 3613 scratch: params.scratch || {}, // scratch objects 3614 edges: [], // array of connected edges 3615 children: [], // array of children 3616 traversalCache: {} // cache of output of traversal functions 3617 }; 3618 3619 // renderedPosition overrides if specified 3620 if( params.renderedPosition ){ 3621 var rpos = params.renderedPosition; 3622 var pan = cy.pan(); 3623 var zoom = cy.zoom(); 3624 3625 this._private.position = { 3626 x: (rpos.x - pan.x) / zoom, 3627 y: (rpos.y - pan.y) / zoom 3628 }; 3629 } 3630 3631 if( is.string( params.classes ) ){ 3632 var classes = params.classes.split( /\s+/ ); 3633 for( var i = 0, l = classes.length; i < l; i++ ){ 3634 var cls = classes[ i ]; 3635 if( !cls || cls === '' ){ continue; } 3636 3637 self._private.classes[ cls ] = true; 3638 } 3639 } 3640 3641 if( params.style || params.css ){ 3642 cy.style().applyBypass( this, params.style || params.css ); 3643 } 3644 3645 if( restore === undefined || restore ){ 3646 this.restore(); 3647 } 3648 3649}; 3650 3651module.exports = Element; 3652 3653},{"../is":83,"../util":100}],23:[function(_dereq_,module,exports){ 3654'use strict'; 3655 3656var define = _dereq_( '../define' ); 3657 3658var elesfn = ({ 3659 on: define.on(), // .on( events [, selector] [, data], handler) 3660 one: define.on( { unbindSelfOnTrigger: true } ), 3661 once: define.on( { unbindAllBindersOnTrigger: true } ), 3662 off: define.off(), // .off( events [, selector] [, handler] ) 3663 trigger: define.trigger(), // .trigger( events [, extraParams] ) 3664 3665 rtrigger: function( event, extraParams ){ // for internal use only 3666 if( this.length === 0 ){ return; } // empty collections don't need to notify anything 3667 3668 // notify renderer 3669 this.cy().notify( { 3670 type: event, 3671 eles: this 3672 } ); 3673 3674 this.trigger( event, extraParams ); 3675 return this; 3676 } 3677}); 3678 3679// aliases: 3680define.eventAliasesOn( elesfn ); 3681 3682module.exports = elesfn; 3683 3684},{"../define":44}],24:[function(_dereq_,module,exports){ 3685'use strict'; 3686 3687var is = _dereq_( '../is' ); 3688var Selector = _dereq_( '../selector' ); 3689 3690var elesfn = ({ 3691 nodes: function( selector ){ 3692 return this.filter( function( i, element ){ 3693 return element.isNode(); 3694 } ).filter( selector ); 3695 }, 3696 3697 edges: function( selector ){ 3698 return this.filter( function( i, element ){ 3699 return element.isEdge(); 3700 } ).filter( selector ); 3701 }, 3702 3703 filter: function( filter ){ 3704 if( filter === undefined ){ // check this first b/c it's the most common/performant case 3705 return this; 3706 } else if( is.string( filter ) || is.elementOrCollection( filter ) ){ 3707 return Selector( filter ).filter( this ); 3708 } else if( is.fn( filter ) ){ 3709 var elements = []; 3710 3711 for( var i = 0; i < this.length; i++ ){ 3712 var ele = this[ i ]; 3713 3714 if( filter.apply( ele, [ i, ele ] ) ){ 3715 elements.push( ele ); 3716 } 3717 } 3718 3719 return this.spawn( elements ); 3720 } 3721 3722 return this.spawn(); // if not handled by above, give 'em an empty collection 3723 }, 3724 3725 not: function( toRemove ){ 3726 if( !toRemove ){ 3727 return this; 3728 } else { 3729 3730 if( is.string( toRemove ) ){ 3731 toRemove = this.filter( toRemove ); 3732 } 3733 3734 var elements = []; 3735 3736 for( var i = 0; i < this.length; i++ ){ 3737 var element = this[ i ]; 3738
3739 var remove = toRemove._private.ids[ element.id() ]; 3740 if( !remove ){ 3741 elements.push( element ); 3742 } 3743 } 3744 3745 return this.spawn( elements ); 3746 } 3747 3748 }, 3749 3750 absoluteComplement: function(){ 3751 var cy = this._private.cy; 3752 3753 return cy.mutableElements().not( this ); 3754 }, 3755 3756 intersect: function( other ){ 3757 // if a selector is specified, then filter by it instead 3758 if( is.string( other ) ){ 3759 var selector = other; 3760 return this.filter( selector ); 3761 } 3762 3763 var elements = []; 3764 var col1 = this; 3765 var col2 = other; 3766 var col1Smaller = this.length < other.length; 3767 // var ids1 = col1Smaller ? col1._private.ids : col2._private.ids; 3768 var ids2 = col1Smaller ? col2._private.ids : col1._private.ids; 3769 var col = col1Smaller ? col1 : col2; 3770 3771 for( var i = 0; i < col.length; i++ ){ 3772 var id = col[ i ]._private.data.id; 3773 var ele = ids2[ id ]; 3774 3775 if( ele ){ 3776 elements.push( ele ); 3777 } 3778 } 3779 3780 return this.spawn( elements ); 3781 }, 3782 3783 xor: function( other ){ 3784 var cy = this._private.cy; 3785 3786 if( is.string( other ) ){ 3787 other = cy.$( other ); 3788 } 3789 3790 var elements = []; 3791 var col1 = this; 3792 var col2 = other; 3793 3794 var add = function( col, other ){ 3795 3796 for( var i = 0; i < col.length; i++ ){ 3797 var ele = col[ i ]; 3798 var id = ele._private.data.id; 3799 var inOther = other._private.ids[ id ]; 3800 3801 if( !inOther ){ 3802 elements.push( ele ); 3803 } 3804 } 3805 3806 }; 3807 3808 add( col1, col2 ); 3809 add( col2, col1 ); 3810 3811 return this.spawn( elements ); 3812 }, 3813 3814 diff: function( other ){ 3815 var cy = this._private.cy; 3816 3817 if( is.string( other ) ){ 3818 other = cy.$( other ); 3819 } 3820 3821 var left = []; 3822 var right = []; 3823 var both = []; 3824 var col1 = this; 3825 var col2 = other; 3826 3827 var add = function( col, other, retEles ){ 3828 3829 for( var i = 0; i < col.length; i++ ){ 3830 var ele = col[ i ]; 3831 var id = ele._private.data.id; 3832 var inOther = other._private.ids[ id ]; 3833 3834 if( inOther ){ 3835 both.push( ele ); 3836 } else { 3837 retEles.push( ele ); 3838 } 3839 } 3840 3841 }; 3842 3843 add( col1, col2, left ); 3844 add( col2, col1, right ); 3845 3846 return { 3847 left: this.spawn( left, { unique: true } ), 3848 right: this.spawn( right, { unique: true } ), 3849 both: this.spawn( both, { unique: true } ) 3850 }; 3851 }, 3852 3853 add: function( toAdd ){ 3854 var cy = this._private.cy; 3855 3856 if( !toAdd ){ 3857 return this; 3858 } 3859 3860 if( is.string( toAdd ) ){ 3861 var selector = toAdd; 3862 toAdd = cy.mutableElements().filter( selector ); 3863 } 3864 3865 var elements = []; 3866 3867 for( var i = 0; i < this.length; i++ ){ 3868 elements.push( this[ i ] ); 3869 } 3870 3871 for( var i = 0; i < toAdd.length; i++ ){ 3872 3873 var add = !this._private.ids[ toAdd[ i ].id() ]; 3874 if( add ){ 3875 elements.push( toAdd[ i ] ); 3876 } 3877 } 3878 3879 return this.spawn( elements ); 3880 }, 3881 3882 // in place merge on calling collection 3883 merge: function( toAdd ){ 3884 var _p = this._private; 3885 var cy = _p.cy; 3886 3887 if( !toAdd ){ 3888 return this; 3889 } 3890 3891 if( toAdd && is.string( toAdd ) ){ 3892 var selector = toAdd; 3893 toAdd = cy.mutableElements().filter( selector ); 3894 } 3895 3896 for( var i = 0; i < toAdd.length; i++ ){ 3897 var toAddEle = toAdd[ i ]; 3898 var id = toAddEle._private.data.id; 3899 var add = !_p.ids[ id ]; 3900 3901 if( add ){ 3902 var index = this.length++; 3903 3904 this[ index ] = toAddEle; 3905 _p.ids[ id ] = toAddEle; 3906 _p.indexes[ id ] = index; 3907 } else { // replace 3908 var index = _p.indexes[ id ]; 3909 3910 this[ index ] = toAddEle; 3911 _p.ids[ id ] = toAddEle; 3912 } 3913 } 3914 3915 return this; // chaining 3916 }, 3917 3918 // remove single ele in place in calling collection 3919 unmergeOne: function( ele ){ 3920 ele = ele[0]; 3921 3922 var _p = this._private; 3923 var id = ele._private.data.id; 3924 var i = _p.indexes[ id ]; 3925 3926 if( i == null ){ 3927 return this; // no need to remove 3928 } 3929 3930 // remove ele 3931 this[ i ] = undefined; 3932 _p.ids[ id ] = undefined; 3933 _p.indexes[ id ] = undefined; 3934 3935 var unmergedLastEle = i === this.length - 1; 3936 3937 // replace empty spot with last ele in collection 3938 if( this.length > 1 && !unmergedLastEle ){ 3939 var lastEleI = this.length - 1; 3940 var lastEle = this[ lastEleI ]; 3941 var lastEleId = lastEle._private.data.id; 3942 3943 this[ lastEleI ] = undefined; 3944 this[ i ] = lastEle; 3945 _p.indexes[ lastEleId ] = i; 3946 } 3947 3948 // the collection is now 1 ele smaller 3949 this.length--; 3950 3951 return this; 3952 }, 3953 3954 // remove eles in place on calling collection 3955 unmerge: function( toRemove ){ 3956 var cy = this._private.cy; 3957 3958 if( !toRemove ){ 3959 return this; 3960 } 3961 3962 if( toRemove && is.string( toRemove ) ){ 3963 var selector = toRemove; 3964 toRemove = cy.mutableElements().filter( selector ); 3965 } 3966 3967 for( var i = 0; i < toRemove.length; i++ ){ 3968 this.unmergeOne( toRemove[ i ] ); 3969 } 3970 3971 return this; // chaining 3972 }, 3973 3974 map: function( mapFn, thisArg ){ 3975 var arr = []; 3976 var eles = this; 3977 3978 for( var i = 0; i < eles.length; i++ ){ 3979 var ele = eles[ i ]; 3980 var ret = thisArg ? mapFn.apply( thisArg, [ ele, i, eles ] ) : mapFn( ele, i, eles ); 3981 3982 arr.push( ret ); 3983 } 3984 3985 return arr; 3986 }, 3987 3988 stdFilter: function( fn, thisArg ){ 3989 var filterEles = []; 3990 var eles = this; 3991 3992 for( var i = 0; i < eles.length; i++ ){ 3993 var ele = eles[ i ]; 3994 var include = thisArg ? fn.apply( thisArg, [ ele, i, eles ] ) : fn( ele, i, eles ); 3995 3996 if( include ){ 3997 filterEles.push( ele ); 3998 } 3999 } 4000 4001 return this.spawn( filterEles ); 4002 }, 4003 4004 max: function( valFn, thisArg ){ 4005 var max = -Infinity; 4006 var maxEle; 4007 var eles = this; 4008 4009 for( var i = 0; i < eles.length; i++ ){ 4010 var ele = eles[ i ]; 4011 var val = thisArg ? valFn.apply( thisArg, [ ele, i, eles ] ) : valFn( ele, i, eles ); 4012 4013 if( val > max ){ 4014 max = val; 4015 maxEle = ele; 4016 } 4017 } 4018 4019 return { 4020 value: max, 4021 ele: maxEle 4022 }; 4023 }, 4024 4025 min: function( valFn, thisArg ){ 4026 var min = Infinity; 4027 var minEle; 4028 var eles = this; 4029 4030 for( var i = 0; i < eles.length; i++ ){ 4031 var ele = eles[ i ]; 4032 var val = thisArg ? valFn.apply( thisArg, [ ele, i, eles ] ) : valFn( ele, i, eles ); 4033 4034 if( val < min ){ 4035 min = val; 4036 minEle = ele; 4037 } 4038 } 4039 4040 return { 4041 value: min, 4042 ele: minEle 4043 }; 4044 } 4045}); 4046 4047// aliases 4048var fn = elesfn; 4049fn[ 'u' ] = fn[ '|' ] = fn[ '+' ] = fn.union = fn.or = fn.add; 4050fn[ '\\' ] = fn[ '!' ] = fn[ '-' ] = fn.difference = fn.relativeComplement = fn.subtract = fn.not; 4051fn[ 'n' ] = fn[ '&' ] = fn[ '.' ] = fn.and = fn.intersection = fn.intersect; 4052fn[ '^' ] = fn[ '(+)' ] = fn[ '(-)' ] = fn.symmetricDifference = fn.symdiff = fn.xor; 4053fn.fnFilter = fn.filterFn = fn.stdFilter; 4054fn.complement = fn.abscomp = fn.absoluteComplement; 4055 4056module.exports = elesfn; 4057 4058},{"../is":83,"../selector":87}],25:[function(_dereq_,module,exports){ 4059'use strict'; 4060 4061var elesfn = ({ 4062 isNode: function(){ 4063 return this.group() === 'nodes'; 4064 }, 4065 4066 isEdge: function(){ 4067 return this.group() === 'edges'; 4068 }, 4069 4070 isLoop: function(){
4071 return this.isEdge() && this.source().id() === this.target().id(); 4072 }, 4073 4074 isSimple: function(){ 4075 return this.isEdge() && this.source().id() !== this.target().id(); 4076 }, 4077 4078 group: function(){ 4079 var ele = this[0]; 4080 4081 if( ele ){ 4082 return ele._private.group; 4083 } 4084 } 4085}); 4086 4087 4088module.exports = elesfn; 4089 4090},{}],26:[function(_dereq_,module,exports){ 4091'use strict'; 4092 4093var util = _dereq_( '../util' ); 4094var is = _dereq_( '../is' ); 4095 4096var Element = _dereq_( './element' ); 4097 4098// factory for generating edge ids when no id is specified for a new element 4099var idFactory = { 4100 generate: function( cy, element, tryThisId ){ 4101 var id = tryThisId != null ? tryThisId : util.uuid(); 4102 4103 while( cy.hasElementWithId( id ) ){ 4104 id = util.uuid(); 4105 } 4106 4107 return id; 4108 } 4109}; 4110 4111// represents a set of nodes, edges, or both together 4112var Collection = function( cy, elements, options ){ 4113 if( cy === undefined || !is.core( cy ) ){ 4114 util.error( 'A collection must have a reference to the core' ); 4115 return; 4116 } 4117 4118 var ids = {}; 4119 var indexes = {}; 4120 var createdElements = false; 4121 4122 if( !elements ){ 4123 elements = []; 4124 } else if( elements.length > 0 && is.plainObject( elements[0] ) && !is.element( elements[0] ) ){ 4125 createdElements = true; 4126 4127 // make elements from json and restore all at once later 4128 var eles = []; 4129 var elesIds = {}; 4130 4131 for( var i = 0, l = elements.length; i < l; i++ ){ 4132 var json = elements[ i ]; 4133 4134 if( json.data == null ){ 4135 json.data = {}; 4136 } 4137 4138 var data = json.data; 4139 4140 // make sure newly created elements have valid ids 4141 if( data.id == null ){ 4142 data.id = idFactory.generate( cy, json ); 4143 } else if( cy.hasElementWithId( data.id ) || elesIds[ data.id ] ){ 4144 continue; // can't create element if prior id already exists 4145 } 4146 4147 var ele = new Element( cy, json, false ); 4148 eles.push( ele ); 4149 elesIds[ data.id ] = true; 4150 } 4151 4152 elements = eles; 4153 } 4154 4155 this.length = 0; 4156 4157 for( var i = 0, l = elements.length; i < l; i++ ){ 4158 var element = elements[ i ]; 4159 if( !element ){ continue; } 4160 4161 var id = element._private.data.id; 4162 4163 if( !options || (options.unique && !ids[ id ] ) ){ 4164 ids[ id ] = element; 4165 indexes[ id ] = this.length; 4166 4167 this[ this.length ] = element; 4168 this.length++; 4169 } 4170 } 4171 4172 this._private = { 4173 cy: cy, 4174 ids: ids, 4175 indexes: indexes 4176 }; 4177 4178 // restore the elements if we created them from json 4179 if( createdElements ){ 4180 this.restore(); 4181 } 4182}; 4183 4184// Functions 4185//////////////////////////////////////////////////////////////////////////////////////////////////// 4186 4187// keep the prototypes in sync (an element has the same functions as a collection) 4188// and use elefn and elesfn as shorthands to the prototypes 4189var elesfn = Element.prototype = Collection.prototype; 4190 4191elesfn.instanceString = function(){ 4192 return 'collection'; 4193}; 4194 4195elesfn.spawn = function( cy, eles, opts ){ 4196 if( !is.core( cy ) ){ // cy is optional 4197 opts = eles; 4198 eles = cy; 4199 cy = this.cy(); 4200 } 4201 4202 return new Collection( cy, eles, opts ); 4203}; 4204 4205elesfn.spawnSelf = function(){ 4206 return this.spawn( this ); 4207}; 4208 4209elesfn.cy = function(){ 4210 return this._private.cy; 4211}; 4212 4213elesfn.element = function(){ 4214 return this[0]; 4215}; 4216 4217elesfn.collection = function(){ 4218 if( is.collection( this ) ){ 4219 return this; 4220 } else { // an element 4221 return new Collection( this._private.cy, [ this ] ); 4222 } 4223}; 4224 4225elesfn.unique = function(){ 4226 return new Collection( this._private.cy, this, { unique: true } ); 4227}; 4228 4229elesfn.hasElementWithId = function( id ){ 4230 return !!this._private.ids[ id ]; 4231}; 4232 4233elesfn.getElementById = function( id ){ 4234 var cy = this._private.cy; 4235 var ele = this._private.ids[ id ]; 4236 4237 return ele ? ele : new Collection( cy ); // get ele or empty collection 4238}; 4239 4240elesfn.poolIndex = function(){ 4241 var cy = this._private.cy; 4242 var eles = cy._private.elements; 4243 var id = this._private.data.id; 4244 4245 return eles._private.indexes[ id ]; 4246}; 4247 4248elesfn.json = function( obj ){ 4249 var ele = this.element(); 4250 var cy = this.cy(); 4251 4252 if( ele == null && obj ){ return this; } // can't set to no eles 4253 4254 if( ele == null ){ return undefined; } // can't get from no eles 4255 4256 var p = ele._private; 4257 4258 if( is.plainObject( obj ) ){ // set 4259 4260 cy.startBatch(); 4261 4262 if( obj.data ){ 4263 ele.data( obj.data ); 4264 } 4265 4266 if( obj.position ){ 4267 ele.position( obj.position ); 4268 } 4269 4270 // ignore group -- immutable 4271 4272 var checkSwitch = function( k, trueFnName, falseFnName ){ 4273 var obj_k = obj[ k ]; 4274 4275 if( obj_k != null && obj_k !== p[ k ] ){ 4276 if( obj_k ){ 4277 ele[ trueFnName ](); 4278 } else { 4279 ele[ falseFnName ](); 4280 } 4281 } 4282 }; 4283 4284 checkSwitch( 'removed', 'remove', 'restore' ); 4285 4286 checkSwitch( 'selected', 'select', 'unselect' ); 4287 4288 checkSwitch( 'selectable', 'selectify', 'unselectify' ); 4289 4290 checkSwitch( 'locked', 'lock', 'unlock' ); 4291 4292 checkSwitch( 'grabbable', 'grabify', 'ungrabify' ); 4293 4294 if( obj.classes != null ){ 4295 ele.classes( obj.classes ); 4296 } 4297 4298 cy.endBatch(); 4299 4300 return this; 4301 4302 } else if( obj === undefined ){ // get 4303 4304 var json = { 4305 data: util.copy( p.data ), 4306 position: util.copy( p.position ), 4307 group: p.group, 4308 removed: p.removed, 4309 selected: p.selected, 4310 selectable: p.selectable, 4311 locked: p.locked, 4312 grabbable: p.grabbable, 4313 classes: null 4314 }; 4315 4316 json.classes = Object.keys( p.classes ).filter(function( cls ){ 4317 return p.classes[cls]; 4318 }).join(' '); 4319 4320 return json; 4321 } 4322}; 4323 4324elesfn.jsons = function(){ 4325 var jsons = []; 4326 4327 for( var i = 0; i < this.length; i++ ){ 4328 var ele = this[ i ]; 4329 var json = ele.json(); 4330 4331 jsons.push( json ); 4332 } 4333 4334 return jsons; 4335}; 4336 4337elesfn.clone = function(){ 4338 var cy = this.cy(); 4339 var elesArr = []; 4340 4341 for( var i = 0; i < this.length; i++ ){ 4342 var ele = this[ i ]; 4343 var json = ele.json(); 4344 var clone = new Element( cy, json, false ); // NB no restore 4345 4346 elesArr.push( clone ); 4347 } 4348 4349 return new Collection( cy, elesArr ); 4350}; 4351elesfn.copy = elesfn.clone; 4352 4353elesfn.restore = function( notifyRenderer ){ 4354 var self = this; 4355 var cy = self.cy(); 4356 var cy_p = cy._private; 4357 4358 if( notifyRenderer === undefined ){ 4359 notifyRenderer = true; 4360 } 4361 4362 // create arrays of nodes and edges, since we need to 4363 // restore the nodes first 4364 var nodes = []; 4365 var edges = []; 4366 var elements; 4367 for( var i = 0, l = self.length; i < l; i++ ){ 4368 var ele = self[ i ]; 4369 4370 if( !ele.removed() ){ 4371 // don't need to handle this ele 4372 continue; 4373 } 4374 4375 // keep nodes first in the array and edges after 4376 if( ele.isNode() ){ // put to front of array if node 4377 nodes.push( ele ); 4378 } else { // put to end of array if edge 4379 edges.push( ele ); 4380 } 4381 } 4382 4383 elements = nodes.concat( edges ); 4384 4385 var i; 4386 var removeFromElements = function(){ 4387 elements.splice( i, 1 ); 4388 i--; 4389 }; 4390 4391 // now, restore each element 4392 for( i = 0; i < elements.length; i++ ){ 4393 var ele = elements[ i ]; 4394 4395 var _private = ele._private; 4396 var data = _private.data; 4397 4398 // the traversal cache should start fresh when ele is added 4399 _private.traversalCache = null; 4400 4401 // set id and validate 4402 if( data.id === undefined ){ 4403 data.id = idFactory.generate( cy, ele ); 4404 4405 } else if( is.number( data.id ) ){ 4406 data.id = '' + data.id; // now it's a string 4407 4408 } else if( is.emptyString( data.id ) || !is.string( data.id ) ){
4409 util.error( 'Can not create element with invalid string ID `' + data.id + '`' ); 4410 4411 // can't create element if it has empty string as id or non-string id 4412 removeFromElements(); 4413 continue; 4414 } else if( cy.hasElementWithId( data.id ) ){ 4415 util.error( 'Can not create second element with ID `' + data.id + '`' ); 4416 4417 // can't create element if one already has that id 4418 removeFromElements(); 4419 continue; 4420 } 4421 4422 var id = data.id; // id is finalised, now let's keep a ref 4423 4424 if( ele.isNode() ){ // extra checks for nodes 4425 var node = ele; 4426 var pos = _private.position; 4427 4428 // make sure the nodes have a defined position 4429 4430 if( pos.x == null ){ 4431 pos.x = 0; 4432 } 4433 4434 if( pos.y == null ){ 4435 pos.y = 0; 4436 } 4437 } 4438 4439 if( ele.isEdge() ){ // extra checks for edges 4440 4441 var edge = ele; 4442 var fields = [ 'source', 'target' ]; 4443 var fieldsLength = fields.length; 4444 var badSourceOrTarget = false; 4445 for( var j = 0; j < fieldsLength; j++ ){ 4446 4447 var field = fields[ j ]; 4448 var val = data[ field ]; 4449 4450 if( is.number( val ) ){ 4451 val = data[ field ] = '' + data[ field ]; // now string 4452 } 4453 4454 if( val == null || val === '' ){ 4455 // can't create if source or target is not defined properly 4456 util.error( 'Can not create edge `' + id + '` with unspecified ' + field ); 4457 badSourceOrTarget = true; 4458 } else if( !cy.hasElementWithId( val ) ){ 4459 // can't create edge if one of its nodes doesn't exist 4460 util.error( 'Can not create edge `' + id + '` with nonexistant ' + field + ' `' + val + '`' ); 4461 badSourceOrTarget = true; 4462 } 4463 } 4464 4465 if( badSourceOrTarget ){ removeFromElements(); continue; } // can't create this 4466 4467 var src = cy.getElementById( data.source ); 4468 var tgt = cy.getElementById( data.target ); 4469 4470 src._private.edges.push( edge ); 4471 tgt._private.edges.push( edge ); 4472 4473 edge._private.source = src; 4474 edge._private.target = tgt; 4475 } // if is edge 4476 4477 // create mock ids / indexes maps for element so it can be used like collections 4478 _private.ids = {}; 4479 _private.ids[ id ] = ele; 4480 _private.indexes = {}; 4481 _private.indexes[ id ] = ele; 4482 4483 _private.removed = false; 4484 cy.addToPool( ele ); 4485 } // for each element 4486 4487 // do compound node sanity checks 4488 for( var i = 0; i < nodes.length; i++ ){ // each node 4489 var node = nodes[ i ]; 4490 var data = node._private.data; 4491 4492 if( is.number( data.parent ) ){ // then automake string 4493 data.parent = '' + data.parent; 4494 } 4495 4496 var parentId = data.parent; 4497 4498 var specifiedParent = parentId != null; 4499 4500 if( specifiedParent ){ 4501 var parent = cy.getElementById( parentId ); 4502 4503 if( parent.empty() ){ 4504 // non-existant parent; just remove it 4505 data.parent = undefined; 4506 } else { 4507 var selfAsParent = false; 4508 var ancestor = parent; 4509 while( !ancestor.empty() ){ 4510 if( node.same( ancestor ) ){ 4511 // mark self as parent and remove from data 4512 selfAsParent = true; 4513 data.parent = undefined; // remove parent reference 4514 4515 // exit or we loop forever 4516 break; 4517 } 4518 4519 ancestor = ancestor.parent(); 4520 } 4521 4522 if( !selfAsParent ){ 4523 // connect with children 4524 parent[0]._private.children.push( node ); 4525 node._private.parent = parent[0]; 4526 4527 // let the core know we have a compound graph 4528 cy_p.hasCompoundNodes = true; 4529 } 4530 } // else 4531 } // if specified parent 4532 } // for each node 4533 4534 if( elements.length > 0 ){ 4535 var restored = new Collection( cy, elements ); 4536 4537 for( var i = 0; i < restored.length; i++ ){ 4538 var ele = restored[i]; 4539 4540 if( ele.isNode() ){ continue; } 4541 4542 // adding an edge invalidates the traversal caches for the parallel edges 4543 var pedges = ele.parallelEdges(); 4544 for( var j = 0; j < pedges.length; j++ ){ 4545 pedges[j]._private.traversalCache = null; 4546 } 4547 4548 // adding an edge invalidates the traversal cache for the connected nodes 4549 ele.source()[0]._private.traversalCache = null; 4550 ele.target()[0]._private.traversalCache = null; 4551 } 4552
4553 var toUpdateStyle; 4554 4555 if( cy_p.hasCompoundNodes ){ 4556 toUpdateStyle = restored.add( restored.connectedNodes() ).add( restored.parent() ); 4557 } else { 4558 toUpdateStyle = restored; 4559 } 4560 4561 toUpdateStyle.updateStyle( notifyRenderer ); 4562 4563 if( notifyRenderer ){ 4564 restored.rtrigger( 'add' ); 4565 } else { 4566 restored.trigger( 'add' ); 4567 } 4568 } 4569 4570 return self; // chainability 4571}; 4572 4573elesfn.removed = function(){ 4574 var ele = this[0]; 4575 return ele && ele._private.removed; 4576}; 4577 4578elesfn.inside = function(){ 4579 var ele = this[0]; 4580 return ele && !ele._private.removed; 4581}; 4582 4583elesfn.remove = function( notifyRenderer ){ 4584 var self = this; 4585 var removed = []; 4586 var elesToRemove = []; 4587 var elesToRemoveIds = {}; 4588 var cy = self._private.cy; 4589 4590 if( notifyRenderer === undefined ){ 4591 notifyRenderer = true; 4592 } 4593 4594 // add connected edges 4595 function addConnectedEdges( node ){ 4596 var edges = node._private.edges; 4597 for( var i = 0; i < edges.length; i++ ){ 4598 add( edges[ i ] ); 4599 } 4600 } 4601 4602 4603 // add descendant nodes 4604 function addChildren( node ){ 4605 var children = node._private.children; 4606 4607 for( var i = 0; i < children.length; i++ ){ 4608 add( children[ i ] ); 4609 } 4610 } 4611 4612 function add( ele ){ 4613 var alreadyAdded = elesToRemoveIds[ ele.id() ]; 4614 if( alreadyAdded ){ 4615 return; 4616 } else { 4617 elesToRemoveIds[ ele.id() ] = true; 4618 } 4619 4620 if( ele.isNode() ){ 4621 elesToRemove.push( ele ); // nodes are removed last 4622 4623 addConnectedEdges( ele ); 4624 addChildren( ele ); 4625 } else { 4626 elesToRemove.unshift( ele ); // edges are removed first 4627 } 4628 } 4629 4630 // make the list of elements to remove 4631 // (may be removing more than specified due to connected edges etc) 4632 4633 for( var i = 0, l = self.length; i < l; i++ ){ 4634 var ele = self[ i ]; 4635 4636 add( ele ); 4637 } 4638 4639 function removeEdgeRef( node, edge ){ 4640 var connectedEdges = node._private.edges; 4641 4642 util.removeFromArray( connectedEdges, edge ); 4643 4644 // removing an edges invalidates the traversal cache for its nodes 4645 node._private.traversalCache = null; 4646 } 4647 4648 function removeParallelRefs( edge ){ 4649 // removing an edge invalidates the traversal caches for the parallel edges 4650 var pedges = edge.parallelEdges(); 4651 for( var j = 0; j < pedges.length; j++ ){ 4652 pedges[j]._private.traversalCache = null; 4653 } 4654 } 4655 4656 var alteredParents = []; 4657 alteredParents.ids = {}; 4658 4659 function removeChildRef( parent, ele ){ 4660 ele = ele[0]; 4661 parent = parent[0]; 4662 4663 var children = parent._private.children; 4664 var pid = parent.id(); 4665 4666 util.removeFromArray( children, ele ); 4667 4668 if( !alteredParents.ids[ pid ] ){ 4669 alteredParents.ids[ pid ] = true; 4670 alteredParents.push( parent ); 4671 } 4672 } 4673 4674 // remove from core pool 4675 cy.removeFromPool( elesToRemove ); 4676 4677 for( var i = 0; i < elesToRemove.length; i++ ){ 4678 var ele = elesToRemove[ i ]; 4679 4680 // mark as removed 4681 ele._private.removed = true; 4682 4683 // add to list of removed elements 4684 removed.push( ele ); 4685 4686 if( ele.isEdge() ){ // remove references to this edge in its connected nodes 4687 var src = ele.source()[0]; 4688 var tgt = ele.target()[0]; 4689 4690 removeEdgeRef( src, ele ); 4691 removeEdgeRef( tgt, ele ); 4692 removeParallelRefs( ele ); 4693 4694 } else { // remove reference to parent 4695 var parent = ele.parent(); 4696 4697 if( parent.length !== 0 ){ 4698 removeChildRef( parent, ele ); 4699 } 4700 } 4701 } 4702 4703 // check to see if we have a compound graph or not 4704 var elesStillInside = cy._private.elements; 4705 cy._private.hasCompoundNodes = false; 4706 for( var i = 0; i < elesStillInside.length; i++ ){ 4707 var ele = elesStillInside[ i ]; 4708 4709 if( ele.isParent() ){ 4710 cy._private.hasCompoundNodes = true; 4711 break; 4712 } 4713 } 4714 4715 var removedElements = new Collection( this.cy(), removed ); 4716 if( removedElements.size() > 0 ){ 4717 // must manually notify since trigger won't do this automatically once removed 4718 4719 if( notifyRenderer ){ 4720 this.cy().notify( { 4721 type: 'remove', 4722 eles: removedElements 4723 } ); 4724 } 4725 4726 removedElements.trigger( 'remove' ); 4727 } 4728 4729 // the parents who were modified by the removal need their style updated 4730 for( var i = 0; i < alteredParents.length; i++ ){ 4731 var ele = alteredParents[ i ]; 4732 4733 if( !ele.removed() ){ 4734 ele.updateStyle(); 4735 } 4736 } 4737 4738 return new Collection( cy, removed ); 4739}; 4740 4741elesfn.move = function( struct ){ 4742 var cy = this._private.cy; 4743 4744 if( struct.source !== undefined || struct.target !== undefined ){ 4745 var srcId = struct.source; 4746 var tgtId = struct.target; 4747 var srcExists = cy.hasElementWithId( srcId ); 4748 var tgtExists = cy.hasElementWithId( tgtId ); 4749 4750 if( srcExists || tgtExists ){ 4751 var jsons = this.jsons(); 4752 4753 this.remove(); 4754 4755 for( var i = 0; i < jsons.length; i++ ){ 4756 var json = jsons[i]; 4757 var ele = this[i]; 4758 4759 if( json.group === 'edges' ){ 4760 if( srcExists ){ json.data.source = srcId; } 4761 4762 if( tgtExists ){ json.data.target = tgtId; } 4763 4764 json.scratch = ele._private.scratch; 4765 } 4766 } 4767 4768 return cy.add( jsons ); 4769 } 4770 4771 } else if( struct.parent !== undefined ){ // move node to new parent 4772 var parentId = struct.parent; 4773 var parentExists = parentId === null || cy.hasElementWithId( parentId ); 4774 4775 if( parentExists ){ 4776 var jsons = this.jsons(); 4777 var descs = this.descendants(); 4778 var descsEtcJsons = descs.union( descs.union( this ).connectedEdges() ).jsons(); 4779 4780 this.remove();
4780 // NB: also removes descendants and their connected edges 4781 4782 for( var i = 0; i < jsons.length; i++ ){ 4783 var json = jsons[i]; 4784 var ele = this[i]; 4785 4786 if( json.group === 'nodes' ){ 4787 json.data.parent = parentId === null ? undefined : parentId; 4788 4789 json.scratch = ele._private.scratch; 4790 } 4791 } 4792 4793 return cy.add( jsons.concat( descsEtcJsons ) ); 4794 } 4795 } 4796 4797 return this; // if nothing done 4798}; 4799 4800[ 4801 _dereq_( './algorithms' ), 4802 _dereq_( './animation' ), 4803 _dereq_( './class' ), 4804 _dereq_( './comparators' ), 4805 _dereq_( './compounds' ), 4806 _dereq_( './data' ), 4807 _dereq_( './degree' ), 4808 _dereq_( './dimensions' ), 4809 _dereq_( './events' ), 4810 _dereq_( './filter' ), 4811 _dereq_( './group' ), 4812 _dereq_( './index' ), 4813 _dereq_( './iteration' ), 4814 _dereq_( './layout' ), 4815 _dereq_( './style' ), 4816 _dereq_( './switch-functions' ), 4817 _dereq_( './traversing' ) 4818].forEach( function( props ){ 4819 util.extend( elesfn, props ); 4820} ); 4821 4822module.exports = Collection; 4823 4824},{"../is":83,"../util":100,"./algorithms":11,"./animation":15,"./class":16,"./comparators":17,"./compounds":18,"./data":19,"./degree":20,"./dimensions":21,"./element":22,"./events":23,"./filter":24,"./group":25,"./index":26,"./iteration":27,"./layout":28,"./style":29,"./switch-functions":30,"./traversing":31}],27:[function(_dereq_,module,exports){ 4825'use strict'; 4826 4827var is = _dereq_( '../is' ); 4828var zIndexSort = _dereq_( './zsort' ); 4829 4830var elesfn = ({ 4831 each: function( fn ){ 4832 if( is.fn( fn ) ){ 4833 for( var i = 0; i < this.length; i++ ){ 4834 var ele = this[ i ]; 4835 var ret = fn.apply( ele, [ i, ele ] ); 4836 4837 if( ret === false ){ break; } // exit each early on return false 4838 } 4839 } 4840 return this; 4841 }, 4842 4843 forEach: function( fn, thisArg ){ 4844 if( is.fn( fn ) ){ 4845 4846 for( var i = 0; i < this.length; i++ ){ 4847 var ele = this[ i ]; 4848 var ret = thisArg ? fn.apply( thisArg, [ ele, i, this ] ) : fn( ele, i, this ); 4849 4850 if( ret === false ){ break; } // exit each early on return false 4851 } 4852 } 4853 4854 return this; 4855 }, 4856 4857 toArray: function(){ 4858 var array = []; 4859 4860 for( var i = 0; i < this.length; i++ ){ 4861 array.push( this[ i ] ); 4862 } 4863 4864 return array; 4865 }, 4866 4867 slice: function( start, end ){ 4868 var array = []; 4869 var thisSize = this.length; 4870 4871 if( end == null ){ 4872 end = thisSize; 4873 } 4874 4875 if( start == null ){ 4876 start = 0; 4877 } 4878 4879 if( start < 0 ){ 4880 start = thisSize + start; 4881 } 4882 4883 if( end < 0 ){ 4884 end = thisSize + end; 4885 } 4886 4887 for( var i = start; i >= 0 && i < end && i < thisSize; i++ ){ 4888 array.push( this[ i ] ); 4889 } 4890 4891 return this.spawn( array ); 4892 }, 4893 4894 size: function(){ 4895 return this.length; 4896 }, 4897 4898 eq: function( i ){ 4899 return this[ i ] || this.spawn(); 4900 }, 4901 4902 first: function(){ 4903 return this[0] || this.spawn(); 4904 }, 4905 4906 last: function(){ 4907 return this[ this.length - 1 ] || this.spawn(); 4908 }, 4909 4910 empty: function(){ 4911 return this.length === 0; 4912 }, 4913 4914 nonempty: function(){ 4915 return !this.empty(); 4916 }, 4917 4918 sort: function( sortFn ){ 4919 if( !is.fn( sortFn ) ){ 4920 return this; 4921 } 4922 4923 var sorted = this.toArray().sort( sortFn ); 4924 4925 return this.spawn( sorted ); 4926 }, 4927 4928 sortByZIndex: function(){ 4929 return this.sort( zIndexSort ); 4930 }, 4931 4932 zDepth: function(){ 4933 var ele = this[0]; 4934 if( !ele ){ return undefined; } 4935 4936 // var cy = ele.cy(); 4937 var _p = ele._private; 4938 var group = _p.group; 4939 4940 if( group === 'nodes' ){ 4941 var depth = _p.data.parent ? ele.parents().size() : 0; 4942 4943 if( !ele.isParent() ){ 4944 return Number.MAX_VALUE; // childless nodes always on top 4945 } 4946 4947 return depth; 4948 } else { 4949 var src = _p.source; 4950 var tgt = _p.target; 4951 var srcDepth = src.zDepth(); 4952 var tgtDepth = tgt.zDepth(); 4953 4954 return Math.max( srcDepth, tgtDepth, 0 ); // depth of deepest parent 4955 } 4956 } 4957}); 4958 4959module.exports = elesfn; 4960 4961},{"../is":83,"./zsort":32}],28:[function(_dereq_,module,exports){ 4962'use strict'; 4963 4964var is = _dereq_( '../is' ); 4965var util = _dereq_( '../util' ); 4966var Promise = _dereq_('../promise'); 4967 4968var elesfn = ({ 4969 4970 // using standard layout options, apply position function (w/ or w/o animation) 4971 layoutPositions: function( layout, options, fn ){ 4972 var nodes = this.nodes(); 4973 var cy = this.cy(); 4974 4975 layout.trigger( { type: 'layoutstart', layout: layout } ); 4976 4977 layout.animations = []; 4978 4979 if( options.animate ){ 4980 for( var i = 0; i < nodes.length; i++ ){ 4981 var node = nodes[ i ]; 4982
4983 var newPos = fn.call( node, i, node ); 4984 var pos = node.position(); 4985 4986 if( !is.number( pos.x ) || !is.number( pos.y ) ){ 4987 node.silentPosition( { x: 0, y: 0 } ); 4988 } 4989 4990 var ani = node.animation( { 4991 position: newPos, 4992 duration: options.animationDuration, 4993 easing: options.animationEasing 4994 } ); 4995 4996 layout.animations.push( ani ); 4997 4998 ani.play(); 4999 } 5000 5001 var onStep; 5002 cy.on( 'step.*', ( onStep = function(){ 5003 if( options.fit ){ 5004 cy.fit( options.eles, options.padding ); 5005 } 5006 }) ); 5007 5008 layout.one('layoutstop', function(){ 5009 cy.off('step.*', onStep); 5010 }); 5011 5012 layout.one( 'layoutready', options.ready ); 5013 layout.trigger( { type: 'layoutready', layout: layout } ); 5014 5015 Promise.all( layout.animations.map(function( ani ){ 5016 return ani.promise(); 5017 }) ).then(function(){ 5018 cy.off('step.*', onStep); 5019 5020 if( options.zoom != null ){ 5021 cy.zoom( options.zoom ); 5022 } 5023 5024 if( options.pan ){ 5025 cy.pan( options.pan ); 5026 } 5027 5028 if( options.fit ){ 5029 cy.fit( options.eles, options.padding ); 5030 } 5031 5032 layout.one( 'layoutstop', options.stop ); 5033 layout.trigger( { type: 'layoutstop', layout: layout } ); 5034 }); 5035 } else { 5036 nodes.positions( fn ); 5037 5038 if( options.fit ){ 5039 cy.fit( options.eles, options.padding ); 5040 } 5041 5042 if( options.zoom != null ){ 5043 cy.zoom( options.zoom ); 5044 } 5045 5046 if( options.pan ){ 5047 cy.pan( options.pan ); 5048 } 5049 5050 layout.one( 'layoutready', options.ready ); 5051 layout.trigger( { type: 'layoutready', layout: layout } ); 5052 5053 layout.one( 'layoutstop', options.stop ); 5054 layout.trigger( { type: 'layoutstop', layout: layout } ); 5055 } 5056 5057 return this; // chaining 5058 }, 5059 5060 layout: function( options ){ 5061 var cy = this.cy(); 5062 5063 cy.layout( util.extend( {}, options, { 5064 eles: this 5065 } ) ); 5066 5067 return this; 5068 }, 5069 5070 makeLayout: function( options ){ 5071 var cy = this.cy(); 5072 5073 return cy.makeLayout( util.extend( {}, options, { 5074 eles: this 5075 } ) ); 5076 } 5077 5078}); 5079 5080// aliases: 5081elesfn.createLayout = elesfn.makeLayout; 5082 5083module.exports = elesfn; 5084 5085},{"../is":83,"../promise":86,"../util":100}],29:[function(_dereq_,module,exports){ 5086'use strict'; 5087 5088var is = _dereq_( '../is' ); 5089 5090var elesfn = ({ 5091 5092 // fully updates (recalculates) the style for the elements 5093 updateStyle: function( notifyRenderer ){ 5094 var cy = this._private.cy; 5095 5096 if( !cy.styleEnabled() ){ return this; } 5097 5098 if( cy._private.batchingStyle ){ 5099 var bEles = cy._private.batchStyleEles; 5100 5101 bEles.merge( this ); 5102 5103 return this; // chaining and exit early when batching 5104 } 5105 5106 var hasCompounds = cy.hasCompoundNodes(); 5107 var style = cy.style(); 5108 var updatedEles = this; 5109 5110 notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false; 5111 5112 if( hasCompounds ){ // then add everything up and down for compound selector checks 5113 updatedEles = this.spawnSelf().merge( this.descendants() ).merge( this.parents() ); 5114 } 5115 5116 style.apply( updatedEles ); 5117 5118 if( hasCompounds ){ 5119 var updatedCompounds = updatedEles.updateCompoundBounds(); 5120 5121 // disable for performance for now 5122 // (as updatedCompounds would be a subset of updatedEles ayway b/c of selectors check) 5123 // if( updatedCompounds.length > 0 ){ 5124 // updatedEles.merge( updatedCompounds ); 5125 // } 5126 } 5127 5128 if( notifyRenderer ){ 5129 updatedEles.rtrigger( 'style' ); // let renderer know we changed style 5130 } else { 5131 updatedEles.trigger( 'style' ); // just fire the event 5132 } 5133 5134 return this; // chaining 5135 }, 5136 5137 // just update the mappers in the elements' styles; cheaper than eles.updateStyle() 5138 updateMappers: function( notifyRenderer ){ 5139 var cy = this._private.cy; 5140 var style = cy.style(); 5141 notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false; 5142 5143 if( !cy.styleEnabled() ){ return this; } 5144 5145 style.updateMappers( this ); 5146 5147 var updatedCompounds = this.updateCompoundBounds(); 5148 var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this; 5149 5150 if( notifyRenderer ){ 5151 toNotify.rtrigger( 'style' );
5151 // let renderer know we changed style 5152 } else { 5153 toNotify.trigger( 'style' ); // just fire the event 5154 } 5155 return this; // chaining 5156 }, 5157 5158 // get the internal parsed style object for the specified property 5159 parsedStyle: function( property ){ 5160 var ele = this[0]; 5161 if( !ele.cy().styleEnabled() ){ return; } 5162 5163 if( ele ){ 5164 return ele._private.style[ property ] || ele.cy().style().getDefaultProperty( property ); 5165 } 5166 }, 5167 5168 // get the specified css property as a rendered value (i.e. on-screen value) 5169 // or get the whole rendered style if no property specified (NB doesn't allow setting) 5170 renderedStyle: function( property ){ 5171 var cy = this.cy(); 5172 if( !cy.styleEnabled() ){ return this; } 5173 5174 var ele = this[0]; 5175 5176 if( ele ){ 5177 var renstyle = ele.cy().style().getRenderedStyle( ele ); 5178 5179 if( property === undefined ){ 5180 return renstyle; 5181 } else { 5182 return renstyle[ property ]; 5183 } 5184 } 5185 }, 5186 5187 // read the calculated css style of the element or override the style (via a bypass) 5188 style: function( name, value ){ 5189 var cy = this.cy(); 5190 5191 if( !cy.styleEnabled() ){ return this; } 5192 5193 var updateTransitions = false; 5194 var style = cy.style(); 5195 5196 if( is.plainObject( name ) ){ // then extend the bypass 5197 var props = name; 5198 style.applyBypass( this, props, updateTransitions ); 5199 5200 var updatedCompounds = this.updateCompoundBounds(); 5201 var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this; 5202 toNotify.rtrigger( 'style' ); // let the renderer know we've updated style 5203 5204 } else if( is.string( name ) ){ 5205 5206 if( value === undefined ){ // then get the property from the style 5207 var ele = this[0]; 5208 5209 if( ele ){ 5210 return style.getStylePropertyValue( ele, name ); 5211 } else { // empty collection => can't get any value 5212 return; 5213 } 5214 5215 } else { // then set the bypass with the property value 5216 style.applyBypass( this, name, value, updateTransitions ); 5217 5218 var updatedCompounds = this.updateCompoundBounds(); 5219 var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this; 5220 toNotify.rtrigger( 'style' ); // let the renderer know we've updated style 5221 } 5222 5223 } else if( name === undefined ){ 5224 var ele = this[0]; 5225 5226 if( ele ){ 5227 return style.getRawStyle( ele ); 5228 } else { // empty collection => can't get any value 5229 return; 5230 } 5231 } 5232 5233 return this; // chaining 5234 }, 5235 5236 removeStyle: function( names ){ 5237 var cy = this.cy(); 5238 5239 if( !cy.styleEnabled() ){ return this; } 5240 5241 var updateTransitions = false; 5242 var style = cy.style(); 5243 var eles = this; 5244 5245 if( names === undefined ){ 5246 for( var i = 0; i < eles.length; i++ ){ 5247 var ele = eles[ i ]; 5248 5249 style.removeAllBypasses( ele, updateTransitions ); 5250 } 5251 } else { 5252 names = names.split( /\s+/ ); 5253 5254 for( var i = 0; i < eles.length; i++ ){ 5255 var ele = eles[ i ]; 5256 5257 style.removeBypasses( ele, names, updateTransitions ); 5258 } 5259 } 5260 5261 var updatedCompounds = this.updateCompoundBounds(); 5262 var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this; 5263 toNotify.rtrigger( 'style' ); // let the renderer know we've updated style 5264 5265 return this; // chaining 5266 }, 5267 5268 show: function(){ 5269 this.css( 'display', 'element' ); 5270 return this; // chaining 5271 }, 5272 5273 hide: function(){ 5274 this.css( 'display', 'none' ); 5275 return this; // chaining 5276 }, 5277 5278 visible: function(){ 5279 var cy = this.cy(); 5280 if( !cy.styleEnabled() ){ return true; } 5281 5282 var ele = this[0]; 5283 var hasCompoundNodes = cy.hasCompoundNodes(); 5284 5285 if( ele ){ 5286 if( 5287 ele.pstyle( 'visibility' ).value !== 'visible' 5288 || ele.pstyle( 'display' ).value !== 'element' 5289 || ele.pstyle('width').pfValue === 0 5290 ){ 5291 return false; 5292 } 5293 5294 if( ele._private.group === 'nodes' ){ 5295 if( ele.pstyle('height').pfValue === 0 ){ return false; } 5296 5297 if( !hasCompoundNodes ){ return true; } 5298 5299 var parents = ele._private.data.parent ? ele.parents() : null; 5300 5301 if( parents ){ 5302 for( var i = 0; i < parents.length; i++ ){ 5303 var parent = parents[ i ]; 5304 var pVis = parent.pstyle( 'visibility' ).value; 5305 var pDis = parent.pstyle( 'display' ).value; 5306 5307 if( pVis !== 'visible' || pDis !== 'element' ){ 5308 return false;
5309 } 5310 } 5311 } 5312 5313 return true; 5314 } else { 5315 var src = ele._private.source; 5316 var tgt = ele._private.target; 5317 5318 return src.visible() && tgt.visible(); 5319 } 5320 5321 } 5322 }, 5323 5324 hidden: function(){ 5325 var ele = this[0]; 5326 5327 if( ele ){ 5328 return !ele.visible(); 5329 } 5330 }, 5331 5332 effectiveOpacity: function(){ 5333 var cy = this.cy(); 5334 if( !cy.styleEnabled() ){ return 1; } 5335 5336 var hasCompoundNodes = cy.hasCompoundNodes(); 5337 var ele = this[0]; 5338 5339 if( ele ){ 5340 var _p = ele._private; 5341 var parentOpacity = ele.pstyle( 'opacity' ).value; 5342 5343 if( !hasCompoundNodes ){ return parentOpacity; } 5344 5345 var parents = !_p.data.parent ? null : ele.parents(); 5346 5347 if( parents ){ 5348 for( var i = 0; i < parents.length; i++ ){ 5349 var parent = parents[ i ]; 5350 var opacity = parent.pstyle( 'opacity' ).value; 5351 5352 parentOpacity = opacity * parentOpacity; 5353 } 5354 } 5355 5356 return parentOpacity; 5357 } 5358 }, 5359 5360 transparent: function(){ 5361 var cy = this.cy(); 5362 if( !cy.styleEnabled() ){ return false; } 5363 5364 var ele = this[0]; 5365 var hasCompoundNodes = ele.cy().hasCompoundNodes(); 5366 5367 if( ele ){ 5368 if( !hasCompoundNodes ){ 5369 return ele.pstyle( 'opacity' ).value === 0; 5370 } else { 5371 return ele.effectiveOpacity() === 0; 5372 } 5373 } 5374 }, 5375 5376 backgrounding: function(){ 5377 var cy = this.cy(); 5378 if( !cy.styleEnabled() ){ return false; } 5379 5380 var ele = this[0]; 5381 5382 return ele._private.backgrounding ? true : false; 5383 } 5384 5385}); 5386 5387 5388elesfn.bypass = elesfn.css = elesfn.style; 5389elesfn.renderedCss = elesfn.renderedStyle; 5390elesfn.removeBypass = elesfn.removeCss = elesfn.removeStyle; 5391elesfn.pstyle = elesfn.parsedStyle; 5392 5393module.exports = elesfn; 5394 5395},{"../is":83}],30:[function(_dereq_,module,exports){ 5396'use strict'; 5397 5398var elesfn = {}; 5399 5400function defineSwitchFunction( params ){ 5401 return function(){ 5402 var args = arguments; 5403 var changedEles = []; 5404 5405 // e.g. cy.nodes().select( data, handler ) 5406 if( args.length === 2 ){ 5407 var data = args[0]; 5408 var handler = args[1]; 5409 this.on( params.event, data, handler ); 5410 } 5411 5412 // e.g. cy.nodes().select( handler ) 5413 else if( args.length === 1 ){ 5414 var handler = args[0]; 5415 this.on( params.event, handler ); 5416 } 5417 5418 // e.g. cy.nodes().select() 5419 else if( args.length === 0 ){ 5420 for( var i = 0; i < this.length; i++ ){ 5421 var ele = this[ i ]; 5422 var able = !params.ableField || ele._private[ params.ableField ]; 5423 var changed = ele._private[ params.field ] != params.value; 5424 5425 if( params.overrideAble ){ 5426 var overrideAble = params.overrideAble( ele ); 5427 5428 if( overrideAble !== undefined ){ 5429 able = overrideAble; 5430 5431 if( !overrideAble ){ return this; } // to save cycles assume not able for all on override 5432 } 5433 } 5434 5435 if( able ){ 5436 ele._private[ params.field ] = params.value; 5437 5438 if( changed ){ 5439 changedEles.push( ele ); 5440 } 5441 } 5442 } 5443 5444 var changedColl = this.spawn( changedEles ); 5445 changedColl.updateStyle(); // change of state => possible change of style 5446 changedColl.trigger( params.event ); 5447 } 5448 5449 return this; 5450 }; 5451} 5452 5453function defineSwitchSet( params ){ 5454 elesfn[ params.field ] = function(){ 5455 var ele = this[0]; 5456 5457 if( ele ){ 5458 if( params.overrideField ){ 5459 var val = params.overrideField( ele ); 5460 5461 if( val !== undefined ){ 5462 return val; 5463 } 5464 } 5465 5466 return ele._private[ params.field ]; 5467 } 5468 }; 5469 5470 elesfn[ params.on ] = defineSwitchFunction( { 5471 event: params.on, 5472 field: params.field, 5473 ableField: params.ableField, 5474 overrideAble: params.overrideAble, 5475 value: true 5476 } ); 5477 5478 elesfn[ params.off ] = defineSwitchFunction( { 5479 event: params.off, 5480 field: params.field, 5481 ableField: params.ableField, 5482 overrideAble: params.overrideAble, 5483 value: false 5484 } ); 5485} 5486 5487defineSwitchSet( { 5488 field: 'locked', 5489 overrideField: function( ele ){ 5490 return ele.cy().autolock() ? true : undefined; 5491 }, 5492 on: 'lock', 5493 off: 'unlock' 5494} ); 5495 5496defineSwitchSet( { 5497 field: 'grabbable', 5498 overrideField: function( ele ){ 5499 return ele.cy().autoungrabify() ? false : undefined; 5500 }, 5501 on: 'grabify', 5502 off: 'ungrabify' 5503} ); 5504 5505defineSwitchSet( { 5506 field: 'selected', 5507 ableField: 'selectable', 5508 overrideAble: function( ele ){ 5509 return ele.cy().autounselectify() ? false : undefined; 5510 }, 5511 on: 'select', 5512 off: 'unselect' 5513} ); 5514 5515defineSwitchSet( { 5516 field: 'selectable', 5517 overrideField: function( ele ){ 5518 return ele.cy().autounselectify() ? false : undefined; 5519 }, 5520 on: 'selectify', 5521 off: 'unselectify' 5522} ); 5523 5524elesfn.deselect = elesfn.unselect; 5525 5526elesfn.grabbed = function(){ 5527 var ele = this[0]; 5528 if( ele ){ 5529 return ele._private.grabbed; 5530 } 5531}; 5532 5533defineSwitchSet( { 5534 field: 'active', 5535 on: 'activate', 5536 off: 'unactivate' 5537} ); 5538 5539elesfn.inactive = function(){ 5540 var ele = this[0]; 5541 if( ele ){ 5542 return !ele._private.active; 5543 } 5544}; 5545 5546module.exports = elesfn; 5547 5548},{}],31:[function(_dereq_,module,exports){ 5549'use strict'; 5550 5551var util = _dereq_( '../util' ); 5552var is = _dereq_( '../is' ); 5553 5554var elesfn = {}; 5555 5556var cache = function( fn, name ){ 5557 return function traversalCache( arg1, arg2, arg3, arg4 ){ 5558 var selectorOrEles = arg1; 5559 var eles = this; 5560 var key; 5561 5562 if( selectorOrEles == null ){ 5563 key = 'null';
5564 } else if( is.elementOrCollection( selectorOrEles ) && selectorOrEles.length === 1 ){ 5565 key = '#' + selectorOrEles.id(); 5566 } 5567 5568 if( eles.length === 1 && key ){ 5569 var _p = eles[0]._private; 5570 var tch = _p.traversalCache = _p.traversalCache || {}; 5571 var ch = tch[ name ] = tch[ name ] || {}; 5572 var cacheHit = ch[ key ]; 5573 5574 if( cacheHit ){ 5575 return cacheHit; 5576 } else { 5577 return ( ch[ key ] = fn.call( eles, arg1, arg2, arg3, arg4 ) ); 5578 } 5579 } else { 5580 return fn.call( eles, arg1, arg2, arg3, arg4 ); 5581 } 5582 }; 5583}; 5584 5585// DAG functions 5586//////////////// 5587 5588var defineDagExtremity = function( params ){ 5589 return function dagExtremityImpl( selector ){ 5590 var eles = this; 5591 var ret = []; 5592 5593 for( var i = 0; i < eles.length; i++ ){ 5594 var ele = eles[ i ]; 5595 if( !ele.isNode() ){ 5596 continue; 5597 } 5598 5599 var disqualified = false; 5600 var edges = ele.connectedEdges(); 5601 5602 for( var j = 0; j < edges.length; j++ ){ 5603 var edge = edges[j]; 5604 var src = edge.source(); 5605 var tgt = edge.target(); 5606 5607 if( 5608 ( params.noIncomingEdges && tgt === ele && src !== ele ) 5609 || ( params.noOutgoingEdges && src === ele && tgt !== ele ) 5610 ){ 5611 disqualified = true; 5612 break; 5613 } 5614 } 5615 5616 if( !disqualified ){ 5617 ret.push( ele ); 5618 } 5619 } 5620 5621 return this.spawn( ret, { unique: true } ).filter( selector ); 5622 }; 5623}; 5624 5625var defineDagOneHop = function( params ){ 5626 return function( selector ){ 5627 var eles = this; 5628 var oEles = []; 5629 5630 for( var i = 0; i < eles.length; i++ ){ 5631 var ele = eles[ i ]; 5632 5633 if( !ele.isNode() ){ continue; } 5634 5635 var edges = ele.connectedEdges(); 5636 for( var j = 0; j < edges.length; j++ ){ 5637 var edge = edges[ j ]; 5638 var src = edge.source(); 5639 var tgt = edge.target(); 5640 5641 if( params.outgoing && src === ele ){ 5642 oEles.push( edge ); 5643 oEles.push( tgt ); 5644 } else if( params.incoming && tgt === ele ){ 5645 oEles.push( edge ); 5646 oEles.push( src ); 5647 } 5648 } 5649 } 5650 5651 return this.spawn( oEles, { unique: true } ).filter( selector ); 5652 }; 5653}; 5654 5655var defineDagAllHops = function( params ){ 5656 return function( selector ){ 5657 var eles = this; 5658 var sEles = []; 5659 var sElesIds = {}; 5660 5661 for( ;; ){ 5662 var next = params.outgoing ? eles.outgoers() : eles.incomers(); 5663 5664 if( next.length === 0 ){ break; } // done if none left 5665 5666 var newNext = false; 5667 for( var i = 0; i < next.length; i++ ){ 5668 var n = next[ i ]; 5669 var nid = n.id(); 5670 5671 if( !sElesIds[ nid ] ){ 5672 sElesIds[ nid ] = true; 5673 sEles.push( n ); 5674 newNext = true; 5675 } 5676 } 5677 5678 if( !newNext ){ break; } // done if touched all outgoers already 5679 5680 eles = next; 5681 } 5682 5683 return this.spawn( sEles, { unique: true } ).filter( selector ); 5684 }; 5685}; 5686 5687util.extend( elesfn, { 5688 // get the root nodes in the DAG 5689 roots: defineDagExtremity({ noIncomingEdges: true }), 5690 5691 // get the leaf nodes in the DAG 5692 leaves: defineDagExtremity({ noOutgoingEdges: true }), 5693 5694 // normally called children in graph theory 5695 // these nodes =edges=> outgoing nodes 5696 outgoers: cache( defineDagOneHop({ outgoing: true }) , 'outgoers' ), 5697 5698 // aka DAG descendants 5699 successors: defineDagAllHops({ outgoing: true }), 5700 5701 // normally called parents in graph theory 5702 // these nodes <=edges= incoming nodes 5703 incomers: cache( defineDagOneHop({ incoming: true }), 'incomers' ), 5704 5705 // aka DAG ancestors 5706 predecessors: defineDagAllHops({ incoming: true }) 5707} ); 5708 5709 5710// Neighbourhood functions 5711////////////////////////// 5712 5713util.extend( elesfn, { 5714 neighborhood: cache(function( selector ){ 5715 var elements = []; 5716 var nodes = this.nodes(); 5717 5718 for( var i = 0; i < nodes.length; i++ ){ // for all nodes 5719 var node = nodes[ i ]; 5720 var connectedEdges = node.connectedEdges(); 5721 5722 // for each connected edge, add the edge and the other node 5723 for( var j = 0; j < connectedEdges.length; j++ ){ 5724 var edge = connectedEdges[ j ]; 5725 var src = edge.source(); 5726 var tgt = edge.target(); 5727 var otherNode = node === src ? tgt : src; 5728 5729 // need check in case of loop 5730 if( otherNode.length > 0 ){ 5731 elements.push( otherNode[0] ); // add node 1 hop away 5732 } 5733 5734 // add connected edge 5735 elements.push( edge[0] ); 5736 } 5737 5738 } 5739 5740 return ( this.spawn( elements, { unique: true } ) ).filter( selector ); 5741 }, 'neighborhood'), 5742 5743 closedNeighborhood: function( selector ){ 5744 return this.neighborhood().add( this ).filter( selector ); 5745 }, 5746 5747 openNeighborhood: function( selector ){ 5748 return this.neighborhood( selector ); 5749 } 5750} ); 5751 5752// aliases 5753elesfn.neighbourhood = elesfn.neighborhood; 5754elesfn.closedNeighbourhood = elesfn.closedNeighborhood; 5755elesfn.openNeighbourhood = elesfn.openNeighborhood; 5756 5757// Edge functions
5758///////////////// 5759 5760util.extend( elesfn, { 5761 source: cache(function sourceImpl( selector ){ 5762 var ele = this[0]; 5763 var src; 5764 5765 if( ele ){ 5766 src = ele._private.source || ele.cy().collection(); 5767 } 5768 5769 return src && selector ? src.filter( selector ) : src; 5770 }, 'source'), 5771 5772 target: cache(function targetImpl( selector ){ 5773 var ele = this[0]; 5774 var tgt; 5775 5776 if( ele ){ 5777 tgt = ele._private.target || ele.cy().collection(); 5778 } 5779 5780 return tgt && selector ? tgt.filter( selector ) : tgt; 5781 }, 'target'), 5782 5783 sources: defineSourceFunction( { 5784 attr: 'source' 5785 } ), 5786 5787 targets: defineSourceFunction( { 5788 attr: 'target' 5789 } ) 5790} ); 5791 5792function defineSourceFunction( params ){ 5793 return function sourceImpl( selector ){ 5794 var sources = []; 5795 5796 for( var i = 0; i < this.length; i++ ){ 5797 var ele = this[ i ]; 5798 var src = ele._private[ params.attr ]; 5799 5800 if( src ){ 5801 sources.push( src ); 5802 } 5803 } 5804 5805 return this.spawn( sources, { unique: true } ).filter( selector ); 5806 }; 5807} 5808 5809util.extend( elesfn, { 5810 edgesWith: cache( defineEdgesWithFunction(), 'edgesWith', true ), 5811 5812 edgesTo: cache( defineEdgesWithFunction( { 5813 thisIsSrc: true 5814 } ), 'edgesTo', true ) 5815} ); 5816 5817function defineEdgesWithFunction( params ){ 5818 5819 return function edgesWithImpl( otherNodes ){ 5820 var elements = []; 5821 var cy = this._private.cy; 5822 var p = params || {}; 5823 5824 // get elements if a selector is specified 5825 if( is.string( otherNodes ) ){ 5826 otherNodes = cy.$( otherNodes ); 5827 } 5828 5829 var thisIds = this._private.ids; 5830 var otherIds = otherNodes._private.ids; 5831 5832 for( var h = 0; h < otherNodes.length; h++ ){ 5833 var edges = otherNodes[ h ]._private.edges; 5834 5835 for( var i = 0; i < edges.length; i++ ){ 5836 var edge = edges[ i ]; 5837 var edgeData = edge._private.data; 5838 var thisToOther = thisIds[ edgeData.source ] && otherIds[ edgeData.target ]; 5839 var otherToThis = otherIds[ edgeData.source ] && thisIds[ edgeData.target ]; 5840 var edgeConnectsThisAndOther = thisToOther || otherToThis; 5841 5842 if( !edgeConnectsThisAndOther ){ continue; } 5843 5844 if( p.thisIsSrc || p.thisIsTgt ){ 5845 if( p.thisIsSrc && !thisToOther ){ continue; } 5846 5847 if( p.thisIsTgt && !otherToThis ){ continue; } 5848 } 5849 5850 elements.push( edge ); 5851 } 5852 } 5853 5854 return this.spawn( elements, { unique: true } ); 5855 }; 5856} 5857 5858util.extend( elesfn, { 5859 connectedEdges: cache(function( selector ){ 5860 var retEles = []; 5861 5862 var eles = this; 5863 for( var i = 0; i < eles.length; i++ ){ 5864 var node = eles[ i ]; 5865 if( !node.isNode() ){ continue; } 5866 5867 var edges = node._private.edges; 5868 5869 for( var j = 0; j < edges.length; j++ ){ 5870 var edge = edges[ j ]; 5871 retEles.push( edge ); 5872 } 5873 } 5874 5875 return this.spawn( retEles, { unique: true } ).filter( selector ); 5876 }, 'connectedEdges'), 5877 5878 connectedNodes: cache(function( selector ){ 5879 var retEles = []; 5880 5881 var eles = this; 5882 for( var i = 0; i < eles.length; i++ ){ 5883 var edge = eles[ i ]; 5884 if( !edge.isEdge() ){ continue; } 5885 5886 retEles.push( edge.source()[0] ); 5887 retEles.push( edge.target()[0] ); 5888 } 5889 5890 return this.spawn( retEles, { unique: true } ).filter( selector ); 5891 }, 'connectedNodes'), 5892 5893 parallelEdges: cache( defineParallelEdgesFunction(), 'parallelEdges' ), 5894 5895 codirectedEdges: cache( defineParallelEdgesFunction( { 5896 codirected: true 5897 } ), 'codirectedEdges' ) 5898} ); 5899 5900function defineParallelEdgesFunction( params ){ 5901 var defaults = { 5902 codirected: false 5903 }; 5904 params = util.extend( {}, defaults, params ); 5905 5906 return function parallelEdgesImpl( selector ){ // micro-optimised for renderer 5907 var elements = []; 5908 var edges = this.edges(); 5909 var p = params; 5910 5911 // look at all the edges in the collection 5912 for( var i = 0; i < edges.length; i++ ){ 5913 var edge1 = edges[ i ]; 5914 var edge1_p = edge1._private; 5915 var src1 = edge1_p.source; 5916 var srcid1 = src1._private.data.id; 5917 var tgtid1 = edge1_p.data.target; 5918 var srcEdges1 = src1._private.edges; 5919 5920 // look at edges connected to the src node of this edge 5921 for( var j = 0; j < srcEdges1.length; j++ ){ 5922 var edge2 = srcEdges1[ j ]; 5923 var edge2data = edge2._private.data; 5924 var tgtid2 = edge2data.target; 5925 var srcid2 = edge2data.source; 5926 5927 var codirected = tgtid2 === tgtid1 && srcid2 === srcid1; 5928 var oppdirected = srcid1 === tgtid2 && tgtid1 === srcid2; 5929 5930 if( (p.codirected && codirected) || (!p.codirected && (codirected || oppdirected)) ){ 5931 elements.push( edge2 ); 5932 } 5933 } 5934 } 5935 5936 return this.spawn( elements, { unique: true } ).filter( selector ); 5937 }; 5938 5939} 5940 5941// Misc functions
5942///////////////// 5943 5944util.extend( elesfn, { 5945 components: function(){ 5946 var self = this; 5947 var cy = self.cy(); 5948 var visited = self.spawn(); 5949 var unvisited = self.nodes().spawnSelf(); 5950 var components = []; 5951 5952 var visitInComponent = function( node, component ){ 5953 visited.merge( node ); 5954 unvisited.unmerge( node ); 5955 component.merge( node ); 5956 }; 5957 5958 if( unvisited.empty() ){ return self.spawn(); } 5959 5960 do { 5961 var component = cy.collection(); 5962 components.push( component ); 5963 5964 var root = unvisited[0]; 5965 visitInComponent( root, component ); 5966 5967 self.bfs({ 5968 directed: false, 5969 roots: root, 5970 visit: function( i, depth, v, e, u ){ 5971 visitInComponent( v, component ); 5972 } 5973 } ); 5974 5975 } while( unvisited.length > 0 ); 5976 5977 return components.map(function( component ){ 5978 var connectedEdges = component.connectedEdges().stdFilter(function( edge ){ 5979 return component.anySame( edge.source() ) && component.anySame( edge.target() ); 5980 }); 5981 5982 return component.union( connectedEdges ); 5983 }); 5984 } 5985} ); 5986 5987module.exports = elesfn; 5988 5989},{"../is":83,"../util":100}],32:[function(_dereq_,module,exports){ 5990'use strict'; 5991 5992var zIndexSort = function( a, b ){ 5993 var cy = a.cy(); 5994 var zDiff = a.pstyle( 'z-index' ).value - b.pstyle( 'z-index' ).value; 5995 var depthA = 0; 5996 var depthB = 0; 5997 var hasCompoundNodes = cy.hasCompoundNodes(); 5998 var aIsNode = a.isNode(); 5999 var aIsEdge = !aIsNode; 6000 var bIsNode = b.isNode(); 6001 var bIsEdge = !bIsNode; 6002 6003 // no need to calculate element depth if there is no compound node 6004 if( hasCompoundNodes ){ 6005 depthA = a.zDepth(); 6006 depthB = b.zDepth(); 6007 } 6008 6009 var depthDiff = depthA - depthB; 6010 var sameDepth = depthDiff === 0; 6011 6012 if( sameDepth ){ 6013 6014 if( aIsNode && bIsEdge ){ 6015 return 1; // 'a' is a node, it should be drawn later 6016 6017 } else if( aIsEdge && bIsNode ){ 6018 return -1; // 'a' is an edge, it should be drawn first 6019 6020 } else { // both nodes or both edges 6021 if( zDiff === 0 ){ // same z-index => compare indices in the core (order added to graph w/ last on top) 6022 return a.poolIndex() - b.poolIndex(); 6023 } else { 6024 return zDiff; 6025 } 6026 } 6027 6028 // elements on different level 6029 } else { 6030 return depthDiff; // deeper element should be drawn later 6031 } 6032 6033}; 6034 6035module.exports = zIndexSort; 6036 6037},{}],33:[function(_dereq_,module,exports){ 6038'use strict'; 6039 6040var is = _dereq_( '../is' ); 6041var util = _dereq_( '../util' ); 6042var Collection = _dereq_( '../collection' ); 6043var Element = _dereq_( '../collection/element' ); 6044 6045var corefn = { 6046 add: function( opts ){ 6047 6048 var elements; 6049 var cy = this; 6050 6051 // add the elements 6052 if( is.elementOrCollection( opts ) ){ 6053 var eles = opts; 6054 6055 if( eles._private.cy === cy ){ // same instance => just restore 6056 elements = eles.restore(); 6057 6058 } else { // otherwise, copy from json 6059 var jsons = []; 6060 6061 for( var i = 0; i < eles.length; i++ ){ 6062 var ele = eles[ i ]; 6063 jsons.push( ele.json() ); 6064 } 6065 6066 elements = new Collection( cy, jsons ); 6067 } 6068 } 6069 6070 // specify an array of options 6071 else if( is.array( opts ) ){ 6072 var jsons = opts; 6073 6074 elements = new Collection( cy, jsons ); 6075 } 6076 6077 // specify via opts.nodes and opts.edges 6078 else if( is.plainObject( opts ) && (is.array( opts.nodes ) || is.array( opts.edges )) ){ 6079 var elesByGroup = opts; 6080 var jsons = []; 6081 6082 var grs = [ 'nodes', 'edges' ]; 6083 for( var i = 0, il = grs.length; i < il; i++ ){ 6084 var group = grs[ i ]; 6085 var elesArray = elesByGroup[ group ]; 6086 6087 if( is.array( elesArray ) ){ 6088 6089 for( var j = 0, jl = elesArray.length; j < jl; j++ ){ 6090 var json = util.extend( { group: group }, elesArray[ j ] ); 6091 6092 jsons.push( json ); 6093 } 6094 } 6095 } 6096 6097 elements = new Collection( cy, jsons ); 6098 } 6099 6100 // specify options for one element 6101 else { 6102 var json = opts; 6103 elements = (new Element( cy, json )).collection(); 6104 } 6105 6106 return elements; 6107 }, 6108 6109 remove: function( collection ){ 6110 if( is.elementOrCollection( collection ) ){ 6111 // already have right ref 6112 } else if( is.string( collection ) ){ 6113 var selector = collection; 6114 collection = this.$( selector ); 6115 } 6116 6117 return collection.remove(); 6118 }, 6119 6120 load: function( elements, onload, ondone ){ 6121 var cy = this; 6122 6123 cy.notifications( false ); 6124 6125 // remove old elements 6126 var oldEles = cy.mutableElements(); 6127 if( oldEles.length > 0 ){ 6128 oldEles.remove(); 6129 } 6130 6131 if( elements != null ){ 6132 if( is.plainObject( elements ) || is.array( elements ) ){ 6133 cy.add( elements ); 6134 } 6135 } 6136 6137 cy.one( 'layoutready', function( e ){ 6138 cy.notifications( true ); 6139 cy.trigger( e ); // we missed this event by turning notifications off, so pass it on 6140 6141 cy.notify( { 6142 type: 'load', 6143 eles: cy.mutableElements() 6144 } ); 6145 6146 cy.one( 'load', onload ); 6147 cy.trigger( 'load' ); 6148 } ).one( 'layoutstop', function(){ 6149 cy.one( 'done', ondone ); 6150 cy.trigger( 'done' ); 6151 } ); 6152 6153 var layoutOpts = util.extend( {}, cy._private.options.layout ); 6154 layoutOpts.eles = cy.elements(); 6155 6156 cy.layout( layoutOpts ); 6157 6158 return this; 6159 } 6160}; 6161 6162module.exports = corefn; 6163 6164},{"../collection":26,"../collection/element":22,"../is":83,"../util":100}],34:[function(_dereq_,module,exports){ 6165'use strict'; 6166 6167var define = _dereq_( '../define' ); 6168var util = _dereq_( '../util' ); 6169var is = _dereq_( '../is' ); 6170
6171var corefn = ({ 6172 6173 // pull in animation functions 6174 animate: define.animate(), 6175 animation: define.animation(), 6176 animated: define.animated(), 6177 clearQueue: define.clearQueue(), 6178 delay: define.delay(), 6179 delayAnimation: define.delayAnimation(), 6180 stop: define.stop(), 6181 6182 addToAnimationPool: function( eles ){ 6183 var cy = this; 6184 6185 if( !cy.styleEnabled() ){ return; } // save cycles when no style used 6186 6187 cy._private.aniEles.merge( eles ); 6188 }, 6189 6190 stopAnimationLoop: function(){ 6191 this._private.animationsRunning = false; 6192 }, 6193 6194 startAnimationLoop: function(){ 6195 var cy = this; 6196 6197 cy._private.animationsRunning = true; 6198 6199 if( !cy.styleEnabled() ){ return; } // save cycles when no style used 6200 6201 // NB the animation loop will exec in headless environments if style enabled 6202 // and explicit cy.destroy() is necessary to stop the loop 6203 6204 function globalAnimationStep(){ 6205 if( !cy._private.animationsRunning ){ return; } 6206 6207 util.requestAnimationFrame( function animationStep( now ){ 6208 handleElements( now ); 6209 globalAnimationStep(); 6210 } ); 6211 } 6212 6213 var renderer = cy.renderer(); 6214 6215 if( renderer && renderer.beforeRender ){ // let the renderer schedule animations 6216 renderer.beforeRender( function rendererAnimationStep( willDraw, now ){ 6217 handleElements( now ); 6218 }, renderer.beforeRenderPriorities.animations ); 6219 } else { // manage the animation loop ourselves 6220 globalAnimationStep(); // first call 6221 } 6222 6223 function handleElements( now ){ 6224 var eles = cy._private.aniEles; 6225 var doneEles = []; 6226 6227 function handleElement( ele, isCore ){ 6228 var _p = ele._private; 6229 var current = _p.animation.current; 6230 var queue = _p.animation.queue; 6231 var ranAnis = false; 6232 6233 // if nothing currently animating, get something from the queue 6234 if( current.length === 0 ){ 6235 var next = queue.shift(); 6236 6237 if( next ){ 6238 current.push( next ); 6239 } 6240 } 6241 6242 var callbacks = function( callbacks ){ 6243 for( var j = callbacks.length - 1; j >= 0; j-- ){ 6244 var cb = callbacks[ j ]; 6245 6246 cb(); 6247 } 6248 6249 callbacks.splice( 0, callbacks.length ); 6250 }; 6251 6252 // step and remove if done 6253 for( var i = current.length - 1; i >= 0; i-- ){ 6254 var ani = current[ i ]; 6255 var ani_p = ani._private; 6256 6257 if( ani_p.stopped ){ 6258 current.splice( i, 1 ); 6259 6260 ani_p.hooked = false; 6261 ani_p.playing = false; 6262 ani_p.started = false; 6263 6264 callbacks( ani_p.frames ); 6265 6266 continue; 6267 } 6268 6269 if( !ani_p.playing && !ani_p.applying ){ continue; } 6270 6271 // an apply() while playing shouldn't do anything 6272 if( ani_p.playing && ani_p.applying ){ 6273 ani_p.applying = false; 6274 } 6275 6276 if( !ani_p.started ){ 6277 startAnimation( ele, ani, now ); 6278 } 6279 6280 step( ele, ani, now, isCore ); 6281 6282 if( is.fn( ani_p.step ) ){ 6283 ani_p.step.call( ele, now ); 6284 } 6285 6286 if( ani_p.applying ){ 6287 ani_p.applying = false; 6288 } 6289 6290 callbacks( ani_p.frames ); 6291 6292 if( ani.completed() ){ 6293 current.splice( i, 1 ); 6294 6295 ani_p.hooked = false; 6296 ani_p.playing = false; 6297 ani_p.started = false; 6298 6299 callbacks( ani_p.completes ); 6300 } 6301 6302 ranAnis = true; 6303 } 6304 6305 if( !isCore && current.length === 0 && queue.length === 0 ){ 6306 doneEles.push( ele ); 6307 } 6308 6309 return ranAnis; 6310 } // handleElement 6311 6312 // handle all eles 6313 var ranEleAni = false; 6314 for( var e = 0; e < eles.length; e++ ){ 6315 var ele = eles[ e ]; 6316 var handledThisEle = handleElement( ele ); 6317 6318 ranEleAni = ranEleAni || handledThisEle; 6319 } // each element 6320 6321 var ranCoreAni = handleElement( cy, true ); 6322 6323 // notify renderer 6324 if( ranEleAni || ranCoreAni ){ 6325 if( eles.length > 0 ){ 6326 var updatedEles = eles.updateCompoundBounds().spawnSelf().merge( eles ); 6327 6328 cy.notify({ 6329 type: 'draw', 6330 eles: updatedEles 6331 }); 6332 } else { 6333 cy.notify({ 6334 type: 'draw' 6335 }); 6336 } 6337 } 6338 6339 // remove elements from list of currently animating if its queues are empty 6340 eles.unmerge( doneEles ); 6341 6342 cy.trigger('step'); 6343 6344 } // handleElements 6345 6346 function startAnimation( self, ani, now ){ 6347 var isCore = is.core( self ); 6348 var isEles = !isCore; 6349 var ele = self; 6350 var style = cy._private.style; 6351 var ani_p = ani._private; 6352 6353 if( isEles ){ 6354 var pos = ele._private.position; 6355 6356 ani_p.startPosition = ani_p.startPosition || { 6357 x: pos.x, 6358 y: pos.y 6359 }; 6360 6361 ani_p.startStyle = ani_p.startStyle || style.getAnimationStartStyle( ele, ani_p.style ); 6362 } 6363 6364 if( isCore ){ 6365 var pan = cy._private.pan; 6366 6367 ani_p.startPan = ani_p.startPan || { 6368 x: pan.x, 6369 y: pan.y 6370 }; 6371 6372 ani_p.startZoom = ani_p.startZoom != null ? ani_p.startZoom : cy._private.zoom; 6373 } 6374 6375 ani_p.started = true;
vendor: 2,828 bytes, lines 6376-6480
6376 ani_p.startTime = now - ani_p.progress * ani_p.duration; 6377 } 6378 6379 function step( self, ani, now, isCore ){ 6380 var style = cy._private.style; 6381 var isEles = !isCore; 6382 var _p = self._private; 6383 var ani_p = ani._private; 6384 var pEasing = ani_p.easing; 6385 var startTime = ani_p.startTime; 6386 6387 if( !ani_p.easingImpl ){ 6388 6389 if( pEasing == null ){ // use default 6390 ani_p.easingImpl = easings[ 'linear' ]; 6391 6392 } else { // then define w/ name 6393 var easingVals; 6394 6395 if( is.string( pEasing ) ){ 6396 var easingProp = style.parse( 'transition-timing-function', pEasing ); 6397 6398 easingVals = easingProp.value; 6399 6400 } else { // then assume preparsed array 6401 easingVals = pEasing; 6402 } 6403 6404 var name, args; 6405 6406 if( is.string( easingVals ) ){ 6407 name = easingVals; 6408 args = []; 6409 } else { 6410 name = easingVals[1]; 6411 args = easingVals.slice( 2 ).map( function( n ){ return +n; } ); 6412 } 6413 6414 if( args.length > 0 ){ // create with args 6415 if( name === 'spring' ){ 6416 args.push( ani_p.duration ); // need duration to generate spring 6417 } 6418 6419 ani_p.easingImpl = easings[ name ].apply( null, args ); 6420 } else { // static impl by name 6421 ani_p.easingImpl = easings[ name ]; 6422 } 6423 } 6424 6425 } 6426 6427 var easing = ani_p.easingImpl; 6428 var percent; 6429 6430 if( ani_p.duration === 0 ){ 6431 percent = 1; 6432 } else { 6433 percent = (now - startTime) / ani_p.duration; 6434 } 6435 6436 if( ani_p.applying ){ 6437 percent = ani_p.progress; 6438 } 6439 6440 if( percent < 0 ){ 6441 percent = 0; 6442 } else if( percent > 1 ){ 6443 percent = 1; 6444 } 6445 6446 if( ani_p.delay == null ){ // then update 6447 6448 var startPos = ani_p.startPosition; 6449 var endPos = ani_p.position; 6450 var pos = _p.position; 6451 if( endPos && isEles ){ 6452 if( valid( startPos.x, endPos.x ) ){ 6453 pos.x = ease( startPos.x, endPos.x, percent, easing ); 6454 } 6455 6456 if( valid( startPos.y, endPos.y ) ){ 6457 pos.y = ease( startPos.y, endPos.y, percent, easing ); 6458 } 6459 6460 self.trigger('position'); 6461 } 6462 6463 var startPan = ani_p.startPan; 6464 var endPan = ani_p.pan; 6465 var pan = _p.pan; 6466 var animatingPan = endPan != null && isCore; 6467 if( animatingPan ){ 6468 if( valid( startPan.x, endPan.x ) ){ 6469 pan.x = ease( startPan.x, endPan.x, percent, easing ); 6470 } 6471 6472 if( valid( startPan.y, endPan.y ) ){ 6473 pan.y = ease( startPan.y, endPan.y, percent, easing ); 6474 } 6475 6476 self.trigger( 'pan' ); 6477 } 6478 6479 var startZoom = ani_p.startZoom; 6480 var endZoom = ani_p.zoom;
6481 var animatingZoom = endZoom != null && isCore; 6482 if( animatingZoom ){ 6483 if( valid( startZoom, endZoom ) ){ 6484 _p.zoom = ease( startZoom, endZoom, percent, easing ); 6485 } 6486 6487 self.trigger( 'zoom' ); 6488 } 6489 6490 if( animatingPan || animatingZoom ){ 6491 self.trigger( 'viewport' ); 6492 } 6493 6494 var props = ani_p.style; 6495 if( props && props.length > 0 && isEles ){ 6496 for( var i = 0; i < props.length; i++ ){ 6497 var prop = props[ i ]; 6498 var name = prop.name; 6499 var end = prop; 6500 6501 var start = ani_p.startStyle[ name ]; 6502 var easedVal = ease( start, end, percent, easing ); 6503 6504 style.overrideBypass( self, name, easedVal ); 6505 } // for props 6506 6507 self.trigger('style'); 6508 6509 } // if 6510 6511 } 6512 6513 ani_p.progress = percent; 6514 6515 return percent; 6516 } 6517 6518 function valid( start, end ){ 6519 if( start == null || end == null ){ 6520 return false; 6521 } 6522 6523 if( is.number( start ) && is.number( end ) ){ 6524 return true; 6525 } else if( (start) && (end) ){ 6526 return true; 6527 } 6528 6529 return false; 6530 } 6531 6532 // assumes p0 = 0, p3 = 1 6533 function evalCubicBezier( p1, p2, t ){ 6534 var one_t = 1 - t; 6535 var tsq = t * t; 6536 6537 return ( 3 * one_t * one_t * t * p1 ) + ( 3 * one_t * tsq * p2 ) + tsq * t; 6538 } 6539 6540 function cubicBezier( p1, p2 ){ 6541 return function( start, end, percent ){ 6542 return start + (end - start) * evalCubicBezier( p1, p2, percent ); 6543 }; 6544 } 6545 6546 /*! Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */ 6547 /* Given a tension, friction, and duration, a simulation at 60FPS will first run without a defined duration in order to calculate the full path. A second pass 6548 then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */ 6549 var generateSpringRK4 = (function(){ 6550 function springAccelerationForState( state ){ 6551 return (-state.tension * state.x) - (state.friction * state.v); 6552 } 6553 6554 function springEvaluateStateWithDerivative( initialState, dt, derivative ){ 6555 var state = { 6556 x: initialState.x + derivative.dx * dt, 6557 v: initialState.v + derivative.dv * dt, 6558 tension: initialState.tension, 6559 friction: initialState.friction 6560 }; 6561 6562 return { dx: state.v, dv: springAccelerationForState( state ) }; 6563 } 6564 6565 function springIntegrateState( state, dt ){ 6566 var a = { 6567 dx: state.v, 6568 dv: springAccelerationForState( state ) 6569 }, 6570 b = springEvaluateStateWithDerivative( state, dt * 0.5, a ), 6571 c = springEvaluateStateWithDerivative( state, dt * 0.5, b ), 6572 d = springEvaluateStateWithDerivative( state, dt, c ), 6573 dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx), 6574 dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv); 6575 6576 state.x = state.x + dxdt * dt; 6577 state.v = state.v + dvdt * dt; 6578 6579 return state; 6580 } 6581 6582 return function springRK4Factory( tension, friction, duration ){ 6583 6584 var initState = { 6585 x: -1, 6586 v: 0, 6587 tension: null, 6588 friction: null 6589 }, 6590 path = [0], 6591 time_lapsed = 0, 6592 tolerance = 1 / 10000, 6593 DT = 16 / 1000, 6594 have_duration, dt, last_state; 6595 6596 tension = parseFloat( tension ) || 500; 6597 friction = parseFloat( friction ) || 20; 6598 duration = duration || null; 6599 6600 initState.tension = tension; 6601 initState.friction = friction; 6602 6603 have_duration = duration !== null; 6604 6605 /* Calculate the actual time it takes for this animation to complete with the provided conditions. */ 6606 if( have_duration ){ 6607 /* Run the simulation without a duration. */ 6608 time_lapsed = springRK4Factory( tension, friction ); 6609 /* Compute the adjusted time delta. */ 6610 dt = time_lapsed / duration * DT; 6611 } else { 6612 dt = DT; 6613 } 6614 6615 while( true ){ 6616 /* Next/step function .*/ 6617 last_state = springIntegrateState( last_state || initState, dt ); 6618 /* Store the position. */ 6619 path.push( 1 + last_state.x ); 6620 time_lapsed += 16; 6621 /* If the change threshold is reached, break. */ 6622 if( !(Math.abs( last_state.x ) > tolerance && Math.abs( last_state.v ) > tolerance) ){ 6623 break; 6624 } 6625 } 6626 6627 /* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the 6628 computed path and returns a snapshot of the position according to a given percentComplete. */ 6629 return !have_duration ? time_lapsed : function( percentComplete ){ return path[ (percentComplete * (path.length - 1)) | 0 ]; }; 6630 }; 6631 }()); 6632 6633 var easings = { 6634 'linear': function( start, end, percent ){ 6635 return start + (end - start) * percent; 6636 }, 6637 6638 // default easings 6639 'ease': cubicBezier( 0.25, 0.1, 0.25, 1 ), 6640 'ease-in': cubicBezier( 0.42, 0, 1, 1 ), 6641 'ease-out': cubicBezier( 0, 0, 0.58, 1 ), 6642 'ease-in-out': cubicBezier( 0.42, 0, 0.58, 1 ), 6643 6644 // sine 6645 'ease-in-sine': cubicBezier( 0.47, 0, 0.745, 0.715 ), 6646 'ease-out-sine': cubicBezier( 0.39, 0.575, 0.565, 1 ), 6647 'ease-in-out-sine': cubicBezier( 0.445, 0.05, 0.55, 0.95 ), 6648 6649 // quad 6650 'ease-in-quad': cubicBezier( 0.55, 0.085, 0.68, 0.53 ), 6651 'ease-out-quad': cubicBezier( 0.25, 0.46, 0.45, 0.94 ),
6652 'ease-in-out-quad': cubicBezier( 0.455, 0.03, 0.515, 0.955 ), 6653 6654 // cubic 6655 'ease-in-cubic': cubicBezier( 0.55, 0.055, 0.675, 0.19 ), 6656 'ease-out-cubic': cubicBezier( 0.215, 0.61, 0.355, 1 ), 6657 'ease-in-out-cubic': cubicBezier( 0.645, 0.045, 0.355, 1 ), 6658 6659 // quart 6660 'ease-in-quart': cubicBezier( 0.895, 0.03, 0.685, 0.22 ), 6661 'ease-out-quart': cubicBezier( 0.165, 0.84, 0.44, 1 ), 6662 'ease-in-out-quart': cubicBezier( 0.77, 0, 0.175, 1 ), 6663 6664 // quint 6665 'ease-in-quint': cubicBezier( 0.755, 0.05, 0.855, 0.06 ), 6666 'ease-out-quint': cubicBezier( 0.23, 1, 0.32, 1 ), 6667 'ease-in-out-quint': cubicBezier( 0.86, 0, 0.07, 1 ), 6668 6669 // expo 6670 'ease-in-expo': cubicBezier( 0.95, 0.05, 0.795, 0.035 ), 6671 'ease-out-expo': cubicBezier( 0.19, 1, 0.22, 1 ), 6672 'ease-in-out-expo': cubicBezier( 1, 0, 0, 1 ), 6673 6674 // circ 6675 'ease-in-circ': cubicBezier( 0.6, 0.04, 0.98, 0.335 ), 6676 'ease-out-circ': cubicBezier( 0.075, 0.82, 0.165, 1 ), 6677 'ease-in-out-circ': cubicBezier( 0.785, 0.135, 0.15, 0.86 ), 6678 6679 6680 // user param easings... 6681 6682 'spring': function( tension, friction, duration ){ 6683 if( duration === 0 ){ // can't get a spring w/ duration 0 6684 return easings.linear; // duration 0 => jump to end so impl doesn't matter 6685 } 6686 6687 var spring = generateSpringRK4( tension, friction, duration ); 6688 6689 return function( start, end, percent ){ 6690 return start + (end - start) * spring( percent ); 6691 }; 6692 }, 6693 6694 'cubic-bezier': function( x1, y1, x2, y2 ){ 6695 return cubicBezier( x1, y1, x2, y2 ); 6696 } 6697 }; 6698 6699 function ease( startProp, endProp, percent, easingFn ){ 6700 if( percent < 0 ){ 6701 percent = 0; 6702 } else if( percent > 1 ){ 6703 percent = 1; 6704 } 6705 6706 var start, end; 6707 6708 if( startProp.pfValue != null || startProp.value != null ){ 6709 start = startProp.pfValue != null ? startProp.pfValue : startProp.value; 6710 } else { 6711 start = startProp; 6712 } 6713 6714 if( endProp.pfValue != null || endProp.value != null ){ 6715 end = endProp.pfValue != null ? endProp.pfValue : endProp.value; 6716 } else { 6717 end = endProp; 6718 } 6719 6720 if( is.number( start ) && is.number( end ) ){ 6721 return easingFn( start, end, percent ); 6722 6723 } else if( is.array( start ) && is.array( end ) ){ 6724 var easedArr = []; 6725 6726 for( var i = 0; i < end.length; i++ ){ 6727 var si = start[ i ]; 6728 var ei = end[ i ]; 6729 6730 if( si != null && ei != null ){ 6731 var val = easingFn( si, ei, percent ); 6732 6733 if( startProp.roundValue ){ val = Math.round( val ); } 6734 6735 easedArr.push( val ); 6736 } else { 6737 easedArr.push( ei ); 6738 } 6739 } 6740 6741 return easedArr; 6742 } 6743 6744 return undefined; 6745 } 6746 6747 } 6748 6749}); 6750 6751module.exports = corefn; 6752 6753},{"../define":44,"../is":83,"../util":100}],35:[function(_dereq_,module,exports){ 6754'use strict'; 6755 6756var define = _dereq_( '../define' ); 6757 6758var corefn = ({ 6759 on: define.on(), // .on( events [, selector] [, data], handler) 6760 one: define.on( { unbindSelfOnTrigger: true } ), 6761 once: define.on( { unbindAllBindersOnTrigger: true } ), 6762 off: define.off(), // .off( events [, selector] [, handler] ) 6763 trigger: define.trigger() // .trigger( events [, extraParams] ) 6764}); 6765 6766define.eventAliasesOn( corefn ); 6767 6768module.exports = corefn; 6769 6770},{"../define":44}],36:[function(_dereq_,module,exports){ 6771'use strict'; 6772 6773var corefn = ({ 6774 6775 png: function( options ){ 6776 var renderer = this._private.renderer; 6777 options = options || {}; 6778 6779 return renderer.png( options ); 6780 }, 6781 6782 jpg: function( options ){ 6783 var renderer = this._private.renderer; 6784 options = options || {}; 6785 6786 options.bg = options.bg || '#fff'; 6787 6788 return renderer.jpg( options ); 6789 } 6790 6791}); 6792 6793corefn.jpeg = corefn.jpg; 6794 6795module.exports = corefn; 6796 6797},{}],37:[function(_dereq_,module,exports){ 6798'use strict'; 6799 6800var window = _dereq_( '../window' ); 6801var util = _dereq_( '../util' ); 6802var Collection = _dereq_( '../collection' ); 6803var is = _dereq_( '../is' ); 6804var Promise = _dereq_( '../promise' ); 6805var define = _dereq_( '../define' ); 6806 6807var Core = function( opts ){ 6808 var cy = this; 6809 6810 opts = util.extend( {}, opts ); 6811
6812 var container = opts.container; 6813 6814 // allow for passing a wrapped jquery object 6815 // e.g. cytoscape({ container: $('#cy') }) 6816 if( container && !is.htmlElement( container ) && is.htmlElement( container[0] ) ){ 6817 container = container[0]; 6818 } 6819 6820 var reg = container ? container._cyreg : null; // e.g. already registered some info (e.g. readies) via jquery 6821 reg = reg || {}; 6822 6823 if( reg && reg.cy ){ 6824 reg.cy.destroy(); 6825 6826 reg = {}; // old instance => replace reg completely 6827 } 6828 6829 var readies = reg.readies = reg.readies || []; 6830 6831 if( container ){ container._cyreg = reg; } // make sure container assoc'd reg points to this cy 6832 reg.cy = cy; 6833 6834 var head = window !== undefined && container !== undefined && !opts.headless; 6835 var options = opts; 6836 options.layout = util.extend( { name: head ? 'grid' : 'null' }, options.layout ); 6837 options.renderer = util.extend( { name: head ? 'canvas' : 'null' }, options.renderer ); 6838 6839 var defVal = function( def, val, altVal ){ 6840 if( val !== undefined ){ 6841 return val; 6842 } else if( altVal !== undefined ){ 6843 return altVal; 6844 } else { 6845 return def; 6846 } 6847 }; 6848 6849 var _p = this._private = { 6850 container: container, // html dom ele container 6851 ready: false, // whether ready has been triggered 6852 initrender: false, // has initrender has been triggered 6853 options: options, // cached options 6854 elements: new Collection( this ), // elements in the graph 6855 listeners: [], // list of listeners 6856 aniEles: new Collection( this ), // elements being animated 6857 scratch: {}, // scratch object for core 6858 layout: null, 6859 renderer: null, 6860 notificationsEnabled: true, // whether notifications are sent to the renderer 6861 minZoom: 1e-50, 6862 maxZoom: 1e50, 6863 zoomingEnabled: defVal( true, options.zoomingEnabled ), 6864 userZoomingEnabled: defVal( true, options.userZoomingEnabled ), 6865 panningEnabled: defVal( true, options.panningEnabled ), 6866 userPanningEnabled: defVal( true, options.userPanningEnabled ), 6867 boxSelectionEnabled: defVal( true, options.boxSelectionEnabled ), 6868 autolock: defVal( false, options.autolock, options.autolockNodes ), 6869 autoungrabify: defVal( false, options.autoungrabify, options.autoungrabifyNodes ), 6870 autounselectify: defVal( false, options.autounselectify ), 6871 styleEnabled: options.styleEnabled === undefined ? head : options.styleEnabled, 6872 zoom: is.number( options.zoom ) ? options.zoom : 1, 6873 pan: { 6874 x: is.plainObject( options.pan ) && is.number( options.pan.x ) ? options.pan.x : 0, 6875 y: is.plainObject( options.pan ) && is.number( options.pan.y ) ? options.pan.y : 0 6876 }, 6877 animation: { // object for currently-running animations 6878 current: [], 6879 queue: [] 6880 }, 6881 hasCompoundNodes: false 6882 }; 6883 6884 // set selection type 6885 var selType = options.selectionType; 6886 if( selType === undefined || (selType !== 'additive' && selType !== 'single') ){ 6887 // then set default 6888 6889 _p.selectionType = 'single'; 6890 } else { 6891 _p.selectionType = selType; 6892 } 6893 6894 // init zoom bounds 6895 if( is.number( options.minZoom ) && is.number( options.maxZoom ) && options.minZoom < options.maxZoom ){ 6896 _p.minZoom = options.minZoom; 6897 _p.maxZoom = options.maxZoom; 6898 } else if( is.number( options.minZoom ) && options.maxZoom === undefined ){ 6899 _p.minZoom = options.minZoom; 6900 } else if( is.number( options.maxZoom ) && options.minZoom === undefined ){ 6901 _p.maxZoom = options.maxZoom; 6902 } 6903 6904 var loadExtData = function( extData, next ){ 6905 var anyIsPromise = extData.some( is.promise ); 6906 6907 if( anyIsPromise ){ 6908 return Promise.all( extData ).then( next ); // load all data asynchronously, then exec rest of init 6909 } else { 6910 next( extData ); // exec synchronously for convenience 6911 } 6912 }; 6913 6914 // create the renderer 6915 cy.initRenderer( util.extend( { 6916 hideEdgesOnViewport: options.hideEdgesOnViewport, 6917 textureOnViewport: options.textureOnViewport, 6918 wheelSensitivity: is.number( options.wheelSensitivity ) && options.wheelSensitivity > 0 ? options.wheelSensitivity : 1, 6919 motionBlur: options.motionBlur === undefined ? false : options.motionBlur, // off by default 6920 motionBlurOpacity: options.motionBlurOpacity === undefined ? 0.05 : options.motionBlurOpacity, 6921 pixelRatio: is.number( options.pixelRatio ) && options.pixelRatio > 0 ? options.pixelRatio : undefined, 6922 desktopTapThreshold: options.desktopTapThreshold === undefined ? 4 : options.desktopTapThreshold, 6923 touchTapThreshold: options.touchTapThreshold === undefined ? 8 : options.touchTapThreshold 6924 }, options.renderer ) ); 6925 6926 loadExtData([ options.style, options.elements ], function( thens ){ 6927 var initStyle = thens[0]; 6928 var initEles = thens[1]; 6929 6930 // init style 6931 if( _p.styleEnabled ){ 6932 cy.setStyle( initStyle ); 6933 } 6934 6935 // trigger the passed function for the `initrender` event 6936 if( options.initrender ){ 6937 cy.on( 'initrender', options.initrender ); 6938 cy.on( 'initrender', function(){ 6939 _p.initrender = true; 6940 } ); 6941 } 6942 6943 // initial load 6944 cy.load( initEles, function(){ // onready 6945 cy.startAnimationLoop(); 6946 _p.ready = true; 6947 6948 // if a ready callback is specified as an option, the bind it 6949 if( is.fn( options.ready ) ){ 6950 cy.on( 'ready', options.ready ); 6951 } 6952 6953 // bind all the ready handlers registered before creating this instance 6954 for( var i = 0; i < readies.length; i++ ){ 6955 var fn = readies[ i ]; 6956 cy.on( 'ready', fn ); 6957 } 6958 if( reg ){ reg.readies = []; } // clear b/c we've bound them all and don't want to keep it around in case a new core uses the same div etc 6959 6960 cy.trigger( 'ready' ); 6961 }, options.done ); 6962 6963 } ); 6964}; 6965
6966var corefn = Core.prototype; // short alias 6967 6968util.extend( corefn, { 6969 instanceString: function(){ 6970 return 'core'; 6971 }, 6972 6973 isReady: function(){ 6974 return this._private.ready; 6975 }, 6976 6977 ready: function( fn ){ 6978 if( this.isReady() ){ 6979 this.trigger( 'ready', [], fn ); // just calls fn as though triggered via ready event 6980 } else { 6981 this.on( 'ready', fn ); 6982 } 6983 6984 return this; 6985 }, 6986 6987 initrender: function(){ 6988 return this._private.initrender; 6989 }, 6990 6991 destroy: function(){ 6992 var cy = this; 6993 6994 cy.stopAnimationLoop(); 6995 6996 cy.destroyRenderer(); 6997 6998 return cy; 6999 }, 7000 7001 hasElementWithId: function( id ){ 7002 return this._private.elements.hasElementWithId( id ); 7003 }, 7004 7005 getElementById: function( id ){ 7006 return this._private.elements.getElementById( id ); 7007 }, 7008 7009 selectionType: function(){ 7010 return this._private.selectionType; 7011 }, 7012 7013 hasCompoundNodes: function(){ 7014 return this._private.hasCompoundNodes; 7015 }, 7016 7017 headless: function(){ 7018 return this._private.options.renderer.name === 'null'; 7019 }, 7020 7021 styleEnabled: function(){ 7022 return this._private.styleEnabled; 7023 }, 7024 7025 addToPool: function( eles ){ 7026 this._private.elements.merge( eles ); 7027 7028 return this; // chaining 7029 }, 7030 7031 removeFromPool: function( eles ){ 7032 this._private.elements.unmerge( eles ); 7033 7034 return this; 7035 }, 7036 7037 container: function(){ 7038 return this._private.container; 7039 }, 7040 7041 options: function(){ 7042 return util.copy( this._private.options ); 7043 }, 7044 7045 json: function( obj ){ 7046 var cy = this; 7047 var _p = cy._private; 7048 var eles = cy.mutableElements(); 7049 7050 if( is.plainObject( obj ) ){ // set 7051 7052 cy.startBatch(); 7053 7054 if( obj.elements ){ 7055 var idInJson = {}; 7056 7057 var updateEles = function( jsons, gr ){ 7058 for( var i = 0; i < jsons.length; i++ ){ 7059 var json = jsons[ i ]; 7060 var id = json.data.id; 7061 var ele = cy.getElementById( id ); 7062 7063 idInJson[ id ] = true; 7064 7065 if( ele.length !== 0 ){ // existing element should be updated 7066 ele.json( json ); 7067 } else { // otherwise should be added 7068 if( gr ){ 7069 cy.add( util.extend( { group: gr }, json ) ); 7070 } else { 7071 cy.add( json ); 7072 } 7073 } 7074 } 7075 }; 7076 7077 if( is.array( obj.elements ) ){ // elements: [] 7078 updateEles( obj.elements ); 7079 7080 } else { // elements: { nodes: [], edges: [] } 7081 var grs = [ 'nodes', 'edges' ]; 7082 for( var i = 0; i < grs.length; i++ ){ 7083 var gr = grs[ i ]; 7084 var elements = obj.elements[ gr ]; 7085 7086 if( is.array( elements ) ){ 7087 updateEles( elements, gr ); 7088 } 7089 } 7090 } 7091 7092 // elements not specified in json should be removed 7093 eles.stdFilter( function( ele ){ 7094 return !idInJson[ ele.id() ]; 7095 } ).remove(); 7096 } 7097 7098 if( obj.style ){ 7099 cy.style( obj.style ); 7100 } 7101 7102 if( obj.zoom != null && obj.zoom !== _p.zoom ){ 7103 cy.zoom( obj.zoom ); 7104 } 7105 7106 if( obj.pan ){ 7107 if( obj.pan.x !== _p.pan.x || obj.pan.y !== _p.pan.y ){ 7108 cy.pan( obj.pan ); 7109 } 7110 } 7111 7112 var fields = [ 7113 'minZoom', 'maxZoom', 'zoomingEnabled', 'userZoomingEnabled', 7114 'panningEnabled', 'userPanningEnabled', 7115 'boxSelectionEnabled', 7116 'autolock', 'autoungrabify', 'autounselectify' 7117 ]; 7118 7119 for( var i = 0; i < fields.length; i++ ){ 7120 var f = fields[ i ]; 7121 7122 if( obj[ f ] != null ){ 7123 cy[ f ]( obj[ f ] ); 7124 } 7125 } 7126 7127 cy.endBatch(); 7128 7129 return this; // chaining 7130 } else if( obj === undefined ){ // get 7131 var json = {}; 7132 7133 json.elements = {}; 7134 eles.forEach( function( ele ){ 7135 var group = ele.group(); 7136 7137 if( !json.elements[ group ] ){ 7138 json.elements[ group ] = []; 7139 } 7140 7141 json.elements[ group ].push( ele.json() ); 7142 } ); 7143 7144 if( this._private.styleEnabled ){ 7145 json.style = cy.style().json(); 7146 } 7147 7148 json.zoomingEnabled = cy._private.zoomingEnabled; 7149 json.userZoomingEnabled = cy._private.userZoomingEnabled; 7150 json.zoom = cy._private.zoom; 7151 json.minZoom = cy._private.minZoom; 7152 json.maxZoom = cy._private.maxZoom; 7153 json.panningEnabled = cy._private.panningEnabled; 7154 json.userPanningEnabled = cy._private.userPanningEnabled; 7155 json.pan = util.copy( cy._private.pan ); 7156 json.boxSelectionEnabled = cy._private.boxSelectionEnabled; 7157 json.renderer = util.copy( cy._private.options.renderer ); 7158 json.hideEdgesOnViewport = cy._private.options.hideEdgesOnViewport; 7159 json.textureOnViewport = cy._private.options.textureOnViewport; 7160 json.wheelSensitivity = cy._private.options.wheelSensitivity; 7161 json.motionBlur = cy._private.options.motionBlur; 7162 7163 return json; 7164 } 7165 }, 7166 7167 scratch: define.data( { 7168 field: 'scratch', 7169 bindingEvent: 'scratch', 7170 allowBinding: true, 7171 allowSetting: true, 7172 settingEvent: 'scratch', 7173 settingTriggersEvent: true, 7174 triggerFnName: 'trigger', 7175 allowGetting: true 7176 } ), 7177 7178 removeScratch: define.removeData( { 7179 field: 'scratch', 7180 event: 'scratch', 7181 triggerFnName: 'trigger', 7182 triggerEvent: true 7183 } ) 7184 7185} ); 7186 7187[ 7188 _dereq_( './add-remove' ), 7189 _dereq_( './animation' ), 7190 _dereq_( './events' ), 7191 _dereq_( './export' ), 7192 _dereq_( './layout' ), 7193 _dereq_( './notification' ), 7194 _dereq_( './renderer' ), 7195 _dereq_( './search' ), 7196 _dereq_( './style' ), 7197 _dereq_( './viewport' )
7198].forEach( function( props ){ 7199 util.extend( corefn, props ); 7200} ); 7201 7202module.exports = Core; 7203 7204},{"../collection":26,"../define":44,"../is":83,"../promise":86,"../util":100,"../window":107,"./add-remove":33,"./animation":34,"./events":35,"./export":36,"./layout":38,"./notification":39,"./renderer":40,"./search":41,"./style":42,"./viewport":43}],38:[function(_dereq_,module,exports){ 7205'use strict'; 7206 7207var util = _dereq_( '../util' ); 7208var is = _dereq_( '../is' ); 7209 7210var corefn = ({ 7211 7212 layout: function( params ){ 7213 var layout = this._private.prevLayout = ( params == null ? this._private.prevLayout : this.makeLayout( params ) ); 7214 7215 layout.run(); 7216 7217 return this; // chaining 7218 }, 7219 7220 makeLayout: function( options ){ 7221 var cy = this; 7222 7223 if( options == null ){ 7224 util.error( 'Layout options must be specified to make a layout' ); 7225 return; 7226 } 7227 7228 if( options.name == null ){ 7229 util.error( 'A `name` must be specified to make a layout' ); 7230 return; 7231 } 7232 7233 var name = options.name; 7234 var Layout = cy.extension( 'layout', name ); 7235 7236 if( Layout == null ){ 7237 util.error( 'Can not apply layout: No such layout `' + name + '` found; did you include its JS file?' ); 7238 return; 7239 } 7240 7241 var eles; 7242 if( is.string( options.eles ) ){ 7243 eles = cy.$( options.eles ); 7244 } else { 7245 eles = options.eles != null ? options.eles : cy.$(); 7246 } 7247 7248 var layout = new Layout( util.extend( {}, options, { 7249 cy: cy, 7250 eles: eles 7251 } ) ); 7252 7253 return layout; 7254 } 7255 7256}); 7257 7258corefn.createLayout = corefn.makeLayout; 7259 7260module.exports = corefn; 7261 7262},{"../is":83,"../util":100}],39:[function(_dereq_,module,exports){ 7263'use strict'; 7264 7265var corefn = ({ 7266 notify: function( params ){ 7267 var _p = this._private; 7268 7269 if( _p.batchingNotify ){ 7270 var bEles = _p.batchNotifyEles; 7271 var bTypes = _p.batchNotifyTypes; 7272 7273 if( params.eles ){ 7274 bEles.merge( params.eles ); 7275 } 7276 7277 if( !bTypes.ids[ params.type ] ){ 7278 bTypes.push( params.type ); 7279 bTypes.ids[ params.type ] = true; 7280 } 7281 7282 return; // notifications are disabled during batching 7283 } 7284 7285 if( !_p.notificationsEnabled ){ return; } // exit on disabled 7286 7287 var renderer = this.renderer(); 7288 7289 // exit if destroy() called on core or renderer in between frames #1499 7290 // TODO first check this.isDestroyed() in >=3.1 #1440 7291 if( !renderer ){ return; } 7292 7293 renderer.notify( params ); 7294 }, 7295 7296 notifications: function( bool ){ 7297 var p = this._private; 7298 7299 if( bool === undefined ){ 7300 return p.notificationsEnabled; 7301 } else { 7302 p.notificationsEnabled = bool ? true : false; 7303 } 7304 }, 7305 7306 noNotifications: function( callback ){ 7307 this.notifications( false ); 7308 callback(); 7309 this.notifications( true ); 7310 }, 7311 7312 startBatch: function(){ 7313 var _p = this._private; 7314 7315 if( _p.batchCount == null ){ 7316 _p.batchCount = 0; 7317 } 7318 7319 if( _p.batchCount === 0 ){ 7320 _p.batchingStyle = _p.batchingNotify = true; 7321 _p.batchStyleEles = this.collection(); 7322 _p.batchNotifyEles = this.collection(); 7323 _p.batchNotifyTypes = []; 7324 _p.batchNotifyTypes.ids = {}; 7325 } 7326 7327 _p.batchCount++; 7328 7329 return this; 7330 }, 7331 7332 endBatch: function(){ 7333 var _p = this._private; 7334 7335 _p.batchCount--; 7336 7337 if( _p.batchCount === 0 ){ 7338 // update style for dirty eles 7339 _p.batchingStyle = false; 7340 _p.batchStyleEles.updateStyle(); 7341 7342 // notify the renderer of queued eles and event types 7343 _p.batchingNotify = false; 7344 this.notify( { 7345 type: _p.batchNotifyTypes, 7346 eles: _p.batchNotifyEles 7347 } ); 7348 } 7349 7350 return this; 7351 }, 7352 7353 batch: function( callback ){ 7354 this.startBatch(); 7355 callback(); 7356 this.endBatch(); 7357 7358 return this; 7359 }, 7360 7361 // for backwards compatibility 7362 batchData: function( map ){ 7363 var cy = this; 7364 7365 return this.batch( function(){ 7366 var ids = Object.keys( map ); 7367 7368 for( var i = 0; i < ids.length; i++ ){ 7369 var id = ids[i]; 7370 var data = map[ id ]; 7371 var ele = cy.getElementById( id ); 7372 7373 ele.data( data ); 7374 } 7375 } ); 7376 } 7377}); 7378 7379module.exports = corefn; 7380 7381},{}],40:[function(_dereq_,module,exports){ 7382'use strict'; 7383 7384var util = _dereq_( '../util' ); 7385
7386var corefn = ({ 7387 7388 renderTo: function( context, zoom, pan, pxRatio ){ 7389 var r = this._private.renderer; 7390 7391 r.renderTo( context, zoom, pan, pxRatio ); 7392 return this; 7393 }, 7394 7395 renderer: function(){ 7396 return this._private.renderer; 7397 }, 7398 7399 forceRender: function(){ 7400 this.notify( { 7401 type: 'draw' 7402 } ); 7403 7404 return this; 7405 }, 7406 7407 resize: function(){ 7408 this.invalidateSize(); 7409 7410 this.notify( { 7411 type: 'resize' 7412 } ); 7413 7414 this.trigger( 'resize' ); 7415 7416 return this; 7417 }, 7418 7419 initRenderer: function( options ){ 7420 var cy = this; 7421 7422 var RendererProto = cy.extension( 'renderer', options.name ); 7423 if( RendererProto == null ){ 7424 util.error( 'Can not initialise: No such renderer `%s` found; did you include its JS file?', options.name ); 7425 return; 7426 } 7427 7428 var rOpts = util.extend( {}, options, { 7429 cy: cy 7430 } ); 7431 7432 cy._private.renderer = new RendererProto( rOpts ); 7433 }, 7434 7435 destroyRenderer: function(){ 7436 var cy = this; 7437 7438 cy.notify( { type: 'destroy' } ); // destroy the renderer 7439 7440 var domEle = cy.container(); 7441 if( domEle ){ 7442 domEle._cyreg = null; 7443 7444 while( domEle.childNodes.length > 0 ){ 7445 domEle.removeChild( domEle.childNodes[0] ); 7446 } 7447 } 7448 7449 cy._private.renderer = null; // to be extra safe, remove the ref 7450 }, 7451 7452 onRender: function( fn ){ 7453 return this.on('render', fn); 7454 }, 7455 7456 offRender: function( fn ){ 7457 return this.off('render', fn); 7458 } 7459 7460}); 7461 7462corefn.invalidateDimensions = corefn.resize; 7463 7464module.exports = corefn; 7465 7466},{"../util":100}],41:[function(_dereq_,module,exports){ 7467'use strict'; 7468 7469var is = _dereq_( '../is' ); 7470var Collection = _dereq_( '../collection' ); 7471 7472var corefn = ({ 7473 7474 // get a collection 7475 // - empty collection on no args 7476 // - collection of elements in the graph on selector arg 7477 // - guarantee a returned collection when elements or collection specified 7478 collection: function( eles, opts ){ 7479 7480 if( is.string( eles ) ){ 7481 return this.$( eles ); 7482 7483 } else if( is.elementOrCollection( eles ) ){ 7484 return eles.collection(); 7485 7486 } else if( is.array( eles ) ){ 7487 return new Collection( this, eles, opts ); 7488 } 7489 7490 return new Collection( this ); 7491 }, 7492 7493 nodes: function( selector ){ 7494 var nodes = this.$( function(){ 7495 return this.isNode(); 7496 } ); 7497 7498 if( selector ){ 7499 return nodes.filter( selector ); 7500 } 7501 7502 return nodes; 7503 }, 7504 7505 edges: function( selector ){ 7506 var edges = this.$( function(){ 7507 return this.isEdge(); 7508 } ); 7509 7510 if( selector ){ 7511 return edges.filter( selector ); 7512 } 7513 7514 return edges; 7515 }, 7516 7517 // search the graph like jQuery 7518 $: function( selector ){ 7519 var eles = this._private.elements; 7520 7521 if( selector ){ 7522 return eles.filter( selector ); 7523 } else { 7524 return eles.spawnSelf(); 7525 } 7526 }, 7527 7528 mutableElements: function(){ 7529 return this._private.elements; 7530 } 7531 7532}); 7533 7534// aliases 7535corefn.elements = corefn.filter = corefn.$; 7536 7537module.exports = corefn; 7538 7539},{"../collection":26,"../is":83}],42:[function(_dereq_,module,exports){ 7540'use strict'; 7541 7542var is = _dereq_( '../is' ); 7543var Style = _dereq_( '../style' ); 7544 7545var corefn = ({ 7546 7547 style: function( newStyle ){ 7548 if( newStyle ){ 7549 var s = this.setStyle( newStyle ); 7550 7551 s.update(); 7552 } 7553 7554 return this._private.style; 7555 }, 7556 7557 setStyle: function( style ){ 7558 var _p = this._private; 7559 7560 if( is.stylesheet( style ) ){ 7561 _p.style = style.generateStyle( this ); 7562 7563 } else if( is.array( style ) ){ 7564 _p.style = Style.fromJson( this, style ); 7565 7566 } else if( is.string( style ) ){ 7567 _p.style = Style.fromString( this, style ); 7568 7569 } else { 7570 _p.style = Style( this ); 7571 } 7572 7573 return _p.style; 7574 } 7575}); 7576 7577module.exports = corefn; 7578 7579},{"../is":83,"../style":92}],43:[function(_dereq_,module,exports){ 7580'use strict'; 7581 7582var is = _dereq_( '../is' ); 7583 7584var corefn = ({ 7585 7586 autolock: function( bool ){ 7587 if( bool !== undefined ){ 7588 this._private.autolock = bool ? true : false; 7589 } else { 7590 return this._private.autolock; 7591 } 7592 7593 return this; // chaining 7594 }, 7595 7596 autoungrabify: function( bool ){ 7597 if( bool !== undefined ){ 7598 this._private.autoungrabify = bool ? true : false;
7599 } else { 7600 return this._private.autoungrabify; 7601 } 7602 7603 return this; // chaining 7604 }, 7605 7606 autounselectify: function( bool ){ 7607 if( bool !== undefined ){ 7608 this._private.autounselectify = bool ? true : false; 7609 } else { 7610 return this._private.autounselectify; 7611 } 7612 7613 return this; // chaining 7614 }, 7615 7616 panningEnabled: function( bool ){ 7617 if( bool !== undefined ){ 7618 this._private.panningEnabled = bool ? true : false; 7619 } else { 7620 return this._private.panningEnabled; 7621 } 7622 7623 return this; // chaining 7624 }, 7625 7626 userPanningEnabled: function( bool ){ 7627 if( bool !== undefined ){ 7628 this._private.userPanningEnabled = bool ? true : false; 7629 } else { 7630 return this._private.userPanningEnabled; 7631 } 7632 7633 return this; // chaining 7634 }, 7635 7636 zoomingEnabled: function( bool ){ 7637 if( bool !== undefined ){ 7638 this._private.zoomingEnabled = bool ? true : false; 7639 } else { 7640 return this._private.zoomingEnabled; 7641 } 7642 7643 return this; // chaining 7644 }, 7645 7646 userZoomingEnabled: function( bool ){ 7647 if( bool !== undefined ){ 7648 this._private.userZoomingEnabled = bool ? true : false; 7649 } else { 7650 return this._private.userZoomingEnabled; 7651 } 7652 7653 return this; // chaining 7654 }, 7655 7656 boxSelectionEnabled: function( bool ){ 7657 if( bool !== undefined ){ 7658 this._private.boxSelectionEnabled = bool ? true : false; 7659 } else { 7660 return this._private.boxSelectionEnabled; 7661 } 7662 7663 return this; // chaining 7664 }, 7665 7666 pan: function(){ 7667 var args = arguments; 7668 var pan = this._private.pan; 7669 var dim, val, dims, x, y; 7670 7671 switch( args.length ){ 7672 case 0: // .pan() 7673 return pan; 7674 7675 case 1: 7676 7677 if( is.string( args[0] ) ){ // .pan('x') 7678 dim = args[0]; 7679 return pan[ dim ]; 7680 7681 } else if( is.plainObject( args[0] ) ){ // .pan({ x: 0, y: 100 }) 7682 if( !this._private.panningEnabled ){ 7683 return this; 7684 } 7685 7686 dims = args[0]; 7687 x = dims.x; 7688 y = dims.y; 7689 7690 if( is.number( x ) ){ 7691 pan.x = x; 7692 } 7693 7694 if( is.number( y ) ){ 7695 pan.y = y; 7696 } 7697 7698 this.trigger( 'pan viewport' ); 7699 } 7700 break; 7701 7702 case 2: // .pan('x', 100) 7703 if( !this._private.panningEnabled ){ 7704 return this; 7705 } 7706 7707 dim = args[0]; 7708 val = args[1]; 7709 7710 if( (dim === 'x' || dim === 'y') && is.number( val ) ){ 7711 pan[ dim ] = val; 7712 } 7713 7714 this.trigger( 'pan viewport' ); 7715 break; 7716 7717 default: 7718 break; // invalid 7719 } 7720 7721 this.notify( { // notify the renderer that the viewport changed 7722 type: 'viewport' 7723 } ); 7724 7725 return this; // chaining 7726 }, 7727 7728 panBy: function( params ){ 7729 var args = arguments; 7730 var pan = this._private.pan; 7731 var dim, val, dims, x, y; 7732 7733 if( !this._private.panningEnabled ){ 7734 return this; 7735 } 7736 7737 switch( args.length ){ 7738 case 1: 7739 7740 if( is.plainObject( args[0] ) ){ // .panBy({ x: 0, y: 100 }) 7741 dims = args[0]; 7742 x = dims.x; 7743 y = dims.y; 7744 7745 if( is.number( x ) ){ 7746 pan.x += x; 7747 } 7748 7749 if( is.number( y ) ){ 7750 pan.y += y; 7751 } 7752 7753 this.trigger( 'pan viewport' ); 7754 } 7755 break; 7756 7757 case 2: // .panBy('x', 100) 7758 dim = args[0]; 7759 val = args[1]; 7760 7761 if( (dim === 'x' || dim === 'y') && is.number( val ) ){ 7762 pan[ dim ] += val; 7763 } 7764 7765 this.trigger( 'pan viewport' ); 7766 break; 7767 7768 default: 7769 break; // invalid 7770 } 7771 7772 this.notify( { // notify the renderer that the viewport changed 7773 type: 'viewport' 7774 } ); 7775 7776 return this; // chaining 7777 }, 7778 7779 fit: function( elements, padding ){ 7780 var viewportState = this.getFitViewport( elements, padding ); 7781 7782 if( viewportState ){ 7783 var _p = this._private; 7784 _p.zoom = viewportState.zoom; 7785 _p.pan = viewportState.pan; 7786 7787 this.trigger( 'pan zoom viewport' ); 7788 7789 this.notify( { // notify the renderer that the viewport changed 7790 type: 'viewport' 7791 } ); 7792 } 7793 7794 return this; // chaining 7795 }, 7796 7797 getFitViewport: function( elements, padding ){ 7798 if( is.number( elements ) && padding === undefined ){ // elements is optional 7799 padding = elements; 7800 elements = undefined; 7801 } 7802 7803 if( !this._private.panningEnabled || !this._private.zoomingEnabled ){ 7804 return; 7805 } 7806 7807 var bb; 7808 7809 if( is.string( elements ) ){ 7810 var sel = elements; 7811 elements = this.$( sel ); 7812 7813 } else if( is.boundingBox( elements ) ){ // assume bb 7814 var bbe = elements; 7815 bb = { 7816 x1: bbe.x1, 7817 y1: bbe.y1, 7818 x2: bbe.x2, 7819 y2: bbe.y2 7820 }; 7821 7822 bb.w = bb.x2 - bb.x1; 7823 bb.h = bb.y2 - bb.y1; 7824 7825 } else if( !is.elementOrCollection( elements ) ){ 7826 elements = this.mutableElements(); 7827 } 7828 7829 bb = bb || elements.boundingBox(); 7830 7831 var w = this.width(); 7832 var h = this.height(); 7833 var zoom; 7834 padding = is.number( padding ) ? padding : 0; 7835 7836 if( !isNaN( w ) && !isNaN( h ) && w > 0 && h > 0 && !isNaN( bb.w ) && !isNaN( bb.h ) && bb.w > 0 && bb.h > 0 ){ 7837 zoom = Math.min( (w - 2 * padding) / bb.w, (h - 2 * padding) / bb.h ); 7838 7839 // crop zoom 7840 zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom; 7841 zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom; 7842 7843 var pan = { // now pan to middle 7844 x: (w - zoom * ( bb.x1 + bb.x2 )) / 2,
7845 y: (h - zoom * ( bb.y1 + bb.y2 )) / 2 7846 }; 7847 7848 return { 7849 zoom: zoom, 7850 pan: pan 7851 }; 7852 } 7853 7854 return; 7855 }, 7856 7857 minZoom: function( zoom ){ 7858 if( zoom === undefined ){ 7859 return this._private.minZoom; 7860 } else if( is.number( zoom ) ){ 7861 this._private.minZoom = zoom; 7862 } 7863 7864 return this; 7865 }, 7866 7867 maxZoom: function( zoom ){ 7868 if( zoom === undefined ){ 7869 return this._private.maxZoom; 7870 } else if( is.number( zoom ) ){ 7871 this._private.maxZoom = zoom; 7872 } 7873 7874 return this; 7875 }, 7876 7877 zoom: function( params ){ 7878 var pos; // in rendered px 7879 var zoom; 7880 7881 if( params === undefined ){ // then get the zoom 7882 return this._private.zoom; 7883 7884 } else if( is.number( params ) ){ // then set the zoom 7885 zoom = params; 7886 7887 } else if( is.plainObject( params ) ){ // then zoom about a point 7888 zoom = params.level; 7889 7890 if( params.position ){ 7891 var p = params.position; 7892 var pan = this._private.pan; 7893 var z = this._private.zoom; 7894 7895 pos = { // convert to rendered px 7896 x: p.x * z + pan.x, 7897 y: p.y * z + pan.y 7898 }; 7899 } else if( params.renderedPosition ){ 7900 pos = params.renderedPosition; 7901 } 7902 7903 if( pos && !this._private.panningEnabled ){ 7904 return this; // panning disabled 7905 } 7906 } 7907 7908 if( !this._private.zoomingEnabled ){ 7909 return this; // zooming disabled 7910 } 7911 7912 if( !is.number( zoom ) || ( pos && (!is.number( pos.x ) || !is.number( pos.y )) ) ){ 7913 return this; // can't zoom with invalid params 7914 } 7915 7916 // crop zoom 7917 zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom; 7918 zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom; 7919 7920 if( pos ){ // set zoom about position 7921 var pan1 = this._private.pan; 7922 var zoom1 = this._private.zoom; 7923 var zoom2 = zoom; 7924 7925 var pan2 = { 7926 x: -zoom2 / zoom1 * (pos.x - pan1.x) + pos.x, 7927 y: -zoom2 / zoom1 * (pos.y - pan1.y) + pos.y 7928 }; 7929 7930 this._private.zoom = zoom; 7931 this._private.pan = pan2; 7932 7933 var posChanged = pan1.x !== pan2.x || pan1.y !== pan2.y; 7934 this.trigger( ' zoom ' + (posChanged ? ' pan ' : '') + ' viewport ' ); 7935 7936 } else { // just set the zoom 7937 this._private.zoom = zoom; 7938 this.trigger( 'zoom viewport' ); 7939 } 7940 7941 this.notify( { // notify the renderer that the viewport changed 7942 type: 'viewport' 7943 } ); 7944 7945 return this; // chaining 7946 }, 7947 7948 viewport: function( opts ){ 7949 var _p = this._private; 7950 var zoomDefd = true; 7951 var panDefd = true; 7952 var events = []; // to trigger 7953 var zoomFailed = false; 7954 var panFailed = false; 7955 7956 if( !opts ){ return this; } 7957 if( !is.number( opts.zoom ) ){ zoomDefd = false; } 7958 if( !is.plainObject( opts.pan ) ){ panDefd = false; } 7959 if( !zoomDefd && !panDefd ){ return this; } 7960 7961 if( zoomDefd ){ 7962 var z = opts.zoom; 7963 7964 if( z < _p.minZoom || z > _p.maxZoom || !_p.zoomingEnabled ){ 7965 zoomFailed = true; 7966 7967 } else { 7968 _p.zoom = z; 7969 7970 events.push( 'zoom' ); 7971 } 7972 } 7973 7974 if( panDefd && (!zoomFailed || !opts.cancelOnFailedZoom) && _p.panningEnabled ){ 7975 var p = opts.pan; 7976 7977 if( is.number( p.x ) ){ 7978 _p.pan.x = p.x; 7979 panFailed = false; 7980 } 7981 7982 if( is.number( p.y ) ){ 7983 _p.pan.y = p.y; 7984 panFailed = false; 7985 } 7986 7987 if( !panFailed ){ 7988 events.push( 'pan' ); 7989 } 7990 } 7991 7992 if( events.length > 0 ){ 7993 events.push( 'viewport' ); 7994 this.trigger( events.join( ' ' ) ); 7995 7996 this.notify( { 7997 type: 'viewport' 7998 } ); 7999 } 8000 8001 return this; // chaining 8002 }, 8003 8004 center: function( elements ){ 8005 var pan = this.getCenterPan( elements ); 8006 8007 if( pan ){ 8008 this._private.pan = pan; 8009 8010 this.trigger( 'pan viewport' ); 8011 8012 this.notify( { // notify the renderer that the viewport changed 8013 type: 'viewport' 8014 } ); 8015 } 8016 8017 return this; // chaining 8018 }, 8019 8020 getCenterPan: function( elements, zoom ){ 8021 if( !this._private.panningEnabled ){ 8022 return; 8023 } 8024 8025 if( is.string( elements ) ){ 8026 var selector = elements; 8027 elements = this.mutableElements().filter( selector ); 8028 } else if( !is.elementOrCollection( elements ) ){ 8029 elements = this.mutableElements(); 8030 } 8031 8032 var bb = elements.boundingBox(); 8033 var w = this.width(); 8034 var h = this.height(); 8035 zoom = zoom === undefined ? this._private.zoom : zoom; 8036 8037 var pan = { // middle 8038 x: (w - zoom * ( bb.x1 + bb.x2 )) / 2,
8039 y: (h - zoom * ( bb.y1 + bb.y2 )) / 2 8040 }; 8041 8042 return pan; 8043 }, 8044 8045 reset: function(){ 8046 if( !this._private.panningEnabled || !this._private.zoomingEnabled ){ 8047 return this; 8048 } 8049 8050 this.viewport( { 8051 pan: { x: 0, y: 0 }, 8052 zoom: 1 8053 } ); 8054 8055 return this; // chaining 8056 }, 8057 8058 invalidateSize: function(){ 8059 this._private.sizeCache = null; 8060 }, 8061 8062 size: function(){ 8063 var _p = this._private; 8064 var container = _p.container; 8065 8066 return ( _p.sizeCache = _p.sizeCache || ( container ? { 8067 width: container.clientWidth, 8068 height: container.clientHeight 8069 } : { // fallback if no container (not 0 b/c can be used for dividing etc) 8070 width: 1, 8071 height: 1 8072 } ) ); 8073 }, 8074 8075 width: function(){ 8076 return this.size().width; 8077 }, 8078 8079 height: function(){ 8080 return this.size().height; 8081 }, 8082 8083 extent: function(){ 8084 var pan = this._private.pan; 8085 var zoom = this._private.zoom; 8086 var rb = this.renderedExtent(); 8087 8088 var b = { 8089 x1: ( rb.x1 - pan.x ) / zoom, 8090 x2: ( rb.x2 - pan.x ) / zoom, 8091 y1: ( rb.y1 - pan.y ) / zoom, 8092 y2: ( rb.y2 - pan.y ) / zoom 8093 }; 8094 8095 b.w = b.x2 - b.x1; 8096 b.h = b.y2 - b.y1; 8097 8098 return b; 8099 }, 8100 8101 renderedExtent: function(){ 8102 var width = this.width(); 8103 var height = this.height(); 8104 8105 return { 8106 x1: 0, 8107 y1: 0, 8108 x2: width, 8109 y2: height, 8110 w: width, 8111 h: height 8112 }; 8113 } 8114}); 8115 8116// aliases 8117corefn.centre = corefn.center; 8118 8119// backwards compatibility 8120corefn.autolockNodes = corefn.autolock; 8121corefn.autoungrabifyNodes = corefn.autoungrabify; 8122 8123module.exports = corefn; 8124 8125},{"../is":83}],44:[function(_dereq_,module,exports){ 8126'use strict'; 8127 8128// use this module to cherry pick functions into your prototype 8129// (useful for functions shared between the core and collections, for example) 8130 8131// e.g. 8132// var foo = define.foo({ /* params... */ }) 8133 8134var util = _dereq_( './util' ); 8135var is = _dereq_( './is' ); 8136var Selector = _dereq_( './selector' ); 8137var Promise = _dereq_( './promise' ); 8138var Event = _dereq_( './event' ); 8139var Animation = _dereq_( './animation' ); 8140 8141var define = { 8142 8143 // access data field 8144 data: function( params ){ 8145 var defaults = { 8146 field: 'data', 8147 bindingEvent: 'data', 8148 allowBinding: false, 8149 allowSetting: false, 8150 allowGetting: false, 8151 settingEvent: 'data', 8152 settingTriggersEvent: false, 8153 triggerFnName: 'trigger', 8154 immutableKeys: {}, // key => true if immutable 8155 updateStyle: false, 8156 onSet: function( self ){}, 8157 canSet: function( self ){ return true; } 8158 }; 8159 params = util.extend( {}, defaults, params ); 8160 8161 return function dataImpl( name, value ){ 8162 var p = params; 8163 var self = this; 8164 var selfIsArrayLike = self.length !== undefined; 8165 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8166 var single = selfIsArrayLike ? self[0] : self; 8167 8168 // .data('foo', ...) 8169 if( is.string( name ) ){ // set or get property 8170 8171 // .data('foo') 8172 if( p.allowGetting && value === undefined ){ // get 8173 8174 var ret; 8175 if( single ){ 8176 ret = single._private[ p.field ][ name ]; 8177 } 8178 return ret; 8179 8180 // .data('foo', 'bar') 8181 } else if( p.allowSetting && value !== undefined ){ // set 8182 var valid = !p.immutableKeys[ name ]; 8183 if( valid ){ 8184 for( var i = 0, l = all.length; i < l; i++ ){ 8185 if( p.canSet( all[ i ] ) ){ 8186 all[ i ]._private[ p.field ][ name ] = value; 8187 } 8188 } 8189 8190 // update mappers if asked 8191 if( p.updateStyle ){ self.updateStyle(); } 8192 8193 // call onSet callback 8194 p.onSet( self ); 8195 8196 if( p.settingTriggersEvent ){ 8197 self[ p.triggerFnName ]( p.settingEvent ); 8198 } 8199 } 8200 } 8201 8202 // .data({ 'foo': 'bar' }) 8203 } else if( p.allowSetting && is.plainObject( name ) ){ // extend 8204 var obj = name; 8205 var k, v; 8206 var keys = Object.keys( obj ); 8207 8208 for( var i = 0; i < keys.length; i++ ){ 8209 k = keys[ i ]; 8210 v = obj[ k ]; 8211
8212 var valid = !p.immutableKeys[ k ]; 8213 if( valid ){ 8214 for( var j = 0; j < all.length; j++ ){ 8215 var ele = all[j]; 8216 8217 if( p.canSet( ele ) ){ 8218 ele._private[ p.field ][ k ] = v; 8219 } 8220 } 8221 } 8222 } 8223 8224 // update mappers if asked 8225 if( p.updateStyle ){ self.updateStyle(); } 8226 8227 // call onSet callback 8228 p.onSet( self ); 8229 8230 if( p.settingTriggersEvent ){ 8231 self[ p.triggerFnName ]( p.settingEvent ); 8232 } 8233 8234 // .data(function(){ ... }) 8235 } else if( p.allowBinding && is.fn( name ) ){ // bind to event 8236 var fn = name; 8237 self.on( p.bindingEvent, fn ); 8238 8239 // .data() 8240 } else if( p.allowGetting && name === undefined ){ // get whole object 8241 var ret; 8242 if( single ){ 8243 ret = single._private[ p.field ]; 8244 } 8245 return ret; 8246 } 8247 8248 return self; // maintain chainability 8249 }; // function 8250 }, // data 8251 8252 // remove data field 8253 removeData: function( params ){ 8254 var defaults = { 8255 field: 'data', 8256 event: 'data', 8257 triggerFnName: 'trigger', 8258 triggerEvent: false, 8259 immutableKeys: {} // key => true if immutable 8260 }; 8261 params = util.extend( {}, defaults, params ); 8262 8263 return function removeDataImpl( names ){ 8264 var p = params; 8265 var self = this; 8266 var selfIsArrayLike = self.length !== undefined; 8267 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8268 8269 // .removeData('foo bar') 8270 if( is.string( names ) ){ // then get the list of keys, and delete them 8271 var keys = names.split( /\s+/ ); 8272 var l = keys.length; 8273 8274 for( var i = 0; i < l; i++ ){ // delete each non-empty key 8275 var key = keys[ i ]; 8276 if( is.emptyString( key ) ){ continue; } 8277 8278 var valid = !p.immutableKeys[ key ]; // not valid if immutable 8279 if( valid ){ 8280 for( var i_a = 0, l_a = all.length; i_a < l_a; i_a++ ){ 8281 all[ i_a ]._private[ p.field ][ key ] = undefined; 8282 } 8283 } 8284 } 8285 8286 if( p.triggerEvent ){ 8287 self[ p.triggerFnName ]( p.event ); 8288 } 8289 8290 // .removeData() 8291 } else if( names === undefined ){ // then delete all keys 8292 8293 for( var i_a = 0, l_a = all.length; i_a < l_a; i_a++ ){ 8294 var _privateFields = all[ i_a ]._private[ p.field ]; 8295 var keys = Object.keys( _privateFields ); 8296 8297 for( var i = 0; i < keys.length; i++ ){ 8298 var key = keys[i]; 8299 var validKeyToDelete = !p.immutableKeys[ key ]; 8300 8301 if( validKeyToDelete ){ 8302 _privateFields[ key ] = undefined; 8303 } 8304 } 8305 } 8306 8307 if( p.triggerEvent ){ 8308 self[ p.triggerFnName ]( p.event ); 8309 } 8310 } 8311 8312 return self; // maintain chaining 8313 }; // function 8314 }, // removeData 8315 8316 // event function reusable stuff 8317 event: { 8318 regex: /(\w+)(\.(?:\w+|\*))?/, // regex for matching event strings (e.g. "click.namespace") 8319 universalNamespace: '.*', // matches as if no namespace specified and prevents users from unbinding accidentally 8320 optionalTypeRegex: /(\w+)?(\.(?:\w+|\*))?/, 8321 falseCallback: function(){ return false; } 8322 }, 8323 8324 // event binding 8325 on: function( params ){ 8326 var defaults = { 8327 unbindSelfOnTrigger: false, 8328 unbindAllBindersOnTrigger: false 8329 }; 8330 params = util.extend( {}, defaults, params ); 8331 8332 return function onImpl( events, selector, data, callback ){ 8333 var self = this; 8334 var selfIsArrayLike = self.length !== undefined; 8335 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8336 var eventsIsString = is.string( events ); 8337 var p = params; 8338 8339 if( is.plainObject( selector ) ){ // selector is actually data 8340 callback = data; 8341 data = selector; 8342 selector = undefined; 8343 } else if( is.fn( selector ) || selector === false ){ // selector is actually callback 8344 callback = selector; 8345 data = undefined; 8346 selector = undefined; 8347 } 8348 8349 if( is.fn( data ) || data === false ){ // data is actually callback 8350 callback = data; 8351 data = undefined; 8352 } 8353 8354 // if there isn't a callback, we can't really do anything 8355 // (can't speak for mapped events arg version) 8356 if( !(is.fn( callback ) || callback === false) && eventsIsString ){ 8357 return self; // maintain chaining 8358 } 8359 8360 if( eventsIsString ){ // then convert to map 8361 var map = {}; 8362 map[ events ] = callback; 8363 events = map; 8364 } 8365 8366 var keys = Object.keys( events ); 8367 8368 for( var k = 0; k < keys.length; k++ ){ 8369 var evts = keys[k]; 8370 8371 callback = events[ evts ]; 8372 if( callback === false ){
8373 callback = define.event.falseCallback; 8374 } 8375 8376 if( !is.fn( callback ) ){ continue; } 8377 8378 evts = evts.split( /\s+/ ); 8379 for( var i = 0; i < evts.length; i++ ){ 8380 var evt = evts[ i ]; 8381 if( is.emptyString( evt ) ){ continue; } 8382 8383 var match = evt.match( define.event.regex ); // type[.namespace] 8384 8385 if( match ){ 8386 var type = match[1]; 8387 var namespace = match[2] ? match[2] : undefined; 8388 8389 var listener = { 8390 callback: callback, // callback to run 8391 data: data, // extra data in eventObj.data 8392 delegated: selector ? true : false, // whether the evt is delegated 8393 selector: selector, // the selector to match for delegated events 8394 selObj: new Selector( selector ), // cached selector object to save rebuilding 8395 type: type, // the event type (e.g. 'click') 8396 namespace: namespace, // the event namespace (e.g. ".foo") 8397 unbindSelfOnTrigger: p.unbindSelfOnTrigger, 8398 unbindAllBindersOnTrigger: p.unbindAllBindersOnTrigger, 8399 binders: all // who bound together 8400 }; 8401 8402 for( var j = 0; j < all.length; j++ ){ 8403 var _p = all[ j ]._private = all[ j ]._private || {}; 8404 8405 _p.listeners = _p.listeners || []; 8406 _p.listeners.push( listener ); 8407 } 8408 } 8409 } // for events array 8410 } // for events map 8411 8412 return self; // maintain chaining 8413 }; // function 8414 }, // on 8415 8416 eventAliasesOn: function( proto ){ 8417 var p = proto; 8418 8419 p.addListener = p.listen = p.bind = p.on; 8420 p.removeListener = p.unlisten = p.unbind = p.off; 8421 p.emit = p.trigger; 8422 8423 // this is just a wrapper alias of .on() 8424 p.pon = p.promiseOn = function( events, selector ){ 8425 var self = this; 8426 var args = Array.prototype.slice.call( arguments, 0 ); 8427 8428 return new Promise( function( resolve, reject ){ 8429 var callback = function( e ){ 8430 self.off.apply( self, offArgs ); 8431 8432 resolve( e ); 8433 }; 8434 8435 var onArgs = args.concat( [ callback ] ); 8436 var offArgs = onArgs.concat( [] ); 8437 8438 self.on.apply( self, onArgs ); 8439 } ); 8440 }; 8441 }, 8442 8443 off: function offImpl( params ){ 8444 var defaults = { 8445 }; 8446 params = util.extend( {}, defaults, params ); 8447 8448 return function( events, selector, callback ){ 8449 var self = this; 8450 var selfIsArrayLike = self.length !== undefined; 8451 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8452 var eventsIsString = is.string( events ); 8453 8454 if( arguments.length === 0 ){ // then unbind all 8455 8456 for( var i = 0; i < all.length; i++ ){ 8457 all[ i ]._private = all[ i ]._private || {}; 8458 8459 _p.listeners = []; 8460 } 8461 8462 return self; // maintain chaining 8463 } 8464 8465 if( is.fn( selector ) || selector === false ){ // selector is actually callback 8466 callback = selector; 8467 selector = undefined; 8468 } 8469 8470 if( eventsIsString ){ // then convert to map 8471 var map = {}; 8472 map[ events ] = callback; 8473 events = map; 8474 } 8475 8476 var keys = Object.keys( events ); 8477 8478 for( var k = 0; k < keys.length; k++ ){ 8479 var evts = keys[k]; 8480 8481 callback = events[ evts ]; 8482 8483 if( callback === false ){ 8484 callback = define.event.falseCallback; 8485 } 8486 8487 evts = evts.split( /\s+/ ); 8488 for( var h = 0; h < evts.length; h++ ){ 8489 var evt = evts[ h ]; 8490 if( is.emptyString( evt ) ){ continue; } 8491 8492 var match = evt.match( define.event.optionalTypeRegex ); // [type][.namespace] 8493 if( match ){ 8494 var type = match[1] ? match[1] : undefined; 8495 var namespace = match[2] ? match[2] : undefined; 8496 8497 for( var i = 0; i < all.length; i++ ){ // 8498 var _p = all[ i ]._private = all[ i ]._private || {}; 8499 var listeners = _p.listeners = _p.listeners || []; 8500 8501 for( var j = 0; j < listeners.length; j++ ){ 8502 var listener = listeners[ j ]; 8503 var nsMatches = !namespace || namespace === listener.n
8503amespace; 8504 var typeMatches = !type || listener.type === type; 8505 var cbMatches = !callback || callback === listener.callback; 8506 var listenerMatches = nsMatches && typeMatches && cbMatches; 8507 8508 // delete listener if it matches 8509 if( listenerMatches ){ 8510 listeners.splice( j, 1 ); 8511 j--; 8512 } 8513 } // for listeners 8514 } // for all 8515 } // if match 8516 } // for events array 8517 8518 } // for events map 8519 8520 return self; // maintain chaining 8521 }; // function 8522 }, // off 8523 8524 trigger: function( params ){ 8525 var defaults = {}; 8526 params = util.extend( {}, defaults, params ); 8527 8528 return function triggerImpl( events, extraParams, fnToTrigger ){ 8529 var self = this; 8530 var selfIsArrayLike = self.length !== undefined; 8531 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8532 var eventsIsString = is.string( events ); 8533 var eventsIsObject = is.plainObject( events ); 8534 var eventsIsEvent = is.event( events ); 8535 var _p = this._private = this._private || {}; 8536 var cy = _p.cy || ( is.core( this ) ? this : null ); 8537 var hasCompounds = cy ? cy.hasCompoundNodes() : false; 8538 8539 if( eventsIsString ){ // then make a plain event object for each event name 8540 var evts = events.split( /\s+/ ); 8541 events = []; 8542 8543 for( var i = 0; i < evts.length; i++ ){ 8544 var evt = evts[ i ]; 8545 if( is.emptyString( evt ) ){ continue; } 8546 8547 var match = evt.match( define.event.regex ); // type[.namespace] 8548 var type = match[1]; 8549 var namespace = match[2] ? match[2] : undefined; 8550 8551 events.push( { 8552 type: type, 8553 namespace: namespace 8554 } ); 8555 } 8556 } else if( eventsIsObject ){ // put in length 1 array 8557 var eventArgObj = events; 8558 8559 events = [ eventArgObj ]; 8560 } 8561 8562 if( extraParams ){ 8563 if( !is.array( extraParams ) ){ // make sure extra params are in an array if specified 8564 extraParams = [ extraParams ]; 8565 } 8566 } else { // otherwise, we've got nothing 8567 extraParams = []; 8568 } 8569 8570 for( var i = 0; i < events.length; i++ ){ // trigger each event in order 8571 var evtObj = events[ i ]; 8572 8573 for( var j = 0; j < all.length; j++ ){ // for each 8574 var triggerer = all[ j ]; 8575 var _p = triggerer._private = triggerer._private || {}; 8576 var listeners = _p.listeners = _p.listeners || []; 8577 var triggererIsElement = is.element( triggerer ); 8578 var bubbleUp = triggererIsElement || params.layout; 8579 8580 // create the event for this element from the event object 8581 var evt; 8582 8583 if( eventsIsEvent ){ // then just get the object 8584 evt = evtObj; 8585 8586 evt.cyTarget = evt.cyTarget || triggerer; 8587 evt.cy = evt.cy || cy; 8588 8589 } else { // then we have to make one 8590 evt = new Event( evtObj, { 8591 cyTarget: triggerer, 8592 cy: cy, 8593 namespace: evtObj.namespace 8594 } ); 8595 } 8596 8597 // if a layout was specified, then put it in the typed event 8598 if( evtObj.layout ){ 8599 evt.layout = evtObj.layout; 8600 } 8601 8602 // if triggered by layout, put in event 8603 if( params.layout ){ 8604 evt.layout = triggerer; 8605 } 8606 8607 // create a rendered position based on the passed position 8608 if( evt.cyPosition ){ 8609 var pos = evt.cyPosition; 8610 var zoom = cy.zoom(); 8611 var pan = cy.pan(); 8612 8613 evt.cyRenderedPosition = { 8614 x: pos.x * zoom + pan.x, 8615 y: pos.y * zoom + pan.y 8616 }; 8617 } 8618 8619 if( fnToTrigger ){ // then override the listeners list with just the one we specified 8620 listeners = [ { 8621 namespace: evt.namespace, 8622 type: evt.type, 8623 callback: fnToTrigger 8624 } ]; 8625 } 8626 8627 for( var k = 0; k < listeners.length; k++ ){ // check each listener 8628 var lis = listeners[ k ]; 8629 var nsMatches = !lis.namespace || lis.namespace === evt.namespace || lis.namespace === define.event.universalNamespace; 8630 var typeMatches = lis.type === evt.type;
8631 var targetMatches = lis.delegated ? ( triggerer !== evt.cyTarget && is.element( evt.cyTarget ) && lis.selObj.matches( evt.cyTarget ) ) : (true); // we're not going to validate the hierarchy; that's too expensive 8632 var listenerMatches = nsMatches && typeMatches && targetMatches; 8633 8634 if( listenerMatches ){ // then trigger it 8635 var args = [ evt ]; 8636 args = args.concat( extraParams ); // add extra params to args list 8637 8638 if( lis.data ){ // add on data plugged into binding 8639 evt.data = lis.data; 8640 } else { // or clear it in case the event obj is reused 8641 evt.data = undefined; 8642 } 8643 8644 if( lis.unbindSelfOnTrigger || lis.unbindAllBindersOnTrigger ){ // then remove listener 8645 listeners.splice( k, 1 ); 8646 k--; 8647 } 8648 8649 if( lis.unbindAllBindersOnTrigger ){ // then delete the listener for all binders 8650 var binders = lis.binders; 8651 for( var l = 0; l < binders.length; l++ ){ 8652 var binder = binders[ l ]; 8653 if( !binder || binder === triggerer ){ continue; } // already handled triggerer or we can't handle it 8654 8655 var binderListeners = binder._private.listeners; 8656 for( var m = 0; m < binderListeners.length; m++ ){ 8657 var binderListener = binderListeners[ m ]; 8658 8659 if( binderListener === lis ){ // delete listener from list 8660 binderListeners.splice( m, 1 ); 8661 m--; 8662 } 8663 } 8664 } 8665 } 8666 8667 // run the callback 8668 var context = lis.delegated ? evt.cyTarget : triggerer; 8669 var ret = lis.callback.apply( context, args ); 8670 8671 if( ret === false || evt.isPropagationStopped() ){ 8672 // then don't bubble 8673 bubbleUp = false; 8674 8675 if( ret === false ){ 8676 // returning false is a shorthand for stopping propagation and preventing the def. action 8677 evt.stopPropagation(); 8678 evt.preventDefault(); 8679 } 8680 } 8681 } // if listener matches 8682 } // for each listener 8683 8684 // bubble up event for elements 8685 if( bubbleUp ){ 8686 var parent = hasCompounds ? triggerer._private.parent : null; 8687 var hasParent = parent != null && parent.length !== 0; 8688 8689 if( hasParent ){ // then bubble up to parent 8690 parent = parent[0]; 8691 parent.trigger( evt ); 8692 } else { // otherwise, bubble up to the core 8693 cy.trigger( evt ); 8694 } 8695 } 8696 8697 } // for each of all 8698 } // for each event 8699 8700 return self; // maintain chaining 8701 }; // function 8702 }, // trigger 8703 8704 animated: function( fnParams ){ 8705 var defaults = {}; 8706 fnParams = util.extend( {}, defaults, fnParams ); 8707 8708 return function animatedImpl(){ 8709 var self = this; 8710 var selfIsArrayLike = self.length !== undefined; 8711 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8712 var cy = this._private.cy || this; 8713 8714 if( !cy.styleEnabled() ){ return false; } 8715 8716 var ele = all[0]; 8717 8718 if( ele ){ 8719 return ele._private.animation.current.length > 0; 8720 } 8721 }; 8722 }, // animated 8723 8724 clearQueue: function( fnParams ){ 8725 var defaults = {}; 8726 fnParams = util.extend( {}, defaults, fnParams ); 8727 8728 return function clearQueueImpl(){ 8729 var self = this; 8730 var selfIsArrayLike = self.length !== undefined; 8731 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8732 var cy = this._private.cy || this; 8733 8734 if( !cy.styleEnabled() ){ return this; } 8735 8736 for( var i = 0; i < all.length; i++ ){ 8737 var ele = all[ i ]; 8738 ele._private.animation.queue = []; 8739 } 8740 8741 return this; 8742 }; 8743 }, // clearQueue 8744 8745 delay: function( fnParams ){ 8746 var defaults = {}; 8747 fnParams = util.extend( {}, defaults, fnParams ); 8748 8749 return function delayImpl( time, complete ){ 8750 var cy = this._private.cy || this; 8751 8752 if( !cy.styleEnabled() ){ return this; } 8753 8754 return this.animate( { 8755 delay: time, 8756 duration: time, 8757 complete: complete 8758 } ); 8759 }; 8760 }, // delay 8761 8762 delayAnimation: function( fnParams ){ 8763 var defaults = {}; 8764 fnParams = util.extend( {}, defaults, fnParams ); 8765 8766 return function delayAnimationImpl( time, complete ){ 8767 var cy = this._private.cy || this; 8768 8769 if( !cy.styleEnabled() ){ return this; } 8770 8771 return this.animation( { 8772 delay: time, 8773 duration: time, 8774 complete: complete 8775 } ); 8776 }; 8777 }, // delay 8778 8779 animation: function( fnParams ){ 8780 var defaults = {}; 8781 fnParams = util.extend( {}, defaults, fnParams ); 8782 8783 return function animationImpl( properties, params ){ 8784 var self = this; 8785 var selfIsArrayLike = self.length !== undefined; 8786 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8787 var cy = this._private.cy || this; 8788 var isCore = !selfIsArrayLike; 8789 var isEles = !isCore; 8790 8791 if( !cy.styleEnabled() ){ return this; } 8792 8793 var style = cy.style(); 8794 8795 properties = util.extend( {}, properties, params ); 8796 8797 if( properties.duration === undefined ){ 8798 properties.duration = 400; 8799 } 8800 8801 switch( properties.duration ){ 8802 case 'slow': 8803 properties.duration = 600; 8804 break; 8805 case 'fast': 8806 properties.duration = 200; 8807 break; 8808 } 8809 8810 var propertiesEmpty = Object.keys( properties ).length === 0; 8811 8812 if( propertiesEmpty ){ 8813 return new Animation( all[0], properties ); // nothing to animate 8814 } 8815 8816 if( isEles ){ 8817 properties.style = style.getPropsList( properties.style || properties.css ); 8818 8819 properties.css = undefined; 8820 } 8821 8822 if( properties.renderedPosition && isEles ){ 8823 var rpos = properties.renderedPosition; 8824 var pan = cy.pan(); 8825 var zoom = cy.zoom(); 8826 8827 properties.position = { 8828 x: ( rpos.x - pan.x ) / zoom, 8829 y: ( rpos.y - pan.y ) / zoom 8830 }; 8831 } 8832 8833 // override pan w/ panBy if set 8834 if( properties.panBy && isCore ){ 8835 var panBy = properties.panBy; 8836 var cyPan = cy.pan(); 8837 8838 properties.pan = { 8839 x: cyPan.x + panBy.x, 8840 y: cyPan.y + panBy.y 8841 }; 8842 } 8843 8844 // override pan w/ center if set 8845 var center = properties.center || properties.centre; 8846 if( center && isCore ){ 8847 var centerPan = cy.getCenterPan( center.eles, properties.zoom ); 8848 8849 if( centerPan ){ 8850 properties.pan = centerPan; 8851 } 8852 } 8853 8854 // override pan & zoom w/ fit if set 8855 if( properties.fit && isCore ){ 8856 var fit = properties.fit;
8857 var fitVp = cy.getFitViewport( fit.eles || fit.boundingBox, fit.padding ); 8858 8859 if( fitVp ){ 8860 properties.pan = fitVp.pan; 8861 properties.zoom = fitVp.zoom; 8862 } 8863 } 8864 8865 return new Animation( all[0], properties ); 8866 }; 8867 }, // animate 8868 8869 animate: function( fnParams ){ 8870 var defaults = {}; 8871 fnParams = util.extend( {}, defaults, fnParams ); 8872 8873 return function animateImpl( properties, params ){ 8874 var self = this; 8875 var selfIsArrayLike = self.length !== undefined; 8876 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8877 var cy = this._private.cy || this; 8878 8879 if( !cy.styleEnabled() ){ return this; } 8880 8881 if( params ){ 8882 properties = util.extend( {}, properties, params ); 8883 } 8884 8885 // manually hook and run the animation 8886 for( var i = 0; i < all.length; i++ ){ 8887 var ele = all[ i ]; 8888 var queue = ele.animated() && (properties.queue === undefined || properties.queue); 8889 8890 var ani = ele.animation( properties, (queue ? { queue: true } : undefined) ); 8891 8892 ani.play(); 8893 } 8894 8895 return this; // chaining 8896 }; 8897 }, // animate 8898 8899 stop: function( fnParams ){ 8900 var defaults = {}; 8901 fnParams = util.extend( {}, defaults, fnParams ); 8902 8903 return function stopImpl( clearQueue, jumpToEnd ){ 8904 var self = this; 8905 var selfIsArrayLike = self.length !== undefined; 8906 var all = selfIsArrayLike ? self : [ self ]; // put in array if not array-like 8907 var cy = this._private.cy || this; 8908 8909 if( !cy.styleEnabled() ){ return this; } 8910 8911 for( var i = 0; i < all.length; i++ ){ 8912 var ele = all[ i ]; 8913 var _p = ele._private; 8914 var anis = _p.animation.current; 8915 8916 for( var j = 0; j < anis.length; j++ ){ 8917 var ani = anis[ j ]; 8918 var ani_p = ani._private; 8919 8920 if( jumpToEnd ){ 8921 // next iteration of the animation loop, the animation 8922 // will go straight to the end and be removed 8923 ani_p.duration = 0; 8924 } 8925 } 8926 8927 // clear the queue of future animations 8928 if( clearQueue ){ 8929 _p.animation.queue = []; 8930 } 8931 8932 if( !jumpToEnd ){ 8933 _p.animation.current = []; 8934 } 8935 } 8936 8937 // we have to notify (the animation loop doesn't do it for us on `stop`) 8938 cy.notify( { 8939 eles: this, 8940 type: 'draw' 8941 } ); 8942 8943 return this; 8944 }; 8945 } // stop 8946 8947}; // define 8948 8949module.exports = define; 8950 8951},{"./animation":2,"./event":45,"./is":83,"./promise":86,"./selector":87,"./util":100}],45:[function(_dereq_,module,exports){ 8952'use strict'; 8953 8954/*! 8955Event object based on jQuery events, MIT license 8956 8957https://jquery.org/license/ 8958https://tldrlegal.com/license/mit-license 8959https://github.com/jquery/jquery/blob/master/src/event.js 8960*/ 8961 8962var Event = function( src, props ){ 8963 // Allow instantiation without the 'new' keyword 8964 if( !(this instanceof Event) ){ 8965 return new Event( src, props ); 8966 } 8967 8968 // Event object 8969 if( src && src.type ){ 8970 this.originalEvent = src; 8971 this.type = src.type; 8972 8973 // Events bubbling up the document may have been marked as prevented 8974 // by a handler lower down the tree; reflect the correct value. 8975 this.isDefaultPrevented = ( src.defaultPrevented ) ? returnTrue : returnFalse; 8976 8977 // Event type 8978 } else { 8979 this.type = src; 8980 } 8981 8982 // Put explicitly provided properties onto the event object 8983 if( props ){ 8984 // util.extend( this, props ); 8985 8986 // more efficient to manually copy fields we use 8987 this.type = props.type !== undefined ? props.type : this.type; 8988 this.cy = props.cy; 8989 this.cyTarget = props.cyTarget; 8990 this.cyPosition = props.cyPosition; 8991 this.cyRenderedPosition = props.cyRenderedPosition; 8992 this.namespace = props.namespace; 8993 this.layout = props.layout; 8994 this.data = props.data; 8995 this.message = props.message; 8996 } 8997 8998 // Create a timestamp if incoming event doesn't have one 8999 this.timeStamp = src && src.timeStamp || Date.now(); 9000}; 9001 9002function returnFalse(){ 9003 return false; 9004} 9005 9006function returnTrue(){ 9007 return true; 9008} 9009 9010// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html 9011Event.prototype = { 9012 instanceString: function(){ 9013 return 'event'; 9014 }, 9015 9016 preventDefault: function(){ 9017 this.isDefaultPrevented = returnTrue; 9018 9019 var e = this.originalEvent; 9020 if( !e ){ 9021 return; 9022 } 9023 9024 // if preventDefault exists run it on the original event 9025 if( e.preventDefault ){ 9026 e.preventDefault(); 9027 } 9028 }, 9029 9030 stopPropagation: function(){ 9031 this.isPropagationStopped = returnTrue; 9032 9033 var e = this.originalEvent; 9034 if( !e ){ 9035 return; 9036 } 9037 9038 // if stopPropagation exists run it on the original event 9039 if( e.stopPropagation ){ 9040 e.stopPropagation(); 9041 } 9042 }, 9043 9044 stopImmediatePropagation: function(){ 9045 this.isImmediatePropagationStopped = returnTrue; 9046 this.stopPropagation(); 9047 }, 9048 9049 isDefaultPrevented: returnFalse, 9050 isPropagationStopped: returnFalse, 9051 isImmediatePropagationStopped: returnFalse 9052}; 9053 9054module.exports = Event; 9055 9056},{}],46:[function(_dereq_,module,exports){ 9057'use strict'; 9058 9059var util = _dereq_( './util' ); 9060var define = _dereq_( './define' ); 9061var Collection = _dereq_( './collection' ); 9062var Core = _dereq_( './core' ); 9063var incExts = _dereq_( './extensions' ); 9064var is = _dereq_( './is' ); 9065 9066// registered extensions to cytoscape, indexed by name 9067var extensions = {}; 9068 9069// registered modules for extensions, indexed by name 9070var modules = {}; 9071 9072function setExtension( type, name, registrant ){ 9073 9074 var ext = registrant; 9075 9076 var overrideErr = function( field ){ 9077 util.error( 'Can not register `' + name + '` for `' + type + '` since `' + field + '` already exists in the prototype and can not be overridden' ); 9078 }; 9079 9080 if( type === 'core' ){ 9081 if( Core.prototype[ name ] ){ 9082 return overrideErr( name ); 9083 } else { 9084 Core.prototype[ name ] = registrant; 9085 } 9086 9087 } else if( type === 'collection' ){ 9088 if( Collection.prototype[ name ] ){ 9089 return overrideErr( name ); 9090 } else { 9091 Collection.prototype[ name ] = registrant; 9092 } 9093 9094 } else if( type === 'layout' ){ 9095 // fill in missing layout functions in the prototype 9096 9097 var Layout = function( options ){ 9098 this.options = options; 9099 9100 registrant.call( this, options ); 9101 9102 // make sure layout has _private for use w/ std apis like .on() 9103 if( !is.plainObject( this._private ) ){ 9104 this._private = {}; 9105 } 9106 9107 this._private.cy = options.cy; 9108 this._private.listeners = []; 9109 }; 9110 9111 var layoutProto = Layout.prototype = Object.create( registrant.prototype ); 9112 9113 var optLayoutFns = []; 9114 9115 for( var i = 0; i < optLayoutFns.length; i++ ){ 9116 var fnName = optLayoutFns[ i ]; 9117 9118 layoutProto[ fnName ] = layoutProto[ fnName ] || function(){ return this; }; 9119 } 9120 9121 // either .start() or .run() is defined, so autogen the other 9122 if( layoutProto.start && !layoutProto.run ){ 9123 layoutProto.run = function(){ this.start(); return this; }; 9124 } else if( !layoutProto.start && layoutProto.run ){ 9125 layoutProto.start = function(){ this.run(); return this; }; 9126 } 9127 9128 if( !layoutProto.stop ){ 9129 layoutProto.stop = function(){ 9130 var opts = this.options; 9131 9132 if( opts && opts.animate ){ 9133 var anis = this.animations; 9134 for( var i = 0; i < anis.length; i++ ){ 9135 anis[ i ].stop(); 9136 } 9137 } 9138 9139 this.trigger( 'layoutstop' ); 9140 9141 return this; 9142 }; 9143 } 9144 9145 if( !layoutProto.destroy ){ 9146 layoutProto.destroy = function(){ 9147 return this; 9148 }; 9149 } 9150 9151 layoutProto.on = define.on( { layout: true } ); 9152 layoutProto.one = define.on( { layout: true, unbindSelfOnTrigger: true } ); 9153 layoutProto.once = define.on( { layout: true, unbindAllBindersOnTrigger: true } ); 9154 layoutProto.off = define.off( { layout: true } ); 9155 layoutProto.trigger = define.trigger( { layout: true } ); 9156 9157 define.eventAliasesOn( layoutProto ); 9158 9159 ext = Layout; // replace with our wrapped layout 9160 9161 } else if( type === 'renderer' && name !== 'null' && name !== 'base' ){ 9162 // user registered renderers inherit from base 9163 9164 var BaseRenderer = getExtension( 'renderer', 'base' ); 9165 var bProto = BaseRenderer.prototype; 9166 var RegistrantRenderer = registrant; 9167 var rProto = registrant.prototype; 9168 9169 var Renderer = function(){ 9170 BaseRenderer.apply( this, arguments ); 9171 RegistrantRenderer.apply( this, arguments ); 9172 }; 9173 9174 var proto = Renderer.prototype; 9175 9176 for( var pName in bProto ){ 9177 var pVal = bProto[ pName ]; 9178 var existsInR = rProto[ pName ] != null; 9179 9180 if( existsInR ){ 9181 return overrideErr( pName ); 9182 } 9183 9184 proto[ pName ] = pVal; // take impl from base 9185 } 9186 9187 for( var pName in rProto ){ 9188 proto[ pName ] = rProto[ pName ]; // take impl from registrant 9189 } 9190 9191 bProto.clientFunctions.forEach( function( name ){ 9192 proto[ name ] = proto[ name ] || function(){ 9193 util.error( 'Renderer does not implement `renderer.' + name + '()` on its prototype' ); 9194 }; 9195 } ); 9196 9197 ext = Renderer; 9198 9199 } 9200 9201 return util.setMap( { 9202 map: extensions, 9203 keys: [ type, name ], 9204 value: ext 9205 } ); 9206} 9207 9208function getExtension( type, name ){ 9209 return util.getMap( { 9210 map: extensions, 9211 keys: [ type, name ] 9212 } ); 9213} 9214 9215function setModule( type, name, moduleType, moduleName, registrant ){ 9216 return util.setMap( { 9217 map: modules, 9218 keys: [ type, name, moduleType, moduleName ], 9219 value: registrant 9220 } ); 9221} 9222 9223function getModule( type, name, moduleType, moduleName ){ 9224 return util.getMap( { 9225 map: modules, 9226 keys: [ type, name, moduleType, moduleName ] 9227 } ); 9228} 9229 9230var extension = function(){ 9231 // e.g. extension('renderer', 'svg') 9232 if( arguments.length === 2 ){ 9233 return getExtension.apply( null, arguments ); 9234 } 9235 9236 // e.g. extension('renderer', 'svg', { ... }) 9237 else if( arguments.length === 3 ){ 9238 return setExtension.apply( null, arguments ); 9239 } 9240 9241 // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse') 9242 else if( arguments.length === 4 ){ 9243 return getModule.apply( null, arguments ); 9244 } 9245 9246 // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse', { ... }) 9247 else if( arguments.length === 5 ){ 9248 return setModule.apply( null, arguments ); 9249 } 9250 9251 else { 9252 util.error( 'Invalid extension access syntax' ); 9253 } 9254 9255}; 9256 9257// allows a core instance to access extensions internally 9258Core.prototype.extension = extension; 9259 9260// included extensions
9261incExts.forEach( function( group ){ 9262 group.extensions.forEach( function( ext ){ 9263 setExtension( group.type, ext.name, ext.impl ); 9264 } ); 9265} ); 9266 9267module.exports = extension; 9268 9269},{"./collection":26,"./core":37,"./define":44,"./extensions":47,"./is":83,"./util":100}],47:[function(_dereq_,module,exports){ 9270'use strict'; 9271 9272module.exports = [ 9273 { 9274 type: 'layout', 9275 extensions: _dereq_( './layout' ) 9276 }, 9277 9278 { 9279 type: 'renderer', 9280 extensions: _dereq_( './renderer' ) 9281 } 9282]; 9283 9284},{"./layout":53,"./renderer":78}],48:[function(_dereq_,module,exports){ 9285'use strict'; 9286 9287var util = _dereq_( '../../util' ); 9288var math = _dereq_( '../../math' ); 9289var is = _dereq_( '../../is' ); 9290 9291var defaults = { 9292 fit: true, // whether to fit the viewport to the graph 9293 directed: false, // whether the tree is directed downwards (or edges can point in any direction if false) 9294 padding: 30, // padding on fit 9295 circle: false, // put depths in concentric circles if true, put depths top down if false 9296 spacingFactor: 1.75, // positive spacing factor, larger => more space between nodes (N.B. n/a if causes overlap) 9297 boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 9298 avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space 9299 roots: undefined, // the roots of the trees 9300 maximalAdjustments: 0, // how many times to try to position the nodes in a maximal way (i.e. no backtracking) 9301 animate: false, // whether to transition the node positions 9302 animationDuration: 500, // duration of animation in ms if enabled 9303 animationEasing: undefined, // easing of animation if enabled 9304 ready: undefined, // callback on layoutready 9305 stop: undefined // callback on layoutstop 9306}; 9307 9308function BreadthFirstLayout( options ){ 9309 this.options = util.extend( {}, defaults, options ); 9310} 9311 9312BreadthFirstLayout.prototype.run = function(){ 9313 var params = this.options; 9314 var options = params; 9315 9316 var cy = params.cy; 9317 var eles = options.eles; 9318 var nodes = eles.nodes().not( ':parent' ); 9319 var graph = eles; 9320 9321 var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : { 9322 x1: 0, y1: 0, w: cy.width(), h: cy.height() 9323 } ); 9324 9325 var roots; 9326 if( is.elementOrCollection( options.roots ) ){ 9327 roots = options.roots; 9328 } else if( is.array( options.roots ) ){ 9329 var rootsArray = []; 9330 9331 for( var i = 0; i < options.roots.length; i++ ){ 9332 var id = options.roots[ i ]; 9333 var ele = cy.getElementById( id ); 9334 rootsArray.push( ele ); 9335 } 9336 9337 roots = cy.collection( rootsArray ); 9338 } else if( is.string( options.roots ) ){ 9339 roots = cy.$( options.roots ); 9340 9341 } else { 9342 if( options.directed ){ 9343 roots = nodes.roots(); 9344 } else { 9345 var components = []; 9346 var unhandledNodes = nodes; 9347 9348 while( unhandledNodes.length > 0 ){ 9349 var currComp = cy.collection(); 9350 9351 eles.bfs( { 9352 roots: unhandledNodes[0], 9353 visit: function( i, depth, node, edge, pNode ){ 9354 currComp = currComp.add( node ); 9355 }, 9356 directed: false 9357 } ); 9358 9359 unhandledNodes = unhandledNodes.not( currComp ); 9360 components.push( currComp ); 9361 } 9362 9363 roots = cy.collection(); 9364 for( var i = 0; i < components.length; i++ ){ 9365 var comp = components[ i ]; 9366 var maxDegree = comp.maxDegree( false ); 9367 var compRoots = comp.filter( function(){ 9368 return this.degree( false ) === maxDegree; 9369 } ); 9370 9371 roots = roots.add( compRoots ); 9372 } 9373 9374 } 9375 } 9376 9377 9378 var depths = []; 9379 var foundByBfs = {}; 9380 var id2depth = {}; 9381 var prevNode = {}; 9382 var prevEdge = {}; 9383 var successors = {}; 9384 9385 // find the depths of the nodes 9386 graph.bfs( { 9387 roots: roots, 9388 directed: options.directed, 9389 visit: function( i, depth, node, edge, pNode ){ 9390 var ele = this[0]; 9391 var id = ele.id(); 9392 9393 if( !depths[ depth ] ){ 9394 depths[ depth ] = []; 9395 } 9396 9397 depths[ depth ].push( ele ); 9398 foundByBfs[ id ] = true; 9399 id2depth[ id ] = depth; 9400 prevNode[ id ] = pNode; 9401 prevEdge[ id ] = edge; 9402 9403 if( pNode ){ 9404 var prevId = pNode.id(); 9405 var succ = successors[ prevId ] = successors[ prevId ] || []; 9406
9407 succ.push( node ); 9408 } 9409 } 9410 } ); 9411 9412 // check for nodes not found by bfs 9413 var orphanNodes = []; 9414 for( var i = 0; i < nodes.length; i++ ){ 9415 var ele = nodes[ i ]; 9416 9417 if( foundByBfs[ ele.id() ] ){ 9418 continue; 9419 } else { 9420 orphanNodes.push( ele ); 9421 } 9422 } 9423 9424 // assign orphan nodes a depth from their neighborhood 9425 var maxChecks = orphanNodes.length * 3; 9426 var checks = 0; 9427 while( orphanNodes.length !== 0 && checks < maxChecks ){ 9428 var node = orphanNodes.shift(); 9429 var neighbors = node.neighborhood().nodes(); 9430 var assignedDepth = false; 9431 9432 for( var i = 0; i < neighbors.length; i++ ){ 9433 var depth = id2depth[ neighbors[ i ].id() ]; 9434 9435 if( depth !== undefined ){ 9436 depths[ depth ].push( node ); 9437 assignedDepth = true; 9438 break; 9439 } 9440 } 9441 9442 if( !assignedDepth ){ 9443 orphanNodes.push( node ); 9444 } 9445 9446 checks++; 9447 } 9448 9449 // assign orphan nodes that are still left to the depth of their subgraph 9450 while( orphanNodes.length !== 0 ){ 9451 var node = orphanNodes.shift(); 9452 //var subgraph = graph.bfs( node ).path; 9453 var assignedDepth = false; 9454 9455 // for( var i = 0; i < subgraph.length; i++ ){ 9456 // var depth = id2depth[ subgraph[i].id() ]; 9457 9458 // if( depth !== undefined ){ 9459 // depths[depth].push( node ); 9460 // assignedDepth = true; 9461 // break; 9462 // } 9463 // } 9464 9465 if( !assignedDepth ){ // worst case if the graph really isn't tree friendly, then just dump it in 0 9466 if( depths.length === 0 ){ 9467 depths.push( [] ); 9468 } 9469 9470 depths[0].push( node ); 9471 } 9472 } 9473 9474 // assign the nodes a depth and index 9475 var assignDepthsToEles = function(){ 9476 for( var i = 0; i < depths.length; i++ ){ 9477 var eles = depths[ i ]; 9478 9479 for( var j = 0; j < eles.length; j++ ){ 9480 var ele = eles[ j ]; 9481 9482 ele._private.scratch.breadthfirst = { 9483 depth: i, 9484 index: j 9485 }; 9486 } 9487 } 9488 }; 9489 assignDepthsToEles(); 9490 9491 9492 var intersectsDepth = function( node ){ // returns true if has edges pointing in from a higher depth 9493 var edges = node.connectedEdges( function(){ 9494 return this.data( 'target' ) === node.id(); 9495 } ); 9496 var thisInfo = node._private.scratch.breadthfirst; 9497 var highestDepthOfOther = 0; 9498 var highestOther; 9499 for( var i = 0; i < edges.length; i++ ){ 9500 var edge = edges[ i ]; 9501 var otherNode = edge.source()[0]; 9502 var otherInfo = otherNode._private.scratch.breadthfirst; 9503 9504 if( thisInfo.depth <= otherInfo.depth && highestDepthOfOther < otherInfo.depth ){ 9505 highestDepthOfOther = otherInfo.depth; 9506 highestOther = otherNode; 9507 } 9508 } 9509 9510 return highestOther; 9511 }; 9512 9513 // make maximal if so set by adjusting depths 9514 for( var adj = 0; adj < options.maximalAdjustments; adj++ ){ 9515 9516 var nDepths = depths.length; 9517 var elesToMove = []; 9518 for( var i = 0; i < nDepths; i++ ){ 9519 var depth = depths[ i ]; 9520 9521 var nDepth = depth.length; 9522 for( var j = 0; j < nDepth; j++ ){ 9523 var ele = depth[ j ]; 9524 var info = ele._private.scratch.breadthfirst; 9525 var intEle = intersectsDepth( ele ); 9526 9527 if( intEle ){ 9528 info.intEle = intEle; 9529 elesToMove.push( ele ); 9530 } 9531 } 9532 } 9533 9534 for( var i = 0; i < elesToMove.length; i++ ){ 9535 var ele = elesToMove[ i ]; 9536 var info = ele._private.scratch.breadthfirst; 9537 var intEle = info.intEle; 9538 var intInfo = intEle._private.scratch.breadthfirst; 9539 9540 depths[ info.depth ].splice( info.index, 1 ); // remove from old depth & index 9541 9542 // add to end of new depth 9543 var newDepth = intInfo.depth + 1; 9544 while( newDepth > depths.length - 1 ){ 9545 depths.push( [] ); 9546 } 9547 depths[ newDepth ].push( ele ); 9548 9549 info.depth = newDepth; 9550 info.index = depths[ newDepth ].length - 1; 9551 } 9552 9553 assignDepthsToEles(); 9554 } 9555 9556 // find min distance we need to leave between nodes 9557 var minDistance = 0; 9558 if( options.avoidOverlap ){ 9559 for( var i = 0; i < nodes.length; i++ ){ 9560 var n = nodes[ i ]; 9561 var nbb = n.boundingBox(); 9562 var w = nbb.w; 9563 var h = nbb.h; 9564 9565 minDistance = Math.max( minDistance, w, h ); 9566 } 9567 minDistance *= options.spacingFactor; // just to have some nice spacing 9568 } 9569 9570 // get the weighted percent for an element based on its connectivity to other levels 9571 var cachedWeightedPercent = {}; 9572 var getWeightedPercent = function( ele ){ 9573 if( cachedWeightedPercent[ ele.id() ] ){ 9574 return cachedWeightedPercent[ ele.id() ]; 9575 } 9576 9577 var eleDepth = ele._private.scratch.breadthfirst.depth; 9578 var neighbors = ele.neighborhood().nodes().not( ':parent' ); 9579 var percent = 0; 9580 var samples = 0; 9581 9582 for( var i = 0; i < neighbors.length; i++ ){ 9583 var neighbor = neighbors[ i ]; 9584 var bf = neighbor._private.scratch.breadthfirst; 9585 var index = bf.index; 9586 var depth = bf.depth; 9587 var nDepth = depths[ depth ].length; 9588 9589 if( eleDepth > depth || eleDepth === 0 ){ // only get influenced by elements above 9590 percent += index / nDepth; 9591 samples++; 9592 } 9593 } 9594 9595 samples = Math.max( 1, samples ); 9596 percent = percent / samples; 9597 9598 if( samples === 0 ){ // so lone nodes have a "don't care" state in sorting 9599 percent = undefined; 9600 } 9601 9602 cachedWeightedPercent[ ele.id() ] = percent; 9603 return percent; 9604 }; 9605 9606 9607 // rearrange the indices in each depth level based on connectivity 9608 9609 var sortFn = function( a, b ){ 9610 var apct = getWeightedPercent( a ); 9611 var bpct = getWeightedPercent( b ); 9612 9613 return apct - bpct; 9614 }; 9615 9616 for( var times = 0; times < 3; times++ ){ // do it a few times b/c the depths are dynamic and we want a more stable result 9617 9618 for( var i = 0; i < depths.length; i++ ){ 9619 depths[ i ] = depths[ i ].sort( sortFn ); 9620 } 9621 assignDepthsToEles(); // and update 9622 9623 } 9624 9625 var biggestDepthSize = 0; 9626 for( var i = 0; i < depths.length; i++ ){ 9627 biggestDepthSize = Math.max( depths[ i ].length, biggestDepthSize ); 9628 } 9629 9630 var center = { 9631 x: bb.x1 + bb.w / 2, 9632 y: bb.x1 + bb.h / 2 9633 }; 9634 9635 var getPosition = function( ele, isBottomDepth ){ 9636 var info = ele._private.scratch.breadthfirst; 9637 var depth = info.depth; 9638 var index = info.index; 9639 var depthSize = depths[ depth ].length; 9640 9641 var distanceX = Math.max( bb.w / (depthSize + 1), minDistance ); 9642 var distanceY = Math.max( bb.h / (depths.length + 1), minDistance ); 9643 var radiusStepSize = Math.min( bb.w / 2 / depths.length, bb.h / 2 / depths.length ); 9644 radiusStepSize = Math.max( radiusStepSize, minDistance ); 9645 9646 if( !options.circle ){ 9647 9648 var epos = { 9649 x: center.x + (index + 1 - (depthSize + 1) / 2) * distanceX, 9650 y: (depth + 1) * distanceY 9651 }; 9652 9653 if( isBottomDepth ){ 9654 return epos; 9655 } 9656
9657 // var succs = successors[ ele.id() ]; 9658 // if( succs ){ 9659 // epos.x = 0; 9660 // 9661 // for( var i = 0 ; i < succs.length; i++ ){ 9662 // var spos = pos[ succs[i].id() ]; 9663 // 9664 // epos.x += spos.x; 9665 // } 9666 // 9667 // epos.x /= succs.length; 9668 // } else { 9669 // //debugger; 9670 // } 9671 9672 return epos; 9673 9674 } else { 9675 if( options.circle ){ 9676 var radius = radiusStepSize * depth + radiusStepSize - (depths.length > 0 && depths[0].length <= 3 ? radiusStepSize / 2 : 0); 9677 var theta = 2 * Math.PI / depths[ depth ].length * index; 9678 9679 if( depth === 0 && depths[0].length === 1 ){ 9680 radius = 1; 9681 } 9682 9683 return { 9684 x: center.x + radius * Math.cos( theta ), 9685 y: center.y + radius * Math.sin( theta ) 9686 }; 9687 9688 } else { 9689 return { 9690 x: center.x + (index + 1 - (depthSize + 1) / 2) * distanceX, 9691 y: (depth + 1) * distanceY 9692 }; 9693 } 9694 } 9695 9696 }; 9697 9698 // get positions in reverse depth order 9699 var pos = {}; 9700 for( var i = depths.length - 1; i >= 0; i-- ){ 9701 var depth = depths[ i ]; 9702 9703 for( var j = 0; j < depth.length; j++ ){ 9704 var node = depth[ j ]; 9705 9706 pos[ node.id() ] = getPosition( node, i === depths.length - 1 ); 9707 } 9708 } 9709 9710 nodes.layoutPositions( this, options, function(){ 9711 return pos[ this.id() ]; 9712 } ); 9713 9714 return this; // chaining 9715}; 9716 9717module.exports = BreadthFirstLayout; 9718 9719},{"../../is":83,"../../math":85,"../../util":100}],49:[function(_dereq_,module,exports){ 9720'use strict'; 9721 9722var util = _dereq_( '../../util' ); 9723var math = _dereq_( '../../math' ); 9724var is = _dereq_( '../../is' ); 9725 9726var defaults = { 9727 fit: true, // whether to fit the viewport to the graph 9728 padding: 30, // the padding on fit 9729 boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 9730 avoidOverlap: true, // prevents node overlap, may overflow boundingBox and radius if not enough space 9731 radius: undefined, // the radius of the circle 9732 startAngle: 3 / 2 * Math.PI, // where nodes start in radians 9733 sweep: undefined, // how many radians should be between the first and last node (defaults to full circle) 9734 clockwise: true, // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) 9735 sort: undefined, // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 9736 animate: false, // whether to transition the node positions 9737 animationDuration: 500, // duration of animation in ms if enabled 9738 animationEasing: undefined, // easing of animation if enabled 9739 ready: undefined, // callback on layoutready 9740 stop: undefined // callback on layoutstop 9741}; 9742 9743function CircleLayout( options ){ 9744 this.options = util.extend( {}, defaults, options ); 9745} 9746 9747CircleLayout.prototype.run = function(){ 9748 var params = this.options; 9749 var options = params; 9750 9751 var cy = params.cy; 9752 var eles = options.eles; 9753 9754 var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise; 9755 9756 var nodes = eles.nodes().not( ':parent' ); 9757 9758 if( options.sort ){ 9759 nodes = nodes.sort( options.sort ); 9760 } 9761 9762 var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : { 9763 x1: 0, y1: 0, w: cy.width(), h: cy.height() 9764 } ); 9765 9766 var center = { 9767 x: bb.x1 + bb.w / 2, 9768 y: bb.y1 + bb.h / 2 9769 }; 9770 9771 var sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / nodes.length : options.sweep; 9772 9773 var dTheta = sweep / ( Math.max( 1, nodes.length - 1 ) ); 9774 var r; 9775 9776 var minDistance = 0; 9777 for( var i = 0; i < nodes.length; i++ ){ 9778 var n = nodes[ i ]; 9779 var nbb = n.boundingBox(); 9780 var w = nbb.w; 9781 var h = nbb.h; 9782 9783 minDistance = Math.max( minDistance, w, h ); 9784 } 9785 9786 if( is.number( options.radius ) ){ 9787 r = options.radius; 9788 } else if( nodes.length <= 1 ){ 9789 r = 0; 9790 } else { 9791 r = Math.min( bb.h, bb.w ) / 2 - minDistance; 9792 } 9793 9794 // calculate the radius 9795 if( nodes.length > 1 && options.avoidOverlap ){ // but only if more than one node (can't overlap) 9796 minDistance *= 1.75; // just to have some nice spacing 9797 9798 var dcos = Math.cos( dTheta ) - Math.cos( 0 ); 9799 var dsin = Math.sin( dTheta ) - Math.sin( 0 ); 9800 var rMin = Math.sqrt( minDistance * minDistance / ( dcos * dcos + dsin * dsin ) ); // s.t. no nodes overlapping 9801 r = Math.max( rMin, r ); 9802 } 9803 9804 var getPos = function( i, ele ){ 9805 var theta = options.startAngle + i * dTheta * ( clockwise ? 1 : -1 ); 9806 9807 var rx = r * Math.cos( theta ); 9808 var ry = r * Math.sin( theta ); 9809 var pos = { 9810 x: center.x + rx, 9811 y: center.y + ry 9812 }; 9813 9814 return pos; 9815 }; 9816 9817 nodes.layoutPositions( this, options, getPos ); 9818 9819 return this; // chaining 9820}; 9821 9822module.exports = CircleLayout; 9823 9824},{"../../is":83,"../../math":85,"../../util":100}],50:[function(_dereq_,module,exports){ 9825'use strict'; 9826 9827var util = _dereq_( '../../util' ); 9828var math = _dereq_( '../../math' ); 9829 9830var defaults = { 9831 fit: true, // whether to fit the viewport to the graph 9832 padding: 30, // the padding on fit
9833 startAngle: 3 / 2 * Math.PI, // where nodes start in radians 9834 sweep: undefined, // how many radians should be between the first and last node (defaults to full circle) 9835 clockwise: true, // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) 9836 equidistant: false, // whether levels have an equal radial distance betwen them, may cause bounding box overflow 9837 minNodeSpacing: 10, // min spacing between outside of nodes (used for radius adjustment) 9838 boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 9839 avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space 9840 height: undefined, // height of layout area (overrides container height) 9841 width: undefined, // width of layout area (overrides container width) 9842 concentric: function( node ){ // returns numeric value for each node, placing higher nodes in levels towards the centre 9843 return node.degree(); 9844 }, 9845 levelWidth: function( nodes ){ // the variation of concentric values in each level 9846 return nodes.maxDegree() / 4; 9847 }, 9848 animate: false, // whether to transition the node positions 9849 animationDuration: 500, // duration of animation in ms if enabled 9850 animationEasing: undefined, // easing of animation if enabled 9851 ready: undefined, // callback on layoutready 9852 stop: undefined // callback on layoutstop 9853}; 9854 9855function ConcentricLayout( options ){ 9856 this.options = util.extend( {}, defaults, options ); 9857} 9858 9859ConcentricLayout.prototype.run = function(){ 9860 var params = this.options; 9861 var options = params; 9862 9863 var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise; 9864 9865 var cy = params.cy; 9866 9867 var eles = options.eles; 9868 var nodes = eles.nodes().not( ':parent' ); 9869 9870 var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : { 9871 x1: 0, y1: 0, w: cy.width(), h: cy.height() 9872 } ); 9873 9874 var center = { 9875 x: bb.x1 + bb.w / 2, 9876 y: bb.y1 + bb.h / 2 9877 }; 9878 9879 var nodeValues = []; // { node, value } 9880 var theta = options.startAngle; 9881 var maxNodeSize = 0; 9882 9883 for( var i = 0; i < nodes.length; i++ ){ 9884 var node = nodes[ i ]; 9885 var value; 9886 9887 // calculate the node value 9888 value = options.concentric.apply( node, [ node ] ); 9889 nodeValues.push( { 9890 value: value, 9891 node: node 9892 } ); 9893 9894 // for style mapping 9895 node._private.scratch.concentric = value; 9896 } 9897 9898 // in case we used the `concentric` in style 9899 nodes.updateStyle(); 9900 9901 // calculate max size now based on potentially updated mappers 9902 for( var i = 0; i < nodes.length; i++ ){ 9903 var node = nodes[ i ]; 9904 var nbb = node.boundingBox(); 9905 9906 maxNodeSize = Math.max( maxNodeSize, nbb.w, nbb.h ); 9907 } 9908 9909 // sort node values in descreasing order 9910 nodeValues.sort( function( a, b ){ 9911 return b.value - a.value; 9912 } ); 9913 9914 var levelWidth = options.levelWidth( nodes ); 9915 9916 // put the values into levels 9917 var levels = [ [] ]; 9918 var currentLevel = levels[0]; 9919 for( var i = 0; i < nodeValues.length; i++ ){ 9920 var val = nodeValues[ i ]; 9921 9922 if( currentLevel.length > 0 ){ 9923 var diff = Math.abs( currentLevel[0].value - val.value ); 9924 9925 if( diff >= levelWidth ){ 9926 currentLevel = []; 9927 levels.push( currentLevel ); 9928 } 9929 } 9930 9931 currentLevel.push( val ); 9932 } 9933 9934 // create positions from levels 9935 9936 var minDist = maxNodeSize + options.minNodeSpacing; // min dist between nodes 9937 9938 if( !options.avoidOverlap ){ // then strictly constrain to bb 9939 var firstLvlHasMulti = levels.length > 0 && levels[0].length > 1; 9940 var maxR = ( Math.min( bb.w, bb.h ) / 2 - minDist ); 9941 var rStep = maxR / ( levels.length + firstLvlHasMulti ? 1 : 0 ); 9942 9943 minDist = Math.min( minDist, rStep ); 9944 } 9945 9946 // find the metrics for each level 9947 var r = 0; 9948 for( var i = 0; i < levels.length; i++ ){ 9949 var level = levels[ i ]; 9950 var sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / level.length : options.sweep; 9951 var dTheta = level.dTheta = sweep / ( Math.max( 1, level.length - 1 ) ); 9952 9953 // calculate the radius 9954 if( level.length > 1 && options.avoidOverlap ){ // but only if more than one node (can't overlap) 9955 var dcos = Math.cos( dTheta ) - Math.cos( 0 ); 9956 var dsin = Math.sin( dTheta ) - Math.sin( 0 ); 9957 var rMin = Math.sqrt( minDist * minDist / ( dcos * dcos + dsin * dsin ) ); // s.t. no nodes overlapping 9958 9959 r = Math.max( rMin, r ); 9960 } 9961 9962 level.r = r; 9963 9964 r += minDist; 9965 } 9966 9967 if( options.equidistant ){ 9968 var rDeltaMax = 0; 9969 var r = 0; 9970 9971 for( var i = 0; i < levels.length; i++ ){ 9972 var level = levels[ i ]; 9973 var rDelta = level.r - r; 9974 9975 rDeltaMax = Math.max( rDeltaMax, rDelta ); 9976 } 9977 9978 r = 0; 9979 for( var i = 0; i < levels.length; i++ ){ 9980 var level = levels[ i ]; 9981 9982 if( i === 0 ){ 9983 r = level.r; 9984 } 9985 9986 level.r = r; 9987 9988 r += rDeltaMax; 9989 } 9990 } 9991 9992 // calculate the node positions 9993 var pos = {}; // id => position 9994 for( var i = 0; i < levels.length; i++ ){ 9995 var level = levels[ i ]; 9996 var dTheta = level.dTheta; 9997 var r = level.r; 9998 9999 for( var j = 0; j < level.length; j++ ){ 10000 var val = level[ j ]; 10001 var theta = options.startAngle + (clockwise ? 1 : -1) * dTheta * j; 10002 10003 var p = { 10004 x: center.x + r * Math.cos( theta ), 10005 y: center.y + r * Math.sin( theta ) 10006 }; 10007 10008 pos[ val.node.id() ] = p; 10009 } 10010 } 10011 10012 // position the nodes 10013 nodes.layoutPositions( this, options, function(){ 10014 var id = this.id(); 10015 10016 return pos[ id ]; 10017 } ); 10018 10019 return this; // chaining 10020}; 10021 10022module.exports = ConcentricLayout; 10023 10024},{"../../math":85,"../../util":100}],51:[function(_dereq_,module,exports){ 10025'use strict'; 10026 10027/* 10028The CoSE layout was written by Gerardo Huck. 10029https://www.linkedin.com/in/gerardohuck/ 10030 10031Based on the following article: 10032http://dl.acm.org/citation.cfm?id=1498047 10033 10034Modifications tracked on Github. 10035*/ 10036 10037var util = _dereq_( '../../util' ); 10038var math = _dereq_( '../../math' ); 10039var Thread = _dereq_( '../../thread' ); 10040var is = _dereq_( '../../is' ); 10041 10042var DEBUG; 10043 10044/** 10045 * @brief : default layout options 10046 */ 10047var defaults = { 10048 // Called on `layoutready` 10049 ready: function(){}, 10050 10051 // Called on `layoutstop` 10052 stop: function(){}, 10053 10054 // Whether to animate while running the layout 10055 animate: true, 10056 10057 // The layout animates only after this many milliseconds 10058 // (prevents flashing on fast runs) 10059 animationThreshold: 250, 10060 10061 // Number of iterations between consecutive screen positions update 10062 // (0 -> only updated on the end) 10063 refresh: 20, 10064 10065 // Whether to fit the network view after when done 10066 fit: true, 10067 10068 // Padding on fit
10069 padding: 30, 10070 10071 // Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 10072 boundingBox: undefined, 10073 10074 // Randomize the initial positions of the nodes (true) or use existing positions (false) 10075 randomize: false, 10076 10077 // Extra spacing between components in non-compound graphs 10078 componentSpacing: 100, 10079 10080 // Node repulsion (non overlapping) multiplier 10081 nodeRepulsion: function( node ){ return 400000; }, 10082 10083 // Node repulsion (overlapping) multiplier 10084 nodeOverlap: 10, 10085 10086 // Ideal edge (non nested) length 10087 idealEdgeLength: function( edge ){ return 10; }, 10088 10089 // Divisor to compute edge forces 10090 edgeElasticity: function( edge ){ return 100; }, 10091 10092 // Nesting factor (multiplier) to compute ideal edge length for nested edges 10093 nestingFactor: 5, 10094 10095 // Gravity force (constant) 10096 gravity: 80, 10097 10098 // Maximum number of iterations to perform 10099 numIter: 1000, 10100 10101 // Initial temperature (maximum node displacement) 10102 initialTemp: 200, 10103 10104 // Cooling factor (how the temperature is reduced between consecutive iterations 10105 coolingFactor: 0.95, 10106 10107 // Lower temperature threshold (below this point the layout will end) 10108 minTemp: 1.0, 10109 10110 // Whether to use threading to speed up the layout 10111 useMultitasking: true 10112}; 10113 10114 10115/** 10116 * @brief : constructor 10117 * @arg options : object containing layout options 10118 */ 10119function CoseLayout( options ){ 10120 this.options = util.extend( {}, defaults, options ); 10121 10122 this.options.layout = this; 10123} 10124 10125 10126/** 10127 * @brief : runs the layout 10128 */ 10129CoseLayout.prototype.run = function(){ 10130 var options = this.options; 10131 var cy = options.cy; 10132 var layout = this; 10133 var thread = this.thread; 10134 10135 if( !thread || thread.stopped() ){ 10136 thread = this.thread = Thread( { disabled: !options.useMultitasking } ); 10137 } 10138 10139 layout.stopped = false; 10140 10141 layout.trigger( { type: 'layoutstart', layout: layout } ); 10142 10143 // Set DEBUG - Global variable 10144 if( true === options.debug ){ 10145 DEBUG = true; 10146 } else { 10147 DEBUG = false; 10148 } 10149 10150 // Initialize layout info 10151 var layoutInfo = createLayoutInfo( cy, layout, options ); 10152 10153 // Show LayoutInfo contents if debugging 10154 if( DEBUG ){ 10155 printLayoutInfo( layoutInfo ); 10156 } 10157 10158 // If required, randomize node positions 10159 if (options.randomize) { 10160 randomizePositions( layoutInfo, cy ); 10161 } 10162 10163 var startTime = Date.now(); 10164 var refreshRequested = false; 10165 var refresh = function( rOpts ){ 10166 rOpts = rOpts || {}; 10167 10168 if( refreshRequested && !rOpts.next ){ 10169 return; 10170 } 10171 10172 if( !rOpts.force && Date.now() - startTime < options.animationThreshold ){ 10173 return; 10174 } 10175 10176 refreshRequested = true; 10177 10178 util.requestAnimationFrame( function(){ 10179 refreshPositions( layoutInfo, cy, options ); 10180 10181 // Fit the graph if necessary 10182 if( true === options.fit ){ 10183 cy.fit( options.padding ); 10184 } 10185 10186 refreshRequested = false; 10187 10188 if( rOpts.next ){ rOpts.next(); } 10189 }); 10190 }; 10191 10192 thread.on( 'message', function( e ){ 10193 var layoutNodes = e.message; 10194 10195 layoutInfo.layoutNodes = layoutNodes; 10196 refresh(); 10197 } ); 10198 10199 thread.pass( { 10200 layoutInfo: layoutInfo, 10201 options: { 10202 animate: options.animate, 10203 refresh: options.refresh, 10204 componentSpacing: options.componentSpacing, 10205 nodeOverlap: options.nodeOverlap, 10206 nestingFactor: options.nestingFactor, 10207 gravity: options.gravity, 10208 numIter: options.numIter, 10209 initialTemp: options.initialTemp, 10210 coolingFactor: options.coolingFactor, 10211 minTemp: options.minTemp 10212 } 10213 } ).run( function( pass ){ 10214 var layoutInfo = pass.layoutInfo; 10215 var options = pass.options; 10216 var stopped = false; 10217 10218 /** 10219 * @brief : Performs one iteration of the physical simulation 10220 * @arg layoutInfo : LayoutInfo object already initialized 10221 * @arg cy : Cytoscape object 10222 * @arg options : Layout options 10223 */ 10224 var step = function( layoutInfo, options, step ){ 10225 // var s = "\n\n###############################"; 10226 // s += "\nSTEP: " + step; 10227 // s += "\n###############################\n"; 10228 // logDebug(s); 10229 10230 // Calculate node repulsions 10231 calculateNodeForces( layoutInfo, options ); 10232 // Calculate edge forces 10233 calculateEdgeForces( layoutInfo, options ); 10234 // Calculate gravity forces 10235 calculateGravityForces( layoutInfo, options ); 10236 // Propagate forces from parent to child 10237 propagateForces( layoutInfo, options ); 10238 // Update positions based on calculated forces 10239 updatePositions( layoutInfo, options ); 10240 }; 10241 10242 /** 10243 * @brief : Computes the node repulsion forces 10244 */ 10245 var calculateNodeForces = function( layoutInfo, options ){ 10246 // Go through each of the graphs in graphSet 10247 // Nodes only repel each other if they belong to the same graph 10248 // var s = 'calculateNodeForces'; 10249 // logDebug(s); 10250 for( var i = 0; i < layoutInfo.graphSet.length; i ++ ){ 10251 var graph = layoutInfo.graphSet[ i ]; 10252 var numNodes = graph.length; 10253 10254 // s = "Set: " + graph.toString(); 10255 // logDebug(s); 10256 10257 // Now get all the pairs of nodes 10258 // Only get each pair once, (A, B) = (B, A) 10259 for( var j = 0; j < numNodes; j++ ){ 10260 var node1 = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ graph[ j ] ] ]; 10261 10262 for( var k = j + 1; k < numNodes; k++ ){ 10263 var node2 = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ graph[ k ] ] ]; 10264 10265 nodeRepulsion( node1, node2, layoutInfo, options ); 10266 } 10267 } 10268 } 10269 }; 10270 10271 /** 10272 * @brief : Compute the node repulsion forces between a pair of nodes 10273 */ 10274 var nodeRepulsion = function( node1, node2, layoutInfo, options ){ 10275 // var s = "Node repulsion. Node1: " + node1.id + " Node2: " + node2.id; 10276 10277 var cmptId1 = node1.cmptId; 10278 var cmptId2 = node2.cmptId; 10279 10280 if( cmptId1 !== cmptId2 && !layoutInfo.isCompound ){ return; } 10281
10282 // Get direction of line connecting both node centers 10283 var directionX = node2.positionX - node1.positionX; 10284 var directionY = node2.positionY - node1.positionY; 10285 // s += "\ndirectionX: " + directionX + ", directionY: " + directionY; 10286 10287 // If both centers are the same, apply a random force 10288 if( 0 === directionX && 0 === directionY ){ 10289 // s += "\nNodes have the same position."; 10290 return; // TODO could be improved with random force 10291 } 10292 10293 var overlap = nodesOverlap( node1, node2, directionX, directionY ); 10294 10295 if( overlap > 0 ){ 10296 // s += "\nNodes DO overlap."; 10297 // s += "\nOverlap: " + overlap; 10298 // If nodes overlap, repulsion force is proportional 10299 // to the overlap 10300 var force = options.nodeOverlap * overlap; 10301 10302 // Compute the module and components of the force vector 10303 var distance = Math.sqrt( directionX * directionX + directionY * directionY ); 10304 // s += "\nDistance: " + distance; 10305 var forceX = force * directionX / distance; 10306 var forceY = force * directionY / distance; 10307 10308 } else { 10309 // s += "\nNodes do NOT overlap."; 10310 // If there's no overlap, force is inversely proportional 10311 // to squared distance 10312 10313 // Get clipping points for both nodes 10314 var point1 = findClippingPoint( node1, directionX, directionY ); 10315 var point2 = findClippingPoint( node2, -1 * directionX, -1 * directionY ); 10316 10317 // Use clipping points to compute distance 10318 var distanceX = point2.x - point1.x; 10319 var distanceY = point2.y - point1.y; 10320 var distanceSqr = distanceX * distanceX + distanceY * distanceY; 10321 var distance = Math.sqrt( distanceSqr ); 10322 // s += "\nDistance: " + distance; 10323 10324 // Compute the module and components of the force vector 10325 var force = ( node1.nodeRepulsion + node2.nodeRepulsion ) / distanceSqr; 10326 var forceX = force * distanceX / distance; 10327 var forceY = force * distanceY / distance; 10328 } 10329 10330 // Apply force 10331 if( !node1.isLocked ){ 10332 node1.offsetX -= forceX; 10333 node1.offsetY -= forceY; 10334 } 10335 10336 if( !node2.isLocked ){ 10337 node2.offsetX += forceX; 10338 node2.offsetY += forceY; 10339 } 10340 10341 // s += "\nForceX: " + forceX + " ForceY: " + forceY; 10342 // logDebug(s); 10343 10344 return; 10345 }; 10346 10347 /** 10348 * @brief : Determines whether two nodes overlap or not 10349 * @return : Amount of overlapping (0 => no overlap) 10350 */ 10351 var nodesOverlap = function( node1, node2, dX, dY ){ 10352 10353 if( dX > 0 ){ 10354 var overlapX = node1.maxX - node2.minX; 10355 } else { 10356 var overlapX = node2.maxX - node1.minX; 10357 } 10358 10359 if( dY > 0 ){ 10360 var overlapY = node1.maxY - node2.minY; 10361 } else { 10362 var overlapY = node2.maxY - node1.minY; 10363 } 10364 10365 if( overlapX >= 0 && overlapY >= 0 ){ 10366 return Math.sqrt( overlapX * overlapX + overlapY * overlapY ); 10367 } else { 10368 return 0; 10369 } 10370 }; 10371 10372 /** 10373 * @brief : Finds the point in which an edge (direction dX, dY) intersects 10374 * the rectangular bounding box of it's source/target node 10375 */ 10376 var findClippingPoint = function( node, dX, dY ){ 10377 10378 // Shorcuts 10379 var X = node.positionX; 10380 var Y = node.positionY; 10381 var H = node.height || 1; 10382 var W = node.width || 1; 10383 var dirSlope = dY / dX; 10384 var nodeSlope = H / W; 10385 10386 // var s = 'Computing clipping point of node ' + node.id + 10387 // " . Height: " + H + ", Width: " + W + 10388 // "\nDirection " + dX + ", " + dY; 10389 // 10390 // Compute intersection 10391 var res = {}; 10392 do { 10393 // Case: Vertical direction (up) 10394 if( 0 === dX && 0 < dY ){ 10395 res.x = X; 10396 // s += "\nUp direction"; 10397 res.y = Y + H / 2; 10398 break; 10399 } 10400 10401 // Case: Vertical direction (down) 10402 if( 0 === dX && 0 > dY ){ 10403 res.x = X; 10404 res.y = Y + H / 2; 10405 // s += "\nDown direction"; 10406 break; 10407 } 10408 10409 // Case: Intersects the right border 10410 if( 0 < dX && 10411 -1 * nodeSlope <= dirSlope && 10412 dirSlope <= nodeSlope ){ 10413 res.x = X + W / 2; 10414 res.y = Y + (W * dY / 2 / dX); 10415 // s += "\nRightborder"; 10416 break; 10417 } 10418 10419 // Case: Intersects the left border 10420 if( 0 > dX && 10421 -1 * nodeSlope <= dirSlope && 10422 dirSlope <= nodeSlope ){ 10423 res.x = X - W / 2; 10424 res.y = Y - (W * dY / 2 / dX); 10425 // s += "\nLeftborder"; 10426 break; 10427 } 10428 10429 // Case: Intersects the top border 10430 if( 0 < dY && 10431 ( dirSlope <= -1 * nodeSlope || 10432 dirSlope >= nodeSlope ) ){ 10433 res.x = X + (H * dX / 2 / dY); 10434 res.y = Y + H / 2; 10435 // s += "\nTop border"; 10436 break; 10437 } 10438 10439 // Case: Intersects the bottom border 10440 if( 0 > dY && 10441 ( dirSlope <= -1 * nodeSlope || 10442 dirSlope >= nodeSlope ) ){ 10443 res.x = X - (H * dX / 2 / dY); 10444 res.y = Y - H / 2; 10445 // s += "\nBottom border"; 10446 break; 10447 } 10448 10449 } while( false); 10450 10451 // s += "\nClipping point found at " + res.x + ", " + res.y; 10452 // logDebug(s); 10453 return res; 10454 }; 10455 10456 /** 10457 * @brief : Calculates all edge forces 10458 */ 10459 var calculateEdgeForces = function( layoutInfo, options ){ 10460 // Iterate over all edges 10461 for( var i = 0; i < layoutInfo.edgeSize; i++ ){ 10462 // Get edge, source & target nodes 10463 var edge = layoutInfo.layoutEdges[ i ]; 10464 var sourceIx = layoutInfo.idToIndex[ edge.sourceId ]; 10465 var source = layoutInfo.layoutNodes[ sourceIx ]; 10466 var targetIx = layoutInfo.idToIndex[ edge.targetId ]; 10467 var target = layoutInfo.layoutNodes[ targetIx ]; 10468
vendor: 8,918 bytes, lines 10469-10729
10469 // Get direction of line connecting both node centers 10470 var directionX = target.positionX - source.positionX; 10471 var directionY = target.positionY - source.positionY; 10472 10473 // If both centers are the same, do nothing. 10474 // A random force has already been applied as node repulsion 10475 if( 0 === directionX && 0 === directionY ){ 10476 return; 10477 } 10478 10479 // Get clipping points for both nodes 10480 var point1 = findClippingPoint( source, directionX, directionY ); 10481 var point2 = findClippingPoint( target, -1 * directionX, -1 * directionY ); 10482 10483 10484 var lx = point2.x - point1.x; 10485 var ly = point2.y - point1.y; 10486 var l = Math.sqrt( lx * lx + ly * ly ); 10487 10488 var force = Math.pow( edge.idealLength - l, 2 ) / edge.elasticity; 10489 10490 if( 0 !== l ){ 10491 var forceX = force * lx / l; 10492 var forceY = force * ly / l; 10493 } else { 10494 var forceX = 0; 10495 var forceY = 0; 10496 } 10497 10498 // Add this force to target and source nodes 10499 if( !source.isLocked ){ 10500 source.offsetX += forceX; 10501 source.offsetY += forceY; 10502 } 10503 10504 if( !target.isLocked ){ 10505 target.offsetX -= forceX; 10506 target.offsetY -= forceY; 10507 } 10508 10509 // var s = 'Edge force between nodes ' + source.id + ' and ' + target.id; 10510 // s += "\nDistance: " + l + " Force: (" + forceX + ", " + forceY + ")"; 10511 // logDebug(s); 10512 } 10513 }; 10514 10515 /** 10516 * @brief : Computes gravity forces for all nodes 10517 */ 10518 var calculateGravityForces = function( layoutInfo, options ){ 10519 var distThreshold = 1; 10520 10521 // var s = 'calculateGravityForces'; 10522 // logDebug(s); 10523 for( var i = 0; i < layoutInfo.graphSet.length; i ++ ){ 10524 var graph = layoutInfo.graphSet[ i ]; 10525 var numNodes = graph.length; 10526 10527 // s = "Set: " + graph.toString(); 10528 // logDebug(s); 10529 10530 // Compute graph center 10531 if( 0 === i ){ 10532 var centerX = layoutInfo.clientHeight / 2; 10533 var centerY = layoutInfo.clientWidth / 2; 10534 } else { 10535 // Get Parent node for this graph, and use its position as center 10536 var temp = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ graph[0] ] ]; 10537 var parent = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ temp.parentId ] ]; 10538 var centerX = parent.positionX; 10539 var centerY = parent.positionY; 10540 } 10541 // s = "Center found at: " + centerX + ", " + centerY; 10542 // logDebug(s); 10543 10544 // Apply force to all nodes in graph 10545 for( var j = 0; j < numNodes; j++ ){ 10546 var node = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ graph[ j ] ] ]; 10547 // s = "Node: " + node.id; 10548 10549 if( node.isLocked ){ continue; } 10550 10551 var dx = centerX - node.positionX; 10552 var dy = centerY - node.positionY; 10553 var d = Math.sqrt( dx * dx + dy * dy ); 10554 if( d > distThreshold ){ 10555 var fx = options.gravity * dx / d; 10556 var fy = options.gravity * dy / d; 10557 node.offsetX += fx; 10558 node.offsetY += fy; 10559 // s += ": Applied force: " + fx + ", " + fy; 10560 } else { 10561 // s += ": skypped since it's too close to center"; 10562 } 10563 // logDebug(s); 10564 } 10565 } 10566 }; 10567 10568 /** 10569 * @brief : This function propagates the existing offsets from 10570 * parent nodes to its descendents. 10571 * @arg layoutInfo : layoutInfo Object 10572 * @arg cy : cytoscape Object 10573 * @arg options : Layout options 10574 */ 10575 var propagateForces = function( layoutInfo, options ){ 10576 // Inline implementation of a queue, used for traversing the graph in BFS order 10577 var queue = []; 10578 var start = 0; // Points to the start the queue 10579 var end = -1; // Points to the end of the queue 10580 10581 // logDebug('propagateForces'); 10582 10583 // Start by visiting the nodes in the root graph 10584 queue.push.apply( queue, layoutInfo.graphSet[0] ); 10585 end += layoutInfo.graphSet[0].length; 10586 10587 // Traverse the graph, level by level, 10588 while( start <= end ){ 10589 // Get the node to visit and remove it from queue 10590 var nodeId = queue[ start++ ]; 10591 var nodeIndex = layoutInfo.idToIndex[ nodeId ]; 10592 var node = layoutInfo.layoutNodes[ nodeIndex ]; 10593 var children = node.children; 10594 10595 // We only need to process the node if it's compound 10596 if( 0 < children.length && !node.isLocked ){ 10597 var offX = node.offsetX; 10598 var offY = node.offsetY; 10599 10600 // var s = "Propagating offset from parent node : " + node.id + 10601 // ". OffsetX: " + offX + ". OffsetY: " + offY; 10602 // s += "\n Children: " + children.toString(); 10603 // logDebug(s); 10604 10605 for( var i = 0; i < children.length; i++ ){ 10606 var childNode = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ children[ i ] ] ]; 10607 // Propagate offset 10608 childNode.offsetX += offX; 10609 childNode.offsetY += offY; 10610 // Add children to queue to be visited 10611 queue[ ++end ] = children[ i ]; 10612 } 10613 10614 // Reset parent offsets 10615 node.offsetX = 0; 10616 node.offsetY = 0; 10617 } 10618 10619 } 10620 }; 10621 10622 /** 10623 * @brief : Updates the layout model positions, based on 10624 * the accumulated forces 10625 */ 10626 var updatePositions = function( layoutInfo, options ){ 10627 // var s = 'Updating positions'; 10628 // logDebug(s); 10629 10630 // Reset boundaries for compound nodes 10631 for( var i = 0; i < layoutInfo.nodeSize; i++ ){ 10632 var n = layoutInfo.layoutNodes[ i ]; 10633 if( 0 < n.children.length ){ 10634 // logDebug("Resetting boundaries of compound node: " + n.id); 10635 n.maxX = undefined; 10636 n.minX = undefined; 10637 n.maxY = undefined; 10638 n.minY = undefined; 10639 } 10640 } 10641 10642 for( var i = 0; i < layoutInfo.nodeSize; i++ ){ 10643 var n = layoutInfo.layoutNodes[ i ]; 10644 if( 0 < n.children.length || n.isLocked ){ 10645 // No need to set compound or locked node position 10646 // logDebug("Skipping position update of node: " + n.id); 10647 continue; 10648 } 10649 // s = "Node: " + n.id + " Previous position: (" + 10650 // n.positionX + ", " + n.positionY + ")."; 10651 10652 // Limit displacement in order to improve stability 10653 var tempForce = limitForce( n.offsetX, n.offsetY, layoutInfo.temperature ); 10654 n.positionX += tempForce.x; 10655 n.positionY += tempForce.y; 10656 n.offsetX = 0; 10657 n.offsetY = 0; 10658 n.minX = n.positionX - n.width; 10659 n.maxX = n.positionX + n.width; 10660 n.minY = n.positionY - n.height; 10661 n.maxY = n.positionY + n.height; 10662 // s += " New Position: (" + n.positionX + ", " + n.positionY + ")."; 10663 // logDebug(s); 10664 10665 // Update ancestry boudaries 10666 updateAncestryBoundaries( n, layoutInfo ); 10667 } 10668 10669 // Update size, position of compund nodes 10670 for( var i = 0; i < layoutInfo.nodeSize; i++ ){ 10671 var n = layoutInfo.layoutNodes[ i ]; 10672 if( 0 < n.children.length && !n.isLocked ){ 10673 n.positionX = (n.maxX + n.minX) / 2; 10674 n.positionY = (n.maxY + n.minY) / 2; 10675 n.width = n.maxX - n.minX; 10676 n.height = n.maxY - n.minY; 10677 // s = "Updating position, size of compound node " + n.id; 10678 // s += "\nPositionX: " + n.positionX + ", PositionY: " + n.positionY; 10679 // s += "\nWidth: " + n.width + ", Height: " + n.height; 10680 // logDebug(s); 10681 } 10682 } 10683 }; 10684 10685 /** 10686 * @brief : Limits a force (forceX, forceY) to be not 10687 * greater (in modulo) than max. 10688 8 Preserves force direction. 10689 */ 10690 var limitForce = function( forceX, forceY, max ){ 10691 // var s = "Limiting force: (" + forceX + ", " + forceY + "). Max: " + max; 10692 var force = Math.sqrt( forceX * forceX + forceY * forceY ); 10693 10694 if( force > max ){ 10695 var res = { 10696 x: max * forceX / force, 10697 y: max * forceY / force 10698 }; 10699 10700 } else { 10701 var res = { 10702 x: forceX, 10703 y: forceY 10704 }; 10705 } 10706 10707 // s += ".\nResult: (" + res.x + ", " + res.y + ")"; 10708 // logDebug(s); 10709 10710 return res; 10711 }; 10712 10713 /** 10714 * @brief : Function used for keeping track of compound node 10715 * sizes, since they should bound all their subnodes. 10716 */ 10717 var updateAncestryBoundaries = function( node, layoutInfo ){ 10718 // var s = "Propagating new position/size of node " + node.id; 10719 var parentId = node.parentId; 10720 if( null == parentId ){ 10721 // If there's no parent, we are done 10722 // s += ". No parent node."; 10723 // logDebug(s); 10724 return; 10725 } 10726 10727 // Get Parent Node 10728 var p = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ parentId ] ]; 10729 var flag = false;
10730 10731 // MaxX 10732 if( null == p.maxX || node.maxX + p.padRight > p.maxX ){ 10733 p.maxX = node.maxX + p.padRight; 10734 flag = true; 10735 // s += "\nNew maxX for parent node " + p.id + ": " + p.maxX; 10736 } 10737 10738 // MinX 10739 if( null == p.minX || node.minX - p.padLeft < p.minX ){ 10740 p.minX = node.minX - p.padLeft; 10741 flag = true; 10742 // s += "\nNew minX for parent node " + p.id + ": " + p.minX; 10743 } 10744 10745 // MaxY 10746 if( null == p.maxY || node.maxY + p.padBottom > p.maxY ){ 10747 p.maxY = node.maxY + p.padBottom; 10748 flag = true; 10749 // s += "\nNew maxY for parent node " + p.id + ": " + p.maxY; 10750 } 10751 10752 // MinY 10753 if( null == p.minY || node.minY - p.padTop < p.minY ){ 10754 p.minY = node.minY - p.padTop; 10755 flag = true; 10756 // s += "\nNew minY for parent node " + p.id + ": " + p.minY; 10757 } 10758 10759 // If updated boundaries, propagate changes upward 10760 if( flag ){ 10761 // logDebug(s); 10762 return updateAncestryBoundaries( p, layoutInfo ); 10763 } 10764 10765 // s += ". No changes in boundaries/position of parent node " + p.id; 10766 // logDebug(s); 10767 return; 10768 }; 10769 10770 var separateComponents = function( layutInfo, options ){ 10771 var nodes = layoutInfo.layoutNodes; 10772 var components = []; 10773 10774 for( var i = 0; i < nodes.length; i++ ){ 10775 var node = nodes[ i ]; 10776 var cid = node.cmptId; 10777 var component = components[ cid ] = components[ cid ] || []; 10778 10779 component.push( node ); 10780 } 10781 10782 var totalA = 0; 10783 10784 for( var i = 0; i < components.length; i++ ){ 10785 var c = components[ i ]; 10786 10787 if( !c ){ continue; } 10788 10789 c.x1 = Infinity; 10790 c.x2 = -Infinity; 10791 c.y1 = Infinity; 10792 c.y2 = -Infinity; 10793 10794 for( var j = 0; j < c.length; j++ ){ 10795 var n = c[ j ]; 10796 10797 c.x1 = Math.min( c.x1, n.positionX - n.width / 2 ); 10798 c.x2 = Math.max( c.x2, n.positionX + n.width / 2 ); 10799 c.y1 = Math.min( c.y1, n.positionY - n.height / 2 ); 10800 c.y2 = Math.max( c.y2, n.positionY + n.height / 2 ); 10801 } 10802 10803 c.w = c.x2 - c.x1; 10804 c.h = c.y2 - c.y1; 10805 10806 totalA += c.w * c.h; 10807 } 10808 10809 components.sort( function( c1, c2 ){ 10810 return c2.w * c2.h - c1.w * c1.h; 10811 } ); 10812 10813 var x = 0; 10814 var y = 0; 10815 var usedW = 0; 10816 var rowH = 0; 10817 var maxRowW = Math.sqrt( totalA ) * layoutInfo.clientWidth / layoutInfo.clientHeight; 10818 10819 for( var i = 0; i < components.length; i++ ){ 10820 var c = components[ i ]; 10821 10822 if( !c ){ continue; } 10823 10824 for( var j = 0; j < c.length; j++ ){ 10825 var n = c[ j ]; 10826 10827 if( !n.isLocked ){ 10828 n.positionX += x; 10829 n.positionY += y; 10830 } 10831 } 10832 10833 x += c.w + options.componentSpacing; 10834 usedW += c.w + options.componentSpacing; 10835 rowH = Math.max( rowH, c.h ); 10836 10837 if( usedW > maxRowW ){ 10838 y += rowH + options.componentSpacing; 10839 x = 0; 10840 usedW = 0; 10841 rowH = 0; 10842 } 10843 } 10844 }; 10845 10846 var mainLoop = function( i ){ 10847 if( stopped ){ 10848 // logDebug("Layout manually stopped. Stopping computation in step " + i); 10849 return false; 10850 } 10851 10852 // Do one step in the phisical simulation 10853 step( layoutInfo, options, i ); 10854 10855 // Update temperature 10856 layoutInfo.temperature = layoutInfo.temperature * options.coolingFactor; 10857 // logDebug("New temperature: " + layoutInfo.temperature); 10858 10859 if( layoutInfo.temperature < options.minTemp ){ 10860 // logDebug("Temperature drop below minimum threshold. Stopping computation in step " + i); 10861 return false; 10862 } 10863 10864 return true; 10865 }; 10866 10867 var i = 0; 10868 var loopRet; 10869 10870 do { 10871 var f = 0; 10872 10873 while( f < options.refresh && i < options.numIter ){ 10874 var loopRet = mainLoop( i ); 10875 if( !loopRet ){ break; } 10876 10877 f++; 10878 i++; 10879 } 10880 10881 if( options.animate ){ 10882 broadcast( layoutInfo.layoutNodes ); // eslint-disable-line no-undef 10883 } 10884 10885 } while( loopRet && i + 1 < options.numIter ); 10886 10887 separateComponents( layoutInfo, options ); 10888 10889 return layoutInfo; 10890 } ).then( function( layoutInfoUpdated ){ 10891 layoutInfo.layoutNodes = layoutInfoUpdated.layoutNodes; // get the positions 10892 10893 thread.stop(); 10894 done(); 10895 } ); 10896 10897 var done = function(){ 10898 refresh({ 10899 force: true, 10900 next: function(){ 10901 // Layout has finished 10902 layout.one('layoutstop', options.stop); 10903 layout.trigger({ type: 'layoutstop', layout: layout }); 10904 } 10905 }); 10906 }; 10907 10908 return this; // chaining 10909}; 10910 10911 10912/** 10913 * @brief : called on continuous layouts to stop them before they finish 10914 */ 10915CoseLayout.prototype.stop = function(){ 10916 this.stopped = true; 10917 10918 if( this.thread ){ 10919 this.thread.stop(); 10920 } 10921 10922 this.trigger( 'layoutstop' ); 10923 10924 return this; // chaining 10925}; 10926 10927CoseLayout.prototype.destroy = function(){ 10928 if( this.thread ){ 10929 this.thread.stop(); 10930 } 10931 10932 return this; // chaining 10933}; 10934 10935 10936/** 10937 * @brief : Creates an object which is contains all the data 10938 * used in the layout process 10939 * @arg cy : cytoscape.js object 10940 * @return : layoutInfo object initialized 10941 */ 10942var createLayoutInfo = function( cy, layout, options ){ 10943 // Shortcut 10944 var edges = options.eles.edges(); 10945 var nodes = options.eles.nodes(); 10946 10947 var layoutInfo = { 10948 isCompound: cy.hasCompoundNodes(), 10949 layoutNodes: [], 10950 idToIndex: {}, 10951 nodeSize: nodes.size(), 10952 graphSet: [], 10953 indexToGraph: [], 10954 layoutEdges: [], 10955 edgeSize: edges.size(), 10956 temperature: options.initialTemp, 10957 clientWidth: cy.width(), 10958 clientHeight: cy.width(), 10959 boundingBox: math.makeBoundingBox( options.boundingBox ? options.boundingBox : { 10960 x1: 0, y1: 0, w: cy.width(), h: cy.height() 10961 } ) 10962 }; 10963 10964 var components = options.eles.components(); 10965 var id2cmptId = {}; 10966 10967 for( var i = 0; i < components.length; i++ ){ 10968 var component = components[ i ]; 10969 10970 for( var j = 0; j < component.length; j++ ){ 10971 var node = component[ j ]; 10972 10973 id2cmptId[ node.id() ] = i; 10974 } 10975 } 10976 10977 // Iterate over all nodes, creating layout nodes 10978 for( var i = 0; i < layoutInfo.nodeSize; i++ ){ 10979 var n = nodes[ i ]; 10980 var nbb = n.boundingBox(); 10981 10982 var tempNode = {}; 10983 tempNode.isLocked = n.locked(); 10984 tempNode.id = n.data( 'id' ); 10985 tempNode.parentId = n.data( 'parent' ); 10986 tempNode.cmptId = id2cmptId[ n.id() ]; 10987 tempNode.children = []; 10988 tempNode.positionX = n.position( 'x' ); 10989 tempNode.positionY = n.position( 'y' ); 10990 tempNode.offsetX = 0; 10991 tempNode.offsetY = 0; 10992 tempNode.height = nbb.w; 10993 tempNode.width = nbb.h; 10994 tempNode.maxX = tempNode.positionX + tempNode.width / 2; 10995 tempNode.minX = tempNode.positionX - tempNode.width / 2; 10996 tempNode.maxY = tempNode.positionY + tempNode.height / 2; 10997 tempNode.minY = tempNode.positionY - tempNode.height / 2; 10998 tempNode.padLeft = parseFloat( n.style( 'padding-left' ) ); 10999 tempNode.padRight = parseFloat( n.style( 'padding-right' ) ); 11000 tempNode.padTop = parseFloat( n.style( 'padding-top' ) ); 11001 tempNode.padBottom = parseFloat( n.style( 'padding-bottom' ) ); 11002 11003 // forces 11004 tempNode.nodeRepulsion = is.fn( options.nodeRepulsion ) ? options.nodeRepulsion.call( n, n ) : options.nodeRepulsion; 11005 11006 // Add new node 11007 layoutInfo.layoutNodes.push( tempNode ); 11008 // Add entry to id-index map 11009 layoutInfo.idToIndex[ tempNode.id ] = i; 11010 } 11011 11012 // Inline implementation of a queue, used for traversing the graph in BFS order 11013 var queue = []; 11014 var start = 0; // Points to the start the queue 11015 var end = -1; // Points to the end of the queue 11016 11017 var tempGraph = []; 11018 11019 // Second pass to add child information and 11020 // initialize queue for hierarchical traversal 11021 for( var i = 0; i < layoutInfo.nodeSize; i++ ){ 11022 var n = layoutInfo.layoutNodes[ i ]; 11023 var p_id = n.parentId; 11024 // Check if node n has a parent node 11025 if( null != p_id ){ 11026 // Add node Id to parent's list of children 11027 layoutInfo.layoutNodes[ layoutInfo.idToIndex[ p_id ] ].children.push( n.id ); 11028 } else { 11029 // If a node doesn't have a parent, then it's in the root graph 11030 queue[ ++end ] = n.id; 11031 tempGraph.push( n.id ); 11032 } 11033 } 11034 11035 // Add root graph to graphSet 11036 layoutInfo.graphSet.push( tempGraph ); 11037 11038 // Traverse the graph, level by level, 11039 while( start <= end ){ 11040 // Get the node to visit and remove it from queue 11041 var node_id = queue[ start++ ]; 11042 var node_ix = layoutInfo.idToIndex[ node_id ]; 11043 var node = layoutInfo.layoutNodes[ node_ix ]; 11044 var children = node.children; 11045 if( children.length > 0 ){ 11046 // Add children nodes as a new graph to graph set 11047 layoutInfo.graphSet.push( children ); 11048 // Add children to que queue to be visited 11049 for( var i = 0; i < children.length; i++ ){ 11050 queue[ ++end ] = children[ i ]; 11051 } 11052 } 11053 } 11054 11055 // Create indexToGraph map 11056 for( var i = 0; i < layoutInfo.graphSet.length; i++ ){ 11057 var graph = layoutInfo.graphSet[ i ]; 11058 for( var j = 0; j < graph.length; j++ ){ 11059 var index = layoutInfo.idToIndex[ graph[ j ] ]; 11060 layoutInfo.indexToGraph[ index ] = i; 11061 } 11062 } 11063 11064 // Iterate over all edges, creating Layout Edges 11065 for( var i = 0; i < layoutInfo.edgeSize; i++ ){ 11066 var e = edges[ i ]; 11067 var tempEdge = {}; 11068 tempEdge.id = e.data( 'id' ); 11069 tempEdge.sourceId = e.data( 'source' ); 11070 tempEdge.targetId = e.data( 'target' ); 11071 11072 // Compute ideal length 11073 var idealLength = is.fn( options.idealEdgeLength ) ? options.idealEdgeLength.call( e, e ) : options.idealEdgeLength; 11074 var elasticity = is.fn( options.edgeElasticity ) ? options.edgeElasticity.call( e, e ) : options.edgeElasticity; 11075 11076 // Check if it's an inter graph edge 11077 var sourceIx = layoutInfo.idToIndex[ tempEdge.sourceId ]; 11078 var targetIx = layoutInfo.idToIndex[ tempEdge.targetId ]; 11079 var sourceGraph = layoutInfo.indexToGraph[ sourceIx ]; 11080 var targetGraph = layoutInfo.indexToGraph[ targetIx ]; 11081 11082 if( sourceGraph != targetGraph ){ 11083 // Find lowest common graph ancestor 11084 var lca = findLCA( tempEdge.sourceId, tempEdge.targetId, layoutInfo ); 11085 11086 // Compute sum of node depths, relative to lca graph 11087 var lcaGraph = layoutInfo.graphSet[ lca ]; 11088 var depth = 0; 11089 11090 // Source depth 11091 var tempNode = layoutInfo.layoutNodes[ sourceIx ];
11092 while( -1 === lcaGraph.indexOf( tempNode.id ) ){ 11093 tempNode = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ tempNode.parentId ] ]; 11094 depth++; 11095 } 11096 11097 // Target depth 11098 tempNode = layoutInfo.layoutNodes[ targetIx ]; 11099 while( -1 === lcaGraph.indexOf( tempNode.id ) ){ 11100 tempNode = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ tempNode.parentId ] ]; 11101 depth++; 11102 } 11103 11104 // logDebug('LCA of nodes ' + tempEdge.sourceId + ' and ' + tempEdge.targetId + 11105 // ". Index: " + lca + " Contents: " + lcaGraph.toString() + 11106 // ". Depth: " + depth); 11107 11108 // Update idealLength 11109 idealLength *= depth * options.nestingFactor; 11110 } 11111 11112 tempEdge.idealLength = idealLength; 11113 tempEdge.elasticity = elasticity; 11114 11115 layoutInfo.layoutEdges.push( tempEdge ); 11116 } 11117 11118 // Finally, return layoutInfo object 11119 return layoutInfo; 11120}; 11121 11122 11123/** 11124 * @brief : This function finds the index of the lowest common 11125 * graph ancestor between 2 nodes in the subtree 11126 * (from the graph hierarchy induced tree) whose 11127 * root is graphIx 11128 * 11129 * @arg node1: node1's ID 11130 * @arg node2: node2's ID 11131 * @arg layoutInfo: layoutInfo object 11132 * 11133 */ 11134var findLCA = function( node1, node2, layoutInfo ){ 11135 // Find their common ancester, starting from the root graph 11136 var res = findLCA_aux( node1, node2, 0, layoutInfo ); 11137 if( 2 > res.count ){ 11138 // If aux function couldn't find the common ancester, 11139 // then it is the root graph 11140 return 0; 11141 } else { 11142 return res.graph; 11143 } 11144}; 11145 11146 11147/** 11148 * @brief : Auxiliary function used for LCA computation 11149 * 11150 * @arg node1 : node1's ID 11151 * @arg node2 : node2's ID 11152 * @arg graphIx : subgraph index 11153 * @arg layoutInfo : layoutInfo object 11154 * 11155 * @return : object of the form {count: X, graph: Y}, where: 11156 * X is the number of ancesters (max: 2) found in 11157 * graphIx (and it's subgraphs), 11158 * Y is the graph index of the lowest graph containing 11159 * all X nodes 11160 */ 11161var findLCA_aux = function( node1, node2, graphIx, layoutInfo ){ 11162 var graph = layoutInfo.graphSet[ graphIx ]; 11163 // If both nodes belongs to graphIx 11164 if( -1 < graph.indexOf( node1 ) && -1 < graph.indexOf( node2 ) ){ 11165 return {count: 2, graph: graphIx}; 11166 } 11167 11168 // Make recursive calls for all subgraphs 11169 var c = 0; 11170 for( var i = 0; i < graph.length; i++ ){ 11171 var nodeId = graph[ i ]; 11172 var nodeIx = layoutInfo.idToIndex[ nodeId ]; 11173 var children = layoutInfo.layoutNodes[ nodeIx ].children; 11174 11175 // If the node has no child, skip it 11176 if( 0 === children.length ){ 11177 continue; 11178 } 11179 11180 var childGraphIx = layoutInfo.indexToGraph[ layoutInfo.idToIndex[ children[0] ] ]; 11181 var result = findLCA_aux( node1, node2, childGraphIx, layoutInfo ); 11182 if( 0 === result.count ){ 11183 // Neither node1 nor node2 are present in this subgraph 11184 continue; 11185 } else if( 1 === result.count ){ 11186 // One of (node1, node2) is present in this subgraph 11187 c++; 11188 if( 2 === c ){ 11189 // We've already found both nodes, no need to keep searching 11190 break; 11191 } 11192 } else { 11193 // Both nodes are present in this subgraph 11194 return result; 11195 } 11196 } 11197 11198 return {count: c, graph: graphIx}; 11199}; 11200 11201 11202/** 11203 * @brief: printsLayoutInfo into js console 11204 * Only used for debbuging 11205 */ 11206var printLayoutInfo = function( layoutInfo ){ 11207 /* eslint-disable */ 11208 11209 if( !DEBUG ){ 11210 return; 11211 } 11212 console.debug( 'layoutNodes:' ); 11213 for( var i = 0; i < layoutInfo.nodeSize; i++ ){ 11214 var n = layoutInfo.layoutNodes[ i ]; 11215 var s = 11216 '\nindex: ' + i + 11217 '\nId: ' + n.id + 11218 '\nChildren: ' + n.children.toString() + 11219 '\nparentId: ' + n.parentId + 11220 '\npositionX: ' + n.positionX + 11221 '\npositionY: ' + n.positionY + 11222 '\nOffsetX: ' + n.offsetX + 11223 '\nOffsetY: ' + n.offsetY + 11224 '\npadLeft: ' + n.padLeft + 11225 '\npadRight: ' + n.padRight + 11226 '\npadTop: ' + n.padTop + 11227 '\npadBottom: ' + n.padBottom; 11228 11229 console.debug( s ); 11230 } 11231 11232 console.debug( 'idToIndex' ); 11233 for( var i in layoutInfo.idToIndex ){ 11234 console.debug( 'Id: ' + i + '\nIndex: ' + layoutInfo.idToIndex[ i ] ); 11235 } 11236 11237 console.debug( 'Graph Set' ); 11238 var set = layoutInfo.graphSet; 11239 for( var i = 0; i < set.length; i ++ ){ 11240 console.debug( 'Set : ' + i + ': ' + set[ i ].toString() ); 11241 } 11242 11243 var s = 'IndexToGraph'; 11244 for( var i = 0; i < layoutInfo.indexToGraph.length; i ++ ){ 11245 s += '\nIndex : ' + i + ' Graph: ' + layoutInfo.indexToGraph[ i ]; 11246 } 11247 console.debug( s ); 11248 11249 s = 'Layout Edges'; 11250 for( var i = 0; i < layoutInfo.layoutEdges.length; i++ ){ 11251 var e = layoutInfo.layoutEdges[ i ]; 11252 s += '\nEdge Index: ' + i + ' ID: ' + e.id + 11253 ' SouceID: ' + e.sourceId + ' TargetId: ' + e.targetId + 11254 ' Ideal Length: ' + e.idealLength; 11255 } 11256 console.debug( s ); 11257 11258 s = 'nodeSize: ' + layoutInfo.nodeSize; 11259 s += '\nedgeSize: ' + layoutInfo.edgeSize; 11260 s += '\ntemperature: ' + layoutInfo.temperature; 11261 console.debug( s ); 11262 11263 return; 11264 /* eslint-enable */ 11265}; 11266 11267 11268/** 11269 * @brief : Randomizes the position of all nodes 11270 */ 11271var randomizePositions = function( layoutInfo, cy ){ 11272 var width = layoutInfo.clientWidth; 11273 var height = layoutInfo.clientHeight; 11274 11275 for( var i = 0; i < layoutInfo.nodeSize; i++ ){ 11276 var n = layoutInfo.layoutNodes[ i ]; 11277 11278 // No need to randomize compound nodes or locked nodes 11279 if( 0 === n.children.length && !n.isLocked ){ 11280 n.positionX = Math.random() * width; 11281 n.positionY = Math.random() * height; 11282 } 11283 } 11284}; 11285 11286 11287/** 11288 * @brief : Updates the positions of nodes in the network 11289 * @arg layoutInfo : LayoutInfo object 11290 * @arg cy : Cytoscape object 11291 * @arg options : Layout options 11292 */ 11293var refreshPositions = function( layoutInfo, cy, options ){ 11294 // var s = 'Refreshing positions'; 11295 // logDebug(s); 11296 11297 var layout = options.layout; 11298 var nodes = options.eles.nodes(); 11299 var bb = layoutInfo.boundingBox; 11300 var coseBB = { x1: Infinity, x2: -Infinity, y1: Infinity, y2: -Infinity }; 11301 11302 if( options.boundingBox ){
11303 nodes.forEach( function( node ){ 11304 var lnode = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ node.data( 'id' ) ] ]; 11305 11306 coseBB.x1 = Math.min( coseBB.x1, lnode.positionX ); 11307 coseBB.x2 = Math.max( coseBB.x2, lnode.positionX ); 11308 11309 coseBB.y1 = Math.min( coseBB.y1, lnode.positionY ); 11310 coseBB.y2 = Math.max( coseBB.y2, lnode.positionY ); 11311 } ); 11312 11313 coseBB.w = coseBB.x2 - coseBB.x1; 11314 coseBB.h = coseBB.y2 - coseBB.y1; 11315 } 11316 11317 nodes.positions( function( i, ele ){ 11318 var lnode = layoutInfo.layoutNodes[ layoutInfo.idToIndex[ ele.data( 'id' ) ] ]; 11319 // s = "Node: " + lnode.id + ". Refreshed position: (" + 11320 // lnode.positionX + ", " + lnode.positionY + ")."; 11321 // logDebug(s); 11322 11323 if( options.boundingBox ){ // then add extra bounding box constraint 11324 var pctX = (lnode.positionX - coseBB.x1) / coseBB.w; 11325 var pctY = (lnode.positionY - coseBB.y1) / coseBB.h; 11326 11327 return { 11328 x: bb.x1 + pctX * bb.w, 11329 y: bb.y1 + pctY * bb.h 11330 }; 11331 } else { 11332 return { 11333 x: lnode.positionX, 11334 y: lnode.positionY 11335 }; 11336 } 11337 } ); 11338 11339 // Trigger layoutReady only on first call 11340 if( true !== layoutInfo.ready ){ 11341 // s = 'Triggering layoutready'; 11342 // logDebug(s); 11343 layoutInfo.ready = true; 11344 layout.one( 'layoutready', options.ready ); 11345 layout.trigger( { type: 'layoutready', layout: this } ); 11346 } 11347}; 11348 11349/** 11350 * @brief : Logs a debug message in JS console, if DEBUG is ON 11351 */ 11352// var logDebug = function(text) { 11353// if (DEBUG) { 11354// console.debug(text); 11355// } 11356// }; 11357 11358module.exports = CoseLayout; 11359 11360},{"../../is":83,"../../math":85,"../../thread":98,"../../util":100}],52:[function(_dereq_,module,exports){ 11361'use strict'; 11362 11363var util = _dereq_( '../../util' ); 11364var math = _dereq_( '../../math' ); 11365 11366var defaults = { 11367 fit: true, // whether to fit the viewport to the graph 11368 padding: 30, // padding used on fit 11369 boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 11370 avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space 11371 avoidOverlapPadding: 10, // extra spacing around nodes when avoidOverlap: true 11372 condense: false, // uses all available space on false, uses minimal space on true 11373 rows: undefined, // force num of rows in the grid 11374 cols: undefined, // force num of columns in the grid 11375 position: function( node ){}, // returns { row, col } for element 11376 sort: undefined, // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } 11377 animate: false, // whether to transition the node positions 11378 animationDuration: 500, // duration of animation in ms if enabled 11379 animationEasing: undefined, // easing of animation if enabled 11380 ready: undefined, // callback on layoutready 11381 stop: undefined // callback on layoutstop 11382}; 11383 11384function GridLayout( options ){ 11385 this.options = util.extend( {}, defaults, options ); 11386} 11387 11388GridLayout.prototype.run = function(){ 11389 var params = this.options; 11390 var options = params; 11391 11392 var cy = params.cy; 11393 var eles = options.eles; 11394 var nodes = eles.nodes().not( ':parent' ); 11395 11396 if( options.sort ){ 11397 nodes = nodes.sort( options.sort ); 11398 } 11399 11400 var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : { 11401 x1: 0, y1: 0, w: cy.width(), h: cy.height() 11402 } ); 11403 11404 if( bb.h === 0 || bb.w === 0 ){ 11405 nodes.layoutPositions( this, options, function(){ 11406 return { x: bb.x1, y: bb.y1 }; 11407 } ); 11408 11409 } else { 11410 11411 // width/height * splits^2 = cells where splits is number of times to split width 11412 var cells = nodes.size(); 11413 var splits = Math.sqrt( cells * bb.h / bb.w ); 11414 var rows = Math.round( splits ); 11415 var cols = Math.round( bb.w / bb.h * splits ); 11416 11417 var small = function( val ){ 11418 if( val == null ){ 11419 return Math.min( rows, cols ); 11420 } else { 11421 var min = Math.min( rows, cols ); 11422 if( min == rows ){ 11423 rows = val; 11424 } else { 11425 cols = val; 11426 } 11427 } 11428 }; 11429 11430 var large = function( val ){ 11431 if( val == null ){ 11432 return Math.max( rows, cols ); 11433 } else { 11434 var max = Math.max( rows, cols ); 11435 if( max == rows ){ 11436 rows = val; 11437 } else { 11438 cols = val; 11439 } 11440 } 11441 }; 11442 11443 var oRows = options.rows; 11444 var oCols = options.cols != null ? options.cols : options.columns; 11445
vendor: 3,537 bytes, lines 11446-11583
11446 // if rows or columns were set in options, use those values 11447 if( oRows != null && oCols != null ){ 11448 rows = oRows; 11449 cols = oCols; 11450 } else if( oRows != null && oCols == null ){ 11451 rows = oRows; 11452 cols = Math.ceil( cells / rows ); 11453 } else if( oRows == null && oCols != null ){ 11454 cols = oCols; 11455 rows = Math.ceil( cells / cols ); 11456 } 11457 11458 // otherwise use the automatic values and adjust accordingly 11459 11460 // if rounding was up, see if we can reduce rows or columns 11461 else if( cols * rows > cells ){ 11462 var sm = small(); 11463 var lg = large(); 11464 11465 // reducing the small side takes away the most cells, so try it first 11466 if( (sm - 1) * lg >= cells ){ 11467 small( sm - 1 ); 11468 } else if( (lg - 1) * sm >= cells ){ 11469 large( lg - 1 ); 11470 } 11471 } else { 11472 11473 // if rounding was too low, add rows or columns 11474 while( cols * rows < cells ){ 11475 var sm = small(); 11476 var lg = large(); 11477 11478 // try to add to larger side first (adds less in multiplication) 11479 if( (lg + 1) * sm >= cells ){ 11480 large( lg + 1 ); 11481 } else { 11482 small( sm + 1 ); 11483 } 11484 } 11485 } 11486 11487 var cellWidth = bb.w / cols; 11488 var cellHeight = bb.h / rows; 11489 11490 if( options.condense ){ 11491 cellWidth = 0; 11492 cellHeight = 0; 11493 } 11494 11495 if( options.avoidOverlap ){ 11496 for( var i = 0; i < nodes.length; i++ ){ 11497 var node = nodes[ i ]; 11498 var pos = node._private.position; 11499 11500 if( pos.x == null || pos.y == null ){ // for bb 11501 pos.x = 0; 11502 pos.y = 0; 11503 } 11504 11505 var nbb = node.boundingBox(); 11506 var p = options.avoidOverlapPadding; 11507 11508 var w = nbb.w + p; 11509 var h = nbb.h + p; 11510 11511 cellWidth = Math.max( cellWidth, w ); 11512 cellHeight = Math.max( cellHeight, h ); 11513 } 11514 } 11515 11516 var cellUsed = {}; // e.g. 'c-0-2' => true 11517 11518 var used = function( row, col ){ 11519 return cellUsed[ 'c-' + row + '-' + col ] ? true : false; 11520 }; 11521 11522 var use = function( row, col ){ 11523 cellUsed[ 'c-' + row + '-' + col ] = true; 11524 }; 11525 11526 // to keep track of current cell position 11527 var row = 0; 11528 var col = 0; 11529 var moveToNextCell = function(){ 11530 col++; 11531 if( col >= cols ){ 11532 col = 0; 11533 row++; 11534 } 11535 }; 11536 11537 // get a cache of all the manual positions 11538 var id2manPos = {}; 11539 for( var i = 0; i < nodes.length; i++ ){ 11540 var node = nodes[ i ]; 11541 var rcPos = options.position( node ); 11542 11543 if( rcPos && (rcPos.row !== undefined || rcPos.col !== undefined) ){ // must have at least row or col def'd 11544 var pos = { 11545 row: rcPos.row, 11546 col: rcPos.col 11547 }; 11548 11549 if( pos.col === undefined ){ // find unused col 11550 pos.col = 0; 11551 11552 while( used( pos.row, pos.col ) ){ 11553 pos.col++; 11554 } 11555 } else if( pos.row === undefined ){ // find unused row 11556 pos.row = 0; 11557 11558 while( used( pos.row, pos.col ) ){ 11559 pos.row++; 11560 } 11561 } 11562 11563 id2manPos[ node.id() ] = pos; 11564 use( pos.row, pos.col ); 11565 } 11566 } 11567 11568 var getPos = function( i, element ){ 11569 var x, y; 11570 11571 if( element.locked() || element.isParent() ){ 11572 return false; 11573 } 11574 11575 // see if we have a manual position set 11576 var rcPos = id2manPos[ element.id() ]; 11577 if( rcPos ){ 11578 x = rcPos.col * cellWidth + cellWidth / 2 + bb.x1; 11579 y = rcPos.row * cellHeight + cellHeight / 2 + bb.y1; 11580 11581 } else { // otherwise set automatically 11582 11583 while( used( row, col ) ){
11584 moveToNextCell(); 11585 } 11586 11587 x = col * cellWidth + cellWidth / 2 + bb.x1; 11588 y = row * cellHeight + cellHeight / 2 + bb.y1; 11589 use( row, col ); 11590 11591 moveToNextCell(); 11592 } 11593 11594 return { x: x, y: y }; 11595 11596 }; 11597 11598 nodes.layoutPositions( this, options, getPos ); 11599 } 11600 11601 return this; // chaining 11602 11603}; 11604 11605module.exports = GridLayout; 11606 11607},{"../../math":85,"../../util":100}],53:[function(_dereq_,module,exports){ 11608'use strict'; 11609 11610module.exports = [ 11611 { name: 'breadthfirst', impl: _dereq_( './breadthfirst' ) }, 11612 { name: 'circle', impl: _dereq_( './circle' ) }, 11613 { name: 'concentric',impl: _dereq_( './concentric' ) }, 11614 { name: 'cose', impl: _dereq_( './cose' ) }, 11615 { name: 'grid', impl: _dereq_( './grid' ) }, 11616 { name: 'null', impl: _dereq_( './null' ) }, 11617 { name: 'preset', impl: _dereq_( './preset' ) }, 11618 { name: 'random', impl: _dereq_( './random' ) } 11619]; 11620 11621},{"./breadthfirst":48,"./circle":49,"./concentric":50,"./cose":51,"./grid":52,"./null":54,"./preset":55,"./random":56}],54:[function(_dereq_,module,exports){ 11622'use strict'; 11623 11624var util = _dereq_( '../../util' ); 11625 11626// default layout options 11627var defaults = { 11628 ready: function(){}, // on layoutready 11629 stop: function(){} // on layoutstop 11630}; 11631 11632// constructor 11633// options : object containing layout options 11634function NullLayout( options ){ 11635 this.options = util.extend( {}, defaults, options ); 11636} 11637 11638// runs the layout 11639NullLayout.prototype.run = function(){ 11640 var options = this.options; 11641 var eles = options.eles; // elements to consider in the layout 11642 var layout = this; 11643 11644 // cy is automatically populated for us in the constructor 11645 var cy = options.cy; // jshint ignore:line 11646 11647 layout.trigger( 'layoutstart' ); 11648 11649 // puts all nodes at (0, 0) 11650 eles.nodes().positions( function(){ 11651 return { 11652 x: 0, 11653 y: 0 11654 }; 11655 } ); 11656 11657 // trigger layoutready when each node has had its position set at least once 11658 layout.one( 'layoutready', options.ready ); 11659 layout.trigger( 'layoutready' ); 11660 11661 // trigger layoutstop when the layout stops (e.g. finishes) 11662 layout.one( 'layoutstop', options.stop ); 11663 layout.trigger( 'layoutstop' ); 11664 11665 return this; // chaining 11666}; 11667 11668// called on continuous layouts to stop them before they finish 11669NullLayout.prototype.stop = function(){ 11670 return this; // chaining 11671}; 11672 11673module.exports = NullLayout; 11674 11675},{"../../util":100}],55:[function(_dereq_,module,exports){ 11676'use strict'; 11677 11678var util = _dereq_( '../../util' ); 11679var is = _dereq_( '../../is' ); 11680 11681var defaults = { 11682 positions: undefined, // map of (node id) => (position obj); or function(node){ return somPos; } 11683 zoom: undefined, // the zoom level to set (prob want fit = false if set) 11684 pan: undefined, // the pan level to set (prob want fit = false if set) 11685 fit: true, // whether to fit to viewport 11686 padding: 30, // padding on fit 11687 animate: false, // whether to transition the node positions 11688 animationDuration: 500, // duration of animation in ms if enabled 11689 animationEasing: undefined, // easing of animation if enabled 11690 ready: undefined, // callback on layoutready 11691 stop: undefined // callback on layoutstop 11692}; 11693 11694function PresetLayout( options ){ 11695 this.options = util.extend( {}, defaults, options ); 11696} 11697 11698PresetLayout.prototype.run = function(){ 11699 var options = this.options; 11700 var eles = options.eles; 11701 11702 var nodes = eles.nodes(); 11703 var posIsFn = is.fn( options.positions ); 11704 11705 function getPosition( node ){ 11706 if( options.positions == null ){ 11707 return null; 11708 } 11709 11710 if( posIsFn ){ 11711 return options.positions.apply( node, [ node ] ); 11712 } 11713 11714 var pos = options.positions[ node._private.data.id ]; 11715 11716 if( pos == null ){ 11717 return null; 11718 } 11719 11720 return pos; 11721 } 11722 11723 nodes.layoutPositions( this, options, function( i, node ){ 11724 var position = getPosition( node ); 11725 11726 if( node.locked() || position == null ){ 11727 return false; 11728 } 11729 11730 return position; 11731 } ); 11732 11733 return this; // chaining 11734}; 11735 11736module.exports = PresetLayout; 11737 11738},{"../../is":83,"../../util":100}],56:[function(_dereq_,module,exports){ 11739'use strict'; 11740 11741var util = _dereq_( '../../util' ); 11742var math = _dereq_( '../../math' ); 11743 11744var defaults = { 11745 fit: true, // whether to fit to viewport 11746 padding: 30, // fit padding 11747 boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } 11748 animate: false, // whether to transition the node positions 11749 animationDuration: 500, // duration of animation in ms if enabled 11750 animationEasing: undefined, // easing of animation if enabled 11751 ready: undefined, // callback on layoutready 11752 stop: undefined // callback on layoutstop 11753}; 11754 11755function RandomLayout( options ){ 11756 this.options = util.extend( {}, defaults, options ); 11757} 11758 11759RandomLayout.prototype.run = function(){ 11760 var options = this.options; 11761 var cy = options.cy; 11762 var eles = options.eles; 11763 var nodes = eles.nodes().not( ':parent' ); 11764 11765 var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : { 11766 x1: 0, y1: 0, w: cy.width(), h: cy.height() 11767 } ); 11768 11769 var getPos = function( i, node ){ 11770 return { 11771 x: bb.x1 + Math.round( Math.random() * bb.w ), 11772 y: bb.y1 + Math.round( Math.random() * bb.h ) 11773 }; 11774 }; 11775 11776 nodes.layoutPositions( this, options, getPos ); 11777 11778 return this; // chaining 11779}; 11780 11781module.exports = RandomLayout; 11782 11783},{"../../math":85,"../../util":100}],57:[function(_dereq_,module,exports){ 11784'use strict'; 11785 11786var math = _dereq_( '../../../math' ); 11787var is = _dereq_( '../../../is' ); 11788var util = _dereq_( '../../../util' ); 11789 11790var BRp = {}; 11791 11792BRp.arrowShapeWidth = 0.3; 11793 11794BRp.registerArrowShapes = function(){ 11795 var arrowShapes = this.arrowShapes = {}; 11796 var renderer = this; 11797 11798 // Contract for arrow shapes: 11799 // 0, 0 is arrow tip 11800 // (0, 1) is direction towards node 11801 // (1, 0) is right 11802 // 11803 // functional api: 11804 // collide: check x, y in shape 11805 // roughCollide: called before collide, no false negatives 11806 // draw: draw 11807 // spacing: dist(arrowTip, nodeBoundary) 11808 // gap: dist(edgeTip, nodeBoundary), edgeTip may != arrowTip 11809 11810 var bbCollide = function( x, y, size, angle, translation, padding ){ 11811 var x1 = translation.x - size / 2 - padding; 11812 var x2 = translation.x + size / 2 + padding; 11813 var y1 = translation.y - size / 2 - padding; 11814 var y2 = translation.y + size / 2 + padding; 11815 11816 var inside = (x1 <= x && x <= x2) && (y1 <= y && y <= y2); 11817 11818 return inside; 11819 }; 11820 11821 var transform = function( x, y, size, angle, translation ){ 11822 var xRotated = x * Math.cos( angle ) - y * Math.sin( angle ); 11823 var yRotated = x * Math.sin( angle ) + y * Math.cos( angle ); 11824 11825 var xScaled = xRotated * size;
11826 var yScaled = yRotated * size; 11827 11828 var xTranslated = xScaled + translation.x; 11829 var yTranslated = yScaled + translation.y; 11830 11831 return { 11832 x: xTranslated, 11833 y: yTranslated 11834 }; 11835 }; 11836 11837 var transformPoints = function( pts, size, angle, translation ){ 11838 var retPts = []; 11839 11840 for( var i = 0; i < pts.length; i += 2 ){ 11841 var x = pts[ i ]; 11842 var y = pts[ i + 1]; 11843 11844 retPts.push( transform( x, y, size, angle, translation ) ); 11845 } 11846 11847 return retPts; 11848 }; 11849 11850 var pointsToArr = function( pts ){ 11851 var ret = []; 11852 11853 for( var i = 0; i < pts.length; i++ ){ 11854 var p = pts[ i ]; 11855 11856 ret.push( p.x, p.y ); 11857 } 11858 11859 return ret; 11860 }; 11861 11862 var defineArrowShape = function( name, defn ){ 11863 if( is.string( defn ) ){ 11864 defn = arrowShapes[ defn ]; 11865 } 11866 11867 arrowShapes[ name ] = util.extend( { 11868 name: name, 11869 11870 points: [ 11871 -0.15, -0.3, 11872 0.15, -0.3, 11873 0.15, 0.3, 11874 -0.15, 0.3 11875 ], 11876 11877 collide: function( x, y, size, angle, translation, padding ){ 11878 var points = pointsToArr( transformPoints( this.points, size + 2 * padding, angle, translation ) ); 11879 var inside = math.pointInsidePolygonPoints( x, y, points ); 11880 11881 return inside; 11882 }, 11883 11884 roughCollide: bbCollide, 11885 11886 draw: function( context, size, angle, translation ){ 11887 var points = transformPoints( this.points, size, angle, translation ); 11888 11889 renderer.arrowShapeImpl( 'polygon' )( context, points ); 11890 }, 11891 11892 spacing: function( edge ){ 11893 return 0; 11894 }, 11895 11896 gap: function( edge ){ 11897 return edge.pstyle( 'width' ).pfValue * 2; 11898 } 11899 }, defn ); 11900 }; 11901 11902 defineArrowShape( 'none', { 11903 collide: util.falsify, 11904 11905 roughCollide: util.falsify, 11906 11907 draw: util.noop, 11908 11909 spacing: util.zeroify, 11910 11911 gap: util.zeroify 11912 } ); 11913 11914 defineArrowShape( 'triangle', { 11915 points: [ 11916 -0.15, -0.3, 11917 0, 0, 11918 0.15, -0.3 11919 ] 11920 } ); 11921 11922 defineArrowShape( 'arrow', 'triangle' ); 11923 11924 defineArrowShape( 'triangle-backcurve', { 11925 points: arrowShapes[ 'triangle' ].points, 11926 11927 controlPoint: [ 0, -0.15 ], 11928 11929 roughCollide: bbCollide, 11930 11931 draw: function( context, size, angle, translation ){ 11932 var ptsTrans = transformPoints( this.points, size, angle, translation ); 11933 var ctrlPt = this.controlPoint; 11934 var ctrlPtTrans = transform( ctrlPt[0], ctrlPt[1], size, angle, translation ); 11935 11936 renderer.arrowShapeImpl( this.name )( context, ptsTrans, ctrlPtTrans ); 11937 }, 11938 11939 gap: function( edge ){ 11940 return edge.pstyle( 'width' ).pfValue; 11941 } 11942 } ); 11943 11944 11945 defineArrowShape( 'triangle-tee', { 11946 points: [ 11947 -0.15, -0.3, 11948 0, 0, 11949 0.15, -0.3, 11950 -0.15, -0.3 11951 ], 11952 11953 pointsTee: [ 11954 -0.15, -0.4, 11955 -0.15, -0.5, 11956 0.15, -0.5, 11957 0.15, -0.4 11958 ], 11959 11960 collide: function( x, y, size, angle, translation, padding ){ 11961 var triPts = pointsToArr( transformPoints( this.points, size + 2 * padding, angle, translation ) ); 11962 var teePts = pointsToArr( transformPoints( this.pointsTee, size + 2 * padding, angle, translation ) ); 11963 11964 var inside = math.pointInsidePolygonPoints( x, y, triPts ) || math.pointInsidePolygonPoints( x, y, teePts ); 11965 11966 return inside; 11967 }, 11968 11969 draw: function( context, size, angle, translation ){ 11970 var triPts = transformPoints( this.points, size, angle, translation ); 11971 var teePts = transformPoints( this.pointsTee, size, angle, translation ); 11972 11973 renderer.arrowShapeImpl( this.name )( context, triPts, teePts ); 11974 } 11975 } ); 11976 11977 defineArrowShape( 'vee', { 11978 points: [ 11979 -0.15, -0.3, 11980 0, 0, 11981 0.15, -0.3, 11982 0, -0.15 11983 ], 11984 11985 gap: function( edge ){ 11986 return edge.pstyle( 'width' ).pfValue; 11987 } 11988 } ); 11989 11990 defineArrowShape( 'circle', { 11991 radius: 0.15, 11992 11993 collide: function( x, y, size, angle, translation, padding ){ 11994 var t = translation; 11995 var inside = ( Math.pow( t.x - x, 2 ) + Math.pow( t.y - y, 2 ) <= Math.pow( (size + 2 * padding) * this.radius, 2 ) ); 11996 11997 return inside; 11998 }, 11999 12000 draw: function( context, size, angle, translation ){ 12001 renderer.arrowShapeImpl( this.name )( context, translation.x, translation.y, this.radius * size ); 12002 }, 12003 12004 spacing: function( edge ){ 12005 return renderer.getArrowWidth( edge.pstyle( 'width' ).pfValue ) 12006 * this.radius; 12007 } 12008 } ); 12009 12010 defineArrowShape( 'inhibitor', { 12011 points: [
12012 -0.15, 0, 12013 -0.15, -0.1, 12014 0.15, -0.1, 12015 0.15, 0 12016 ], 12017 12018 spacing: function( edge ){ 12019 return 1; 12020 }, 12021 12022 gap: function( edge ){ 12023 return 1; 12024 } 12025 } ); 12026 12027 defineArrowShape( 'tee', 'inhibitor' ); 12028 12029 defineArrowShape( 'square', { 12030 points: [ 12031 -0.15, 0.00, 12032 0.15, 0.00, 12033 0.15, -0.3, 12034 -0.15, -0.3 12035 ] 12036 } ); 12037 12038 defineArrowShape( 'diamond', { 12039 points: [ 12040 -0.15, -0.15, 12041 0, -0.3, 12042 0.15, -0.15, 12043 0, 0 12044 ], 12045 12046 gap: function( edge ){ 12047 return edge.pstyle( 'width' ).pfValue; 12048 } 12049 } ); 12050 12051}; 12052 12053module.exports = BRp; 12054 12055},{"../../../is":83,"../../../math":85,"../../../util":100}],58:[function(_dereq_,module,exports){ 12056'use strict'; 12057 12058var math = _dereq_( '../../../math' ); 12059var is = _dereq_( '../../../is' ); 12060var util = _dereq_( '../../../util' ); 12061var zIndexSort = _dereq_( '../../../collection/zsort' ); 12062 12063var BRp = {}; 12064 12065BRp.registerCalculationListeners = function(){ 12066 var cy = this.cy; 12067 var elesToUpdate = cy.collection(); 12068 var r = this; 12069 12070 var enqueue = function( eles, e ){ 12071 elesToUpdate.merge( eles ); 12072 12073 for( var i = 0; i < eles.length; i++ ){ 12074 var ele = eles[i]; 12075 var _p = ele._private; 12076 var rstyle = _p.rstyle; 12077 12078 rstyle.clean = false; 12079 _p.bbCache = null; 12080 12081 var evts = rstyle.dirtyEvents = rstyle.dirtyEvents || { length: 0 }; 12082 12083 if( !evts[ e.type ] ){ 12084 evts[ e.type ] = true; 12085 evts.length++; 12086// 12087 // elesToUpdate.merge( ele ); 12088 } 12089 } 12090 }; 12091 12092 r.binder( cy ) 12093 // nodes 12094 12095 .on('position.* style.* free.*', 'node', function onDirtyModNode( e ){ 12096 var node = e.cyTarget; 12097 12098 enqueue( node, e ); 12099 enqueue( node.connectedEdges(), e ); 12100 12101 if( cy.hasCompoundNodes() ){ 12102 var parents = node.parents(); 12103 12104 enqueue( parents, e ); 12105 enqueue( parents.connectedEdges(), e ); 12106 } 12107 }) 12108 12109 .on('add.* background.*', 'node', function onDirtyAddNode( e ){ 12110 var ele = e.cyTarget; 12111 12112 enqueue( ele, e ); 12113 }) 12114 12115 // edges 12116 12117 .on('add.* style.*', 'edge', function onDirtyEdge( e ){ 12118 var edge = e.cyTarget; 12119 12120 enqueue( edge, e ); 12121 enqueue( edge.parallelEdges(), e ); 12122 }) 12123 12124 .on('remove.*', 'edge', function onDirtyRemoveEdge( e ){ 12125 var edge = e.cyTarget; 12126 var pEdges = edge.parallelEdges(); 12127 12128 for( var i = 0; i < pEdges.length; i++ ){ 12129 var pEdge = pEdges[i]; 12130 12131 if( !pEdge.removed() ){ 12132 enqueue( pEdge, e ); 12133 } 12134 } 12135 }) 12136 ; 12137 12138 var updateEleCalcs = function( willDraw ){ 12139 if( willDraw ){ 12140 var fns = r.onUpdateEleCalcsFns; 12141 12142 if( fns ){ for( var i = 0; i < fns.length; i++ ){ 12143 var fn = fns[i]; 12144 12145 fn( willDraw, elesToUpdate ); 12146 } } 12147 12148 r.recalculateRenderedStyle( elesToUpdate, false ); 12149 12150 for( var i = 0; i < elesToUpdate.length; i++ ){ 12151 elesToUpdate[i]._private.rstyle.dirtyEvents = null; 12152 } 12153 12154 elesToUpdate = cy.collection(); 12155 } 12156 }; 12157 12158 r.beforeRender( updateEleCalcs, r.beforeRenderPriorities.eleCalcs ); 12159}; 12160 12161BRp.onUpdateEleCalcs = function( fn ){ 12162 var fns = this.onUpdateEleCalcsFns = this.onUpdateEleCalcsFns || []; 12163 12164 fns.push( fn ); 12165}; 12166 12167BRp.recalculateRenderedStyle = function( eles, useCache ){ 12168 var edges = []; 12169 var nodes = []; 12170 12171 // the renderer can't be used for calcs when destroyed, e.g. ele.boundingBox() 12172 if( this.destroyed ){ return; } 12173 12174 // use cache by default for perf 12175 if( useCache === undefined ){ useCache = true; } 12176 12177 for( var i = 0; i < eles.length; i++ ){ 12178 var ele = eles[ i ]; 12179 var _p = ele._private; 12180 var rstyle = _p.rstyle; 12181 12182 // only update if dirty and in graph 12183 if( (useCache && rstyle.clean) || ele.removed() ){ continue; } 12184 12185 if( _p.group === 'nodes' ){ 12186 nodes.push( ele ); 12187 } else { // edges 12188 edges.push( ele ); 12189 } 12190 12191 rstyle.clean = true; 12192 // rstyle.dirtyEvents = null; 12193 } 12194 12195 // update node data from projections 12196 for( var i = 0; i < nodes.length; i++ ){ 12197 var ele = nodes[i]; 12198 var _p = ele._private; 12199 var rstyle = _p.rstyle; 12200 var pos = _p.position; 12201 12202 this.recalculateNodeLabelProjection( ele ); 12203 12204 rstyle.nodeX = pos.x; 12205 rstyle.nodeY = pos.y; 12206 rstyle.nodeW = ele.pstyle( 'width' ).pfValue; 12207 rstyle.nodeH = ele.pstyle( 'height' ).pfValue; 12208 } 12209 12210 this.recalculateEdgeProjections( edges ); 12211 12212 // update edge data from projections 12213 for( var i = 0; i < edges.length; i++ ){ 12214 var ele = edges[ i ]; 12215 var _p = ele._private; 12216 var rstyle = _p.rstyle; 12217 var rs = _p.rscratch; 12218 12219 this.recalculateEdgeLabelProjections( ele ); 12220 12221 // update rstyle positions 12222 rstyle.srcX = rs.arrowStartX; 12223 rstyle.srcY = rs.arrowStartY; 12224 rstyle.tgtX = rs.arrowEndX; 12225 rstyle.tgtY = rs.arrowEndY; 12226 rstyle.midX = rs.midX; 12227 rstyle.midY = rs.midY; 12228 rstyle.labelAngle = rs.labelAngle; 12229 rstyle.sourceLabelAngle = rs.sourceLabelAngle; 12230 rstyle.targetLabelAngle = rs.targetLabelAngle; 12231 } 12232}; 12233 12234// Project mouse 12235BRp.projectIntoViewport = function( clientX, clientY ){ 12236 var offsets = this.findContainerClientCoords(); 12237 var offsetLeft = offsets[0]; 12238 var offsetTop = offsets[1]; 12239 12240 var x = clientX - offsetLeft; 12241 var y = clientY - offsetTop; 12242 12243 x -= this.cy.pan().x; y -= this.cy.pan().y; x /= this.cy.zoom(); y /= this.cy.zoom(); 12244 return [ x, y ]; 12245}; 12246 12247BRp.findContainerClientCoords = function(){ 12248 var container = this.container; 12249 12250 var bb = this.containerBB = this.containerBB || container.getBoundingClientRect(); 12251 12252 return [ bb.left, bb.top, bb.right - bb.left, bb.bottom - bb.top ]; 12253}; 12254 12255BRp.invalidateContainerClientCoordsCache = function(){ 12256 this.containerBB = null; 12257}; 12258 12259BRp.findNearestElement = function( x, y, visibleElementsOnly, isTouch ){ 12260 return this.findNearestElements( x, y, visibleElementsOnly, isTouch )[0]; 12261}; 12262 12263BRp.findNearestElements = function( x, y, visibleElementsOnly, isTouch ){ 12264 var self = this; 12265 var r = this; 12266 var eles = r.getCachedZSortedEles(); 12267 var near = []; // 1 node max, 1 edge max 12268 var zoom = r.cy.zoom(); 12269 var hasCompounds = r.cy.hasCompoundNodes(); 12270 var edgeThreshold = (isTouch ? 24 : 8) / zoom; 12271 var nodeThreshold = (isTouch ? 8 : 2) / zoom; 12272 var labelThreshold = (isTouch ? 8 : 2) / zoom; 12273 var minSqDist = Infinity; 12274 var nearEdge; 12275 var nearNode; 12276 12277 function addEle( ele, sqDist ){ 12278 if( ele.isNode() ){ 12279 if( nearNode ){ 12280 return; // can't replace node 12281 } else { 12282 nearNode = ele; 12283 near.push( ele ); 12284 } 12285 } 12286 12287 if( ele.isEdge() && ( sqDist == null || sqDist < minSqDist ) ){ 12288 if( nearEdge ){ // then replace existing edge 12289 // can replace only if same z-index 12290 if( nearEdge.pstyle( 'z-index' ).value === ele.pstyle('z-index').value ){ 12291 for( var i = 0; i < near.length; i++ ){
12292 if( near[i].isEdge() ){ 12293 near[i] = ele; 12294 nearEdge = ele; 12295 minSqDist = sqDist != null ? sqDist : minSqDist; 12296 break; 12297 } 12298 } 12299 } 12300 } else { 12301 near.push( ele ); 12302 nearEdge = ele; 12303 minSqDist = sqDist != null ? sqDist : minSqDist; 12304 } 12305 } 12306 } 12307 12308 function checkNode( node ){ 12309 var _p = node._private; 12310 12311 if( node.pstyle( 'events' ).strValue === 'no' ){ return; } 12312 12313 var width = node.outerWidth() + 2 * nodeThreshold; 12314 var height = node.outerHeight() + 2 * nodeThreshold; 12315 var hw = width / 2; 12316 var hh = height / 2; 12317 var pos = _p.position; 12318 12319 if( 12320 pos.x - hw <= x && x <= pos.x + hw // bb check x 12321 && 12322 pos.y - hh <= y && y <= pos.y + hh // bb check y 12323 ){ 12324 var visible = !visibleElementsOnly || ( node.visible() && !node.transparent() ); 12325 12326 // exit early if invisible edge and must be visible 12327 if( visibleElementsOnly && !visible ){ 12328 return; 12329 } 12330 12331 var shape = r.nodeShapes[ self.getNodeShape( node ) ]; 12332 12333 if( 12334 shape.checkPoint( x, y, 0, width, height, pos.x, pos.y ) 12335 ){ 12336 addEle( node, 0 ); 12337 return true; 12338 } 12339 12340 } 12341 } 12342 12343 function checkEdge( edge ){ 12344 var _p = edge._private; 12345 12346 if( edge.pstyle('events').strValue === 'no' ){ return; } 12347 12348 var rs = _p.rscratch; 12349 var width = edge.pstyle( 'width' ).pfValue / 2 + edgeThreshold; // more like a distance radius from centre 12350 var widthSq = width * width; 12351 var width2 = width * 2; 12352 var src = _p.source; 12353 var tgt = _p.target; 12354 var inEdgeBB = false; 12355 var sqDist; 12356 12357 // exit early if invisible edge and must be visible 12358 var passedVisibilityCheck; 12359 var passesVisibilityCheck = function(){ 12360 if( passedVisibilityCheck !== undefined ){ 12361 return passedVisibilityCheck; 12362 } 12363 12364 if( !visibleElementsOnly ){ 12365 passedVisibilityCheck = true; 12366 return true; 12367 } 12368 12369 var visible = edge.visible() && !edge.transparent(); 12370 if( visible ){ 12371 passedVisibilityCheck = true; 12372 return true; 12373 } 12374 12375 passedVisibilityCheck = false; 12376 return false; 12377 }; 12378 12379 if( rs.edgeType === 'segments' || rs.edgeType === 'straight' || rs.edgeType === 'haystack' ){ 12380 var pts = rs.allpts; 12381 12382 for( var i = 0; i + 3 < pts.length; i += 2 ){ 12383 if( 12384 (inEdgeBB = math.inLineVicinity( x, y, pts[ i ], pts[ i + 1], pts[ i + 2], pts[ i + 3], width2 )) 12385 && passesVisibilityCheck() && 12386 widthSq > ( sqDist = math.sqdistToFiniteLine( x, y, pts[ i ], pts[ i + 1], pts[ i + 2], pts[ i + 3] ) ) 12387 ){ 12388 addEle( edge, sqDist ); 12389 return true; 12390 } 12391 } 12392 12393 } else if( rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound' ){ 12394 var pts = rs.allpts; 12395 for( var i = 0; i + 5 < rs.allpts.length; i += 4 ){ 12396 if( 12397 (inEdgeBB = math.inBezierVicinity( x, y, pts[ i ], pts[ i + 1], pts[ i + 2], pts[ i + 3], pts[ i + 4], pts[ i + 5], width2 )) 12398 && passesVisibilityCheck() && 12399 (widthSq > (sqDist = math.sqdistToQuadraticBezier( x, y, pts[ i ], pts[ i + 1], pts[ i + 2], pts[ i + 3], pts[ i + 4], pts[ i + 5] )) ) 12400 ){ 12401 addEle( edge, sqDist ); 12402 return true; 12403 } 12404 } 12405 } 12406 12407 // if we're close to the edge but didn't hit it, maybe we hit its arrows 12408 if( inEdgeBB && passesVisibilityCheck() ){ 12409 var src = src || _p.source; 12410 var tgt = tgt || _p.target; 12411 12412 var eWidth = edge.pstyle( 'width' ).pfValue; 12413 var arSize = self.getArrowWidth( eWidth ); 12414 12415 var arrows = [ 12416 { name: 'source', x: rs.arrowStartX, y: rs.arrowStartY, angle: rs.srcArrowAngle }, 12417 { name: 'target', x: rs.arrowEndX, y: rs.arrowEndY, angle: rs.tgtArrowAngle }, 12418 { name: 'mid-source', x: rs.midX, y: rs.midY, angle: rs.midsrcArrowAngle }, 12419 { name: 'mid-target', x: rs.midX, y: rs.midY, angle: rs.midtgtArrowAngle } 12420 ]; 12421 12422 for( var i = 0; i < arrows.length; i++ ){ 12423 var ar = arrows[ i ];
12424 var shape = r.arrowShapes[ edge.pstyle( ar.name + '-arrow-shape' ).value ]; 12425 12426 if( 12427 shape.roughCollide( x, y, arSize, ar.angle, { x: ar.x, y: ar.y }, edgeThreshold ) 12428 && 12429 shape.collide( x, y, arSize, ar.angle, { x: ar.x, y: ar.y }, edgeThreshold ) 12430 ){ 12431 addEle( edge ); 12432 break; 12433 } 12434 } 12435 } 12436 12437 // for compound graphs, hitting edge may actually want a connected node instead (b/c edge may have greater z-index precedence) 12438 if( hasCompounds && near.length > 0 ){ 12439 checkNode( src ); 12440 checkNode( tgt ); 12441 } 12442 } 12443 12444 function preprop( obj, name, pre ){ 12445 return util.getPrefixedProperty( obj, name, pre ); 12446 } 12447 12448 function checkLabel( ele, prefix ){ 12449 var _p = ele._private; 12450 var th = labelThreshold; 12451 12452 var prefixDash; 12453 if( prefix ){ 12454 prefixDash = prefix + '-'; 12455 } else { 12456 prefixDash = ''; 12457 } 12458 12459 var text = ele.pstyle( prefixDash + 'label' ).value; 12460 var eventsEnabled = ele.pstyle( 'text-events' ).strValue === 'yes'; 12461 12462 if( !eventsEnabled || !text ){ return; } 12463 12464 var rstyle = _p.rstyle; 12465 var bw = ele.pstyle('text-border-width').pfValue; 12466 var lw = preprop( rstyle, 'labelWidth', prefix ) + bw/2 + 2*th; 12467 var lh = preprop( rstyle, 'labelHeight', prefix ) + bw/2 + 2*th; 12468 var lx = preprop( rstyle, 'labelX', prefix ); 12469 var ly = preprop( rstyle, 'labelY', prefix ); 12470 12471 var theta = preprop( _p.rscratch, 'labelAngle', prefix ); 12472 12473 var lx1 = lx - lw / 2; 12474 var lx2 = lx + lw / 2; 12475 var ly1 = ly - lh / 2; 12476 var ly2 = ly + lh / 2; 12477 12478 if( theta ){ 12479 var cos = Math.cos( theta ); 12480 var sin = Math.sin( theta ); 12481 12482 var rotate = function( x, y ){ 12483 x = x - lx; 12484 y = y - ly; 12485 12486 return { 12487 x: x * cos - y * sin + lx, 12488 y: x * sin + y * cos + ly 12489 }; 12490 }; 12491 12492 var px1y1 = rotate( lx1, ly1 ); 12493 var px1y2 = rotate( lx1, ly2 ); 12494 var px2y1 = rotate( lx2, ly1 ); 12495 var px2y2 = rotate( lx2, ly2 ); 12496 12497 var points = [ 12498 px1y1.x, px1y1.y, 12499 px2y1.x, px2y1.y, 12500 px2y2.x, px2y2.y, 12501 px1y2.x, px1y2.y 12502 ]; 12503 12504 if( math.pointInsidePolygonPoints( x, y, points ) ){ 12505 addEle( ele ); 12506 return true; 12507 } 12508 } else { // do a cheaper bb check 12509 var bb = { 12510 w: lw, 12511 h: lh, 12512 x1: lx1, 12513 x2: lx2, 12514 y1: ly1, 12515 y2: ly2 12516 }; 12517 12518 if( math.inBoundingBox( bb, x, y ) ){ 12519 addEle( ele ); 12520 return true; 12521 } 12522 } 12523 12524 } 12525 12526 for( var i = eles.length - 1; i >= 0; i-- ){ // reverse order for precedence 12527 var ele = eles[ i ]; 12528 12529 if( ele.isNode() ){ 12530 checkNode( ele ) || checkLabel( ele ); 12531 12532 } else { // then edge 12533 checkEdge( ele ) || checkLabel( ele ) || checkLabel( ele, 'source' ) || checkLabel( ele, 'target' ); 12534 } 12535 } 12536 12537 return near; 12538}; 12539 12540// 'Give me everything from this box' 12541BRp.getAllInBox = function( x1, y1, x2, y2 ){ 12542 var eles = this.getCachedZSortedEles(); 12543 var nodes = eles.nodes; 12544 var edges = eles.edges; 12545 var box = []; 12546 12547 var x1c = Math.min( x1, x2 ); 12548 var x2c = Math.max( x1, x2 ); 12549 var y1c = Math.min( y1, y2 ); 12550 var y2c = Math.max( y1, y2 ); 12551 12552 x1 = x1c; 12553 x2 = x2c; 12554 y1 = y1c; 12555 y2 = y2c; 12556 12557 var boxBb = math.makeBoundingBox( { 12558 x1: x1, y1: y1, 12559 x2: x2, y2: y2 12560 } ); 12561 12562 for( var i = 0; i < nodes.length; i++ ){ 12563 var node = nodes[ i ]; 12564 var nodeBb = node.boundingBox( { 12565 includeNodes: true, 12566 includeEdges: false, 12567 includeLabels: false, 12568 includeShadows: false 12569 } ); 12570 12571 if( math.boundingBoxesIntersect( boxBb, nodeBb ) ){ 12572 box.push( nodes[ i ] ); 12573 } 12574 } 12575 12576 for( var e = 0; e < edges.length; e++ ){ 12577 var edge = edges[ e ]; 12578 var _p = edge._private; 12579 var rs = _p.rscratch; 12580 12581 if( rs.startX != null && rs.startY != null && !math.inBoundingBox( boxBb, rs.startX, rs.startY ) ){ continue; } 12582 if( rs.endX != null && rs.endY != null && !math.inBoundingBox( boxBb, rs.endX, rs.endY ) ){ continue; } 12583 12584 if( rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound' || rs.edgeType === 'segments' || rs.edgeType === 'haystack' ){ 12585 12586 var pts = _p.rstyle.bezierPts || _p.rstyle.linePts || _p.rstyle.haystackPts; 12587 var allInside = true; 12588 12589 for( var i = 0; i < pts.length; i++ ){ 12590 if( !math.pointInBoundingBox( boxBb, pts[ i ] ) ){ 12591 allInside = false;
12592 break; 12593 } 12594 } 12595 12596 if( allInside ){ 12597 box.push( edge ); 12598 } 12599 12600 } else if( rs.edgeType === 'haystack' || rs.edgeType === 'straight' ){ 12601 box.push( edge ); 12602 } 12603 12604 } 12605 12606 return box; 12607}; 12608 12609 12610/** 12611 * Returns the shape of the given node. If the height or width of the given node 12612 * is set to auto, the node is considered to be a compound. 12613 * 12614 * @param node a node 12615 * @return {String} shape of the node 12616 */ 12617BRp.getNodeShape = function( node ){ 12618 var r = this; 12619 var shape = node.pstyle( 'shape' ).value; 12620 12621 if( node.isParent() ){ 12622 if( shape === 'rectangle' || shape === 'roundrectangle' ){ 12623 return shape; 12624 } else { 12625 return 'rectangle'; 12626 } 12627 } 12628 12629 if( shape === 'polygon' ){ 12630 var points = node.pstyle( 'shape-polygon-points' ).value; 12631 12632 return r.nodeShapes.makePolygon( points ).name; 12633 } 12634 12635 return shape; 12636}; 12637 12638BRp.updateCachedZSortedEles = function(){ 12639 this.getCachedZSortedEles( true ); 12640}; 12641 12642BRp.updateCachedGrabbedEles = function(){ 12643 var eles = this.cachedZSortedEles; 12644 12645 eles.drag = []; 12646 eles.nondrag = []; 12647 12648 var grabTargets = []; 12649 12650 for( var i = 0; i < eles.length; i++ ){ 12651 var ele = eles[i]; 12652 var rs = ele._private.rscratch; 12653 12654 if( ele.grabbed() && !ele.isParent() ){ 12655 grabTargets.push( ele ); 12656 } else if( rs.inDragLayer ){ 12657 eles.drag.push( ele ); 12658 } else { 12659 eles.nondrag.push( ele ); 12660 } 12661 } 12662 12663 // put the grab target nodes last so it's on top of its neighbourhood 12664 for( var i = 0; i < grabTargets.length; i++ ){ 12665 var ele = grabTargets[i]; 12666 12667 eles.drag.push( ele ); 12668 } 12669}; 12670 12671BRp.getCachedZSortedEles = function( forceRecalc ){ 12672 if( forceRecalc || !this.cachedZSortedEles ){ 12673 //console.time('cachezorder') 12674 12675 var cyEles = this.cy.mutableElements(); 12676 var eles = []; 12677 12678 eles.nodes = []; 12679 eles.edges = []; 12680 12681 for( var i = 0; i < cyEles.length; i++ ){ 12682 var ele = cyEles[i]; 12683 12684 if( ele.animated() || (ele.visible() && !ele.transparent()) ){ 12685 eles.push( ele ); 12686 12687 if( ele.isNode() ){ 12688 eles.nodes.push( ele ); 12689 } else { 12690 eles.edges.push( ele ); 12691 } 12692 } 12693 } 12694 12695 eles.sort( zIndexSort ); 12696 12697 this.cachedZSortedEles = eles; 12698 12699 this.updateCachedGrabbedEles(); 12700 12701 //console.log('make cache') 12702 12703 //console.timeEnd('cachezorder') 12704 } else { 12705 eles = this.cachedZSortedEles; 12706 //console.log('read cache') 12707 } 12708 12709 return eles; 12710}; 12711 12712function pushBezierPts( r, edge, pts ){ 12713 var qbezierAt = function( p1, p2, p3, t ){ return math.qbezierAt( p1, p2, p3, t ); }; 12714 var _p = edge._private; 12715 var bpts = _p.rstyle.bezierPts; 12716 12717 for( var i = 0; i < r.bezierProjPcts.length; i++ ){ 12718 var p = r.bezierProjPcts[i]; 12719 12720 bpts.push( { 12721 x: qbezierAt( pts[0], pts[2], pts[4], p ), 12722 y: qbezierAt( pts[1], pts[3], pts[5], p ) 12723 } ); 12724 } 12725} 12726 12727BRp.projectLines = function( edge ){ 12728 var _p = edge._private; 12729 var rs = _p.rscratch; 12730 var et = rs.edgeType; 12731 12732 // clear the cached points state 12733 _p.rstyle.bezierPts = null; 12734 _p.rstyle.linePts = null; 12735 _p.rstyle.haystackPts = null; 12736 12737 if( et === 'multibezier' || et === 'bezier' || et === 'self' || et === 'compound' ){ 12738 var bpts = _p.rstyle.bezierPts = []; // jshint ignore:line 12739 12740 for( var i = 0; i + 5 < rs.allpts.length; i += 4 ){ 12741 pushBezierPts( this, edge, rs.allpts.slice( i, i + 6 ) ); 12742 } 12743 } else if( et === 'segments' ){ 12744 var lpts = _p.rstyle.linePts = []; 12745 12746 for( var i = 0; i + 1 < rs.allpts.length; i += 2 ){ 12747 lpts.push( { 12748 x: rs.allpts[ i ], 12749 y: rs.allpts[ i + 1] 12750 } ); 12751 } 12752 } else if( et === 'haystack' ){ 12753 var hpts = rs.haystackPts; 12754 12755 _p.rstyle.haystackPts = [ 12756 { x: hpts[0], y: hpts[1] }, 12757 { x: hpts[2], y: hpts[3] } 12758 ]; 12759 } 12760 12761 _p.rstyle.arrowWidth = this.getArrowWidth( edge.pstyle('width').pfValue ) * this.arrowShapeWidth; 12762}; 12763 12764BRp.projectBezier = BRp.projectLines; 12765 12766BRp.recalculateNodeLabelProjection = function( node ){ 12767 var content = node.pstyle( 'label' ).strValue; 12768 12769 if( is.emptyString(content) ){ return; } 12770 12771 var textX, textY; 12772 var _p = node._private; 12773 var nodeWidth = node.width(); 12774 var nodeHeight = node.height(); 12775 var paddingLeft = node.pstyle('padding-left').pfValue; 12776 var paddingRight = node.pstyle('padding-right').pfValue; 12777 var paddingTop = node.pstyle('padding-top').pfValue; 12778 var paddingBottom = node.pstyle('padding-bottom').pfValue; 12779 var nodePos = _p.position; 12780 var textHalign = node.pstyle( 'text-halign' ).strValue; 12781 var textValign = node.pstyle( 'text-valign' ).strValue; 12782 var rs = _p.rscratch; 12783 var rstyle = _p.rstyle; 12784 12785 switch( textHalign ){ 12786 case 'left': 12787 textX = nodePos.x - nodeWidth / 2 - paddingLeft; 12788 break; 12789 12790 case 'right': 12791 textX = nodePos.x + nodeWidth / 2 + paddingRight; 12792 break; 12793 12794 default: // e.g. center 12795 textX = nodePos.x; 12796 } 12797 12798 switch( textValign ){ 12799 case 'top': 12800 textY = nodePos.y - nodeHeight / 2 - paddingTop; 12801 break; 12802 12803 case 'bottom': 12804 textY = nodePos.y + nodeHeight / 2 + paddingBottom; 12805 break; 12806 12807 default: // e.g. middle 12808 textY = nodePos.y; 12809 } 12810 12811 rs.labelX = textX; 12812 rs.labelY = textY; 12813 rstyle.labelX = textX; 12814 rstyle.labelY = textY; 12815 12816 this.applyLabelDimensions( node ); 12817}; 12818 12819BRp.recalculateEdgeLabelProjections = function( edge ){ 12820 var p; 12821 var _p = edge._private; 12822 var rs = _p.rscratch; 12823 var r = this; 12824 var content = { 12825 mid: edge.pstyle('label').strValue, 12826 source: edge.pstyle('source-label').strValue, 12827 target: edge.pstyle('target-label').strValue 12828 }; 12829 12830 if( content.mid || content.source || content.target ){ 12831 // then we have to calculate... 12832 } else { 12833 return; // no labels => no calcs 12834 } 12835 12836 // add center point to style so bounding box calculations can use it 12837 // 12838 p = { 12839 x: rs.midX, 12840 y: rs.midY 12841 }; 12842 12843 var setRs = function( propName, prefix, value ){ 12844 util.setPrefixedProperty( _p.rscratch, propName, prefix, value ); 12845 util.setPrefixedProperty( _p.rstyle, propName, prefix, value ); 12846 }; 12847 12848 setRs( 'labelX', null, p.x ); 12849 setRs( 'labelY', null, p.y ); 12850 12851 var createControlPointInfo = function(){ 12852 if( createControlPointInfo.cache ){ return createControlPointInfo.cache; } // use cache so only 1x per edge 12853 12854 var ctrlpts = []; 12855 12856 // store each ctrlpt info init 12857 for( var i = 0; i + 5 < rs.allpts.length; i += 4 ){ 12858 var p0 = { x: rs.allpts[i], y: rs.allpts[i+1] }; 12859 var p1 = { x: rs.allpts[i+2], y: rs.allpts[i+3] }; // ctrlpt 12860 var p2 = { x: rs.allpts[i+4], y: rs.allpts[i+5] }; 12861 12862 ctrlpts.push({ 12863 p0: p0, 12864 p1: p1, 12865 p2: p2, 12866 startDist: 0, 12867 length: 0, 12868 segments: [] 12869 }); 12870 } 12871 12872 var bpts = _p.rstyle.bezierPts; 12873 var nProjs = r.bezierProjPcts.length; 12874 12875 function addSegment( cp, p0, p1, t0, t1 ){ 12876 var length = math.dist( p0, p1 ); 12877 var prevSegment = cp.segments[ cp.segments.length - 1 ]; 12878 var segment = { 12879 p0: p0, 12880 p1: p1, 12881 t0: t0, 12882 t1: t1, 12883 startDist: prevSegment ? prevSegment.startDist + prevSegment.length : 0, 12884 length: length 12885 }; 12886 12887 cp.segments.push( segment ); 12888 12889 cp.length += length; 12890 } 12891 12892 // update each ctrlpt with segment info 12893 for( var i = 0; i < ctrlpts.length; i++ ){ 12894 var cp = ctrlpts[i]; 12895 var prevCp = ctrlpts[i - 1]; 12896 12897 if( prevCp ){ 12898 cp.startDist = prevCp.startDist + prevCp.length; 12899 } 12900 12901 addSegment( 12902 cp, 12903 cp.p0, bpts[ i * nProjs ], 12904 0, r.bezierProjPcts[ 0 ] 12905 ); // first 12906 12907 for( var j = 0; j < nProjs - 1; j++ ){ 12908 addSegment( 12909 cp, 12910 bpts[ i * nProjs + j ], bpts[ i * nProjs + j + 1 ], 12911 r.bezierProjPcts[ j ], r.bezierProjPcts[ j + 1 ] 12912 ); 12913 } 12914 12915 addSegment( 12916 cp, 12917 bpts[ i * nProjs + nProjs - 1 ], cp.p2, 12918 r.bezierProjPcts[ nProjs - 1 ], 1 12919 ); // last 12920 } 12921 12922 return ( createControlPointInfo.cache = ctrlpts ); 12923 }; 12924 12925 var calculateEndProjection = function( prefix ){ 12926 var angle; 12927 var isSrc = prefix === 'source'; 12928 12929 if( !content[ prefix ] ){ return; } 12930 12931 var offset = edge.pstyle(prefix+'-text-offset').pfValue; 12932 12933 var lineAngle = function( p0, p1 ){ 12934 var dx = p1.x - p0.x; 12935 var dy = p1.y - p0.y; 12936 12937 return Math.atan( dy / dx ); 12938 }; 12939 12940 var bezierAngle = function( p0, p1, p2, t ){ 12941 var t0 = math.bound( 0, t - 0.001, 1 ); 12942 var t1 = math.bound( 0, t + 0.001, 1 ); 12943 12944 var lp0 = math.qbezierPtAt( p0, p1, p2, t0 ); 12945 var lp1 = math.qbezierPtAt( p0, p1, p2, t1 ); 12946 12947 return lineAngle( lp0, lp1 ); 12948 }; 12949 12950 switch( rs.edgeType ){ 12951 case 'self': 12952 case 'compound': 12953 case 'bezier': 12954 case 'multibezier': 12955 var cps = createControlPointInfo(); 12956 var selected; 12957 var startDist = 0; 12958 var totalDist = 0; 12959 12960 // find the segment we're on 12961 for( var i = 0; i < cps.length; i++ ){ 12962 var cp = cps[ isSrc ? i : cps.length - 1 - i ]; 12963 12964 for( var j = 0; j < cp.segments.length; j++ ){ 12965 var seg = cp.segments[ isSrc ? j : cp.segments.length - 1 - j ]; 12966 var lastSeg = i === cps.length - 1 && j === cp.segments.length - 1; 12967 12968 startDist = totalDist; 12969 totalDist += seg.length; 12970 12971 if( totalDist >= offset || lastSeg ){ 12972 selected = { cp: cp, segment: seg }; 12973 break; 12974 } 12975 } 12976 12977 if( selected ){ break; } 12978 } 12979 12980 var cp = selected.cp; 12981 var seg = selected.segment; 12982 var tSegment = ( offset - startDist ) / ( seg.length ); 12983 var segDt = seg.t1 - seg.t0; 12984 var t = isSrc ? seg.t0 + segDt * tSegment : seg.t1 - segDt * tSegment; 12985 12986 t = math.bound( 0, t, 1 ); 12987 p = math.qbezierPtAt( cp.p0, cp.p1, cp.p2, t ); 12988 angle = bezierAngle( cp.p0, cp.p1, cp.p2, t, p ); 12989 12990 break; 12991 12992 case 'straight': 12993 case 'segments': 12994 case 'haystack': 12995 var d = 0, di, d0; 12996 var p0, p1; 12997 var l = rs.allpts.length; 12998 12999 for( var i = 0; i + 3 < l; i += 2 ){ 13000 if( isSrc ){ 13001 p0 = { x: rs.allpts[i], y: rs.allpts[i+1] }; 13002 p1 = { x: rs.allpts[i+2], y: rs.allpts[i+3] }; 13003 } else { 13004 p0 = { x: rs.allpts[l-2-i], y: rs.allpts[l-1-i] }; 13005 p1 = { x: rs.allpts[l-4-i], y: rs.allpts[l-3-i] }; 13006 } 13007 13008 di = math.dist( p0, p1 ); 13009 d0 = d; 13010 d += di; 13011 13012 if( d >= offset ){ break; } 13013 } 13014 13015 var pD = offset - d0; 13016 var t = pD / di; 13017 13018 t = math.bound( 0, t, 1 ); 13019 p = math.lineAt( p0, p1, t ); 13020 angle = lineAngle( p0, p1 ); 13021 13022 break; 13023 } 13024 13025 setRs( 'labelX', prefix, p.x ); 13026 setRs( 'labelY', prefix, p.y ); 13027 setRs( 'labelAutoAngle', prefix, angle ); 13028 }; 13029 13030 calculateEndProjection( 'source' ); 13031 calculateEndProjection( 'target' ); 13032 13033 this.applyLabelDimensions( edge ); 13034}; 13035 13036BRp.applyLabelDimensions = function( ele ){ 13037 this.applyPrefixedLabelDimensions( ele ); 13038 13039 if( ele.isEdge() ){ 13040 this.applyPrefixedLabelDimensions( ele, 'source' ); 13041 this.applyPrefixedLabelDimensions( ele, 'target' ); 13042 } 13043}; 13044 13045BRp.applyPrefixedLabelDimensions = function( ele, prefix ){ 13046 var _p = ele._private; 13047 13048 var text = this.getLabelText( ele, prefix ); 13049 var labelDims = this.calculateLabelDimensions( ele, text ); 13050 13051 util.setPrefixedProperty( _p.rstyle, 'labelWidth', prefix, labelDims.width ); 13052 util.setPrefixedProperty( _p.rscratch, 'labelWidth', prefix, labelDims.width ); 13053 13054 util.setPrefixedProperty( _p.rstyle, 'labelHeight', prefix, labelDims.height ); 13055 util.setPrefixedProperty( _p.rscratch, 'labelHeight', prefix, labelDims.height ); 13056}; 13057 13058BRp.getLabelText = function( ele, prefix ){ 13059 var _p = ele._private; 13060 var pfd = prefix ? prefix + '-' : ''; 13061 var text = ele.pstyle( pfd + 'label' ).strValue; 13062 var textTransform = ele.pstyle( 'text-transform' ).value; 13063 var rscratch = function( propName, value ){ 13064 if( value ){ 13065 util.setPrefixedProperty( _p.rscratch, propName, prefix, value ); 13066 return value; 13067 } else { 13068 return util.getPrefixedProperty( _p.rscratch, propName, prefix ); 13069 } 13070 }; 13071 13072 if( textTransform == 'none' ){ 13073 // passthrough 13074 } else if( textTransform == 'uppercase' ){ 13075 text = text.toUpperCase(); 13076 } else if( textTransform == 'lowercase' ){ 13077 text = text.toLowerCase(); 13078 } 13079 13080 if( ele.pstyle( 'text-wrap' ).value === 'wrap' ){ 13081 //console.log('wrap'); 13082 13083 var labelKey = rscratch( 'labelKey' ); 13084 13085 // save recalc if the label is the same as before 13086 if( labelKey && rscratch( 'labelWrapKey' ) === labelKey ){ 13087 // console.log('wrap cache hit'); 13088 return rscratch( 'labelWrapCachedText' ); 13089 } 13090 // console.log('wrap cache miss'); 13091 13092 var lines = text.split( '\n' ); 13093 var maxW = ele.pstyle( 'text-max-width' ).pfValue; 13094 var wrappedLines = []; 13095 13096 for( var l = 0; l < lines.length; l++ ){ 13097 var line = lines[ l ]; 13098 var lineDims = this.calculateLabelDimensions( ele, line, 'line=' + line ); 13099 var lineW = lineDims.width; 13100 13101 if( lineW > maxW ){ // line is too long 13102 var words = line.split( /\s+/ ); // NB: assume collapsed whitespace into single space 13103 var subline = ''; 13104 13105 for( var w = 0; w < words.length; w++ ){ 13106 var word = words[ w ]; 13107 var testLine = subline.length === 0 ? word : subline + ' ' + word; 13108 var testDims = this.calculateLabelDimensions( ele, testLine, 'testLine=' + testLine ); 13109 var testW = testDims.width; 13110 13111 if( testW <= maxW ){ // word fits on current line 13112 subline += word + ' '; 13113 } else { // word starts new line 13114 wrappedLines.push( subline ); 13115 subline = word + ' '; 13116 } 13117 } 13118 13119 // if there's remaining text, put it in a wrapped line 13120 if( !subline.match( /^\s+$/ ) ){ 13121 wrappedLines.push( subline ); 13122 } 13123 } else { // line is already short enough 13124 wrappedLines.push( line ); 13125 } 13126 } // for 13127 13128 rscratch( 'labelWrapCachedLines', wrappedLines ); 13129 text = rscratch( 'labelWrapCachedText', wrappedLines.join( '\n' ) ); 13130 rscratch( 'labelWrapKey', labelKey ); 13131 13132 // console.log(text) 13133 } // if wrap 13134 13135 return text; 13136}; 13137 13138BRp.calculateLabelDimensions = function( ele, text, extraKey ){ 13139 var r = this; 13140 13141 var cacheKey = ele._private.labelStyleKey + '$@$' + text; 13142 13143 if( extraKey ){ 13144 cacheKey += '$@$' + extraKey; 13145 } 13146 13147 var cache = r.labelDimCache || (r.labelDimCache = {}); 13148 13149 if( cache[ cacheKey ] ){ 13150 return cache[ cacheKey ]; 13151 } 13152 13153 var sizeMult = 1; // increase the scale to increase accuracy w.r.t. zoomed text 13154 var fStyle = ele.pstyle( 'font-style' ).strValue; 13155 var size = ( sizeMult * ele.pstyle( 'font-size' ).pfValue ) + 'px'; 13156 var family = ele.pstyle( 'font-family' ).strValue; 13157 var weight = ele.pstyle( 'font-weight' ).strValue; 13158 13159 var div = this.labelCalcDiv; 13160 13161 if( !div ){ 13162 div = this.labelCalcDiv = document.createElement( 'div' ); // eslint-disable-line no-undef 13163 document.body.appendChild( div ); // eslint-disable-line no-undef 13164 } 13165 13166 var ds = div.style; 13167 13168 // from ele style 13169 ds.fontFamily = family; 13170 ds.fontStyle = fStyle; 13171 ds.fontSize = size; 13172 ds.fontWeight = weight; 13173 13174 // forced style 13175 ds.position = 'absolute'; 13176 ds.left = '-9999px'; 13177 ds.top = '-9999px'; 13178 ds.zIndex = '-1'; 13179 ds.visibility = 'hidden'; 13180 ds.pointerEvents = 'none'; 13181 ds.padding = '0'; 13182 ds.lineHeight = '1'; 13183 13184 if( ele.pstyle( 'text-wrap' ).value === 'wrap' ){ 13185 ds.whiteSpace = 'pre'; // so newlines are taken into account 13186 } else { 13187 ds.whiteSpace = 'normal'; 13188 } 13189 13190 // put label content in div 13191 div.textContent = text; 13192 13193 cache[ cacheKey ] = { 13194 width: Math.ceil( div.clientWidth / sizeMult ),
13195 height: Math.ceil( div.clientHeight / sizeMult ) 13196 }; 13197 13198 return cache[ cacheKey ]; 13199}; 13200 13201BRp.recalculateEdgeProjections = function( edges ){ 13202 this.findEdgeControlPoints( edges ); 13203}; 13204 13205 13206// Find edge control points 13207BRp.findEdgeControlPoints = function( edges ){ 13208 if( !edges || edges.length === 0 ){ return; } 13209 13210 var r = this; 13211 var cy = r.cy; 13212 var hasCompounds = cy.hasCompoundNodes(); 13213 var hashTable = {}; 13214 var pairIds = []; 13215 var haystackEdges = []; 13216 13217 // create a table of edge (src, tgt) => list of edges between them 13218 var pairId; 13219 for( var i = 0; i < edges.length; i++ ){ 13220 var edge = edges[ i ]; 13221 var _p = edge._private; 13222 var data = _p.data; 13223 var curveStyle = edge.pstyle( 'curve-style' ).value; 13224 var edgeIsUnbundled = curveStyle === 'unbundled-bezier' || curveStyle === 'segments'; 13225 13226 // ignore edges who are not to be displayed 13227 // they shouldn't take up space 13228 if( edge.pstyle( 'display').value === 'none' ){ 13229 continue; 13230 } 13231 13232 if( curveStyle === 'haystack' ){ 13233 haystackEdges.push( edge ); 13234 continue; 13235 } 13236 13237 var srcId = data.source; 13238 var tgtId = data.target; 13239 13240 pairId = srcId > tgtId ? 13241 tgtId + '$-$' + srcId : 13242 srcId + '$-$' + tgtId ; 13243 13244 if( edgeIsUnbundled ){ 13245 pairId = 'unbundled' + '$-$' + data.id; 13246 } 13247 13248 if( hashTable[ pairId ] == null ){ 13249 hashTable[ pairId ] = []; 13250 pairIds.push( pairId ); 13251 } 13252 13253 hashTable[ pairId ].push( edge ); 13254 13255 if( edgeIsUnbundled ){ 13256 hashTable[ pairId ].hasUnbundled = true; 13257 } 13258 } 13259 13260 var src, tgt, src_p, tgt_p, srcPos, tgtPos, srcW, srcH, tgtW, tgtH, srcShape, tgtShape; 13261 var vectorNormInverse; 13262 var badBezier; 13263 13264 // for each pair (src, tgt), create the ctrl pts 13265 // Nested for loop is OK; total number of iterations for both loops = edgeCount 13266 for( var p = 0; p < pairIds.length; p++ ){ 13267 pairId = pairIds[ p ]; 13268 var pairEdges = hashTable[ pairId ]; 13269 13270 // for each pair id, the edges should be sorted by index 13271 pairEdges.sort( function( edge1, edge2 ){ 13272 return edge1.poolIndex() - edge2.poolIndex(); 13273 } ); 13274 13275 src = pairEdges[0]._private.source; 13276 tgt = pairEdges[0]._private.target; 13277 13278 // make sure src/tgt distinction is consistent for bundled edges 13279 if( !pairEdges.hasUnbundled && src.id() > tgt.id() ){ 13280 var temp = src; 13281 src = tgt; 13282 tgt = temp; 13283 } 13284 13285 src_p = src._private; 13286 tgt_p = tgt._private; 13287 13288 srcPos = src_p.position; 13289 tgtPos = tgt_p.position; 13290 13291 srcW = src.outerWidth(); 13292 srcH = src.outerHeight(); 13293 13294 tgtW = tgt.outerWidth(); 13295 tgtH = tgt.outerHeight(); 13296 13297 srcShape = r.nodeShapes[ this.getNodeShape( src ) ]; 13298 tgtShape = r.nodeShapes[ this.getNodeShape( tgt ) ]; 13299 13300 badBezier = false;
13301 13302 13303 if( (pairEdges.length > 1 && src !== tgt) || pairEdges.hasUnbundled ){ 13304 13305 // pt outside src shape to calc distance/displacement from src to tgt 13306 var srcOutside = srcShape.intersectLine( 13307 srcPos.x, 13308 srcPos.y, 13309 srcW, 13310 srcH, 13311 tgtPos.x, 13312 tgtPos.y, 13313 0 13314 ); 13315 13316 // pt outside tgt shape to calc distance/displacement from src to tgt 13317 var tgtOutside = tgtShape.intersectLine( 13318 tgtPos.x, 13319 tgtPos.y, 13320 tgtW, 13321 tgtH, 13322 srcPos.x, 13323 srcPos.y, 13324 0 13325 ); 13326 13327 var midptSrcPts = { 13328 x1: srcOutside[0], 13329 x2: tgtOutside[0], 13330 y1: srcOutside[1], 13331 y2: tgtOutside[1] 13332 }; 13333 13334 var posPts = { 13335 x1: srcPos.x, 13336 x2: tgtPos.x, 13337 y1: srcPos.y, 13338 y2: tgtPos.y 13339 }; 13340 13341 var dy = ( tgtOutside[1] - srcOutside[1] ); 13342 var dx = ( tgtOutside[0] - srcOutside[0] ); 13343 var l = Math.sqrt( dx * dx + dy * dy ); 13344 13345 var vector = { 13346 x: dx, 13347 y: dy 13348 }; 13349 13350 var vectorNorm = { 13351 x: vector.x / l, 13352 y: vector.y / l 13353 }; 13354 vectorNormInverse = { 13355 x: -vectorNorm.y, 13356 y: vectorNorm.x 13357 }; 13358 13359 13360 // if node shapes overlap, then no ctrl pts to draw 13361 if( 13362 tgtShape.checkPoint( srcOutside[0], srcOutside[1], 0, tgtW, tgtH, tgtPos.x, tgtPos.y ) && 13363 srcShape.checkPoint( tgtOutside[0], tgtOutside[1], 0, srcW, srcH, srcPos.x, srcPos.y ) 13364 ){ 13365 vectorNormInverse = {}; 13366 badBezier = true; 13367 } 13368 13369 } 13370 13371 var edge; 13372 var edge_p; 13373 var rs; 13374 13375 for( var i = 0; i < pairEdges.length; i++ ){ 13376 edge = pairEdges[ i ]; 13377 edge_p = edge._private; 13378 rs = edge_p.rscratch; 13379 13380 var edgeIndex1 = rs.lastEdgeIndex; 13381 var edgeIndex2 = i; 13382 13383 var numEdges1 = rs.lastNumEdges; 13384 var numEdges2 = pairEdges.length; 13385 13386 var curveStyle = edge.pstyle( 'curve-style' ).value; 13387 var ctrlptDists = edge.pstyle( 'control-point-distances' ); 13388 var ctrlptWs = edge.pstyle( 'control-point-weights' ); 13389 var bezierN = ctrlptDists && ctrlptWs ? Math.min( ctrlptDists.value.length, ctrlptWs.value.length ) : 1; 13390 var stepSize = edge.pstyle( 'control-point-step-size' ).pfValue; 13391 var ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[0] : undefined; 13392 var ctrlptWeight = ctrlptWs.value[0]; 13393 var edgeIsUnbundled = curveStyle === 'unbundled-bezier' || curveStyle === 'segments'; 13394 13395 var srcX1 = rs.lastSrcCtlPtX; 13396 var srcX2 = srcPos.x; 13397 var srcY1 = rs.lastSrcCtlPtY; 13398 var srcY2 = srcPos.y; 13399 var srcW1 = rs.lastSrcCtlPtW; 13400 var srcW2 = src.outerWidth(); 13401 var srcH1 = rs.lastSrcCtlPtH; 13402 var srcH2 = src.outerHeight(); 13403 13404 var tgtX1 = rs.lastTgtCtlPtX; 13405 var tgtX2 = tgtPos.x; 13406 var tgtY1 = rs.lastTgtCtlPtY; 13407 var tgtY2 = tgtPos.y; 13408 var tgtW1 = rs.lastTgtCtlPtW; 13409 var tgtW2 = tgt.outerWidth(); 13410 var tgtH1 = rs.lastTgtCtlPtH; 13411 var tgtH2 = tgt.outerHeight(); 13412 13413 var width1 = rs.lastW; 13414 var width2 = edge.pstyle( 'control-point-step-size' ).pfValue; 13415 13416 var edgeDistances = edge.pstyle('edge-distances').value; 13417 13418 if( badBezier ){ 13419 rs.badBezier = true; 13420 } else { 13421 rs.badBezier = false;
13422 } 13423 13424 if( srcX1 === srcX2 && srcY1 === srcY2 && srcW1 === srcW2 && srcH1 === srcH2 13425 && tgtX1 === tgtX2 && tgtY1 === tgtY2 && tgtW1 === tgtW2 && tgtH1 === tgtH2 13426 && width1 === width2 13427 && ((edgeIndex1 === edgeIndex2 && numEdges1 === numEdges2) || edgeIsUnbundled) ){ 13428 // console.log('edge ctrl pt cache HIT') 13429 continue; // then the control points haven't changed and we can skip calculating them 13430 } else { 13431 rs.lastSrcCtlPtX = srcX2; 13432 rs.lastSrcCtlPtY = srcY2; 13433 rs.lastSrcCtlPtW = srcW2; 13434 rs.lastSrcCtlPtH = srcH2; 13435 rs.lastTgtCtlPtX = tgtX2; 13436 rs.lastTgtCtlPtY = tgtY2; 13437 rs.lastTgtCtlPtW = tgtW2; 13438 rs.lastTgtCtlPtH = tgtH2; 13439 rs.lastEdgeIndex = edgeIndex2; 13440 rs.lastNumEdges = numEdges2; 13441 rs.lastWidth = width2; 13442 // console.log('edge ctrl pt cache MISS') 13443 } 13444 13445 if( src === tgt ){ 13446 // Self-edge 13447 13448 rs.edgeType = 'self'; 13449 13450 var j = i; 13451 var loopDist = stepSize; 13452 13453 if( edgeIsUnbundled ){ 13454 j = 0; 13455 loopDist = ctrlptDist; 13456 } 13457 13458 rs.ctrlpts = [ 13459 srcPos.x, 13460 srcPos.y - (1 + Math.pow( srcH, 1.12 ) / 100) * loopDist * (j / 3 + 1), 13461 13462 srcPos.x - (1 + Math.pow( srcW, 1.12 ) / 100) * loopDist * (j / 3 + 1), 13463 srcPos.y 13464 ]; 13465 13466 } else if( 13467 hasCompounds && 13468 ( src.isParent() || src.isChild() || tgt.isParent() || tgt.isChild() ) && 13469 ( src.parents().anySame( tgt ) || tgt.parents().anySame( src ) ) 13470 ){ 13471 // Compound edge 13472 13473 rs.edgeType = 'compound'; 13474 13475 // because the line approximation doesn't apply for compound beziers 13476 // (loop/self edges are already elided b/c of cheap src==tgt check) 13477 rs.badBezier = false;
13478 13479 var j = i; 13480 var loopDist = stepSize; 13481 13482 if( edgeIsUnbundled ){ 13483 j = 0; 13484 loopDist = ctrlptDist; 13485 } 13486 13487 var loopW = 50; 13488 13489 var loopaPos = { 13490 x: srcPos.x - srcW / 2, 13491 y: srcPos.y - srcH / 2 13492 }; 13493 13494 var loopbPos = { 13495 x: tgtPos.x - tgtW / 2, 13496 y: tgtPos.y - tgtH / 2 13497 }; 13498 13499 var loopPos = { 13500 x: Math.min( loopaPos.x, loopbPos.x ), 13501 y: Math.min( loopaPos.y, loopbPos.y ) 13502 }; 13503 13504 // avoids cases with impossible beziers 13505 var minCompoundStretch = 0.5; 13506 var compoundStretchA = Math.max( minCompoundStretch, Math.log( srcW * 0.01 ) ); 13507 var compoundStretchB = Math.max( minCompoundStretch, Math.log( tgtW * 0.01 ) ); 13508 13509 rs.ctrlpts = [ 13510 loopPos.x, 13511 loopPos.y - (1 + Math.pow( loopW, 1.12 ) / 100) * loopDist * (j / 3 + 1) * compoundStretchA, 13512 13513 loopPos.x - (1 + Math.pow( loopW, 1.12 ) / 100) * loopDist * (j / 3 + 1) * compoundStretchB, 13514 loopPos.y 13515 ]; 13516 13517 } else if( curveStyle === 'segments' ){ 13518 // Segments (multiple straight lines) 13519 13520 rs.edgeType = 'segments'; 13521 rs.segpts = []; 13522 13523 var segmentWs = edge.pstyle( 'segment-weights' ).pfValue; 13524 var segmentDs = edge.pstyle( 'segment-distances' ).pfValue; 13525 var segmentsN = Math.min( segmentWs.length, segmentDs.length ); 13526 13527 for( var s = 0; s < segmentsN; s++ ){ 13528 var w = segmentWs[ s ]; 13529 var d = segmentDs[ s ]; 13530 13531 var w1 = 1 - w; 13532 var w2 = w; 13533 13534 var midptPts = edgeDistances === 'node-position' ? posPts : midptSrcPts; 13535 13536 var adjustedMidpt = { 13537 x: midptPts.x1 * w1 + midptPts.x2 * w2, 13538 y: midptPts.y1 * w1 + midptPts.y2 * w2 13539 }; 13540 13541 rs.segpts.push( 13542 adjustedMidpt.x + vectorNormInverse.x * d, 13543 adjustedMidpt.y + vectorNormInverse.y * d 13544 ); 13545 } 13546 13547 // Straight edge 13548 } else if( 13549 pairEdges.length % 2 === 1 13550 && i === Math.floor( pairEdges.length / 2 ) 13551 && !edgeIsUnbundled 13552 ){ 13553 13554 rs.edgeType = 'straight'; 13555 13556 } else { 13557 // (Multi)bezier 13558 13559 var multi = edgeIsUnbundled; 13560 13561 rs.edgeType = multi ? 'multibezier' : 'bezier'; 13562 rs.ctrlpts = []; 13563 13564 for( var b = 0; b < bezierN; b++ ){ 13565 var normctrlptDist = (0.5 - pairEdges.length / 2 + i) * stepSize; 13566 var manctrlptDist; 13567 var sign = math.signum( normctrlptDist ); 13568 13569 if( multi ){ 13570 ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[ b ] : stepSize; // fall back on step size 13571 ctrlptWeight = ctrlptWs.value[ b ]; 13572 } 13573 13574 if( edgeIsUnbundled ){ // multi or single unbundled 13575 manctrlptDist = ctrlptDist; 13576 } else { 13577 manctrlptDist = ctrlptDist !== undefined ? sign * ctrlptDist : undefined; 13578 } 13579 13580 var distanceFromMidpoint = manctrlptDist !== undefined ? manctrlptDist : normctrlptDist; 13581 13582 var w1 = 1 - ctrlptWeight; 13583 var w2 = ctrlptWeight; 13584 13585 var midptPts = edgeDistances === 'node-position' ? posPts : midptSrcPts; 13586 13587 var adjustedMidpt = { 13588 x: midptPts.x1 * w1 + midptPts.x2 * w2, 13589 y: midptPts.y1 * w1 + midptPts.y2 * w2 13590 }; 13591 13592 rs.ctrlpts.push( 13593 adjustedMidpt.x + vectorNormInverse.x * distanceFromMidpoint, 13594 adjustedMidpt.y + vectorNormInverse.y * distanceFromMidpoint 13595 ); 13596 } 13597 13598 } 13599 13600 // find endpts for edge 13601 this.findEndpoints( edge ); 13602 13603 var badStart = !is.number( rs.startX ) || !is.number( rs.startY ); 13604 var badAStart = !is.number( rs.arrowStartX ) || !is.number( rs.arrowStartY ); 13605 var badEnd = !is.number( rs.endX ) || !is.number( rs.endY ); 13606 var badAEnd = !is.number( rs.arrowEndX ) || !is.number( rs.arrowEndY ); 13607 13608 var minCpADistFactor = 3; 13609 var arrowW = this.getArrowWidth( edge.pstyle( 'width' ).pfValue ) * this.arrowShapeWidth; 13610 var minCpADist = minCpADistFactor * arrowW; 13611 13612 if( rs.edgeType === 'bezier' ){ 13613 var startACpDist = math.dist( { x: rs.ctrlpts[0], y: rs.ctrlpts[1] }, { x: rs.startX, y: rs.startY } ); 13614 var closeStartACp = startACpDist < minCpADist; 13615 var endACpDist = math.dist( { x: rs.ctrlpts[0], y: rs.ctrlpts[1] }, { x: rs.endX, y: rs.endY } ); 13616 var closeEndACp = endACpDist < minCpADist; 13617 13618 var overlapping = false;
vendor: 2,347 bytes, lines 13619-13689
13619 13620 if( badStart || badAStart || closeStartACp ){ 13621 overlapping = true; 13622 13623 // project control point along line from src centre to outside the src shape 13624 // (otherwise intersection will yield nothing) 13625 var cpD = { // delta 13626 x: rs.ctrlpts[0] - srcPos.x, 13627 y: rs.ctrlpts[1] - srcPos.y 13628 }; 13629 var cpL = Math.sqrt( cpD.x * cpD.x + cpD.y * cpD.y ); // length of line 13630 var cpM = { // normalised delta 13631 x: cpD.x / cpL, 13632 y: cpD.y / cpL 13633 }; 13634 var radius = Math.max( srcW, srcH ); 13635 var cpProj = { // *2 radius guarantees outside shape 13636 x: rs.ctrlpts[0] + cpM.x * 2 * radius, 13637 y: rs.ctrlpts[1] + cpM.y * 2 * radius 13638 }; 13639 13640 var srcCtrlPtIntn = srcShape.intersectLine( 13641 srcPos.x, 13642 srcPos.y, 13643 srcW, 13644 srcH, 13645 cpProj.x, 13646 cpProj.y, 13647 0 13648 ); 13649 13650 if( closeStartACp ){ 13651 rs.ctrlpts[0] = rs.ctrlpts[0] + cpM.x * (minCpADist - startACpDist); 13652 rs.ctrlpts[1] = rs.ctrlpts[1] + cpM.y * (minCpADist - startACpDist); 13653 } else { 13654 rs.ctrlpts[0] = srcCtrlPtIntn[0] + cpM.x * minCpADist; 13655 rs.ctrlpts[1] = srcCtrlPtIntn[1] + cpM.y * minCpADist; 13656 } 13657 } 13658 13659 if( badEnd || badAEnd || closeEndACp ){ 13660 overlapping = true; 13661 13662 // project control point along line from tgt centre to outside the tgt shape 13663 // (otherwise intersection will yield nothing) 13664 var cpD = { // delta 13665 x: rs.ctrlpts[0] - tgtPos.x, 13666 y: rs.ctrlpts[1] - tgtPos.y 13667 }; 13668 var cpL = Math.sqrt( cpD.x * cpD.x + cpD.y * cpD.y ); // length of line 13669 var cpM = { // normalised delta 13670 x: cpD.x / cpL, 13671 y: cpD.y / cpL 13672 }; 13673 var radius = Math.max( srcW, srcH ); 13674 var cpProj = { // *2 radius guarantees outside shape 13675 x: rs.ctrlpts[0] + cpM.x * 2 * radius, 13676 y: rs.ctrlpts[1] + cpM.y * 2 * radius 13677 }; 13678 13679 var tgtCtrlPtIntn = tgtShape.intersectLine( 13680 tgtPos.x, 13681 tgtPos.y, 13682 tgtW, 13683 tgtH, 13684 cpProj.x, 13685 cpProj.y, 13686 0 13687 ); 13688 13689 if( closeEndACp ){
13690 rs.ctrlpts[0] = rs.ctrlpts[0] + cpM.x * (minCpADist - endACpDist); 13691 rs.ctrlpts[1] = rs.ctrlpts[1] + cpM.y * (minCpADist - endACpDist); 13692 } else { 13693 rs.ctrlpts[0] = tgtCtrlPtIntn[0] + cpM.x * minCpADist; 13694 rs.ctrlpts[1] = tgtCtrlPtIntn[1] + cpM.y * minCpADist; 13695 } 13696 13697 } 13698 13699 if( overlapping ){ 13700 // recalc endpts 13701 this.findEndpoints( edge ); 13702 } 13703 13704 } 13705 13706 if( rs.edgeType === 'multibezier' || rs.edgeType === 'bezier' || rs.edgeType === 'self' || rs.edgeType === 'compound' ){ 13707 rs.allpts = []; 13708 13709 rs.allpts.push( rs.startX, rs.startY ); 13710 13711 for( var b = 0; b + 1 < rs.ctrlpts.length; b += 2 ){ 13712 // ctrl pt itself 13713 rs.allpts.push( rs.ctrlpts[ b ], rs.ctrlpts[ b + 1] ); 13714 13715 // the midpt between ctrlpts as intermediate destination pts 13716 if( b + 3 < rs.ctrlpts.length ){ 13717 rs.allpts.push( (rs.ctrlpts[ b ] + rs.ctrlpts[ b + 2]) / 2, (rs.ctrlpts[ b + 1] + rs.ctrlpts[ b + 3]) / 2 ); 13718 } 13719 } 13720 13721 rs.allpts.push( rs.endX, rs.endY ); 13722 13723 var m, mt; 13724 if( rs.ctrlpts.length / 2 % 2 === 0 ){ 13725 m = rs.allpts.length / 2 - 1; 13726 13727 rs.midX = rs.allpts[ m ]; 13728 rs.midY = rs.allpts[ m + 1]; 13729 } else { 13730 m = rs.allpts.length / 2 - 3; 13731 mt = 0.5; 13732 13733 rs.midX = math.qbezierAt( rs.allpts[ m ], rs.allpts[ m + 2], rs.allpts[ m + 4], mt ); 13734 rs.midY = math.qbezierAt( rs.allpts[ m + 1], rs.allpts[ m + 3], rs.allpts[ m + 5], mt ); 13735 } 13736 13737 } else if( rs.edgeType === 'straight' ){ 13738 // need to calc these after endpts 13739 rs.allpts = [ rs.startX, rs.startY, rs.endX, rs.endY ]; 13740 13741 // default midpt for labels etc 13742 rs.midX = ( rs.startX + rs.endX + rs.arrowStartX + rs.arrowEndX ) / 4; 13743 rs.midY = ( rs.startY + rs.endY + rs.arrowStartY + rs.arrowEndY ) / 4; 13744 13745 } else if( rs.edgeType === 'segments' ){ 13746 rs.allpts = []; 13747 rs.allpts.push( rs.startX, rs.startY ); 13748 rs.allpts.push.apply( rs.allpts, rs.segpts ); 13749 rs.allpts.push( rs.endX, rs.endY ); 13750 13751 if( rs.segpts.length % 4 === 0 ){ 13752 var i2 = rs.segpts.length / 2; 13753 var i1 = i2 - 2; 13754 13755 rs.midX = ( rs.segpts[ i1 ] + rs.segpts[ i2 ] ) / 2; 13756 rs.midY = ( rs.segpts[ i1 + 1] + rs.segpts[ i2 + 1] ) / 2; 13757 } else { 13758 var i1 = rs.segpts.length / 2 - 1; 13759 13760 rs.midX = rs.segpts[ i1 ]; 13761 rs.midY = rs.segpts[ i1 + 1]; 13762 } 13763 13764 13765 } 13766 13767 this.projectLines( edge ); 13768 this.calculateArrowAngles( edge ); 13769 this.recalculateEdgeLabelProjections( edge ); 13770 this.calculateLabelAngles( edge ); 13771 13772 } // for pair edges 13773 } // for pair ids 13774 13775 for( var i = 0; i < haystackEdges.length; i++ ){ 13776 var edge = haystackEdges[ i ]; 13777 var _p = edge._private; 13778 var rscratch = _p.rscratch; 13779 var rs = rscratch; 13780 13781 if( !rscratch.haystack ){ 13782 var angle = Math.random() * 2 * Math.PI; 13783 13784 rscratch.source = { 13785 x: Math.cos( angle ), 13786 y: Math.sin( angle ) 13787 }; 13788 13789 var angle = Math.random() * 2 * Math.PI; 13790 13791 rscratch.target = { 13792 x: Math.cos( angle ), 13793 y: Math.sin( angle ) 13794 }; 13795 13796 } 13797 13798 var src = _p.source; 13799 var tgt = _p.target; 13800 var srcPos = src._private.position; 13801 var tgtPos = tgt._private.position; 13802 var srcW = src.width(); 13803 var tgtW = tgt.width(); 13804 var srcH = src.height(); 13805 var tgtH = tgt.height(); 13806 var radius = edge.pstyle( 'haystack-radius' ).value; 13807 var halfRadius = radius / 2; // b/c have to half width/height 13808 13809 rs.haystackPts = rs.allpts = [ 13810 rs.source.x * srcW * halfRadius + srcPos.x, 13811 rs.source.y * srcH * halfRadius + srcPos.y, 13812 rs.target.x * tgtW * halfRadius + tgtPos.x, 13813 rs.target.y * tgtH * halfRadius + tgtPos.y 13814 ]; 13815 13816 rs.midX = (rs.allpts[0] + rs.allpts[2]) / 2; 13817 rs.midY = (rs.allpts[1] + rs.allpts[3]) / 2; 13818 13819 // always override as haystack in case set to different type previously 13820 rscratch.edgeType = 'haystack'; 13821 rscratch.haystack = true; 13822 13823 this.projectLines( edge ); 13824 this.calculateArrowAngles( edge ); 13825 this.recalculateEdgeLabelProjections( edge ); 13826 this.calculateLabelAngles( edge ); 13827 } 13828 13829 return hashTable; 13830}; 13831 13832var getAngleFromDisp = function( dispX, dispY ){ 13833 return Math.atan2( dispY, dispX ) - Math.PI / 2; 13834}; 13835 13836BRp.calculateArrowAngles = function( edge ){ 13837 var rs = edge._private.rscratch; 13838 var isHaystack = rs.edgeType === 'haystack'; 13839 var isMultibezier = rs.edgeType === 'multibezier'; 13840 var isSegments = rs.edgeType === 'segments'; 13841 var isCompound = rs.edgeType === 'compound'; 13842 var isSelf = rs.edgeType === 'self'; 13843 13844 // Displacement gives direction for arrowhead orientation 13845 var dispX, dispY; 13846 var startX, startY, endX, endY; 13847 13848 var srcPos = edge._private.source._private.position; 13849 var tgtPos = edge._private.target._private.position; 13850 13851 if( isHaystack ){ 13852 startX = rs.haystackPts[0]; 13853 startY = rs.haystackPts[1]; 13854 endX = rs.haystackPts[2]; 13855 endY = rs.haystackPts[3]; 13856 } else { 13857 startX = rs.arrowStartX; 13858 startY = rs.arrowStartY; 13859 endX = rs.arrowEndX; 13860 endY = rs.arrowEndY; 13861 } 13862 13863 // source 13864 // 13865 13866 dispX = srcPos.x - startX; 13867 dispY = srcPos.y - startY; 13868 13869 rs.srcArrowAngle = getAngleFromDisp( dispX, dispY ); 13870 13871 // mid target 13872 // 13873 13874 var midX = rs.midX; 13875 var midY = rs.midY; 13876 13877 if( isHaystack ){ 13878 midX = ( startX + endX ) / 2; 13879 midY = ( startY + endY ) / 2; 13880 } 13881 13882 dispX = endX - startX; 13883 dispY = endY - startY; 13884 13885 if( isSelf ){ 13886 dispX = -1; 13887 dispY = 1; 13888 } else if( isSegments ){ 13889 var pts = rs.allpts; 13890 13891 if( pts.length / 2 % 2 === 0 ){ 13892 var i2 = pts.length / 2; 13893 var i1 = i2 - 2; 13894 13895 dispX = ( pts[ i2 ] - pts[ i1 ] ); 13896 dispY = ( pts[ i2 + 1] - pts[ i1 + 1] ); 13897 } else { 13898 var i2 = pts.length / 2 - 1; 13899 var i1 = i2 - 2; 13900 var i3 = i2 + 2; 13901 13902 dispX = ( pts[ i2 ] - pts[ i1 ] ); 13903 dispY = ( pts[ i2 + 1] - pts[ i1 + 1] ); 13904 } 13905 } else if( isMultibezier || isCompound ){ 13906 var pts = rs.allpts; 13907 var cpts = rs.ctrlpts; 13908 var bp0x, bp0y; 13909 var bp1x, bp1y; 13910 13911 if( cpts.length / 2 % 2 === 0 ){ 13912 var p0 = pts.length / 2 - 1; // startpt 13913 var ic = p0 + 2; 13914 var p1 = ic + 2; 13915 13916 bp0x = math.qbezierAt( pts[ p0 ], pts[ ic ], pts[ p1 ], 0.0 ); 13917 bp0y = math.qbezierAt( pts[ p0 + 1], pts[ ic + 1], pts[ p1 + 1], 0.0 ); 13918 13919 bp1x = math.qbezierAt( pts[ p0 ], pts[ ic ], pts[ p1 ], 0.0001 ); 13920 bp1y = math.qbezierAt( pts[ p0 + 1], pts[ ic + 1], pts[ p1 + 1], 0.0001 ); 13921 } else { 13922 var ic = pts.length / 2 - 1; // ctrpt 13923 var p0 = ic - 2; // startpt 13924 var p1 = ic + 2; // endpt 13925 13926 bp0x = math.qbezierAt( pts[ p0 ], pts[ ic ], pts[ p1 ], 0.4999 ); 13927 bp0y = math.qbezierAt( pts[ p0 + 1], pts[ ic + 1], pts[ p1 + 1], 0.4999 ); 13928 13929 bp1x = math.qbezierAt( pts[ p0 ], pts[ ic ], pts[ p1 ], 0.5 ); 13930 bp1y = math.qbezierAt( pts[ p0 + 1], pts[ ic + 1], pts[ p1 + 1], 0.5 ); 13931 } 13932 13933 dispX = ( bp1x - bp0x ); 13934 dispY = ( bp1y - bp0y ); 13935 } 13936 13937 rs.midtgtArrowAngle = getAngleFromDisp( dispX, dispY ); 13938 13939 rs.midDispX = dispX; 13940 rs.midDispY = dispY; 13941 13942 // mid source 13943 // 13944 13945 dispX *= -1; 13946 dispY *= -1; 13947 13948 if( isSegments ){ 13949 var pts = rs.allpts; 13950 13951 if( pts.length / 2 % 2 === 0 ){ 13952 // already ok 13953 } else { 13954 var i2 = pts.length / 2 - 1; 13955 var i3 = i2 + 2; 13956 13957 dispX = -( pts[ i3 ] - pts[ i2 ] ); 13958 dispY = -( pts[ i3 + 1] - pts[ i2 + 1] ); 13959 } 13960 } 13961 13962 rs.midsrcArrowAngle = getAngleFromDisp( dispX, dispY ); 13963 13964 // target 13965 // 13966 13967 dispX = tgtPos.x - endX; 13968 dispY = tgtPos.y - endY; 13969 13970 rs.tgtArrowAngle = getAngleFromDisp( dispX, dispY ); 13971}; 13972 13973BRp.calculateLabelAngles = function( ele ){ 13974 var _p = ele._private; 13975 var rs = _p.rscratch; 13976 var isEdge = ele.isEdge(); 13977 var rot = ele.pstyle( 'text-rotation' ); 13978 var rotStr = rot.strValue; 13979 13980 if( rotStr === 'none' ){ 13981 rs.labelAngle = rs.sourceLabelAngle = rs.targetLabelAngle = 0; 13982 } else if( isEdge && rotStr === 'autorotate' ){ 13983 rs.labelAngle = Math.atan( rs.midDispY / rs.midDispX ); 13984 rs.sourceLabelAngle = rs.sourceLabelAutoAngle; 13985 rs.targetLabelAngle = rs.targetLabelAutoAngle; 13986 } else if( rotStr === 'autorotate' ){ 13987 rs.labelAngle = rs.sourceLabelAngle = rs.targetLabelAngle = 0; 13988 } else { 13989 rs.labelAngle = rs.sourceLabelAngle = rs.targetLabelAngle = rot.pfValue; 13990 } 13991}; 13992 13993 13994BRp.findEndpoints = function( edge ){ 13995 var r = this; 13996 var intersect; 13997 13998 var source = edge.source()[0]; 13999 var target = edge.target()[0]; 14000 14001 var src_p = source._private; 14002 var tgt_p = target._private; 14003 14004 var srcPos = src_p.position; 14005 var tgtPos = tgt_p.position; 14006 14007 var tgtArShape = edge.pstyle( 'target-arrow-shape' ).value; 14008 var srcArShape = edge.pstyle( 'source-arrow-shape' ).value; 14009 14010 var rs = edge._private.rscratch; 14011 14012 var et = rs.edgeType; 14013 var bezier = et === 'bezier' || et === 'multibezier' || et === 'self' || et === 'compound'; 14014 var multi = et !== 'bezier'; 14015 var lines = et === 'straight' || et === 'segments'; 14016 var segments = et === 'segments'; 14017 var hasEndpts = bezier || multi || lines; 14018 14019 var p1, p2; 14020 14021 if( bezier ){ 14022 var cpStart = [ rs.ctrlpts[0], rs.ctrlpts[1] ]; 14023 var cpEnd = multi ? [ rs.ctrlpts[ rs.ctrlpts.length - 2], rs.ctrlpts[ rs.ctrlpts.length - 1] ] : cpStart; 14024 14025 p1 = cpEnd; 14026 p2 = cpStart; 14027 } else if( lines ){ 14028 var srcArrowFromPt = !segments ? [ tgtPos.x, tgtPos.y ] : rs.segpts.slice( 0, 2 ); 14029 var tgtArrowFromPt = !segments ? [ srcPos.x, srcPos.y ] : rs.segpts.slice( rs.segpts.length - 2 ); 14030 14031 p1 = tgtArrowFromPt; 14032 p2 = srcArrowFromPt; 14033 } 14034 14035 intersect = r.nodeShapes[ this.getNodeShape( target ) ].intersectLine( 14036 tgtPos.x, 14037 tgtPos.y, 14038 target.outerWidth(), 14039 target.outerHeight(), 14040 p1[0], 14041 p1[1], 14042 0 14043 ); 14044 14045 var arrowEnd = math.shortenIntersection( intersect, p1, 14046 r.arrowShapes[ tgtArShape ].spacing( edge ) ); 14047 var edgeEnd = math.shortenIntersection( intersect, p1, 14048 r.arrowShapes[ tgtArShape ].gap( edge ) ); 14049 14050 rs.endX = edgeEnd[0]; 14051 rs.endY = edgeEnd[1]; 14052 14053 rs.arrowEndX = arrowEnd[0]; 14054 rs.arrowEndY = arrowEnd[1]; 14055 14056 intersect = r.nodeShapes[ this.getNodeShape( source ) ].intersectLine( 14057 srcPos.x, 14058 srcPos.y, 14059 source.outerWidth(), 14060 source.outerHeight(), 14061 p2[0], 14062 p2[1], 14063 0 14064 ); 14065 14066 var arrowStart = math.shortenIntersection( 14067 intersect, p2, 14068 r.arrowShapes[ srcArShape ].spacing( edge ) 14069 ); 14070 var edgeStart = math.shortenIntersection( 14071 intersect, p2, 14072 r.arrowShapes[ srcArShape ].gap( edge ) 14073 ); 14074 14075 rs.startX = edgeStart[0]; 14076 rs.startY = edgeStart[1]; 14077 14078 rs.arrowStartX = arrowStart[0]; 14079 rs.arrowStartY = arrowStart[1]; 14080 14081 if( hasEndpts ){ 14082 if( !is.number( rs.startX ) || !is.number( rs.startY ) || !is.number( rs.endX ) || !is.number( rs.endY ) ){ 14083 rs.badLine = true; 14084 } else { 14085 rs.badLine = false;
14086 } 14087 } 14088}; 14089 14090BRp.getArrowWidth = BRp.getArrowHeight = function( edgeWidth ){ 14091 var cache = this.arrowWidthCache = this.arrowWidthCache || {}; 14092 14093 var cachedVal = cache[ edgeWidth ]; 14094 if( cachedVal ){ 14095 return cachedVal; 14096 } 14097 14098 cachedVal = Math.max( Math.pow( edgeWidth * 13.37, 0.9 ), 29 ); 14099 cache[ edgeWidth ] = cachedVal; 14100 14101 return cachedVal; 14102}; 14103 14104module.exports = BRp; 14105 14106},{"../../../collection/zsort":32,"../../../is":83,"../../../math":85,"../../../util":100}],59:[function(_dereq_,module,exports){ 14107'use strict'; 14108 14109var BRp = {}; 14110 14111BRp.getCachedImage = function( url, crossOrigin, onLoad ){ 14112 var r = this; 14113 var imageCache = r.imageCache = r.imageCache || {}; 14114 var cache = imageCache[ url ]; 14115 14116 if( cache ){ 14117 if( !cache.image.complete ){ 14118 cache.image.addEventListener('load', onLoad); 14119 } 14120 14121 return cache.image; 14122 } else { 14123 cache = imageCache[ url ] = imageCache[ url ] || {}; 14124 14125 var image = cache.image = new Image(); // eslint-disable-line no-undef 14126 image.addEventListener('load', onLoad); 14127 14128 // #1582 safari doesn't load data uris with crossOrigin properly 14129 // https://bugs.webkit.org/show_bug.cgi?id=123978 14130 var dataUriPrefix = 'data:'; 14131 var isDataUri = url.substring( 0, dataUriPrefix.length ).toLowerCase() === dataUriPrefix; 14132 if( !isDataUri ){ 14133 image.crossOrigin = crossOrigin; // prevent tainted canvas 14134 } 14135 14136 image.src = url; 14137 14138 return image; 14139 } 14140}; 14141 14142module.exports = BRp; 14143 14144},{}],60:[function(_dereq_,module,exports){ 14145'use strict'; 14146 14147var is = _dereq_( '../../../is' ); 14148var util = _dereq_( '../../../util' ); 14149 14150var BaseRenderer = function( options ){ this.init( options ); }; 14151var BR = BaseRenderer; 14152var BRp = BR.prototype; 14153 14154BRp.clientFunctions = [ 'redrawHint', 'render', 'renderTo', 'matchCanvasSize', 'nodeShapeImpl', 'arrowShapeImpl' ]; 14155 14156BRp.init = function( options ){ 14157 var r = this; 14158 14159 r.options = options; 14160 14161 r.cy = options.cy; 14162 14163 r.container = options.cy.container(); 14164 14165 r.selection = [ undefined, undefined, undefined, undefined, 0]; // Coordinates for selection box, plus enabled flag 14166 14167 r.bezierProjPcts = [ 0.05, 0.225, 0.4, 0.5, 0.6, 0.775, 0.95 ]; 14168 14169 //--Pointer-related data 14170 r.hoverData = {down: null, last: null, 14171 downTime: null, triggerMode: null, 14172 dragging: false, 14173 initialPan: [ null, null ], capture: false}; 14174 14175 r.dragData = {possibleDragElements: []}; 14176 14177 r.touchData = { 14178 start: null, capture: false, 14179 14180 // These 3 fields related to tap, taphold events 14181 startPosition: [ null, null, null, null, null, null ], 14182 singleTouchStartTime: null, 14183 singleTouchMoved: true, 14184 14185 now: [ null, null, null, null, null, null ], 14186 earlier: [ null, null, null, null, null, null ] 14187 }; 14188
14189 r.redraws = 0; 14190 r.showFps = options.showFps; 14191 14192 r.hideEdgesOnViewport = options.hideEdgesOnViewport; 14193 r.hideLabelsOnViewport = options.hideLabelsOnViewport; 14194 r.textureOnViewport = options.textureOnViewport; 14195 r.wheelSensitivity = options.wheelSensitivity; 14196 r.motionBlurEnabled = options.motionBlur; // on by default 14197 r.forcedPixelRatio = options.pixelRatio; 14198 r.motionBlur = options.motionBlur; // for initial kick off 14199 r.motionBlurOpacity = options.motionBlurOpacity; 14200 r.motionBlurTransparency = 1 - r.motionBlurOpacity; 14201 r.motionBlurPxRatio = 1; 14202 r.mbPxRBlurry = 1; //0.8; 14203 r.minMbLowQualFrames = 4; 14204 r.fullQualityMb = false; 14205 r.clearedForMotionBlur = []; 14206 r.desktopTapThreshold = options.desktopTapThreshold; 14207 r.desktopTapThreshold2 = options.desktopTapThreshold * options.desktopTapThreshold; 14208 r.touchTapThreshold = options.touchTapThreshold; 14209 r.touchTapThreshold2 = options.touchTapThreshold * options.touchTapThreshold; 14210 r.tapholdDuration = 500; 14211 14212 r.bindings = []; 14213 r.beforeRenderCallbacks = []; 14214 r.beforeRenderPriorities = { // higher priority execs before lower one 14215 animations: 400, 14216 eleCalcs: 300, 14217 eleTxrDeq: 200, 14218 lyrTxrDeq: 100 14219 }; 14220 14221 r.registerNodeShapes(); 14222 r.registerArrowShapes(); 14223 r.registerCalculationListeners(); 14224 r.load(); 14225}; 14226 14227BRp.notify = function( params ){ 14228 var types; 14229 var r = this; 14230 14231 // the renderer can't be notified after it's destroyed 14232 if( this.destroyed ){ return; } 14233 14234 if( is.array( params.type ) ){ 14235 types = params.type; 14236 14237 } else { 14238 types = [ params.type ]; 14239 } 14240 14241 var has = {}; 14242 for( var i = 0; i < types.length; i++ ){ 14243 var type = types[ i ]; 14244 14245 has[ type ] = true; 14246 } // for 14247 14248 if( has['destroy'] ){ 14249 r.destroy(); 14250 return; 14251 } 14252 14253 if( has['add'] || has['remove'] || has['load'] || has['style'] ){ 14254 r.updateCachedZSortedEles(); 14255 } 14256 14257 if( has['viewport'] ){ 14258 r.redrawHint( 'select', true ); 14259 } 14260 14261 if( has['load'] || has['resize'] ){ 14262 r.invalidateContainerClientCoordsCache(); 14263 r.matchCanvasSize( r.container ); 14264 } 14265 14266 r.redrawHint( 'eles', true ); 14267 r.redrawHint( 'drag', true ); 14268 14269 this.startRenderLoop(); 14270 14271 this.redraw(); 14272}; 14273 14274BRp.destroy = function(){ 14275 var r = this; 14276 14277 r.destroyed = true; 14278 14279 r.cy.stopAnimationLoop(); 14280 14281 for( var i = 0; i < r.bindings.length; i++ ){ 14282 var binding = r.bindings[ i ]; 14283 var b = binding; 14284 var tgt = b.target; 14285 14286 ( tgt.off || tgt.removeEventListener ).apply( tgt, b.args ); 14287 } 14288 14289 r.bindings = []; 14290 r.beforeRenderCallbacks = []; 14291 r.onUpdateEleCalcsFns = []; 14292 14293 if( r.removeObserver ){ 14294 r.removeObserver.disconnect(); 14295 } 14296 14297 if( r.styleObserver ){ 14298 r.styleObserver.disconnect(); 14299 } 14300 14301 if( r.labelCalcDiv ){ 14302 try { 14303 document.body.removeChild( r.labelCalcDiv ); // eslint-disable-line no-undef 14304 } catch( e ){ 14305 // ie10 issue #1014 14306 } 14307 } 14308}; 14309 14310[ 14311 _dereq_( './arrow-shapes' ), 14312 _dereq_( './coord-ele-math' ), 14313 _dereq_( './images' ),
14314 _dereq_( './load-listeners' ), 14315 _dereq_( './node-shapes' ), 14316 _dereq_( './redraw' ) 14317].forEach( function( props ){ 14318 util.extend( BRp, props ); 14319} ); 14320 14321module.exports = BR; 14322 14323},{"../../../is":83,"../../../util":100,"./arrow-shapes":57,"./coord-ele-math":58,"./images":59,"./load-listeners":61,"./node-shapes":62,"./redraw":63}],61:[function(_dereq_,module,exports){ 14324'use strict'; 14325 14326var is = _dereq_( '../../../is' ); 14327var util = _dereq_( '../../../util' ); 14328var math = _dereq_( '../../../math' ); 14329var Event = _dereq_( '../../../event' ); 14330 14331var BRp = {}; 14332 14333BRp.registerBinding = function( target, event, handler, useCapture ){ 14334 var args = Array.prototype.slice.apply( arguments, [1] ); // copy 14335 var b = this.binder( target ); 14336 14337 return b.on.apply( b, args ); 14338}; 14339 14340BRp.binder = function( tgt ){ 14341 var r = this; 14342 14343 var on = function(){ 14344 var args = arguments; 14345 14346 r.bindings.push({ 14347 target: tgt, 14348 args: args 14349 }); 14350 14351 ( tgt.addEventListener || tgt.on ).apply( tgt, args ); 14352 14353 return this; 14354 }; 14355 14356 return { 14357 on: on, 14358 addEventListener: on, 14359 addListener: on, 14360 bind: on 14361 }; 14362}; 14363 14364BRp.nodeIsDraggable = function( node ){ 14365 return ( 14366 node 14367 && node.isNode() 14368 && !node.locked() 14369 && node.grabbable() 14370 ); 14371}; 14372 14373BRp.nodeIsGrabbable = function( node ){ 14374 return ( 14375 this.nodeIsDraggable( node ) 14376 && node.pstyle( 'opacity' ).value !== 0 14377 && node.pstyle( 'visibility' ).value === 'visible' 14378 && node.pstyle( 'display' ).value === 'element' 14379 ); 14380}; 14381 14382BRp.load = function(){ 14383 var r = this; 14384 14385 var triggerEvents = function( target, names, e, props ){ 14386 if( target == null ){ 14387 target = r.cy; 14388 } 14389 14390 for( var i = 0; i < names.length; i++ ){ 14391 var name = names[ i ]; 14392 14393 var event = new Event( e, util.extend( { type: name }, props ) ); 14394 target.trigger( event ); 14395 } 14396 }; 14397 14398 var isMultSelKeyDown = function( e ){ 14399 return e.shiftKey || e.metaKey || e.ctrlKey; // maybe e.altKey 14400 }; 14401 14402 var allowPanningPassthrough = function( down, downs ){ 14403 var allowPassthrough = true; 14404 14405 if( r.cy.hasCompoundNodes() && down && down.isEdge() ){ 14406 // a compound node below the edge => no passthrough panning 14407 for( var i = 0; downs && i < downs.length; i++ ){ 14408 var down = downs[i]; 14409 14410 if( down.isNode() && down.isParent() ){ 14411 allowPassthrough = false; 14412 break; 14413 } 14414 } 14415 } else { 14416 allowPassthrough = true; 14417 } 14418 14419 return allowPassthrough; 14420 }; 14421 14422 var getDragListIds = function( opts ){ 14423 var listHasId; 14424 14425 if( opts.addToList && r.cy.hasCompoundNodes() ){ // only needed for c
14425ompound graphs 14426 if( !opts.addToList.hasId ){ // build ids lookup if doesn't already exist 14427 opts.addToList.hasId = {}; 14428 14429 for( var i = 0; i < opts.addToList.length; i++ ){ 14430 var ele = opts.addToList[ i ]; 14431 14432 opts.addToList.hasId[ ele.id() ] = true; 14433 } 14434 } 14435 14436 listHasId = opts.addToList.hasId; 14437 } 14438 14439 return listHasId || {}; 14440 }; 14441 14442 var setGrabbed = function( ele ){ 14443 ele[0]._private.grabbed = true; 14444 }; 14445 14446 var setFreed = function( ele ){ 14447 ele[0]._private.grabbed = false; 14448 }; 14449 14450 var setInDragLayer = function( ele ){ 14451 ele[0]._private.rscratch.inDragLayer = true; 14452 }; 14453 14454 var setOutDragLayer = function( ele ){ 14455 ele[0]._private.rscratch.inDragLayer = false; 14456 }; 14457 14458 var setGrabTarget = function( ele ){ 14459 ele[0]._private.rscratch.isGrabTarget = true; 14460 }; 14461 14462 var removeGrabTarget = function( ele ){ 14463 ele[0]._private.rscratch.isGrabTarget = false; 14464 }; 14465 14466 var addToDragList = function( ele, opts ){ 14467 var listHasId = getDragListIds( opts ); 14468 14469 if( !listHasId[ ele.id() ] ){ 14470 opts.addToList.push( ele ); 14471 listHasId[ ele.id() ] = true; 14472 14473 setGrabbed( ele ); 14474 } 14475 }; 14476 14477 // helper function to determine which child nodes and inner edges 14478 // of a compound node to be dragged as well as the grabbed and selected nodes 14479 var addDescendantsToDrag = function( node, opts ){ 14480 if( !node.cy().hasCompoundNodes() ){ 14481 return; 14482 } 14483 14484 if( opts.inDragLayer == null && opts.addToList == null ){ return; } // nothing to do 14485 14486 var innerNodes = node.descendants(); 14487 14488 if( opts.inDragLayer ){ 14489 innerNodes.forEach( setInDragLayer );
14490 innerNodes.connectedEdges().forEach( setInDragLayer ); 14491 } 14492 14493 if( opts.addToList ){ 14494 innerNodes.forEach(function( ele ){ 14495 addToDragList( ele, opts ); 14496 }); 14497 } 14498 }; 14499 14500 // adds the given nodes and its neighbourhood to the drag layer 14501 var addNodesToDrag = function( nodes, opts ){ 14502 opts = opts || {}; 14503 14504 var hasCompoundNodes = nodes.cy().hasCompoundNodes(); 14505 14506 if( opts.inDragLayer ){ 14507 nodes.forEach( setInDragLayer ); 14508 14509 nodes.neighborhood().stdFilter(function( ele ){ 14510 return !hasCompoundNodes || ele.isEdge(); 14511 }).forEach( setInDragLayer ); 14512 } 14513 14514 if( opts.addToList ){ 14515 nodes.forEach(function( ele ){ 14516 addToDragList( ele, opts ); 14517 }); 14518 } 14519 14520 addDescendantsToDrag( nodes, opts ); // always add to drag 14521 14522 // also add nodes and edges related to the topmost ancestor 14523 updateAncestorsInDragLayer( nodes, { 14524 inDragLayer: opts.inDragLayer 14525 } ); 14526 14527 r.updateCachedGrabbedEles(); 14528 }; 14529 14530 var addNodeToDrag = addNodesToDrag; 14531 14532 var freeDraggedElements = function( grabbedEles ){ 14533 if( !grabbedEles ){ return; } 14534 14535 grabbedEles.hasId = {}; // clear the id list 14536 14537 // just go over all elements rather than doing a bunch of (possibly expensive) traversals 14538 r.getCachedZSortedEles().forEach(function( ele ){ 14539 setFreed( ele ); 14540 setOutDragLayer( ele ); 14541 removeGrabTarget( ele ); 14542 }); 14543 14544 r.updateCachedGrabbedEles(); 14545 }; 14546 14547 // helper function to determine which ancestor nodes and edges should go 14548 // to the drag layer (or should be removed from drag layer). 14549 var updateAncestorsInDragLayer = function( node, opts ){ 14550 14551 if( opts.inDragLayer == null && opts.addToList == null ){ return; } // nothing to do 14552 14553 if( !node.cy().hasCompoundNodes() ){ 14554 return; 14555 } 14556 14557 // find top-level parent 14558 var parent = node.ancestors().orphans(); 14559 14560 // no parent node: no nodes to add to the drag layer 14561 if( parent.same( node ) ){ 14562 return; 14563 } 14564 14565 var nodes = parent.descendants().spawnSelf() 14566 .merge( parent ) 14567 .unmerge( node ) 14568 .unmerge( node.descendants() ) 14569 ; 14570 14571 var edges = nodes.connectedEdges(); 14572 14573 if( opts.inDragLayer ){
14574 edges.forEach( setInDragLayer ); 14575 nodes.forEach( setInDragLayer ); 14576 } 14577 14578 if( opts.addToList ){ 14579 nodes.forEach(function( ele ){ 14580 addToDragList( ele, opts ); 14581 }); 14582 } 14583 }; 14584 14585 var haveMutationsApi = typeof MutationObserver !== 'undefined'; 14586 14587 // watch for when the cy container is removed from the dom 14588 if( haveMutationsApi ){ 14589 r.removeObserver = new MutationObserver( function( mutns ){ // eslint-disable-line no-undef 14590 for( var i = 0; i < mutns.length; i++ ){ 14591 var mutn = mutns[ i ]; 14592 var rNodes = mutn.removedNodes; 14593 14594 if( rNodes ){ for( var j = 0; j < rNodes.length; j++ ){ 14595 var rNode = rNodes[ j ]; 14596 14597 if( rNode === r.container ){ 14598 r.destroy(); 14599 break; 14600 } 14601 } } 14602 } 14603 } ); 14604 14605 if( r.container.parentNode ){ 14606 r.removeObserver.observe( r.container.parentNode, { childList: true } ); 14607 } 14608 } else { 14609 r.registerBinding( r.container, 'DOMNodeRemoved', function( e ){ 14610 r.destroy(); 14611 } ); 14612 } 14613 14614 var onResize = util.debounce( function(){ 14615 r.cy.invalidateSize(); 14616 r.invalidateContainerClientCoordsCache(); 14617 14618 r.matchCanvasSize( r.container ); 14619 r.redrawHint( 'eles', true ); 14620 r.redrawHint( 'drag', true ); 14621 r.redraw(); 14622 }, 100 ); 14623 14624 if( haveMutationsApi ){ 14625 r.styleObserver = new MutationObserver( onResize ); // eslint-disable-line no-undef 14626 14627 r.styleObserver.observe( r.container, { attributes: true } ); 14628 } 14629 14630 // auto resize 14631 r.registerBinding( window, 'resize', onResize ); // eslint-disable-line no-undef 14632 14633 var invalCtnrBBOnScroll = function( domEle ){ 14634 r.registerBinding( domEle, 'scroll', function( e ){ 14635 r.invalidateContainerClientCoordsCache(); 14636 } ); 14637 }; 14638 14639 var bbCtnr = r.cy.container(); 14640 14641 for( ;; ){ 14642 14643 invalCtnrBBOnScroll( bbCtnr ); 14644 14645 if( bbCtnr.parentNode ){ 14646 bbCtnr = bbCtnr.parentNode; 14647 } else { 14648 break; 14649 } 14650 14651 } 14652 14653 // stop right click menu from appearing on cy 14654 r.registerBinding( r.container, 'contextmenu', function( e ){ 14655 e.preventDefault(); 14656 } ); 14657 14658 var inBoxSelection = function(){ 14659 return r.selection[4] !== 0; 14660 }; 14661 14662 // Primary key 14663 r.registerBinding( r.container, 'mousedown', function mousedownHandler( e ){ 14664 e.preventDefault(); 14665 r.hoverData.capture = true; 14666 r.hoverData.which = e.which; 14667 14668 var cy = r.cy; 14669 var gpos = [ e.clientX, e.clientY ]; 14670 var pos = r.projectIntoViewport( gpos[0], gpos[1] ); 14671 var select = r.selection; 14672 var nears = r.findNearestElements( pos[0], pos[1], true, false ); 14673 var near = nears[0]; 14674 var draggedElements = r.dragData.possibleDragElements; 14675 14676 r.hoverData.mdownPos = pos; 14677 r.hoverData.mdownGPos = gpos; 14678 14679 var checkForTaphold = function(){ 14680 r.hoverData.tapholdCancelled = false; 14681 14682 clearTimeout( r.hoverData.tapholdTimeout ); 14683 14684 r.hoverData.tapholdTimeout = setTimeout( function(){ 14685 14686 if( r.hoverData.tapholdCancelled ){ 14687 return; 14688 } else { 14689 var ele = r.hoverData.down; 14690 14691 if( ele ){ 14692 ele.trigger( new Event( e, { 14693 type: 'taphold', 14694 cyPosition: { x: pos[0], y: pos[1] } 14695 } ) ); 14696 } else { 14697 cy.trigger( new Event( e, { 14698 type: 'taphold', 14699 cyPosition: { x: pos[0], y: pos[1] } 14700 } ) ); 14701 } 14702 } 14703 14704 }, r.tapholdDuration ); 14705 }; 14706 14707 // Right click button 14708 if( e.which == 3 ){ 14709 14710 r.hoverData.cxtStarted = true; 14711 14712 var cxtEvt = new Event( e, { 14713 type: 'cxttapstart', 14714 cyPosition: { x: pos[0], y: pos[1] } 14715 } ); 14716 14717 if( near ){ 14718 near.activate(); 14719 near.trigger( cxtEvt ); 14720 14721 r.hoverData.down = near; 14722 } else { 14723 cy.trigger( cxtEvt ); 14724 } 14725 14726 r.hoverData.downTime = (new Date()).getTime(); 14727 r.hoverData.cxtDragged = false; 14728 14729 // Primary button 14730 } else if( e.which == 1 ){ 14731 14732 if( near ){ 14733 near.activate(); 14734 } 14735 14736 // Element dragging 14737 { 14738 // If something is under the cursor and it is draggable, prepare to grab it 14739 if( near != null ){ 14740 14741 if( r.nodeIsGrabbable( near ) ){ 14742 14743 var grabEvent = new Event( e, { 14744 type: 'grab', 14745 cyPosition: { x: pos[0], y: pos[1] } 14746 } ); 14747 14748 setGrabTarget( near ); 14749 14750 if( !near.selected() ){ 14751 14752 draggedElements = r.dragData.possibleDragElements = []; 14753 addNodeToDrag( near, { addToList: draggedElements } ); 14754 14755 near.trigger( grabEvent ); 14756 14757 } else if( near.selected() ){ 14758 draggedElements = r.dragData.possibleDragElements = [ ]; 14759 14760 var selectedNodes = cy.$( function(){ return this.isNode() && this.selected() && r.nodeIsGrabbable( this ); } ); 14761 14762 addNodesToDrag( selectedNodes, { addToList: draggedElements } ); 14763 14764 near.trigger( grabEvent ); 14765 } 14766 14767 r.redrawHint( 'eles', true ); 14768 r.redrawHint( 'drag', true ); 14769 14770 } 14771 14772 } 14773 14774 r.hoverData.down = near; 14775 r.hoverData.downs = nears; 14776 r.hoverData.downTime = (new Date()).getTime(); 14777 } 14778 14779 triggerEvents( near, [ 'mousedown', 'tapstart', 'vmousedown' ], e, { 14780 cyPosition: { x: pos[0], y: pos[1] } 14781 } ); 14782 14783 if( near == null ){ 14784 select[4] = 1; 14785 14786 r.data.bgActivePosistion = { 14787 x: pos[0], 14788 y: pos[1] 14789 }; 14790 14791 r.redrawHint( 'select', true ); 14792 14793 r.redraw(); 14794 } else if( near.isEdge() ){ 14795 select[4] = 1; // for future pan 14796 } 14797 14798 checkForTaphold(); 14799 14800 } 14801 14802 // Initialize selection box coordinates 14803 select[0] = select[2] = pos[0]; 14804 select[1] = select[3] = pos[1]; 14805 14806 }, false ); 14807 14808 r.registerBinding( window, 'mousemove', function mousemoveHandler( e ){ // eslint-disable-line no-undef 14809 var preventDefault = false;
14810 var capture = r.hoverData.capture; 14811 14812 // save cycles if mouse events aren't to be captured 14813 if( !capture ){ 14814 var containerPageCoords = r.findContainerClientCoords(); 14815 14816 if( e.clientX > containerPageCoords[0] && e.clientX < containerPageCoords[0] + r.canvasWidth 14817 && e.clientY > containerPageCoords[1] && e.clientY < containerPageCoords[1] + r.canvasHeight 14818 ){ 14819 // inside container bounds so OK 14820 } else { 14821 return; 14822 } 14823 14824 var cyContainer = r.container; 14825 var target = e.target; 14826 var tParent = target.parentNode; 14827 var containerIsTarget = false; 14828 14829 while( tParent ){ 14830 if( tParent === cyContainer ){ 14831 containerIsTarget = true; 14832 break; 14833 } 14834 14835 tParent = tParent.parentNode; 14836 } 14837 14838 if( !containerIsTarget ){ return; } // if target is outisde cy container, then this event is not for us 14839 } 14840 14841 var cy = r.cy; 14842 var zoom = cy.zoom(); 14843 var gpos = [ e.clientX, e.clientY ]; 14844 var pos = r.projectIntoViewport( gpos[0], gpos[1] ); 14845 var mdownPos = r.hoverData.mdownPos; 14846 var mdownGPos = r.hoverData.mdownGPos; 14847 var select = r.selection; 14848 14849 var near = null; 14850 if( !r.hoverData.draggingEles && !r.hoverData.dragging && !r.hoverData.selecting ){ 14851 near = r.findNearestElement( pos[0], pos[1], true, false ); 14852 } 14853 var last = r.hoverData.last; 14854 var down = r.hoverData.down; 14855 14856 var disp = [ pos[0] - select[2], pos[1] - select[3] ]; 14857 14858 var draggedElements = r.dragData.possibleDragElements; 14859 14860 var isOverThresholdDrag; 14861 14862 if( mdownGPos ){ 14863 var dx = gpos[0] - mdownGPos[0]; 14864 var dx2 = dx * dx; 14865 var dy = gpos[1] - mdownGPos[1]; 14866 var dy2 = dy * dy; 14867 var dist2 = dx2 + dy2; 14868 14869 isOverThresholdDrag = dist2 >= r.desktopTapThreshold2; 14870 } 14871 14872 var multSelKeyDown = isMultSelKeyDown( e ); 14873 14874 if (isOverThresholdDrag) { 14875 r.hoverData.tapholdCancelled = true; 14876 } 14877 14878 var updateDragDelta = function(){ 14879 var dragDelta = r.hoverData.dragDelta = r.hoverData.dragDelta || []; 14880 14881 if( dragDelta.length === 0 ){ 14882 dragDelta.push( disp[0] ); 14883 dragDelta.push( disp[1] ); 14884 } else { 14885 dragDelta[0] += disp[0]; 14886 dragDelta[1] += disp[1]; 14887 } 14888 }; 14889 14890 14891 preventDefault = true; 14892 14893 triggerEvents( near, [ 'mousemove', 'vmousemove', 'tapdrag' ], e, { 14894 cyPosition: { x: pos[0], y: pos[1] } 14895 } ); 14896 14897 // trigger context drag if rmouse down 14898 if( r.hoverData.which === 3 ){ 14899 // but only if over threshold 14900 if( isOverThresholdDrag ){ 14901 var cxtEvt = new Event( e, { 14902 type: 'cxtdrag', 14903 cyPosition: { x: pos[0], y: pos[1] } 14904 } ); 14905 14906 if( down ){ 14907 down.trigger( cxtEvt ); 14908 } else { 14909 cy.trigger( cxtEvt ); 14910 } 14911 14912 r.hoverData.cxtDragged = true; 14913 14914 if( !r.hoverData.cxtOver || near !== r.hoverData.cxtOver ){ 14915 14916 if( r.hoverData.cxtOver ){ 14917 r.hoverData.cxtOver.trigger( new Event( e, { 14918 type: 'cxtdragout', 14919 cyPosition: { x: pos[0], y: pos[1] } 14920 } ) ); 14921 } 14922 14923 r.hoverData.cxtOver = near; 14924 14925 if( near ){ 14926 near.trigger( new Event( e, { 14927 type: 'cxtdragover', 14928 cyPosition: { x: pos[0], y: pos[1] } 14929 } ) ); 14930 } 14931 14932 } 14933 } 14934 14935 // Check if we are drag panning the entire graph 14936 } else if( r.hoverData.dragging ){ 14937 preventDefault = true; 14938 14939 if( cy.panningEnabled() && cy.userPanningEnabled() ){ 14940 var deltaP; 14941 14942 if( r.hoverData.justStartedPan ){ 14943 var mdPos = r.hoverData.mdownPos; 14944 14945 deltaP = { 14946 x: ( pos[0] - mdPos[0] ) * zoom, 14947 y: ( pos[1] - mdPos[1] ) * zoom 14948 }; 14949 14950 r.hoverData.justStartedPan = false; 14951 14952 } else { 14953 deltaP = { 14954 x: disp[0] * zoom, 14955 y: disp[1] * zoom 14956 }; 14957 14958 } 14959 14960 cy.panBy( deltaP ); 14961 14962 r.hoverData.dragged = true; 14963 } 14964 14965 // Needs reproject due to pan changing viewport 14966 pos = r.projectIntoViewport( e.clientX, e.clientY ); 14967 14968 // Checks primary button down & out of time & mouse not moved much 14969 } else if(
14970 select[4] == 1 && (down == null || down.isEdge()) 14971 ){ 14972 14973 if( isOverThresholdDrag ){ 14974 14975 if( !r.hoverData.dragging && cy.boxSelectionEnabled() && ( multSelKeyDown || !cy.panningEnabled() || !cy.userPanningEnabled() ) ){ 14976 r.data.bgActivePosistion = undefined; 14977 14978 if( !r.hoverData.selecting ){ 14979 cy.trigger('boxstart'); 14980 } 14981 14982 r.hoverData.selecting = true; 14983 14984 r.redrawHint( 'select', true ); 14985 r.redraw(); 14986 14987 } else if( !r.hoverData.selecting && cy.panningEnabled() && cy.userPanningEnabled() ){ 14988 var allowPassthrough = allowPanningPassthrough( down, r.hoverData.downs ); 14989 14990 if( allowPassthrough ){ 14991 r.hoverData.dragging = true; 14992 r.hoverData.justStartedPan = true; 14993 select[4] = 0; 14994 14995 r.data.bgActivePosistion = math.array2point( mdownPos ); 14996 14997 r.redrawHint( 'select', true ); 14998 r.redraw(); 14999 } 15000 } 15001 15002 if( down && down.isEdge() && down.active() ){ down.unactivate(); } 15003 15004 } 15005 15006 } else { 15007 if( down && down.isEdge() && down.active() ){ down.unactivate(); } 15008 15009 if( ( !down || !down.grabbed() ) && near != last ){ 15010 15011 if( last ){ 15012 triggerEvents( last, [ 'mouseout', 'tapdragout' ], e, { 15013 cyPosition: { x: pos[0], y: pos[1] } 15014 } ); 15015 } 15016 15017 if( near ){ 15018 triggerEvents( near, [ 'mouseover', 'tapdragover' ], e, { 15019 cyPosition: { x: pos[0], y: pos[1] } 15020 } ); 15021 } 15022 15023 r.hoverData.last = near; 15024 } 15025 15026 if( down && r.nodeIsDraggable( down ) ){ 15027 15028 if( isOverThresholdDrag ){ // then drag 15029 15030 var justStartedDrag = !r.dragData.didDrag; 15031 15032 if( justStartedDrag ){ 15033 r.redrawHint( 'eles', true ); 15034 } 15035 15036 r.dragData.didDrag = true; // indicate that we actually did drag the node 15037 15038 var toTrigger = []; 15039 15040 // now, add the elements to the drag layer if not done already 15041 if( !r.hoverData.draggingEles ){ 15042 addNodesToDrag( cy.collection( draggedElements ), { inDragLayer: true } ); 15043 } 15044 15045 for( var i = 0; i < draggedElements.length; i++ ){ 15046 var dEle = draggedElements[ i ]; 15047 15048 // Locked nodes not draggable, as well as non-visible nodes 15049 if( r.nodeIsDraggable( dEle ) && dEle.grabbed() ){ 15050 var dPos = dEle._private.position; 15051 15052 toTrigger.push( dEle ); 15053 15054 if( is.number( disp[0] ) && is.number( disp[1] ) ){ 15055 var updatePos = !dEle.isParent(); 15056 15057 if( updatePos ){ 15058 dPos.x += disp[0]; 15059 dPos.y += disp[1]; 15060 } 15061 15062 if( justStartedDrag ){ 15063 var dragDelta = r.hoverData.dragDelta; 15064 15065 if( updatePos && dragDelta && is.number( dragDelta[0] ) && is.number( dragDelta[1] ) ){ 15066 dPos.x += dragDelta[0]; 15067 dPos.y += dragDelta[1]; 15068 } 15069 } 15070 } 15071 15072 } 15073 } 15074 15075 r.hoverData.draggingEles = true; 15076 15077 var tcol = cy.collection( toTrigger ); 15078 15079 tcol.updateCompoundBounds(); 15080 tcol.trigger( 'position drag' ); 15081 15082 r.redrawHint( 'drag', true ); 15083 r.redraw(); 15084 15085 } else { // otherwise save drag delta for when we actually start dragging so the relative grab pos is constant 15086 updateDragDelta(); 15087 } 15088 } 15089 15090 // prevent the dragging from triggering text selection on the page 15091 preventDefault = true; 15092 } 15093 15094 select[2] = pos[0]; select[3] = pos[1]; 15095 15096 if( preventDefault ){ 15097 if( e.stopPropagation ) e.stopPropagation(); 15098 if( e.preventDefault ) e.preventDefault(); 15099 return false; 15100 } 15101 }, false ); 15102 15103 r.registerBinding( window, 'mouseup', function mouseupHandler( e ){ // eslint-disable-line no-undef 15104 var capture = r.hoverData.capture; 15105 if( !capture ){ return; } 15106 r.hoverData.capture = false; 15107 15108 var cy = r.cy; var pos = r.projectIntoViewport( e.clientX, e.clientY ); var select = r.selection; 15109 var near = r.findNearestElement( pos[0], pos[1], true, false ); 15110 var draggedElements = r.dragData.possibleDragElements; var down = r.hoverData.down; 15111 var multSelKeyDown = isMultSelKeyDown( e ); 15112 15113 if( r.data.bgActivePosistion ){ 15114 r.redrawHint( 'select', true ); 15115 r.redraw(); 15116 } 15117 15118 r.hoverData.tapholdCancelled = true; 15119 15120 r.data.bgActivePosistion = undefined; // not active bg now 15121 15122 if( down ){ 15123 down.unactivate(); 15124 } 15125 15126 if( r.hoverData.which === 3 ){ 15127 var cxtEvt = new Event( e, { 15128 type: 'cxttapend', 15129 cyPosition: { x: pos[0], y: pos[1] } 15130 } ); 15131 15132 if( down ){ 15133 down.trigger( cxtEvt ); 15134 } else { 15135 cy.trigger( cxtEvt ); 15136 } 15137 15138 if( !r.hoverData.cxtDragged ){ 15139 var cxtTap = new Event( e, { 15140 type: 'cxttap', 15141 cyPosition: { x: pos[0], y: pos[1] } 15142 } ); 15143 15144 if( down ){ 15145 down.trigger( cxtTap ); 15146 } else { 15147 cy.trigger( cxtTap ); 15148 } 15149 } 15150 15151 r.hoverData.cxtDragged = false;
15152 r.hoverData.which = null; 15153 15154 } else if( r.hoverData.which === 1 ){ 15155 15156 // Deselect all elements if nothing is currently under the mouse cursor and we aren't dragging something 15157 if( (down == null) // not mousedown on node 15158 && !r.dragData.didDrag // didn't move the node around 15159 && !r.hoverData.selecting // not box selection 15160 && !r.hoverData.dragged // didn't pan 15161 && !isMultSelKeyDown( e ) 15162 ){ 15163 15164 cy.$( function(){ 15165 return this.selected(); 15166 } ).unselect(); 15167 15168 if( draggedElements.length > 0 ){ 15169 r.redrawHint( 'eles', true ); 15170 } 15171 15172 r.dragData.possibleDragElements = draggedElements = []; 15173 } 15174 15175 triggerEvents( near, [ 'mouseup', 'tapend', 'vmouseup' ], e, { 15176 cyPosition: { x: pos[0], y: pos[1] } 15177 } ); 15178 15179 if( 15180 !r.dragData.didDrag // didn't move a node around 15181 && !r.hoverData.dragged // didn't pan 15182 && !r.hoverData.selecting // not box selection 15183 ){ 15184 triggerEvents( down, ['click', 'tap', 'vclick'], e, { 15185 cyPosition: { x: pos[0], y: pos[1] } 15186 } ); 15187 } 15188 15189 // Single selection 15190 if( near == down && !r.dragData.didDrag && !r.hoverData.selecting ){ 15191 if( near != null && near._private.selectable ){ 15192 15193 if( r.hoverData.dragging ){ 15194 // if panning, don't change selection state 15195 } else if( cy.selectionType() === 'additive' || multSelKeyDown ){ 15196 if( near.selected() ){ 15197 near.unselect(); 15198 } else { 15199 near.select(); 15200 } 15201 } else { 15202 if( !multSelKeyDown ){ 15203 cy.$( ':selected' ).unmerge( near ).unselect(); 15204 near.select(); 15205 } 15206 } 15207 15208 r.redrawHint( 'eles', true ); 15209 } 15210 } 15211 15212 if( r.hoverData.selecting ){ 15213 var box = cy.collection( r.getAllInBox( select[0], select[1], select[2], select[3] ) ); 15214 15215 r.redrawHint( 'select', true ); 15216 15217 if( box.length > 0 ){ 15218 r.redrawHint( 'eles', true ); 15219 } 15220 15221 cy.trigger('boxend'); 15222 15223 var eleWouldBeSelected = function( ele ){ return ele.selectable() && !ele.selected(); }; 15224 15225 if( cy.selectionType() === 'additive' ){ 15226 box 15227 .trigger('box') 15228 .stdFilter( eleWouldBeSelected ) 15229 .select() 15230 .trigger('boxselect') 15231 ; 15232 } else { 15233 if( !multSelKeyDown ){ 15234 cy.$( ':selected' ).unmerge( box ).unselect(); 15235 } 15236 15237 box 15238 .trigger('box') 15239 .stdFilter( eleWouldBeSelected ) 15240 .select() 15241 .trigger('boxselect') 15242 ; 15243 } 15244 15245 // always need redraw in case eles unselectable 15246 r.redraw(); 15247 15248 } 15249 15250 // Cancel drag pan 15251 if( r.hoverData.dragging ){ 15252 r.hoverData.dragging = false; 15253 15254 r.redrawHint( 'select', true ); 15255 r.redrawHint( 'eles', true ); 15256 15257 r.redraw(); 15258 } 15259 15260 if( !select[4] ) { 15261 r.redrawHint('drag', true); 15262 r.redrawHint('eles', true); 15263 15264 var downWasGrabbed = down && down.grabbed(); 15265 15266 freeDraggedElements( draggedElements ); 15267 15268 if( downWasGrabbed ){ down.trigger('free'); } 15269 } 15270 15271 } // else not right mouse 15272 15273 select[4] = 0; r.hoverData.down = null; 15274 15275 r.hoverData.cxtStarted = false; 15276 r.hoverData.draggingEles = false; 15277 r.hoverData.selecting = false; 15278 r.dragData.didDrag = false; 15279 r.hoverData.dragged = false; 15280 r.hoverData.dragDelta = []; 15281 r.hoverData.mdownPos = null; 15282 r.hoverData.mdownGPos = null; 15283 15284 }, false ); 15285 15286 var wheelHandler = function( e ){ 15287 15288 15289 if( r.scrollingPage ){ return; } // while scrolling, ignore wheel-to-zoom 15290 15291 var cy = r.cy; 15292 var pos = r.projectIntoViewport( e.clientX, e.clientY ); 15293 var rpos = [ pos[0] * cy.zoom() + cy.pan().x, 15294 pos[1] * cy.zoom() + cy.pan().y ]; 15295 15296 if( r.hoverData.draggingEles || r.hoverData.dragging || r.hoverData.cxtStarted || inBoxSelection() ){ // if pan dragging or cxt dragging, wheel movements make no zoom 15297 e.preventDefault(); 15298 return; 15299 } 15300 15301 if( cy.panningEnabled() && cy.userPanningEnabled() && cy.zoomingEnabled() && cy.userZoomingEnabled() ){ 15302 e.preventDefault(); 15303 15304 r.data.wheelZooming = true; 15305 clearTimeout( r.data.wheelTimeout ); 15306 r.data.wheelTimeout = setTimeout( function(){ 15307 r.data.wheelZooming = false;
15308 15309 r.redrawHint( 'eles', true ); 15310 r.redraw(); 15311 }, 150 ); 15312 15313 var diff; 15314 15315 if( e.deltaY != null ){ 15316 diff = e.deltaY / -250; 15317 } else if( e.wheelDeltaY != null ){ 15318 diff = e.wheelDeltaY / 1000; 15319 } else { 15320 diff = e.wheelDelta / 1000; 15321 } 15322 15323 diff = diff * r.wheelSensitivity; 15324 15325 var needsWheelFix = e.deltaMode === 1; 15326 if( needsWheelFix ){ // fixes slow wheel events on ff/linux and ff/windows 15327 diff *= 33; 15328 } 15329 15330 cy.zoom( { 15331 level: cy.zoom() * Math.pow( 10, diff ), 15332 renderedPosition: { x: rpos[0], y: rpos[1] } 15333 } ); 15334 } 15335 15336 }; 15337 15338 // Functions to help with whether mouse wheel should trigger zooming 15339 // -- 15340 r.registerBinding( r.container, 'wheel', wheelHandler, true ); 15341 15342 // disable nonstandard wheel events 15343 // r.registerBinding(r.container, 'mousewheel', wheelHandler, true); 15344 // r.registerBinding(r.container, 'DOMMouseScroll', wheelHandler, true); 15345 // r.registerBinding(r.container, 'MozMousePixelScroll', wheelHandler, true); // older firefox 15346 15347 r.registerBinding( window, 'scroll', function scrollHandler( e ){ // eslint-disable-line no-undef 15348 r.scrollingPage = true; 15349 15350 clearTimeout( r.scrollingPageTimeout ); 15351 r.scrollingPageTimeout = setTimeout( function(){ 15352 r.scrollingPage = false; 15353 }, 250 ); 15354 }, true ); 15355 15356 // Functions to help with handling mouseout/mouseover on the Cytoscape container 15357 // Handle mouseout on Cytoscape container 15358 r.registerBinding( r.container, 'mouseout', function mouseOutHandler( e ){ 15359 var pos = r.projectIntoViewport( e.clientX, e.clientY ); 15360 15361 r.cy.trigger( new Event( e, { 15362 type: 'mouseout', 15363 cyPosition: { x: pos[0], y: pos[1] } 15364 } ) ); 15365 }, false ); 15366 15367 r.registerBinding( r.container, 'mouseover', function mouseOverHandler( e ){ 15368 var pos = r.projectIntoViewport( e.clientX, e.clientY ); 15369 15370 r.cy.trigger( new Event( e, { 15371 type: 'mouseover', 15372 cyPosition: { x: pos[0], y: pos[1] } 15373 } ) ); 15374 }, false ); 15375 15376 var f1x1, f1y1, f2x1, f2y1; // starting points for pinch-to-zoom 15377 var distance1, distance1Sq; // initial distance between finger 1 and finger 2 for pinch-to-zoom 15378 var center1, modelCenter1; // center point on start pinch to zoom 15379 var offsetLeft, offsetTop; 15380 var containerWidth, containerHeight; 15381 var twoFingersStartInside; 15382 15383 var distance = function( x1, y1, x2, y2 ){ 15384 return Math.sqrt( (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) ); 15385 }; 15386 15387 var distanceSq = function( x1, y1, x2, y2 ){ 15388 return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); 15389 }; 15390 15391 var touchstartHandler; 15392 r.registerBinding( r.container, 'touchstart', touchstartHandler = function( e ){ 15393 r.touchData.capture = true; 15394 r.data.bgActivePosistion = undefined; 15395 15396 var cy = r.cy; 15397 var now = r.touchData.now; 15398 var earlier = r.touchData.earlier; 15399 15400 if( e.touches[0] ){ var pos = r.projectIntoViewport( e.touches[0].clientX, e.touches[0].clientY ); now[0] = pos[0]; now[1] = pos[1]; } 15401 if( e.touches[1] ){ var pos = r.projectIntoViewport( e.touches[1].clientX, e.touches[1].clientY ); now[2] = pos[0]; now[3] = pos[1]; } 15402 if( e.touches[2] ){ var pos = r.projectIntoViewport( e.touches[2].clientX, e.touches[2].clientY ); now[4] = pos[0]; now[5] = pos[1]; } 15403 15404 // record starting points for pinch-to-zoom 15405 if( e.touches[1] ){ 15406 15407 freeDraggedElements( r.dragData.touchDragEles ); 15408 15409 var offsets = r.findContainerClientCoords(); 15410 offsetLeft = offsets[0]; 15411 offsetTop = offsets[1]; 15412 containerWidth = offsets[2]; 15413 containerHeight = offsets[3]; 15414 15415 f1x1 = e.touches[0].clientX - offsetLeft; 15416 f1y1 = e.touches[0].clientY - offsetTop; 15417 15418 f2x1 = e.touches[1].clientX - offsetLeft; 15419 f2y1 = e.touches[1].clientY - offsetTop; 15420 15421 twoFingersStartInside = 15422 0 <= f1x1 && f1x1 <= containerWidth 15423 && 0 <= f2x1 && f2x1 <= containerWidth 15424 && 0 <= f1y1 && f1y1 <= containerHeight 15425 && 0 <= f2y1 && f2y1 <= containerHeight 15426 ; 15427 15428 var pan = cy.pan(); 15429 var zoom = cy.zoom(); 15430 15431 distance1 = distance( f1x1, f1y1, f2x1, f2y1 ); 15432 distance1Sq = distanceSq( f1x1, f1y1, f2x1, f2y1 ); 15433 center1 = [ (f1x1 + f2x1) / 2, (f1y1 + f2y1) / 2 ]; 15434 modelCenter1 = [ 15435 (center1[0] - pan.x) / zoom, 15436 (center1[1] - pan.y) / zoom 15437 ]; 15438 15439 // consider context tap 15440 var cxtDistThreshold = 200; 15441 var cxtDistThresholdSq = cxtDistThreshold * cxtDistThreshold; 15442 if( distance1Sq < cxtDistThresholdSq && !e.touches[2] ){ 15443 15444 var near1 = r.findNearestElement( now[0], now[1], true, true ); 15445 var near2 = r.findNearestElement( now[2], now[3], true, true ); 15446 15447 if( near1 && near1.isNode() ){ 15448 near1.activate().trigger( new Event( e, { 15449 type: 'cxttapstart', 15450 cyPosition: { x: now[0], y: now[1] } 15451 } ) ); 15452 r.touchData.start = near1; 15453 15454 } else if( near2 && near2.isNode() ){ 15455 near2.activate().trigger( new Event( e, { 15456 type: 'cxttapstart', 15457 cyPosition: { x: now[0], y: now[1] } 15458 } ) ); 15459 r.touchData.start = near2; 15460 15461 } else { 15462 cy.trigger( new Event( e, { 15463 type: 'cxttapstart', 15464 cyPosition: { x: now[0], y: now[1] } 15465 } ) ); 15466 r.touchData.start = null; 15467 } 15468 15469 if( r.touchData.start ){ r.touchData.start._private.grabbed = false; } 15470 r.touchData.cxt = true; 15471 r.touchData.cxtDragged = false;
15472 r.data.bgActivePosistion = undefined; 15473 15474 r.redraw(); 15475 return; 15476 15477 } 15478 15479 } 15480 15481 if( e.touches[2] ){ 15482 // ignore 15483 } else if( e.touches[1] ){ 15484 // ignore 15485 } else if( e.touches[0] ){ 15486 var nears = r.findNearestElements( now[0], now[1], true, true ); 15487 var near = nears[0]; 15488 15489 if( near != null ){ 15490 near.activate(); 15491 15492 r.touchData.start = near; 15493 r.touchData.starts = nears; 15494 15495 if( r.nodeIsGrabbable( near ) ){ 15496 15497 var draggedEles = r.dragData.touchDragEles = []; 15498 15499 r.redrawHint( 'eles', true ); 15500 r.redrawHint( 'drag', true ); 15501 15502 if( near.selected() ){ 15503 // reset drag elements, since near will be added again 15504 15505 var selectedNodes = cy.$( function(){ 15506 return this.selected() && r.nodeIsGrabbable( this ); 15507 } ); 15508 15509 addNodesToDrag( selectedNodes, { addToList: draggedEles } ); 15510 } else { 15511 addNodeToDrag( near, { addToList: draggedEles } ); 15512 } 15513 15514 setGrabTarget( near ); 15515 15516 near.trigger( new Event( e, { 15517 type: 'grab', 15518 cyPosition: { x: now[0], y: now[1] } 15519 } ) ); 15520 } 15521 } 15522 15523 triggerEvents( near, [ 'touchstart', 'tapstart', 'vmousedown' ], e, { 15524 cyPosition: { x: now[0], y: now[1] } 15525 } ); 15526 15527 if( near == null ){ 15528 r.data.bgActivePosistion = { 15529 x: pos[0], 15530 y: pos[1] 15531 }; 15532 15533 r.redrawHint( 'select', true ); 15534 r.redraw(); 15535 } 15536 15537 15538 // Tap, taphold 15539 // ----- 15540 15541 r.touchData.startPosition = []; 15542 for (var i=0; i<now.length; i++) { 15543 earlier[i] = now[i]; 15544 r.touchData.startPosition[i] = now[i]; 15545 } 15546 15547 r.touchData.startGPosition = [ e.touches[0].clientX, e.touches[0].clientY ]; 15548 15549 r.touchData.singleTouchMoved = false; 15550 r.touchData.singleTouchStartTime = +new Date(); 15551 15552 clearTimeout( r.touchData.tapholdTimeout ); 15553 r.touchData.tapholdTimeout = setTimeout( function(){ 15554 if( 15555 r.touchData.singleTouchMoved === false 15556 && !r.pinching // if pinching, then taphold unselect shouldn't take effect 15557 && !r.touchData.selecting // box selection shouldn't allow taphold through 15558 ){ 15559 triggerEvents( r.touchData.start, [ 'taphold' ], e, { 15560 cyPosition: { x: now[0], y: now[1] } 15561 } ); 15562 15563 if( !r.touchData.start ){ 15564 cy.$( ':selected' ).unselect(); 15565 } 15566 15567 } 15568 }, r.tapholdDuration ); 15569 } 15570 15571 }, false ); 15572 15573 var touchmoveHandler; 15574 r.registerBinding(window, 'touchmove', touchmoveHandler = function(e) { // eslint-disable-line no-undef 15575 var select = r.selection; 15576 var capture = r.touchData.capture; 15577 var cy = r.cy; 15578 var now = r.touchData.now; 15579 var earlier = r.touchData.earlier; 15580 var zoom = cy.zoom(); 15581 15582 if( e.touches[0] ){ var pos = r.projectIntoViewport( e.touches[0].clientX, e.touches[0].clientY ); now[0] = pos[0]; now[1] = pos[1]; } 15583 if( e.touches[1] ){ var pos = r.projectIntoViewport( e.touches[1].clientX, e.touches[1].clientY ); now[2] = pos[0]; now[3] = pos[1]; } 15584 if( e.touches[2] ){ var pos = r.projectIntoViewport( e.touches[2].clientX, e.touches[2].clientY ); now[4] = pos[0]; now[5] = pos[1]; } 15585 15586 var isOverThresholdDrag; 15587 15588 if( capture && e.touches[0] ){ 15589 var disp = []; for (var j=0;j<now.length;j++) { disp[j] = now[j] - earlier[j]; } 15590 var startGPos = r.touchData.startGPosition; 15591 var dx = e.touches[0].clientX - startGPos[0]; 15592 var dx2 = dx * dx; 15593 var dy = e.touches[0].clientY - startGPos[1]; 15594 var dy2 = dy * dy; 15595 var dist2 = dx2 + dy2; 15596 15597 isOverThresholdDrag = dist2 >= r.touchTapThreshold2; 15598 } 15599 15600 // context swipe cancelling 15601 if( capture && r.touchData.cxt ){ 15602 e.preventDefault(); 15603 15604 var f1x2 = e.touches[0].clientX - offsetLeft, f1y2 = e.touches[0].clientY - offsetTop; 15605 var f2x2 = e.touches[1].clientX - offsetLeft, f2y2 = e.touches[1].clientY - offsetTop; 15606 // var distance2 = distance( f1x2, f1y2, f2x2, f2y2 ); 15607 var distance2Sq = distanceSq( f1x2, f1y2, f2x2, f2y2 ); 15608 var factorSq = distance2Sq / distance1Sq; 15609 15610 var distThreshold = 150; 15611 var distThresholdSq = distThreshold * distThreshold; 15612 var factorThreshold = 1.5; 15613 var factorThresholdSq = factorThreshold * factorThreshold; 15614 15615 // cancel ctx gestures if the distance b/t the fingers increases 15616 if( factorSq >= factorThresholdSq || distance2Sq >= distThresholdSq ){ 15617 r.touchData.cxt = false;
15618 if( r.touchData.start ){ r.touchData.start.unactivate(); r.touchData.start = null; } 15619 r.data.bgActivePosistion = undefined; 15620 r.redrawHint( 'select', true ); 15621 15622 var cxtEvt = new Event( e, { 15623 type: 'cxttapend', 15624 cyPosition: { x: now[0], y: now[1] } 15625 } ); 15626 if( r.touchData.start ){ 15627 r.touchData.start.trigger( cxtEvt ); 15628 } else { 15629 cy.trigger( cxtEvt ); 15630 } 15631 } 15632 15633 } 15634 15635 // context swipe 15636 if( capture && r.touchData.cxt ){ 15637 var cxtEvt = new Event( e, { 15638 type: 'cxtdrag', 15639 cyPosition: { x: now[0], y: now[1] } 15640 } ); 15641 r.data.bgActivePosistion = undefined; 15642 r.redrawHint( 'select', true ); 15643 15644 if( r.touchData.start ){ 15645 r.touchData.start.trigger( cxtEvt ); 15646 } else { 15647 cy.trigger( cxtEvt ); 15648 } 15649 15650 if( r.touchData.start ){ r.touchData.start._private.grabbed = false; } 15651 r.touchData.cxtDragged = true; 15652 15653 var near = r.findNearestElement( now[0], now[1], true, true ); 15654 15655 if( !r.touchData.cxtOver || near !== r.touchData.cxtOver ){ 15656 15657 if( r.touchData.cxtOver ){ 15658 r.touchData.cxtOver.trigger( new Event( e, { 15659 type: 'cxtdragout', 15660 cyPosition: { x: now[0], y: now[1] } 15661 } ) ); 15662 } 15663 15664 r.touchData.cxtOver = near; 15665 15666 if( near ){ 15667 near.trigger( new Event( e, { 15668 type: 'cxtdragover', 15669 cyPosition: { x: now[0], y: now[1] } 15670 } ) ); 15671 15672 } 15673 15674 } 15675 15676 // box selection 15677 } else if( capture && e.touches[2] && cy.boxSelectionEnabled() ){ 15678 e.preventDefault(); 15679 15680 r.data.bgActivePosistion = undefined; 15681 15682 this.lastThreeTouch = +new Date(); 15683 15684 if( !r.touchData.selecting ){ 15685 cy.trigger('boxstart'); 15686 } 15687 15688 r.touchData.selecting = true; 15689 15690 r.redrawHint( 'select', true ); 15691 15692 if( !select || select.length === 0 || select[0] === undefined ){ 15693 select[0] = (now[0] + now[2] + now[4]) / 3; 15694 select[1] = (now[1] + now[3] + now[5]) / 3; 15695 select[2] = (now[0] + now[2] + now[4]) / 3 + 1; 15696 select[3] = (now[1] + now[3] + now[5]) / 3 + 1; 15697 } else { 15698 select[2] = (now[0] + now[2] + now[4]) / 3; 15699 select[3] = (now[1] + now[3] + now[5]) / 3; 15700 } 15701 15702 select[4] = 1; 15703 r.touchData.selecting = true; 15704 15705 r.redraw(); 15706 15707 // pinch to zoom 15708 } else if( capture && e.touches[1] && cy.zoomingEnabled() && cy.panningEnabled() && cy.userZoomingEnabled() && cy.userPanningEnabled() ){ // two fingers => pinch to zoom 15709 e.preventDefault(); 15710 15711 r.data.bgActivePosistion = undefined; 15712 r.redrawHint( 'select', true ); 15713 15714 var draggedEles = r.dragData.touchDragEles; 15715 if( draggedEles ){ 15716 r.redrawHint( 'drag', true ); 15717 15718 for( var i = 0; i < draggedEles.length; i++ ){ 15719 draggedEles[ i ]._private.grabbed = false; 15720 draggedEles[ i ]._private.rscratch.inDragLayer = false; 15721 } 15722 } 15723 15724 // (x2, y2) for fingers 1 and 2 15725 var f1x2 = e.touches[0].clientX - offsetLeft, f1y2 = e.touches[0].clientY - offsetTop; 15726 var f2x2 = e.touches[1].clientX - offsetLeft, f2y2 = e.touches[1].clientY - offsetTop; 15727 15728 15729 var distance2 = distance( f1x2, f1y2, f2x2, f2y2 ); 15730 // var distance2Sq = distanceSq( f1x2, f1y2, f2x2, f2y2 ); 15731 // var factor = Math.sqrt( distance2Sq ) / Math.sqrt( distance1Sq ); 15732 var factor = distance2 / distance1; 15733 15734 if( factor != 1 && twoFingersStartInside ){ 15735 // delta finger1 15736 var df1x = f1x2 - f1x1; 15737 var df1y = f1y2 - f1y1; 15738 15739 // delta finger 2 15740 var df2x = f2x2 - f2x1; 15741 var df2y = f2y2 - f2y1; 15742 15743 // translation is the normalised vector of the two fingers movement 15744 // i.e. so pinching cancels out and moving together pans 15745 var tx = (df1x + df2x) / 2; 15746 var ty = (df1y + df2y) / 2; 15747 15748 // adjust factor by the speed multiplier 15749 // var speed = 1.5; 15750 // if( factor > 1 ){ 15751 // factor = (factor - 1) * speed + 1; 15752 // } else { 15753 // factor = 1 - (1 - factor) * speed; 15754 // } 15755 15756 // now calculate the zoom 15757 var zoom1 = cy.zoom(); 15758 var zoom2 = zoom1 * factor; 15759 var pan1 = cy.pan(); 15760 15761 // the model center point converted to the current rendered pos 15762 var ctrx = modelCenter1[0] * zoom1 + pan1.x; 15763 var ctry = modelCenter1[1] * zoom1 + pan1.y; 15764 15765 var pan2 = { 15766 x: -zoom2 / zoom1 * (ctrx - pan1.x - tx) + ctrx, 15767 y: -zoom2 / zoom1 * (ctry - pan1.y - ty) + ctry 15768 }; 15769 15770 // remove dragged eles 15771 if( r.touchData.start ){ 15772 var draggedEles = r.dragData.touchDragEles; 15773 15774 freeDraggedElements( draggedEles ); 15775 15776 r.redrawHint( 'drag', true ); 15777 r.redrawHint( 'eles', true ); 15778 15779 r.touchData.start 15780 .trigger( 'free' ) 15781 .unactivate() 15782 ; 15783 } 15784 15785 cy.viewport( { 15786 zoom: zoom2, 15787 pan: pan2, 15788 cancelOnFailedZoom: true 15789 } ); 15790 15791 distance1 = distance2; 15792 f1x1 = f1x2; 15793 f1y1 = f1y2; 15794 f2x1 = f2x2; 15795 f2y1 = f2y2; 15796 15797 r.pinching = true; 15798 } 15799 15800 // Re-project 15801 if( e.touches[0] ){ var pos = r.projectIntoViewport( e.touches[0].clientX, e.touches[0].clientY ); now[0] = pos[0]; now[1] = pos[1]; } 15802 if( e.touches[1] ){ var pos = r.projectIntoViewport( e.touches[1].clientX, e.touches[1].clientY ); now[2] = pos[0]; now[3] = pos[1]; } 15803 if( e.touches[2] ){ var pos = r.projectIntoViewport( e.touches[2].clientX, e.touches[2].clientY ); now[4] = pos[0]; now[5] = pos[1]; } 15804 15805 } else if( e.touches[0] ){ 15806 var start = r.touchData.start; 15807 var last = r.touchData.last; 15808 var near; 15809 15810 if( !r.hoverData.draggingEles && !r.swipePanning ){ 15811 near = r.findNearestElement( now[0], now[1], true, true ); 15812 } 15813 15814 if( capture && start != null ){ 15815 e.preventDefault(); 15816 } 15817 15818 // dragging nodes 15819 if( capture && start != null && r.nodeIsDraggable( start ) ){ 15820 15821 if( isOverThresholdDrag ){ // then dragging can happen 15822 var draggedEles = r.dragData.touchDragEles; 15823 var justStartedDrag = !r.dragData.didDrag; 15824 15825 if( justStartedDrag ){ 15826 addNodesToDrag( cy.collection( draggedEles ), { inDragLayer: true } ); 15827 } 15828 15829 for( var k = 0; k < draggedEles.length; k++ ){ 15830 var draggedEle = draggedEles[ k ]; 15831 15832 if( r.nodeIsDraggable( draggedEle ) && draggedEle.grabbed() ){ 15833 r.dragData.didDrag = true; 15834 var dPos = draggedEle._private.position;
15835 var updatePos = !draggedEle.isParent(); 15836 15837 if( updatePos && is.number( disp[0] ) && is.number( disp[1] ) ){ 15838 dPos.x += disp[0]; 15839 dPos.y += disp[1]; 15840 } 15841 15842 if( justStartedDrag ){ 15843 r.redrawHint( 'eles', true ); 15844 15845 var dragDelta = r.touchData.dragDelta; 15846 15847 if( updatePos && dragDelta && is.number( dragDelta[0] ) && is.number( dragDelta[1] ) ){ 15848 dPos.x += dragDelta[0]; 15849 dPos.y += dragDelta[1]; 15850 } 15851 15852 } 15853 } 15854 } 15855 15856 var tcol = cy.collection( draggedEles ); 15857 15858 tcol.updateCompoundBounds(); 15859 tcol.trigger( 'position drag' ); 15860 15861 r.hoverData.draggingEles = true; 15862 15863 r.redrawHint( 'drag', true ); 15864 15865 if( 15866 r.touchData.startPosition[0] == earlier[0] 15867 && r.touchData.startPosition[1] == earlier[1] 15868 ){ 15869 15870 r.redrawHint( 'eles', true ); 15871 } 15872 15873 r.redraw(); 15874 } else { // otherise keep track of drag delta for later 15875 var dragDelta = r.touchData.dragDelta = r.touchData.dragDelta || []; 15876 15877 if( dragDelta.length === 0 ){ 15878 dragDelta.push( disp[0] ); 15879 dragDelta.push( disp[1] ); 15880 } else { 15881 dragDelta[0] += disp[0]; 15882 dragDelta[1] += disp[1]; 15883 } 15884 } 15885 } 15886 15887 // touchmove 15888 { 15889 triggerEvents( (start || near), [ 'touchmove', 'tapdrag', 'vmousemove' ], e, { 15890 cyPosition: { x: now[0], y: now[1] } 15891 } ); 15892 15893 if( ( !start || !start.grabbed() ) && near != last ){ 15894 if( last ){ last.trigger( new Event( e, { type: 'tapdragout', cyPosition: { x: now[0], y: now[1] } } ) ); } 15895 if( near ){ near.trigger( new Event( e, { type: 'tapdragover', cyPosition: { x: now[0], y: now[1] } } ) ); } 15896 } 15897 15898 r.touchData.last = near; 15899 } 15900 15901 // check to cancel taphold 15902 if( capture ){ 15903 for( var i = 0; i < now.length; i++ ){ 15904 if( now[ i ] 15905 && r.touchData.startPosition[ i ] 15906 && isOverThresholdDrag ){ 15907 15908 r.touchData.singleTouchMoved = true; 15909 } 15910 } 15911 } 15912 15913 // panning 15914 if( 15915 capture 15916 && ( start == null || start.isEdge() ) 15917 && cy.panningEnabled() && cy.userPanningEnabled() 15918 ){ 15919 15920 var allowPassthrough = allowPanningPassthrough( start, r.touchData.starts ); 15921 15922 if( allowPassthrough ){ 15923 e.preventDefault(); 15924 15925 if( r.swipePanning ){ 15926 cy.panBy( { 15927 x: disp[0] * zoom, 15928 y: disp[1] * zoom 15929 } ); 15930 15931 } else if( isOverThresholdDrag ){ 15932 r.swipePanning = true; 15933 15934 cy.panBy( { 15935 x: dx * zoom, 15936 y: dy * zoom 15937 } ); 15938 15939 if( start ){ 15940 start.unactivate(); 15941 15942 if( !r.data.bgActivePosistion ){ 15943 r.data.bgActivePosistion = math.array2point( r.touchData.startPosition ); 15944 } 15945 15946 r.redrawHint( 'select', true ); 15947 15948 r.touchData.start = null; 15949 } 15950 } 15951 15952 } 15953 15954 // Re-project 15955 var pos = r.projectIntoViewport( e.touches[0].clientX, e.touches[0].clientY ); 15956 now[0] = pos[0]; now[1] = pos[1]; 15957 } 15958 } 15959 15960 for( var j = 0; j < now.length; j++ ){ earlier[ j ] = now[ j ]; } 15961 //r.redraw(); 15962 15963 }, false ); 15964 15965 var touchcancelHandler; 15966 r.registerBinding( window, 'touchcancel', touchcancelHandler = function( e ){ // eslint-disable-line no-undef 15967 var start = r.touchData.start; 15968 15969 r.touchData.capture = false; 15970 15971 if( start ){ 15972 start.unactivate(); 15973 } 15974 } ); 15975 15976 var touchendHandler; 15977 r.registerBinding( window, 'touchend', touchendHandler = function( e ){ // eslint-disable-line no-undef 15978 var start = r.touchData.start; 15979 15980 var capture = r.touchData.capture; 15981 15982 if( capture ){ 15983 r.touchData.capture = false; 15984 15985 e.preventDefault(); 15986 } else { 15987 return; 15988 } 15989 15990 var select = r.selection; 15991 15992 r.swipePanning = false;
15993 r.hoverData.draggingEles = false; 15994 15995 var cy = r.cy; 15996 var zoom = cy.zoom(); 15997 var now = r.touchData.now; 15998 var earlier = r.touchData.earlier; 15999 16000 if( e.touches[0] ){ var pos = r.projectIntoViewport( e.touches[0].clientX, e.touches[0].clientY ); now[0] = pos[0]; now[1] = pos[1]; } 16001 if( e.touches[1] ){ var pos = r.projectIntoViewport( e.touches[1].clientX, e.touches[1].clientY ); now[2] = pos[0]; now[3] = pos[1]; } 16002 if( e.touches[2] ){ var pos = r.projectIntoViewport( e.touches[2].clientX, e.touches[2].clientY ); now[4] = pos[0]; now[5] = pos[1]; } 16003 16004 if( start ){ 16005 start.unactivate(); 16006 } 16007 16008 var ctxTapend; 16009 if( r.touchData.cxt ){ 16010 ctxTapend = new Event( e, { 16011 type: 'cxttapend', 16012 cyPosition: { x: now[0], y: now[1] } 16013 } ); 16014 16015 if( start ){ 16016 start.trigger( ctxTapend ); 16017 } else { 16018 cy.trigger( ctxTapend ); 16019 } 16020 16021 if( !r.touchData.cxtDragged ){ 16022 var ctxTap = new Event( e, { 16023 type: 'cxttap', 16024 cyPosition: { x: now[0], y: now[1] } 16025 } ); 16026 16027 if( start ){ 16028 start.trigger( ctxTap ); 16029 } else { 16030 cy.trigger( ctxTap ); 16031 } 16032 16033 } 16034 16035 if( r.touchData.start ){ r.touchData.start._private.grabbed = false; } 16036 r.touchData.cxt = false; 16037 r.touchData.start = null; 16038 16039 r.redraw(); 16040 return; 16041 } 16042 16043 // no more box selection if we don't have three fingers 16044 if( !e.touches[2] && cy.boxSelectionEnabled() && r.touchData.selecting ){ 16045 r.touchData.selecting = false; 16046 16047 var box = cy.collection( r.getAllInBox( select[0], select[1], select[2], select[3] ) ); 16048 16049 select[0] = undefined; 16050 select[1] = undefined; 16051 select[2] = undefined; 16052 select[3] = undefined; 16053 select[4] = 0; 16054 16055 r.redrawHint( 'select', true ); 16056 16057 cy.trigger('boxend'); 16058 16059 var eleWouldBeSelected = function( ele ){ return ele.selectable() && !ele.selected(); }; 16060 16061 box 16062 .trigger('box') 16063 .stdFilter( eleWouldBeSelected ) 16064 .select() 16065 .trigger('boxselect') 16066 ; 16067 16068 if( box.nonempty() ){ 16069 r.redrawHint( 'eles', true ); 16070 } 16071 16072 r.redraw(); 16073 } 16074 16075 if( start != null ){ 16076 start.unactivate(); 16077 } 16078 16079 if( e.touches[2] ){ 16080 r.data.bgActivePosistion = undefined; 16081 r.redrawHint( 'select', true ); 16082 } else if( e.touches[1] ){ 16083 // ignore 16084 } else if( e.touches[0] ){ 16085 // ignore 16086 16087 // Last touch released 16088 } else if( !e.touches[0] ){ 16089 16090 r.data.bgActivePosistion = undefined; 16091 r.redrawHint( 'select', true ); 16092 16093 var draggedEles = r.dragData.touchDragEles; 16094 16095 if( start != null ){ 16096 16097 var startWasGrabbed = start._private.grabbed; 16098 16099 freeDraggedElements( draggedEles ); 16100 16101 r.redrawHint( 'drag', true ); 16102 r.redrawHint( 'eles', true ); 16103 16104 if( startWasGrabbed ){ 16105 start.trigger( 'free' ); 16106 } 16107 16108 triggerEvents( start, [ 'touchend', 'tapend', 'vmouseup', 'tapdragout' ], e, { 16109 cyPosition: { x: now[0], y: now[1] } 16110 } ); 16111 16112 start.unactivate(); 16113 16114 r.touchData.start = null; 16115 16116 } else { 16117 var near = r.findNearestElement( now[0], now[1], true, true ); 16118 16119 triggerEvents( near, [ 'touchend', 'tapend', 'vmouseup', 'tapdragout' ], e, { 16120 cyPosition: { x: now[0], y: now[1] } 16121 } ); 16122 16123 } 16124 16125 var dx = r.touchData.startPosition[0] - now[0]; 16126 var dx2 = dx * dx; 16127 var dy = r.touchData.startPosition[1] - now[1]; 16128 var dy2 = dy * dy; 16129 var dist2 = dx2 + dy2; 16130 var rdist2 = dist2 * zoom * zoom; 16131 16132 // Prepare to select the currently touched node, only if it hasn't been dragged past a certain distance 16133 if( start != null 16134 && !r.dragData.didDrag // didn't drag nodes around 16135 && start._private.selectable 16136 && rdist2 < r.touchTapThreshold2 16137 && !r.pinching // pinch to zoom should not affect selection 16138 ){ 16139 16140 if( cy.selectionType() === 'single' ){ 16141 cy.$( ':selected' ).unmerge( start ).unselect(); 16142 start.select(); 16143 } else { 16144 if( start.selected() ){ 16145 start.unselect(); 16146 } else { 16147 start.select(); 16148 } 16149 } 16150 16151 r.redrawHint( 'eles', true ); 16152 } 16153 16154 // Tap event, roughly same as mouse click event for touch 16155 if( !r.touchData.singleTouchMoved ){ 16156 triggerEvents( start, [ 'tap', 'vclick' ], e, { 16157 cyPosition: { x: now[0], y: now[1] } 16158 } ); 16159 } 16160 16161 r.touchData.singleTouchMoved = true; 16162 } 16163 16164 for( var j = 0; j < now.length; j++ ){ earlier[ j ] = now[ j ]; } 16165 16166 r.dragData.didDrag = false; // reset for next mousedown 16167 16168 if( e.touches.length === 0 ){ 16169 r.touchData.dragDelta = []; 16170 r.touchData.startPosition = null; 16171 r.touchData.startGPosition = null; 16172 } 16173 16174 if( e.touches.length < 2 ){ 16175 r.pinching = false;
16176 r.redrawHint( 'eles', true ); 16177 r.redraw(); 16178 } 16179 16180 //r.redraw(); 16181 16182 }, false ); 16183 16184 // fallback compatibility layer for ms pointer events 16185 if( typeof TouchEvent === 'undefined' ){ 16186 16187 var pointers = []; 16188 16189 var makeTouch = function( e ){ 16190 return { 16191 clientX: e.clientX, 16192 clientY: e.clientY, 16193 force: 1, 16194 identifier: e.pointerId, 16195 pageX: e.pageX, 16196 pageY: e.pageY, 16197 radiusX: e.width / 2, 16198 radiusY: e.height / 2, 16199 screenX: e.screenX, 16200 screenY: e.screenY, 16201 target: e.target 16202 }; 16203 }; 16204 16205 var makePointer = function( e ){ 16206 return { 16207 event: e, 16208 touch: makeTouch( e ) 16209 }; 16210 }; 16211 16212 var addPointer = function( e ){ 16213 pointers.push( makePointer( e ) ); 16214 }; 16215 16216 var removePointer = function( e ){ 16217 for( var i = 0; i < pointers.length; i++ ){ 16218 var p = pointers[ i ]; 16219 16220 if( p.event.pointerId === e.pointerId ){ 16221 pointers.splice( i, 1 ); 16222 return; 16223 } 16224 } 16225 }; 16226 16227 var updatePointer = function( e ){ 16228 var p = pointers.filter( function( p ){ 16229 return p.event.pointerId === e.pointerId; 16230 } )[0]; 16231 16232 p.event = e; 16233 p.touch = makeTouch( e ); 16234 }; 16235 16236 var addTouchesToEvent = function( e ){ 16237 e.touches = pointers.map( function( p ){ 16238 return p.touch; 16239 } ); 16240 }; 16241 16242 r.registerBinding( r.container, 'pointerdown', function( e ){ 16243 if( e.pointerType === 'mouse' ){ return; } // mouse already handled 16244 16245 e.preventDefault(); 16246 16247 addPointer( e ); 16248 16249 addTouchesToEvent( e ); 16250 touchstartHandler( e ); 16251 } ); 16252 16253 r.registerBinding( r.container, 'pointerup', function( e ){ 16254 if( e.pointerType === 'mouse' ){ return; } // mouse already handled 16255 16256 removePointer( e ); 16257 16258 addTouchesToEvent( e ); 16259 touchendHandler( e ); 16260 } ); 16261 16262 r.registerBinding( r.container, 'pointercancel', function( e ){ 16263 if( e.pointerType === 'mouse' ){ return; } // mouse already handled 16264 16265 removePointer( e ); 16266 16267 addTouchesToEvent( e ); 16268 touchcancelHandler( e ); 16269 } ); 16270 16271 r.registerBinding( r.container, 'pointermove', function( e ){ 16272 if( e.pointerType === 'mouse' ){ return; } // mouse already handled 16273 16274 e.preventDefault(); 16275 16276 updatePointer( e ); 16277 16278 addTouchesToEvent( e ); 16279 touchmoveHandler( e ); 16280 } ); 16281 16282 } 16283}; 16284 16285module.exports = BRp; 16286 16287},{"../../../event":45,"../../../is":83,"../../../math":85,"../../../util":100}],62:[function(_dereq_,module,exports){ 16288'use strict'; 16289 16290var math = _dereq_( '../../../math' ); 16291 16292var BRp = {}; 16293 16294BRp.generatePolygon = function( name, points ){ 16295 return ( this.nodeShapes[ name ] = { 16296 renderer: this, 16297 16298 name: name, 16299 16300 points: points, 16301 16302 draw: function( context, centerX, centerY, width, height ){ 16303 this.renderer.nodeShapeImpl( 'polygon', context, centerX, centerY, width, height, this.points ); 16304 }, 16305 16306 intersectLine: function( nodeX, nodeY, width, height, x, y, padding ){ 16307 return math.polygonIntersectLine( 16308 x, y, 16309 this.points, 16310 nodeX, 16311 nodeY, 16312 width / 2, height / 2, 16313 padding ) 16314 ; 16315 }, 16316 16317 checkPoint: function( x, y, padding, width, height, centerX, centerY ){ 16318 return math.pointInsidePolygon( x, y, this.points, 16319 centerX, centerY, width, height, [0, -1], padding ) 16320 ; 16321 } 16322 } ); 16323}; 16324 16325BRp.generateEllipse = function(){ 16326 return ( this.nodeShapes['ellipse'] = { 16327 renderer: this, 16328 16329 name: 'ellipse', 16330 16331 draw: function( context, centerX, centerY, width, height ){ 16332 this.renderer.nodeShapeImpl( this.name, context, centerX, centerY, width, height ); 16333 }, 16334 16335 intersectLine: function( nodeX, nodeY, width, height, x, y, padding ){ 16336 return math.intersectLineEllipse( 16337 x, y, 16338 nodeX, 16339 nodeY, 16340 width / 2 + padding, 16341 height / 2 + padding ) 16342 ; 16343 }, 16344 16345 checkPoint: function( x, y, padding, width, height, centerX, centerY ){ 16346 x -= centerX; 16347 y -= centerY; 16348 16349 x /= (width / 2 + padding); 16350 y /= (height / 2 + padding); 16351 16352 return x * x + y * y <= 1; 16353 } 16354 } ); 16355}; 16356 16357BRp.generateRoundRectangle = function(){ 16358 return ( this.nodeShapes['roundrectangle'] = { 16359 renderer: this, 16360 16361 name: 'roundrectangle', 16362 16363 points: math.generateUnitNgonPointsFitToSquare( 4, 0 ), 16364 16365 draw: function( context, centerX, centerY, width, height ){ 16366 this.renderer.nodeShapeImpl( this.name, context, centerX, centerY, width, height ); 16367 }, 16368 16369 intersectLine: function( nodeX, nodeY, width, height, x, y, padding ){ 16370 return math.roundRectangleIntersectLine( 16371 x, y, 16372 nodeX, 16373 nodeY, 16374 width, height, 16375 padding ) 16376 ; 16377 }, 16378 16379 // Looks like the width passed into this function is actually the total width / 2 16380 checkPoint: function( 16381 x, y, padding, width, height, centerX, centerY ){ 16382 16383 var cornerRadius = math.getRoundRectangleRadius( width, height ); 16384 16385 // Check hBox 16386 if( math.pointInsidePolygon( x, y, this.points, 16387 centerX, centerY, width, height - 2 * cornerRadius, [0, -1], padding ) ){ 16388 return true; 16389 } 16390 16391 // Check vBox 16392 if( math.pointInsidePolygon( x, y, this.points, 16393 centerX, centerY, width - 2 * cornerRadius, height, [0, -1], padding ) ){ 16394 return true; 16395 } 16396 16397 var checkInEllipse = function( x, y, centerX, centerY, width, height, padding ){ 16398 x -= centerX; 16399 y -= centerY; 16400 16401 x /= (width / 2 + padding); 16402 y /= (height / 2 + padding); 16403 16404 return (x * x + y * y <= 1); 16405 }; 16406 16407 16408 // Check top left quarter circle 16409 if( checkInEllipse( x, y, 16410 centerX - width / 2 + cornerRadius, 16411 centerY - height / 2 + cornerRadius, 16412 cornerRadius * 2, cornerRadius * 2, padding ) ){ 16413 16414 return true; 16415 } 16416 16417 // Check top right quarter circle 16418 if( checkInEllipse( x, y, 16419 centerX + width / 2 - cornerRadius, 16420 centerY - height / 2 + cornerRadius, 16421 cornerRadius * 2, cornerRadius * 2, padding ) ){ 16422 16423 return true; 16424 } 16425 16426 // Check bottom right quarter circle 16427 if( checkInEllipse( x, y, 16428 centerX + width / 2 - cornerRadius, 16429 centerY + height / 2 - cornerRadius, 16430 cornerRadius * 2, cornerRadius * 2, padding ) ){ 16431 16432 return true; 16433 } 16434 16435 // Check bottom left quarter circle 16436 if( checkInEllipse( x, y, 16437 centerX - width / 2 + cornerRadius, 16438 centerY + height / 2 - cornerRadius, 16439 cornerRadius * 2, cornerRadius * 2, padding ) ){ 16440 16441 return true; 16442 } 16443 16444 return false;
16445 } 16446 } ); 16447}; 16448 16449BRp.registerNodeShapes = function(){ 16450 var nodeShapes = this.nodeShapes = {}; 16451 var renderer = this; 16452 16453 this.generateEllipse(); 16454 16455 this.generatePolygon( 'triangle', math.generateUnitNgonPointsFitToSquare( 3, 0 ) ); 16456 16457 this.generatePolygon( 'rectangle', math.generateUnitNgonPointsFitToSquare( 4, 0 ) ); 16458 nodeShapes[ 'square' ] = nodeShapes[ 'rectangle' ]; 16459 16460 this.generateRoundRectangle(); 16461 16462 this.generatePolygon( 'diamond', [ 16463 0, 1, 16464 1, 0, 16465 0, -1, 16466 -1, 0 16467 ] ); 16468 16469 this.generatePolygon( 'pentagon', math.generateUnitNgonPointsFitToSquare( 5, 0 ) ); 16470 16471 this.generatePolygon( 'hexagon', math.generateUnitNgonPointsFitToSquare( 6, 0 ) ); 16472 16473 this.generatePolygon( 'heptagon', math.generateUnitNgonPointsFitToSquare( 7, 0 ) ); 16474 16475 this.generatePolygon( 'octagon', math.generateUnitNgonPointsFitToSquare( 8, 0 ) ); 16476 16477 var star5Points = new Array( 20 ); 16478 { 16479 var outerPoints = math.generateUnitNgonPoints( 5, 0 ); 16480 var innerPoints = math.generateUnitNgonPoints( 5, Math.PI / 5 ); 16481 16482 // Outer radius is 1; inner radius of star is smaller 16483 var innerRadius = 0.5 * (3 - Math.sqrt( 5 )); 16484 innerRadius *= 1.57; 16485 16486 for( var i = 0;i < innerPoints.length / 2;i++ ){ 16487 innerPoints[ i * 2] *= innerRadius; 16488 innerPoints[ i * 2 + 1] *= innerRadius; 16489 } 16490 16491 for( var i = 0;i < 20 / 4;i++ ){ 16492 star5Points[ i * 4] = outerPoints[ i * 2]; 16493 star5Points[ i * 4 + 1] = outerPoints[ i * 2 + 1]; 16494 16495 star5Points[ i * 4 + 2] = innerPoints[ i * 2]; 16496 star5Points[ i * 4 + 3] = innerPoints[ i * 2 + 1]; 16497 } 16498 } 16499 16500 star5Points = math.fitPolygonToSquare( star5Points ); 16501 16502 this.generatePolygon( 'star', star5Points ); 16503 16504 this.generatePolygon( 'vee', [ 16505 -1, -1, 16506 0, -0.333, 16507 1, -1, 16508 0, 1 16509 ] ); 16510 16511 this.generatePolygon( 'rhomboid', [ 16512 -1, -1, 16513 0.333, -1, 16514 1, 1, 16515 -0.333, 1 16516 ] ); 16517 16518 nodeShapes.makePolygon = function( points ){ 16519 16520 // use caching on user-specified polygons so they are as fast as native shapes 16521 16522 var key = points.join( '$' ); 16523 var name = 'polygon-' + key; 16524 var shape; 16525 16526 if( (shape = this[ name ]) ){ // got cached shape 16527 return shape; 16528 } 16529 16530 // create and cache new shape 16531 return renderer.generatePolygon( name, points ); 16532 }; 16533 16534}; 16535 16536module.exports = BRp; 16537 16538},{"../../../math":85}],63:[function(_dereq_,module,exports){ 16539'use strict'; 16540 16541var util = _dereq_( '../../../util' ); 16542 16543var BRp = {}; 16544 16545BRp.timeToRender = function(){ 16546 return this.redrawTotalTime / this.redrawCount; 16547}; 16548 16549BRp.redraw = function( options ){ 16550 options = options || util.staticEmptyObject(); 16551 16552 var r = this; 16553 16554 if( r.averageRedrawTime === undefined ){ r.averageRedrawTime = 0; } 16555 if( r.lastRedrawTime === undefined ){ r.lastRedrawTime = 0; } 16556 if( r.lastDrawTime === undefined ){ r.lastDrawTime = 0; } 16557 16558 r.requestedFrame = true; 16559 r.renderOptions = options; 16560}; 16561 16562BRp.beforeRender = function( fn, priority ){ 16563 // the renderer can't add tick callbacks when destroyed 16564 if( this.destroyed ){ return; } 16565 16566 priority = priority || 0; 16567 16568 var cbs = this.beforeRenderCallbacks; 16569 16570 cbs.push({ fn: fn, priority: priority }); 16571 16572 // higher priority callbacks executed first 16573 cbs.sort(function( a, b ){ return b.priority - a.priority; }); 16574}; 16575 16576var beforeRenderCallbacks = function( r, willDraw, startTime ){ 16577 var cbs = r.beforeRenderCallbacks; 16578 16579 for( var i = 0; i < cbs.length; i++ ){ 16580 cbs[i].fn( willDraw, startTime ); 16581 } 16582}; 16583 16584BRp.startRenderLoop = function(){ 16585 var r = this; 16586 16587 if( r.renderLoopStarted ){ 16588 return; 16589 } else { 16590 r.renderLoopStarted = true; 16591 } 16592 16593 var renderFn = function( requestTime ){ 16594 if( r.destroyed ){ return; } 16595 16596 if( r.requestedFrame && !r.skipFrame ){ 16597 beforeRenderCallbacks( r, true, requestTime ); 16598 16599 var startTime = util.performanceNow(); 16600 16601 r.render( r.renderOptions ); 16602 16603 var endTime = r.lastDrawTime = util.performanceNow(); 16604 16605 if( r.averageRedrawTime === undefined ){ 16606 r.averageRedrawTime = endTime - startTime; 16607 } 16608 16609 if( r.redrawCount === undefined ){ 16610 r.redrawCount = 0; 16611 } 16612 16613 r.redrawCount++; 16614 16615 if( r.redrawTotalTime === undefined ){ 16616 r.redrawTotalTime = 0; 16617 } 16618 16619 var duration = endTime - startTime; 16620 16621 r.redrawTotalTime += duration; 16622 r.lastRedrawTime = duration; 16623 16624 // use a weighted average with a bias from the previous average so we don't spike so easily 16625 r.averageRedrawTime = r.averageRedrawTime / 2 + duration / 2; 16626 16627 r.requestedFrame = false;
16628 } else { 16629 beforeRenderCallbacks( r, false, requestTime ); 16630 } 16631 16632 r.skipFrame = false; 16633 16634 util.requestAnimationFrame( renderFn ); 16635 }; 16636 16637 util.requestAnimationFrame( renderFn ); 16638 16639}; 16640 16641module.exports = BRp; 16642 16643},{"../../../util":100}],64:[function(_dereq_,module,exports){ 16644'use strict'; 16645 16646var CRp = {}; 16647 16648var impl; 16649 16650CRp.arrowShapeImpl = function( name ){ 16651 return ( impl || (impl = { 16652 'polygon': function( context, points ){ 16653 for( var i = 0; i < points.length; i++ ){ 16654 var pt = points[ i ]; 16655 16656 context.lineTo( pt.x, pt.y ); 16657 } 16658 }, 16659 16660 'triangle-backcurve': function( context, points, controlPoint ){ 16661 var firstPt; 16662 16663 for( var i = 0; i < points.length; i++ ){ 16664 var pt = points[ i ]; 16665 16666 if( i === 0 ){ 16667 firstPt = pt; 16668 } 16669 16670 context.lineTo( pt.x, pt.y ); 16671 } 16672 16673 context.quadraticCurveTo( controlPoint.x, controlPoint.y, firstPt.x, firstPt.y ); 16674 }, 16675 16676 'triangle-tee': function( context, trianglePoints, teePoints ){ 16677 if( context.beginPath ){ context.beginPath(); } 16678 16679 var triPts = trianglePoints; 16680 for( var i = 0; i < triPts.length; i++ ){ 16681 var pt = triPts[ i ]; 16682 16683 context.lineTo( pt.x, pt.y ); 16684 } 16685 16686 if( context.closePath ){ context.closePath(); } 16687 16688 if( context.beginPath ){ context.beginPath(); } 16689 16690 var teePts = teePoints; 16691 var firstTeePt = teePoints[0]; 16692 context.moveTo( firstTeePt.x, firstTeePt.y ); 16693 16694 for( var i = 0; i < teePts.length; i++ ){ 16695 var pt = teePts[ i ]; 16696 16697 context.lineTo( pt.x, pt.y ); 16698 } 16699 16700 if( context.closePath ){ context.closePath(); } 16701 }, 16702 16703 'circle': function( context, rx, ry, r ){ 16704 context.arc( rx, ry, r, 0, Math.PI * 2, false ); 16705 } 16706 }) )[ name ]; 16707}; 16708 16709module.exports = CRp; 16710 16711},{}],65:[function(_dereq_,module,exports){ 16712'use strict'; 16713 16714var CRp = {}; 16715 16716CRp.drawEdge = function( context, edge, shiftToOriginWithBb, drawLabel, drawOverlayInstead ){ 16717 var rs = edge._private.rscratch; 16718 var usePaths = this.usePaths(); 16719 16720 // if bezier ctrl pts can not be calculated, then die 16721 if( rs.badLine || isNaN(rs.allpts[0]) ){ // isNaN in case edge is impossible and browser bugs (e.g. safari) 16722 return; 16723 } 16724 16725 if( !edge.visible() ){ return; } 16726 16727 var bb; 16728 if( shiftToOriginWithBb ){ 16729 bb = shiftToOriginWithBb; 16730 16731 context.translate( -bb.x1, -bb.y1 ); 16732 } 16733 16734 var overlayPadding = edge.pstyle( 'overlay-padding' ).pfValue; 16735 var overlayOpacity = edge.pstyle( 'overlay-opacity' ).value; 16736 var overlayColor = edge.pstyle( 'overlay-color' ).value; 16737 16738 // Edge color & opacity 16739 if( drawOverlayInstead ){ 16740 16741 if( overlayOpacity === 0 ){ // exit early if no overlay 16742 return; 16743 } 16744 16745 this.strokeStyle( context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity ); 16746 context.lineCap = 'round'; 16747 16748 if( rs.edgeType == 'self' && !usePaths ){ 16749 context.lineCap = 'butt'; 16750 } 16751 16752 } else { 16753 var lineColor = edge.pstyle( 'line-color' ).value; 16754 16755 this.strokeStyle( context, lineColor[0], lineColor[1], lineColor[2], edge.pstyle( 'opacity' ).value ); 16756 16757 context.lineCap = 'butt'; 16758 } 16759 16760 context.lineJoin = 'round'; 16761 16762 var edgeWidth = edge.pstyle( 'width' ).pfValue + (drawOverlayInstead ? 2 * overlayPadding : 0); 16763 var lineStyle = drawOverlayInstead ? 'solid' : edge.pstyle( 'line-style' ).value; 16764 context.lineWidth = edgeWidth; 16765 16766 var shadowBlur = edge.pstyle( 'shadow-blur' ).pfValue; 16767 var shadowOpacity = edge.pstyle( 'shadow-opacity' ).value; 16768 var shadowColor = edge.pstyle( 'shadow-color' ).value; 16769 var shadowOffsetX = edge.pstyle( 'shadow-offset-x' ).pfValue; 16770 var shadowOffsetY = edge.pstyle( 'shadow-offset-y' ).pfValue; 16771 16772 this.shadowStyle( context, shadowColor, drawOverlayInstead ? 0 : shadowOpacity, shadowBlur, shadowOffsetX, shadowOffsetY ); 16773 16774 this.drawEdgePath( 16775 edge, 16776 context, 16777 rs.allpts, 16778 lineStyle, 16779 edgeWidth 16780 ); 16781 16782 this.drawArrowheads( context, edge, drawOverlayInstead ); 16783 16784 this.shadowStyle( context, 'transparent', 0 ); // reset for next guy 16785 16786 if( !drawOverlayInstead ){ 16787 this.drawEdge( context, edge, false, drawLabel, true ); 16788 } 16789 16790 this.drawElementText( context, edge, drawLabel ); 16791 16792 if( shiftToOriginWithBb ){ 16793 context.translate( bb.x1, bb.y1 ); 16794 } 16795}; 16796 16797
16798CRp.drawEdgePath = function( edge, context, pts, type, width ){ 16799 var rs = edge._private.rscratch; 16800 var canvasCxt = context; 16801 var path; 16802 var pathCacheHit = false; 16803 var usePaths = this.usePaths(); 16804 16805 if( usePaths ){ 16806 var pathCacheKey = pts.join( '$' ); 16807 var keyMatches = rs.pathCacheKey && rs.pathCacheKey === pathCacheKey; 16808 16809 if( keyMatches ){ 16810 path = context = rs.pathCache; 16811 pathCacheHit = true; 16812 } else { 16813 path = context = new Path2D(); // eslint-disable-line no-undef 16814 rs.pathCacheKey = pathCacheKey; 16815 rs.pathCache = path; 16816 } 16817 } 16818 16819 if( canvasCxt.setLineDash ){ // for very outofdate browsers 16820 switch( type ){ 16821 case 'dotted': 16822 canvasCxt.setLineDash( [ 1, 1 ] ); 16823 break; 16824 16825 case 'dashed': 16826 canvasCxt.setLineDash( [ 6, 3 ] ); 16827 break; 16828 16829 case 'solid': 16830 canvasCxt.setLineDash( [ ] ); 16831 break; 16832 } 16833 } 16834 16835 if( !pathCacheHit && !rs.badLine ){ 16836 if( context.beginPath ){ context.beginPath(); } 16837 context.moveTo( pts[0], pts[1] ); 16838 16839 switch( rs.edgeType ){ 16840 case 'bezier': 16841 case 'self': 16842 case 'compound': 16843 case 'multibezier': 16844 for( var i = 2; i + 3 < pts.length; i += 4 ){ 16845 context.quadraticCurveTo( pts[ i ], pts[ i + 1], pts[ i + 2], pts[ i + 3] ); 16846 } 16847 break; 16848 16849 case 'straight': 16850 case 'segments': 16851 case 'haystack': 16852 for( var i = 2; i + 1 < pts.length; i += 2 ){ 16853 context.lineTo( pts[ i ], pts[ i + 1] ); 16854 } 16855 break; 16856 } 16857 } 16858 16859 context = canvasCxt; 16860 if( usePaths ){ 16861 context.stroke( path ); 16862 } else { 16863 context.stroke(); 16864 } 16865 16866 // reset any line dashes 16867 if( context.setLineDash ){ // for very outofdate browsers 16868 context.setLineDash( [ ] ); 16869 } 16870 16871}; 16872 16873CRp.drawArrowheads = function( context, edge, drawOverlayInstead ){ 16874 if( drawOverlayInstead ){ return; } // don't do anything for overlays 16875 16876 var rs = edge._private.rscratch; 16877 var isHaystack = rs.edgeType === 'haystack'; 16878 16879 if( !isHaystack ){ 16880 this.drawArrowhead( context, edge, 'source', rs.arrowStartX, rs.arrowStartY, rs.srcArrowAngle ); 16881 } 16882 16883 this.drawArrowhead( context, edge, 'mid-target', rs.midX, rs.midY, rs.midtgtArrowAngle ); 16884 16885 this.drawArrowhead( context, edge, 'mid-source', rs.midX, rs.midY, rs.midsrcArrowAngle ); 16886 16887 if( !isHaystack ){ 16888 this.drawArrowhead( context, edge, 'target', rs.arrowEndX, rs.arrowEndY, rs.tgtArrowAngle ); 16889 } 16890}; 16891 16892CRp.drawArrowhead = function( context, edge, prefix, x, y, angle ){ 16893 if( isNaN( x ) || x == null || isNaN( y ) || y == null || isNaN( angle ) || angle == null ){ return; } 16894 16895 var self = this; 16896 var arrowShape = edge.pstyle( prefix + '-arrow-shape' ).value; 16897 16898 if( arrowShape === 'none' ){ 16899 return; 16900 } 16901 16902 var gco = context.globalCompositeOperation; 16903 16904 var arrowClearFill = edge.pstyle( prefix + '-arrow-fill' ).value === 'hollow' ? 'both' : 'filled'; 16905 var arrowFill = edge.pstyle( prefix + '-arrow-fill' ).value; 16906 var opacity = edge.pstyle( 'opacity' ).value; 16907 16908 if( arrowShape === 'half-triangle-overshot' ){ 16909 arrowFill = 'hollow'; 16910 arrowClearFill = 'hollow'; 16911 } 16912 16913 if( opacity !== 1 || arrowFill === 'hollow' ){ // then extra clear is needed 16914 context.globalCompositeOperation = 'destination-out'; 16915 16916 self.fillStyle( context, 255, 255, 255, 1 ); 16917 self.strokeStyle( context, 255, 255, 255, 1 ); 16918 16919 self.drawArrowShape( edge, prefix, context, 16920 arrowClearFill, edge.pstyle( 'width' ).pfValue, edge.pstyle( prefix + '-arrow-shape' ).value, 16921 x, y, angle 16922 ); 16923 16924 context.globalCompositeOperation = gco; 16925 } // otherwise, the opaque arrow clears it for free :) 16926 16927 var color = edge.pstyle( prefix + '-arrow-color' ).value; 16928 self.fillStyle( context, color[0], color[1], color[2], opacity ); 16929 self.strokeStyle( context, color[0], color[1], color[2], opacity ); 16930 16931 self.drawArrowShape( edge, prefix, context, 16932 arrowFill, edge.pstyle( 'width' ).pfValue, edge.pstyle( prefix + '-arrow-shape' ).value, 16933 x, y, angle 16934 ); 16935}; 16936 16937CRp.drawArrowShape = function( edge, arrowType, context, fill, edgeWidth, shape, x, y, angle ){ 16938 var r = this; 16939 var usePaths = this.usePaths(); 16940 var rs = edge._private.rscratch; 16941 var pathCacheHit = false;
16942 var path; 16943 var canvasContext = context; 16944 var translation = { x: x, y: y }; 16945 var size = this.getArrowWidth( edgeWidth ); 16946 var shapeImpl = r.arrowShapes[ shape ]; 16947 16948 if( usePaths ){ 16949 var pathCacheKey = size + '$' + shape + '$' + angle + '$' + x + '$' + y; 16950 rs.arrowPathCacheKey = rs.arrowPathCacheKey || {}; 16951 rs.arrowPathCache = rs.arrowPathCache || {}; 16952 16953 var alreadyCached = rs.arrowPathCacheKey[ arrowType ] === pathCacheKey; 16954 if( alreadyCached ){ 16955 path = context = rs.arrowPathCache[ arrowType ]; 16956 pathCacheHit = true; 16957 } else { 16958 path = context = new Path2D(); // eslint-disable-line no-undef 16959 rs.arrowPathCacheKey[ arrowType ] = pathCacheKey; 16960 rs.arrowPathCache[ arrowType ] = path; 16961 } 16962 } 16963 16964 if( context.beginPath ){ context.beginPath(); } 16965 16966 if( !pathCacheHit ){ 16967 shapeImpl.draw( context, size, angle, translation ); 16968 } 16969 16970 if( !shapeImpl.leavePathOpen && context.closePath ){ 16971 context.closePath(); 16972 } 16973 16974 context = canvasContext; 16975 16976 if( fill === 'filled' || fill === 'both' ){ 16977 if( usePaths ){ 16978 context.fill( path ); 16979 } else { 16980 context.fill(); 16981 } 16982 } 16983 16984 if( fill === 'hollow' || fill === 'both' ){ 16985 context.lineWidth = ( shapeImpl.matchEdgeWidth ? edgeWidth : 1 ); 16986 context.lineJoin = 'miter'; 16987 16988 if( usePaths ){ 16989 context.stroke( path ); 16990 } else { 16991 context.stroke(); 16992 } 16993 16994 } 16995}; 16996 16997module.exports = CRp; 16998 16999},{}],66:[function(_dereq_,module,exports){ 17000'use strict'; 17001 17002var math = _dereq_( '../../../math' ); 17003 17004var CRp = {}; 17005 17006CRp.drawElement = function( context, ele, shiftToOriginWithBb, showLabel ){ 17007 var r = this; 17008 17009 if( ele.isNode() ){ 17010 r.drawNode( context, ele, shiftToOriginWithBb, showLabel ); 17011 } else { 17012 r.drawEdge( context, ele, shiftToOriginWithBb, showLabel ); 17013 } 17014}; 17015 17016CRp.drawCachedElement = function( context, ele, pxRatio, extent ){ 17017 var r = this; 17018 var bb = ele.boundingBox(); 17019 17020 if( bb.w === 0 || bb.h === 0 ){ return; } 17021 17022 if( !extent || math.boundingBoxesIntersect( bb, extent ) ){ 17023 var cache = r.data.eleTxrCache.getElement( ele, bb, pxRatio ); 17024 17025 if( cache ){ 17026 context.drawImage( cache.texture.canvas, cache.x, 0, cache.width, cache.height, bb.x1, bb.y1, bb.w, bb.h ); 17027 } else { // if the element is not cacheable, then draw directly 17028 r.drawElement( context, ele ); 17029 } 17030 } 17031}; 17032 17033CRp.drawElements = function( context, eles ){ 17034 var r = this; 17035 17036 for( var i = 0; i < eles.length; i++ ){ 17037 var ele = eles[ i ]; 17038 17039 r.drawElement( context, ele ); 17040 } 17041}; 17042 17043CRp.drawCachedElements = function( context, eles, pxRatio, extent ){ 17044 var r = this; 17045 17046 for( var i = 0; i < eles.length; i++ ){ 17047 var ele = eles[ i ]; 17048 17049 r.drawCachedElement( context, ele, pxRatio, extent ); 17050 } 17051}; 17052 17053CRp.drawCachedNodes = function( context, eles, pxRatio, extent ){ 17054 var r = this; 17055 17056 for( var i = 0; i < eles.length; i++ ){ 17057 var ele = eles[ i ]; 17058 17059 if( !ele.isNode() ){ continue; } 17060 17061 r.drawCachedElement( context, ele, pxRatio, extent ); 17062 } 17063}; 17064 17065CRp.drawLayeredElements = function( context, eles, pxRatio, extent ){ 17066 var r = this; 17067 17068 var layers = r.data.lyrTxrCache.getLayers( eles, pxRatio ); 17069 17070 if( layers ){ 17071 for( var i = 0; i < layers.length; i++ ){ 17072 var layer = layers[i]; 17073 var bb = layer.bb; 17074 17075 if( bb.w === 0 || bb.h === 0 ){ continue; } 17076 17077 context.drawImage( layer.canvas, bb.x1, bb.y1, bb.w, bb.h ); 17078 } 17079 } else { // fall back on plain caching if no layers 17080 r.drawCachedElements( context, eles, pxRatio, extent ); 17081 } 17082}; 17083 17084module.exports = CRp; 17085 17086},{"../../../math":85}],67:[function(_dereq_,module,exports){ 17087'use strict'; 17088 17089var CRp = {}; 17090 17091CRp.safeDrawImage = function( context, img, ix, iy, iw, ih, x, y, w, h ){ 17092 var r = this; 17093 17094 try { 17095 context.drawImage( img, ix, iy, iw, ih, x, y, w, h ); 17096 } catch( e ){ 17097 r.redrawHint( 'eles', true ); 17098 r.redrawHint( 'drag', true ); 17099 17100 r.drawingImage = true; 17101 17102 r.redraw(); 17103 } 17104}; 17105 17106CRp.drawInscribedImage = function( context, img, node ){ 17107 var r = this; 17108 var nodeX = node._private.position.x; 17109 var nodeY = node._private.position.y; 17110 var fit = node.pstyle( 'background-fit' ).value; 17111 var xPos = node.pstyle( 'background-position-x' ); 17112 var yPos = node.pstyle( 'background-position-y' ); 17113 var repeat = node.pstyle( 'background-repeat' ).value; 17114 var nodeW = node.width(); 17115 var nodeH = node.height(); 17116 var rs = node._private.rscratch; 17117 var clip = node.pstyle( 'background-clip' ).value; 17118 var shouldClip = clip === 'node'; 17119 var imgOpacity = node.pstyle( 'background-image-opacity' ).value; 17120 17121 var imgW = img.width || img.cachedW;
17122 var imgH = img.height || img.cachedH; 17123 17124 // workaround for broken browsers like ie 17125 if( null == imgW || null == imgH ){ 17126 document.body.appendChild( img ); // eslint-disable-line no-undef 17127 17128 imgW = img.cachedW = img.width || img.offsetWidth; 17129 imgH = img.cachedH = img.height || img.offsetHeight; 17130 17131 document.body.removeChild( img ); // eslint-disable-line no-undef 17132 } 17133 17134 var w = imgW; 17135 var h = imgH; 17136 17137 var bgW = node.pstyle( 'background-width' ); 17138 if( bgW.value !== 'auto' ){ 17139 if( bgW.units === '%' ){ 17140 w = bgW.value / 100 * nodeW; 17141 } else { 17142 w = bgW.pfValue; 17143 } 17144 } 17145 17146 var bgH = node.pstyle( 'background-height' ); 17147 if( bgH.value !== 'auto' ){ 17148 if( bgH.units === '%' ){ 17149 h = bgH.value / 100 * nodeH; 17150 } else { 17151 h = bgH.pfValue; 17152 } 17153 } 17154 17155 if( w === 0 || h === 0 ){ 17156 return; // no point in drawing empty image (and chrome is broken in this case) 17157 } 17158 17159 if( fit === 'contain' ){ 17160 var scale = Math.min( nodeW / w, nodeH / h ); 17161 17162 w *= scale; 17163 h *= scale; 17164 17165 } else if( fit === 'cover' ){ 17166 var scale = Math.max( nodeW / w, nodeH / h ); 17167 17168 w *= scale; 17169 h *= scale; 17170 } 17171 17172 var x = (nodeX - nodeW / 2); // left 17173 if( xPos.units === '%' ){ 17174 x += (nodeW - w) * xPos.value / 100; 17175 } else { 17176 x += xPos.pfValue; 17177 } 17178 17179 var y = (nodeY - nodeH / 2); // top 17180 if( yPos.units === '%' ){ 17181 y += (nodeH - h) * yPos.value / 100; 17182 } else { 17183 y += yPos.pfValue; 17184 } 17185 17186 if( rs.pathCache ){ 17187 x -= nodeX; 17188 y -= nodeY; 17189 17190 nodeX = 0; 17191 nodeY = 0; 17192 } 17193 17194 var gAlpha = context.globalAlpha; 17195 17196 context.globalAlpha = imgOpacity; 17197 17198 if( repeat === 'no-repeat' ){ 17199 17200 if( shouldClip ){ 17201 context.save(); 17202 17203 if( rs.pathCache ){ 17204 context.clip( rs.pathCache ); 17205 } else { 17206 r.nodeShapes[ r.getNodeShape( node ) ].draw( 17207 context, 17208 nodeX, nodeY, 17209 nodeW, nodeH ); 17210 17211 context.clip(); 17212 } 17213 } 17214 17215 r.safeDrawImage( context, img, 0, 0, imgW, imgH, x, y, w, h ); 17216 17217 if( shouldClip ){ 17218 context.restore(); 17219 } 17220 } else { 17221 var pattern = context.createPattern( img, repeat ); 17222 context.fillStyle = pattern; 17223 17224 r.nodeShapes[ r.getNodeShape( node ) ].draw( 17225 context, 17226 nodeX, nodeY, 17227 nodeW, nodeH ); 17228 17229 context.translate( x, y ); 17230 context.fill(); 17231 context.translate( -x, -y ); 17232 } 17233 17234 context.globalAlpha = gAlpha; 17235 17236}; 17237 17238module.exports = CRp; 17239 17240},{}],68:[function(_dereq_,module,exports){ 17241'use strict'; 17242 17243var util = _dereq_( '../../../util' ); 17244var math = _dereq_( '../../../math' ); 17245 17246var CRp = {}; 17247 17248CRp.eleTextBiggerThanMin = function( ele, scale ){ 17249 if( !scale ){ 17250 var zoom = ele.cy().zoom(); 17251 var pxRatio = this.getPixelRatio(); 17252 var lvl = Math.ceil( math.log2( zoom * pxRatio ) ); // the effective texture level 17253 17254 scale = Math.pow( 2, lvl ); 17255 } 17256 17257 var computedSize = ele.pstyle( 'font-size' ).pfValue * scale; 17258 var minSize = ele.pstyle( 'min-zoomed-font-size' ).pfValue; 17259 17260 if( computedSize < minSize ){ 17261 return false; 17262 } 17263 17264 return true; 17265}; 17266 17267CRp.drawElementText = function( context, ele, force ){ 17268 var r = this; 17269 17270 if( force === undefined ){ 17271 if( !r.eleTextBiggerThanMin( ele ) ){ return; } 17272 } else { 17273 if( !force ){ return; } 17274 } 17275 17276 if( ele.isNode() ){ 17277 var label = ele.pstyle( 'label' ); 17278 17279 if( !label || !label.value ){ return; } 17280 17281 var textHalign = ele.pstyle( 'text-halign' ).strValue; 17282 var textValign = ele.pstyle( 'text-valign' ).strValue; 17283 17284 switch( textHalign ){ 17285 case 'left': 17286 context.textAlign = 'right'; 17287 break; 17288 17289 case 'right': 17290 context.textAlign = 'left'; 17291 break; 17292 17293 default: // e.g. center 17294 context.textAlign = 'center'; 17295 } 17296 17297 context.textBaseline = 'bottom'; 17298 } else { 17299 var label = ele.pstyle( 'label' ); 17300 var srcLabel = ele.pstyle( 'source-label' ); 17301 var tgtLabel = ele.pstyle( 'target-label' ); 17302 17303 if( 17304 ( !label || !label.value ) 17305 && ( !srcLabel || !srcLabel.value ) 17306 && ( !tgtLabel || !tgtLabel.value ) 17307 ){ 17308 return; 17309 } 17310 17311 context.textAlign = 'center'; 17312 context.textBaseline = 'bottom'; 17313 } 17314 17315 17316 r.drawText( context, ele ); 17317 17318 if( ele.isEdge() ){ 17319 r.drawText( context, ele, 'source' ); 17320 17321 r.drawText( context, ele, 'target' ); 17322 } 17323}; 17324 17325CRp.drawNodeText = CRp.drawEdgeText = CRp.drawElementText; 17326 17327CRp.getFontCache = function( context ){ 17328 var cache; 17329 17330 this.fontCaches = this.fontCaches || []; 17331 17332 for( var i = 0; i < this.fontCaches.length; i++ ){ 17333 cache = this.fontCaches[ i ]; 17334 17335 if( cache.context === context ){ 17336 return cache; 17337 } 17338 } 17339 17340 cache = { 17341 context: context 17342 }; 17343 this.fontCaches.push( cache ); 17344 17345 return cache; 17346}; 17347 17348// set up canvas context with font
17349// returns transformed text string 17350CRp.setupTextStyle = function( context, ele ){ 17351 // Font style 17352 var parentOpacity = ele.effectiveOpacity(); 17353 var labelStyle = ele.pstyle( 'font-style' ).strValue; 17354 var labelSize = ele.pstyle( 'font-size' ).pfValue + 'px'; 17355 var labelFamily = ele.pstyle( 'font-family' ).strValue; 17356 var labelWeight = ele.pstyle( 'font-weight' ).strValue; 17357 var opacity = ele.pstyle( 'text-opacity' ).value * ele.pstyle( 'opacity' ).value * parentOpacity; 17358 var outlineOpacity = ele.pstyle( 'text-outline-opacity' ).value * opacity; 17359 var color = ele.pstyle( 'color' ).value; 17360 var outlineColor = ele.pstyle( 'text-outline-color' ).value; 17361 var shadowBlur = ele.pstyle( 'text-shadow-blur' ).pfValue; 17362 var shadowOpacity = ele.pstyle( 'text-shadow-opacity' ).value; 17363 var shadowColor = ele.pstyle( 'text-shadow-color' ).value; 17364 var shadowOffsetX = ele.pstyle( 'text-shadow-offset-x' ).pfValue; 17365 var shadowOffsetY = ele.pstyle( 'text-shadow-offset-y' ).pfValue; 17366 17367 var fontCacheKey = ele._private.fontKey; 17368 var cache = this.getFontCache( context ); 17369 17370 if( cache.key !== fontCacheKey ){ 17371 context.font = labelStyle + ' ' + labelWeight + ' ' + labelSize + ' ' + labelFamily; 17372 17373 cache.key = fontCacheKey; 17374 } 17375 17376 // Calculate text draw position based on text alignment 17377 17378 // so text outlines aren't jagged 17379 context.lineJoin = 'round'; 17380 17381 this.fillStyle( context, color[ 0 ], color[ 1 ], color[ 2 ], opacity ); 17382 17383 this.strokeStyle( context, outlineColor[ 0 ], outlineColor[ 1 ], outlineColor[ 2 ], outlineOpacity ); 17384 17385 this.shadowStyle( context, shadowColor, shadowOpacity, shadowBlur, shadowOffsetX, shadowOffsetY ); 17386}; 17387 17388function roundRect( ctx, x, y, width, height, radius ){ 17389 var radius = radius || 5; 17390 ctx.beginPath(); 17391 ctx.moveTo( x + radius, y ); 17392 ctx.lineTo( x + width - radius, y ); 17393 ctx.quadraticCurveTo( x + width, y, x + width, y + radius ); 17394 ctx.lineTo( x + width, y + height - radius ); 17395 ctx.quadraticCurveTo( x + width, y + height, x + width - radius, y + height ); 17396 ctx.lineTo( x + radius, y + height ); 17397 ctx.quadraticCurveTo( x, y + height, x, y + height - radius ); 17398 ctx.lineTo( x, y + radius ); 17399 ctx.quadraticCurveTo( x, y, x + radius, y ); 17400 ctx.closePath(); 17401 ctx.fill(); 17402} 17403 17404// Draw text 17405CRp.drawText = function( context, ele, prefix ){ 17406 var _p = ele._private; 17407 var rscratch = _p.rscratch; 17408 var parentOpacity = ele.effectiveOpacity(); 17409 if( parentOpacity === 0 || ele.pstyle( 'text-opacity' ).value === 0 ){ 17410 return; 17411 } 17412 17413 var textX = util.getPrefixedProperty( rscratch, 'labelX', prefix ); 17414 var textY = util.getPrefixedProperty( rscratch, 'labelY', prefix ); 17415 var text = this.getLabelText( ele, prefix ); 17416 17417 if( text != null && text !== '' && !isNaN( textX ) && !isNaN( textY ) ){ 17418 this.setupTextStyle( context, ele ); 17419 17420 var pdash = prefix ? prefix + '-' : ''; 17421 var textW = util.getPrefixedProperty( rscratch, 'labelWidth', prefix ); 17422 var textH = util.getPrefixedProperty( rscratch, 'labelHeight', prefix ); 17423 var textAngle = util.getPrefixedProperty( rscratch, 'labelAngle', prefix ); 17424 var marginX = ele.pstyle( pdash + 'text-margin-x' ).pfValue; 17425 var marginY = ele.pstyle( pdash + 'text-margin-y' ).pfValue; 17426 17427 var isEdge = ele.isEdge(); 17428 var isNode = ele.isNode(); 17429 17430 var halign = ele.pstyle( 'text-halign' ).value; 17431 var valign = ele.pstyle( 'text-valign' ).value; 17432 17433 if( isEdge ){ 17434 halign = 'center'; 17435 valign = 'center'; 17436 } 17437 17438 textX += marginX; 17439 textY += marginY; 17440 17441 var rotation = ele.pstyle( 'text-rotation' ); 17442 var theta; 17443 17444 if( rotation.strValue === 'autorotate' ){ 17445 theta = isEdge ? textAngle : 0; 17446 } else if( rotation.strValue === 'none' ){ 17447 theta = 0; 17448 } else { 17449 theta = rotation.pfValue; 17450 } 17451 17452 if( theta !== 0 ){ 17453 var orgTextX = textX; 17454 var orgTextY = textY; 17455 17456 context.translate( orgTextX, orgTextY ); 17457 context.rotate( theta ); 17458 17459 textX = 0; 17460 textY = 0; 17461 } 17462 17463 if( isNode ){ 17464 var pLeft = ele.pstyle( 'padding-left' ).pfValue; 17465 var pRight = ele.pstyle( 'padding-right' ).pfValue; 17466 var pTop = ele.pstyle( 'padding-top' ).pfValue; 17467 var pBottom = ele.pstyle( 'padding-bottom' ).pfValue; 17468 17469 textX += pLeft / 2; 17470 textX -= pRight / 2; 17471 17472 textY += pTop / 2; 17473 textY -= pBottom / 2; 17474 } 17475 17476 switch( valign ){ 17477 case 'top': 17478 break; 17479 case 'center': 17480 textY += textH / 2; 17481 break; 17482 case 'bottom': 17483 textY += textH; 17484 break; 17485 } 17486 17487 var backgroundOpacity = ele.pstyle( 'text-background-opacity' ).value; 17488 var borderOpacity = ele.pstyle( 'text-border-opacity' ).value; 17489 var textBorderWidth = ele.pstyle( 'text-border-width' ).pfValue; 17490 17491 if( backgroundOpacity > 0 || ( textBorderWidth > 0 && borderOpacity > 0 ) ){ 17492 var bgX = textX; 17493 17494 switch( halign ){ 17495 case 'left': 17496 bgX -= textW; 17497 break; 17498 case 'center': 17499 bgX -= textW / 2; 17500 break; 17501 case 'right': 17502 break; 17503 } 17504 17505 var bgY = textY - textH; 17506 17507 if( backgroundOpacity > 0 ){ 17508 var textFill = context.fillStyle;
17509 var textBackgroundColor = ele.pstyle( 'text-background-color' ).value; 17510 17511 context.fillStyle = 'rgba(' + textBackgroundColor[ 0 ] + ',' + textBackgroundColor[ 1 ] + ',' + textBackgroundColor[ 2 ] + ',' + backgroundOpacity * parentOpacity + ')'; 17512 var styleShape = ele.pstyle( 'text-background-shape' ).strValue; 17513 if( styleShape == 'roundrectangle' ){ 17514 roundRect( context, bgX, bgY, textW, textH, 2 ); 17515 } else { 17516 context.fillRect( bgX, bgY, textW, textH ); 17517 } 17518 context.fillStyle = textFill; 17519 } 17520 17521 if( textBorderWidth > 0 && borderOpacity > 0 ){ 17522 var textStroke = context.strokeStyle; 17523 var textLineWidth = context.lineWidth; 17524 var textBorderColor = ele.pstyle( 'text-border-color' ).value; 17525 var textBorderStyle = ele.pstyle( 'text-border-style' ).value; 17526 17527 context.strokeStyle = 'rgba(' + textBorderColor[ 0 ] + ',' + textBorderColor[ 1 ] + ',' + textBorderColor[ 2 ] + ',' + borderOpacity * parentOpacity + ')'; 17528 context.lineWidth = textBorderWidth; 17529 17530 if( context.setLineDash ){ // for very outofdate browsers 17531 switch( textBorderStyle ){ 17532 case 'dotted': 17533 context.setLineDash( [ 1, 1 ] ); 17534 break; 17535 case 'dashed': 17536 context.setLineDash( [ 4, 2 ] ); 17537 break; 17538 case 'double': 17539 context.lineWidth = textBorderWidth / 4; // 50% reserved for white between the two borders 17540 context.setLineDash( [] ); 17541 break; 17542 case 'solid': 17543 context.setLineDash( [] ); 17544 break; 17545 } 17546 } 17547 17548 context.strokeRect( bgX, bgY, textW, textH ); 17549 17550 if( textBorderStyle === 'double' ){ 17551 var whiteWidth = textBorderWidth / 2; 17552 17553 context.strokeRect( bgX + whiteWidth, bgY + whiteWidth, textW - whiteWidth * 2, textH - whiteWidth * 2 ); 17554 } 17555 17556 if( context.setLineDash ){ // for very outofdate browsers 17557 context.setLineDash( [] ); 17558 } 17559 context.lineWidth = textLineWidth; 17560 context.strokeStyle = textStroke; 17561 } 17562 17563 } 17564 17565 var lineWidth = 2 * ele.pstyle( 'text-outline-width' ).pfValue; // *2 b/c the stroke is drawn centred on the middle 17566 17567 if( lineWidth > 0 ){ 17568 context.lineWidth = lineWidth; 17569 } 17570 17571 if( ele.pstyle( 'text-wrap' ).value === 'wrap' ){ 17572 var lines = rscratch.labelWrapCachedLines; 17573 var lineHeight = textH / lines.length; 17574 17575 switch( valign ){ 17576 case 'top': 17577 textY -= ( lines.length - 1 ) * lineHeight; 17578 break; 17579 case 'center': 17580 case 'bottom': 17581 textY -= ( lines.length - 1 ) * lineHeight; 17582 break; 17583 } 17584 17585 for( var l = 0; l < lines.length; l++ ){ 17586 if( lineWidth > 0 ){ 17587 context.strokeText( lines[ l ], textX, textY ); 17588 } 17589 17590 context.fillText( lines[ l ], textX, textY ); 17591 17592 textY += lineHeight; 17593 } 17594 17595 } else { 17596 if( lineWidth > 0 ){ 17597 context.strokeText( text, textX, textY ); 17598 } 17599 17600 context.fillText( text, textX, textY ); 17601 } 17602 17603 if( theta !== 0 ){ 17604 context.rotate( -theta ); 17605 context.translate( -orgTextX, -orgTextY ); 17606 } 17607 17608 this.shadowStyle( context, 'transparent', 0 ); // reset for next guy 17609 } 17610}; 17611 17612module.exports = CRp; 17613 17614},{"../../../math":85,"../../../util":100}],69:[function(_dereq_,module,exports){ 17615'use strict'; 17616 17617var is = _dereq_( '../../../is' ); 17618 17619var CRp = {}; 17620 17621CRp.drawNode = function( context, node, shiftToOriginWithBb, drawLabel ){ 17622 var r = this; 17623 var nodeWidth, nodeHeight; 17624 var rs = node._private.rscratch; 17625 var _p = node._private; 17626 var pos = pos || _p.position; 17627 17628 if( !is.number( pos.x ) || !is.number( pos.y ) ){ 17629 return; // can't draw node with undefined position 17630 } 17631 17632 if( !node.visible() ){ return; } 17633 17634 var parentOpacity = node.effectiveOpacity(); 17635 17636 var usePaths = this.usePaths(); 17637 var path; 17638 var pathCacheHit = false; 17639 17640 nodeWidth = node.width() + node.pstyle( 'padding-left' ).pfValue + node.pstyle( 'padding-right' ).pfValue; 17641 nodeHeight = node.height() + node.pstyle( 'padding-top' ).pfValue + node.pstyle( 'padding-bottom' ).pfValue; 17642 17643 context.lineWidth = node.pstyle( 'border-width' ).pfValue; 17644 17645 // 17646 // setup shift 17647 17648 var bb; 17649 if( shiftToOriginWithBb ){ 17650 bb = shiftToOriginWithBb; 17651 17652 context.translate( -bb.x1, -bb.y1 ); 17653 } 17654 17655 // 17656 // load bg image 17657 17658 var bgImgProp = node.pstyle( 'background-image' ); 17659 var url = bgImgProp.value[2] || bgImgProp.value[1]; 17660 var image; 17661 17662 if( url !== undefined ){ 17663 17664 var bgImgCrossOrigin = node.pstyle( 'background-image-crossorigin' ); 17665 17666 // get image, and if not loaded then ask to redraw when later loaded 17667 image = this.getCachedImage( url, bgImgCrossOrigin, function(){ 17668 node.trigger('background'); 17669 17670 r.redrawHint( 'eles', true ); 17671 r.redrawHint( 'drag', true ); 17672 17673 r.drawingImage = true; 17674 17675 r.redraw(); 17676 } ); 17677 17678 var prevBging = _p.backgrounding; 17679 _p.backgrounding = !image.complete; 17680 17681 if( prevBging !== _p.backgrounding ){ // update style b/c :backgrounding state changed 17682 node.updateStyle( false ); 17683 } 17684 } 17685 17686 // 17687 // setup styles 17688 17689 var bgColor = node.pstyle( 'background-color' ).value; 17690 var borderColor = node.pstyle( 'border-color' ).value; 17691 var borderStyle = node.pstyle( 'border-style' ).value; 17692 17693 this.fillStyle( context, bgColor[0], bgColor[1], bgColor[2], node.pstyle( 'background-opacity' ).value * parentOpacity ); 17694 17695 this.strokeStyle( context, borderColor[0], borderColor[1], borderColor[2], node.pstyle( 'border-opacity' ).value * parentOpacity ); 17696 17697 var shadowBlur = node.pstyle( 'shadow-blur' ).pfValue; 17698 var shadowOpacity = node.pstyle( 'shadow-opacity' ).value; 17699 var shadowColor = node.pstyle( 'shadow-color' ).value; 17700 var shadowOffsetX = node.pstyle( 'shadow-offset-x' ).pfValue; 17701 var shadowOffsetY = node.pstyle( 'shadow-offset-y' ).pfValue; 17702 17703 this.shadowStyle( context, shadowColor, shadowOpacity, shadowBlur, shadowOffsetX, shadowOffsetY ); 17704 17705 context.lineJoin = 'miter'; // so borders are square with the node shape 17706 17707 if( context.setLineDash ){ // for very outofdate browsers 17708 switch( borderStyle ){ 17709 case 'dotted': 17710 context.setLineDash( [ 1, 1 ] ); 17711 break; 17712 17713 case 'dashed': 17714 context.setLineDash( [ 4, 2 ] ); 17715 break; 17716 17717 case 'solid': 17718 case 'double': 17719 context.setLineDash( [ ] ); 17720 break; 17721 } 17722 } 17723 17724 17725 // 17726 // draw shape 17727 17728 var styleShape = node.pstyle('shape').strValue; 17729 var shapePts = node.pstyle('shape-polygon-points').pfValue; 17730 17731 if( usePaths ){ 17732 var pathCacheKey = styleShape + '$' + nodeWidth + '$' + nodeHeight + ( styleShape === 'polygon' ? '$' + shapePts.join('$') : '' ); 17733 17734 context.translate( pos.x, pos.y ); 17735 17736 if( rs.pathCacheKey === pathCacheKey ){ 17737 path = rs.pathCache; 17738 pathCacheHit = true; 17739 } else { 17740 path = new Path2D(); // eslint-disable-line no-undef 17741 rs.pathCacheKey = pathCacheKey; 17742 rs.pathCache = path; 17743 } 17744 } 17745 17746 if( !pathCacheHit ){ 17747 17748 var npos = pos; 17749 17750 if( usePaths ){ 17751 npos = { 17752 x: 0, 17753 y: 0 17754 }; 17755 } 17756 17757 r.nodeShapes[ this.getNodeShape( node ) ].draw( 17758 ( path || context ), 17759 npos.x, 17760 npos.y, 17761 nodeWidth, 17762 nodeHeight ); 17763 } 17764 17765 if( usePaths ){ 17766 context.fill( path ); 17767 } else { 17768 context.fill(); 17769 } 17770 17771 this.shadowStyle( context, 'transparent', 0 ); // reset for next guy 17772 17773 // 17774 // bg image 17775 17776 if( url !== undefined ){ 17777 if( image.complete ){ 17778 this.drawInscribedImage( context, image, node ); 17779 } 17780 } 17781 17782 // 17783 // pie 17784 17785 var darkness = node.pstyle( 'background-blacken' ).value; 17786 var borderWidth = node.pstyle( 'border-width' ).pfValue; 17787 17788 if( this.hasPie( node ) ){ 17789 this.drawPie( context, node, parentOpacity ); 17790 17791 // redraw path for blacken and border 17792 if( darkness !== 0 || borderWidth !== 0 ){ 17793 17794 if( !usePaths ){
17795 r.nodeShapes[ this.getNodeShape( node ) ].draw( 17796 context, 17797 pos.x, 17798 pos.y, 17799 nodeWidth, 17800 nodeHeight ); 17801 } 17802 } 17803 } 17804 17805 // 17806 // darken/lighten 17807 17808 if( darkness > 0 ){ 17809 this.fillStyle( context, 0, 0, 0, darkness ); 17810 17811 if( usePaths ){ 17812 context.fill( path ); 17813 } else { 17814 context.fill(); 17815 } 17816 17817 } else if( darkness < 0 ){ 17818 this.fillStyle( context, 255, 255, 255, -darkness ); 17819 17820 if( usePaths ){ 17821 context.fill( path ); 17822 } else { 17823 context.fill(); 17824 } 17825 } 17826 17827 // 17828 // border 17829 17830 if( borderWidth > 0 ){ 17831 17832 if( usePaths ){ 17833 context.stroke( path ); 17834 } else { 17835 context.stroke(); 17836 } 17837 17838 if( borderStyle === 'double' ){ 17839 context.lineWidth = node.pstyle( 'border-width' ).pfValue / 3; 17840 17841 var gco = context.globalCompositeOperation; 17842 context.globalCompositeOperation = 'destination-out'; 17843 17844 if( usePaths ){ 17845 context.stroke( path ); 17846 } else { 17847 context.stroke(); 17848 } 17849 17850 context.globalCompositeOperation = gco; 17851 } 17852 17853 } 17854 17855 if( usePaths ){ 17856 context.translate( -pos.x, -pos.y ); 17857 } 17858 17859 // reset in case we changed the border style 17860 if( context.setLineDash ){ // for very outofdate browsers 17861 context.setLineDash( [ ] ); 17862 } 17863 17864 // 17865 // label 17866 17867 r.drawElementText( context, node, drawLabel ); 17868 17869 // 17870 // overlay 17871 17872 var overlayPadding = node.pstyle( 'overlay-padding' ).pfValue; 17873 var overlayOpacity = node.pstyle( 'overlay-opacity' ).value; 17874 var overlayColor = node.pstyle( 'overlay-color' ).value; 17875 17876 if( overlayOpacity > 0 ){ 17877 this.fillStyle( context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity ); 17878 17879 r.nodeShapes[ 'roundrectangle' ].draw( 17880 context, 17881 node._private.position.x, 17882 node._private.position.y, 17883 nodeWidth + overlayPadding * 2, 17884 nodeHeight + overlayPadding * 2 17885 ); 17886 17887 context.fill(); 17888 } 17889 17890 // 17891 // clean up shift 17892 17893 if( shiftToOriginWithBb ){ 17894 context.translate( bb.x1, bb.y1 ); 17895 } 17896 17897}; 17898 17899// does the node have at least one pie piece? 17900CRp.hasPie = function( node ){ 17901 node = node[0]; // ensure ele ref 17902 17903 return node._private.hasPie; 17904}; 17905 17906CRp.drawPie = function( context, node, nodeOpacity, pos ){ 17907 node = node[0]; // ensure ele ref 17908 17909 var _p = node._private; 17910 var cyStyle = node.cy().style(); 17911 var pieSize = node.pstyle( 'pie-size' ); 17912 var nodeW = node.width(); 17913 var nodeH = node.height(); 17914 var pos = pos || _p.position; 17915 var x = pos.x; 17916 var y = pos.y; 17917 var radius = Math.min( nodeW, nodeH ) / 2; // must fit in node 17918 var lastPercent = 0; // what % to continue drawing pie slices from on [0, 1] 17919 var usePaths = this.usePaths(); 17920 17921 if( usePaths ){ 17922 x = 0; 17923 y = 0; 17924 } 17925 17926 if( pieSize.units === '%' ){ 17927 radius = radius * pieSize.value / 100; 17928 } else if( pieSize.pfValue !== undefined ){ 17929 radius = pieSize.pfValue / 2; 17930 } 17931 17932 for( var i = 1; i <= cyStyle.pieBackgroundN; i++ ){ // 1..N 17933 var size = node.pstyle( 'pie-' + i + '-background-size' ).value; 17934 var color = node.pstyle( 'pie-' + i + '-background-color' ).value; 17935 var opacity = node.pstyle( 'pie-' + i + '-background-opacity' ).value * nodeOpacity; 17936 var percent = size / 100; // map integer range [0, 100] to [0, 1] 17937 17938 // percent can't push beyond 1 17939 if( percent + lastPercent > 1 ){ 17940 percent = 1 - lastPercent; 17941 } 17942 17943 var angleStart = 1.5 * Math.PI + 2 * Math.PI * lastPercent; // start at 12 o'clock and go clockwise 17944 var angleDelta = 2 * Math.PI * percent; 17945 var angleEnd = angleStart + angleDelta; 17946 17947 // ignore if 17948 // - zero size 17949 // - we're already beyond the full circle 17950 // - adding the current slice would go beyond the full circle 17951 if( size === 0 || lastPercent >= 1 || lastPercent + percent > 1 ){ 17952 continue; 17953 } 17954 17955 context.beginPath(); 17956 context.moveTo( x, y ); 17957 context.arc( x, y, radius, angleStart, angleEnd ); 17958 context.closePath(); 17959 17960 this.fillStyle( context, color[0], color[1], color[2], opacity ); 17961 17962 context.fill(); 17963 17964 lastPercent += percent; 17965 } 17966 17967}; 17968 17969 17970module.exports = CRp; 17971 17972},{"../../../is":83}],70:[function(_dereq_,module,exports){ 17973'use strict'; 17974 17975var CRp = {}; 17976 17977var util = _dereq_( '../../../util' ); 17978 17979var motionBlurDelay = 100; 17980 17981// var isFirefox = typeof InstallTrigger !== 'undefined'; 17982 17983CRp.getPixelRatio = function(){ 17984 var context = this.data.contexts[0]; 17985 17986 if( this.forcedPixelRatio != null ){ 17987 return this.forcedPixelRatio; 17988 } 17989 17990 var backingStore = context.backingStorePixelRatio || 17991 context.webkitBackingStorePixelRatio || 17992 context.mozBackingStorePixelRatio || 17993 context.msBackingStorePixelRatio || 17994 context.oBackingStorePixelRatio || 17995 context.backingStorePixelRatio || 1; 17996 17997 return (window.devicePixelRatio || 1) / backingStore; // eslint-disable-line no-undef 17998}; 17999 18000CRp.paintCache = function( context ){ 18001 var caches = this.paintCaches = this.paintCaches || []; 18002 var needToCreateCache = true; 18003 var cache; 18004 18005 for( var i = 0; i < caches.length; i++ ){ 18006 cache = caches[ i ]; 18007 18008 if( cache.context === context ){ 18009 needToCreateCache = false;
18010 break; 18011 } 18012 } 18013 18014 if( needToCreateCache ){ 18015 cache = { 18016 context: context 18017 }; 18018 caches.push( cache ); 18019 } 18020 18021 return cache; 18022}; 18023 18024CRp.fillStyle = function( context, r, g, b, a ){ 18025 context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 18026 18027 // turn off for now, seems context does its own caching 18028 18029 // var cache = this.paintCache(context); 18030 18031 // var fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 18032 18033 // if( cache.fillStyle !== fillStyle ){ 18034 // context.fillStyle = cache.fillStyle = fillStyle; 18035 // } 18036}; 18037 18038CRp.strokeStyle = function( context, r, g, b, a ){ 18039 context.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 18040 18041 // turn off for now, seems context does its own caching 18042 18043 // var cache = this.paintCache(context); 18044 18045 // var strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; 18046 18047 // if( cache.strokeStyle !== strokeStyle ){ 18048 // context.strokeStyle = cache.strokeStyle = strokeStyle; 18049 // } 18050}; 18051 18052CRp.shadowStyle = function( context, color, opacity, blur, offsetX, offsetY ){ 18053 var zoom = this.cy.zoom(); 18054 18055 // var cache = this.paintCache( context ); 18056 // 18057 // // don't make expensive changes to the shadow style if it's not used 18058 // if( cache.shadowOpacity === 0 && opacity === 0 ){ 18059 // return; 18060 // } 18061 // 18062 // cache.shadowOpacity = opacity; 18063 18064 if( opacity > 0 ){ 18065 context.shadowBlur = blur * zoom; 18066 context.shadowColor = 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + opacity + ')'; 18067 context.shadowOffsetX = offsetX * zoom; 18068 context.shadowOffsetY = offsetY * zoom; 18069 } else { 18070 context.shadowBlur = 0; 18071 context.shadowColor = 'transparent'; 18072 context.shadowOffsetX = 0; 18073 context.shadowOffsetY = 0; 18074 } 18075}; 18076 18077// Resize canvas 18078CRp.matchCanvasSize = function( container ){ 18079 var r = this; 18080 var data = r.data; 18081 var width = container.clientWidth; 18082 var height = container.clientHeight; 18083 var pixelRatio = r.getPixelRatio(); 18084 var mbPxRatio = r.motionBlurPxRatio; 18085 18086 if( 18087 container === r.data.bufferCanvases[ r.MOTIONBLUR_BUFFER_NODE ] || 18088 container === r.data.bufferCanvases[ r.MOTIONBLUR_BUFFER_DRAG ] 18089 ){ 18090 pixelRatio = mbPxRatio; 18091 } 18092 18093 var canvasWidth = width * pixelRatio; 18094 var canvasHeight = height * pixelRatio; 18095 var canvas; 18096 18097 if( canvasWidth === r.canvasWidth && canvasHeight === r.canvasHeight ){ 18098 return; // save cycles if same 18099 } 18100 18101 r.fontCaches = null; // resizing resets the style 18102 18103 var canvasContainer = data.canvasContainer; 18104 canvasContainer.style.width = width + 'px'; 18105 canvasContainer.style.height = height + 'px'; 18106 18107 for( var i = 0; i < r.CANVAS_LAYERS; i++ ){ 18108 18109 canvas = data.canvases[ i ]; 18110 18111 if( canvas.width !== canvasWidth || canvas.height !== canvasHeight ){ 18112 18113 canvas.width = canvasWidth; 18114 canvas.height = canvasHeight; 18115 18116 canvas.style.width = width + 'px'; 18117 canvas.style.height = height + 'px'; 18118 } 18119 } 18120 18121 for( var i = 0; i < r.BUFFER_COUNT; i++ ){ 18122 18123 canvas = data.bufferCanvases[ i ]; 18124 18125 if( canvas.width !== canvasWidth || canvas.height !== canvasHeight ){ 18126 18127 canvas.width = canvasWidth; 18128 canvas.height = canvasHeight; 18129 18130 canvas.style.width = width + 'px'; 18131 canvas.style.height = height + 'px'; 18132 } 18133 } 18134 18135 r.textureMult = 1; 18136 if( pixelRatio <= 1 ){ 18137 canvas = data.bufferCanvases[ r.TEXTURE_BUFFER ]; 18138 18139 r.textureMult = 2; 18140 canvas.width = canvasWidth * r.textureMult; 18141 canvas.height = canvasHeight * r.textureMult; 18142 } 18143 18144 r.canvasWidth = canvasWidth; 18145 r.canvasHeight = canvasHeight; 18146 18147}; 18148 18149CRp.renderTo = function( cxt, zoom, pan, pxRatio ){ 18150 this.render( { 18151 forcedContext: cxt, 18152 forcedZoom: zoom, 18153 forcedPan: pan, 18154 drawAllLayers: true, 18155 forcedPxRatio: pxRatio 18156 } ); 18157}; 18158 18159CRp.render = function( options ){ 18160 options = options || util.staticEmptyObject(); 18161 18162 var forcedContext = options.forcedContext; 18163 var drawAllLayers = options.drawAllLayers; 18164 var drawOnlyNodeLayer = options.drawOnlyNodeLayer; 18165 var forcedZoom = options.forcedZoom; 18166 var forcedPan = options.forcedPan; 18167 var r = this; 18168 var pixelRatio = options.forcedPxRatio === undefined ? this.getPixelRatio() : options.forcedPxRatio; 18169 var cy = r.cy; var data = r.data; 18170 var needDraw = data.canvasNeedsRedraw; 18171 var textureDraw = r.textureOnViewport && !forcedContext && (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming); 18172 var motionBlur = options.motionBlur !== undefined ? options.motionBlur : r.motionBlur; 18173 var mbPxRatio = r.motionBlurPxRatio; 18174 var hasCompoundNodes = cy.hasCompoundNodes(); 18175 var inNodeDragGesture = r.hoverData.draggingEles; 18176 var inBoxSelection = r.hoverData.selecting || r.touchData.selecting ? true : false;
vendor: 4,620 bytes, lines 18177-18355
18177 motionBlur = motionBlur && !forcedContext && r.motionBlurEnabled && !inBoxSelection; 18178 var motionBlurFadeEffect = motionBlur; 18179 18180 if( !forcedContext ){ 18181 if( r.prevPxRatio !== pixelRatio ){ 18182 r.invalidateContainerClientCoordsCache(); 18183 r.matchCanvasSize( r.container ); 18184 18185 r.redrawHint('eles', true); 18186 r.redrawHint('drag', true); 18187 } 18188 18189 r.prevPxRatio = pixelRatio; 18190 } 18191 18192 if( !forcedContext && r.motionBlurTimeout ){ 18193 clearTimeout( r.motionBlurTimeout ); 18194 } 18195 18196 if( motionBlur ){ 18197 if( r.mbFrames == null ){ 18198 r.mbFrames = 0; 18199 } 18200 18201 if( !r.drawingImage ){ // image loading frames don't count towards motion blur blurry frames 18202 r.mbFrames++; 18203 } 18204 18205 if( r.mbFrames < 3 ){ // need several frames before even high quality motionblur 18206 motionBlurFadeEffect = false; 18207 } 18208 18209 // go to lower quality blurry frames when several m/b frames have been rendered (avoids flashing) 18210 if( r.mbFrames > r.minMbLowQualFrames ){ 18211 //r.fullQualityMb = false; 18212 r.motionBlurPxRatio = r.mbPxRBlurry; 18213 } 18214 } 18215 18216 if( r.clearingMotionBlur ){ 18217 r.motionBlurPxRatio = 1; 18218 } 18219 18220 // b/c drawToContext() may be async w.r.t. redraw(), keep track of last texture frame 18221 // because a rogue async texture frame would clear needDraw 18222 if( r.textureDrawLastFrame && !textureDraw ){ 18223 needDraw[ r.NODE ] = true; 18224 needDraw[ r.SELECT_BOX ] = true; 18225 } 18226 18227 var coreStyle = cy.style()._private.coreStyle; 18228 18229 var zoom = cy.zoom(); 18230 var effectiveZoom = forcedZoom !== undefined ? forcedZoom : zoom; 18231 var pan = cy.pan(); 18232 var effectivePan = { 18233 x: pan.x, 18234 y: pan.y 18235 }; 18236 18237 var vp = { 18238 zoom: zoom, 18239 pan: { 18240 x: pan.x, 18241 y: pan.y 18242 } 18243 }; 18244 var prevVp = r.prevViewport; 18245 var viewportIsDiff = prevVp === undefined || vp.zoom !== prevVp.zoom || vp.pan.x !== prevVp.pan.x || vp.pan.y !== prevVp.pan.y; 18246 18247 // we want the low quality motionblur only when the viewport is being manipulated etc (where it's not noticed) 18248 if( !viewportIsDiff && !(inNodeDragGesture && !hasCompoundNodes) ){ 18249 r.motionBlurPxRatio = 1; 18250 } 18251 18252 if( forcedPan ){ 18253 effectivePan = forcedPan; 18254 } 18255 18256 // apply pixel ratio 18257 18258 effectiveZoom *= pixelRatio; 18259 effectivePan.x *= pixelRatio; 18260 effectivePan.y *= pixelRatio; 18261 18262 var eles = r.getCachedZSortedEles(); 18263 18264 function mbclear( context, x, y, w, h ){ 18265 var gco = context.globalCompositeOperation; 18266 18267 context.globalCompositeOperation = 'destination-out'; 18268 r.fillStyle( context, 255, 255, 255, r.motionBlurTransparency ); 18269 context.fillRect( x, y, w, h ); 18270 18271 context.globalCompositeOperation = gco; 18272 } 18273 18274 function setContextTransform( context, clear ){ 18275 var ePan, eZoom, w, h; 18276 18277 if( !r.clearingMotionBlur && (context === data.bufferContexts[ r.MOTIONBLUR_BUFFER_NODE ] || context === data.bufferContexts[ r.MOTIONBLUR_BUFFER_DRAG ]) ){ 18278 ePan = { 18279 x: pan.x * mbPxRatio, 18280 y: pan.y * mbPxRatio 18281 }; 18282 18283 eZoom = zoom * mbPxRatio; 18284 18285 w = r.canvasWidth * mbPxRatio; 18286 h = r.canvasHeight * mbPxRatio; 18287 } else { 18288 ePan = effectivePan; 18289 eZoom = effectiveZoom; 18290 18291 w = r.canvasWidth; 18292 h = r.canvasHeight; 18293 } 18294 18295 context.setTransform( 1, 0, 0, 1, 0, 0 ); 18296 18297 if( clear === 'motionBlur' ){ 18298 mbclear( context, 0, 0, w, h ); 18299 } else if( !forcedContext && (clear === undefined || clear) ){ 18300 context.clearRect( 0, 0, w, h ); 18301 } 18302 18303 if( !drawAllLayers ){ 18304 context.translate( ePan.x, ePan.y ); 18305 context.scale( eZoom, eZoom ); 18306 } 18307 if( forcedPan ){ 18308 context.translate( forcedPan.x, forcedPan.y ); 18309 } 18310 if( forcedZoom ){ 18311 context.scale( forcedZoom, forcedZoom ); 18312 } 18313 } 18314 18315 if( !textureDraw ){ 18316 r.textureDrawLastFrame = false; 18317 } 18318 18319 if( textureDraw ){ 18320 r.textureDrawLastFrame = true; 18321 18322 var bb; 18323 18324 if( !r.textureCache ){ 18325 r.textureCache = {}; 18326 18327 bb = r.textureCache.bb = cy.mutableElements().boundingBox(); 18328 18329 r.textureCache.texture = r.data.bufferCanvases[ r.TEXTURE_BUFFER ]; 18330 18331 var cxt = r.data.bufferContexts[ r.TEXTURE_BUFFER ]; 18332 18333 cxt.setTransform( 1, 0, 0, 1, 0, 0 ); 18334 cxt.clearRect( 0, 0, r.canvasWidth * r.textureMult, r.canvasHeight * r.textureMult ); 18335 18336 r.render( { 18337 forcedContext: cxt, 18338 drawOnlyNodeLayer: true, 18339 forcedPxRatio: pixelRatio * r.textureMult 18340 } ); 18341 18342 var vp = r.textureCache.viewport = { 18343 zoom: cy.zoom(), 18344 pan: cy.pan(), 18345 width: r.canvasWidth, 18346 height: r.canvasHeight 18347 }; 18348 18349 vp.mpan = { 18350 x: (0 - vp.pan.x) / vp.zoom, 18351 y: (0 - vp.pan.y) / vp.zoom 18352 }; 18353 } 18354 18355 needDraw[ r.DRAG ] = false;
18356 needDraw[ r.NODE ] = false; 18357 18358 var context = data.contexts[ r.NODE ]; 18359 18360 var texture = r.textureCache.texture; 18361 var vp = r.textureCache.viewport; 18362 bb = r.textureCache.bb; 18363 18364 context.setTransform( 1, 0, 0, 1, 0, 0 ); 18365 18366 if( motionBlur ){ 18367 mbclear( context, 0, 0, vp.width, vp.height ); 18368 } else { 18369 context.clearRect( 0, 0, vp.width, vp.height ); 18370 } 18371 18372 var outsideBgColor = coreStyle[ 'outside-texture-bg-color' ].value; 18373 var outsideBgOpacity = coreStyle[ 'outside-texture-bg-opacity' ].value; 18374 r.fillStyle( context, outsideBgColor[0], outsideBgColor[1], outsideBgColor[2], outsideBgOpacity ); 18375 context.fillRect( 0, 0, vp.width, vp.height ); 18376 18377 var zoom = cy.zoom(); 18378 18379 setContextTransform( context, false ); 18380 18381 context.clearRect( vp.mpan.x, vp.mpan.y, vp.width / vp.zoom / pixelRatio, vp.height / vp.zoom / pixelRatio ); 18382 context.drawImage( texture, vp.mpan.x, vp.mpan.y, vp.width / vp.zoom / pixelRatio, vp.height / vp.zoom / pixelRatio ); 18383 18384 } else if( r.textureOnViewport && !forcedContext ){ // clear the cache since we don't need it 18385 r.textureCache = null; 18386 } 18387 18388 var extent = cy.extent(); 18389 var vpManip = (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming || r.hoverData.draggingEles); 18390 var hideEdges = r.hideEdgesOnViewport && vpManip; 18391 18392 var needMbClear = []; 18393 18394 needMbClear[ r.NODE ] = !needDraw[ r.NODE ] && motionBlur && !r.clearedForMotionBlur[ r.NODE ] || r.clearingMotionBlur; 18395 if( needMbClear[ r.NODE ] ){ r.clearedForMotionBlur[ r.NODE ] = true; } 18396 18397 needMbClear[ r.DRAG ] = !needDraw[ r.DRAG ] && motionBlur && !r.clearedForMotionBlur[ r.DRAG ] || r.clearingMotionBlur; 18398 if( needMbClear[ r.DRAG ] ){ r.clearedForMotionBlur[ r.DRAG ] = true; } 18399 18400 if( needDraw[ r.NODE ] || drawAllLayers || drawOnlyNodeLayer || needMbClear[ r.NODE ] ){ 18401 var useBuffer = motionBlur && !needMbClear[ r.NODE ] && mbPxRatio !== 1; 18402 var context = forcedContext || ( useBuffer ? r.data.bufferContexts[ r.MOTIONBLUR_BUFFER_NODE ] : data.contexts[ r.NODE ] ); 18403 var clear = motionBlur && !useBuffer ? 'motionBlur' : undefined; 18404 18405 setContextTransform( context, clear ); 18406 18407 if( hideEdges ){ 18408 r.drawCachedNodes( context, eles.nondrag, pixelRatio, extent ); 18409 } else { 18410 r.drawLayeredElements( context, eles.nondrag, pixelRatio, extent ); 18411 } 18412 18413 if( !drawAllLayers && !motionBlur ){ 18414 needDraw[ r.NODE ] = false; 18415 } 18416 } 18417 18418 if( !drawOnlyNodeLayer && (needDraw[ r.DRAG ] || drawAllLayers || needMbClear[ r.DRAG ]) ){ 18419 var useBuffer = motionBlur && !needMbClear[ r.DRAG ] && mbPxRatio !== 1; 18420 var context = forcedContext || ( useBuffer ? r.data.bufferContexts[ r.MOTIONBLUR_BUFFER_DRAG ] : data.contexts[ r.DRAG ] ); 18421 18422 setContextTransform( context, motionBlur && !useBuffer ? 'motionBlur' : undefined ); 18423 18424 if( hideEdges ){ 18425 r.drawCachedNodes( context, eles.drag, pixelRatio, extent ); 18426 } else { 18427 r.drawCachedElements( context, eles.drag, pixelRatio, extent ); 18428 } 18429 18430 if( !drawAllLayers && !motionBlur ){ 18431 needDraw[ r.DRAG ] = false; 18432 } 18433 } 18434 18435 if( r.showFps || (!drawOnlyNodeLayer && (needDraw[ r.SELECT_BOX ] && !drawAllLayers)) ){ 18436 var context = forcedContext || data.contexts[ r.SELECT_BOX ]; 18437 18438 setContextTransform( context ); 18439 18440 if( r.selection[4] == 1 && ( r.hoverData.selecting || r.touchData.selecting ) ){ 18441 var zoom = r.cy.zoom(); 18442 var borderWidth = coreStyle[ 'selection-box-border-width' ].value / zoom; 18443 18444 context.lineWidth = borderWidth; 18445 context.fillStyle = 'rgba(' 18446 + coreStyle[ 'selection-box-color' ].value[0] + ',' 18447 + coreStyle[ 'selection-box-color' ].value[1] + ',' 18448 + coreStyle[ 'selection-box-color' ].value[2] + ',' 18449 + coreStyle[ 'selection-box-opacity' ].value + ')'; 18450 18451 context.fillRect( 18452 r.selection[0], 18453 r.selection[1], 18454 r.selection[2] - r.selection[0], 18455 r.selection[3] - r.selection[1] ); 18456 18457 if( borderWidth > 0 ){ 18458 context.strokeStyle = 'rgba(' 18459 + coreStyle[ 'selection-box-border-color' ].value[0] + ',' 18460 + coreStyle[ 'selection-box-border-color' ].value[1] + ',' 18461 + coreStyle[ 'selection-box-border-color' ].value[2] + ',' 18462 + coreStyle[ 'selection-box-opacity' ].value + ')'; 18463 18464 context.strokeRect( 18465 r.selection[0], 18466 r.selection[1], 18467 r.selection[2] - r.selection[0], 18468 r.selection[3] - r.selection[1] ); 18469 } 18470 } 18471 18472 if( data.bgActivePosistion && !r.hoverData.selecting ){ 18473 var zoom = r.cy.zoom(); 18474 var pos = data.bgActivePosistion; 18475 18476 context.fillStyle = 'rgba(' 18477 + coreStyle[ 'active-bg-color' ].value[0] + ',' 18478 + coreStyle[ 'active-bg-color' ].value[1] + ',' 18479 + coreStyle[ 'active-bg-color' ].value[2] + ',' 18480 + coreStyle[ 'active-bg-opacity' ].value + ')'; 18481 18482 context.beginPath(); 18483 context.arc( pos.x, pos.y, coreStyle[ 'active-bg-size' ].pfValue / zoom, 0, 2 * Math.PI ); 18484 context.fill(); 18485 } 18486 18487 var timeToRender = r.lastRedrawTime; 18488 if( r.showFps && timeToRender ){ 18489 timeToRender = Math.round( timeToRender ); 18490 var fps = Math.round( 1000 / timeToRender ); 18491 18492 context.setTransform( 1, 0, 0, 1, 0, 0 ); 18493 18494 context.fillStyle = 'rgba(255, 0, 0, 0.75)'; 18495 context.strokeStyle = 'rgba(255, 0, 0, 0.75)'; 18496 context.lineWidth = 1; 18497 context.fillText( '1 frame = ' + timeToRender + ' ms = ' + fps + ' fps', 0, 20 ); 18498 18499 var maxFps = 60; 18500 context.strokeRect( 0, 30, 250, 20 ); 18501 context.fillRect( 0, 30, 250 * Math.min( fps / maxFps, 1 ), 20 ); 18502 } 18503 18504 if( !drawAllLayers ){ 18505 needDraw[ r.SELECT_BOX ] = false;
18506 } 18507 } 18508 18509 // motionblur: blit rendered blurry frames 18510 if( motionBlur && mbPxRatio !== 1 ){ 18511 var cxtNode = data.contexts[ r.NODE ]; 18512 var txtNode = r.data.bufferCanvases[ r.MOTIONBLUR_BUFFER_NODE ]; 18513 18514 var cxtDrag = data.contexts[ r.DRAG ]; 18515 var txtDrag = r.data.bufferCanvases[ r.MOTIONBLUR_BUFFER_DRAG ]; 18516 18517 var drawMotionBlur = function( cxt, txt, needClear ){ 18518 cxt.setTransform( 1, 0, 0, 1, 0, 0 ); 18519 18520 if( needClear || !motionBlurFadeEffect ){ 18521 cxt.clearRect( 0, 0, r.canvasWidth, r.canvasHeight ); 18522 } else { 18523 mbclear( cxt, 0, 0, r.canvasWidth, r.canvasHeight ); 18524 } 18525 18526 var pxr = mbPxRatio; 18527 18528 cxt.drawImage( 18529 txt, // img 18530 0, 0, // sx, sy 18531 r.canvasWidth * pxr, r.canvasHeight * pxr, // sw, sh 18532 0, 0, // x, y 18533 r.canvasWidth, r.canvasHeight // w, h 18534 ); 18535 }; 18536 18537 if( needDraw[ r.NODE ] || needMbClear[ r.NODE ] ){ 18538 drawMotionBlur( cxtNode, txtNode, needMbClear[ r.NODE ] ); 18539 needDraw[ r.NODE ] = false; 18540 } 18541 18542 if( needDraw[ r.DRAG ] || needMbClear[ r.DRAG ] ){ 18543 drawMotionBlur( cxtDrag, txtDrag, needMbClear[ r.DRAG ] ); 18544 needDraw[ r.DRAG ] = false; 18545 } 18546 } 18547 18548 r.prevViewport = vp; 18549 18550 if( r.clearingMotionBlur ){ 18551 r.clearingMotionBlur = false; 18552 r.motionBlurCleared = true; 18553 r.motionBlur = true; 18554 } 18555 18556 if( motionBlur ){ 18557 r.motionBlurTimeout = setTimeout( function(){ 18558 r.motionBlurTimeout = null; 18559 18560 r.clearedForMotionBlur[ r.NODE ] = false; 18561 r.clearedForMotionBlur[ r.DRAG ] = false; 18562 r.motionBlur = false; 18563 r.clearingMotionBlur = !textureDraw; 18564 r.mbFrames = 0; 18565 18566 needDraw[ r.NODE ] = true; 18567 needDraw[ r.DRAG ] = true; 18568 18569 r.redraw(); 18570 }, motionBlurDelay ); 18571 } 18572 18573 r.drawingImage = false; 18574 18575 18576 if( !forcedContext && !r.initrender ){ 18577 r.initrender = true; 18578 cy.trigger( 'initrender' ); 18579 } 18580 18581 if( !forcedContext ){ 18582 cy.trigger('render'); 18583 } 18584 18585}; 18586 18587module.exports = CRp; 18588 18589},{"../../../util":100}],71:[function(_dereq_,module,exports){ 18590'use strict'; 18591 18592var math = _dereq_( '../../../math' ); 18593 18594var CRp = {}; 18595 18596// @O Polygon drawing 18597CRp.drawPolygonPath = function( 18598 context, x, y, width, height, points ){ 18599 18600 var halfW = width / 2; 18601 var halfH = height / 2; 18602 18603 if( context.beginPath ){ context.beginPath(); } 18604 18605 context.moveTo( x + halfW * points[0], y + halfH * points[1] ); 18606 18607 for( var i = 1; i < points.length / 2; i++ ){ 18608 context.lineTo( x + halfW * points[ i * 2], y + halfH * points[ i * 2 + 1] ); 18609 } 18610 18611 context.closePath(); 18612}; 18613 18614// Round rectangle drawing 18615CRp.drawRoundRectanglePath = function( 18616 context, x, y, width, height ){ 18617 18618 var halfWidth = width / 2; 18619 var halfHeight = height / 2; 18620 var cornerRadius = math.getRoundRectangleRadius( width, height ); 18621 18622 if( context.beginPath ){ context.beginPath(); } 18623 18624 // Start at top middle 18625 context.moveTo( x, y - halfHeight ); 18626 // Arc from middle top to right side 18627 context.arcTo( x + halfWidth, y - halfHeight, x + halfWidth, y, cornerRadius ); 18628 // Arc from right side to bottom 18629 context.arcTo( x + halfWidth, y + halfHeight, x, y + halfHeight, cornerRadius ); 18630 // Arc from bottom to left side 18631 context.arcTo( x - halfWidth, y + halfHeight, x - halfWidth, y, cornerRadius ); 18632 // Arc from left side to topBorder 18633 context.arcTo( x - halfWidth, y - halfHeight, x, y - halfHeight, cornerRadius ); 18634 // Join line 18635 context.lineTo( x, y - halfHeight ); 18636 18637 18638 context.closePath(); 18639}; 18640 18641var sin0 = Math.sin( 0 ); 18642var cos0 = Math.cos( 0 ); 18643 18644var sin = {}; 18645var cos = {}; 18646 18647var ellipseStepSize = Math.PI / 40; 18648 18649for( var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize ){ 18650 sin[ i ] = Math.sin( i ); 18651 cos[ i ] = Math.cos( i ); 18652} 18653 18654CRp.drawEllipsePath = function( context, centerX, centerY, width, height ){ 18655 if( context.beginPath ){ context.beginPath(); } 18656 18657 if( context.ellipse ){ 18658 context.ellipse( centerX, centerY, width / 2, height / 2, 0, 0, 2 * Math.PI ); 18659 } else { 18660 var xPos, yPos; 18661 var rw = width / 2; 18662 var rh = height / 2; 18663 for( var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize ){ 18664 xPos = centerX - (rw * sin[ i ]) * sin0 + (rw * cos[ i ]) * cos0; 18665 yPos = centerY + (rh * cos[ i ]) * sin0 + (rh * sin[ i ]) * cos0; 18666 18667 if( i === 0 ){ 18668 context.moveTo( xPos, yPos ); 18669 } else { 18670 context.lineTo( xPos, yPos ); 18671 } 18672 } 18673 } 18674 18675 context.closePath(); 18676 }; 18677 18678module.exports = CRp; 18679 18680},{"../../../math":85}],72:[function(_dereq_,module,exports){ 18681'use strict'; 18682 18683var math = _dereq_( '../../../math' ); 18684var util = _dereq_( '../../../util' ); 18685var Heap = _dereq_( '../../../heap' ); 18686var defs = _dereq_( './texture-cache-defs' ); 18687 18688var minTxrH = 25; // the size of the texture cache for small height eles (special case) 18689var txrStepH = 50; // the min size of the regular cache, and the size it increases with each step up 18690var minLvl = -4; // when scaling smaller than that we don't need to re-render 18691var maxLvl = 2; // when larger than this scale just render directly (caching is not helpful) 18692var maxZoom = 3.99; // beyond this zoom level, layered textures are not used 18693var eleTxrSpacing = 8; // spacing between elements on textures to avoid blitting overlaps 18694var defTxrWidth = 1024; // default/minimum texture width 18695var maxTxrW = 1024; // the maximum width of a texture 18696var maxTxrH = 1024; // the maximum height of a texture 18697var minUtility = 0.5; // if usage of texture is less than this, it is retired 18698var maxFullness = 0.8; // fullness of texture after which queue removal is checked 18699var maxFullnessChecks = 10; // dequeued after this many checks 18700var allowEdgeTxrCaching = false; // whether edges can be cached as textures (TODO maybe better on if webgl supported?) 18701var allowParentTxrCaching = false; // whether parent nodes can be cached as textures (TODO maybe better on if webgl supported?) 18702var deqCost = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame 18703var deqAvgCost = 0.1; // % of add'l rendering cost compared to average overall redraw time 18704var deqNoDrawCost = 0.9; // % of avg frame time that can be used for dequeueing when not drawing 18705var deqFastCost = 0.9; // % of frame time to be used when >60fps 18706var deqRedrawThreshold = 100; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile 18707var maxDeqSize = 1; // number of eles to dequeue and render at higher texture in each batch 18708 18709var getTxrReasons = { 18710 dequeue: 'dequeue', 18711 downscale: 'downscale', 18712 highQuality: 'highQuality' 18713}; 18714 18715var ElementTextureCache = function( renderer ){ 18716 var self = this; 18717 18718 self.renderer = renderer; 18719 self.onDequeues = []; 18720 18721 self.setupDequeueing(); 18722}; 18723 18724var ETCp = ElementTextureCache.prototype; 18725 18726ETCp.reasons = getTxrReasons; 18727 18728// the list of textures in which new subtextures for elements can be placed 18729ETCp.getTextureQueue = function( txrH ){ 18730 var self = this; 18731 self.eleImgCaches = self.eleImgCaches || {}; 18732 18733 return ( self.eleImgCaches[ txrH ] = self.eleImgCaches[ txrH ] || [] ); 18734}; 18735 18736// the list of usused textures which can be recycled (in use in texture queue) 18737ETCp.getRetiredTextureQueue = function( txrH ){ 18738 var self = this; 18739 18740 var rtxtrQs = self.eleImgCaches.retired = self.eleImgCaches.retired || {}; 18741 var rtxtrQ = rtxtrQs[ txrH ] = rtxtrQs[ txrH ] || []; 18742 18743 return rtxtrQ; 18744}; 18745 18746// queue of element draw requests at different scale levels 18747ETCp.getElementQueue = function(){ 18748 var self = this; 18749 18750 var q = self.eleCacheQueue = self.eleCacheQueue || new Heap(function( a, b ){ 18751 return b.reqs - a.reqs; 18752 }); 18753 18754 return q; 18755}; 18756 18757// queue of element draw requests at different scale levels (element id lookup) 18758ETCp.getElementIdToQueue = function(){ 18759 var self = this; 18760 18761 var id2q = self.eleIdToCacheQueue = self.eleIdToCacheQueue || {}; 18762 18763 return id2q; 18764}; 18765 18766ETCp.getElement = function( ele, bb, pxRatio, lvl, reason ){ 18767 var self = this; 18768 var r = this.renderer; 18769 var rs = ele._private.rscratch; 18770 var zoom = r.cy.zoom(); 18771 18772 if( bb.w === 0 || bb.h === 0 ){ return null; } 18773 18774 if( lvl == null ){
18775 lvl = Math.ceil( math.log2( zoom * pxRatio ) ); 18776 } 18777 18778 if( lvl < minLvl ){ 18779 lvl = minLvl; 18780 } else if( zoom >= maxZoom || lvl > maxLvl ){ 18781 return null; 18782 } 18783 18784 var scale = Math.pow( 2, lvl ); 18785 var eleScaledH = bb.h * scale; 18786 var eleScaledW = bb.w * scale; 18787 var caches = rs.imgCaches = rs.imgCaches || {}; 18788 var eleCache = caches[lvl]; 18789 18790 if( eleCache ){ 18791 return eleCache; 18792 } 18793 18794 var txrH; // which texture height this ele belongs to 18795 18796 if( eleScaledH <= minTxrH ){ 18797 txrH = minTxrH; 18798 } else if( eleScaledH <= txrStepH ){ 18799 txrH = txrStepH; 18800 } else { 18801 txrH = Math.ceil( eleScaledH / txrStepH ) * txrStepH; 18802 } 18803 18804 if( 18805 eleScaledH > maxTxrH 18806 || eleScaledW > maxTxrW 18807 || ( !allowEdgeTxrCaching && ele.isEdge() ) 18808 || ( !allowParentTxrCaching && ele.isParent() ) 18809 ){ 18810 return null; // caching large elements is not efficient 18811 } 18812 18813 var txrQ = self.getTextureQueue( txrH ); 18814 18815 // first try the second last one in case it has space at the end 18816 var txr = txrQ[ txrQ.length - 2 ]; 18817 18818 var addNewTxr = function(){ 18819 return self.recycleTexture( txrH, eleScaledW ) || self.addTexture( txrH, eleScaledW ); 18820 }; 18821 18822 // try the last one if there is no second last one 18823 if( !txr ){ 18824 txr = txrQ[ txrQ.length - 1 ]; 18825 } 18826 18827 // if the last one doesn't exist, we need a first one 18828 if( !txr ){ 18829 txr = addNewTxr(); 18830 } 18831 18832 // if there's no room in the current texture, we need a new one 18833 if( txr.width - txr.usedWidth < eleScaledW ){ 18834 txr = addNewTxr(); 18835 } 18836 18837 var scaledLabelShown = r.eleTextBiggerThanMin( ele, scale ); 18838 var scalableFrom = function( otherCache ){ 18839 return otherCache && otherCache.scaledLabelShown === scaledLabelShown; 18840 }; 18841 18842 var deqing = reason && reason === getTxrReasons.dequeue; 18843 var highQualityReq = reason && reason === getTxrReasons.highQuality; 18844 var downscaleReq = reason && reason === getTxrReasons.downscale; 18845 18846 var higherCache; // the nearest cache with a higher level 18847 for( var l = lvl + 1; l <= maxLvl; l++ ){ 18848 var c = caches[l]; 18849 18850 if( c ){ higherCache = c; break; } 18851 } 18852 18853 var oneUpCache = higherCache && higherCache.level === lvl + 1 ? higherCache : null; 18854 18855 var downscale = function(){ 18856 txr.context.drawImage( 18857 oneUpCache.texture.canvas, 18858 oneUpCache.x, 0, 18859 oneUpCache.width, oneUpCache.height, 18860 txr.usedWidth, 0, 18861 eleScaledW, eleScaledH 18862 ); 18863 }; 18864 18865 if( scalableFrom(oneUpCache) ){ 18866 // then we can relatively cheaply rescale the existing image w/o rerendering 18867 downscale(); 18868 18869 } else if( scalableFrom(higherCache) ){ 18870 // then use the higher cache for now and queue the next level down 18871 // to cheaply scale towards the smaller level 18872 18873 if( highQualityReq ){ 18874 for( var l = higherCache.level; l > lvl; l-- ){ 18875 oneUpCache = self.getElement( ele, bb, pxRatio, l, getTxrReasons.downscale ); 18876 } 18877 18878 downscale(); 18879 18880 } else { 18881 self.queueElement( ele, bb, higherCache.level - 1 ); 18882 18883 return higherCache; 18884 } 18885 } else { 18886 18887 var lowerCache; // the nearest cache with a lower level 18888 if( !deqing && !highQualityReq && !downscaleReq ){ 18889 for( var l = lvl - 1; l >= minLvl; l-- ){ 18890 var c = caches[l]; 18891 18892 if( c ){ lowerCache = c; break; } 18893 } 18894 } 18895 18896 if( scalableFrom(lowerCache) ){ 18897 // then use the lower quality cache for now and queue the better one for later 18898 18899 self.queueElement( ele, bb, lvl ); 18900 18901 return lowerCache; 18902 } 18903 18904 txr.context.translate( txr.usedWidth, 0 ); 18905 txr.context.scale( scale, scale ); 18906 18907 r.drawElement( txr.context, ele, bb, scaledLabelShown ); 18908 18909 txr.context.scale( 1/scale, 1/scale ); 18910 txr.context.translate( -txr.usedWidth, 0 ); 18911 } 18912 18913 eleCache = caches[lvl] = { 18914 ele: ele, 18915 x: txr.usedWidth, 18916 texture: txr, 18917 level: lvl, 18918 scale: scale, 18919 width: eleScaledW, 18920 height: eleScaledH, 18921 scaledLabelShown: scaledLabelShown 18922 }; 18923 18924 txr.usedWidth += Math.ceil( eleScaledW + eleTxrSpacing ); 18925 18926 txr.eleCaches.push( eleCache ); 18927 18928 self.checkTextureFullness( txr ); 18929 18930 return eleCache; 18931}; 18932 18933ETCp.invalidateElement = function( ele ){ 18934 var self = this; 18935 var caches = ele._private.rscratch.imgCaches; 18936 18937 if( caches ){ 18938 for( var lvl = minLvl; lvl <= maxLvl; lvl++ ){ 18939 var cache = caches[ lvl ]; 18940 18941 if( cache ){ 18942 var txr = cache.texture; 18943 18944 // remove space from the texture it belongs to
18945 txr.invalidatedWidth += cache.width; 18946 18947 // remove refs with the element 18948 caches[ lvl ] = null; 18949 util.removeFromArray( txr.eleCaches, cache ); 18950 18951 // might have to remove the entire texture if it's not efficiently using its space 18952 self.checkTextureUtility( txr ); 18953 } 18954 } 18955 } 18956}; 18957 18958ETCp.checkTextureUtility = function( txr ){ 18959 // invalidate all entries in the cache if the cache size is small 18960 if( txr.invalidatedWidth >= minUtility * txr.width ){ 18961 this.retireTexture( txr ); 18962 } 18963}; 18964 18965ETCp.checkTextureFullness = function( txr ){ 18966 // if texture has been mostly filled and passed over several times, remove 18967 // it from the queue so we don't need to waste time looking at it to put new things 18968 18969 var self = this; 18970 var txrQ = self.getTextureQueue( txr.height ); 18971 18972 if( txr.usedWidth / txr.width > maxFullness && txr.fullnessChecks >= maxFullnessChecks ){ 18973 util.removeFromArray( txrQ, txr ); 18974 } else { 18975 txr.fullnessChecks++; 18976 } 18977}; 18978 18979ETCp.retireTexture = function( txr ){ 18980 var self = this; 18981 var txrH = txr.height; 18982 var txrQ = self.getTextureQueue( txrH ); 18983 18984 // retire the texture from the active / searchable queue: 18985 18986 util.removeFromArray( txrQ, txr ); 18987 18988 txr.retired = true; 18989 18990 // remove the refs from the eles to the caches: 18991 18992 var eleCaches = txr.eleCaches; 18993 18994 for( var i = 0; i < eleCaches.length; i++ ){ 18995 var eleCache = eleCaches[i]; 18996 var ele = eleCache.ele; 18997 var lvl = eleCache.level; 18998 var imgCaches = ele._private.rscratch.imgCaches; 18999 19000 if( imgCaches ){ 19001 imgCaches[ lvl ] = null; 19002 } 19003 } 19004 19005 util.clearArray( eleCaches ); 19006 19007 // add the texture to a retired queue so it can be recycled in future: 19008 19009 var rtxtrQ = self.getRetiredTextureQueue( txrH ); 19010 19011 rtxtrQ.push( txr ); 19012}; 19013 19014ETCp.addTexture = function( txrH, minW ){ 19015 var self = this; 19016 var txrQ = self.getTextureQueue( txrH ); 19017 var txr = {}; 19018 19019 txrQ.push( txr ); 19020 19021 txr.eleCaches = []; 19022 19023 txr.height = txrH; 19024 txr.width = Math.max( defTxrWidth, minW ); 19025 txr.usedWidth = 0; 19026 txr.invalidatedWidth = 0; 19027 txr.fullnessChecks = 0; 19028 19029 txr.canvas = document.createElement('canvas'); // eslint-disable-line no-undef 19030 txr.canvas.width = txr.width; 19031 txr.canvas.height = txr.height; 19032 19033 txr.context = txr.canvas.getContext('2d'); 19034 19035 return txr; 19036}; 19037 19038ETCp.recycleTexture = function( txrH, minW ){ 19039 var self = this; 19040 var txrQ = self.getTextureQueue( txrH ); 19041 var rtxtrQ = self.getRetiredTextureQueue( txrH ); 19042 19043 for( var i = 0; i < rtxtrQ.length; i++ ){ 19044 var txr = rtxtrQ[i]; 19045 19046 if( txr.width >= minW ){ 19047 txr.retired = false; 19048 19049 txr.usedWidth = 0; 19050 txr.invalidatedWidth = 0; 19051 txr.fullnessChecks = 0; 19052 19053 util.clearArray( txr.eleCaches ); 19054 19055 txr.context.clearRect( 0, 0, txr.width, txr.height ); 19056 19057 util.removeFromArray( rtxtrQ, txr ); 19058 txrQ.push( txr ); 19059 19060 return txr; 19061 } 19062 } 19063}; 19064 19065ETCp.queueElement = function( ele, bb, lvl ){ 19066 var self = this; 19067 var q = self.getElementQueue(); 19068 var id2q = self.getElementIdToQueue(); 19069 var id = ele.id(); 19070 var existingReq = id2q[ id ]; 19071 19072 if( existingReq ){ // use the max lvl b/c in between lvls are cheap to make 19073 existingReq.level = Math.max( existingReq.level, lvl ); 19074 existingReq.reqs++; 19075 19076 q.updateItem( existingReq ); 19077 } else { 19078 var req = { 19079 ele: ele, 19080 bb: bb, 19081 position: math.copyPosition( ele.position() ), 19082 level: lvl, 19083 reqs: 1 19084 }; 19085 19086 if( ele.isEdge() ){ 19087 req.positions = { 19088 source: math.copyPosition( ele.source().position() ), 19089 target: math.copyPosition( ele.target().position() ) 19090 }; 19091 } 19092 19093 q.push( req ); 19094 19095 id2q[ id ] = req; 19096 } 19097}; 19098 19099ETCp.dequeue = function( pxRatio, extent ){ 19100 var self = this; 19101 var q = self.getElementQueue(); 19102 var id2q = self.getElementIdToQueue(); 19103 var dequeued = []; 19104 19105 for( var i = 0; i < maxDeqSize; i++ ){ 19106 if( q.size() > 0 ){ 19107 var req = q.pop(); 19108 19109 id2q[ req.ele.id() ] = null; 19110 19111 dequeued.push( req ); 19112 19113 var ele = req.ele; 19114 var bb; 19115 19116 if( 19117 ( ele.isEdge() 19118 && ( 19119 !math.arePositionsSame( ele.source().position(), req.positions.source ) 19120 || !math.arePositionsSame( ele.target().position(), req.positions.target ) 19121 ) 19122 ) 19123 || ( !math.arePositionsSame( ele.position(), req.position ) ) 19124 ){ 19125 bb = ele.boundingBox(); 19126 } else { 19127 bb = req.bb; 19128 } 19129 19130 self.getElement( req.ele, bb, pxRatio, req.level, getTxrReasons.dequeue ); 19131 } else { 19132 break; 19133 } 19134 } 19135 19136 return dequeued; 19137}; 19138 19139ETCp.onDequeue = function( fn ){ this.onDequeues.push( fn ); }; 19140ETCp.offDequeue = function( fn ){ util.removeFromArray( this.onDequeues, fn ); }; 19141 19142ETCp.setupDequeueing = defs.setupDequeueing({ 19143 deqRedrawThreshold: deqRedrawThreshold, 19144 deqCost: deqCost, 19145 deqAvgCost: deqAvgCost, 19146 deqNoDrawCost: deqNoDrawCost, 19147 deqFastCost: deqFastCost, 19148 deq: function( self, pxRatio, extent ){ 19149 return self.dequeue( pxRatio, extent ); 19150 }, 19151 onDeqd: function( self, deqd ){ 19152 for( var i = 0; i < self.onDequeues.length; i++ ){ 19153 var fn = self.onDequeues[i]; 19154 19155 fn( deqd ); 19156 } 19157 }, 19158 shouldRedraw: function( self, deqd, pxRatio, extent ){ 19159 for( var i = 0; i < deqd.length; i++ ){ 19160 var bb = deqd[i].bb; 19161 19162 if( math.boundingBoxesIntersect( bb, extent ) ){ 19163 return true; 19164 } 19165 } 19166 19167 return false;
19168 }, 19169 priority: function( self ){ 19170 return self.renderer.beforeRenderPriorities.eleTxrDeq; 19171 } 19172}); 19173 19174module.exports = ElementTextureCache; 19175 19176},{"../../../heap":81,"../../../math":85,"../../../util":100,"./texture-cache-defs":77}],73:[function(_dereq_,module,exports){ 19177'use strict'; 19178 19179var is = _dereq_( '../../../is' ); 19180 19181var CRp = {}; 19182 19183CRp.createBuffer = function( w, h ){ 19184 var buffer = document.createElement( 'canvas' ); // eslint-disable-line no-undef 19185 buffer.width = w; 19186 buffer.height = h; 19187 19188 return [ buffer, buffer.getContext( '2d' ) ]; 19189}; 19190 19191CRp.bufferCanvasImage = function( options ){ 19192 var cy = this.cy; 19193 var eles = cy.mutableElements(); 19194 var bb = eles.boundingBox(); 19195 var width = options.full ? Math.ceil( bb.w ) : this.container.clientWidth; 19196 var height = options.full ? Math.ceil( bb.h ) : this.container.clientHeight; 19197 var specdMaxDims = is.number( options.maxWidth ) || is.number( options.maxHeight ); 19198 var pxRatio = this.getPixelRatio(); 19199 var scale = 1; 19200 19201 if( options.scale !== undefined ){ 19202 width *= options.scale; 19203 height *= options.scale; 19204 19205 scale = options.scale; 19206 } else if( specdMaxDims ){ 19207 var maxScaleW = Infinity; 19208 var maxScaleH = Infinity; 19209 19210 if( is.number( options.maxWidth ) ){ 19211 maxScaleW = scale * options.maxWidth / width; 19212 } 19213 19214 if( is.number( options.maxHeight ) ){ 19215 maxScaleH = scale * options.maxHeight / height; 19216 } 19217 19218 scale = Math.min( maxScaleW, maxScaleH ); 19219 19220 width *= scale; 19221 height *= scale; 19222 } 19223 19224 if( !specdMaxDims ){ 19225 width *= pxRatio; 19226 height *= pxRatio; 19227 scale *= pxRatio; 19228 } 19229 19230 var buffCanvas = document.createElement( 'canvas' ); // eslint-disable-line no-undef 19231 19232 buffCanvas.width = width; 19233 buffCanvas.height = height; 19234 19235 buffCanvas.style.width = width + 'px'; 19236 buffCanvas.style.height = height + 'px'; 19237 19238 var buffCxt = buffCanvas.getContext( '2d' ); 19239 19240 // Rasterize the layers, but only if container has nonzero size 19241 if( width > 0 && height > 0 ){ 19242 19243 buffCxt.clearRect( 0, 0, width, height ); 19244 19245 if( options.bg ){ 19246 buffCxt.fillStyle = options.bg; 19247 buffCxt.rect( 0, 0, width, height ); 19248 buffCxt.fill(); 19249 } 19250 19251 buffCxt.globalCompositeOperation = 'source-over'; 19252 19253 var zsortedEles = this.getCachedZSortedEles(); 19254 19255 if( options.full ){ // draw the full bounds of the graph 19256 buffCxt.translate( -bb.x1 * scale, -bb.y1 * scale ); 19257 buffCxt.scale( scale, scale ); 19258 19259 this.drawElements( buffCxt, zsortedEles ); 19260 } else { // draw the current view 19261 var pan = cy.pan(); 19262 19263 var translation = { 19264 x: pan.x * scale, 19265 y: pan.y * scale 19266 }; 19267 19268 scale *= cy.zoom(); 19269 19270 buffCxt.translate( translation.x, translation.y ); 19271 buffCxt.scale( scale, scale ); 19272 19273 this.drawElements( buffCxt, zsortedEles ); 19274 } 19275 } 19276 19277 return buffCanvas; 19278}; 19279 19280CRp.png = function( options ){ 19281 return this.bufferCanvasImage( options ).toDataURL( 'image/png' ); 19282}; 19283 19284CRp.jpg = function( options ){ 19285 return this.bufferCanvasImage( options ).toDataURL( 'image/jpeg' ); 19286}; 19287 19288module.exports = CRp; 19289 19290},{"../../../is":83}],74:[function(_dereq_,module,exports){ 19291/* 19292The canvas renderer was written by Yue Dong. 19293 19294Modifications tracked on Github. 19295*/ 19296 19297'use strict'; 19298 19299var util = _dereq_( '../../../util' ); 19300var is = _dereq_( '../../../is' ); 19301var ElementTextureCache = _dereq_('./ele-texture-cache'); 19302var LayeredTextureCache = _dereq_('./layered-texture-cache'); 19303 19304var CR = CanvasRenderer; 19305var CRp = CanvasRenderer.prototype; 19306 19307CRp.CANVAS_LAYERS = 3; 19308// 19309CRp.SELECT_BOX = 0; 19310CRp.DRAG = 1; 19311CRp.NODE = 2; 19312 19313CRp.BUFFER_COUNT = 3; 19314// 19315CRp.TEXTURE_BUFFER = 0; 19316CRp.MOTIONBLUR_BUFFER_NODE = 1; 19317CRp.MOTIONBLUR_BUFFER_DRAG = 2; 19318 19319function CanvasRenderer( options ){ 19320 var r = this; 19321 19322 r.data = { 19323 canvases: new Array( CRp.CANVAS_LAYERS ), 19324 contexts: new Array( CRp.CANVAS_LAYERS ), 19325 canvasNeedsRedraw: new Array( CRp.CANVAS_LAYERS ), 19326 19327 bufferCanvases: new Array( CRp.BUFFER_COUNT ), 19328 bufferContexts: new Array( CRp.CANVAS_LAYERS ), 19329 }; 19330 19331 r.data.canvasContainer = document.createElement( 'div' ); // eslint-disable-line no-undef 19332 var containerStyle = r.data.canvasContainer.style; 19333 r.data.canvasContainer.setAttribute( 'style', '-webkit-tap-highlight-color: rgba(0,0,0,0);' ); 19334 containerStyle.position = 'relative'; 19335 containerStyle.zIndex = '0'; 19336 containerStyle.overflow = 'hidden'; 19337 19338 var container = options.cy.container(); 19339 container.appendChild( r.data.canvasContainer ); 19340 container.setAttribute( 'style', ( container.getAttribute( 'style' ) || '' ) + '-webkit-tap-highlight-color: rgba(0,0,0,0);' ); 19341 19342 for( var i = 0; i < CRp.CANVAS_LAYERS; i++ ){
19343 var canvas = r.data.canvases[ i ] = document.createElement( 'canvas' ); // eslint-disable-line no-undef 19344 r.data.contexts[ i ] = canvas.getContext( '2d' ); 19345 canvas.setAttribute( 'style', '-webkit-user-select: none; -moz-user-select: -moz-none; user-select: none; -webkit-tap-highlight-color: rgba(0,0,0,0); outline-style: none;' + ( is.ms() ? ' -ms-touch-action: none; touch-action: none; ' : '' ) ); 19346 canvas.style.position = 'absolute'; 19347 canvas.setAttribute( 'data-id', 'layer' + i ); 19348 canvas.style.zIndex = String( CRp.CANVAS_LAYERS - i ); 19349 r.data.canvasContainer.appendChild( canvas ); 19350 19351 r.data.canvasNeedsRedraw[ i ] = false; 19352 } 19353 r.data.topCanvas = r.data.canvases[0]; 19354 19355 r.data.canvases[ CRp.NODE ].setAttribute( 'data-id', 'layer' + CRp.NODE + '-node' ); 19356 r.data.canvases[ CRp.SELECT_BOX ].setAttribute( 'data-id', 'layer' + CRp.SELECT_BOX + '-selectbox' ); 19357 r.data.canvases[ CRp.DRAG ].setAttribute( 'data-id', 'layer' + CRp.DRAG + '-drag' ); 19358 19359 for( var i = 0; i < CRp.BUFFER_COUNT; i++ ){ 19360 r.data.bufferCanvases[ i ] = document.createElement( 'canvas' ); // eslint-disable-line no-undef 19361 r.data.bufferContexts[ i ] = r.data.bufferCanvases[ i ].getContext( '2d' ); 19362 r.data.bufferCanvases[ i ].style.position = 'absolute'; 19363 r.data.bufferCanvases[ i ].setAttribute( 'data-id', 'buffer' + i ); 19364 r.data.bufferCanvases[ i ].style.zIndex = String( -i - 1 ); 19365 r.data.bufferCanvases[ i ].style.visibility = 'hidden'; 19366 //r.data.canvasContainer.appendChild(r.data.bufferCanvases[i]); 19367 } 19368 19369 r.pathsEnabled = true; 19370 19371 r.data.eleTxrCache = new ElementTextureCache( r ); 19372 r.data.lyrTxrCache = new LayeredTextureCache( r, r.data.eleTxrCache ); 19373
19374 r.onUpdateEleCalcs(function invalidateTextureCaches( willDraw, eles ){ 19375 for( var i = 0; i < eles.length; i++ ){ 19376 var ele = eles[i]; 19377 var rs = ele._private.rstyle; 19378 var de = rs.dirtyEvents; 19379 19380 if( ele.isNode() && de && de.length === 1 && de['position'] ){ 19381 // then keep cached ele texture 19382 } else { 19383 r.data.eleTxrCache.invalidateElement( ele ); 19384 } 19385 } 19386 19387 if( eles.length > 0 ){ 19388 r.data.lyrTxrCache.invalidateElements( eles ); 19389 } 19390 }); 19391} 19392 19393CRp.redrawHint = function( group, bool ){ 19394 var r = this; 19395 19396 switch( group ){ 19397 case 'eles': 19398 r.data.canvasNeedsRedraw[ CRp.NODE ] = bool; 19399 break; 19400 case 'drag': 19401 r.data.canvasNeedsRedraw[ CRp.DRAG ] = bool; 19402 break; 19403 case 'select': 19404 r.data.canvasNeedsRedraw[ CRp.SELECT_BOX ] = bool; 19405 break; 19406 } 19407}; 19408 19409// whether to use Path2D caching for drawing 19410var pathsImpld = typeof Path2D !== 'undefined'; 19411 19412CRp.path2dEnabled = function( on ){ 19413 if( on === undefined ){ 19414 return this.pathsEnabled; 19415 } 19416 19417 this.pathsEnabled = on ? true : false; 19418}; 19419 19420CRp.usePaths = function(){ 19421 return pathsImpld && this.pathsEnabled; 19422}; 19423 19424[ 19425 _dereq_( './arrow-shapes' ), 19426 _dereq_( './drawing-elements' ), 19427 _dereq_( './drawing-edges' ), 19428 _dereq_( './drawing-images' ), 19429 _dereq_( './drawing-label-text' ), 19430 _dereq_( './drawing-nodes' ), 19431 _dereq_( './drawing-redraw' ), 19432 _dereq_( './drawing-shapes' ), 19433 _dereq_( './export-image' ), 19434 _dereq_( './node-shapes' ) 19435].forEach( function( props ){ 19436 util.extend( CRp, props ); 19437} ); 19438 19439module.exports = CR; 19440 19441},{"../../../is":83,"../../../util":100,"./arrow-shapes":64,"./drawing-edges":65,"./drawing-elements":66,"./drawing-images":67,"./drawing-label-text":68,"./drawing-nodes":69,"./drawing-redraw":70,"./drawing-shapes":71,"./ele-texture-cache":72,"./export-image":73,"./layered-texture-cache":75,"./node-shapes":76}],75:[function(_dereq_,module,exports){ 19442'use strict'; 19443 19444var util = _dereq_( '../../../util' ); 19445var math = _dereq_( '../../../math' ); 19446var Heap = _dereq_( '../../../heap' ); 19447var is = _dereq_( '../../../is' ); 19448var defs = _dereq_( './texture-cache-defs' ); 19449 19450var defNumLayers = 1; // default number of layers to use 19451var minLvl = -4; // when scaling smaller than that we don't need to re-render 19452var maxLvl = 2; // when larger than this scale just render directly (caching is not helpful) 19453var maxZoom = 3.99; // beyond this zoom level, layered textures are not used 19454var deqRedrawThreshold = 50; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile 19455var refineEleDebounceTime = 50; // time to debounce sharper ele texture updates 19456var disableEleImgSmoothing = true; // when drawing eles on layers from an ele cache ; crisper and more performant when true 19457var deqCost = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame 19458var deqAvgCost = 0.1; // % of add'l rendering cost compared to average overall redraw time 19459var deqNoDrawCost = 0.9; // % of avg frame time that can be used for dequeueing when not drawing 19460var deqFastCost = 0.9; // % of frame time to be used when >60fps 19461var maxDeqSize = 1; // number of eles to dequeue and render at higher texture in each batch 19462var invalidThreshold = 250; // time threshold for disabling b/c of invalidations 19463var maxLayerArea = 4000 * 4000; // layers can't be bigger than this 19464var alwaysQueue = true; // never draw all the layers in a level on a frame; draw directly until all dequeued 19465var useHighQualityEleTxrReqs = true; // whether to use high quality ele txr requests (generally faster and cheaper in the longterm) 19466 19467var useEleTxrCaching = true; // whether to use individual ele texture caching underneath this cache 19468 19469// var log = function(){ console.log.apply( console, arguments ); }; 19470 19471var LayeredTextureCache = function( renderer, eleTxrCache ){ 19472 var self = this; 19473 19474 var r = self.renderer = renderer; 19475 19476 self.layersByLevel = {}; // e.g. 2 => [ layer1, layer2, ..., layerN ] 19477 19478 self.firstGet = true; 19479 19480 self.lastInvalidationTime = util.performanceNow() - 2*invalidThreshold; 19481 19482 self.skipping = false; 19483 19484 r.beforeRender(function( willDraw, now ){
19485 if( now - self.lastInvalidationTime <= invalidThreshold ){ 19486 self.skipping = true; 19487 } else { 19488 self.skipping = false; 19489 } 19490 }); 19491 19492 var qSort = function(a, b){ 19493 return b.reqs - a.reqs; 19494 }; 19495 19496 self.layersQueue = new Heap( qSort ); 19497 19498 self.eleTxrCache = eleTxrCache; 19499 19500 self.setupEleCacheInvalidation(); 19501 19502 self.setupDequeueing(); 19503}; 19504 19505var LTCp = LayeredTextureCache.prototype; 19506 19507var layerIdPool = 0; 19508var MAX_INT = Math.pow(2, 53) - 1; 19509 19510LTCp.makeLayer = function( bb, lvl ){ 19511 var scale = Math.pow( 2, lvl ); 19512 19513 var w = Math.ceil( bb.w * scale ); 19514 var h = Math.ceil( bb.h * scale ); 19515 19516 var canvas = document.createElement('canvas'); // eslint-disable-line no-undef 19517 19518 canvas.width = w; 19519 canvas.height = h; 19520 19521 var layer = { 19522 id: (layerIdPool = ++layerIdPool % MAX_INT ), 19523 bb: bb, 19524 level: lvl, 19525 width: w, 19526 height: h, 19527 canvas: canvas, 19528 context: canvas.getContext('2d'), 19529 eles: [], 19530 elesQueue: [], 19531 reqs: 0 19532 }; 19533 19534 // log('make layer %s with w %s and h %s and lvl %s', layer.id, layer.width, layer.height, layer.level); 19535 19536 var cxt = layer.context; 19537 var dx = -layer.bb.x1; 19538 var dy = -layer.bb.y1; 19539 19540 // do the transform on creation to save cycles (it's the same for all eles) 19541 cxt.scale( scale, scale ); 19542 cxt.translate( dx, dy ); 19543 19544 return layer; 19545}; 19546 19547LTCp.getLayers = function( eles, pxRatio, lvl ){ 19548 var self = this; 19549 var r = self.renderer; 19550 var cy = r.cy; 19551 var zoom = cy.zoom(); 19552 var firstGet = self.firstGet; 19553 19554 self.firstGet = false;
19555 19556 // log('--\nget layers with %s eles', eles.length); 19557 //log eles.map(function(ele){ return ele.id() }) ); 19558 19559 if( lvl == null ){ 19560 lvl = Math.ceil( math.log2( zoom * pxRatio ) ); 19561 19562 if( lvl < minLvl ){ 19563 lvl = minLvl; 19564 } else if( zoom >= maxZoom || lvl > maxLvl ){ 19565 return null; 19566 } 19567 } 19568 19569 self.validateLayersElesOrdering( lvl, eles ); 19570 19571 var layersByLvl = self.layersByLevel; 19572 var scale = Math.pow( 2, lvl ); 19573 var layers = layersByLvl[ lvl ] = layersByLvl[ lvl ] || []; 19574 var bb; 19575 19576 var lvlComplete = self.levelIsComplete( lvl, eles ); 19577 var tmpLayers; 19578 19579 var checkTempLevels = function(){ 19580 var canUseAsTmpLvl = function( l ){ 19581 self.validateLayersElesOrdering( l, eles ); 19582 19583 if( self.levelIsComplete( l, eles ) ){ 19584 tmpLayers = layersByLvl[l]; 19585 return true; 19586 } 19587 }; 19588 19589 var checkLvls = function( dir ){ 19590 if( tmpLayers ){ return; } 19591 19592 for( var l = lvl + dir; minLvl <= l && l <= maxLvl; l += dir ){ 19593 if( canUseAsTmpLvl(l) ){ break; } 19594 } 19595 }; 19596 19597 checkLvls( +1 ); 19598 checkLvls( -1 ); 19599 19600 // remove the invalid layers; they will be replaced as needed later in this function 19601 for( var i = layers.length - 1; i >= 0; i-- ){ 19602 var layer = layers[i]; 19603 19604 if( layer.invalid ){ 19605 util.removeFromArray( layers, layer ); 19606 } 19607 } 19608 }; 19609 19610 if( !lvlComplete ){ 19611 // if the current level is incomplete, then use the closest, best quality layerset temporarily 19612 // and later queue the current layerset so we can get the proper quality level soon 19613 19614 checkTempLevels(); 19615 19616 } else { 19617 // log('level complete, using existing layers\n--'); 19618 return layers; 19619 } 19620 19621 var getBb = function(){ 19622 if( !bb ){ 19623 bb = math.makeBoundingBox(); 19624 19625 for( var i = 0; i < eles.length; i++ ){ 19626 math.updateBoundingBox( bb, eles[i].boundingBox() ); 19627 } 19628 } 19629 19630 return bb; 19631 }; 19632 19633 var makeLayer = function( opts ){ 19634 opts = opts || {}; 19635 19636 var after = opts.after; 19637 19638 getBb(); 19639 19640 var area = ( bb.w * scale ) * ( bb.h * scale ); 19641 19642 if( area > maxLayerArea ){ 19643 return null; 19644 } 19645 19646 var layer = self.makeLayer( bb, lvl ); 19647 19648 if( after != null ){ 19649 var index = layers.indexOf( after ) + 1; 19650 19651 layers.splice( index, 0, layer ); 19652 } else if( opts.insert === undefined || opts.insert ){ 19653 // no after specified => first layer made so put at start 19654 layers.unshift( layer ); 19655 } 19656 19657 // if( tmpLayers ){ 19658 //self.queueLayer( layer ); 19659 // } 19660 19661 return layer; 19662 }; 19663 19664 if( self.skipping && !firstGet ){ 19665 // log('skip layers'); 19666 return null; 19667 } 19668 19669 // log('do layers'); 19670 19671 var layer = null; 19672 var maxElesPerLayer = eles.length / defNumLayers; 19673 var allowLazyQueueing = alwaysQueue && !firstGet; 19674 19675 for( var i = 0; i < eles.length; i++ ){ 19676 var ele = eles[i]; 19677 var rs = ele._private.rscratch; 19678 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {}; 19679 19680 // log('look at ele', ele.id()); 19681 19682 var existingLayer = caches[ lvl ]; 19683 19684 if( existingLayer ){ 19685 // reuse layer for later eles 19686 // log('reuse layer for', ele.id()); 19687 layer = existingLayer; 19688 continue; 19689 } 19690 19691 if( 19692 !layer 19693 || layer.eles.length >= maxElesPerLayer 19694 || !math.boundingBoxInBoundingBox( layer.bb, ele.boundingBox() ) 19695 ){ 19696 // log('make new layer for ele %s', ele.id()); 19697 19698 layer = makeLayer({ insert: true, after: layer }); 19699 19700 // if now layer can be built then we can't use layers at this level 19701 if( !layer ){ return null; } 19702 19703 // log('new layer with id %s', layer.id); 19704 } 19705 19706 if( tmpLayers || allowLazyQueueing ){ 19707 // log('queue ele %s in layer %s', ele.id(), layer.id); 19708 self.queueLayer( layer, ele ); 19709 } else { 19710 // log('draw ele %s in layer %s', ele.id(), layer.id); 19711 self.drawEleInLayer( layer, ele, lvl, pxRatio ); 19712 } 19713 19714 layer.eles.push( ele ); 19715 19716 caches[ lvl ] = layer; 19717 } 19718 19719 // log('--'); 19720 19721 if( tmpLayers ){ // then we only queued the current layerset and can't draw it yet 19722 return tmpLayers; 19723 } 19724 19725 if( allowLazyQueueing ){ 19726 // log('lazy queue level', lvl); 19727 return null; 19728 } 19729 19730 return layers; 19731}; 19732 19733// a layer may want to use an ele cache of a higher level to avoid blurriness 19734// so the layer level might not equal the ele level 19735LTCp.getEleLevelForLayerLevel = function( lvl, pxRatio ){ 19736 return lvl; 19737}; 19738 19739function imgSmoothing( context, bool ){
19740 if( context.imageSmoothingEnabled != null ){ 19741 context.imageSmoothingEnabled = bool; 19742 } else { 19743 context.webkitImageSmoothingEnabled = bool; 19744 context.mozImageSmoothingEnabled = bool; 19745 context.msImageSmoothingEnabled = bool; 19746 } 19747} 19748 19749LTCp.drawEleInLayer = function( layer, ele, lvl, pxRatio ){ 19750 var self = this; 19751 var r = this.renderer; 19752 var context = layer.context; 19753 var bb = ele.boundingBox(); 19754 19755 if( bb.w === 0 || bb.h === 0 ){ return; } 19756 19757 var eleCache = self.eleTxrCache; 19758 var reason = useHighQualityEleTxrReqs ? eleCache.reasons.highQuality : undefined; 19759 19760 lvl = self.getEleLevelForLayerLevel( lvl, pxRatio ); 19761 19762 var cache = useEleTxrCaching ? eleCache.getElement( ele, bb, null, lvl, reason ) : null; 19763 19764 if( cache ){ 19765 if( disableEleImgSmoothing ){ imgSmoothing( context, false ); } 19766 19767 context.drawImage( cache.texture.canvas, cache.x, 0, cache.width, cache.height, bb.x1, bb.y1, bb.w, bb.h ); 19768 19769 if( disableEleImgSmoothing ){ imgSmoothing( context, true ); } 19770 } else { // if the element is not cacheable, then draw directly 19771 r.drawElement( context, ele ); 19772 } 19773}; 19774 19775LTCp.levelIsComplete = function( lvl, eles ){ 19776 var self = this; 19777 var layers = self.layersByLevel[ lvl ]; 19778 19779 if( !layers || layers.length === 0 ){ return false; } 19780 19781 var numElesInLayers = 0; 19782 19783 for( var i = 0; i < layers.length; i++ ){ 19784 var layer = layers[i]; 19785 19786 // if there are any eles needed to be drawn yet, the level is not complete 19787 if( layer.reqs > 0 ){ return false; } 19788 19789 // if the layer is invalid, the level is not complete 19790 if( layer.invalid ){ return false; } 19791 19792 numElesInLayers += layer.eles.length; 19793 } 19794 19795 // we should have exactly the number of eles passed in to be complete 19796 if( numElesInLayers !== eles.length ){ return false; } 19797 19798 return true; 19799}; 19800 19801LTCp.validateLayersElesOrdering = function( lvl, eles ){ 19802 var layers = this.layersByLevel[ lvl ]; 19803 19804 if( !layers ){ return; } 19805 19806 // if in a layer the eles are not in the same order, then the layer is invalid 19807 // (i.e. there is an ele in between the eles in the layer) 19808 19809 for( var i = 0; i < layers.length; i++ ){ 19810 var layer = layers[i]; 19811 var offset = -1; 19812 19813 // find the offset 19814 for( var j = 0; j < eles.length; j++ ){ 19815 if( layer.eles[0] === eles[j] ){ 19816 offset = j; 19817 break; 19818 } 19819 } 19820 19821 if( offset < 0 ){
19822 // then the layer has nonexistant elements and is invalid 19823 this.invalidateLayer( layer ); 19824 continue; 19825 } 19826 19827 // the eles in the layer must be in the same continuous order, else the layer is invalid 19828 19829 var o = offset; 19830 19831 for( var j = 0; j < layer.eles.length; j++ ){ 19832 if( layer.eles[j] !== eles[o+j] ){ 19833 // log('invalidate based on ordering', layer.id); 19834 19835 this.invalidateLayer( layer ); 19836 break; 19837 } 19838 } 19839 } 19840}; 19841 19842LTCp.updateElementsInLayers = function( eles, update ){ 19843 var self = this; 19844 var isEles = is.element( eles[0] ); 19845 19846 // collect udpated elements (cascaded from the layers) and update each 19847 // layer itself along the way 19848 for( var i = 0; i < eles.length; i++ ){ 19849 var req = isEles ? null : eles[i]; 19850 var ele = isEles ? eles[i] : eles[i].ele; 19851 var rs = ele._private.rscratch; 19852 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {}; 19853 19854 for( var l = minLvl; l <= maxLvl; l++ ){ 19855 var layer = caches[l]; 19856 19857 if( !layer ){ continue; } 19858 19859 // if update is a request from the ele cache, then it affects only 19860 // the matching level 19861 if( req && self.getEleLevelForLayerLevel( layer.level ) !== req.level ){ 19862 continue; 19863 } 19864 19865 update( layer, ele, req ); 19866 } 19867 } 19868}; 19869 19870LTCp.haveLayers = function(){ 19871 var self = this; 19872 var haveLayers = false; 19873 19874 for( var l = minLvl; l <= maxLvl; l++ ){ 19875 var layers = self.layersByLevel[l]; 19876 19877 if( layers && layers.length > 0 ){ 19878 haveLayers = true; 19879 break; 19880 } 19881 } 19882 19883 return haveLayers; 19884}; 19885 19886LTCp.invalidateElements = function( eles ){ 19887 var self = this; 19888 19889 self.lastInvalidationTime = util.performanceNow(); 19890 19891 // log('update invalidate layer time from eles'); 19892 19893 if( eles.length === 0 || !self.haveLayers() ){ return; } 19894 19895 self.updateElementsInLayers( eles, function invalAssocLayers( layer, ele, req ){ 19896 self.invalidateLayer( layer ); 19897 } ); 19898}; 19899 19900LTCp.invalidateLayer = function( layer ){ 19901 // log('update invalidate layer time'); 19902 19903 this.lastInvalidationTime = util.performanceNow(); 19904 19905 if( layer.invalid ){ return; } // save cycles 19906 19907 var lvl = layer.level; 19908 var eles = layer.eles; 19909 var layers = this.layersByLevel[ lvl ]; 19910 19911 // log('invalidate layer', layer.id ); 19912 19913 util.removeFromArray( layers, layer ); 19914 // layer.eles = []; 19915 19916 layer.elesQueue = []; 19917 19918 layer.invalid = true; 19919 19920 if( layer.replacement ){ 19921 layer.replacement.invalid = true; 19922 } 19923 19924 for( var i = 0; i < eles.length; i++ ){ 19925 var caches = eles[i]._private.rscratch.imgLayerCaches; 19926 19927 if( caches ){ 19928 caches[ lvl ] = null; 19929 } 19930 } 19931}; 19932 19933LTCp.refineElementTextures = function( eles ){ 19934 var self = this; 19935 19936 // log('refine', eles.length); 19937 19938 self.updateElementsInLayers( eles, function refineEachEle( layer, ele, req ){ 19939 var rLyr = layer.replacement; 19940 19941 if( !rLyr ){ 19942 rLyr = layer.replacement = self.makeLayer( layer.bb, layer.level ); 19943 rLyr.replaces = layer; 19944 rLyr.eles = layer.eles; 19945 19946 // log('make replacement layer %s for %s with level %s', rLyr.id, layer.id, rLyr.level); 19947 } 19948 19949 if( !rLyr.reqs ){ 19950 for( var i = 0; i < rLyr.eles.length; i++ ){ 19951 self.queueLayer( rLyr, rLyr.eles[i] ); 19952 } 19953 19954 // log('queue replacement layer refinement', rLyr.id); 19955 } 19956 } ); 19957}; 19958 19959LTCp.setupEleCacheInvalidation = function(){ 19960 var self = this; 19961 var eleDeqs = []; 19962 19963 if( !useEleTxrCaching ){ return; } 19964 19965 var updatedElesInLayers = util.debounce( function(){ 19966 self.refineElementTextures( eleDeqs ); 19967 19968 eleDeqs = []; 19969 }, refineEleDebounceTime ); 19970 19971 self.eleTxrCache.onDequeue(function( reqs ){ 19972 for( var i = 0; i < reqs.length; i++ ){ 19973 eleDeqs.push( reqs[i] ); 19974 } 19975 19976 updatedElesInLayers(); 19977 }); 19978}; 19979 19980LTCp.queueLayer = function( layer, ele ){ 19981 var self = this; 19982 var q = self.layersQueue; 19983 var elesQ = layer.elesQueue; 19984 var hasId = elesQ.hasId = elesQ.hasId || {}; 19985 19986 // if a layer is going to be replaced, queuing is a waste of time 19987 if( layer.replacement ){ return; } 19988 19989 if( ele ){ 19990 if( hasId[ ele.id() ] ){ 19991 return; 19992 } 19993 19994 elesQ.push( ele ); 19995 hasId[ ele.id() ] = true; 19996 } 19997 19998 if( layer.reqs ){ 19999 layer.reqs++; 20000 20001 q.updateItem( layer ); 20002 } else { 20003 layer.reqs = 1; 20004 20005 q.push( layer ); 20006 } 20007}; 20008 20009LTCp.dequeue = function( pxRatio ){ 20010 var self = this; 20011 var q = self.layersQueue; 20012 var deqd = []; 20013 var eleDeqs = 0; 20014 20015 while( eleDeqs < maxDeqSize ){ 20016 if( q.size() === 0 ){ break; } 20017 20018 var layer = q.peek(); 20019 20020 // if a layer has been or will be replaced, then don't waste time with it 20021 if( layer.replacement ){ 20022 // log('layer %s in queue skipped b/c it already has a replacement', layer.id); 20023 q.pop(); 20024 continue; 20025 } 20026 20027 // if this is a replacement layer that has been superceded, then forget it 20028 if( layer.replaces && layer !== layer.replaces.replacement ){ 20029 // log('layer is no longer the most uptodate replacement; dequeued', layer.id) 20030 q.pop(); 20031 continue; 20032 } 20033 20034 if( layer.invalid ){
20035 // log('replacement layer %s is invalid; dequeued', layer.id); 20036 q.pop(); 20037 continue; 20038 } 20039 20040 var ele = layer.elesQueue.shift(); 20041 20042 if( ele ){ 20043 // log('dequeue layer %s', layer.id); 20044 20045 self.drawEleInLayer( layer, ele, layer.level, pxRatio ); 20046 20047 eleDeqs++; 20048 } 20049 20050 if( deqd.length === 0 ){ 20051 // we need only one entry in deqd to queue redrawing etc 20052 deqd.push( true ); 20053 } 20054 20055 // if the layer has all its eles done, then remove from the queue 20056 if( layer.elesQueue.length === 0 ){ 20057 q.pop(); 20058 20059 layer.reqs = 0; 20060 20061 // log('dequeue of layer %s complete', layer.id); 20062 20063 // when a replacement layer is dequeued, it replaces the old layer in the level 20064 if( layer.replaces ){ 20065 self.applyLayerReplacement( layer ); 20066 } 20067 20068 self.requestRedraw(); 20069 } 20070 } 20071 20072 return deqd; 20073}; 20074 20075LTCp.applyLayerReplacement = function( layer ){ 20076 var self = this; 20077 var layersInLevel = self.layersByLevel[ layer.level ]; 20078 var replaced = layer.replaces; 20079 var index = layersInLevel.indexOf( replaced ); 20080 20081 // if the replaced layer is not in the active list for the level, then replacing 20082 // refs would be a mistake (i.e. overwriting the true active layer) 20083 if( index < 0 || replaced.invalid ){ 20084 // log('replacement layer would have no effect', layer.id); 20085 return; 20086 } 20087 20088 layersInLevel[ index ] = layer; // replace level ref 20089 20090 // replace refs in eles 20091 for( var i = 0; i < layer.eles.length; i++ ){ 20092 var _p = layer.eles[i]._private; 20093 var cache = _p.imgLayerCaches = _p.imgLayerCaches || {}; 20094 20095 if( cache ){ 20096 cache[ layer.level ] = layer; 20097 } 20098 } 20099 20100 // log('apply replacement layer %s over %s', layer.id, replaced.id); 20101 20102 self.requestRedraw(); 20103}; 20104 20105LTCp.requestRedraw = util.debounce( function(){ 20106 var r = this.renderer; 20107 20108 r.redrawHint( 'eles', true ); 20109 r.redrawHint( 'drag', true ); 20110 r.redraw(); 20111}, 100 ); 20112 20113LTCp.setupDequeueing = defs.setupDequeueing({ 20114 deqRedrawThreshold: deqRedrawThreshold, 20115 deqCost: deqCost, 20116 deqAvgCost: deqAvgCost, 20117 deqNoDrawCost: deqNoDrawCost, 20118 deqFastCost: deqFastCost, 20119 deq: function( self, pxRatio ){ 20120 return self.dequeue( pxRatio ); 20121 }, 20122 onDeqd: util.noop, 20123 shouldRedraw: util.trueify, 20124 priority: function( self ){ 20125 return self.renderer.beforeRenderPriorities.lyrTxrDeq; 20126 } 20127}); 20128 20129module.exports = LayeredTextureCache; 20130 20131},{"../../../heap":81,"../../../is":83,"../../../math":85,"../../../util":100,"./texture-cache-defs":77}],76:[function(_dereq_,module,exports){ 20132'use strict'; 20133 20134var CRp = {}; 20135 20136CRp.nodeShapeImpl = function( name, context, centerX, centerY, width, height, points ){ 20137 switch( name ){ 20138 case 'ellipse': 20139 return this.drawEllipsePath( context, centerX, centerY, width, height ); 20140 case 'polygon': 20141 return this.drawPolygonPath( context, centerX, centerY, width, height, points ); 20142 case 'roundrectangle': 20143 return this.drawRoundRectanglePath( context, centerX, centerY, width, height ); 20144 } 20145}; 20146 20147module.exports = CRp; 20148 20149},{}],77:[function(_dereq_,module,exports){ 20150'use strict'; 20151 20152var util = _dereq_( '../../../util' ); 20153 20154var fullFpsTime = 1000/60; // assume 60 frames per second 20155 20156module.exports = { 20157 setupDequeueing: function( opts ){ 20158 return function setupDequeueingImpl(){ 20159 var self = this; 20160 var r = this.renderer; 20161 20162 if( self.dequeueingSetup ){ 20163 return; 20164 } else { 20165 self.dequeueingSetup = true; 20166 } 20167 20168 var queueRedraw = util.debounce( function(){ 20169 r.redrawHint( 'eles', true ); 20170 r.redrawHint( 'drag', true ); 20171 20172 r.redraw(); 20173 }, opts.deqRedrawThreshold ); 20174 20175 var dequeue = function( willDraw, frameStartTime ){ 20176 var startTime = util.performanceNow(); 20177 var avgRenderTime = r.averageRedrawTime; 20178 var renderTime = r.lastRedrawTime; 20179 var deqd = []; 20180 var extent = r.cy.extent(); 20181 var pixelRatio = r.getPixelRatio(); 20182 20183 while( true ){ 20184 var now = util.performanceNow(); 20185 var duration = now - startTime; 20186 var frameDuration = now - frameStartTime; 20187 20188 if( renderTime < fullFpsTime ){ 20189 // if we're rendering faster than the ideal fps, then do dequeueing 20190 // during all of the remaining frame time 20191 20192 var timeAvailable = fullFpsTime - ( willDraw ? avgRenderTime : 0 ); 20193 20194 if( frameDuration >= opts.deqFastCost * timeAvailable ){ 20195 break; 20196 } 20197 } else { 20198 if( willDraw ){ 20199 if( 20200 duration >= opts.deqCost * renderTime 20201 || duration >= opts.deqAvgCost * avgRenderTime 20202 ){ 20203 break; 20204 } 20205 } else if( frameDuration >= opts.deqNoDrawCost * fullFpsTime ){ 20206 break; 20207 } 20208 } 20209 20210 var thisDeqd = opts.deq( self, pixelRatio, extent ); 20211 20212 if( thisDeqd.length > 0 ){ 20213 for( var i = 0; i < thisDeqd.length; i++ ){ 20214 deqd.push( thisDeqd[i] ); 20215 } 20216 } else { 20217 break; 20218 } 20219 } 20220 20221 // callbacks on dequeue 20222 if( deqd.length > 0 ){ 20223 opts.onDeqd( self, deqd ); 20224 20225 if( !willDraw && opts.shouldRedraw( self, deqd, pixelRatio, extent ) ){ 20226 queueRedraw(); 20227 } 20228 } 20229 }; 20230 20231 var priority = opts.priority || util.noop; 20232 20233 r.beforeRender( dequeue, priority( self ) ); 20234 }; 20235 } 20236}; 20237 20238},{"../../../util":100}],78:[function(_dereq_,module,exports){ 20239'use strict'; 20240 20241module.exports = [ 20242 { name: 'null', impl: _dereq_( './null' ) }, 20243 { name: 'base', impl: _dereq_( './base' ) }, 20244 { name: 'canvas', impl: _dereq_( './canvas' ) } 20245]; 20246 20247},{"./base":60,"./canvas":74,"./null":79}],79:[function(_dereq_,module,exports){ 20248'use strict'; 20249 20250function NullRenderer( options ){ 20251 this.options = options; 20252 this.notifications = 0; // for testing 20253} 20254 20255var noop = function(){}; 20256 20257NullRenderer.prototype = { 20258 recalculateRenderedStyle: noop, 20259 notify: function(){ this.notifications++; }, 20260 init: noop 20261}; 20262 20263module.exports = NullRenderer; 20264 20265},{}],80:[function(_dereq_,module,exports){
20266/*! Weaver licensed under MIT (https://tldrlegal.com/license/mit-license), copyright Max Franz */ 20267 20268'use strict'; 20269 20270var is = _dereq_('./is'); 20271var util = _dereq_('./util'); 20272var Thread = _dereq_('./thread'); 20273var Promise = _dereq_('./promise'); 20274var define = _dereq_('./define'); 20275 20276var Fabric = function( N ){ 20277 if( !(this instanceof Fabric) ){ 20278 return new Fabric( N ); 20279 } 20280 20281 this._private = { 20282 pass: [] 20283 }; 20284 20285 var defN = 4; 20286 20287 if( is.number(N) ){ 20288 // then use the specified number of threads 20289 } if( typeof navigator !== 'undefined' && navigator.hardwareConcurrency != null ){ 20290 N = navigator.hardwareConcurrency; 20291 } else { 20292 try{ 20293 N = _dereq_('os').cpus().length; 20294 } catch( err ){ 20295 N = defN; 20296 } 20297 } // TODO could use an estimation here but would the additional expense be worth it? 20298 20299 for( var i = 0; i < N; i++ ){ 20300 this[i] = new Thread(); 20301 } 20302 20303 this.length = N; 20304}; 20305 20306var fabfn = Fabric.prototype; // short alias 20307 20308util.extend(fabfn, { 20309 20310 instanceString: function(){ return 'fabric'; }, 20311 20312 // require fn in all threads 20313 require: function( fn, as ){ 20314 for( var i = 0; i < this.length; i++ ){ 20315 var thread = this[i]; 20316 20317 thread.require( fn, as ); 20318 } 20319 20320 return this; 20321 }, 20322 20323 // get a random thread 20324 random: function(){ 20325 var i = Math.round( (this.length - 1) * Math.random() ); 20326 var thread = this[i]; 20327 20328 return thread; 20329 }, 20330 20331 // run on random thread 20332 run: function( fn ){ 20333 var pass = this._private.pass.shift(); 20334 20335 return this.random().pass( pass ).run( fn ); 20336 }, 20337 20338 // sends a random thread a message 20339 message: function( m ){ 20340 return this.random().message( m ); 20341 }, 20342 20343 // send all threads a message 20344 broadcast: function( m ){ 20345 for( var i = 0; i < this.length; i++ ){ 20346 var thread = this[i]; 20347 20348 thread.message( m ); 20349 } 20350 20351 return this; // chaining 20352 }, 20353 20354 // stop all threads 20355 stop: function(){ 20356 for( var i = 0; i < this.length; i++ ){ 20357 var thread = this[i]; 20358 20359 thread.stop(); 20360 } 20361 20362 return this; // chaining 20363 }, 20364 20365 // pass data to be used with .spread() etc. 20366 pass: function( data ){ 20367 var pass = this._private.pass; 20368 20369 if( is.array(data) ){ 20370 pass.push( data ); 20371 } else { 20372 throw 'Only arrays may be used with fabric.pass()'; 20373 } 20374 20375 return this; // chaining 20376 }, 20377 20378 spreadSize: function(){ 20379 var subsize = Math.ceil( this._private.pass[0].length / this.length ); 20380 20381 subsize = Math.max( 1, subsize ); // don't pass less than one ele to each thread 20382 20383 return subsize; 20384 }, 20385 20386 // split the data into slices to spread the data equally among threads 20387 spread: function( fn ){ 20388 var self = this; 20389 var _p = self._private; 20390 var subsize = self.spreadSize(); // number of pass eles to handle in each thread 20391 var pass = _p.pass.shift().concat([]); // keep a copy 20392 var runPs = []; 20393 20394 for( var i = 0; i < this.length; i++ ){ 20395 var thread = this[i]; 20396 var slice = pass.splice( 0, subsize ); 20397 20398 var runP = thread.pass( slice ).run( fn ); 20399 20400 runPs.push( runP ); 20401 20402 var doneEarly = pass.length === 0; 20403 if( doneEarly ){ break; } 20404 } 20405 20406 return Promise.all( runPs ).then(function( thens ){ 20407 var postpass = []; 20408 var p = 0; 20409 20410 // fill postpass with the total result joined from all threads 20411 for( var i = 0; i < thens.length; i++ ){ 20412 var then = thens[i]; // array result from thread i 20413 20414 for( var j = 0; j < then.length; j++ ){ 20415 var t = then[j]; // array element 20416 20417 postpass[ p++ ] = t; 20418 } 20419 } 20420 20421 return postpass; 20422 }); 20423 }, 20424 20425 // parallel version of array.map() 20426 map: function( fn ){ 20427 var self = this; 20428 20429 self.require( fn, '_$_$_fabmap' ); 20430 20431 return self.spread(function( split ){ 20432 var mapped = []; 20433 var origResolve = resolve; // jshint ignore:line 20434 20435 resolve = function( val ){ // jshint ignore:line 20436 mapped.push( val ); 20437 }; 20438 20439 for( var i = 0; i < split.length; i++ ){ 20440 var oldLen = mapped.length; 20441 var ret = _$_$_fabmap( split[i] ); // jshint ignore:line 20442 var nothingInsdByResolve = oldLen === mapped.length; 20443 20444 if( nothingInsdByResolve ){ 20445 mapped.push( ret ); 20446 } 20447 } 20448 20449 resolve = origResolve; // jshint ignore:line 20450 20451 return mapped; 20452 }); 20453 20454 }, 20455 20456 // parallel version of array.filter() 20457 filter: function( fn ){ 20458 var _p = this._private; 20459 var pass = _p.pass[0]; 20460 20461 return this.map( fn ).then(function( include ){ 20462 var ret = []; 20463 20464 for( var i = 0; i < pass.length; i++ ){ 20465 var datum = pass[i]; 20466 var incDatum = include[i]; 20467 20468 if( incDatum ){
20469 ret.push( datum ); 20470 } 20471 } 20472 20473 return ret; 20474 }); 20475 }, 20476 20477 // sorts the passed array using a divide and conquer strategy 20478 sort: function( cmp ){ 20479 var self = this; 20480 var P = this._private.pass[0].length; 20481 var subsize = this.spreadSize(); 20482 20483 cmp = cmp || function( a, b ){ // default comparison function 20484 if( a < b ){ 20485 return -1; 20486 } else if( a > b ){ 20487 return 1; 20488 } 20489 20490 return 0; 20491 }; 20492 20493 self.require( cmp, '_$_$_cmp' ); 20494 20495 return self.spread(function( split ){ // sort each split normally 20496 var sortedSplit = split.sort( _$_$_cmp ); // jshint ignore:line 20497 resolve( sortedSplit ); // jshint ignore:line 20498 20499 }).then(function( joined ){ 20500 // do all the merging in the main thread to minimise data transfer 20501 20502 // TODO could do merging in separate threads but would incur add'l cost of data transfer 20503 // for each level of the merge 20504 20505 var merge = function( i, j, max ){ 20506 // don't overflow array 20507 j = Math.min( j, P ); 20508 max = Math.min( max, P ); 20509 20510 // left and right sides of merge 20511 var l = i; 20512 var r = j; 20513 20514 var sorted = []; 20515 20516 for( var k = l; k < max; k++ ){ 20517 20518 var eleI = joined[i]; 20519 var eleJ = joined[j]; 20520 20521 if( i < r && ( j >= max || cmp(eleI, eleJ) <= 0 ) ){ 20522 sorted.push( eleI ); 20523 i++; 20524 } else { 20525 sorted.push( eleJ ); 20526 j++; 20527 } 20528 20529 } 20530 20531 // in the array proper, put the sorted values 20532 for( var k = 0; k < sorted.length; k++ ){ // kth sorted item 20533 var index = l + k; 20534 20535 joined[ index ] = sorted[k]; 20536 } 20537 }; 20538 20539 for( var splitL = subsize; splitL < P; splitL *= 2 ){ // merge until array is "split" as 1 20540 20541 for( var i = 0; i < P; i += 2*splitL ){ 20542 merge( i, i + splitL, i + 2*splitL ); 20543 } 20544 20545 } 20546 20547 return joined; 20548 }); 20549 } 20550 20551 20552}); 20553 20554var defineRandomPasser = function( opts ){ 20555 opts = opts || {}; 20556 20557 return function( fn, arg1 ){ 20558 var pass = this._private.pass.shift(); 20559 20560 return this.random().pass( pass )[ opts.threadFn ]( fn, arg1 ); 20561 }; 20562}; 20563 20564util.extend(fabfn, { 20565 randomMap: defineRandomPasser({ threadFn: 'map' }), 20566 20567 reduce: defineRandomPasser({ threadFn: 'reduce' }), 20568 20569 reduceRight: defineRandomPasser({ threadFn: 'reduceRight' }) 20570}); 20571 20572// aliases 20573var fn = fabfn; 20574fn.promise = fn.run; 20575fn.terminate = fn.halt = fn.stop; 20576fn.include = fn.require; 20577 20578// pull in event apis 20579util.extend(fabfn, { 20580 on: define.on(), 20581 one: define.on({ unbindSelfOnTrigger: true }), 20582 off: define.off(), 20583 trigger: define.trigger() 20584}); 20585 20586define.eventAliasesOn( fabfn ); 20587 20588module.exports = Fabric; 20589 20590},{"./define":44,"./is":83,"./promise":86,"./thread":98,"./util":100,"os":undefined}],81:[function(_dereq_,module,exports){ 20591/*! 20592Ported by Xueqiao Xu <[email protected]>; 20593 20594PSF LICENSE AGREEMENT FOR PYTHON 2.7.2 20595 205961. This LICENSE AGREEMENT is between the Python Software Foundation (âPSFâ), and the Individual or Organization (âLicenseeâ) accessing and otherwise using Python 2.7.2 software in source or binary form and its associated documentation. 205972. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 2.7.2 alone or in any derivative version, provided, however, that PSFâs License Agreement and PSFâs notice of copyright, i.e., âCopyright © 2001-2012 Python Software Foundation; All Rights Reservedâ are retained in Python 2.7.2 alone or in any derivative version prepared by Licensee. 205983. In the event Licensee prepares a derivative work that is based on or incorporates Python 2.7.2 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 2.7.2. 205994. PSF is making Python 2.7.2 available to Licensee on an âAS ISâ basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.7.2 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
206005. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 2.7.2 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.7.2, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 206016. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 206027. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 206038. By copying, installing or otherwise using Python 2.7.2, Licensee agrees to be bound by the terms and conditions of this License Agreement. 20604*/ 20605 20606'use strict'; 20607// Generated by CoffeeScript 1.8.0 20608 20609var Heap, defaultCmp, floor, heapify, heappop, heappush, heappushpop, heapreplace, insort, min, nlargest, nsmallest, updateItem, _siftdown, _siftup; 20610 20611floor = Math.floor, min = Math.min; 20612 20613 20614/* 20615Default comparison function to be used 20616 */ 20617 20618defaultCmp = function( x, y ){ 20619 if( x < y ){ 20620 return -1; 20621 } 20622 if( x > y ){ 20623 return 1; 20624 } 20625 return 0; 20626}; 20627 20628 20629/* 20630Insert item x in list a, and keep it sorted assuming a is sorted. 20631 20632If x is already in a, insert it to the right of the rightmost x. 20633 20634Optional args lo (default 0) and hi (default a.length) bound the slice 20635of a to be searched. 20636 */ 20637 20638insort = function( a, x, lo, hi, cmp ){ 20639 var mid; 20640 if( lo == null ){ 20641 lo = 0; 20642 } 20643 if( cmp == null ){ 20644 cmp = defaultCmp; 20645 } 20646 if( lo < 0 ){ 20647 throw new Error( 'lo must be non-negative' ); 20648 } 20649 if( hi == null ){ 20650 hi = a.length; 20651 } 20652 while( lo < hi ){ 20653 mid = floor( (lo + hi) / 2 ); 20654 if( cmp( x, a[ mid ] ) < 0 ){ 20655 hi = mid; 20656 } else { 20657 lo = mid + 1; 20658 } 20659 } 20660 return ([].splice.apply( a, [ lo, lo - lo ].concat( x ) ), x); 20661}; 20662 20663 20664/* 20665Push item onto heap, maintaining the heap invariant. 20666 */ 20667 20668heappush = function( array, item, cmp ){ 20669 if( cmp == null ){ 20670 cmp = defaultCmp; 20671 } 20672 array.push( item ); 20673 return _siftdown( array, 0, array.length - 1, cmp ); 20674}; 20675 20676 20677/* 20678Pop the smallest item off the heap, maintaining the heap invariant. 20679 */ 20680 20681heappop = function( array, cmp ){ 20682 var lastelt, returnitem; 20683 if( cmp == null ){ 20684 cmp = defaultCmp; 20685 } 20686 lastelt = array.pop(); 20687 if( array.length ){ 20688 returnitem = array[0]; 20689 array[0] = lastelt; 20690 _siftup( array, 0, cmp ); 20691 } else { 20692 returnitem = lastelt; 20693 } 20694 return returnitem; 20695}; 20696 20697 20698/* 20699Pop and return the current smallest value, and add the new item. 20700 20701This is more efficient than heappop() followed by heappush(), and can be 20702more appropriate when using a fixed size heap. Note that the value 20703returned may be larger than item! That constrains reasonable use of 20704this routine unless written as part of a conditional replacement: 20705 if item > array[0] 20706 item = heapreplace(array, item) 20707 */ 20708 20709heapreplace = function( array, item, cmp ){ 20710 var returnitem; 20711 if( cmp == null ){ 20712 cmp = defaultCmp; 20713 } 20714 returnitem = array[0]; 20715 array[0] = item; 20716 _siftup( array, 0, cmp ); 20717 return returnitem; 20718}; 20719 20720 20721/* 20722Fast version of a heappush followed by a heappop. 20723 */ 20724 20725heappushpop = function( array, item, cmp ){ 20726 var _ref; 20727 if( cmp == null ){ 20728 cmp = defaultCmp; 20729 } 20730 if( array.length && cmp( array[0], item ) < 0 ){ 20731 _ref = [ array[0], item ], item = _ref[0], array[0] = _ref[1]; 20732 _siftup( array, 0, cmp ); 20733 } 20734 return item; 20735}; 20736 20737 20738/* 20739Transform list into a heap, in-place, in O(array.length) time. 20740 */ 20741 20742heapify = function( array, cmp ){ 20743 var i, _i, _j, _len, _ref, _ref1, _results, _results1; 20744 if( cmp == null ){ 20745 cmp = defaultCmp; 20746 } 20747 _ref1 = (function(){ 20748 _results1 = []; 20749 for( var _j = 0, _ref = floor( array.length / 2 ); 0 <= _ref ? _j < _ref : _j > _ref; 0 <= _ref ? _j++ : _j-- ){ _results1.push( _j ); } 20750 return _results1; 20751 }).apply( this ).reverse(); 20752 _results = []; 20753 for( _i = 0, _len = _ref1.length; _i < _len; _i++ ){ 20754 i = _ref1[ _i ]; 20755 _results.push( _siftup( array, i, cmp ) ); 20756 } 20757 return _results; 20758}; 20759 20760 20761/* 20762Update the position of the given item in the heap. 20763This function should be called every time the item is being modified. 20764 */ 20765 20766updateItem = function( array, item, cmp ){ 20767 var pos; 20768 if( cmp == null ){ 20769 cmp = defaultCmp; 20770 } 20771 pos = array.indexOf( item ); 20772 if( pos === -1 ){ 20773 return; 20774 } 20775 _siftdown( array, 0, pos, cmp ); 20776 return _siftup( array, pos, cmp ); 20777}; 20778 20779 20780/* 20781Find the n largest elements in a dataset. 20782 */ 20783 20784nlargest = function( array, n, cmp ){ 20785 var elem, result, _i, _len, _ref; 20786 if( cmp == null ){ 20787 cmp = defaultCmp; 20788 } 20789 result = array.slice( 0, n ); 20790 if( !result.length ){ 20791 return result; 20792 } 20793 heapify( result, cmp ); 20794 _ref = array.slice( n ); 20795 for( _i = 0, _len = _ref.length; _i < _len; _i++ ){ 20796 elem = _ref[ _i ]; 20797 heappushpop( result, elem, cmp ); 20798 } 20799 return result.sort( cmp ).reverse(); 20800}; 20801 20802 20803/* 20804Find the n smallest elements in a dataset. 20805 */ 20806 20807nsmallest = function( array, n, cmp ){ 20808 var elem, i, los, result, _i, _j, _len, _ref, _ref1, _results; 20809 if( cmp == null ){ 20810 cmp = defaultCmp; 20811 } 20812 if( n * 10 <= array.length ){ 20813 result = array.slice( 0, n ).sort( cmp ); 20814 if( !result.length ){ 20815 return result; 20816 } 20817 los = result[ result.length - 1]; 20818 _ref = array.slice( n ); 20819 for( _i = 0, _len = _ref.length; _i < _len; _i++ ){ 20820 elem = _ref[ _i ]; 20821 if( cmp( elem, los ) < 0 ){ 20822 insort( result, elem, 0, null, cmp ); 20823 result.pop(); 20824 los = result[ result.length - 1]; 20825 } 20826 } 20827 return result; 20828 } 20829 heapify( array, cmp ); 20830 _results = []; 20831 for( i = _j = 0, _ref1 = min( n, array.length ); 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j ){ 20832 _results.push( heappop( array, cmp ) ); 20833 } 20834 return _results; 20835}; 20836 20837_siftdown = function( array, startpos, pos, cmp ){ 20838 var newitem, parent, parentpos; 20839 if( cmp == null ){ 20840 cmp = defaultCmp; 20841 } 20842 newitem = array[ pos ]; 20843 while( pos > startpos ){ 20844 parentpos = (pos - 1) >> 1; 20845 parent = array[ parentpos ]; 20846 if( cmp( newitem, parent ) < 0 ){ 20847 array[ pos ] = parent; 20848 pos = parentpos; 20849 continue; 20850 } 20851 break; 20852 } 20853 return array[ pos ] = newitem; 20854}; 20855 20856_siftup = function( array, pos, cmp ){ 20857 var childpos, endpos, newitem, rightpos, startpos; 20858 if( cmp == null ){ 20859 cmp = defaultCmp; 20860 } 20861 endpos = array.length; 20862 startpos = pos; 20863 newitem = array[ pos ]; 20864 childpos = 2 * pos + 1; 20865 while( childpos < endpos ){ 20866 rightpos = childpos + 1; 20867 if( rightpos < endpos && !(cmp( array[ childpos ], array[ rightpos ] ) < 0) ){ 20868 childpos = rightpos; 20869 } 20870 array[ pos ] = array[ childpos ]; 20871 pos = childpos; 20872 childpos = 2 * pos + 1; 20873 } 20874 array[ pos ] = newitem; 20875 return _siftdown( array, startpos, pos, cmp ); 20876}; 20877 20878Heap = (function(){ 20879 Heap.push = heappush; 20880 20881 Heap.pop = heappop; 20882 20883 Heap.replace = heapreplace; 20884 20885 Heap.pushpop = heappushpop; 20886 20887 Heap.heapify = heapify; 20888 20889 Heap.updateItem = updateItem; 20890 20891 Heap.nlargest = nlargest; 20892 20893 Heap.nsmallest = nsmallest; 20894
20895 function Heap( cmp ){ 20896 this.cmp = cmp != null ? cmp : defaultCmp; 20897 this.nodes = []; 20898 } 20899 20900 Heap.prototype.push = function( x ){ 20901 return heappush( this.nodes, x, this.cmp ); 20902 }; 20903 20904 Heap.prototype.pop = function(){ 20905 return heappop( this.nodes, this.cmp ); 20906 }; 20907 20908 Heap.prototype.peek = function(){ 20909 return this.nodes[0]; 20910 }; 20911 20912 Heap.prototype.contains = function( x ){ 20913 return this.nodes.indexOf( x ) !== -1; 20914 }; 20915 20916 Heap.prototype.replace = function( x ){ 20917 return heapreplace( this.nodes, x, this.cmp ); 20918 }; 20919 20920 Heap.prototype.pushpop = function( x ){ 20921 return heappushpop( this.nodes, x, this.cmp ); 20922 }; 20923 20924 Heap.prototype.heapify = function(){ 20925 return heapify( this.nodes, this.cmp ); 20926 }; 20927 20928 Heap.prototype.updateItem = function( x ){ 20929 return updateItem( this.nodes, x, this.cmp ); 20930 }; 20931 20932 Heap.prototype.clear = function(){ 20933 return this.nodes = []; 20934 }; 20935 20936 Heap.prototype.empty = function(){ 20937 return this.nodes.length === 0; 20938 }; 20939 20940 Heap.prototype.size = function(){ 20941 return this.nodes.length; 20942 }; 20943 20944 Heap.prototype.clone = function(){ 20945 var heap; 20946 heap = new Heap(); 20947 heap.nodes = this.nodes.slice( 0 ); 20948 return heap; 20949 }; 20950 20951 Heap.prototype.toArray = function(){ 20952 return this.nodes.slice( 0 ); 20953 }; 20954 20955 Heap.prototype.insert = Heap.prototype.push; 20956 20957 Heap.prototype.top = Heap.prototype.peek; 20958 20959 Heap.prototype.front = Heap.prototype.peek; 20960 20961 Heap.prototype.has = Heap.prototype.contains; 20962 20963 Heap.prototype.copy = Heap.prototype.clone; 20964 20965 return Heap; 20966 20967})(); 20968 20969module.exports = Heap; 20970 20971},{}],82:[function(_dereq_,module,exports){ 20972'use strict'; 20973 20974_dereq_('./-preamble'); 20975 20976var window = _dereq_( './window' ); 20977var is = _dereq_( './is' ); 20978var Core = _dereq_( './core' ); 20979var extension = _dereq_( './extension' ); 20980var registerJquery = _dereq_( './jquery-plugin' ); 20981var Stylesheet = _dereq_( './stylesheet' ); 20982var Thread = _dereq_( './thread' ); 20983var Fabric = _dereq_( './fabric' ); 20984 20985var cytoscape = function( options ){ // jshint ignore:line 20986 // if no options specified, use default 20987 if( options === undefined ){ 20988 options = {}; 20989 } 20990 20991 // create instance 20992 if( is.plainObject( options ) ){ 20993 return new Core( options ); 20994 } 20995 20996 // allow for registration of extensions 20997 else if( is.string( options ) ){ 20998 return extension.apply( extension, arguments ); 20999 } 21000}; 21001 21002// replaced by build system 21003cytoscape.version = _dereq_('./version.json'); 21004 21005// try to register w/ jquery 21006if( window && window.jQuery ){ 21007 registerJquery( window.jQuery, cytoscape ); 21008} 21009 21010// expose register api 21011cytoscape.registerJquery = function( jQuery ){ 21012 registerJquery( jQuery, cytoscape ); 21013}; 21014 21015// expose public apis (mostly for extensions) 21016cytoscape.stylesheet = cytoscape.Stylesheet = Stylesheet; 21017cytoscape.thread = cytoscape.Thread = Thread; 21018cytoscape.fabric = cytoscape.Fabric = Fabric; 21019 21020module.exports = cytoscape; 21021 21022},{"./-preamble":1,"./core":37,"./extension":46,"./fabric":80,"./is":83,"./jquery-plugin":84,"./stylesheet":97,"./thread":98,"./version.json":106,"./window":107}],83:[function(_dereq_,module,exports){ 21023'use strict'; 21024 21025/*global HTMLElement DocumentTouch */ 21026 21027var window = _dereq_( './window' ); 21028var navigator = window ? window.navigator : null; 21029var document = window ? window.document : null; 21030 21031var typeofstr = typeof ''; 21032var typeofobj = typeof {}; 21033var typeoffn = typeof function(){}; 21034var typeofhtmlele = typeof HTMLElement; 21035 21036var instanceStr = function( obj ){ 21037 return obj && obj.instanceString && is.fn( obj.instanceString ) ? obj.instanceString() : null; 21038}; 21039 21040var is = { 21041 defined: function( obj ){ 21042 return obj != null; // not undefined or null 21043 }, 21044 21045 string: function( obj ){ 21046 return obj != null && typeof obj == typeofstr; 21047 }, 21048 21049 fn: function( obj ){ 21050 return obj != null && typeof obj === typeoffn; 21051 }, 21052 21053 array: function( obj ){ 21054 return Array.isArray ? Array.isArray( obj ) : obj != null && obj instanceof Array; 21055 }, 21056 21057 plainObject: function( obj ){ 21058 return obj != null && typeof obj === typeofobj && !is.array( obj ) && obj.constructor === Object; 21059 }, 21060 21061 object: function( obj ){ 21062 return obj != null && typeof obj === typeofobj; 21063 }, 21064 21065 number: function( obj ){ 21066 return obj != null && typeof obj === typeof 1 && !isNaN( obj ); 21067 }, 21068 21069 integer: function( obj ){ 21070 return is.number( obj ) && Math.floor( obj ) === obj; 21071 }, 21072 21073 bool: function( obj ){ 21074 return obj != null && typeof obj === typeof true; 21075 }, 21076 21077 htmlElement: function( obj ){ 21078 if( 'undefined' === typeofhtmlele ){ 21079 return undefined; 21080 } else { 21081 return null != obj && obj instanceof HTMLElement; 21082 } 21083 }, 21084 21085 elementOrCollection: function( obj ){
21086 return is.element( obj ) || is.collection( obj ); 21087 }, 21088 21089 element: function( obj ){ 21090 return instanceStr( obj ) === 'collection' && obj._private.single; 21091 }, 21092 21093 collection: function( obj ){ 21094 return instanceStr( obj ) === 'collection' && !obj._private.single; 21095 }, 21096 21097 core: function( obj ){ 21098 return instanceStr( obj ) === 'core'; 21099 }, 21100 21101 style: function( obj ){ 21102 return instanceStr( obj ) === 'style'; 21103 }, 21104 21105 stylesheet: function( obj ){ 21106 return instanceStr( obj ) === 'stylesheet'; 21107 }, 21108 21109 event: function( obj ){ 21110 return instanceStr( obj ) === 'event'; 21111 }, 21112 21113 thread: function( obj ){ 21114 return instanceStr( obj ) === 'thread'; 21115 }, 21116 21117 fabric: function( obj ){ 21118 return instanceStr( obj ) === 'fabric'; 21119 }, 21120 21121 emptyString: function( obj ){ 21122 if( obj === undefined || obj === null ){ // null is empty 21123 return true; 21124 } else if( obj === '' || obj.match( /^\s+$/ ) ){ 21125 return true; // empty string is empty 21126 } 21127 21128 return false; // otherwise, we don't know what we've got 21129 }, 21130 21131 nonemptyString: function( obj ){ 21132 if( obj && is.string( obj ) && obj !== '' && !obj.match( /^\s+$/ ) ){ 21133 return true; 21134 } 21135 21136 return false; 21137 }, 21138 21139 domElement: function( obj ){ 21140 if( typeof HTMLElement === 'undefined' ){ 21141 return false; // we're not in a browser so it doesn't matter 21142 } else { 21143 return obj instanceof HTMLElement; 21144 } 21145 }, 21146 21147 boundingBox: function( obj ){ 21148 return is.plainObject( obj ) && 21149 is.number( obj.x1 ) && is.number( obj.x2 ) && 21150 is.number( obj.y1 ) && is.number( obj.y2 ) 21151 ; 21152 }, 21153 21154 promise: function( obj ){ 21155 return is.object( obj ) && is.fn( obj.then ); 21156 }, 21157 21158 touch: function(){ 21159 return window && ( ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch ); 21160 }, 21161 21162 gecko: function(){ 21163 return window && ( typeof InstallTrigger !== 'undefined' || ('MozAppearance' in document.documentElement.style) ); 21164 }, 21165 21166 webkit: function(){ 21167 return window && ( typeof webkitURL !== 'undefined' || ('WebkitAppearance' in document.documentElement.style) ); 21168 }, 21169 21170 chromium: function(){ 21171 return window && ( typeof chrome !== 'undefined' ); 21172 }, 21173 21174 khtml: function(){ 21175 return navigator && navigator.vendor.match( /kde/i ); // probably a better way to detect this... 21176 }, 21177 21178 khtmlEtc: function(){ 21179 return is.khtml() || is.webkit() || is.chromium(); 21180 }, 21181 21182 ms: function(){ 21183 return navigator && navigator.userAgent.match( /msie|trident|edge/i ); // probably a better way to detect this... 21184 }, 21185 21186 windows: function(){ 21187 return navigator && navigator.appVersion.match( /Win/i ); 21188 }, 21189 21190 mac: function(){ 21191 return navigator && navigator.appVersion.match( /Mac/i ); 21192 }, 21193 21194 linux: function(){ 21195 return navigator && navigator.appVersion.match( /Linux/i ); 21196 }, 21197 21198 unix: function(){ 21199 return navigator && navigator.appVersion.match( /X11/i ); 21200 } 21201}; 21202 21203module.exports = is; 21204 21205},{"./window":107}],84:[function(_dereq_,module,exports){ 21206'use strict'; 21207 21208var is = _dereq_( './is' ); 21209 21210var cyReg = function( $ele ){ 21211 var d = $ele[0]._cyreg = $ele[0]._cyreg || {}; 21212 21213 return d; 21214}; 21215 21216var registerJquery = function( $, cytoscape ){ 21217 if( !$ ){ return; } // no jquery => don't need this 21218 21219 if( $.fn.cytoscape ){ return; } // already registered 21220 21221 // allow calls on a jQuery selector by proxying calls to $.cytoscape 21222 // e.g. $("#foo").cytoscape(options) => $.cytoscape(options) on #foo 21223 $.fn.cytoscape = function( opts ){ 21224 var $this = $( this ); 21225 21226 // get object 21227 if( opts === 'get' ){ 21228 return cyReg( $this ).cy; 21229 } 21230 21231 // bind to ready 21232 else if( is.fn( opts ) ){ 21233 21234 var ready = opts; 21235 var cy = cyReg( $this ).cy; 21236 21237 if( cy && cy.isReady() ){ // already ready so just trigger now 21238 cy.trigger( 'ready', [], ready ); 21239 21240 } else { // not yet ready, so add to readies list 21241 var data = cyReg( $this ); 21242 var readies = data.readies = data.readies || []; 21243 21244 readies.push( ready ); 21245 } 21246 21247 } 21248 21249 // proxy to create instance 21250 else if( is.plainObject( opts ) ){ 21251 return $this.each( function(){ 21252 var options = $.extend( {}, opts, { 21253 container: $( this )[0] 21254 } ); 21255 21256 cytoscape( options ); 21257 } ); 21258 } 21259 }; 21260 21261 // allow access to the global cytoscape object under jquery for legacy reasons 21262 $.cytoscape = cytoscape; 21263 21264 // use short alias (cy) if not already defined 21265 if( $.fn.cy == null && $.cy == null ){ 21266 $.fn.cy = $.fn.cytoscape; 21267 $.cy = $.cytoscape; 21268 } 21269}; 21270 21271module.exports = registerJquery; 21272 21273},{"./is":83}],85:[function(_dereq_,module,exports){ 21274'use strict'; 21275 21276var math = {}; 21277 21278math.arePositionsSame = function( p1, p2 ){ 21279 return p1.x === p2.x && p1.y === p2.y; 21280}; 21281 21282math.copyPosition = function( p ){ 21283 return { x: p.x, y: p.y }; 21284}; 21285 21286math.array2point = function( arr ){ 21287 return { 21288 x: arr[0], 21289 y: arr[1] 21290 }; 21291}; 21292 21293math.deg2rad = function( deg ){ 21294 return Math.PI * deg / 180; 21295}; 21296 21297math.log2 = Math.log2 || function( n ){ 21298 return Math.log( n ) / Math.log( 2 ); 21299}; 21300 21301math.signum = function( x ){ 21302 if( x > 0 ){ 21303 return 1; 21304 } else if( x < 0 ){ 21305 return -1; 21306 } else { 21307 return 0; 21308 } 21309}; 21310 21311math.dist = function( p1, p2 ){ 21312 return Math.sqrt( math.sqdist( p1, p2 ) ); 21313}; 21314 21315math.sqdist = function( p1, p2 ){ 21316 var dx = p2.x - p1.x; 21317 var dy = p2.y - p1.y; 21318 21319 return dx * dx + dy * dy; 21320}; 21321 21322// from http://en.wikipedia.org/wiki/Bézier_curve#Quadratic_curves 21323math.qbezierAt = function( p0, p1, p2, t ){ 21324 return (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2; 21325}; 21326 21327math.qbezierPtAt = function( p0, p1, p2, t ){ 21328 return { 21329 x: math.qbezierAt( p0.x, p1.x, p2.x, t ), 21330 y: math.qbezierAt( p0.y, p1.y, p2.y, t ) 21331 }; 21332}; 21333 21334math.lineAt = function( p0, p1, t, d ){ 21335 var vec = { 21336 x: p1.x - p0.x, 21337 y: p1.y - p0.y 21338 }; 21339 21340 var vecDist = math.dist( p0, p1 ); 21341 21342 var normVec = { 21343 x: vec.x / vecDist, 21344 y: vec.y / vecDist 21345 }; 21346 21347 t = t == null ? 0 : t; 21348 21349 var d = d != null ? d : t * vecDist; 21350 21351 return { 21352 x: p0.x + normVec.x * d, 21353 y: p0.y + normVec.y * d 21354 }; 21355}; 21356 21357math.lineAtDist = function( p0, p1, d ){ 21358 return math.lineAt( p0, p1, undefined, d ); 21359}; 21360 21361// get angle at A via cosine law 21362math.triangleAngle = function( A, B, C ){ 21363 var a = math.dist( B, C ); 21364 var b = math.dist( A, C ); 21365 var c = math.dist( A, B ); 21366 21367 return Math.acos( (a*a + b*b - c*c)/(2*a*b) ); 21368}; 21369 21370math.bound = function( min, val, max ){ 21371 return Math.max( min, Math.min( max, val ) ); 21372}; 21373 21374// makes a full bb (x1, y1, x2, y2, w, h) from implicit params 21375math.makeBoundingBox = function( bb ){ 21376 if( bb == null ){ 21377 return { 21378 x1: Infinity, 21379 y1: Infinity, 21380 x2: -Infinity, 21381 y2: -Infinity, 21382 w: 0, 21383 h: 0 21384 }; 21385 } else if( bb.x1 != null && bb.y1 != null ){ 21386 if( bb.x2 != null && bb.y2 != null && bb.x2 >= bb.x1 && bb.y2 >= bb.y1 ){ 21387 return { 21388 x1: bb.x1, 21389 y1: bb.y1, 21390 x2: bb.x2, 21391 y2: bb.y2, 21392 w: bb.x2 - bb.x1, 21393 h: bb.y2 - bb.y1 21394 }; 21395 } else if( bb.w != null && bb.h != null && bb.w >= 0 && bb.h >= 0 ){ 21396 return { 21397 x1: bb.x1, 21398 y1: bb.y1, 21399 x2: bb.x1 + bb.w, 21400 y2: bb.y1 + bb.h, 21401 w: bb.w, 21402 h: bb.h 21403 }; 21404 } 21405 } 21406}; 21407 21408math.updateBoundingBox = function( bb1, bb2 ){ 21409 // update bb1 with bb2 bounds 21410 21411 bb1.x1 = Math.min( bb1.x1, bb2.x1 ); 21412 bb1.x2 = Math.max( bb1.x2, bb2.x2 ); 21413 bb1.w = bb1.x2 - bb1.x1; 21414 21415 bb1.y1 = Math.min( bb1.y1, bb2.y1 ); 21416 bb1.y2 = Math.max( bb1.y2, bb2.y2 ); 21417 bb1.h = bb1.y2 - bb1.y1; 21418}; 21419 21420math.expandBoundingBox = function( bb, padding ){ 21421 bb.x1 -= padding; 21422 bb.x2 += padding; 21423 bb.y1 -= padding; 21424 bb.y2 += padding; 21425 bb.w = bb.x2 - bb.x1; 21426 bb.h = bb.y2 - bb.y1; 21427 21428 return bb; 21429}; 21430 21431math.boundingBoxesIntersect = function( bb1, bb2 ){ 21432 // case: one bb to right of other 21433 if( bb1.x1 > bb2.x2 ){ return false; } 21434 if( bb2.x1 > bb1.x2 ){ return false; } 21435 21436 // case: one bb to left of other 21437 if( bb1.x2 < bb2.x1 ){ return false; } 21438 if( bb2.x2 < bb1.x1 ){ return false; } 21439 21440 // case: one bb above other 21441 if( bb1.y2 < bb2.y1 ){ return false; } 21442 if( bb2.y2 < bb1.y1 ){ return false; } 21443 21444 // case: one bb below other 21445 if( bb1.y1 > bb2.y2 ){ return false; } 21446 if( bb2.y1 > bb1.y2 ){ return false; } 21447 21448 // otherwise, must have some overlap 21449 return true; 21450}; 21451 21452math.inBoundingBox = function( bb, x, y ){ 21453 return bb.x1 <= x && x <= bb.x2 && bb.y1 <= y && y <= bb.y2; 21454}; 21455 21456math.pointInBoundingBox = function( bb, pt ){ 21457 return this.inBoundingBox( bb, pt.x, pt.y ); 21458}; 21459 21460math.boundingBoxInBoundingBox = function( bb1, bb2 ){ 21461 return ( 21462 math.inBoundingBox( bb1, bb2.x1, bb2.y1 ) 21463 && math.inBoundingBox( bb1, bb2.x2, bb2.y2 ) 21464 ); 21465}; 21466 21467math.roundRectangleIntersectLine = function( 21468 x, y, nodeX, nodeY, width, height, padding ){ 21469 21470 var cornerRadius = this.getRoundRectangleRadius( width, height ); 21471 21472 var halfWidth = width / 2; 21473 var halfHeight = height / 2; 21474 21475 // Check intersections with straight line segments 21476 var straightLineIntersections; 21477 21478 // Top segment, left to right 21479 { 21480 var topStartX = nodeX - halfWidth + cornerRadius - padding; 21481 var topStartY = nodeY - halfHeight - padding; 21482 var topEndX = nodeX + halfWidth - cornerRadius + padding; 21483 var topEndY = topStartY; 21484 21485 straightLineIntersections = this.finiteLinesIntersect( 21486 x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false ); 21487 21488 if( straightLineIntersections.length > 0 ){ 21489 return straightLineIntersections; 21490 } 21491 } 21492 21493 // Right segment, top to bottom 21494 { 21495 var rightStartX = nodeX + halfWidth + padding; 21496 var rightStartY = nodeY - halfHeight + cornerRadius - padding; 21497 var rightEndX = rightStartX; 21498 var rightEndY = nodeY + halfHeight - cornerRadius + padding; 21499 21500 straightLineIntersections = this.finiteLinesIntersect(
21501 x, y, nodeX, nodeY, rightStartX, rightStartY, rightEndX, rightEndY, false ); 21502 21503 if( straightLineIntersections.length > 0 ){ 21504 return straightLineIntersections; 21505 } 21506 } 21507 21508 // Bottom segment, left to right 21509 { 21510 var bottomStartX = nodeX - halfWidth + cornerRadius - padding; 21511 var bottomStartY = nodeY + halfHeight + padding; 21512 var bottomEndX = nodeX + halfWidth - cornerRadius + padding; 21513 var bottomEndY = bottomStartY; 21514 21515 straightLineIntersections = this.finiteLinesIntersect( 21516 x, y, nodeX, nodeY, bottomStartX, bottomStartY, bottomEndX, bottomEndY, false ); 21517 21518 if( straightLineIntersections.length > 0 ){ 21519 return straightLineIntersections; 21520 } 21521 } 21522 21523 // Left segment, top to bottom 21524 { 21525 var leftStartX = nodeX - halfWidth - padding; 21526 var leftStartY = nodeY - halfHeight + cornerRadius - padding; 21527 var leftEndX = leftStartX; 21528 var leftEndY = nodeY + halfHeight - cornerRadius + padding; 21529 21530 straightLineIntersections = this.finiteLinesIntersect( 21531 x, y, nodeX, nodeY, leftStartX, leftStartY, leftEndX, leftEndY, false ); 21532 21533 if( straightLineIntersections.length > 0 ){ 21534 return straightLineIntersections; 21535 } 21536 } 21537 21538 // Check intersections with arc segments 21539 var arcIntersections; 21540 21541 // Top Left 21542 { 21543 var topLeftCenterX = nodeX - halfWidth + cornerRadius; 21544 var topLeftCenterY = nodeY - halfHeight + cornerRadius; 21545 arcIntersections = this.intersectLineCircle( 21546 x, y, nodeX, nodeY, 21547 topLeftCenterX, topLeftCenterY, cornerRadius + padding ); 21548 21549 // Ensure the intersection is on the desired quarter of the circle 21550 if( arcIntersections.length > 0 21551 && arcIntersections[0] <= topLeftCenterX 21552 && arcIntersections[1] <= topLeftCenterY ){ 21553 return [ arcIntersections[0], arcIntersections[1] ]; 21554 } 21555 } 21556 21557 // Top Right 21558 { 21559 var topRightCenterX = nodeX + halfWidth - cornerRadius; 21560 var topRightCenterY = nodeY - halfHeight + cornerRadius; 21561 arcIntersections = this.intersectLineCircle( 21562 x, y, nodeX, nodeY, 21563 topRightCenterX, topRightCenterY, cornerRadius + padding ); 21564 21565 // Ensure the intersection is on the desired quarter of the circle 21566 if( arcIntersections.length > 0 21567 && arcIntersections[0] >= topRightCenterX 21568 && arcIntersections[1] <= topRightCenterY ){ 21569 return [ arcIntersections[0], arcIntersections[1] ]; 21570 } 21571 } 21572 21573 // Bottom Right 21574 { 21575 var bottomRightCenterX = nodeX + halfWidth - cornerRadius; 21576 var bottomRightCenterY = nodeY + halfHeight - cornerRadius; 21577 arcIntersections = this.intersectLineCircle( 21578 x, y, nodeX, nodeY, 21579 bottomRightCenterX, bottomRightCenterY, cornerRadius + padding ); 21580 21581 // Ensure the intersection is on the desired quarter of the circle 21582 if( arcIntersections.length > 0 21583 && arcIntersections[0] >= bottomRightCenterX 21584 && arcIntersections[1] >= bottomRightCenterY ){ 21585 return [ arcIntersections[0], arcIntersections[1] ]; 21586 } 21587 } 21588 21589 // Bottom Left 21590 { 21591 var bottomLeftCenterX = nodeX - halfWidth + cornerRadius; 21592 var bottomLeftCenterY = nodeY + halfHeight - cornerRadius; 21593 arcIntersections = this.intersectLineCircle( 21594 x, y, nodeX, nodeY, 21595 bottomLeftCenterX, bottomLeftCenterY, cornerRadius + padding ); 21596 21597 // Ensure the intersection is on the desired quarter of the circle 21598 if( arcIntersections.length > 0 21599 && arcIntersections[0] <= bottomLeftCenterX 21600 && arcIntersections[1] >= bottomLeftCenterY ){ 21601 return [ arcIntersections[0], arcIntersections[1] ]; 21602 } 21603 } 21604 21605 return []; // if nothing 21606}; 21607 21608math.inLineVicinity = function( x, y, lx1, ly1, lx2, ly2, tolerance ){ 21609 var t = tolerance; 21610 21611 var x1 = Math.min( lx1, lx2 ); 21612 var x2 = Math.max( lx1, lx2 ); 21613 var y1 = Math.min( ly1, ly2 ); 21614 var y2 = Math.max( ly1, ly2 ); 21615 21616 return x1 - t <= x && x <= x2 + t 21617 && y1 - t <= y && y <= y2 + t; 21618}; 21619 21620math.inBezierVicinity = function( 21621 x, y, x1, y1, x2, y2, x3, y3, tolerance ){ 21622 21623 var bb = { 21624 x1: Math.min( x1, x3, x2 ) - tolerance, 21625 x2: Math.max( x1, x3, x2 ) + tolerance, 21626 y1: Math.min( y1, y3, y2 ) - tolerance, 21627 y2: Math.max( y1, y3, y2 ) + tolerance 21628 }; 21629 21630 // if outside the rough bounding box for the bezier, then it can't be a hit 21631 if( x < bb.x1 || x > bb.x2 || y < bb.y1 || y > bb.y2 ){ 21632 // console.log('bezier out of rough bb') 21633 return false;
21634 } else { 21635 // console.log('do more expensive check'); 21636 return true; 21637 } 21638 21639}; 21640 21641math.solveCubic = function( a, b, c, d, result ){ 21642 21643 // Solves a cubic function, returns root in form [r1, i1, r2, i2, r3, i3], where 21644 // r is the real component, i is the imaginary component 21645 21646 // An implementation of the Cardano method from the year 1545 21647 // http://en.wikipedia.org/wiki/Cubic_function#The_nature_of_the_roots 21648 21649 b /= a; 21650 c /= a; 21651 d /= a; 21652 21653 var discriminant, q, r, dum1, s, t, term1, r13; 21654 21655 q = (3.0 * c - (b * b)) / 9.0; 21656 r = -(27.0 * d) + b * (9.0 * c - 2.0 * (b * b)); 21657 r /= 54.0; 21658 21659 discriminant = q * q * q + r * r; 21660 result[1] = 0; 21661 term1 = (b / 3.0); 21662 21663 if( discriminant > 0 ){ 21664 s = r + Math.sqrt( discriminant ); 21665 s = ((s < 0) ? -Math.pow( -s, (1.0 / 3.0) ) : Math.pow( s, (1.0 / 3.0) )); 21666 t = r - Math.sqrt( discriminant ); 21667 t = ((t < 0) ? -Math.pow( -t, (1.0 / 3.0) ) : Math.pow( t, (1.0 / 3.0) )); 21668 result[0] = -term1 + s + t; 21669 term1 += (s + t) / 2.0; 21670 result[4] = result[2] = -term1; 21671 term1 = Math.sqrt( 3.0 ) * (-t + s) / 2; 21672 result[3] = term1; 21673 result[5] = -term1; 21674 return; 21675 } 21676 21677 result[5] = result[3] = 0; 21678 21679 if( discriminant === 0 ){ 21680 r13 = ((r < 0) ? -Math.pow( -r, (1.0 / 3.0) ) : Math.pow( r, (1.0 / 3.0) )); 21681 result[0] = -term1 + 2.0 * r13; 21682 result[4] = result[2] = -(r13 + term1); 21683 return; 21684 } 21685 21686 q = -q; 21687 dum1 = q * q * q; 21688 dum1 = Math.acos( r / Math.sqrt( dum1 ) ); 21689 r13 = 2.0 * Math.sqrt( q ); 21690 result[0] = -term1 + r13 * Math.cos( dum1 / 3.0 ); 21691 result[2] = -term1 + r13 * Math.cos( (dum1 + 2.0 * Math.PI) / 3.0 ); 21692 result[4] = -term1 + r13 * Math.cos( (dum1 + 4.0 * Math.PI) / 3.0 ); 21693 21694 return; 21695}; 21696 21697math.sqdistToQuadraticBezier = function( 21698 x, y, x1, y1, x2, y2, x3, y3 ){ 21699 21700 // Find minimum distance by using the minimum of the distance 21701 // function between the given point and the curve 21702 21703 // This gives the coefficients of the resulting cubic equation 21704 // whose roots tell us where a possible minimum is 21705 // (Coefficients are divided by 4) 21706 21707 var a = 1.0 * x1 * x1 - 4 * x1 * x2 + 2 * x1 * x3 + 4 * x2 * x2 - 4 * x2 * x3 + x3 * x3 21708 + y1 * y1 - 4 * y1 * y2 + 2 * y1 * y3 + 4 * y2 * y2 - 4 * y2 * y3 + y3 * y3; 21709 21710 var b = 1.0 * 9 * x1 * x2 - 3 * x1 * x1 - 3 * x1 * x3 - 6 * x2 * x2 + 3 * x2 * x3 21711 + 9 * y1 * y2 - 3 * y1 * y1 - 3 * y1 * y3 - 6 * y2 * y2 + 3 * y2 * y3; 21712 21713 var c = 1.0 * 3 * x1 * x1 - 6 * x1 * x2 + x1 * x3 - x1 * x + 2 * x2 * x2 + 2 * x2 * x - x3 * x 21714 + 3 * y1 * y1 - 6 * y1 * y2 + y1 * y3 - y1 * y + 2 * y2 * y2 + 2 * y2 * y - y3 * y; 21715 21716 var d = 1.0 * x1 * x2 - x1 * x1 + x1 * x - x2 * x 21717 + y1 * y2 - y1 * y1 + y1 * y - y2 * y; 21718 21719 // debug("coefficients: " + a / a + ", " + b / a + ", " + c / a + ", " + d / a); 21720 21721 var roots = []; 21722 21723 // Use the cubic solving algorithm 21724 this.solveCubic( a, b, c, d, roots ); 21725 21726 var zeroThreshold = 0.0000001; 21727 21728 var params = []; 21729 21730 for( var index = 0; index < 6; index += 2 ){ 21731 if( Math.abs( roots[ index + 1] ) < zeroThreshold 21732 && roots[ index ] >= 0 21733 && roots[ index ] <= 1.0 ){ 21734 params.push( roots[ index ] ); 21735 } 21736 } 21737 21738 params.push( 1.0 ); 21739 params.push( 0.0 ); 21740 21741 var minDistanceSquared = -1; 21742 var closestParam; 21743 21744 var curX, curY, distSquared; 21745 for( var i = 0; i < params.length; i++ ){ 21746 curX = Math.pow( 1.0 - params[ i ], 2.0 ) * x1 21747 + 2.0 * (1 - params[ i ]) * params[ i ] * x2 21748 + params[ i ] * params[ i ] * x3; 21749 21750 curY = Math.pow( 1 - params[ i ], 2.0 ) * y1 21751 + 2 * (1.0 - params[ i ]) * params[ i ] * y2 21752 + params[ i ] * params[ i ] * y3; 21753 21754 distSquared = Math.pow( curX - x, 2 ) + Math.pow( curY - y, 2 ); 21755 // debug('distance for param ' + params[i] + ": " + Math.sqrt(distSquared)); 21756 if( minDistanceSquared >= 0 ){ 21757 if( distSquared < minDistanceSquared ){ 21758 minDistanceSquared = distSquared; 21759 closestParam = params[ i ]; 21760 } 21761 } else { 21762 minDistanceSquared = distSquared; 21763 closestParam = params[ i ]; 21764 } 21765 } 21766 21767 return minDistanceSquared; 21768}; 21769 21770math.sqdistToFiniteLine = function( x, y, x1, y1, x2, y2 ){ 21771 var offset = [ x - x1, y - y1 ]; 21772 var line = [ x2 - x1, y2 - y1 ]; 21773 21774 var lineSq = line[0] * line[0] + line[1] * line[1]; 21775 var hypSq = offset[0] * offset[0] + offset[1] * offset[1]; 21776 21777 var dotProduct = offset[0] * line[0] + offset[1] * line[1]; 21778 var adjSq = dotProduct * dotProduct / lineSq; 21779 21780 if( dotProduct < 0 ){ 21781 return hypSq; 21782 } 21783 21784 if( adjSq > lineSq ){ 21785 return (x - x2) * (x - x2) + (y - y2) * (y - y2); 21786 } 21787 21788 return hypSq - adjSq; 21789}; 21790 21791math.pointInsidePolygonPoints = function( x, y, points ){ 21792 var x1, y1, x2, y2; 21793 var y3; 21794 21795 // Intersect with vertical line through (x, y) 21796 var up = 0; 21797 var down = 0; 21798 for( var i = 0; i < points.length / 2; i++ ){ 21799 21800 x1 = points[ i * 2]; 21801 y1 = points[ i * 2 + 1]; 21802 21803 if( i + 1 < points.length / 2 ){ 21804 x2 = points[ (i + 1) * 2]; 21805 y2 = points[ (i + 1) * 2 + 1]; 21806 } else { 21807 x2 = points[ (i + 1 - points.length / 2) * 2]; 21808 y2 = points[ (i + 1 - points.length / 2) * 2 + 1]; 21809 } 21810 21811 if( x1 == x && x2 == x ){ 21812 // then ignore 21813 } else if( (x1 >= x && x >= x2) 21814 || (x1 <= x && x <= x2) ){ 21815 21816 y3 = (x - x1) / (x2 - x1) * (y2 - y1) + y1; 21817 21818 if( y3 > y ){ 21819 up++; 21820 } 21821 21822 if( y3 < y ){ 21823 down++; 21824 } 21825 21826 } else { 21827 continue; 21828 } 21829 21830 } 21831 21832 if( up % 2 === 0 ){ 21833 return false;
21834 } else { 21835 return true; 21836 } 21837}; 21838 21839math.pointInsidePolygon = function( 21840 x, y, basePoints, centerX, centerY, width, height, direction, padding ){ 21841 21842 //var direction = arguments[6]; 21843 var transformedPoints = new Array( basePoints.length ); 21844 21845 // Gives negative angle 21846 var angle; 21847 21848 if( direction[0] != null ){ 21849 angle = Math.atan( direction[1] / direction[0] ); 21850 21851 if( direction[0] < 0 ){ 21852 angle = angle + Math.PI / 2; 21853 } else { 21854 angle = -angle - Math.PI / 2; 21855 } 21856 } else { 21857 angle = direction; 21858 } 21859 21860 var cos = Math.cos( -angle ); 21861 var sin = Math.sin( -angle ); 21862 21863 // console.log("base: " + basePoints); 21864 for( var i = 0; i < transformedPoints.length / 2; i++ ){ 21865 transformedPoints[ i * 2] = 21866 width / 2 * (basePoints[ i * 2] * cos 21867 - basePoints[ i * 2 + 1] * sin); 21868 21869 transformedPoints[ i * 2 + 1] = 21870 height / 2 * (basePoints[ i * 2 + 1] * cos 21871 + basePoints[ i * 2] * sin); 21872 21873 transformedPoints[ i * 2] += centerX; 21874 transformedPoints[ i * 2 + 1] += centerY; 21875 } 21876 21877 var points; 21878 21879 if( padding > 0 ){ 21880 var expandedLineSet = this.expandPolygon( 21881 transformedPoints, 21882 -padding ); 21883 21884 points = this.joinLines( expandedLineSet ); 21885 } else { 21886 points = transformedPoints; 21887 } 21888 21889 return math.pointInsidePolygonPoints( x, y, points ); 21890}; 21891 21892math.joinLines = function( lineSet ){ 21893 21894 var vertices = new Array( lineSet.length / 2 ); 21895 21896 var currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY; 21897 var nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY; 21898 21899 for( var i = 0; i < lineSet.length / 4; i++ ){ 21900 currentLineStartX = lineSet[ i * 4]; 21901 currentLineStartY = lineSet[ i * 4 + 1]; 21902 currentLineEndX = lineSet[ i * 4 + 2]; 21903 currentLineEndY = lineSet[ i * 4 + 3]; 21904 21905 if( i < lineSet.length / 4 - 1 ){ 21906 nextLineStartX = lineSet[ (i + 1) * 4]; 21907 nextLineStartY = lineSet[ (i + 1) * 4 + 1]; 21908 nextLineEndX = lineSet[ (i + 1) * 4 + 2]; 21909 nextLineEndY = lineSet[ (i + 1) * 4 + 3]; 21910 } else { 21911 nextLineStartX = lineSet[0]; 21912 nextLineStartY = lineSet[1]; 21913 nextLineEndX = lineSet[2]; 21914 nextLineEndY = lineSet[3]; 21915 } 21916 21917 var intersection = this.finiteLinesIntersect( 21918 currentLineStartX, currentLineStartY, 21919 currentLineEndX, currentLineEndY, 21920 nextLineStartX, nextLineStartY, 21921 nextLineEndX, nextLineEndY, 21922 true ); 21923 21924 vertices[ i * 2] = intersection[0]; 21925 vertices[ i * 2 + 1] = intersection[1]; 21926 } 21927 21928 return vertices; 21929}; 21930 21931math.expandPolygon = function( points, pad ){ 21932 21933 var expandedLineSet = new Array( points.length * 2 ); 21934 21935 var currentPointX, currentPointY, nextPointX, nextPointY; 21936 21937 for( var i = 0; i < points.length / 2; i++ ){ 21938 currentPointX = points[ i * 2]; 21939 currentPointY = points[ i * 2 + 1]; 21940 21941 if( i < points.length / 2 - 1 ){ 21942 nextPointX = points[ (i + 1) * 2]; 21943 nextPointY = points[ (i + 1) * 2 + 1]; 21944 } else { 21945 nextPointX = points[0]; 21946 nextPointY = points[1]; 21947 } 21948 21949 // Current line: [currentPointX, currentPointY] to [nextPointX, nextPointY] 21950 21951 // Assume CCW polygon winding 21952 21953 var offsetX = (nextPointY - currentPointY); 21954 var offsetY = -(nextPointX - currentPointX); 21955 21956 // Normalize 21957 var offsetLength = Math.sqrt( offsetX * offsetX + offsetY * offsetY ); 21958 var normalizedOffsetX = offsetX / offsetLength; 21959 var normalizedOffsetY = offsetY / offsetLength; 21960 21961 expandedLineSet[ i * 4] = currentPointX + normalizedOffsetX * pad; 21962 expandedLineSet[ i * 4 + 1] = currentPointY + normalizedOffsetY * pad; 21963 expandedLineSet[ i * 4 + 2] = nextPointX + normalizedOffsetX * pad; 21964 expandedLineSet[ i * 4 + 3] = nextPointY + normalizedOffsetY * pad; 21965 } 21966 21967 return expandedLineSet; 21968}; 21969 21970math.intersectLineEllipse = function( 21971 x, y, centerX, centerY, ellipseWradius, ellipseHradius ){ 21972 21973 var dispX = centerX - x; 21974 var dispY = centerY - y; 21975 21976 dispX /= ellipseWradius; 21977 dispY /= ellipseHradius; 21978 21979 var len = Math.sqrt( dispX * dispX + dispY * dispY ); 21980 21981 var newLength = len - 1; 21982 21983 if( newLength < 0 ){ 21984 return []; 21985 } 21986 21987 var lenProportion = newLength / len; 21988 21989 return [ (centerX - x) * lenProportion + x, (centerY - y) * lenProportion + y ]; 21990}; 21991 21992// Returns intersections of increasing distance from line's start point
21993math.intersectLineCircle = function( 21994 x1, y1, x2, y2, centerX, centerY, radius ){ 21995 21996 // Calculate d, direction vector of line 21997 var d = [ x2 - x1, y2 - y1 ]; // Direction vector of line 21998 var c = [ centerX, centerY ]; // Center of circle 21999 var f = [ x1 - centerX, y1 - centerY ]; 22000 22001 var a = d[0] * d[0] + d[1] * d[1]; 22002 var b = 2 * (f[0] * d[0] + f[1] * d[1]); 22003 var c = (f[0] * f[0] + f[1] * f[1]) - radius * radius ; 22004 22005 var discriminant = b * b - 4 * a * c; 22006 22007 if( discriminant < 0 ){ 22008 return []; 22009 } 22010 22011 var t1 = (-b + Math.sqrt( discriminant )) / (2 * a); 22012 var t2 = (-b - Math.sqrt( discriminant )) / (2 * a); 22013 22014 var tMin = Math.min( t1, t2 ); 22015 var tMax = Math.max( t1, t2 ); 22016 var inRangeParams = []; 22017 22018 if( tMin >= 0 && tMin <= 1 ){ 22019 inRangeParams.push( tMin ); 22020 } 22021 22022 if( tMax >= 0 && tMax <= 1 ){ 22023 inRangeParams.push( tMax ); 22024 } 22025 22026 if( inRangeParams.length === 0 ){ 22027 return []; 22028 } 22029 22030 var nearIntersectionX = inRangeParams[0] * d[0] + x1; 22031 var nearIntersectionY = inRangeParams[0] * d[1] + y1; 22032 22033 if( inRangeParams.length > 1 ){ 22034 22035 if( inRangeParams[0] == inRangeParams[1] ){ 22036 return [ nearIntersectionX, nearIntersectionY ]; 22037 } else { 22038 22039 var farIntersectionX = inRangeParams[1] * d[0] + x1; 22040 var farIntersectionY = inRangeParams[1] * d[1] + y1; 22041 22042 return [ nearIntersectionX, nearIntersectionY, farIntersectionX, farIntersectionY ]; 22043 } 22044 22045 } else { 22046 return [ nearIntersectionX, nearIntersectionY ]; 22047 } 22048 22049}; 22050 22051math.findCircleNearPoint = function( centerX, centerY, 22052 radius, farX, farY ){ 22053 22054 var displacementX = farX - centerX; 22055 var displacementY = farY - centerY; 22056 var distance = Math.sqrt( displacementX * displacementX 22057 + displacementY * displacementY ); 22058 22059 var unitDisplacementX = displacementX / distance; 22060 var unitDisplacementY = displacementY / distance; 22061 22062 return [ centerX + unitDisplacementX * radius, 22063 centerY + unitDisplacementY * radius ]; 22064}; 22065 22066math.findMaxSqDistanceToOrigin = function( points ){ 22067 var maxSqDistance = 0.000001; 22068 var sqDistance; 22069 22070 for( var i = 0; i < points.length / 2; i++ ){ 22071 22072 sqDistance = points[ i * 2] * points[ i * 2] 22073 + points[ i * 2 + 1] * points[ i * 2 + 1]; 22074 22075 if( sqDistance > maxSqDistance ){ 22076 maxSqDistance = sqDistance; 22077 } 22078 } 22079 22080 return maxSqDistance; 22081}; 22082 22083math.midOfThree = function( a, b, c ){ 22084 if( (b <= a && a <= c) || (c <= a && a <= b) ){ 22085 return a; 22086 } else if( (a <= b && b <= c) || (c <= b && b <= a) ){ 22087 return b; 22088 } else { 22089 return c; 22090 } 22091}; 22092 22093math.finiteLinesIntersect = function( x1, y1, x2, y2, x3, y3, x4, y4, infiniteLines ){ 22094 22095 var dx13 = x1 - x3; 22096 var dx21 = x2 - x1; 22097 var dx43 = x4 - x3; 22098 22099 var dy13 = y1 - y3; 22100 var dy21 = y2 - y1; 22101 var dy43 = y4 - y3; 22102 22103 var ua_t = dx43 * dy13 - dy43 * dx13; 22104 var ub_t = dx21 * dy13 - dy21 * dx13; 22105 var u_b = dy43 * dx21 - dx43 * dy21; 22106 22107 if( u_b !== 0 ){ 22108 var ua = ua_t / u_b; 22109 var ub = ub_t / u_b; 22110 22111 var flptThreshold = 0.001; 22112 var min = 0 - flptThreshold; 22113 var max = 1 + flptThreshold; 22114 22115 if( min <= ua && ua <= max && min <= ub && ub <= max ){ 22116 return [ x1 + ua * dx21, y1 + ua * dy21 ]; 22117 22118 } else { 22119 if( !infiniteLines ){ 22120 return []; 22121 } else { 22122 return [ x1 + ua * dx21, y1 + ua * dy21 ]; 22123 } 22124 } 22125 } else { 22126 if( ua_t === 0 || ub_t === 0 ){ 22127 22128 // Parallel, coincident lines. Check if overlap 22129 22130 // Check endpoint of second line 22131 if( this.midOfThree( x1, x2, x4 ) === x4 ){ 22132 return [ x4, y4 ]; 22133 } 22134 22135 // Check start point of second line 22136 if( this.midOfThree( x1, x2, x3 ) === x3 ){ 22137 return [ x3, y3 ]; 22138 } 22139 22140 // Endpoint of first line 22141 if( this.midOfThree( x3, x4, x2 ) === x2 ){ 22142 return [ x2, y2 ]; 22143 } 22144 22145 return []; 22146 } else { 22147 22148 // Parallel, non-coincident 22149 return []; 22150 } 22151 } 22152}; 22153 22154math.polygonIntersectLine = function( 22155 x, y, basePoints, centerX, centerY, width, height, padding ){ 22156 22157 var intersections = []; 22158 var intersection; 22159 22160 var transformedPoints = new Array( basePoints.length ); 22161 22162 for( var i = 0; i < transformedPoints.length / 2; i++ ){ 22163 transformedPoints[ i * 2] = basePoints[ i * 2] * width + centerX; 22164 transformedPoints[ i * 2 + 1] = basePoints[ i * 2 + 1] * height + centerY; 22165 } 22166 22167 var points; 22168 22169 if( padding > 0 ){ 22170 var expandedLineSet = math.expandPolygon( 22171 transformedPoints, 22172 -padding ); 22173 22174 points = math.joinLines( expandedLineSet ); 22175 } else { 22176 points = transformedPoints; 22177 } 22178 // var points = transformedPoints; 22179 22180 var currentX, currentY, nextX, nextY; 22181 22182 for( var i = 0; i < points.length / 2; i++ ){ 22183 22184 currentX = points[ i * 2]; 22185 currentY = points[ i * 2 + 1]; 22186 22187 if( i < points.length / 2 - 1 ){ 22188 nextX = points[ (i + 1) * 2]; 22189 nextY = points[ (i + 1) * 2 + 1]; 22190 } else { 22191 nextX = points[0]; 22192 nextY = points[1]; 22193 } 22194 22195 intersection = this.finiteLinesIntersect( 22196 x, y, centerX, centerY, 22197 currentX, currentY, 22198 nextX, nextY ); 22199 22200 if( intersection.length !== 0 ){ 22201 intersections.push( intersection[0], intersection[1] ); 22202 } 22203 } 22204 22205 return intersections; 22206}; 22207 22208math.shortenIntersection = function( 22209 intersection, offset, amount ){ 22210 22211 var disp = [ intersection[0] - offset[0], intersection[1] - offset[1] ]; 22212 22213 var length = Math.sqrt( disp[0] * disp[0] + disp[1] * disp[1] ); 22214 22215 var lenRatio = (length - amount) / length; 22216 22217 if( lenRatio < 0 ){ 22218 lenRatio = 0.00001; 22219 } 22220 22221 return [ offset[0] + lenRatio * disp[0], offset[1] + lenRatio * disp[1] ]; 22222}; 22223 22224math.generateUnitNgonPointsFitToSquare = function( sides, rotationRadians ){ 22225 var points = math.generateUnitNgonPoints( sides, rotationRadians ); 22226 points = math.fitPolygonToSquare( points ); 22227 22228 return points; 22229}; 22230 22231math.fitPolygonToSquare = function( points ){ 22232 var x, y; 22233 var sides = points.length / 2; 22234 var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; 22235 22236 for( var i = 0; i < sides; i++ ){ 22237 x = points[2 * i ]; 22238 y = points[2 * i + 1]; 22239 22240 minX = Math.min( minX, x ); 22241 maxX = Math.max( maxX, x ); 22242 minY = Math.min( minY, y ); 22243 maxY = Math.max( maxY, y ); 22244 } 22245 22246 // stretch factors 22247 var sx = 2 / (maxX - minX); 22248 var sy = 2 / (maxY - minY); 22249 22250 for( var i = 0; i < sides; i++ ){ 22251 x = points[2 * i ] = points[2 * i ] * sx; 22252 y = points[2 * i + 1] = points[2 * i + 1] * sy; 22253 22254 minX = Math.min( minX, x ); 22255 maxX = Math.max( maxX, x ); 22256 minY = Math.min( minY, y ); 22257 maxY = Math.max( maxY, y ); 22258 } 22259 22260 if( minY < -1 ){ 22261 for( var i = 0; i < sides; i++ ){ 22262 y = points[2 * i + 1] = points[2 * i + 1] + (-1 - minY); 22263 } 22264 } 22265 22266 return points; 22267}; 22268 22269math.generateUnitNgonPoints = function( sides, rotationRadians ){ 22270 22271 var increment = 1.0 / sides * 2 * Math.PI; 22272 var startAngle = sides % 2 === 0 ? 22273 Math.PI / 2.0 + increment / 2.0 : Math.PI / 2.0; 22274 // console.log(nodeShapes['square']); 22275 startAngle += rotationRadians; 22276 22277 var points = new Array( sides * 2 ); 22278 22279 var currentAngle, x, y; 22280 for( var i = 0; i < sides; i++ ){ 22281 currentAngle = i * increment + startAngle; 22282 22283 x = points[2 * i ] = Math.cos( currentAngle );// * (1 + i/2); 22284 y = points[2 * i + 1] = Math.sin( -currentAngle );// * (1 + i/2); 22285 } 22286 22287 return points; 22288}; 22289 22290math.getRoundRectangleRadius = function( width, height ){ 22291 22292 // Set the default radius, unless half of width or height is smaller than default 22293 return Math.min( width / 4, height / 4, 8 ); 22294}; 22295 22296module.exports = math; 22297 22298},{}],86:[function(_dereq_,module,exports){ 22299/*!
22300Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable 22301Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com) 22302Licensed under The MIT License (http://opensource.org/licenses/MIT) 22303*/ 22304 22305'use strict'; 22306 22307/* promise states [Promises/A+ 2.1] */ 22308var STATE_PENDING = 0; /* [Promises/A+ 2.1.1] */ 22309var STATE_FULFILLED = 1; /* [Promises/A+ 2.1.2] */ 22310var STATE_REJECTED = 2; /* [Promises/A+ 2.1.3] */ 22311 22312/* promise object constructor */ 22313var api = function( executor ){ 22314 /* optionally support non-constructor/plain-function call */ 22315 if( !(this instanceof api) ) 22316 return new api( executor ); 22317 22318 /* initialize object */ 22319 this.id = 'Thenable/1.0.7'; 22320 this.state = STATE_PENDING; /* initial state */ 22321 this.fulfillValue = undefined; /* initial value */ /* [Promises/A+ 1.3, 2.1.2.2] */ 22322 this.rejectReason = undefined; /* initial reason */ /* [Promises/A+ 1.5, 2.1.3.2] */ 22323 this.onFulfilled = []; /* initial handlers */ 22324 this.onRejected = []; /* initial handlers */ 22325 22326 /* provide optional information-hiding proxy */ 22327 this.proxy = { 22328 then: this.then.bind( this ) 22329 }; 22330 22331 /* support optional executor function */ 22332 if( typeof executor === 'function' ) 22333 executor.call( this, this.fulfill.bind( this ), this.reject.bind( this ) ); 22334}; 22335 22336/* promise API methods */ 22337api.prototype = { 22338 /* promise resolving methods */ 22339 fulfill: function( value ){ return deliver( this, STATE_FULFILLED, 'fulfillValue', value ); }, 22340 reject: function( value ){ return deliver( this, STATE_REJECTED, 'rejectReason', value ); }, 22341 22342 /* "The then Method" [Promises/A+ 1.1, 1.2, 2.2] */ 22343 then: function( onFulfilled, onRejected ){ 22344 var curr = this; 22345 var next = new api(); /* [Promises/A+ 2.2.7] */ 22346 curr.onFulfilled.push( 22347 resolver( onFulfilled, next, 'fulfill' ) ); /* [Promises/A+ 2.2.2/2.2.6] */ 22348 curr.onRejected.push( 22349 resolver( onRejected, next, 'reject' ) ); /* [Promises/A+ 2.2.3/2.2.6] */ 22350 execute( curr ); 22351 return next.proxy; /* [Promises/A+ 2.2.7, 3.3] */ 22352 } 22353}; 22354 22355/* deliver an action */ 22356var deliver = function( curr, state, name, value ){ 22357 if( curr.state === STATE_PENDING ){ 22358 curr.state = state; /* [Promises/A+ 2.1.2.1, 2.1.3.1] */ 22359 curr[ name ] = value; /* [Promises/A+ 2.1.2.2, 2.1.3.2] */ 22360 execute( curr ); 22361 } 22362 return curr; 22363}; 22364 22365/* execute all handlers */ 22366var execute = function( curr ){ 22367 if( curr.state === STATE_FULFILLED ) 22368 execute_handlers( curr, 'onFulfilled', curr.fulfillValue ); 22369 else if( curr.state === STATE_REJECTED ) 22370 execute_handlers( curr, 'onRejected', curr.rejectReason ); 22371}; 22372 22373/* execute particular set of handlers */ 22374var execute_handlers = function( curr, name, value ){ 22375 /* global setImmediate: true */ 22376 /* global setTimeout: true */ 22377 22378 /* short-circuit processing */ 22379 if( curr[ name ].length === 0 ) 22380 return; 22381 22382 /* iterate over all handlers, exactly once */ 22383 var handlers = curr[ name ]; 22384 curr[ name ] = []; /* [Promises/A+ 2.2.2.3, 2.2.3.3] */ 22385 var func = function(){ 22386 for( var i = 0; i < handlers.length; i++ ) 22387 handlers[ i ]( value ); /* [Promises/A+ 2.2.5] */ 22388 }; 22389 22390 /* execute procedure asynchronously */ /* [Promises/A+ 2.2.4, 3.1] */ 22391 if( typeof setImmediate === 'function' ) 22392 setImmediate( func ); 22393 else 22394 setTimeout( func, 0 ); 22395}; 22396 22397/* generate a resolver function */ 22398var resolver = function( cb, next, method ){ 22399 return function( value ){ 22400 if( typeof cb !== 'function' ) /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */ 22401 next[ method ].call( next, value ); /* [Promises/A+ 2.2.7.3, 2.2.7.4] */ 22402 else { 22403 var result;
22404 try { result = cb( value ); } /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */ 22405 catch( e ){ 22406 next.reject( e ); /* [Promises/A+ 2.2.7.2] */ 22407 return; 22408 } 22409 resolve( next, result ); /* [Promises/A+ 2.2.7.1] */ 22410 } 22411 }; 22412}; 22413 22414/* "Promise Resolution Procedure" */ /* [Promises/A+ 2.3] */ 22415var resolve = function( promise, x ){ 22416 /* sanity check arguments */ /* [Promises/A+ 2.3.1] */ 22417 if( promise === x || promise.proxy === x ){ 22418 promise.reject( new TypeError( 'cannot resolve promise with itself' ) ); 22419 return; 22420 } 22421 22422 /* surgically check for a "then" method 22423 (mainly to just call the "getter" of "then" only once) */ 22424 var then; 22425 if( (typeof x === 'object' && x !== null) || typeof x === 'function' ){ 22426 try { then = x.then; } /* [Promises/A+ 2.3.3.1, 3.5] */ 22427 catch( e ){ 22428 promise.reject( e ); /* [Promises/A+ 2.3.3.2] */ 22429 return; 22430 } 22431 } 22432 22433 /* handle own Thenables [Promises/A+ 2.3.2] 22434 and similar "thenables" [Promises/A+ 2.3.3] */ 22435 if( typeof then === 'function' ){ 22436 var resolved = false; 22437 try { 22438 /* call retrieved "then" method */ /* [Promises/A+ 2.3.3.3] */ 22439 then.call( x, 22440 /* resolvePromise */ /* [Promises/A+ 2.3.3.3.1] */ 22441 function( y ){ 22442 if( resolved ) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */ 22443 if( y === x ) /* [Promises/A+ 3.6] */ 22444 promise.reject( new TypeError( 'circular thenable chain' ) ); 22445 else 22446 resolve( promise, y ); 22447 }, 22448 22449 /* rejectPromise */ /* [Promises/A+ 2.3.3.3.2] */ 22450 function( r ){ 22451 if( resolved ) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */ 22452 promise.reject( r ); 22453 } 22454 ); 22455 } 22456 catch( e ){ 22457 if( !resolved ) /* [Promises/A+ 2.3.3.3.3] */ 22458 promise.reject( e ); /* [Promises/A+ 2.3.3.3.4] */ 22459 } 22460 return; 22461 } 22462 22463 /* handle other values */ 22464 promise.fulfill( x ); /* [Promises/A+ 2.3.4, 2.3.3.4] */ 22465}; 22466 22467// so we always have Promise.all() 22468api.all = function( ps ){ 22469 return new api(function( resolveAll, rejectAll ){ 22470 var vals = new Array( ps.length ); 22471 var doneCount = 0; 22472 22473 var fulfill = function( i, val ){ 22474 vals[ i ] = val; 22475 doneCount++; 22476 22477 if( doneCount === ps.length ){ 22478 resolveAll( vals ); 22479 } 22480 }; 22481 22482 for( var i = 0; i < ps.length; i++ ){ 22483 (function( i ){ 22484 var p = ps[i]; 22485 var isPromise = p != null && p.then != null; 22486 22487 if( isPromise ){ 22488 p.then( function( val ){ 22489 fulfill( i, val ); 22490 }, function( err ){ 22491 rejectAll( err ); 22492 } ); 22493 } else { 22494 var val = p; 22495 fulfill( i, val ); 22496 } 22497 })( i ); 22498 } 22499 22500 } ); 22501}; 22502 22503api.resolve = function( val ){ 22504 return new api(function( resolve, reject ){ resolve( val ); }); 22505}; 22506 22507api.reject = function( val ){ 22508 return new api(function( resolve, reject ){ reject( val ); }); 22509}; 22510 22511module.exports = typeof Promise !== 'undefined' ? Promise : api; // eslint-disable-line no-undef 22512 22513},{}],87:[function(_dereq_,module,exports){ 22514'use strict'; 22515 22516var is = _dereq_( './is' ); 22517var util = _dereq_( './util' ); 22518 22519var Selector = function( selector ){ 22520 22521 if( !(this instanceof Selector) ){ 22522 return new Selector( selector ); 22523 } 22524 22525 var self = this; 22526 22527 self._private = { 22528 selectorText: null, 22529 invalid: true 22530 }; 22531 22532 // storage for parsed queries 22533 var newQuery = function(){ 22534 return { 22535 classes: [], 22536 colonSelectors: [], 22537 data: [], 22538 group: null, 22539 ids: [], 22540 meta: [], 22541 22542 // fake selectors 22543 collection: null, // a collection to match against 22544 filter: null, // filter function 22545 22546 // these are defined in the upward direction rather than down (e.g. child) 22547 // because we need to go up in Selector.filter() 22548 parent: null, // parent query obj 22549 ancestor: null, // ancestor query obj 22550 subject: null, // defines subject in compound query (subject query obj; points to self if subject) 22551 22552 // use these only when subject has been defined 22553 child: null, 22554 descendant: null 22555 }; 22556 }; 22557 22558 if( !selector || ( is.string( selector ) && selector.match( /^\s*$/ ) ) ){ 22559 22560 self.length = 0; 22561 22562 } else if( selector === '*' || selector === 'edge' || selector === 'node' ){ 22563 22564 // make single, group-only selectors cheap to make and cheap to filter 22565 22566 self[0] = newQuery(); 22567 self[0].group = selector === '*' ? selector : selector + 's'; 22568 self[0].groupOnly = true;
22569 self._private.invalid = false; 22570 self._private.selectorText = selector; 22571 self.length = 1; 22572 22573 } else if( is.elementOrCollection( selector ) ){ 22574 22575 var collection = selector.collection(); 22576 22577 self[0] = newQuery(); 22578 self[0].collection = collection; 22579 self.length = 1; 22580 22581 } else if( is.fn( selector ) ){ 22582 22583 self[0] = newQuery(); 22584 self[0].filter = selector; 22585 self.length = 1; 22586 22587 } else if( is.string( selector ) ){ 22588 22589 // the current subject in the query 22590 var currentSubject = null; 22591 22592 // tokens in the query language 22593 var tokens = { 22594 metaChar: '[\\!\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]', // chars we need to escape in var names, etc 22595 comparatorOp: '=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=', // binary comparison op (used in data selectors) 22596 boolOp: '\\?|\\!|\\^', // boolean (unary) operators (used in data selectors) 22597 string: '"(?:\\\\"|[^"])+"' + '|' + "'(?:\\\\'|[^'])+'", // string literals (used in data selectors) -- doublequotes | singlequotes 22598 number: util.regex.number, // number literal (used in data selectors) --- e.g. 0.1234, 1234, 12e123 22599 meta: 'degree|indegree|outdegree', // allowed metadata fields (i.e. allowed functions to use from Collection) 22600 separator: '\\s*,\\s*', // queries are separated by commas, e.g. edge[foo = 'bar'], node.someClass 22601 descendant: '\\s+', 22602 child: '\\s+>\\s+', 22603 subject: '\\$' 22604 }; 22605 tokens.variable = '(?:[\\w-]|(?:\\\\' + tokens.metaChar + '))+'; // a variable name 22606 tokens.value = tokens.string + '|' + tokens.number; // a value literal, either a string or number 22607 tokens.className = tokens.variable; // a class name (follows variable conventions) 22608 tokens.id = tokens.variable; // an element id (follows variable conventions) 22609 22610 // when a token like a variable has escaped meta characters, we need to clean the backslashes out 22611 // so that values get compared properly in Selector.filter() 22612 var cleanMetaChars = function( str ){ 22613 return str.replace( new RegExp( '\\\\(' + tokens.metaChar + ')', 'g' ), function( match, $1, offset, original ){ 22614 return $1; 22615 } ); 22616 }; 22617 22618 // add @ variants to comparatorOp 22619 var ops = tokens.comparatorOp.split( '|' ); 22620 for( var i = 0; i < ops.length; i++ ){ 22621 var op = ops[ i ]; 22622 tokens.comparatorOp += '|@' + op; 22623 } 22624 22625 // add ! variants to comparatorOp 22626 var ops = tokens.comparatorOp.split( '|' ); 22627 for( var i = 0; i < ops.length; i++ ){ 22628 var op = ops[ i ]; 22629 22630 if( op.indexOf( '!' ) >= 0 ){ continue; } // skip ops that explicitly contain ! 22631 if( op === '=' ){ continue; } // skip = b/c != is explicitly defined 22632 22633 tokens.comparatorOp += '|\\!' + op; 22634 } 22635 22636 // NOTE: add new expression syntax here to have it recognised by the parser; 22637 // - a query contains all adjacent (i.e. no separator in between) expressions; 22638 // - the current query is stored in self[i] --- you can use the reference to `this` in the populate function; 22639 // - you need to check the query objects in Selector.filter() for it actually filter properly, but that's pretty straight forward 22640 // - when you add something here, also add to Selector.toString() 22641 var exprs = [ 22642 { 22643 name: 'group', 22644 query: true, 22645 regex: '(node|edge|\\*)', 22646 populate: function( group ){ 22647 this.group = group === '*' ? group : group + 's'; 22648 } 22649 }, 22650 22651 { 22652 name: 'state', 22653 query: true, 22654 // NB: if one colon selector is a substring of another from its start, place the longer one first 22655 // e.g. :foobar|:foo 22656 regex: '(:selected|:unselected|:locked|:unlocked|:visible|:hidden|:transparent|:grabbed|:free|:removed|:inside|:grabbable|:ungrabbable|:animated|:unanimated|:selectable|:unselectable|:orphan|:nonorphan|:parent|:child|:loop|:simple|:active|:inactive|:touch|:backgrounding|:nonbackgrounding)', 22657 populate: function( state ){ 22658 this.colonSelectors.push( state ); 22659 } 22660 }, 22661 22662 { 22663 name: 'id', 22664 query: true, 22665 regex: '\\#(' + tokens.id + ')', 22666 populate: function( id ){ 22667 this.ids.push( cleanMetaChars( id ) ); 22668 } 22669 }, 22670 22671 { 22672 name: 'className', 22673 query: true, 22674 regex: '\\.(' + tokens.className + ')', 22675 populate: function( className ){ 22676 this.classes.push( cleanMetaChars( className ) ); 22677 } 22678 }, 22679 22680 { 22681 name: 'dataExists', 22682 query: true, 22683 regex: '\\[\\s*(' + tokens.variable + ')\\s*\\]', 22684 populate: function( variable ){ 22685 this.data.push( { 22686 field: cleanMetaChars( variable ) 22687 } ); 22688 } 22689 }, 22690 22691 { 22692 name: 'dataCompare', 22693 query: true, 22694 regex: '\\[\\s*(' + tokens.variable + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.value + ')\\s*\\]', 22695 populate: function( variable, comparatorOp, value ){ 22696 var valueIsString = new RegExp( '^' + tokens.string + '$' ).exec( value ) != null; 22697 22698 if( valueIsString ){ 22699 value = value.substring( 1, value.length - 1 ); 22700 } else { 22701 value = parseFloat( value ); 22702 } 22703 22704 this.data.push( { 22705 field: cleanMetaChars( variable ), 22706 operator: comparatorOp, 22707 value: value 22708 } ); 22709 } 22710 }, 22711 22712 { 22713 name: 'dataBool', 22714 query: true, 22715 regex: '\\[\\s*(' + tokens.boolOp + ')\\s*(' + tokens.variable + ')\\s*\\]', 22716 populate: function( boolOp, variable ){ 22717 this.data.push( { 22718 field: cleanMetaChars( variable ), 22719 operator: boolOp 22720 } ); 22721 } 22722 }, 22723 22724 { 22725 name: 'metaCompare', 22726 query: true, 22727 regex: '\\[\\[\\s*(' + tokens.meta + ')\\s*(' + tokens.comparatorOp + ')\\s*(' + tokens.number + ')\\s*\\]\\]', 22728 populate: function( meta, comparatorOp, number ){ 22729 this.meta.push( { 22730 field: cleanMetaChars( meta ), 22731 operator: comparatorOp, 22732 value: parseFloat( number ) 22733 } ); 22734 } 22735 }, 22736 22737 { 22738 name: 'nextQuery', 22739 separator: true, 22740 regex: tokens.separator, 22741 populate: function(){
22742 // go on to next query 22743 self[ ++i ] = newQuery(); 22744 currentSubject = null; 22745 } 22746 }, 22747 22748 { 22749 name: 'child', 22750 separator: true, 22751 regex: tokens.child, 22752 populate: function(){ 22753 // this query is the parent of the following query 22754 var childQuery = newQuery(); 22755 childQuery.parent = this; 22756 childQuery.subject = currentSubject; 22757 22758 // we're now populating the child query with expressions that follow 22759 self[ i ] = childQuery; 22760 } 22761 }, 22762 22763 { 22764 name: 'descendant', 22765 separator: true, 22766 regex: tokens.descendant, 22767 populate: function(){ 22768 // this query is the ancestor of the following query 22769 var descendantQuery = newQuery(); 22770 descendantQuery.ancestor = this; 22771 descendantQuery.subject = currentSubject; 22772 22773 // we're now populating the descendant query with expressions that follow 22774 self[ i ] = descendantQuery; 22775 } 22776 }, 22777 22778 { 22779 name: 'subject', 22780 modifier: true, 22781 regex: tokens.subject, 22782 populate: function(){ 22783 if( currentSubject != null && this.subject != this ){ 22784 util.error( 'Redefinition of subject in selector `' + selector + '`' ); 22785 return false; 22786 } 22787 22788 currentSubject = this; 22789 this.subject = this; 22790 } 22791 22792 } 22793 ]; 22794 22795 self._private.selectorText = selector; 22796 var remaining = selector; 22797 var i = 0; 22798 22799 // of all the expressions, find the first match in the remaining text 22800 var consumeExpr = function( expectation ){ 22801 var expr; 22802 var match; 22803 var name; 22804 22805 for( var j = 0; j < exprs.length; j++ ){ 22806 var e = exprs[ j ]; 22807 var n = e.name; 22808 22809 // ignore this expression if it doesn't meet the expectation function 22810 if( is.fn( expectation ) && !expectation( n, e ) ){ continue; } 22811 22812 var m = remaining.match( new RegExp( '^' + e.regex ) ); 22813 22814 if( m != null ){ 22815 match = m; 22816 expr = e; 22817 name = n; 22818 22819 var consumed = m[0]; 22820 remaining = remaining.substring( consumed.length ); 22821 22822 break; // we've consumed one expr, so we can return now 22823 } 22824 } 22825 22826 return { 22827 expr: expr, 22828 match: match, 22829 name: name 22830 }; 22831 }; 22832 22833 // consume all leading whitespace 22834 var consumeWhitespace = function(){ 22835 var match = remaining.match( /^\s+/ ); 22836 22837 if( match ){ 22838 var consumed = match[0]; 22839 remaining = remaining.substring( consumed.length ); 22840 } 22841 }; 22842 22843 self[0] = newQuery(); // get started 22844 22845 consumeWhitespace(); // get rid of leading whitespace 22846 for( ;; ){ 22847 var check = consumeExpr(); 22848 22849 if( check.expr == null ){ 22850 util.error( 'The selector `' + selector + '`is invalid' ); 22851 return; 22852 } else { 22853 var args = []; 22854 for( var j = 1; j < check.match.length; j++ ){ 22855 args.push( check.match[ j ] ); 22856 } 22857 22858 // let the token populate the selector object (i.e. in self[i]) 22859 var ret = check.expr.populate.apply( self[ i ], args ); 22860 22861 if( ret === false ){ return; } // exit if population failed 22862 } 22863 22864 // we're done when there's nothing left to parse 22865 if( remaining.match( /^\s*$/ ) ){ 22866 break; 22867 } 22868 } 22869 22870 self.length = i + 1; 22871 22872 // adjust references for subject 22873 for( var j = 0; j < self.length; j++ ){ 22874 var query = self[ j ]; 22875 22876 if( query.subject != null ){ 22877 // go up the tree until we reach the subject 22878 for( ;; ){ 22879 if( query.subject == query ){ break; } // done if subject is self 22880 22881 if( query.parent != null ){ // swap parent/child reference 22882 var parent = query.parent; 22883 var child = query; 22884 22885 child.parent = null; 22886 parent.child = child; 22887 22888 query = parent; // go up the tree 22889 } else if( query.ancestor != null ){ // swap ancestor/descendant 22890 var ancestor = query.ancestor; 22891 var descendant = query; 22892 22893 descendant.ancestor = null;
22894 ancestor.descendant = descendant; 22895 22896 query = ancestor; // go up the tree 22897 } else { 22898 util.error( 'When adjusting references for the selector `' + query + '`, neither parent nor ancestor was found' ); 22899 break; 22900 } 22901 } // for 22902 22903 self[ j ] = query.subject; // subject should be the root query 22904 } // if 22905 } // for 22906 22907 } else { 22908 util.error( 'A selector must be created from a string; found ' + selector ); 22909 return; 22910 } 22911 22912 self._private.invalid = false; 22913 22914}; 22915 22916var selfn = Selector.prototype; 22917 22918selfn.size = function(){ 22919 return this.length; 22920}; 22921 22922selfn.eq = function( i ){ 22923 return this[ i ]; 22924}; 22925 22926var queryMatches = function( query, ele ){ 22927 var ele_p = ele._private; 22928 22929 // make single group-only selectors really cheap to check since they're the most common ones 22930 if( query.groupOnly ){ 22931 return query.group === '*' || query.group === ele_p.group; 22932 } 22933 22934 // check group 22935 if( query.group != null && query.group != '*' && query.group != ele_p.group ){ 22936 return false; 22937 } 22938 22939 var cy = ele.cy(); 22940 22941 // check colon selectors 22942 var allColonSelectorsMatch = true; 22943 for( var k = 0; k < query.colonSelectors.length; k++ ){ 22944 var sel = query.colonSelectors[ k ]; 22945 22946 switch( sel ){ 22947 case ':selected': 22948 allColonSelectorsMatch = ele.selected(); 22949 break; 22950 case ':unselected': 22951 allColonSelectorsMatch = !ele.selected(); 22952 break; 22953 case ':selectable': 22954 allColonSelectorsMatch = ele.selectable(); 22955 break; 22956 case ':unselectable': 22957 allColonSelectorsMatch = !ele.selectable(); 22958 break; 22959 case ':locked': 22960 allColonSelectorsMatch = ele.locked(); 22961 break; 22962 case ':unlocked': 22963 allColonSelectorsMatch = !ele.locked(); 22964 break; 22965 case ':visible': 22966 allColonSelectorsMatch = ele.visible(); 22967 break; 22968 case ':hidden': 22969 allColonSelectorsMatch = !ele.visible(); 22970 break; 22971 case ':transparent': 22972 allColonSelectorsMatch = ele.transparent(); 22973 break; 22974 case ':grabbed': 22975 allColonSelectorsMatch = ele.grabbed(); 22976 break; 22977 case ':free': 22978 allColonSelectorsMatch = !ele.grabbed(); 22979 break; 22980 case ':removed': 22981 allColonSelectorsMatch = ele.removed(); 22982 break; 22983 case ':inside': 22984 allColonSelectorsMatch = !ele.removed(); 22985 break; 22986 case ':grabbable': 22987 allColonSelectorsMatch = ele.grabbable(); 22988 break; 22989 case ':ungrabbable': 22990 allColonSelectorsMatch = !ele.grabbable(); 22991 break; 22992 case ':animated': 22993 allColonSelectorsMatch = ele.animated(); 22994 break; 22995 case ':unanimated': 22996 allColonSelectorsMatch = !ele.animated(); 22997 break; 22998 case ':parent': 22999 allColonSelectorsMatch = ele.isNode() && ele.children().nonempty(); 23000 break; 23001 case ':child': 23002 case ':nonorphan': 23003 allColonSelectorsMatch = ele.isNode() && ele.parent().nonempty(); 23004 break; 23005 case ':orphan': 23006 allColonSelectorsMatch = ele.isNode() && ele.parent().empty(); 23007 break; 23008 case ':loop': 23009 allColonSelectorsMatch = ele.isEdge() && ele.data( 'source' ) === ele.data( 'target' ); 23010 break; 23011 case ':simple': 23012 allColonSelectorsMatch = ele.isEdge() && ele.data( 'source' ) !== ele.data( 'target' ); 23013 break; 23014 case ':active': 23015 allColonSelectorsMatch = ele.active(); 23016 break; 23017 case ':inactive': 23018 allColonSelectorsMatch = !ele.active(); 23019 break; 23020 case ':touch': 23021 allColonSelectorsMatch = is.touch(); 23022 break; 23023 case ':backgrounding': 23024 allColonSelectorsMatch = ele.backgrounding(); 23025 break; 23026 case ':nonbackgrounding': 23027 allColonSelectorsMatch = !ele.backgrounding(); 23028 break; 23029 } 23030 23031 if( !allColonSelectorsMatch ) break; 23032 } 23033 if( !allColonSelectorsMatch ) return false; 23034 23035 // check id 23036 var allIdsMatch = true; 23037 for( var k = 0; k < query.ids.length; k++ ){ 23038 var id = query.ids[ k ]; 23039 var actualId = ele_p.data.id; 23040 23041 allIdsMatch = allIdsMatch && (id == actualId); 23042 23043 if( !allIdsMatch ) break; 23044 } 23045 if( !allIdsMatch ) return false;
23046 23047 // check classes 23048 var allClassesMatch = true; 23049 for( var k = 0; k < query.classes.length; k++ ){ 23050 var cls = query.classes[ k ]; 23051 23052 allClassesMatch = allClassesMatch && ele.hasClass( cls ); 23053 23054 if( !allClassesMatch ) break; 23055 } 23056 if( !allClassesMatch ) return false; 23057 23058 // generic checking for data/metadata 23059 var operandsMatch = function( params ){ 23060 var allDataMatches = true; 23061 for( var k = 0; k < query[ params.name ].length; k++ ){ 23062 var data = query[ params.name ][ k ]; 23063 var operator = data.operator; 23064 var value = data.value; 23065 var field = data.field; 23066 var matches; 23067 23068 if( operator != null && value != null ){ 23069 23070 var fieldVal = params.fieldValue( field ); 23071 var fieldStr = !is.string( fieldVal ) && !is.number( fieldVal ) ? '' : '' + fieldVal; 23072 var valStr = '' + value; 23073 23074 var caseInsensitive = false; 23075 if( operator.indexOf( '@' ) >= 0 ){ 23076 fieldStr = fieldStr.toLowerCase(); 23077 valStr = valStr.toLowerCase(); 23078 23079 operator = operator.replace( '@', '' ); 23080 caseInsensitive = true; 23081 } 23082 23083 var notExpr = false; 23084 if( operator.indexOf( '!' ) >= 0 ){ 23085 operator = operator.replace( '!', '' ); 23086 notExpr = true; 23087 } 23088 23089 // if we're doing a case insensitive comparison, then we're using a STRING comparison 23090 // even if we're comparing numbers 23091 if( caseInsensitive ){ 23092 value = valStr.toLowerCase(); 23093 fieldVal = fieldStr.toLowerCase(); 23094 } 23095 23096 var isIneqCmp = false; 23097 23098 switch( operator ){ 23099 case '*=': 23100 matches = fieldStr.indexOf( valStr ) >= 0; 23101 break; 23102 case '$=': 23103 matches = fieldStr.indexOf( valStr, fieldStr.length - valStr.length ) >= 0; 23104 break; 23105 case '^=': 23106 matches = fieldStr.indexOf( valStr ) === 0; 23107 break; 23108 case '=': 23109 matches = fieldVal === value; 23110 break; 23111 case '>': 23112 isIneqCmp = true; 23113 matches = fieldVal > value; 23114 break; 23115 case '>=': 23116 isIneqCmp = true; 23117 matches = fieldVal >= value; 23118 break; 23119 case '<': 23120 isIneqCmp = true; 23121 matches = fieldVal < value; 23122 break; 23123 case '<=': 23124 isIneqCmp = true; 23125 matches = fieldVal <= value; 23126 break; 23127 default: 23128 matches = false; 23129 break; 23130 } 23131 23132 // apply the not op, but null vals for inequalities should always stay non-matching 23133 if( notExpr && ( fieldVal != null || !isIneqCmp ) ){ 23134 matches = !matches; 23135 } 23136 } else if( operator != null ){ 23137 switch( operator ){ 23138 case '?': 23139 matches = params.fieldTruthy( field ); 23140 break; 23141 case '!': 23142 matches = !params.fieldTruthy( field ); 23143 break; 23144 case '^': 23145 matches = params.fieldUndefined( field ); 23146 break; 23147 } 23148 } else { 23149 matches = !params.fieldUndefined( field ); 23150 } 23151 23152 if( !matches ){ 23153 allDataMatches = false; 23154 break; 23155 } 23156 } // for 23157 23158 return allDataMatches; 23159 }; // operandsMatch 23160 23161 // check data matches 23162 var allDataMatches = operandsMatch( { 23163 name: 'data', 23164 fieldValue: function( field ){ 23165 return ele_p.data[ field ]; 23166 }, 23167 fieldUndefined: function( field ){ 23168 return ele_p.data[ field ] === undefined; 23169 }, 23170 fieldTruthy: function( field ){ 23171 if( ele_p.data[ field ] ){ 23172 return true; 23173 } 23174 return false; 23175 } 23176 } ); 23177 23178 if( !allDataMatches ){ 23179 return false; 23180 } 23181 23182 // check metadata matches 23183 var allMetaMatches = operandsMatch( { 23184 name: 'meta', 23185 fieldValue: function( field ){ 23186 return ele[ field ](); 23187 }, 23188 fieldUndefined: function( field ){ 23189 return ele[ field ]() == null; 23190 }, 23191 fieldTruthy: function( field ){ 23192 if( ele[ field ]() ){ 23193 return true; 23194 } 23195 return false; 23196 } 23197 } ); 23198 23199 if( !allMetaMatches ){ 23200 return false; 23201 } 23202 23203 // check collection 23204 if( query.collection != null ){ 23205 var matchesAny = query.collection.hasElementWithId( ele.id() ); 23206 23207 if( !matchesAny ){ 23208 return false;
23209 } 23210 } 23211 23212 // check filter function 23213 if( query.filter != null && ele.collection().filter( query.filter ).size() === 0 ){ 23214 return false; 23215 } 23216 23217 // check parent/child relations 23218 var confirmRelations = function( query, eles ){ 23219 if( query != null ){ 23220 var matches = false; 23221 23222 if( !cy.hasCompoundNodes() ){ 23223 return false; 23224 } 23225 23226 eles = eles(); // save cycles if query == null 23227 23228 // query must match for at least one element (may be recursive) 23229 for( var i = 0; i < eles.length; i++ ){ 23230 if( queryMatches( query, eles[ i ] ) ){ 23231 matches = true; 23232 break; 23233 } 23234 } 23235 23236 return matches; 23237 } else { 23238 return true; 23239 } 23240 }; 23241 23242 if( !confirmRelations( query.parent, function(){ 23243 return ele.parent(); 23244 } ) ){ return false; } 23245 23246 if( !confirmRelations( query.ancestor, function(){ 23247 return ele.parents(); 23248 } ) ){ return false; } 23249 23250 if( !confirmRelations( query.child, function(){ 23251 return ele.children(); 23252 } ) ){ return false; } 23253 23254 if( !confirmRelations( query.descendant, function(){ 23255 return ele.descendants(); 23256 } ) ){ return false; } 23257 23258 // we've reached the end, so we've matched everything for this query 23259 return true; 23260}; // queryMatches 23261 23262// filter an existing collection 23263selfn.filter = function( collection ){ 23264 var self = this; 23265 var cy = collection.cy(); 23266 23267 // don't bother trying if it's invalid 23268 if( self._private.invalid ){ 23269 return cy.collection(); 23270 } 23271 23272 var selectorFunction = function( i, element ){ 23273 for( var j = 0; j < self.length; j++ ){ 23274 var query = self[ j ]; 23275 23276 if( queryMatches( query, element ) ){ 23277 return true; 23278 } 23279 } 23280 23281 return false; 23282 }; 23283 23284 if( self._private.selectorText == null ){ 23285 selectorFunction = function(){ return true; }; 23286 } 23287 23288 var filteredCollection = collection.filter( selectorFunction ); 23289 23290 return filteredCollection; 23291}; // filter 23292 23293// does selector match a single element? 23294selfn.matches = function( ele ){ 23295 var self = this; 23296 23297 // don't bother trying if it's invalid 23298 if( self._private.invalid ){ 23299 return false; 23300 } 23301 23302 for( var j = 0; j < self.length; j++ ){ 23303 var query = self[ j ]; 23304 23305 if( queryMatches( query, ele ) ){ 23306 return true; 23307 } 23308 } 23309 23310 return false; 23311}; // filter 23312 23313// ith query to string 23314selfn.toString = selfn.selector = function(){ 23315 23316 var str = ''; 23317 23318 var clean = function( obj ){ 23319 if( obj == null ){ 23320 return ''; 23321 } else { 23322 return obj; 23323 } 23324 }; 23325 23326 var cleanVal = function( val ){ 23327 if( is.string( val ) ){ 23328 return '"' + val + '"'; 23329 } else { 23330 return clean( val ); 23331 } 23332 }; 23333 23334 var space = function( val ){ 23335 return ' ' + val + ' '; 23336 }; 23337 23338 var queryToString = function( query ){ 23339 var str = ''; 23340 23341 if( query.subject === query ){ 23342 str += '$'; 23343 } 23344 23345 var group = clean( query.group ); 23346 str += group.substring( 0, group.length - 1 ); 23347 23348 for( var j = 0; j < query.data.length; j++ ){ 23349 var data = query.data[ j ]; 23350 23351 if( data.value ){ 23352 str += '[' + data.field + space( clean( data.operator ) ) + cleanVal( data.value ) + ']'; 23353 } else { 23354 str += '[' + clean( data.operator ) + data.field + ']'; 23355 } 23356 } 23357 23358 for( var j = 0; j < query.meta.length; j++ ){ 23359 var meta = query.meta[ j ]; 23360 str += '[[' + meta.field + space( clean( meta.operator ) ) + cleanVal( meta.value ) + ']]'; 23361 } 23362 23363 for( var j = 0; j < query.colonSelectors.length; j++ ){ 23364 var sel = query.colonSelectors[ i ]; 23365 str += sel; 23366 } 23367 23368 for( var j = 0; j < query.ids.length; j++ ){ 23369 var sel = '#' + query.ids[ i ]; 23370 str += sel; 23371 } 23372 23373 for( var j = 0; j < query.classes.length; j++ ){ 23374 var sel = '.' + query.classes[ j ]; 23375 str += sel; 23376 } 23377 23378 if( query.parent != null ){ 23379 str = queryToString( query.parent ) + ' > ' + str; 23380 } 23381 23382 if( query.ancestor != null ){ 23383 str = queryToString( query.ancestor ) + ' ' + str; 23384 } 23385 23386 if( query.child != null ){ 23387 str += ' > ' + queryToString( query.child ); 23388 } 23389 23390 if( query.descendant != null ){ 23391 str += ' ' + queryToString( query.descendant ); 23392 } 23393 23394 return str; 23395 }; 23396 23397 for( var i = 0; i < this.length; i++ ){ 23398 var query = this[ i ]; 23399 23400 str += queryToString( query ); 23401 23402 if( this.length > 1 && i < this.length - 1 ){ 23403 str += ', '; 23404 } 23405 } 23406 23407 return str; 23408}; 23409 23410module.exports = Selector; 23411 23412},{"./is":83,"./util":100}],88:[function(_dereq_,module,exports){ 23413'use strict'; 23414 23415var util = _dereq_( '../util' ); 23416var is = _dereq_( '../is' ); 23417 23418var styfn = {}; 23419 23420// (potentially expensive calculation) 23421// apply the style to the element based on 23422// - its bypass 23423// - what selectors match it 23424styfn.apply = function( eles ){ 23425 var self = this; 23426 var _p = self._private; 23427 23428 if( _p.newStyle ){ // clear style caches 23429 _p.contextStyles = {}; 23430 _p.propDiffs = {}; 23431 23432 self.cleanElements( eles, true ); 23433 } 23434 23435 for( var ie = 0; ie < eles.length; ie++ ){ 23436 var ele = eles[ ie ]; 23437 23438 var cxtMeta = self.getContextMeta( ele ); 23439 var cxtStyle = self.getContextStyle( cxtMeta ); 23440 var app = self.applyContextStyle( cxtMeta, cxtStyle, ele ); 23441 23442 self.updateTransitions( ele, app.diffProps ); 23443 self.updateStyleHints( ele ); 23444 23445 } // for elements 23446 23447 _p.newStyle = false;
23448}; 23449 23450styfn.getPropertiesDiff = function( oldCxtKey, newCxtKey ){ 23451 var self = this; 23452 var cache = self._private.propDiffs = self._private.propDiffs || {}; 23453 var dualCxtKey = oldCxtKey + '-' + newCxtKey; 23454 var cachedVal = cache[ dualCxtKey ]; 23455 23456 if( cachedVal ){ 23457 return cachedVal; 23458 } 23459 23460 var diffProps = []; 23461 var addedProp = {}; 23462 23463 for( var i = 0; i < self.length; i++ ){ 23464 var cxt = self[ i ]; 23465 var oldHasCxt = oldCxtKey[ i ] === 't'; 23466 var newHasCxt = newCxtKey[ i ] === 't'; 23467 var cxtHasDiffed = oldHasCxt !== newHasCxt; 23468 var cxtHasMappedProps = cxt.mappedProperties.length > 0; 23469 23470 if( cxtHasDiffed || cxtHasMappedProps ){ 23471 var props; 23472 23473 if( cxtHasDiffed && cxtHasMappedProps ){ 23474 props = cxt.properties; // suffices b/c mappedProperties is a subset of properties 23475 } else if( cxtHasDiffed ){ 23476 props = cxt.properties; // need to check them all 23477 } else if( cxtHasMappedProps ){ 23478 props = cxt.mappedProperties; // only need to check mapped 23479 } 23480 23481 for( var j = 0; j < props.length; j++ ){ 23482 var prop = props[ j ]; 23483 var name = prop.name; 23484 23485 // if a later context overrides this property, then the fact that this context has switched/diffed doesn't matter 23486 // (semi expensive check since it makes this function O(n^2) on context length, but worth it since overall result 23487 // is cached) 23488 var laterCxtOverrides = false; 23489 for( var k = i + 1; k < self.length; k++ ){ 23490 var laterCxt = self[ k ]; 23491 var hasLaterCxt = newCxtKey[ k ] === 't'; 23492 23493 if( !hasLaterCxt ){ continue; } // can't override unless the context is active 23494 23495 laterCxtOverrides = laterCxt.properties[ prop.name ] != null; 23496 23497 if( laterCxtOverrides ){ break; } // exit early as long as one later context overrides 23498 } 23499 23500 if( !addedProp[ name ] && !laterCxtOverrides ){ 23501 addedProp[ name ] = true; 23502 diffProps.push( name ); 23503 } 23504 } // for props 23505 } // if 23506 23507 } // for contexts 23508 23509 cache[ dualCxtKey ] = diffProps; 23510 return diffProps; 23511}; 23512 23513styfn.getContextMeta = function( ele ){ 23514 var self = this; 23515 var cxtKey = ''; 23516 var diffProps; 23517 var prevKey = ele._private.styleCxtKey || ''; 23518 23519 if( self._private.newStyle ){ 23520 prevKey = ''; // since we need to apply all style if a fresh stylesheet 23521 } 23522 23523 // get the cxt key 23524 for( var i = 0; i < self.length; i++ ){ 23525 var context = self[ i ]; 23526 var contextSelectorMatches = context.selector && context.selector.matches( ele ); // NB: context.selector may be null for 'core' 23527 23528 if( contextSelectorMatches ){ 23529 cxtKey += 't'; 23530 } else { 23531 cxtKey += 'f'; 23532 } 23533 } // for context 23534 23535 diffProps = self.getPropertiesDiff( prevKey, cxtKey ); 23536 23537 ele._private.styleCxtKey = cxtKey; 23538 23539 return { 23540 key: cxtKey, 23541 diffPropNames: diffProps 23542 }; 23543}; 23544 23545// gets a computed ele style object based on matched contexts 23546styfn.getContextStyle = function( cxtMeta ){ 23547 var cxtKey = cxtMeta.key; 23548 var self = this; 23549 var cxtStyles = this._private.contextStyles = this._private.contextStyles || {}; 23550 23551 // if already computed style, returned cached copy 23552 if( cxtStyles[ cxtKey ] ){ return cxtStyles[ cxtKey ]; } 23553 23554 var style = { 23555 _private: { 23556 key: cxtKey 23557 } 23558 }; 23559 23560 for( var i = 0; i < self.length; i++ ){ 23561 var cxt = self[ i ]; 23562 var hasCxt = cxtKey[ i ] === 't'; 23563 23564 if( !hasCxt ){ continue; } 23565 23566 for( var j = 0; j < cxt.properties.length; j++ ){ 23567 var prop = cxt.properties[ j ]; 23568 var styProp = style[ prop.name ] = prop; 23569 23570 styProp.context = cxt; 23571 } 23572 } 23573 23574 cxtStyles[ cxtKey ] = style; 23575 return style; 23576}; 23577 23578styfn.applyContextStyle = function( cxtMeta, cxtStyle, ele ){ 23579 var self = this; 23580 var diffProps = cxtMeta.diffPropNames; 23581 var retDiffProps = {}; 23582 23583 for( var i = 0; i < diffProps.length; i++ ){ 23584 var diffPropName = diffProps[ i ]; 23585 var cxtProp = cxtStyle[ diffPropName ]; 23586 var eleProp = ele.pstyle( diffPropName ); 23587 23588 if( !cxtProp ){ // no context prop means delete
23589 if( !eleProp ){ 23590 continue; // no existing prop means nothing needs to be removed 23591 // nb affects initial application on mapped values like control-point-distances 23592 } else if( eleProp.bypass ){ 23593 cxtProp = { name: diffPropName, deleteBypassed: true }; 23594 } else { 23595 cxtProp = { name: diffPropName, delete: true }; 23596 } 23597 } 23598 23599 // save cycles when the context prop doesn't need to be applied 23600 if( eleProp === cxtProp ){ continue; } 23601 23602 var retDiffProp = retDiffProps[ diffPropName ] = { 23603 prev: eleProp 23604 }; 23605 23606 self.applyParsedProperty( ele, cxtProp ); 23607 23608 retDiffProp.next = ele.pstyle( diffPropName ); 23609 23610 if( retDiffProp.next && retDiffProp.next.bypass ){ 23611 retDiffProp.next = retDiffProp.next.bypassed; 23612 } 23613 } 23614 23615 return { 23616 diffProps: retDiffProps 23617 }; 23618}; 23619 23620styfn.updateStyleHints = function(ele){ 23621 var _p = ele._private; 23622 var self = this; 23623 23624 if( ele.removed() ){ return; } 23625 23626 // set whether has pie or not; for greater efficiency 23627 var hasPie = false; 23628 if( _p.group === 'nodes' ){ 23629 for( var i = 1; i <= self.pieBackgroundN; i++ ){ // 1..N 23630 var size = ele.pstyle( 'pie-' + i + '-background-size' ).value; 23631 23632 if( size > 0 ){ 23633 hasPie = true; 23634 break; 23635 } 23636 } 23637 } 23638 23639 _p.hasPie = hasPie; 23640 23641 var transform = ele.pstyle( 'text-transform' ).strValue; 23642 var content = ele.pstyle( 'label' ).strValue; 23643 var srcContent = ele.pstyle( 'source-label' ).strValue; 23644 var tgtContent = ele.pstyle( 'target-label' ).strValue; 23645 var fStyle = ele.pstyle( 'font-style' ).strValue; 23646 var size = ele.pstyle( 'font-size' ).pfValue + 'px'; 23647 var family = ele.pstyle( 'font-family' ).strValue; 23648 // var variant = style['font-variant'].strValue; 23649 var weight = ele.pstyle( 'font-weight' ).strValue; 23650 var valign = ele.pstyle( 'text-valign' ).strValue; 23651 var halign = ele.pstyle( 'text-valign' ).strValue; 23652 var oWidth = ele.pstyle( 'text-outline-width' ).pfValue; 23653 var wrap = ele.pstyle( 'text-wrap' ).strValue; 23654 var wrapW = ele.pstyle( 'text-max-width' ).pfValue; 23655 var labelStyleKey = fStyle + '$' + size + '$' + family + '$' + weight + '$' + transform + '$' + valign + '$' + halign + '$' + oWidth + '$' + wrap + '$' + wrapW; 23656 _p.labelStyleKey = labelStyleKey; 23657 _p.sourceLabelKey = labelStyleKey + '$' + srcContent; 23658 _p.targetLabelKey = labelStyleKey + '$' + tgtContent; 23659 _p.labelKey = labelStyleKey + '$' + content; 23660 _p.fontKey = fStyle + '$' + weight + '$' + size + '$' + family; 23661 23662 _p.styleKey = Date.now(); 23663}; 23664 23665// apply a property to the style (for internal use) 23666// returns whether application was successful 23667// 23668// now, this function flattens the property, and here's how: 23669// 23670// for parsedProp:{ bypass: true, deleteBypass: true } 23671// no property is generated, instead the bypass property in the 23672// element's style is replaced by what's pointed to by the `bypassed` 23673// field in the bypass property (i.e. restoring the property the 23674// bypass was overriding) 23675// 23676// for parsedProp:{ mapped: truthy } 23677// the generated flattenedProp:{ mapping: prop } 23678// 23679// for parsedProp:{ bypass: true } 23680// the generated flattenedProp:{ bypassed: parsedProp } 23681styfn.applyParsedProperty = function( ele, parsedProp ){ 23682 var self = this; 23683 var prop = parsedProp; 23684 var style = ele._private.style; 23685 var fieldVal, flatProp; 23686 var types = self.types; 23687 var type = self.properties[ prop.name ].type; 23688 var propIsBypass = prop.bypass; 23689 var origProp = style[ prop.name ]; 23690 var origPropIsBypass = origProp && origProp.bypass; 23691 var _p = ele._private; 23692 23693 // edges connected to compound nodes can not be haystacks 23694 if( 23695 parsedProp.name === 'curve-style' 23696 && parsedProp.value === 'haystack' 23697 && ele.isEdge() 23698 && ( ele.isLoop() || ele.source().isParent() || ele.target().isParent() ) 23699 ){ 23700 prop = parsedProp = this.parse( parsedProp.name, 'bezier', propIsBypass ); 23701 } 23702 23703 if( prop.delete ){ // delete the property and use the default value on falsey value 23704 style[ prop.name ] = undefined; 23705 23706 return true; 23707 } 23708 23709 if( prop.deleteBypassed ){ // delete the property that the 23710 if( !origProp ){ 23711 return true; // can't delete if no prop 23712 23713 } else if( origProp.bypass ){ // delete bypassed 23714 origProp.bypassed = undefined; 23715 return true; 23716 23717 } else { 23718 return false; // we're unsuccessful deleting the bypassed 23719 } 23720 } 23721 23722 // check if we need to delete the current bypass 23723 if( prop.deleteBypass ){ // then this property is just here to indicate we need to delete
23724 if( !origProp ){ 23725 return true; // property is already not defined 23726 23727 } else if( origProp.bypass ){ // then replace the bypass property with the original 23728 // because the bypassed property was already applied (and therefore parsed), we can just replace it (no reapplying necessary) 23729 style[ prop.name ] = origProp.bypassed; 23730 return true; 23731 23732 } else { 23733 return false; // we're unsuccessful deleting the bypass 23734 } 23735 } 23736 23737 var printMappingErr = function(){ 23738 util.error( 'Do not assign mappings to elements without corresponding data (e.g. ele `' + ele.id() + '` for property `' + prop.name + '` with data field `' + prop.field + '`); try a `[' + prop.field + ']` selector to limit scope to elements with `' + prop.field + '` defined' ); 23739 }; 23740 23741 // put the property in the style objects 23742 switch( prop.mapped ){ // flatten the property if mapped 23743 case types.mapData: 23744 case types.mapLayoutData: 23745 case types.mapScratch: 23746 23747 var isLayout = prop.mapped === types.mapLayoutData; 23748 var isScratch = prop.mapped === types.mapScratch; 23749 23750 // flatten the field (e.g. data.foo.bar) 23751 var fields = prop.field.split( '.' ); 23752 var fieldVal; 23753 23754 if( isScratch || isLayout ){ 23755 fieldVal = _p.scratch; 23756 } else { 23757 fieldVal = _p.data; 23758 } 23759 23760 for( var i = 0; i < fields.length && fieldVal; i++ ){ 23761 var field = fields[ i ]; 23762 fieldVal = fieldVal[ field ]; 23763 } 23764 23765 var percent; 23766 if( !is.number( fieldVal ) ){ // then keep the mapping but assume 0% for now 23767 percent = 0; 23768 } else { 23769 percent = (fieldVal - prop.fieldMin) / (prop.fieldMax - prop.fieldMin); 23770 } 23771 23772 // make sure to bound percent value 23773 if( percent < 0 ){ 23774 percent = 0; 23775 } else if( percent > 1 ){ 23776 percent = 1; 23777 } 23778 23779 if( type.color ){ 23780 var r1 = prop.valueMin[0]; 23781 var r2 = prop.valueMax[0]; 23782 var g1 = prop.valueMin[1]; 23783 var g2 = prop.valueMax[1]; 23784 var b1 = prop.valueMin[2]; 23785 var b2 = prop.valueMax[2]; 23786 var a1 = prop.valueMin[3] == null ? 1 : prop.valueMin[3]; 23787 var a2 = prop.valueMax[3] == null ? 1 : prop.valueMax[3]; 23788 23789 var clr = [ 23790 Math.round( r1 + (r2 - r1) * percent ), 23791 Math.round( g1 + (g2 - g1) * percent ), 23792 Math.round( b1 + (b2 - b1) * percent ), 23793 Math.round( a1 + (a2 - a1) * percent ) 23794 ]; 23795 23796 flatProp = { // colours are simple, so just create the flat property instead of expensive string parsing 23797 bypass: prop.bypass, // we're a bypass if the mapping property is a bypass 23798 name: prop.name, 23799 value: clr, 23800 strValue: 'rgb(' + clr[0] + ', ' + clr[1] + ', ' + clr[2] + ')' 23801 }; 23802 23803 } else if( type.number ){ 23804 var calcValue = prop.valueMin + (prop.valueMax - prop.valueMin) * percent; 23805 flatProp = this.parse( prop.name, calcValue, prop.bypass, true ); 23806 23807 } else { 23808 return false; // can only map to colours and numbers 23809 } 23810 23811 if( !flatProp ){ // if we can't flatten the property, then use the origProp so we still keep the mapping itself 23812 flatProp = this.parse( prop.name, origProp.strValue, prop.bypass, true ); 23813 } 23814 23815 if( !flatProp ){ printMappingErr(); } 23816 flatProp.mapping = prop; // keep a reference to the mapping 23817 prop = flatProp; // the flattened (mapped) property is the one we want 23818 23819 break; 23820 23821 // direct mapping 23822 case types.data: 23823 case types.layoutData: 23824 case types.scratch: 23825 var isLayout = prop.mapped === types.layoutData; 23826 var isScratch = prop.mapped === types.scratch; 23827 23828 // flatten the field (e.g. data.foo.bar) 23829 var fields = prop.field.split( '.' ); 23830 var fieldVal; 23831 23832 if( isScratch || isLayout ){ 23833 fieldVal = _p.scratch; 23834 } else { 23835 fieldVal = _p.data; 23836 } 23837 23838 if( fieldVal ){ for( var i = 0; i < fields.length; i++ ){ 23839 var field = fields[ i ]; 23840 fieldVal = fieldVal[ field ]; 23841 } } 23842 23843 flatProp = this.parse( prop.name, fieldVal, prop.bypass, true ); 23844 23845 if( !flatProp ){ // if we can't flatten the property, then use the origProp so we still keep the mapping itself 23846 var flatPropVal = origProp ? origProp.strValue : ''; 23847 23848 flatProp = this.parse( prop.name, flatPropVal, prop.bypass, true ); 23849 } 23850 23851 if( !flatProp ){ printMappingErr(); } 23852 flatProp.mapping = prop; // keep a reference to the mapping 23853 prop = flatProp; // the flattened (mapped) property is the one we want 23854 23855 break; 23856 23857 case types.fn: 23858 var fn = prop.value; 23859 var fnRetVal = fn( ele ); 23860 23861 flatProp = this.parse( prop.name, fnRetVal, prop.bypass, true ); 23862 flatProp.mapping = prop; // keep a reference to the mapping 23863 prop = flatProp; // the flattened (mapped) property is the one we want 23864 23865 break; 23866 23867 case undefined: 23868 break; // just set the property 23869 23870 default: 23871 return false; // not a valid mapping 23872 } 23873 23874 // if the property is a bypass property, then link the resultant property to the original one 23875 if( propIsBypass ){ 23876 if( origPropIsBypass ){ // then this bypass overrides the existing one 23877 prop.bypassed = origProp.bypassed; // steal bypassed prop from old bypass 23878 } else { // then link the orig prop to the new bypass 23879 prop.bypassed = origProp; 23880 } 23881 23882 style[ prop.name ] = prop; // and set 23883 23884 } else { // prop is not bypass 23885 if( origPropIsBypass ){ // then keep the orig prop (since it's a bypass) and link to the new prop 23886 origProp.bypassed = prop; 23887 } else { // then just replace the old prop with the new one 23888 style[ prop.name ] = prop; 23889 } 23890 } 23891 23892 return true; 23893}; 23894 23895styfn.cleanElements = function( eles, keepBypasses ){ 23896 var self = this; 23897 var props = self.properties; 23898 23899 for( var i = 0; i < eles.length; i++ ){ 23900 var ele = eles[i]; 23901 23902 if( !keepBypasses ){ 23903 ele._private.style = {}; 23904 } else { 23905 var style = ele._private.style; 23906 23907 for( var j = 0; j < props.length; j++ ){ 23908 var prop = props[j]; 23909 var eleProp = style[ prop.name ]; 23910 23911 if( eleProp ){ 23912 if( eleProp.bypass ){ 23913 eleProp.bypassed = null; 23914 } else { 23915 style[ prop.name ] = null; 23916 } 23917 } 23918 } 23919 } 23920 } 23921}; 23922 23923// updates the visual style for all elements (useful for manual style modification after init) 23924styfn.update = function(){ 23925 var cy = this._private.cy; 23926 var eles = cy.mutableElements(); 23927 23928 eles.updateStyle(); 23929}; 23930 23931// just update the functional properties (i.e. mappings) in the elements' 23932// styles (less expensive than recalculation) 23933styfn.updateMappers = function( eles ){ 23934 var self = this; 23935 23936 for( var i = 0; i < eles.length; i++ ){ // for each ele 23937 var ele = eles[ i ]; 23938 var style = ele._private.style; 23939 23940 for( var j = 0; j < self.properties.length; j++ ){ // for each prop 23941 var prop = self.properties[ j ]; 23942 var propInStyle = style[ prop.name ]; 23943 23944 if( propInStyle && propInStyle.mapping ){ 23945 var mapping = propInStyle.mapping; 23946 this.applyParsedProperty( ele, mapping ); // reapply the mapping property 23947 } 23948 } 23949 23950 this.updateStyleHints( ele ); 23951 } 23952}; 23953 23954// diffProps : { name => { prev, next } } 23955styfn.updateTransitions = function( ele, diffProps, isBypass ){ 23956 var self = this; 23957 var _p = ele._private; 23958 var props = ele.pstyle( 'transition-property' ).value; 23959 var duration = ele.pstyle( 'transition-duration' ).pfValue;
23960 var delay = ele.pstyle( 'transition-delay' ).pfValue; 23961 23962 if( props.length > 0 && duration > 0 ){ 23963 23964 var css = {}; 23965 23966 // build up the style to animate towards 23967 var anyPrev = false; 23968 for( var i = 0; i < props.length; i++ ){ 23969 var prop = props[ i ]; 23970 var styProp = ele.pstyle( prop ); 23971 var diffProp = diffProps[ prop ]; 23972 23973 if( !diffProp ){ continue; } 23974 23975 var prevProp = diffProp.prev; 23976 var fromProp = prevProp; 23977 var toProp = diffProp.next != null ? diffProp.next : styProp; 23978 var diff = false; 23979 var initVal; 23980 var initDt = 0.000001; // delta time % value for initVal (allows animating out of init zero opacity) 23981 23982 if( !fromProp ){ continue; } 23983 23984 // consider px values 23985 if( is.number( fromProp.pfValue ) && is.number( toProp.pfValue ) ){ 23986 diff = toProp.pfValue - fromProp.pfValue; // nonzero is truthy 23987 initVal = fromProp.pfValue + initDt * diff; 23988 23989 // consider numerical values 23990 } else if( is.number( fromProp.value ) && is.number( toProp.value ) ){ 23991 diff = toProp.value - fromProp.value; // nonzero is truthy 23992 initVal = fromProp.value + initDt * diff; 23993 23994 // consider colour values 23995 } else if( is.array( fromProp.value ) && is.array( toProp.value ) ){ 23996 diff = fromProp.value[0] !== toProp.value[0] 23997 || fromProp.value[1] !== toProp.value[1] 23998 || fromProp.value[2] !== toProp.value[2] 23999 ; 24000 24001 initVal = fromProp.strValue; 24002 } 24003 24004 // the previous value is good for an animation only if it's different 24005 if( diff ){ 24006 css[ prop ] = toProp.strValue; // to val 24007 this.applyBypass( ele, prop, initVal ); // from val 24008 anyPrev = true; 24009 } 24010 24011 } // end if props allow ani 24012 24013 // can't transition if there's nothing previous to transition from 24014 if( !anyPrev ){ return; } 24015 24016 _p.transitioning = true; 24017 24018 ele.stop(); 24019 24020 if( delay > 0 ){ 24021 ele.delay( delay ); 24022 } 24023 24024 ele.animate( { 24025 css: css 24026 }, { 24027 duration: duration, 24028 easing: ele.pstyle( 'transition-timing-function' ).value, 24029 queue: false, 24030 complete: function(){ 24031 if( !isBypass ){ 24032 self.removeBypasses( ele, props ); 24033 } 24034 24035 _p.transitioning = false; 24036 } 24037 } ); 24038 24039 } else if( _p.transitioning ){ 24040 ele.stop(); 24041 24042 this.removeBypasses( ele, props ); 24043 24044 _p.transitioning = false; 24045 } 24046}; 24047 24048module.exports = styfn; 24049 24050},{"../is":83,"../util":100}],89:[function(_dereq_,module,exports){ 24051'use strict'; 24052 24053var is = _dereq_( '../is' ); 24054var util = _dereq_( '../util' ); 24055 24056var styfn = {}; 24057 24058// bypasses are applied to an existing style on an element, and just tacked on temporarily 24059// returns true iff application was successful for at least 1 specified property 24060styfn.applyBypass = function( eles, name, value, updateTransitions ){ 24061 var self = this; 24062 var props = []; 24063 var isBypass = true; 24064 24065 // put all the properties (can specify one or many) in an array after parsing them 24066 if( name === '*' || name === '**' ){ // apply to all property names 24067 24068 if( value !== undefined ){ 24069 for( var i = 0; i < self.properties.length; i++ ){ 24070 var prop = self.properties[ i ]; 24071 var name = prop.name; 24072 24073 var parsedProp = this.parse( name, value, true ); 24074 24075 if( parsedProp ){ 24076 props.push( parsedProp ); 24077 } 24078 } 24079 } 24080 24081 } else if( is.string( name ) ){ // then parse the single property 24082 var parsedProp = this.parse( name, value, true ); 24083 24084 if( parsedProp ){ 24085 props.push( parsedProp ); 24086 } 24087 } else if( is.plainObject( name ) ){ // then parse each property 24088 var specifiedProps = name; 24089 updateTransitions = value; 24090 24091 for( var i = 0; i < self.properties.length; i++ ){ 24092 var prop = self.properties[ i ]; 24093 var name = prop.name; 24094 var value = specifiedProps[ name ]; 24095 24096 if( value === undefined ){ // try camel case name too 24097 value = specifiedProps[ util.dash2camel( name ) ]; 24098 } 24099 24100 if( value !== undefined ){ 24101 var parsedProp = this.parse( name, value, true ); 24102 24103 if( parsedProp ){ 24104 props.push( parsedProp ); 24105 } 24106 } 24107 } 24108 } else { // can't do anything without well defined properties 24109 return false;
24110 } 24111 24112 // we've failed if there are no valid properties 24113 if( props.length === 0 ){ return false; } 24114 24115 // now, apply the bypass properties on the elements 24116 var ret = false; // return true if at least one succesful bypass applied 24117 for( var i = 0; i < eles.length; i++ ){ // for each ele 24118 var ele = eles[ i ]; 24119 var diffProps = {}; 24120 var diffProp; 24121 24122 for( var j = 0; j < props.length; j++ ){ // for each prop 24123 var prop = props[ j ]; 24124 24125 if( updateTransitions ){ 24126 var prevProp = ele.pstyle( prop.name ); 24127 diffProp = diffProps[ prop.name ] = { prev: prevProp }; 24128 } 24129 24130 ret = this.applyParsedProperty( ele, prop ) || ret; 24131 24132 if( updateTransitions ){ 24133 diffProp.next = ele.pstyle( prop.name ); 24134 } 24135 24136 } // for props 24137 24138 if( ret ){ 24139 this.updateStyleHints( ele ); 24140 } 24141 24142 if( updateTransitions ){ 24143 this.updateTransitions( ele, diffProps, isBypass ); 24144 } 24145 } // for eles 24146 24147 return ret; 24148}; 24149 24150// only useful in specific cases like animation 24151styfn.overrideBypass = function( eles, name, value ){ 24152 name = util.camel2dash( name ); 24153 24154 for( var i = 0; i < eles.length; i++ ){ 24155 var ele = eles[ i ]; 24156 var prop = ele._private.style[ name ]; 24157 var type = this.properties[ name ].type; 24158 var isColor = type.color; 24159 var isMulti = type.mutiple; 24160 24161 if( !prop || !prop.bypass ){ // need a bypass if one doesn't exist 24162 this.applyBypass( ele, name, value ); 24163 continue; 24164 } 24165 24166 prop.value = value; 24167 24168 if( prop.pfValue != null ){ 24169 prop.pfValue = value; 24170 } 24171 24172 if( isColor ){ 24173 prop.strValue = 'rgb(' + value.join( ',' ) + ')'; 24174 } else if( isMulti ){ 24175 prop.strValue = value.join( ' ' ); 24176 } else { 24177 prop.strValue = '' + value; 24178 } 24179 } 24180}; 24181 24182styfn.removeAllBypasses = function( eles, updateTransitions ){ 24183 return this.removeBypasses( eles, this.propertyNames, updateTransitions ); 24184}; 24185 24186styfn.removeBypasses = function( eles, props, updateTransitions ){ 24187 var isBypass = true; 24188 24189 for( var j = 0; j < eles.length; j++ ){ 24190 var ele = eles[ j ]; 24191 var diffProps = {}; 24192 24193 for( var i = 0; i < props.length; i++ ){ 24194 var name = props[ i ]; 24195 var prop = this.properties[ name ]; 24196 var prevProp = ele.pstyle( prop.name ); 24197 24198 if( !prevProp || !prevProp.bypass ){ 24199 // if a bypass doesn't exist for the prop, nothing needs to be removed 24200 continue; 24201 } 24202 24203 var value = ''; // empty => remove bypass 24204 var parsedProp = this.parse( name, value, true ); 24205 var diffProp = diffProps[ prop.name ] = { prev: prevProp }; 24206 24207 this.applyParsedProperty( ele, parsedProp ); 24208 24209 diffProp.next = ele.pstyle( prop.name ); 24210 } // for props 24211 24212 this.updateStyleHints( ele ); 24213 24214 if( updateTransitions ){ 24215 this.updateTransitions( ele, diffProps, isBypass ); 24216 } 24217 } // for eles 24218}; 24219 24220module.exports = styfn; 24221 24222},{"../is":83,"../util":100}],90:[function(_dereq_,module,exports){ 24223'use strict'; 24224 24225var window = _dereq_( '../window' ); 24226 24227var styfn = {}; 24228 24229// gets what an em size corresponds to in pixels relative to a dom element 24230styfn.getEmSizeInPixels = function(){ 24231 var px = this.containerCss( 'font-size' ); 24232 24233 if( px != null ){ 24234 return parseFloat( px ); 24235 } else { 24236 return 1; // for headless 24237 } 24238}; 24239 24240// gets css property from the core container 24241styfn.containerCss = function( propName ){ 24242 var cy = this._private.cy; 24243 var domElement = cy.container(); 24244 24245 if( window && domElement && window.getComputedStyle ){ 24246 return window.getComputedStyle( domElement ).getPropertyValue( propName ); 24247 } 24248}; 24249 24250module.exports = styfn; 24251 24252},{"../window":107}],91:[function(_dereq_,module,exports){ 24253'use strict'; 24254 24255var util = _dereq_( '../util' ); 24256var is = _dereq_( '../is' ); 24257 24258var styfn = {}; 24259 24260// gets the rendered style for an element 24261styfn.getRenderedStyle = function( ele ){ 24262 return this.getRawStyle( ele, true ); 24263}; 24264 24265// gets the raw style for an element 24266styfn.getRawStyle = function( ele, isRenderedVal ){ 24267 var self = this; 24268 var ele = ele[0]; // insure it's an element 24269 24270 if( ele ){ 24271 var rstyle = {}; 24272 24273 for( var i = 0; i < self.properties.length; i++ ){ 24274 var prop = self.properties[ i ]; 24275 var val = self.getStylePropertyValue( ele, prop.name, isRenderedVal ); 24276 24277 if( val ){ 24278 rstyle[ prop.name ] = val; 24279 rstyle[ util.dash2camel( prop.name ) ] = val; 24280 } 24281 } 24282 24283 return rstyle; 24284 } 24285}; 24286 24287styfn.getStylePropertyValue = function( ele, propName, isRenderedVal ){ 24288 var self = this; 24289 var ele = ele[0]; // insure it's an element 24290 24291 if( ele ){ 24292 var prop = self.properties[ propName ]; 24293 var type = prop.type; 24294 var styleProp = ele.pstyle( prop.name ); 24295 var zoom = ele.cy().zoom(); 24296 24297 if( styleProp ){ 24298 var units = styleProp.units ? type.implicitUnits || 'px' : null; 24299 var val = units ? [].concat( styleProp.pfValue ).map( function( pfValue ){ 24300 return ( pfValue * (isRenderedVal ? zoom : 1) ) + units; 24301 } ).join( ' ' ) : styleProp.strValue; 24302 24303 return val; 24304 } 24305 } 24306}; 24307 24308styfn.getAnimationStartStyle = function( ele, aniProps ){ 24309 var rstyle = {}; 24310 24311 for( var i = 0; i < aniProps.length; i++ ){ 24312 var aniProp = aniProps[ i ]; 24313 var name = aniProp.name; 24314 24315 var styleProp = ele.pstyle( name ); 24316 24317 if( styleProp !== undefined ){ // then make a prop of it 24318 if( is.plainObject( styleProp ) ){ 24319 styleProp = this.parse( name, styleProp.strValue ); 24320 } else { 24321 styleProp = this.parse( name, styleProp ); 24322 } 24323 } 24324 24325 if( styleProp ){ 24326 rstyle[ name ] = styleProp; 24327 } 24328 } 24329 24330 return rstyle; 24331}; 24332 24333styfn.getPropsList = function( propsObj ){ 24334 var self = this; 24335 var rstyle = []; 24336 var style = propsObj; 24337 var props = self.properties; 24338 24339 if( style ){ 24340 var names = Object.keys( style ); 24341 24342 for( var i = 0; i < names.length; i++ ){ 24343 var name = names[i]; 24344 var val = style[ name ]; 24345 var prop = props[ name ] || props[ util.camel2dash( name ) ]; 24346 var styleProp = this.parse( prop.name, val ); 24347 24348 rstyle.push( styleProp ); 24349 } 24350 } 24351 24352 return rstyle; 24353}; 24354 24355module.exports = styfn; 24356 24357},{"../is":83,"../util":100}],92:[function(_dereq_,module,exports){ 24358'use strict'; 24359 24360var is = _dereq_( '../is' ); 24361var util = _dereq_( '../util' ); 24362var Selector = _dereq_( '../selector' ); 24363 24364var Style = function( cy ){ 24365 24366 if( !(this instanceof Style) ){ 24367 return new Style( cy ); 24368 } 24369 24370 if( !is.core( cy ) ){ 24371 util.error( 'A style must have a core reference' ); 24372 return; 24373 } 24374 24375 this._private = { 24376 cy: cy, 24377 coreStyle: {} 24378 }; 24379 24380 this.length = 0; 24381 24382 this.resetToDefault(); 24383}; 24384 24385var styfn = Style.prototype; 24386 24387styfn.instanceString = function(){ 24388 return 'style'; 24389}; 24390
24391// remove all contexts 24392styfn.clear = function(){ 24393 for( var i = 0; i < this.length; i++ ){ 24394 this[ i ] = undefined; 24395 } 24396 this.length = 0; 24397 24398 var _p = this._private; 24399 24400 _p.newStyle = true; 24401 24402 return this; // chaining 24403}; 24404 24405styfn.resetToDefault = function(){ 24406 this.clear(); 24407 this.addDefaultStylesheet(); 24408 24409 return this; 24410}; 24411 24412// builds a style object for the 'core' selector 24413styfn.core = function(){ 24414 return this._private.coreStyle; 24415}; 24416 24417// create a new context from the specified selector string and switch to that context 24418styfn.selector = function( selectorStr ){ 24419 // 'core' is a special case and does not need a selector 24420 var selector = selectorStr === 'core' ? null : new Selector( selectorStr ); 24421 24422 var i = this.length++; // new context means new index 24423 this[ i ] = { 24424 selector: selector, 24425 properties: [], 24426 mappedProperties: [], 24427 index: i 24428 }; 24429 24430 return this; // chaining 24431}; 24432 24433// add one or many css rules to the current context 24434styfn.css = function(){ 24435 var self = this; 24436 var args = arguments; 24437 24438 switch( args.length ){ 24439 case 1: 24440 var map = args[0]; 24441 24442 for( var i = 0; i < self.properties.length; i++ ){ 24443 var prop = self.properties[ i ]; 24444 var mapVal = map[ prop.name ]; 24445 24446 if( mapVal === undefined ){ 24447 mapVal = map[ util.dash2camel( prop.name ) ]; 24448 } 24449 24450 if( mapVal !== undefined ){ 24451 this.cssRule( prop.name, mapVal ); 24452 } 24453 } 24454 24455 break; 24456 24457 case 2: 24458 this.cssRule( args[0], args[1] ); 24459 break; 24460 24461 default: 24462 break; // do nothing if args are invalid 24463 } 24464 24465 return this; // chaining 24466}; 24467styfn.style = styfn.css; 24468 24469// add a single css rule to the current context 24470styfn.cssRule = function( name, value ){ 24471 // name-value pair 24472 var property = this.parse( name, value ); 24473 24474 // add property to current context if valid 24475 if( property ){ 24476 var i = this.length - 1; 24477 this[ i ].properties.push( property ); 24478 this[ i ].properties[ property.name ] = property; // allow access by name as well 24479 24480 if( property.name.match( /pie-(\d+)-background-size/ ) && property.value ){ 24481 this._private.hasPie = true; 24482 } 24483 24484 if( property.mapped ){ 24485 this[ i ].mappedProperties.push( property ); 24486 } 24487 24488 // add to core style if necessary 24489 var currentSelectorIsCore = !this[ i ].selector; 24490 if( currentSelectorIsCore ){ 24491 this._private.coreStyle[ property.name ] = property; 24492 } 24493 } 24494 24495 return this; // chaining 24496}; 24497 24498// static function 24499Style.fromJson = function( cy, json ){ 24500 var style = new Style( cy ); 24501 24502 style.fromJson( json ); 24503 24504 return style; 24505}; 24506 24507Style.fromString = function( cy, string ){ 24508 return new Style( cy ).fromString( string ); 24509}; 24510 24511[ 24512 _dereq_( './apply' ), 24513 _dereq_( './bypass' ), 24514 _dereq_( './container' ), 24515 _dereq_( './get-for-ele' ), 24516 _dereq_( './json' ), 24517 _dereq_( './string-sheet' ), 24518 _dereq_( './properties' ), 24519 _dereq_( './parse' ) 24520].forEach( function( props ){ 24521 util.extend( styfn, props ); 24522} ); 24523 24524 24525Style.types = styfn.types; 24526Style.properties = styfn.properties; 24527 24528module.exports = Style; 24529 24530},{"../is":83,"../selector":87,"../util":100,"./apply":88,"./bypass":89,"./container":90,"./get-for-ele":91,"./json":93,"./parse":94,"./properties":95,"./string-sheet":96}],93:[function(_dereq_,module,exports){ 24531'use strict'; 24532 24533var styfn = {}; 24534 24535styfn.applyFromJson = function( json ){ 24536 var style = this; 24537 24538 for( var i = 0; i < json.length; i++ ){ 24539 var context = json[ i ]; 24540 var selector = context.selector; 24541 var props = context.style || context.css; 24542 var names = Object.keys( props ); 24543 24544 style.selector( selector ); // apply selector 24545 24546 for( var j = 0; j < names.length; j++ ){ 24547 var name = names[j]; 24548 var value = props[ name ]; 24549 24550 style.css( name, value ); // apply property 24551 } 24552 } 24553 24554 return style; 24555}; 24556 24557// accessible cy.style() function 24558styfn.fromJson = function( json ){ 24559 var style = this; 24560 24561 style.resetToDefault(); 24562 style.applyFromJson( json ); 24563 24564 return style; 24565}; 24566 24567// get json from cy.style() api 24568styfn.json = function(){ 24569 var json = []; 24570 24571 for( var i = this.defaultLength; i < this.length; i++ ){ 24572 var cxt = this[ i ]; 24573 var selector = cxt.selector; 24574 var props = cxt.properties; 24575 var css = {}; 24576 24577 for( var j = 0; j < props.length; j++ ){ 24578 var prop = props[ j ]; 24579 css[ prop.name ] = prop.strValue; 24580 } 24581 24582 json.push( { 24583 selector: !selector ? 'core' : selector.toString(), 24584 style: css 24585 } ); 24586 } 24587 24588 return json; 24589}; 24590 24591module.exports = styfn; 24592 24593},{}],94:[function(_dereq_,module,exports){ 24594'use strict'; 24595 24596var util = _dereq_( '../util' ); 24597var is = _dereq_( '../is' ); 24598var math = _dereq_( '../math' ); 24599 24600var styfn = {}; 24601 24602// a caching layer for property parsing 24603styfn.parse = function( name, value, propIsBypass, propIsFlat ){ 24604 var self = this; 24605 24606 // function values can't be cached in all cases, and there isn't much benefit of caching them anyway 24607 if( is.fn( value ) ){ 24608 return self.parseImpl( name, value, propIsBypass, propIsFlat ); 24609 } 24610 24611 var argHash = [ name, value, propIsBypass, propIsFlat ].join( '$' ); 24612 var propCache = self.propCache = self.propCache || {}; 24613 var ret; 24614 24615 if( !(ret = propCache[ argHash ]) ){ 24616 ret = propCache[ argHash ] = self.parseImpl( name, value, propIsBypass, propIsFlat ); 24617 } 24618 24619 // always need a copy since props are mutated later in their lifecycles 24620 ret = util.copy( ret ); 24621 24622 if( ret ){ 24623 ret.value = util.copy( ret.value );
24623 // because it could be an array, e.g. colour 24624 } 24625 24626 return ret; 24627}; 24628 24629// parse a property; return null on invalid; return parsed property otherwise 24630// fields : 24631// - name : the name of the property 24632// - value : the parsed, native-typed value of the property 24633// - strValue : a string value that represents the property value in valid css 24634// - bypass : true iff the property is a bypass property 24635var parseImpl = function( name, value, propIsBypass, propIsFlat ){ 24636 var self = this; 24637 24638 name = util.camel2dash( name ); // make sure the property name is in dash form (e.g. 'property-name' not 'propertyName') 24639 24640 var property = self.properties[ name ]; 24641 var passedValue = value; 24642 var types = self.types; 24643 24644 if( !property ){ return null; } // return null on property of unknown name 24645 if( value === undefined || value === null ){ return null; } // can't assign null 24646 24647 // the property may be an alias 24648 if( property.alias ){ 24649 property = property.pointsTo; 24650 name = property.name; 24651 } 24652 24653 var valueIsString = is.string( value ); 24654 if( valueIsString ){ // trim the value to make parsing easier 24655 value = value.trim(); 24656 } 24657 24658 var type = property.type; 24659 if( !type ){ return null; } // no type, no luck 24660 24661 // check if bypass is null or empty string (i.e. indication to delete bypass property) 24662 if( propIsBypass && (value === '' || value === null) ){ 24663 return { 24664 name: name, 24665 value: value, 24666 bypass: true, 24667 deleteBypass: true 24668 }; 24669 } 24670 24671 // check if value is a function used as a mapper 24672 if( is.fn( value ) ){ 24673 return { 24674 name: name, 24675 value: value, 24676 strValue: 'fn', 24677 mapped: types.fn, 24678 bypass: propIsBypass 24679 }; 24680 } 24681 24682 // check if value is mapped 24683 var data, mapData, layoutData, mapLayoutData, scratch, mapScratch; 24684 if( !valueIsString || propIsFlat ){ 24685 // then don't bother to do the expensive regex checks 24686 24687 } else if( 24688 ( data = new RegExp( types.data.regex ).exec( value ) ) || 24689 ( layoutData = new RegExp( types.layoutData.regex ).exec( value ) ) || 24690 ( scratch = new RegExp( types.scratch.regex ).exec( value ) ) 24691 ){ 24692 if( propIsBypass ){ return false; } // mappers not allowed in bypass 24693 24694 var mapped; 24695 if( data ){ 24696 mapped = types.data; 24697 } else if( layoutData ){ 24698 mapped = types.layoutData; 24699 } else { 24700 mapped = types.scratch; 24701 } 24702 24703 data = data || layoutData || scratch; 24704 24705 return { 24706 name: name, 24707 value: data, 24708 strValue: '' + value, 24709 mapped: mapped, 24710 field: data[1], 24711 bypass: propIsBypass 24712 }; 24713 24714 } else if( 24715 ( mapData = new RegExp( types.mapData.regex ).exec( value ) ) || 24716 ( mapLayoutData = new RegExp( types.mapLayoutData.regex ).exec( value ) ) || 24717 ( mapScratch = new RegExp( types.mapScratch.regex ).exec( value ) ) 24718 ){ 24719 if( propIsBypass ){ return false; } // mappers not allowed in bypass 24720 if( type.multiple ){ return false; } // impossible to map to num 24721 24722 var mapped; 24723 if( mapData ){ 24724 mapped = types.mapData; 24725 } else if( mapLayoutData ){ 24726 mapped = types.mapLayoutData; 24727 } else { 24728 mapped = types.mapScratch; 24729 } 24730 24731 mapData = mapData || mapLayoutData || mapScratch; 24732 24733 // we can map only if the type is a colour or a number 24734 if( !(type.color || type.number) ){ return false; } 24735 24736 var valueMin = this.parse( name, mapData[4] ); // parse to validate 24737 if( !valueMin || valueMin.mapped ){ return false; } // can't be invalid or mapped 24738 24739 var valueMax = this.parse( name, mapData[5] ); // parse to validate 24740 if( !valueMax || valueMax.mapped ){ return false; } // can't be invalid or mapped 24741 24742 // check if valueMin and valueMax are the same 24743 if( valueMin.value === valueMax.value ){ 24744 return false; // can't make much of a mapper without a range 24745 24746 } else if( type.color ){ 24747 var c1 = valueMin.value; 24748 var c2 = valueMax.value; 24749 24750 var same = c1[0] === c2[0] // red 24751 && c1[1] === c2[1] // green 24752 && c1[2] === c2[2] // blue 24753 && ( // optional alpha 24754 c1[3] === c2[3] // same alpha outright 24755 || ( 24756 (c1[3] == null || c1[3] === 1) // full opacity for colour 1? 24757 && 24758 (c2[3] == null || c2[3] === 1) // full opacity for colour 2? 24759 ) 24760 ) 24761 ; 24762 24763 if( same ){ return false; } // can't make a mapper without a range 24764 } 24765 24766 return { 24767 name: name, 24768 value: mapData, 24769 strValue: '' + value, 24770 mapped: mapped, 24771 field: mapData[1], 24772 fieldMin: parseFloat( mapData[2] ), // min & max are numeric 24773 fieldMax: parseFloat( mapData[3] ), 24774 valueMin: valueMin.value, 24775 valueMax: valueMax.value, 24776 bypass: propIsBypass 24777 }; 24778 } 24779 24780 if( type.multiple && propIsFlat !== 'multiple' ){ 24781 var vals; 24782 24783 if( valueIsString ){ 24784 vals = value.split( /\s+/ ); 24785 } else if( is.array( value ) ){ 24786 vals = value; 24787 } else { 24788 vals = [ value ]; 24789 } 24790 24791 if( type.evenMultiple && vals.length % 2 !== 0 ){ return null; } 24792 24793 var valArr = vals.map( function( v ){ 24794 var p = self.parse( name, v, propIsBypass, 'multiple' ); 24795 24796 if( p.pfValue != null ){ 24797 return p.pfValue; 24798 } else { 24799 return p.value; 24800 } 24801 } ); 24802 24803 return { 24804 name: name, 24805 value: valArr, 24806 pfValue: valArr, 24807 strValue: valArr.join( ' ' ), 24808 bypass: propIsBypass, 24809 units: type.number && !type.unitless ? type.implicitUnits || 'px' : undefined 24810 }; 24811 } 24812 24813 // several types also allow enums 24814 var checkEnums = function(){ 24815 for( var i = 0; i < type.enums.length; i++ ){ 24816 var en = type.enums[ i ]; 24817 24818 if( en === value ){ 24819 return { 24820 name: name, 24821 value: value, 24822 strValue: '' + value, 24823 bypass: propIsBypass 24824 }; 24825 } 24826 } 24827 24828 return null; 24829 }; 24830 24831 // check the type and return the appropriate object 24832 if( type.number ){ 24833 var units; 24834 var implicitUnits = 'px'; // not set => px 24835 24836 if( type.units ){ // use specified units if set 24837 units = type.units; 24838 } 24839 24840 if( type.implicitUnits ){ 24841 implicitUnits = type.implicitUnits; 24842 } 24843 24844 if( !type.unitless ){ 24845 if( valueIsString ){ 24846 var unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : ''); 24847 if( units ){ unitsRegex = units; } // only allow explicit units if so set 24848 var match = value.match( '^(' + util.regex.number + ')(' + unitsRegex + ')?' + '$' ); 24849 24850 if( match ){ 24851 value = match[1]; 24852 units = match[2] || implicitUnits; 24853 } 24854 24855 } else if( !units || type.implicitUnits ){ 24856 units = implicitUnits; // implicitly px if unspecified 24857 } 24858 } 24859 24860 value = parseFloat( value ); 24861 24862 // if not a number and enums not allowed, then the value is invali
24862d 24863 if( isNaN( value ) && type.enums === undefined ){ 24864 return null; 24865 } 24866 24867 // check if this number type also accepts special keywords in place of numbers 24868 // (i.e. `left`, `auto`, etc) 24869 if( isNaN( value ) && type.enums !== undefined ){ 24870 value = passedValue; 24871 24872 return checkEnums(); 24873 } 24874 24875 // check if value must be an integer 24876 if( type.integer && !is.integer( value ) ){ 24877 return null; 24878 } 24879 24880 // check value is within range 24881 if( (type.min !== undefined && value < type.min) 24882 || (type.max !== undefined && value > type.max) 24883 ){ 24884 return null; 24885 } 24886 24887 var ret = { 24888 name: name, 24889 value: value, 24890 strValue: '' + value + (units ? units : ''), 24891 units: units, 24892 bypass: propIsBypass 24893 }; 24894 24895 // normalise value in pixels 24896 if( type.unitless || (units !== 'px' && units !== 'em') ){ 24897 ret.pfValue = value; 24898 } else { 24899 ret.pfValue = ( units === 'px' || !units ? (value) : (this.getEmSizeInPixels() * value) ); 24900 } 24901 24902 // normalise value in ms 24903 if( units === 'ms' || units === 's' ){ 24904 ret.pfValue = units === 'ms' ? value : 1000 * value; 24905 } 24906 24907 // normalise value in rad 24908 if( units === 'deg' || units === 'rad' ){ 24909 ret.pfValue = units === 'rad' ? value : math.deg2rad( value ); 24910 } 24911 24912 return ret; 24913 24914 } else if( type.propList ){ 24915 24916 var props = []; 24917 var propsStr = '' + value; 24918 24919 if( propsStr === 'none' ){ 24920 // leave empty 24921 24922 } else { // go over each prop 24923 24924 var propsSplit = propsStr.split( ',' ); 24925 for( var i = 0; i < propsSplit.length; i++ ){ 24926 var propName = propsSplit[ i ].trim(); 24927 24928 if( self.properties[ propName ] ){ 24929 props.push( propName ); 24930 } 24931 } 24932 24933 if( props.length === 0 ){ return null; } 24934 } 24935 24936 return { 24937 name: name, 24938 value: props, 24939 strValue: props.length === 0 ? 'none' : props.join( ', ' ), 24940 bypass: propIsBypass 24941 }; 24942 24943 } else if( type.color ){ 24944 var tuple = util.color2tuple( value ); 24945 24946 if( !tuple ){ return null; } 24947 24948 return { 24949 name: name, 24950 value: tuple, 24951 strValue: '' + value, 24952 bypass: propIsBypass, 24953 roundValue: true 24954 }; 24955 24956 } else if( type.regex || type.regexes ){ 24957 24958 // first check enums 24959 if( type.enums ){ 24960 var enumProp = checkEnums(); 24961 24962 if( enumProp ){ return enumProp; } 24963 } 24964 24965 var regexes = type.regexes ? type.regexes : [ type.regex ]; 24966 24967 for( var i = 0; i < regexes.length; i++ ){ 24968 var regex = new RegExp( regexes[ i ] ); // make a regex from the type string 24969 var m = regex.exec( value ); 24970 24971 if( m ){ // regex matches 24972 return { 24973 name: name, 24974 value: m, 24975 strValue: '' + value, 24976 bypass: propIsBypass 24977 }; 24978 24979 } 24980 } 24981 24982 return null; // didn't match any 24983 24984 } else if( type.string ){ 24985 // just return 24986 return { 24987 name: name, 24988 value: '' + value, 24989 strValue: '' + value, 24990 bypass: propIsBypass 24991 }; 24992 24993 } else if( type.enums ){ // check enums last because it's a combo type in others 24994 return checkEnums(); 24995 24996 } else { 24997 return null; // not a type we can handle 24998 } 24999 25000}; 25001styfn.parseImpl = parseImpl; 25002 25003module.exports = styfn; 25004 25005},{"../is":83,"../math":85,"../util":100}],95:[function(_dereq_,module,exports){ 25006'use strict'; 25007 25008var util = _dereq_( '../util' ); 25009 25010var styfn = {}; 25011 25012(function(){ 25013 var number = util.regex.number; 25014 var rgba = util.regex.rgbaNoBackRefs; 25015 var hsla = util.regex.hslaNoBackRefs; 25016 var hex3 = util.regex.hex3; 25017 var hex6 = util.regex.hex6; 25018 var data = function( prefix ){ return '^' + prefix + '\\s*\\(\\s*([\\w\\.]+)\\s*\\)$'; }; 25019 var mapData = function( prefix ){ 25020 var mapArg = number + '|\\w+|' + rgba + '|' + hsla + '|' + hex3 + '|' + hex6; 25021 return '^' + prefix + '\\s*\\(([\\w\\.]+)\\s*\\,\\s*(' + number + ')\\s*\\,\\s*(' + number + ')\\s*,\\s*(' + mapArg + ')\\s*\\,\\s*(' + mapArg + ')\\)$'; 25022 }; 25023 25024 // each visual style property has a type and needs to be validated according to it 25025 styfn.types = { 25026 time: { number: true, min: 0, units: 's|ms', implicitUnits: 'ms' }, 25027 percent: { number: true, min: 0, max: 100, units: '%', implicitUnits: '%' }, 25028 zeroOneNumber: { number: true, min: 0, max: 1, unitless: true }, 25029 nOneOneNumber: { number: true, min: -1, max: 1, unitless: true }, 25030 nonNegativeInt: { number: true, min: 0, integer: true, unitless: true }, 25031 position: { enums: [ 'parent', 'origin' ] }, 25032 nodeSize: { number: true, min: 0, enums: [ 'label' ] }, 25033 number: { number: true, unitless: true }, 25034 numbers: { number: true, unitless: true, multiple: true }, 25035 size: { number: true, min: 0 }, 25036 bidirectionalSize: { number: true }, // allows negative 25037 bidirectionalSizes: { number: true, multiple: true }, // allows negative 25038 bgSize: { number: true, min: 0, allowPercent: true }, 25039 bgWH: { number: true, min: 0, allowPercent: true, enums: [ 'auto' ] }, 25040 bgPos: { number: true, allowPercent: true }, 25041 bgRepeat: { enums: [ 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' ] }, 25042 bgFit: { enums: [ 'none', 'contain', 'cover' ] },
25043 bgCrossOrigin: { enums: [ 'anonymous', 'use-credentials' ] }, 25044 bgClip: { enums: [ 'none', 'node' ] }, 25045 color: { color: true }, 25046 bool: { enums: [ 'yes', 'no' ] }, 25047 lineStyle: { enums: [ 'solid', 'dotted', 'dashed' ] }, 25048 borderStyle: { enums: [ 'solid', 'dotted', 'dashed', 'double' ] }, 25049 curveStyle: { enums: [ 'bezier', 'unbundled-bezier', 'haystack', 'segments' ] }, 25050 fontFamily: { regex: '^([\\w- \\"]+(?:\\s*,\\s*[\\w- \\"]+)*)$' }, 25051 fontVariant: { enums: [ 'small-caps', 'normal' ] }, 25052 fontStyle: { enums: [ 'italic', 'normal', 'oblique' ] }, 25053 fontWeight: { enums: [ 'normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '800', '900', 100, 200, 300, 400, 500, 600, 700, 800, 900 ] }, 25054 textDecoration: { enums: [ 'none', 'underline', 'overline', 'line-through' ] }, 25055 textTransform: { enums: [ 'none', 'uppercase', 'lowercase' ] }, 25056 textWrap: { enums: [ 'none', 'wrap' ] }, 25057 textBackgroundShape: { enums: [ 'rectangle', 'roundrectangle' ]}, 25058 nodeShape: { enums: [ 'rectangle', 'roundrectangle', 'ellipse', 'triangle', 'square', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'star', 'diamond', 'vee', 'rhomboid', 'polygon' ] }, 25059 compoundIncludeLabels: { enums: [ 'include', 'exclude' ] }, 25060 arrowShape: { enums: [ 'tee', 'triangle', 'triangle-tee', 'triangle-backcurve', 'half-triangle-overshot', 'vee', 'square', 'circle', 'diamond', 'none' ] }, 25061 arrowFill: { enums: [ 'filled', 'hollow' ] }, 25062 display: { enums: [ 'element', 'none' ] }, 25063 visibility: { enums: [ 'hidden', 'visible' ] }, 25064 valign: { enums: [ 'top', 'center', 'bottom' ] }, 25065 halign: { enums: [ 'left', 'center', 'right' ] }, 25066 text: { string: true }, 25067 data: { mapping: true, regex: data( 'data' ) }, 25068 layoutData: { mapping: true, regex: data( 'layoutData' ) }, 25069 scratch: { mapping: true, regex: data( 'scratch' ) }, 25070 mapData: { mapping: true, regex: mapData( 'mapData' ) }, 25071 mapLayoutData: { mapping: true, regex: mapData( 'mapLayoutData' ) }, 25072 mapScratch: { mapping: true, regex: mapData( 'mapScratch' ) }, 25073 fn: { mapping: true, fn: true }, 25074 url: { regex: 'url\\s*\\(\\s*[\'"]?(.+?)[\'"]?\\s*\\)|none|(.+)$' }, 25075 propList: { propList: true }, 25076 angle: { number: true, units: 'deg|rad', implicitUnits: 'rad' }, 25077 textRotation: { number: true, units: 'deg|rad', implicitUnits: 'rad', enums: [ 'none', 'autorotate' ] }, 25078 polygonPointList: { number: true, multiple: true, evenMultiple: true, min: -1, max: 1, unitless: true }, 25079 edgeDistances: { enums: ['intersection', 'node-position'] }, 25080 easing: { 25081 regexes: [ 25082 '^(spring)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$', 25083 '^(cubic-bezier)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$' 25084 ], 25085 enums: [ 25086 'linear', 25087 'ease', 'ease-in', 'ease-out', 'ease-in-out', 25088 'ease-in-sine', 'ease-out-sine', 'ease-in-out-sine', 25089 'ease-in-quad', 'ease-out-quad', 'ease-in-out-quad', 25090 'ease-in-cubic', 'ease-out-cubic', 'ease-in-out-cubic', 25091 'ease-in-quart', 'ease-out-quart', 'ease-in-out-quart', 25092 'ease-in-quint', 'ease-out-quint', 'ease-in-out-quint', 25093 'ease-in-expo', 'ease-out-expo', 'ease-in-out-expo', 25094 'ease-in-circ', 'ease-out-circ', 'ease-in-out-circ' 25095 ] 25096 } 25097 }; 25098 25099 // define visual style properties 25100 var t = styfn.types; 25101 var props = styfn.properties = [ 25102 // main label 25103 { name: 'label', type: t.text }, 25104 { name: 'text-rotation', type: t.textRotation }, 25105 { name: 'text-margin-x', type: t.bidirectionalSize }, 25106 { name: 'text-margin-y', type: t.bidirectionalSize }, 25107 25108 // source label 25109 { name: 'source-label', type: t.text }, 25110 { name: 'source-text-rotation', type: t.textRotation }, 25111 { name: 'source-text-margin-x', type: t.bidirectionalSize }, 25112 { name: 'source-text-margin-y', type: t.bidirectionalSize }, 25113 { name: 'source-text-offset', type: t.size }, 25114 25115 // target label 25116 { name: 'target-label', type: t.text }, 25117 { name: 'target-text-rotation', type: t.textRotation }, 25118 { name: 'target-text-margin-x', type: t.bidirectionalSize }, 25119 { name: 'target-text-margin-y', type: t.bidirectionalSize }, 25120 { name: 'target-text-offset', type: t.size }, 25121 25122 // common label style 25123 { name: 'text-valign', type: t.valign }, 25124 { name: 'text-halign', type: t.halign }, 25125 { name: 'color', type: t.color }, 25126 { name: 'text-outline-color', type: t.color }, 25127 { name: 'text-outline-width', type: t.size }, 25128 { name: 'text-outline-opacity', type: t.zeroOneNumber }, 25129 { name: 'text-opacity', type: t.zeroOneNumber }, 25130 { name: 'text-background-color', type: t.color }, 25131 { name: 'text-background-margin', type: t.size }, 25132 { name: 'text-background-opacity', type: t.zeroOneNumber }, 25133 { name: 'text-border-opacity', type: t.zeroOneNumber }, 25134 { name: 'text-border-color', type: t.color }, 25135 { name: 'text-border-width', type: t.size }, 25136 { name: 'text-border-style', type: t.borderStyle },
25137 { name: 'text-background-shape', type: t.textBackgroundShape}, 25138 // { name: 'text-decoration', type: t.textDecoration }, // not supported in canvas 25139 { name: 'text-transform', type: t.textTransform }, 25140 { name: 'text-wrap', type: t.textWrap }, 25141 { name: 'text-max-width', type: t.size }, 25142 { name: 'text-events', type: t.bool }, 25143 { name: 'font-family', type: t.fontFamily }, 25144 { name: 'font-style', type: t.fontStyle }, 25145 // { name: 'font-variant', type: t.fontVariant }, // not useful 25146 { name: 'font-weight', type: t.fontWeight }, 25147 { name: 'font-size', type: t.size }, 25148 { name: 'min-zoomed-font-size', type: t.size }, 25149 25150 // behaviour 25151 { name: 'events', type: t.bool }, 25152 25153 // visibility 25154 { name: 'display', type: t.display }, 25155 { name: 'visibility', type: t.visibility }, 25156 { name: 'opacity', type: t.zeroOneNumber }, 25157 { name: 'z-index', type: t.nonNegativeInt }, 25158 25159 // overlays 25160 { name: 'overlay-padding', type: t.size }, 25161 { name: 'overlay-color', type: t.color }, 25162 { name: 'overlay-opacity', type: t.zeroOneNumber }, 25163 25164 // shadows 25165 { name: 'shadow-blur', type: t.size }, 25166 { name: 'shadow-color', type: t.color }, 25167 { name: 'shadow-opacity', type: t.zeroOneNumber }, 25168 { name: 'shadow-offset-x', type: t.bidirectionalSize }, 25169 { name: 'shadow-offset-y', type: t.bidirectionalSize }, 25170 25171 // label shadows 25172 { name: 'text-shadow-blur', type: t.size }, 25173 { name: 'text-shadow-color', type: t.color }, 25174 { name: 'text-shadow-opacity', type: t.zeroOneNumber }, 25175 { name: 'text-shadow-offset-x', type: t.bidirectionalSize }, 25176 { name: 'text-shadow-offset-y', type: t.bidirectionalSize }, 25177 25178 // transition anis 25179 { name: 'transition-property', type: t.propList }, 25180 { name: 'transition-duration', type: t.time }, 25181 { name: 'transition-delay', type: t.time }, 25182 { name: 'transition-timing-function', type: t.easing }, 25183 25184 // node body 25185 { name: 'height', type: t.nodeSize }, 25186 { name: 'width', type: t.nodeSize }, 25187 { name: 'shape', type: t.nodeShape }, 25188 { name: 'shape-polygon-points', type: t.polygonPointList }, 25189 { name: 'background-color', type: t.color }, 25190 { name: 'background-opacity', type: t.zeroOneNumber }, 25191 { name: 'background-blacken', type: t.nOneOneNumber }, 25192 { name: 'padding-left', type: t.size }, 25193 { name: 'padding-right', type: t.size }, 25194 { name: 'padding-top', type: t.size }, 25195 { name: 'padding-bottom', type: t.size }, 25196 25197 // node border 25198 { name: 'border-color', type: t.color }, 25199 { name: 'border-opacity', type: t.zeroOneNumber }, 25200 { name: 'border-width', type: t.size }, 25201 { name: 'border-style', type: t.borderStyle }, 25202 25203 // node background images 25204 { name: 'background-image', type: t.url }, 25205 { name: 'background-image-crossorigin', type: t.bgCrossOrigin }, 25206 { name: 'background-image-opacity', type: t.zeroOneNumber }, 25207 { name: 'background-position-x', type: t.bgPos }, 25208 { name: 'background-position-y', type: t.bgPos }, 25209 { name: 'background-repeat', type: t.bgRepeat }, 25210 { name: 'background-fit', type: t.bgFit }, 25211 { name: 'background-clip', type: t.bgClip }, 25212 { name: 'background-width', type: t.bgWH }, 25213 { name: 'background-height', type: t.bgWH }, 25214 25215 // compound props 25216 { name: 'position', type: t.position }, 25217 { name: 'compound-sizing-wrt-labels', type: t.compoundIncludeLabels }, 25218 25219 // edge line 25220 { name: 'line-style', type: t.lineStyle }, 25221 { name: 'line-color', type: t.color }, 25222 { name: 'curve-style', type: t.curveStyle }, 25223 { name: 'haystack-radius', type: t.zeroOneNumber }, 25224 { name: 'control-point-step-size', type: t.size }, 25225 { name: 'control-point-distances', type: t.bidirectionalSizes }, 25226 { name: 'control-point-weights', type: t.numbers }, 25227 { name: 'segment-distances', type: t.bidirectionalSizes }, 25228 { name: 'segment-weights', type: t.numbers }, 25229 { name: 'edge-distances', type: t.edgeDistances }, 25230 25231 // these are just for the core 25232 { name: 'selection-box-color', type: t.color }, 25233 { name: 'selection-box-opacity', type: t.zeroOneNumber }, 25234 { name: 'selection-box-border-color', type: t.color }, 25235 { name: 'selection-box-border-width', type: t.size }, 25236 { name: 'active-bg-color', type: t.color }, 25237 { name: 'active-bg-opacity', type: t.zeroOneNumber }, 25238 { name: 'active-bg-size', type: t.size }, 25239 { name: 'outside-texture-bg-color', type: t.color }, 25240 { name: 'outside-texture-bg-opacity', type: t.zeroOneNumber } 25241 ]; 25242 25243 // define aliases 25244 var aliases = styfn.aliases = [ 25245 { name: 'content', pointsTo: 'label' }, 25246 { name: 'control-point-distance', pointsTo: 'control-point-distances' },
25247 { name: 'control-point-weight', pointsTo: 'control-point-weights' }, 25248 { name: 'edge-text-rotation', pointsTo: 'text-rotation' } 25249 ]; 25250 25251 // pie backgrounds for nodes 25252 styfn.pieBackgroundN = 16; // because the pie properties are numbered, give access to a constant N (for renderer use) 25253 props.push( { name: 'pie-size', type: t.bgSize } ); 25254 for( var i = 1; i <= styfn.pieBackgroundN; i++ ){ 25255 props.push( { name: 'pie-' + i + '-background-color', type: t.color } ); 25256 props.push( { name: 'pie-' + i + '-background-size', type: t.percent } ); 25257 props.push( { name: 'pie-' + i + '-background-opacity', type: t.zeroOneNumber } ); 25258 } 25259 25260 // edge arrows 25261 var arrowPrefixes = styfn.arrowPrefixes = [ 'source', 'mid-source', 'target', 'mid-target' ]; 25262 [ 25263 { name: 'arrow-shape', type: t.arrowShape }, 25264 { name: 'arrow-color', type: t.color }, 25265 { name: 'arrow-fill', type: t.arrowFill } 25266 ].forEach( function( prop ){ 25267 arrowPrefixes.forEach( function( prefix ){ 25268 var name = prefix + '-' + prop.name; 25269 var type = prop.type; 25270 25271 props.push( { name: name, type: type } ); 25272 } ); 25273 }, {} ); 25274 25275 // list of property names 25276 styfn.propertyNames = props.map( function( p ){ return p.name; } ); 25277 25278 // allow access of properties by name ( e.g. style.properties.height ) 25279 for( var i = 0; i < props.length; i++ ){ 25280 var prop = props[ i ]; 25281 25282 props[ prop.name ] = prop; // allow lookup by name 25283 } 25284 25285 // map aliases 25286 for( var i = 0; i < aliases.length; i++ ){ 25287 var alias = aliases[ i ]; 25288 var pointsToProp = props[ alias.pointsTo ]; 25289 var aliasProp = { 25290 name: alias.name, 25291 alias: true, 25292 pointsTo: pointsToProp 25293 }; 25294 25295 // add alias prop for parsing 25296 props.push( aliasProp ); 25297 25298 props[ alias.name ] = aliasProp; // allow lookup by name 25299 } 25300})(); 25301 25302styfn.getDefaultProperty = function( name ){ 25303 return this.getDefaultProperties()[ name ]; 25304}; 25305 25306styfn.getDefaultProperties = util.memoize( function(){ 25307 var rawProps = util.extend( { 25308 'events': 'yes', 25309 'text-events': 'no', 25310 'text-valign': 'top', 25311 'text-halign': 'center', 25312 'color': '#000', 25313 'text-outline-color': '#000', 25314 'text-outline-width': 0, 25315 'text-outline-opacity': 1, 25316 'text-opacity': 1, 25317 'text-decoration': 'none', 25318 'text-transform': 'none', 25319 'text-wrap': 'none', 25320 'text-max-width': 9999, 25321 'text-background-color': '#000', 25322 'text-background-opacity': 0, 25323 'text-background-margin': 0, 25324 'text-border-opacity': 0, 25325 'text-border-width': 0, 25326 'text-border-style': 'solid', 25327 'text-border-color': '#000', 25328 'text-background-shape': 'rectangle', 25329 'font-family': 'Helvetica Neue, Helvetica, sans-serif', 25330 'font-style': 'normal', 25331 // 'font-variant': fontVariant, 25332 'font-weight': 'normal', 25333 'font-size': 16, 25334 'min-zoomed-font-size': 0, 25335 'text-rotation': 'none', 25336 'source-text-rotation': 'none', 25337 'target-text-rotation': 'none', 25338 'visibility': 'visible', 25339 'display': 'element', 25340 'opacity': 1, 25341 'z-index': 0, 25342 'label': '', 25343 'text-margin-x': 0, 25344 'text-margin-y': 0, 25345 'source-label': '', 25346 'source-text-offset': 0, 25347 'source-text-margin-x': 0, 25348 'source-text-margin-y': 0, 25349 'target-label': '', 25350 'target-text-offset': 0, 25351 'target-text-margin-x': 0, 25352 'target-text-margin-y': 0, 25353 'overlay-opacity': 0, 25354 'overlay-color': '#000', 25355 'overlay-padding': 10, 25356 'shadow-opacity': 0, 25357 'shadow-color': '#000', 25358 'shadow-blur': 10, 25359 'shadow-offset-x': 0, 25360 'shadow-offset-y': 0, 25361 'text-shadow-opacity': 0, 25362 'text-shadow-color': '#000', 25363 'text-shadow-blur': 5, 25364 'text-shadow-offset-x': 0, 25365 'text-shadow-offset-y': 0, 25366 'transition-property': 'none', 25367 'transition-duration': 0, 25368 'transition-delay': 0, 25369 'transition-timing-function': 'linear', 25370 25371 // node props 25372 'background-blacken': 0, 25373 'background-color': '#999', 25374 'background-opacity': 1, 25375 'background-image': 'none', 25376 'background-image-crossorigin': 'anonymous', 25377 'background-image-opacity': 1, 25378 'background-position-x': '50%', 25379 'background-position-y': '50%', 25380 'background-repeat': 'no-repeat', 25381 'background-fit': 'none',
25382 'background-clip': 'node', 25383 'background-width': 'auto', 25384 'background-height': 'auto', 25385 'border-color': '#000', 25386 'border-opacity': 1, 25387 'border-width': 0, 25388 'border-style': 'solid', 25389 'height': 30, 25390 'width': 30, 25391 'shape': 'ellipse', 25392 'shape-polygon-points': '-1, -1, 1, -1, 1, 1, -1, 1', 25393 25394 // compound props 25395 'padding-top': 0, 25396 'padding-bottom': 0, 25397 'padding-left': 0, 25398 'padding-right': 0, 25399 'position': 'origin', 25400 'compound-sizing-wrt-labels': 'include' 25401 }, { 25402 // node pie bg 25403 'pie-size': '100%' 25404 }, [ 25405 { name: 'pie-{{i}}-background-color', value: 'black' }, 25406 { name: 'pie-{{i}}-background-size', value: '0%' }, 25407 { name: 'pie-{{i}}-background-opacity', value: 1 } 25408 ].reduce( function( css, prop ){ 25409 for( var i = 1; i <= styfn.pieBackgroundN; i++ ){ 25410 var name = prop.name.replace( '{{i}}', i ); 25411 var val = prop.value; 25412 25413 css[ name ] = val; 25414 } 25415 25416 return css; 25417 }, {} ), { 25418 // edge props 25419 'line-style': 'solid', 25420 'line-color': '#999', 25421 'control-point-step-size': 40, 25422 'control-point-weights': 0.5, 25423 'segment-weights': 0.5, 25424 'segment-distances': 20, 25425 'edge-distances': 'intersection', 25426 'curve-style': 'bezier', 25427 'haystack-radius': 0 25428 }, [ 25429 { name: 'arrow-shape', value: 'none' }, 25430 { name: 'arrow-color', value: '#999' }, 25431 { name: 'arrow-fill', value: 'filled' } 25432 ].reduce( function( css, prop ){ 25433 styfn.arrowPrefixes.forEach( function( prefix ){ 25434 var name = prefix + '-' + prop.name; 25435 var val = prop.value; 25436 25437 css[ name ] = val; 25438 } ); 25439 25440 return css; 25441 }, {} ) ); 25442 25443 var parsedProps = {}; 25444 25445 for( var i = 0; i < this.properties.length; i++ ){ 25446 var prop = this.properties[i]; 25447 25448 if( prop.pointsTo ){ continue; } 25449 25450 var name = prop.name; 25451 var val = rawProps[ name ]; 25452 var parsedProp = this.parse( name, val ); 25453 25454 parsedProps[ name ] = parsedProp; 25455 } 25456 25457 return parsedProps; 25458} ); 25459 25460styfn.addDefaultStylesheet = function(){ 25461 this 25462 .selector( '$node > node' ) // compound (parent) node properties 25463 .css( { 25464 'shape': 'rectangle', 25465 'padding-top': 10, 25466 'padding-right': 10, 25467 'padding-left': 10, 25468 'padding-bottom': 10, 25469 'background-color': '#eee', 25470 'border-color': '#ccc', 25471 'border-width': 1 25472 } ) 25473 .selector( 'edge' ) // just edge properties 25474 .css( { 25475 'width': 3, 25476 'curve-style': 'haystack' 25477 } ) 25478 .selector( ':selected' ) 25479 .css( { 25480 'background-color': '#0169D9', 25481 'line-color': '#0169D9', 25482 'source-arrow-color': '#0169D9', 25483 'target-arrow-color': '#0169D9', 25484 'mid-source-arrow-color': '#0169D9', 25485 'mid-target-arrow-color': '#0169D9' 25486 } ) 25487 .selector( 'node:parent:selected' ) 25488 .css( { 25489 'background-color': '#CCE1F9', 25490 'border-color': '#aec8e5' 25491 } ) 25492 .selector( ':active' ) 25493 .css( { 25494 'overlay-color': 'black', 25495 'overlay-padding': 10, 25496 'overlay-opacity': 0.25 25497 } ) 25498 .selector( 'core' ) // just core properties 25499 .css( { 25500 'selection-box-color': '#ddd', 25501 'selection-box-opacity': 0.65, 25502 'selection-box-border-color': '#aaa', 25503 'selection-box-border-width': 1, 25504 'active-bg-color': 'black', 25505 'active-bg-opacity': 0.15, 25506 'active-bg-size': 30, 25507 'outside-texture-bg-color': '#000', 25508 'outside-texture-bg-opacity': 0.125 25509 } ) 25510 ; 25511 25512 this.defaultLength = this.length; 25513}; 25514 25515module.exports = styfn; 25516 25517},{"../util":100}],96:[function(_dereq_,module,exports){ 25518'use strict'; 25519 25520var util = _dereq_( '../util' ); 25521var Selector = _dereq_( '../selector' ); 25522 25523var styfn = {}; 25524 25525styfn.applyFromString = function( string ){ 25526 var self = this; 25527 var style = this; 25528 var remaining = '' + string; 25529 var selAndBlockStr; 25530 var blockRem; 25531 var propAndValStr; 25532 25533 // remove comments from the style string 25534 remaining = remaining.replace( /[/][*](\s|.)+?[*][/]/g, '' ); 25535 25536 function removeSelAndBlockFromRemaining(){ 25537 // remove the parsed selector and block from the remaining text to parse 25538 if( remaining.length > selAndBlockStr.length ){ 25539 remaining = remaining.substr( selAndBlockStr.length ); 25540 } else { 25541 remaining = ''; 25542 } 25543 } 25544 25545 function removePropAndValFromRem(){ 25546 // remove the parsed property and value from the remaining block text to parse 25547 if( blockRem.length > propAndValStr.length ){ 25548 blockRem = blockRem.substr( propAndValStr.length ); 25549 } else { 25550 blockRem = ''; 25551 } 25552 } 25553 25554 while( true ){ 25555 var nothingLeftToParse = remaining.match( /^\s*$/ ); 25556 if( nothingLeftToParse ){ break; } 25557 25558 var selAndBlock = remaining.match( /^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/ ); 25559 25560 if( !selAndBlock ){ 25561 util.error( 'Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: ' + remaining ); 25562 break; 25563 } 25564 25565 selAndBlockStr = selAndBlock[0]; 25566 25567 // parse the selector 25568 var selectorStr = selAndBlock[1]; 25569 if( selectorStr !== 'core' ){ 25570 var selector = new Selector( selectorStr );
25571 if( selector._private.invalid ){ 25572 util.error( 'Skipping parsing of block: Invalid selector found in string stylesheet: ' + selectorStr ); 25573 25574 // skip this selector and block 25575 removeSelAndBlockFromRemaining(); 25576 continue; 25577 } 25578 } 25579 25580 // parse the block of properties and values 25581 var blockStr = selAndBlock[2]; 25582 var invalidBlock = false; 25583 blockRem = blockStr; 25584 var props = []; 25585 25586 while( true ){ 25587 var nothingLeftToParse = blockRem.match( /^\s*$/ ); 25588 if( nothingLeftToParse ){ break; } 25589 25590 var propAndVal = blockRem.match( /^\s*(.+?)\s*:\s*(.+?)\s*;/ ); 25591 25592 if( !propAndVal ){ 25593 util.error( 'Skipping parsing of block: Invalid formatting of style property and value definitions found in:' + blockStr ); 25594 invalidBlock = true; 25595 break; 25596 } 25597 25598 propAndValStr = propAndVal[0]; 25599 var propStr = propAndVal[1]; 25600 var valStr = propAndVal[2]; 25601 25602 var prop = self.properties[ propStr ]; 25603 if( !prop ){ 25604 util.error( 'Skipping property: Invalid property name in: ' + propAndValStr ); 25605 25606 // skip this property in the block 25607 removePropAndValFromRem(); 25608 continue; 25609 } 25610 25611 var parsedProp = style.parse( propStr, valStr ); 25612 25613 if( !parsedProp ){ 25614 util.error( 'Skipping property: Invalid property definition in: ' + propAndValStr ); 25615 25616 // skip this property in the block 25617 removePropAndValFromRem(); 25618 continue; 25619 } 25620 25621 props.push( { 25622 name: propStr, 25623 val: valStr 25624 } ); 25625 removePropAndValFromRem(); 25626 } 25627 25628 if( invalidBlock ){ 25629 removeSelAndBlockFromRemaining(); 25630 break; 25631 } 25632 25633 // put the parsed block in the style 25634 style.selector( selectorStr ); 25635 for( var i = 0; i < props.length; i++ ){ 25636 var prop = props[ i ]; 25637 style.css( prop.name, prop.val ); 25638 } 25639 25640 removeSelAndBlockFromRemaining(); 25641 } 25642 25643 return style; 25644}; 25645 25646styfn.fromString = function( string ){ 25647 var style = this; 25648 25649 style.resetToDefault(); 25650 style.applyFromString( string ); 25651 25652 return style; 25653}; 25654 25655module.exports = styfn; 25656 25657},{"../selector":87,"../util":100}],97:[function(_dereq_,module,exports){ 25658'use strict'; 25659 25660var is = _dereq_( './is' ); 25661var util = _dereq_( './util' ); 25662var Style = _dereq_( './style' ); 25663 25664// a dummy stylesheet object that doesn't need a reference to the core 25665// (useful for init) 25666var Stylesheet = function(){ 25667 if( !(this instanceof Stylesheet) ){ 25668 return new Stylesheet(); 25669 } 25670 25671 this.length = 0; 25672}; 25673 25674var sheetfn = Stylesheet.prototype; 25675 25676sheetfn.instanceString = function(){ 25677 return 'stylesheet'; 25678}; 25679 25680// just store the selector to be parsed later 25681sheetfn.selector = function( selector ){ 25682 var i = this.length++; 25683 25684 this[ i ] = { 25685 selector: selector, 25686 properties: [] 25687 }; 25688 25689 return this; // chaining 25690}; 25691 25692// just store the property to be parsed later 25693sheetfn.css = function( name, value ){ 25694 var i = this.length - 1; 25695 25696 if( is.string( name ) ){ 25697 this[ i ].properties.push( { 25698 name: name, 25699 value: value 25700 } ); 25701 } else if( is.plainObject( name ) ){ 25702 var map = name; 25703 25704 for( var j = 0; j < Style.properties.length; j++ ){ 25705 var prop = Style.properties[ j ]; 25706 var mapVal = map[ prop.name ]; 25707 25708 if( mapVal === undefined ){ // also try camel case name 25709 mapVal = map[ util.dash2camel( prop.name ) ]; 25710 } 25711 25712 if( mapVal !== undefined ){ 25713 var name = prop.name; 25714 var value = mapVal; 25715 25716 this[ i ].properties.push( { 25717 name: name, 25718 value: value 25719 } ); 25720 } 25721 } 25722 } 25723 25724 return this; // chaining 25725}; 25726 25727sheetfn.style = sheetfn.css; 25728 25729// generate a real style object from the dummy stylesheet 25730sheetfn.generateStyle = function( cy ){ 25731 var style = new Style( cy ); 25732 25733 for( var i = 0; i < this.length; i++ ){ 25734 var context = this[ i ]; 25735 var selector = context.selector; 25736 var props = context.properties; 25737 25738 style.selector( selector ); // apply selector 25739 25740 for( var j = 0; j < props.length; j++ ){ 25741 var prop = props[ j ]; 25742 25743 style.css( prop.name, prop.value ); // apply property 25744 } 25745 } 25746 25747 return style; 25748}; 25749 25750module.exports = Stylesheet; 25751 25752}
25752,{"./is":83,"./style":92,"./util":100}],98:[function(_dereq_,module,exports){ 25753/*! Weaver licensed under MIT (https://tldrlegal.com/license/mit-license), copyright Max Franz */ 25754 25755// cross-env thread/worker 25756// NB : uses (heavyweight) processes on nodejs so best not to create too many threads 25757 25758'use strict'; 25759 25760var window = _dereq_('./window'); 25761var util = _dereq_('./util'); 25762var Promise = _dereq_('./promise'); 25763var Event = _dereq_('./event'); 25764var define = _dereq_('./define'); 25765var is = _dereq_('./is'); 25766 25767var Thread = function( opts ){ 25768 if( !(this instanceof Thread) ){ 25769 return new Thread( opts ); 25770 } 25771 25772 var _p = this._private = { 25773 requires: [], 25774 files: [], 25775 queue: null, 25776 pass: [], 25777 disabled: false 25778 }; 25779 25780 if( is.plainObject(opts) ){ 25781 if( opts.disabled != null ){ 25782 _p.disabled = !!opts.disabled; 25783 } 25784 } 25785 25786}; 25787 25788var thdfn = Thread.prototype; // short alias 25789 25790var stringifyFieldVal = function( val ){ 25791 var valStr = is.fn( val ) ? val.toString() : "JSON.parse('" + JSON.stringify(val) + "')"; 25792 25793 return valStr; 25794}; 25795 25796// allows for requires with prototypes and subobjs etc 25797var fnAsRequire = function( fn ){ 25798 var req; 25799 var fnName; 25800 25801 if( is.object(fn) && fn.fn ){ // manual fn 25802 req = fnAs( fn.fn, fn.name ); 25803 fnName = fn.name; 25804 fn = fn.fn; 25805 } else if( is.fn(fn) ){ // auto fn 25806 req = fn.toString(); 25807 fnName = fn.name; 25808 } else if( is.string(fn) ){ // stringified fn 25809 req = fn; 25810 } else if( is.object(fn) ){ // plain object 25811 if( fn.proto ){ 25812 req = ''; 25813 } else { 25814 req = fn.name + ' = {};'; 25815 } 25816 25817 fnName = fn.name; 25818 fn = fn.obj; 25819 } 25820 25821 req += '\n'; 25822 25823 var protoreq = function( val, subname ){ 25824 if( val.prototype ){ 25825 var protoNonempty = false; 25826 for( var prop in val.prototype ){ protoNonempty = true; break; } // jshint ignore:line 25827 25828 if( protoNonempty ){ 25829 req += fnAsRequire( { 25830 name: subname, 25831 obj: val, 25832 proto: true 25833 }, val ); 25834 } 25835 } 25836 }; 25837 25838 // pull in prototype 25839 if( fn.prototype && fnName != null ){ 25840 25841 for( var name in fn.prototype ){ 25842 var protoStr = ''; 25843 25844 var val = fn.prototype[ name ]; 25845 var valStr = stringifyFieldVal( val ); 25846 var subname = fnName + '.prototype.' + name; 25847 25848 protoStr += subname + ' = ' + valStr + ';\n'; 25849 25850 if( protoStr ){ 25851 req += protoStr; 25852 } 25853 25854 protoreq( val, subname ); // subobject with prototype 25855 } 25856 25857 } 25858 25859 // pull in properties for obj/fns 25860 if( !is.string(fn) ){ for( var name in fn ){ 25861 var propsStr = ''; 25862 25863 if( fn.hasOwnProperty(name) ){ 25864 var val = fn[ name ]; 25865 var valStr = stringifyFieldVal( val ); 25866 var subname = fnName + '["' + name + '"]'; 25867 25868 propsStr += subname + ' = ' + valStr + ';\n'; 25869 } 25870 25871 if( propsStr ){ 25872 req += propsStr; 25873 } 25874 25875 protoreq( val, subname ); // subobject with prototype 25876 } } 25877 25878 return req; 25879}; 25880 25881var isPathStr = function( str ){ 25882 return is.string(str) && str.match(/\.js$/); 25883}; 25884 25885util.extend(thdfn, { 25886 25887 instanceString: function(){ return 'thread'; }, 25888 25889 require: function( fn, as ){ 25890 var requires = this._private.requires; 25891 25892 if( isPathStr(fn) ){ 25893 this._private.files.push( fn ); 25894 25895 return this; 25896 } 25897 25898 if( as ){ 25899 if( is.fn(fn) ){ 25900 fn = { name: as, fn: fn }; 25901 } else { 25902 fn = { name: as, obj: fn }; 25903 } 25904 } else { 25905 if( is.fn(fn) ){ 25906 if( !fn.name ){ 25907 throw 'The function name could not be automatically determined. Use thread.require( someFunction, "someFunction" )'; 25908 } 25909 25910 fn = { name: fn.name, fn: fn }; 25911 } 25912 } 25913 25914 requires.push( fn ); 25915 25916 return this; // chaining 25917 }, 25918 25919 pass: function( data ){ 25920 this._private.pass.push( data ); 25921 25922 return this; // chaining 25923 }, 25924 25925 run: function( fn, pass ){ // fn used like main() 25926 var self = this; 25927 var _p = this._private; 25928 pass = pass || _p.pass.shift(); 25929 25930 if( _p.stopped ){ 25931 throw 'Attempted to run a stopped thread! Start a new thread or do not stop the existing thread and reuse it.'; 25932 } 25933 25934 if( _p.running ){ 25935 return ( _p.queue = _p.queue.then(function(){ // inductive step 25936 return self.run( fn, pass ); 25937 }) ); 25938 } 25939 25940 var useWW = window != null && !_p.disabled; 25941 var useNode = !window && typeof module !== 'undefined' && !_p.disabled; 25942 25943 self.trigger('run'); 25944 25945 var runP = new Promise(function( resolve, reject ){ 25946 25947 _p.running = true; 25948 25949 var threadTechAlreadyExists = _p.ran; 25950 25951 var fnImplStr = is.string( fn ) ? fn : fn.toString(); 25952 25953 // worker code to exec 25954 var fnStr = '\n' + ( _p.requires.map(function( r ){ 25955 return fnAsRequire( r ); 25956 }) ).concat( _p.files.map(function( f ){ 25957 if( useWW ){ 25958 var wwifyFile = function( file ){ 25959 if( file.match(/^\.\//) || file.match(/^\.\./) ){ 25960 return window.location.origin + window.location.pathname + file; 25961 } else if( file.match(/^\//) ){ 25962 return window.location.origin + '/' + file; 25963 } 25964 return file; 25965 }; 25966 25967 return 'importScripts("' + wwifyFile(f) + '");'; 25968 } else if( useNode ) { 25969 return 'eval( require("fs").readFileSync("' + f + '", { encoding: "utf8" }) );'; 25970 } else { 25971 throw 'External file `' + f + '` can not be required without any threading technology.'; 25972 } 25973 }) ).concat([ 25974 '( function(){', 25975 'var ret = (' + fnImplStr + ')(' + JSON.stringify(pass) + ');', 25976 'if( ret !== undefined ){ resolve(ret); }', // assume if ran fn returns defined value (incl. null), that we want to resolve to it 25977 '} )()\n' 25978 ]).join('\n'); 25979 25980 // because we've now consumed the requires, empty the list so we don't dupe on next run() 25981 _p.requires = []; 25982 _p.files = []; 25983 25984 if( useWW ){ 25985 var fnBlob, fnUrl; 25986 25987 // add normalised thread api functions
25988 if( !threadTechAlreadyExists ){ 25989 var fnPre = fnStr + ''; 25990 25991 fnStr = [ 25992 'function _ref_(o){ return eval(o); };', 25993 'function broadcast(m){ return message(m); };', // alias 25994 'function message(m){ postMessage(m); };', 25995 'function listen(fn){', 25996 ' self.addEventListener("message", function(m){ ', 25997 ' if( typeof m === "object" && (m.data.$$eval || m.data === "$$start") ){', 25998 ' } else { ', 25999 ' fn( m.data );', 26000 ' }', 26001 ' });', 26002 '};', 26003 'self.addEventListener("message", function(m){ if( m.data.$$eval ){ eval( m.data.$$eval ); } });', 26004 'function resolve(v){ postMessage({ $$resolve: v }); };', 26005 'function reject(v){ postMessage({ $$reject: v }); };' 26006 ].join('\n'); 26007 26008 fnStr += fnPre; 26009 26010 fnBlob = new Blob([ fnStr ], { 26011 type: 'application/javascript' 26012 }); 26013 fnUrl = window.URL.createObjectURL( fnBlob ); 26014 } 26015 // create webworker and let it exec the serialised code 26016 var ww = _p.webworker = _p.webworker || new Worker( fnUrl ); 26017 26018 if( threadTechAlreadyExists ){ // then just exec new run() code 26019 ww.postMessage({ 26020 $$eval: fnStr 26021 }); 26022 } 26023 26024 // worker messages => events 26025 var cb; 26026 ww.addEventListener('message', cb = function( m ){ 26027 var isObject = is.object(m) && is.object( m.data ); 26028 26029 if( isObject && ('$$resolve' in m.data) ){ 26030 ww.removeEventListener('message', cb); // done listening b/c resolve() 26031 26032 resolve( m.data.$$resolve ); 26033 } else if( isObject && ('$$reject' in m.data) ){ 26034 ww.removeEventListener('message', cb); // done listening b/c reject() 26035 26036 reject( m.data.$$reject ); 26037 } else { 26038 self.trigger( new Event(m, { type: 'message', message: m.data }) ); 26039 } 26040 }, false); 26041 26042 if( !threadTechAlreadyExists ){ 26043 ww.postMessage('$$start'); // start up the worker 26044 } 26045 26046 } else if( useNode ){ 26047 // create a new process 26048 26049 if( !_p.child ){ 26050 _p.child = ( _dereq_('child_process').fork( _dereq_('path').join(__dirname, 'thread-node-fork') ) ); 26051 } 26052 26053 var child = _p.child; 26054 26055 // child process messages => events 26056 var cb; 26057 child.on('message', cb = function( m ){ 26058 if( is.object(m) && ('$$resolve' in m) ){ 26059 child.removeListener('message', cb); // done listening b/c resolve() 26060 26061 resolve( m.$$resolve ); 26062 } else if( is.object(m) && ('$$reject' in m) ){ 26063 child.removeListener('message', cb); // done listening b/c reject() 26064 26065 reject( m.$$reject ); 26066 } else { 26067 self.trigger( new Event({}, { type: 'message', message: m }) ); 26068 } 26069 }); 26070 26071 // ask the child process to eval the worker code 26072 child.send({ 26073 $$eval: fnStr 26074 }); 26075 26076 } else { // use a fallback mechanism using a timeout 26077 26078 var promiseResolve = resolve; 26079 var promiseReject = reject; 26080 26081 var timer = _p.timer = _p.timer || { 26082 26083 listeners: [], 26084 26085 exec: function(){ 26086 // as a string so it can't be mangled by minifiers and processors 26087 fnStr = [ 26088 'function _ref_(o){ return eval(o); };', 26089 'function broadcast(m){ return message(m); };', 26090 'function message(m){ self.trigger( new Event({}, { type: "message", message: m }) ); };', 26091 'function listen(fn){ timer.listeners.push( fn ); };', 26092 'function resolve(v){ promiseResolve(v); };',
26093 'function reject(v){ promiseReject(v); };' 26094 ].join('\n') + fnStr; 26095 26096 // the .run() code 26097 eval( fnStr ); // jshint ignore:line 26098 }, 26099 26100 message: function( m ){ 26101 var ls = timer.listeners; 26102 26103 for( var i = 0; i < ls.length; i++ ){ 26104 var fn = ls[i]; 26105 26106 fn( m ); 26107 } 26108 } 26109 26110 }; 26111 26112 timer.exec(); 26113 } 26114 26115 }).then(function( v ){ 26116 _p.running = false; 26117 _p.ran = true; 26118 26119 self.trigger('ran'); 26120 26121 return v; 26122 }); 26123 26124 if( _p.queue == null ){ 26125 _p.queue = runP; // i.e. first step of inductive promise chain (for queue) 26126 } 26127 26128 return runP; 26129 }, 26130 26131 // send the thread a message 26132 message: function( m ){ 26133 var _p = this._private; 26134 26135 if( _p.webworker ){ 26136 _p.webworker.postMessage( m ); 26137 } 26138 26139 if( _p.child ){ 26140 _p.child.send( m ); 26141 } 26142 26143 if( _p.timer ){ 26144 _p.timer.message( m ); 26145 } 26146 26147 return this; // chaining 26148 }, 26149 26150 stop: function(){ 26151 var _p = this._private; 26152 26153 if( _p.webworker ){ 26154 _p.webworker.terminate(); 26155 } 26156 26157 if( _p.child ){ 26158 _p.child.kill(); 26159 } 26160 26161 if( _p.timer ){ 26162 // nothing we can do if we've run a timeout 26163 } 26164 26165 _p.stopped = true; 26166 26167 return this.trigger('stop'); // chaining 26168 }, 26169 26170 stopped: function(){ 26171 return this._private.stopped; 26172 } 26173 26174}); 26175 26176// turns a stringified function into a (re)named function 26177var fnAs = function( fn, name ){ 26178 var fnStr = fn.toString(); 26179 fnStr = fnStr.replace(/function\s*?\S*?\s*?\(/, 'function ' + n
26179ame + '('); 26180 26181 return fnStr; 26182}; 26183 26184var defineFnal = function( opts ){ 26185 opts = opts || {}; 26186 26187 return function fnalImpl( fn, arg1 ){ 26188 var fnStr = fnAs( fn, '_$_$_' + opts.name ); 26189 26190 this.require( fnStr ); 26191 26192 return this.run( [ 26193 'function( data ){', 26194 ' var origResolve = resolve;', 26195 ' var res = [];', 26196 ' ', 26197 ' resolve = function( val ){', 26198 ' res.push( val );', 26199 ' };', 26200 ' ', 26201 ' var ret = data.' + opts.name + '( _$_$_' + opts.name + ( arguments.length > 1 ? ', ' + JSON.stringify(arg1) : '' ) + ' );', 26202 ' ', 26203 ' resolve = origResolve;', 26204 ' resolve( res.length > 0 ? res : ret );', 26205 '}' 26206 ].join('\n') ); 26207 }; 26208}; 26209 26210util.extend(thdfn, { 26211 reduce: defineFnal({ name: 'reduce' }), 26212 26213 reduceRight: defineFnal({ name: 'reduceRight' }), 26214 26215 map: defineFnal({ name: 'map' }) 26216}); 26217 26218// aliases 26219var fn = thdfn; 26220fn.promise = fn.run; 26221fn.terminate = fn.halt = fn.stop; 26222fn.include = fn.require; 26223 26224// pull in event apis 26225util.extend(thdfn, { 26226 on: define.on(), 26227 one: define.on({ unbindSelfOnTrigger: true }), 26228 off: define.off(), 26229 trigger: define.trigger() 26230}); 26231 26232define.eventAliasesOn( thdfn ); 26233 26234module.exports = Thread; 26235 26236},{"./define":44,"./event":45,"./is":83,"./promise":86,"./util":100,"./window":107,"child_process":undefined,"path":undefined}],99:[function(_dereq_,module,exports){ 26237'use strict'; 26238 26239var is = _dereq_( '../is' ); 26240 26241module.exports = { 26242 // get [r, g, b] from #abc or #aabbcc 26243 hex2tuple: function( hex ){ 26244 if( !(hex.length === 4 || hex.length === 7) || hex[0] !== '#' ){ return; } 26245 26246 var shortHex = hex.length === 4; 26247 var r, g, b; 26248 var base = 16; 26249 26250 if( shortHex ){ 26251 r = parseInt( hex[1] + hex[1], base ); 26252 g = parseInt( hex[2] + hex[2], base ); 26253 b = parseInt( hex[3] + hex[3], base ); 26254 } else { 26255 r = parseInt( hex[1] + hex[2], base ); 26256 g = parseInt( hex[3] + hex[4], base ); 26257 b = parseInt( hex[5] + hex[6], base ); 26258 } 26259 26260 return [ r, g, b ]; 26261 }, 26262 26263 // get [r, g, b, a] from hsl(0, 0, 0) or hsla(0, 0, 0, 0) 26264 hsl2tuple: function( hsl ){ 26265 var ret; 26266 var h, s, l, a, r, g, b; 26267 function hue2rgb( p, q, t ){ 26268 if( t < 0 ) t += 1; 26269 if( t > 1 ) t -= 1; 26270 if( t < 1 / 6 ) return p + (q - p) * 6 * t; 26271 if( t < 1 / 2 ) return q; 26272 if( t < 2 / 3 ) return p + (q - p) * (2 / 3 - t) * 6; 26273 return p; 26274 } 26275 26276 var m = new RegExp( '^' + this.regex.hsla + '$' ).exec( hsl ); 26277 if( m ){ 26278 26279 // get hue 26280 h = parseInt( m[1] ); 26281 if( h < 0 ){ 26282 h = ( 360 - (-1 * h % 360) ) % 360; 26283 } else if( h > 360 ){ 26284 h = h % 360; 26285 } 26286 h /= 360; // normalise on [0, 1] 26287 26288 s = parseFloat( m[2] ); 26289 if( s < 0 || s > 100 ){ return; } // saturation is [0, 100] 26290 s = s / 100; // normalise on [0, 1] 26291 26292 l = parseFloat( m[3] ); 26293 if( l < 0 || l > 100 ){ return; } // lightness is [0, 100] 26294 l = l / 100; // normalise on [0, 1] 26295 26296 a = m[4]; 26297 if( a !== undefined ){ 26298 a = parseFloat( a ); 26299 26300 if( a < 0 || a > 1 ){ return; } // alpha is [0, 1] 26301 } 26302 26303 // now, convert to rgb 26304 // code from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript 26305 if( s === 0 ){ 26306 r = g = b = Math.round( l * 255 ); // achromatic 26307 } else { 26308 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 26309 var p = 2 * l - q; 26310 r = Math.round( 255 * hue2rgb( p, q, h + 1 / 3 ) ); 26311 g = Math.round( 255 * hue2rgb( p, q, h ) ); 26312 b = Math.round( 255 * hue2rgb( p, q, h - 1 / 3 ) ); 26313 } 26314 26315 ret = [ r, g, b, a ]; 26316 } 26317 26318 return ret; 26319 }, 26320 26321 // get [r, g, b, a] from rgb(0, 0, 0) or rgba(0, 0, 0, 0) 26322 rgb2tuple: function( rgb ){ 26323 var ret; 26324 26325 var m = new RegExp( '^' + this.regex.rgba + '$' ).exec( rgb ); 26326 if( m ){ 26327 ret = []; 26328 26329 var isPct = []; 26330 for( var i = 1; i <= 3; i++ ){ 26331 var channel = m[ i ]; 26332 26333 if( channel[ channel.length - 1 ] === '%' ){ 26334 isPct[ i ] = true; 26335 } 26336 channel = parseFloat( channel ); 26337 26338 if( isPct[ i ] ){ 26339 channel = channel / 100 * 255; // normalise to [0, 255] 26340 } 26341 26342 if( channel < 0 || channel > 255 ){ return; } // invalid channel value 26343
26344 ret.push( Math.floor( channel ) ); 26345 } 26346 26347 var atLeastOneIsPct = isPct[1] || isPct[2] || isPct[3]; 26348 var allArePct = isPct[1] && isPct[2] && isPct[3]; 26349 if( atLeastOneIsPct && !allArePct ){ return; } // must all be percent values if one is 26350 26351 var alpha = m[4]; 26352 if( alpha !== undefined ){ 26353 alpha = parseFloat( alpha ); 26354 26355 if( alpha < 0 || alpha > 1 ){ return; } // invalid alpha value 26356 26357 ret.push( alpha ); 26358 } 26359 } 26360 26361 return ret; 26362 }, 26363 26364 colorname2tuple: function( color ){ 26365 return this.colors[ color.toLowerCase() ]; 26366 }, 26367 26368 color2tuple: function( color ){ 26369 return ( is.array( color ) ? color : null ) 26370 || this.colorname2tuple( color ) 26371 || this.hex2tuple( color ) 26372 || this.rgb2tuple( color ) 26373 || this.hsl2tuple( color ); 26374 }, 26375 26376 colors: { 26377 // special colour names 26378 transparent: [0, 0, 0, 0], // NB alpha === 0 26379 26380 // regular colours 26381 aliceblue: [ 240, 248, 255 ], 26382 antiquewhite: [ 250, 235, 215 ], 26383 aqua: [0, 255, 255 ], 26384 aquamarine: [ 127, 255, 212 ], 26385 azure: [ 240, 255, 255 ], 26386 beige: [ 245, 245, 220 ], 26387 bisque: [ 255, 228, 196 ], 26388 black: [0, 0, 0], 26389 blanchedalmond: [ 255, 235, 205 ], 26390 blue: [0, 0, 255 ], 26391 blueviolet: [ 138, 43, 226 ], 26392 brown: [ 165, 42, 42 ], 26393 burlywood: [ 222, 184, 135 ], 26394 cadetblue: [ 95, 158, 160 ], 26395 chartreuse: [ 127, 255, 0], 26396 chocolate: [ 210, 105, 30 ], 26397 coral: [ 255, 127, 80 ], 26398 cornflowerblue: [ 100, 149, 237 ], 26399 cornsilk: [ 255, 248, 220 ], 26400 crimson: [ 220, 20, 60 ], 26401 cyan: [0, 255, 255 ], 26402 darkblue: [0, 0, 139 ], 26403 darkcyan: [0, 139, 139 ], 26404 darkgoldenrod: [ 184, 134, 11 ], 26405 darkgray: [ 169, 169, 169 ], 26406 darkgreen: [0, 100, 0], 26407 darkgrey: [ 169, 169, 169 ], 26408 darkkhaki: [ 189, 183, 107 ], 26409 darkmagenta: [ 139, 0, 139 ], 26410 darkolivegreen: [ 85, 107, 47 ], 26411 darkorange: [ 255, 140, 0], 26412 darkorchid: [ 153, 50, 204 ], 26413 darkred: [ 139, 0, 0], 26414 darksalmon: [ 233, 150, 122 ], 26415 darkseagreen: [ 143, 188, 143 ], 26416 darkslateblue: [ 72, 61, 139 ], 26417 darkslategray: [ 47, 79, 79 ], 26418 darkslategrey: [ 47, 79, 79 ], 26419 darkturquoise: [0, 206, 209 ], 26420 darkviolet: [ 148, 0, 211 ], 26421 deeppink: [ 255, 20, 147 ], 26422 deepskyblue: [0, 191, 255 ], 26423 dimgray: [ 105, 105, 105 ], 26424 dimgrey: [ 105, 105, 105 ], 26425 dodgerblue: [ 30, 144, 255 ], 26426 firebrick: [ 178, 34, 34 ], 26427 floralwhite: [ 255, 250, 240 ], 26428 forestgreen: [ 34, 139, 34 ], 26429 fuchsia: [ 255, 0, 255 ], 26430 gainsboro: [ 220, 220, 220 ], 26431 ghostwhite: [ 248, 248, 255 ], 26432 gold: [ 255, 215, 0], 26433 goldenrod: [ 218, 165, 32 ], 26434 gray: [ 128, 128, 128 ], 26435 grey: [ 128, 128, 128 ], 26436 green: [0, 128, 0], 26437 greenyellow: [ 173, 255, 47 ], 26438 honeydew: [ 240, 255, 240 ], 26439 hotpink: [ 255, 105, 180 ], 26440 indianred: [ 205, 92, 92 ], 26441 indigo: [ 75, 0, 130 ], 26442 ivory: [ 255, 255, 240 ], 26443 khaki: [ 240, 230, 140 ], 26444 lavender: [ 230, 230, 250 ], 26445 lavenderblush: [ 255, 240, 245 ], 26446 lawngreen: [ 124, 252, 0], 26447 lemonchiffon: [ 255, 250, 205 ], 26448 lightblue: [ 173, 216, 230 ], 26449 lightcoral: [ 240, 128, 128 ], 26450 lightcyan: [ 224, 255, 255 ], 26451 lightgoldenrodyellow: [ 250, 250, 210 ], 26452 lightgray: [ 211, 211, 211 ], 26453 lightgreen: [ 144, 238, 144 ], 26454 lightgrey: [ 211, 211, 211 ], 26455 lightpink: [ 255, 182, 193 ], 26456 lightsalmon: [ 255, 160, 122 ], 26457 lightseagreen: [ 32, 178, 170 ], 26458 lightskyblue: [ 135, 206, 250 ], 26459 lightslategray: [ 119, 136, 153 ], 26460 lightslategrey: [ 119, 136, 153 ], 26461 lightsteelblue: [ 176, 196, 222 ], 26462 lightyellow: [ 255, 255, 224 ], 26463 lime: [0, 255, 0], 26464 limegreen: [ 50, 205, 50 ], 26465 linen: [ 250, 240, 230 ], 26466 magenta: [ 255, 0, 255 ], 26467 maroon: [ 128, 0, 0], 26468 mediumaquamarine: [ 102, 205, 170 ], 26469 mediumblue: [0, 0, 205 ], 26470 mediumorchid: [ 186, 85, 211 ], 26471 mediumpurple: [ 147, 112, 219 ], 26472 mediumseagreen: [ 60, 179, 113 ], 26473 mediumslateblue: [ 123, 104, 238 ], 26474 mediumspringgreen: [0, 250, 154 ], 26475 mediumturquoise: [ 72, 209, 204 ], 26476 mediumvioletred: [ 199, 21, 133 ], 26477 midnightblue: [ 25, 25, 112 ], 26478 mintcream: [ 245, 255, 250 ], 26479 mistyrose: [ 255, 228, 225 ], 26480 moccasin: [ 255, 228, 181 ], 26481 navajowhite: [ 255, 222, 173 ], 26482 navy: [0, 0, 128 ], 26483 oldlace: [ 253, 245, 230 ], 26484 olive: [ 128, 128, 0], 26485 olivedrab: [ 107, 142, 35 ], 26486 orange: [ 255, 165, 0], 26487 orangered: [ 255, 69, 0], 26488 orchid: [ 218, 112, 214 ], 26489 palegoldenrod: [ 238, 232, 170 ], 26490 palegreen: [ 152, 251, 152 ], 26491 paleturquoise: [ 175, 238, 238 ], 26492 palevioletred: [ 219, 112, 147 ], 26493 papayawhip: [ 255, 239, 213 ], 26494 peachpuff: [ 255, 218, 185 ], 26495 peru: [ 205, 133, 63 ], 26496 pink: [ 255, 192, 203 ], 26497 plum: [ 221, 160, 221 ], 26498 powderblue: [ 176, 224, 230 ],
26499 purple: [ 128, 0, 128 ], 26500 red: [ 255, 0, 0], 26501 rosybrown: [ 188, 143, 143 ], 26502 royalblue: [ 65, 105, 225 ], 26503 saddlebrown: [ 139, 69, 19 ], 26504 salmon: [ 250, 128, 114 ], 26505 sandybrown: [ 244, 164, 96 ], 26506 seagreen: [ 46, 139, 87 ], 26507 seashell: [ 255, 245, 238 ], 26508 sienna: [ 160, 82, 45 ], 26509 silver: [ 192, 192, 192 ], 26510 skyblue: [ 135, 206, 235 ], 26511 slateblue: [ 106, 90, 205 ], 26512 slategray: [ 112, 128, 144 ], 26513 slategrey: [ 112, 128, 144 ], 26514 snow: [ 255, 250, 250 ], 26515 springgreen: [0, 255, 127 ], 26516 steelblue: [ 70, 130, 180 ], 26517 tan: [ 210, 180, 140 ], 26518 teal: [0, 128, 128 ], 26519 thistle: [ 216, 191, 216 ], 26520 tomato: [ 255, 99, 71 ], 26521 turquoise: [ 64, 224, 208 ], 26522 violet: [ 238, 130, 238 ], 26523 wheat: [ 245, 222, 179 ], 26524 white: [ 255, 255, 255 ], 26525 whitesmoke: [ 245, 245, 245 ], 26526 yellow: [ 255, 255, 0], 26527 yellowgreen: [ 154, 205, 50 ] 26528 } 26529}; 26530 26531},{"../is":83}],100:[function(_dereq_,module,exports){ 26532'use strict'; 26533 26534/*global console */ 26535 26536var is = _dereq_( '../is' ); 26537var math = _dereq_( '../math' ); 26538 26539var util = { 26540 26541 trueify: function(){ return true; }, 26542 26543 falsify: function(){ return false; }, 26544 26545 zeroify: function(){ return 0; }, 26546 26547 noop: function(){}, 26548 26549 error: function( msg ){ 26550 /* eslint-disable */ 26551 if( console.error ){ 26552 console.error.apply( console, arguments ); 26553 26554 if( console.trace ){ console.trace(); } 26555 } else { 26556 console.log.apply( console, arguments ); 26557 26558 if( console.trace ){ console.trace(); } 26559 } 26560 /* eslint-enable */ 26561 }, 26562 26563 clone: function( obj ){ 26564 return this.extend( {}, obj ); 26565 }, 26566 26567 // gets a shallow copy of the argument 26568 copy: function( obj ){ 26569 if( obj == null ){ 26570 return obj; 26571 } if( is.array( obj ) ){ 26572 return obj.slice(); 26573 } else if( is.plainObject( obj ) ){ 26574 return this.clone( obj ); 26575 } else { 26576 return obj; 26577 } 26578 }, 26579 26580 uuid: function( 26581 a,b // placeholders 26582 ){ 26583 for( // loop :) 26584 b=a=''; // b - result , a - numeric variable 26585 a++<36; // 26586 b+=a*51&52 // if "a" is not 9 or 14 or 19 or 24 26587 ? // return a random number or 4 26588 ( 26589 a^15 // if "a" is not 15 26590 ? // genetate a random number from 0 to 15 26591 8^Math.random()* 26592 (a^20?16:4) // unless "a" is 20, in which case a random number from 8 to 11 26593 : 26594 4 // otherwise 4 26595 ).toString(16) 26596 : 26597 '-' // in other cases (if "a" is 9,14,19,24) insert "-" 26598 ); 26599 return b; 26600 } 26601 26602}; 26603 26604util.makeBoundingBox = math.makeBoundingBox.bind( math ); 26605 26606util._staticEmptyObject = {}; 26607 26608util.staticEmptyObject = function(){ 26609 return util._staticEmptyObject; 26610}; 26611 26612util.extend = Object.assign != null ? Object.assign : function( tgt ){ 26613 var args = arguments; 26614 26615 for( var i = 1; i < args.length; i++ ){ 26616 var obj = args[ i ]; 26617 26618 if( !obj ){ continue; } 26619 26620 var keys = Object.keys( obj ); 26621 26622 for( var j = 0; j < keys.length; j++ ){ 26623 var k = keys[j]; 26624 26625 tgt[ k ] = obj[ k ]; 26626 } 26627 } 26628 26629 return tgt; 26630}; 26631 26632util.default = function( val, def ){ 26633 if( val === undefined ){ 26634 return def; 26635 } else { 26636 return val; 26637 } 26638}; 26639 26640util.removeFromArray = function( arr, ele, manyCopies ){ 26641 for( var i = arr.length; i >= 0; i-- ){ 26642 if( arr[i] === ele ){ 26643 arr.splice( i, 1 ); 26644 26645 if( !manyCopies ){ break; } 26646 } 26647 } 26648}; 26649 26650util.clearArray = function( arr ){ 26651 arr.splice( 0, arr.length ); 26652}; 26653 26654util.getPrefixedProperty = function( obj, propName, prefix ){ 26655 if( prefix ){ 26656 propName = this.prependCamel( prefix, propName ); // e.g. (labelWidth, source) => sourceLabelWidth 26657 } 26658 26659 return obj[ propName ]; 26660}; 26661 26662util.setPrefixedProperty = function( obj, propName, prefix, value ){ 26663 if( prefix ){ 26664 propName = this.prependCamel( prefix, propName ); // e.g. (labelWidth, source) => sourceLabelWidth 26665 } 26666 26667 obj[ propName ] = value; 26668}; 26669 26670[ 26671 _dereq_( './colors' ), 26672 _dereq_( './maps' ), 26673 { memoize: _dereq_( './memoize' ) }, 26674 _dereq_( './regex' ), 26675 _dereq_( './strings' ), 26676 _dereq_( './timing' )
26677].forEach( function( req ){ 26678 util.extend( util, req ); 26679} ); 26680 26681module.exports = util; 26682 26683},{"../is":83,"../math":85,"./colors":99,"./maps":101,"./memoize":102,"./regex":103,"./strings":104,"./timing":105}],101:[function(_dereq_,module,exports){ 26684'use strict'; 26685 26686var is = _dereq_( '../is' ); 26687 26688module.exports = { 26689 // has anything been set in the map 26690 mapEmpty: function( map ){ 26691 var empty = true; 26692 26693 if( map != null ){ 26694 return Object.keys( map ).length === 0; 26695 } 26696 26697 return empty; 26698 }, 26699 26700 // pushes to the array at the end of a map (map may not be built) 26701 pushMap: function( options ){ 26702 var array = this.getMap( options ); 26703 26704 if( array == null ){ // if empty, put initial array 26705 this.setMap( this.extend( {}, options, { 26706 value: [ options.value ] 26707 } ) ); 26708 } else { 26709 array.push( options.value ); 26710 } 26711 }, 26712 26713 // sets the value in a map (map may not be built) 26714 setMap: function( options ){ 26715 var obj = options.map; 26716 var key; 26717 var keys = options.keys; 26718 var l = keys.length; 26719 26720 for( var i = 0; i < l; i++ ){ 26721 var key = keys[ i ]; 26722 26723 if( is.plainObject( key ) ){ 26724 this.error( 'Tried to set map with object key' ); 26725 } 26726 26727 if( i < keys.length - 1 ){ 26728 26729 // extend the map if necessary 26730 if( obj[ key ] == null ){ 26731 obj[ key ] = {}; 26732 } 26733 26734 obj = obj[ key ]; 26735 } else { 26736 // set the value 26737 obj[ key ] = options.value; 26738 } 26739 } 26740 }, 26741 26742 // gets the value in a map even if it's not built in places 26743 getMap: function( options ){ 26744 var obj = options.map; 26745 var keys = options.keys; 26746 var l = keys.length; 26747 26748 for( var i = 0; i < l; i++ ){ 26749 var key = keys[ i ]; 26750 26751 if( is.plainObject( key ) ){ 26752 this.error( 'Tried to get map with object key' ); 26753 } 26754 26755 obj = obj[ key ]; 26756 26757 if( obj == null ){ 26758 return obj; 26759 } 26760 } 26761 26762 return obj; 26763 }, 26764 26765 // deletes the entry in the map 26766 deleteMap: function( options ){ 26767 var obj = options.map; 26768 var keys = options.keys; 26769 var l = keys.length; 26770 var keepChildren = options.keepChildren; 26771 26772 for( var i = 0; i < l; i++ ){ 26773 var key = keys[ i ]; 26774 26775 if( is.plainObject( key ) ){ 26776 this.error( 'Tried to delete map with object key' ); 26777 } 26778 26779 var lastKey = i === options.keys.length - 1; 26780 if( lastKey ){ 26781 26782 if( keepChildren ){ // then only delete child fields not in keepChildren 26783 var children = Object.keys( obj ); 26784 26785 for( var j = 0; j < children.length; j++ ){ 26786 var child = children[j]; 26787 26788 if( !keepChildren[ child ] ){ 26789 obj[ child ] = undefined; 26790 } 26791 } 26792 } else { 26793 obj[ key ] = undefined; 26794 } 26795 26796 } else { 26797 obj = obj[ key ]; 26798 } 26799 } 26800 } 26801}; 26802 26803},{"../is":83}],102:[function(_dereq_,module,exports){ 26804'use strict'; 26805 26806module.exports = function memoize( fn, keyFn ){ 26807 if( !keyFn ){ 26808 keyFn = function(){ 26809 if( arguments.length === 1 ){ 26810 return arguments[0]; 26811 } else if( arguments.length === 0 ){ 26812 return 'undefined'; 26813 } 26814 26815 var args = []; 26816 26817 for( var i = 0; i < arguments.length; i++ ){ 26818 args.push( arguments[ i ] ); 26819 } 26820 26821 return args.join( '$' ); 26822 }; 26823 } 26824 26825 var memoizedFn = function(){ 26826 var self = this; 26827 var args = arguments; 26828 var ret; 26829 var k = keyFn.apply( self, args ); 26830 var cache = memoizedFn.cache; 26831 26832 if( !(ret = cache[ k ]) ){ 26833 ret = cache[ k ] = fn.apply( self, args ); 26834 } 26835 26836 return ret; 26837 }; 26838 26839 memoizedFn.cache = {}; 26840 26841 return memoizedFn; 26842}; 26843 26844},{}],103:[function(_dereq_,module,exports){ 26845'use strict'; 26846 26847var number = '(?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))'; 26848 26849var rgba = 'rgb[a]?\\((' + number + '[%]?)\\s*,\\s*(' + number + '[%]?)\\s*,\\s*(' + number + '[%]?)(?:\\s*,\\s*(' + number + '))?\\)'; 26850var rgbaNoBackRefs = 'rgb[a]?\\((?:' + number + '[%]?)\\s*,\\s*(?:' + number + '[%]?)\\s*,\\s*(?:' + number + '[%]?)(?:\\s*,\\s*(?:' + number + '))?\\)'; 26851 26852var hsla = 'hsl[a]?\\((' + number + ')\\s*,\\s*(' + number + '[%])\\s*,\\s*(' + number + '[%])(?:\\s*,\\s*(' + number + '))?\\)'; 26853var hslaNoBackRefs = 'hsl[a]?\\((?:' + number + ')\\s*,\\s*(?:' + number + '[%])\\s*,\\s*(?:' + number + '[%])(?:\\s*,\\s*(?:' + number + '))?\\)'; 26854 26855var hex3 = '\\#[0-9a-fA-F]{3}'; 26856var hex6 = '\\#[0-9a-fA-F]{6}'; 26857 26858module.exports = { 26859 regex: { 26860 number: number, 26861 rgba: rgba, 26862 rgbaNoBackRefs: rgbaNoBackRefs, 26863 hsla: hsla, 26864 hslaNoBackRefs: hslaNoBackRefs, 26865 hex3: hex3, 26866 hex6: hex6 26867 } 26868}; 26869 26870},{}],104:[function(_dereq_,module,exports){ 26871'use strict'; 26872 26873var memoize = _dereq_( './memoize' ); 26874var is = _dereq_( '../is' ); 26875 26876module.exports = { 26877 26878 camel2dash: memoize( function( str ){ 26879 return str.replace( /([A-Z])/g, function( v ){ 26880 return '-' + v.toLowerCase(); 26881 } ); 26882 } ), 26883 26884 dash2camel: memoize( function( str ){ 26885 return str.replace( /(-\w)/g, function( v ){ 26886 return v[1].toUpperCase(); 26887 } ); 26888 } ), 26889 26890 prependCamel: memoize( function( prefix, str ){ 26891 return prefix + str[0].toUpperCase() + str.substring(1); 26892 }, function( prefix, str ){ 26893 return prefix + '$' + str; 26894 } ), 26895 26896 capitalize: function( str ){ 26897 if( is.emptyString( str ) ){ 26898 return str; 26899 } 26900 26901 return str.charAt( 0 ).toUpperCase() + str.substring( 1 ); 26902 } 26903 26904}; 26905 26906},{"../is":83,"./memoize":102}],105:[function(_dereq_,module,exports){ 26907'use strict'; 26908 26909var window = _dereq_( '../window' ); 26910var is = _dereq_( '../is' ); 26911var performance = window ? window.performance : null; 26912 26913var util = {}; 26914 26915var raf = !window ? function( fn ){ 26916 if( fn ){ 26917 setTimeout( function(){ 26918 fn( pnow() ); 26919 }, 1000 / 60 ); 26920 } 26921} : (function(){ 26922 if( window.requestAnimationFrame ){ 26923 return function( fn ){ window.requestAnimationFrame( fn ); }; 26924 } else if( window.mozRequestAnimationFrame ){ 26925 return function( fn ){ window.mozRequestAnimationFrame( fn ); }; 26926 } else if( window.webkitRequestAnimationFrame ){ 26927 return function( fn ){ window.webkitRequestAnimationFrame( fn ); }; 26928 } else if( window.msRequestAnimationFrame ){ 26929 return function( fn ){ window.msRequestAnimationFrame( fn ); }; 26930 } 26931})(); 26932 26933util.requestAnimationFrame = function( fn ){ 26934 raf( fn ); 26935}; 26936 26937var pnow = performance && performance.now ? function(){ return performance.now(); } : function(){ return Date.now(); }; 26938 26939util.performanceNow = pnow; 26940 26941// ported lodash throttle function 26942util.throttle = function( func, wait, options ){ 26943 var leading = true, 26944 trailing = true; 26945 26946 if( options === false ){ 26947 leading = false;
26948 } else if( is.plainObject( options ) ){ 26949 leading = 'leading' in options ? options.leading : leading; 26950 trailing = 'trailing' in options ? options.trailing : trailing; 26951 } 26952 options = options || {}; 26953 options.leading = leading; 26954 options.maxWait = wait; 26955 options.trailing = trailing; 26956 26957 return util.debounce( func, wait, options ); 26958}; 26959 26960util.now = function(){ 26961 return Date.now(); 26962}; 26963 26964util.debounce = function( func, wait, options ){ // ported lodash debounce function 26965 var util = this; 26966 var args, 26967 maxTimeoutId, 26968 result, 26969 stamp, 26970 thisArg, 26971 timeoutId, 26972 trailingCall, 26973 lastCalled = 0, 26974 maxWait = false, 26975 trailing = true; 26976 26977 if( !is.fn( func ) ){ 26978 return; 26979 } 26980 wait = Math.max( 0, wait ) || 0; 26981 if( options === true ){ 26982 var leading = true; 26983 trailing = false; 26984 } else if( is.plainObject( options ) ){ 26985 leading = options.leading; 26986 maxWait = 'maxWait' in options && (Math.max( wait, options.maxWait ) || 0); 26987 trailing = 'trailing' in options ? options.trailing : trailing; 26988 } 26989 var delayed = function(){ 26990 var remaining = wait - (util.now() - stamp); 26991 if( remaining <= 0 ){ 26992 if( maxTimeoutId ){ 26993 clearTimeout( maxTimeoutId ); 26994 } 26995 var isCalled = trailingCall; 26996 maxTimeoutId = timeoutId = trailingCall = undefined; 26997 if( isCalled ){ 26998 lastCalled = util.now(); 26999 result = func.apply( thisArg, args ); 27000 if( !timeoutId && !maxTimeoutId ){ 27001 args = thisArg = null; 27002 } 27003 } 27004 } else { 27005 timeoutId = setTimeout( delayed, remaining ); 27006 } 27007 }; 27008 27009 var maxDelayed = function(){ 27010 if( timeoutId ){ 27011 clearTimeout( timeoutId ); 27012 } 27013 maxTimeoutId = timeoutId = trailingCall = undefined; 27014 if( trailing || (maxWait !== wait) ){ 27015 lastCalled = util.now(); 27016 result = func.apply( thisArg, args ); 27017 if( !timeoutId && !maxTimeoutId ){ 27018 args = thisArg = null; 27019 } 27020 } 27021 }; 27022 27023 return function(){ 27024 args = arguments; 27025 stamp = util.now(); 27026 thisArg = this; 27027 trailingCall = trailing && (timeoutId || !leading); 27028 27029 if( maxWait === false ){ 27030 var leadingCall = leading && !timeoutId; 27031 } else { 27032 if( !maxTimeoutId && !leading ){ 27033 lastCalled = stamp; 27034 } 27035 var remaining = maxWait - (stamp - lastCalled), 27036 isCalled = remaining <= 0; 27037 27038 if( isCalled ){ 27039 if( maxTimeoutId ){ 27040 maxTimeoutId = clearTimeout( maxTimeoutId ); 27041 } 27042 lastCalled = stamp; 27043 result = func.apply( thisArg, args ); 27044 } 27045 else if( !maxTimeoutId ){ 27046 maxTimeoutId = setTimeout( maxDelayed, remaining ); 27047 } 27048 } 27049 if( isCalled && timeoutId ){ 27050 timeoutId = clearTimeout( timeoutId ); 27051 } 27052 else if( !timeoutId && wait !== maxWait ){ 27053 timeoutId = setTimeout( delayed, wait ); 27054 } 27055 if( leadingCall ){ 27056 isCalled = true; 27057 result = func.apply( thisArg, args ); 27058 } 27059 if( isCalled && !timeoutId && !maxTimeoutId ){ 27060 args = thisArg = null; 27061 } 27062 return result; 27063 }; 27064}; 27065 27066module.exports = util; 27067 27068},{"../is":83,"../window":107}],106:[function(_dereq_,module,exports){ 27069module.exports="2.7.12" 27070},{}],107:[function(_dereq_,module,exports){ 27071module.exports = ( typeof window === 'undefined' ? null : window ); // eslint-disable-line no-undef 27072 27073},{}]},{},[82])(82) 27074}); 27075
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.