All Downloads are FREE. Search and download functionalities are using the official Maven repository.

static.dist.cytoscape.js Maven / Gradle / Ivy

There is a newer version: 4.3.3
Show newest version
/*!
 * This file is part of Cytoscape.js 2.5.4.
 *
 * Cytoscape.js is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * Cytoscape.js is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * Cytoscape.js. If not, see .
 */

(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 0) {
      var minPos = findMin(openSet, fScore);
      var cMin = cy.getElementById( openSet[minPos] );
      steps++;

      // If we've found our goal, then we are done
      if (cMin.id() == target.id()) {
        var rPath = reconstructPath(source.id(), target.id(), cameFrom, []);
        rPath.reverse();
        return {
          found : true,
          distance : gScore[cMin.id()],
          path : eles.spawn(rPath),
          steps : steps
        };
      }

      // Add cMin to processed nodes
      closedSet.push(cMin.id());
      // Remove cMin from boundary nodes
      openSet.splice(minPos, 1);

      // Update scores for neighbors of cMin
      // Take into account if graph is directed or not
      var vwEdges = cMin.connectedEdges();
      if( directed ){ vwEdges = vwEdges.stdFilter(function(ele){ return ele.data('source') === cMin.id(); }); }
      vwEdges = vwEdges.intersect(edges);

      for (var i = 0; i < vwEdges.length; i++) {
        var e = vwEdges[i];
        var w = e.connectedNodes().stdFilter(function(n){ return n.id() !== cMin.id(); }).intersect(nodes);

        // if node is in closedSet, ignore it
        if (closedSet.indexOf(w.id()) != -1) {
          continue;
        }

        // New tentative score for node w
        var tempScore = gScore[cMin.id()] + weightFn.apply(e, [e]);

        // Update gScore for node w if:
        //   w not present in openSet
        // OR
        //   tentative gScore is less than previous value

        // w not in openSet
        if (openSet.indexOf(w.id()) == -1) {
          gScore[w.id()] = tempScore;
          fScore[w.id()] = tempScore + heuristic(w);
          openSet.push(w.id()); // Add node to openSet
          cameFrom[w.id()] = cMin.id();
          cameFromEdge[w.id()] = e.id();
          continue;
        }
        // w already in openSet, but with greater gScore
        if (tempScore < gScore[w.id()]) {
          gScore[w.id()] = tempScore;
          fScore[w.id()] = tempScore + heuristic(w);
          cameFrom[w.id()] = cMin.id();
        }

      } // End of neighbors update

    } // End of main loop

    // If we've reached here, then we've not reached our goal
    return {
      found : false,
      distance : undefined,
      path : undefined,
      steps : steps
    };
  }

}); // elesfn


module.exports = elesfn;

},{"../../is":77}],3:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../is');
var util = _dereq_('../../util');

var elesfn = ({

  // Implemented from pseudocode from wikipedia
  bellmanFord: function(options) {
    var eles = this;

    options = options || {};

    // Weight function - optional
    if (options.weight != null && is.fn(options.weight)) {
      var weightFn = options.weight;
    } else {
      // If not specified, assume each edge has equal weight (1)
      var weightFn = function(e) {return 1;};
    }

    // directed - optional
    if (options.directed != null) {
      var directed = options.directed;
    } else {
      var directed = false;
    }

    // root - mandatory!
    if (options.root != null) {
      if (is.string(options.root)) {
        // use it as a selector, e.g. "#rootID
        var source = this.filter(options.root)[0];
      } else {
        var source = options.root[0];
      }
    } else {
      return undefined;
    }

    var cy = this._private.cy;
    var edges = this.edges().stdFilter(function(e){ return !e.isLoop(); });
    var nodes = this.nodes();
    var numNodes = nodes.length;

    // mapping: node id -> position in nodes array
    var id2position = {};
    for (var i = 0; i < numNodes; i++) {
      id2position[nodes[i].id()] = i;
    }

    // Initializations
    var cost = [];
    var predecessor = [];
    var predEdge = [];

    for (var i = 0; i < numNodes; i++) {
      if (nodes[i].id() === source.id()) {
        cost[i] = 0;
      } else {
        cost[i] = Infinity;
      }
      predecessor[i] = undefined;
    }

    // Edges relaxation
    var flag = false;
    for (var i = 1; i < numNodes; i++) {
      flag = false;
      for (var e = 0; e < edges.length; e++) {
        var sourceIndex = id2position[edges[e].source().id()];
        var targetIndex = id2position[edges[e].target().id()];
        var weight = weightFn.apply(edges[e], [edges[e]]);

        var temp = cost[sourceIndex] + weight;
        if (temp < cost[targetIndex]) {
          cost[targetIndex] = temp;
          predecessor[targetIndex] = sourceIndex;
          predEdge[targetIndex] = edges[e];
          flag = true;
        }

        // If undirected graph, we need to take into account the 'reverse' edge
        if (!directed) {
          var temp = cost[targetIndex] + weight;
          if (temp < cost[sourceIndex]) {
            cost[sourceIndex] = temp;
            predecessor[sourceIndex] = targetIndex;
            predEdge[sourceIndex] = edges[e];
            flag = true;
          }
        }
      }

      if (!flag) {
        break;
      }
    }

    if (flag) {
      // Check for negative weight cycles
      for (var e = 0; e < edges.length; e++) {
        var sourceIndex = id2position[edges[e].source().id()];
        var targetIndex = id2position[edges[e].target().id()];
        var weight = weightFn.apply(edges[e], [edges[e]]);

        if (cost[sourceIndex] + weight < cost[targetIndex]) {
          util.error("Graph contains a negative weight cycle for Bellman-Ford");
          return { pathTo: undefined,
               distanceTo: undefined,
               hasNegativeWeightCycle: true};
        }
      }
    }

    // Build result object
    var position2id = [];
    for (var i = 0; i < numNodes; i++) {
      position2id.push(nodes[i].id());
    }


    var res = {
      distanceTo : function(to) {
        if (is.string(to)) {
          // to is a selector string
          var toId = (cy.filter(to)[0]).id();
        } else {
          // to is a node
          var toId = to.id();
        }

        return cost[id2position[toId]];
      },

      pathTo : function(to) {

        var reconstructPathAux = function(predecessor, fromPos, toPos, position2id, acumPath, predEdge) {
          for(;;){
            // Add toId to path
            acumPath.push( cy.getElementById(position2id[toPos]) );
            acumPath.push( predEdge[toPos] );

            if (fromPos === toPos) {
              // reached starting node
              return acumPath;
            }

            // If no path exists, discart acumulated path and return undefined
            var predPos = predecessor[toPos];
            if (typeof predPos === "undefined") {
              return undefined;
            }

            toPos = predPos;
          }

        };

        if (is.string(to)) {
          // to is a selector string
          var toId = (cy.filter(to)[0]).id();
        } else {
          // to is a node
          var toId = to.id();
        }
        var path = [];

        // This returns a reversed path
        var res =  reconstructPathAux(predecessor,
                      id2position[source.id()],
                      id2position[toId],
                      position2id,
                      path,
                      predEdge);

        // Get it in the correct order and return it
        if (res != null) {
          res.reverse();
        }

        return eles.spawn(res);
      },

      hasNegativeWeightCycle: false
    };

    return res;

  } // bellmanFord

}); // elesfn

module.exports = elesfn;

},{"../../is":77,"../../util":94}],4:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../is');

var elesfn = ({

  // Implemented from the algorithm in the paper "On Variants of Shortest-Path Betweenness Centrality and their Generic Computation" by Ulrik Brandes
  betweennessCentrality: function (options) {
    options = options || {};

    // Weight - optional
    if (options.weight != null && is.fn(options.weight)) {
      var weightFn = options.weight;
      var weighted = true;
    } else {
      var weighted = false;
    }

    // Directed - default false
    if (options.directed != null && is.bool(options.directed)) {
      var directed = options.directed;
    } else {
      var directed = false;
    }

    var priorityInsert = function (queue, ele) {
      queue.unshift(ele);
      for (var i = 0; d[queue[i]] < d[queue[i + 1]] && i < queue.length - 1; i++) {
        var tmp = queue[i];
        queue[i] = queue[i + 1];
        queue[i + 1] = tmp;
      }
    };

    var cy = this._private.cy;

    // starting
    var V = this.nodes();
    var A = {};
    var C = {};

    // A contains the neighborhoods of every node
    for (var i = 0; i < V.length; i++) {
      if (directed) {
        A[V[i].id()] = V[i].outgoers("node"); // get outgoers of every node
      } else {
        A[V[i].id()] = V[i].openNeighborhood("node"); // get neighbors of every node
      }
    }

    // C contains the betweenness values
    for (var i = 0; i < V.length; i++) {
      C[V[i].id()] = 0;
    }

    for (var s = 0; s < V.length; s++) {
      var S = []; // stack
      var P = {};
      var g = {};
      var d = {};
      var Q = []; // queue

      // init dictionaries
      for (var i = 0; i < V.length; i++) {
        P[V[i].id()] = [];
        g[V[i].id()] = 0;
        d[V[i].id()] = Number.POSITIVE_INFINITY;
      }

      g[V[s].id()] = 1; // sigma
      d[V[s].id()] = 0; // distance to s

      Q.unshift(V[s].id());

      while (Q.length > 0) {
        var v = Q.pop();
        S.push(v);
        if (weighted) {
          A[v].forEach(function (w) {
            if (cy.$('#' + v).edgesTo(w).length > 0) {
              var edge = cy.$('#' + v).edgesTo(w)[0];
            } else {
              var edge = w.edgesTo('#' + v)[0];
            }

            var edgeWeight = weightFn.apply(edge, [edge]);

            if (d[w.id()] > d[v] + edgeWeight) {
              d[w.id()] = d[v] + edgeWeight;
              if (Q.indexOf(w.id()) < 0) { //if w is not in Q
                priorityInsert(Q, w.id());
              } else { // update position if w is in Q
                Q.splice(Q.indexOf(w.id()), 1);
                priorityInsert(Q, w.id());
              }
              g[w.id()] = 0;
              P[w.id()] = [];
            }
            if (d[w.id()] == d[v] + edgeWeight) {
              g[w.id()] = g[w.id()] + g[v];
              P[w.id()].push(v);
            }
          });
        } else {
          A[v].forEach(function (w) {
            if (d[w.id()] == Number.POSITIVE_INFINITY) {
              Q.unshift(w.id());
              d[w.id()] = d[v] + 1;
            }
            if (d[w.id()] == d[v] + 1) {
              g[w.id()] = g[w.id()] + g[v];
              P[w.id()].push(v);
            }
          });
        }
      }

      var e = {};
      for (var i = 0; i < V.length; i++) {
        e[V[i].id()] = 0;
      }

      while (S.length > 0) {
        var w = S.pop();
        P[w].forEach(function (v) {
          e[v] = e[v] + (g[v] / g[w]) * (1 + e[w]);
          if (w != V[s].id())
            C[w] = C[w] + e[w];
        });
      }
    }

    var max = 0;
    for (var key in C) {
      if (max < C[key])
        max = C[key];
    }

    var ret = {
      betweenness: function (node) {
        if (is.string(node)) {
          var node = (cy.filter(node)[0]).id();
        } else {
          var node = node.id();
        }

        return C[node];
      },

      betweennessNormalized: function (node) {
        if (is.string(node)) {
          var node = (cy.filter(node)[0]).id();
        } else {
          var node = node.id();
        }

        return C[node] / max;
      }
    };

    // alias
    ret.betweennessNormalised = ret.betweennessNormalized;

    return ret;
  } // betweennessCentrality

}); // elesfn

// nice, short mathemathical alias
elesfn.bc = elesfn.betweennessCentrality;

module.exports = elesfn;

},{"../../is":77}],5:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../is');
var Heap = _dereq_('../../heap');

var defineSearch = function( params ){
  params = {
    bfs: params.bfs || !params.dfs,
    dfs: params.dfs || !params.bfs
  };

  // from pseudocode on wikipedia
  return function searchFn( roots, fn, directed ){
    var options;
    var std;
    var thisArg;
    if( is.plainObject(roots) && !is.elementOrCollection(roots) ){
      options = roots;
      roots = options.roots || options.root;
      fn = options.visit;
      directed = options.directed;
      std = options.std;
      thisArg = options.thisArg;
    }

    directed = arguments.length === 2 && !is.fn(fn) ? fn : directed;
    fn = is.fn(fn) ? fn : function(){};

    var cy = this._private.cy;
    var v = roots = is.string(roots) ? this.filter(roots) : roots;
    var Q = [];
    var connectedNodes = [];
    var connectedBy = {};
    var id2depth = {};
    var V = {};
    var j = 0;
    var found;
    var nodes = this.nodes();
    var edges = this.edges();

    // enqueue v
    for( var i = 0; i < v.length; i++ ){
      if( v[i].isNode() ){
        Q.unshift( v[i] );

        if( params.bfs ){
          V[ v[i].id() ] = true;

          connectedNodes.push( v[i] );
        }

        id2depth[ v[i].id() ] = 0;
      }
    }

    while( Q.length !== 0 ){
      var v = params.bfs ? Q.shift() : Q.pop();

      if( params.dfs ){
        if( V[ v.id() ] ){ continue; }

        V[ v.id() ] = true;

        connectedNodes.push( v );
      }

      var depth = id2depth[ v.id() ];
      var prevEdge = connectedBy[ v.id() ];
      var prevNode = prevEdge == null ? undefined : prevEdge.connectedNodes().not( v )[0];
      var ret;

      if( std ){
        ret = fn.call(thisArg, v, prevEdge, prevNode, j++, depth);
      } else {
        ret = fn.call(v, j++, depth, v, prevEdge, prevNode);
      }

      if( ret === true ){
        found = v;
        break;
      }

      if( ret === false ){
        break;
      }

      var vwEdges = v.connectedEdges(directed ? function(){ return this.data('source') === v.id(); } : undefined).intersect( edges );
      for( var i = 0; i < vwEdges.length; i++ ){
        var e = vwEdges[i];
        var w = e.connectedNodes(function(){ return this.id() !== v.id(); }).intersect( nodes );

        if( w.length !== 0 && !V[ w.id() ] ){
          w = w[0];

          Q.push( w );

          if( params.bfs ){
            V[ w.id() ] = true;

            connectedNodes.push( w );
          }

          connectedBy[ w.id() ] = e;

          id2depth[ w.id() ] = id2depth[ v.id() ] + 1;
        }
      }

    }

    var connectedEles = [];

    for( var i = 0; i < connectedNodes.length; i++ ){
      var node = connectedNodes[i];
      var edge = connectedBy[ node.id() ];

      if( edge ){
        connectedEles.push( edge );
      }

      connectedEles.push( node );
    }

    return {
      path: cy.collection( connectedEles, { unique: true } ),
      found: cy.collection( found )
    };
  };
};

// search, spanning trees, etc
var elesfn = ({

  breadthFirstSearch: defineSearch({ bfs: true }),
  depthFirstSearch: defineSearch({ dfs: true }),

  // kruskal's algorithm (finds min spanning tree, assuming undirected graph)
  // implemented from pseudocode from wikipedia
  kruskal: function( weightFn ){
    var cy = this.cy();

    weightFn = is.fn(weightFn) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1)

    function findSet(ele){
      for( var i = 0; i < forest.length; i++ ){
        var eles = forest[i];

        if( eles.anySame(ele) ){
          return {
            eles: eles,
            index: i
          };
        }
      }
    }

    var A = cy.collection(cy, []);
    var forest = [];
    var nodes = this.nodes();

    for( var i = 0; i < nodes.length; i++ ){
      forest.push( nodes[i].collection() );
    }

    var edges = this.edges();
    var S = edges.toArray().sort(function(a, b){
      var weightA = weightFn.call(a, a);
      var weightB = weightFn.call(b, b);

      return weightA - weightB;
    });

    for(var i = 0; i < S.length; i++){
      var edge = S[i];
      var u = edge.source()[0];
      var v = edge.target()[0];
      var setU = findSet(u);
      var setV = findSet(v);

      if( setU.index !== setV.index ){
        A = A.add( edge );

        // combine forests for u and v
        forest[ setU.index ] = setU.eles.add( setV.eles );
        forest.splice( setV.index, 1 );
      }
    }

    return nodes.add( A );

  },

  dijkstra: function( root, weightFn, directed ){
    var options;
    if( is.plainObject(root) && !is.elementOrCollection(root) ){
      options = root;
      root = options.root;
      weightFn = options.weight;
      directed = options.directed;
    }

    var cy = this._private.cy;
    weightFn = is.fn(weightFn) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1)

    var source = is.string(root) ? this.filter(root)[0] : root[0];
    var dist = {};
    var prev = {};
    var knownDist = {};

    var edges = this.edges().filter(function(){ return !this.isLoop(); });
    var nodes = this.nodes();

    var getDist = function(node){
      return dist[ node.id() ];
    };

    var setDist = function(node, d){
      dist[ node.id() ] = d;

      Q.updateItem( node );
    };

    var Q = new Heap(function( a, b ){
      return getDist(a) - getDist(b);
    });

    for( var i = 0; i < nodes.length; i++ ){
      var node = nodes[i];

      dist[ node.id() ] = node.same( source ) ? 0 : Infinity;
      Q.push( node );
    }

    var distBetween = function(u, v){
      var uvs = ( directed ? u.edgesTo(v) : u.edgesWith(v) ).intersect(edges);
      var smallestDistance = Infinity;
      var smallestEdge;

      for( var i = 0; i < uvs.length; i++ ){
        var edge = uvs[i];
        var weight = weightFn.apply( edge, [edge] );

        if( weight < smallestDistance || !smallestEdge ){
          smallestDistance = weight;
          smallestEdge = edge;
        }
      }

      return {
        edge: smallestEdge,
        dist: smallestDistance
      };
    };

    while( Q.size() > 0 ){
      var u = Q.pop();
      var smalletsDist = getDist(u);
      var uid = u.id();

      knownDist[uid] = smalletsDist;

      if( smalletsDist === Math.Infinite ){
        break;
      }

      var neighbors = u.neighborhood().intersect(nodes);
      for( var i = 0; i < neighbors.length; i++ ){
        var v = neighbors[i];
        var vid = v.id();
        var vDist = distBetween(u, v);

        var alt = smalletsDist + vDist.dist;

        if( alt < getDist(v) ){
          setDist(v, alt);

          prev[ vid ] = {
            node: u,
            edge: vDist.edge
          };
        }
      } // for
    } // while

    return {
      distanceTo: function(node){
        var target = is.string(node) ? nodes.filter(node)[0] : node[0];

        return knownDist[ target.id() ];
      },

      pathTo: function(node){
        var target = is.string(node) ? nodes.filter(node)[0] : node[0];
        var S = [];
        var u = target;

        if( target.length > 0 ){
          S.unshift( target );

          while( prev[ u.id() ] ){
            var p = prev[ u.id() ];

            S.unshift( p.edge );
            S.unshift( p.node );

            u = p.node;
          }
        }

        return cy.collection( S );
      }
    };
  }
});

// nice, short mathemathical alias
elesfn.bfs = elesfn.breadthFirstSearch;
elesfn.dfs = elesfn.depthFirstSearch;

module.exports = elesfn;

},{"../../heap":75,"../../is":77}],6:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../is');

var elesfn = ({

  closenessCentralityNormalized: function (options) {
    options = options || {};

    var cy = this.cy();

    var harmonic = options.harmonic;
    if( harmonic === undefined ){
      harmonic = true;
    }

    var closenesses = {};
    var maxCloseness = 0;
    var nodes = this.nodes();
    var fw = this.floydWarshall({ weight: options.weight, directed: options.directed });

    // Compute closeness for every node and find the maximum closeness
    for(var i = 0; i < nodes.length; i++){
      var currCloseness = 0;
      for (var j = 0; j < nodes.length; j++) {
        if (i != j) {
          var d = fw.distance(nodes[i], nodes[j]);

          if( harmonic ){
            currCloseness += 1 / d;
          } else {
            currCloseness += d;
          }
        }
      }

      if( !harmonic ){
        currCloseness = 1 / currCloseness;
      }

      if (maxCloseness < currCloseness){
        maxCloseness = currCloseness;
      }

      closenesses[nodes[i].id()] = currCloseness;
    }

    return {
      closeness: function (node) {
        if (is.string(node)) {
          // from is a selector string
          var node = (cy.filter(node)[0]).id();
        } else {
          // from is a node
          var node = node.id();
        }

        return closenesses[node] / maxCloseness;
      }
    };
  },

  // Implemented from pseudocode from wikipedia
  closenessCentrality: function (options) {
    options = options || {};

    // root - mandatory!
    if (options.root != null) {
      if (is.string(options.root)) {
        // use it as a selector, e.g. "#rootID
        var root = this.filter(options.root)[0];
      } else {
        var root = options.root[0];
      }
    } else {
      return undefined;
    }

    // weight - optional
    if (options.weight != null && is.fn(options.weight)) {
      var weight = options.weight;
    } else {
      var weight = function(){return 1;};
    }

    // directed - optional
    if (options.directed != null && is.bool(options.directed)) {
      var directed = options.directed;
    } else {
      var directed = false;
    }

    var harmonic = options.harmonic;
    if( harmonic === undefined ){
      harmonic = true;
    }

    // we need distance from this node to every other node
    var dijkstra = this.dijkstra({
      root: root,
      weight: weight,
      directed: directed
    });
    var totalDistance = 0;

    var nodes = this.nodes();
    for (var i = 0; i < nodes.length; i++){
      if (nodes[i].id() != root.id()){
        var d = dijkstra.distanceTo(nodes[i]);

        if( harmonic ){
          totalDistance += 1 / d;
        } else {
          totalDistance += d;
        }
      }
    }

    return harmonic ? totalDistance : 1 / totalDistance;
  } // closenessCentrality

}); // elesfn

// nice, short mathemathical alias
elesfn.cc = elesfn.closenessCentrality;
elesfn.ccn = elesfn.closenessCentralityNormalised = elesfn.closenessCentralityNormalized;

module.exports = elesfn;

},{"../../is":77}],7:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../is');
var util = _dereq_('../../util');

var elesfn = ({

  degreeCentralityNormalized: function (options) {
    options = options || {};

    var cy = this.cy();

    // directed - optional
    if (options.directed != null) {
      var directed = options.directed;
    } else {
      var directed = false;
    }

    var nodes = this.nodes();
    var numNodes = nodes.length;

    if (!directed) {
      var degrees = {};
      var maxDegree = 0;

      for (var i = 0; i < numNodes; i++) {
        var node = nodes[i];
        // add current node to the current options object and call degreeCentrality
        var currDegree = this.degreeCentrality(util.extend({}, options, {root: node}));
        if (maxDegree < currDegree.degree)
          maxDegree = currDegree.degree;

        degrees[node.id()] = currDegree.degree;
      }

      return {
        degree: function (node) {
          if (is.string(node)) {
            // from is a selector string
            var node = (cy.filter(node)[0]).id();
          } else {
            // from is a node
            var node = node.id();
          }

          return degrees[node] / maxDegree;
        }
      };
    } else {
      var indegrees = {};
      var outdegrees = {};
      var maxIndegree = 0;
      var maxOutdegree = 0;

      for (var i = 0; i < numNodes; i++) {
        var node = nodes[i];
        // add current node to the current options object and call degreeCentrality
        var currDegree = this.degreeCentrality(util.extend({}, options, {root: node}));

        if (maxIndegree < currDegree.indegree)
          maxIndegree = currDegree.indegree;

        if (maxOutdegree < currDegree.outdegree)
          maxOutdegree = currDegree.outdegree;

        indegrees[node.id()] = currDegree.indegree;
        outdegrees[node.id()] = currDegree.outdegree;
      }

      return {
        indegree: function (node) {
          if (is.string(node)) {
            // from is a selector string
            var node = (cy.filter(node)[0]).id();
          } else {
            // from is a node
            var node = node.id();
          }

          return indegrees[node] / maxIndegree;
        },
        outdegree: function (node) {
          if (is.string(node)) {
            // from is a selector string
            var node = (cy.filter(node)[0]).id();
          } else {
            // from is a node
            var node = node.id();
          }

          return outdegrees[node] / maxOutdegree;
        }

      };
    }

  }, // degreeCentralityNormalized

  // Implemented from the algorithm in Opsahl's paper
  // "Node centrality in weighted networks: Generalizing degree and shortest paths"
  // check the heading 2 "Degree"
  degreeCentrality: function (options) {
    options = options || {};

    var callingEles = this;

    // root - mandatory!
    if (options != null && options.root != null) {
      var root = is.string(options.root) ? this.filter(options.root)[0] : options.root[0];
    } else {
      return undefined;
    }

    // weight - optional
    if (options.weight != null && is.fn(options.weight)) {
      var weightFn = options.weight;
    } else {
      // If not specified, assume each edge has equal weight (1)
      var weightFn = function (e) {
        return 1;
      };
    }

    // directed - optional
    if (options.directed != null) {
      var directed = options.directed;
    } else {
      var directed = false;
    }

    // alpha - optional
    if (options.alpha != null && is.number(options.alpha)) {
      var alpha = options.alpha;
    } else {
      alpha = 0;
    }


    if (!directed) {
      var connEdges = root.connectedEdges().intersection( callingEles );
      var k = connEdges.length;
      var s = 0;

      // Now, sum edge weights
      for (var i = 0; i < connEdges.length; i++) {
        var edge = connEdges[i];
        s += weightFn.apply(edge, [edge]);
      }

      return {
        degree: Math.pow(k, 1 - alpha) * Math.pow(s, alpha)
      };
    } else {
      var incoming = root.connectedEdges('edge[target = "' + root.id() + '"]').intersection( callingEles );
      var outgoing = root.connectedEdges('edge[source = "' + root.id() + '"]').intersection( callingEles );
      var k_in = incoming.length;
      var k_out = outgoing.length;
      var s_in = 0;
      var s_out = 0;

      // Now, sum incoming edge weights
      for (var i = 0; i < incoming.length; i++) {
        var edge = incoming[i];
        s_in += weightFn.apply(edge, [edge]);
      }

      // Now, sum outgoing edge weights
      for (var i = 0; i < outgoing.length; i++) {
        var edge = outgoing[i];
        s_out += weightFn.apply(edge, [edge]);
      }

      return {
        indegree: Math.pow(k_in, 1 - alpha) * Math.pow(s_in, alpha),
        outdegree: Math.pow(k_out, 1 - alpha) * Math.pow(s_out, alpha)
      };
    }
  } // degreeCentrality

}); // elesfn

// nice, short mathemathical alias
elesfn.dc = elesfn.degreeCentrality;
elesfn.dcn = elesfn.degreeCentralityNormalised = elesfn.degreeCentralityNormalized;

module.exports = elesfn;

},{"../../is":77,"../../util":94}],8:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../is');

var elesfn = ({

  // Implemented from pseudocode from wikipedia
  floydWarshall: function(options) {
    options = options || {};

    var cy = this.cy();

    // Weight function - optional
    if (options.weight != null && is.fn(options.weight)) {
      var weightFn = options.weight;
    } else {
      // If not specified, assume each edge has equal weight (1)
      var weightFn = function(e) {return 1;};
    }

    // directed - optional
    if (options.directed != null) {
      var directed = options.directed;
    } else {
      var directed = false;
    }

    var edges = this.edges().stdFilter(function(e){ return !e.isLoop(); });
    var nodes = this.nodes();
    var numNodes = nodes.length;

    // mapping: node id -> position in nodes array
    var id2position = {};
    for (var i = 0; i < numNodes; i++) {
      id2position[nodes[i].id()] = i;
    }

    // Initialize distance matrix
    var dist = [];
    for (var i = 0; i < numNodes; i++) {
      var newRow = new Array(numNodes);
      for (var j = 0; j < numNodes; j++) {
        if (i == j) {
          newRow[j] = 0;
        } else {
          newRow[j] = Infinity;
        }
      }
      dist.push(newRow);
    }

    // Initialize matrix used for path reconstruction
    // Initialize distance matrix
    var next = [];
    var edgeNext = [];

    var initMatrix = function(next){
      for (var i = 0; i < numNodes; i++) {
        var newRow = new Array(numNodes);
        for (var j = 0; j < numNodes; j++) {
          newRow[j] = undefined;
        }
        next.push(newRow);
      }
    };

    initMatrix(next);
    initMatrix(edgeNext);

    // Process edges
    for (var i = 0; i < edges.length ; i++) {
      var sourceIndex = id2position[edges[i].source().id()];
      var targetIndex = id2position[edges[i].target().id()];
      var weight = weightFn.apply(edges[i], [edges[i]]);

      // Check if already process another edge between same 2 nodes
      if (dist[sourceIndex][targetIndex] > weight) {
        dist[sourceIndex][targetIndex] = weight;
        next[sourceIndex][targetIndex] = targetIndex;
        edgeNext[sourceIndex][targetIndex] = edges[i];
      }
    }

    // If undirected graph, process 'reversed' edges
    if (!directed) {
      for (var i = 0; i < edges.length ; i++) {
        var sourceIndex = id2position[edges[i].target().id()];
        var targetIndex = id2position[edges[i].source().id()];
        var weight = weightFn.apply(edges[i], [edges[i]]);

        // Check if already process another edge between same 2 nodes
        if (dist[sourceIndex][targetIndex] > weight) {
          dist[sourceIndex][targetIndex] = weight;
          next[sourceIndex][targetIndex] = targetIndex;
          edgeNext[sourceIndex][targetIndex] = edges[i];
        }
      }
    }

    // Main loop
    for (var k = 0; k < numNodes; k++) {
      for (var i = 0; i < numNodes; i++) {
        for (var j = 0; j < numNodes; j++) {
          if (dist[i][k] + dist[k][j] < dist[i][j]) {
            dist[i][j] = dist[i][k] + dist[k][j];
            next[i][j] = next[i][k];
          }
        }
      }
    }

    // Build result object
    var position2id = [];
    for (var i = 0; i < numNodes; i++) {
      position2id.push(nodes[i].id());
    }

    var res = {
      distance: function(from, to) {
        if (is.string(from)) {
          // from is a selector string
          var fromId = (cy.filter(from)[0]).id();
        } else {
          // from is a node
          var fromId = from.id();
        }

        if (is.string(to)) {
          // to is a selector string
          var toId = (cy.filter(to)[0]).id();
        } else {
          // to is a node
          var toId = to.id();
        }

        return dist[id2position[fromId]][id2position[toId]];
      },

      path: function(from, to) {
        var reconstructPathAux = function(from, to, next, position2id, edgeNext) {
          if (from === to) {
            return cy.getElementById( position2id[from] );
          }
          if (next[from][to] === undefined) {
            return undefined;
          }

          var path = [ cy.getElementById(position2id[from]) ];
          var prev = from;
          while (from !== to) {
            prev = from;
            from = next[from][to];

            var edge = edgeNext[prev][from];
            path.push( edge );

            path.push( cy.getElementById(position2id[from]) );
          }
          return path;
        };

        if (is.string(from)) {
          // from is a selector string
          var fromId = (cy.filter(from)[0]).id();
        } else {
          // from is a node
          var fromId = from.id();
        }

        if (is.string(to)) {
          // to is a selector string
          var toId = (cy.filter(to)[0]).id();
        } else {
          // to is a node
          var toId = to.id();
        }

        var pathArr = reconstructPathAux(id2position[fromId],
                      id2position[toId],
                      next,
                      position2id,
                      edgeNext);

        return cy.collection( pathArr );
      }
    };

    return res;

  } // floydWarshall

}); // elesfn

module.exports = elesfn;

},{"../../is":77}],9:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../../util');

var elesfn = {};

[
  _dereq_('./bfs-dfs'),
  _dereq_('./a-star'),
  _dereq_('./floyd-warshall'),
  _dereq_('./bellman-ford'),
  _dereq_('./kerger-stein'),
  _dereq_('./page-rank'),
  _dereq_('./degree-centrality'),
  _dereq_('./closeness-centrality'),
  _dereq_('./betweenness-centrality')
].forEach(function( props ){
  util.extend( elesfn, props );
});

module.exports = elesfn;

},{"../../util":94,"./a-star":2,"./bellman-ford":3,"./betweenness-centrality":4,"./bfs-dfs":5,"./closeness-centrality":6,"./degree-centrality":7,"./floyd-warshall":8,"./kerger-stein":10,"./page-rank":11}],10:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../../util');

var elesfn = ({

  // Computes the minimum cut of an undirected graph
  // Returns the correct answer with high probability
  kargerStein: function(options) {
    var eles = this;

    options = options || {};

    // Function which colapses 2 (meta) nodes into one
    // Updates the remaining edge lists
    // Receives as a paramater the edge which causes the collapse
    var colapse = function(edgeIndex, nodeMap, remainingEdges) {
      var edgeInfo = remainingEdges[edgeIndex];
      var sourceIn = edgeInfo[1];
      var targetIn = edgeInfo[2];
      var partition1 = nodeMap[sourceIn];
      var partition2 = nodeMap[targetIn];

      // Delete all edges between partition1 and partition2
      var newEdges = remainingEdges.filter(function(edge) {
        if (nodeMap[edge[1]] === partition1 && nodeMap[edge[2]] === partition2) {
          return false;
        }
        if (nodeMap[edge[1]] === partition2 && nodeMap[edge[2]] === partition1) {
          return false;
        }
        return true;
      });

      // All edges pointing to partition2 should now point to partition1
      for (var i = 0; i < newEdges.length; i++) {
        var edge = newEdges[i];
        if (edge[1] === partition2) { // Check source
          newEdges[i] = edge.slice(0);
          newEdges[i][1] = partition1;
        } else if (edge[2] === partition2) { // Check target
          newEdges[i] = edge.slice(0);
          newEdges[i][2] = partition1;
        }
      }

      // Move all nodes from partition2 to partition1
      for (var i = 0; i < nodeMap.length; i++) {
        if (nodeMap[i] === partition2) {
          nodeMap[i] = partition1;
        }
      }

      return newEdges;
    };


    // Contracts a graph until we reach a certain number of meta nodes
    var contractUntil = function(metaNodeMap,
                   remainingEdges,
                   size,
                   sizeLimit) {
      // Stop condition
      if (size <= sizeLimit) {
        return remainingEdges;
      }

      // Choose an edge randomly
      var edgeIndex = Math.floor((Math.random() * remainingEdges.length));

      // Colapse graph based on edge
      var newEdges = colapse(edgeIndex, metaNodeMap, remainingEdges);

      return contractUntil(metaNodeMap,
                 newEdges,
                 size - 1,
                 sizeLimit);
    };

    var cy = this._private.cy;
    var edges = this.edges().stdFilter(function(e){ return !e.isLoop(); });
    var nodes = this.nodes();
    var numNodes = nodes.length;
    var numEdges = edges.length;
    var numIter = Math.ceil(Math.pow(Math.log(numNodes) / Math.LN2, 2));
    var stopSize = Math.floor(numNodes / Math.sqrt(2));

    if (numNodes < 2) {
      util.error("At least 2 nodes are required for Karger-Stein algorithm");
      return undefined;
    }

    // Create numerical identifiers for each node
    // mapping: node id -> position in nodes array
    // for reverse mapping, simply use nodes array
    var id2position = {};
    for (var i = 0; i < numNodes; i++) {
      id2position[nodes[i].id()] = i;
    }

    // Now store edge destination as indexes
    // Format for each edge (edge index, source node index, target node index)
    var edgeIndexes = [];
    for (var i = 0; i < numEdges; i++) {
      var e = edges[i];
      edgeIndexes.push([i, id2position[e.source().id()], id2position[e.target().id()]]);
    }

    // We will store the best cut found here
    var minCutSize = Infinity;
    var minCut;

    // Initial meta node partition
    var originalMetaNode = [];
    for (var i = 0; i < numNodes; i++) {
      originalMetaNode.push(i);
    }

    // Main loop
    for (var iter = 0; iter <= numIter; iter++) {
      // Create new meta node partition
      var metaNodeMap = originalMetaNode.slice(0);

      // Contract until stop point (stopSize nodes)
      var edgesState = contractUntil(metaNodeMap, edgeIndexes, numNodes, stopSize);

      // Create a copy of the colapsed nodes state
      var metaNodeMap2 = metaNodeMap.slice(0);

      // Run 2 iterations starting in the stop state
      var res1 = contractUntil(metaNodeMap, edgesState, stopSize, 2);
      var res2 = contractUntil(metaNodeMap2, edgesState, stopSize, 2);

      // Is any of the 2 results the best cut so far?
      if (res1.length <= res2.length && res1.length < minCutSize) {
        minCutSize = res1.length;
        minCut = [res1, metaNodeMap];
      } else if (res2.length <= res1.length && res2.length < minCutSize) {
        minCutSize = res2.length;
        minCut = [res2, metaNodeMap2];
      }
    } // end of main loop


    // Construct result
    var resEdges = (minCut[0]).map(function(e){ return edges[e[0]]; });
    var partition1 = [];
    var partition2 = [];

    // traverse metaNodeMap for best cut
    var witnessNodePartition = minCut[1][0];
    for (var i = 0; i < minCut[1].length; i++) {
      var partitionId = minCut[1][i];
      if (partitionId === witnessNodePartition) {
        partition1.push(nodes[i]);
      } else {
        partition2.push(nodes[i]);
      }
    }

    var ret = {
      cut: eles.spawn(cy, resEdges),
      partition1: eles.spawn(partition1),
      partition2: eles.spawn(partition2)
    };

    return ret;
  }
}); // elesfn


module.exports = elesfn;

},{"../../util":94}],11:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../is');

var elesfn = ({

  pageRank: function(options) {
    options = options || {};

    var normalizeVector = function(vector) {
      var length = vector.length;

      // First, get sum of all elements
      var total = 0;
      for (var i = 0; i < length; i++) {
        total += vector[i];
      }

      // Now, divide each by the sum of all elements
      for (var i = 0; i < length; i++) {
        vector[i] = vector[i] / total;
      }
    };

    // dampingFactor - optional
    if (options != null &&
      options.dampingFactor != null) {
      var dampingFactor = options.dampingFactor;
    } else {
      var dampingFactor = 0.8; // Default damping factor
    }

    // desired precision - optional
    if (options != null &&
      options.precision != null) {
      var epsilon = options.precision;
    } else {
      var epsilon = 0.000001; // Default precision
    }

    // Max number of iterations - optional
    if (options != null &&
      options.iterations != null) {
      var numIter = options.iterations;
    } else {
      var numIter = 200; // Default number of iterations
    }

    // Weight function - optional
    if (options != null &&
      options.weight != null &&
      is.fn(options.weight)) {
      var weightFn = options.weight;
    } else {
      // If not specified, assume each edge has equal weight (1)
      var weightFn = function(e) {return 1;};
    }

    var cy = this._private.cy;
    var edges = this.edges().stdFilter(function(e){ return !e.isLoop(); });
    var nodes = this.nodes();
    var numNodes = nodes.length;
    var numEdges = edges.length;

    // Create numerical identifiers for each node
    // mapping: node id -> position in nodes array
    // for reverse mapping, simply use nodes array
    var id2position = {};
    for (var i = 0; i < numNodes; i++) {
      id2position[nodes[i].id()] = i;
    }

    // Construct transposed adjacency matrix
    // First lets have a zeroed matrix of the right size
    // We'll also keep track of the sum of each column
    var matrix = [];
    var columnSum = [];
    var additionalProb = (1 - dampingFactor) / numNodes;

    // Create null matric
    for (var i = 0; i < numNodes; i++) {
      var newRow = [];
      for (var j = 0; j < numNodes; j++) {
        newRow.push(0.0);
      }
      matrix.push(newRow);
      columnSum.push(0.0);
    }

    // Now, process edges
    for (var i = 0; i < numEdges; i++) {
      var edge = edges[i];
      var s = id2position[edge.source().id()];
      var t = id2position[edge.target().id()];
      var w = weightFn.apply(edge, [edge]);

      // Update matrix
      matrix[t][s] += w;

      // Update column sum
      columnSum[s] += w;
    }

    // Add additional probability based on damping factor
    // Also, take into account columns that have sum = 0
    var p = 1.0 / numNodes + additionalProb; // Shorthand
    // Traverse matrix, column by column
    for (var j = 0; j < numNodes; j++) {
      if (columnSum[j] === 0) {
        // No 'links' out from node jth, assume equal probability for each possible node
        for (var i = 0; i < numNodes; i++) {
          matrix[i][j] = p;
        }
      } else {
        // Node jth has outgoing link, compute normalized probabilities
        for (var i = 0; i < numNodes; i++) {
          matrix[i][j] = matrix[i][j] / columnSum[j] + additionalProb;
        }
      }
    }

    // Compute dominant eigenvector using power method
    var eigenvector = [];
    var nullVector = [];
    var previous;

    // Start with a vector of all 1's
    // Also, initialize a null vector which will be used as shorthand
    for (var i = 0; i < numNodes; i++) {
      eigenvector.push(1.0);
      nullVector.push(0.0);
    }

    for (var iter = 0; iter < numIter; iter++) {
      // New array with all 0's
      var temp = nullVector.slice(0);

      // Multiply matrix with previous result
      for (var i = 0; i < numNodes; i++) {
        for (var j = 0; j < numNodes; j++) {
          temp[i] += matrix[i][j] * eigenvector[j];
        }
      }

      normalizeVector(temp);
      previous = eigenvector;
      eigenvector = temp;

      var diff = 0;
      // Compute difference (squared module) of both vectors
      for (var i = 0; i < numNodes; i++) {
        diff += Math.pow(previous[i] - eigenvector[i], 2);
      }

      // If difference is less than the desired threshold, stop iterating
      if (diff < epsilon) {
        break;
      }
    }

    // Construct result
    var res = {
      rank : function(node) {
        if (is.string(node)) {
          // is a selector string
          var nodeId = (cy.filter(node)[0]).id();
        } else {
          // is a node object
          var nodeId = node.id();
        }
        return eigenvector[id2position[nodeId]];
      }
    };


    return res;
  } // pageRank

}); // elesfn

module.exports = elesfn;

},{"../../is":77}],12:[function(_dereq_,module,exports){
'use strict';

var define = _dereq_('../define');

var elesfn = ({
  animate: define.animate(),
  animation: define.animation(),
  animated: define.animated(),
  clearQueue: define.clearQueue(),
  delay: define.delay(),
  delayAnimation: define.delayAnimation(),
  stop: define.stop()
});

module.exports = elesfn;

},{"../define":41}],13:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');

var elesfn = ({
  classes: function( classes ){
    classes = classes.match(/\S+/g) || [];
    var self = this;
    var changed = [];
    var classesMap = {};

    // fill in classes map
    for( var i = 0; i < classes.length; i++ ){
      var cls = classes[i];

      classesMap[ cls ] = true;
    }

    // check and update each ele
    for( var j = 0; j < self.length; j++ ){
      var ele = self[j];
      var _p = ele._private;
      var eleClasses = _p.classes;
      var changedEle = false;

      // check if ele has all of the passed classes
      for( var i = 0; i < classes.length; i++ ){
        var cls = classes[i];
        var eleHasClass = eleClasses[ cls ];

        if( !eleHasClass ){
          changedEle = true;
          break;
        }
      }

      // check if ele has classes outside of those passed
      if( !changedEle ){ for( var eleCls in eleClasses ){
        var eleHasClass = eleClasses[ eleCls ];
        var specdClass = classesMap[ eleCls ]; // i.e. this class is passed to the function

        if( eleHasClass && !specdClass ){
          changedEle = true;
          break;
        }
      } }

      if( changedEle ){
        _p.classes = util.copy( classesMap );

        changed.push( ele );
      }
    }

    // trigger update style on those eles that had class changes
    if( changed.length > 0 ){
      this.spawn(changed)
        .updateStyle()
        .trigger('class')
      ;
    }

    return self;
  },

  addClass: function( classes ){
    return this.toggleClass( classes, true );
  },

  hasClass: function( className ){
    var ele = this[0];
    return ( ele != null && ele._private.classes[className] ) ? true : false;
  },

  toggleClass: function( classesStr, toggle ){
    var classes = classesStr.match(/\S+/g) || [];
    var self = this;
    var changed = []; // eles who had classes changed

    for( var i = 0, il = self.length; i < il; i++ ){
      var ele = self[i];
      var changedEle = false;

      for( var j = 0; j < classes.length; j++ ){
        var cls = classes[j];
        var eleClasses = ele._private.classes;
        var hasClass = eleClasses[cls];
        var shouldAdd = toggle || (toggle === undefined && !hasClass);

        if( shouldAdd ){
          eleClasses[cls] = true;

          if( !hasClass && !changedEle ){
            changed.push(ele);
            changedEle = true;
          }
        } else { // then remove
          eleClasses[cls] = false;

          if( hasClass && !changedEle ){
            changed.push(ele);
            changedEle = true;
          }
        }

      } // for j classes
    } // for i eles

    // trigger update style on those eles that had class changes
    if( changed.length > 0 ){
      this.spawn(changed)
        .updateStyle()
        .trigger('class')
      ;
    }

    return self;
  },

  removeClass: function( classes ){
    return this.toggleClass( classes, false );
  },

  flashClass: function( classes, duration ){
    var self = this;

    if( duration == null ){
      duration = 250;
    } else if( duration === 0 ){
      return self; // nothing to do really
    }

    self.addClass( classes );
    setTimeout(function(){
      self.removeClass( classes );
    }, duration);

    return self;
  }
});

module.exports = elesfn;

},{"../util":94}],14:[function(_dereq_,module,exports){
'use strict';

var elesfn = ({
  allAre: function( selector ){
    return this.filter(selector).length === this.length;
  },

  is: function( selector ){
    return this.filter(selector).length > 0;
  },

  some: function( fn, thisArg ){
    for( var i = 0; i < this.length; i++ ){
      var ret = !thisArg ? fn( this[i], i, this ) : fn.apply( thisArg, [ this[i], i, this ] );

      if( ret ){
        return true;
      }
    }

    return false;
  },

  every: function( fn, thisArg ){
    for( var i = 0; i < this.length; i++ ){
      var ret = !thisArg ? fn( this[i], i, this ) : fn.apply( thisArg, [ this[i], i, this ] );

      if( !ret ){
        return false;
      }
    }

    return true;
  },

  same: function( collection ){
    collection = this.cy().collection( collection );

    // cheap extra check
    if( this.length !== collection.length ){
      return false;
    }

    return this.intersect( collection ).length === this.length;
  },

  anySame: function( collection ){
    collection = this.cy().collection( collection );

    return this.intersect( collection ).length > 0;
  },

  allAreNeighbors: function( collection ){
    collection = this.cy().collection( collection );

    return this.neighborhood().intersect( collection ).length === collection.length;
  }
});

elesfn.allAreNeighbours = elesfn.allAreNeighbors;

module.exports = elesfn;

},{}],15:[function(_dereq_,module,exports){
'use strict';

var elesfn = ({
  parent: function( selector ){
    var parents = [];
    var cy = this._private.cy;

    for( var i = 0; i < this.length; i++ ){
      var ele = this[i];
      var parent = cy.getElementById( ele._private.data.parent );

      if( parent.size() > 0 ){
        parents.push( parent );
      }
    }

    return this.spawn( parents, { unique: true } ).filter( selector );
  },

  parents: function( selector ){
    var parents = [];

    var eles = this.parent();
    while( eles.nonempty() ){
      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[i];
        parents.push( ele );
      }

      eles = eles.parent();
    }

    return this.spawn( parents, { unique: true } ).filter( selector );
  },

  commonAncestors: function( selector ){
    var ancestors;

    for( var i = 0; i < this.length; i++ ){
      var ele = this[i];
      var parents = ele.parents();

      ancestors = ancestors || parents;

      ancestors = ancestors.intersect( parents ); // current list must be common with current ele parents set
    }

    return ancestors.filter( selector );
  },

  orphans: function( selector ){
    return this.stdFilter(function( ele ){
      return ele.isNode() && ele.parent().empty();
    }).filter( selector );
  },

  nonorphans: function( selector ){
    return this.stdFilter(function( ele ){
      return ele.isNode() && ele.parent().nonempty();
    }).filter( selector );
  },

  children: function( selector ){
    var children = [];

    for( var i = 0; i < this.length; i++ ){
      var ele = this[i];
      children = children.concat( ele._private.children );
    }

    return this.spawn( children, { unique: true } ).filter( selector );
  },

  siblings: function( selector ){
    return this.parent().children().not( this ).filter( selector );
  },

  isParent: function(){
    var ele = this[0];

    if( ele ){
      return ele._private.children.length !== 0;
    }
  },

  isChild: function(){
    var ele = this[0];

    if( ele ){
      return ele._private.data.parent !== undefined && ele.parent().length !== 0;
    }
  },

  descendants: function( selector ){
    var elements = [];

    function add( eles ){
      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[i];

        elements.push( ele );

        if( ele.children().nonempty() ){
          add( ele.children() );
        }
      }
    }

    add( this.children() );

    return this.spawn( elements, { unique: true } ).filter( selector );
  }
});

// aliases
elesfn.ancestors = elesfn.parents;

module.exports = elesfn;

},{}],16:[function(_dereq_,module,exports){
'use strict';

var define = _dereq_('../define');
var fn, elesfn;

fn = elesfn = ({

  data: define.data({
    field: 'data',
    bindingEvent: 'data',
    allowBinding: true,
    allowSetting: true,
    settingEvent: 'data',
    settingTriggersEvent: true,
    triggerFnName: 'trigger',
    allowGetting: true,
    immutableKeys: {
      'id': true,
      'source': true,
      'target': true,
      'parent': true
    },
    updateStyle: true
  }),

  removeData: define.removeData({
    field: 'data',
    event: 'data',
    triggerFnName: 'trigger',
    triggerEvent: true,
    immutableKeys: {
      'id': true,
      'source': true,
      'target': true,
      'parent': true
    },
    updateStyle: true
  }),

  scratch: define.data({
    field: 'scratch',
    bindingEvent: 'scratch',
    allowBinding: true,
    allowSetting: true,
    settingEvent: 'scratch',
    settingTriggersEvent: true,
    triggerFnName: 'trigger',
    allowGetting: true,
    updateStyle: true
  }),

  removeScratch: define.removeData({
    field: 'scratch',
    event: 'scratch',
    triggerFnName: 'trigger',
    triggerEvent: true,
    updateStyle: true
  }),

  rscratch: define.data({
    field: 'rscratch',
    allowBinding: false,
    allowSetting: true,
    settingTriggersEvent: false,
    allowGetting: true
  }),

  removeRscratch: define.removeData({
    field: 'rscratch',
    triggerEvent: false
  }),

  id: function(){
    var ele = this[0];

    if( ele ){
      return ele._private.data.id;
    }
  }

});

// aliases
fn.attr = fn.data;
fn.removeAttr = fn.removeData;

module.exports = elesfn;

},{"../define":41}],17:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');

var elesfn = {};

function defineDegreeFunction(callback){
  return function( includeLoops ){
    var self = this;

    if( includeLoops === undefined ){
      includeLoops = true;
    }

    if( self.length === 0 ){ return; }

    if( self.isNode() && !self.removed() ){
      var degree = 0;
      var node = self[0];
      var connectedEdges = node._private.edges;

      for( var i = 0; i < connectedEdges.length; i++ ){
        var edge = connectedEdges[i];

        if( !includeLoops && edge.isLoop() ){
          continue;
        }

        degree += callback( node, edge );
      }

      return degree;
    } else {
      return;
    }
  };
}

util.extend(elesfn, {
  degree: defineDegreeFunction(function(node, edge){
    if( edge.source().same( edge.target() ) ){
      return 2;
    } else {
      return 1;
    }
  }),

  indegree: defineDegreeFunction(function(node, edge){
    if( edge.target().same(node) ){
      return 1;
    } else {
      return 0;
    }
  }),

  outdegree: defineDegreeFunction(function(node, edge){
    if( edge.source().same(node) ){
      return 1;
    } else {
      return 0;
    }
  })
});

function defineDegreeBoundsFunction(degreeFn, callback){
  return function( includeLoops ){
    var ret;
    var nodes = this.nodes();

    for( var i = 0; i < nodes.length; i++ ){
      var ele = nodes[i];
      var degree = ele[degreeFn]( includeLoops );
      if( degree !== undefined && (ret === undefined || callback(degree, ret)) ){
        ret = degree;
      }
    }

    return ret;
  };
}

util.extend(elesfn, {
  minDegree: defineDegreeBoundsFunction('degree', function(degree, min){
    return degree < min;
  }),

  maxDegree: defineDegreeBoundsFunction('degree', function(degree, max){
    return degree > max;
  }),

  minIndegree: defineDegreeBoundsFunction('indegree', function(degree, min){
    return degree < min;
  }),

  maxIndegree: defineDegreeBoundsFunction('indegree', function(degree, max){
    return degree > max;
  }),

  minOutdegree: defineDegreeBoundsFunction('outdegree', function(degree, min){
    return degree < min;
  }),

  maxOutdegree: defineDegreeBoundsFunction('outdegree', function(degree, max){
    return degree > max;
  })
});

util.extend(elesfn, {
  totalDegree: function( includeLoops ){
    var total = 0;
    var nodes = this.nodes();

    for( var i = 0; i < nodes.length; i++ ){
      total += nodes[i].degree( includeLoops );
    }

    return total;
  }
});

module.exports = elesfn;

},{"../util":94}],18:[function(_dereq_,module,exports){
'use strict';

var define = _dereq_('../define');
var is = _dereq_('../is');
var util = _dereq_('../util');
var fn, elesfn;

fn = elesfn = ({

  position: define.data({
    field: 'position',
    bindingEvent: 'position',
    allowBinding: true,
    allowSetting: true,
    settingEvent: 'position',
    settingTriggersEvent: true,
    triggerFnName: 'rtrigger',
    allowGetting: true,
    validKeys: ['x', 'y'],
    onSet: function( eles ){
      var updatedEles = eles.updateCompoundBounds();
      updatedEles.rtrigger('position');
    },
    canSet: function( ele ){
      return !ele.locked() && !ele.isParent();
    }
  }),

  // position but no notification to renderer
  silentPosition: define.data({
    field: 'position',
    bindingEvent: 'position',
    allowBinding: false,
    allowSetting: true,
    settingEvent: 'position',
    settingTriggersEvent: false,
    triggerFnName: 'trigger',
    allowGetting: true,
    validKeys: ['x', 'y'],
    onSet: function( eles ){
      eles.updateCompoundBounds();
    },
    canSet: function( ele ){
      return !ele.locked() && !ele.isParent();
    }
  }),

  positions: function( pos, silent ){
    if( is.plainObject(pos) ){
      this.position(pos);

    } else if( is.fn(pos) ){
      var fn = pos;

      for( var i = 0; i < this.length; i++ ){
        var ele = this[i];

        var pos = fn.apply(ele, [i, ele]);

        if( pos && !ele.locked() && !ele.isParent() ){
          var elePos = ele._private.position;
          elePos.x = pos.x;
          elePos.y = pos.y;
        }
      }

      var updatedEles = this.updateCompoundBounds();
      var toTrigger = updatedEles.length > 0 ? this.add( updatedEles ) : this;

      if( silent ){
        toTrigger.trigger('position');
      } else {
        toTrigger.rtrigger('position');
      }
    }

    return this; // chaining
  },

  silentPositions: function( pos ){
    return this.positions( pos, true );
  },

  // get/set the rendered (i.e. on screen) positon of the element
  renderedPosition: function( dim, val ){
    var ele = this[0];
    var cy = this.cy();
    var zoom = cy.zoom();
    var pan = cy.pan();
    var rpos = is.plainObject( dim ) ? dim : undefined;
    var setting = rpos !== undefined || ( val !== undefined && is.string(dim) );

    if( ele && ele.isNode() ){ // must have an element and must be a node to return position
      if( setting ){
        for( var i = 0; i < this.length; i++ ){
          var ele = this[i];

          if( val !== undefined ){ // set one dimension
            ele._private.position[dim] = ( val - pan[dim] )/zoom;
          } else if( rpos !== undefined ){ // set whole position
            ele._private.position = {
              x: ( rpos.x - pan.x ) /zoom,
              y: ( rpos.y - pan.y ) /zoom
            };
          }
        }

        this.rtrigger('position');
      } else { // getting
        var pos = ele._private.position;
        rpos = {
          x: pos.x * zoom + pan.x,
          y: pos.y * zoom + pan.y
        };

        if( dim === undefined ){ // then return the whole rendered position
          return rpos;
        } else { // then return the specified dimension
          return rpos[ dim ];
        }
      }
    } else if( !setting ){
      return undefined; // for empty collection case
    }

    return this; // chaining
  },

  // get/set the position relative to the parent
  relativePosition: function( dim, val ){
    var ele = this[0];
    var cy = this.cy();
    var ppos = is.plainObject( dim ) ? dim : undefined;
    var setting = ppos !== undefined || ( val !== undefined && is.string(dim) );
    var hasCompoundNodes = cy.hasCompoundNodes();

    if( ele && ele.isNode() ){ // must have an element and must be a node to return position
      if( setting ){
        for( var i = 0; i < this.length; i++ ){
          var ele = this[i];
          var parent = hasCompoundNodes ? ele.parent() : null;
          var hasParent = parent && parent.length > 0;
          var relativeToParent = hasParent;

          if( hasParent ){
            parent = parent[0];
          }

          var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 };

          if( val !== undefined ){ // set one dimension
            ele._private.position[dim] = val + origin[dim];
          } else if( ppos !== undefined ){ // set whole position
            ele._private.position = {
              x: ppos.x + origin.x,
              y: ppos.y + origin.y
            };
          }
        }

        this.rtrigger('position');

      } else { // getting
        var pos = ele._private.position;
        var parent = hasCompoundNodes ? ele.parent() : null;
        var hasParent = parent && parent.length > 0;
        var relativeToParent = hasParent;

        if( hasParent ){
          parent = parent[0];
        }

        var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 };

        ppos = {
          x: pos.x - origin.x,
          y: pos.y - origin.y
        };

        if( dim === undefined ){ // then return the whole rendered position
          return ppos;
        } else { // then return the specified dimension
          return ppos[ dim ];
        }
      }
    } else if( !setting ){
      return undefined; // for empty collection case
    }

    return this; // chaining
  },

  renderedBoundingBox: function( options ){
    var bb = this.boundingBox( options );
    var cy = this.cy();
    var zoom = cy.zoom();
    var pan = cy.pan();

    var x1 = bb.x1 * zoom + pan.x;
    var x2 = bb.x2 * zoom + pan.x;
    var y1 = bb.y1 * zoom + pan.y;
    var y2 = bb.y2 * zoom + pan.y;

    return {
      x1: x1,
      x2: x2,
      y1: y1,
      y2: y2,
      w: x2 - x1,
      h: y2 - y1
    };
  },

  updateCompoundBounds: function(){
    var cy = this.cy();

    if( !cy.styleEnabled() || !cy.hasCompoundNodes() ){ return cy.collection(); } // save cycles for non compound graphs or when style disabled

    var updated = [];

    function update( parent ){
      var children = parent.children();
      var style = parent._private.style;
      var includeLabels = style['compound-sizing-wrt-labels'].value === 'include';
      var bb = children.boundingBox({ includeLabels: includeLabels, includeEdges: true });
      var padding = {
        top: style['padding-top'].pfValue,
        bottom: style['padding-bottom'].pfValue,
        left: style['padding-left'].pfValue,
        right: style['padding-right'].pfValue
      };
      var pos = parent._private.position;
      var didUpdate = false;

      if( style['width'].value === 'auto' ){
        parent._private.autoWidth = bb.w;
        pos.x = (bb.x1 + bb.x2 - padding.left + padding.right)/2;
        didUpdate = true;
      }

      if( style['height'].value === 'auto' ){
        parent._private.autoHeight = bb.h;
        pos.y = (bb.y1 + bb.y2 - padding.top + padding.bottom)/2;
        didUpdate = true;
      }

      if( didUpdate ){
        updated.push( parent );
      }
    }

    // go up, level by level
    var eles = this.parent();
    while( eles.nonempty() ){

      // update each parent node in this level
      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[i];

        update( ele );
      }

      // next level
      eles = eles.parent();
    }

    // return changed
    return this.spawn( updated );
  },

  // get the bounding box of the elements (in raw model position)
  boundingBox: function( options ){
    var eles = this;
    var cy = eles._private.cy;
    var cy_p = cy._private;
    var styleEnabled = cy_p.styleEnabled;

    options = options || util.staticEmptyObject();

    var includeNodes = options.includeNodes === undefined ? true : options.includeNodes;
    var includeEdges = options.includeEdges === undefined ? true : options.includeEdges;
    var includeLabels = options.includeLabels === undefined ? true : options.includeLabels;

    // recalculate projections etc
    if( styleEnabled ){
      cy_p.renderer.recalculateRenderedStyle( this );
    }

    var x1 = Infinity;
    var x2 = -Infinity;
    var y1 = Infinity;
    var y2 = -Infinity;

    // find bounds of elements
    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      var _p = ele._private;
      var style = _p.style;
      var display = styleEnabled ? _p.style['display'].value : 'element';
      var isNode = _p.group === 'nodes';
      var ex1, ex2, ey1, ey2, x, y;
      var includedEle = false;

      if( display === 'none' ){ continue; } // then ele doesn't take up space

      if( isNode && includeNodes ){
        includedEle = true;

        var pos = _p.position;
        x = pos.x;
        y = pos.y;
        var w = ele.outerWidth();
        var halfW = w/2;
        var h = ele.outerHeight();
        var halfH = h/2;

        // handle node dimensions
        /////////////////////////

        ex1 = x - halfW;
        ex2 = x + halfW;
        ey1 = y - halfH;
        ey2 = y + halfH;

        x1 = ex1 < x1 ? ex1 : x1;
        x2 = ex2 > x2 ? ex2 : x2;
        y1 = ey1 < y1 ? ey1 : y1;
        y2 = ey2 > y2 ? ey2 : y2;

      } else if( ele.isEdge() && includeEdges ){
        includedEle = true;

        var n1 = _p.source;
        var n1_p = n1._private;
        var n1pos = n1_p.position;

        var n2 = _p.target;
        var n2_p = n2._private;
        var n2pos = n2_p.position;


        // handle edge dimensions (rough box estimate)
        //////////////////////////////////////////////

        var rstyle = _p.rstyle || {};
        var w = 0;
        var wHalf = 0;

        if( styleEnabled ){
          w = style['width'].pfValue;
          wHalf = w/2;
        }

        ex1 = n1pos.x;
        ex2 = n2pos.x;
        ey1 = n1pos.y;
        ey2 = n2pos.y;

        if( ex1 > ex2 ){
          var temp = ex1;
          ex1 = ex2;
          ex2 = temp;
        }

        if( ey1 > ey2 ){
          var temp = ey1;
          ey1 = ey2;
          ey2 = temp;
        }

        // take into account edge width
        ex1 -= wHalf;
        ex2 += wHalf;
        ey1 -= wHalf;
        ey2 += wHalf;

        x1 = ex1 < x1 ? ex1 : x1;
        x2 = ex2 > x2 ? ex2 : x2;
        y1 = ey1 < y1 ? ey1 : y1;
        y2 = ey2 > y2 ? ey2 : y2;

        // handle points along edge (sanity check)
        //////////////////////////////////////////

        if( styleEnabled ){
          var pts = rstyle.bezierPts || rstyle.linePts || [];

          for( var j = 0; j < pts.length; j++ ){
            var pt = pts[j];

            ex1 = pt.x - wHalf;
            ex2 = pt.x + wHalf;
            ey1 = pt.y - wHalf;
            ey2 = pt.y + wHalf;

            x1 = ex1 < x1 ? ex1 : x1;
            x2 = ex2 > x2 ? ex2 : x2;
            y1 = ey1 < y1 ? ey1 : y1;
            y2 = ey2 > y2 ? ey2 : y2;
          }
        }

        // precise haystacks (sanity check)
        ///////////////////////////////////

        if( styleEnabled && style['curve-style'].strValue === 'haystack' ){
          var hpts = rstyle.haystackPts;

          ex1 = hpts[0].x;
          ey1 = hpts[0].y;
          ex2 = hpts[1].x;
          ey2 = hpts[1].y;

          if( ex1 > ex2 ){
            var temp = ex1;
            ex1 = ex2;
            ex2 = temp;
          }

          if( ey1 > ey2 ){
            var temp = ey1;
            ey1 = ey2;
            ey2 = temp;
          }

          x1 = ex1 < x1 ? ex1 : x1;
          x2 = ex2 > x2 ? ex2 : x2;
          y1 = ey1 < y1 ? ey1 : y1;
          y2 = ey2 > y2 ? ey2 : y2;
        }

      } // edges


      // handle label dimensions
      //////////////////////////

      if( styleEnabled ){

        var _p = ele._private;
        var style = _p.style;
        var rstyle = _p.rstyle;
        var label = style['label'].strValue;
        var fontSize = style['font-size'];
        var halign = style['text-halign'];
        var valign = style['text-valign'];
        var labelWidth = rstyle.labelWidth;
        var labelHeight = rstyle.labelHeight;
        var labelX = rstyle.labelX;
        var labelY = rstyle.labelY;
        var isEdge = ele.isEdge();
        var autorotate = style['edge-text-rotation'].strValue === 'autorotate';

        if( includeLabels && label && fontSize && labelHeight != null && labelWidth != null && labelX != null && labelY != null && halign && valign ){
          var lh = labelHeight;
          var lw = labelWidth;
          var lx1, lx2, ly1, ly2;

          if( isEdge ){
            lx1 = labelX - lw/2;
            lx2 = labelX + lw/2;
            ly1 = labelY - lh/2;
            ly2 = labelY + lh/2;

            if( autorotate ){
              var theta = _p.rscratch.labelAngle;
              var cos = Math.cos( theta );
              var sin = Math.sin( theta );

              var rotate = function( x, y ){
                x = x - labelX;
                y = y - labelY;

                return {
                  x: x*cos - y*sin + labelX,
                  y: x*sin + y*cos + labelY
                };
              };

              var px1y1 = rotate( lx1, ly1 );
              var px1y2 = rotate( lx1, ly2 );
              var px2y1 = rotate( lx2, ly1 );
              var px2y2 = rotate( lx2, ly2 );

              lx1 = Math.min( px1y1.x, px1y2.x, px2y1.x, px2y2.x );
              lx2 = Math.max( px1y1.x, px1y2.x, px2y1.x, px2y2.x );
              ly1 = Math.min( px1y1.y, px1y2.y, px2y1.y, px2y2.y );
              ly2 = Math.max( px1y1.y, px1y2.y, px2y1.y, px2y2.y );
            }
          } else {
            switch( halign.value ){
              case 'left':
                lx1 = labelX - lw;
                lx2 = labelX;
                break;

              case 'center':
                lx1 = labelX - lw/2;
                lx2 = labelX + lw/2;
                break;

              case 'right':
                lx1 = labelX;
                lx2 = labelX + lw;
                break;
            }

            switch( valign.value ){
              case 'top':
                ly1 = labelY - lh;
                ly2 = labelY;
                break;

              case 'center':
                ly1 = labelY - lh/2;
                ly2 = labelY + lh/2;
                break;

              case 'bottom':
                ly1 = labelY;
                ly2 = labelY + lh;
                break;
            }
          }

          x1 = lx1 < x1 ? lx1 : x1;
          x2 = lx2 > x2 ? lx2 : x2;
          y1 = ly1 < y1 ? ly1 : y1;
          y2 = ly2 > y2 ? ly2 : y2;
        }
      } // style enabled for labels
    } // for

    var noninf = function(x){
      if( x === Infinity || x === -Infinity ){
        return 0;
      }

      return x;
    };

    x1 = noninf(x1);
    x2 = noninf(x2);
    y1 = noninf(y1);
    y2 = noninf(y2);

    return {
      x1: x1,
      x2: x2,
      y1: y1,
      y2: y2,
      w: x2 - x1,
      h: y2 - y1
    };
  }
});

var defineDimFns = function( opts ){
  opts.uppercaseName = util.capitalize( opts.name );
  opts.autoName = 'auto' + opts.uppercaseName;
  opts.labelName = 'label' + opts.uppercaseName;
  opts.outerName = 'outer' + opts.uppercaseName;
  opts.uppercaseOuterName = util.capitalize( opts.outerName );

  fn[ opts.name ] = function dimImpl(){
    var ele = this[0];
    var _p = ele._private;
    var cy = _p.cy;
    var styleEnabled = cy._private.styleEnabled;

    if( ele ){
      if( styleEnabled ){
        var d = _p.style[ opts.name ];

        switch( d.strValue ){
          case 'auto':
            return _p[ opts.autoName ] || 0;
          case 'label':
            return _p.rstyle[ opts.labelName ] || 0;
          default:
            return d.pfValue;
        }
      } else {
        return 1;
      }
    }
  };

  fn[ 'outer' + opts.uppercaseName ] = function outerDimImpl(){
    var ele = this[0];
    var _p = ele._private;
    var cy = _p.cy;
    var styleEnabled = cy._private.styleEnabled;

    if( ele ){
      if( styleEnabled ){
        var style = _p.style;
        var dim = ele[ opts.name ]();
        var border = style['border-width'].pfValue;
        var padding = style[ opts.paddings[0] ].pfValue + style[ opts.paddings[1] ].pfValue;

        return dim + border + padding;
      } else {
        return 1;
      }
    }
  };

  fn[ 'rendered' + opts.uppercaseName ] = function renderedDimImpl(){
    var ele = this[0];

    if( ele ){
      var d = ele[ opts.name ]();
      return d * this.cy().zoom();
    }
  };

  fn[ 'rendered' + opts.uppercaseOuterName ] = function renderedOuterDimImpl(){
    var ele = this[0];

    if( ele ){
      var od = ele[ opts.outerName ]();
      return od * this.cy().zoom();
    }
  };
};

defineDimFns({
  name: 'width',
  paddings: ['padding-left', 'padding-right']
});

defineDimFns({
  name: 'height',
  paddings: ['padding-top', 'padding-bottom']
});

// aliases
fn.modelPosition = fn.point = fn.position;
fn.modelPositions = fn.points = fn.positions;
fn.renderedPoint = fn.renderedPosition;
fn.relativePoint = fn.relativePosition;
fn.boundingbox = fn.boundingBox;
fn.renderedBoundingbox = fn.renderedBoundingBox;

module.exports = elesfn;

},{"../define":41,"../is":77,"../util":94}],19:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');
var is = _dereq_('../is');

// represents a node or an edge
var Element = function(cy, params, restore){
  if( !(this instanceof Element) ){
    return new Element(cy, params, restore);
  }

  var self = this;
  restore = (restore === undefined || restore ? true : false);

  if( cy === undefined || params === undefined || !is.core(cy) ){
    util.error('An element must have a core reference and parameters set');
    return;
  }

  var group = params.group;

  // try to automatically infer the group if unspecified
  if( group == null ){
    if( params.data.source != null && params.data.target != null ){
      group = 'edges';
    } else {
      group = 'nodes';
    }
  }

  // validate group
  if( group !== 'nodes' && group !== 'edges' ){
    util.error('An element must be of type `nodes` or `edges`; you specified `' + group + '`');
    return;
  }

  // make the element array-like, just like a collection
  this.length = 1;
  this[0] = this;

  // NOTE: when something is added here, add also to ele.json()
  this._private = {
    cy: cy,
    single: true, // indicates this is an element
    data: params.data || {}, // data object
    position: params.position || {}, // (x, y) position pair
    autoWidth: undefined, // width and height of nodes calculated by the renderer when set to special 'auto' value
    autoHeight: undefined,
    listeners: [], // array of bound listeners
    group: group, // string; 'nodes' or 'edges'
    style: {}, // properties as set by the style
    rstyle: {}, // properties for style sent from the renderer to the core
    styleCxts: [], // applied style contexts from the styler
    removed: true, // whether it's inside the vis; true if removed (set true here since we call restore)
    selected: params.selected ? true : false, // whether it's selected
    selectable: params.selectable === undefined ? true : ( params.selectable ? true : false ), // whether it's selectable
    locked: params.locked ? true : false, // whether the element is locked (cannot be moved)
    grabbed: false, // whether the element is grabbed by the mouse; renderer sets this privately
    grabbable: params.grabbable === undefined ? true : ( params.grabbable ? true : false ), // whether the element can be grabbed
    active: false, // whether the element is active from user interaction
    classes: {}, // map ( className => true )
    animation: { // object for currently-running animations
      current: [],
      queue: []
    },
    rscratch: {}, // object in which the renderer can store information
    scratch: params.scratch || {}, // scratch objects
    edges: [], // array of connected edges
    children: [] // array of children
  };

  // renderedPosition overrides if specified
  if( params.renderedPosition ){
    var rpos = params.renderedPosition;
    var pan = cy.pan();
    var zoom = cy.zoom();

    this._private.position = {
      x: (rpos.x - pan.x)/zoom,
      y: (rpos.y - pan.y)/zoom
    };
  }

  if( is.string(params.classes) ){
    var classes = params.classes.split(/\s+/);
    for( var i = 0, l = classes.length; i < l; i++ ){
      var cls = classes[i];
      if( !cls || cls === '' ){ continue; }

      self._private.classes[cls] = true;
    }
  }

  if( params.style || params.css ){
    cy.style().applyBypass( this, params.style || params.css );
  }

  if( restore === undefined || restore ){
    this.restore();
  }

};

module.exports = Element;

},{"../is":77,"../util":94}],20:[function(_dereq_,module,exports){
'use strict';

var define = _dereq_('../define');

var elesfn = ({
  on: define.on(), // .on( events [, selector] [, data], handler)
  one: define.on({ unbindSelfOnTrigger: true }),
  once: define.on({ unbindAllBindersOnTrigger: true }),
  off: define.off(), // .off( events [, selector] [, handler] )
  trigger: define.trigger(), // .trigger( events [, extraParams] )

  rtrigger: function(event, extraParams){ // for internal use only
    if( this.length === 0 ){ return; } // empty collections don't need to notify anything

    // notify renderer
    this.cy().notify({
      type: event,
      collection: this
    });

    this.trigger(event, extraParams);
    return this;
  }
});

// aliases:
define.eventAliasesOn( elesfn );

module.exports = elesfn;

},{"../define":41}],21:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');
var Selector = _dereq_('../selector');

var elesfn = ({
  nodes: function( selector ){
    return this.filter(function(i, element){
      return element.isNode();
    }).filter(selector);
  },

  edges: function( selector ){
    return this.filter(function(i, element){
      return element.isEdge();
    }).filter(selector);
  },

  filter: function( filter ){
    if( is.fn(filter) ){
      var elements = [];

      for( var i = 0; i < this.length; i++ ){
        var ele = this[i];

        if( filter.apply(ele, [i, ele]) ){
          elements.push(ele);
        }
      }

      return this.spawn(elements);

    } else if( is.string(filter) || is.elementOrCollection(filter) ){
      return Selector(filter).filter(this);

    } else if( filter === undefined ){
      return this;
    }

    return this.spawn(); // if not handled by above, give 'em an empty collection
  },

  not: function( toRemove ){
    if( !toRemove ){
      return this;
    } else {

      if( is.string( toRemove ) ){
        toRemove = this.filter( toRemove );
      }

      var elements = [];

      for( var i = 0; i < this.length; i++ ){
        var element = this[i];

        var remove = toRemove._private.ids[ element.id() ];
        if( !remove ){
          elements.push( element );
        }
      }

      return this.spawn( elements );
    }

  },

  absoluteComplement: function(){
    var cy = this._private.cy;

    return cy.elements().not( this );
  },

  intersect: function( other ){
    // if a selector is specified, then filter by it instead
    if( is.string(other) ){
      var selector = other;
      return this.filter( selector );
    }

    var elements = [];
    var col1 = this;
    var col2 = other;
    var col1Smaller = this.length < other.length;
    // var ids1 = col1Smaller ? col1._private.ids : col2._private.ids;
    var ids2 = col1Smaller ? col2._private.ids : col1._private.ids;
    var col = col1Smaller ? col1 : col2;

    for( var i = 0; i < col.length; i++ ){
      var id = col[i]._private.data.id;
      var ele = ids2[ id ];

      if( ele ){
        elements.push( ele );
      }
    }

    return this.spawn( elements );
  },

  xor: function( other ){
    var cy = this._private.cy;

    if( is.string(other) ){
      other = cy.$( other );
    }

    var elements = [];
    var col1 = this;
    var col2 = other;

    var add = function( col, other ){

      for( var i = 0; i < col.length; i++ ){
        var ele = col[i];
        var id = ele._private.data.id;
        var inOther = other._private.ids[ id ];

        if( !inOther ){
          elements.push( ele );
        }
      }

    };

    add( col1, col2 );
    add( col2, col1 );

    return this.spawn( elements );
  },

  diff: function( other ){
    var cy = this._private.cy;

    if( is.string(other) ){
      other = cy.$( other );
    }

    var left = [];
    var right = [];
    var both = [];
    var col1 = this;
    var col2 = other;

    var add = function( col, other, retEles ){

      for( var i = 0; i < col.length; i++ ){
        var ele = col[i];
        var id = ele._private.data.id;
        var inOther = other._private.ids[ id ];

        if( inOther ){
          both.push( ele );
        } else {
          retEles.push( ele );
        }
      }

    };

    add( col1, col2, left );
    add( col2, col1, right );

    return {
      left: this.spawn( left, { unique: true } ),
      right: this.spawn( right, { unique: true } ),
      both: this.spawn( both, { unique: true } )
    };
  },

  add: function( toAdd ){
    var cy = this._private.cy;

    if( !toAdd ){
      return this;
    }

    if( is.string(toAdd) ){
      var selector = toAdd;
      toAdd = cy.elements(selector);
    }

    var elements = [];

    for( var i = 0; i < this.length; i++ ){
      elements.push( this[i] );
    }

    for( var i = 0; i < toAdd.length; i++ ){

      var add = !this._private.ids[ toAdd[i].id() ];
      if( add ){
        elements.push( toAdd[i] );
      }
    }

    return this.spawn(elements);
  },

  // in place merge on calling collection
  merge: function( toAdd ){
    var _p = this._private;
    var cy = _p.cy;

    if( !toAdd ){
      return this;
    }

    if( is.string(toAdd) ){
      var selector = toAdd;
      toAdd = cy.elements(selector);
    }

    for( var i = 0; i < toAdd.length; i++ ){
      var toAddEle = toAdd[i];
      var id = toAddEle.id();
      var add = !_p.ids[ id ];

      if( add ){
        var index = this.length++;

        this[ index ] = toAddEle;
        _p.ids[ id ] = toAddEle;
        _p.indexes[ id ] = index;
      }
    }

    return this; // chaining
  },

  // remove single ele in place in calling collection
  unmergeOne: function( ele ){
    ele = ele[0];

    var _p = this._private;
    var id = ele.id();
    var i = _p.indexes[ id ];

    if( i == null ){
      return this; // no need to remove
    }

    // remove ele
    this[i] = undefined;
    _p.ids[ id ] = undefined;
    _p.indexes[ id ] = undefined;

    var unmergedLastEle = i === this.length - 1;

    // replace empty spot with last ele in collection
    if( this.length > 1 && !unmergedLastEle ){
      var lastEleI = this.length - 1;
      var lastEle = this[ lastEleI ];

      this[ lastEleI ] = undefined;
      this[i] = lastEle;
      _p.indexes[ lastEle.id() ] = i;
    }

    // the collection is now 1 ele smaller
    this.length--;

    return this;
  },

  // remove eles in place on calling collection
  unmerge: function( toRemove ){
    var cy = this._private.cy;

    if( !toRemove ){
      return this;
    }

    if( is.string(toRemove) ){
      var selector = toRemove;
      toRemove = cy.elements(selector);
    }

    for( var i = 0; i < toRemove.length; i++ ){
      this.unmergeOne( toRemove[i] );
    }

    return this; // chaining
  },

  map: function( mapFn, thisArg ){
    var arr = [];
    var eles = this;

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      var ret = thisArg ? mapFn.apply( thisArg, [ele, i, eles] ) : mapFn( ele, i, eles );

      arr.push( ret );
    }

    return arr;
  },

  stdFilter: function( fn, thisArg ){
    var filterEles = [];
    var eles = this;

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      var include = thisArg ? fn.apply( thisArg, [ele, i, eles] ) : fn( ele, i, eles );

      if( include ){
        filterEles.push( ele );
      }
    }

    return this.spawn( filterEles );
  },

  max: function( valFn, thisArg ){
    var max = -Infinity;
    var maxEle;
    var eles = this;

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      var val = thisArg ? valFn.apply( thisArg, [ ele, i, eles ] ) : valFn( ele, i, eles );

      if( val > max ){
        max = val;
        maxEle = ele;
      }
    }

    return {
      value: max,
      ele: maxEle
    };
  },

  min: function( valFn, thisArg ){
    var min = Infinity;
    var minEle;
    var eles = this;

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      var val = thisArg ? valFn.apply( thisArg, [ ele, i, eles ] ) : valFn( ele, i, eles );

      if( val < min ){
        min = val;
        minEle = ele;
      }
    }

    return {
      value: min,
      ele: minEle
    };
  }
});

// aliases
var fn = elesfn;
fn['u'] = fn['|'] = fn['+'] = fn.union = fn.or = fn.add;
fn['\\'] = fn['!'] = fn['-'] = fn.difference = fn.relativeComplement = fn.subtract = fn.not;
fn['n'] = fn['&'] = fn['.'] = fn.and = fn.intersection = fn.intersect;
fn['^'] = fn['(+)'] = fn['(-)'] = fn.symmetricDifference = fn.symdiff = fn.xor;
fn.fnFilter = fn.filterFn = fn.stdFilter;
fn.complement = fn.abscomp = fn.absoluteComplement;

module.exports = elesfn;

},{"../is":77,"../selector":81}],22:[function(_dereq_,module,exports){
'use strict';

var elesfn = ({
  isNode: function(){
    return this.group() === 'nodes';
  },

  isEdge: function(){
    return this.group() === 'edges';
  },

  isLoop: function(){
    return this.isEdge() && this.source().id() === this.target().id();
  },

  isSimple: function(){
    return this.isEdge() && this.source().id() !== this.target().id();
  },

  group: function(){
    var ele = this[0];

    if( ele ){
      return ele._private.group;
    }
  }
});


module.exports = elesfn;

},{}],23:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');
var is = _dereq_('../is');

var Element = _dereq_('./element');

// factory for generating edge ids when no id is specified for a new element
var idFactory = {
  prefix: 'ele',
  id: 0,
  generate: function(cy, element, tryThisId){
    var json = is.element( element ) ? element._private : element;
    var id = tryThisId != null ? tryThisId : this.prefix + this.id;

    if( cy.getElementById(id).empty() ){
      this.id++; // we've used the current id, so move it up
    } else { // otherwise keep trying successive unused ids
      while( !cy.getElementById(id).empty() ){
        id = this.prefix + ( ++this.id );
      }
    }

    return id;
  }
};

// represents a set of nodes, edges, or both together
var Collection = function(cy, elements, options){
  if( !(this instanceof Collection) ){
    return new Collection(cy, elements, options);
  }

  if( cy === undefined || !is.core(cy) ){
    util.error('A collection must have a reference to the core');
    return;
  }

  var ids = {};
  var indexes = {};
  var createdElements = false;

  if( !elements ){
    elements = [];
  } else if( elements.length > 0 && is.plainObject( elements[0] ) && !is.element( elements[0] ) ){
    createdElements = true;

    // make elements from json and restore all at once later
    var eles = [];
    var elesIds = {};

    for( var i = 0, l = elements.length; i < l; i++ ){
      var json = elements[i];

      if( json.data == null ){
        json.data = {};
      }

      var data = json.data;

      // make sure newly created elements have valid ids
      if( data.id == null ){
        data.id = idFactory.generate( cy, json );
      } else if( cy.getElementById( data.id ).length !== 0 || elesIds[ data.id ] ){
        continue; // can't create element if prior id already exists
      }

      var ele = new Element( cy, json, false );
      eles.push( ele );
      elesIds[ data.id ] = true;
    }

    elements = eles;
  }

  this.length = 0;

  for( var i = 0, l = elements.length; i < l; i++ ){
    var element = elements[i];
    if( !element ){  continue; }

    var id = element._private.data.id;

    if( !options || (options.unique && !ids[ id ] ) ){
      ids[ id ] = element;
      indexes[ id ] = this.length;

      this[ this.length ] = element;
      this.length++;
    }
  }

  this._private = {
    cy: cy,
    ids: ids,
    indexes: indexes
  };

  // restore the elements if we created them from json
  if( createdElements ){
    this.restore();
  }
};

// Functions
////////////////////////////////////////////////////////////////////////////////////////////////////

// keep the prototypes in sync (an element has the same functions as a collection)
// and use elefn and elesfn as shorthands to the prototypes
var elesfn = Element.prototype = Collection.prototype;

elesfn.instanceString = function(){
  return 'collection';
};

elesfn.spawn = function( cy, eles, opts ){
  if( !is.core(cy) ){ // cy is optional
    opts = eles;
    eles = cy;
    cy = this.cy();
  }

  return new Collection( cy, eles, opts );
};

elesfn.cy = function(){
  return this._private.cy;
};

elesfn.element = function(){
  return this[0];
};

elesfn.collection = function(){
  if( is.collection(this) ){
    return this;
  } else { // an element
    return new Collection( this._private.cy, [this] );
  }
};

elesfn.unique = function(){
  return new Collection( this._private.cy, this, { unique: true } );
};

elesfn.getElementById = function( id ){
  var cy = this._private.cy;
  var ele = this._private.ids[ id ];

  return ele ? ele : new Collection(cy); // get ele or empty collection
};

elesfn.json = function( obj ){
  var ele = this.element();
  var cy = this.cy();

  if( ele == null && obj ){ return this; } // can't set to no eles

  if( ele == null ){ return undefined; } // can't get from no eles

  var p = ele._private;

  if( is.plainObject(obj) ){ // set

    cy.startBatch();

    if( obj.data ){
      ele.data( obj.data );
    }

    if( obj.position ){
      ele.position( obj.position );
    }

    // ignore group -- immutable

    var checkSwitch = function( k, trueFnName, falseFnName ){
      var obj_k = obj[k];

      if( obj_k != null && obj_k !== p[k] ){
        if( obj_k ){
          ele[ trueFnName ]();
        } else {
          ele[ falseFnName ]();
        }
      }
    };

    checkSwitch( 'removed', 'remove', 'restore' );

    checkSwitch( 'selected', 'select', 'unselect' );

    checkSwitch( 'selectable', 'selectify', 'unselectify' );

    checkSwitch( 'locked', 'lock', 'unlock' );

    checkSwitch( 'grabbable', 'grabify', 'ungrabify' );

    if( obj.classes != null ){
      ele.classes( obj.classes );
    }

    cy.endBatch();

    return this;

  } else if( obj === undefined ){ // get

    var json = {
      data: util.copy( p.data ),
      position: util.copy( p.position ),
      group: p.group,
      removed: p.removed,
      selected: p.selected,
      selectable: p.selectable,
      locked: p.locked,
      grabbable: p.grabbable,
      classes: null
    };

    var classes = [];
    for( var cls in p.classes ){
      if( p.classes[cls] ){
        classes.push(cls);
      }
    }
    json.classes = classes.join(' ');

    return json;
  }
};

elesfn.jsons = function(){
  var jsons = [];

  for( var i = 0; i < this.length; i++ ){
    var ele = this[i];
    var json = ele.json();

    jsons.push( json );
  }

  return jsons;
};

elesfn.clone = function(){
  var cy = this.cy();
  var elesArr = [];

  for( var i = 0; i < this.length; i++ ){
    var ele = this[i];
    var json = ele.json();
    var clone = new Element(cy, json, false); // NB no restore

    elesArr.push( clone );
  }

  return new Collection( cy, elesArr );
};
elesfn.copy = elesfn.clone;

elesfn.restore = function( notifyRenderer ){
  var self = this;
  var restored = [];
  var cy = self.cy();

  if( notifyRenderer === undefined ){
    notifyRenderer = true;
  }

  // create arrays of nodes and edges, since we need to
  // restore the nodes first
  var elements = [];
  var nodes = [], edges = [];
  var numNodes = 0;
  var numEdges = 0;
  for( var i = 0, l = self.length; i < l; i++ ){
    var ele = self[i];

    // keep nodes first in the array and edges after
    if( ele.isNode() ){ // put to front of array if node
      nodes.push( ele );
      numNodes++;
    } else { // put to end of array if edge
      edges.push( ele );
      numEdges++;
    }
  }

  elements = nodes.concat( edges );

  // now, restore each element
  for( var i = 0, l = elements.length; i < l; i++ ){
    var ele = elements[i];

    if( !ele.removed() ){
      // don't need to do anything
      continue;
    }

    var _private = ele._private;
    var data = _private.data;

    // set id and validate
    if( data.id === undefined ){
      data.id = idFactory.generate( cy, ele );

    } else if( is.number(data.id) ){
      data.id = '' + data.id; // now it's a string

    } else if( is.emptyString(data.id) || !is.string(data.id) ){
      util.error('Can not create element with invalid string ID `' + data.id + '`');

      // can't create element if it has empty string as id or non-string id
      continue;
    } else if( cy.getElementById( data.id ).length !== 0 ){
      util.error('Can not create second element with ID `' + data.id + '`');

      // can't create element if one already has that id
      continue;
    }

    var id = data.id; // id is finalised, now let's keep a ref

    if( ele.isNode() ){ // extra checks for nodes
      var node = ele;
      var pos = _private.position;

      // make sure the nodes have a defined position

      if( pos.x == null ){
        pos.x = 0;
      }

      if( pos.y == null ){
        pos.y = 0;
      }
    }

    if( ele.isEdge() ){ // extra checks for edges

      var edge = ele;
      var fields = ['source', 'target'];
      var fieldsLength = fields.length;
      var badSourceOrTarget = false;
      for(var j = 0; j < fieldsLength; j++){

        var field = fields[j];
        var val = data[field];

        if( is.number(val) ){
          val = data[field] = '' + data[field]; // now string
        }

        if( val == null || val === '' ){
          // can't create if source or target is not defined properly
          util.error('Can not create edge `' + id + '` with unspecified ' + field);
          badSourceOrTarget = true;
        } else if( cy.getElementById(val).empty() ){
          // can't create edge if one of its nodes doesn't exist
          util.error('Can not create edge `' + id + '` with nonexistant ' + field + ' `' + val + '`');
          badSourceOrTarget = true;
        }
      }

      if( badSourceOrTarget ){ continue; } // can't create this

      var src = cy.getElementById( data.source );
      var tgt = cy.getElementById( data.target );

      src._private.edges.push( edge );
      tgt._private.edges.push( edge );

      edge._private.source = src;
      edge._private.target = tgt;

    } // if is edge

    // create mock ids map for element so it can be used like collections
    _private.ids = {};
    _private.ids[ id ] = ele;

    _private.removed = false;
    cy.addToPool( ele );

    restored.push( ele );
  } // for each element

  // do compound node sanity checks
  for( var i = 0; i < numNodes; i++ ){ // each node
    var node = elements[i];
    var data = node._private.data;

    if( is.number(data.parent) ){ // then automake string
      data.parent = '' + data.parent;
    }

    var parentId = data.parent;

    var specifiedParent = parentId != null;

    if( specifiedParent ){
      var parent = cy.getElementById( parentId );

      if( parent.empty() ){
        // non-existant parent; just remove it
        data.parent = undefined;
      } else {
        var selfAsParent = false;
        var ancestor = parent;
        while( !ancestor.empty() ){
          if( node.same(ancestor) ){
            // mark self as parent and remove from data
            selfAsParent = true;
            data.parent = undefined; // remove parent reference

            // exit or we loop forever
            break;
          }

          ancestor = ancestor.parent();
        }

        if( !selfAsParent ){
          // connect with children
          parent[0]._private.children.push( node );
          node._private.parent = parent[0];

          // let the core know we have a compound graph
          cy._private.hasCompoundNodes = true;
        }
      } // else
    } // if specified parent
  } // for each node

  restored = new Collection( cy, restored );
  if( restored.length > 0 ){

    var toUpdateStyle = restored.add( restored.connectedNodes() ).add( restored.parent() );
    toUpdateStyle.updateStyle( notifyRenderer );

    if( notifyRenderer ){
      restored.rtrigger('add');
    } else {
      restored.trigger('add');
    }
  }

  return self; // chainability
};

elesfn.removed = function(){
  var ele = this[0];
  return ele && ele._private.removed;
};

elesfn.inside = function(){
  var ele = this[0];
  return ele && !ele._private.removed;
};

elesfn.remove = function( notifyRenderer ){
  var self = this;
  var removed = [];
  var elesToRemove = [];
  var elesToRemoveIds = {};
  var cy = self._private.cy;

  if( notifyRenderer === undefined ){
    notifyRenderer = true;
  }

  // add connected edges
  function addConnectedEdges(node){
    var edges = node._private.edges;
    for( var i = 0; i < edges.length; i++ ){
      add( edges[i] );
    }
  }


  // add descendant nodes
  function addChildren(node){
    var children = node._private.children;

    for( var i = 0; i < children.length; i++ ){
      add( children[i] );
    }
  }

  function add( ele ){
    var alreadyAdded =  elesToRemoveIds[ ele.id() ];
    if( alreadyAdded ){
      return;
    } else {
      elesToRemoveIds[ ele.id() ] = true;
    }

    if( ele.isNode() ){
      elesToRemove.push( ele ); // nodes are removed last

      addConnectedEdges( ele );
      addChildren( ele );
    } else {
      elesToRemove.unshift( ele ); // edges are removed first
    }
  }

  // make the list of elements to remove
  // (may be removing more than specified due to connected edges etc)

  for( var i = 0, l = self.length; i < l; i++ ){
    var ele = self[i];

    add( ele );
  }

  function removeEdgeRef(node, edge){
    var connectedEdges = node._private.edges;
    for( var j = 0; j < connectedEdges.length; j++ ){
      var connectedEdge = connectedEdges[j];

      if( edge === connectedEdge ){
        connectedEdges.splice( j, 1 );
        break;
      }
    }
  }

  function removeChildRef(parent, ele){
    ele = ele[0];
    parent = parent[0];
    var children = parent._private.children;

    for( var j = 0; j < children.length; j++ ){
      if( children[j][0] === ele[0] ){
        children.splice(j, 1);
        break;
      }
    }
  }

  for( var i = 0; i < elesToRemove.length; i++ ){
    var ele = elesToRemove[i];

    // mark as removed
    ele._private.removed = true;

    // remove from core pool
    cy.removeFromPool( ele );

    // add to list of removed elements
    removed.push( ele );

    if( ele.isEdge() ){ // remove references to this edge in its connected nodes
      var src = ele.source()[0];
      var tgt = ele.target()[0];

      removeEdgeRef( src, ele );
      removeEdgeRef( tgt, ele );

    } else { // remove reference to parent
      var parent = ele.parent();

      if( parent.length !== 0 ){
        removeChildRef(parent, ele);
      }
    }
  }

  // check to see if we have a compound graph or not
  var elesStillInside = cy._private.elements;
  cy._private.hasCompoundNodes = false;
  for( var i = 0; i < elesStillInside.length; i++ ){
    var ele = elesStillInside[i];

    if( ele.isParent() ){
      cy._private.hasCompoundNodes = true;
      break;
    }
  }

  var removedElements = new Collection( this.cy(), removed );
  if( removedElements.size() > 0 ){
    // must manually notify since trigger won't do this automatically once removed

    if( notifyRenderer ){
      this.cy().notify({
        type: 'remove',
        collection: removedElements
      });
    }

    removedElements.trigger('remove');
  }

  // check for empty remaining parent nodes
  var checkedParentId = {};
  for( var i = 0; i < elesToRemove.length; i++ ){
    var ele = elesToRemove[i];
    var isNode = ele._private.group === 'nodes';
    var parentId = ele._private.data.parent;

    if( isNode && parentId !== undefined && !checkedParentId[ parentId ] ){
      checkedParentId[ parentId ] = true;
      var parent = cy.getElementById( parentId );

      if( parent && parent.length !== 0 && !parent._private.removed && parent.children().length === 0 ){
        parent.updateStyle();
      }
    }
  }

  return new Collection( cy, removed );
};

elesfn.move = function( struct ){
  var cy = this._private.cy;

  if( struct.source !== undefined || struct.target !== undefined ){
    var srcId = struct.source;
    var tgtId = struct.target;
    var srcExists = cy.getElementById( srcId ).length > 0;
    var tgtExists = cy.getElementById( tgtId ).length > 0;

    if( srcExists || tgtExists ){
      var jsons = this.jsons();

      this.remove();

      for( var i = 0; i < jsons.length; i++ ){
        var json = jsons[i];

        if( json.group === 'edges' ){
          if( srcExists ){ json.data.source = srcId; }
          if( tgtExists ){ json.data.target = tgtId; }
        }
      }

      return cy.add( jsons );
    }

  } else if( struct.parent !== undefined ){ // move node to new parent
    var parentId = struct.parent;
    var parentExists = parentId === null || cy.getElementById( parentId ).length > 0;

    if( parentExists ){
      var jsons = this.jsons();
      var descs = this.descendants();
      var descsEtc = descs.merge( descs.add(this).connectedEdges() );

      this.remove(); // NB: also removes descendants and their connected edges

      for( var i = 0; i < this.length; i++ ){
        var json = jsons[i];

        if( json.group === 'nodes' ){
          json.data.parent = parentId === null ? undefined : parentId;
        }
      }
    }

    return cy.add( jsons ).merge( descsEtc.restore() );
  }

  return this; // if nothing done
};

[
  _dereq_('./algorithms'),
  _dereq_('./animation'),
  _dereq_('./class'),
  _dereq_('./comparators'),
  _dereq_('./compounds'),
  _dereq_('./data'),
  _dereq_('./degree'),
  _dereq_('./dimensions'),
  _dereq_('./events'),
  _dereq_('./filter'),
  _dereq_('./group'),
  _dereq_('./index'),
  _dereq_('./iteration'),
  _dereq_('./layout'),
  _dereq_('./style'),
  _dereq_('./switch-functions'),
  _dereq_('./traversing')
].forEach(function( props ){
  util.extend( elesfn, props );
});

module.exports = Collection;

},{"../is":77,"../util":94,"./algorithms":9,"./animation":12,"./class":13,"./comparators":14,"./compounds":15,"./data":16,"./degree":17,"./dimensions":18,"./element":19,"./events":20,"./filter":21,"./group":22,"./index":23,"./iteration":24,"./layout":25,"./style":26,"./switch-functions":27,"./traversing":28}],24:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');
var zIndexSort = _dereq_('./zsort');

var elesfn = ({
  each: function(fn){
    if( is.fn(fn) ){
      for(var i = 0; i < this.length; i++){
        var ele = this[i];
        var ret = fn.apply( ele, [ i, ele ] );

        if( ret === false ){ break; } // exit each early on return false
      }
    }
    return this;
  },

  forEach: function(fn, thisArg){
    if( is.fn(fn) ){

      for(var i = 0; i < this.length; i++){
        var ele = this[i];
        var ret = thisArg ? fn.apply( thisArg, [ ele, i, this ] ) : fn( ele, i, this );

        if( ret === false ){ break; } // exit each early on return false
      }
    }

    return this;
  },

  toArray: function(){
    var array = [];

    for(var i = 0; i < this.length; i++){
      array.push( this[i] );
    }

    return array;
  },

  slice: function(start, end){
    var array = [];
    var thisSize = this.length;

    if( end == null ){
      end = thisSize;
    }

    if( start == null ){
      start = 0;
    }

    if( start < 0 ){
      start = thisSize + start;
    }

    if( end < 0 ){
      end = thisSize + end;
    }

    for(var i = start; i >= 0 && i < end && i < thisSize; i++){
      array.push( this[i] );
    }

    return this.spawn(array);
  },

  size: function(){
    return this.length;
  },

  eq: function(i){
    return this[i] || this.spawn();
  },

  first: function(){
    return this[0] || this.spawn();
  },

  last: function(){
    return this[ this.length - 1 ] || this.spawn();
  },

  empty: function(){
    return this.length === 0;
  },

  nonempty: function(){
    return !this.empty();
  },

  sort: function( sortFn ){
    if( !is.fn( sortFn ) ){
      return this;
    }

    var sorted = this.toArray().sort( sortFn );

    return this.spawn(sorted);
  },

  sortByZIndex: function(){
    return this.sort( zIndexSort );
  },

  zDepth: function(){
    var ele = this[0];
    if( !ele ){ return undefined; }

    // var cy = ele.cy();
    var _p = ele._private;
    var group = _p.group;

    if( group === 'nodes' ){
      var depth = _p.data.parent ? ele.parents().size() : 0;

      if( !ele.isParent() ){
        return Number.MAX_VALUE; // childless nodes always on top
      }

      return depth;
    } else {
      var src = _p.source;
      var tgt = _p.target;
      var srcDepth = src.zDepth();
      var tgtDepth = tgt.zDepth();

      return Math.max( srcDepth, tgtDepth, 0 ); // depth of deepest parent
    }
  }
});

module.exports = elesfn;

},{"../is":77,"./zsort":29}],25:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');
var util = _dereq_('../util');

var elesfn = ({

  // using standard layout options, apply position function (w/ or w/o animation)
  layoutPositions: function( layout, options, fn ){
    var nodes = this.nodes();
    var cy = this.cy();

    layout.trigger({ type: 'layoutstart', layout: layout });

    layout.animations = [];

    if( options.animate ){
      for( var i = 0; i < nodes.length; i++ ){
        var node = nodes[i];
        var lastNode = i === nodes.length - 1;

        var newPos = fn.call( node, i, node );
        var pos = node.position();

        if( !is.number(pos.x) || !is.number(pos.y) ){
          node.silentPosition({ x: 0, y: 0 });
        }

        var ani = node.animation({
          position: newPos,
          duration: options.animationDuration,
          easing: options.animationEasing,
          step: !lastNode ? undefined : function(){
            if( options.fit ){
              cy.fit( options.eles, options.padding );
            }
          },
          complete: !lastNode ? undefined : function(){
            if( options.zoom != null ){
              cy.zoom( options.zoom );
            }

            if( options.pan ){
              cy.pan( options.pan );
            }

            if( options.fit ){
              cy.fit( options.eles, options.padding );
            }

            layout.one('layoutstop', options.stop);
            layout.trigger({ type: 'layoutstop', layout: layout });
          }
        });

        layout.animations.push( ani );

        ani.play();
      }

      layout.one('layoutready', options.ready);
      layout.trigger({ type: 'layoutready', layout: layout });
    } else {
      nodes.positions( fn );

      if( options.fit ){
        cy.fit( options.eles, options.padding );
      }

      if( options.zoom != null ){
        cy.zoom( options.zoom );
      }

      if( options.pan ){
        cy.pan( options.pan );
      }

      layout.one('layoutready', options.ready);
      layout.trigger({ type: 'layoutready', layout: layout });

      layout.one('layoutstop', options.stop);
      layout.trigger({ type: 'layoutstop', layout: layout });
    }

    return this; // chaining
  },

  layout: function( options ){
    var cy = this.cy();

    cy.layout( util.extend({}, options, {
      eles: this
    }) );

    return this;
  },

  makeLayout: function( options ){
    var cy = this.cy();

    return cy.makeLayout( util.extend({}, options, {
      eles: this
    }) );
  }

});

// aliases:
elesfn.createLayout = elesfn.makeLayout;

module.exports = elesfn;

},{"../is":77,"../util":94}],26:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');

var elesfn = ({

  // fully updates (recalculates) the style for the elements
  updateStyle: function( notifyRenderer ){
    var cy = this._private.cy;

    if( !cy.styleEnabled() ){ return this; }

    if( cy._private.batchingStyle ){
      var bEles = cy._private.batchStyleEles;

      bEles.merge( this );

      return this; // chaining and exit early when batching
    }

    var style = cy.style();
    notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;

    style.apply( this );

    var updatedCompounds = this.updateCompoundBounds();
    var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;

    if( notifyRenderer ){
      toNotify.rtrigger('style'); // let renderer know we changed style
    } else {
      toNotify.trigger('style'); // just fire the event
    }
    return this; // chaining
  },

  // just update the mappers in the elements' styles; cheaper than eles.updateStyle()
  updateMappers: function( notifyRenderer ){
    var cy = this._private.cy;
    var style = cy.style();
    notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;

    if( !cy.styleEnabled() ){ return this; }

    style.updateMappers( this );

    var updatedCompounds = this.updateCompoundBounds();
    var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;

    if( notifyRenderer ){
      toNotify.rtrigger('style'); // let renderer know we changed style
    } else {
      toNotify.trigger('style'); // just fire the event
    }
    return this; // chaining
  },

  // get the specified css property as a rendered value (i.e. on-screen value)
  // or get the whole rendered style if no property specified (NB doesn't allow setting)
  renderedCss: function( property ){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return this; }

    var ele = this[0];

    if( ele ){
      var renstyle = ele.cy().style().getRenderedStyle( ele );

      if( property === undefined ){
        return renstyle;
      } else {
        return renstyle[ property ];
      }
    }
  },

  // read the calculated css style of the element or override the style (via a bypass)
  css: function( name, value ){
    var cy = this.cy();

    if( !cy.styleEnabled() ){ return this; }

    var updateTransitions = false;
    var style = cy.style();

    if( is.plainObject(name) ){ // then extend the bypass
      var props = name;
      style.applyBypass( this, props, updateTransitions );

      var updatedCompounds = this.updateCompoundBounds();
      var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;
      toNotify.rtrigger('style'); // let the renderer know we've updated style

    } else if( is.string(name) ){

      if( value === undefined ){ // then get the property from the style
        var ele = this[0];

        if( ele ){
          return style.getStylePropertyValue( ele, name );
        } else { // empty collection => can't get any value
          return;
        }

      } else { // then set the bypass with the property value
        style.applyBypass( this, name, value, updateTransitions );

        var updatedCompounds = this.updateCompoundBounds();
        var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;
        toNotify.rtrigger('style'); // let the renderer know we've updated style
      }

    } else if( name === undefined ){
      var ele = this[0];

      if( ele ){
        return style.getRawStyle( ele );
      } else { // empty collection => can't get any value
        return;
      }
    }

    return this; // chaining
  },

  removeCss: function( names ){
    var cy = this.cy();

    if( !cy.styleEnabled() ){ return this; }

    var updateTransitions = false;
    var style = cy.style();
    var eles = this;

    if( names === undefined ){
      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[i];

        style.removeAllBypasses( ele, updateTransitions );
      }
    } else {
      names = names.split(/\s+/);

      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[i];

        style.removeBypasses( ele, names, updateTransitions );
      }
    }

    var updatedCompounds = this.updateCompoundBounds();
    var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;
    toNotify.rtrigger('style'); // let the renderer know we've updated style

    return this; // chaining
  },

  show: function(){
    this.css('display', 'element');
    return this; // chaining
  },

  hide: function(){
    this.css('display', 'none');
    return this; // chaining
  },

  visible: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return true; }

    var ele = this[0];
    var hasCompoundNodes = cy.hasCompoundNodes();

    if( ele ){
      var style = ele._private.style;

      if(
        style['visibility'].value !== 'visible'
        || style['display'].value !== 'element'
      ){
        return false;
      }

      if( ele._private.group === 'nodes' ){
        if( !hasCompoundNodes ){ return true; }

        var parents = ele._private.data.parent ? ele.parents() : null;

        if( parents ){
          for( var i = 0; i < parents.length; i++ ){
            var parent = parents[i];
            var pStyle = parent._private.style;
            var pVis = pStyle['visibility'].value;
            var pDis = pStyle['display'].value;

            if( pVis !== 'visible' || pDis !== 'element' ){
              return false;
            }
          }
        }

        return true;
      } else {
        var src = ele._private.source;
        var tgt = ele._private.target;

        return src.visible() && tgt.visible();
      }

    }
  },

  hidden: function(){
    var ele = this[0];

    if( ele ){
      return !ele.visible();
    }
  },

  effectiveOpacity: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return 1; }

    var hasCompoundNodes = cy.hasCompoundNodes();
    var ele = this[0];

    if( ele ){
      var _p = ele._private;
      var parentOpacity = _p.style.opacity.value;

      if( !hasCompoundNodes ){ return parentOpacity; }

      var parents = !_p.data.parent ? null : ele.parents();

      if( parents ){
        for( var i = 0; i < parents.length; i++ ){
          var parent = parents[i];
          var opacity = parent._private.style.opacity.value;

          parentOpacity = opacity * parentOpacity;
        }
      }

      return parentOpacity;
    }
  },

  transparent: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return false; }

    var ele = this[0];
    var hasCompoundNodes = ele.cy().hasCompoundNodes();

    if( ele ){
      if( !hasCompoundNodes ){
        return ele._private.style.opacity.value === 0;
      } else {
        return ele.effectiveOpacity() === 0;
      }
    }
  },

  isFullAutoParent: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return false; }

    var ele = this[0];

    if( ele ){
      var autoW = ele._private.style['width'].value === 'auto';
      var autoH = ele._private.style['height'].value === 'auto';

      return ele.isParent() && autoW && autoH;
    }
  },

  backgrounding: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return false; }

    var ele = this[0];

    return ele._private.backgrounding ? true : false;
  }

});


elesfn.bypass = elesfn.style = elesfn.css;
elesfn.renderedStyle = elesfn.renderedCss;
elesfn.removeBypass = elesfn.removeStyle = elesfn.removeCss;

module.exports = elesfn;

},{"../is":77}],27:[function(_dereq_,module,exports){
'use strict';

var elesfn = {};

function defineSwitchFunction(params){
  return function(){
    var args = arguments;
    var changedEles = [];

    // e.g. cy.nodes().select( data, handler )
    if( args.length === 2 ){
      var data = args[0];
      var handler = args[1];
      this.bind( params.event, data, handler );
    }

    // e.g. cy.nodes().select( handler )
    else if( args.length === 1 ){
      var handler = args[0];
      this.bind( params.event, handler );
    }

    // e.g. cy.nodes().select()
    else if( args.length === 0 ){
      for( var i = 0; i < this.length; i++ ){
        var ele = this[i];
        var able = !params.ableField || ele._private[params.ableField];
        var changed = ele._private[params.field] != params.value;

        if( params.overrideAble ){
          var overrideAble = params.overrideAble(ele);

          if( overrideAble !== undefined ){
            able = overrideAble;

            if( !overrideAble ){ return this; } // to save cycles assume not able for all on override
          }
        }

        if( able ){
          ele._private[params.field] = params.value;

          if( changed ){
            changedEles.push( ele );
          }
        }
      }

      var changedColl = this.spawn( changedEles );
      changedColl.updateStyle(); // change of state => possible change of style
      changedColl.trigger( params.event );
    }

    return this;
  };
}

function defineSwitchSet( params ){
  elesfn[ params.field ] = function(){
    var ele = this[0];

    if( ele ){
      if( params.overrideField ){
        var val = params.overrideField(ele);

        if( val !== undefined ){
          return val;
        }
      }

      return ele._private[ params.field ];
    }
  };

  elesfn[ params.on ] = defineSwitchFunction({
    event: params.on,
    field: params.field,
    ableField: params.ableField,
    overrideAble: params.overrideAble,
    value: true
  });

  elesfn[ params.off ] = defineSwitchFunction({
    event: params.off,
    field: params.field,
    ableField: params.ableField,
    overrideAble: params.overrideAble,
    value: false
  });
}

defineSwitchSet({
  field: 'locked',
  overrideField: function(ele){
    return ele.cy().autolock() ? true : undefined;
  },
  on: 'lock',
  off: 'unlock'
});

defineSwitchSet({
  field: 'grabbable',
  overrideField: function(ele){
    return ele.cy().autoungrabify() ? false : undefined;
  },
  on: 'grabify',
  off: 'ungrabify'
});

defineSwitchSet({
  field: 'selected',
  ableField: 'selectable',
  overrideAble: function(ele){
    return ele.cy().autounselectify() ? false : undefined;
  },
  on: 'select',
  off: 'unselect'
});

defineSwitchSet({
  field: 'selectable',
  overrideField: function(ele){
    return ele.cy().autounselectify() ? false : undefined;
  },
  on: 'selectify',
  off: 'unselectify'
});

elesfn.deselect = elesfn.unselect;

elesfn.grabbed = function(){
  var ele = this[0];
  if( ele ){
    return ele._private.grabbed;
  }
};

defineSwitchSet({
  field: 'active',
  on: 'activate',
  off: 'unactivate'
});

elesfn.inactive = function(){
  var ele = this[0];
  if( ele ){
    return !ele._private.active;
  }
};

module.exports = elesfn;

},{}],28:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');
var is = _dereq_('../is');

var elesfn = {};

util.extend(elesfn, {
  // get the root nodes in the DAG
  roots: function( selector ){
    var eles = this;
    var roots = [];

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      if( !ele.isNode() ){
        continue;
      }

      var hasEdgesPointingIn = ele.connectedEdges(function(){
        return this.data('target') === ele.id() && this.data('source') !== ele.id();
      }).length > 0;

      if( !hasEdgesPointingIn ){
        roots.push( ele );
      }
    }

    return this.spawn( roots, { unique: true } ).filter( selector );
  },

  // get the leaf nodes in the DAG
  leaves: function( selector ){
    var eles = this;
    var leaves = [];

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      if( !ele.isNode() ){
        continue;
      }

      var hasEdgesPointingOut = ele.connectedEdges(function(){
        return this.data('source') === ele.id() && this.data('target') !== ele.id();
      }).length > 0;

      if( !hasEdgesPointingOut ){
        leaves.push( ele );
      }
    }

    return this.spawn( leaves, { unique: true } ).filter( selector );
  },

  // normally called children in graph theory
  // these nodes =edges=> outgoing nodes
  outgoers: function( selector ){
    var eles = this;
    var oEles = [];

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      var eleId = ele.id();

      if( !ele.isNode() ){ continue; }

      var edges = ele._private.edges;
      for( var j = 0; j < edges.length; j++ ){
        var edge = edges[j];
        var srcId = edge._private.data.source;
        var tgtId = edge._private.data.target;

        if( srcId === eleId && tgtId !== eleId ){
          oEles.push( edge );
          oEles.push( edge.target()[0] );
        }
      }
    }

    return this.spawn( oEles, { unique: true } ).filter( selector );
  },

  // aka DAG descendants
  successors: function( selector ){
    var eles = this;
    var sEles = [];
    var sElesIds = {};

    for(;;){
      var outgoers = eles.outgoers();

      if( outgoers.length === 0 ){ break; } // done if no outgoers left

      var newOutgoers = false;
      for( var i = 0; i < outgoers.length; i++ ){
        var outgoer = outgoers[i];
        var outgoerId = outgoer.id();

        if( !sElesIds[ outgoerId ] ){
          sElesIds[ outgoerId ] = true;
          sEles.push( outgoer );
          newOutgoers = true;
        }
      }

      if( !newOutgoers ){ break; } // done if touched all outgoers already

      eles = outgoers;
    }

    return this.spawn( sEles, { unique: true } ).filter( selector );
  },

  // normally called parents in graph theory
  // these nodes <=edges= incoming nodes
  incomers: function( selector ){
    var eles = this;
    var oEles = [];

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      var eleId = ele.id();

      if( !ele.isNode() ){ continue; }

      var edges = ele._private.edges;
      for( var j = 0; j < edges.length; j++ ){
        var edge = edges[j];
        var srcId = edge._private.data.source;
        var tgtId = edge._private.data.target;

        if( tgtId === eleId && srcId !== eleId ){
          oEles.push( edge );
          oEles.push( edge.source()[0] );
        }
      }
    }

    return this.spawn( oEles, { unique: true } ).filter( selector );
  },

  // aka DAG ancestors
  predecessors: function( selector ){
    var eles = this;
    var pEles = [];
    var pElesIds = {};

    for(;;){
      var incomers = eles.incomers();

      if( incomers.length === 0 ){ break; } // done if no incomers left

      var newIncomers = false;
      for( var i = 0; i < incomers.length; i++ ){
        var incomer = incomers[i];
        var incomerId = incomer.id();

        if( !pElesIds[ incomerId ] ){
          pElesIds[ incomerId ] = true;
          pEles.push( incomer );
          newIncomers = true;
        }
      }

      if( !newIncomers ){ break; } // done if touched all incomers already

      eles = incomers;
    }

    return this.spawn( pEles, { unique: true } ).filter( selector );
  }
});


// Neighbourhood functions
//////////////////////////

util.extend(elesfn, {
  neighborhood: function(selector){
    var elements = [];
    var nodes = this.nodes();

    for( var i = 0; i < nodes.length; i++ ){ // for all nodes
      var node = nodes[i];
      var connectedEdges = node.connectedEdges();

      // for each connected edge, add the edge and the other node
      for( var j = 0; j < connectedEdges.length; j++ ){
        var edge = connectedEdges[j];
        var src = edge._private.source;
        var tgt = edge._private.target;
        var otherNode = node === src ? tgt : src;

        // need check in case of loop
        if( otherNode.length > 0 ){
          elements.push( otherNode[0] ); // add node 1 hop away
        }

        // add connected edge
        elements.push( edge[0] );
      }

    }

    return ( this.spawn( elements, { unique: true } ) ).filter( selector );
  },

  closedNeighborhood: function(selector){
    return this.neighborhood().add( this ).filter( selector );
  },

  openNeighborhood: function(selector){
    return this.neighborhood( selector );
  }
});

// aliases
elesfn.neighbourhood = elesfn.neighborhood;
elesfn.closedNeighbourhood = elesfn.closedNeighborhood;
elesfn.openNeighbourhood = elesfn.openNeighborhood;

// Edge functions
/////////////////

util.extend(elesfn, {
  source: function( selector ){
    var ele = this[0];
    var src;

    if( ele ){
      src = ele._private.source;
    }

    return src && selector ? src.filter( selector ) : src;
  },

  target: function( selector ){
    var ele = this[0];
    var tgt;

    if( ele ){
      tgt = ele._private.target;
    }

    return tgt && selector ? tgt.filter( selector ) : tgt;
  },

  sources: defineSourceFunction({
    attr: 'source'
  }),

  targets: defineSourceFunction({
    attr: 'target'
  })
});

function defineSourceFunction( params ){
  return function( selector ){
    var sources = [];

    for( var i = 0; i < this.length; i++ ){
      var ele = this[i];
      var src = ele._private[ params.attr ];

      if( src ){
        sources.push( src );
      }
    }

    return this.spawn( sources, { unique: true } ).filter( selector );
  };
}

util.extend(elesfn, {
  edgesWith: defineEdgesWithFunction(),

  edgesTo: defineEdgesWithFunction({
    thisIs: 'source'
  })
});

function defineEdgesWithFunction( params ){

  return function edgesWithImpl( otherNodes ){
    var elements = [];
    var cy = this._private.cy;
    var p = params || {};

    // get elements if a selector is specified
    if( is.string(otherNodes) ){
      otherNodes = cy.$( otherNodes );
    }

    var thisIds = this._private.ids;
    var otherIds = otherNodes._private.ids;

    for( var h = 0; h < otherNodes.length; h++ ){
      var edges = otherNodes[h]._private.edges;

      for( var i = 0; i < edges.length; i++ ){
        var edge = edges[i];
        var edgeData = edge._private.data;
        var thisToOther = thisIds[ edgeData.source ] && otherIds[ edgeData.target ];
        var otherToThis = otherIds[ edgeData.source ] && thisIds[ edgeData.target ];
        var edgeConnectsThisAndOther = thisToOther || otherToThis;

        if( !edgeConnectsThisAndOther ){ continue; }

        if( p.thisIs ){
          if( p.thisIs === 'source' && !thisToOther ){ continue; }

          if( p.thisIs === 'target' && !otherToThis ){ continue; }
        }

        elements.push( edge );
      }
    }

    return this.spawn( elements, { unique: true } );
  };
}

util.extend(elesfn, {
  connectedEdges: function( selector ){
    var retEles = [];

    var eles = this;
    for( var i = 0; i < eles.length; i++ ){
      var node = eles[i];
      if( !node.isNode() ){ continue; }

      var edges = node._private.edges;

      for( var j = 0; j < edges.length; j++ ){
        var edge = edges[j];
        retEles.push( edge );
      }
    }

    return this.spawn( retEles, { unique: true } ).filter( selector );
  },

  connectedNodes: function( selector ){
    var retEles = [];

    var eles = this;
    for( var i = 0; i < eles.length; i++ ){
      var edge = eles[i];
      if( !edge.isEdge() ){ continue; }

      retEles.push( edge.source()[0] );
      retEles.push( edge.target()[0] );
    }

    return this.spawn( retEles, { unique: true } ).filter( selector );
  },

  parallelEdges: defineParallelEdgesFunction(),

  codirectedEdges: defineParallelEdgesFunction({
    codirected: true
  })
});

function defineParallelEdgesFunction(params){
  var defaults = {
    codirected: false
  };
  params = util.extend({}, defaults, params);

  return function( selector ){
    var elements = [];
    var edges = this.edges();
    var p = params;

    // look at all the edges in the collection
    for( var i = 0; i < edges.length; i++ ){
      var edge1 = edges[i];
      var src1 = edge1.source()[0];
      var srcid1 = src1.id();
      var tgt1 = edge1.target()[0];
      var tgtid1 = tgt1.id();
      var srcEdges1 = src1._private.edges;

      // look at edges connected to the src node of this edge
      for( var j = 0; j < srcEdges1.length; j++ ){
        var edge2 = srcEdges1[j];
        var edge2data = edge2._private.data;
        var tgtid2 = edge2data.target;
        var srcid2 = edge2data.source;

        var codirected = tgtid2 === tgtid1 && srcid2 === srcid1;
        var oppdirected = srcid1 === tgtid2 && tgtid1 === srcid2;

        if( (p.codirected && codirected) || (!p.codirected && (codirected || oppdirected)) ){
          elements.push( edge2 );
        }
      }
    }

    return this.spawn( elements, { unique: true } ).filter( selector );
  };

}

// Misc functions
/////////////////

util.extend(elesfn, {
  components: function(){
    var cy = this.cy();
    var visited = cy.collection();
    var unvisited = this.nodes();
    var components = [];

    var visitInComponent = function( node, component ){
      visited.merge( node );
      unvisited.unmerge( node );
      component.merge( node );
    };

    do {
      var component = cy.collection();
      components.push( component );

      var root = unvisited[0];
      visitInComponent( root, component );

      this.bfs({
        directed: false,
        roots: root,
        visit: function( i, depth, v, e, u ){
          visitInComponent( v, component );
        }
      });

    } while( unvisited.length > 0 );

    return components.map(function( component ){
      return component.closedNeighborhood(); // add the edges
    });
  }
});

module.exports = elesfn;

},{"../is":77,"../util":94}],29:[function(_dereq_,module,exports){
'use strict';

var zIndexSort = function( a, b ){
  var cy = a.cy();
  var a_p = a._private;
  var b_p = b._private;
  var zDiff = a_p.style['z-index'].value - b_p.style['z-index'].value;
  var depthA = 0;
  var depthB = 0;
  var hasCompoundNodes = cy.hasCompoundNodes();
  var aIsNode = a_p.group === 'nodes';
  var aIsEdge = a_p.group === 'edges';
  var bIsNode = b_p.group === 'nodes';
  var bIsEdge = b_p.group === 'edges';

  // no need to calculate element depth if there is no compound node
  if( hasCompoundNodes ){
    depthA = a.zDepth();
    depthB = b.zDepth();
  }

  var depthDiff = depthA - depthB;
  var sameDepth = depthDiff === 0;

  if( sameDepth ){

    if( aIsNode && bIsEdge ){
      return 1; // 'a' is a node, it should be drawn later

    } else if( aIsEdge && bIsNode ){
      return -1; // 'a' is an edge, it should be drawn first

    } else { // both nodes or both edges
      if( zDiff === 0 ){ // same z-index => compare indices in the core (order added to graph w/ last on top)
        return a_p.index - b_p.index;
      } else {
        return zDiff;
      }
    }

  // elements on different level
  } else {
    return depthDiff; // deeper element should be drawn later
  }

};

module.exports = zIndexSort;

},{}],30:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');
var util = _dereq_('../util');
var Collection = _dereq_('../collection');
var Element = _dereq_('../collection/element');
var window = _dereq_('../window');
var document = window ? window.document : null;
var NullRenderer = _dereq_('../extensions/renderer/null');

var corefn = {
  add: function(opts){

    var elements;
    var cy = this;

    // add the elements
    if( is.elementOrCollection(opts) ){
      var eles = opts;

      if( eles._private.cy === cy ){ // same instance => just restore
        elements = eles.restore();

      } else { // otherwise, copy from json
        var jsons = [];

        for( var i = 0; i < eles.length; i++ ){
          var ele = eles[i];
          jsons.push( ele.json() );
        }

        elements = new Collection( cy, jsons );
      }
    }

    // specify an array of options
    else if( is.array(opts) ){
      var jsons = opts;

      elements = new Collection(cy, jsons);
    }

    // specify via opts.nodes and opts.edges
    else if( is.plainObject(opts) && (is.array(opts.nodes) || is.array(opts.edges)) ){
      var elesByGroup = opts;
      var jsons = [];

      var grs = ['nodes', 'edges'];
      for( var i = 0, il = grs.length; i < il; i++ ){
        var group = grs[i];
        var elesArray = elesByGroup[group];

        if( is.array(elesArray) ){

          for( var j = 0, jl = elesArray.length; j < jl; j++ ){
            var json = util.extend( { group: group }, elesArray[j] );

            jsons.push( json );
          }
        }
      }

      elements = new Collection(cy, jsons);
    }

    // specify options for one element
    else {
      var json = opts;
      elements = (new Element( cy, json )).collection();
    }

    return elements;
  },

  remove: function(collection){
    if( is.elementOrCollection(collection) ){
      collection = collection;
    } else if( is.string(collection) ){
      var selector = collection;
      collection = this.$( selector );
    }

    return collection.remove();
  },

  load: function(elements, onload, ondone){
    var cy = this;

    cy.notifications(false);

    // remove old elements
    var oldEles = cy.elements();
    if( oldEles.length > 0 ){
      oldEles.remove();
    }

    if( elements != null ){
      if( is.plainObject(elements) || is.array(elements) ){
        cy.add( elements );
      }
    }

    cy.one('layoutready', function(e){
      cy.notifications(true);
      cy.trigger(e); // we missed this event by turning notifications off, so pass it on

      cy.notify({
        type: 'load',
        collection: cy.elements()
      });

      cy.one('load', onload);
      cy.trigger('load');
    }).one('layoutstop', function(){
      cy.one('done', ondone);
      cy.trigger('done');
    });

    var layoutOpts = util.extend({}, cy._private.options.layout);
    layoutOpts.eles = cy.$();

    cy.layout( layoutOpts );

    return this;
  }
};

module.exports = corefn;

},{"../collection":23,"../collection/element":19,"../extensions/renderer/null":73,"../is":77,"../util":94,"../window":100}],31:[function(_dereq_,module,exports){
'use strict';

var define = _dereq_('../define');
var util = _dereq_('../util');
var is = _dereq_('../is');

var corefn = ({

  // pull in animation functions
  animate: define.animate(),
  animation: define.animation(),
  animated: define.animated(),
  clearQueue: define.clearQueue(),
  delay: define.delay(),
  delayAnimation: define.delayAnimation(),
  stop: define.stop(),

  addToAnimationPool: function( eles ){
    var cy = this;

    if( !cy.styleEnabled() ){ return; } // save cycles when no style used

    cy._private.aniEles.merge( eles );
  },

  stopAnimationLoop: function(){
    this._private.animationsRunning = false;
  },

  startAnimationLoop: function(){
    var cy = this;

    cy._private.animationsRunning = true;

    if( !cy.styleEnabled() ){ return; } // save cycles when no style used

    // NB the animation loop will exec in headless environments if style enabled
    // and explicit cy.destroy() is necessary to stop the loop

    function globalAnimationStep(){
      if( !cy._private.animationsRunning ){ return; }

      util.requestAnimationFrame(function(now){
        handleElements(now);
        globalAnimationStep();
      });
    }

    globalAnimationStep(); // first call

    function handleElements( now ){
      var eles = cy._private.aniEles;
      var doneEles = [];

      function handleElement( ele, isCore ){
        var _p = ele._private;
        var current = _p.animation.current;
        var queue = _p.animation.queue;
        var ranAnis = false;

        // if nothing currently animating, get something from the queue
        if( current.length === 0 ){
          var next = queue.shift();

          if( next ){
            current.push( next );
          }
        }

        var callbacks = function( callbacks ){
          for( var j = callbacks.length - 1; j >= 0; j-- ){
            var cb = callbacks[j];

            cb();
          }

          callbacks.splice( 0, callbacks.length );
        };

        // step and remove if done
        for( var i = current.length - 1; i >= 0; i-- ){
          var ani = current[i];
          var ani_p = ani._private;

          if( ani_p.stopped ){
            current.splice( i, 1 );

            ani_p.hooked = false;
            ani_p.playing = false;
            ani_p.started = false;

            callbacks( ani_p.frames );

            continue;
          }

          if( !ani_p.playing && !ani_p.applying ){ continue; }

          // an apply() while playing shouldn't do anything
          if( ani_p.playing && ani_p.applying ){
            ani_p.applying = false;
          }

          if( !ani_p.started ){
            startAnimation( ele, ani, now );
          }

          step( ele, ani, now, isCore );

          if( ani_p.applying ){
            ani_p.applying = false;
          }

          callbacks( ani_p.frames );

          if( ani.completed() ){
            current.splice(i, 1);

            ani_p.hooked = false;
            ani_p.playing = false;
            ani_p.started = false;

            callbacks( ani_p.completes );
          }

          ranAnis = true;
        }

        if( !isCore && current.length === 0 && queue.length === 0 ){
          doneEles.push( ele );
        }

        return ranAnis;
      } // handleElement

      // handle all eles
      var ranEleAni = false;
      for( var e = 0; e < eles.length; e++ ){
        var ele = eles[e];
        var handledThisEle = handleElement( ele );

        ranEleAni = ranEleAni || handledThisEle;
      } // each element

      var ranCoreAni = handleElement( cy, true );

      // notify renderer
      if( ranEleAni || ranCoreAni ){
        var toNotify;

        if( eles.length > 0 ){
          var updatedEles = eles.updateCompoundBounds();
          toNotify = updatedEles.length > 0 ? eles.add( updatedEles ) : eles;
        }

        cy.notify({
          type: 'draw',
          collection: toNotify
        });
      }

      // remove elements from list of currently animating if its queues are empty
      eles.unmerge( doneEles );

    } // handleElements

    function startAnimation( self, ani, now ){
      var isCore = is.core( self );
      var isEles = !isCore;
      var ele = self;
      var style = cy._private.style;
      var ani_p = ani._private;

      if( isEles ){
        var pos = ele._private.position;

        ani_p.startPosition = ani_p.startPosition || {
          x: pos.x,
          y: pos.y
        };

        ani_p.startStyle = ani_p.startStyle || style.getValueStyle( ele );
      }

      if( isCore ){
        var pan = cy._private.pan;

        ani_p.startPan = ani_p.startPan || {
          x: pan.x,
          y: pan.y
        };

        ani_p.startZoom = ani_p.startZoom != null ? ani_p.startZoom : cy._private.zoom;
      }

      ani_p.started = true;
      ani_p.startTime = now - ani_p.progress * ani_p.duration;
    }

    function step( self, ani, now, isCore ){
      var style = cy._private.style;
      var isEles = !isCore;
      var _p = self._private;
      var ani_p = ani._private;
      var pEasing = ani_p.easing;
      var startTime = ani_p.startTime;

      if( !ani_p.easingImpl ){

        if( pEasing == null ){ // use default
          ani_p.easingImpl = easings['linear'];

        } else { // then define w/ name
          var easingVals;

          if( is.string( pEasing ) ){
            var easingProp = style.parse('transition-timing-function', pEasing);

            easingVals = easingProp.value;

          } else { // then assume preparsed array
            easingVals = pEasing;
          }

          var name, args;

          if( is.string( easingVals ) ){
            name = easingVals;
            args = [];
          } else {
            name = easingVals[1];
            args = easingVals.slice(2).map(function(n){ return +n; });
          }

          if( args.length > 0 ){ // create with args
            if( name === 'spring' ){
              args.push( ani_p.duration ); // need duration to generate spring
            }

            ani_p.easingImpl = easings[ name ].apply( null, args );
          } else { // static impl by name
            ani_p.easingImpl = easings[ name ];
          }
        }

      }

      var easing = ani_p.easingImpl;
      var percent;

      if( ani_p.duration === 0 ){
        percent = 1;
      } else {
        percent = (now - startTime) / ani_p.duration;
      }

      if( ani_p.applying ){
        percent = ani_p.progress;
      }

      if( percent < 0 ){
        percent = 0;
      } else if( percent > 1 ){
        percent = 1;
      }

      if( ani_p.delay == null ){ // then update

        var startPos = ani_p.startPosition;
        var endPos = ani_p.position;
        var pos = _p.position;
        if( endPos && isEles ){
          if( valid( startPos.x, endPos.x ) ){
            pos.x = ease( startPos.x, endPos.x, percent, easing );
          }

          if( valid( startPos.y, endPos.y ) ){
            pos.y = ease( startPos.y, endPos.y, percent, easing );
          }
        }

        var startPan = ani_p.startPan;
        var endPan = ani_p.pan;
        var pan = _p.pan;
        var animatingPan = endPan != null && isCore;
        if( animatingPan ){
          if( valid( startPan.x, endPan.x ) ){
            pan.x = ease( startPan.x, endPan.x, percent, easing );
          }

          if( valid( startPan.y, endPan.y ) ){
            pan.y = ease( startPan.y, endPan.y, percent, easing );
          }

          self.trigger('pan');
        }

        var startZoom = ani_p.startZoom;
        var endZoom = ani_p.zoom;
        var animatingZoom = endZoom != null && isCore;
        if( animatingZoom ){
          if( valid( startZoom, endZoom ) ){
            _p.zoom = ease( startZoom, endZoom, percent, easing );
          }

          self.trigger('zoom');
        }

        if( animatingPan || animatingZoom ){
          self.trigger('viewport');
        }

        var props = ani_p.style;
        if( props && isEles ){

          for( var i = 0; i < props.length; i++ ){
            var prop = props[i];
            var name = prop.name;
            var end = prop;

            var start = ani_p.startStyle[ name ];
            var easedVal = ease( start, end, percent, easing );

            style.overrideBypass( self, name, easedVal );
          } // for props

        } // if

      }

      if( is.fn(ani_p.step) ){
        ani_p.step.apply( self, [ now ] );
      }

      ani_p.progress = percent;

      return percent;
    }

    function valid(start, end){
      if( start == null || end == null ){
        return false;
      }

      if( is.number(start) && is.number(end) ){
        return true;
      } else if( (start) && (end) ){
        return true;
      }

      return false;
    }

    // assumes p0 = 0, p3 = 1
    function evalCubicBezier( p1, p2, t ){
      var one_t = 1 - t;
      var tsq = t*t;

      return ( 3 * one_t * one_t * t * p1 ) + ( 3 * one_t * tsq * p2 ) + tsq * t;
    }

    function cubicBezier( p1, p2 ){
      return function( start, end, percent ){
        return start + (end - start) * evalCubicBezier( p1, p2, percent );
      };
    }

    /* Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */
    /* 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
       then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */
    var generateSpringRK4 = (function () {
        function springAccelerationForState (state) {
            return (-state.tension * state.x) - (state.friction * state.v);
        }

        function springEvaluateStateWithDerivative (initialState, dt, derivative) {
            var state = {
                x: initialState.x + derivative.dx * dt,
                v: initialState.v + derivative.dv * dt,
                tension: initialState.tension,
                friction: initialState.friction
            };

            return { dx: state.v, dv: springAccelerationForState(state) };
        }

        function springIntegrateState (state, dt) {
            var a = {
                    dx: state.v,
                    dv: springAccelerationForState(state)
                },
                b = springEvaluateStateWithDerivative(state, dt * 0.5, a),
                c = springEvaluateStateWithDerivative(state, dt * 0.5, b),
                d = springEvaluateStateWithDerivative(state, dt, c),
                dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx),
                dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv);

            state.x = state.x + dxdt * dt;
            state.v = state.v + dvdt * dt;

            return state;
        }

        return function springRK4Factory (tension, friction, duration) {

            var initState = {
                    x: -1,
                    v: 0,
                    tension: null,
                    friction: null
                },
                path = [0],
                time_lapsed = 0,
                tolerance = 1 / 10000,
                DT = 16 / 1000,
                have_duration, dt, last_state;

            tension = parseFloat(tension) || 500;
            friction = parseFloat(friction) || 20;
            duration = duration || null;

            initState.tension = tension;
            initState.friction = friction;

            have_duration = duration !== null;

            /* Calculate the actual time it takes for this animation to complete with the provided conditions. */
            if (have_duration) {
                /* Run the simulation without a duration. */
                time_lapsed = springRK4Factory(tension, friction);
                /* Compute the adjusted time delta. */
                dt = time_lapsed / duration * DT;
            } else {
                dt = DT;
            }

            while (true) {
                /* Next/step function .*/
                last_state = springIntegrateState(last_state || initState, dt);
                /* Store the position. */
                path.push(1 + last_state.x);
                time_lapsed += 16;
                /* If the change threshold is reached, break. */
                if (!(Math.abs(last_state.x) > tolerance && Math.abs(last_state.v) > tolerance)) {
                    break;
                }
            }

            /* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the
               computed path and returns a snapshot of the position according to a given percentComplete. */
            return !have_duration ? time_lapsed : function(percentComplete) { return path[ (percentComplete * (path.length - 1)) | 0 ]; };
        };
    }());

    var easings = {
      'linear': function( start, end, percent ){
        return start + (end - start) * percent;
      },

      // default easings
      'ease': cubicBezier( 0.25, 0.1, 0.25, 1 ),
      'ease-in': cubicBezier( 0.42, 0, 1, 1 ),
      'ease-out': cubicBezier( 0, 0, 0.58, 1 ),
      'ease-in-out': cubicBezier( 0.42, 0, 0.58, 1 ),

      // sine
      'ease-in-sine': cubicBezier( 0.47, 0, 0.745, 0.715 ),
      'ease-out-sine': cubicBezier( 0.39, 0.575, 0.565, 1 ),
      'ease-in-out-sine': cubicBezier( 0.445, 0.05, 0.55, 0.95 ),

      // quad
      'ease-in-quad': cubicBezier( 0.55, 0.085, 0.68, 0.53 ),
      'ease-out-quad': cubicBezier( 0.25, 0.46, 0.45, 0.94 ),
      'ease-in-out-quad': cubicBezier( 0.455, 0.03, 0.515, 0.955 ),

      // cubic
      'ease-in-cubic': cubicBezier( 0.55, 0.055, 0.675, 0.19 ),
      'ease-out-cubic': cubicBezier( 0.215, 0.61, 0.355, 1 ),
      'ease-in-out-cubic': cubicBezier( 0.645, 0.045, 0.355, 1 ),

      // quart
      'ease-in-quart': cubicBezier( 0.895, 0.03, 0.685, 0.22 ),
      'ease-out-quart': cubicBezier( 0.165, 0.84, 0.44, 1 ),
      'ease-in-out-quart': cubicBezier( 0.77, 0, 0.175, 1 ),

      // quint
      'ease-in-quint': cubicBezier( 0.755, 0.05, 0.855, 0.06 ),
      'ease-out-quint': cubicBezier( 0.23, 1, 0.32, 1 ),
      'ease-in-out-quint': cubicBezier( 0.86, 0, 0.07, 1 ),

      // expo
      'ease-in-expo': cubicBezier( 0.95, 0.05, 0.795, 0.035 ),
      'ease-out-expo': cubicBezier( 0.19, 1, 0.22, 1 ),
      'ease-in-out-expo': cubicBezier( 1, 0, 0, 1 ),

      // circ
      'ease-in-circ': cubicBezier( 0.6, 0.04, 0.98, 0.335 ),
      'ease-out-circ': cubicBezier( 0.075, 0.82, 0.165, 1 ),
      'ease-in-out-circ': cubicBezier( 0.785, 0.135, 0.15, 0.86 ),


      // user param easings...

      'spring': function( tension, friction, duration ){
        var spring = generateSpringRK4( tension, friction, duration );

        return function( start, end, percent ){
          return start + (end - start) * spring( percent );
        };
      },

      'cubic-bezier': function( x1, y1, x2, y2 ){
        return cubicBezier( x1, y1, x2, y2 );
      }
    };

    function ease( startProp, endProp, percent, easingFn ){
      if( percent < 0 ){
        percent = 0;
      } else if( percent > 1 ){
        percent = 1;
      }

      var start, end;

      if( startProp.pfValue != null || startProp.value != null ){
        start = startProp.pfValue != null ? startProp.pfValue : startProp.value;
      } else {
        start = startProp;
      }

      if( endProp.pfValue != null || endProp.value != null ){
        end = endProp.pfValue != null ? endProp.pfValue : endProp.value;
      } else {
        end = endProp;
      }

      if( is.number(start) && is.number(end) ){
        return easingFn( start, end, percent );

      } else if( is.array(start) && is.array(end) ){
        var easedArr = [];

        for( var i = 0; i < end.length; i++ ){
          var si = start[i];
          var ei = end[i];

          if( si != null && ei != null ){
            var val = easingFn(si, ei, percent);

            if( startProp.roundValue ){ val = Math.round( val ); }

            easedArr.push( val );
          } else {
            easedArr.push( ei );
          }
        }

        return easedArr;
      }

      return undefined;
    }

  }

});

module.exports = corefn;

},{"../define":41,"../is":77,"../util":94}],32:[function(_dereq_,module,exports){
'use strict';

var define = _dereq_('../define');

var corefn = ({
  on: define.on(), // .on( events [, selector] [, data], handler)
  one: define.on({ unbindSelfOnTrigger: true }),
  once: define.on({ unbindAllBindersOnTrigger: true }),
  off: define.off(), // .off( events [, selector] [, handler] )
  trigger: define.trigger() // .trigger( events [, extraParams] )
});

define.eventAliasesOn( corefn );

module.exports = corefn;

},{"../define":41}],33:[function(_dereq_,module,exports){
'use strict';

var corefn = ({

  png: function( options ){
    var renderer = this._private.renderer;
    options = options || {};

    return renderer.png( options );
  },

  jpg: function( options ){
    var renderer = this._private.renderer;
    options = options || {};

    options.bg = options.bg || '#fff';

    return renderer.jpg( options );
  }

});

corefn.jpeg = corefn.jpg;

module.exports = corefn;

},{}],34:[function(_dereq_,module,exports){
'use strict';

var window = _dereq_('../window');
var util = _dereq_('../util');
var Collection = _dereq_('../collection');
var is = _dereq_('../is');
var Promise = _dereq_('../promise');
var define = _dereq_('../define');

var Core = function( opts ){
  if( !(this instanceof Core) ){
    return new Core(opts);
  }
  var cy = this;

  opts = util.extend({}, opts);

  var container = opts.container;

  // allow for passing a wrapped jquery object
  // e.g. cytoscape({ container: $('#cy') })
  if( container && !is.htmlElement( container ) && is.htmlElement( container[0] ) ){
    container = container[0];
  }

  var reg = container ? container._cyreg : null; // e.g. already registered some info (e.g. readies) via jquery
  reg = reg || {};

  if( reg && reg.cy ){
    reg.cy.destroy();

    reg = {}; // old instance => replace reg completely
  }

  var readies = reg.readies = reg.readies || [];

  if( container ){ container._cyreg = reg; } // make sure container assoc'd reg points to this cy
  reg.cy = cy;

  var head = window !== undefined && container !== undefined && !opts.headless;
  var options = opts;
  options.layout = util.extend( { name: head ? 'grid' : 'null' }, options.layout );
  options.renderer = util.extend( { name: head ? 'canvas' : 'null' }, options.renderer );

  var defVal = function( def, val, altVal ){
    if( val !== undefined ){
      return val;
    } else if( altVal !== undefined ){
      return altVal;
    } else {
      return def;
    }
  };

  var _p = this._private = {
    container: container, // html dom ele container
    ready: false, // whether ready has been triggered
    initrender: false, // has initrender has been triggered
    options: options, // cached options
    elements: [], // array of elements
    id2index: {}, // element id => index in elements array
    listeners: [], // list of listeners
    onRenders: [], // rendering listeners
    aniEles: Collection(this), // elements being animated
    scratch: {}, // scratch object for core
    layout: null,
    renderer: null,
    notificationsEnabled: true, // whether notifications are sent to the renderer
    minZoom: 1e-50,
    maxZoom: 1e50,
    zoomingEnabled: defVal(true, options.zoomingEnabled),
    userZoomingEnabled: defVal(true, options.userZoomingEnabled),
    panningEnabled: defVal(true, options.panningEnabled),
    userPanningEnabled: defVal(true, options.userPanningEnabled),
    boxSelectionEnabled: defVal(true, options.boxSelectionEnabled),
    autolock: defVal(false, options.autolock, options.autolockNodes),
    autoungrabify: defVal(false, options.autoungrabify, options.autoungrabifyNodes),
    autounselectify: defVal(false, options.autounselectify),
    styleEnabled: options.styleEnabled === undefined ? head : options.styleEnabled,
    zoom: is.number(options.zoom) ? options.zoom : 1,
    pan: {
      x: is.plainObject(options.pan) && is.number(options.pan.x) ? options.pan.x : 0,
      y: is.plainObject(options.pan) && is.number(options.pan.y) ? options.pan.y : 0
    },
    animation: { // object for currently-running animations
      current: [],
      queue: []
    },
    hasCompoundNodes: false,
    deferredExecQueue: []
  };

  // set selection type
  var selType = options.selectionType;
  if( selType === undefined || (selType !== 'additive' && selType !== 'single') ){
    // then set default

    _p.selectionType = 'single';
  } else {
    _p.selectionType = selType;
  }

  // init zoom bounds
  if( is.number(options.minZoom) && is.number(options.maxZoom) && options.minZoom < options.maxZoom ){
    _p.minZoom = options.minZoom;
    _p.maxZoom = options.maxZoom;
  } else if( is.number(options.minZoom) && options.maxZoom === undefined ){
    _p.minZoom = options.minZoom;
  } else if( is.number(options.maxZoom) && options.minZoom === undefined ){
    _p.maxZoom = options.maxZoom;
  }

  var loadExtData = function( next ){
    var anyIsPromise = false;

    for( var i = 0; i < extData.length; i++ ){
      var datum = extData[i];

      if( is.promise(datum) ){
        anyIsPromise = true;
        break;
      }
    }

    if( anyIsPromise ){
      return Promise.all( extData ).then( next ); // load all data asynchronously, then exec rest of init
    } else {
      next( extData ); // exec synchronously for convenience
    }
  };

  // create the renderer
  cy.initRenderer( util.extend({
    hideEdgesOnViewport: options.hideEdgesOnViewport,
    hideLabelsOnViewport: options.hideLabelsOnViewport,
    textureOnViewport: options.textureOnViewport,
    wheelSensitivity: is.number(options.wheelSensitivity) && options.wheelSensitivity > 0 ? options.wheelSensitivity : 1,
    motionBlur: options.motionBlur === undefined ? true : options.motionBlur, // on by default
    motionBlurOpacity: options.motionBlurOpacity === undefined ? 0.05 : options.motionBlurOpacity,
    pixelRatio: is.number(options.pixelRatio) && options.pixelRatio > 0 ? options.pixelRatio : undefined,
    desktopTapThreshold: options.desktopTapThreshold === undefined ? 4 : options.desktopTapThreshold,
    touchTapThreshold: options.touchTapThreshold === undefined ? 8 : options.touchTapThreshold
  }, options.renderer) );

  var extData = [ options.style, options.elements ];
  loadExtData(function( thens ){
    var initStyle = thens[0];
    var initEles = thens[1];

    // init style
    if( _p.styleEnabled ){
      cy.setStyle( initStyle );
    }

    // trigger the passed function for the `initrender` event
    if( options.initrender ){
      cy.on('initrender', options.initrender);
      cy.on('initrender', function(){
        _p.initrender = true;
      });
    }

    // initial load
    cy.load(initEles, function(){ // onready
      cy.startAnimationLoop();
      _p.ready = true;

      // if a ready callback is specified as an option, the bind it
      if( is.fn( options.ready ) ){
        cy.on('ready', options.ready);
      }

      // bind all the ready handlers registered before creating this instance
      for( var i = 0; i < readies.length; i++ ){
        var fn = readies[i];
        cy.on('ready', fn);
      }
      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

      cy.trigger('ready');
    }, options.done);

  });
};

var corefn = Core.prototype; // short alias

util.extend(corefn, {
  instanceString: function(){
    return 'core';
  },

  isReady: function(){
    return this._private.ready;
  },

  ready: function( fn ){
    if( this.isReady() ){
      this.trigger('ready', [], fn); // just calls fn as though triggered via ready event
    } else {
      this.on('ready', fn);
    }

    return this;
  },

  initrender: function(){
    return this._private.initrender;
  },

  destroy: function(){
    var cy = this;

    cy.stopAnimationLoop();

    cy.notify({ type: 'destroy' }); // destroy the renderer

    var domEle = cy.container();
    if( domEle ){
      domEle._cyreg = null;

      while( domEle.childNodes.length > 0 ){
        domEle.removeChild( domEle.childNodes[0] );
      }
    }

    return cy;
  },

  getElementById: function( id ){
    var index = this._private.id2index[ id ];
    if( index !== undefined ){
      return this._private.elements[ index ];
    }

    // worst case, return an empty collection
    return Collection( this );
  },

  selectionType: function(){
    return this._private.selectionType;
  },

  hasCompoundNodes: function(){
    return this._private.hasCompoundNodes;
  },

  styleEnabled: function(){
    return this._private.styleEnabled;
  },

  addToPool: function( eles ){
    var elements = this._private.elements;
    var id2index = this._private.id2index;

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];

      var id = ele._private.data.id;
      var index = id2index[ id ];
      var alreadyInPool = index !== undefined;

      if( !alreadyInPool ){
        index = elements.length;
        elements.push( ele );
        id2index[ id ] = index;
        ele._private.index = index;
      }
    }

    return this; // chaining
  },

  removeFromPool: function( eles ){
    var elements = this._private.elements;
    var id2index = this._private.id2index;

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];

      var id = ele._private.data.id;
      var index = id2index[ id ];
      var inPool = index !== undefined;

      if( inPool ){
        this._private.id2index[ id ] = undefined;
        elements.splice(index, 1);

        // adjust the index of all elements past this index
        for( var j = index; j < elements.length; j++ ){
          var jid = elements[j]._private.data.id;
          id2index[ jid ]--;
          elements[j]._private.index--;
        }
      }
    }
  },

  container: function(){
    return this._private.container;
  },

  options: function(){
    return util.copy( this._private.options );
  },

  json: function( obj ){
    var cy = this;
    var _p = cy._private;

    if( is.plainObject(obj) ){ // set

      cy.startBatch();

      if( obj.elements ){
        var idInJson = {};

        var updateEles = function( jsons, gr ){
          for( var i = 0; i < jsons.length; i++ ){
            var json = jsons[i];
            var id = json.data.id;
            var ele = cy.getElementById( id );

            idInJson[ id ] = true;

            if( ele.length !== 0 ){ // existing element should be updated
              ele.json( json );
            } else { // otherwise should be added
              if( gr ){
                cy.add( util.extend({ group: gr }, json) );
              } else {
                cy.add( json );
              }
            }
          }
        };

        if( is.array(obj.elements) ){ // elements: []
          updateEles( obj.elements );

        } else { // elements: { nodes: [], edges: [] }
          var grs = ['nodes', 'edges'];
          for( var i = 0; i < grs.length; i++ ){
            var gr = grs[i];
            var elements = obj.elements[ gr ];

            if( is.array(elements) ){
              updateEles( elements, gr );
            }
          }
        }

        // elements not specified in json should be removed
        cy.elements().stdFilter(function( ele ){
          return !idInJson[ ele.id() ];
        }).remove();
      }

      if( obj.style ){
        cy.style( obj.style );
      }

      if( obj.zoom != null && obj.zoom !== _p.zoom ){
        cy.zoom( obj.zoom );
      }

      if( obj.pan ){
        if( obj.pan.x !== _p.pan.x || obj.pan.y !== _p.pan.y ){
          cy.pan( obj.pan );
        }
      }

      var fields = [
        'minZoom', 'maxZoom', 'zoomingEnabled', 'userZoomingEnabled',
        'panningEnabled', 'userPanningEnabled',
        'boxSelectionEnabled',
        'autolock', 'autoungrabify', 'autounselectify'
      ];

      for( var i = 0; i < fields.length; i++ ){
        var f = fields[i];

        if( obj[f] != null ){
          cy[f]( obj[f] );
        }
      }

      cy.endBatch();

      return this; // chaining
    } else if( obj === undefined ){ // get
      var json = {};

      json.elements = {};
      cy.elements().each(function(i, ele){
        var group = ele.group();

        if( !json.elements[group] ){
          json.elements[group] = [];
        }

        json.elements[group].push( ele.json() );
      });

      if( this._private.styleEnabled ){
        json.style = cy.style().json();
      }

      json.zoomingEnabled = cy._private.zoomingEnabled;
      json.userZoomingEnabled = cy._private.userZoomingEnabled;
      json.zoom = cy._private.zoom;
      json.minZoom = cy._private.minZoom;
      json.maxZoom = cy._private.maxZoom;
      json.panningEnabled = cy._private.panningEnabled;
      json.userPanningEnabled = cy._private.userPanningEnabled;
      json.pan = util.copy( cy._private.pan );
      json.boxSelectionEnabled = cy._private.boxSelectionEnabled;
      json.renderer = util.copy( cy._private.options.renderer );
      json.hideEdgesOnViewport = cy._private.options.hideEdgesOnViewport;
      json.hideLabelsOnViewport = cy._private.options.hideLabelsOnViewport;
      json.textureOnViewport = cy._private.options.textureOnViewport;
      json.wheelSensitivity = cy._private.options.wheelSensitivity;
      json.motionBlur = cy._private.options.motionBlur;

      return json;
    }
  },

  scratch: define.data({
    field: 'scratch',
    bindingEvent: 'scratch',
    allowBinding: true,
    allowSetting: true,
    settingEvent: 'scratch',
    settingTriggersEvent: true,
    triggerFnName: 'trigger',
    allowGetting: true
  }),

  removeScratch: define.removeData({
    field: 'scratch',
    event: 'scratch',
    triggerFnName: 'trigger',
    triggerEvent: true
  })

});

[
  _dereq_('./add-remove'),
  _dereq_('./animation'),
  _dereq_('./events'),
  _dereq_('./export'),
  _dereq_('./layout'),
  _dereq_('./notification'),
  _dereq_('./renderer'),
  _dereq_('./search'),
  _dereq_('./style'),
  _dereq_('./viewport')
].forEach(function( props ){
  util.extend( corefn, props );
});

module.exports = Core;

},{"../collection":23,"../define":41,"../is":77,"../promise":80,"../util":94,"../window":100,"./add-remove":30,"./animation":31,"./events":32,"./export":33,"./layout":35,"./notification":36,"./renderer":37,"./search":38,"./style":39,"./viewport":40}],35:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');
var is = _dereq_('../is');

var corefn = ({

  layout: function( params ){
    var layout = this._private.prevLayout = ( params == null ? this._private.prevLayout : this.makeLayout( params ) );

    layout.run();

    return this; // chaining
  },

  makeLayout: function( options ){
    var cy = this;

    if( options == null ){
      util.error('Layout options must be specified to make a layout');
      return;
    }

    if( options.name == null ){
      util.error('A `name` must be specified to make a layout');
      return;
    }

    var name = options.name;
    var Layout = cy.extension('layout', name);

    if( Layout == null ){
      util.error('Can not apply layout: No such layout `' + name + '` found; did you include its JS file?');
      return;
    }

    var eles;
    if( is.string( options.eles ) ){
      eles = cy.$( options.eles );
    } else {
      eles = options.eles != null ? options.eles : cy.$();
    }

    var layout = new Layout( util.extend({}, options, {
      cy: cy,
      eles: eles
    }) );

    return layout;
  }

});

corefn.createLayout = corefn.makeLayout;

module.exports = corefn;

},{"../is":77,"../util":94}],36:[function(_dereq_,module,exports){
'use strict';

var corefn = ({
  notify: function( params ){
    var _p = this._private;

    if( _p.batchingNotify ){
      var bEles = _p.batchNotifyEles;
      var bTypes = _p.batchNotifyTypes;

      if( params.collection ){
        bEles.merge( params.collection );
      }

      if( !bTypes.ids[ params.type ] ){
        bTypes.push( params.type );
      }

      return; // notifications are disabled during batching
    }

    if( !_p.notificationsEnabled ){ return; } // exit on disabled

    var renderer = this.renderer();

    renderer.notify(params);
  },

  notifications: function( bool ){
    var p = this._private;

    if( bool === undefined ){
      return p.notificationsEnabled;
    } else {
      p.notificationsEnabled = bool ? true : false;
    }
  },

  noNotifications: function( callback ){
    this.notifications(false);
    callback();
    this.notifications(true);
  },

  startBatch: function(){
    var _p = this._private;

    if( _p.batchCount == null ){
      _p.batchCount = 0;
    }

    if( _p.batchCount === 0 ){
      _p.batchingStyle = _p.batchingNotify = true;
      _p.batchStyleEles = this.collection();
      _p.batchNotifyEles = this.collection();
      _p.batchNotifyTypes = [];

      _p.batchNotifyTypes.ids = {};
    }

    _p.batchCount++;

    return this;
  },

  endBatch: function(){
    var _p = this._private;

    _p.batchCount--;

    if( _p.batchCount === 0 ){
      // update style for dirty eles
      _p.batchingStyle = false;
      _p.batchStyleEles.updateStyle();

      // notify the renderer of queued eles and event types
      _p.batchingNotify = false;
      this.notify({
        type: _p.batchNotifyTypes,
        collection: _p.batchNotifyEles
      });
    }

    return this;
  },

  batch: function( callback ){
    this.startBatch();
    callback();
    this.endBatch();

    return this;
  },

  // for backwards compatibility
  batchData: function( map ){
    var cy = this;

    return this.batch(function(){
      for( var id in map ){
        var data = map[id];
        var ele = cy.getElementById( id );

        ele.data( data );
      }
    });
  }
});

module.exports = corefn;

},{}],37:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');

var corefn = ({

  renderTo: function( context, zoom, pan, pxRatio ){
    var r = this._private.renderer;

    r.renderTo( context, zoom, pan, pxRatio );
    return this;
  },

  renderer: function(){
    return this._private.renderer;
  },

  forceRender: function(){
    this.notify({
      type: 'draw'
    });

    return this;
  },

  resize: function(){
    this.notify({
      type: 'resize'
    });

    this.trigger('resize');

    return this;
  },

  initRenderer: function( options ){
    var cy = this;

    var RendererProto = cy.extension('renderer', options.name);
    if( RendererProto == null ){
      util.error('Can not initialise: No such renderer `%s` found; did you include its JS file?', options.name);
      return;
    }

    var rOpts = util.extend({}, options, {
      cy: cy
    });
    var renderer = cy._private.renderer = new RendererProto( rOpts );

    renderer.init( rOpts );

  },

  triggerOnRender: function(){
    var cbs = this._private.onRenders;

    for( var i = 0; i < cbs.length; i++ ){
      var cb = cbs[i];

      cb();
    }

    return this;
  },

  onRender: function( cb ){
    this._private.onRenders.push( cb );

    return this;
  },

  offRender: function( fn ){
    var cbs = this._private.onRenders;

    if( fn == null ){ // unbind all
      this._private.onRenders = [];
      return this;
    }

    for( var i = 0; i < cbs.length; i++ ){ // unbind specified
      var cb = cbs[i];

      if( fn === cb ){
        cbs.splice( i, 1 );
        break;
      }
    }

    return this;
  }

});

corefn.invalidateDimensions = corefn.resize;

module.exports = corefn;

},{"../util":94}],38:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');
var Collection = _dereq_('../collection');

var corefn = ({

  // get a collection
  // - empty collection on no args
  // - collection of elements in the graph on selector arg
  // - guarantee a returned collection when elements or collection specified
  collection: function( eles, opts ){

    if( is.string( eles ) ){
      return this.$( eles );

    } else if( is.elementOrCollection( eles ) ){
      return eles.collection();

    } else if( is.array( eles ) ){
      return Collection( this, eles, opts );
    }

    return Collection( this );
  },

  nodes: function( selector ){
    var nodes = this.$(function(){
      return this.isNode();
    });

    if( selector ){
      return nodes.filter( selector );
    }

    return nodes;
  },

  edges: function( selector ){
    var edges = this.$(function(){
      return this.isEdge();
    });

    if( selector ){
      return edges.filter( selector );
    }

    return edges;
  },

  // search the graph like jQuery
  $: function( selector ){
    var eles = new Collection( this, this._private.elements );

    if( selector ){
      return eles.filter( selector );
    }

    return eles;
  }

});

// aliases
corefn.elements = corefn.filter = corefn.$;

module.exports = corefn;

},{"../collection":23,"../is":77}],39:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');
var Style = _dereq_('../style');

var corefn = ({

  style: function( newStyle ){
    if( newStyle ){
      var s = this.setStyle( newStyle );

      s.update();
    }

    return this._private.style;
  },

  setStyle: function( style ){
    var _p = this._private;

    if( is.stylesheet(style) ){
      _p.style = style.generateStyle(this);

    } else if( is.array(style) ) {
      _p.style = Style.fromJson(this, style);

    } else if( is.string(style) ){
      _p.style = Style.fromString(this, style);

    } else {
      _p.style = Style( this );
    }

    return _p.style;
  }
});

module.exports = corefn;

},{"../is":77,"../style":86}],40:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');

var corefn = ({

  autolock: function(bool){
    if( bool !== undefined ){
      this._private.autolock = bool ? true : false;
    } else {
      return this._private.autolock;
    }

    return this; // chaining
  },

  autoungrabify: function(bool){
    if( bool !== undefined ){
      this._private.autoungrabify = bool ? true : false;
    } else {
      return this._private.autoungrabify;
    }

    return this; // chaining
  },

  autounselectify: function(bool){
    if( bool !== undefined ){
      this._private.autounselectify = bool ? true : false;
    } else {
      return this._private.autounselectify;
    }

    return this; // chaining
  },

  panningEnabled: function( bool ){
    if( bool !== undefined ){
      this._private.panningEnabled = bool ? true : false;
    } else {
      return this._private.panningEnabled;
    }

    return this; // chaining
  },

  userPanningEnabled: function( bool ){
    if( bool !== undefined ){
      this._private.userPanningEnabled = bool ? true : false;
    } else {
      return this._private.userPanningEnabled;
    }

    return this; // chaining
  },

  zoomingEnabled: function( bool ){
    if( bool !== undefined ){
      this._private.zoomingEnabled = bool ? true : false;
    } else {
      return this._private.zoomingEnabled;
    }

    return this; // chaining
  },

  userZoomingEnabled: function( bool ){
    if( bool !== undefined ){
      this._private.userZoomingEnabled = bool ? true : false;
    } else {
      return this._private.userZoomingEnabled;
    }

    return this; // chaining
  },

  boxSelectionEnabled: function( bool ){
    if( bool !== undefined ){
      this._private.boxSelectionEnabled = bool ? true : false;
    } else {
      return this._private.boxSelectionEnabled;
    }

    return this; // chaining
  },

  pan: function(){
    var args = arguments;
    var pan = this._private.pan;
    var dim, val, dims, x, y;

    switch( args.length ){
    case 0: // .pan()
      return pan;

    case 1:

      if( is.string( args[0] ) ){ // .pan('x')
        dim = args[0];
        return pan[ dim ];

      } else if( is.plainObject( args[0] ) ) { // .pan({ x: 0, y: 100 })
        if( !this._private.panningEnabled ){
          return this;
        }

        dims = args[0];
        x = dims.x;
        y = dims.y;

        if( is.number(x) ){
          pan.x = x;
        }

        if( is.number(y) ){
          pan.y = y;
        }

        this.trigger('pan viewport');
      }
      break;

    case 2: // .pan('x', 100)
      if( !this._private.panningEnabled ){
        return this;
      }

      dim = args[0];
      val = args[1];

      if( (dim === 'x' || dim === 'y') && is.number(val) ){
        pan[dim] = val;
      }

      this.trigger('pan viewport');
      break;

    default:
      break; // invalid
    }

    this.notify({ // notify the renderer that the viewport changed
      type: 'viewport'
    });

    return this; // chaining
  },

  panBy: function(params){
    var args = arguments;
    var pan = this._private.pan;
    var dim, val, dims, x, y;

    if( !this._private.panningEnabled ){
      return this;
    }

    switch( args.length ){
    case 1:

      if( is.plainObject( args[0] ) ) { // .panBy({ x: 0, y: 100 })
        dims = args[0];
        x = dims.x;
        y = dims.y;

        if( is.number(x) ){
          pan.x += x;
        }

        if( is.number(y) ){
          pan.y += y;
        }

        this.trigger('pan viewport');
      }
      break;

    case 2: // .panBy('x', 100)
      dim = args[0];
      val = args[1];

      if( (dim === 'x' || dim === 'y') && is.number(val) ){
        pan[dim] += val;
      }

      this.trigger('pan viewport');
      break;

    default:
      break; // invalid
    }

    this.notify({ // notify the renderer that the viewport changed
      type: 'viewport'
    });

    return this; // chaining
  },

  fit: function( elements, padding ){
    var viewportState = this.getFitViewport( elements, padding );

    if( viewportState ){
      var _p = this._private;
      _p.zoom = viewportState.zoom;
      _p.pan = viewportState.pan;

      this.trigger('pan zoom viewport');

      this.notify({ // notify the renderer that the viewport changed
        type: 'viewport'
      });
    }

    return this; // chaining
  },

  getFitViewport: function( elements, padding ){
    if( is.number(elements) && padding === undefined ){ // elements is optional
      padding = elements;
      elements = undefined;
    }

    if( !this._private.panningEnabled || !this._private.zoomingEnabled ){
      return;
    }

    var bb;

    if( is.string(elements) ){
      var sel = elements;
      elements = this.$( sel );

    } else if( is.boundingBox(elements) ){ // assume bb
      var bbe = elements;
      bb = {
        x1: bbe.x1,
        y1: bbe.y1,
        x2: bbe.x2,
        y2: bbe.y2
      };

      bb.w = bb.x2 - bb.x1;
      bb.h = bb.y2 - bb.y1;

    } else if( !is.elementOrCollection(elements) ){
      elements = this.elements();
    }

    bb = bb || elements.boundingBox();

    var w = this.width();
    var h = this.height();
    var zoom;
    padding = is.number(padding) ? padding : 0;

    if( !isNaN(w) && !isNaN(h) && w > 0 && h > 0 && !isNaN(bb.w) && !isNaN(bb.h) &&  bb.w > 0 && bb.h > 0 ){
      zoom = Math.min( (w - 2*padding)/bb.w, (h - 2*padding)/bb.h );

      // crop zoom
      zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom;
      zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom;

      var pan = { // now pan to middle
        x: (w - zoom*( bb.x1 + bb.x2 ))/2,
        y: (h - zoom*( bb.y1 + bb.y2 ))/2
      };

      return {
        zoom: zoom,
        pan: pan
      };
    }

    return;
  },

  minZoom: function( zoom ){
    if( zoom === undefined ){
      return this._private.minZoom;
    } else if( is.number(zoom) ){
      this._private.minZoom = zoom;
    }

    return this;
  },

  maxZoom: function( zoom ){
    if( zoom === undefined ){
      return this._private.maxZoom;
    } else if( is.number(zoom) ){
      this._private.maxZoom = zoom;
    }

    return this;
  },

  zoom: function( params ){
    var pos; // in rendered px
    var zoom;

    if( params === undefined ){ // then get the zoom
      return this._private.zoom;

    } else if( is.number(params) ){ // then set the zoom
      zoom = params;

    } else if( is.plainObject(params) ){ // then zoom about a point
      zoom = params.level;

      if( params.position ){
        var p = params.position;
        var pan = this._private.pan;
        var z = this._private.zoom;

        pos = { // convert to rendered px
          x: p.x * z + pan.x,
          y: p.y * z + pan.y
        };
      } else if( params.renderedPosition ){
        pos = params.renderedPosition;
      }

      if( pos && !this._private.panningEnabled ){
        return this; // panning disabled
      }
    }

    if( !this._private.zoomingEnabled ){
      return this; // zooming disabled
    }

    if( !is.number(zoom) || ( pos && (!is.number(pos.x) || !is.number(pos.y)) ) ){
      return this; // can't zoom with invalid params
    }

    // crop zoom
    zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom;
    zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom;

    if( pos ){ // set zoom about position
      var pan1 = this._private.pan;
      var zoom1 = this._private.zoom;
      var zoom2 = zoom;

      var pan2 = {
        x: -zoom2/zoom1 * (pos.x - pan1.x) + pos.x,
        y: -zoom2/zoom1 * (pos.y - pan1.y) + pos.y
      };

      this._private.zoom = zoom;
      this._private.pan = pan2;

      var posChanged = pan1.x !== pan2.x || pan1.y !== pan2.y;
      this.trigger(' zoom ' + (posChanged ? ' pan ' : '') + ' viewport ' );

    } else { // just set the zoom
      this._private.zoom = zoom;
      this.trigger('zoom viewport');
    }

    this.notify({ // notify the renderer that the viewport changed
      type: 'viewport'
    });

    return this; // chaining
  },

  viewport: function( opts ){
    var _p = this._private;
    var zoomDefd = true;
    var panDefd = true;
    var events = []; // to trigger
    var zoomFailed = false;
    var panFailed = false;

    if( !opts ){ return this; }
    if( !is.number(opts.zoom) ){ zoomDefd = false; }
    if( !is.plainObject(opts.pan) ){ panDefd = false; }
    if( !zoomDefd && !panDefd ){ return this; }

    if( zoomDefd ){
      var z = opts.zoom;

      if( z < _p.minZoom || z > _p.maxZoom || !_p.zoomingEnabled ){
        zoomFailed = true;

      } else {
        _p.zoom = z;

        events.push('zoom');
      }
    }

    if( panDefd && (!zoomFailed || !opts.cancelOnFailedZoom) && _p.panningEnabled ){
      var p = opts.pan;

      if( is.number(p.x) ){
        _p.pan.x = p.x;
        panFailed = false;
      }

      if( is.number(p.y) ){
        _p.pan.y = p.y;
        panFailed = false;
      }

      if( !panFailed ){
        events.push('pan');
      }
    }

    if( events.length > 0 ){
      events.push('viewport');
      this.trigger( events.join(' ') );

      this.notify({
        type: 'viewport'
      });
    }

    return this; // chaining
  },

  center: function( elements ){
    var pan = this.getCenterPan( elements );

    if( pan ){
      this._private.pan = pan;

      this.trigger('pan viewport');

      this.notify({ // notify the renderer that the viewport changed
        type: 'viewport'
      });
    }

    return this; // chaining
  },

  getCenterPan: function( elements, zoom ){
    if( !this._private.panningEnabled ){
      return;
    }

    if( is.string(elements) ){
      var selector = elements;
      elements = this.elements( selector );
    } else if( !is.elementOrCollection(elements) ){
      elements = this.elements();
    }

    var bb = elements.boundingBox();
    var w = this.width();
    var h = this.height();
    zoom = zoom === undefined ? this._private.zoom : zoom;

    var pan = { // middle
      x: (w - zoom*( bb.x1 + bb.x2 ))/2,
      y: (h - zoom*( bb.y1 + bb.y2 ))/2
    };

    return pan;
  },

  reset: function(){
    if( !this._private.panningEnabled || !this._private.zoomingEnabled ){
      return this;
    }

    this.viewport({
      pan: { x: 0, y: 0 },
      zoom: 1
    });

    return this; // chaining
  },

  width: function(){
    var container = this._private.container;

    if( container ){
      return container.clientWidth;
    }

    return 1; // fallback if no container (not 0 b/c can be used for dividing etc)
  },

  height: function(){
    var container = this._private.container;

    if( container ){
      return container.clientHeight;
    }

    return 1; // fallback if no container (not 0 b/c can be used for dividing etc)
  },

  extent: function(){
    var pan = this._private.pan;
    var zoom = this._private.zoom;
    var rb = this.renderedExtent();

    var b = {
      x1: ( rb.x1 - pan.x )/zoom,
      x2: ( rb.x2 - pan.x )/zoom,
      y1: ( rb.y1 - pan.y )/zoom,
      y2: ( rb.y2 - pan.y )/zoom
    };

    b.w = b.x2 - b.x1;
    b.h = b.y2 - b.y1;

    return b;
  },

  renderedExtent: function(){
    var width = this.width();
    var height = this.height();

    return {
      x1: 0,
      y1: 0,
      x2: width,
      y2: height,
      w: width,
      h: height
    };
  }
});

// aliases
corefn.centre = corefn.center;

// backwards compatibility
corefn.autolockNodes = corefn.autolock;
corefn.autoungrabifyNodes = corefn.autoungrabify;

module.exports = corefn;

},{"../is":77}],41:[function(_dereq_,module,exports){
'use strict';

// use this module to cherry pick functions into your prototype
// (useful for functions shared between the core and collections, for example)

// e.g.
// var foo = define.foo({ /* params... */ })

var util = _dereq_('./util');
var is = _dereq_('./is');
var Selector = _dereq_('./selector');
var Promise = _dereq_('./promise');
var Event = _dereq_('./event');
var Animation = _dereq_('./animation');

var define = {

  // access data field
  data: function( params ){
    var defaults = {
      field: 'data',
      bindingEvent: 'data',
      allowBinding: false,
      allowSetting: false,
      allowGetting: false,
      settingEvent: 'data',
      settingTriggersEvent: false,
      triggerFnName: 'trigger',
      immutableKeys: {}, // key => true if immutable
      updateStyle: false,
      onSet: function( self ){},
      canSet: function( self ){ return true; }
    };
    params = util.extend({}, defaults, params);

    return function dataImpl( name, value ){
      var p = params;
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
      var single = selfIsArrayLike ? self[0] : self;

      // .data('foo', ...)
      if( is.string(name) ){ // set or get property

        // .data('foo')
        if( p.allowGetting && value === undefined ){ // get

          var ret;
          if( single ){
            ret = single._private[ p.field ][ name ];
          }
          return ret;

        // .data('foo', 'bar')
        } else if( p.allowSetting && value !== undefined ) { // set
          var valid = !p.immutableKeys[name];
          if( valid ){
            for( var i = 0, l = all.length; i < l; i++ ){
              if( p.canSet( all[i] ) ){
                all[i]._private[ p.field ][ name ] = value;
              }
            }

            // update mappers if asked
            if( p.updateStyle ){ self.updateStyle(); }

            // call onSet callback
            p.onSet( self );

            if( p.settingTriggersEvent ){
              self[ p.triggerFnName ]( p.settingEvent );
            }
          }
        }

      // .data({ 'foo': 'bar' })
      } else if( p.allowSetting && is.plainObject(name) ){ // extend
        var obj = name;
        var k, v;

        for( k in obj ){
          v = obj[ k ];

          var valid = !p.immutableKeys[k];
          if( valid ){
            for( var i = 0, l = all.length; i < l; i++ ){
              if( p.canSet( all[i] ) ){
                all[i]._private[ p.field ][ k ] = v;
              }
            }
          }
        }

        // update mappers if asked
        if( p.updateStyle ){ self.updateStyle(); }

        // call onSet callback
        p.onSet( self );

        if( p.settingTriggersEvent ){
          self[ p.triggerFnName ]( p.settingEvent );
        }

      // .data(function(){ ... })
      } else if( p.allowBinding && is.fn(name) ){ // bind to event
        var fn = name;
        self.bind( p.bindingEvent, fn );

      // .data()
      } else if( p.allowGetting && name === undefined ){ // get whole object
        var ret;
        if( single ){
          ret = single._private[ p.field ];
        }
        return ret;
      }

      return self; // maintain chainability
    }; // function
  }, // data

  // remove data field
  removeData: function( params ){
    var defaults = {
      field: 'data',
      event: 'data',
      triggerFnName: 'trigger',
      triggerEvent: false,
      immutableKeys: {} // key => true if immutable
    };
    params = util.extend({}, defaults, params);

    return function removeDataImpl( names ){
      var p = params;
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like

      // .removeData('foo bar')
      if( is.string(names) ){ // then get the list of keys, and delete them
        var keys = names.split(/\s+/);
        var l = keys.length;

        for( var i = 0; i < l; i++ ){ // delete each non-empty key
          var key = keys[i];
          if( is.emptyString(key) ){ continue; }

          var valid = !p.immutableKeys[ key ]; // not valid if immutable
          if( valid ){
            for( var i_a = 0, l_a = all.length; i_a < l_a; i_a++ ){
              all[ i_a ]._private[ p.field ][ key ] = undefined;
            }
          }
        }

        if( p.triggerEvent ){
          self[ p.triggerFnName ]( p.event );
        }

      // .removeData()
      } else if( names === undefined ){ // then delete all keys

        for( var i_a = 0, l_a = all.length; i_a < l_a; i_a++ ){
          var _privateFields = all[ i_a ]._private[ p.field ];

          for( var key in _privateFields ){
            var validKeyToDelete = !p.immutableKeys[ key ];

            if( validKeyToDelete ){
              _privateFields[ key ] = undefined;
            }
          }
        }

        if( p.triggerEvent ){
          self[ p.triggerFnName ]( p.event );
        }
      }

      return self; // maintain chaining
    }; // function
  }, // removeData

  // event function reusable stuff
  event: {
    regex: /(\w+)(\.\w+)?/, // regex for matching event strings (e.g. "click.namespace")
    optionalTypeRegex: /(\w+)?(\.\w+)?/,
    falseCallback: function(){ return false; }
  },

  // event binding
  on: function( params ){
    var defaults = {
      unbindSelfOnTrigger: false,
      unbindAllBindersOnTrigger: false
    };
    params = util.extend({}, defaults, params);

    return function onImpl(events, selector, data, callback){
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
      var eventsIsString = is.string(events);
      var p = params;

      if( is.plainObject(selector) ){ // selector is actually data
        callback = data;
        data = selector;
        selector = undefined;
      } else if( is.fn(selector) || selector === false ){ // selector is actually callback
        callback = selector;
        data = undefined;
        selector = undefined;
      }

      if( is.fn(data) || data === false ){ // data is actually callback
        callback = data;
        data = undefined;
      }

      // if there isn't a callback, we can't really do anything
      // (can't speak for mapped events arg version)
      if( !(is.fn(callback) || callback === false) && eventsIsString ){
        return self; // maintain chaining
      }

      if( eventsIsString ){ // then convert to map
        var map = {};
        map[ events ] = callback;
        events = map;
      }

      for( var evts in events ){
        callback = events[evts];
        if( callback === false ){
          callback = define.event.falseCallback;
        }

        if( !is.fn(callback) ){ continue; }

        evts = evts.split(/\s+/);
        for( var i = 0; i < evts.length; i++ ){
          var evt = evts[i];
          if( is.emptyString(evt) ){ continue; }

          var match = evt.match( define.event.regex ); // type[.namespace]

          if( match ){
            var type = match[1];
            var namespace = match[2] ? match[2] : undefined;

            var listener = {
              callback: callback, // callback to run
              data: data, // extra data in eventObj.data
              delegated: selector ? true : false, // whether the evt is delegated
              selector: selector, // the selector to match for delegated events
              selObj: new Selector(selector), // cached selector object to save rebuilding
              type: type, // the event type (e.g. 'click')
              namespace: namespace, // the event namespace (e.g. ".foo")
              unbindSelfOnTrigger: p.unbindSelfOnTrigger,
              unbindAllBindersOnTrigger: p.unbindAllBindersOnTrigger,
              binders: all // who bound together
            };

            for( var j = 0; j < all.length; j++ ){
              var _p = all[j]._private;

              _p.listeners = _p.listeners || [];
              _p.listeners.push( listener );
            }
          }
        } // for events array
      } // for events map

      return self; // maintain chaining
    }; // function
  }, // on

  eventAliasesOn: function( proto ){
    var p = proto;

    p.addListener = p.listen = p.bind = p.on;
    p.removeListener = p.unlisten = p.unbind = p.off;
    p.emit = p.trigger;

    // this is just a wrapper alias of .on()
    p.pon = p.promiseOn = function( events, selector ){
      var self = this;
      var args = Array.prototype.slice.call( arguments, 0 );

      return new Promise(function( resolve, reject ){
        var callback = function( e ){
          self.off.apply( self, offArgs );

          resolve( e );
        };

        var onArgs = args.concat([ callback ]);
        var offArgs = onArgs.concat([]);

        self.on.apply( self, onArgs );
      });
    };
  },

  off: function offImpl( params ){
    var defaults = {
    };
    params = util.extend({}, defaults, params);

    return function(events, selector, callback){
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
      var eventsIsString = is.string(events);

      if( arguments.length === 0 ){ // then unbind all

        for( var i = 0; i < all.length; i++ ){
          all[i]._private.listeners = [];
        }

        return self; // maintain chaining
      }

      if( is.fn(selector) || selector === false ){ // selector is actually callback
        callback = selector;
        selector = undefined;
      }

      if( eventsIsString ){ // then convert to map
        var map = {};
        map[ events ] = callback;
        events = map;
      }

      for( var evts in events ){
        callback = events[evts];

        if( callback === false ){
          callback = define.event.falseCallback;
        }

        evts = evts.split(/\s+/);
        for( var h = 0; h < evts.length; h++ ){
          var evt = evts[h];
          if( is.emptyString(evt) ){ continue; }

          var match = evt.match( define.event.optionalTypeRegex ); // [type][.namespace]
          if( match ){
            var type = match[1] ? match[1] : undefined;
            var namespace = match[2] ? match[2] : undefined;

            for( var i = 0; i < all.length; i++ ){ //
              var listeners = all[i]._private.listeners = all[i]._private.listeners || [];

              for( var j = 0; j < listeners.length; j++ ){
                var listener = listeners[j];
                var nsMatches = !namespace || namespace === listener.namespace;
                var typeMatches = !type || listener.type === type;
                var cbMatches = !callback || callback === listener.callback;
                var listenerMatches = nsMatches && typeMatches && cbMatches;

                // delete listener if it matches
                if( listenerMatches ){
                  listeners.splice(j, 1);
                  j--;
                }
              } // for listeners
            } // for all
          } // if match
        } // for events array

      } // for events map

      return self; // maintain chaining
    }; // function
  }, // off

  trigger: function( params ){
    var defaults = {};
    params = util.extend({}, defaults, params);

    return function triggerImpl(events, extraParams, fnToTrigger){
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
      var eventsIsString = is.string(events);
      var eventsIsObject = is.plainObject(events);
      var eventsIsEvent = is.event(events);
      var cy = this._private.cy || ( is.core(this) ? this : null );
      var hasCompounds = cy ? cy.hasCompoundNodes() : false;

      if( eventsIsString ){ // then make a plain event object for each event name
        var evts = events.split(/\s+/);
        events = [];

        for( var i = 0; i < evts.length; i++ ){
          var evt = evts[i];
          if( is.emptyString(evt) ){ continue; }

          var match = evt.match( define.event.regex ); // type[.namespace]
          var type = match[1];
          var namespace = match[2] ? match[2] : undefined;

          events.push( {
            type: type,
            namespace: namespace
          } );
        }
      } else if( eventsIsObject ){ // put in length 1 array
        var eventArgObj = events;

        events = [ eventArgObj ];
      }

      if( extraParams ){
        if( !is.array(extraParams) ){ // make sure extra params are in an array if specified
          extraParams = [ extraParams ];
        }
      } else { // otherwise, we've got nothing
        extraParams = [];
      }

      for( var i = 0; i < events.length; i++ ){ // trigger each event in order
        var evtObj = events[i];

        for( var j = 0; j < all.length; j++ ){ // for each
          var triggerer = all[j];
          var listeners = triggerer._private.listeners = triggerer._private.listeners || [];
          var triggererIsElement = is.element(triggerer);
          var bubbleUp = triggererIsElement || params.layout;

          // create the event for this element from the event object
          var evt;

          if( eventsIsEvent ){ // then just get the object
            evt = evtObj;

            evt.cyTarget = evt.cyTarget || triggerer;
            evt.cy = evt.cy || cy;

          } else { // then we have to make one
            evt = new Event( evtObj, {
              cyTarget: triggerer,
              cy: cy,
              namespace: evtObj.namespace
            } );
          }

          // if a layout was specified, then put it in the typed event
          if( evtObj.layout ){
            evt.layout = evtObj.layout;
          }

          // if triggered by layout, put in event
          if( params.layout ){
            evt.layout = triggerer;
          }

          // create a rendered position based on the passed position
          if( evt.cyPosition ){
            var pos = evt.cyPosition;
            var zoom = cy.zoom();
            var pan = cy.pan();

            evt.cyRenderedPosition = {
              x: pos.x * zoom + pan.x,
              y: pos.y * zoom + pan.y
            };
          }

          if( fnToTrigger ){ // then override the listeners list with just the one we specified
            listeners = [{
              namespace: evt.namespace,
              type: evt.type,
              callback: fnToTrigger
            }];
          }

          for( var k = 0; k < listeners.length; k++ ){ // check each listener
            var lis = listeners[k];
            var nsMatches = !lis.namespace || lis.namespace === evt.namespace;
            var typeMatches = lis.type === evt.type;
            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
            var listenerMatches = nsMatches && typeMatches && targetMatches;

            if( listenerMatches ){ // then trigger it
              var args = [ evt ];
              args = args.concat( extraParams ); // add extra params to args list

              if( lis.data ){ // add on data plugged into binding
                evt.data = lis.data;
              } else { // or clear it in case the event obj is reused
                evt.data = undefined;
              }

              if( lis.unbindSelfOnTrigger || lis.unbindAllBindersOnTrigger ){ // then remove listener
                listeners.splice(k, 1);
                k--;
              }

              if( lis.unbindAllBindersOnTrigger ){ // then delete the listener for all binders
                var binders = lis.binders;
                for( var l = 0; l < binders.length; l++ ){
                  var binder = binders[l];
                  if( !binder || binder === triggerer ){ continue; } // already handled triggerer or we can't handle it

                  var binderListeners = binder._private.listeners;
                  for( var m = 0; m < binderListeners.length; m++ ){
                    var binderListener = binderListeners[m];

                    if( binderListener === lis ){ // delete listener from list
                      binderListeners.splice(m, 1);
                      m--;
                    }
                  }
                }
              }

              // run the callback
              var context = lis.delegated ? evt.cyTarget : triggerer;
              var ret = lis.callback.apply( context, args );

              if( ret === false || evt.isPropagationStopped() ){
                // then don't bubble
                bubbleUp = false;

                if( ret === false ){
                  // returning false is a shorthand for stopping propagation and preventing the def. action
                  evt.stopPropagation();
                  evt.preventDefault();
                }
              }
            } // if listener matches
          } // for each listener

          // bubble up event for elements
          if( bubbleUp ){
            var parent = hasCompounds ? triggerer._private.parent : null;
            var hasParent = parent != null && parent.length !== 0;

            if( hasParent ){ // then bubble up to parent
              parent = parent[0];
              parent.trigger(evt);
            } else { // otherwise, bubble up to the core
              cy.trigger(evt);
            }
          }

        } // for each of all
      } // for each event

      return self; // maintain chaining
    }; // function
  }, // trigger

  animated: function( fnParams ){
    var defaults = {};
    fnParams = util.extend({}, defaults, fnParams);

    return function animatedImpl(){
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
      var cy = this._private.cy || this;

      if( !cy.styleEnabled() ){ return false; }

      var ele = all[0];

      if( ele ){
        return ele._private.animation.current.length > 0;
      }
    };
  }, // animated

  clearQueue: function( fnParams ){
    var defaults = {};
    fnParams = util.extend({}, defaults, fnParams);

    return function clearQueueImpl(){
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
      var cy = this._private.cy || this;

      if( !cy.styleEnabled() ){ return this; }

      for( var i = 0; i < all.length; i++ ){
        var ele = all[i];
        ele._private.animation.queue = [];
      }

      return this;
    };
  }, // clearQueue

  delay: function( fnParams ){
    var defaults = {};
    fnParams = util.extend({}, defaults, fnParams);

    return function delayImpl( time, complete ){
      var cy = this._private.cy || this;

      if( !cy.styleEnabled() ){ return this; }

      return this.animate({
        delay: time,
        duration: time,
        complete: complete
      });
    };
  }, // delay

  delayAnimation: function( fnParams ){
    var defaults = {};
    fnParams = util.extend({}, defaults, fnParams);

    return function delayAnimationImpl( time, complete ){
      var cy = this._private.cy || this;

      if( !cy.styleEnabled() ){ return this; }

      return this.animation({
        delay: time,
        duration: time,
        complete: complete
      });
    };
  }, // delay

  animation: function( fnParams ){
    var defaults = {};
    fnParams = util.extend({}, defaults, fnParams);

    return function animationImpl( properties, params ){
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
      var cy = this._private.cy || this;
      var isCore = !selfIsArrayLike;
      var isEles = !isCore;

      if( !cy.styleEnabled() ){ return this; }

      var style = cy.style();

      properties = util.extend( {}, properties, params );

      if( properties.duration === undefined ){
        properties.duration = 400;
      }

      switch( properties.duration ){
      case 'slow':
        properties.duration = 600;
        break;
      case 'fast':
        properties.duration = 200;
        break;
      }

      var propertiesEmpty = true;
      if( properties ){ for( var i in properties ){ // jshint ignore:line
        propertiesEmpty = false;
        break;
      } }

      if( propertiesEmpty ){
        return new Animation( all[0], properties ); // nothing to animate
      }

      if( isEles ){
        properties.style = style.getPropsList( properties.style || properties.css );

        properties.css = undefined;
      }

      if( properties.renderedPosition && isEles ){
        var rpos = properties.renderedPosition;
        var pan = cy.pan();
        var zoom = cy.zoom();

        properties.position = {
          x: ( rpos.x - pan.x ) /zoom,
          y: ( rpos.y - pan.y ) /zoom
        };
      }

      // override pan w/ panBy if set
      if( properties.panBy && isCore ){
        var panBy = properties.panBy;
        var cyPan = cy.pan();

        properties.pan = {
          x: cyPan.x + panBy.x,
          y: cyPan.y + panBy.y
        };
      }

      // override pan w/ center if set
      var center = properties.center || properties.centre;
      if( center && isCore ){
        var centerPan = cy.getCenterPan( center.eles, properties.zoom );

        if( centerPan ){
          properties.pan = centerPan;
        }
      }

      // override pan & zoom w/ fit if set
      if( properties.fit && isCore ){
        var fit = properties.fit;
        var fitVp = cy.getFitViewport( fit.eles || fit.boundingBox, fit.padding );

        if( fitVp ){
          properties.pan = fitVp.pan;
          properties.zoom = fitVp.zoom;
        }
      }

      return new Animation( all[0], properties );
    };
  }, // animate

  animate: function( fnParams ){
    var defaults = {};
    fnParams = util.extend({}, defaults, fnParams);

    return function animateImpl( properties, params ){
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
      var cy = this._private.cy || this;

      if( !cy.styleEnabled() ){ return this; }

      if( params ){
        properties = util.extend( {}, properties, params );
      }

      // manually hook and run the animation
      for( var i = 0; i < all.length; i++ ){
        var ele = all[i];
        var queue = ele.animated() && (properties.queue === undefined || properties.queue);

        var ani = ele.animation( properties, (queue ? { queue: true } : undefined) );

        ani.play();
      }

      return this; // chaining
    };
  }, // animate

  stop: function( fnParams ){
    var defaults = {};
    fnParams = util.extend({}, defaults, fnParams);

    return function stopImpl( clearQueue, jumpToEnd ){
      var self = this;
      var selfIsArrayLike = self.length !== undefined;
      var all = selfIsArrayLike ? self : [self]; // put in array if not array-like
      var cy = this._private.cy || this;

      if( !cy.styleEnabled() ){ return this; }

      for( var i = 0; i < all.length; i++ ){
        var ele = all[i];
        var _p = ele._private;
        var anis = _p.animation.current;

        for( var j = 0; j < anis.length; j++ ){
          var ani = anis[j];
          var ani_p = ani._private;

          if( jumpToEnd ){
            // next iteration of the animation loop, the animation
            // will go straight to the end and be removed
            ani_p.duration = 0;
          }
        }

        // clear the queue of future animations
        if( clearQueue ){
          _p.animation.queue = [];
        }

        if( !jumpToEnd ){
          _p.animation.current = [];
        }
      }

      // we have to notify (the animation loop doesn't do it for us on `stop`)
      cy.notify({
        collection: this,
        type: 'draw'
      });

      return this;
    };
  } // stop

}; // define

module.exports = define;

},{"./animation":1,"./event":42,"./is":77,"./promise":80,"./selector":81,"./util":94}],42:[function(_dereq_,module,exports){
'use strict';

// ref
// https://github.com/jquery/jquery/blob/master/src/event.js

var Event = function( src, props ) {
  // Allow instantiation without the 'new' keyword
  if ( !(this instanceof Event) ) {
    return new Event( src, props );
  }

  // Event object
  if ( src && src.type ) {
    this.originalEvent = src;
    this.type = src.type;

    // Events bubbling up the document may have been marked as prevented
    // by a handler lower down the tree; reflect the correct value.
    this.isDefaultPrevented = ( src.defaultPrevented ) ? returnTrue : returnFalse;

  // Event type
  } else {
    this.type = src;
  }

  // Put explicitly provided properties onto the event object
  if ( props ) {
    // util.extend( this, props );

    // more efficient to manually copy fields we use
    this.type = props.type !== undefined ? props.type : this.type;
    this.cy = props.cy;
    this.cyTarget = props.cyTarget;
    this.cyPosition = props.cyPosition;
    this.cyRenderedPosition = props.cyRenderedPosition;
    this.namespace = props.namespace;
    this.layout = props.layout;
    this.data = props.data;
    this.message = props.message;
  }

  // Create a timestamp if incoming event doesn't have one
  this.timeStamp = src && src.timeStamp || Date.now();
};

function returnFalse() {
  return false;
}

function returnTrue() {
  return true;
}

// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
Event.prototype = {
  instanceString: function(){
    return 'event';
  },

  preventDefault: function() {
    this.isDefaultPrevented = returnTrue;

    var e = this.originalEvent;
    if ( !e ) {
      return;
    }

    // if preventDefault exists run it on the original event
    if ( e.preventDefault ) {
      e.preventDefault();
    }
  },

  stopPropagation: function() {
    this.isPropagationStopped = returnTrue;

    var e = this.originalEvent;
    if ( !e ) {
      return;
    }

    // if stopPropagation exists run it on the original event
    if ( e.stopPropagation ) {
      e.stopPropagation();
    }
  },

  stopImmediatePropagation: function() {
    this.isImmediatePropagationStopped = returnTrue;
    this.stopPropagation();
  },

  isDefaultPrevented: returnFalse,
  isPropagationStopped: returnFalse,
  isImmediatePropagationStopped: returnFalse
};

module.exports = Event;

},{}],43:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('./util');
var define = _dereq_('./define');
var Collection = _dereq_('./collection');
var Core = _dereq_('./core');
var incExts = _dereq_('./extensions');
var is = _dereq_('./is');

// registered extensions to cytoscape, indexed by name
var extensions = {};

// registered modules for extensions, indexed by name
var modules = {};

function setExtension( type, name, registrant ){

  var ext = registrant;

  if( type === 'core' ){
    Core.prototype[ name ] = registrant;

  } else if( type === 'collection' ){
    Collection.prototype[ name ] = registrant;

  } else if( type === 'layout' ){
    // fill in missing layout functions in the prototype

    var Layout = function( options ){
      this.options = options;

      registrant.call( this, options );

      // make sure layout has _private for use w/ std apis like .on()
      if( !is.plainObject(this._private) ){
        this._private = {};
      }

      this._private.cy = options.cy;
      this._private.listeners = [];
    };

    var layoutProto = Layout.prototype = Object.create( registrant.prototype );

    var optLayoutFns = [];

    for( var i = 0; i < optLayoutFns.length; i++ ){
      var fnName = optLayoutFns[i];

      layoutProto[fnName] = layoutProto[fnName] || function(){ return this; };
    }

    // either .start() or .run() is defined, so autogen the other
    if( layoutProto.start && !layoutProto.run ){
      layoutProto.run = function(){ this.start(); return this; };
    } else if( !layoutProto.start && layoutProto.run ){
      layoutProto.start = function(){ this.run(); return this; };
    }

    if( !layoutProto.stop ){
      layoutProto.stop = function(){
        var opts = this.options;

        if( opts && opts.animate ){
          var anis = this.animations;
          for( var i = 0; i < anis.length; i++ ){
            anis[i].stop();
          }
        }

        this.trigger('layoutstop');

        return this;
      };
    }

    if( !layoutProto.destroy ){
      layoutProto.destroy = function(){
        return this;
      };
    }

    layoutProto.on = define.on({ layout: true });
    layoutProto.one = define.on({ layout: true, unbindSelfOnTrigger: true });
    layoutProto.once = define.on({ layout: true, unbindAllBindersOnTrigger: true });
    layoutProto.off = define.off({ layout: true });
    layoutProto.trigger = define.trigger({ layout: true });

    define.eventAliasesOn( layoutProto );

    ext = Layout; // replace with our wrapped layout

  } else if( type === 'renderer' && name !== 'null' && name !== 'base' ){
    // user registered renderers inherit from base

    var bProto = getExtension( 'renderer', 'base' ).prototype;
    var rProto = registrant.prototype;

    for( var pName in bProto ){
      var pVal = bProto[ pName ];
      var existsInR = rProto[ pName ] != null;

      if( existsInR ){
        util.error('Can not register renderer `' + name + '` since it overrides `' + pName + '` in its prototype');
        return;
      }

      rProto[ pName ] = pVal; // take impl from base
    }

    bProto.clientFunctions.forEach(function( name ){
      rProto[ name ] = rProto[ name ] || function(){
        util.error('Renderer does not implement `renderer.' + name + '()` on its prototype');
      };
    });

  }

  return util.setMap({
    map: extensions,
    keys: [ type, name ],
    value: ext
  });
}

function getExtension(type, name){
  return util.getMap({
    map: extensions,
    keys: [ type, name ]
  });
}

function setModule(type, name, moduleType, moduleName, registrant){
  return util.setMap({
    map: modules,
    keys: [ type, name, moduleType, moduleName ],
    value: registrant
  });
}

function getModule(type, name, moduleType, moduleName){
  return util.getMap({
    map: modules,
    keys: [ type, name, moduleType, moduleName ]
  });
}

var extension = function(){
  // e.g. extension('renderer', 'svg')
  if( arguments.length === 2 ){
    return getExtension.apply(null, arguments);
  }

  // e.g. extension('renderer', 'svg', { ... })
  else if( arguments.length === 3 ){
    return setExtension.apply(null, arguments);
  }

  // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse')
  else if( arguments.length === 4 ){
    return getModule.apply(null, arguments);
  }

  // e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse', { ... })
  else if( arguments.length === 5 ){
    return setModule.apply(null, arguments);
  }

  else {
    util.error('Invalid extension access syntax');
  }

};

// allows a core instance to access extensions internally
Core.prototype.extension = extension;

// included extensions
incExts.forEach(function( group ){
  group.extensions.forEach(function( ext ){
    setExtension( group.type, ext.name, ext.impl );
  });
});

module.exports = extension;

},{"./collection":23,"./core":34,"./define":41,"./extensions":44,"./is":77,"./util":94}],44:[function(_dereq_,module,exports){
'use strict';

module.exports = [
  {
    type: 'layout',
    extensions: _dereq_('./layout')
  },

  {
    type: 'renderer',
    extensions: _dereq_('./renderer')
  }
];

},{"./layout":50,"./renderer":72}],45:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../../util');
var math = _dereq_('../../math');
var is = _dereq_('../../is');

var defaults = {
  fit: true, // whether to fit the viewport to the graph
  directed: false, // whether the tree is directed downwards (or edges can point in any direction if false)
  padding: 30, // padding on fit
  circle: false, // put depths in concentric circles if true, put depths top down if false
  spacingFactor: 1.75, // positive spacing factor, larger => more space between nodes (N.B. n/a if causes overlap)
  boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
  avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space
  roots: undefined, // the roots of the trees
  maximalAdjustments: 0, // how many times to try to position the nodes in a maximal way (i.e. no backtracking)
  animate: false, // whether to transition the node positions
  animationDuration: 500, // duration of animation in ms if enabled
  animationEasing: undefined, // easing of animation if enabled
  ready: undefined, // callback on layoutready
  stop: undefined // callback on layoutstop
};

function BreadthFirstLayout( options ){
  this.options = util.extend({}, defaults, options);
}

BreadthFirstLayout.prototype.run = function(){
  var params = this.options;
  var options = params;

  var cy = params.cy;
  var eles = options.eles;
  var nodes = eles.nodes().not(':parent');
  var graph = eles;

  var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : {
    x1: 0, y1: 0, w: cy.width(), h: cy.height()
  } );

  var roots;
  if( is.elementOrCollection(options.roots) ){
    roots = options.roots;
  } else if( is.array(options.roots) ){
    var rootsArray = [];

    for( var i = 0; i < options.roots.length; i++ ){
      var id = options.roots[i];
      var ele = cy.getElementById( id );
      rootsArray.push( ele );
    }

    roots = cy.collection( rootsArray );
  } else if( is.string(options.roots) ){
    roots = cy.$( options.roots );

  } else {
    if( options.directed ){
      roots = nodes.roots();
    } else {
      var components = [];
      var unhandledNodes = nodes;

      while( unhandledNodes.length > 0 ){
        var currComp = cy.collection();

        eles.bfs({
          roots: unhandledNodes[0],
          visit: function(i, depth, node, edge, pNode){
            currComp = currComp.add( node );
          },
          directed: false
        });

        unhandledNodes = unhandledNodes.not( currComp );
        components.push( currComp );
      }

      roots = cy.collection();
      for( var i = 0; i < components.length; i++ ){
        var comp = components[i];
        var maxDegree = comp.maxDegree( false );
        var compRoots = comp.filter(function(){
          return this.degree(false) === maxDegree;
        });

        roots = roots.add( compRoots );
      }

    }
  }


  var depths = [];
  var foundByBfs = {};
  var id2depth = {};
  var prevNode = {};
  var prevEdge = {};
  var successors = {};

  // find the depths of the nodes
  graph.bfs({
    roots: roots,
    directed: options.directed,
    visit: function(i, depth, node, edge, pNode){
      var ele = this[0];
      var id = ele.id();

      if( !depths[depth] ){
        depths[depth] = [];
      }

      depths[depth].push( ele );
      foundByBfs[ id ] = true;
      id2depth[ id ] = depth;
      prevNode[ id ] = pNode;
      prevEdge[ id ] = edge;

      if( pNode ){
        var prevId = pNode.id();
        var succ = successors[ prevId ] = successors[ prevId ] || [];

        succ.push( node );
      }
    }
  });

  // check for nodes not found by bfs
  var orphanNodes = [];
  for( var i = 0; i < nodes.length; i++ ){
    var ele = nodes[i];

    if( foundByBfs[ ele.id() ] ){
      continue;
    } else {
      orphanNodes.push( ele );
    }
  }

  // assign orphan nodes a depth from their neighborhood
  var maxChecks = orphanNodes.length * 3;
  var checks = 0;
  while( orphanNodes.length !== 0 && checks < maxChecks ){
    var node = orphanNodes.shift();
    var neighbors = node.neighborhood().nodes();
    var assignedDepth = false;

    for( var i = 0; i < neighbors.length; i++ ){
      var depth = id2depth[ neighbors[i].id() ];

      if( depth !== undefined ){
        depths[depth].push( node );
        assignedDepth = true;
        break;
      }
    }

    if( !assignedDepth ){
      orphanNodes.push( node );
    }

    checks++;
  }

  // assign orphan nodes that are still left to the depth of their subgraph
  while( orphanNodes.length !== 0 ){
    var node = orphanNodes.shift();
    //var subgraph = graph.bfs( node ).path;
    var assignedDepth = false;

    // for( var i = 0; i < subgraph.length; i++ ){
    //   var depth = id2depth[ subgraph[i].id() ];

    //   if( depth !== undefined ){
    //     depths[depth].push( node );
    //     assignedDepth = true;
    //     break;
    //   }
    // }

    if( !assignedDepth ){ // worst case if the graph really isn't tree friendly, then just dump it in 0
      if( depths.length === 0 ){
        depths.push([]);
      }

      depths[0].push( node );
    }
  }

  // assign the nodes a depth and index
  var assignDepthsToEles = function(){
    for( var i = 0; i < depths.length; i++ ){
      var eles = depths[i];

      for( var j = 0; j < eles.length; j++ ){
        var ele = eles[j];

        ele._private.scratch.breadthfirst = {
          depth: i,
          index: j
        };
      }
    }
  };
  assignDepthsToEles();


  var intersectsDepth = function( node ){ // returns true if has edges pointing in from a higher depth
    var edges = node.connectedEdges(function(){
      return this.data('target') === node.id();
    });
    var thisInfo = node._private.scratch.breadthfirst;
    var highestDepthOfOther = 0;
    var highestOther;
    for( var i = 0; i < edges.length; i++ ){
      var edge = edges[i];
      var otherNode = edge.source()[0];
      var otherInfo = otherNode._private.scratch.breadthfirst;

      if( thisInfo.depth <= otherInfo.depth && highestDepthOfOther < otherInfo.depth ){
        highestDepthOfOther = otherInfo.depth;
        highestOther = otherNode;
      }
    }

    return highestOther;
  };

   // make maximal if so set by adjusting depths
  for( var adj = 0; adj < options.maximalAdjustments; adj++ ){

    var nDepths = depths.length;
    var elesToMove = [];
    for( var i = 0; i < nDepths; i++ ){
      var depth = depths[i];

      var nDepth = depth.length;
      for( var j = 0; j < nDepth; j++ ){
        var ele = depth[j];
        var info = ele._private.scratch.breadthfirst;
        var intEle = intersectsDepth(ele);

        if( intEle ){
          info.intEle = intEle;
          elesToMove.push( ele );
        }
      }
    }

    for( var i = 0; i < elesToMove.length; i++ ){
      var ele = elesToMove[i];
      var info = ele._private.scratch.breadthfirst;
      var intEle = info.intEle;
      var intInfo = intEle._private.scratch.breadthfirst;

      depths[ info.depth ].splice( info.index, 1 ); // remove from old depth & index

      // add to end of new depth
      var newDepth = intInfo.depth + 1;
      while( newDepth > depths.length - 1 ){
        depths.push([]);
      }
      depths[ newDepth ].push( ele );

      info.depth = newDepth;
      info.index = depths[newDepth].length - 1;
    }

    assignDepthsToEles();
  }

  // find min distance we need to leave between nodes
  var minDistance = 0;
  if( options.avoidOverlap ){
    for( var i = 0; i < nodes.length; i++ ){
      var n = nodes[i];
      var nbb = n.boundingBox();
      var w = nbb.w;
      var h = nbb.h;

      minDistance = Math.max(minDistance, w, h);
    }
    minDistance *= options.spacingFactor; // just to have some nice spacing
  }

  // get the weighted percent for an element based on its connectivity to other levels
  var cachedWeightedPercent = {};
  var getWeightedPercent = function( ele ){
    if( cachedWeightedPercent[ ele.id() ] ){
      return cachedWeightedPercent[ ele.id() ];
    }

    var eleDepth = ele._private.scratch.breadthfirst.depth;
    var neighbors = ele.neighborhood().nodes().not(':parent');
    var percent = 0;
    var samples = 0;

    for( var i = 0; i < neighbors.length; i++ ){
      var neighbor = neighbors[i];
      var bf = neighbor._private.scratch.breadthfirst;
      var index = bf.index;
      var depth = bf.depth;
      var nDepth = depths[depth].length;

      if( eleDepth > depth || eleDepth === 0 ){ // only get influenced by elements above
        percent += index / nDepth;
        samples++;
      }
    }

    samples = Math.max(1, samples);
    percent = percent / samples;

    if( samples === 0 ){ // so lone nodes have a "don't care" state in sorting
      percent = undefined;
    }

    cachedWeightedPercent[ ele.id() ] = percent;
    return percent;
  };


  // rearrange the indices in each depth level based on connectivity

  var sortFn = function(a, b){
    var apct = getWeightedPercent( a );
    var bpct = getWeightedPercent( b );

    return apct - bpct;
  };

  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

    for( var i = 0; i < depths.length; i++ ){
      depths[i] = depths[i].sort( sortFn );
    }
    assignDepthsToEles(); // and update

  }

  var biggestDepthSize = 0;
  for( var i = 0; i < depths.length; i++ ){
    biggestDepthSize = Math.max( depths[i].length, biggestDepthSize );
  }

  var center = {
    x: bb.x1 + bb.w/2,
    y: bb.x1 + bb.h/2
  };

  var getPosition = function( ele, isBottomDepth ){
    var info = ele._private.scratch.breadthfirst;
    var depth = info.depth;
    var index = info.index;
    var depthSize = depths[depth].length;

    var distanceX = Math.max( bb.w / (depthSize + 1), minDistance );
    var distanceY = Math.max( bb.h / (depths.length + 1), minDistance );
    var radiusStepSize = Math.min( bb.w / 2 / depths.length, bb.h / 2 / depths.length );
    radiusStepSize = Math.max( radiusStepSize, minDistance );

    if( !options.circle ){

      var epos = {
        x: center.x + (index + 1 - (depthSize + 1)/2) * distanceX,
        y: (depth + 1) * distanceY
      };

      if( isBottomDepth ){
        return epos;
      }

      // var succs = successors[ ele.id() ];
      // if( succs ){
      //   epos.x = 0;
      //
      //   for( var i = 0 ; i < succs.length; i++ ){
      //     var spos = pos[ succs[i].id() ];
      //
      //     epos.x += spos.x;
      //   }
      //
      //   epos.x /= succs.length;
      // } else {
      //   //debugger;
      // }

      return epos;

    } else {
      if( options.circle ){
        var radius = radiusStepSize * depth + radiusStepSize - (depths.length > 0 && depths[0].length <= 3 ? radiusStepSize/2 : 0);
        var theta = 2 * Math.PI / depths[depth].length * index;

        if( depth === 0 && depths[0].length === 1 ){
          radius = 1;
        }

        return {
          x: center.x + radius * Math.cos(theta),
          y: center.y + radius * Math.sin(theta)
        };

      } else {
        return {
          x: center.x + (index + 1 - (depthSize + 1)/2) * distanceX,
          y: (depth + 1) * distanceY
        };
      }
    }

  };

  // get positions in reverse depth order
  var pos = {};
  for( var i = depths.length - 1; i >=0; i-- ){
    var depth = depths[i];

    for( var j = 0; j < depth.length; j++ ){
      var node = depth[j];

      pos[ node.id() ] = getPosition( node, i === depths.length - 1 );
    }
  }

  nodes.layoutPositions(this, options, function(){
    return pos[ this.id() ];
  });

  return this; // chaining
};

module.exports = BreadthFirstLayout;

},{"../../is":77,"../../math":79,"../../util":94}],46:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../../util');
var math = _dereq_('../../math');
var is = _dereq_('../../is');

var defaults = {
  fit: true, // whether to fit the viewport to the graph
  padding: 30, // the padding on fit
  boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
  avoidOverlap: true, // prevents node overlap, may overflow boundingBox and radius if not enough space
  radius: undefined, // the radius of the circle
  startAngle: 3/2 * Math.PI, // where nodes start in radians
  sweep: undefined, // how many radians should be between the first and last node (defaults to full circle)
  clockwise: true, // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false)
  sort: undefined, // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') }
  animate: false, // whether to transition the node positions
  animationDuration: 500, // duration of animation in ms if enabled
  animationEasing: undefined, // easing of animation if enabled
  ready: undefined, // callback on layoutready
  stop: undefined // callback on layoutstop
};

function CircleLayout( options ){
  this.options = util.extend({}, defaults, options);
}

CircleLayout.prototype.run = function(){
  var params = this.options;
  var options = params;

  var cy = params.cy;
  var eles = options.eles;

  var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise;

  var nodes = eles.nodes().not(':parent');

  if( options.sort ){
    nodes = nodes.sort( options.sort );
  }

  var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : {
    x1: 0, y1: 0, w: cy.width(), h: cy.height()
  } );

  var center = {
    x: bb.x1 + bb.w/2,
    y: bb.y1 + bb.h/2
  };

  var sweep = options.sweep === undefined ? 2*Math.PI - 2*Math.PI/nodes.length : options.sweep;

  var dTheta = sweep / ( Math.max(1, nodes.length - 1) );
  var r;

  var minDistance = 0;
  for( var i = 0; i < nodes.length; i++ ){
    var n = nodes[i];
    var nbb = n.boundingBox();
    var w = nbb.w;
    var h = nbb.h;

    minDistance = Math.max(minDistance, w, h);
  }

  if( is.number(options.radius) ){
    r = options.radius;
  } else if( nodes.length <= 1 ){
    r = 0;
  } else {
    r = Math.min( bb.h, bb.w )/2 - minDistance;
  }

  // calculate the radius
  if( nodes.length > 1 && options.avoidOverlap ){ // but only if more than one node (can't overlap)
    minDistance *= 1.75; // just to have some nice spacing

    var dcos = Math.cos(dTheta) - Math.cos(0);
    var dsin = Math.sin(dTheta) - Math.sin(0);
    var rMin = Math.sqrt( minDistance * minDistance / ( dcos*dcos + dsin*dsin ) ); // s.t. no nodes overlapping
    r = Math.max( rMin, r );
  }

  var getPos = function( i, ele ){
    var theta = options.startAngle + i * dTheta * ( clockwise ? 1 : -1 );

    var rx = r * Math.cos( theta );
    var ry = r * Math.sin( theta );
    var pos = {
      x: center.x + rx,
      y: center.y + ry
    };

    return pos;
  };

  nodes.layoutPositions( this, options, getPos );

  return this; // chaining
};

module.exports = CircleLayout;

},{"../../is":77,"../../math":79,"../../util":94}],47:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../../util');
var math = _dereq_('../../math');

var defaults = {
  fit: true, // whether to fit the viewport to the graph
  padding: 30, // the padding on fit
  startAngle: 3/2 * Math.PI, // where nodes start in radians
  sweep: undefined, // how many radians should be between the first and last node (defaults to full circle)
  clockwise: true, // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false)
  equidistant: false, // whether levels have an equal radial distance betwen them, may cause bounding box overflow
  minNodeSpacing: 10, // min spacing between outside of nodes (used for radius adjustment)
  boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
  avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space
  height: undefined, // height of layout area (overrides container height)
  width: undefined, // width of layout area (overrides container width)
  concentric: function(node){ // returns numeric value for each node, placing higher nodes in levels towards the centre
    return node.degree();
  },
  levelWidth: function(nodes){ // the variation of concentric values in each level
    return nodes.maxDegree() / 4;
  },
  animate: false, // whether to transition the node positions
  animationDuration: 500, // duration of animation in ms if enabled
  animationEasing: undefined, // easing of animation if enabled
  ready: undefined, // callback on layoutready
  stop: undefined // callback on layoutstop
};

function ConcentricLayout( options ){
  this.options = util.extend({}, defaults, options);
}

ConcentricLayout.prototype.run = function(){
  var params = this.options;
  var options = params;

  var clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise;

  var cy = params.cy;

  var eles = options.eles;
  var nodes = eles.nodes().not(':parent');

  var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : {
    x1: 0, y1: 0, w: cy.width(), h: cy.height()
  } );

  var center = {
    x: bb.x1 + bb.w/2,
    y: bb.y1 + bb.h/2
  };

  var nodeValues = []; // { node, value }
  var theta = options.startAngle;
  var maxNodeSize = 0;

  for( var i = 0; i < nodes.length; i++ ){
    var node = nodes[i];
    var value;

    // calculate the node value
    value = options.concentric.apply(node, [ node ]);
    nodeValues.push({
      value: value,
      node: node
    });

    // for style mapping
    node._private.scratch.concentric = value;
  }

  // in case we used the `concentric` in style
  nodes.updateStyle();

  // calculate max size now based on potentially updated mappers
  for( var i = 0; i < nodes.length; i++ ){
    var node = nodes[i];
    var nbb = node.boundingBox();

    maxNodeSize = Math.max( maxNodeSize, nbb.w, nbb.h );
  }

  // sort node values in descreasing order
  nodeValues.sort(function(a, b){
    return b.value - a.value;
  });

  var levelWidth = options.levelWidth( nodes );

  // put the values into levels
  var levels = [ [] ];
  var currentLevel = levels[0];
  for( var i = 0; i < nodeValues.length; i++ ){
    var val = nodeValues[i];

    if( currentLevel.length > 0 ){
      var diff = Math.abs( currentLevel[0].value - val.value );

      if( diff >= levelWidth ){
        currentLevel = [];
        levels.push( currentLevel );
      }
    }

    currentLevel.push( val );
  }

  // create positions from levels

  var minDist = maxNodeSize + options.minNodeSpacing; // min dist between nodes

  if( !options.avoidOverlap ){ // then strictly constrain to bb
    var firstLvlHasMulti = levels.length > 0 && levels[0].length > 1;
    var maxR = ( Math.min(bb.w, bb.h) / 2 - minDist );
    var rStep = maxR / ( levels.length + firstLvlHasMulti ? 1 : 0 );

    minDist = Math.min( minDist, rStep );
  }

  // find the metrics for each level
  var r = 0;
  for( var i = 0; i < levels.length; i++ ){
    var level = levels[i];
    var sweep = options.sweep === undefined ? 2*Math.PI - 2*Math.PI/level.length : options.sweep;
    var dTheta = level.dTheta = sweep / ( Math.max(1, level.length - 1) );

    // calculate the radius
    if( level.length > 1 && options.avoidOverlap ){ // but only if more than one node (can't overlap)
      var dcos = Math.cos(dTheta) - Math.cos(0);
      var dsin = Math.sin(dTheta) - Math.sin(0);
      var rMin = Math.sqrt( minDist * minDist / ( dcos*dcos + dsin*dsin ) ); // s.t. no nodes overlapping

      r = Math.max( rMin, r );
    }

    level.r = r;

    r += minDist;
  }

  if( options.equidistant ){
    var rDeltaMax = 0;
    var r = 0;

    for( var i = 0; i < levels.length; i++ ){
      var level = levels[i];
      var rDelta = level.r - r;

      rDeltaMax = Math.max( rDeltaMax, rDelta );
    }

    r = 0;
    for( var i = 0; i < levels.length; i++ ){
      var level = levels[i];

      if( i === 0 ){
        r = level.r;
      }

      level.r = r;

      r += rDeltaMax;
    }
  }

  // calculate the node positions
  var pos = {}; // id => position
  for( var i = 0; i < levels.length; i++ ){
    var level = levels[i];
    var dTheta = level.dTheta;
    var r = level.r;

    for( var j = 0; j < level.length; j++ ){
      var val = level[j];
      var theta = options.startAngle + (clockwise ? 1 : -1) * dTheta * j;

      var p = {
        x: center.x + r * Math.cos(theta),
        y: center.y + r * Math.sin(theta)
      };

      pos[ val.node.id() ] = p;
    }
  }

  // position the nodes
  nodes.layoutPositions(this, options, function(){
    var id = this.id();

    return pos[id];
  });

  return this; // chaining
};

module.exports = ConcentricLayout;

},{"../../math":79,"../../util":94}],48:[function(_dereq_,module,exports){
'use strict';

/*
The CoSE layout was written by Gerardo Huck.
https://www.linkedin.com/in/gerardohuck/

Based on the following article:
http://dl.acm.org/citation.cfm?id=1498047

Modifications tracked on Github.
*/

var util = _dereq_('../../util');
var math = _dereq_('../../math');
var Thread = _dereq_('../../thread');
var is = _dereq_('../../is');

var DEBUG;

/**
 * @brief :  default layout options
 */
var defaults = {
  // Called on `layoutready`
  ready               : function() {},

  // Called on `layoutstop`
  stop                : function() {},

  // Whether to animate while running the layout
  animate             : true,

  // The layout animates only after this many milliseconds
  // (prevents flashing on fast runs)
  animationThreshold  : 250,

  // Number of iterations between consecutive screen positions update
  // (0 -> only updated on the end)
  refresh             : 20,

  // Whether to fit the network view after when done
  fit                 : true,

  // Padding on fit
  padding             : 30,

  // Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
  boundingBox         : undefined,

  // Extra spacing between components in non-compound graphs
  componentSpacing    : 100,

  // Node repulsion (non overlapping) multiplier
  nodeRepulsion       : function( node ){ return 400000; },

  // Node repulsion (overlapping) multiplier
  nodeOverlap         : 10,

  // Ideal edge (non nested) length
  idealEdgeLength     : function( edge ){ return 10; },

  // Divisor to compute edge forces
  edgeElasticity      : function( edge ){ return 100; },

  // Nesting factor (multiplier) to compute ideal edge length for nested edges
  nestingFactor       : 5,

  // Gravity force (constant)
  gravity             : 80,

  // Maximum number of iterations to perform
  numIter             : 1000,

  // Initial temperature (maximum node displacement)
  initialTemp         : 200,

  // Cooling factor (how the temperature is reduced between consecutive iterations
  coolingFactor       : 0.95,

  // Lower temperature threshold (below this point the layout will end)
  minTemp             : 1.0,

  // Whether to use threading to speed up the layout
  useMultitasking     : true
};


/**
 * @brief       : constructor
 * @arg options : object containing layout options
 */
function CoseLayout(options) {
  this.options = util.extend({}, defaults, options);

  this.options.layout = this;
}


/**
 * @brief : runs the layout
 */
CoseLayout.prototype.run = function() {
  var options = this.options;
  var cy      = options.cy;
  var layout  = this;
  var thread  = this.thread;

  if( !thread || thread.stopped() ){
    thread = this.thread = Thread({ disabled: !options.useMultitasking });
  }

  layout.stopped = false;

  layout.trigger({ type: 'layoutstart', layout: layout });

  // Set DEBUG - Global variable
  if (true === options.debug) {
    DEBUG = true;
  } else {
    DEBUG = false;
  }

  // Initialize layout info
  var layoutInfo = createLayoutInfo(cy, layout, options);

  // Show LayoutInfo contents if debugging
  if (DEBUG) {
    printLayoutInfo(layoutInfo);
  }

  // If required, randomize node positions
  // if (true === options.randomize) {
    randomizePositions(layoutInfo, cy);
  // }

  var startTime = Date.now();
  var refreshRequested = false;
  var refresh = function( rOpts ){
    rOpts = rOpts || {};

    if( refreshRequested ){
      return;
    }

    if( !rOpts.force && Date.now() - startTime < options.animationThreshold ){
      return;
    }

    refreshRequested = true;

    util.requestAnimationFrame(function(){
      refreshPositions(layoutInfo, cy, options);

      // Fit the graph if necessary
      if (true === options.fit) {
        cy.fit( options.padding );
      }

      refreshRequested = false;
    });
  };

  thread.on('message', function( e ){
    var layoutNodes = e.message;

    layoutInfo.layoutNodes = layoutNodes;
    refresh();
  });

  thread.pass({
    layoutInfo: layoutInfo,
    options: {
      animate: options.animate,
      refresh: options.refresh,
      componentSpacing: options.componentSpacing,
      nodeOverlap: options.nodeOverlap,
      nestingFactor: options.nestingFactor,
      gravity: options.gravity,
      numIter: options.numIter,
      initialTemp: options.initialTemp,
      coolingFactor: options.coolingFactor,
      minTemp: options.minTemp
    }
  }).run(function( pass ){
    var layoutInfo = pass.layoutInfo;
    var options = pass.options;
    var stopped = false;

    /**
     * @brief          : Performs one iteration of the physical simulation
     * @arg layoutInfo : LayoutInfo object already initialized
     * @arg cy         : Cytoscape object
     * @arg options    : Layout options
     */
    var step = function(layoutInfo, options, step) {
      // var s = "\n\n###############################";
      // s += "\nSTEP: " + step;
      // s += "\n###############################\n";
      // logDebug(s);

      // Calculate node repulsions
      calculateNodeForces(layoutInfo, options);
      // Calculate edge forces
      calculateEdgeForces(layoutInfo, options);
      // Calculate gravity forces
      calculateGravityForces(layoutInfo, options);
      // Propagate forces from parent to child
      propagateForces(layoutInfo, options);
      // Update positions based on calculated forces
      updatePositions(layoutInfo, options);
    };

    /**
     * @brief : Computes the node repulsion forces
     */
    var calculateNodeForces = function(layoutInfo, options) {
      // Go through each of the graphs in graphSet
      // Nodes only repel each other if they belong to the same graph
      // var s = 'calculateNodeForces';
      // logDebug(s);
      for (var i = 0; i < layoutInfo.graphSet.length; i ++) {
        var graph    = layoutInfo.graphSet[i];
        var numNodes = graph.length;

        // s = "Set: " + graph.toString();
        // logDebug(s);

        // Now get all the pairs of nodes
        // Only get each pair once, (A, B) = (B, A)
        for (var j = 0; j < numNodes; j++) {
          var node1 = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[j]]];

          for (var k = j + 1; k < numNodes; k++) {
            var node2 = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[k]]];

            nodeRepulsion(node1, node2, layoutInfo, options);
          }
        }
      }
    };

    /**
     * @brief : Compute the node repulsion forces between a pair of nodes
     */
    var nodeRepulsion = function(node1, node2, layoutInfo, options) {
      // var s = "Node repulsion. Node1: " + node1.id + " Node2: " + node2.id;

      var cmptId1 = node1.cmptId;
      var cmptId2 = node2.cmptId;

      if( cmptId1 !== cmptId2 && !layoutInfo.isCompound ){ return; }

      // Get direction of line connecting both node centers
      var directionX = node2.positionX - node1.positionX;
      var directionY = node2.positionY - node1.positionY;
      // s += "\ndirectionX: " + directionX + ", directionY: " + directionY;

      // If both centers are the same, apply a random force
      if (0 === directionX && 0 === directionY) {
        // s += "\nNodes have the same position.";
        return; // TODO could be improved with random force
      }

      var overlap = nodesOverlap(node1, node2, directionX, directionY);

      if (overlap > 0) {
        // s += "\nNodes DO overlap.";
        // s += "\nOverlap: " + overlap;
        // If nodes overlap, repulsion force is proportional
        // to the overlap
        var force    = options.nodeOverlap * overlap;

        // Compute the module and components of the force vector
        var distance = Math.sqrt(directionX * directionX + directionY * directionY);
        // s += "\nDistance: " + distance;
        var forceX   = force * directionX / distance;
        var forceY   = force * directionY / distance;

      } else {
        // s += "\nNodes do NOT overlap.";
        // If there's no overlap, force is inversely proportional
        // to squared distance

        // Get clipping points for both nodes
        var point1 = findClippingPoint(node1, directionX, directionY);
        var point2 = findClippingPoint(node2, -1 * directionX, -1 * directionY);

        // Use clipping points to compute distance
        var distanceX   = point2.x - point1.x;
        var distanceY   = point2.y - point1.y;
        var distanceSqr = distanceX * distanceX + distanceY * distanceY;
        var distance    = Math.sqrt(distanceSqr);
        // s += "\nDistance: " + distance;

        // Compute the module and components of the force vector
        var force  = ( node1.nodeRepulsion + node2.nodeRepulsion ) / distanceSqr;
        var forceX = force * distanceX / distance;
        var forceY = force * distanceY / distance;
      }

      // Apply force
      if( !node1.isLocked ){
        node1.offsetX -= forceX;
        node1.offsetY -= forceY;
      }

      if( !node2.isLocked ){
        node2.offsetX += forceX;
        node2.offsetY += forceY;
      }

      // s += "\nForceX: " + forceX + " ForceY: " + forceY;
      // logDebug(s);

      return;
    };

    /**
     * @brief  : Determines whether two nodes overlap or not
     * @return : Amount of overlapping (0 => no overlap)
     */
    var nodesOverlap = function(node1, node2, dX, dY) {

      if (dX > 0) {
        var overlapX = node1.maxX - node2.minX;
      } else {
        var overlapX = node2.maxX - node1.minX;
      }

      if (dY > 0) {
        var overlapY = node1.maxY - node2.minY;
      } else {
        var overlapY = node2.maxY - node1.minY;
      }

      if (overlapX >= 0 && overlapY >= 0) {
        return Math.sqrt(overlapX * overlapX + overlapY * overlapY);
      } else {
        return 0;
      }
    };

    /**
     * @brief : Finds the point in which an edge (direction dX, dY) intersects
     *          the rectangular bounding box of it's source/target node
     */
    var findClippingPoint = function(node, dX, dY) {

      // Shorcuts
      var X = node.positionX;
      var Y = node.positionY;
      var H = node.height || 1;
      var W = node.width || 1;
      var dirSlope     = dY / dX;
      var nodeSlope    = H / W;

      // var s = 'Computing clipping point of node ' + node.id +
      //   " . Height:  " + H + ", Width: " + W +
      //   "\nDirection " + dX + ", " + dY;
      //
      // Compute intersection
      var res = {};
      do {
        // Case: Vertical direction (up)
        if (0 === dX && 0 < dY) {
          res.x = X;
          // s += "\nUp direction";
          res.y = Y + H / 2;
          break;
        }

        // Case: Vertical direction (down)
        if (0 === dX && 0 > dY) {
          res.x = X;
          res.y = Y + H / 2;
          // s += "\nDown direction";
          break;
        }

        // Case: Intersects the right border
        if (0 < dX &&
        -1 * nodeSlope <= dirSlope &&
        dirSlope <= nodeSlope) {
          res.x = X + W / 2;
          res.y = Y + (W * dY / 2 / dX);
          // s += "\nRightborder";
          break;
        }

        // Case: Intersects the left border
        if (0 > dX &&
        -1 * nodeSlope <= dirSlope &&
        dirSlope <= nodeSlope) {
          res.x = X - W / 2;
          res.y = Y - (W * dY / 2 / dX);
          // s += "\nLeftborder";
          break;
        }

        // Case: Intersects the top border
        if (0 < dY &&
        ( dirSlope <= -1 * nodeSlope ||
          dirSlope >= nodeSlope )) {
          res.x = X + (H * dX / 2 / dY);
          res.y = Y + H / 2;
          // s += "\nTop border";
          break;
        }

        // Case: Intersects the bottom border
        if (0 > dY &&
        ( dirSlope <= -1 * nodeSlope ||
          dirSlope >= nodeSlope )) {
          res.x = X - (H * dX / 2 / dY);
          res.y = Y - H / 2;
          // s += "\nBottom border";
          break;
        }

      } while (false);

      // s += "\nClipping point found at " + res.x + ", " + res.y;
      // logDebug(s);
      return res;
    };

    /**
     * @brief : Calculates all edge forces
     */
    var calculateEdgeForces = function(layoutInfo, options) {
      // Iterate over all edges
      for (var i = 0; i < layoutInfo.edgeSize; i++) {
        // Get edge, source & target nodes
        var edge     = layoutInfo.layoutEdges[i];
        var sourceIx = layoutInfo.idToIndex[edge.sourceId];
        var source   = layoutInfo.layoutNodes[sourceIx];
        var targetIx = layoutInfo.idToIndex[edge.targetId];
        var target   = layoutInfo.layoutNodes[targetIx];

        // Get direction of line connecting both node centers
        var directionX = target.positionX - source.positionX;
        var directionY = target.positionY - source.positionY;

        // If both centers are the same, do nothing.
        // A random force has already been applied as node repulsion
        if (0 === directionX && 0 === directionY) {
        return;
        }

        // Get clipping points for both nodes
        var point1 = findClippingPoint(source, directionX, directionY);
        var point2 = findClippingPoint(target, -1 * directionX, -1 * directionY);


        var lx = point2.x - point1.x;
        var ly = point2.y - point1.y;
        var l  = Math.sqrt(lx * lx + ly * ly);

        var force  = Math.pow(edge.idealLength - l, 2) / edge.elasticity;

        if (0 !== l) {
          var forceX = force * lx / l;
          var forceY = force * ly / l;
        } else {
          var forceX = 0;
          var forceY = 0;
        }

        // Add this force to target and source nodes
        if( !source.isLocked ){
          source.offsetX += forceX;
          source.offsetY += forceY;
        }

        if( !target.isLocked ){
          target.offsetX -= forceX;
          target.offsetY -= forceY;
        }

        // var s = 'Edge force between nodes ' + source.id + ' and ' + target.id;
        // s += "\nDistance: " + l + " Force: (" + forceX + ", " + forceY + ")";
        // logDebug(s);
      }
    };

    /**
     * @brief : Computes gravity forces for all nodes
     */
    var calculateGravityForces = function(layoutInfo, options) {
      var distThreshold = 1;

      // var s = 'calculateGravityForces';
      // logDebug(s);
      for (var i = 0; i < layoutInfo.graphSet.length; i ++) {
        var graph    = layoutInfo.graphSet[i];
        var numNodes = graph.length;

        // s = "Set: " + graph.toString();
        // logDebug(s);

        // Compute graph center
        if (0 === i) {
          var centerX   = layoutInfo.clientHeight / 2;
          var centerY   = layoutInfo.clientWidth  / 2;
        } else {
          // Get Parent node for this graph, and use its position as center
          var temp    = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[0]]];
          var parent  = layoutInfo.layoutNodes[layoutInfo.idToIndex[temp.parentId]];
          var centerX = parent.positionX;
          var centerY = parent.positionY;
        }
        // s = "Center found at: " + centerX + ", " + centerY;
        // logDebug(s);

        // Apply force to all nodes in graph
        for (var j = 0; j < numNodes; j++) {
          var node = layoutInfo.layoutNodes[layoutInfo.idToIndex[graph[j]]];
          // s = "Node: " + node.id;

          if( node.isLocked ){ continue; }

          var dx = centerX - node.positionX;
          var dy = centerY - node.positionY;
          var d  = Math.sqrt(dx * dx + dy * dy);
          if (d > distThreshold) {
            var fx = options.gravity * dx / d;
            var fy = options.gravity * dy / d;
            node.offsetX += fx;
            node.offsetY += fy;
            // s += ": Applied force: " + fx + ", " + fy;
          } else {
            // s += ": skypped since it's too close to center";
          }
          // logDebug(s);
        }
      }
    };

    /**
     * @brief          : This function propagates the existing offsets from
     *                   parent nodes to its descendents.
     * @arg layoutInfo : layoutInfo Object
     * @arg cy         : cytoscape Object
     * @arg options    : Layout options
     */
    var propagateForces = function(layoutInfo, options) {
      // Inline implementation of a queue, used for traversing the graph in BFS order
      var queue = [];
      var start = 0;   // Points to the start the queue
      var end   = -1;  // Points to the end of the queue

      // logDebug('propagateForces');

      // Start by visiting the nodes in the root graph
      queue.push.apply(queue, layoutInfo.graphSet[0]);
      end += layoutInfo.graphSet[0].length;

      // Traverse the graph, level by level,
      while (start <= end) {
        // Get the node to visit and remove it from queue
        var nodeId    = queue[start++];
        var nodeIndex = layoutInfo.idToIndex[nodeId];
        var node      = layoutInfo.layoutNodes[nodeIndex];
        var children  = node.children;

        // We only need to process the node if it's compound
        if (0 < children.length && !node.isLocked) {
          var offX = node.offsetX;
          var offY = node.offsetY;

          // var s = "Propagating offset from parent node : " + node.id +
          //   ". OffsetX: " + offX + ". OffsetY: " + offY;
          // s += "\n Children: " + children.toString();
          // logDebug(s);

          for (var i = 0; i < children.length; i++) {
            var childNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[children[i]]];
            // Propagate offset
            childNode.offsetX += offX;
            childNode.offsetY += offY;
            // Add children to queue to be visited
            queue[++end] = children[i];
          }

          // Reset parent offsets
          node.offsetX = 0;
          node.offsetY = 0;
        }

      }
    };

    /**
     * @brief : Updates the layout model positions, based on
     *          the accumulated forces
     */
    var updatePositions = function(layoutInfo, options) {
      // var s = 'Updating positions';
      // logDebug(s);

      // Reset boundaries for compound nodes
      for (var i = 0; i < layoutInfo.nodeSize; i++) {
        var n = layoutInfo.layoutNodes[i];
        if (0 < n.children.length) {
          // logDebug("Resetting boundaries of compound node: " + n.id);
          n.maxX = undefined;
          n.minX = undefined;
          n.maxY = undefined;
          n.minY = undefined;
        }
      }

      for (var i = 0; i < layoutInfo.nodeSize; i++) {
        var n = layoutInfo.layoutNodes[i];
        if (0 < n.children.length || n.isLocked) {
          // No need to set compound or locked node position
          // logDebug("Skipping position update of node: " + n.id);
          continue;
        }
        // s = "Node: " + n.id + " Previous position: (" +
        // n.positionX + ", " + n.positionY + ").";

        // Limit displacement in order to improve stability
        var tempForce = limitForce(n.offsetX, n.offsetY, layoutInfo.temperature);
        n.positionX += tempForce.x;
        n.positionY += tempForce.y;
        n.offsetX = 0;
        n.offsetY = 0;
        n.minX    = n.positionX - n.width;
        n.maxX    = n.positionX + n.width;
        n.minY    = n.positionY - n.height;
        n.maxY    = n.positionY + n.height;
        // s += " New Position: (" + n.positionX + ", " + n.positionY + ").";
        // logDebug(s);

        // Update ancestry boudaries
        updateAncestryBoundaries(n, layoutInfo);
      }

      // Update size, position of compund nodes
      for (var i = 0; i < layoutInfo.nodeSize; i++) {
        var n = layoutInfo.layoutNodes[i];
        if ( 0 < n.children.length && !n.isLocked ) {
          n.positionX = (n.maxX + n.minX) / 2;
          n.positionY = (n.maxY + n.minY) / 2;
          n.width     = n.maxX - n.minX;
          n.height    = n.maxY - n.minY;
          // s = "Updating position, size of compound node " + n.id;
          // s += "\nPositionX: " + n.positionX + ", PositionY: " + n.positionY;
          // s += "\nWidth: " + n.width + ", Height: " + n.height;
          // logDebug(s);
        }
      }
    };

    /**
     * @brief : Limits a force (forceX, forceY) to be not
     *          greater (in modulo) than max.
     8          Preserves force direction.
     */
    var limitForce = function(forceX, forceY, max) {
      // var s = "Limiting force: (" + forceX + ", " + forceY + "). Max: " + max;
      var force = Math.sqrt(forceX * forceX + forceY * forceY);

      if (force > max) {
        var res = {
        x : max * forceX / force,
        y : max * forceY / force
        };

      } else {
        var res = {
        x : forceX,
        y : forceY
        };
      }

      // s += ".\nResult: (" + res.x + ", " + res.y + ")";
      // logDebug(s);

      return res;
    };

    /**
     * @brief : Function used for keeping track of compound node
     *          sizes, since they should bound all their subnodes.
     */
    var updateAncestryBoundaries = function(node, layoutInfo) {
      // var s = "Propagating new position/size of node " + node.id;
      var parentId = node.parentId;
      if (null == parentId) {
        // If there's no parent, we are done
        // s += ". No parent node.";
        // logDebug(s);
        return;
      }

      // Get Parent Node
      var p = layoutInfo.layoutNodes[layoutInfo.idToIndex[parentId]];
      var flag = false;

      // MaxX
      if (null == p.maxX || node.maxX + p.padRight > p.maxX) {
        p.maxX = node.maxX + p.padRight;
        flag = true;
        // s += "\nNew maxX for parent node " + p.id + ": " + p.maxX;
      }

      // MinX
      if (null == p.minX || node.minX - p.padLeft < p.minX) {
        p.minX = node.minX - p.padLeft;
        flag = true;
        // s += "\nNew minX for parent node " + p.id + ": " + p.minX;
      }

      // MaxY
      if (null == p.maxY || node.maxY + p.padBottom > p.maxY) {
        p.maxY = node.maxY + p.padBottom;
        flag = true;
        // s += "\nNew maxY for parent node " + p.id + ": " + p.maxY;
      }

      // MinY
      if (null == p.minY || node.minY - p.padTop < p.minY) {
        p.minY = node.minY - p.padTop;
        flag = true;
        // s += "\nNew minY for parent node " + p.id + ": " + p.minY;
      }

      // If updated boundaries, propagate changes upward
      if (flag) {
        // logDebug(s);
        return updateAncestryBoundaries(p, layoutInfo);
      }

      // s += ". No changes in boundaries/position of parent node " + p.id;
      // logDebug(s);
      return;
    };

    var separateComponents = function(layutInfo, options){
      var nodes = layoutInfo.layoutNodes;
      var components = [];

      for( var i = 0; i < nodes.length; i++ ){
        var node = nodes[i];
        var cid = node.cmptId;
        var component = components[ cid ] = components[ cid ] || [];

        component.push( node );
      }

      var totalA = 0;

      for( var i = 0; i < components.length; i++ ){
        var c = components[i];
        c.x1 = Infinity;
        c.x2 = -Infinity;
        c.y1 = Infinity;
        c.y2 = -Infinity;

        for( var j = 0; j < c.length; j++ ){
          var n = c[j];

          c.x1 = Math.min( c.x1, n.positionX - n.width/2 );
          c.x2 = Math.max( c.x2, n.positionX + n.width/2 );
          c.y1 = Math.min( c.y1, n.positionY - n.height/2 );
          c.y2 = Math.max( c.y2, n.positionY + n.height/2 );
        }

        c.w = c.x2 - c.x1;
        c.h = c.y2 - c.y1;

        totalA += c.w * c.h;
      }

      components.sort(function( c1, c2 ){
        return c2.w*c2.h - c1.w*c1.h;
      });

      var x = 0;
      var y = 0;
      var usedW = 0;
      var rowH = 0;
      var maxRowW = Math.sqrt( totalA ) * layoutInfo.clientWidth / layoutInfo.clientHeight;

      for( var i = 0; i < components.length; i++ ){
        var c = components[i];

        for( var j = 0; j < c.length; j++ ){
          var n = c[j];

          if( !n.isLocked ){
            n.positionX += x;
            n.positionY += y;
          }
        }

        x += c.w + options.componentSpacing;
        usedW += c.w + options.componentSpacing;
        rowH = Math.max( rowH, c.h );

        if( usedW > maxRowW ){
          y += rowH + options.componentSpacing;
          x = 0;
          usedW = 0;
          rowH = 0;
        }
      }
    };

    var mainLoop = function(i){
      if( stopped ){
        // logDebug("Layout manually stopped. Stopping computation in step " + i);
        return false;
      }

      // Do one step in the phisical simulation
      step(layoutInfo, options, i);

      // Update temperature
      layoutInfo.temperature = layoutInfo.temperature * options.coolingFactor;
      // logDebug("New temperature: " + layoutInfo.temperature);

      if (layoutInfo.temperature < options.minTemp) {
        // logDebug("Temperature drop below minimum threshold. Stopping computation in step " + i);
        return false;
      }

      return true;
    };

    var i = 0;
    var loopRet;

    do {
      var f = 0;

      while( f < options.refresh && i < options.numIter ){
        var loopRet = mainLoop(i);
        if( !loopRet ){ break; }

        f++;
        i++;
      }

      if( options.animate ){
        broadcast( layoutInfo.layoutNodes ); // jshint ignore:line
      }

    } while ( loopRet && i + 1 < options.numIter );

    separateComponents( layoutInfo, options );

    return layoutInfo;
  }).then(function( layoutInfoUpdated ){
    layoutInfo.layoutNodes = layoutInfoUpdated.layoutNodes; // get the positions

    thread.stop();
    done();
  });

  var done = function(){
    refresh({ force: true });

    // Layout has finished
    layout.one('layoutstop', options.stop);
    layout.trigger({ type: 'layoutstop', layout: layout });
  };

  return this; // chaining
};


/**
 * @brief : called on continuous layouts to stop them before they finish
 */
CoseLayout.prototype.stop = function(){
  this.stopped = true;

  if( this.thread ){
    this.thread.stop();
  }

  this.trigger('layoutstop');

  return this; // chaining
};

CoseLayout.prototype.destroy = function(){
  if( this.thread ){
    this.thread.stop();
  }

  return this; // chaining
};


/**
 * @brief     : Creates an object which is contains all the data
 *              used in the layout process
 * @arg cy    : cytoscape.js object
 * @return    : layoutInfo object initialized
 */
var createLayoutInfo = function(cy, layout, options) {
  // Shortcut
  var edges = options.eles.edges();
  var nodes = options.eles.nodes();

  var layoutInfo   = {
    isCompound   : cy.hasCompoundNodes(),
    layoutNodes  : [],
    idToIndex    : {},
    nodeSize     : nodes.size(),
    graphSet     : [],
    indexToGraph : [],
    layoutEdges  : [],
    edgeSize     : edges.size(),
    temperature  : options.initialTemp,
    clientWidth  : cy.width(),
    clientHeight : cy.width(),
    boundingBox  : math.makeBoundingBox( options.boundingBox ? options.boundingBox : {
                     x1: 0, y1: 0, w: cy.width(), h: cy.height()
                   } )
  };

  var components = options.eles.components();
  var id2cmptId = {};

  for( var i = 0; i < components.length; i++ ){
    var component = components[i];

    for( var j = 0; j < component.length; j++ ){
      var node = component[j];

      id2cmptId[ node.id() ] = i;
    }
  }

  // Iterate over all nodes, creating layout nodes
  for (var i = 0; i < layoutInfo.nodeSize; i++) {
    var n = nodes[i];
    var nbb = n.boundingBox();

    var tempNode        = {};
    tempNode.isLocked   = n.locked();
    tempNode.id         = n.data('id');
    tempNode.parentId   = n.data('parent');
    tempNode.cmptId     = id2cmptId[ n.id() ];
    tempNode.children   = [];
    tempNode.positionX  = n.position('x');
    tempNode.positionY  = n.position('y');
    tempNode.offsetX    = 0;
    tempNode.offsetY    = 0;
    tempNode.height     = nbb.w;
    tempNode.width      = nbb.h;
    tempNode.maxX       = tempNode.positionX + tempNode.width  / 2;
    tempNode.minX       = tempNode.positionX - tempNode.width  / 2;
    tempNode.maxY       = tempNode.positionY + tempNode.height / 2;
    tempNode.minY       = tempNode.positionY - tempNode.height / 2;
    tempNode.padLeft    = parseFloat( n.style('padding-left') );
    tempNode.padRight   = parseFloat( n.style('padding-right') );
    tempNode.padTop     = parseFloat( n.style('padding-top') );
    tempNode.padBottom  = parseFloat( n.style('padding-bottom') );

    // forces
    tempNode.nodeRepulsion = is.fn( options.nodeRepulsion ) ? options.nodeRepulsion.call( n, n ) : options.nodeRepulsion;

    // Add new node
    layoutInfo.layoutNodes.push(tempNode);
    // Add entry to id-index map
    layoutInfo.idToIndex[tempNode.id] = i;
  }

  // Inline implementation of a queue, used for traversing the graph in BFS order
  var queue = [];
  var start = 0;   // Points to the start the queue
  var end   = -1;  // Points to the end of the queue

  var tempGraph = [];

  // Second pass to add child information and
  // initialize queue for hierarchical traversal
  for (var i = 0; i < layoutInfo.nodeSize; i++) {
    var n = layoutInfo.layoutNodes[i];
    var p_id = n.parentId;
    // Check if node n has a parent node
    if (null != p_id) {
    // Add node Id to parent's list of children
    layoutInfo.layoutNodes[layoutInfo.idToIndex[p_id]].children.push(n.id);
    } else {
    // If a node doesn't have a parent, then it's in the root graph
    queue[++end] = n.id;
    tempGraph.push(n.id);
    }
  }

  // Add root graph to graphSet
  layoutInfo.graphSet.push(tempGraph);

  // Traverse the graph, level by level,
  while (start <= end) {
    // Get the node to visit and remove it from queue
    var node_id  = queue[start++];
    var node_ix  = layoutInfo.idToIndex[node_id];
    var node     = layoutInfo.layoutNodes[node_ix];
    var children = node.children;
    if (children.length > 0) {
    // Add children nodes as a new graph to graph set
    layoutInfo.graphSet.push(children);
    // Add children to que queue to be visited
    for (var i = 0; i < children.length; i++) {
      queue[++end] = children[i];
    }
    }
  }

  // Create indexToGraph map
  for (var i = 0; i < layoutInfo.graphSet.length; i++) {
    var graph = layoutInfo.graphSet[i];
    for (var j = 0; j < graph.length; j++) {
    var index = layoutInfo.idToIndex[graph[j]];
    layoutInfo.indexToGraph[index] = i;
    }
  }

  // Iterate over all edges, creating Layout Edges
  for (var i = 0; i < layoutInfo.edgeSize; i++) {
    var e = edges[i];
    var tempEdge = {};
    tempEdge.id       = e.data('id');
    tempEdge.sourceId = e.data('source');
    tempEdge.targetId = e.data('target');

    // Compute ideal length
    var idealLength = is.fn( options.idealEdgeLength ) ? options.idealEdgeLength.call( e, e ) : options.idealEdgeLength;
    var elasticity = is.fn( options.edgeElasticity ) ? options.edgeElasticity.call( e, e ) : options.edgeElasticity;

    // Check if it's an inter graph edge
    var sourceIx    = layoutInfo.idToIndex[tempEdge.sourceId];
    var targetIx    = layoutInfo.idToIndex[tempEdge.targetId];
    var sourceGraph = layoutInfo.indexToGraph[sourceIx];
    var targetGraph = layoutInfo.indexToGraph[targetIx];

    if (sourceGraph != targetGraph) {
      // Find lowest common graph ancestor
      var lca = findLCA(tempEdge.sourceId, tempEdge.targetId, layoutInfo);

      // Compute sum of node depths, relative to lca graph
      var lcaGraph = layoutInfo.graphSet[lca];
      var depth    = 0;

      // Source depth
      var tempNode = layoutInfo.layoutNodes[sourceIx];
      while ( -1 === lcaGraph.indexOf(tempNode.id) ) {
        tempNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[tempNode.parentId]];
        depth++;
      }

      // Target depth
      tempNode = layoutInfo.layoutNodes[targetIx];
      while ( -1 === lcaGraph.indexOf(tempNode.id) ) {
        tempNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[tempNode.parentId]];
        depth++;
      }

      // logDebug('LCA of nodes ' + tempEdge.sourceId + ' and ' + tempEdge.targetId +
        //  ". Index: " + lca + " Contents: " + lcaGraph.toString() +
        //  ". Depth: " + depth);

      // Update idealLength
      idealLength *= depth * options.nestingFactor;
    }

    tempEdge.idealLength = idealLength;
    tempEdge.elasticity = elasticity;

    layoutInfo.layoutEdges.push(tempEdge);
  }

  // Finally, return layoutInfo object
  return layoutInfo;
};


/**
 * @brief : This function finds the index of the lowest common
 *          graph ancestor between 2 nodes in the subtree
 *          (from the graph hierarchy induced tree) whose
 *          root is graphIx
 *
 * @arg node1: node1's ID
 * @arg node2: node2's ID
 * @arg layoutInfo: layoutInfo object
 *
 */
var findLCA = function(node1, node2, layoutInfo) {
  // Find their common ancester, starting from the root graph
  var res = findLCA_aux(node1, node2, 0, layoutInfo);
  if (2 > res.count) {
    // If aux function couldn't find the common ancester,
    // then it is the root graph
    return 0;
  } else {
    return res.graph;
  }
};


/**
 * @brief          : Auxiliary function used for LCA computation
 *
 * @arg node1      : node1's ID
 * @arg node2      : node2's ID
 * @arg graphIx    : subgraph index
 * @arg layoutInfo : layoutInfo object
 *
 * @return         : object of the form {count: X, graph: Y}, where:
 *                   X is the number of ancesters (max: 2) found in
 *                   graphIx (and it's subgraphs),
 *                   Y is the graph index of the lowest graph containing
 *                   all X nodes
 */
var findLCA_aux = function(node1, node2, graphIx, layoutInfo) {
  var graph = layoutInfo.graphSet[graphIx];
  // If both nodes belongs to graphIx
  if (-1 < graph.indexOf(node1) && -1 < graph.indexOf(node2)) {
    return {count:2, graph:graphIx};
  }

  // Make recursive calls for all subgraphs
  var c = 0;
  for (var i = 0; i < graph.length; i++) {
    var nodeId   = graph[i];
    var nodeIx   = layoutInfo.idToIndex[nodeId];
    var children = layoutInfo.layoutNodes[nodeIx].children;

    // If the node has no child, skip it
    if (0 === children.length) {
    continue;
    }

    var childGraphIx = layoutInfo.indexToGraph[layoutInfo.idToIndex[children[0]]];
    var result = findLCA_aux(node1, node2, childGraphIx, layoutInfo);
    if (0 === result.count) {
    // Neither node1 nor node2 are present in this subgraph
    continue;
    } else if (1 === result.count) {
    // One of (node1, node2) is present in this subgraph
    c++;
    if (2 === c) {
      // We've already found both nodes, no need to keep searching
      break;
    }
    } else {
    // Both nodes are present in this subgraph
    return result;
    }
  }

  return {count:c, graph:graphIx};
};


/**
 * @brief: printsLayoutInfo into js console
 *         Only used for debbuging
 */
var printLayoutInfo = function(layoutInfo) {
  /* jshint ignore:start */

  if (!DEBUG) {
    return;
  }
  console.debug("layoutNodes:");
  for (var i = 0; i < layoutInfo.nodeSize; i++) {
    var n = layoutInfo.layoutNodes[i];
    var s =
    "\nindex: "     + i +
    "\nId: "        + n.id +
    "\nChildren: "  + n.children.toString() +
    "\nparentId: "  + n.parentId  +
    "\npositionX: " + n.positionX +
    "\npositionY: " + n.positionY +
    "\nOffsetX: " + n.offsetX +
    "\nOffsetY: " + n.offsetY +
    "\npadLeft: " + n.padLeft +
    "\npadRight: " + n.padRight +
    "\npadTop: " + n.padTop +
    "\npadBottom: " + n.padBottom;

    console.debug(s);
  }

  console.debug('idToIndex');
  for (var i in layoutInfo.idToIndex) {
    console.debug("Id: " + i + "\nIndex: " + layoutInfo.idToIndex[i]);
  }

  console.debug('Graph Set');
  var set = layoutInfo.graphSet;
  for (var i = 0; i < set.length; i ++) {
    console.debug("Set : " + i + ": " + set[i].toString());
  }

  var s = 'IndexToGraph';
  for (var i = 0; i < layoutInfo.indexToGraph.length; i ++) {
    s += "\nIndex : " + i + " Graph: "+ layoutInfo.indexToGraph[i];
  }
  console.debug(s);

  s = 'Layout Edges';
  for (var i = 0; i < layoutInfo.layoutEdges.length; i++) {
    var e = layoutInfo.layoutEdges[i];
    s += "\nEdge Index: " + i + " ID: " + e.id +
    " SouceID: " + e.sourceId + " TargetId: " + e.targetId +
    " Ideal Length: " + e.idealLength;
  }
  console.debug(s);

  s =  "nodeSize: " + layoutInfo.nodeSize;
  s += "\nedgeSize: " + layoutInfo.edgeSize;
  s += "\ntemperature: " + layoutInfo.temperature;
  console.debug(s);

  return;
  /* jshint ignore:end */
};


/**
 * @brief : Randomizes the position of all nodes
 */
var randomizePositions = function(layoutInfo, cy) {
  var width     = layoutInfo.clientWidth;
  var height    = layoutInfo.clientHeight;

  for (var i = 0; i < layoutInfo.nodeSize; i++) {
    var n = layoutInfo.layoutNodes[i];

    // No need to randomize compound nodes or locked nodes
    if ( 0 === n.children.length && !n.isLocked ) {
      n.positionX = Math.random() * width;
      n.positionY = Math.random() * height;
    }
  }
};


/**
 * @brief          : Updates the positions of nodes in the network
 * @arg layoutInfo : LayoutInfo object
 * @arg cy         : Cytoscape object
 * @arg options    : Layout options
 */
var refreshPositions = function(layoutInfo, cy, options) {
  // var s = 'Refreshing positions';
  // logDebug(s);

  var layout = options.layout;
  var nodes = options.eles.nodes();
  var bb = layoutInfo.boundingBox;
  var coseBB = { x1: Infinity, x2: -Infinity, y1: Infinity, y2: -Infinity };

  if( options.boundingBox ){
    nodes.forEach(function( node ){
      var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[node.data('id')]];

      coseBB.x1 = Math.min( coseBB.x1, lnode.positionX );
      coseBB.x2 = Math.max( coseBB.x2, lnode.positionX );

      coseBB.y1 = Math.min( coseBB.y1, lnode.positionY );
      coseBB.y2 = Math.max( coseBB.y2, lnode.positionY );
    });

    coseBB.w = coseBB.x2 - coseBB.x1;
    coseBB.h = coseBB.y2 - coseBB.y1;
  }

  nodes.positions(function(i, ele) {
    var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[ele.data('id')]];
    // s = "Node: " + lnode.id + ". Refreshed position: (" +
    // lnode.positionX + ", " + lnode.positionY + ").";
    // logDebug(s);

    if( options.boundingBox ){ // then add extra bounding box constraint
      var pctX = (lnode.positionX - coseBB.x1) / coseBB.w;
      var pctY = (lnode.positionY - coseBB.y1) / coseBB.h;

      return {
        x: bb.x1 + pctX * bb.w,
        y: bb.y1 + pctY * bb.h
      };
    } else {
      return {
        x: lnode.positionX,
        y: lnode.positionY
      };
    }
  });

  // Trigger layoutReady only on first call
  if (true !== layoutInfo.ready) {
    // s = 'Triggering layoutready';
    // logDebug(s);
    layoutInfo.ready = true;
    layout.one('layoutready', options.ready);
    layout.trigger({ type: 'layoutready', layout: this });
  }
};

/**
 * @brief : Logs a debug message in JS console, if DEBUG is ON
 */
// var logDebug = function(text) {
//   if (DEBUG) {
//     console.debug(text);
//   }
// };

module.exports = CoseLayout;

},{"../../is":77,"../../math":79,"../../thread":92,"../../util":94}],49:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../../util');
var math = _dereq_('../../math');

var defaults = {
  fit: true, // whether to fit the viewport to the graph
  padding: 30, // padding used on fit
  boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
  avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space
  avoidOverlapPadding: 10, // extra spacing around nodes when avoidOverlap: true
  condense: false, // uses all available space on false, uses minimal space on true
  rows: undefined, // force num of rows in the grid
  cols: undefined, // force num of columns in the grid
  position: function( node ){}, // returns { row, col } for element
  sort: undefined, // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') }
  animate: false, // whether to transition the node positions
  animationDuration: 500, // duration of animation in ms if enabled
  animationEasing: undefined, // easing of animation if enabled
  ready: undefined, // callback on layoutready
  stop: undefined // callback on layoutstop
};

function GridLayout( options ){
  this.options = util.extend({}, defaults, options);
}

GridLayout.prototype.run = function(){
  var params = this.options;
  var options = params;

  var cy = params.cy;
  var eles = options.eles;
  var nodes = eles.nodes().not(':parent');

  if( options.sort ){
    nodes = nodes.sort( options.sort );
  }

  var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : {
    x1: 0, y1: 0, w: cy.width(), h: cy.height()
  } );

  if( bb.h === 0 || bb.w === 0){
    nodes.layoutPositions(this, options, function(){
      return { x: bb.x1, y: bb.y1 };
    });

  } else {

    // width/height * splits^2 = cells where splits is number of times to split width
    var cells = nodes.size();
    var splits = Math.sqrt( cells * bb.h/bb.w );
    var rows = Math.round( splits );
    var cols = Math.round( bb.w/bb.h * splits );

    var small = function(val){
      if( val == null ){
        return Math.min(rows, cols);
      } else {
        var min = Math.min(rows, cols);
        if( min == rows ){
          rows = val;
        } else {
          cols = val;
        }
      }
    };

    var large = function(val){
      if( val == null ){
        return Math.max(rows, cols);
      } else {
        var max = Math.max(rows, cols);
        if( max == rows ){
          rows = val;
        } else {
          cols = val;
        }
      }
    };

    var oRows = options.rows;
    var oCols = options.cols != null ? options.cols : options.columns;

    // if rows or columns were set in options, use those values
    if( oRows != null && oCols != null ){
      rows = oRows;
      cols = oCols;
    } else if( oRows != null && oCols == null ){
      rows = oRows;
      cols = Math.ceil( cells / rows );
    } else if( oRows == null && oCols != null ){
      cols = oCols;
      rows = Math.ceil( cells / cols );
    }

    // otherwise use the automatic values and adjust accordingly

    // if rounding was up, see if we can reduce rows or columns
    else if( cols * rows > cells ){
      var sm = small();
      var lg = large();

      // reducing the small side takes away the most cells, so try it first
      if( (sm - 1) * lg >= cells ){
        small(sm - 1);
      } else if( (lg - 1) * sm >= cells ){
        large(lg - 1);
      }
    } else {

      // if rounding was too low, add rows or columns
      while( cols * rows < cells ){
        var sm = small();
        var lg = large();

        // try to add to larger side first (adds less in multiplication)
        if( (lg + 1) * sm >= cells ){
          large(lg + 1);
        } else {
          small(sm + 1);
        }
      }
    }

    var cellWidth = bb.w / cols;
    var cellHeight = bb.h / rows;

    if( options.condense ){
      cellWidth = 0;
      cellHeight = 0;
    }

    if( options.avoidOverlap ){
      for( var i = 0; i < nodes.length; i++ ){
        var node = nodes[i];
        var pos = node._private.position;

        if( pos.x == null || pos.y == null ){ // for bb
          pos.x = 0;
          pos.y = 0;
        }

        var nbb = node.boundingBox();
        var p = options.avoidOverlapPadding;

        var w = nbb.w + p;
        var h = nbb.h + p;

        cellWidth = Math.max( cellWidth, w );
        cellHeight = Math.max( cellHeight, h );
      }
    }

    var cellUsed = {}; // e.g. 'c-0-2' => true

    var used = function(row, col){
      return cellUsed['c-' + row + '-' + col] ? true : false;
    };

    var use = function(row, col){
      cellUsed['c-' + row + '-' + col] = true;
    };

    // to keep track of current cell position
    var row = 0;
    var col = 0;
    var moveToNextCell = function(){
      col++;
      if( col >= cols ){
        col = 0;
        row++;
      }
    };

    // get a cache of all the manual positions
    var id2manPos = {};
    for( var i = 0; i < nodes.length; i++ ){
      var node = nodes[i];
      var rcPos = options.position( node );

      if( rcPos && (rcPos.row !== undefined || rcPos.col !== undefined) ){ // must have at least row or col def'd
        var pos = {
          row: rcPos.row,
          col: rcPos.col
        };

        if( pos.col === undefined ){ // find unused col
          pos.col = 0;

          while( used(pos.row, pos.col) ){
            pos.col++;
          }
        } else if( pos.row === undefined ){ // find unused row
          pos.row = 0;

          while( used(pos.row, pos.col) ){
            pos.row++;
          }
        }

        id2manPos[ node.id() ] = pos;
        use( pos.row, pos.col );
      }
    }

    var getPos = function(i, element){
      var x, y;

      if( element.locked() || element.isFullAutoParent() ){
        return false;
      }

      // see if we have a manual position set
      var rcPos = id2manPos[ element.id() ];
      if( rcPos ){
        x = rcPos.col * cellWidth + cellWidth/2 + bb.x1;
        y = rcPos.row * cellHeight + cellHeight/2 + bb.y1;

      } else { // otherwise set automatically

        while( used(row, col) ){
          moveToNextCell();
        }

        x = col * cellWidth + cellWidth/2 + bb.x1;
        y = row * cellHeight + cellHeight/2 + bb.y1;
        use( row, col );

        moveToNextCell();
      }

      return { x: x, y: y };

    };

    nodes.layoutPositions( this, options, getPos );
  }

  return this; // chaining

};

module.exports = GridLayout;

},{"../../math":79,"../../util":94}],50:[function(_dereq_,module,exports){
'use strict';

module.exports = [
  { name: 'breadthfirst', impl: _dereq_('./breadthfirst') },
  { name: 'circle', impl: _dereq_('./circle') },
  { name: 'concentric',impl: _dereq_('./concentric') },
  { name: 'cose', impl: _dereq_('./cose') },
  { name: 'grid', impl: _dereq_('./grid') },
  { name: 'null', impl: _dereq_('./null') },
  { name: 'preset', impl: _dereq_('./preset') },
  { name: 'random', impl: _dereq_('./random') }
];

},{"./breadthfirst":45,"./circle":46,"./concentric":47,"./cose":48,"./grid":49,"./null":51,"./preset":52,"./random":53}],51:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../../util');

// default layout options
var defaults = {
  ready: function(){}, // on layoutready
  stop: function(){} // on layoutstop
};

// constructor
// options : object containing layout options
function NullLayout( options ){
  this.options = util.extend({}, defaults, options);
}

// runs the layout
NullLayout.prototype.run = function(){
  var options = this.options;
  var eles = options.eles; // elements to consider in the layout
  var layout = this;

  // cy is automatically populated for us in the constructor
  var cy = options.cy; // jshint ignore:line

  layout.trigger('layoutstart');

  // puts all nodes at (0, 0)
  eles.nodes().positions(function(){
    return {
      x: 0,
      y: 0
    };
  });

  // trigger layoutready when each node has had its position set at least once
  layout.one('layoutready', options.ready);
  layout.trigger('layoutready');

  // trigger layoutstop when the layout stops (e.g. finishes)
  layout.one('layoutstop', options.stop);
  layout.trigger('layoutstop');

  return this; // chaining
};

// called on continuous layouts to stop them before they finish
NullLayout.prototype.stop = function(){
  return this; // chaining
};

module.exports = NullLayout;

},{"../../util":94}],52:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../../util');
var is = _dereq_('../../is');

var defaults = {
  positions: undefined, // map of (node id) => (position obj); or function(node){ return somPos; }
  zoom: undefined, // the zoom level to set (prob want fit = false if set)
  pan: undefined, // the pan level to set (prob want fit = false if set)
  fit: true, // whether to fit to viewport
  padding: 30, // padding on fit
  animate: false, // whether to transition the node positions
  animationDuration: 500, // duration of animation in ms if enabled
  animationEasing: undefined, // easing of animation if enabled
  ready: undefined, // callback on layoutready
  stop: undefined // callback on layoutstop
};

function PresetLayout( options ){
  this.options = util.extend({}, defaults, options);
}

PresetLayout.prototype.run = function(){
  var options = this.options;
  var eles = options.eles;

  var nodes = eles.nodes();
  var posIsFn = is.fn( options.positions );

  function getPosition(node){
    if( options.positions == null ){
      return null;
    }

    if( posIsFn ){
      return options.positions.apply( node, [ node ] );
    }

    var pos = options.positions[node._private.data.id];

    if( pos == null ){
      return null;
    }

    return pos;
  }

  nodes.layoutPositions(this, options, function(i, node){
    var position = getPosition(node);

    if( node.locked() || position == null ){
      return false;
    }

    return position;
  });

  return this; // chaining
};

module.exports = PresetLayout;

},{"../../is":77,"../../util":94}],53:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../../util');
var math = _dereq_('../../math');

var defaults = {
  fit: true, // whether to fit to viewport
  padding: 30, // fit padding
  boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
  animate: false, // whether to transition the node positions
  animationDuration: 500, // duration of animation in ms if enabled
  animationEasing: undefined, // easing of animation if enabled
  ready: undefined, // callback on layoutready
  stop: undefined // callback on layoutstop
};

function RandomLayout( options ){
  this.options = util.extend({}, defaults, options);
}

RandomLayout.prototype.run = function(){
  var options = this.options;
  var cy = options.cy;
  var eles = options.eles;
  var nodes = eles.nodes().not(':parent');

  var bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : {
    x1: 0, y1: 0, w: cy.width(), h: cy.height()
  } );

  var getPos = function( i, node ){
    return {
      x: bb.x1 + Math.round( Math.random() * bb.w ),
      y: bb.y1 + Math.round( Math.random() * bb.h )
    };
  };

  nodes.layoutPositions( this, options, getPos );

  return this; // chaining
};

module.exports = RandomLayout;

},{"../../math":79,"../../util":94}],54:[function(_dereq_,module,exports){
'use strict';

var math = _dereq_('../../../math');
var is = _dereq_('../../../is');
var util = _dereq_('../../../util');

var BRp = {};

BRp.arrowShapeHeight = 0.3;

BRp.registerArrowShapes = function(){
  var arrowShapes = this.arrowShapes = {};
  var renderer = this;

  // Contract for arrow shapes:
  // 0, 0 is arrow tip
  // (0, 1) is direction towards node
  // (1, 0) is right
  //
  // functional api:
  // collide: check x, y in shape
  // roughCollide: called before collide, no false negatives
  // draw: draw
  // spacing: dist(arrowTip, nodeBoundary)
  // gap: dist(edgeTip, nodeBoundary), edgeTip may != arrowTip

  var bbCollide = function( x, y, size, angle, translation, padding ){
    var x1 = translation.x - size/2 - padding;
    var x2 = translation.x + size/2 + padding;
    var y1 = translation.y - size/2 - padding;
    var y2 = translation.y + size/2 + padding;

    var inside = (x1 <= x && x <= x2) && (y1 <= y && y <= y2);

    return inside;
  };

  var transform = function( x, y, size, angle, translation ){
    var xRotated = x * Math.cos(angle) - y * Math.sin(angle);
    var yRotated = x * Math.sin(angle) + y * Math.cos(angle);

    var xScaled = xRotated * size;
    var yScaled = yRotated * size;

    var xTranslated = xScaled + translation.x;
    var yTranslated = yScaled + translation.y;

    return {
      x: xTranslated,
      y: yTranslated
    };
  };

  var transformPoints = function( pts, size, angle, translation ){
    var retPts = [];

    for( var i = 0; i < pts.length; i += 2 ){
      var x = pts[i];
      var y = pts[i + 1];

      retPts.push( transform(x, y, size, angle, translation) );
    }

    return retPts;
  };

  var pointsToArr = function( pts ){
    var ret = [];

    for( var i = 0; i < pts.length; i++ ){
      var p = pts[i];

      ret.push( p.x, p.y );
    }

    return ret;
  };

  var defineArrowShape = function( name, defn ){
    if( is.string(defn) ){
      defn = arrowShapes[ defn ];
    }

    arrowShapes[ name ] = util.extend( {
      name: name,

      points: [
        -0.15, -0.3,
        0.15, -0.3,
        0.15, 0.3,
        -0.15, 0.3
      ],

      collide: function( x, y, size, angle, translation, padding ){
        var points = pointsToArr( transformPoints( this.points, size + 2*padding, angle, translation ) );
        var inside = math.pointInsidePolygonPoints( x, y, points );

        return inside;
      },

      roughCollide: bbCollide,

      draw: function( context, size, angle, translation ){
        var points = transformPoints( this.points, size, angle, translation );

        renderer.arrowShapeImpl('polygon')( context, points );
      },

      spacing: function( edge ){
        return 0;
      },

      gap: function( edge ){
        return edge._private.style['width'].pfValue * 2;
      }
    }, defn );
  };

  defineArrowShape( 'none', {
    collide: util.falsify,

    roughCollide: util.falsify,

    draw: util.noop,

    spacing: util.zeroify,

    gap: util.zeroify
  } );

  defineArrowShape( 'triangle', {
    points: [
      -0.15, -0.3,
      0, 0,
      0.15, -0.3
    ]
  } );

  defineArrowShape( 'arrow', 'triangle' );

  defineArrowShape( 'triangle-backcurve', {
    points: arrowShapes['triangle'].points,

    controlPoint: [ 0, -0.15 ],

    roughCollide: bbCollide,

    draw: function( context, size, angle, translation ){
      var ptsTrans = transformPoints( this.points, size, angle, translation );
      var ctrlPt = this.controlPoint;
      var ctrlPtTrans = transform( ctrlPt[0], ctrlPt[1], size, angle, translation );

      renderer.arrowShapeImpl( this.name )( context, ptsTrans, ctrlPtTrans );
    },

    gap: function( edge ){
      return edge._private.style['width'].pfValue;
    }
  } );


  defineArrowShape( 'triangle-tee', {
    points: [
      -0.15, -0.3,
      0, 0,
      0.15, -0.3,
      -0.15, -0.3
    ],

    pointsTee: [
      -0.15, -0.4,
      -0.15, -0.5,
      0.15, -0.5,
      0.15, -0.4
    ],

    collide: function( x, y, size, angle, translation, padding ){
      var triPts = pointsToArr( transformPoints( this.points, size + 2*padding, angle, translation ) );
      var teePts = pointsToArr( transformPoints( this.pointsTee, size + 2*padding, angle, translation ) );

      var inside = math.pointInsidePolygonPoints( x, y, triPts ) || math.pointInsidePolygonPoints( x, y, teePts );

      return inside;
    },

    draw: function( context, size, angle, translation ){
      var triPts = transformPoints( this.points, size, angle, translation );
      var teePts = transformPoints( this.pointsTee, size, angle, translation );

      renderer.arrowShapeImpl( this.name )( context, triPts, teePts );
    }
  } );

  defineArrowShape( 'vee', {
    points: [
      -0.15, -0.3,
      0, 0,
      0.15, -0.3,
      0, -0.15
    ],

    gap: function( edge ){
      return edge._private.style['width'].pfValue;
    }
  } );

  defineArrowShape( 'half-triangle-overshot', {
    points: [
      0, -0.25,
      -0.5, -0.25,
      0.5, 0.25
    ],

    leavePathOpen: true,

    matchEdgeWidth: true
  } );

  defineArrowShape( 'circle', {
    radius: 0.15,

    collide: function( x, y, size, angle, translation, padding ){
      var t = translation;
      var inside = ( Math.pow(t.x - x, 2) + Math.pow(t.y - y, 2) <= Math.pow((size + 2*padding) * this.radius, 2) );

      return inside;
    },

    draw: function( context, size, angle, translation ){
      renderer.arrowShapeImpl( this.name )( context, translation.x, translation.y, this.radius * size );
    },

    spacing: function( edge ){
      return renderer.getArrowWidth(edge._private.style['width'].pfValue)
        * this.radius;
    }
  } );

  defineArrowShape( 'inhibitor', {
    points: [
      -0.25, 0,
      -0.25, -0.1,
      0.25, -0.1,
      0.25, 0
    ],

    spacing: function( edge ){
      return 1;
    },

    gap: function( edge ){
      return 1;
    }
  } );

  defineArrowShape( 'tee', 'inhibitor' );

  defineArrowShape( 'square', {
    points: [
      -0.15, 0.00,
      0.15, 0.00,
      0.15, -0.3,
      -0.15, -0.3
    ]
  } );

  defineArrowShape( 'diamond', {
    points: [
      -0.15, -0.15,
      0, -0.3,
      0.15, -0.15,
      0, 0
    ],

    gap: function( edge ){
      return edge._private.style['width'].pfValue;
    }
  } );

};

module.exports = BRp;

},{"../../../is":77,"../../../math":79,"../../../util":94}],55:[function(_dereq_,module,exports){
'use strict';

var BRp = {};

var delEleCache = function( r ){
  r.eleEache = null;
};

var getEleCache = function( r ){
  if( !r.eleEache ){
    r.eleEache = {
      nodes: r.cy.nodes(),
      edges: r.cy.edges()
    };
  }

  return r.eleEache;
};

BRp.getCachedElements = function(){
  return getEleCache( this );
};

BRp.getCachedNodes = function(){
  return getEleCache( this ).nodes;
};

BRp.getCachedEdges = function(){
  return getEleCache( this ).edges;
};

BRp.updateElementsCache = function(){
  var r = this;

  delEleCache( r );

  return getEleCache( r );
};

module.exports = BRp;

},{}],56:[function(_dereq_,module,exports){
'use strict';

var math = _dereq_('../../../math');
var is = _dereq_('../../../is');
var zIndexSort = _dereq_('../../../collection/zsort');

var BRp = {};

// Project mouse
BRp.projectIntoViewport = function(clientX, clientY) {
  var offsets = this.findContainerClientCoords();
  var offsetLeft = offsets[0];
  var offsetTop = offsets[1];

  var x = clientX - offsetLeft;
  var y = clientY - offsetTop;

  x -= this.cy.pan().x; y -= this.cy.pan().y; x /= this.cy.zoom(); y /= this.cy.zoom();
  return [x, y];
};

BRp.findContainerClientCoords = function() {
  var container = this.container;

  var bb = this.containerBB = this.containerBB || container.getBoundingClientRect();

  return [bb.left, bb.top, bb.right - bb.left, bb.bottom - bb.top];
};

BRp.invalidateContainerClientCoordsCache = function(){
  this.containerBB = null;
};

// Find nearest element
BRp.findNearestElement = function(x, y, visibleElementsOnly, isTouch){
  var self = this;
  var r = this;
  var eles = r.getCachedZSortedEles();
  var near = [];
  var zoom = r.cy.zoom();
  var hasCompounds = r.cy.hasCompoundNodes();
  var edgeThreshold = (isTouch ? 24 : 8) / zoom;
  var nodeThreshold = (isTouch ? 8 : 2) / zoom;
  var labelThreshold = (isTouch ? 8 : 2) / zoom;

  function checkNode(node){
    var _p = node._private;

    if( _p.style['events'].strValue === 'no' ){ return; }

    var width = node.outerWidth() + 2*nodeThreshold;
    var height = node.outerHeight() + 2*nodeThreshold;
    var hw = width/2;
    var hh = height/2;
    var pos = _p.position;

    if(
      pos.x - hw <= x && x <= pos.x + hw // bb check x
        &&
      pos.y - hh <= y && y <= pos.y + hh // bb check y
    ){
      var visible = !visibleElementsOnly || ( node.visible() && !node.transparent() );

      // exit early if invisible edge and must be visible
      if( visibleElementsOnly && !visible ){
        return;
      }

      var shape = r.nodeShapes[ self.getNodeShape(node) ];

      if(
        shape.checkPoint(x, y, 0, width, height, pos.x, pos.y)
      ){
        near.push( node );
      }

    }
  }

  function checkEdge(edge){
    var _p = edge._private;

    if( _p.style['events'].strValue === 'no' ){ return; }

    var rs = _p.rscratch;
    var style = _p.style;
    var width = style['width'].pfValue/2 + edgeThreshold; // more like a distance radius from centre
    var widthSq = width * width;
    var width2 = width * 2;
    var src = _p.source;
    var tgt = _p.target;
    var inEdgeBB = false;
    var sqDist;

    // exit early if invisible edge and must be visible
    var passedVisibilityCheck;
    var passesVisibilityCheck = function(){
      if( passedVisibilityCheck !== undefined ){
        return passedVisibilityCheck;
      }

      if( !visibleElementsOnly ){
        passedVisibilityCheck = true;
        return true;
      }

      var visible = edge.visible() && !edge.transparent();
      if( visible ){
        passedVisibilityCheck = true;
        return true;
      }

      passedVisibilityCheck = false;
      return false;
    };

    if( rs.edgeType === 'segments' || rs.edgeType === 'straight' || rs.edgeType === 'haystack' ){
      var pts = rs.allpts;

      for( var i = 0; i + 3 < pts.length; i += 2 ){
        if(
          (inEdgeBB = math.inLineVicinity(x, y, pts[i], pts[i+1], pts[i+2], pts[i+3], width2))
            && passesVisibilityCheck() &&
          widthSq > ( sqDist = math.sqDistanceToFiniteLine(x, y, pts[i], pts[i+1], pts[i+2], pts[i+3]) )
        ){
          near.push( edge );
        }
      }

    } else if( rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound' ){
      var pts = rs.allpts;
      for( var i = 0; i + 5 < rs.allpts.length; i += 4 ){
        if(
          (inEdgeBB = math.inBezierVicinity(x, y, pts[i], pts[i+1], pts[i+2], pts[i+3], pts[i+4], pts[i+5], width2))
            && passesVisibilityCheck() &&
          (widthSq > (sqDist = math.sqDistanceToQuadraticBezier(x, y, pts[i], pts[i+1], pts[i+2], pts[i+3], pts[i+4], pts[i+5])) )
        ){
          near.push( edge );
        }
      }
    }

    // if we're close to the edge but didn't hit it, maybe we hit its arrows
    if( inEdgeBB && passesVisibilityCheck() && near.length === 0 || near[near.length - 1] !== edge ){
      var src = src || _p.source;
      var tgt = tgt || _p.target;

      var eWidth = style['width'].pfValue;
      var arSize = self.getArrowWidth( eWidth );

      var arrows = [
        { name: 'source', x: rs.arrowStartX, y: rs.arrowStartY, angle: rs.srcArrowAngle },
        { name: 'target', x: rs.arrowEndX, y: rs.arrowEndY, angle: rs.tgtArrowAngle },
        { name: 'mid-source', x: rs.midX, y: rs.midY, angle: rs.midsrcArrowAngle },
        { name: 'mid-target', x: rs.midX, y: rs.midY, angle: rs.midtgtArrowAngle }
      ];

      for( var i = 0; i < arrows.length; i++ ){
        var ar = arrows[i];
        var shape = r.arrowShapes[ style[ar.name+'-arrow-shape'].value ];

        if(
          shape.roughCollide(x, y, arSize, ar.angle, { x: ar.x, y: ar.y }, edgeThreshold)
           &&
          shape.collide(x, y, arSize, ar.angle, { x: ar.x, y: ar.y }, edgeThreshold)
        ){
          near.push( edge );
          break;
        }
      }
    }

    // for compound graphs, hitting edge may actually want a connected node instead (b/c edge may have greater z-index precedence)
    if( hasCompounds &&  near.length > 0 && near[ near.length - 1 ] === edge ){
      checkNode( src );
      checkNode( tgt );
    }
  }

  function checkLabel(ele){
    var _p = ele._private;
    var th = labelThreshold;

    if( _p.style['text-events'].strValue === 'no' ){ return; }

    // adjust bb w/ angle
    if( _p.group === 'edges' && _p.style['edge-text-rotation'].strValue === 'autorotate' ){

      var rstyle = _p.rstyle;
      var lw = rstyle.labelWidth + 2*th;
      var lh = rstyle.labelHeight + 2*th;
      var lx = rstyle.labelX;
      var ly = rstyle.labelY;

      var theta = _p.rscratch.labelAngle;
      var cos = Math.cos( theta );
      var sin = Math.sin( theta );

      var rotate = function( x, y ){
        x = x - lx;
        y = y - ly;

        return {
          x: x*cos - y*sin + lx,
          y: x*sin + y*cos + ly
        };
      };

      var lx1 = lx - lw/2;
      var lx2 = lx + lw/2;
      var ly1 = ly - lh/2;
      var ly2 = ly + lh/2;

      var px1y1 = rotate( lx1, ly1 );
      var px1y2 = rotate( lx1, ly2 );
      var px2y1 = rotate( lx2, ly1 );
      var px2y2 = rotate( lx2, ly2 );

      var points = [
        px1y1.x, px1y1.y,
        px2y1.x, px2y1.y,
        px2y2.x, px2y2.y,
        px1y2.x, px1y2.y
      ];

      if( math.pointInsidePolygonPoints( x, y, points ) ){
        near.push( ele );
      }

    } else {
      var bb = ele.boundingBox({
        includeLabels: true,
        includeNodes: false,
        includeEdges: false
      });

      // adjust bb w/ threshold
      bb.x1 -= th;
      bb.y1 -= th;
      bb.x2 += th;
      bb.y2 += th;
      bb.w = bb.x2 - bb.x1;
      bb.h = bb.y2 - bb.y1;

      if( math.inBoundingBox( bb, x, y ) ){
        near.push( ele );
      }
    }

  }

  for( var i = eles.length - 1; i >= 0; i-- ){ // reverse order for precedence
    var ele = eles[i];
    var _p = ele._private;

    if( near.length > 0 ){ break; } // since we check in z-order, first found is top and best result => exit early

    if( _p.group === 'nodes' ){
      checkNode( ele );

    } else  { // then edge
      checkEdge( ele );
    }

    checkLabel( ele );

  }


  if( near.length > 0 ){
    return near[ near.length - 1 ];
  } else {
    return null;
  }
};

// 'Give me everything from this box'
BRp.getAllInBox = function(x1, y1, x2, y2) {
  var nodes = this.getCachedNodes();
  var edges = this.getCachedEdges();
  var box = [];

  var x1c = Math.min(x1, x2);
  var x2c = Math.max(x1, x2);
  var y1c = Math.min(y1, y2);
  var y2c = Math.max(y1, y2);

  x1 = x1c;
  x2 = x2c;
  y1 = y1c;
  y2 = y2c;

  var boxBb = math.makeBoundingBox({
    x1: x1, y1: y1,
    x2: x2, y2: y2
  });

  for ( var i = 0; i < nodes.length; i++ ){
    var node = nodes[i];
    var nodeBb = node.boundingBox({
      includeNodes: true,
      includeEdges: false,
      includeLabels: false
    });

    if( math.boundingBoxesIntersect(boxBb, nodeBb) ){
      box.push(nodes[i]);
    }
  }

  for( var e = 0; e < edges.length; e++ ){
    var edge = edges[e];
    var _p = edge._private;
    var rs = _p.rscratch;

    if( rs.startX != null && rs.startY != null && !math.inBoundingBox( boxBb, rs.startX, rs.startY ) ){ continue; }
    if( rs.endX != null && rs.endY != null && !math.inBoundingBox( boxBb, rs.endX, rs.endY ) ){ continue; }

    if( rs.edgeType === 'bezier' || rs.edgeType === 'multibezier' || rs.edgeType === 'self' || rs.edgeType === 'compound' || rs.edgeType === 'segments' || rs.edgeType === 'haystack' ){

      var pts = _p.rstyle.bezierPts || _p.rstyle.linePts || _p.rstyle.haystackPts;
      var allInside = true;

      for( var i = 0; i < pts.length; i++ ){
        if( !math.pointInBoundingBox( boxBb, pts[i] ) ){
          allInside = false;
          break;
        }
      }

      if( allInside ){
        box.push( edge );
      }

    } else if( rs.edgeType === 'haystack' || rs.edgeType === 'straight' ){
      box.push( edge );
    }

  }

  return box;
};


/**
 * Returns the shape of the given node. If the height or width of the given node
 * is set to auto, the node is considered to be a compound.
 *
 * @param node          a node
 * @return {String}     shape of the node
 */
BRp.getNodeShape = function( node ){
  var r = this;
  var style = node._private.style;
  var shape = style['shape'].value;

  if( node.isParent() ){
    if( shape === 'rectangle' || shape === 'roundrectangle' ){
      return shape;
    } else {
      return 'rectangle';
    }
  }

  if( shape === 'polygon' ){
    var points = style['shape-polygon-points'].value;

    return r.nodeShapes.makePolygon( points ).name;
  }

  return shape;
};

BRp.updateCachedZSortedEles = function(){
  this.getCachedZSortedEles( true );
};

BRp.getCachedZSortedEles = function( forceRecalc ){
  var lastNodes = this.lastZOrderCachedNodes;
  var lastEdges = this.lastZOrderCachedEdges;
  var nodes = this.getCachedNodes();
  var edges = this.getCachedEdges();
  var eles = [];

  if( forceRecalc || !lastNodes || !lastEdges || lastNodes !== nodes || lastEdges !== edges ){
    //console.time('cachezorder')

    for( var i = 0; i < nodes.length; i++ ){
      var n = nodes[i];

      if( n.animated() || (n.visible() && !n.transparent()) ){
        eles.push( n );
      }
    }

    for( var i = 0; i < edges.length; i++ ){
      var e = edges[i];

      if( e.animated() || (e.visible() && !e.transparent()) ){
        eles.push( e );
      }
    }

    eles.sort( zIndexSort );
    this.cachedZSortedEles = eles;
    //console.log('make cache')

    //console.timeEnd('cachezorder')
  } else {
    eles = this.cachedZSortedEles;
    //console.log('read cache')
  }

  this.lastZOrderCachedNodes = nodes;
  this.lastZOrderCachedEdges = edges;

  return eles;
};

function pushBezierPts(edge, pts){
  var qbezierAt = function( p1, p2, p3, t ){ return math.qbezierAt(p1, p2, p3, t); };
  var _p = edge._private;
  var bpts = _p.rstyle.bezierPts;

  bpts.push({
    x: qbezierAt( pts[0], pts[2], pts[4], 0.05 ),
    y: qbezierAt( pts[1], pts[3], pts[5], 0.05 )
  });

  bpts.push({
    x: qbezierAt( pts[0], pts[2], pts[4], 0.25 ),
    y: qbezierAt( pts[1], pts[3], pts[5], 0.25 )
  });

  bpts.push({
    x: qbezierAt( pts[0], pts[2], pts[4], 0.4 ),
    y: qbezierAt( pts[1], pts[3], pts[5], 0.4 )
  });

  bpts.push({
    x: qbezierAt( pts[0], pts[2], pts[4], 0.5 ),
    y: qbezierAt( pts[1], pts[3], pts[5], 0.5 )
  });

  bpts.push({
    x: qbezierAt( pts[0], pts[2], pts[4], 0.6 ),
    y: qbezierAt( pts[1], pts[3], pts[5], 0.6 )
  });

  bpts.push({
    x: qbezierAt( pts[0], pts[2], pts[4], 0.75 ),
    y: qbezierAt( pts[1], pts[3], pts[5], 0.75 )
  });

  bpts.push({
    x: qbezierAt( pts[0], pts[2], pts[4], 0.95 ),
    y: qbezierAt( pts[1], pts[3], pts[5], 0.95 )
  });
}

BRp.projectLines = function( edge ){
  var _p = edge._private;
  var rs = _p.rscratch;
  var et = rs.edgeType;

  if( et === 'multibezier' ||  et === 'bezier' ||  et === 'self' ||  et === 'compound' ){
    var bpts = _p.rstyle.bezierPts = []; // jshint ignore:line

    for( var i = 0; i + 5 < rs.allpts.length; i += 4 ){
      pushBezierPts( edge, rs.allpts.slice(i, i+6) );
    }
  } else if(  et === 'segments' ){
    var lpts = _p.rstyle.linePts = [];

    for( var i = 0; i + 1 < rs.allpts.length; i += 2 ){
      lpts.push({
        x: rs.allpts[i],
        y: rs.allpts[i+1]
      });
    }
  } else if( et === 'haystack' ){
    var hpts = rs.haystackPts;

    _p.rstyle.haystackPts = [
      { x: hpts[0], y: hpts[1] },
      { x: hpts[2], y: hpts[3] }
    ];
  }
};

BRp.projectBezier = BRp.projectLines;

BRp.recalculateNodeLabelProjection = function( node ){
  var content = node._private.style['label'].strValue;
  if( !content || content.match(/^\s+$/) ){ return; }

  var textX, textY;
  var nodeWidth = node.outerWidth();
  var nodeHeight = node.outerHeight();
  var nodePos = node._private.position;
  var textHalign = node._private.style['text-halign'].strValue;
  var textValign = node._private.style['text-valign'].strValue;
  var rs = node._private.rscratch;
  var rstyle = node._private.rstyle;

  switch( textHalign ){
    case 'left':
      textX = nodePos.x - nodeWidth / 2;
      break;

    case 'right':
      textX = nodePos.x + nodeWidth / 2;
      break;

    default: // e.g. center
      textX = nodePos.x;
  }

  switch( textValign ){
    case 'top':
      textY = nodePos.y - nodeHeight / 2;
      break;

    case 'bottom':
      textY = nodePos.y + nodeHeight / 2;
      break;

    default: // e.g. middle
      textY = nodePos.y;
  }

  rs.labelX = textX;
  rs.labelY = textY;
  rstyle.labelX = textX;
  rstyle.labelY = textY;

  this.applyLabelDimensions( node );
};

BRp.recalculateEdgeLabelProjection = function( edge ){
  var content = edge._private.style['label'].strValue;
  if( !content || content.match(/^\s+$/) ){ return; }

  var textX, textY;
  var _p = edge._private;
  var rs = _p.rscratch;
  //var style = _p.style;
  var rstyle = _p.rstyle;

  textX = rs.midX;
  textY = rs.midY;

  // add center point to style so bounding box calculations can use it
  rs.labelX = textX;
  rs.labelY = textY;
  rstyle.labelX = textX;
  rstyle.labelY = textY;

  this.applyLabelDimensions( edge );
};

BRp.applyLabelDimensions = function( ele ){
  var rs = ele._private.rscratch;
  var rstyle = ele._private.rstyle;

  var text = this.getLabelText( ele );
  var labelDims = this.calculateLabelDimensions( ele, text );

  rstyle.labelWidth = labelDims.width;
  rs.labelWidth = labelDims.width;

  rstyle.labelHeight = labelDims.height;
  rs.labelHeight = labelDims.height;
};

BRp.getLabelText = function( ele ){
  var style = ele._private.style;
  var text = ele._private.style['label'].strValue;
  var textTransform = style['text-transform'].value;
  var rscratch = ele._private.rscratch;

  if (textTransform == 'none') {
  } else if (textTransform == 'uppercase') {
    text = text.toUpperCase();
  } else if (textTransform == 'lowercase') {
    text = text.toLowerCase();
  }

  if( style['text-wrap'].value === 'wrap' ){
    //console.log('wrap');

    // save recalc if the label is the same as before
    if( rscratch.labelWrapKey === rscratch.labelKey ){
      // console.log('wrap cache hit');
      return rscratch.labelWrapCachedText;
    }
    // console.log('wrap cache miss');

    var lines = text.split('\n');
    var maxW = style['text-max-width'].pfValue;
    var wrappedLines = [];

    for( var l = 0; l < lines.length; l++ ){
      var line = lines[l];
      var lineDims = this.calculateLabelDimensions( ele, line, 'line=' + line );
      var lineW = lineDims.width;

      if( lineW > maxW ){ // line is too long
        var words = line.split(/\s+/); // NB: assume collapsed whitespace into single space
        var subline = '';

        for( var w = 0; w < words.length; w++ ){
          var word = words[w];
          var testLine = subline.length === 0 ? word : subline + ' ' + word;
          var testDims = this.calculateLabelDimensions( ele, testLine, 'testLine=' + testLine );
          var testW = testDims.width;

          if( testW <= maxW ){ // word fits on current line
            subline += word + ' ';
          } else { // word starts new line
            wrappedLines.push( subline );
            subline = word + ' ';
          }
        }

        // if there's remaining text, put it in a wrapped line
        if( !subline.match(/^\s+$/) ){
          wrappedLines.push( subline );
        }
      } else { // line is already short enough
        wrappedLines.push( line );
      }
    } // for

    rscratch.labelWrapCachedLines = wrappedLines;
    rscratch.labelWrapCachedText = text = wrappedLines.join('\n');
    rscratch.labelWrapKey = rscratch.labelKey;

    // console.log(text)
  } // if wrap

  return text;
};

BRp.calculateLabelDimensions = function( ele, text, extraKey ){
  var r = this;
  var style = ele._private.style;
  var fStyle = style['font-style'].strValue;
  var size = style['font-size'].pfValue + 'px';
  var family = style['font-family'].strValue;
  // var variant = style['font-variant'].strValue;
  var weight = style['font-weight'].strValue;

  var cacheKey = ele._private.labelKey;

  if( extraKey ){
    cacheKey += '$@$' + extraKey;
  }

  var cache = r.labelDimCache || (r.labelDimCache = {});

  if( cache[cacheKey] ){
    return cache[cacheKey];
  }

  var div = this.labelCalcDiv;

  if( !div ){
    div = this.labelCalcDiv = document.createElement('div');
    document.body.appendChild( div );
  }

  var ds = div.style;

  // from ele style
  ds.fontFamily = family;
  ds.fontStyle = fStyle;
  ds.fontSize = size;
  // ds.fontVariant = variant;
  ds.fontWeight = weight;

  // forced style
  ds.position = 'absolute';
  ds.left = '-9999px';
  ds.top = '-9999px';
  ds.zIndex = '-1';
  ds.visibility = 'hidden';
  ds.pointerEvents = 'none';
  ds.padding = '0';
  ds.lineHeight = '1';

  if( style['text-wrap'].value === 'wrap' ){
    ds.whiteSpace = 'pre'; // so newlines are taken into account
  } else {
    ds.whiteSpace = 'normal';
  }

  // put label content in div
  div.textContent = text;

  cache[cacheKey] = {
    width: div.clientWidth,
    height: div.clientHeight
  };

  return cache[cacheKey];
};

BRp.recalculateRenderedStyle = function( eles ){
  var edges = [];
  var nodes = [];
  var handledEdge = {};

  for( var i = 0; i < eles.length; i++ ){
    var ele = eles[i];
    var _p = ele._private;
    var style = _p.style;
    var rs = _p.rscratch;
    var rstyle = _p.rstyle;
    var id = _p.data.id;
    var bbStyleSame = rs.boundingBoxKey != null && _p.boundingBoxKey === rs.boundingBoxKey;
    var labelStyleSame = rs.labelKey != null && _p.labelKey === rs.labelKey;
    var styleSame = bbStyleSame && labelStyleSame;

    if( _p.group === 'nodes' ){
      var pos = _p.position;
      var posSame = rstyle.nodeX != null && rstyle.nodeY != null && pos.x === rstyle.nodeX && pos.y === rstyle.nodeY;
      var wSame = rstyle.nodeW != null && rstyle.nodeW === style['width'].pfValue;
      var hSame = rstyle.nodeH != null && rstyle.nodeH === style['height'].pfValue;

      if( !posSame || !styleSame || !wSame || !hSame ){
        nodes.push( ele );
      }

      rstyle.nodeX = pos.x;
      rstyle.nodeY = pos.y;
      rstyle.nodeW = style['width'].pfValue;
      rstyle.nodeH = style['height'].pfValue;
    } else { // edges

      var srcPos = _p.source._private.position;
      var tgtPos = _p.target._private.position;
      var srcSame = rstyle.srcX != null && rstyle.srcY != null && srcPos.x === rstyle.srcX && srcPos.y === rstyle.srcY;
      var tgtSame = rstyle.tgtX != null && rstyle.tgtY != null && tgtPos.x === rstyle.tgtX && tgtPos.y === rstyle.tgtY;
      var positionsSame = srcSame && tgtSame;

      if( !positionsSame || !styleSame ){
        if( rs.edgeType === 'bezier' || rs.edgeType === 'straight' || rs.edgeType === 'self' || rs.edgeType === 'compound' ){
          if( !handledEdge[ id ] ){
            edges.push( ele );
            handledEdge[ id ] = true;

            var parallelEdges = ele.parallelEdges();
            for( var i = 0; i < parallelEdges.length; i++ ){
              var pEdge = parallelEdges[i];
              var pId = pEdge._private.data.id;

              if( !handledEdge[ pId ] ){
                edges.push( pEdge );
                handledEdge[ pId ] = true;
              }

            }
          }
        } else {
          edges.push( ele );
        }
      } // if positions diff

      // update rstyle positions
      rstyle.srcX = srcPos.x;
      rstyle.srcY = srcPos.y;
      rstyle.tgtX = tgtPos.x;
      rstyle.tgtY = tgtPos.y;

    } // if edges

    rs.boundingBoxKey = _p.boundingBoxKey;
    rs.labelKey = _p.labelKey;
  }

  this.recalculateEdgeProjections( edges );
  this.recalculateLabelProjections( nodes, edges );
};

BRp.recalculateLabelProjections = function( nodes, edges ){
  for( var i = 0; i < nodes.length; i++ ){
    this.recalculateNodeLabelProjection( nodes[i] );
  }

  for( var i = 0; i < edges.length; i++ ){
    this.recalculateEdgeLabelProjection( edges[i] );
  }
};

BRp.recalculateEdgeProjections = function( edges ){
  this.findEdgeControlPoints( edges );
};


// Find edge control points
BRp.findEdgeControlPoints = function(edges) {
  if( !edges || edges.length === 0 ){ return; }

  var r = this;
  var cy = r.cy;
  var hasCompounds = cy.hasCompoundNodes();
  var hashTable = {};
  var pairIds = [];
  var haystackEdges = [];
  var autorotateEdges = [];

  // create a table of edge (src, tgt) => list of edges between them
  var pairId;
  for (var i = 0; i < edges.length; i++){
    var edge = edges[i];
    var _p = edge._private;
    var data = _p.data;
    var style = _p.style;
    var curveStyle = style['curve-style'].value;
    var edgeIsUnbundled = curveStyle === 'unbundled-bezier' || curveStyle === 'segments';

    // ignore edges who are not to be displayed
    // they shouldn't take up space
    if( style.display.value === 'none' ){
      continue;
    }

    if( style['edge-text-rotation'].strValue === 'autorotate' ){
      autorotateEdges.push( edge );
    }

    if( curveStyle === 'haystack' ){
      haystackEdges.push( edge );
      continue;
    }

    var srcId = data.source;
    var tgtId = data.target;

    pairId = srcId > tgtId ?
      tgtId + '$-$' + srcId :
      srcId + '$-$' + tgtId ;

    if( edgeIsUnbundled ){
      pairId = 'unbundled' + '$-$' + data.id;
    }

    if( hashTable[pairId] == null ){
      hashTable[pairId] = [];
      pairIds.push( pairId );
    }

    hashTable[pairId].push( edge );

    if( edgeIsUnbundled ){
      hashTable[pairId].hasUnbundled = true;
    }
  }

  var src, tgt, src_p, tgt_p, srcPos, tgtPos, srcW, srcH, tgtW, tgtH, srcShape, tgtShape;
  var vectorNormInverse;
  var badBezier;

  // for each pair (src, tgt), create the ctrl pts
  // Nested for loop is OK; total number of iterations for both loops = edgeCount
  for (var p = 0; p < pairIds.length; p++) {
    pairId = pairIds[p];
    var pairEdges = hashTable[pairId];

    // for each pair id, the edges should be sorted by index
    pairEdges.sort(function(edge1, edge2){
      return edge1._private.index - edge2._private.index;
    });

    src = pairEdges[0]._private.source;
    tgt = pairEdges[0]._private.target;

    src_p = src._private;
    tgt_p = tgt._private;

    // make sure src/tgt distinction is consistent
    // (src/tgt in this case are just for ctrlpts and don't actually have to be true src/tgt)
    if( src_p.data.id > tgt_p.data.id ){
      var temp = src;
      src = tgt;
      tgt = temp;
    }

    srcPos = src_p.position;
    tgtPos = tgt_p.position;

    srcW = src.outerWidth();
    srcH = src.outerHeight();

    tgtW = tgt.outerWidth();
    tgtH = tgt.outerHeight();

    srcShape = r.nodeShapes[ this.getNodeShape(src) ];
    tgtShape = r.nodeShapes[ this.getNodeShape(tgt) ];

    badBezier = false;


    if( (pairEdges.length > 1 && src !== tgt) || pairEdges.hasUnbundled ){

      // pt outside src shape to calc distance/displacement from src to tgt
      var srcOutside = srcShape.intersectLine(
        srcPos.x,
        srcPos.y,
        srcW,
        srcH,
        tgtPos.x,
        tgtPos.y,
        0
      );

      // pt outside tgt shape to calc distance/displacement from src to tgt
      var tgtOutside = tgtShape.intersectLine(
        tgtPos.x,
        tgtPos.y,
        tgtW,
        tgtH,
        srcPos.x,
        srcPos.y,
        0
      );

      var midptSrcPts = {
        x1: srcOutside[0],
        x2: tgtOutside[0],
        y1: srcOutside[1],
        y2: tgtOutside[1]
      };

      var dy = ( tgtOutside[1] - srcOutside[1] );
      var dx = ( tgtOutside[0] - srcOutside[0] );
      var l = Math.sqrt( dx*dx + dy*dy );

      var vector = {
        x: dx,
        y: dy
      };

      var vectorNorm = {
        x: vector.x/l,
        y: vector.y/l
      };
      vectorNormInverse = {
        x: -vectorNorm.y,
        y: vectorNorm.x
      };


      // if src intersection is inside tgt or tgt intersection is inside src, then no ctrl pts to draw
      if(
        tgtShape.checkPoint( srcOutside[0], srcOutside[1], 0, tgtW, tgtH, tgtPos.x, tgtPos.y )  ||
        srcShape.checkPoint( tgtOutside[0], tgtOutside[1], 0, srcW, srcH, srcPos.x, srcPos.y )
      ){
        vectorNormInverse = {};
        badBezier = true;
      }

    }

    var edge;
    var edge_p;
    var rs;

    for (var i = 0; i < pairEdges.length; i++) {
      edge = pairEdges[i];
      edge_p = edge._private;
      rs = edge_p.rscratch;

      var edgeIndex1 = rs.lastEdgeIndex;
      var edgeIndex2 = i;

      var numEdges1 = rs.lastNumEdges;
      var numEdges2 = pairEdges.length;

      var eStyle = edge_p.style;
      var style = eStyle;
      var curveStyle = eStyle['curve-style'].value;
      var ctrlptDists = eStyle['control-point-distances'];
      var ctrlptWs = eStyle['control-point-weights'];
      var bezierN = ctrlptDists && ctrlptWs ? Math.min( ctrlptDists.value.length, ctrlptWs.value.length ) : 1;
      var stepSize = eStyle['control-point-step-size'].pfValue;
      var ctrlptDist = ctrlptDists !== undefined ? ctrlptDists.pfValue[0] : undefined;
      var ctrlptWeight = ctrlptWs.value[0];
      var edgeIsUnbundled = curveStyle === 'unbundled-bezier' || curveStyle === 'segments';

      var swappedDirection = edge_p.source !== src;

      if( swappedDirection && edgeIsUnbundled ){
        ctrlptDist *= -1;
      }

      var srcX1 = rs.lastSrcCtlPtX;
      var srcX2 = srcPos.x;
      var srcY1 = rs.lastSrcCtlPtY;
      var srcY2 = srcPos.y;
      var srcW1 = rs.lastSrcCtlPtW;
      var srcW2 = src.outerWidth();
      var srcH1 = rs.lastSrcCtlPtH;
      var srcH2 = src.outerHeight();

      var tgtX1 = rs.lastTgtCtlPtX;
      var tgtX2 = tgtPos.x;
      var tgtY1 = rs.lastTgtCtlPtY;
      var tgtY2 = tgtPos.y;
      var tgtW1 = rs.lastTgtCtlPtW;
      var tgtW2 = tgt.outerWidth();
      var tgtH1 = rs.lastTgtCtlPtH;
      var tgtH2 = tgt.outerHeight();

      var width1 = rs.lastW;
      var width2 = eStyle['control-point-step-size'].pfValue;

      if( badBezier ){
        rs.badBezier = true;
      } else {
        rs.badBezier = false;
      }

      if( srcX1 === srcX2 && srcY1 === srcY2 && srcW1 === srcW2 && srcH1 === srcH2
      &&  tgtX1 === tgtX2 && tgtY1 === tgtY2 && tgtW1 === tgtW2 && tgtH1 === tgtH2
      &&  width1 === width2
      &&  ((edgeIndex1 === edgeIndex2 && numEdges1 === numEdges2) || edgeIsUnbundled) ){
        // console.log('edge ctrl pt cache HIT')
        continue; // then the control points haven't changed and we can skip calculating them
      } else {
        rs.lastSrcCtlPtX = srcX2;
        rs.lastSrcCtlPtY = srcY2;
        rs.lastSrcCtlPtW = srcW2;
        rs.lastSrcCtlPtH = srcH2;
        rs.lastTgtCtlPtX = tgtX2;
        rs.lastTgtCtlPtY = tgtY2;
        rs.lastTgtCtlPtW = tgtW2;
        rs.lastTgtCtlPtH = tgtH2;
        rs.lastEdgeIndex = edgeIndex2;
        rs.lastNumEdges = numEdges2;
        rs.lastWidth = width2;
        // console.log('edge ctrl pt cache MISS')
      }

      if( src === tgt ){
        // Self-edge

        rs.edgeType = 'self';

        var j = i;
        var loopDist = stepSize;

        if( edgeIsUnbundled ){
          j = 0;
          loopDist = ctrlptDist;
        }

        rs.ctrlpts = [
          srcPos.x,
          srcPos.y - (1 + Math.pow(srcH, 1.12) / 100) * loopDist * (j / 3 + 1),

          srcPos.x - (1 + Math.pow(srcW, 1.12) / 100) * loopDist * (j / 3 + 1),
          srcPos.y
        ];

      } else if(
        hasCompounds &&
        ( src.isParent() || src.isChild() || tgt.isParent() || tgt.isChild() ) &&
        ( src.parents().anySame(tgt) || tgt.parents().anySame(src) )
      ){
        // Compound edge

        rs.edgeType = 'compound';

        // because the line approximation doesn't apply for compound beziers
        // (loop/self edges are already elided b/c of cheap src==tgt check)
        rs.badBezier = false;

        var j = i;
        var loopDist = stepSize;

        if( edgeIsUnbundled ){
          j = 0;
          loopDist = ctrlptDist;
        }

        var loopW = 50;

        var loopaPos = {
          x: srcPos.x - srcW/2,
          y: srcPos.y - srcH/2
        };

        var loopbPos = {
          x: tgtPos.x - tgtW/2,
          y: tgtPos.y - tgtH/2
        };

        var loopPos = {
          x: Math.min( loopaPos.x, loopbPos.x ),
          y: Math.min( loopaPos.y, loopbPos.y )
        };

        // avoids cases with impossible beziers
        var minCompoundStretch = 0.5;
        var compoundStretchA = Math.max( minCompoundStretch, Math.log(srcW * 0.01) );
        var compoundStretchB = Math.max( minCompoundStretch, Math.log(tgtW * 0.01) );

        rs.ctrlpts = [
          loopPos.x,
          loopPos.y - (1 + Math.pow(loopW, 1.12) / 100) * loopDist * (j / 3 + 1) * compoundStretchA,

          loopPos.x - (1 + Math.pow(loopW, 1.12) / 100) * loopDist * (j / 3 + 1) * compoundStretchB,
          loopPos.y
        ];

      } else if( curveStyle === 'segments' ){
        // Segments (multiple straight lines)

        rs.edgeType = 'segments';
        rs.segpts = [];

        var segmentWs = eStyle['segment-weights'].pfValue;
        var segmentDs = eStyle['segment-distances'].pfValue;
        var segmentsN = Math.min( segmentWs.length, segmentDs.length );

        for( var s = 0; s < segmentsN; s++ ){
          var w = segmentWs[s];
          var d = segmentDs[s];

          // d = swappedDirection ? -d : d;
          //
          // d = Math.abs(d);

          // var w1 = !swappedDirection ? (1 - w) : w;
          // var w2 = !swappedDirection ? w : (1 - w);

          var w1 = (1 - w);
          var w2 = w;

          var adjustedMidpt = {
            x: midptSrcPts.x1 * w1 + midptSrcPts.x2 * w2,
            y: midptSrcPts.y1 * w1 + midptSrcPts.y2 * w2
          };

          rs.segpts.push(
            adjustedMidpt.x + vectorNormInverse.x * d,
            adjustedMidpt.y + vectorNormInverse.y * d
          );
        }

      // Straight edge
      } else if (
        pairEdges.length % 2 === 1
        && i === Math.floor(pairEdges.length / 2)
        && !edgeIsUnbundled
      ){

        rs.edgeType = 'straight';

      } else {
        // (Multi)bezier

        var multi = edgeIsUnbundled;

        rs.edgeType = multi ? 'multibezier' : 'bezier';
        rs.ctrlpts = [];

        for( var b = 0; b < bezierN; b++ ){
          var normctrlptDist = (0.5 - pairEdges.length / 2 + i) * stepSize;
          var manctrlptDist;
          var sign = math.signum( normctrlptDist );

          if( multi ){
            ctrlptDist = ctrlptDists ? ctrlptDists.pfValue[b] : stepSize; // fall back on step size
            ctrlptWeight = ctrlptWs.value[b];
          }

          if( edgeIsUnbundled ){ // multi or single unbundled
            manctrlptDist = ctrlptDist;
          } else {
            manctrlptDist = ctrlptDist !== undefined ? sign * ctrlptDist : undefined;
          }

          var distanceFromMidpoint = manctrlptDist !== undefined ? manctrlptDist : normctrlptDist;

          var w1 = !swappedDirection || edgeIsUnbundled ? (1 - ctrlptWeight) : ctrlptWeight;
          var w2 = !swappedDirection || edgeIsUnbundled ? ctrlptWeight : (1 - ctrlptWeight);

          var adjustedMidpt = {
            x: midptSrcPts.x1 * w1 + midptSrcPts.x2 * w2,
            y: midptSrcPts.y1 * w1 + midptSrcPts.y2 * w2
          };

          rs.ctrlpts.push(
            adjustedMidpt.x + vectorNormInverse.x * distanceFromMidpoint,
            adjustedMidpt.y + vectorNormInverse.y * distanceFromMidpoint
          );
        }

      }

      // find endpts for edge
      this.findEndpoints( edge );

      var badStart = !is.number( rs.startX ) || !is.number( rs.startY );
      var badAStart = !is.number( rs.arrowStartX ) || !is.number( rs.arrowStartY );
      var badEnd = !is.number( rs.endX ) || !is.number( rs.endY );
      var badAEnd = !is.number( rs.arrowEndX ) || !is.number( rs.arrowEndY );

      var minCpADistFactor = 3;
      var arrowW = this.getArrowWidth( eStyle['width'].pfValue ) * this.arrowShapeHeight;
      var minCpADist = minCpADistFactor * arrowW;

      if( rs.edgeType === 'bezier' ){
        var startACpDist = math.distance( { x: rs.ctrlpts[0], y: rs.ctrlpts[1] }, { x: rs.startX, y: rs.startY } );
        var closeStartACp = startACpDist < minCpADist;
        var endACpDist = math.distance( { x: rs.ctrlpts[0], y: rs.ctrlpts[1] }, { x: rs.endX, y: rs.endY } );
        var closeEndACp = endACpDist < minCpADist;

        var overlapping = false;

        if( badStart || badAStart || closeStartACp ){
          overlapping = true;

          // project control point along line from src centre to outside the src shape
          // (otherwise intersection will yield nothing)
          var cpD = { // delta
            x: rs.ctrlpts[0] - srcPos.x,
            y: rs.ctrlpts[1] - srcPos.y
          };
          var cpL = Math.sqrt( cpD.x*cpD.x + cpD.y*cpD.y ); // length of line
          var cpM = { // normalised delta
            x: cpD.x / cpL,
            y: cpD.y / cpL
          };
          var radius = Math.max(srcW, srcH);
          var cpProj = { // *2 radius guarantees outside shape
            x: rs.ctrlpts[0] + cpM.x * 2 * radius,
            y: rs.ctrlpts[1] + cpM.y * 2 * radius
          };

          var srcCtrlPtIntn = srcShape.intersectLine(
            srcPos.x,
            srcPos.y,
            srcW,
            srcH,
            cpProj.x,
            cpProj.y,
            0
          );

          if( closeStartACp ){
            rs.ctrlpts[0] = rs.ctrlpts[0] + cpM.x * (minCpADist - startACpDist);
            rs.ctrlpts[1] = rs.ctrlpts[1] + cpM.y * (minCpADist - startACpDist);
          } else {
            rs.ctrlpts[0] = srcCtrlPtIntn[0] + cpM.x * minCpADist;
            rs.ctrlpts[1] = srcCtrlPtIntn[1] + cpM.y * minCpADist;
          }
        }

        if( badEnd || badAEnd || closeEndACp ){
          overlapping = true;

          // project control point along line from tgt centre to outside the tgt shape
          // (otherwise intersection will yield nothing)
          var cpD = { // delta
            x: rs.ctrlpts[0] - tgtPos.x,
            y: rs.ctrlpts[1] - tgtPos.y
          };
          var cpL = Math.sqrt( cpD.x*cpD.x + cpD.y*cpD.y ); // length of line
          var cpM = { // normalised delta
            x: cpD.x / cpL,
            y: cpD.y / cpL
          };
          var radius = Math.max(srcW, srcH);
          var cpProj = { // *2 radius guarantees outside shape
            x: rs.ctrlpts[0] + cpM.x * 2 * radius,
            y: rs.ctrlpts[1] + cpM.y * 2 * radius
          };

          var tgtCtrlPtIntn = tgtShape.intersectLine(
            tgtPos.x,
            tgtPos.y,
            tgtW,
            tgtH,
            cpProj.x,
            cpProj.y,
            0
          );

          if( closeEndACp ){
            rs.ctrlpts[0] = rs.ctrlpts[0] + cpM.x * (minCpADist - endACpDist);
            rs.ctrlpts[1] = rs.ctrlpts[1] + cpM.y * (minCpADist - endACpDist);
          } else {
            rs.ctrlpts[0] = tgtCtrlPtIntn[0] + cpM.x * minCpADist;
            rs.ctrlpts[1] = tgtCtrlPtIntn[1] + cpM.y * minCpADist;
          }

        }

        if( overlapping ){
          // recalc endpts
          this.findEndpoints( edge );
        }

      }

      if( rs.edgeType === 'multibezier' || rs.edgeType === 'bezier' || rs.edgeType === 'self' || rs.edgeType === 'compound' ){
        rs.allpts = [];

        rs.allpts.push( rs.startX, rs.startY );

        for( var b = 0; b+1 < rs.ctrlpts.length; b += 2 ){
          // ctrl pt itself
          rs.allpts.push( rs.ctrlpts[b], rs.ctrlpts[b+1] );

          // the midpt between ctrlpts as intermediate destination pts
          if( b + 3 < rs.ctrlpts.length ){
            rs.allpts.push( (rs.ctrlpts[b] + rs.ctrlpts[b+2])/2, (rs.ctrlpts[b+1] + rs.ctrlpts[b+3])/2 );
          }
        }

        rs.allpts.push( rs.endX, rs.endY );

        var m, mt;
        if( rs.edgeType === 'bezier' ){
          rs.midX = math.qbezierAt( rs.arrowStartX, rs.ctrlpts[0], rs.arrowEndX, 0.5 );
          rs.midY = math.qbezierAt( rs.arrowStartY, rs.ctrlpts[1], rs.arrowEndY, 0.5 );
        } else if( rs.ctrlpts.length/2 % 2 === 0 ){
          m = rs.allpts.length/2 - 1;

          rs.midX = rs.allpts[m];
          rs.midY = rs.allpts[m+1];
        } else {
          m = rs.allpts.length/2 - 3;
          mt = 0.5;

          rs.midX = math.qbezierAt( rs.allpts[m], rs.allpts[m+2], rs.allpts[m+4], mt );
          rs.midY = math.qbezierAt( rs.allpts[m+1], rs.allpts[m+3], rs.allpts[m+5], mt );
        }

      } else if( rs.edgeType === 'straight' ){
        // need to calc these after endpts
        rs.allpts = [ rs.startX, rs.startY, rs.endX, rs.endY ];

        // default midpt for labels etc
        rs.midX = ( rs.arrowStartX + rs.arrowEndX )/2;
        rs.midY = ( rs.arrowStartY + rs.arrowEndY )/2;

      } else if( rs.edgeType === 'segments' ){
        rs.allpts = [];
        rs.allpts.push( rs.startX, rs.startY );
        rs.allpts.push.apply( rs.allpts, rs.segpts );
        rs.allpts.push( rs.endX, rs.endY );

        if( rs.segpts.length % 4 === 0 ){
          var i2 = rs.segpts.length / 2;
          var i1 = i2 - 2;

          rs.midX = ( rs.segpts[i1] + rs.segpts[i2] ) / 2;
          rs.midY = ( rs.segpts[i1+1] + rs.segpts[i2+1] ) / 2;
        } else {
          var i1 = rs.segpts.length / 2 - 1;

          rs.midX = rs.segpts[i1];
          rs.midY = rs.segpts[i1+1];
        }


      }

      this.projectLines( edge );
      this.calculateArrowAngles( edge );
      this.recalculateEdgeLabelProjection( edge );

    }
  }

  for( var i = 0; i < haystackEdges.length; i++ ){
    var edge = haystackEdges[i];
    var _p = edge._private;
    var style = _p.style;
    var rscratch = _p.rscratch;
    var rs = rscratch;

    if( !rscratch.haystack ){
      var angle = Math.random() * 2 * Math.PI;

      rscratch.source = {
        x: Math.cos(angle),
        y: Math.sin(angle)
      };

      var angle = Math.random() * 2 * Math.PI;

      rscratch.target = {
        x: Math.cos(angle),
        y: Math.sin(angle)
      };

    }

    var src = _p.source;
    var tgt = _p.target;
    var srcPos = src._private.position;
    var tgtPos = tgt._private.position;
    var srcW = src.width();
    var tgtW = tgt.width();
    var srcH = src.height();
    var tgtH = tgt.height();
    var radius = style['haystack-radius'].value;
    var halfRadius = radius/2; // b/c have to half width/height

    rs.haystackPts = rs.allpts = [
      rs.source.x * srcW * halfRadius + srcPos.x,
      rs.source.y * srcH * halfRadius + srcPos.y,
      rs.target.x * tgtW * halfRadius + tgtPos.x,
      rs.target.y * tgtH * halfRadius + tgtPos.y
    ];

    rs.midX = (rs.allpts[0] + rs.allpts[2])/2;
    rs.midY = (rs.allpts[1] + rs.allpts[3])/2;

    // always override as haystack in case set to different type previously
    rscratch.edgeType = 'haystack';
    rscratch.haystack = true;

    this.projectLines( edge );
    this.calculateArrowAngles( edge );
    this.recalculateEdgeLabelProjection( edge );
  }

  for( var i = 0 ; i < autorotateEdges.length; i++ ){
    var edge = autorotateEdges[i];
    var rs = edge._private.rscratch;

    rs.labelAngle = Math.atan( rs.midDispY / rs.midDispX );
  }

  return hashTable;
};

var getAngleFromDisp = function( dispX, dispY ){
  return Math.atan2( dispY, dispX ) - Math.PI/2;
};

BRp.calculateArrowAngles = function( edge ){
  var rs = edge._private.rscratch;
  var isHaystack = rs.edgeType === 'haystack';
  var isMultibezier = rs.edgeType === 'multibezier';
  var isSegments = rs.edgeType === 'segments';
  var isCompound = rs.edgeType === 'compound';
  var isSelf = rs.edgeType === 'self';

  // Displacement gives direction for arrowhead orientation
  var dispX, dispY;
  var startX, startY, endX, endY;

  var srcPos = edge.source().position();
  var tgtPos = edge.target().position();

  if( isHaystack ){
    startX = rs.haystackPts[0];
    startY = rs.haystackPts[1];
    endX = rs.haystackPts[2];
    endY = rs.haystackPts[3];
  } else {
    startX = rs.arrowStartX;
    startY = rs.arrowStartY;
    endX = rs.arrowEndX;
    endY = rs.arrowEndY;
  }

  // source
  //

  dispX = srcPos.x - startX;
  dispY = srcPos.y - startY;

  rs.srcArrowAngle = getAngleFromDisp( dispX, dispY );

  // mid target
  //

  var midX = rs.midX;
  var midY = rs.midY;

  if( isHaystack ){
    midX = ( startX + endX )/2;
    midY = ( startY + endY )/2;
  }

  dispX = endX - startX;
  dispY = endY - startY;

  if( isSelf ){
    dispX = -1;
    dispY = 1;
  } else if( isSegments ){
    var pts = rs.allpts;

    if( pts.length / 2 % 2 === 0 ){
      var i2 = pts.length / 2;
      var i1 = i2 - 2;

      dispX = ( pts[i2] - pts[i1] );
      dispY = ( pts[i2+1] - pts[i1+1] );
    } else {
      var i2 = pts.length / 2 - 1;
      var i1 = i2 - 2;
      var i3 = i2 + 2;

      dispX = ( pts[i2] - pts[i1] );
      dispY = ( pts[i2+1] - pts[i1+1] );
    }
  } else if( isMultibezier || isCompound ){
    var pts = rs.allpts;
    var cpts = rs.ctrlpts;
    var bp0x, bp0y;
    var bp1x, bp1y;

    if( cpts.length / 2 % 2 === 0 ){
      var p0 = pts.length / 2 - 1; // startpt
      var ic = p0 + 2;
      var p1 = ic + 2;

      bp0x = math.qbezierAt( pts[p0], pts[ic], pts[p1], 0.0 );
      bp0y = math.qbezierAt( pts[p0+1], pts[ic+1], pts[p1+1], 0.0 );

      bp1x = math.qbezierAt( pts[p0], pts[ic], pts[p1], 0.0001 );
      bp1y = math.qbezierAt( pts[p0+1], pts[ic+1], pts[p1+1], 0.0001 );
    } else {
      var ic = pts.length / 2 - 1; // ctrpt
      var p0 = ic - 2; // startpt
      var p1 = ic + 2; // endpt

      bp0x = math.qbezierAt( pts[p0], pts[ic], pts[p1], 0.4999 );
      bp0y = math.qbezierAt( pts[p0+1], pts[ic+1], pts[p1+1], 0.4999 );

      bp1x = math.qbezierAt( pts[p0], pts[ic], pts[p1], 0.5 );
      bp1y = math.qbezierAt( pts[p0+1], pts[ic+1], pts[p1+1], 0.5 );
    }

    dispX = ( bp1x - bp0x );
    dispY = ( bp1y - bp0y );
  }

  rs.midtgtArrowAngle = getAngleFromDisp( dispX, dispY );

  rs.midDispX = dispX;
  rs.midDispY = dispY;

  // mid source
  //

  dispX *= -1;
  dispY *= -1;

  if( isSegments ){
    var pts = rs.allpts;

    if( pts.length / 2 % 2 === 0 ){
      // already ok
    } else {
      var i2 = pts.length / 2 - 1;
      var i3 = i2 + 2;

      dispX = -( pts[i3] - pts[i2] );
      dispY = -( pts[i3+1] - pts[i2+1] );
    }
  }

  rs.midsrcArrowAngle = getAngleFromDisp( dispX, dispY );

  // target
  //

  dispX = tgtPos.x - endX;
  dispY = tgtPos.y - endY;

  rs.tgtArrowAngle = getAngleFromDisp( dispX, dispY );
};


BRp.findEndpoints = function( edge ){
  var r = this;
  var intersect;

  var source = edge.source()[0];
  var target = edge.target()[0];

  var src_p = source._private;
  var tgt_p = target._private;

  var srcPos = src_p.position;
  var tgtPos = tgt_p.position;

  var tgtArShape = edge._private.style['target-arrow-shape'].value;
  var srcArShape = edge._private.style['source-arrow-shape'].value;

  var rs = edge._private.rscratch;

  var et = rs.edgeType;
  var bezier = et === 'bezier' || et === 'multibezier' || et === 'self' || et === 'compound';
  var multi = et !== 'bezier';
  var lines = et === 'straight' || et === 'segments';
  var segments = et === 'segments';

  var p1, p2;

  if( bezier ){
    var cpStart = [ rs.ctrlpts[0], rs.ctrlpts[1] ];
    var cpEnd = multi ? [ rs.ctrlpts[rs.ctrlpts.length - 2], rs.ctrlpts[rs.ctrlpts.length - 1] ] : cpStart;

    p1 = cpEnd;
    p2 = cpStart;
  } else if( lines ){
    var srcArrowFromPt = !segments ? [ tgtPos.x, tgtPos.y ] : rs.segpts.slice( 0, 2 );
    var tgtArrowFromPt = !segments ? [ srcPos.x, srcPos.y ] : rs.segpts.slice( rs.segpts.length - 2 );

    p1 = tgtArrowFromPt;
    p2 = srcArrowFromPt;
  }

  intersect = r.nodeShapes[this.getNodeShape(target)].intersectLine(
    tgtPos.x,
    tgtPos.y,
    target.outerWidth(),
    target.outerHeight(),
    p1[0],
    p1[1],
    0
  );

  var arrowEnd = math.shortenIntersection(intersect, p1,
    r.arrowShapes[tgtArShape].spacing(edge));
  var edgeEnd = math.shortenIntersection(intersect, p1,
    r.arrowShapes[tgtArShape].gap(edge));

  rs.endX = edgeEnd[0];
  rs.endY = edgeEnd[1];

  rs.arrowEndX = arrowEnd[0];
  rs.arrowEndY = arrowEnd[1];

  intersect = r.nodeShapes[this.getNodeShape(source)].intersectLine(
    srcPos.x,
    srcPos.y,
    source.outerWidth(),
    source.outerHeight(),
    p2[0],
    p2[1],
    0
  );

  var arrowStart = math.shortenIntersection(
    intersect, p2,
    r.arrowShapes[srcArShape].spacing(edge)
  );
  var edgeStart = math.shortenIntersection(
    intersect, p2,
    r.arrowShapes[srcArShape].gap(edge)
  );

  rs.startX = edgeStart[0];
  rs.startY = edgeStart[1];

  rs.arrowStartX = arrowStart[0];
  rs.arrowStartY = arrowStart[1];

  if( lines ){
    if( !is.number(rs.startX) || !is.number(rs.startY) || !is.number(rs.endX) || !is.number(rs.endY) ){
      rs.badLine = true;
    } else {
      rs.badLine = false;
    }
  }
};

BRp.getArrowWidth = BRp.getArrowHeight = function(edgeWidth) {
  var cache = this.arrowWidthCache = this.arrowWidthCache || {};

  var cachedVal = cache[edgeWidth];
  if( cachedVal ){
    return cachedVal;
  }

  cachedVal =  Math.max(Math.pow(edgeWidth * 13.37, 0.9), 29);
  cache[edgeWidth] = cachedVal;

  return cachedVal;
};

module.exports = BRp;

},{"../../../collection/zsort":29,"../../../is":77,"../../../math":79}],57:[function(_dereq_,module,exports){
'use strict';

var BRp = {};

BRp.getCachedImage = function(url, onLoad) {
  var r = this;
  var imageCache = r.imageCache = r.imageCache || {};

  if( imageCache[url] && imageCache[url].image ){
    return imageCache[url].image;
  }

  var cache = imageCache[url] = imageCache[url] || {};

  var image = cache.image = new Image();
  image.addEventListener('load', onLoad);
  image.src = url;

  return image;
};

module.exports = BRp;

},{}],58:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../../is');
var util = _dereq_('../../../util');

var BaseRenderer = function(){};
var BR = BaseRenderer;
var BRp = BR.prototype;

BRp.clientFunctions = [ 'redrawHint', 'render', 'renderTo', 'matchCanvasSize', 'nodeShapeImpl', 'arrowShapeImpl' ];

BRp.init = function( options ){
  var r = this;

  r.options = options;

  r.cy = options.cy;

  r.container = options.cy.container();

  r.selection = [undefined, undefined, undefined, undefined, 0]; // Coordinates for selection box, plus enabled flag

  //--Pointer-related data
  r.hoverData = {down: null, last: null,
      downTime: null, triggerMode: null,
      dragging: false,
      initialPan: [null, null], capture: false};

  r.dragData = {possibleDragElements: []};

  r.touchData = {
      start: null, capture: false,

      // These 3 fields related to tap, taphold events
      startPosition: [null, null, null, null, null, null],
      singleTouchStartTime: null,
      singleTouchMoved: true,

      now: [null, null, null, null, null, null],
      earlier: [null, null, null, null, null, null]
  };

  r.redraws = 0;
  r.showFps = options.showFps;

  r.hideEdgesOnViewport = options.hideEdgesOnViewport;
  r.hideLabelsOnViewport = options.hideLabelsOnViewport;
  r.textureOnViewport = options.textureOnViewport;
  r.wheelSensitivity = options.wheelSensitivity;
  r.motionBlurEnabled = options.motionBlur; // on by default
  r.forcedPixelRatio = options.pixelRatio;
  r.motionBlur = true; // for initial kick off
  r.motionBlurOpacity = options.motionBlurOpacity;
  r.motionBlurTransparency = 1 - r.motionBlurOpacity;
  r.motionBlurPxRatio = 1;
  r.mbPxRBlurry = 1; //0.8;
  r.minMbLowQualFrames = 4;
  r.fullQualityMb = false;
  r.clearedForMotionBlur = [];
  r.desktopTapThreshold = options.desktopTapThreshold;
  r.desktopTapThreshold2 = options.desktopTapThreshold * options.desktopTapThreshold;
  r.touchTapThreshold = options.touchTapThreshold;
  r.touchTapThreshold2 = options.touchTapThreshold * options.touchTapThreshold;
  r.tapholdDuration = 500;

  r.bindings = [];

  r.registerNodeShapes();
  r.registerArrowShapes();
  r.load();
};

BRp.notify = function(params) {
  var types;
  var r = this;

  if( is.array( params.type ) ){
    types = params.type;

  } else {
    types = [ params.type ];
  }

  for( var i = 0; i < types.length; i++ ){
    var type = types[i];

    switch( type ){
      case 'destroy':
        r.destroy();
        return;

      case 'add':
      case 'remove':
      case 'load':
        r.updateElementsCache();
        break;

      case 'viewport':
        r.redrawHint('select', true);
        break;

      case 'style':
        r.updateCachedZSortedEles();
        break;
    }

    if( type === 'load' || type === 'resize' ){
      r.invalidateContainerClientCoordsCache();
      r.matchCanvasSize(r.container);
    }
  } // for

  r.redrawHint('eles', true);
  r.redrawHint('drag', true);

  this.startRenderLoop();

  this.redraw();
};

BRp.destroy = function(){
  this.destroyed = true;

  this.cy.stopAnimationLoop();

  for( var i = 0; i < this.bindings.length; i++ ){
    var binding = this.bindings[i];
    var b = binding;

    b.target.removeEventListener(b.event, b.handler, b.useCapture);
  }

  if( this.removeObserver ){
    this.removeObserver.disconnect();
  }

  if( this.labelCalcDiv ){
    try{
      document.body.removeChild(this.labelCalcDiv);
    } catch(e){
      // ie10 issue #1014
    }
  }
};

[
  _dereq_('./arrow-shapes'),
  _dereq_('./cached-eles'),
  _dereq_('./coord-ele-math'),
  _dereq_('./images'),
  _dereq_('./load-listeners'),
  _dereq_('./node-shapes'),
  _dereq_('./redraw')
].forEach(function( props ){
  util.extend( BRp, props );
});

module.exports = BR;

},{"../../../is":77,"../../../util":94,"./arrow-shapes":54,"./cached-eles":55,"./coord-ele-math":56,"./images":57,"./load-listeners":59,"./node-shapes":60,"./redraw":61}],59:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../../is');
var util = _dereq_('../../../util');
var Event = _dereq_('../../../event');
var Collection = _dereq_('../../../collection');

var BRp = {};

BRp.registerBinding = function(target, event, handler, useCapture){
  this.bindings.push({
    target: target,
    event: event,
    handler: handler,
    useCapture: useCapture
  });

  target.addEventListener(event, handler, useCapture);
};

BRp.nodeIsDraggable = function(node) {
  if (node._private.style['opacity'].value !== 0
    && node._private.style['visibility'].value == 'visible'
    && node._private.style['display'].value == 'element'
    && !node.locked()
    && node.grabbable() ) {

    return true;
  }

  return false;
};

BRp.load = function() {
  var r = this;

  var triggerEvents = function( target, names, e, props ){
    if( target == null ){
      target = r.cy;
    }

    for( var i = 0; i < names.length; i++ ){
      var name = names[i];

      var event = Event( e, util.extend({ type: name }, props) );
      target.trigger( event );
    }
  };

  var isMultSelKeyDown = function( e ){
    return e.shiftKey || e.metaKey || e.ctrlKey; // maybe e.altKey
  };

  var getDragListIds = function(opts){
    var listHasId;

    if( opts.addToList && r.cy.hasCompoundNodes() ){ // only needed for compound graphs
      if( !opts.addToList.hasId ){ // build ids lookup if doesn't already exist
        opts.addToList.hasId = {};

        for( var i = 0; i < opts.addToList.length; i++ ){
          var ele = opts.addToList[i];

          opts.addToList.hasId[ ele.id() ] = true;
        }
      }

      listHasId = opts.addToList.hasId;
    }

    return listHasId || {};
  };

  // helper function to determine which child nodes and inner edges
  // of a compound node to be dragged as well as the grabbed and selected nodes
  var addDescendantsToDrag = function(node, opts){
    if( !node._private.cy.hasCompoundNodes() ){
      return;
    }

    if( opts.inDragLayer == null && opts.addToList == null ){ return; } // nothing to do

    var listHasId = getDragListIds( opts );

    var innerNodes = node.descendants();

    for( var i = 0; i < innerNodes.size(); i++ ){
      var iNode = innerNodes[i];
      var _p = iNode._private;

      if( opts.inDragLayer ){
        _p.rscratch.inDragLayer = true;
      }

      if( opts.addToList && !listHasId[ iNode.id() ] ){
        opts.addToList.push( iNode );
        listHasId[ iNode.id() ] = true;

        _p.grabbed = true;
      }

      var edges = _p.edges;
      for( var j = 0; opts.inDragLayer && j < edges.length; j++ ){
        edges[j]._private.rscratch.inDragLayer = true;
      }
    }
  };

  // adds the given nodes, and its edges to the drag layer
  var addNodeToDrag = function(node, opts){

    var _p = node._private;
    var listHasId = getDragListIds( opts );

    if( opts.inDragLayer ){
      _p.rscratch.inDragLayer = true;
    }

    if( opts.addToList && !listHasId[ node.id() ] ){
      opts.addToList.push( node );
      listHasId[ node.id() ] = true;

      _p.grabbed = true;
    }

    var edges = _p.edges;
    for( var i = 0; opts.inDragLayer && i < edges.length; i++ ){
      edges[i]._private.rscratch.inDragLayer = true;
    }

    addDescendantsToDrag( node, opts ); // always add to drag

    // also add nodes and edges related to the topmost ancestor
    updateAncestorsInDragLayer( node, {
      inDragLayer: opts.inDragLayer
    } );
  };

  var freeDraggedElements = function( draggedElements ){
    if( !draggedElements ){ return; }

    for (var i=0; i < draggedElements.length; i++) {

      var dEi_p = draggedElements[i]._private;

      if(dEi_p.group === 'nodes') {
        dEi_p.rscratch.inDragLayer = false;
        dEi_p.grabbed = false;

        var sEdges = dEi_p.edges;
        for( var j = 0; j < sEdges.length; j++ ){ sEdges[j]._private.rscratch.inDragLayer = false; }

        // for compound nodes, also remove related nodes and edges from the drag layer
        updateAncestorsInDragLayer(draggedElements[i], { inDragLayer: false });

      } else if( dEi_p.group === 'edges' ){
        dEi_p.rscratch.inDragLayer = false;
      }

    }
  };

  // helper function to determine which ancestor nodes and edges should go
  // to the drag layer (or should be removed from drag layer).
  var updateAncestorsInDragLayer = function(node, opts) {

    if( opts.inDragLayer == null && opts.addToList == null ){ return; } // nothing to do

    // find top-level parent
    var parent = node;

    if( !node._private.cy.hasCompoundNodes() ){
      return;
    }

    while( parent.parent().nonempty() ){
      parent = parent.parent()[0];
    }

    // no parent node: no nodes to add to the drag layer
    if( parent == node ){
      return;
    }

    var nodes = parent.descendants()
      .merge( parent )
      .unmerge( node )
      .unmerge( node.descendants() )
    ;

    var edges = nodes.connectedEdges();

    var listHasId = getDragListIds( opts );

    for( var i = 0; i < nodes.size(); i++ ){
      if( opts.inDragLayer !== undefined ){
        nodes[i]._private.rscratch.inDragLayer = opts.inDragLayer;
      }

      if( opts.addToList && !listHasId[ nodes[i].id() ] ){
        opts.addToList.push( nodes[i] );
        listHasId[ nodes[i].id() ] = true;

        nodes[i]._private.grabbed = true;
      }
    }

    for( var j = 0; opts.inDragLayer !== undefined && j < edges.length; j++ ) {
      edges[j]._private.rscratch.inDragLayer = opts.inDragLayer;
    }
  };

  if( typeof MutationObserver !== 'undefined' ){
    r.removeObserver = new MutationObserver(function( mutns ){
      for( var i = 0; i < mutns.length; i++ ){
        var mutn = mutns[i];
        var rNodes = mutn.removedNodes;

        if( rNodes ){ for( var j = 0; j < rNodes.length; j++ ){
          var rNode = rNodes[j];

          if( rNode === r.container ){
            r.destroy();
            break;
          }
        } }
      }
    });

    if( r.container.parentNode ){
      r.removeObserver.observe( r.container.parentNode, { childList: true } );
    }
  } else {
    r.registerBinding(r.container, 'DOMNodeRemoved', function(e){
      r.destroy();
    });
  }



  // auto resize
  r.registerBinding(window, 'resize', util.debounce( function(e) {
    r.invalidateContainerClientCoordsCache();

    r.matchCanvasSize(r.container);
    r.redrawHint('eles', true);
    r.redraw();
  }, 100 ) );

  var invalCtnrBBOnScroll = function(domEle){
    r.registerBinding(domEle, 'scroll', function(e){
      r.invalidateContainerClientCoordsCache();
    } );
  };

  var bbCtnr = r.cy.container();

  for( ;; ){

    invalCtnrBBOnScroll( bbCtnr );

    if( bbCtnr.parentNode ){
      bbCtnr = bbCtnr.parentNode;
    } else {
      break;
    }

  }

  // stop right click menu from appearing on cy
  r.registerBinding(r.container, 'contextmenu', function(e){
    e.preventDefault();
  });

  var inBoxSelection = function(){
    return r.selection[4] !== 0;
  };

  // Primary key
  r.registerBinding(r.container, 'mousedown', function(e) {
    e.preventDefault();
    r.hoverData.capture = true;
    r.hoverData.which = e.which;

    var cy = r.cy;
    var pos = r.projectIntoViewport(e.clientX, e.clientY);
    var select = r.selection;
    var near = r.findNearestElement(pos[0], pos[1], true, false);
    var draggedElements = r.dragData.possibleDragElements;

    r.hoverData.mdownPos = pos;

    var checkForTaphold = function(){
      r.hoverData.tapholdCancelled = false;

      clearTimeout( r.hoverData.tapholdTimeout );

      r.hoverData.tapholdTimeout = setTimeout(function(){

        if( r.hoverData.tapholdCancelled ){
          return;
        } else {
          var ele = r.hoverData.down;

          if( ele ){
            ele.trigger( Event(e, {
              type: 'taphold',
              cyPosition: { x: pos[0], y: pos[1] }
            }) );
          } else {
            cy.trigger( Event(e, {
              type: 'taphold',
              cyPosition: { x: pos[0], y: pos[1] }
            }) );
          }
        }

      }, r.tapholdDuration);
    };

    // Right click button
    if( e.which == 3 ){

      r.hoverData.cxtStarted = true;

      var cxtEvt = Event(e, {
        type: 'cxttapstart',
        cyPosition: { x: pos[0], y: pos[1] }
      });

      if( near ){
        near.activate();
        near.trigger( cxtEvt );

        r.hoverData.down = near;
      } else {
        cy.trigger( cxtEvt );
      }

      r.hoverData.downTime = (new Date()).getTime();
      r.hoverData.cxtDragged = false;

    // Primary button
    } else if (e.which == 1) {

      if( near ){
        near.activate();
      }

      // Element dragging
      {
        // If something is under the cursor and it is draggable, prepare to grab it
        if (near != null) {

          if( r.nodeIsDraggable(near) ){

            var grabEvent = Event(e, {
              type: 'grab',
              cyPosition: { x: pos[0], y: pos[1] }
            });

            if ( near.isNode() && !near.selected() ){

              draggedElements = r.dragData.possibleDragElements = [];
              addNodeToDrag( near, { addToList: draggedElements } );

              near.trigger(grabEvent);

            } else if ( near.isNode() && near.selected() ){
              draggedElements = r.dragData.possibleDragElements = [  ];

              var selectedNodes = cy.$(function(){ return this.isNode() && this.selected(); });

              for( var i = 0; i < selectedNodes.length; i++ ){

                // Only add this selected node to drag if it is draggable, eg. has nonzero opacity
                if( r.nodeIsDraggable( selectedNodes[i] ) ){
                  addNodeToDrag( selectedNodes[i], { addToList: draggedElements } );
                }
              }

              near.trigger( grabEvent );
            }

            r.redrawHint('eles', true);
            r.redrawHint('drag', true);

          }

        }

        r.hoverData.down = near;
        r.hoverData.downTime = (new Date()).getTime();
      }

      triggerEvents( near, ['mousedown', 'tapstart', 'vmousedown'], e, {
        cyPosition: { x: pos[0], y: pos[1] }
      } );

      if ( near == null ) {
        select[4] = 1;

        r.data.bgActivePosistion = {
          x: pos[0],
          y: pos[1]
        };

        r.redrawHint('select', true);

        r.redraw();
      } else if( near.isEdge() ){
        select[4] = 1; // for future pan
      }

      checkForTaphold();

    }

    // Initialize selection box coordinates
    select[0] = select[2] = pos[0];
    select[1] = select[3] = pos[1];

  }, false);

  r.registerBinding(window, 'mousemove', function(e) {
    var preventDefault = false;
    var capture = r.hoverData.capture;

    // save cycles if mouse events aren't to be captured
    if ( !capture ){
      var containerPageCoords = r.findContainerClientCoords();

      if (e.clientX > containerPageCoords[0] && e.clientX < containerPageCoords[0] + r.canvasWidth
        && e.clientY > containerPageCoords[1] && e.clientY < containerPageCoords[1] + r.canvasHeight
      ) {
        // inside container bounds so OK
      } else {
        return;
      }

      var cyContainer = r.container;
      var target = e.target;
      var tParent = target.parentNode;
      var containerIsTarget = false;

      while( tParent ){
        if( tParent === cyContainer ){
          containerIsTarget = true;
          break;
        }

        tParent = tParent.parentNode;
      }

      if( !containerIsTarget ){ return; } // if target is outisde cy container, then this event is not for us
    }

    var cy = r.cy;
    var zoom = cy.zoom();
    var pos = r.projectIntoViewport(e.clientX, e.clientY);
    var select = r.selection;

    var near = null;
    if( !r.hoverData.draggingEles ){
      near = r.findNearestElement(pos[0], pos[1], true, false);
    }
    var last = r.hoverData.last;
    var down = r.hoverData.down;

    var disp = [pos[0] - select[2], pos[1] - select[3]];

    var draggedElements = r.dragData.possibleDragElements;

    var dx = select[2] - select[0];
    var dx2 = dx * dx;
    var dy = select[3] - select[1];
    var dy2 = dy * dy;
    var dist2 = dx2 + dy2;
    var rdist2 = dist2 * zoom * zoom;

    var multSelKeyDown = isMultSelKeyDown( e );

    r.hoverData.tapholdCancelled = true;

    var updateDragDelta = function(){
      var dragDelta = r.hoverData.dragDelta = r.hoverData.dragDelta || [];

      if( dragDelta.length === 0 ){
        dragDelta.push( disp[0] );
        dragDelta.push( disp[1] );
      } else {
        dragDelta[0] += disp[0];
        dragDelta[1] += disp[1];
      }
    };


    preventDefault = true;

    triggerEvents( near, ['mousemove', 'vmousemove', 'tapdrag'], e, {
      cyPosition: { x: pos[0], y: pos[1] }
    } );

    // trigger context drag if rmouse down
    if( r.hoverData.which === 3 ){
      var cxtEvt = Event(e, {
        type: 'cxtdrag',
        cyPosition: { x: pos[0], y: pos[1] }
      });

      if( down ){
        down.trigger( cxtEvt );
      } else {
        cy.trigger( cxtEvt );
      }

      r.hoverData.cxtDragged = true;

      if( !r.hoverData.cxtOver || near !== r.hoverData.cxtOver ){

        if( r.hoverData.cxtOver ){
          r.hoverData.cxtOver.trigger( Event(e, {
            type: 'cxtdragout',
            cyPosition: { x: pos[0], y: pos[1] }
          }) );
        }

        r.hoverData.cxtOver = near;

        if( near ){
          near.trigger( Event(e, {
            type: 'cxtdragover',
            cyPosition: { x: pos[0], y: pos[1] }
          }) );
        }

      }

    // Check if we are drag panning the entire graph
    } else if (r.hoverData.dragging) {
      preventDefault = true;

      if( cy.panningEnabled() && cy.userPanningEnabled() ){
        var deltaP;

        if( r.hoverData.justStartedPan ){
          var mdPos = r.hoverData.mdownPos;

          deltaP = {
            x: ( pos[0] - mdPos[0] ) * zoom,
            y: ( pos[1] - mdPos[1] ) * zoom
          };

          r.hoverData.justStartedPan = false;

        } else {
          deltaP = {
            x: disp[0] * zoom,
            y: disp[1] * zoom
          };

        }

        cy.panBy( deltaP );

        r.hoverData.dragged = true;
      }

      // Needs reproject due to pan changing viewport
      pos = r.projectIntoViewport(e.clientX, e.clientY);

    // Checks primary button down & out of time & mouse not moved much
    } else if(
        select[4] == 1 && (down == null || down.isEdge())
    ){

      if( !r.hoverData.dragging && cy.boxSelectionEnabled() && ( multSelKeyDown || !cy.panningEnabled() || !cy.userPanningEnabled() ) ){
        r.data.bgActivePosistion = undefined;
        r.hoverData.selecting = true;

        r.redrawHint('select', true);
        r.redraw();

      } else if( !r.hoverData.selecting && cy.panningEnabled() && cy.userPanningEnabled() ){
        r.hoverData.dragging = true;
        r.hoverData.justStartedPan = true;
        select[4] = 0;

        r.data.bgActivePosistion = {
          x: pos[0],
          y: pos[1]
        };

        r.redrawHint('select', true);
        r.redraw();
      }

      if( down && down.isEdge() && down.active() ){ down.unactivate(); }

    } else {
      if( down && down.isEdge() && down.active() ){ down.unactivate(); }

      if (near != last) {

        if (last) {
          triggerEvents( last, ['mouseout', 'tapdragout'], e, {
            cyPosition: { x: pos[0], y: pos[1] }
          } );
        }

        if (near) {
          triggerEvents( near, ['mouseover', 'tapdragover'], e, {
            cyPosition: { x: pos[0], y: pos[1] }
          } );
        }

        r.hoverData.last = near;
      }

      if( down && down.isNode() && r.nodeIsDraggable(down) ){

        if( rdist2 >= r.desktopTapThreshold2 ){ // then drag

          var justStartedDrag = !r.dragData.didDrag;

          if( justStartedDrag ) {
            r.redrawHint('eles', true);
          }

          r.dragData.didDrag = true; // indicate that we actually did drag the node

          var toTrigger = [];

          for( var i = 0; i < draggedElements.length; i++ ){
            var dEle = draggedElements[i];

            // now, add the elements to the drag layer if not done already
            if( !r.hoverData.draggingEles ){
              addNodeToDrag( dEle, { inDragLayer: true } );
            }

            // Locked nodes not draggable, as well as non-visible nodes
            if( dEle.isNode() && r.nodeIsDraggable(dEle) && dEle.grabbed() ){
              var dPos = dEle._private.position;

              toTrigger.push( dEle );

              if( is.number(disp[0]) && is.number(disp[1]) ){
                var updatePos = !dEle.isParent();

                if( updatePos ){
                  dPos.x += disp[0];
                  dPos.y += disp[1];
                }

                if( justStartedDrag ){
                  var dragDelta = r.hoverData.dragDelta;

                  if( updatePos && is.number(dragDelta[0]) && is.number(dragDelta[1]) ){
                    dPos.x += dragDelta[0];
                    dPos.y += dragDelta[1];
                  }
                }
              }

            }
          }

          r.hoverData.draggingEles = true;

          var tcol = (Collection(cy, toTrigger));

          tcol.updateCompoundBounds();
          tcol.trigger('position drag');

          r.redrawHint('drag', true);
          r.redraw();

        } else { // otherwise save drag delta for when we actually start dragging so the relative grab pos is constant
          updateDragDelta();
        }
      }

      // prevent the dragging from triggering text selection on the page
      preventDefault = true;
    }

    select[2] = pos[0]; select[3] = pos[1];

    if( preventDefault ){
      if(e.stopPropagation) e.stopPropagation();
        if(e.preventDefault) e.preventDefault();
        return false;
      }
  }, false);

  r.registerBinding(window, 'mouseup', function(e) {
    var capture = r.hoverData.capture;
    if (!capture) { return; }
    r.hoverData.capture = false;

    var cy = r.cy; var pos = r.projectIntoViewport(e.clientX, e.clientY); var select = r.selection;
    var near = r.findNearestElement(pos[0], pos[1], true, false);
    var draggedElements = r.dragData.possibleDragElements; var down = r.hoverData.down;
    var multSelKeyDown = isMultSelKeyDown( e );

    if( r.data.bgActivePosistion ){
      r.redrawHint('select', true);
      r.redraw();
    }

    r.hoverData.tapholdCancelled = true;

    r.data.bgActivePosistion = undefined; // not active bg now

    if( down ){
      down.unactivate();
    }

    if( r.hoverData.which === 3 ){
      var cxtEvt = Event(e, {
        type: 'cxttapend',
        cyPosition: { x: pos[0], y: pos[1] }
      });

      if( down ){
        down.trigger( cxtEvt );
      } else {
        cy.trigger( cxtEvt );
      }

      if( !r.hoverData.cxtDragged ){
        var cxtTap = Event(e, {
          type: 'cxttap',
          cyPosition: { x: pos[0], y: pos[1] }
        });

        if( down ){
          down.trigger( cxtTap );
        } else {
          cy.trigger( cxtTap );
        }
      }

      r.hoverData.cxtDragged = false;
      r.hoverData.which = null;

    } else if( r.hoverData.which === 1 ) {

      // Deselect all elements if nothing is currently under the mouse cursor and we aren't dragging something
      if ( (down == null) // not mousedown on node
        && !r.dragData.didDrag // didn't move the node around
        && !r.hoverData.selecting // not box selection
        && !r.hoverData.dragged // didn't pan
        && !isMultSelKeyDown( e )
      ) {

        cy.$(function(){
          return this.selected();
        }).unselect();

        if (draggedElements.length > 0) {
          r.redrawHint('eles', true);
        }

        r.dragData.possibleDragElements = draggedElements = [];
      }

      triggerEvents( near, ['mouseup', 'tapend', 'vmouseup'], e, {
        cyPosition: { x: pos[0], y: pos[1] }
      } );

      if(
        !r.dragData.didDrag // didn't move a node around
        && !r.hoverData.dragged // didn't pan
      ){
        triggerEvents( near, ['click', 'tap', 'vclick'], e, {
          cyPosition: { x: pos[0], y: pos[1] }
        } );
      }

      // Single selection
      if( near == down && !r.dragData.didDrag && !r.hoverData.selecting ){
        if( near != null && near._private.selectable ){

          if( r.hoverData.dragging ){
            // if panning, don't change selection state
          } else if( cy.selectionType() === 'additive' || multSelKeyDown ){
            if( near.selected() ){
              near.unselect();
            } else {
              near.select();
            }
          } else {
            if( !multSelKeyDown ){
              cy.$(':selected').unmerge( near ).unselect();
              near.select();
            }
          }

          r.redrawHint('eles', true);
        }
      }

      if ( r.hoverData.selecting ) {
        var newlySelected = [];
        var box = r.getAllInBox( select[0], select[1], select[2], select[3] );

        r.redrawHint('select', true);

        if( box.length > 0 ) {
          r.redrawHint('eles', true);
        }

        for( var i = 0; i < box.length; i++ ){
          if( box[i]._private.selectable ){
            newlySelected.push( box[i] );
          }
        }

        var newlySelCol = Collection( cy, newlySelected );

        if( cy.selectionType() === 'additive' ){
          newlySelCol.select();
        } else {
          if( !multSelKeyDown ){
            cy.$(':selected').unmerge( newlySelCol ).unselect();
          }

          newlySelCol.select();
        }

        // always need redraw in case eles unselectable
        r.redraw();

      }

      // Cancel drag pan
      if( r.hoverData.dragging ){
        r.hoverData.dragging = false;

        r.redrawHint('select', true);
        r.redrawHint('eles', true);

        r.redraw();
      }

      if (!select[4]) {


        r.redrawHint('drag', true);
        r.redrawHint('eles', true);

        freeDraggedElements( draggedElements );

        if( down ){ down.trigger('free'); }
      }

    } // else not right mouse

    select[4] = 0; r.hoverData.down = null;

    r.hoverData.cxtStarted = false;
    r.hoverData.draggingEles = false;
    r.hoverData.selecting = false;
    r.dragData.didDrag = false;
    r.hoverData.dragged = false;
    r.hoverData.dragDelta = [];

  }, false);

  var wheelHandler = function(e) {


    if( r.scrollingPage ){ return; } // while scrolling, ignore wheel-to-zoom

    var cy = r.cy;
    var pos = r.projectIntoViewport(e.clientX, e.clientY);
    var rpos = [pos[0] * cy.zoom() + cy.pan().x,
                  pos[1] * cy.zoom() + cy.pan().y];

    if( r.hoverData.draggingEles || r.hoverData.dragging || r.hoverData.cxtStarted || inBoxSelection() ){ // if pan dragging or cxt dragging, wheel movements make no zoom
      e.preventDefault();
      return;
    }

    if( cy.panningEnabled() && cy.userPanningEnabled() && cy.zoomingEnabled() && cy.userZoomingEnabled() ){
      e.preventDefault();

      r.data.wheelZooming = true;
      clearTimeout( r.data.wheelTimeout );
      r.data.wheelTimeout = setTimeout(function(){
        r.data.wheelZooming = false;

        r.redrawHint('eles', true);
        r.redraw();
      }, 150);

      var diff = e.deltaY / -250 || e.wheelDeltaY / 1000 || e.wheelDelta / 1000;
      diff = diff * r.wheelSensitivity;

      var needsWheelFix = e.deltaMode === 1;
      if( needsWheelFix ){ // fixes slow wheel events on ff/linux and ff/windows
        diff *= 33;
      }

      cy.zoom({
        level: cy.zoom() * Math.pow(10, diff),
        renderedPosition: { x: rpos[0], y: rpos[1] }
      });
    }

  };

  // Functions to help with whether mouse wheel should trigger zooming
  // --
  r.registerBinding(r.container, 'wheel', wheelHandler, true);

  // disable nonstandard wheel events
  // r.registerBinding(r.container, 'mousewheel', wheelHandler, true);
  // r.registerBinding(r.container, 'DOMMouseScroll', wheelHandler, true);
  // r.registerBinding(r.container, 'MozMousePixelScroll', wheelHandler, true); // older firefox

  r.registerBinding(window, 'scroll', function(e){
    r.scrollingPage = true;

    clearTimeout( r.scrollingPageTimeout );
    r.scrollingPageTimeout = setTimeout(function(){
      r.scrollingPage = false;
    }, 250);
  }, true);

  // Functions to help with handling mouseout/mouseover on the Cytoscape container
        // Handle mouseout on Cytoscape container
  r.registerBinding(r.container, 'mouseout', function(e) {
    var pos = r.projectIntoViewport(e.clientX, e.clientY);

    r.cy.trigger(Event(e, {
      type: 'mouseout',
      cyPosition: { x: pos[0], y: pos[1] }
    }));
  }, false);

  r.registerBinding(r.container, 'mouseover', function(e) {
    var pos = r.projectIntoViewport(e.clientX, e.clientY);

    r.cy.trigger(Event(e, {
      type: 'mouseover',
      cyPosition: { x: pos[0], y: pos[1] }
    }));
  }, false);

  var f1x1, f1y1, f2x1, f2y1; // starting points for pinch-to-zoom
  var distance1, distance1Sq; // initial distance between finger 1 and finger 2 for pinch-to-zoom
  var center1, modelCenter1; // center point on start pinch to zoom
  var offsetLeft, offsetTop;
  var containerWidth, containerHeight;
  var twoFingersStartInside;

  var distance = function(x1, y1, x2, y2){
    return Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
  };

  var distanceSq = function(x1, y1, x2, y2){
    return (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
  };

  var touchstartHandler;
  r.registerBinding(r.container, 'touchstart', touchstartHandler = function(e) {
    r.touchData.capture = true;
    r.data.bgActivePosistion = undefined;

    var cy = r.cy;
    var nodes = r.getCachedNodes();
    var edges = r.getCachedEdges();
    var now = r.touchData.now;
    var earlier = r.touchData.earlier;

    if (e.touches[0]) { var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); now[0] = pos[0]; now[1] = pos[1]; }
    if (e.touches[1]) { var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); now[2] = pos[0]; now[3] = pos[1]; }
    if (e.touches[2]) { var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); now[4] = pos[0]; now[5] = pos[1]; }


    // record starting points for pinch-to-zoom
    if( e.touches[1] ){

      // anything in the set of dragged eles should be released
      var release = function( eles ){
        for( var i = 0; i < eles.length; i++ ){
          eles[i]._private.grabbed = false;
          eles[i]._private.rscratch.inDragLayer = false;
          if( eles[i].active() ){ eles[i].unactivate(); }
        }
      };
      release(nodes);
      release(edges);

      var offsets = r.findContainerClientCoords();
      offsetLeft = offsets[0];
      offsetTop = offsets[1];
      containerWidth = offsets[2];
      containerHeight = offsets[3];

      f1x1 = e.touches[0].clientX - offsetLeft;
      f1y1 = e.touches[0].clientY - offsetTop;

      f2x1 = e.touches[1].clientX - offsetLeft;
      f2y1 = e.touches[1].clientY - offsetTop;

      twoFingersStartInside =
           0 <= f1x1 && f1x1 <= containerWidth
        && 0 <= f2x1 && f2x1 <= containerWidth
        && 0 <= f1y1 && f1y1 <= containerHeight
        && 0 <= f2y1 && f2y1 <= containerHeight
      ;

      var pan = cy.pan();
      var zoom = cy.zoom();

      distance1 = distance( f1x1, f1y1, f2x1, f2y1 );
      distance1Sq = distanceSq( f1x1, f1y1, f2x1, f2y1 );
      center1 = [ (f1x1 + f2x1)/2, (f1y1 + f2y1)/2 ];
      modelCenter1 = [
        (center1[0] - pan.x) / zoom,
        (center1[1] - pan.y) / zoom
      ];

      // consider context tap
      var cxtDistThreshold = 200;
      var cxtDistThresholdSq = cxtDistThreshold * cxtDistThreshold;
      if( distance1Sq < cxtDistThresholdSq && !e.touches[2] ){

        var near1 = r.findNearestElement(now[0], now[1], true, true);
        var near2 = r.findNearestElement(now[2], now[3], true, true);

        if( near1 && near1.isNode() ){
          near1.activate().trigger( Event(e, {
            type: 'cxttapstart',
            cyPosition: { x: now[0], y: now[1] }
          }) );
          r.touchData.start = near1;

        } else if( near2 && near2.isNode() ){
          near2.activate().trigger( Event(e, {
            type: 'cxttapstart',
            cyPosition: { x: now[0], y: now[1] }
          }) );
          r.touchData.start = near2;

        } else {
          cy.trigger( Event(e, {
            type: 'cxttapstart',
            cyPosition: { x: now[0], y: now[1] }
          }) );
          r.touchData.start = null;
        }

        if( r.touchData.start ){ r.touchData.start._private.grabbed = false; }
        r.touchData.cxt = true;
        r.touchData.cxtDragged = false;
        r.data.bgActivePosistion = undefined;

        r.redraw();
        return;

      }

    }

    if (e.touches[2]) {

    } else if (e.touches[1]) {

    } else if (e.touches[0]) {
      var near = r.findNearestElement(now[0], now[1], true, true);

      if (near != null) {
        near.activate();

        r.touchData.start = near;

        if( near.isNode() && r.nodeIsDraggable(near) ){

          var draggedEles = r.dragData.touchDragEles = [];

          r.redrawHint('eles', true);
          r.redrawHint('drag', true);

          if( near.selected() ){
            // reset drag elements, since near will be added again

            var selectedNodes = cy.$(function(){
              return this.isNode() && this.selected();
            });

            for( var k = 0; k < selectedNodes.length; k++ ){
              var selectedNode = selectedNodes[k];

              if( r.nodeIsDraggable(selectedNode) ){
                addNodeToDrag( selectedNode, { addToList: draggedEles } );
              }
            }
          } else {
            addNodeToDrag( near, { addToList: draggedEles } );
          }

          near.trigger( Event(e, {
            type: 'grab',
            cyPosition: { x: now[0], y: now[1] }
          }) );
        }
      }

      triggerEvents( near, ['touchstart', 'tapstart', 'vmousedown'], e, {
        cyPosition: { x: now[0], y: now[1] }
      } );

      if (near == null) {
        r.data.bgActivePosistion = {
          x: pos[0],
          y: pos[1]
        };

        r.redrawHint('select', true);
        r.redraw();
      }


      // Tap, taphold
      // -----

      for (var i=0; i= factorThresholdSq || distance2Sq >= distThresholdSq ){
        r.touchData.cxt = false;
        if( r.touchData.start ){ r.touchData.start.unactivate(); r.touchData.start = null; }
        r.data.bgActivePosistion = undefined;
        r.redrawHint('select', true);

        var cxtEvt = Event(e, {
          type: 'cxttapend',
          cyPosition: { x: now[0], y: now[1] }
        });
        if( r.touchData.start ){
          r.touchData.start.trigger( cxtEvt );
        } else {
          cy.trigger( cxtEvt );
        }
      }

    }

    // context swipe
    if( capture && r.touchData.cxt ){
      var cxtEvt = Event(e, {
        type: 'cxtdrag',
        cyPosition: { x: now[0], y: now[1] }
      });
      r.data.bgActivePosistion = undefined;
      r.redrawHint('select', true);

      if( r.touchData.start ){
        r.touchData.start.trigger( cxtEvt );
      } else {
        cy.trigger( cxtEvt );
      }

      if( r.touchData.start ){ r.touchData.start._private.grabbed = false; }
      r.touchData.cxtDragged = true;

      var near = r.findNearestElement(now[0], now[1], true, true);

      if( !r.touchData.cxtOver || near !== r.touchData.cxtOver ){

        if( r.touchData.cxtOver ){
          r.touchData.cxtOver.trigger( Event(e, {
            type: 'cxtdragout',
            cyPosition: { x: now[0], y: now[1] }
          }) );
        }

        r.touchData.cxtOver = near;

        if( near ){
          near.trigger( Event(e, {
            type: 'cxtdragover',
            cyPosition: { x: now[0], y: now[1] }
          }) );

        }

      }

    // box selection
    } else if( capture && e.touches[2] && cy.boxSelectionEnabled() ){
      e.preventDefault();

      r.data.bgActivePosistion = undefined;

      this.lastThreeTouch = +new Date();
      r.touchData.selecting = true;

      r.redrawHint('select', true);

      if( !select || select.length === 0 || select[0] === undefined ){
        select[0] = (now[0] + now[2] + now[4])/3;
        select[1] = (now[1] + now[3] + now[5])/3;
        select[2] = (now[0] + now[2] + now[4])/3 + 1;
        select[3] = (now[1] + now[3] + now[5])/3 + 1;
      } else {
        select[2] = (now[0] + now[2] + now[4])/3;
        select[3] = (now[1] + now[3] + now[5])/3;
      }

      select[4] = 1;
      r.touchData.selecting = true;

      r.redraw();

    // pinch to zoom
    } else if ( capture && e.touches[1] && cy.zoomingEnabled() && cy.panningEnabled() && cy.userZoomingEnabled() && cy.userPanningEnabled() ) { // two fingers => pinch to zoom
      e.preventDefault();

      r.data.bgActivePosistion = undefined;
      r.redrawHint('select', true);

      var draggedEles = r.dragData.touchDragEles;
      if( draggedEles ){
        r.redrawHint('drag', true);

        for( var i = 0; i < draggedEles.length; i++ ){
          draggedEles[i]._private.grabbed = false;
          draggedEles[i]._private.rscratch.inDragLayer = false;
        }
      }

      // (x2, y2) for fingers 1 and 2
      var f1x2 = e.touches[0].clientX - offsetLeft, f1y2 = e.touches[0].clientY - offsetTop;
      var f2x2 = e.touches[1].clientX - offsetLeft, f2y2 = e.touches[1].clientY - offsetTop;


      var distance2 = distance( f1x2, f1y2, f2x2, f2y2 );
      // var distance2Sq = distanceSq( f1x2, f1y2, f2x2, f2y2 );
      // var factor = Math.sqrt( distance2Sq ) / Math.sqrt( distance1Sq );
      var factor = distance2 / distance1;

      if( factor != 1 && twoFingersStartInside){
        // delta finger1
        var df1x = f1x2 - f1x1;
        var df1y = f1y2 - f1y1;

        // delta finger 2
        var df2x = f2x2 - f2x1;
        var df2y = f2y2 - f2y1;

        // translation is the normalised vector of the two fingers movement
        // i.e. so pinching cancels out and moving together pans
        var tx = (df1x + df2x)/2;
        var ty = (df1y + df2y)/2;

        // adjust factor by the speed multiplier
        // var speed = 1.5;
        // if( factor > 1 ){
        //   factor = (factor - 1) * speed + 1;
        // } else {
        //   factor = 1 - (1 - factor) * speed;
        // }

        // now calculate the zoom
        var zoom1 = cy.zoom();
        var zoom2 = zoom1 * factor;
        var pan1 = cy.pan();

        // the model center point converted to the current rendered pos
        var ctrx = modelCenter1[0] * zoom1 + pan1.x;
        var ctry = modelCenter1[1] * zoom1 + pan1.y;

        var pan2 = {
          x: -zoom2/zoom1 * (ctrx - pan1.x - tx) + ctrx,
          y: -zoom2/zoom1 * (ctry - pan1.y - ty) + ctry
        };

        // remove dragged eles
        if( r.touchData.start ){
          var draggedEles = r.dragData.touchDragEles;

          if( draggedEles ){ for( var i = 0; i < draggedEles.length; i++ ){
            var dEi_p = draggedEles[i]._private;

            dEi_p.grabbed = false;
            dEi_p.rscratch.inDragLayer = false;
          } }

          var start_p = r.touchData.start._private;
          start_p.active = false;
          start_p.grabbed = false;
          start_p.rscratch.inDragLayer = false;

          r.redrawHint('drag', true);

          r.touchData.start
            .trigger('free')
            .trigger('unactivate')
          ;
        }

        cy.viewport({
          zoom: zoom2,
          pan: pan2,
          cancelOnFailedZoom: true
        });

        distance1 = distance2;
        f1x1 = f1x2;
        f1y1 = f1y2;
        f2x1 = f2x2;
        f2y1 = f2y2;

        r.pinching = true;
      }

      // Re-project
      if (e.touches[0]) { var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); now[0] = pos[0]; now[1] = pos[1]; }
      if (e.touches[1]) { var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); now[2] = pos[0]; now[3] = pos[1]; }
      if (e.touches[2]) { var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); now[4] = pos[0]; now[5] = pos[1]; }

    } else if (e.touches[0]) {
      var start = r.touchData.start;
      var last = r.touchData.last;
      var near = near || r.findNearestElement(now[0], now[1], true, true);

      if( start != null ){
        e.preventDefault();
      }

      // dragging nodes
      if( start != null && start._private.group == 'nodes' && r.nodeIsDraggable(start) ){

        if( rdist2 >= r.touchTapThreshold2 ){ // then dragging can happen
          var draggedEles = r.dragData.touchDragEles;
          var justStartedDrag = !r.dragData.didDrag;

          for( var k = 0; k < draggedEles.length; k++ ){
            var draggedEle = draggedEles[k];

            if( justStartedDrag ){
              addNodeToDrag( draggedEle, { inDragLayer: true } );
            }

            if( r.nodeIsDraggable(draggedEle) && draggedEle.isNode() && draggedEle.grabbed() ){
              r.dragData.didDrag = true;
              var dPos = draggedEle._private.position;
              var updatePos = !draggedEle.isParent();

              if( updatePos && is.number(disp[0]) && is.number(disp[1]) ){
                dPos.x += disp[0];
                dPos.y += disp[1];
              }

              if( justStartedDrag ){
                r.redrawHint('eles', true);

                var dragDelta = r.touchData.dragDelta;

                if( updatePos && is.number(dragDelta[0]) && is.number(dragDelta[1]) ){
                  dPos.x += dragDelta[0];
                  dPos.y += dragDelta[1];
                }

              }
            }
          }

          var tcol = Collection(cy, draggedEles);

          tcol.updateCompoundBounds();
          tcol.trigger('position drag');

          r.hoverData.draggingEles = true;

          r.redrawHint('drag', true);

          if(
               r.touchData.startPosition[0] == earlier[0]
            && r.touchData.startPosition[1] == earlier[1]
          ){

            r.redrawHint('eles', true);
          }

          r.redraw();
        } else { // otherise keep track of drag delta for later
          var dragDelta = r.touchData.dragDelta = r.touchData.dragDelta || [];

          if( dragDelta.length === 0 ){
            dragDelta.push( disp[0] );
            dragDelta.push( disp[1] );
          } else {
            dragDelta[0] += disp[0];
            dragDelta[1] += disp[1];
          }
        }
      }

      // touchmove
      {
        triggerEvents( (start || near), ['touchmove', 'tapdrag', 'vmousemove'], e, {
          cyPosition: { x: now[0], y: now[1] }
        } );

        if (near != last) {
          if (last) { last.trigger(Event(e, { type: 'tapdragout', cyPosition: { x: now[0], y: now[1] } })); }
          if (near) { near.trigger(Event(e, { type: 'tapdragover', cyPosition: { x: now[0], y: now[1] } })); }
        }

        r.touchData.last = near;
      }

      // check to cancel taphold
      for (var i=0;i r.touchTapThreshold2 ){

          r.touchData.singleTouchMoved = true;
        }
      }

      // panning
      if(
          capture
          && ( start == null || start.isEdge() )
          && cy.panningEnabled() && cy.userPanningEnabled()
      ){

        e.preventDefault();

        if( r.swipePanning ){
          cy.panBy({
            x: disp[0] * zoom,
            y: disp[1] * zoom
          });

        } else if( rdist2 >= r.touchTapThreshold2 ){
          r.swipePanning = true;

          cy.panBy({
            x: dx * zoom,
            y: dy * zoom
          });

          if( start ){
            start.unactivate();

            if( !r.data.bgActivePosistion ){
              r.data.bgActivePosistion = {
                x: now[0],
                y: now[1]
              };
            }

            r.redrawHint('select', true);

            r.touchData.start = null;
          }
        }

        // Re-project
        var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY);
        now[0] = pos[0]; now[1] = pos[1];
      }
    }

    for (var j=0; j 0 ) {
        r.redrawHint('eles', true);
      } else {
        r.redraw();
      }
    }

    var updateStartStyle = false;

    if( start != null ){
      start._private.active = false;
      updateStartStyle = true;
      start.unactivate();
    }

    if (e.touches[2]) {
      r.data.bgActivePosistion = undefined;
      r.redrawHint('select', true);
    } else if (e.touches[1]) {

    } else if (e.touches[0]) {

    // Last touch released
    } else if (!e.touches[0]) {

      r.data.bgActivePosistion = undefined;
      r.redrawHint('select', true);

      var draggedEles = r.dragData.touchDragEles;

      if (start != null ) {

        var startWasGrabbed = start._private.grabbed;

        freeDraggedElements( draggedEles );

        r.redrawHint('drag', true);
        r.redrawHint('eles', true);

        if( startWasGrabbed ){
          start.trigger('free');
        }

        triggerEvents( start, ['touchend', 'tapend', 'vmouseup'], e, {
          cyPosition: { x: now[0], y: now[1] }
        } );

        start.unactivate();

        r.touchData.start = null;

      } else {
        var near = r.findNearestElement(now[0], now[1], true, true);

        triggerEvents( near, ['touchend', 'tapend', 'vmouseup'], e, {
          cyPosition: { x: now[0], y: now[1] }
        } );

      }

      var dx = r.touchData.startPosition[0] - now[0];
      var dx2 = dx * dx;
      var dy = r.touchData.startPosition[1] - now[1];
      var dy2 = dy * dy;
      var dist2 = dx2 + dy2;
      var rdist2 = dist2 * zoom * zoom;

      // Prepare to select the currently touched node, only if it hasn't been dragged past a certain distance
      if (start != null
          && !r.dragData.didDrag // didn't drag nodes around
          && start._private.selectable
          && rdist2 < r.touchTapThreshold2
          && !r.pinching // pinch to zoom should not affect selection
      ) {

        if( cy.selectionType() === 'single' ){
          cy.$(':selected').unmerge( start ).unselect();
          start.select();
        } else {
          if( start.selected() ){
            start.unselect();
          } else {
            start.select();
          }
        }

        updateStartStyle = true;


        r.redrawHint('eles', true);
      }

      // Tap event, roughly same as mouse click event for touch
      if( !r.touchData.singleTouchMoved ){
        triggerEvents( start, ['tap', 'vclick'], e, {
          cyPosition: { x: now[0], y: now[1] }
        } );
      }

      r.touchData.singleTouchMoved = true;
    }

    for( var j = 0; j < now.length; j++ ){ earlier[j] = now[j]; }

    r.dragData.didDrag = false; // reset for next mousedown

    if( e.touches.length === 0 ){
      r.touchData.dragDelta = [];
    }

    if( updateStartStyle && start ){
      start.updateStyle(false);
    }

    if( e.touches.length < 2 ){
      r.pinching = false;
      r.redrawHint('eles', true);
      r.redraw();
    }

    //r.redraw();

  }, false);

  // fallback compatibility layer for ms pointer events
  if( typeof TouchEvent === 'undefined' ){

    var pointers = [];

    var makeTouch = function( e ){
      return {
        clientX: e.clientX,
        clientY: e.clientY,
        force: 1,
        identifier: e.pointerId,
        pageX: e.pageX,
        pageY: e.pageY,
        radiusX: e.width/2,
        radiusY: e.height/2,
        screenX: e.screenX,
        screenY: e.screenY,
        target: e.target
      };
    };

    var makePointer = function( e ){
      return {
        event: e,
        touch: makeTouch(e)
      };
    };

    var addPointer = function( e ){
      pointers.push( makePointer(e) );
    };

    var removePointer = function( e ){
      for( var i = 0; i < pointers.length; i++ ){
        var p = pointers[i];

        if( p.event.pointerId === e.pointerId ){
          pointers.splice( i, 1 );
          return;
        }
      }
    };

    var updatePointer = function( e ){
      var p = pointers.filter(function( p ){
        return p.event.pointerId === e.pointerId;
      })[0];

      p.event = e;
      p.touch = makeTouch(e);
    };

    var addTouchesToEvent = function( e ){
      e.touches = pointers.map(function( p ){
        return p.touch;
      });
    };

    var pointerIsMouse = function( e ){
      return e.pointerType === 'mouse' || e.pointerType === 4;
    };

    r.registerBinding(r.container, 'pointerdown', function(e){
      if( pointerIsMouse(e) ){ return; } // mouse already handled

      e.preventDefault();

      addPointer( e );

      addTouchesToEvent( e );
      touchstartHandler( e );
    });

    r.registerBinding(r.container, 'pointerup', function(e){
      if( pointerIsMouse(e) ){ return; } // mouse already handled

      removePointer( e );

      addTouchesToEvent( e );
      touchendHandler( e );
    });

    r.registerBinding(r.container, 'pointercancel', function(e){
      if( pointerIsMouse(e) ){ return; } // mouse already handled

      removePointer( e );

      addTouchesToEvent( e );
      touchcancelHandler( e );
    });

    r.registerBinding(r.container, 'pointermove', function(e){
      if( pointerIsMouse(e) ){ return; } // mouse already handled

      e.preventDefault();

      updatePointer( e );

      addTouchesToEvent( e );
      touchmoveHandler( e );
    });

  }
};

module.exports = BRp;

},{"../../../collection":23,"../../../event":42,"../../../is":77,"../../../util":94}],60:[function(_dereq_,module,exports){
'use strict';

var math = _dereq_('../../../math');

var BRp = {};

BRp.registerNodeShapes = function(){
  var nodeShapes = this.nodeShapes = {};
  var renderer = this;

  nodeShapes['ellipse'] = {
    name: 'ellipse',

    draw: function( context, centerX, centerY, width, height ){
      renderer.nodeShapeImpl( this.name )( context, centerX, centerY, width, height );
    },

    intersectLine: function( nodeX, nodeY, width, height, x, y, padding ){
      return math.intersectLineEllipse(
        x, y,
        nodeX,
        nodeY,
        width / 2 + padding,
        height / 2 + padding)
      ;
    },

    checkPoint: function( x, y, padding, width, height, centerX, centerY ){
      x -= centerX;
      y -= centerY;

      x /= (width / 2 + padding);
      y /= (height / 2 + padding);

      return x*x + y*y <= 1;
    }
  };

  function generatePolygon( name, points ){
    return ( nodeShapes[name] = {
      name: name,

      points: points,

      draw: function( context, centerX, centerY, width, height ){
        renderer.nodeShapeImpl('polygon')( context, centerX, centerY, width, height, this.points );
      },

      intersectLine: function( nodeX, nodeY, width, height, x, y, padding ){
        return math.polygonIntersectLine(
            x, y,
            this.points,
            nodeX,
            nodeY,
            width / 2, height / 2,
            padding)
          ;
      },

      checkPoint: function( x, y, padding, width, height, centerX, centerY ){
        return math.pointInsidePolygon(x, y, nodeShapes[name].points,
          centerX, centerY, width, height, [0, -1], padding)
        ;
      }
    } );
  }

  generatePolygon( 'triangle', math.generateUnitNgonPointsFitToSquare(3, 0) );

  generatePolygon( 'square', math.generateUnitNgonPointsFitToSquare(4, 0) );
  nodeShapes['rectangle'] = nodeShapes['square'];

  nodeShapes['roundrectangle'] = {
    name: 'roundrectangle',

    points: math.generateUnitNgonPointsFitToSquare(4, 0),

    draw: function( context, centerX, centerY, width, height ){
      renderer.nodeShapeImpl( this.name )( context, centerX, centerY, width, height );
    },

    intersectLine: function( nodeX, nodeY, width, height, x, y, padding ){
      return math.roundRectangleIntersectLine(
        x, y,
        nodeX,
        nodeY,
        width, height,
        padding)
      ;
    },

    // Looks like the width passed into this function is actually the total width / 2
    checkPoint: function(
      x, y, padding, width, height, centerX, centerY ){

      var cornerRadius = math.getRoundRectangleRadius(width, height);

      // Check hBox
      if (math.pointInsidePolygon(x, y, this.points,
        centerX, centerY, width, height - 2 * cornerRadius, [0, -1], padding) ){
        return true;
      }

      // Check vBox
      if (math.pointInsidePolygon(x, y, this.points,
        centerX, centerY, width - 2 * cornerRadius, height, [0, -1], padding) ){
        return true;
      }

      var checkInEllipse = function( x, y, centerX, centerY, width, height, padding ){
        x -= centerX;
        y -= centerY;

        x /= (width / 2 + padding);
        y /= (height / 2 + padding);

        return (x*x + y*y <= 1);
      };


      // Check top left quarter circle
      if (checkInEllipse(x, y,
        centerX - width / 2 + cornerRadius,
        centerY - height / 2 + cornerRadius,
        cornerRadius * 2, cornerRadius * 2, padding) ){

        return true;
      }

      // Check top right quarter circle
      if (checkInEllipse(x, y,
        centerX + width / 2 - cornerRadius,
        centerY - height / 2 + cornerRadius,
        cornerRadius * 2, cornerRadius * 2, padding) ){

        return true;
      }

      // Check bottom right quarter circle
      if (checkInEllipse(x, y,
        centerX + width / 2 - cornerRadius,
        centerY + height / 2 - cornerRadius,
        cornerRadius * 2, cornerRadius * 2, padding) ){

        return true;
      }

      // Check bottom left quarter circle
      if (checkInEllipse(x, y,
        centerX - width / 2 + cornerRadius,
        centerY + height / 2 - cornerRadius,
        cornerRadius * 2, cornerRadius * 2, padding) ){

        return true;
      }

      return false;
    }
  };

  generatePolygon( 'diamond', [
    0, 1,
    1, 0,
    0, -1,
    -1, 0
  ] );

  generatePolygon( 'pentagon', math.generateUnitNgonPointsFitToSquare(5, 0) );

  generatePolygon( 'hexagon', math.generateUnitNgonPointsFitToSquare(6, 0) );

  generatePolygon( 'heptagon', math.generateUnitNgonPointsFitToSquare(7, 0) );

  generatePolygon( 'octagon', math.generateUnitNgonPointsFitToSquare(8, 0) );

  var star5Points = new Array(20);
  {
    var outerPoints = math.generateUnitNgonPoints(5, 0);
    var innerPoints = math.generateUnitNgonPoints(5, Math.PI / 5);

    // Outer radius is 1; inner radius of star is smaller
    var innerRadius = 0.5 * (3 - Math.sqrt(5));
    innerRadius *= 1.57;

    for (var i=0;i redrawLimit ? minRedrawLimit : redrawLimit;
  redrawLimit = redrawLimit < maxRedrawLimit ? redrawLimit : maxRedrawLimit;

  if( r.lastDrawTime === undefined ){ r.lastDrawTime = 0; }

  var nowTime = Date.now();
  var timeElapsed = nowTime - r.lastDrawTime;
  var callAfterLimit = timeElapsed >= redrawLimit;

  if( !forcedContext ){
    if( !callAfterLimit || r.currentlyDrawing ){
      r.skipFrame = true;
      return;
    }
  }

  r.requestedFrame = true;
  r.currentlyDrawing = true;
  r.renderOptions = options;
};

BRp.startRenderLoop = function(){
  var r = this;

  var renderFn = function(){
    if( r.destroyed ){ return; }

    if( r.requestedFrame && !r.skipFrame ){
      var startTime = util.performanceNow();

      r.render( r.renderOptions );

      var endTime = r.lastRedrawTime = util.performanceNow();

      if( r.averageRedrawTime === undefined ){
        r.averageRedrawTime = endTime - startTime;
      }

      if( r.redrawCount === undefined ){
        r.redrawCount = 0;
      }

      r.redrawCount++;

      if( r.redrawTotalTime === undefined ){
        r.redrawTotalTime = 0;
      }

      var duration = endTime - startTime;

      r.redrawTotalTime += duration;
      r.lastRedrawTime = duration;

      // use a weighted average with a bias from the previous average so we don't spike so easily
      r.averageRedrawTime = r.averageRedrawTime/2 + duration/2;

      r.requestedFrame = false;
    }

    r.skipFrame = false;

    util.requestAnimationFrame( renderFn );
  };

  util.requestAnimationFrame( renderFn );

};

module.exports = BRp;

},{"../../../util":94}],62:[function(_dereq_,module,exports){
'use strict';

var CRp = {};

var impl;

CRp.arrowShapeImpl = function( name ){
  return ( impl || (impl = {
    'polygon': function( context, points ){
      for( var i = 0; i < points.length; i++ ){
        var pt = points[i];

        context.lineTo( pt.x, pt.y );
      }
    },

    'triangle-backcurve': function( context, points, controlPoint ){
      var firstPt;

      for( var i = 0; i < points.length; i++ ){
        var pt = points[i];

        if( i === 0 ){
          firstPt = pt;
        }

        context.lineTo( pt.x, pt.y );
      }

      context.quadraticCurveTo( controlPoint.x, controlPoint.y, firstPt.x, firstPt.y );
    },

    'triangle-tee': function( context, trianglePoints, teePoints ){
      var triPts = trianglePoints;
      for( var i = 0; i < triPts.length; i++ ){
        var pt = triPts[i];

        context.lineTo( pt.x, pt.y );
      }

      var teePts = teePoints;
      var firstTeePt = teePoints[0];
      context.moveTo( firstTeePt.x, firstTeePt.y );

      for( var i = 0; i < teePts.length; i++ ){
        var pt = teePts[i];

        context.lineTo( pt.x, pt.y );
      }
    },

    'circle': function( context, rx, ry, r ){
      context.arc(rx, ry, r, 0, Math.PI * 2, false);
    }
  }) )[ name ];
};

module.exports = CRp;

},{}],63:[function(_dereq_,module,exports){
'use strict';

var CRp = {};

CRp.drawEdge = function(context, edge, drawOverlayInstead) {
  var rs = edge._private.rscratch;
  var usePaths = this.usePaths();

  // if bezier ctrl pts can not be calculated, then die
  if( rs.badBezier || rs.badLine || isNaN( rs.allpts[0] ) ){ // iNaN in case edge is impossible and browser bugs (e.g. safari)
    return;
  }

  var style = edge._private.style;

  // Edge line width
  if (style['width'].pfValue <= 0) {
    return;
  }

  var overlayPadding = style['overlay-padding'].pfValue;
  var overlayOpacity = style['overlay-opacity'].value;
  var overlayColor = style['overlay-color'].value;

  // Edge color & opacity
  if( drawOverlayInstead ){

    if( overlayOpacity === 0 ){ // exit early if no overlay
      return;
    }

    this.strokeStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity);
    context.lineCap = 'round';

    if( rs.edgeType == 'self' && !usePaths ){
      context.lineCap = 'butt';
    }

  } else {
    var lineColor = style['line-color'].value;

    this.strokeStyle(context, lineColor[0], lineColor[1], lineColor[2], style.opacity.value);

    context.lineCap = 'butt';
  }

  var edgeWidth = style['width'].pfValue + (drawOverlayInstead ? 2 * overlayPadding : 0);
  var lineStyle = drawOverlayInstead ? 'solid' : style['line-style'].value;
  context.lineWidth = edgeWidth;

  var shadowBlur = style['shadow-blur'].pfValue;
  var shadowOpacity = style['shadow-opacity'].value;
  var shadowColor = style['shadow-color'].value;
  var shadowOffsetX = style['shadow-offset-x'].pfValue;
  var shadowOffsetY = style['shadow-offset-y'].pfValue;

  this.shadowStyle(context,  shadowColor, drawOverlayInstead ? 0 : shadowOpacity, shadowBlur, shadowOffsetX, shadowOffsetY);

  this.drawEdgePath(
    edge,
    context,
    rs.allpts,
    lineStyle,
    edgeWidth
  );

  this.drawArrowheads(context, edge, drawOverlayInstead);

  this.shadowStyle(context, 'transparent', 0); // reset for next guy

};


CRp.drawEdgePath = function(edge, context, pts, type, width) {
  var rs = edge._private.rscratch;
  var canvasCxt = context;
  var path;
  var pathCacheHit = false;
  var usePaths = this.usePaths();

  if( usePaths ){
    var pathCacheKey = pts.join('$');
    var keyMatches = rs.pathCacheKey && rs.pathCacheKey === pathCacheKey;

    if( keyMatches ){
      path = context = rs.pathCache;
      pathCacheHit = true;
    } else {
      path = context = new Path2D();
      rs.pathCacheKey = pathCacheKey;
      rs.pathCache = path;
    }
  }

  if( canvasCxt.setLineDash ){ // for very outofdate browsers
    switch( type ){
      case 'dotted':
        canvasCxt.setLineDash([ 1, 1 ]);
        break;

      case 'dashed':
        canvasCxt.setLineDash([ 6, 3 ]);
        break;

      case 'solid':
        canvasCxt.setLineDash([ ]);
        break;
    }
  }

  if( !pathCacheHit ){
    if( context.beginPath ){ context.beginPath(); }
    context.moveTo( pts[0], pts[1] );

    switch( rs.edgeType ){
      case 'bezier':
      case 'self':
      case 'compound':
      case 'multibezier':
        if( !rs.badBezier ){
          for( var i = 2; i + 3 < pts.length; i += 4 ){
            context.quadraticCurveTo( pts[i], pts[i+1], pts[i+2], pts[i+3] );
          }
        }
        break;

      case 'straight':
      case 'segments':
      case 'haystack':
        if( !rs.badLine ){
          for( var i = 2; i + 1 < pts.length; i += 2 ){
            context.lineTo( pts[i], pts[i+1] );
          }
        }
        break;
    }
  }

  context = canvasCxt;
  if( usePaths ){
    context.stroke( path );
  } else {
    context.stroke();
  }

  // reset any line dashes
  if( context.setLineDash ){ // for very outofdate browsers
    context.setLineDash([ ]);
  }

};

CRp.drawArrowheads = function(context, edge, drawOverlayInstead) {
  if( drawOverlayInstead ){ return; } // don't do anything for overlays

  var rs = edge._private.rscratch;
  var isHaystack = rs.edgeType === 'haystack';

  if( !isHaystack ){
    this.drawArrowhead( context, edge, 'source', rs.arrowStartX, rs.arrowStartY, rs.srcArrowAngle );
  }

  this.drawArrowhead( context, edge, 'mid-target', rs.midX, rs.midY, rs.midtgtArrowAngle );

  this.drawArrowhead( context, edge, 'mid-source', rs.midX, rs.midY, rs.midsrcArrowAngle );

  if( !isHaystack ){
    this.drawArrowhead( context, edge, 'target', rs.arrowEndX, rs.arrowEndY, rs.tgtArrowAngle );
  }
};

CRp.drawArrowhead = function( context, edge, prefix, x, y, angle ){
  if( isNaN(x) || x == null || isNaN(y) || y == null || isNaN(angle) || angle == null ){ return; }

  var self = this;
  var style = edge._private.style;
  var arrowShape = style[prefix + '-arrow-shape'].value;

  if( arrowShape === 'none' ){
    return;
  }

  var gco = context.globalCompositeOperation;

  var arrowClearFill = style[prefix + '-arrow-fill'].value === 'hollow' ? 'both' : 'filled';
  var arrowFill = style[prefix + '-arrow-fill'].value;

  if( arrowShape === 'half-triangle-overshot' ){
    arrowFill = 'hollow';
    arrowClearFill = 'hollow';
  }

  if( style.opacity.value !== 1 || arrowFill === 'hollow' ){ // then extra clear is needed
    context.globalCompositeOperation = 'destination-out';

    self.fillStyle(context, 255, 255, 255, 1);
    self.strokeStyle(context, 255, 255, 255, 1);

    self.drawArrowShape( edge, prefix, context,
      arrowClearFill, style['width'].pfValue, style[prefix + '-arrow-shape'].value,
      x, y, angle
    );

    context.globalCompositeOperation = gco;
  } // otherwise, the opaque arrow clears it for free :)

  var color = style[prefix + '-arrow-color'].value;
  self.fillStyle(context, color[0], color[1], color[2], style.opacity.value);
  self.strokeStyle(context, color[0], color[1], color[2], style.opacity.value);

  self.drawArrowShape( edge, prefix, context,
    arrowFill, style['width'].pfValue, style[prefix + '-arrow-shape'].value,
    x, y, angle
  );
};

CRp.drawArrowShape = function(edge, arrowType, context, fill, edgeWidth, shape, x, y, angle) {
  var r = this;
  var usePaths = this.usePaths();
  var rs = edge._private.rscratch;
  var pathCacheHit = false;
  var path;
  var canvasContext = context;
  var translation = { x: x, y: y };
  var size = this.getArrowWidth( edgeWidth );
  var shapeImpl = r.arrowShapes[shape];

  if( usePaths ){
    var pathCacheKey = size + '$' + shape + '$' + angle + '$' + x + '$' + y;
    rs.arrowPathCacheKey = rs.arrowPathCacheKey || {};
    rs.arrowPathCache = rs.arrowPathCache || {};

    var alreadyCached = rs.arrowPathCacheKey[arrowType] === pathCacheKey;
    if( alreadyCached ){
      path = context = rs.arrowPathCache[arrowType];
      pathCacheHit = true;
    } else {
      path = context = new Path2D();
      rs.arrowPathCacheKey[arrowType] = pathCacheKey;
      rs.arrowPathCache[arrowType] = path;
    }
  }

  if( context.beginPath ){ context.beginPath(); }

  if( !pathCacheHit ){
    shapeImpl.draw(context, size, angle, translation);
  }

  if( !shapeImpl.leavePathOpen && context.closePath ){
    context.closePath();
  }

  context = canvasContext;

  if( fill === 'filled' || fill === 'both' ){
    if( usePaths ){
      context.fill( path );
    } else {
      context.fill();
    }
  }

  if( fill === 'hollow' || fill === 'both' ){
    context.lineWidth = ( shapeImpl.matchEdgeWidth ? edgeWidth : 1 );
    context.lineJoin = 'miter';

    if( usePaths ){
      context.stroke( path );
    } else {
      context.stroke();
    }

  }
};

module.exports = CRp;

},{}],64:[function(_dereq_,module,exports){
'use strict';

var CRp = {};

CRp.safeDrawImage = function( context, img, ix, iy, iw, ih, x, y, w, h ){
  var r = this;

  try {
    context.drawImage( img, ix, iy, iw, ih, x, y, w, h );
  } catch(e){
    r.data.canvasNeedsRedraw[r.NODE] = true;
    r.data.canvasNeedsRedraw[r.DRAG] = true;

    r.drawingImage = true;

    r.redraw();
  }
};

CRp.drawInscribedImage = function(context, img, node) {
  var r = this;
  var nodeX = node._private.position.x;
  var nodeY = node._private.position.y;
  var style = node._private.style;
  var fit = style['background-fit'].value;
  var xPos = style['background-position-x'];
  var yPos = style['background-position-y'];
  var repeat = style['background-repeat'].value;
  var nodeW = node.width();
  var nodeH = node.height();
  var rs = node._private.rscratch;
  var clip = style['background-clip'].value;
  var shouldClip = clip === 'node';
  var imgOpacity = style['background-image-opacity'].value;

  var imgW = img.width || img.cachedW;
  var imgH = img.height || img.cachedH;

  // workaround for broken browsers like ie
  if( null == imgW || null == imgH ){
    document.body.appendChild( img );

    imgW = img.cachedW = img.width || img.offsetWidth;
    imgH = img.cachedH = img.height || img.offsetHeight;

    document.body.removeChild( img );
  }

  var w = imgW;
  var h = imgH;

  var bgW = style['background-width'];
  if( bgW.value !== 'auto' ){
    if( bgW.units === '%' ){
      w = bgW.value/100 * nodeW;
    } else {
      w = bgW.pfValue;
    }
  }

  var bgH = style['background-height'];
  if( bgH.value !== 'auto' ){
    if( bgH.units === '%' ){
      h = bgH.value/100 * nodeH;
    } else {
      h = bgH.pfValue;
    }
  }

  if( w === 0 || h === 0 ){
    return; // no point in drawing empty image (and chrome is broken in this case)
  }

  if( fit === 'contain' ){
    var scale = Math.min( nodeW/w, nodeH/h );

    w *= scale;
    h *= scale;

  } else if( fit === 'cover' ){
    var scale = Math.max( nodeW/w, nodeH/h );

    w *= scale;
    h *= scale;
  }

  var x = (nodeX - nodeW/2); // left
  if( xPos.units === '%' ){
    x += (nodeW - w) * xPos.value/100;
  } else {
    x += xPos.pfValue;
  }

  var y = (nodeY - nodeH/2); // top
  if( yPos.units === '%' ){
    y += (nodeH - h) * yPos.value/100;
  } else {
    y += yPos.pfValue;
  }

  if( rs.pathCache ){
    x -= nodeX;
    y -= nodeY;

    nodeX = 0;
    nodeY = 0;
  }

  var gAlpha = context.globalAlpha;

  context.globalAlpha = imgOpacity;

  if( repeat === 'no-repeat' ){

    if( shouldClip ){
      context.save();

      if( rs.pathCache ){
        context.clip( rs.pathCache );
      } else {
        r.nodeShapes[r.getNodeShape(node)].draw(
          context,
          nodeX, nodeY,
          nodeW, nodeH);

        context.clip();
      }
    }

    r.safeDrawImage( context, img, 0, 0, imgW, imgH, x, y, w, h );

    if( shouldClip ){
      context.restore();
    }
  } else {
    var pattern = context.createPattern( img, repeat );
    context.fillStyle = pattern;

    r.nodeShapes[r.getNodeShape(node)].draw(
        context,
        nodeX, nodeY,
        nodeW, nodeH);

      context.translate(x, y);
      context.fill();
      context.translate(-x, -y);
  }

  context.globalAlpha = gAlpha;

};

module.exports = CRp;

},{}],65:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../../is');

var CRp = {};

// Draw edge text
CRp.drawEdgeText = function(context, edge) {
  var text = edge._private.style['label'].strValue;

  if( !text || text.match(/^\s+$/) ){
    return;
  }

  if( this.hideEdgesOnViewport && (this.dragData.didDrag || this.pinching || this.hoverData.dragging || this.data.wheel || this.swipePanning) ){ return; } // save cycles on pinching

  var computedSize = edge._private.style['font-size'].pfValue * edge.cy().zoom();
  var minSize = edge._private.style['min-zoomed-font-size'].pfValue;

  if( computedSize < minSize ){
    return;
  }

  // Calculate text draw position

  context.textAlign = 'center';
  context.textBaseline = 'middle';

  var rs = edge._private.rscratch;
  if( !is.number( rs.labelX ) || !is.number( rs.labelY ) ){ return; } // no pos => label can't be rendered

  var style = edge._private.style;
  var autorotate = style['edge-text-rotation'].strValue === 'autorotate';
  var theta;

  if( autorotate ){
    theta = rs.labelAngle;

    context.translate(rs.labelX, rs.labelY);
    context.rotate(theta);

    this.drawText(context, edge, 0, 0);

    context.rotate(-theta);
    context.translate(-rs.labelX, -rs.labelY);
  } else {
    this.drawText(context, edge, rs.labelX, rs.labelY);
  }

};

// Draw node text
CRp.drawNodeText = function(context, node) {
  var text = node._private.style['label'].strValue;

  if ( !text || text.match(/^\s+$/) ) {
    return;
  }

  var computedSize = node._private.style['font-size'].pfValue * node.cy().zoom();
  var minSize = node._private.style['min-zoomed-font-size'].pfValue;

  if( computedSize < minSize ){
    return;
  }

  // this.recalculateNodeLabelProjection( node );

  var textHalign = node._private.style['text-halign'].strValue;
  var textValign = node._private.style['text-valign'].strValue;
  var rs = node._private.rscratch;
  if( !is.number( rs.labelX ) || !is.number( rs.labelY ) ){ return; } // no pos => label can't be rendered

  switch( textHalign ){
    case 'left':
      context.textAlign = 'right';
      break;

    case 'right':
      context.textAlign = 'left';
      break;

    default: // e.g. center
      context.textAlign = 'center';
  }

  switch( textValign ){
    case 'top':
      context.textBaseline = 'bottom';
      break;

    case 'bottom':
      context.textBaseline = 'top';
      break;

    default: // e.g. center
      context.textBaseline = 'middle';
  }

  this.drawText(context, node, rs.labelX, rs.labelY);
};

CRp.getFontCache = function(context){
  var cache;

  this.fontCaches = this.fontCaches || [];

  for( var i = 0; i < this.fontCaches.length; i++ ){
    cache = this.fontCaches[i];

    if( cache.context === context ){
      return cache;
    }
  }

  cache = {
    context: context
  };
  this.fontCaches.push(cache);

  return cache;
};

// set up canvas context with font
// returns transformed text string
CRp.setupTextStyle = function( context, element ){
  // Font style
  var parentOpacity = element.effectiveOpacity();
  var style = element._private.style;
  var labelStyle = style['font-style'].strValue;
  var labelSize = style['font-size'].pfValue + 'px';
  var labelFamily = style['font-family'].strValue;
  var labelWeight = style['font-weight'].strValue;
  var opacity = style['text-opacity'].value * style['opacity'].value * parentOpacity;
  var outlineOpacity = style['text-outline-opacity'].value * opacity;
  var color = style['color'].value;
  var outlineColor = style['text-outline-color'].value;
  var shadowBlur = style['text-shadow-blur'].pfValue;
  var shadowOpacity = style['text-shadow-opacity'].value;
  var shadowColor = style['text-shadow-color'].value;
  var shadowOffsetX = style['text-shadow-offset-x'].pfValue;
  var shadowOffsetY = style['text-shadow-offset-y'].pfValue;

  var fontCacheKey = element._private.fontKey;
  var cache = this.getFontCache(context);

  if( cache.key !== fontCacheKey ){
    context.font = labelStyle + ' ' + labelWeight + ' ' + labelSize + ' ' + labelFamily;

    cache.key = fontCacheKey;
  }

  var text = this.getLabelText( element );

  // Calculate text draw position based on text alignment

  // so text outlines aren't jagged
  context.lineJoin = 'round';

  this.fillStyle(context, color[0], color[1], color[2], opacity);

  this.strokeStyle(context, outlineColor[0], outlineColor[1], outlineColor[2], outlineOpacity);

  this.shadowStyle(context, shadowColor, shadowOpacity, shadowBlur, shadowOffsetX, shadowOffsetY);

  return text;
};

function roundRect(ctx, x, y, width, height, radius) {
  var radius = radius || 5;
  ctx.beginPath();
  ctx.moveTo(x + radius, y);
  ctx.lineTo(x + width - radius, y);
  ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
  ctx.lineTo(x + width, y + height - radius);
  ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  ctx.lineTo(x + radius, y + height);
  ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
  ctx.lineTo(x, y + radius);
  ctx.quadraticCurveTo(x, y, x + radius, y);
  ctx.closePath();
  ctx.fill();
}

// Draw text
CRp.drawText = function(context, element, textX, textY) {
  var _p = element._private;
  var style = _p.style;
  var rstyle = _p.rstyle;
  var rscratch = _p.rscratch;
  var parentOpacity = element.effectiveOpacity();
  if( parentOpacity === 0 || style['text-opacity'].value === 0){ return; }

  var text = this.setupTextStyle( context, element );
  var halign = style['text-halign'].value;
  var valign = style['text-valign'].value;

  if( element.isEdge() ){
    halign = 'center';
    valign = 'center';
  }

  if( element.isNode() ){
    var pLeft = style['padding-left'].pfValue;
    var pRight = style['padding-right'].pfValue;
    var pTop = style['padding-top'].pfValue;
    var pBottom = style['padding-bottom'].pfValue;

    textX += pLeft/2;
    textX -= pRight/2;

    textY += pTop/2;
    textY -= pBottom/2;
  }

  if ( text != null && !isNaN(textX) && !isNaN(textY)) {
    var backgroundOpacity = style['text-background-opacity'].value;
    var borderOpacity = style['text-border-opacity'].value;
    var textBorderWidth = style['text-border-width'].pfValue;

    if( backgroundOpacity > 0 || (textBorderWidth > 0 && borderOpacity > 0) ){
      var margin = 4 + textBorderWidth/2;

      if (element.isNode()) {
        //Move textX, textY to include the background margins
        if (valign === 'top') {
          textY -= margin;
        } else if (valign === 'bottom') {
          textY += margin;
        }
        if (halign === 'left') {
          textX -= margin;
        } else if (halign === 'right') {
          textX += margin;
        }
      }

      var bgWidth = rstyle.labelWidth;
      var bgHeight = rstyle.labelHeight;
      var bgX = textX;

      if (halign) {
        if (halign == 'center') {
          bgX = bgX - bgWidth / 2;
        } else if (halign == 'left') {
          bgX = bgX- bgWidth;
        }
      }

      var bgY = textY;

      if (element.isNode()) {
        if (valign == 'top') {
           bgY = bgY - bgHeight;
        } else if (valign == 'center') {
          bgY = bgY- bgHeight / 2;
        }
      } else {
        bgY = bgY - bgHeight / 2;
      }

      if (style['edge-text-rotation'].strValue === 'autorotate') {
        textY = 0;
        bgWidth += 4;
        bgX = textX - bgWidth / 2;
        bgY = textY - bgHeight / 2;
      } else {
        // Adjust with border width & margin
        bgX -= margin;
        bgY -= margin;
        bgHeight += margin*2;
        bgWidth += margin*2;
      }

      if( backgroundOpacity > 0 ){
        var textFill = context.fillStyle;
        var textBackgroundColor = style['text-background-color'].value;

        context.fillStyle = 'rgba(' + textBackgroundColor[0] + ',' + textBackgroundColor[1] + ',' + textBackgroundColor[2] + ',' + backgroundOpacity * parentOpacity + ')';
        var styleShape = style['text-background-shape'].strValue;
        if (styleShape == 'roundrectangle') {
          roundRect(context, bgX, bgY, bgWidth, bgHeight, 2);
        } else {
          context.fillRect(bgX,bgY,bgWidth,bgHeight);
        }
        context.fillStyle = textFill;
      }

      if( textBorderWidth > 0 && borderOpacity > 0 ){
        var textStroke = context.strokeStyle;
        var textLineWidth = context.lineWidth;
        var textBorderColor = style['text-border-color'].value;
        var textBorderStyle = style['text-border-style'].value;

        context.strokeStyle = 'rgba(' + textBorderColor[0] + ',' + textBorderColor[1] + ',' + textBorderColor[2] + ',' + borderOpacity * parentOpacity + ')';
        context.lineWidth = textBorderWidth;

        if( context.setLineDash ){ // for very outofdate browsers
          switch( textBorderStyle ){
            case 'dotted':
              context.setLineDash([ 1, 1 ]);
              break;
            case 'dashed':
              context.setLineDash([ 4, 2 ]);
              break;
            case 'double':
              context.lineWidth = textBorderWidth/4; // 50% reserved for white between the two borders
              context.setLineDash([ ]);
              break;
            case 'solid':
              context.setLineDash([ ]);
              break;
          }
        }

        context.strokeRect(bgX,bgY,bgWidth,bgHeight);

        if( textBorderStyle === 'double' ){
          var whiteWidth = textBorderWidth/2;

          context.strokeRect(bgX+whiteWidth,bgY+whiteWidth,bgWidth-whiteWidth*2,bgHeight-whiteWidth*2);
        }

        if( context.setLineDash ){ // for very outofdate browsers
          context.setLineDash([ ]);
        }
        context.lineWidth = textLineWidth;
        context.strokeStyle = textStroke;
      }

    }

    var lineWidth = 2  * style['text-outline-width'].pfValue; // *2 b/c the stroke is drawn centred on the middle

    if( lineWidth > 0 ){
      context.lineWidth = lineWidth;
    }

    if( style['text-wrap'].value === 'wrap' ){
      var lines = rscratch.labelWrapCachedLines;
      var lineHeight = rstyle.labelHeight / lines.length;

      switch( valign ){
        case 'top':
          textY -= (lines.length - 1) * lineHeight;
          break;

        case 'bottom':
          // nothing required
          break;

        default:
        case 'center':
          textY -= (lines.length - 1) * lineHeight / 2;
      }

      for( var l = 0; l < lines.length; l++ ){
        if( lineWidth > 0 ){
          context.strokeText( lines[l], textX, textY );
        }

        context.fillText( lines[l], textX, textY );

        textY += lineHeight;
      }

    } else {
      if( lineWidth > 0 ){
        context.strokeText( text, textX, textY );
      }

      context.fillText( text, textX, textY );
    }


    this.shadowStyle(context, 'transparent', 0); // reset for next guy
  }
};


module.exports = CRp;

},{"../../../is":77}],66:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../../is');

var CRp = {};

// Draw node
CRp.drawNode = function(context, node, drawOverlayInstead) {

  var r = this;
  var nodeWidth, nodeHeight;
  var style = node._private.style;
  var rs = node._private.rscratch;
  var _p = node._private;
  var pos = _p.position;

  if( !is.number(pos.x) || !is.number(pos.y) ){
    return; // can't draw node with undefined position
  }

  var usePaths = this.usePaths();
  var canvasContext = context;
  var path;
  var pathCacheHit = false;

  var overlayPadding = style['overlay-padding'].pfValue;
  var overlayOpacity = style['overlay-opacity'].value;
  var overlayColor = style['overlay-color'].value;

  if( drawOverlayInstead && overlayOpacity === 0 ){ // exit early if drawing overlay but none to draw
    return;
  }

  var parentOpacity = node.effectiveOpacity();
  if( parentOpacity === 0 ){ return; }

  nodeWidth = node.width() + style['padding-left'].pfValue + style['padding-right'].pfValue;
  nodeHeight = node.height() + style['padding-top'].pfValue + style['padding-bottom'].pfValue;

  context.lineWidth = style['border-width'].pfValue;

  if( drawOverlayInstead === undefined || !drawOverlayInstead ){

    var url = style['background-image'].value[2] ||
      style['background-image'].value[1];
    var image;

    if (url !== undefined) {

      // get image, and if not loaded then ask to redraw when later loaded
      image = this.getCachedImage(url, function(){
        r.data.canvasNeedsRedraw[r.NODE] = true;
        r.data.canvasNeedsRedraw[r.DRAG] = true;

        r.drawingImage = true;

        r.redraw();
      });

      var prevBging = _p.backgrounding;
      _p.backgrounding = !image.complete;

      if( prevBging !== _p.backgrounding ){ // update style b/c :backgrounding state changed
        node.updateStyle( false );
      }
    }

    // Node color & opacity

    var bgColor = style['background-color'].value;
    var borderColor = style['border-color'].value;
    var borderStyle = style['border-style'].value;

    this.fillStyle(context, bgColor[0], bgColor[1], bgColor[2], style['background-opacity'].value * parentOpacity);

    this.strokeStyle(context, borderColor[0], borderColor[1], borderColor[2], style['border-opacity'].value * parentOpacity);

    var shadowBlur = style['shadow-blur'].pfValue;
    var shadowOpacity = style['shadow-opacity'].value;
    var shadowColor = style['shadow-color'].value;
    var shadowOffsetX = style['shadow-offset-x'].pfValue;
    var shadowOffsetY = style['shadow-offset-y'].pfValue;

    this.shadowStyle(context, shadowColor, shadowOpacity, shadowBlur, shadowOffsetX, shadowOffsetY);

    context.lineJoin = 'miter'; // so borders are square with the node shape

    if( context.setLineDash ){ // for very outofdate browsers
      switch( borderStyle ){
        case 'dotted':
          context.setLineDash([ 1, 1 ]);
          break;

        case 'dashed':
          context.setLineDash([ 4, 2 ]);
          break;

        case 'solid':
        case 'double':
          context.setLineDash([ ]);
          break;
      }
    }


    var styleShape = style['shape'].strValue;

    if( usePaths ){
      var pathCacheKey = styleShape + '$' + nodeWidth +'$' + nodeHeight;

      context.translate( pos.x, pos.y );

      if( rs.pathCacheKey === pathCacheKey ){
        path = context = rs.pathCache;
        pathCacheHit = true;
      } else {
        path = context = new Path2D();
        rs.pathCacheKey = pathCacheKey;
        rs.pathCache = path;
      }
    }

    if( !pathCacheHit ){

      var npos = pos;

      if( usePaths ){
        npos = {
          x: 0,
          y: 0
        };
      }

      r.nodeShapes[this.getNodeShape(node)].draw(
            context,
            npos.x,
            npos.y,
            nodeWidth,
            nodeHeight);
    }

    context = canvasContext;

    if( usePaths ){
      context.fill( path );
    } else {
      context.fill();
    }

    this.shadowStyle(context, 'transparent', 0); // reset for next guy

    if (url !== undefined) {
      if( image.complete ){
        this.drawInscribedImage(context, image, node);
      }
    }

    var darkness = style['background-blacken'].value;
    var borderWidth = style['border-width'].pfValue;

    if( this.hasPie(node) ){
      this.drawPie( context, node, parentOpacity );

      // redraw path for blacken and border
      if( darkness !== 0 || borderWidth !== 0 ){

        if( !usePaths ){
          r.nodeShapes[this.getNodeShape(node)].draw(
              context,
              pos.x,
              pos.y,
              nodeWidth,
              nodeHeight);
        }
      }
    }

    if( darkness > 0 ){
      this.fillStyle(context, 0, 0, 0, darkness);

      if( usePaths ){
        context.fill( path );
      } else {
        context.fill();
      }

    } else if( darkness < 0 ){
      this.fillStyle(context, 255, 255, 255, -darkness);

      if( usePaths ){
        context.fill( path );
      } else {
        context.fill();
      }
    }

    // Border width, draw border
    if (borderWidth > 0) {

      if( usePaths ){
        context.stroke( path );
      } else {
        context.stroke();
      }

      if( borderStyle === 'double' ){
        context.lineWidth = style['border-width'].pfValue/3;

        var gco = context.globalCompositeOperation;
        context.globalCompositeOperation = 'destination-out';

        if( usePaths ){
          context.stroke( path );
        } else {
          context.stroke();
        }

        context.globalCompositeOperation = gco;
      }

    }

    if( usePaths ){
      context.translate( -pos.x, -pos.y );
    }

    // reset in case we changed the border style
    if( context.setLineDash ){ // for very outofdate browsers
      context.setLineDash([ ]);
    }

  // draw the overlay
  } else {

    if( overlayOpacity > 0 ){
      this.fillStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity);

      r.nodeShapes['roundrectangle'].draw(
        context,
        node._private.position.x,
        node._private.position.y,
        nodeWidth + overlayPadding * 2,
        nodeHeight + overlayPadding * 2
      );

      context.fill();
    }
  }

};

// does the node have at least one pie piece?
CRp.hasPie = function(node){
  node = node[0]; // ensure ele ref

  return node._private.hasPie;
};

CRp.drawPie = function( context, node, nodeOpacity ){
  node = node[0]; // ensure ele ref

  var _p = node._private;
  var cyStyle = node.cy().style();
  var style = _p.style;
  var pieSize = style['pie-size'];
  var nodeW = node.width();
  var nodeH = node.height();
  var x = _p.position.x;
  var y = _p.position.y;
  var radius = Math.min( nodeW, nodeH ) / 2; // must fit in node
  var lastPercent = 0; // what % to continue drawing pie slices from on [0, 1]
  var usePaths = this.usePaths();

  if( usePaths ){
    x = 0;
    y = 0;
  }

  if( pieSize.units === '%' ){
    radius = radius * pieSize.value / 100;
  } else if( pieSize.pfValue !== undefined ){
    radius = pieSize.pfValue / 2;
  }

  for( var i = 1; i <= cyStyle.pieBackgroundN; i++ ){ // 1..N
    var size = style['pie-' + i + '-background-size'].value;
    var color = style['pie-' + i + '-background-color'].value;
    var opacity = style['pie-' + i + '-background-opacity'].value * nodeOpacity;
    var percent = size / 100; // map integer range [0, 100] to [0, 1]

    // percent can't push beyond 1
    if( percent + lastPercent > 1 ){
      percent = 1 - lastPercent;
    }

    var angleStart = 1.5 * Math.PI + 2 * Math.PI * lastPercent; // start at 12 o'clock and go clockwise
    var angleDelta = 2 * Math.PI * percent;
    var angleEnd = angleStart + angleDelta;

    // ignore if
    // - zero size
    // - we're already beyond the full circle
    // - adding the current slice would go beyond the full circle
    if( size === 0 || lastPercent >= 1 || lastPercent + percent > 1 ){
      continue;
    }

    context.beginPath();
    context.moveTo(x, y);
    context.arc( x, y, radius, angleStart, angleEnd );
    context.closePath();

    this.fillStyle(context, color[0], color[1], color[2], opacity);

    context.fill();

    lastPercent += percent;
  }

};


module.exports = CRp;

},{"../../../is":77}],67:[function(_dereq_,module,exports){
'use strict';

var CRp = {};

var util = _dereq_('../../../util');
var math = _dereq_('../../../math');

var motionBlurDelay = 100;

// var isFirefox = typeof InstallTrigger !== 'undefined';

CRp.getPixelRatio = function(){
  var context = this.data.contexts[0];

  if( this.forcedPixelRatio != null ){
    return this.forcedPixelRatio;
  }

  var backingStore = context.backingStorePixelRatio ||
    context.webkitBackingStorePixelRatio ||
    context.mozBackingStorePixelRatio ||
    context.msBackingStorePixelRatio ||
    context.oBackingStorePixelRatio ||
    context.backingStorePixelRatio || 1;

  return (window.devicePixelRatio || 1) / backingStore;
};

CRp.paintCache = function(context){
  var caches = this.paintCaches = this.paintCaches || [];
  var needToCreateCache = true;
  var cache;

  for(var i = 0; i < caches.length; i++ ){
    cache = caches[i];

    if( cache.context === context ){
      needToCreateCache = false;
      break;
    }
  }

  if( needToCreateCache ){
    cache = {
      context: context
    };
    caches.push( cache );
  }

  return cache;
};

CRp.fillStyle = function(context, r, g, b, a){
  context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';

  // turn off for now, seems context does its own caching

  // var cache = this.paintCache(context);

  // var fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';

  // if( cache.fillStyle !== fillStyle ){
  //   context.fillStyle = cache.fillStyle = fillStyle;
  // }
};

CRp.strokeStyle = function(context, r, g, b, a){
  context.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';

  // turn off for now, seems context does its own caching

  // var cache = this.paintCache(context);

  // var strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';

  // if( cache.strokeStyle !== strokeStyle ){
  //   context.strokeStyle = cache.strokeStyle = strokeStyle;
  // }
};

CRp.shadowStyle = function(context, color, opacity, blur, offsetX, offsetY){
  var zoom = this.cy.zoom();

  var cache = this.paintCache(context);

  // don't make expensive changes to the shadow style if it's not used
  if( cache.shadowOpacity === 0 && opacity === 0 ){
    return;
  }

  cache.shadowOpacity = opacity;

  if (opacity > 0) {
    context.shadowBlur = blur * zoom;
    context.shadowColor = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + opacity + ")";
    context.shadowOffsetX = offsetX * zoom;
    context.shadowOffsetY = offsetY * zoom;
  } else {
    context.shadowBlur = 0;
    context.shadowColor = "transparent";
  }
};

// Resize canvas
CRp.matchCanvasSize = function(container) {
  var r = this;
  var data = r.data;
  var width = container.clientWidth;
  var height = container.clientHeight;
  var pixelRatio = r.getPixelRatio();
  var mbPxRatio = r.motionBlurPxRatio;

  if(
    container === r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_NODE] ||
    container === r.data.bufferCanvases[r.MOTIONBLUR_BUFFER_DRAG]
  ){
    pixelRatio = mbPxRatio;
  }

  var canvasWidth = width * pixelRatio;
  var canvasHeight = height * pixelRatio;
  var canvas;

  if( canvasWidth === r.canvasWidth && canvasHeight === r.canvasHeight ){
    return; // save cycles if same
  }

  r.fontCaches = null; // resizing resets the style

  var canvasContainer = data.canvasContainer;
  canvasContainer.style.width = width + 'px';
  canvasContainer.style.height = height + 'px';

  for (var i = 0; i < r.CANVAS_LAYERS; i++) {

    canvas = data.canvases[i];

    if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {

      canvas.width = canvasWidth;
      canvas.height = canvasHeight;

      canvas.style.width = width + 'px';
      canvas.style.height = height + 'px';
    }
  }

  for (var i = 0; i < r.BUFFER_COUNT; i++) {

    canvas = data.bufferCanvases[i];

    if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {

      canvas.width = canvasWidth;
      canvas.height = canvasHeight;

      canvas.style.width = width + 'px';
      canvas.style.height = height + 'px';
    }
  }

  r.textureMult = 1;
  if( pixelRatio <= 1 ){
    canvas = data.bufferCanvases[ r.TEXTURE_BUFFER ];

    r.textureMult = 2;
    canvas.width = canvasWidth * r.textureMult;
    canvas.height = canvasHeight * r.textureMult;
  }

  r.canvasWidth = canvasWidth;
  r.canvasHeight = canvasHeight;

};

CRp.renderTo = function( cxt, zoom, pan, pxRatio ){
  this.render({
    forcedContext: cxt,
    forcedZoom: zoom,
    forcedPan: pan,
    drawAllLayers: true,
    forcedPxRatio: pxRatio
  });
};

CRp.render = function( options ) {
  options = options || util.staticEmptyObject();

  var forcedContext = options.forcedContext;
  var drawAllLayers = options.drawAllLayers;
  var drawOnlyNodeLayer = options.drawOnlyNodeLayer;
  var forcedZoom = options.forcedZoom;
  var forcedPan = options.forcedPan;
  var r = this;
  var pixelRatio = options.forcedPxRatio === undefined ? this.getPixelRatio() : options.forcedPxRatio;
  var cy = r.cy; var data = r.data;
  var needDraw = data.canvasNeedsRedraw;
  var textureDraw = r.textureOnViewport && !forcedContext && (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming);
  var motionBlur = options.motionBlur !== undefined ? options.motionBlur : r.motionBlur;
  var mbPxRatio = r.motionBlurPxRatio;
  var hasCompoundNodes = cy.hasCompoundNodes();
  var inNodeDragGesture = r.hoverData.draggingEles;
  var inBoxSelection = r.hoverData.selecting || r.touchData.selecting ? true : false;
  motionBlur = motionBlur && !forcedContext && r.motionBlurEnabled && !inBoxSelection;
  var motionBlurFadeEffect = motionBlur;

  if( !forcedContext && r.motionBlurTimeout ){
    clearTimeout( r.motionBlurTimeout );
  }

  if( motionBlur ){
    if( r.mbFrames == null ){
      r.mbFrames = 0;
    }

    if( !r.drawingImage ){ // image loading frames don't count towards motion blur blurry frames
      r.mbFrames++;
    }

    if( r.mbFrames < 3 ){ // need several frames before even high quality motionblur
      motionBlurFadeEffect = false;
    }

    // go to lower quality blurry frames when several m/b frames have been rendered (avoids flashing)
    if( r.mbFrames > r.minMbLowQualFrames ){
      //r.fullQualityMb = false;
      r.motionBlurPxRatio = r.mbPxRBlurry;
    }
  }

  if( r.clearingMotionBlur ){
    r.motionBlurPxRatio = 1;
  }

  // b/c drawToContext() may be async w.r.t. redraw(), keep track of last texture frame
  // because a rogue async texture frame would clear needDraw
  if( r.textureDrawLastFrame && !textureDraw ){
    needDraw[r.NODE] = true;
    needDraw[r.SELECT_BOX] = true;
  }

  var edges = r.getCachedEdges();
  var coreStyle = cy.style()._private.coreStyle;

  var zoom = cy.zoom();
  var effectiveZoom = forcedZoom !== undefined ? forcedZoom : zoom;
  var pan = cy.pan();
  var effectivePan = {
    x: pan.x,
    y: pan.y
  };

  var vp = {
    zoom: zoom,
    pan: {
      x: pan.x,
      y: pan.y
    }
  };
  var prevVp = r.prevViewport;
  var viewportIsDiff = prevVp === undefined || vp.zoom !== prevVp.zoom || vp.pan.x !== prevVp.pan.x || vp.pan.y !== prevVp.pan.y;

  // we want the low quality motionblur only when the viewport is being manipulated etc (where it's not noticed)
  if( !viewportIsDiff && !(inNodeDragGesture && !hasCompoundNodes) ){
    r.motionBlurPxRatio = 1;
  }

  if( forcedPan ){
    effectivePan = forcedPan;
  }

  // apply pixel ratio

  effectiveZoom *= pixelRatio;
  effectivePan.x *= pixelRatio;
  effectivePan.y *= pixelRatio;

  var eles = {
    drag: {
      nodes: [],
      edges: [],
      eles: []
    },
    nondrag: {
      nodes: [],
      edges: [],
      eles: []
    }
  };

  function mbclear( context, x, y, w, h ){
    var gco = context.globalCompositeOperation;

    context.globalCompositeOperation = 'destination-out';
    r.fillStyle( context, 255, 255, 255, r.motionBlurTransparency );
    context.fillRect(x, y, w, h);

    context.globalCompositeOperation = gco;
  }

  function setContextTransform(context, clear){
    var ePan, eZoom, w, h;

    if( !r.clearingMotionBlur && (context === data.bufferContexts[r.MOTIONBLUR_BUFFER_NODE] || context === data.bufferContexts[r.MOTIONBLUR_BUFFER_DRAG]) ){
      ePan = {
        x: pan.x * mbPxRatio,
        y: pan.y * mbPxRatio
      };

      eZoom = zoom * mbPxRatio;

      w = r.canvasWidth * mbPxRatio;
      h = r.canvasHeight * mbPxRatio;
    } else {
      ePan = effectivePan;
      eZoom = effectiveZoom;

      w = r.canvasWidth;
      h = r.canvasHeight;
    }

    context.setTransform(1, 0, 0, 1, 0, 0);

    if( clear === 'motionBlur' ){
      mbclear(context, 0, 0, w, h);
    } else if( !forcedContext && (clear === undefined || clear) ){
      context.clearRect(0, 0, w, h);
    }

    if( !drawAllLayers ){
      context.translate( ePan.x, ePan.y );
      context.scale( eZoom, eZoom );
    }
    if( forcedPan ){
      context.translate( forcedPan.x, forcedPan.y );
    }
    if( forcedZoom ){
      context.scale( forcedZoom, forcedZoom );
    }
  }

  if( !textureDraw ){
    r.textureDrawLastFrame = false;
  }

  if( textureDraw ){
    r.textureDrawLastFrame = true;

    var bb;

    if( !r.textureCache ){
      r.textureCache = {};

      bb = r.textureCache.bb = cy.elements().boundingBox();

      r.textureCache.texture = r.data.bufferCanvases[ r.TEXTURE_BUFFER ];

      var cxt = r.data.bufferContexts[ r.TEXTURE_BUFFER ];

      cxt.setTransform(1, 0, 0, 1, 0, 0);
      cxt.clearRect(0, 0, r.canvasWidth * r.textureMult, r.canvasHeight * r.textureMult);

      r.render({
        forcedContext: cxt,
        drawOnlyNodeLayer: true,
        forcedPxRatio: pixelRatio * r.textureMult
      });

      var vp = r.textureCache.viewport = {
        zoom: cy.zoom(),
        pan: cy.pan(),
        width: r.canvasWidth,
        height: r.canvasHeight
      };

      vp.mpan = {
        x: (0 - vp.pan.x)/vp.zoom,
        y: (0 - vp.pan.y)/vp.zoom
      };
    }

    needDraw[r.DRAG] = false;
    needDraw[r.NODE] = false;

    var context = data.contexts[r.NODE];

    var texture = r.textureCache.texture;
    var vp = r.textureCache.viewport;
    bb = r.textureCache.bb;

    context.setTransform(1, 0, 0, 1, 0, 0);

    if( motionBlur ){
      mbclear(context, 0, 0, vp.width, vp.height);
    } else {
      context.clearRect(0, 0, vp.width, vp.height);
    }

    var outsideBgColor = coreStyle['outside-texture-bg-color'].value;
    var outsideBgOpacity = coreStyle['outside-texture-bg-opacity'].value;
    r.fillStyle( context, outsideBgColor[0], outsideBgColor[1], outsideBgColor[2], outsideBgOpacity );
    context.fillRect( 0, 0, vp.width, vp.height );

    var zoom = cy.zoom();

    setContextTransform( context, false );

    context.clearRect( vp.mpan.x, vp.mpan.y, vp.width/vp.zoom/pixelRatio, vp.height/vp.zoom/pixelRatio );
    context.drawImage( texture, vp.mpan.x, vp.mpan.y, vp.width/vp.zoom/pixelRatio, vp.height/vp.zoom/pixelRatio );

  } else if( r.textureOnViewport && !forcedContext ){ // clear the cache since we don't need it
    r.textureCache = null;
  }

  var vpManip = (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming || r.hoverData.draggingEles);
  var hideEdges = r.hideEdgesOnViewport && vpManip;
  var hideLabels = r.hideLabelsOnViewport && vpManip;

  if (needDraw[r.DRAG] || needDraw[r.NODE] || drawAllLayers || drawOnlyNodeLayer) {
    if( hideEdges ){
    } else {
      r.findEdgeControlPoints(edges);
    }

    var zEles = r.getCachedZSortedEles();
    var extent = cy.extent();

    for (var i = 0; i < zEles.length; i++) {
      var ele = zEles[i];
      var list;
      var bb = forcedContext ? null : ele.boundingBox();
      var insideExtent = forcedContext ? true : math.boundingBoxesIntersect( extent, bb );

      if( !insideExtent ){ continue; } // no need to render

      if ( ele._private.rscratch.inDragLayer ) {
        list = eles.drag;
      } else {
        list = eles.nondrag;
      }

      list.eles.push( ele );
    }

  }


  function drawElements( list, context ){
    var eles = list.eles;

    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];

      if( ele.isNode() ){
        r.drawNode(context, ele);

        if( !hideLabels ){
          r.drawNodeText(context, ele);
        }

        r.drawNode(context, ele, true);
      } else if( !hideEdges ) {
        r.drawEdge(context, ele);

        if( !hideLabels ){
          r.drawEdgeText(context, ele);
        }

        r.drawEdge(context, ele, true);
      }


    }

  }

  var needMbClear = [];

  needMbClear[r.NODE] = !needDraw[r.NODE] && motionBlur && !r.clearedForMotionBlur[r.NODE] || r.clearingMotionBlur;
  if( needMbClear[r.NODE] ){ r.clearedForMotionBlur[r.NODE] = true; }

  needMbClear[r.DRAG] = !needDraw[r.DRAG] && motionBlur && !r.clearedForMotionBlur[r.DRAG] || r.clearingMotionBlur;
  if( needMbClear[r.DRAG] ){ r.clearedForMotionBlur[r.DRAG] = true; }

  if( needDraw[r.NODE] || drawAllLayers || drawOnlyNodeLayer || needMbClear[r.NODE] ){
    var useBuffer = motionBlur && !needMbClear[r.NODE] && mbPxRatio !== 1;
    var context = forcedContext || ( useBuffer ? r.data.bufferContexts[ r.MOTIONBLUR_BUFFER_NODE ] : data.contexts[r.NODE] );
    var clear = motionBlur && !useBuffer ? 'motionBlur' : undefined;

    setContextTransform( context, clear );
    drawElements(eles.nondrag, context);

    if( !drawAllLayers && !motionBlur ){
      needDraw[r.NODE] = false;
    }
  }

  if ( !drawOnlyNodeLayer && (needDraw[r.DRAG] || drawAllLayers || needMbClear[r.DRAG]) ) {
    var useBuffer = motionBlur && !needMbClear[r.DRAG] && mbPxRatio !== 1;
    var context = forcedContext || ( useBuffer ? r.data.bufferContexts[ r.MOTIONBLUR_BUFFER_DRAG ] : data.contexts[r.DRAG] );

    setContextTransform( context, motionBlur && !useBuffer ? 'motionBlur' : undefined );
    drawElements(eles.drag, context);

    if( !drawAllLayers && !motionBlur ){
      needDraw[r.DRAG] = false;
    }
  }

  if( r.showFps || (!drawOnlyNodeLayer && (needDraw[r.SELECT_BOX] && !drawAllLayers)) ) {
    var context = forcedContext || data.contexts[r.SELECT_BOX];

    setContextTransform( context );

    if( r.selection[4] == 1 && ( r.hoverData.selecting || r.touchData.selecting ) ){
      var zoom = r.cy.zoom();
      var borderWidth = coreStyle['selection-box-border-width'].value / zoom;

      context.lineWidth = borderWidth;
      context.fillStyle = "rgba("
        + coreStyle['selection-box-color'].value[0] + ","
        + coreStyle['selection-box-color'].value[1] + ","
        + coreStyle['selection-box-color'].value[2] + ","
        + coreStyle['selection-box-opacity'].value + ")";

      context.fillRect(
        r.selection[0],
        r.selection[1],
        r.selection[2] - r.selection[0],
        r.selection[3] - r.selection[1]);

      if (borderWidth > 0) {
        context.strokeStyle = "rgba("
          + coreStyle['selection-box-border-color'].value[0] + ","
          + coreStyle['selection-box-border-color'].value[1] + ","
          + coreStyle['selection-box-border-color'].value[2] + ","
          + coreStyle['selection-box-opacity'].value + ")";

        context.strokeRect(
          r.selection[0],
          r.selection[1],
          r.selection[2] - r.selection[0],
          r.selection[3] - r.selection[1]);
      }
    }

    if( data.bgActivePosistion && !r.hoverData.selecting ){
      var zoom = r.cy.zoom();
      var pos = data.bgActivePosistion;

      context.fillStyle = "rgba("
        + coreStyle['active-bg-color'].value[0] + ","
        + coreStyle['active-bg-color'].value[1] + ","
        + coreStyle['active-bg-color'].value[2] + ","
        + coreStyle['active-bg-opacity'].value + ")";

      context.beginPath();
      context.arc(pos.x, pos.y, coreStyle['active-bg-size'].pfValue / zoom, 0, 2 * Math.PI);
      context.fill();
    }

    var timeToRender = r.lastRedrawTime;
    if( r.showFps && timeToRender ){
      timeToRender = Math.round( timeToRender );
      var fps = Math.round(1000/timeToRender);

      context.setTransform(1, 0, 0, 1, 0, 0);

      context.fillStyle = 'rgba(255, 0, 0, 0.75)';
      context.strokeStyle = 'rgba(255, 0, 0, 0.75)';
      context.lineWidth = 1;
      context.fillText( '1 frame = ' + timeToRender + ' ms = ' + fps + ' fps', 0, 20);

      var maxFps = 60;
      context.strokeRect(0, 30, 250, 20);
      context.fillRect(0, 30, 250 * Math.min(fps/maxFps, 1), 20);
    }

    if( !drawAllLayers ){
      needDraw[r.SELECT_BOX] = false;
    }
  }

  // motionblur: blit rendered blurry frames
  if( motionBlur && mbPxRatio !== 1 ){
    var cxtNode = data.contexts[r.NODE];
    var txtNode = r.data.bufferCanvases[ r.MOTIONBLUR_BUFFER_NODE ];

    var cxtDrag = data.contexts[r.DRAG];
    var txtDrag = r.data.bufferCanvases[ r.MOTIONBLUR_BUFFER_DRAG ];

    var drawMotionBlur = function( cxt, txt, needClear ){
      cxt.setTransform(1, 0, 0, 1, 0, 0);

      if( needClear || !motionBlurFadeEffect ){
        cxt.clearRect( 0, 0, r.canvasWidth, r.canvasHeight );
      } else {
        mbclear( cxt, 0, 0, r.canvasWidth, r.canvasHeight );
      }

      var pxr = mbPxRatio;

      cxt.drawImage(
        txt, // img
        0, 0, // sx, sy
        r.canvasWidth * pxr, r.canvasHeight * pxr, // sw, sh
        0, 0, // x, y
        r.canvasWidth, r.canvasHeight // w, h
      );
    };

    if( needDraw[r.NODE] || needMbClear[r.NODE] ){
      drawMotionBlur( cxtNode, txtNode, needMbClear[r.NODE] );
      needDraw[r.NODE] = false;
    }

    if( needDraw[r.DRAG] || needMbClear[r.DRAG] ){
      drawMotionBlur( cxtDrag, txtDrag, needMbClear[r.DRAG] );
      needDraw[r.DRAG] = false;
    }
  }

  r.currentlyDrawing = false;

  r.prevViewport = vp;

  if( r.clearingMotionBlur ){
    r.clearingMotionBlur = false;
    r.motionBlurCleared = true;
    r.motionBlur = true;
  }

  if( motionBlur ){
    r.motionBlurTimeout = setTimeout(function(){
      r.motionBlurTimeout = null;

      r.clearedForMotionBlur[r.NODE] = false;
      r.clearedForMotionBlur[r.DRAG] = false;
      r.motionBlur = false;
      r.clearingMotionBlur = !textureDraw;
      r.mbFrames = 0;

      needDraw[r.NODE] = true;
      needDraw[r.DRAG] = true;

      r.redraw();
    }, motionBlurDelay);
  }

  r.drawingImage = false;


  if( !forcedContext && !r.initrender ){
    r.initrender = true;
    cy.trigger('initrender');
  }

  if( !forcedContext ){
    cy.triggerOnRender();
  }

};

module.exports = CRp;

},{"../../../math":79,"../../../util":94}],68:[function(_dereq_,module,exports){
'use strict';

  var math = _dereq_('../../../math');

  var CRp = {};

  // @O Polygon drawing
  CRp.drawPolygonPath = function(
    context, x, y, width, height, points) {

    var halfW = width / 2;
    var halfH = height / 2;

    if( context.beginPath ){ context.beginPath(); }

    context.moveTo( x + halfW * points[0], y + halfH * points[1] );

    for (var i = 1; i < points.length / 2; i++) {
      context.lineTo( x + halfW * points[i * 2], y + halfH * points[i * 2 + 1] );
    }

    context.closePath();
  };

  // Round rectangle drawing
  CRp.drawRoundRectanglePath = function(
    context, x, y, width, height, radius) {

    var halfWidth = width / 2;
    var halfHeight = height / 2;
    var cornerRadius = math.getRoundRectangleRadius(width, height);

    if( context.beginPath ){ context.beginPath(); }

    // Start at top middle
    context.moveTo(x, y - halfHeight);
    // Arc from middle top to right side
    context.arcTo(x + halfWidth, y - halfHeight, x + halfWidth, y, cornerRadius);
    // Arc from right side to bottom
    context.arcTo(x + halfWidth, y + halfHeight, x, y + halfHeight, cornerRadius);
    // Arc from bottom to left side
    context.arcTo(x - halfWidth, y + halfHeight, x - halfWidth, y, cornerRadius);
    // Arc from left side to topBorder
    context.arcTo(x - halfWidth, y - halfHeight, x, y - halfHeight, cornerRadius);
    // Join line
    context.lineTo(x, y - halfHeight);


    context.closePath();
  };

  var sin0 = Math.sin(0);
  var cos0 = Math.cos(0);

  var sin = {};
  var cos = {};

  var ellipseStepSize = Math.PI / 40;

  for (var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize ) {
    sin[i] = Math.sin(i);
    cos[i] = Math.cos(i);
  }

  CRp.drawEllipsePath = function(context, centerX, centerY, width, height){
    if( context.beginPath ){ context.beginPath(); }

    if( context.ellipse ){
      context.ellipse( centerX, centerY, width/2, height/2, 0, 0, 2*Math.PI );
    } else {
      var xPos, yPos;
      var rw = width/2;
      var rh = height/2;
      for (var i = 0 * Math.PI; i < 2 * Math.PI; i += ellipseStepSize ) {
        xPos = centerX - (rw * sin[i]) * sin0 + (rw * cos[i]) * cos0;
        yPos = centerY + (rh * cos[i]) * sin0 + (rh * sin[i]) * cos0;

        if (i === 0) {
          context.moveTo(xPos, yPos);
        } else {
          context.lineTo(xPos, yPos);
        }
      }
    }

    context.closePath();
  };

module.exports = CRp;

},{"../../../math":79}],69:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../../../is');

var CRp = {};

CRp.createBuffer = function(w, h) {
  var buffer = document.createElement('canvas');
  buffer.width = w;
  buffer.height = h;

  return [buffer, buffer.getContext('2d')];
};

CRp.bufferCanvasImage = function( options ){
  var cy = this.cy;
  var bb = cy.elements().boundingBox();
  var width = options.full ? Math.ceil(bb.w) : this.container.clientWidth;
  var height = options.full ? Math.ceil(bb.h) : this.container.clientHeight;
  var scale = 1;

  if( options.scale !== undefined ){
    width *= options.scale;
    height *= options.scale;

    scale = options.scale;
  } else if( is.number(options.maxWidth) || is.number(options.maxHeight) ){
    var maxScaleW = Infinity;
    var maxScaleH = Infinity;

    if( is.number(options.maxWidth) ){
      maxScaleW = scale * options.maxWidth / width;
    }

    if( is.number(options.maxHeight) ){
      maxScaleH = scale * options.maxHeight / height;
    }

    scale = Math.min( maxScaleW, maxScaleH );

    width *= scale;
    height *= scale;
  }

  var buffCanvas = document.createElement('canvas');

  buffCanvas.width = width;
  buffCanvas.height = height;

  buffCanvas.style.width = width + 'px';
  buffCanvas.style.height = height + 'px';

  var buffCxt = buffCanvas.getContext('2d');

  // Rasterize the layers, but only if container has nonzero size
  if (width > 0 && height > 0) {

    buffCxt.clearRect( 0, 0, width, height );

    if( options.bg ){
      buffCxt.fillStyle = options.bg;
      buffCxt.rect( 0, 0, width, height );
      buffCxt.fill();
    }

    buffCxt.globalCompositeOperation = 'source-over';

    if( options.full ){ // draw the full bounds of the graph
      this.render({
        forcedContext: buffCxt,
        drawAllLayers: true,
        forcedZoom: scale,
        forcedPan: { x: -bb.x1*scale, y: -bb.y1*scale },
        forcedPxRatio: 1
      });
    } else { // draw the current view
      var cyPan = cy.pan();
      var pan = {
        x: cyPan.x * scale,
        y: cyPan.y * scale
      };
      var zoom = cy.zoom() * scale;

      this.render({
        forcedContext: buffCxt,
        drawAllLayers: true,
        forcedZoom: zoom,
        forcedPan: pan,
        forcedPxRatio: 1
      });
    }
  }

  return buffCanvas;
};

CRp.png = function( options ){
  return this.bufferCanvasImage( options ).toDataURL('image/png');
};

CRp.jpg = function( options ){
  return this.bufferCanvasImage( options ).toDataURL('image/jpeg');
};

module.exports = CRp;

},{"../../../is":77}],70:[function(_dereq_,module,exports){
/*
The canvas renderer was written by Yue Dong.

Modifications tracked on Github.
*/

'use strict';

var util = _dereq_('../../../util');
var is = _dereq_('../../../is');

var CR = CanvasRenderer;
var CRp = CanvasRenderer.prototype;

CRp.CANVAS_LAYERS = 3;
//
CRp.SELECT_BOX = 0;
CRp.DRAG = 1;
CRp.NODE = 2;

CRp.BUFFER_COUNT = 3;
//
CRp.TEXTURE_BUFFER = 0;
CRp.MOTIONBLUR_BUFFER_NODE = 1;
CRp.MOTIONBLUR_BUFFER_DRAG = 2;

function CanvasRenderer(options) {
  var r = this;

  r.data = {
    canvases: new Array(CRp.CANVAS_LAYERS),
    contexts: new Array(CRp.CANVAS_LAYERS),
    canvasNeedsRedraw: new Array(CRp.CANVAS_LAYERS),

    bufferCanvases: new Array(CRp.BUFFER_COUNT),
    bufferContexts: new Array(CRp.CANVAS_LAYERS)
  };

  r.data.canvasContainer = document.createElement('div');
  var containerStyle = r.data.canvasContainer.style;
  r.data.canvasContainer.setAttribute('style', '-webkit-tap-highlight-color: rgba(0,0,0,0);');
  containerStyle.position = 'relative';
  containerStyle.zIndex = '0';
  containerStyle.overflow = 'hidden';

  var container = options.cy.container();
  container.appendChild( r.data.canvasContainer );
  container.setAttribute('style', ( container.getAttribute('style') || '' ) + '-webkit-tap-highlight-color: rgba(0,0,0,0);');

  for (var i = 0; i < CRp.CANVAS_LAYERS; i++) {
    var canvas = r.data.canvases[i] = document.createElement('canvas');
    r.data.contexts[i] = canvas.getContext('2d');
    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; ' : '' ) );
    canvas.style.position = 'absolute';
    canvas.setAttribute('data-id', 'layer' + i);
    canvas.style.zIndex = String(CRp.CANVAS_LAYERS - i);
    r.data.canvasContainer.appendChild(canvas);

    r.data.canvasNeedsRedraw[i] = false;
  }
  r.data.topCanvas = r.data.canvases[0];

  r.data.canvases[CRp.NODE].setAttribute('data-id', 'layer' + CRp.NODE + '-node');
  r.data.canvases[CRp.SELECT_BOX].setAttribute('data-id', 'layer' + CRp.SELECT_BOX + '-selectbox');
  r.data.canvases[CRp.DRAG].setAttribute('data-id', 'layer' + CRp.DRAG + '-drag');

  for (var i = 0; i < CRp.BUFFER_COUNT; i++) {
    r.data.bufferCanvases[i] = document.createElement('canvas');
    r.data.bufferContexts[i] = r.data.bufferCanvases[i].getContext('2d');
    r.data.bufferCanvases[i].style.position = 'absolute';
    r.data.bufferCanvases[i].setAttribute('data-id', 'buffer' + i);
    r.data.bufferCanvases[i].style.zIndex = String(-i - 1);
    r.data.bufferCanvases[i].style.visibility = 'hidden';
    //r.data.canvasContainer.appendChild(r.data.bufferCanvases[i]);
  }

  r.pathsEnabled = true;
}

CRp.redrawHint = function( group, bool ){
  var r = this;

  switch( group ){
    case 'eles':
      r.data.canvasNeedsRedraw[ CRp.NODE ] = bool;
      break;
    case 'drag':
      r.data.canvasNeedsRedraw[ CRp.DRAG ] = bool;
      break;
    case 'select':
      r.data.canvasNeedsRedraw[ CRp.SELECT_BOX ] = bool;
      break;
  }
};

// whether to use Path2D caching for drawing
var pathsImpld = typeof Path2D !== 'undefined';

CRp.path2dEnabled = function( on ){
  if( on === undefined ){
    return this.pathsEnabled;
  }

  this.pathsEnabled = on ? true : false;
};

CRp.usePaths = function(){
  return pathsImpld && this.pathsEnabled;
};

[
  _dereq_('./arrow-shapes'),
  _dereq_('./drawing-edges'),
  _dereq_('./drawing-images'),
  _dereq_('./drawing-label-text'),
  _dereq_('./drawing-nodes'),
  _dereq_('./drawing-redraw'),
  _dereq_('./drawing-shapes'),
  _dereq_('./export-image'),
  _dereq_('./node-shapes')
].forEach(function( props ){
  util.extend( CRp, props );
});

module.exports = CR;

},{"../../../is":77,"../../../util":94,"./arrow-shapes":62,"./drawing-edges":63,"./drawing-images":64,"./drawing-label-text":65,"./drawing-nodes":66,"./drawing-redraw":67,"./drawing-shapes":68,"./export-image":69,"./node-shapes":71}],71:[function(_dereq_,module,exports){
'use strict';

var CRp = {};

var impl;

CRp.nodeShapeImpl = function( name ){
  var self = this;

  return ( impl || (impl = {
    'ellipse': function( context, centerX, centerY, width, height ){
      self.drawEllipsePath( context, centerX, centerY, width, height );
    },

    'polygon': function( context, centerX, centerY, width, height, points ){
      self.drawPolygonPath( context, centerX, centerY, width, height, points );
    },

    'roundrectangle': function( context, centerX, centerY, width, height ){
      self.drawRoundRectanglePath( context, centerX, centerY, width, height, 10 );
    }
  }) )[ name ];
};

module.exports = CRp;

},{}],72:[function(_dereq_,module,exports){
'use strict';

module.exports = [
  { name: 'null', impl: _dereq_('./null') },
  { name: 'base', impl: _dereq_('./base') },
  { name: 'canvas', impl: _dereq_('./canvas') }
];

},{"./base":58,"./canvas":70,"./null":73}],73:[function(_dereq_,module,exports){
'use strict';

function NullRenderer(options){
  this.options = options;
  this.notifications = 0; // for testing
}

var noop = function(){};

NullRenderer.prototype = {
  recalculateRenderedStyle: noop,
  notify: function(){ this.notifications++; },
  init: noop
};

module.exports = NullRenderer;

},{}],74:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('./is');
var util = _dereq_('./util');
var Thread = _dereq_('./thread');
var Promise = _dereq_('./promise');
var define = _dereq_('./define');

var Fabric = function( N ){
  if( !(this instanceof Fabric) ){
    return new Fabric( N );
  }

  this._private = {
    pass: []
  };

  var defN = 4;

  if( is.number(N) ){
    // then use the specified number of threads
  } if( typeof navigator !== 'undefined' && navigator.hardwareConcurrency != null ){
    N = navigator.hardwareConcurrency;
  } else {
    try{
      N = _dereq_('os').cpus().length;
    } catch( err ){
      N = defN;
    }
  } // TODO could use an estimation here but would the additional expense be worth it?

  for( var i = 0; i < N; i++ ){
    this[i] = new Thread();
  }

  this.length = N;
};

var fabfn = Fabric.prototype; // short alias

util.extend(fabfn, {

  instanceString: function(){ return 'fabric'; },

  // require fn in all threads
  require: function( fn, as ){
    for( var i = 0; i < this.length; i++ ){
      var thread = this[i];

      thread.require( fn, as );
    }

    return this;
  },

  // get a random thread
  random: function(){
    var i = Math.round( (this.length - 1) * Math.random() );
    var thread = this[i];

    return thread;
  },

  // run on random thread
  run: function( fn ){
    var pass = this._private.pass.shift();

    return this.random().pass( pass ).run( fn );
  },

  // sends a random thread a message
  message: function( m ){
    return this.random().message( m );
  },

  // send all threads a message
  broadcast: function( m ){
    for( var i = 0; i < this.length; i++ ){
      var thread = this[i];

      thread.message( m );
    }

    return this; // chaining
  },

  // stop all threads
  stop: function(){
    for( var i = 0; i < this.length; i++ ){
      var thread = this[i];

      thread.stop();
    }

    return this; // chaining
  },

  // pass data to be used with .spread() etc.
  pass: function( data ){
    var pass = this._private.pass;

    if( is.array(data) ){
      pass.push( data );
    } else {
      throw 'Only arrays may be used with fabric.pass()';
    }

    return this; // chaining
  },

  spreadSize: function(){
    var subsize =  Math.ceil( this._private.pass[0].length / this.length );

    subsize = Math.max( 1, subsize ); // don't pass less than one ele to each thread

    return subsize;
  },

  // split the data into slices to spread the data equally among threads
  spread: function( fn ){
    var self = this;
    var _p = self._private;
    var subsize = self.spreadSize(); // number of pass eles to handle in each thread
    var pass = _p.pass.shift().concat([]); // keep a copy
    var runPs = [];

    for( var i = 0; i < this.length; i++ ){
      var thread = this[i];
      var slice = pass.splice( 0, subsize );

      var runP = thread.pass( slice ).run( fn );

      runPs.push( runP );

      var doneEarly = pass.length === 0;
      if( doneEarly ){ break; }
    }

    return Promise.all( runPs ).then(function( thens ){
      var postpass = [];
      var p = 0;

      // fill postpass with the total result joined from all threads
      for( var i = 0; i < thens.length; i++ ){
        var then = thens[i]; // array result from thread i

        for( var j = 0; j < then.length; j++ ){
          var t = then[j]; // array element

          postpass[ p++ ] = t;
        }
      }

      return postpass;
    });
  },

  // parallel version of array.map()
  map: function( fn ){
    var self = this;

    self.require( fn, '_$_$_fabmap' );

    return self.spread(function( split ){
      var mapped = [];
      var origResolve = resolve; // jshint ignore:line

      resolve = function( val ){ // jshint ignore:line
        mapped.push( val );
      };

      for( var i = 0; i < split.length; i++ ){
        var oldLen = mapped.length;
        var ret = _$_$_fabmap( split[i] ); // jshint ignore:line
        var nothingInsdByResolve = oldLen === mapped.length;

        if( nothingInsdByResolve ){
          mapped.push( ret );
        }
      }

      resolve = origResolve; // jshint ignore:line

      return mapped;
    });

  },

  // parallel version of array.filter()
  filter: function( fn ){
    var _p = this._private;
    var pass = _p.pass[0];

    return this.map( fn ).then(function( include ){
      var ret = [];

      for( var i = 0; i < pass.length; i++ ){
        var datum = pass[i];
        var incDatum = include[i];

        if( incDatum ){
          ret.push( datum );
        }
      }

      return ret;
    });
  },

  // sorts the passed array using a divide and conquer strategy
  sort: function( cmp ){
    var self = this;
    var P = this._private.pass[0].length;
    var subsize = this.spreadSize();

    cmp = cmp || function( a, b ){ // default comparison function
      if( a < b ){
        return -1;
      } else if( a > b ){
        return 1;
      }

      return 0;
    };

    self.require( cmp, '_$_$_cmp' );

    return self.spread(function( split ){ // sort each split normally
      var sortedSplit = split.sort( _$_$_cmp ); // jshint ignore:line
      resolve( sortedSplit ); // jshint ignore:line

    }).then(function( joined ){
      // do all the merging in the main thread to minimise data transfer

      // TODO could do merging in separate threads but would incur add'l cost of data transfer
      // for each level of the merge

      var merge = function( i, j, max ){
        // don't overflow array
        j = Math.min( j, P );
        max = Math.min( max, P );

        // left and right sides of merge
        var l = i;
        var r = j;

        var sorted = [];

        for( var k = l; k < max; k++ ){

          var eleI = joined[i];
          var eleJ = joined[j];

          if( i < r && ( j >= max || cmp(eleI, eleJ) <= 0 ) ){
            sorted.push( eleI );
            i++;
          } else {
            sorted.push( eleJ );
            j++;
          }

        }

        // in the array proper, put the sorted values
        for( var k = 0; k < sorted.length; k++ ){ // kth sorted item
          var index = l + k;

          joined[ index ] = sorted[k];
        }
      };

      for( var splitL = subsize; splitL < P; splitL *= 2 ){ // merge until array is "split" as 1

        for( var i = 0; i < P; i += 2*splitL ){
          merge( i, i + splitL, i + 2*splitL );
        }

      }

      return joined;
    });
  }


});

var defineRandomPasser = function( opts ){
  opts = opts || {};

  return function( fn, arg1 ){
    var pass = this._private.pass.shift();

    return this.random().pass( pass )[ opts.threadFn ]( fn, arg1 );
  };
};

util.extend(fabfn, {
  randomMap: defineRandomPasser({ threadFn: 'map' }),

  reduce: defineRandomPasser({ threadFn: 'reduce' }),

  reduceRight: defineRandomPasser({ threadFn: 'reduceRight' })
});

// aliases
var fn = fabfn;
fn.promise = fn.run;
fn.terminate = fn.halt = fn.stop;
fn.include = fn.require;

// pull in event apis
util.extend(fabfn, {
  on: define.on(),
  one: define.on({ unbindSelfOnTrigger: true }),
  off: define.off(),
  trigger: define.trigger()
});

define.eventAliasesOn( fabfn );

module.exports = Fabric;

},{"./define":41,"./is":77,"./promise":80,"./thread":92,"./util":94,"os":undefined}],75:[function(_dereq_,module,exports){
'use strict';
/* jshint ignore:start */

// Generated by CoffeeScript 1.8.0
(function() {
  var Heap, defaultCmp, floor, heapify, heappop, heappush, heappushpop, heapreplace, insort, min, nlargest, nsmallest, updateItem, _siftdown, _siftup;

  floor = Math.floor, min = Math.min;


  /*
  Default comparison function to be used
   */

  defaultCmp = function(x, y) {
    if (x < y) {
      return -1;
    }
    if (x > y) {
      return 1;
    }
    return 0;
  };


  /*
  Insert item x in list a, and keep it sorted assuming a is sorted.

  If x is already in a, insert it to the right of the rightmost x.

  Optional args lo (default 0) and hi (default a.length) bound the slice
  of a to be searched.
   */

  insort = function(a, x, lo, hi, cmp) {
    var mid;
    if (lo == null) {
      lo = 0;
    }
    if (cmp == null) {
      cmp = defaultCmp;
    }
    if (lo < 0) {
      throw new Error('lo must be non-negative');
    }
    if (hi == null) {
      hi = a.length;
    }
    while (lo < hi) {
      mid = floor((lo + hi) / 2);
      if (cmp(x, a[mid]) < 0) {
        hi = mid;
      } else {
        lo = mid + 1;
      }
    }
    return ([].splice.apply(a, [lo, lo - lo].concat(x)), x);
  };


  /*
  Push item onto heap, maintaining the heap invariant.
   */

  heappush = function(array, item, cmp) {
    if (cmp == null) {
      cmp = defaultCmp;
    }
    array.push(item);
    return _siftdown(array, 0, array.length - 1, cmp);
  };


  /*
  Pop the smallest item off the heap, maintaining the heap invariant.
   */

  heappop = function(array, cmp) {
    var lastelt, returnitem;
    if (cmp == null) {
      cmp = defaultCmp;
    }
    lastelt = array.pop();
    if (array.length) {
      returnitem = array[0];
      array[0] = lastelt;
      _siftup(array, 0, cmp);
    } else {
      returnitem = lastelt;
    }
    return returnitem;
  };


  /*
  Pop and return the current smallest value, and add the new item.

  This is more efficient than heappop() followed by heappush(), and can be
  more appropriate when using a fixed size heap. Note that the value
  returned may be larger than item! That constrains reasonable use of
  this routine unless written as part of a conditional replacement:
      if item > array[0]
        item = heapreplace(array, item)
   */

  heapreplace = function(array, item, cmp) {
    var returnitem;
    if (cmp == null) {
      cmp = defaultCmp;
    }
    returnitem = array[0];
    array[0] = item;
    _siftup(array, 0, cmp);
    return returnitem;
  };


  /*
  Fast version of a heappush followed by a heappop.
   */

  heappushpop = function(array, item, cmp) {
    var _ref;
    if (cmp == null) {
      cmp = defaultCmp;
    }
    if (array.length && cmp(array[0], item) < 0) {
      _ref = [array[0], item], item = _ref[0], array[0] = _ref[1];
      _siftup(array, 0, cmp);
    }
    return item;
  };


  /*
  Transform list into a heap, in-place, in O(array.length) time.
   */

  heapify = function(array, cmp) {
    var i, _i, _j, _len, _ref, _ref1, _results, _results1;
    if (cmp == null) {
      cmp = defaultCmp;
    }
    _ref1 = (function() {
      _results1 = [];
      for (var _j = 0, _ref = floor(array.length / 2); 0 <= _ref ? _j < _ref : _j > _ref; 0 <= _ref ? _j++ : _j--){ _results1.push(_j); }
      return _results1;
    }).apply(this).reverse();
    _results = [];
    for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
      i = _ref1[_i];
      _results.push(_siftup(array, i, cmp));
    }
    return _results;
  };


  /*
  Update the position of the given item in the heap.
  This function should be called every time the item is being modified.
   */

  updateItem = function(array, item, cmp) {
    var pos;
    if (cmp == null) {
      cmp = defaultCmp;
    }
    pos = array.indexOf(item);
    if (pos === -1) {
      return;
    }
    _siftdown(array, 0, pos, cmp);
    return _siftup(array, pos, cmp);
  };


  /*
  Find the n largest elements in a dataset.
   */

  nlargest = function(array, n, cmp) {
    var elem, result, _i, _len, _ref;
    if (cmp == null) {
      cmp = defaultCmp;
    }
    result = array.slice(0, n);
    if (!result.length) {
      return result;
    }
    heapify(result, cmp);
    _ref = array.slice(n);
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      elem = _ref[_i];
      heappushpop(result, elem, cmp);
    }
    return result.sort(cmp).reverse();
  };


  /*
  Find the n smallest elements in a dataset.
   */

  nsmallest = function(array, n, cmp) {
    var elem, i, los, result, _i, _j, _len, _ref, _ref1, _results;
    if (cmp == null) {
      cmp = defaultCmp;
    }
    if (n * 10 <= array.length) {
      result = array.slice(0, n).sort(cmp);
      if (!result.length) {
        return result;
      }
      los = result[result.length - 1];
      _ref = array.slice(n);
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        elem = _ref[_i];
        if (cmp(elem, los) < 0) {
          insort(result, elem, 0, null, cmp);
          result.pop();
          los = result[result.length - 1];
        }
      }
      return result;
    }
    heapify(array, cmp);
    _results = [];
    for (i = _j = 0, _ref1 = min(n, array.length); 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
      _results.push(heappop(array, cmp));
    }
    return _results;
  };

  _siftdown = function(array, startpos, pos, cmp) {
    var newitem, parent, parentpos;
    if (cmp == null) {
      cmp = defaultCmp;
    }
    newitem = array[pos];
    while (pos > startpos) {
      parentpos = (pos - 1) >> 1;
      parent = array[parentpos];
      if (cmp(newitem, parent) < 0) {
        array[pos] = parent;
        pos = parentpos;
        continue;
      }
      break;
    }
    return array[pos] = newitem;
  };

  _siftup = function(array, pos, cmp) {
    var childpos, endpos, newitem, rightpos, startpos;
    if (cmp == null) {
      cmp = defaultCmp;
    }
    endpos = array.length;
    startpos = pos;
    newitem = array[pos];
    childpos = 2 * pos + 1;
    while (childpos < endpos) {
      rightpos = childpos + 1;
      if (rightpos < endpos && !(cmp(array[childpos], array[rightpos]) < 0)) {
        childpos = rightpos;
      }
      array[pos] = array[childpos];
      pos = childpos;
      childpos = 2 * pos + 1;
    }
    array[pos] = newitem;
    return _siftdown(array, startpos, pos, cmp);
  };

  Heap = (function() {
    Heap.push = heappush;

    Heap.pop = heappop;

    Heap.replace = heapreplace;

    Heap.pushpop = heappushpop;

    Heap.heapify = heapify;

    Heap.updateItem = updateItem;

    Heap.nlargest = nlargest;

    Heap.nsmallest = nsmallest;

    function Heap(cmp) {
      this.cmp = cmp != null ? cmp : defaultCmp;
      this.nodes = [];
    }

    Heap.prototype.push = function(x) {
      return heappush(this.nodes, x, this.cmp);
    };

    Heap.prototype.pop = function() {
      return heappop(this.nodes, this.cmp);
    };

    Heap.prototype.peek = function() {
      return this.nodes[0];
    };

    Heap.prototype.contains = function(x) {
      return this.nodes.indexOf(x) !== -1;
    };

    Heap.prototype.replace = function(x) {
      return heapreplace(this.nodes, x, this.cmp);
    };

    Heap.prototype.pushpop = function(x) {
      return heappushpop(this.nodes, x, this.cmp);
    };

    Heap.prototype.heapify = function() {
      return heapify(this.nodes, this.cmp);
    };

    Heap.prototype.updateItem = function(x) {
      return updateItem(this.nodes, x, this.cmp);
    };

    Heap.prototype.clear = function() {
      return this.nodes = [];
    };

    Heap.prototype.empty = function() {
      return this.nodes.length === 0;
    };

    Heap.prototype.size = function() {
      return this.nodes.length;
    };

    Heap.prototype.clone = function() {
      var heap;
      heap = new Heap();
      heap.nodes = this.nodes.slice(0);
      return heap;
    };

    Heap.prototype.toArray = function() {
      return this.nodes.slice(0);
    };

    Heap.prototype.insert = Heap.prototype.push;

    Heap.prototype.top = Heap.prototype.peek;

    Heap.prototype.front = Heap.prototype.peek;

    Heap.prototype.has = Heap.prototype.contains;

    Heap.prototype.copy = Heap.prototype.clone;

    return Heap;

  })();

  (function(root, factory) {
    if (typeof define === 'function' && define.amd) {
      return define([], factory);
    } else if (typeof exports === 'object') {
      return module.exports = factory();
    } else {
      return root.Heap = factory();
    }
  })(this, function() {
    return Heap;
  });

}).call(this);

/* jshint ignore:end */

},{}],76:[function(_dereq_,module,exports){
'use strict';

var window = _dereq_('./window');
var is = _dereq_('./is');
var Core = _dereq_('./core');
var extension = _dereq_('./extension');
var registerJquery = _dereq_('./jquery-plugin');
var Stylesheet = _dereq_('./stylesheet');
var Thread = _dereq_('./thread');
var Fabric = _dereq_('./fabric');

var cytoscape = function( options ){ // jshint ignore:line
  // if no options specified, use default
  if( options === undefined ){
    options = {};
  }

  // create instance
  if( is.plainObject( options ) ){
    return new Core( options );
  }

  // allow for registration of extensions
  else if( is.string( options ) ) {
    return extension.apply(extension, arguments);
  }
};

// replaced by build system
cytoscape.version = '2.5.4';

// try to register w/ jquery
if( window && window.jQuery ){
  registerJquery( window.jQuery, cytoscape );
}

// expose register api
cytoscape.registerJquery = function( jQuery ){
  registerJquery( jQuery, cytoscape );
};

// expose public apis (mostly for extensions)
cytoscape.stylesheet = cytoscape.Stylesheet = Stylesheet;
cytoscape.thread = cytoscape.Thread = Thread;
cytoscape.fabric = cytoscape.Fabric = Fabric;

module.exports = cytoscape;

},{"./core":34,"./extension":43,"./fabric":74,"./is":77,"./jquery-plugin":78,"./stylesheet":91,"./thread":92,"./window":100}],77:[function(_dereq_,module,exports){
'use strict';

var window = _dereq_('./window');
var navigator = window ? window.navigator : null;

var typeofstr = typeof '';
var typeofobj = typeof {};
var typeoffn = typeof function(){};
var typeofhtmlele = typeof HTMLElement;

var instanceStr = function( obj ){
  return obj && obj.instanceString && is.fn( obj.instanceString ) ? obj.instanceString() : null;
};

var is = {
  defined: function(obj){
    return obj != null; // not undefined or null
  },

  string: function(obj){
    return obj != null && typeof obj == typeofstr;
  },

  fn: function(obj){
    return obj != null && typeof obj === typeoffn;
  },

  array: function(obj){
    return Array.isArray ? Array.isArray(obj) : obj != null && obj instanceof Array;
  },

  plainObject: function(obj){
    return obj != null && typeof obj === typeofobj && !is.array(obj) && obj.constructor === Object;
  },

  object: function(obj){
    return obj != null && typeof obj === typeofobj;
  },

  number: function(obj){
    return obj != null && typeof obj === typeof 1 && !isNaN(obj);
  },

  integer: function( obj ){
    return is.number(obj) && Math.floor(obj) === obj;
  },

  bool: function(obj){
    return obj != null && typeof obj === typeof true;
  },

  htmlElement: function(obj){
    if( 'undefined' === typeofhtmlele ){
      return undefined;
    } else {
      return null != obj && obj instanceof HTMLElement;
    }
  },

  elementOrCollection: function(obj){
    return is.element(obj) || is.collection(obj);
  },

  element: function(obj){
    return instanceStr(obj) === 'collection' && obj._private.single;
  },

  collection: function(obj){
    return instanceStr(obj) === 'collection' && !obj._private.single;
  },

  core: function(obj){
    return instanceStr(obj) === 'core';
  },

  style: function(obj){
    return instanceStr(obj) === 'style';
  },

  stylesheet: function(obj){
    return instanceStr(obj) === 'stylesheet';
  },

  event: function(obj){
    return instanceStr(obj) === 'event';
  },

  thread: function(obj){
    return instanceStr(obj) === 'thread';
  },

  fabric: function(obj){
    return instanceStr(obj) === 'fabric';
  },

  emptyString: function(obj){
    if( !obj ){ // null is empty
      return true;
    } else if( is.string(obj) ){
      if( obj === '' || obj.match(/^\s+$/) ){
        return true; // empty string is empty
      }
    }

    return false; // otherwise, we don't know what we've got
  },

  nonemptyString: function(obj){
    if( obj && is.string(obj) && obj !== '' && !obj.match(/^\s+$/) ){
      return true;
    }

    return false;
  },

  domElement: function(obj){
    if( typeof HTMLElement === 'undefined' ){
      return false; // we're not in a browser so it doesn't matter
    } else {
      return obj instanceof HTMLElement;
    }
  },

  boundingBox: function(obj){
    return is.plainObject(obj) &&
      is.number(obj.x1) && is.number(obj.x2) &&
      is.number(obj.y1) && is.number(obj.y2)
    ;
  },

  promise: function(obj){
    return is.object(obj) && is.fn(obj.then);
  },

  touch: function(){
    return window && ( ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch );
  },

  gecko: function(){
    return typeof InstallTrigger !== 'undefined' || ('MozAppearance' in document.documentElement.style);
  },

  webkit: function(){
    return typeof webkitURL !== 'undefined' || ('WebkitAppearance' in document.documentElement.style);
  },

  chromium: function(){
    return typeof chrome !== 'undefined';
  },

  khtml: function(){
    return navigator && navigator.vendor.match(/kde/i); // probably a better way to detect this...
  },

  khtmlEtc: function(){
    return is.khtml() || is.webkit() || is.chromium();
  },

  ms: function(){
     return navigator && navigator.userAgent.match(/msie|trident|edge/i); // probably a better way to detect this...
  },

  windows: function(){
    return navigator && navigator.appVersion.match(/Win/i);
  },

  mac: function(){
    return navigator && navigator.appVersion.match(/Mac/i);
  },

  linux: function(){
    return navigator && navigator.appVersion.match(/Linux/i);
  },

  unix: function(){
    return navigator && navigator.appVersion.match(/X11/i);
  }
};

module.exports = is;

},{"./window":100}],78:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('./is');

var cyReg = function( $ele ){
  var d = $ele[0]._cyreg = $ele[0]._cyreg || {};

  return d;
};

var registerJquery = function( $, cytoscape ){
  if( !$ ){ return; } // no jquery => don't need this

  if( $.fn.cytoscape ){ return; } // already registered

  // allow calls on a jQuery selector by proxying calls to $.cytoscape
  // e.g. $("#foo").cytoscape(options) => $.cytoscape(options) on #foo
  $.fn.cytoscape = function(opts){
    var $this = $(this);

    // get object
    if( opts === 'get' ){
      return cyReg( $this ).cy;
    }

    // bind to ready
    else if( is.fn(opts) ){

      var ready = opts;
      var cy = cyReg( $this ).cy;

      if( cy && cy.isReady() ){ // already ready so just trigger now
        cy.trigger('ready', [], ready);

      } else { // not yet ready, so add to readies list
        var data = cyReg( $this );
        var readies = data.readies = data.readies || [];

        readies.push( ready );
      }

    }

    // proxy to create instance
    else if( is.plainObject(opts) ){
      return $this.each(function(){
        var options = $.extend({}, opts, {
          container: $(this)[0]
        });

        cytoscape(options);
      });
    }
  };

  // allow access to the global cytoscape object under jquery for legacy reasons
  $.cytoscape = cytoscape;

  // use short alias (cy) if not already defined
  if( $.fn.cy == null && $.cy == null ){
    $.fn.cy = $.fn.cytoscape;
    $.cy = $.cytoscape;
  }
};

module.exports = registerJquery;

},{"./is":77}],79:[function(_dereq_,module,exports){
'use strict';

var math = {};

math.signum = function(x){
  if( x > 0 ){
    return 1;
  } else if( x < 0 ){
    return -1;
  } else {
    return 0;
  }
};

math.distance = function( p1, p2 ){
  return Math.sqrt( math.sqDistance(p1, p2) );
};

math.sqDistance = function( p1, p2 ){
  var dx = p2.x - p1.x;
  var dy = p2.y - p1.y;

  return dx*dx + dy*dy;
};

// from http://en.wikipedia.org/wiki/Bézier_curve#Quadratic_curves
math.qbezierAt = function(p0, p1, p2, t){
  return (1 - t)*(1 - t)*p0 + 2*(1 - t)*t*p1 + t*t*p2;
};

math.qbezierPtAt = function(p0, p1, p2, t){
  return {
    x: math.qbezierAt( p0.x, p1.x, p2.x, t ),
    y: math.qbezierAt( p0.y, p1.y, p2.y, t )
  };
};

// makes a full bb (x1, y1, x2, y2, w, h) from implicit params
math.makeBoundingBox = function( bb ){
  if( bb.x1 != null && bb.y1 != null ){
    if( bb.x2 != null && bb.y2 != null && bb.x2 >= bb.x1 && bb.y2 >= bb.y1 ){
      return {
        x1: bb.x1,
        y1: bb.y1,
        x2: bb.x2,
        y2: bb.y2,
        w: bb.x2 - bb.x1,
        h: bb.y2 - bb.y1
      };
    } else if( bb.w != null && bb.h != null && bb.w >= 0 && bb.h >= 0 ){
      return {
        x1: bb.x1,
        y1: bb.y1,
        x2: bb.x1 + bb.w,
        y2: bb.y1 + bb.h,
        w: bb.w,
        h: bb.h
      };
    }
  }
};

math.boundingBoxesIntersect = function( bb1, bb2 ){
  // case: one bb to right of other
  if( bb1.x1 > bb2.x2 ){ return false; }
  if( bb2.x1 > bb1.x2 ){ return false; }

  // case: one bb to left of other
  if( bb1.x2 < bb2.x1 ){ return false; }
  if( bb2.x2 < bb1.x1 ){ return false; }

  // case: one bb above other
  if( bb1.y2 < bb2.y1 ){ return false; }
  if( bb2.y2 < bb1.y1 ){ return false; }

  // case: one bb below other
  if( bb1.y1 > bb2.y2 ){ return false; }
  if( bb2.y1 > bb1.y2 ){ return false; }

  // otherwise, must have some overlap
  return true;
};

math.inBoundingBox = function( bb, x, y ){
  return bb.x1 <= x && x <= bb.x2 && bb.y1 <= y && y <= bb.y2;
};

math.pointInBoundingBox = function( bb, pt ){
  return this.inBoundingBox( bb, pt.x, pt.y );
};

math.roundRectangleIntersectLine = function(
  x, y, nodeX, nodeY, width, height, padding) {

  var cornerRadius = this.getRoundRectangleRadius(width, height);

  var halfWidth = width / 2;
  var halfHeight = height / 2;

  // Check intersections with straight line segments
  var straightLineIntersections;

  // Top segment, left to right
  {
    var topStartX = nodeX - halfWidth + cornerRadius - padding;
    var topStartY = nodeY - halfHeight - padding;
    var topEndX = nodeX + halfWidth - cornerRadius + padding;
    var topEndY = topStartY;

    straightLineIntersections = this.finiteLinesIntersect(
      x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false);

    if (straightLineIntersections.length > 0) {
      return straightLineIntersections;
    }
  }

  // Right segment, top to bottom
  {
    var rightStartX = nodeX + halfWidth + padding;
    var rightStartY = nodeY - halfHeight + cornerRadius - padding;
    var rightEndX = rightStartX;
    var rightEndY = nodeY + halfHeight - cornerRadius + padding;

    straightLineIntersections = this.finiteLinesIntersect(
      x, y, nodeX, nodeY, rightStartX, rightStartY, rightEndX, rightEndY, false);

    if (straightLineIntersections.length > 0) {
      return straightLineIntersections;
    }
  }

  // Bottom segment, left to right
  {
    var bottomStartX = nodeX - halfWidth + cornerRadius - padding;
    var bottomStartY = nodeY + halfHeight + padding;
    var bottomEndX = nodeX + halfWidth - cornerRadius + padding;
    var bottomEndY = bottomStartY;

    straightLineIntersections = this.finiteLinesIntersect(
      x, y, nodeX, nodeY, bottomStartX, bottomStartY, bottomEndX, bottomEndY, false);

    if (straightLineIntersections.length > 0) {
      return straightLineIntersections;
    }
  }

  // Left segment, top to bottom
  {
    var leftStartX = nodeX - halfWidth - padding;
    var leftStartY = nodeY - halfHeight + cornerRadius - padding;
    var leftEndX = leftStartX;
    var leftEndY = nodeY + halfHeight - cornerRadius + padding;

    straightLineIntersections = this.finiteLinesIntersect(
      x, y, nodeX, nodeY, leftStartX, leftStartY, leftEndX, leftEndY, false);

    if (straightLineIntersections.length > 0) {
      return straightLineIntersections;
    }
  }

  // Check intersections with arc segments
  var arcIntersections;

  // Top Left
  {
    var topLeftCenterX = nodeX - halfWidth + cornerRadius;
    var topLeftCenterY = nodeY - halfHeight + cornerRadius;
    arcIntersections = this.intersectLineCircle(
      x, y, nodeX, nodeY,
      topLeftCenterX, topLeftCenterY, cornerRadius + padding);

    // Ensure the intersection is on the desired quarter of the circle
    if (arcIntersections.length > 0
      && arcIntersections[0] <= topLeftCenterX
      && arcIntersections[1] <= topLeftCenterY) {
      return [arcIntersections[0], arcIntersections[1]];
    }
  }

  // Top Right
  {
    var topRightCenterX = nodeX + halfWidth - cornerRadius;
    var topRightCenterY = nodeY - halfHeight + cornerRadius;
    arcIntersections = this.intersectLineCircle(
      x, y, nodeX, nodeY,
      topRightCenterX, topRightCenterY, cornerRadius + padding);

    // Ensure the intersection is on the desired quarter of the circle
    if (arcIntersections.length > 0
      && arcIntersections[0] >= topRightCenterX
      && arcIntersections[1] <= topRightCenterY) {
      return [arcIntersections[0], arcIntersections[1]];
    }
  }

  // Bottom Right
  {
    var bottomRightCenterX = nodeX + halfWidth - cornerRadius;
    var bottomRightCenterY = nodeY + halfHeight - cornerRadius;
    arcIntersections = this.intersectLineCircle(
      x, y, nodeX, nodeY,
      bottomRightCenterX, bottomRightCenterY, cornerRadius + padding);

    // Ensure the intersection is on the desired quarter of the circle
    if (arcIntersections.length > 0
      && arcIntersections[0] >= bottomRightCenterX
      && arcIntersections[1] >= bottomRightCenterY) {
      return [arcIntersections[0], arcIntersections[1]];
    }
  }

  // Bottom Left
  {
    var bottomLeftCenterX = nodeX - halfWidth + cornerRadius;
    var bottomLeftCenterY = nodeY + halfHeight - cornerRadius;
    arcIntersections = this.intersectLineCircle(
      x, y, nodeX, nodeY,
      bottomLeftCenterX, bottomLeftCenterY, cornerRadius + padding);

    // Ensure the intersection is on the desired quarter of the circle
    if (arcIntersections.length > 0
      && arcIntersections[0] <= bottomLeftCenterX
      && arcIntersections[1] >= bottomLeftCenterY) {
      return [arcIntersections[0], arcIntersections[1]];
    }
  }

  return []; // if nothing
};

math.inLineVicinity = function(x, y, lx1, ly1, lx2, ly2, tolerance){
  var t = tolerance;

  var x1 = Math.min(lx1, lx2);
  var x2 = Math.max(lx1, lx2);
  var y1 = Math.min(ly1, ly2);
  var y2 = Math.max(ly1, ly2);

  return x1 - t <= x && x <= x2 + t
    && y1 - t <= y && y <= y2 + t;
};

math.inBezierVicinity = function(
  x, y, x1, y1, x2, y2, x3, y3, tolerance) {

  var bb = {
    x1: Math.min( x1, x3, x2 ) - tolerance,
    x2: Math.max( x1, x3, x2 ) + tolerance,
    y1: Math.min( y1, y3, y2 ) - tolerance,
    y2: Math.max( y1, y3, y2 ) + tolerance
  };

  // if outside the rough bounding box for the bezier, then it can't be a hit
  if( x < bb.x1 || x > bb.x2 || y < bb.y1 || y > bb.y2 ){
    // console.log('bezier out of rough bb')
    return false;
  } else {
    // console.log('do more expensive check');
    return true;
  }

};

math.solveCubic = function(a, b, c, d, result) {

  // Solves a cubic function, returns root in form [r1, i1, r2, i2, r3, i3], where
  // r is the real component, i is the imaginary component

  // An implementation of the Cardano method from the year 1545
  // http://en.wikipedia.org/wiki/Cubic_function#The_nature_of_the_roots

  b /= a;
  c /= a;
  d /= a;

  var discriminant, q, r, dum1, s, t, term1, r13;

  q = (3.0 * c - (b * b)) / 9.0;
  r = -(27.0 * d) + b * (9.0 * c - 2.0 * (b * b));
  r /= 54.0;

  discriminant = q * q * q + r * r;
  result[1] = 0;
  term1 = (b / 3.0);

  if (discriminant > 0) {
    s = r + Math.sqrt(discriminant);
    s = ((s < 0) ? -Math.pow(-s, (1.0 / 3.0)) : Math.pow(s, (1.0 / 3.0)));
    t = r - Math.sqrt(discriminant);
    t = ((t < 0) ? -Math.pow(-t, (1.0 / 3.0)) : Math.pow(t, (1.0 / 3.0)));
    result[0] = -term1 + s + t;
    term1 += (s + t) / 2.0;
    result[4] = result[2] = -term1;
    term1 = Math.sqrt(3.0) * (-t + s) / 2;
    result[3] = term1;
    result[5] = -term1;
    return;
  }

  result[5] = result[3] = 0;

  if (discriminant === 0) {
    r13 = ((r < 0) ? -Math.pow(-r, (1.0 / 3.0)) : Math.pow(r, (1.0 / 3.0)));
    result[0] = -term1 + 2.0 * r13;
    result[4] = result[2] = -(r13 + term1);
    return;
  }

  q = -q;
  dum1 = q * q * q;
  dum1 = Math.acos(r / Math.sqrt(dum1));
  r13 = 2.0 * Math.sqrt(q);
  result[0] = -term1 + r13 * Math.cos(dum1 / 3.0);
  result[2] = -term1 + r13 * Math.cos((dum1 + 2.0 * Math.PI) / 3.0);
  result[4] = -term1 + r13 * Math.cos((dum1 + 4.0 * Math.PI) / 3.0);

  return;
};

math.sqDistanceToQuadraticBezier = function(
  x, y, x1, y1, x2, y2, x3, y3) {

  // Find minimum distance by using the minimum of the distance
  // function between the given point and the curve

  // This gives the coefficients of the resulting cubic equation
  // whose roots tell us where a possible minimum is
  // (Coefficients are divided by 4)

  var a = 1.0 * x1*x1 - 4*x1*x2 + 2*x1*x3 + 4*x2*x2 - 4*x2*x3 + x3*x3
    + y1*y1 - 4*y1*y2 + 2*y1*y3 + 4*y2*y2 - 4*y2*y3 + y3*y3;

  var b = 1.0 * 9*x1*x2 - 3*x1*x1 - 3*x1*x3 - 6*x2*x2 + 3*x2*x3
    + 9*y1*y2 - 3*y1*y1 - 3*y1*y3 - 6*y2*y2 + 3*y2*y3;

  var c = 1.0 * 3*x1*x1 - 6*x1*x2 + x1*x3 - x1*x + 2*x2*x2 + 2*x2*x - x3*x
    + 3*y1*y1 - 6*y1*y2 + y1*y3 - y1*y + 2*y2*y2 + 2*y2*y - y3*y;

  var d = 1.0 * x1*x2 - x1*x1 + x1*x - x2*x
    + y1*y2 - y1*y1 + y1*y - y2*y;

  // debug("coefficients: " + a / a + ", " + b / a + ", " + c / a + ", " + d / a);

  var roots = [];

  // Use the cubic solving algorithm
  this.solveCubic(a, b, c, d, roots);

  var zeroThreshold = 0.0000001;

  var params = [];

  for (var index = 0; index < 6; index += 2) {
    if (Math.abs(roots[index + 1]) < zeroThreshold
        && roots[index] >= 0
        && roots[index] <= 1.0) {
      params.push(roots[index]);
    }
  }

  params.push(1.0);
  params.push(0.0);

  var minDistanceSquared = -1;
  var closestParam;

  var curX, curY, distSquared;
  for (var i = 0; i < params.length; i++) {
    curX = Math.pow(1.0 - params[i], 2.0) * x1
      + 2.0 * (1 - params[i]) * params[i] * x2
      + params[i] * params[i] * x3;

    curY = Math.pow(1 - params[i], 2.0) * y1
      + 2 * (1.0 - params[i]) * params[i] * y2
      + params[i] * params[i] * y3;

    distSquared = Math.pow(curX - x, 2) + Math.pow(curY - y, 2);
    // debug('distance for param ' + params[i] + ": " + Math.sqrt(distSquared));
    if (minDistanceSquared >= 0) {
      if (distSquared < minDistanceSquared) {
        minDistanceSquared = distSquared;
        closestParam = params[i];
      }
    } else {
      minDistanceSquared = distSquared;
      closestParam = params[i];
    }
  }

  return minDistanceSquared;
};

math.sqDistanceToFiniteLine = function(x, y, x1, y1, x2, y2) {
  var offset = [x - x1, y - y1];
  var line = [x2 - x1, y2 - y1];

  var lineSq = line[0] * line[0] + line[1] * line[1];
  var hypSq = offset[0] * offset[0] + offset[1] * offset[1];

  var dotProduct = offset[0] * line[0] + offset[1] * line[1];
  var adjSq = dotProduct * dotProduct / lineSq;

  if (dotProduct < 0) {
    return hypSq;
  }

  if (adjSq > lineSq) {
    return (x - x2) * (x - x2) + (y - y2) * (y - y2);
  }

  return hypSq - adjSq;
};

math.pointInsidePolygonPoints = function(x, y, points){
  var x1, y1, x2, y2;
  var y3;

  // Intersect with vertical line through (x, y)
  var up = 0;
  var down = 0;
  for (var i = 0; i < points.length / 2; i++) {

    x1 = points[i * 2];
    y1 = points[i * 2 + 1];

    if (i + 1 < points.length / 2) {
      x2 = points[(i + 1) * 2];
      y2 = points[(i + 1) * 2 + 1];
    } else {
      x2 = points[(i + 1 - points.length / 2) * 2];
      y2 = points[(i + 1 - points.length / 2) * 2 + 1];
    }

    if (x1 == x && x2 == x) {

    } else if ((x1 >= x && x >= x2)
      || (x1 <= x && x <= x2)) {

      y3 = (x - x1) / (x2 - x1) * (y2 - y1) + y1;

      if (y3 > y) {
        up++;
      }

      if (y3 < y) {
        down++;
      }

    } else {
      continue;
    }

  }

  if (up % 2 === 0) {
    return false;
  } else {
    return true;
  }
};

math.pointInsidePolygon = function(
  x, y, basePoints, centerX, centerY, width, height, direction, padding) {

  //var direction = arguments[6];
  var transformedPoints = new Array(basePoints.length);

  // Gives negative angle
  var angle;

  if( direction[0] != null ){
    angle = Math.atan(direction[1] / direction[0]);

    if (direction[0] < 0) {
      angle = angle + Math.PI / 2;
    } else {
      angle = -angle - Math.PI / 2;
    }
  } else {
    angle = direction;
  }

  var cos = Math.cos(-angle);
  var sin = Math.sin(-angle);

  //    console.log("base: " + basePoints);
  for (var i = 0; i < transformedPoints.length / 2; i++) {
    transformedPoints[i * 2] =
      width / 2 * (basePoints[i * 2] * cos
        - basePoints[i * 2 + 1] * sin);

    transformedPoints[i * 2 + 1] =
      height / 2 * (basePoints[i * 2 + 1] * cos
        + basePoints[i * 2] * sin);

    transformedPoints[i * 2] += centerX;
    transformedPoints[i * 2 + 1] += centerY;
  }

  var points;

  if (padding > 0) {
    var expandedLineSet = this.expandPolygon(
      transformedPoints,
      -padding);

    points = this.joinLines(expandedLineSet);
  } else {
    points = transformedPoints;
  }

  return math.pointInsidePolygonPoints( x, y, points );
};

math.joinLines = function(lineSet) {

  var vertices = new Array(lineSet.length / 2);

  var currentLineStartX, currentLineStartY, currentLineEndX, currentLineEndY;
  var nextLineStartX, nextLineStartY, nextLineEndX, nextLineEndY;

  for (var i = 0; i < lineSet.length / 4; i++) {
    currentLineStartX = lineSet[i * 4];
    currentLineStartY = lineSet[i * 4 + 1];
    currentLineEndX = lineSet[i * 4 + 2];
    currentLineEndY = lineSet[i * 4 + 3];

    if (i < lineSet.length / 4 - 1) {
      nextLineStartX = lineSet[(i + 1) * 4];
      nextLineStartY = lineSet[(i + 1) * 4 + 1];
      nextLineEndX = lineSet[(i + 1) * 4 + 2];
      nextLineEndY = lineSet[(i + 1) * 4 + 3];
    } else {
      nextLineStartX = lineSet[0];
      nextLineStartY = lineSet[1];
      nextLineEndX = lineSet[2];
      nextLineEndY = lineSet[3];
    }

    var intersection = this.finiteLinesIntersect(
      currentLineStartX, currentLineStartY,
      currentLineEndX, currentLineEndY,
      nextLineStartX, nextLineStartY,
      nextLineEndX, nextLineEndY,
      true);

    vertices[i * 2] = intersection[0];
    vertices[i * 2 + 1] = intersection[1];
  }

  return vertices;
};

math.expandPolygon = function(points, pad) {

  var expandedLineSet = new Array(points.length * 2);

  var currentPointX, currentPointY, nextPointX, nextPointY;

  for (var i = 0; i < points.length / 2; i++) {
    currentPointX = points[i * 2];
    currentPointY = points[i * 2 + 1];

    if (i < points.length / 2 - 1) {
      nextPointX = points[(i + 1) * 2];
      nextPointY = points[(i + 1) * 2 + 1];
    } else {
      nextPointX = points[0];
      nextPointY = points[1];
    }

    // Current line: [currentPointX, currentPointY] to [nextPointX, nextPointY]

    // Assume CCW polygon winding

    var offsetX = (nextPointY - currentPointY);
    var offsetY = -(nextPointX - currentPointX);

    // Normalize
    var offsetLength = Math.sqrt(offsetX * offsetX + offsetY * offsetY);
    var normalizedOffsetX = offsetX / offsetLength;
    var normalizedOffsetY = offsetY / offsetLength;

    expandedLineSet[i * 4] = currentPointX + normalizedOffsetX * pad;
    expandedLineSet[i * 4 + 1] = currentPointY + normalizedOffsetY * pad;
    expandedLineSet[i * 4 + 2] = nextPointX + normalizedOffsetX * pad;
    expandedLineSet[i * 4 + 3] = nextPointY + normalizedOffsetY * pad;
  }

  return expandedLineSet;
};

math.intersectLineEllipse = function(
  x, y, centerX, centerY, ellipseWradius, ellipseHradius) {

  var dispX = centerX - x;
  var dispY = centerY - y;

  dispX /= ellipseWradius;
  dispY /= ellipseHradius;

  var len = Math.sqrt(dispX * dispX + dispY * dispY);

  var newLength = len - 1;

  if (newLength < 0) {
    return [];
  }

  var lenProportion = newLength / len;

  return [(centerX - x) * lenProportion + x, (centerY - y) * lenProportion + y];
};

// Returns intersections of increasing distance from line's start point
math.intersectLineCircle = function(
  x1, y1, x2, y2, centerX, centerY, radius) {

  // Calculate d, direction vector of line
  var d = [x2 - x1, y2 - y1]; // Direction vector of line
  var c = [centerX, centerY]; // Center of circle
  var f = [x1 - centerX, y1 - centerY];

  var a = d[0] * d[0] + d[1] * d[1];
  var b = 2 * (f[0] * d[0] + f[1] * d[1]);
  var c = (f[0] * f[0] + f[1] * f[1]) - radius * radius ;

  var discriminant = b*b-4*a*c;

  if (discriminant < 0) {
    return [];
  }

  var t1 = (-b + Math.sqrt(discriminant)) / (2 * a);
  var t2 = (-b - Math.sqrt(discriminant)) / (2 * a);

  var tMin = Math.min(t1, t2);
  var tMax = Math.max(t1, t2);
  var inRangeParams = [];

  if (tMin >= 0 && tMin <= 1) {
    inRangeParams.push(tMin);
  }

  if (tMax >= 0 && tMax <= 1) {
    inRangeParams.push(tMax);
  }

  if (inRangeParams.length === 0) {
    return [];
  }

  var nearIntersectionX = inRangeParams[0] * d[0] + x1;
  var nearIntersectionY = inRangeParams[0] * d[1] + y1;

  if (inRangeParams.length > 1) {

    if (inRangeParams[0] == inRangeParams[1]) {
      return [nearIntersectionX, nearIntersectionY];
    } else {

      var farIntersectionX = inRangeParams[1] * d[0] + x1;
      var farIntersectionY = inRangeParams[1] * d[1] + y1;

      return [nearIntersectionX, nearIntersectionY, farIntersectionX, farIntersectionY];
    }

  } else {
    return [nearIntersectionX, nearIntersectionY];
  }

};

math.findCircleNearPoint = function(centerX, centerY,
  radius, farX, farY) {

  var displacementX = farX - centerX;
  var displacementY = farY - centerY;
  var distance = Math.sqrt(displacementX * displacementX
    + displacementY * displacementY);

  var unitDisplacementX = displacementX / distance;
  var unitDisplacementY = displacementY / distance;

  return [centerX + unitDisplacementX * radius,
    centerY + unitDisplacementY * radius];
};

math.findMaxSqDistanceToOrigin = function(points) {
  var maxSqDistance = 0.000001;
  var sqDistance;

  for (var i = 0; i < points.length / 2; i++) {

    sqDistance = points[i * 2] * points[i * 2]
      + points[i * 2 + 1] * points[i * 2 + 1];

    if (sqDistance > maxSqDistance) {
      maxSqDistance = sqDistance;
    }
  }

  return maxSqDistance;
};

math.finiteLinesIntersect = function(
  x1, y1, x2, y2, x3, y3, x4, y4, infiniteLines) {

  var ua_t = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
  var ub_t = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
  var u_b = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);

  if (u_b !== 0) {
    var ua = ua_t / u_b;
    var ub = ub_t / u_b;

    if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
      return [x1 + ua * (x2 - x1), y1 + ua * (y2 - y1)];

    } else {
      if (!infiniteLines) {
        return [];
      } else {
        return [x1 + ua * (x2 - x1), y1 + ua * (y2 - y1)];
      }
    }
  } else {
    if (ua_t === 0 || ub_t === 0) {

      // Parallel, coincident lines. Check if overlap

      // Check endpoint of second line
      if ([x1, x2, x4].sort()[1] === x4) {
        return [x4, y4];
      }

      // Check start point of second line
      if ([x1, x2, x3].sort()[1] === x3) {
        return [x3, y3];
      }

      // Endpoint of first line
      if ([x3, x4, x2].sort()[1] === x2) {
        return [x2, y2];
      }

      return [];
    } else {

      // Parallel, non-coincident
      return [];
    }
  }
};

math.polygonIntersectLine = function(
  x, y, basePoints, centerX, centerY, width, height, padding) {

  var intersections = [];
  var intersection;

  var transformedPoints = new Array(basePoints.length);

  for (var i = 0; i < transformedPoints.length / 2; i++) {
    transformedPoints[i * 2] = basePoints[i * 2] * width + centerX;
    transformedPoints[i * 2 + 1] = basePoints[i * 2 + 1] * height + centerY;
  }

  var points;

  if (padding > 0) {
    var expandedLineSet = math.expandPolygon(
      transformedPoints,
      -padding);

    points = math.joinLines(expandedLineSet);
  } else {
    points = transformedPoints;
  }
  // var points = transformedPoints;

  var currentX, currentY, nextX, nextY;

  for (var i = 0; i < points.length / 2; i++) {

    currentX = points[i * 2];
    currentY = points[i * 2 + 1];

    if (i < points.length / 2 - 1) {
      nextX = points[(i + 1) * 2];
      nextY = points[(i + 1) * 2 + 1];
    } else {
      nextX = points[0];
      nextY = points[1];
    }

    intersection = this.finiteLinesIntersect(
      x, y, centerX, centerY,
      currentX, currentY,
      nextX, nextY);

    if (intersection.length !== 0) {
      intersections.push(intersection[0], intersection[1]);
    }
  }

  return intersections;
};

math.shortenIntersection = function(
  intersection, offset, amount) {

  var disp = [intersection[0] - offset[0], intersection[1] - offset[1]];

  var length = Math.sqrt(disp[0] * disp[0] + disp[1] * disp[1]);

  var lenRatio = (length - amount) / length;

  if (lenRatio < 0) {
    lenRatio = 0.00001;
  }

  return [offset[0] + lenRatio * disp[0], offset[1] + lenRatio * disp[1]];
};

math.generateUnitNgonPointsFitToSquare = function(sides, rotationRadians) {
  var points = math.generateUnitNgonPoints(sides, rotationRadians);
  points = math.fitPolygonToSquare(points);

  return points;
};

math.fitPolygonToSquare = function(points){
  var x, y;
  var sides = points.length/2;
  var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;

  for (var i = 0; i < sides; i++) {
    x = points[2 * i];
    y = points[2 * i + 1];

    minX = Math.min( minX, x );
    maxX = Math.max( maxX, x );
    minY = Math.min( minY, y );
    maxY = Math.max( maxY, y );
  }

  // stretch factors
  var sx = 2 / (maxX - minX);
  var sy = 2 / (maxY - minY);

  for (var i = 0; i < sides; i++){
    x = points[2 * i] = points[2 * i] * sx;
    y = points[2 * i + 1] = points[2 * i + 1] * sy;

    minX = Math.min( minX, x );
    maxX = Math.max( maxX, x );
    minY = Math.min( minY, y );
    maxY = Math.max( maxY, y );
  }

  if( minY < -1 ){
    for (var i = 0; i < sides; i++){
      y = points[2 * i + 1] = points[2 * i + 1] + (-1 -minY);
    }
  }

  return points;
};

math.generateUnitNgonPoints = function(sides, rotationRadians) {

  var increment = 1.0 / sides * 2 * Math.PI;
  var startAngle = sides % 2 === 0 ?
    Math.PI / 2.0 + increment / 2.0 : Math.PI / 2.0;
    //    console.log(nodeShapes['square']);
  startAngle += rotationRadians;

  var points = new Array(sides * 2);

  var currentAngle, x, y;
  for (var i = 0; i < sides; i++) {
    currentAngle = i * increment + startAngle;

    x = points[2 * i] = Math.cos(currentAngle);// * (1 + i/2);
    y = points[2 * i + 1] = Math.sin(-currentAngle);//  * (1 + i/2);
  }

  return points;
};

math.getRoundRectangleRadius = function(width, height) {

  // Set the default radius, unless half of width or height is smaller than default
  return Math.min(width / 4, height / 4, 8);
};

module.exports = math;

},{}],80:[function(_dereq_,module,exports){
// internal, minimal Promise impl s.t. apis can return promises in old envs
// based on thenable (http://github.com/rse/thenable)

'use strict';

/*  promise states [Promises/A+ 2.1]  */
var STATE_PENDING   = 0;                                         /*  [Promises/A+ 2.1.1]  */
var STATE_FULFILLED = 1;                                         /*  [Promises/A+ 2.1.2]  */
var STATE_REJECTED  = 2;                                         /*  [Promises/A+ 2.1.3]  */

/*  promise object constructor  */
var api = function (executor) {
  /*  optionally support non-constructor/plain-function call  */
  if (!(this instanceof api))
    return new api(executor);

  /*  initialize object  */
  this.id           = "Thenable/1.0.7";
  this.state        = STATE_PENDING; /*  initial state  */
  this.fulfillValue = undefined;     /*  initial value  */     /*  [Promises/A+ 1.3, 2.1.2.2]  */
  this.rejectReason = undefined;     /*  initial reason */     /*  [Promises/A+ 1.5, 2.1.3.2]  */
  this.onFulfilled  = [];            /*  initial handlers  */
  this.onRejected   = [];            /*  initial handlers  */

  /*  provide optional information-hiding proxy  */
  this.proxy = {
    then: this.then.bind(this)
  };

  /*  support optional executor function  */
  if (typeof executor === "function")
    executor.call(this, this.fulfill.bind(this), this.reject.bind(this));
};

/*  promise API methods  */
api.prototype = {
  /*  promise resolving methods  */
  fulfill: function (value) { return deliver(this, STATE_FULFILLED, "fulfillValue", value); },
  reject:  function (value) { return deliver(this, STATE_REJECTED,  "rejectReason", value); },

  /*  "The then Method" [Promises/A+ 1.1, 1.2, 2.2]  */
  then: function (onFulfilled, onRejected) {
    var curr = this;
    var next = new api();                                    /*  [Promises/A+ 2.2.7]  */
    curr.onFulfilled.push(
      resolver(onFulfilled, next, "fulfill"));             /*  [Promises/A+ 2.2.2/2.2.6]  */
    curr.onRejected.push(
      resolver(onRejected,  next, "reject" ));             /*  [Promises/A+ 2.2.3/2.2.6]  */
    execute(curr);
    return next.proxy;                                       /*  [Promises/A+ 2.2.7, 3.3]  */
  }
};

/*  deliver an action  */
var deliver = function (curr, state, name, value) {
  if (curr.state === STATE_PENDING) {
    curr.state = state;                                      /*  [Promises/A+ 2.1.2.1, 2.1.3.1]  */
    curr[name] = value;                                      /*  [Promises/A+ 2.1.2.2, 2.1.3.2]  */
    execute(curr);
  }
  return curr;
};

/*  execute all handlers  */
var execute = function (curr) {
  if (curr.state === STATE_FULFILLED)
    execute_handlers(curr, "onFulfilled", curr.fulfillValue);
  else if (curr.state === STATE_REJECTED)
    execute_handlers(curr, "onRejected",  curr.rejectReason);
};

/*  execute particular set of handlers  */
var execute_handlers = function (curr, name, value) {
  /* global setImmediate: true */
  /* global setTimeout: true */

  /*  short-circuit processing  */
  if (curr[name].length === 0)
    return;

  /*  iterate over all handlers, exactly once  */
  var handlers = curr[name];
  curr[name] = [];                                             /*  [Promises/A+ 2.2.2.3, 2.2.3.3]  */
  var func = function () {
    for (var i = 0; i < handlers.length; i++)
      handlers[i](value);                                  /*  [Promises/A+ 2.2.5]  */
  };

  /*  execute procedure asynchronously  */                     /*  [Promises/A+ 2.2.4, 3.1]  */
  if (typeof setImmediate === "function")
    setImmediate(func);
  else
    setTimeout(func, 0);
};

/*  generate a resolver function  */
var resolver = function (cb, next, method) {
  return function (value) {
    if (typeof cb !== "function")                            /*  [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4]  */
      next[method].call(next, value);                      /*  [Promises/A+ 2.2.7.3, 2.2.7.4]  */
    else {
      var result;
      try { result = cb(value); }                          /*  [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2]  */
      catch (e) {
        next.reject(e);                                  /*  [Promises/A+ 2.2.7.2]  */
        return;
      }
      resolve(next, result);                               /*  [Promises/A+ 2.2.7.1]  */
    }
  };
};

/*  "Promise Resolution Procedure"  */                           /*  [Promises/A+ 2.3]  */
var resolve = function (promise, x) {
  /*  sanity check arguments  */                               /*  [Promises/A+ 2.3.1]  */
  if (promise === x || promise.proxy === x) {
    promise.reject(new TypeError("cannot resolve promise with itself"));
    return;
  }

  /*  surgically check for a "then" method
    (mainly to just call the "getter" of "then" only once)  */
  var then;
  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    try { then = x.then; }                                   /*  [Promises/A+ 2.3.3.1, 3.5]  */
    catch (e) {
      promise.reject(e);                                   /*  [Promises/A+ 2.3.3.2]  */
      return;
    }
  }

  /*  handle own Thenables    [Promises/A+ 2.3.2]
    and similar "thenables" [Promises/A+ 2.3.3]  */
  if (typeof then === "function") {
    var resolved = false;
    try {
      /*  call retrieved "then" method */                  /*  [Promises/A+ 2.3.3.3]  */
      then.call(x,
        /*  resolvePromise  */                           /*  [Promises/A+ 2.3.3.3.1]  */
        function (y) {
          if (resolved) return; resolved = true;       /*  [Promises/A+ 2.3.3.3.3]  */
          if (y === x)                                 /*  [Promises/A+ 3.6]  */
            promise.reject(new TypeError("circular thenable chain"));
          else
            resolve(promise, y);
        },

        /*  rejectPromise  */                            /*  [Promises/A+ 2.3.3.3.2]  */
        function (r) {
          if (resolved) return; resolved = true;       /*  [Promises/A+ 2.3.3.3.3]  */
          promise.reject(r);
        }
      );
    }
    catch (e) {
      if (!resolved)                                       /*  [Promises/A+ 2.3.3.3.3]  */
        promise.reject(e);                               /*  [Promises/A+ 2.3.3.3.4]  */
    }
    return;
  }

  /*  handle other values  */
  promise.fulfill(x);                                          /*  [Promises/A+ 2.3.4, 2.3.3.4]  */
};

// use native promises where possible
var Promise = typeof Promise === 'undefined' ? api : Promise;

// so we always have Promise.all()
Promise.all = Promise.all || function( ps ){
  return new Promise(function( resolveAll, rejectAll ){
    var vals = new Array( ps.length );
    var doneCount = 0;

    var fulfill = function( i, val ){
      vals[i] = val;
      doneCount++;

      if( doneCount === ps.length ){
        resolveAll( vals );
      }
    };

    for( var i = 0; i < ps.length; i++ ){
      (function( i ){
        var p = ps[i];
        var isPromise = p.then != null;

        if( isPromise ){
          p.then(function( val ){
            fulfill( i, val );
          }, function( err ){
            rejectAll( err );
          });
        } else {
          var val = p;
          fulfill( i, val );
        }
      })( i );
    }

  });
};

module.exports = Promise;

},{}],81:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('./is');
var util = _dereq_('./util');

var Selector = function( onlyThisGroup, selector ){

  if( !(this instanceof Selector) ){
    return new Selector(onlyThisGroup, selector);
  }

  if( selector === undefined && onlyThisGroup !== undefined ){
    selector = onlyThisGroup;
    onlyThisGroup = undefined;
  }

  var self = this;

  self._private = {
    selectorText: null,
    invalid: true
  };

  if( !selector || ( is.string(selector) && selector.match(/^\s*$/) ) ){

    if( onlyThisGroup == null ){
      // ignore
      self.length = 0;
    } else {
      self[0] = newQuery();
      self[0].group = onlyThisGroup;
      self.length = 1;
    }

  } else if( is.elementOrCollection( selector ) ){
    var collection = selector.collection();

    self[0] = newQuery();
    self[0].collection = collection;
    self.length = 1;

  } else if( is.fn( selector ) ) {
    self[0] = newQuery();
    self[0].filter = selector;
    self.length = 1;

  } else if( is.string( selector ) ){

    // the current subject in the query
    var currentSubject = null;

    // storage for parsed queries
    var newQuery = function(){
      return {
        classes: [],
        colonSelectors: [],
        data: [],
        group: null,
        ids: [],
        meta: [],

        // fake selectors
        collection: null, // a collection to match against
        filter: null, // filter function

        // these are defined in the upward direction rather than down (e.g. child)
        // because we need to go up in Selector.filter()
        parent: null, // parent query obj
        ancestor: null, // ancestor query obj
        subject: null, // defines subject in compound query (subject query obj; points to self if subject)

        // use these only when subject has been defined
        child: null,
        descendant: null
      };
    };

    // tokens in the query language
    var tokens = {
      metaChar: '[\\!\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]', // chars we need to escape in var names, etc
      comparatorOp: '=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=', // binary comparison op (used in data selectors)
      boolOp: '\\?|\\!|\\^', // boolean (unary) operators (used in data selectors)
      string: '"(?:\\\\"|[^"])+"' + '|' + "'(?:\\\\'|[^'])+'", // string literals (used in data selectors) -- doublequotes | singlequotes
      number: util.regex.number, // number literal (used in data selectors) --- e.g. 0.1234, 1234, 12e123
      meta: 'degree|indegree|outdegree', // allowed metadata fields (i.e. allowed functions to use from Collection)
      separator: '\\s*,\\s*', // queries are separated by commas, e.g. edge[foo = 'bar'], node.someClass
      descendant: '\\s+',
      child: '\\s+>\\s+',
      subject: '\\$'
    };
    tokens.variable = '(?:[\\w-]|(?:\\\\'+ tokens.metaChar +'))+'; // a variable name
    tokens.value = tokens.string + '|' + tokens.number; // a value literal, either a string or number
    tokens.className = tokens.variable; // a class name (follows variable conventions)
    tokens.id = tokens.variable; // an element id (follows variable conventions)

    // when a token like a variable has escaped meta characters, we need to clean the backslashes out
    // so that values get compared properly in Selector.filter()
    var cleanMetaChars = function(str){
      return str.replace(new RegExp('\\\\(' + tokens.metaChar + ')', 'g'), function(match, $1, offset, original){
        return $1;
      });
    };

    // add @ variants to comparatorOp
    var ops = tokens.comparatorOp.split('|');
    for( var i = 0; i < ops.length; i++ ){
      var op = ops[i];
      tokens.comparatorOp += '|@' + op;
    }

    // add ! variants to comparatorOp
    var ops = tokens.comparatorOp.split('|');
    for( var i = 0; i < ops.length; i++ ){
      var op = ops[i];

      if( op.indexOf('!') >= 0 ){ continue; } // skip ops that explicitly contain !
      if( op === '=' ){ continue; } // skip = b/c != is explicitly defined

      tokens.comparatorOp += '|\\!' + op;
    }

    // NOTE: add new expression syntax here to have it recognised by the parser;
    // - a query contains all adjacent (i.e. no separator in between) expressions;
    // - the current query is stored in self[i] --- you can use the reference to `this` in the populate function;
    // - you need to check the query objects in Selector.filter() for it actually filter properly, but that's pretty straight forward
    // - when you add something here, also add to Selector.toString()
    var exprs = [
      {
        name: 'group',
        query: true,
        regex: '(node|edge|\\*)',
        populate: function( group ){
          this.group = group == "*" ? group : group + 's';
        }
      },

      {
        name: 'state',
        query: true,
        // NB: if one colon selector is a substring of another from its start, place the longer one first
        // e.g. :foobar|:foo
        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)',
        populate: function( state ){
          this.colonSelectors.push( state );
        }
      },

      {
        name: 'id',
        query: true,
        regex: '\\#('+ tokens.id +')',
        populate: function( id ){
          this.ids.push( cleanMetaChars(id) );
        }
      },

      {
        name: 'className',
        query: true,
        regex: '\\.('+ tokens.className +')',
        populate: function( className ){
          this.classes.push( cleanMetaChars(className) );
        }
      },

      {
        name: 'dataExists',
        query: true,
        regex: '\\[\\s*('+ tokens.variable +')\\s*\\]',
        populate: function( variable ){
          this.data.push({
            field: cleanMetaChars(variable)
          });
        }
      },

      {
        name: 'dataCompare',
        query: true,
        regex: '\\[\\s*('+ tokens.variable +')\\s*('+ tokens.comparatorOp +')\\s*('+ tokens.value +')\\s*\\]',
        populate: function( variable, comparatorOp, value ){
          var valueIsString = new RegExp('^' + tokens.string + '$').exec(value) != null;

          if( valueIsString ){
            value = value.substring(1, value.length - 1);
          } else {
            value = parseFloat(value);
          }

          this.data.push({
            field: cleanMetaChars(variable),
            operator: comparatorOp,
            value: value
          });
        }
      },

      {
        name: 'dataBool',
        query: true,
        regex: '\\[\\s*('+ tokens.boolOp +')\\s*('+ tokens.variable +')\\s*\\]',
        populate: function( boolOp, variable ){
          this.data.push({
            field: cleanMetaChars(variable),
            operator: boolOp
          });
        }
      },

      {
        name: 'metaCompare',
        query: true,
        regex: '\\[\\[\\s*('+ tokens.meta +')\\s*('+ tokens.comparatorOp +')\\s*('+ tokens.number +')\\s*\\]\\]',
        populate: function( meta, comparatorOp, number ){
          this.meta.push({
            field: cleanMetaChars(meta),
            operator: comparatorOp,
            value: parseFloat(number)
          });
        }
      },

      {
        name: 'nextQuery',
        separator: true,
        regex: tokens.separator,
        populate: function(){
          // go on to next query
          self[++i] = newQuery();
          currentSubject = null;
        }
      },

      {
        name: 'child',
        separator: true,
        regex: tokens.child,
        populate: function(){
          // this query is the parent of the following query
          var childQuery = newQuery();
          childQuery.parent = this;
          childQuery.subject = currentSubject;

          // we're now populating the child query with expressions that follow
          self[i] = childQuery;
        }
      },

      {
        name: 'descendant',
        separator: true,
        regex: tokens.descendant,
        populate: function(){
          // this query is the ancestor of the following query
          var descendantQuery = newQuery();
          descendantQuery.ancestor = this;
          descendantQuery.subject = currentSubject;

          // we're now populating the descendant query with expressions that follow
          self[i] = descendantQuery;
        }
      },

      {
        name: 'subject',
        modifier: true,
        regex: tokens.subject,
        populate: function(){
          if( currentSubject != null && this.subject != this ){
            util.error('Redefinition of subject in selector `' + selector + '`');
            return false;
          }

          currentSubject = this;
          this.subject = this;
        }

      }
    ];

    self._private.selectorText = selector;
    var remaining = selector;
    var i = 0;

    // of all the expressions, find the first match in the remaining text
    var consumeExpr = function( expectation ){
      var expr;
      var match;
      var name;

      for( var j = 0; j < exprs.length; j++ ){
        var e = exprs[j];
        var n = e.name;

        // ignore this expression if it doesn't meet the expectation function
        if( is.fn( expectation ) && !expectation(n, e) ){ continue; }

        var m = remaining.match(new RegExp( '^' + e.regex ));

        if( m != null ){
          match = m;
          expr = e;
          name = n;

          var consumed = m[0];
          remaining = remaining.substring( consumed.length );

          break; // we've consumed one expr, so we can return now
        }
      }

      return {
        expr: expr,
        match: match,
        name: name
      };
    };

    // consume all leading whitespace
    var consumeWhitespace = function(){
      var match = remaining.match(/^\s+/);

      if( match ){
        var consumed = match[0];
        remaining = remaining.substring( consumed.length );
      }
    };

    self[0] = newQuery(); // get started

    consumeWhitespace(); // get rid of leading whitespace
    for(;;){
      var check = consumeExpr();

      if( check.expr == null ){
        util.error('The selector `'+ selector +'`is invalid');
        return;
      } else {
        var args = [];
        for(var j = 1; j < check.match.length; j++){
          args.push( check.match[j] );
        }

        // let the token populate the selector object (i.e. in self[i])
        var ret = check.expr.populate.apply( self[i], args );

        if( ret === false ){ return; } // exit if population failed
      }

      // we're done when there's nothing left to parse
      if( remaining.match(/^\s*$/) ){
        break;
      }
    }

    self.length = i + 1;

    // adjust references for subject
    for(var j = 0; j < self.length; j++){
      var query = self[j];

      if( query.subject != null ){
        // go up the tree until we reach the subject
        for(;;){
          if( query.subject == query ){ break; } // done if subject is self

          if( query.parent != null ){ // swap parent/child reference
            var parent = query.parent;
            var child = query;

            child.parent = null;
            parent.child = child;

            query = parent; // go up the tree
          } else if( query.ancestor != null ){ // swap ancestor/descendant
            var ancestor = query.ancestor;
            var descendant = query;

            descendant.ancestor = null;
            ancestor.descendant = descendant;

            query = ancestor; // go up the tree
          } else {
            util.error('When adjusting references for the selector `'+ query +'`, neither parent nor ancestor was found');
            break;
          }
        } // for

        self[j] = query.subject; // subject should be the root query
      } // if
    } // for

    // make sure for each query that the subject group matches the implicit group if any
    if( onlyThisGroup != null ){
      for(var j = 0; j < self.length; j++){
        if( self[j].group != null && self[j].group != onlyThisGroup ){
          util.error('Group `'+ self[j].group +'` conflicts with implicit group `'+ onlyThisGroup +'` in selector `'+ selector +'`');
          return;
        }

        self[j].group = onlyThisGroup; // set to implicit group
      }
    }

  } else {
    util.error('A selector must be created from a string; found ' + selector);
    return;
  }

  self._private.invalid = false;

};

var selfn = Selector.prototype;

selfn.size = function(){
  return this.length;
};

selfn.eq = function(i){
  return this[i];
};

var queryMatches = function(query, element){
  // check group
  if( query.group != null && query.group != '*' && query.group != element._private.group ){
    return false;
  }

  var cy = element.cy();

  // check colon selectors
  var allColonSelectorsMatch = true;
  for(var k = 0; k < query.colonSelectors.length; k++){
    var sel = query.colonSelectors[k];

    switch(sel){
    case ':selected':
      allColonSelectorsMatch = element.selected();
      break;
    case ':unselected':
      allColonSelectorsMatch = !element.selected();
      break;
    case ':selectable':
      allColonSelectorsMatch = element.selectable();
      break;
    case ':unselectable':
      allColonSelectorsMatch = !element.selectable();
      break;
    case ':locked':
      allColonSelectorsMatch = element.locked();
      break;
    case ':unlocked':
      allColonSelectorsMatch = !element.locked();
      break;
    case ':visible':
      allColonSelectorsMatch = element.visible();
      break;
    case ':hidden':
      allColonSelectorsMatch = !element.visible();
      break;
    case ':transparent':
      allColonSelectorsMatch = element.transparent();
      break;
    case ':grabbed':
      allColonSelectorsMatch = element.grabbed();
      break;
    case ':free':
      allColonSelectorsMatch = !element.grabbed();
      break;
    case ':removed':
      allColonSelectorsMatch = element.removed();
      break;
    case ':inside':
      allColonSelectorsMatch = !element.removed();
      break;
    case ':grabbable':
      allColonSelectorsMatch = element.grabbable();
      break;
    case ':ungrabbable':
      allColonSelectorsMatch = !element.grabbable();
      break;
    case ':animated':
      allColonSelectorsMatch = element.animated();
      break;
    case ':unanimated':
      allColonSelectorsMatch = !element.animated();
      break;
    case ':parent':
      allColonSelectorsMatch = element.isNode() && element.children().nonempty();
      break;
    case ':child':
    case ':nonorphan':
      allColonSelectorsMatch = element.isNode() && element.parent().nonempty();
      break;
    case ':orphan':
      allColonSelectorsMatch = element.isNode() && element.parent().empty();
      break;
    case ':loop':
      allColonSelectorsMatch = element.isEdge() && element.data('source') === element.data('target');
      break;
    case ':simple':
      allColonSelectorsMatch = element.isEdge() && element.data('source') !== element.data('target');
      break;
    case ':active':
      allColonSelectorsMatch = element.active();
      break;
    case ':inactive':
      allColonSelectorsMatch = !element.active();
      break;
    case ':touch':
      allColonSelectorsMatch = is.touch();
      break;
    case ':backgrounding':
      allColonSelectorsMatch = element.backgrounding();
      break;
    case ':nonbackgrounding':
      allColonSelectorsMatch = !element.backgrounding();
      break;
    }

    if( !allColonSelectorsMatch ) break;
  }
  if( !allColonSelectorsMatch ) return false;

  // check id
  var allIdsMatch = true;
  for(var k = 0; k < query.ids.length; k++){
    var id = query.ids[k];
    var actualId = element._private.data.id;

    allIdsMatch = allIdsMatch && (id == actualId);

    if( !allIdsMatch ) break;
  }
  if( !allIdsMatch ) return false;

  // check classes
  var allClassesMatch = true;
  for(var k = 0; k < query.classes.length; k++){
    var cls = query.classes[k];

    allClassesMatch = allClassesMatch && element.hasClass(cls);

    if( !allClassesMatch ) break;
  }
  if( !allClassesMatch ) return false;

  // generic checking for data/metadata
  var operandsMatch = function(params){
    var allDataMatches = true;
    for(var k = 0; k < query[params.name].length; k++){
      var data = query[params.name][k];
      var operator = data.operator;
      var value = data.value;
      var field = data.field;
      var matches;

      if( operator != null && value != null ){

        var fieldVal = params.fieldValue(field);
        var fieldStr = !is.string(fieldVal) && !is.number(fieldVal) ? '' : '' + fieldVal;
        var valStr = '' + value;

        var caseInsensitive = false;
        if( operator.indexOf('@') >= 0 ){
          fieldStr = fieldStr.toLowerCase();
          valStr = valStr.toLowerCase();

          operator = operator.replace('@', '');
          caseInsensitive = true;
        }

        var notExpr = false;
        var handledNotExpr = false;
        if( operator.indexOf('!') >= 0 ){
          operator = operator.replace('!', '');
          notExpr = true;
        }

        // if we're doing a case insensitive comparison, then we're using a STRING comparison
        // even if we're comparing numbers
        if( caseInsensitive ){
          value = valStr.toLowerCase();
          fieldVal = fieldStr.toLowerCase();
        }

        switch(operator){
        case '*=':
          matches = fieldStr.search(valStr) >= 0;
          break;
        case '$=':
          matches = new RegExp(valStr + '$').exec(fieldStr) != null;
          break;
        case '^=':
          matches = new RegExp('^' + valStr).exec(fieldStr) != null;
          break;
        case '=':
          matches = fieldVal === value;
          break;
        case '!=':
          matches = fieldVal !== value;
          break;
        case '>':
          matches = !notExpr ? fieldVal > value : fieldVal <= value;
          handledNotExpr = true;
          break;
        case '>=':
          matches = !notExpr ? fieldVal >= value : fieldVal < value;
          handledNotExpr = true;
          break;
        case '<':
          matches = !notExpr ? fieldVal < value : fieldVal >= value;
          handledNotExpr = true;
          break;
        case '<=':
          matches = !notExpr ? fieldVal <= value : fieldVal > value;
          handledNotExpr = true;
          break;
        default:
          matches = false;
          break;

        }
      } else if( operator != null ){
        switch(operator){
        case '?':
          matches = params.fieldTruthy(field);
          break;
        case '!':
          matches = !params.fieldTruthy(field);
          break;
        case '^':
          matches = params.fieldUndefined(field);
          break;
        }
      } else {
        matches = !params.fieldUndefined(field);
      }

      if( notExpr && !handledNotExpr ){
        matches = !matches;
        handledNotExpr = true;
      }

      if( !matches ){
        allDataMatches = false;
        break;
      }
    } // for

    return allDataMatches;
  }; // operandsMatch

  // check data matches
  var allDataMatches = operandsMatch({
    name: 'data',
    fieldValue: function(field){
      return element._private.data[field];
    },
    fieldRef: function(field){
      return 'element._private.data.' + field;
    },
    fieldUndefined: function(field){
      return element._private.data[field] === undefined;
    },
    fieldTruthy: function(field){
      if( element._private.data[field] ){
        return true;
      }
      return false;
    }
  });

  if( !allDataMatches ){
    return false;
  }

  // check metadata matches
  var allMetaMatches = operandsMatch({
    name: 'meta',
    fieldValue: function(field){
      return element[field]();
    },
    fieldRef: function(field){
      return 'element.' + field + '()';
    },
    fieldUndefined: function(field){
      return element[field]() == null;
    },
    fieldTruthy: function(field){
      if( element[field]() ){
        return true;
      }
      return false;
    }
  });

  if( !allMetaMatches ){
    return false;
  }

  // check collection
  if( query.collection != null ){
    var matchesAny = query.collection._private.ids[ element.id() ] != null;

    if( !matchesAny ){
      return false;
    }
  }

  // check filter function
  if( query.filter != null && element.collection().filter( query.filter ).size() === 0 ){
    return false;
  }


  // check parent/child relations
  var confirmRelations = function( query, elements ){
    if( query != null ){
      var matches = false;

      if( !cy.hasCompoundNodes() ){
        return false;
      }

      elements = elements(); // make elements functional so we save cycles if query == null

      // query must match for at least one element (may be recursive)
      for(var i = 0; i < elements.length; i++){
        if( queryMatches( query, elements[i] ) ){
          matches = true;
          break;
        }
      }

      return matches;
    } else {
      return true;
    }
  };

  if (! confirmRelations(query.parent, function(){
    return element.parent();
  }) ){ return false; }

  if (! confirmRelations(query.ancestor, function(){
    return element.parents();
  }) ){ return false; }

  if (! confirmRelations(query.child, function(){
    return element.children();
  }) ){ return false; }

  if (! confirmRelations(query.descendant, function(){
    return element.descendants();
  }) ){ return false; }

  // we've reached the end, so we've matched everything for this query
  return true;
}; // queryMatches

// filter an existing collection
selfn.filter = function(collection){
  var self = this;
  var cy = collection.cy();

  // don't bother trying if it's invalid
  if( self._private.invalid ){
    return cy.collection();
  }

  var selectorFunction = function(i, element){
    for(var j = 0; j < self.length; j++){
      var query = self[j];

      if( queryMatches(query, element) ){
        return true;
      }
    }

    return false;
  };

  if( self._private.selectorText == null ){
    selectorFunction = function(){ return true; };
  }

  var filteredCollection = collection.filter( selectorFunction );

  return filteredCollection;
}; // filter

// does selector match a single element?
selfn.matches = function(ele){
  var self = this;

  // don't bother trying if it's invalid
  if( self._private.invalid ){
    return false;
  }

  for(var j = 0; j < self.length; j++){
    var query = self[j];

    if( queryMatches(query, ele) ){
      return true;
    }
  }

  return false;
}; // filter

// ith query to string
selfn.toString = selfn.selector = function(){

  var str = '';

  var clean = function(obj, isValue){
    if( is.string(obj) ){
      return isValue ? '"' + obj + '"' : obj;
    }
    return '';
  };

  var queryToString = function(query){
    var str = '';

    if( query.subject === query ){
      str += '$';
    }

    var group = clean(query.group);
    str += group.substring(0, group.length - 1);

    for(var j = 0; j < query.data.length; j++){
      var data = query.data[j];

      if( data.value ){
        str += '[' + data.field + clean(data.operator) + clean(data.value, true) + ']';
      } else {
        str += '[' + clean(data.operator) + data.field + ']';
      }
    }

    for(var j = 0; j < query.meta.length; j++){
      var meta = query.meta[j];
      str += '[[' + meta.field + clean(meta.operator) + clean(meta.value, true) + ']]';
    }

    for(var j = 0; j < query.colonSelectors.length; j++){
      var sel = query.colonSelectors[i];
      str += sel;
    }

    for(var j = 0; j < query.ids.length; j++){
      var sel = '#' + query.ids[i];
      str += sel;
    }

    for(var j = 0; j < query.classes.length; j++){
      var sel = '.' + query.classes[j];
      str += sel;
    }

    if( query.parent != null ){
      str = queryToString( query.parent ) + ' > ' + str;
    }

    if( query.ancestor != null ){
      str = queryToString( query.ancestor ) + ' ' + str;
    }

    if( query.child != null ){
      str += ' > ' + queryToString( query.child );
    }

    if( query.descendant != null ){
      str += ' ' + queryToString( query.descendant );
    }

    return str;
  };

  for(var i = 0; i < this.length; i++){
    var query = this[i];

    str += queryToString( query );

    if( this.length > 1 && i < this.length - 1 ){
      str += ', ';
    }
  }

  return str;
};

module.exports = Selector;

},{"./is":77,"./util":94}],82:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');
var is = _dereq_('../is');

var styfn = {};

// (potentially expensive calculation)
// apply the style to the element based on
// - its bypass
// - what selectors match it
styfn.apply = function( eles ){
  var self = this;

  if( self._private.newStyle ){ // clear style caches
    this._private.contextStyles = {};
    this._private.propDiffs = {};
  }

  for( var ie = 0; ie < eles.length; ie++ ){
    var ele = eles[ie];
    var cxtMeta = self.getContextMeta( ele );
    var cxtStyle = self.getContextStyle( cxtMeta );
    var app = self.applyContextStyle( cxtMeta, cxtStyle, ele );

    self.updateTransitions( ele, app.diffProps );
    self.updateStyleHints( ele );

  } // for elements

  self._private.newStyle = false;
};

styfn.getPropertiesDiff = function( oldCxtKey, newCxtKey ){
  var self = this;
  var cache = self._private.propDiffs = self._private.propDiffs || {};
  var dualCxtKey = oldCxtKey + '-' + newCxtKey;
  var cachedVal = cache[dualCxtKey];

  if( cachedVal ){
    return cachedVal;
  }

  var diffProps = [];
  var addedProp = {};

  for( var i = 0; i < self.length; i++ ){
    var cxt = self[i];
    var oldHasCxt = oldCxtKey[i] === 't';
    var newHasCxt = newCxtKey[i] === 't';
    var cxtHasDiffed = oldHasCxt !== newHasCxt;
    var cxtHasMappedProps = cxt.mappedProperties.length > 0;

    if( cxtHasDiffed || cxtHasMappedProps ){
      var props;

      if( cxtHasDiffed && cxtHasMappedProps ){
        props = cxt.properties; // suffices b/c mappedProperties is a subset of properties
      } else if( cxtHasDiffed ){
        props = cxt.properties; // need to check them all
      } else if( cxtHasMappedProps ){
        props = cxt.mappedProperties; // only need to check mapped
      }

      for( var j = 0; j < props.length; j++ ){
        var prop = props[j];
        var name = prop.name;

        // if a later context overrides this property, then the fact that this context has switched/diffed doesn't matter
        // (semi expensive check since it makes this function O(n^2) on context length, but worth it since overall result
        // is cached)
        var laterCxtOverrides = false;
        for( var k = i + 1; k < self.length; k++ ){
          var laterCxt = self[k];
          var hasLaterCxt = newCxtKey[k] === 't';

          if( !hasLaterCxt ){ continue; } // can't override unless the context is active

          laterCxtOverrides = laterCxt.properties[ prop.name ] != null;

          if( laterCxtOverrides ){ break; } // exit early as long as one later context overrides
        }

        if( !addedProp[name] && !laterCxtOverrides ){
          addedProp[name] = true;
          diffProps.push( name );
        }
      } // for props
    } // if

  } // for contexts

  cache[ dualCxtKey ] = diffProps;
  return diffProps;
};

styfn.getContextMeta = function( ele ){
  var self = this;
  var cxtKey = '';
  var diffProps;
  var prevKey = ele._private.styleCxtKey || '';

  if( self._private.newStyle ){
    prevKey = ''; // since we need to apply all style if a fresh stylesheet
  }

  // get the cxt key
  for( var i = 0; i < self.length; i++ ){
    var context = self[i];
    var contextSelectorMatches = context.selector && context.selector.matches( ele ); // NB: context.selector may be null for 'core'

    if( contextSelectorMatches ){
      cxtKey += 't';
    } else {
      cxtKey += 'f';
    }
  } // for context

  diffProps = self.getPropertiesDiff( prevKey, cxtKey );

  ele._private.styleCxtKey = cxtKey;

  return {
    key: cxtKey,
    diffPropNames: diffProps
  };
};

// gets a computed ele style object based on matched contexts
styfn.getContextStyle = function( cxtMeta ){
  var cxtKey = cxtMeta.key;
  var self = this;
  var cxtStyles = this._private.contextStyles = this._private.contextStyles || {};

  // if already computed style, returned cached copy
  if( cxtStyles[cxtKey] ){ return cxtStyles[cxtKey]; }

  var style = {
    _private: {
      key: cxtKey
    }
  };

  for( var i = 0; i < self.length; i++ ){
    var cxt = self[i];
    var hasCxt = cxtKey[i] === 't';

    if( !hasCxt ){ continue; }

    for( var j = 0; j < cxt.properties.length; j++ ){
      var prop = cxt.properties[j];
      var styProp = style[ prop.name ] = prop;

      styProp.context = cxt;
    }
  }

  cxtStyles[cxtKey] = style;
  return style;
};

styfn.applyContextStyle = function( cxtMeta, cxtStyle, ele ){
  var self = this;
  var diffProps = cxtMeta.diffPropNames;
  var retDiffProps = {};

  for( var i = 0; i < diffProps.length; i++ ){
    var diffPropName = diffProps[i];
    var cxtProp = cxtStyle[ diffPropName ];
    var eleProp = ele._private.style[ diffPropName ];

    // save cycles when the context prop doesn't need to be applied
    if( !cxtProp || eleProp === cxtProp ){ continue; }

    var retDiffProp = retDiffProps[ diffPropName ] = {
      prev: eleProp
    };

    self.applyParsedProperty( ele, cxtProp );

    retDiffProp.next = ele._private.style[ diffPropName ];

    if( retDiffProp.next && retDiffProp.next.bypass ){
      retDiffProp.next = retDiffProp.next.bypassed;
    }
  }

  return {
    diffProps: retDiffProps
  };
};

styfn.updateStyleHints = function(ele){
  var _p = ele._private;
  var self = this;
  var style = _p.style;

  if( ele.removed() ){ return; }

  // set whether has pie or not; for greater efficiency
  var hasPie = false;
  if( _p.group === 'nodes' && self._private.hasPie ){
    for( var i = 1; i <= self.pieBackgroundN; i++ ){ // 1..N
      var size = _p.style['pie-' + i + '-background-size'].value;

      if( size > 0 ){
        hasPie = true;
        break;
      }
    }
  }

  _p.hasPie = hasPie;

  var transform = style['text-transform'].strValue;
  var content = style['label'].strValue;
  var fStyle = style['font-style'].strValue;
  var size = style['font-size'].pfValue + 'px';
  var family = style['font-family'].strValue;
  // var variant = style['font-variant'].strValue;
  var weight = style['font-weight'].strValue;
  var valign = style['text-valign'].strValue;
  var halign = style['text-valign'].strValue;
  var oWidth = style['text-outline-width'].pfValue;
  var wrap = style['text-wrap'].strValue;
  var wrapW = style['text-max-width'].pfValue;
  _p.labelKey = fStyle +'$'+ size +'$'+ family +'$'+ weight +'$'+ content +'$'+ transform +'$'+ valign +'$'+ halign +'$'+ oWidth + '$' + wrap + '$' + wrapW;
  _p.fontKey = fStyle +'$'+ weight +'$'+ size +'$'+ family;

  var width = style['width'].pfValue;
  var height = style['height'].pfValue;
  var borderW = style['border-width'].pfValue;
  _p.boundingBoxKey = width +'$'+ height +'$'+ borderW;

  if( ele._private.group === 'edges' ){
    var cpss = style['control-point-step-size'].pfValue;
    var cpd = style['control-point-distances'] ? style['control-point-distances'].pfValue.join('_') : undefined;
    var cpw = style['control-point-weights'].value.join('_');
    var curve = style['curve-style'].strValue;
    var sd = style['segment-distances'] ? style['segment-distances'].pfValue.join('_') : undefined;
    var sw = style['segment-weights'].value.join('_');

    _p.boundingBoxKey += '$'+ cpss +'$'+ cpd +'$'+ cpw +'$'+ sd +'$'+ sw +'$'+ curve;
  }

  _p.styleKey = Date.now();
};

// apply a property to the style (for internal use)
// returns whether application was successful
//
// now, this function flattens the property, and here's how:
//
// for parsedProp:{ bypass: true, deleteBypass: true }
// no property is generated, instead the bypass property in the
// element's style is replaced by what's pointed to by the `bypassed`
// field in the bypass property (i.e. restoring the property the
// bypass was overriding)
//
// for parsedProp:{ mapped: truthy }
// the generated flattenedProp:{ mapping: prop }
//
// for parsedProp:{ bypass: true }
// the generated flattenedProp:{ bypassed: parsedProp }
styfn.applyParsedProperty = function( ele, parsedProp ){
  var self = this;
  var prop = parsedProp;
  var style = ele._private.style;
  var fieldVal, flatProp;
  var types = self.types;
  var type = self.properties[ prop.name ].type;
  var propIsBypass = prop.bypass;
  var origProp = style[ prop.name ];
  var origPropIsBypass = origProp && origProp.bypass;
  var _p = ele._private;

  // can't apply auto to width or height unless it's a parent node
  if( (parsedProp.name === 'height' || parsedProp.name === 'width') && ele.isNode() ){
    if( parsedProp.value === 'auto' && !ele.isParent() ){
      return false;
    } else if( parsedProp.value !== 'auto' && ele.isParent() ){
      prop = parsedProp = this.parse( parsedProp.name, 'auto', propIsBypass );
    }
  }

  // check if we need to delete the current bypass
  if( propIsBypass && prop.deleteBypass ){ // then this property is just here to indicate we need to delete
    var currentProp = style[ prop.name ];

    // can only delete if the current prop is a bypass and it points to the property it was overriding
    if( !currentProp ){
      return true; // property is already not defined
    } else if( currentProp.bypass && currentProp.bypassed ){ // then replace the bypass property with the original

      // because the bypassed property was already applied (and therefore parsed), we can just replace it (no reapplying necessary)
      style[ prop.name ] = currentProp.bypassed;
      return true;

    } else {
      return false; // we're unsuccessful deleting the bypass
    }
  }

  var printMappingErr = function(){
    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');
  };

  // put the property in the style objects
  switch( prop.mapped ){ // flatten the property if mapped
  case types.mapData:
  case types.mapLayoutData:
  case types.mapScratch:

    var isLayout = prop.mapped === types.mapLayoutData;
    var isScratch = prop.mapped === types.mapScratch;

    // flatten the field (e.g. data.foo.bar)
    var fields = prop.field.split(".");
    var fieldVal;

    if( isScratch || isLayout ){
      fieldVal = _p.scratch;
    } else {
      fieldVal = _p.data;
    }

    for( var i = 0; i < fields.length && fieldVal; i++ ){
      var field = fields[i];
      fieldVal = fieldVal[ field ];
    }

    var percent;
    if( !is.number(fieldVal) ){ // then keep the mapping but assume 0% for now
      percent = 0;
    } else {
      percent = (fieldVal - prop.fieldMin) / (prop.fieldMax - prop.fieldMin);
    }

    // make sure to bound percent value
    if( percent < 0 ){
      percent = 0;
    } else if( percent > 1 ){
      percent = 1;
    }

    if( type.color ){
      var r1 = prop.valueMin[0];
      var r2 = prop.valueMax[0];
      var g1 = prop.valueMin[1];
      var g2 = prop.valueMax[1];
      var b1 = prop.valueMin[2];
      var b2 = prop.valueMax[2];
      var a1 = prop.valueMin[3] == null ? 1 : prop.valueMin[3];
      var a2 = prop.valueMax[3] == null ? 1 : prop.valueMax[3];

      var clr = [
        Math.round( r1 + (r2 - r1)*percent ),
        Math.round( g1 + (g2 - g1)*percent ),
        Math.round( b1 + (b2 - b1)*percent ),
        Math.round( a1 + (a2 - a1)*percent )
      ];

      flatProp = { // colours are simple, so just create the flat property instead of expensive string parsing
        bypass: prop.bypass, // we're a bypass if the mapping property is a bypass
        name: prop.name,
        value: clr,
        strValue: 'rgb(' + clr[0] + ', ' + clr[1] + ', ' + clr[2] + ')'
      };

    } else if( type.number ){
      var calcValue = prop.valueMin + (prop.valueMax - prop.valueMin) * percent;
      flatProp = this.parse( prop.name, calcValue, prop.bypass, true );

    } else {
      return false; // can only map to colours and numbers
    }

    if( !flatProp ){ // if we can't flatten the property, then use the origProp so we still keep the mapping itself
      flatProp = this.parse( prop.name, origProp.strValue, prop.bypass, true );
    }

    if( !flatProp ){ printMappingErr(); }
    flatProp.mapping = prop; // keep a reference to the mapping
    prop = flatProp; // the flattened (mapped) property is the one we want

    break;

  // direct mapping
  case types.data:
  case types.layoutData:
  case types.scratch:
    var isLayout = prop.mapped === types.layoutData;
    var isScratch = prop.mapped === types.scratch;

    // flatten the field (e.g. data.foo.bar)
    var fields = prop.field.split(".");
    var fieldVal;

    if( isScratch || isLayout ){
      fieldVal = _p.scratch;
    } else {
      fieldVal = _p.data;
    }

    if( fieldVal ){ for( var i = 0; i < fields.length; i++ ){
      var field = fields[i];
      fieldVal = fieldVal[ field ];
    } }

    flatProp = this.parse( prop.name, fieldVal, prop.bypass, true );

    if( !flatProp ){ // if we can't flatten the property, then use the origProp so we still keep the mapping itself
      var flatPropVal = origProp ? origProp.strValue : '';

      flatProp = this.parse( prop.name, flatPropVal, prop.bypass, true );
    }

    if( !flatProp ){ printMappingErr(); }
    flatProp.mapping = prop; // keep a reference to the mapping
    prop = flatProp; // the flattened (mapped) property is the one we want

    break;

  case types.fn:
    var fn = prop.value;
    var fnRetVal = fn( ele );

    flatProp = this.parse( prop.name, fnRetVal, prop.bypass, true );
    flatProp.mapping = prop; // keep a reference to the mapping
    prop = flatProp; // the flattened (mapped) property is the one we want

    break;

  case undefined:
    break; // just set the property

  default:
    return false; // not a valid mapping
  }

  // if the property is a bypass property, then link the resultant property to the original one
  if( propIsBypass ){
    if( origPropIsBypass ){ // then this bypass overrides the existing one
      prop.bypassed = origProp.bypassed; // steal bypassed prop from old bypass
    } else { // then link the orig prop to the new bypass
      prop.bypassed = origProp;
    }

    style[ prop.name ] = prop; // and set

  } else { // prop is not bypass
    if( origPropIsBypass ){ // then keep the orig prop (since it's a bypass) and link to the new prop
      origProp.bypassed = prop;
    } else { // then just replace the old prop with the new one
      style[ prop.name ] = prop;
    }
  }

  return true;
};

// updates the visual style for all elements (useful for manual style modification after init)
styfn.update = function(){
  var cy = this._private.cy;
  var eles = cy.elements();

  eles.updateStyle();
};

// just update the functional properties (i.e. mappings) in the elements'
// styles (less expensive than recalculation)
styfn.updateMappers = function( eles ){
  var self = this;

  for( var i = 0; i < eles.length; i++ ){ // for each ele
    var ele = eles[i];
    var style = ele._private.style;

    for( var j = 0; j < self.properties.length; j++ ){ // for each prop
      var prop = self.properties[j];
      var propInStyle = style[ prop.name ];

      if( propInStyle && propInStyle.mapping ){
        var mapping = propInStyle.mapping;
        this.applyParsedProperty( ele, mapping ); // reapply the mapping property
      }
    }

    this.updateStyleHints( ele );
  }
};

// diffProps : { name => { prev, next } }
styfn.updateTransitions = function( ele, diffProps, isBypass ){
  var self = this;
  var _p = ele._private;
  var style = _p.style;
  var props = style['transition-property'].value;
  var duration = style['transition-duration'].pfValue;
  var delay = style['transition-delay'].pfValue;
  var css = {};

  if( props.length > 0 && duration > 0 ){

    // build up the style to animate towards
    var anyPrev = false;
    for( var i = 0; i < props.length; i++ ){
      var prop = props[i];
      var styProp = style[ prop ];
      var diffProp = diffProps[ prop ];

      if( !diffProp ){ continue; }

      var prevProp = diffProp.prev;
      var fromProp = prevProp;
      var toProp = diffProp.next != null ? diffProp.next : styProp;
      var diff = false;
      var initVal;
      var initDt = 0.000001; // delta time % value for initVal (allows animating out of init zero opacity)

      if( !fromProp ){ continue; }

      // consider px values
      if( is.number( fromProp.pfValue ) && is.number( toProp.pfValue ) ){
        diff = toProp.pfValue - fromProp.pfValue; // nonzero is truthy
        initVal = fromProp.pfValue + initDt * diff;

      // consider numerical values
      } else if( is.number( fromProp.value ) && is.number( toProp.value ) ){
        diff = toProp.value - fromProp.value; // nonzero is truthy
        initVal = fromProp.value + initDt * diff;

      // consider colour values
      } else if( is.array( fromProp.value ) && is.array( toProp.value ) ){
        diff = fromProp.value[0] !== toProp.value[0]
          || fromProp.value[1] !== toProp.value[1]
          || fromProp.value[2] !== toProp.value[2]
        ;

        initVal = fromProp.strValue;
      }

      // the previous value is good for an animation only if it's different
      if( diff ){
        css[ prop ] = toProp.strValue; // to val
        this.applyBypass( ele, prop, initVal ); // from val
        anyPrev = true;
      }

    } // end if props allow ani

    // can't transition if there's nothing previous to transition from
    if( !anyPrev ){ return; }

    _p.transitioning = true;

    ele.stop();

    if( delay > 0 ){
      ele.delay( delay );
    }

    ele.animate({
      css: css
    }, {
      duration: duration,
      easing: style['transition-timing-function'].value,
      queue: false,
      complete: function(){
        if( !isBypass ){
          self.removeBypasses( ele, props );
        }

        _p.transitioning = false;
      }
    });

  } else if( _p.transitioning ){
    ele.stop();

    this.removeBypasses( ele, props );

    _p.transitioning = false;
  }
};

module.exports = styfn;

},{"../is":77,"../util":94}],83:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');
var util = _dereq_('../util');

var styfn = {};

// bypasses are applied to an existing style on an element, and just tacked on temporarily
// returns true iff application was successful for at least 1 specified property
styfn.applyBypass = function( eles, name, value, updateTransitions ){
  var self = this;
  var props = [];
  var isBypass = true;

  // put all the properties (can specify one or many) in an array after parsing them
  if( name === "*" || name === "**" ){ // apply to all property names

    if( value !== undefined ){
      for( var i = 0; i < self.properties.length; i++ ){
        var prop = self.properties[i];
        var name = prop.name;

        var parsedProp = this.parse(name, value, true);

        if( parsedProp ){
          props.push( parsedProp );
        }
      }
    }

  } else if( is.string(name) ){ // then parse the single property
    var parsedProp = this.parse(name, value, true);

    if( parsedProp ){
      props.push( parsedProp );
    }
  } else if( is.plainObject(name) ){ // then parse each property
    var specifiedProps = name;
    updateTransitions = value;

    for( var i = 0; i < self.properties.length; i++ ){
      var prop = self.properties[i];
      var name = prop.name;
      var value = specifiedProps[ name ];

      if( value === undefined ){ // try camel case name too
        value = specifiedProps[ util.dash2camel(name) ];
      }

      if( value !== undefined ){
        var parsedProp = this.parse(name, value, true);

        if( parsedProp ){
          props.push( parsedProp );
        }
      }
    }
  } else { // can't do anything without well defined properties
    return false;
  }

  // we've failed if there are no valid properties
  if( props.length === 0 ){ return false; }

  // now, apply the bypass properties on the elements
  var ret = false; // return true if at least one succesful bypass applied
  for( var i = 0; i < eles.length; i++ ){ // for each ele
    var ele = eles[i];
    var style = ele._private.style;
    var diffProps = {};
    var diffProp;

    for( var j = 0; j < props.length; j++ ){ // for each prop
      var prop = props[j];

      if( updateTransitions ){
        var prevProp = style[ prop.name ];
        diffProp = diffProps[ prop.name ] = { prev: prevProp };
      }

      ret = this.applyParsedProperty( ele, prop ) || ret;

      if( updateTransitions ){
        diffProp.next = style[ prop.name ];
      }

    } // for props

    if( ret ){
      this.updateStyleHints( ele );
    }

    if( updateTransitions ){
      this.updateTransitions( ele, diffProps, isBypass );
    }
  } // for eles

  return ret;
};

// only useful in specific cases like animation
styfn.overrideBypass = function( eles, name, value ){
  name = util.camel2dash(name);

  for( var i = 0; i < eles.length; i++ ){
    var ele = eles[i];
    var prop = ele._private.style[ name ];
    var type = this.properties[ name ].type;
    var isColor = type.color;
    var isMulti = type.mutiple;

    if( !prop.bypass ){ // need a bypass if one doesn't exist
      this.applyBypass( ele, name, value );
      continue;
    }

    prop.value = value;

    if( prop.pfValue != null ){
      prop.pfValue = value;
    }

    if( isColor ){
      prop.strValue = 'rgb(' + value.join(',') + ')';
    } else if( isMulti ){
      prop.strValue = value.join(' ');
    } else {
      prop.strValue = '' + value;
    }
  }
};

styfn.removeAllBypasses = function( eles, updateTransitions ){
  return this.removeBypasses( eles, this.propertyNames, updateTransitions );
};

styfn.removeBypasses = function( eles, props, updateTransitions ){
  var isBypass = true;

  for( var j = 0; j < eles.length; j++ ){
    var ele = eles[j];
    var diffProps = {};
    var style = ele._private.style;

    for( var i = 0; i < props.length; i++ ){
      var name = props[i];
      var prop = this.properties[ name ];
      var value = ''; // empty => remove bypass
      var parsedProp = this.parse(name, value, true);
      var prevProp = style[ prop.name ];
      var diffProp = diffProps[ prop.name ] = { prev: prevProp };

      this.applyParsedProperty(ele, parsedProp);

      diffProp.next = style[ prop.name ];
    } // for props

    this.updateStyleHints( ele );

    if( updateTransitions ){
      this.updateTransitions( ele, diffProps, isBypass );
    }
  } // for eles
};

module.exports = styfn;

},{"../is":77,"../util":94}],84:[function(_dereq_,module,exports){
'use strict';

var window = _dereq_('../window');

var styfn = {};

// gets what an em size corresponds to in pixels relative to a dom element
styfn.getEmSizeInPixels = function(){
  var px = this.containerCss('font-size');

  if( px != null ){
    return parseFloat( px );
  } else {
    return 1; // for headless
  }
};

// gets css property from the core container
styfn.containerCss = function( propName ){
  var cy = this._private.cy;
  var domElement = cy.container();

  if( window && domElement && window.getComputedStyle ){
    return window.getComputedStyle(domElement).getPropertyValue( propName );
  }
};

module.exports = styfn;

},{"../window":100}],85:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');
var is = _dereq_('../is');

var styfn = {};

// gets the rendered style for an element
styfn.getRenderedStyle = function( ele ){
  return this.getRawStyle( ele, true );
};

// gets the raw style for an element
styfn.getRawStyle = function( ele, isRenderedVal ){
  var self = this;
  var ele = ele[0]; // insure it's an element

  if( ele ){
    var rstyle = {};

    for( var i = 0; i < self.properties.length; i++ ){
      var prop = self.properties[i];
      var val = self.getStylePropertyValue( ele, prop.name, isRenderedVal );

      if( val ){
        rstyle[ prop.name ] = val;
        rstyle[ util.dash2camel(prop.name) ] = val;
      }
    }

    return rstyle;
  }
};

styfn.getStylePropertyValue = function( ele, propName, isRenderedVal ){
  var self = this;
  var ele = ele[0]; // insure it's an element

  if( ele ){
    var style = ele._private.style;
    var prop = self.properties[ propName ];
    var type = prop.type;
    var styleProp = style[ prop.name ];
    var zoom = ele.cy().zoom();

    if( styleProp ){
      var units = styleProp.units ? type.implicitUnits || 'px' : null;
      var val = units ? [].concat( styleProp.pfValue ).map(function( pfValue ){
        return ( pfValue * (isRenderedVal ? zoom : 1) ) + units;
      }).join(' ') : styleProp.strValue;

      return val;
    }
  }
};

// gets the value style for an element (useful for things like animations)
styfn.getValueStyle = function( ele ){
  var self = this;
  var rstyle = {};
  var style;
  var isEle = is.element(ele);

  if( isEle ){
    style = ele._private.style;
  } else {
    style = ele; // just passed the style itself
  }

  if( style ){
    for( var i = 0; i < self.properties.length; i++ ){
      var prop = self.properties[i];
      var styleProp = style[ prop.name ] || style[ util.dash2camel(prop.name) ];

      if( styleProp !== undefined ){ // then make a prop of it
        if( is.plainObject( styleProp ) ){
          styleProp = this.parse( prop.name, styleProp.strValue );
        } else {
          styleProp = this.parse( prop.name, styleProp );
        }
      }

      if( styleProp ){
        rstyle[ prop.name ] = styleProp;
        rstyle[ util.dash2camel(prop.name) ] = styleProp;
      }
    }
  }

  return rstyle;
};

styfn.getPropsList = function( propsObj ){
  var self = this;
  var rstyle = [];
  var style = propsObj;
  var props = self.properties;

  if( style ){
    for( var name in style ){
      var val = style[name];
      var prop = props[name] || props[ util.camel2dash(name) ];
      var styleProp = this.parse( prop.name, val );

      rstyle.push( styleProp );
    }
  }

  return rstyle;
};

module.exports = styfn;

},{"../is":77,"../util":94}],86:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');
var util = _dereq_('../util');
var Selector = _dereq_('../selector');

var Style = function( cy ){

  if( !(this instanceof Style) ){
    return new Style(cy);
  }

  if( !is.core(cy) ){
    util.error('A style must have a core reference');
    return;
  }

  this._private = {
    cy: cy,
    coreStyle: {},
    newStyle: true
  };

  this.length = 0;

  this.addDefaultStylesheet();
};

var styfn = Style.prototype;

styfn.instanceString = function(){
  return 'style';
};

// remove all contexts
styfn.clear = function(){
  for( var i = 0; i < this.length; i++ ){
    this[i] = undefined;
  }
  this.length = 0;
  this._private.newStyle = true;

  return this; // chaining
};

styfn.resetToDefault = function(){
  this.clear();
  this.addDefaultStylesheet();

  return this;
};

// builds a style object for the 'core' selector
styfn.core = function(){
  return this._private.coreStyle;
};

// create a new context from the specified selector string and switch to that context
styfn.selector = function( selectorStr ){
  // 'core' is a special case and does not need a selector
  var selector = selectorStr === 'core' ? null : new Selector( selectorStr );

  var i = this.length++; // new context means new index
  this[i] = {
    selector: selector,
    properties: [],
    mappedProperties: [],
    index: i
  };

  return this; // chaining
};

// add one or many css rules to the current context
styfn.css = function(){
  var self = this;
  var args = arguments;

  switch( args.length ){
  case 1:
    var map = args[0];

    for( var i = 0; i < self.properties.length; i++ ){
      var prop = self.properties[i];
      var mapVal = map[ prop.name ];

      if( mapVal === undefined ){
        mapVal = map[ util.dash2camel(prop.name) ];
      }

      if( mapVal !== undefined ){
        this.cssRule( prop.name, mapVal );
      }
    }

    break;

  case 2:
    this.cssRule( args[0], args[1] );
    break;

  default:
    break; // do nothing if args are invalid
  }

  return this; // chaining
};
styfn.style = styfn.css;

// add a single css rule to the current context
styfn.cssRule = function( name, value ){
  // name-value pair
  var property = this.parse( name, value );

  // add property to current context if valid
  if( property ){
    var i = this.length - 1;
    this[i].properties.push( property );
    this[i].properties[ property.name ] = property; // allow access by name as well

    if( property.name.match(/pie-(\d+)-background-size/) && property.value ){
      this._private.hasPie = true;
    }

    if( property.mapped ){
      this[i].mappedProperties.push( property );
    }

    // add to core style if necessary
    var currentSelectorIsCore = !this[i].selector;
    if( currentSelectorIsCore ){
      this._private.coreStyle[ property.name ] = property;
    }
  }

  return this; // chaining
};

// static function
Style.fromJson = function( cy, json ){
  var style = new Style( cy );

  style.fromJson( json );

  return style;
};

Style.fromString = function( cy, string ){
  return new Style( cy ).fromString( string );
};

[
  _dereq_('./apply'),
  _dereq_('./bypass'),
  _dereq_('./container'),
  _dereq_('./get-for-ele'),
  _dereq_('./json'),
  _dereq_('./string-sheet'),
  _dereq_('./properties'),
  _dereq_('./parse')
].forEach(function( props ){
  util.extend( styfn, props );
});


Style.types = styfn.types;
Style.properties = styfn.properties;

module.exports = Style;

},{"../is":77,"../selector":81,"../util":94,"./apply":82,"./bypass":83,"./container":84,"./get-for-ele":85,"./json":87,"./parse":88,"./properties":89,"./string-sheet":90}],87:[function(_dereq_,module,exports){
'use strict';

var styfn = {};

styfn.applyFromJson = function( json ){
  var style = this;

  for( var i = 0; i < json.length; i++ ){
    var context = json[i];
    var selector = context.selector;
    var props = context.style || context.css;

    style.selector( selector ); // apply selector

    for( var name in props ){
      var value = props[name];

      style.css( name, value ); // apply property
    }
  }

  return style;
};

// accessible cy.style() function
styfn.fromJson = function( json ){
  var style = this;

  style.resetToDefault();
  style.applyFromJson( json );

  return style;
};

// get json from cy.style() api
styfn.json = function(){
  var json = [];

  for( var i = this.defaultLength; i < this.length; i++ ){
    var cxt = this[i];
    var selector = cxt.selector;
    var props = cxt.properties;
    var css = {};

    for( var j = 0; j < props.length; j++ ){
      var prop = props[j];
      css[ prop.name ] = prop.strValue;
    }

    json.push({
      selector: !selector ? 'core' : selector.toString(),
      style: css
    });
  }

  return json;
};

module.exports = styfn;

},{}],88:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');
var is = _dereq_('../is');

var styfn = {};

// a caching layer for property parsing
styfn.parse = function( name, value, propIsBypass, propIsFlat ){
  var argHash = [ name, value, propIsBypass, propIsFlat ].join('$');
  var propCache = this.propCache = this.propCache || {};
  var ret;
  var impl = parseImpl.bind( this );

  if( !(ret = propCache[argHash]) ){
    ret = propCache[argHash] = impl( name, value, propIsBypass, propIsFlat );
  }

  // always need a copy since props are mutated later in their lifecycles
  ret = util.copy( ret );

  if( ret ){
    ret.value = util.copy( ret.value ); // because it could be an array, e.g. colour
  }

  return ret;
};

// parse a property; return null on invalid; return parsed property otherwise
// fields :
// - name : the name of the property
// - value : the parsed, native-typed value of the property
// - strValue : a string value that represents the property value in valid css
// - bypass : true iff the property is a bypass property
var parseImpl = function( name, value, propIsBypass, propIsFlat ){
  var self = this;

  name = util.camel2dash( name ); // make sure the property name is in dash form (e.g. 'property-name' not 'propertyName')

  var property = self.properties[ name ];
  var passedValue = value;
  var types = self.types;

  if( !property ){ return null; } // return null on property of unknown name
  if( value === undefined || value === null ){ return null; } // can't assign null

  // the property may be an alias
  if( property.alias ){
    property = property.pointsTo;
    name = property.name;
  }

  var valueIsString = is.string(value);
  if( valueIsString ){ // trim the value to make parsing easier
    value = value.trim();
  }

  var type = property.type;
  if( !type ){ return null; } // no type, no luck

  // check if bypass is null or empty string (i.e. indication to delete bypass property)
  if( propIsBypass && (value === '' || value === null) ){
    return {
      name: name,
      value: value,
      bypass: true,
      deleteBypass: true
    };
  }

  // check if value is a function used as a mapper
  if( is.fn(value) ){
    return {
      name: name,
      value: value,
      strValue: 'fn',
      mapped: types.fn,
      bypass: propIsBypass
    };
  }

  // check if value is mapped
  var data, mapData, layoutData, mapLayoutData, scratch, mapScratch;
  if( !valueIsString || propIsFlat ){
    // then don't bother to do the expensive regex checks

  } else if(
    ( data = new RegExp( types.data.regex ).exec( value ) ) ||
    ( layoutData = new RegExp( types.layoutData.regex ).exec( value ) ) ||
    ( scratch = new RegExp( types.scratch.regex ).exec( value ) )
  ){
    if( propIsBypass ){ return false; } // mappers not allowed in bypass

    var mapped;
    if( data ){
      mapped = types.data;
    } else if( layoutData ){
      mapped = types.layoutData;
    } else {
      mapped = types.scratch;
    }

    data = data || layoutData || scratch;

    return {
      name: name,
      value: data,
      strValue: '' + value,
      mapped: mapped,
      field: data[1],
      bypass: propIsBypass
    };

  } else if(
    ( mapData = new RegExp( types.mapData.regex ).exec( value ) ) ||
    ( mapLayoutData = new RegExp( types.mapLayoutData.regex ).exec( value ) ) ||
    ( mapScratch = new RegExp( types.mapScratch.regex ).exec( value ) )
  ){
    if( propIsBypass ){ return false; } // mappers not allowed in bypass
    if( type.multiple ){ return false; } // impossible to map to num

    var mapped;
    if( mapData ){
      mapped = types.mapData;
    } else if( mapLayoutData ){
      mapped = types.mapLayoutData;
    } else {
      mapped = types.mapScratch;
    }

    mapData = mapData || mapLayoutData || mapScratch;

    // we can map only if the type is a colour or a number
    if( !(type.color || type.number) ){ return false; }

    var valueMin = this.parse( name, mapData[4] ); // parse to validate
    if( !valueMin || valueMin.mapped ){ return false; } // can't be invalid or mapped

    var valueMax = this.parse( name, mapData[5] ); // parse to validate
    if( !valueMax || valueMax.mapped ){ return false; } // can't be invalid or mapped

    // check if valueMin and valueMax are the same
    if( valueMin.value === valueMax.value ){
      return false; // can't make much of a mapper without a range

    } else if( type.color ){
      var c1 = valueMin.value;
      var c2 = valueMax.value;

      var same = c1[0] === c2[0] // red
        && c1[1] === c2[1] // green
        && c1[2] === c2[2] // blue
        && ( // optional alpha
          c1[3] === c2[3] // same alpha outright
          || (
            (c1[3] == null || c1[3] === 1) // full opacity for colour 1?
            &&
            (c2[3] == null || c2[3] === 1) // full opacity for colour 2?
          )
        )
      ;

      if( same ){ return false; } // can't make a mapper without a range
    }

    return {
      name: name,
      value: mapData,
      strValue: '' + value,
      mapped: mapped,
      field: mapData[1],
      fieldMin: parseFloat( mapData[2] ), // min & max are numeric
      fieldMax: parseFloat( mapData[3] ),
      valueMin: valueMin.value,
      valueMax: valueMax.value,
      bypass: propIsBypass
    };
  }

  if( type.multiple && propIsFlat !== 'multiple' ){
    var vals;

    if( valueIsString ){
      vals = value.split(/\s+/);
    } else if( is.array(value) ){
      vals = value;
    } else {
      vals = [ value ];
    }

    if( type.evenMultiple && vals.length % 2 !== 0 ){ return null; }

    var valArr = vals.map(function( v ){
      var p = self.parse( name, v, propIsBypass, 'multiple' );

      if( p.pfValue != null ){
        return p.pfValue;
      } else {
        return p.value;
      }
    });

    return {
      name: name,
      value: valArr,
      pfValue: valArr,
      strValue: valArr.join(' '),
      bypass: propIsBypass,
      units: type.number && !type.unitless ? type.implicitUnits || 'px' : undefined
    };
  }

  // several types also allow enums
  var checkEnums = function(){
    for( var i = 0; i < type.enums.length; i++ ){
      var en = type.enums[i];

      if( en === value ){
        return {
          name: name,
          value: value,
          strValue: '' + value,
          bypass: propIsBypass
        };
      }
    }

    return null;
  };

  // check the type and return the appropriate object
  if( type.number ){
    var units;
    var implicitUnits = 'px'; // not set => px

    if( type.units ){ // use specified units if set
      units = type.units;
    }

    if( type.implicitUnits ){
      implicitUnits = type.implicitUnits;
    }

    if( !type.unitless ){
      if( valueIsString ){
        var unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : '');
        if( units ){ unitsRegex = units; } // only allow explicit units if so set
        var match = value.match( '^(' + util.regex.number + ')(' + unitsRegex + ')?' + '$' );

        if( match ){
          value = match[1];
          units = match[2] || implicitUnits;
        }

      } else if( !units || type.implicitUnits ) {
        units = implicitUnits; // implicitly px if unspecified
      }
    }

    value = parseFloat( value );

    // if not a number and enums not allowed, then the value is invalid
    if( isNaN(value) && type.enums === undefined ){
      return null;
    }

    // check if this number type also accepts special keywords in place of numbers
    // (i.e. `left`, `auto`, etc)
    if( isNaN(value) && type.enums !== undefined ){
      value = passedValue;

      return checkEnums();
    }

    // check if value must be an integer
    if( type.integer && !is.integer(value) ){
      return null;
    }

    // check value is within range
    if( (type.min !== undefined && value < type.min)
    || (type.max !== undefined && value > type.max)
    ){
      return null;
    }

    var ret = {
      name: name,
      value: value,
      strValue: '' + value + (units ? units : ''),
      units: units,
      bypass: propIsBypass
    };

    // normalise value in pixels
    if( type.unitless || (units !== 'px' && units !== 'em') ){
      ret.pfValue = value;
    } else {
      ret.pfValue = ( units === 'px' || !units ? (value) : (this.getEmSizeInPixels() * value) );
    }

    // normalise value in ms
    if( units === 'ms' || units === 's' ){
      ret.pfValue = units === 'ms' ? value : 1000 * value;
    }

    // normalise value in rad
    if( units === 'deg' || units === 'rad' ){
      ret.pfValue = units === 'rad' ? value : value * Math.PI/180;
    }

    return ret;

  } else if( type.propList ) {

    var props = [];
    var propsStr = '' + value;

    if( propsStr === 'none' ){
      // leave empty

    } else { // go over each prop

      var propsSplit = propsStr.split(',');
      for( var i = 0; i < propsSplit.length; i++ ){
        var propName = propsSplit[i].trim();

        if( self.properties[propName] ){
          props.push( propName );
        }
      }

      if( props.length === 0 ){ return null; }
    }

    return {
      name: name,
      value: props,
      strValue: props.length === 0 ? 'none' : props.join(', '),
      bypass: propIsBypass
    };

  } else if( type.color ){
    var tuple = util.color2tuple( value );

    if( !tuple ){ return null; }

    return {
      name: name,
      value: tuple,
      strValue: '' + value,
      bypass: propIsBypass,
      roundValue: true
    };

  } else if( type.regex || type.regexes ){

    // first check enums
    if( type.enums ){
      var enumProp = checkEnums();

      if( enumProp ){ return enumProp; }
    }

    var regexes = type.regexes ? type.regexes : [ type.regex ];

    for( var i = 0; i < regexes.length; i++ ){
      var regex = new RegExp( regexes[i] ); // make a regex from the type string
      var m = regex.exec( value );

      if( m ){ // regex matches
        return {
          name: name,
          value: m,
          strValue: '' + value,
          bypass: propIsBypass
        };

      }
    }

    return null; // didn't match any

  } else if( type.string ){
    // just return
    return {
      name: name,
      value: value,
      strValue: '' + value,
      bypass: propIsBypass
    };

  } else if( type.enums ){ // check enums last because it's a combo type in others
    return checkEnums();

  } else {
    return null; // not a type we can handle
  }

};

module.exports = styfn;

},{"../is":77,"../util":94}],89:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');

var styfn = {};

(function(){
  var number = util.regex.number;
  var rgba = util.regex.rgbaNoBackRefs;
  var hsla = util.regex.hslaNoBackRefs;
  var hex3 = util.regex.hex3;
  var hex6 = util.regex.hex6;
  var data = function( prefix ){ return '^' + prefix + '\\s*\\(\\s*([\\w\\.]+)\\s*\\)$'; };
  var mapData = function( prefix ){
    var mapArg = number + '|\\w+|' + rgba + '|' + hsla + '|' + hex3 + '|' + hex6;
    return '^' + prefix + '\\s*\\(([\\w\\.]+)\\s*\\,\\s*(' + number + ')\\s*\\,\\s*(' + number + ')\\s*,\\s*(' + mapArg + ')\\s*\\,\\s*(' + mapArg + ')\\)$';
  };

  // each visual style property has a type and needs to be validated according to it
  styfn.types = {
    time: { number: true, min: 0, units: 's|ms', implicitUnits: 'ms' },
    percent: { number: true, min: 0, max: 100, units: '%', implicitUnits: '%' },
    zeroOneNumber: { number: true, min: 0, max: 1, unitless: true },
    nOneOneNumber: { number: true, min: -1, max: 1, unitless: true },
    nonNegativeInt: { number: true, min: 0, integer: true, unitless: true },
    position: { enums: ['parent', 'origin'] },
    nodeSize: { number: true, min: 0, enums: ['auto', 'label'] },
    number: { number: true, unitless: true },
    numbers: { number: true, unitless: true, multiple: true },
    size: { number: true, min: 0 },
    bidirectionalSize: { number: true }, // allows negative
    bidirectionalSizes: { number: true, multiple: true }, // allows negative
    bgSize: { number: true, min: 0, allowPercent: true },
    bgWH: { number: true, min: 0, allowPercent: true, enums: ['auto'] },
    bgPos: { number: true, allowPercent: true },
    bgRepeat: { enums: ['repeat', 'repeat-x', 'repeat-y', 'no-repeat'] },
    bgFit: { enums: ['none', 'contain', 'cover'] },
    bgClip: { enums: ['none', 'node'] },
    color: { color: true },
    bool: { enums: ['yes', 'no'] },
    lineStyle: { enums: ['solid', 'dotted', 'dashed'] },
    borderStyle: { enums: ['solid', 'dotted', 'dashed', 'double'] },
    curveStyle: { enums: ['bezier', 'unbundled-bezier', 'haystack', 'segments'] },
    fontFamily: { regex: '^([\\w- \\"]+(?:\\s*,\\s*[\\w- \\"]+)*)$' },
    fontVariant: { enums: ['small-caps', 'normal'] },
    fontStyle: { enums: ['italic', 'normal', 'oblique'] },
    fontWeight: { enums: ['normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '800', '900', 100, 200, 300, 400, 500, 600, 700, 800, 900] },
    textDecoration: { enums: ['none', 'underline', 'overline', 'line-through'] },
    textTransform: { enums: ['none', 'uppercase', 'lowercase'] },
    textWrap: { enums: ['none', 'wrap'] },
    textBackgroundShape: { enums: ['rectangle', 'roundrectangle']},
    nodeShape: { enums: ['rectangle', 'roundrectangle', 'ellipse', 'triangle', 'square', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'star', 'diamond', 'vee', 'rhomboid', 'polygon'] },
    compoundIncludeLabels: { enums: ['include', 'exclude'] },
    arrowShape: { enums: ['tee', 'triangle', 'triangle-tee', 'triangle-backcurve', 'half-triangle-overshot', 'vee', 'square', 'circle', 'diamond', 'none'] },
    arrowFill: { enums: ['filled', 'hollow'] },
    display: { enums: ['element', 'none'] },
    visibility: { enums: ['hidden', 'visible'] },
    valign: { enums: ['top', 'center', 'bottom'] },
    halign: { enums: ['left', 'center', 'right'] },
    text: { string: true },
    data: { mapping: true, regex: data('data') },
    layoutData: { mapping: true, regex: data('layoutData') },
    scratch: { mapping: true, regex: data('scratch') },
    mapData: { mapping: true, regex: mapData('mapData') },
    mapLayoutData: { mapping: true, regex: mapData('mapLayoutData') },
    mapScratch: { mapping: true, regex: mapData('mapScratch') },
    fn: { mapping: true, fn: true },
    url: { regex: '^url\\s*\\(\\s*([^\\s]+)\\s*\\s*\\)|none|(.+)$' },
    propList: { propList: true },
    angle: { number: true, units: 'deg|rad', implicitUnits: 'rad' },
    textRotation: { enums: ['none', 'autorotate'] },
    polygonPointList: { number: true, multiple: true, evenMultiple: true, min: -1, max: 1, unitless: true },
    easing: {
      regexes: [
        '^(spring)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$',
        '^(cubic-bezier)\\s*\\(\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*,\\s*(' + number + ')\\s*\\)$'
      ],
      enums: [
        'linear',
        'ease', 'ease-in', 'ease-out', 'ease-in-out',
        'ease-in-sine', 'ease-out-sine', 'ease-in-out-sine',
        'ease-in-quad', 'ease-out-quad', 'ease-in-out-quad',
        'ease-in-cubic', 'ease-out-cubic', 'ease-in-out-cubic',
        'ease-in-quart', 'ease-out-quart', 'ease-in-out-quart',
        'ease-in-quint', 'ease-out-quint', 'ease-in-out-quint',
        'ease-in-expo', 'ease-out-expo', 'ease-in-out-expo',
        'ease-in-circ', 'ease-out-circ', 'ease-in-out-circ'
      ]
    }
  };

  // define visual style properties
  var t = styfn.types;
  var props = styfn.properties = [
    // labels
    { name: 'text-valign', type: t.valign },
    { name: 'text-halign', type: t.halign },
    { name: 'color', type: t.color },
    { name: 'label', type: t.text },
    { name: 'text-outline-color', type: t.color },
    { name: 'text-outline-width', type: t.size },
    { name: 'text-outline-opacity', type: t.zeroOneNumber },
    { name: 'text-opacity', type: t.zeroOneNumber },
    { name: 'text-background-color', type: t.color },
    { name: 'text-background-opacity', type: t.zeroOneNumber },
    { name: 'text-border-opacity', type: t.zeroOneNumber },
    { name: 'text-border-color', type: t.color },
    { name: 'text-border-width', type: t.size },
    { name: 'text-border-style', type: t.borderStyle },
    { name: 'text-background-shape', type: t.textBackgroundShape},
    // { name: 'text-decoration', type: t.textDecoration }, // not supported in canvas
    { name: 'text-transform', type: t.textTransform },
    { name: 'text-wrap', type: t.textWrap },
    { name: 'text-max-width', type: t.size },
    { name: 'text-events', type: t.bool },

    // { name: 'text-rotation', type: t.angle }, // TODO disabled b/c rotation breaks bounding boxes
    { name: 'font-family', type: t.fontFamily },
    { name: 'font-style', type: t.fontStyle },
    // { name: 'font-variant', type: t.fontVariant }, // not useful
    { name: 'font-weight', type: t.fontWeight },
    { name: 'font-size', type: t.size },
    { name: 'min-zoomed-font-size', type: t.size },
    { name: 'edge-text-rotation', type: t.textRotation },

    // behaviour
    { name: 'events', type: t.bool },

    // visibility
    { name: 'display', type: t.display },
    { name: 'visibility', type: t.visibility },
    { name: 'opacity', type: t.zeroOneNumber },
    { name: 'z-index', type: t.nonNegativeInt },

    // overlays
    { name: 'overlay-padding', type: t.size },
    { name: 'overlay-color', type: t.color },
    { name: 'overlay-opacity', type: t.zeroOneNumber },

    // shadows
    { name: 'shadow-blur', type: t.size },
    { name: 'shadow-color', type: t.color },
    { name: 'shadow-opacity', type: t.zeroOneNumber },
    { name: 'shadow-offset-x', type: t.bidirectionalSize },
    { name: 'shadow-offset-y', type: t.bidirectionalSize },

    // label shadows
    { name: 'text-shadow-blur', type: t.size },
    { name: 'text-shadow-color', type: t.color },
    { name: 'text-shadow-opacity', type: t.zeroOneNumber },
    { name: 'text-shadow-offset-x', type: t.bidirectionalSize },
    { name: 'text-shadow-offset-y', type: t.bidirectionalSize },

    // transition anis
    { name: 'transition-property', type: t.propList },
    { name: 'transition-duration', type: t.time },
    { name: 'transition-delay', type: t.time },
    { name: 'transition-timing-function', type: t.easing },

    // node body
    { name: 'height', type: t.nodeSize },
    { name: 'width', type: t.nodeSize },
    { name: 'shape', type: t.nodeShape },
    { name: 'shape-polygon-points', type: t.polygonPointList },
    { name: 'background-color', type: t.color },
    { name: 'background-opacity', type: t.zeroOneNumber },
    { name: 'background-blacken', type: t.nOneOneNumber },
    { name: 'padding-left', type: t.size },
    { name: 'padding-right', type: t.size },
    { name: 'padding-top', type: t.size },
    { name: 'padding-bottom', type: t.size },

    // node border
    { name: 'border-color', type: t.color },
    { name: 'border-opacity', type: t.zeroOneNumber },
    { name: 'border-width', type: t.size },
    { name: 'border-style', type: t.borderStyle },

    // node background images
    { name: 'background-image', type: t.url },
    { name: 'background-image-opacity', type: t.zeroOneNumber },
    { name: 'background-position-x', type: t.bgPos },
    { name: 'background-position-y', type: t.bgPos },
    { name: 'background-repeat', type: t.bgRepeat },
    { name: 'background-fit', type: t.bgFit },
    { name: 'background-clip', type: t.bgClip },
    { name: 'background-width', type: t.bgWH },
    { name: 'background-height', type: t.bgWH },

    // compound props
    { name: 'position', type: t.position },
    { name: 'compound-sizing-wrt-labels', type: t.compoundIncludeLabels },

    // edge line
    { name: 'line-style', type: t.lineStyle },
    { name: 'line-color', type: t.color },
    { name: 'curve-style', type: t.curveStyle },
    { name: 'haystack-radius', type: t.zeroOneNumber },
    { name: 'control-point-step-size', type: t.size },
    { name: 'control-point-distances', type: t.bidirectionalSizes },
    { name: 'control-point-weights', type: t.numbers },
    { name: 'segment-distances', type: t.bidirectionalSizes },
    { name: 'segment-weights', type: t.numbers },

    // these are just for the core
    { name: 'selection-box-color', type: t.color },
    { name: 'selection-box-opacity', type: t.zeroOneNumber },
    { name: 'selection-box-border-color', type: t.color },
    { name: 'selection-box-border-width', type: t.size },
    { name: 'active-bg-color', type: t.color },
    { name: 'active-bg-opacity', type: t.zeroOneNumber },
    { name: 'active-bg-size', type: t.size },
    { name: 'outside-texture-bg-color', type: t.color },
    { name: 'outside-texture-bg-opacity', type: t.zeroOneNumber }
  ];

  // define aliases
  var aliases = styfn.aliases = [
    { name: 'content', pointsTo: 'label' },
    { name: 'control-point-distance', pointsTo: 'control-point-distances' },
    { name: 'control-point-weight', pointsTo: 'control-point-weights' }
  ];

  // pie backgrounds for nodes
  styfn.pieBackgroundN = 16; // because the pie properties are numbered, give access to a constant N (for renderer use)
  props.push({ name: 'pie-size', type: t.bgSize });
  for( var i = 1; i <= styfn.pieBackgroundN; i++ ){
    props.push({ name: 'pie-'+i+'-background-color', type: t.color });
    props.push({ name: 'pie-'+i+'-background-size', type: t.percent });
    props.push({ name: 'pie-'+i+'-background-opacity', type: t.zeroOneNumber });
  }

  // edge arrows
  var arrowPrefixes = styfn.arrowPrefixes = ['source', 'mid-source', 'target', 'mid-target'];
  [
    { name: 'arrow-shape', type: t.arrowShape },
    { name: 'arrow-color', type: t.color },
    { name: 'arrow-fill', type: t.arrowFill }
  ].forEach(function( prop ){
    arrowPrefixes.forEach(function( prefix ){
      var name = prefix + '-' + prop.name;
      var type = prop.type;

      props.push({ name: name, type: type });
    });
  }, {});

  // list of property names
  styfn.propertyNames = props.map(function(p){ return p.name; });

  // allow access of properties by name ( e.g. style.properties.height )
  for( var i = 0; i < props.length; i++ ){
    var prop = props[i];

    props[ prop.name ] = prop; // allow lookup by name
  }

  // map aliases
  for( var i = 0; i < aliases.length; i++ ){
    var alias = aliases[i];
    var pointsToProp = props[ alias.pointsTo ];
    var aliasProp = {
      name: alias.name,
      alias: true,
      pointsTo: pointsToProp
    };

    // add alias prop for parsing
    props.push( aliasProp );

    props[ alias.name ] = aliasProp; // allow lookup by name
  }
})();

// adds the default stylesheet to the current style
styfn.addDefaultStylesheet = function(){
  // fill the style with the default stylesheet
  this
    .selector('node, edge') // common properties
      .css( util.extend( {
        'events': 'yes',
        'text-events': 'no',
        'text-valign': 'top',
        'text-halign': 'center',
        'color': '#000',
        'text-outline-color': '#000',
        'text-outline-width': 0,
        'text-outline-opacity': 1,
        'text-opacity': 1,
        'text-decoration': 'none',
        'text-transform': 'none',
        'text-wrap': 'none',
        'text-max-width': 9999,
        'text-background-color': '#000',
        'text-background-opacity': 0,
        'text-border-opacity': 0,
        'text-border-width': 0,
        'text-border-style': 'solid',
        'text-border-color':'#000',
        'text-background-shape':'rectangle',
        'font-family': 'Helvetica Neue, Helvetica, sans-serif',
        'font-style': 'normal',
        // 'font-variant': fontVariant,
        'font-weight': 'normal',
        'font-size': 16,
        'min-zoomed-font-size': 0,
        'edge-text-rotation': 'none',
        'visibility': 'visible',
        'display': 'element',
        'opacity': 1,
        'z-index': 0,
        'label': '',
        'overlay-opacity': 0,
        'overlay-color': '#000',
        'overlay-padding': 10,
        'shadow-opacity': 0,
        'shadow-color': '#000',
        'shadow-blur': 10,
        'shadow-offset-x': 0,
        'shadow-offset-y': 0,
        'text-shadow-opacity': 0,
        'text-shadow-color': '#000',
        'text-shadow-blur': 5,
        'text-shadow-offset-x': 0,
        'text-shadow-offset-y': 0,
        'transition-property': 'none',
        'transition-duration': 0,
        'transition-delay': 0,
        'transition-timing-function': 'linear',

        // node props
        'background-blacken': 0,
        'background-color': '#888',
        'background-opacity': 1,
        'background-image': 'none',
        'background-image-opacity': 1,
        'background-position-x': '50%',
        'background-position-y': '50%',
        'background-repeat': 'no-repeat',
        'background-fit': 'none',
        'background-clip': 'node',
        'background-width': 'auto',
        'background-height': 'auto',
        'border-color': '#000',
        'border-opacity': 1,
        'border-width': 0,
        'border-style': 'solid',
        'height': 30,
        'width': 30,
        'shape': 'ellipse',
        'shape-polygon-points': '-1, -1,   1, -1,   1, 1,   -1, 1',

        // compound props
        'padding-top': 0,
        'padding-bottom': 0,
        'padding-left': 0,
        'padding-right': 0,
        'position': 'origin',
        'compound-sizing-wrt-labels': 'include'
      }, {
        // node pie bg
        'pie-size': '100%'
      }, [
        { name: 'pie-{{i}}-background-color', value: 'black' },
        { name: 'pie-{{i}}-background-size', value: '0%' },
        { name: 'pie-{{i}}-background-opacity', value: 1 }
      ].reduce(function( css, prop ){
        for( var i = 1; i <= styfn.pieBackgroundN; i++ ){
          var name = prop.name.replace('{{i}}', i);
          var val = prop.value;

          css[ name ] = val;
        }

        return css;
      }, {}), {
        // edge props
        'line-style': 'solid',
        'line-color': '#ddd',
        'control-point-step-size': 40,
        'control-point-weights': 0.5,
        'segment-weights': 0.5,
        'segment-distances': 20,
        'curve-style': 'bezier',
        'haystack-radius': 0.8
      }, [
        { name: 'arrow-shape', value: 'none' },
        { name: 'arrow-color', value: '#ddd' },
        { name: 'arrow-fill', value: 'filled' }
      ].reduce(function( css, prop ){
        styfn.arrowPrefixes.forEach(function( prefix ){
          var name = prefix + '-' + prop.name;
          var val = prop.value;

          css[ name ] = val;
        });

        return css;
      }, {}) ) )
    .selector('$node > node') // compound (parent) node properties
      .css({
        'width': 'auto',
        'height': 'auto',
        'shape': 'rectangle',
        'padding-top': 10,
        'padding-right': 10,
        'padding-left': 10,
        'padding-bottom': 10
      })
    .selector('edge') // just edge properties
      .css({
        'width': 1
      })
    .selector(':active')
      .css({
        'overlay-color': 'black',
        'overlay-padding': 10,
        'overlay-opacity': 0.25
      })
    .selector('core') // just core properties
      .css({
        'selection-box-color': '#ddd',
        'selection-box-opacity': 0.65,
        'selection-box-border-color': '#aaa',
        'selection-box-border-width': 1,
        'active-bg-color': 'black',
        'active-bg-opacity': 0.15,
        'active-bg-size': 30,
        'outside-texture-bg-color': '#000',
        'outside-texture-bg-opacity': 0.125
      })
  ;

  this.defaultLength = this.length;
};

module.exports = styfn;

},{"../util":94}],90:[function(_dereq_,module,exports){
'use strict';

var util = _dereq_('../util');
var Selector = _dereq_('../selector');

var styfn = {};

styfn.applyFromString = function( string ){
  var self = this;
  var style = this;
  var remaining = '' + string;
  var selAndBlockStr;
  var blockRem;
  var propAndValStr;

  // remove comments from the style string
  remaining = remaining.replace(/[/][*](\s|.)+?[*][/]/g, '');

  function removeSelAndBlockFromRemaining(){
    // remove the parsed selector and block from the remaining text to parse
    if( remaining.length > selAndBlockStr.length ){
      remaining = remaining.substr( selAndBlockStr.length );
    } else {
      remaining = '';
    }
  }

  function removePropAndValFromRem(){
    // remove the parsed property and value from the remaining block text to parse
    if( blockRem.length > propAndValStr.length ){
      blockRem = blockRem.substr( propAndValStr.length );
    } else {
      blockRem = '';
    }
  }

  while(true){
    var nothingLeftToParse = remaining.match(/^\s*$/);
    if( nothingLeftToParse ){ break; }

    var selAndBlock = remaining.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/);

    if( !selAndBlock ){
      util.error('Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: ' + remaining);
      break;
    }

    selAndBlockStr = selAndBlock[0];

    // parse the selector
    var selectorStr = selAndBlock[1];
    if( selectorStr !== 'core' ){
      var selector = new Selector( selectorStr );
      if( selector._private.invalid ){
        util.error('Skipping parsing of block: Invalid selector found in string stylesheet: ' + selectorStr);

        // skip this selector and block
        removeSelAndBlockFromRemaining();
        continue;
      }
    }

    // parse the block of properties and values
    var blockStr = selAndBlock[2];
    var invalidBlock = false;
    blockRem = blockStr;
    var props = [];

    while(true){
      var nothingLeftToParse = blockRem.match(/^\s*$/);
      if( nothingLeftToParse ){ break; }

      var propAndVal = blockRem.match(/^\s*(.+?)\s*:\s*(.+?)\s*;/);

      if( !propAndVal ){
        util.error('Skipping parsing of block: Invalid formatting of style property and value definitions found in:' + blockStr);
        invalidBlock = true;
        break;
      }

      propAndValStr = propAndVal[0];
      var propStr = propAndVal[1];
      var valStr = propAndVal[2];

      var prop = self.properties[ propStr ];
      if( !prop ){
        util.error('Skipping property: Invalid property name in: ' + propAndValStr);

        // skip this property in the block
        removePropAndValFromRem();
        continue;
      }

      var parsedProp = style.parse( propStr, valStr );

      if( !parsedProp ){
        util.error('Skipping property: Invalid property definition in: ' + propAndValStr);

        // skip this property in the block
        removePropAndValFromRem();
        continue;
      }

      props.push({
        name: propStr,
        val: valStr
      });
      removePropAndValFromRem();
    }

    if( invalidBlock ){
      removeSelAndBlockFromRemaining();
      break;
    }

    // put the parsed block in the style
    style.selector( selectorStr );
    for( var i = 0; i < props.length; i++ ){
      var prop = props[i];
      style.css( prop.name, prop.val );
    }

    removeSelAndBlockFromRemaining();
  }

  return style;
};

styfn.fromString = function( string ){
  var style = this;

  style.resetToDefault();
  style.applyFromString( string );

  return style;
};

module.exports = styfn;

},{"../selector":81,"../util":94}],91:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('./is');
var util = _dereq_('./util');
var Style = _dereq_('./style');

// a dummy stylesheet object that doesn't need a reference to the core
// (useful for init)
var Stylesheet = function(){
  if( !(this instanceof Stylesheet) ){
    return new Stylesheet();
  }

  this.length = 0;
};

var sheetfn = Stylesheet.prototype;

sheetfn.instanceString = function(){
  return 'stylesheet';
};

// just store the selector to be parsed later
sheetfn.selector = function( selector ){
  var i = this.length++;

  this[i] = {
    selector: selector,
    properties: []
  };

  return this; // chaining
};

// just store the property to be parsed later
sheetfn.css = function( name, value ){
  var i = this.length - 1;

  if( is.string(name) ){
    this[i].properties.push({
      name: name,
      value: value
    });
  } else if( is.plainObject(name) ){
    var map = name;

    for( var j = 0; j < Style.properties.length; j++ ){
      var prop = Style.properties[j];
      var mapVal = map[ prop.name ];

      if( mapVal === undefined ){ // also try camel case name
        mapVal = map[ util.dash2camel(prop.name) ];
      }

      if( mapVal !== undefined ){
        var name = prop.name;
        var value = mapVal;

        this[i].properties.push({
          name: name,
          value: value
        });
      }
    }
  }

  return this; // chaining
};

sheetfn.style = sheetfn.css;

// generate a real style object from the dummy stylesheet
sheetfn.generateStyle = function( cy ){
  var style = new Style(cy);

  for( var i = 0; i < this.length; i++ ){
    var context = this[i];
    var selector = context.selector;
    var props = context.properties;

    style.selector(selector); // apply selector

    for( var j = 0; j < props.length; j++ ){
      var prop = props[j];

      style.css( prop.name, prop.value ); // apply property
    }
  }

  return style;
};

module.exports = Stylesheet;

},{"./is":77,"./style":86,"./util":94}],92:[function(_dereq_,module,exports){
// cross-env thread/worker
// NB : uses (heavyweight) processes on nodejs so best not to create too many threads

'use strict';

var window = _dereq_('./window');
var util = _dereq_('./util');
var Promise = _dereq_('./promise');
var Event = _dereq_('./event');
var define = _dereq_('./define');
var is = _dereq_('./is');

var Thread = function( opts ){
  if( !(this instanceof Thread) ){
    return new Thread( opts );
  }

  var _p = this._private = {
    requires: [],
    files: [],
    queue: null,
    pass: [],
    disabled: false
  };

  if( is.plainObject(opts) ){
    if( opts.disabled != null ){
      _p.disabled = !!opts.disabled;
    }
  }

};

var thdfn = Thread.prototype; // short alias

var stringifyFieldVal = function( val ){
  var valStr = is.fn( val ) ? val.toString() : "JSON.parse('" + JSON.stringify(val) + "')";

  return valStr;
};

// allows for requires with prototypes and subobjs etc
var fnAsRequire = function( fn ){
  var req;
  var fnName;

  if( is.object(fn) && fn.fn ){ // manual fn
    req = fnAs( fn.fn, fn.name );
    fnName = fn.name;
    fn = fn.fn;
  } else if( is.fn(fn) ){ // auto fn
    req = fn.toString();
    fnName = fn.name;
  } else if( is.string(fn) ){ // stringified fn
    req = fn;
  } else if( is.object(fn) ){ // plain object
    if( fn.proto ){
      req = '';
    } else {
      req = fn.name + ' = {};';
    }

    fnName = fn.name;
    fn = fn.obj;
  }

  req += '\n';

  var protoreq = function( val, subname ){
    if( val.prototype ){
      var protoNonempty = false;
      for( var prop in val.prototype ){ protoNonempty = true; break; } // jshint ignore:line

      if( protoNonempty ){
        req += fnAsRequire( {
          name: subname,
          obj: val,
          proto: true
        }, val );
      }
    }
  };

  // pull in prototype
  if( fn.prototype && fnName != null ){

    for( var name in fn.prototype ){
      var protoStr = '';

      var val = fn.prototype[ name ];
      var valStr = stringifyFieldVal( val );
      var subname = fnName + '.prototype.' + name;

      protoStr += subname + ' = ' + valStr + ';\n';

      if( protoStr ){
        req += protoStr;
      }

      protoreq( val, subname ); // subobject with prototype
    }

  }

  // pull in properties for obj/fns
  if( !is.string(fn) ){ for( var name in fn ){
    var propsStr = '';

    if( fn.hasOwnProperty(name) ){
      var val = fn[ name ];
      var valStr = stringifyFieldVal( val );
      var subname = fnName + '["' + name + '"]';

      propsStr += subname + ' = ' + valStr + ';\n';
    }

    if( propsStr ){
      req += propsStr;
    }

    protoreq( val, subname ); // subobject with prototype
  } }

  return req;
};

var isPathStr = function( str ){
  return is.string(str) && str.match(/\.js$/);
};

util.extend(thdfn, {

  instanceString: function(){ return 'thread'; },

  require: function( fn, as ){
    var requires = this._private.requires;

    if( isPathStr(fn) ){
      this._private.files.push( fn );

      return this;
    }

    if( as ){
      if( is.fn(fn) ){
        fn = { name: as, fn: fn };
      } else {
        fn = { name: as, obj: fn };
      }
    } else {
      if( is.fn(fn) ){
        if( !fn.name ){
          throw 'The function name could not be automatically determined.  Use thread.require( someFunction, "someFunction" )';
        }

        fn = { name: fn.name, fn: fn };
      }
    }

    requires.push( fn );

    return this; // chaining
  },

  pass: function( data ){
    this._private.pass.push( data );

    return this; // chaining
  },

  run: function( fn, pass ){ // fn used like main()
    var self = this;
    var _p = this._private;
    pass = pass || _p.pass.shift();

    if( _p.stopped ){
      throw 'Attempted to run a stopped thread!  Start a new thread or do not stop the existing thread and reuse it.';
    }

    if( _p.running ){
      return ( _p.queue = _p.queue.then(function(){ // inductive step
        return self.run( fn, pass );
      }) );
    }

    var useWW = window != null && !_p.disabled;
    var useNode = !window && typeof module !== 'undefined' && !_p.disabled;

    self.trigger('run');

    var runP = new Promise(function( resolve, reject ){

      _p.running = true;

      var threadTechAlreadyExists = _p.ran;

      var fnImplStr = is.string( fn ) ? fn : fn.toString();

      // worker code to exec
      var fnStr = '\n' + ( _p.requires.map(function( r ){
        return fnAsRequire( r );
      }) ).concat( _p.files.map(function( f ){
        if( useWW ){
          var wwifyFile = function( file ){
            if( file.match(/^\.\//) || file.match(/^\.\./) ){
              return window.location.origin + window.location.pathname + file;
            } else if( file.match(/^\//) ){
              return window.location.origin + '/' + file;
            }
            return file;
          };

          return 'importScripts("' + wwifyFile(f) + '");';
        } else if( useNode ) {
          return 'eval( require("fs").readFileSync("' + f + '", { encoding: "utf8" }) );';
        } else {
          throw 'External file `' + f + '` can not be required without any threading technology.';
        }
      }) ).concat([
        '( function(){',
          'var ret = (' + fnImplStr + ')(' + JSON.stringify(pass) + ');',
          'if( ret !== undefined ){ resolve(ret); }', // assume if ran fn returns defined value (incl. null), that we want to resolve to it
        '} )()\n'
      ]).join('\n');

      // because we've now consumed the requires, empty the list so we don't dupe on next run()
      _p.requires = [];
      _p.files = [];

      if( useWW ){
        var fnBlob, fnUrl;

        // add normalised thread api functions
        if( !threadTechAlreadyExists ){
          var fnPre = fnStr + '';

          fnStr = [
            'function _ref_(o){ return eval(o); };',
            'function broadcast(m){ return message(m); };', // alias
            'function message(m){ postMessage(m); };',
            'function listen(fn){',
            '  self.addEventListener("message", function(m){ ',
            '    if( typeof m === "object" && (m.data.$$eval || m.data === "$$start") ){',
            '    } else { ',
            '      fn( m.data );',
            '    }',
            '  });',
            '};',
            'self.addEventListener("message", function(m){  if( m.data.$$eval ){ eval( m.data.$$eval ); }  });',
            'function resolve(v){ postMessage({ $$resolve: v }); };',
            'function reject(v){ postMessage({ $$reject: v }); };'
          ].join('\n');

          fnStr += fnPre;

          fnBlob = new Blob([ fnStr ], {
            type: 'application/javascript'
          });
          fnUrl = window.URL.createObjectURL( fnBlob );
        }
        // create webworker and let it exec the serialised code
        var ww = _p.webworker = _p.webworker || new Worker( fnUrl );

        if( threadTechAlreadyExists ){ // then just exec new run() code
          ww.postMessage({
            $$eval: fnStr
          });
        }

        // worker messages => events
        var cb;
        ww.addEventListener('message', cb = function( m ){
          var isObject = is.object(m) && is.object( m.data );

          if( isObject && ('$$resolve' in m.data) ){
            ww.removeEventListener('message', cb); // done listening b/c resolve()

            resolve( m.data.$$resolve );
          } else if( isObject && ('$$reject' in m.data) ){
            ww.removeEventListener('message', cb); // done listening b/c reject()

            reject( m.data.$$reject );
          } else {
            self.trigger( new Event(m, { type: 'message', message: m.data }) );
          }
        }, false);

        if( !threadTechAlreadyExists ){
          ww.postMessage('$$start'); // start up the worker
        }

      } else if( useNode ){
        // create a new process

        if( !_p.child ){
          _p.child = ( _dereq_('child_process').fork( _dereq_('path').join(__dirname, 'thread-node-fork') ) );
        }

        var child = _p.child;

        // child process messages => events
        var cb;
        child.on('message', cb = function( m ){
          if( is.object(m) && ('$$resolve' in m) ){
            child.removeListener('message', cb); // done listening b/c resolve()

            resolve( m.$$resolve );
          } else if( is.object(m) && ('$$reject' in m) ){
            child.removeListener('message', cb); // done listening b/c reject()

            reject( m.$$reject );
          } else {
            self.trigger( new Event({}, { type: 'message', message: m }) );
          }
        });

        // ask the child process to eval the worker code
        child.send({
          $$eval: fnStr
        });

      } else { // use a fallback mechanism using a timeout

        var promiseResolve = resolve;
        var promiseReject = reject;

        var timer = _p.timer = _p.timer || {

          listeners: [],

          exec: function(){
            // as a string so it can't be mangled by minifiers and processors
            fnStr = [
              'function _ref_(o){ return eval(o); };',
              'function broadcast(m){ return message(m); };',
              'function message(m){ self.trigger( new Event({}, { type: "message", message: m }) ); };',
              'function listen(fn){ timer.listeners.push( fn ); };',
              'function resolve(v){ promiseResolve(v); };',
              'function reject(v){ promiseReject(v); };'
            ].join('\n') + fnStr;

            // the .run() code
            eval( fnStr ); // jshint ignore:line
          },

          message: function( m ){
            var ls = timer.listeners;

            for( var i = 0; i < ls.length; i++ ){
              var fn = ls[i];

              fn( m );
            }
          }

        };

        timer.exec();
      }

    }).then(function( v ){
      _p.running = false;
      _p.ran = true;

      self.trigger('ran');

      return v;
    });

    if( _p.queue == null ){
      _p.queue = runP; // i.e. first step of inductive promise chain (for queue)
    }

    return runP;
  },

  // send the thread a message
  message: function( m ){
    var _p = this._private;

    if( _p.webworker ){
      _p.webworker.postMessage( m );
    }

    if( _p.child ){
      _p.child.send( m );
    }

    if( _p.timer ){
      _p.timer.message( m );
    }

    return this; // chaining
  },

  stop: function(){
    var _p = this._private;

    if( _p.webworker ){
      _p.webworker.terminate();
    }

    if( _p.child ){
      _p.child.kill();
    }

    if( _p.timer ){
      // nothing we can do if we've run a timeout
    }

    _p.stopped = true;

    return this.trigger('stop'); // chaining
  },

  stopped: function(){
    return this._private.stopped;
  }

});

// turns a stringified function into a (re)named function
var fnAs = function( fn, name ){
  var fnStr = fn.toString();
  fnStr = fnStr.replace(/function\s*?\S*?\s*?\(/, 'function ' + name + '(');

  return fnStr;
};

var defineFnal = function( opts ){
  opts = opts || {};

  return function fnalImpl( fn, arg1 ){
    var fnStr = fnAs( fn, '_$_$_' + opts.name );

    this.require( fnStr );

    return this.run( [
      'function( data ){',
      '  var origResolve = resolve;',
      '  var res = [];',
      '  ',
      '  resolve = function( val ){',
      '    res.push( val );',
      '  };',
      '  ',
      '  var ret = data.' + opts.name + '( _$_$_' + opts.name + ( arguments.length > 1 ? ', ' + JSON.stringify(arg1) : '' ) + ' );',
      '  ',
      '  resolve = origResolve;',
      '  resolve( res.length > 0 ? res : ret );',
      '}'
    ].join('\n') );
  };
};

util.extend(thdfn, {
  reduce: defineFnal({ name: 'reduce' }),

  reduceRight: defineFnal({ name: 'reduceRight' }),

  map: defineFnal({ name: 'map' })
});

// aliases
var fn = thdfn;
fn.promise = fn.run;
fn.terminate = fn.halt = fn.stop;
fn.include = fn.require;

// pull in event apis
util.extend(thdfn, {
  on: define.on(),
  one: define.on({ unbindSelfOnTrigger: true }),
  off: define.off(),
  trigger: define.trigger()
});

define.eventAliasesOn( thdfn );

module.exports = Thread;

},{"./define":41,"./event":42,"./is":77,"./promise":80,"./util":94,"./window":100,"child_process":undefined,"path":undefined}],93:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');

module.exports = {
  // get [r, g, b] from #abc or #aabbcc
  hex2tuple: function( hex ){
    if( !(hex.length === 4 || hex.length === 7) || hex[0] !== "#" ){ return; }

    var shortHex = hex.length === 4;
    var r, g, b;
    var base = 16;

    if( shortHex ){
      r = parseInt( hex[1] + hex[1], base );
      g = parseInt( hex[2] + hex[2], base );
      b = parseInt( hex[3] + hex[3], base );
    } else {
      r = parseInt( hex[1] + hex[2], base );
      g = parseInt( hex[3] + hex[4], base );
      b = parseInt( hex[5] + hex[6], base );
    }

    return [r, g, b];
  },

  // get [r, g, b, a] from hsl(0, 0, 0) or hsla(0, 0, 0, 0)
  hsl2tuple: function( hsl ){
    var ret;
    var h, s, l, a, r, g, b;
    function hue2rgb(p, q, t){
      if(t < 0) t += 1;
      if(t > 1) t -= 1;
      if(t < 1/6) return p + (q - p) * 6 * t;
      if(t < 1/2) return q;
      if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
      return p;
    }

    var m = new RegExp("^" + this.regex.hsla + "$").exec(hsl);
    if( m ){

      // get hue
      h = parseInt( m[1] );
      if( h < 0 ){
        h = ( 360 - (-1*h % 360) ) % 360;
      } else if( h > 360 ){
        h = h % 360;
      }
      h /= 360; // normalise on [0, 1]

      s = parseFloat( m[2] );
      if( s < 0 || s > 100 ){ return; } // saturation is [0, 100]
      s = s/100; // normalise on [0, 1]

      l = parseFloat( m[3] );
      if( l < 0 || l > 100 ){ return; } // lightness is [0, 100]
      l = l/100; // normalise on [0, 1]

      a = m[4];
      if( a !== undefined ){
        a = parseFloat( a );

        if( a < 0 || a > 1 ){ return; } // alpha is [0, 1]
      }

      // now, convert to rgb
      // code from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
      if( s === 0 ){
        r = g = b = Math.round(l * 255); // achromatic
      } else {
        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
        r = Math.round( 255 * hue2rgb(p, q, h + 1/3) );
        g = Math.round( 255 * hue2rgb(p, q, h) );
        b = Math.round( 255 * hue2rgb(p, q, h - 1/3) );
      }

      ret = [r, g, b, a];
    }

    return ret;
  },

  // get [r, g, b, a] from rgb(0, 0, 0) or rgba(0, 0, 0, 0)
  rgb2tuple: function( rgb ){
    var ret;

    var m = new RegExp("^" + this.regex.rgba + "$").exec(rgb);
    if( m ){
      ret = [];

      var isPct = [];
      for( var i = 1; i <= 3; i++ ){
        var channel = m[i];

        if( channel[ channel.length - 1 ] === "%" ){
          isPct[i] = true;
        }
        channel = parseFloat( channel );

        if( isPct[i] ){
          channel = channel/100 * 255; // normalise to [0, 255]
        }

        if( channel < 0 || channel > 255 ){ return; } // invalid channel value

        ret.push( Math.floor(channel) );
      }

      var atLeastOneIsPct = isPct[1] || isPct[2] || isPct[3];
      var allArePct = isPct[1] && isPct[2] && isPct[3];
      if( atLeastOneIsPct && !allArePct ){ return; } // must all be percent values if one is

      var alpha = m[4];
      if( alpha !== undefined ){
        alpha = parseFloat( alpha );

        if( alpha < 0 || alpha > 1 ){ return; } // invalid alpha value

        ret.push( alpha );
      }
    }

    return ret;
  },

  colorname2tuple: function( color ){
    return this.colors[ color.toLowerCase() ];
  },

  color2tuple: function( color ){
    return ( is.array(color) ? color : null )
      || this.colorname2tuple(color)
      || this.hex2tuple(color)
      || this.rgb2tuple(color)
      || this.hsl2tuple(color);
  },

  colors: {
    // special colour names
    transparent: [0, 0, 0, 0], // NB alpha === 0

    // regular colours
    aliceblue: [240, 248, 255],
    antiquewhite: [250, 235, 215],
    aqua: [0, 255, 255],
    aquamarine: [127, 255, 212],
    azure: [240, 255, 255],
    beige: [245, 245, 220],
    bisque: [255, 228, 196],
    black: [0, 0, 0],
    blanchedalmond: [255, 235, 205],
    blue: [0, 0, 255],
    blueviolet: [138, 43, 226],
    brown: [165, 42, 42],
    burlywood: [222, 184, 135],
    cadetblue: [95, 158, 160],
    chartreuse: [127, 255, 0],
    chocolate: [210, 105, 30],
    coral: [255, 127, 80],
    cornflowerblue: [100, 149, 237],
    cornsilk: [255, 248, 220],
    crimson: [220, 20, 60],
    cyan: [0, 255, 255],
    darkblue: [0, 0, 139],
    darkcyan: [0, 139, 139],
    darkgoldenrod: [184, 134, 11],
    darkgray: [169, 169, 169],
    darkgreen: [0, 100, 0],
    darkgrey: [169, 169, 169],
    darkkhaki: [189, 183, 107],
    darkmagenta: [139, 0, 139],
    darkolivegreen: [85, 107, 47],
    darkorange: [255, 140, 0],
    darkorchid: [153, 50, 204],
    darkred: [139, 0, 0],
    darksalmon: [233, 150, 122],
    darkseagreen: [143, 188, 143],
    darkslateblue: [72, 61, 139],
    darkslategray: [47, 79, 79],
    darkslategrey: [47, 79, 79],
    darkturquoise: [0, 206, 209],
    darkviolet: [148, 0, 211],
    deeppink: [255, 20, 147],
    deepskyblue: [0, 191, 255],
    dimgray: [105, 105, 105],
    dimgrey: [105, 105, 105],
    dodgerblue: [30, 144, 255],
    firebrick: [178, 34, 34],
    floralwhite: [255, 250, 240],
    forestgreen: [34, 139, 34],
    fuchsia: [255, 0, 255],
    gainsboro: [220, 220, 220],
    ghostwhite: [248, 248, 255],
    gold: [255, 215, 0],
    goldenrod: [218, 165, 32],
    gray: [128, 128, 128],
    grey: [128, 128, 128],
    green: [0, 128, 0],
    greenyellow: [173, 255, 47],
    honeydew: [240, 255, 240],
    hotpink: [255, 105, 180],
    indianred: [205, 92, 92],
    indigo: [75, 0, 130],
    ivory: [255, 255, 240],
    khaki: [240, 230, 140],
    lavender: [230, 230, 250],
    lavenderblush: [255, 240, 245],
    lawngreen: [124, 252, 0],
    lemonchiffon: [255, 250, 205],
    lightblue: [173, 216, 230],
    lightcoral: [240, 128, 128],
    lightcyan: [224, 255, 255],
    lightgoldenrodyellow: [250, 250, 210],
    lightgray: [211, 211, 211],
    lightgreen: [144, 238, 144],
    lightgrey: [211, 211, 211],
    lightpink: [255, 182, 193],
    lightsalmon: [255, 160, 122],
    lightseagreen: [32, 178, 170],
    lightskyblue: [135, 206, 250],
    lightslategray: [119, 136, 153],
    lightslategrey: [119, 136, 153],
    lightsteelblue: [176, 196, 222],
    lightyellow: [255, 255, 224],
    lime: [0, 255, 0],
    limegreen: [50, 205, 50],
    linen: [250, 240, 230],
    magenta: [255, 0, 255],
    maroon: [128, 0, 0],
    mediumaquamarine: [102, 205, 170],
    mediumblue: [0, 0, 205],
    mediumorchid: [186, 85, 211],
    mediumpurple: [147, 112, 219],
    mediumseagreen: [60, 179, 113],
    mediumslateblue: [123, 104, 238],
    mediumspringgreen: [0, 250, 154],
    mediumturquoise: [72, 209, 204],
    mediumvioletred: [199, 21, 133],
    midnightblue: [25, 25, 112],
    mintcream: [245, 255, 250],
    mistyrose: [255, 228, 225],
    moccasin: [255, 228, 181],
    navajowhite: [255, 222, 173],
    navy: [0, 0, 128],
    oldlace: [253, 245, 230],
    olive: [128, 128, 0],
    olivedrab: [107, 142, 35],
    orange: [255, 165, 0],
    orangered: [255, 69, 0],
    orchid: [218, 112, 214],
    palegoldenrod: [238, 232, 170],
    palegreen: [152, 251, 152],
    paleturquoise: [175, 238, 238],
    palevioletred: [219, 112, 147],
    papayawhip: [255, 239, 213],
    peachpuff: [255, 218, 185],
    peru: [205, 133, 63],
    pink: [255, 192, 203],
    plum: [221, 160, 221],
    powderblue: [176, 224, 230],
    purple: [128, 0, 128],
    red: [255, 0, 0],
    rosybrown: [188, 143, 143],
    royalblue: [65, 105, 225],
    saddlebrown: [139, 69, 19],
    salmon: [250, 128, 114],
    sandybrown: [244, 164, 96],
    seagreen: [46, 139, 87],
    seashell: [255, 245, 238],
    sienna: [160, 82, 45],
    silver: [192, 192, 192],
    skyblue: [135, 206, 235],
    slateblue: [106, 90, 205],
    slategray: [112, 128, 144],
    slategrey: [112, 128, 144],
    snow: [255, 250, 250],
    springgreen: [0, 255, 127],
    steelblue: [70, 130, 180],
    tan: [210, 180, 140],
    teal: [0, 128, 128],
    thistle: [216, 191, 216],
    tomato: [255, 99, 71],
    turquoise: [64, 224, 208],
    violet: [238, 130, 238],
    wheat: [245, 222, 179],
    white: [255, 255, 255],
    whitesmoke: [245, 245, 245],
    yellow: [255, 255, 0],
    yellowgreen: [154, 205, 50]
  }
};

},{"../is":77}],94:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');
var math = _dereq_('../math');

var util = {

  falsify: function(){ return false; },

  zeroify: function(){ return 0; },

  noop: function(){},

  /* jshint ignore:start */
  error: function( msg ){
    if( console.error ){
      console.error.apply( console, arguments );

      if( console.trace ){ console.trace(); }
    } else {
      console.log.apply( console, arguments );

      if( console.trace ){ console.trace(); }
    }
  },
  /* jshint ignore:end */

  clone: function( obj ){
    return this.extend( {}, obj );
  },

  // gets a shallow copy of the argument
  copy: function( obj ){
    if( obj == null ){
      return obj;
    } if( is.array(obj) ){
      return obj.slice();
    } else if( is.plainObject(obj) ){
      return this.clone( obj );
    } else {
      return obj;
    }
  }

};

util.makeBoundingBox = math.makeBoundingBox.bind( math );

util._staticEmptyObject = {};

util.staticEmptyObject = function(){
  return util._staticEmptyObject;
};

util.extend = Object.assign != null ? Object.assign : function( tgt ){
  var args = arguments;

  for( var i = 1; i < args.length; i++ ){
    var obj = args[i];

    for( var k in obj ){
      tgt[k] = obj[k];
    }
  }

  return tgt;
};

[
  _dereq_('./colors'),
  _dereq_('./maps'),
  { memoize: _dereq_('./memoize') },
  _dereq_('./regex'),
  _dereq_('./strings'),
  _dereq_('./timing')
].forEach(function( req ){
  util.extend( util, req );
});

module.exports = util;

},{"../is":77,"../math":79,"./colors":93,"./maps":95,"./memoize":96,"./regex":97,"./strings":98,"./timing":99}],95:[function(_dereq_,module,exports){
'use strict';

var is = _dereq_('../is');

module.exports = {
  // has anything been set in the map
  mapEmpty: function( map ){
    var empty = true;

    if( map != null ){
      for(var i in map){ // jshint ignore:line
        empty = false;
        break;
      }
    }

    return empty;
  },

  // pushes to the array at the end of a map (map may not be built)
  pushMap: function( options ){
    var array = this.getMap(options);

    if( array == null ){ // if empty, put initial array
      this.setMap( this.extend({}, options, {
        value: [ options.value ]
      }) );
    } else {
      array.push( options.value );
    }
  },

  // sets the value in a map (map may not be built)
  setMap: function( options ){
    var obj = options.map;
    var key;
    var keys = options.keys;
    var l = keys.length;

    for(var i = 0; i < l; i++){
      var key = keys[i];

      if( is.plainObject( key ) ){
        this.error('Tried to set map with object key');
      }

      if( i < keys.length - 1 ){

        // extend the map if necessary
        if( obj[key] == null ){
          obj[key] = {};
        }

        obj = obj[key];
      } else {
        // set the value
        obj[key] = options.value;
      }
    }
  },

  // gets the value in a map even if it's not built in places
  getMap: function( options ){
    var obj = options.map;
    var keys = options.keys;
    var l = keys.length;

    for(var i = 0; i < l; i++){
      var key = keys[i];

      if( is.plainObject( key ) ){
        this.error('Tried to get map with object key');
      }

      obj = obj[key];

      if( obj == null ){
        return obj;
      }
    }

    return obj;
  },

  // deletes the entry in the map
  deleteMap: function( options ){
    var obj = options.map;
    var keys = options.keys;
    var l = keys.length;
    var keepChildren = options.keepChildren;

    for(var i = 0; i < l; i++){
      var key = keys[i];

      if( is.plainObject( key ) ){
        this.error('Tried to delete map with object key');
      }

      var lastKey = i === options.keys.length - 1;
      if( lastKey ){

        if( keepChildren ){ // then only delete child fields not in keepChildren
          for( var child in obj ){
            if( !keepChildren[child] ){
              obj[child] = undefined;
            }
          }
        } else {
          obj[key] = undefined;
        }

      } else {
        obj = obj[key];
      }
    }
  }
};

},{"../is":77}],96:[function(_dereq_,module,exports){
'use strict';

module.exports = function memoize( fn, keyFn ){
  var self = this;
  var cache = {};

  if( !keyFn ){
    keyFn = function(){
      if( arguments.length === 1 ){
        return arguments[0];
      }

      var args = [];

      for( var i = 0; i < arguments.length; i++ ){
        args.push( arguments[i] );
      }

      return args.join('$');
    };
  }

  return function memoizedFn(){
    var args = arguments;
    var ret;
    var k = keyFn.apply( self, args );

    if( !(ret = cache[k]) ){
      ret = cache[k] = fn.apply( self, args );
    }

    return ret;
  };
};

},{}],97:[function(_dereq_,module,exports){
'use strict';

var number = "(?:[-+]?(?:(?:\\d+|\\d*\\.\\d+)(?:[Ee][+-]?\\d+)?))";

var rgba = "rgb[a]?\\(("+ number +"[%]?)\\s*,\\s*("+ number +"[%]?)\\s*,\\s*("+ number +"[%]?)(?:\\s*,\\s*("+ number +"))?\\)";
var rgbaNoBackRefs = "rgb[a]?\\((?:"+ number +"[%]?)\\s*,\\s*(?:"+ number +"[%]?)\\s*,\\s*(?:"+ number +"[%]?)(?:\\s*,\\s*(?:"+ number +"))?\\)";

var hsla = "hsl[a]?\\(("+ number +")\\s*,\\s*("+ number +"[%])\\s*,\\s*("+ number +"[%])(?:\\s*,\\s*("+ number +"))?\\)";
var hslaNoBackRefs = "hsl[a]?\\((?:"+ number +")\\s*,\\s*(?:"+ number +"[%])\\s*,\\s*(?:"+ number +"[%])(?:\\s*,\\s*(?:"+ number +"))?\\)";

var hex3 = "\\#[0-9a-fA-F]{3}";
var hex6 = "\\#[0-9a-fA-F]{6}";

module.exports = {
  regex: {
    number: number,
    rgba: rgba,
    rgbaNoBackRefs: rgbaNoBackRefs,
    hsla: hsla,
    hslaNoBackRefs: hslaNoBackRefs,
    hex3: hex3,
    hex6: hex6
  }
};

},{}],98:[function(_dereq_,module,exports){
'use strict';

var memoize = _dereq_('./memoize');
var is = _dereq_('../is');

module.exports = {

  camel2dash: memoize( function( str ){
    return str.replace(/([A-Z])/g, function( v ){
      return '-' + v.toLowerCase();
    });
  } ),

  dash2camel: memoize( function( str ){
    return str.replace(/(-\w)/g, function( v ){
      return v[1].toUpperCase();
    });
  } ),

  capitalize: function(str){
    if( is.emptyString(str) ){
      return str;
    }

    return str.charAt(0).toUpperCase() + str.substring(1);
  }

};

},{"../is":77,"./memoize":96}],99:[function(_dereq_,module,exports){
'use strict';

var window = _dereq_('../window');
var is = _dereq_('../is');
var performance = window ? window.performance : null;

var util = {};

var raf = !window ? null : ( window.requestAnimationFrame || window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame || window.msRequestAnimationFrame );

raf = raf || function( fn ){
  if( fn ){
    setTimeout(function(){
      fn( pnow() );
    }, 1000/60);
  }
};

util.requestAnimationFrame = function(fn){
  raf( fn );
};

var pnow = performance && performance.now ? function(){ return performance.now(); } : function(){ return Date.now(); };

util.performanceNow = pnow;

// ported lodash throttle function
util.throttle = function(func, wait, options) {
  var leading = true,
      trailing = true;

  if (options === false) {
    leading = false;
  } else if (is.plainObject(options)) {
    leading = 'leading' in options ? options.leading : leading;
    trailing = 'trailing' in options ? options.trailing : trailing;
  }
  options = options || {};
  options.leading = leading;
  options.maxWait = wait;
  options.trailing = trailing;

  return util.debounce(func, wait, options);
};

util.now = function(){
  return Date.now();
};

util.debounce = function(func, wait, options) { // ported lodash debounce function
  var util = this;
  var args,
      maxTimeoutId,
      result,
      stamp,
      thisArg,
      timeoutId,
      trailingCall,
      lastCalled = 0,
      maxWait = false,
      trailing = true;

  if (!is.fn(func)) {
    return;
  }
  wait = Math.max(0, wait) || 0;
  if (options === true) {
    var leading = true;
    trailing = false;
  } else if (is.plainObject(options)) {
    leading = options.leading;
    maxWait = 'maxWait' in options && (Math.max(wait, options.maxWait) || 0);
    trailing = 'trailing' in options ? options.trailing : trailing;
  }
  var delayed = function() {
    var remaining = wait - (util.now() - stamp);
    if (remaining <= 0) {
      if (maxTimeoutId) {
        clearTimeout(maxTimeoutId);
      }
      var isCalled = trailingCall;
      maxTimeoutId = timeoutId = trailingCall = undefined;
      if (isCalled) {
        lastCalled = util.now();
        result = func.apply(thisArg, args);
        if (!timeoutId && !maxTimeoutId) {
          args = thisArg = null;
        }
      }
    } else {
      timeoutId = setTimeout(delayed, remaining);
    }
  };

  var maxDelayed = function() {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    maxTimeoutId = timeoutId = trailingCall = undefined;
    if (trailing || (maxWait !== wait)) {
      lastCalled = util.now();
      result = func.apply(thisArg, args);
      if (!timeoutId && !maxTimeoutId) {
        args = thisArg = null;
      }
    }
  };

  return function() {
    args = arguments;
    stamp = util.now();
    thisArg = this;
    trailingCall = trailing && (timeoutId || !leading);

    if (maxWait === false) {
      var leadingCall = leading && !timeoutId;
    } else {
      if (!maxTimeoutId && !leading) {
        lastCalled = stamp;
      }
      var remaining = maxWait - (stamp - lastCalled),
          isCalled = remaining <= 0;

      if (isCalled) {
        if (maxTimeoutId) {
          maxTimeoutId = clearTimeout(maxTimeoutId);
        }
        lastCalled = stamp;
        result = func.apply(thisArg, args);
      }
      else if (!maxTimeoutId) {
        maxTimeoutId = setTimeout(maxDelayed, remaining);
      }
    }
    if (isCalled && timeoutId) {
      timeoutId = clearTimeout(timeoutId);
    }
    else if (!timeoutId && wait !== maxWait) {
      timeoutId = setTimeout(delayed, wait);
    }
    if (leadingCall) {
      isCalled = true;
      result = func.apply(thisArg, args);
    }
    if (isCalled && !timeoutId && !maxTimeoutId) {
      args = thisArg = null;
    }
    return result;
  };
};

module.exports = util;

},{"../is":77,"../window":100}],100:[function(_dereq_,module,exports){
module.exports = ( typeof window === 'undefined' ? null : window );

},{}]},{},[76])(76)
});


//# sourceMappingURL=cytoscape.js.map




© 2015 - 2025 Weber Informatics LLC | Privacy Policy