PageSourceSearch

http://wikigenomes.org/static/wiki/js/cytoscape.js

js wikigenomes.org collected 2026-09-25 23:42:15 UTC 724,675 bytes, 27,075 lines download raw bytes

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.