diff --git a/ajax/libs/cytoscape/2.2.9/cytoscape.js b/ajax/libs/cytoscape/2.2.9/cytoscape.js
new file mode 100644
index 000000000..cc872d4f1
--- /dev/null
+++ b/ajax/libs/cytoscape/2.2.9/cytoscape.js
@@ -0,0 +1,19397 @@
+/*!
+ * This file is part of cytoscape.js 2.2.9.
+ *
+ * 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 .
+ */
+
+
+// this is put as a global var in the browser
+// or it's just a global to this module if commonjs
+
+var cytoscape;
+
+(function(window){ 'use strict';
+
+ // the object iteself is a function that init's an instance of cytoscape
+
+ var $$ = cytoscape = function(){ // jshint ignore:line
+ return cytoscape.init.apply(cytoscape, arguments);
+ };
+
+ // allow functional access to cytoscape.js
+ // e.g. var cyto = $.cytoscape({ selector: "#foo", ... });
+ // var nodes = cyto.nodes();
+ $$.init = function( options ){
+
+ // 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
+ // e.g. $.cytoscape('renderer', 'svg', SvgRenderer);
+ // e.g. $.cytoscape('renderer', 'svg', 'nodeshape', 'ellipse', SvgEllipseNodeShape);
+ // e.g. $.cytoscape('core', 'doSomething', function(){ /* doSomething code */ });
+ // e.g. $.cytoscape('collection', 'doSomething', function(){ /* doSomething code */ });
+ else if( $$.is.string( options ) ) {
+ return $$.extension.apply($$.extension, arguments);
+ }
+ };
+
+ // define the function namespace here, since it has members in many places
+ $$.fn = {};
+
+ if( typeof exports !== 'undefined' ){ // expose as a commonjs module
+ exports = module.exports = cytoscape;
+ }
+
+ if( typeof define !== 'undefined' ){ // expose as an amd/requirejs module
+ define('cytoscape', function(){
+ return cytoscape;
+ });
+ }
+
+ // make sure we always register in the window just in case (e.g. w/ derbyjs)
+ if( window ){
+ window.cytoscape = cytoscape;
+ }
+
+})( typeof window === 'undefined' ? null : window );
+
+// type testing utility functions
+
+;(function($$, window){ 'use strict';
+
+ $$.is = {
+ string: function(obj){
+ return obj != null && typeof obj == typeof '';
+ },
+
+ fn: function(obj){
+ return obj != null && typeof obj === typeof function(){};
+ },
+
+ array: function(obj){
+ return obj != null && obj instanceof Array;
+ },
+
+ plainObject: function(obj){
+ return obj != null && typeof obj === typeof {} && !$$.is.array(obj) && obj.constructor === Object;
+ },
+
+ number: function(obj){
+ return obj != null && typeof obj === typeof 1 && !isNaN(obj);
+ },
+
+ integer: function( obj ){
+ return $$.is.number(obj) && Math.floor(obj) === obj;
+ },
+
+ color: function(obj){
+ return obj != null && typeof obj === typeof '' && $.Color(obj).toString() !== '';
+ },
+
+ bool: function(obj){
+ return obj != null && typeof obj === typeof true;
+ },
+
+ elementOrCollection: function(obj){
+ return $$.is.element(obj) || $$.is.collection(obj);
+ },
+
+ element: function(obj){
+ return obj instanceof $$.Element && obj._private.single;
+ },
+
+ collection: function(obj){
+ return obj instanceof $$.Collection && !obj._private.single;
+ },
+
+ core: function(obj){
+ return obj instanceof $$.Core;
+ },
+
+ style: function(obj){
+ return obj instanceof $$.Style;
+ },
+
+ stylesheet: function(obj){
+ return obj instanceof $$.Stylesheet;
+ },
+
+ event: function(obj){
+ return obj instanceof $$.Event;
+ },
+
+ 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;
+ }
+
+
+ },
+
+ touch: function(){
+ return window && ( ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch );
+ }
+ };
+
+})( cytoscape, typeof window === 'undefined' ? null : window );
+
+;(function($$, window){ 'use strict';
+
+ // utility functions only for internal use
+
+ $$.util = {
+
+ // the jquery extend() function
+ // NB: modified to use $$.is etc since we can't use jquery functions
+ extend: function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === 'boolean' ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== 'object' && !$$.is.fn(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( $$.is.plainObject(copy) || (copyIsArray = $$.is.array(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && $$.is.array(src) ? src : [];
+
+ } else {
+ clone = src && $$.is.plainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = $$.util.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+ },
+
+ // ported lodash throttle function
+ 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);
+ },
+
+ now: function(){
+ return +new Date();
+ },
+
+ // ported lodash debounce function
+ debounce: function(func, wait, options) {
+ 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;
+ };
+ },
+
+ error: function( msg ){
+ if( console ){
+ if( console.error ){
+ console.error( msg );
+ } else if( console.log ){
+ console.log( msg );
+ } else {
+ throw msg;
+ }
+ } else {
+ throw msg;
+ }
+ },
+
+ clone: function( obj ){
+ var target = {};
+ for (var i in obj) {
+ if ( obj.hasOwnProperty(i) ) { // TODO is this hasOwnProperty() call necessary for our use?
+ target[i] = obj[i];
+ }
+ }
+ return target;
+ },
+
+ // 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 $$.util.clone( obj );
+ } else {
+ return obj;
+ }
+ },
+
+ // 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 = $$.util.getMap(options);
+
+ if( array == null ){ // if empty, put initial array
+ $$.util.setMap( $.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 ) ){
+ $$.util.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 ) ){
+ $$.util.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 ) ){
+ $$.util.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] ){
+ delete obj[child];
+ }
+ }
+ } else {
+ delete obj[key];
+ }
+
+ } else {
+ obj = obj[key];
+ }
+ }
+ },
+
+ capitalize: function(str){
+ if( $$.is.emptyString(str) ){
+ return str;
+ }
+
+ return str.charAt(0).toUpperCase() + str.substring(1);
+ },
+
+ camel2dash: function( str ){
+ var ret = [];
+
+ for( var i = 0; i < str.length; i++ ){
+ var ch = str[i];
+ var chLowerCase = ch.toLowerCase();
+ var isUpperCase = ch !== chLowerCase;
+
+ if( isUpperCase ){
+ ret.push( '-' );
+ ret.push( chLowerCase );
+ } else {
+ ret.push( ch );
+ }
+ }
+
+ var noUpperCases = ret.length === str.length;
+ if( noUpperCases ){ return str; } // cheaper than .join()
+
+ return ret.join('');
+ },
+
+ dash2camel: function( str ){
+ var ret = [];
+ var nextIsUpper = false;
+
+ for( var i = 0; i < str.length; i++ ){
+ var ch = str[i];
+ var isDash = ch === '-';
+
+ if( isDash ){
+ nextIsUpper = true;
+ } else {
+ if( nextIsUpper ){
+ ret.push( ch.toUpperCase() );
+ } else {
+ ret.push( ch );
+ }
+
+ nextIsUpper = false;
+ }
+ }
+
+ return ret.join('');
+ },
+
+ // strip spaces from beginning of string and end of string
+ trim: function( str ){
+ var first, last;
+
+ // find first non-space char
+ for( first = 0; first < str.length && str[first] === ' '; first++ ){}
+
+ // find last non-space char
+ for( last = str.length - 1; last > first && str[last] === ' '; last-- ){}
+
+ return str.substring(first, last + 1);
+ },
+
+ // 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("^" + $$.util.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("^" + $$.util.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 $$.util.colors[ color.toLowerCase() ];
+ },
+
+ color2tuple: function( color ){
+ return $$.util.colorname2tuple(color)
+ || $$.util.hex2tuple(color)
+ || $$.util.rgb2tuple(color)
+ || $$.util.hsl2tuple(color);
+ },
+
+ tuple2hex: function( tuple ){
+ var r = tuple[0];
+ var g = tuple[1];
+ var b = tuple[2];
+
+ function ch2hex( ch ){
+ var hex = ch.toString(16);
+
+ if( hex.length === 1 ){
+ hex = '0' + hex;
+ }
+
+ return hex;
+ }
+
+ return '#' + ch2hex(r) + ch2hex(g) + ch2hex(b);
+ },
+
+ 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]
+ }
+
+ };
+
+ $$.util.regex = {};
+
+ $$.util.regex.number = "(?:[-]?\\d*\\.\\d+|[-]?\\d+|[-]?\\d*\\.\\d+[eE]\\d+)";
+
+ $$.util.regex.rgba = "rgb[a]?\\(("+ $$.util.regex.number +"[%]?)\\s*,\\s*("+ $$.util.regex.number +"[%]?)\\s*,\\s*("+ $$.util.regex.number +"[%]?)(?:\\s*,\\s*("+ $$.util.regex.number +"))?\\)";
+ $$.util.regex.rgbaNoBackRefs = "rgb[a]?\\((?:"+ $$.util.regex.number +"[%]?)\\s*,\\s*(?:"+ $$.util.regex.number +"[%]?)\\s*,\\s*(?:"+ $$.util.regex.number +"[%]?)(?:\\s*,\\s*(?:"+ $$.util.regex.number +"))?\\)";
+
+ $$.util.regex.hsla = "hsl[a]?\\(("+ $$.util.regex.number +")\\s*,\\s*("+ $$.util.regex.number +"[%])\\s*,\\s*("+ $$.util.regex.number +"[%])(?:\\s*,\\s*("+ $$.util.regex.number +"))?\\)";
+ $$.util.regex.hslaNoBackRefs = "hsl[a]?\\((?:"+ $$.util.regex.number +")\\s*,\\s*(?:"+ $$.util.regex.number +"[%])\\s*,\\s*(?:"+ $$.util.regex.number +"[%])(?:\\s*,\\s*(?:"+ $$.util.regex.number +"))?\\)";
+
+ $$.util.regex.hex3 = "\\#[0-9a-fA-F]{3}";
+ $$.util.regex.hex6 = "\\#[0-9a-fA-F]{6}";
+
+ var raf = !window ? null : ( window.requestAnimationFrame || window.mozRequestAnimationFrame ||
+ window.webkitRequestAnimationFrame || window.msRequestAnimationFrame );
+
+ raf = raf || function(fn){ if(fn){ setTimeout(fn, 1000/60) } };
+
+ $$.util.requestAnimationFrame = function(fn){
+ raf( fn );
+ };
+
+})( cytoscape, typeof window === 'undefined' ? null : window );
+
+;(function($$){ 'use strict';
+
+ $$.math = {};
+
+ $$.math.signum = function(x){
+ if( x > 0 ){
+ return 1;
+ } else if( x < 0 ){
+ return -1;
+ } else {
+ return 0;
+ }
+ };
+
+ $$.math.distance = function( p1, p2 ){
+ var dx = p2.x - p1.x;
+ var dy = p2.y - p1.y;
+
+ return Math.sqrt( 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 )
+ };
+ };
+
+ $$.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.roundRectangleIntersectBox = function(
+ boxX1, boxY1, boxX2, boxY2, width, height, centerX, centerY, padding) {
+
+ // We have the following shpae
+
+ // _____
+ // _| |_
+ // | |
+ // |_ _|
+ // |_____|
+ //
+ // With a quarter circle at each corner.
+
+ var cornerRadius = this.getRoundRectangleRadius(width, height);
+
+ var hBoxTopLeftX = centerX - width / 2 - padding;
+ var hBoxTopLeftY = centerY - height / 2 + cornerRadius - padding;
+ var hBoxBottomRightX = centerX + width / 2 + padding;
+ var hBoxBottomRightY = centerY + height / 2 - cornerRadius + padding;
+
+ var vBoxTopLeftX = centerX - width / 2 + cornerRadius - padding;
+ var vBoxTopLeftY = centerY - height / 2 - padding;
+ var vBoxBottomRightX = centerX + width / 2 - cornerRadius + padding;
+ var vBoxBottomRightY = centerY + height / 2 + padding;
+
+ // Check if the box is out of bounds
+ var boxMinX = Math.min(boxX1, boxX2);
+ var boxMaxX = Math.max(boxX1, boxX2);
+ var boxMinY = Math.min(boxY1, boxY2);
+ var boxMaxY = Math.max(boxY1, boxY2);
+
+ if (boxMaxX < hBoxTopLeftX) {
+ return false;
+ } else if (boxMinX > hBoxBottomRightX) {
+ return false;
+ }
+
+ if (boxMaxY < vBoxTopLeftY) {
+ return false;
+ } else if (boxMinY > vBoxBottomRightY) {
+ return false;
+ }
+
+ // Check if an hBox point is in given box
+ if (hBoxTopLeftX >= boxMinX && hBoxTopLeftX <= boxMaxX
+ && hBoxTopLeftY >= boxMinY && hBoxTopLeftY <= boxMaxY) {
+ return true;
+ }
+
+ if (hBoxBottomRightX >= boxMinX && hBoxBottomRightX <= boxMaxX
+ && hBoxTopLeftY >= boxMinY && hBoxTopLeftY <= boxMaxY) {
+ return true;
+ }
+
+ if (hBoxBottomRightX >= boxMinX && hBoxBottomRightX <= boxMaxX
+ && hBoxBottomRightY >= boxMinY && hBoxBottomRightY <= boxMaxY) {
+ return true;
+ }
+
+ if (hBoxTopLeftX >= boxMinX && hBoxTopLeftX <= boxMaxX
+ && hBoxBottomRightY >= boxMinY && hBoxBottomRightY <= boxMaxY) {
+ return true;
+ }
+
+ // Check if a given point box is in the hBox
+ if (boxMinX >= hBoxTopLeftX && boxMinX <= hBoxBottomRightX
+ && boxMinY >= hBoxTopLeftY && boxMinY <= hBoxBottomRightY) {
+ return true;
+ }
+
+ if (boxMaxX >= hBoxTopLeftX && boxMaxX <= hBoxBottomRightX
+ && boxMinY >= hBoxTopLeftY && boxMinY <= hBoxBottomRightY) {
+ return true;
+ }
+
+ if (boxMaxX >= hBoxTopLeftX && boxMaxX <= hBoxBottomRightX
+ && boxMaxY >= hBoxTopLeftY && boxMaxY <= hBoxBottomRightY) {
+ return true;
+ }
+
+ if (boxMinX >= hBoxTopLeftX && boxMinX <= hBoxBottomRightX
+ && boxMaxY >= hBoxTopLeftY && boxMaxY <= hBoxBottomRightY) {
+ return true;
+ }
+
+ // Check if an vBox point is in given box
+ if (vBoxTopLeftX >= boxMinX && vBoxTopLeftX <= boxMaxX
+ && vBoxTopLeftY >= boxMinY && vBoxTopLeftY <= boxMaxY) {
+ return true;
+ }
+
+ if (vBoxBottomRightX >= boxMinX && vBoxBottomRightX <= boxMaxX
+ && vBoxTopLeftY >= boxMinY && vBoxTopLeftY <= boxMaxY) {
+ return true;
+ }
+
+ if (vBoxBottomRightX >= boxMinX && vBoxBottomRightX <= boxMaxX
+ && vBoxBottomRightY >= boxMinY && vBoxBottomRightY <= boxMaxY) {
+ return true;
+ }
+
+ if (vBoxTopLeftX >= boxMinX && vBoxTopLeftX <= boxMaxX
+ && vBoxBottomRightY >= boxMinY && vBoxBottomRightY <= boxMaxY) {
+ return true;
+ }
+
+ // Check if a given point box is in the vBox
+ if (boxMinX >= vBoxTopLeftX && boxMinX <= vBoxBottomRightX
+ && boxMinY >= vBoxTopLeftY && boxMinY <= vBoxBottomRightY) {
+ return true;
+ }
+
+ if (boxMaxX >= vBoxTopLeftX && boxMaxX <= vBoxBottomRightX
+ && boxMinY >= vBoxTopLeftY && boxMinY <= vBoxBottomRightY) {
+ return true;
+ }
+
+ if (boxMaxX >= vBoxTopLeftX && boxMaxX <= vBoxBottomRightX
+ && boxMaxY >= vBoxTopLeftY && boxMaxY <= vBoxBottomRightY) {
+ return true;
+ }
+
+ if (boxMinX >= vBoxTopLeftX && boxMinX <= vBoxBottomRightX
+ && boxMaxY >= vBoxTopLeftY && boxMaxY <= vBoxBottomRightY) {
+ return true;
+ }
+
+ // Lastly, check if one of the ellipses coincide with the box
+
+ if (this.boxIntersectEllipse(boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
+ cornerRadius * 2, cornerRadius * 2, vBoxTopLeftX + padding, hBoxTopLeftY + padding)) {
+ return true;
+ }
+
+ if (this.boxIntersectEllipse(boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
+ cornerRadius * 2, cornerRadius * 2, vBoxBottomRightX - padding, hBoxTopLeftY + padding)) {
+ return true;
+ }
+
+ if (this.boxIntersectEllipse(boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
+ cornerRadius * 2, cornerRadius * 2, vBoxBottomRightX - padding, hBoxBottomRightY - padding)) {
+ return true;
+ }
+
+ if (this.boxIntersectEllipse(boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
+ cornerRadius * 2, cornerRadius * 2, vBoxTopLeftX + padding, hBoxBottomRightY - padding)) {
+ return true;
+ }
+
+ return false;
+ };
+
+ // @O Approximate collision functions
+ $$.math.checkInBoundingCircle = function(
+ x, y, farthestPointSqDistance, padding, width, height, centerX, centerY) {
+
+ x = (x - centerX) / (width + padding);
+ y = (y - centerY) / (height + padding);
+
+ return (x * x + y * y) <= farthestPointSqDistance;
+ };
+
+ $$.math.checkInBoundingBox = function(
+ x, y, points, padding, width, height, centerX, centerY) {
+
+ // Assumes width, height >= 0, points.length > 0
+
+ var minX = points[0], minY = points[1];
+ var maxX = points[0], maxY = points[1];
+
+ for (var i = 1; i < points.length / 2; i++) {
+
+ if (points[i * 2] < minX) {
+ minX = points[i * 2];
+ } else if (points[i * 2] > maxX) {
+ maxX = points[i * 2];
+ }
+
+ if (points[i * 2 + 1] < minY) {
+ minY = points[i * 2 + 1];
+ } else if (points[i * 2 + 1] > maxY) {
+ maxY = points[i * 2 + 1];
+ }
+ }
+
+ x -= centerX;
+ y -= centerY;
+
+ x /= width;
+ y /= height;
+
+ if (x < minX) {
+ return false;
+ } else if (x > maxX) {
+ return false;
+ }
+
+ if (y < minY) {
+ return false;
+ } else if (y > maxY) {
+ return false;
+ }
+
+ return true;
+ };
+
+ $$.math.boxInBezierVicinity = function(
+ x1box, y1box, x2box, y2box, x1, y1, x2, y2, x3, y3, tolerance) {
+
+ // Return values:
+ // 0 - curve is not in box
+ // 1 - curve may be in box; needs precise check
+ // 2 - curve is in box
+
+ // midpoint
+ var midX = 0.25 * x1 + 0.5 * x2 + 0.25 * x3;
+ var midY = 0.25 * y1 + 0.5 * y2 + 0.25 * y3;
+
+ var boxMinX = Math.min(x1box, x2box) - tolerance;
+ var boxMinY = Math.min(y1box, y2box) - tolerance;
+ var boxMaxX = Math.max(x1box, x2box) + tolerance;
+ var boxMaxY = Math.max(y1box, y2box) + tolerance;
+
+ if (x1 >= boxMinX && x1 <= boxMaxX && y1 >= boxMinY && y1 <= boxMaxY) { // (x1, y1) in box
+ return 1;
+ } else if (x3 >= boxMinX && x3 <= boxMaxX && y3 >= boxMinY && y3 <= boxMaxY) { // (x3, y3) in box
+ return 1;
+ } else if (midX >= boxMinX && midX <= boxMaxX && midY >= boxMinY && midY <= boxMaxY) { // (midX, midY) in box
+ return 1;
+ } else if (x2 >= boxMinX && x2 <= boxMaxX && y2 >= boxMinY && y2 <= boxMaxY) { // ctrl pt in box
+ return 1;
+ }
+
+ var curveMinX = Math.min(x1, midX, x3);
+ var curveMinY = Math.min(y1, midY, y3);
+ var curveMaxX = Math.max(x1, midX, x3);
+ var curveMaxY = Math.max(y1, midY, y3);
+
+ /*
+ console.log(curveMinX + ", " + curveMinY + ", " + curveMaxX
+ + ", " + curveMaxY);
+ if (curveMinX == undefined) {
+ console.log("undefined curveMinX: " + x1 + ", " + x2 + ", " + x3);
+ }
+ */
+
+ if (curveMinX > boxMaxX
+ || curveMaxX < boxMinX
+ || curveMinY > boxMaxY
+ || curveMaxY < boxMinY) {
+
+ return 0;
+ }
+
+ return 1;
+ };
+
+ $$.math.checkBezierInBox = function(
+ x1box, y1box, x2box, y2box, x1, y1, x2, y2, x3, y3, tolerance) {
+
+ function sampleInBox(t){
+ var x = $$.math.qbezierAt(x1, x2, x3, t);
+ var y = $$.math.qbezierAt(y1, y2, y3, t);
+
+ return x1box <= x && x <= x2box
+ && y1box <= y && y <= y2box
+ ;
+ }
+
+ for( var t = 0; t <= 1; t += 0.25 ){
+ if( !sampleInBox(t) ){
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ $$.math.checkStraightEdgeInBox = function(
+ x1box, y1box, x2box, y2box, x1, y1, x2, y2, tolerance) {
+
+ return x1box <= x1 && x1 <= x2box
+ && x1box <= x2 && x2 <= x2box
+ && y1box <= y1 && y1 <= y2box
+ && y1box <= y2 && y2 <= y2box
+ ;
+ };
+
+ $$.math.checkStraightEdgeCrossesBox = function(
+ x1box, y1box, x2box, y2box, x1, y1, x2, y2, tolerance) {
+
+ //console.log(arguments);
+
+ var boxMinX = Math.min(x1box, x2box) - tolerance;
+ var boxMinY = Math.min(y1box, y2box) - tolerance;
+ var boxMaxX = Math.max(x1box, x2box) + tolerance;
+ var boxMaxY = Math.max(y1box, y2box) + tolerance;
+
+ // Check left + right bounds
+ var aX = x2 - x1;
+ var bX = x1;
+ var yValue;
+
+ // Top and bottom
+ var aY = y2 - y1;
+ var bY = y1;
+ var xValue;
+
+ if (Math.abs(aX) < 0.0001) {
+ return (x1 >= boxMinX && x1 <= boxMaxX
+ && Math.min(y1, y2) <= boxMinY
+ && Math.max(y1, y2) >= boxMaxY);
+ }
+
+ var tLeft = (boxMinX - bX) / aX;
+ if (tLeft > 0 && tLeft <= 1) {
+ yValue = aY * tLeft + bY;
+ if (yValue >= boxMinY && yValue <= boxMaxY) {
+ return true;
+ }
+ }
+
+ var tRight = (boxMaxX - bX) / aX;
+ if (tRight > 0 && tRight <= 1) {
+ yValue = aY * tRight + bY;
+ if (yValue >= boxMinY && yValue <= boxMaxY) {
+ return true;
+ }
+ }
+
+ var tTop = (boxMinY - bY) / aY;
+ if (tTop > 0 && tTop <= 1) {
+ xValue = aX * tTop + bX;
+ if (xValue >= boxMinX && xValue <= boxMaxX) {
+ return true;
+ }
+ }
+
+ var tBottom = (boxMaxY - bY) / aY;
+ if (tBottom > 0 && tBottom <= 1) {
+ xValue = aX * tBottom + bX;
+ if (xValue >= boxMinX && xValue <= boxMaxX) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ $$.math.checkBezierCrossesBox = function(
+ x1box, y1box, x2box, y2box, x1, y1, x2, y2, x3, y3, tolerance) {
+
+ var boxMinX = Math.min(x1box, x2box) - tolerance;
+ var boxMinY = Math.min(y1box, y2box) - tolerance;
+ var boxMaxX = Math.max(x1box, x2box) + tolerance;
+ var boxMaxY = Math.max(y1box, y2box) + tolerance;
+
+ if (x1 >= boxMinX && x1 <= boxMaxX && y1 >= boxMinY && y1 <= boxMaxY) {
+ return true;
+ } else if (x3 >= boxMinX && x3 <= boxMaxX && y3 >= boxMinY && y3 <= boxMaxY) {
+ return true;
+ }
+
+ var aX = x1 - 2 * x2 + x3;
+ var bX = -2 * x1 + 2 * x2;
+ var cX = x1;
+
+ var xIntervals = [];
+
+ if (Math.abs(aX) < 0.0001) {
+ var leftParam = (boxMinX - x1) / bX;
+ var rightParam = (boxMaxX - x1) / bX;
+
+ xIntervals.push(leftParam, rightParam);
+ } else {
+ // Find when x coordinate of the curve crosses the left side of the box
+ var discriminantX1 = bX * bX - 4 * aX * (cX - boxMinX);
+ var tX1, tX2;
+ if (discriminantX1 > 0) {
+ var sqrt = Math.sqrt(discriminantX1);
+ tX1 = (-bX + sqrt) / (2 * aX);
+ tX2 = (-bX - sqrt) / (2 * aX);
+
+ xIntervals.push(tX1, tX2);
+ }
+
+ var discriminantX2 = bX * bX - 4 * aX * (cX - boxMaxX);
+ var tX3, tX4;
+ if (discriminantX2 > 0) {
+ var sqrt = Math.sqrt(discriminantX2);
+ tX3 = (-bX + sqrt) / (2 * aX);
+ tX4 = (-bX - sqrt) / (2 * aX);
+
+ xIntervals.push(tX3, tX4);
+ }
+ }
+
+ xIntervals.sort(function(a, b) { return a - b; });
+
+ var aY = y1 - 2 * y2 + y3;
+ var bY = -2 * y1 + 2 * y2;
+ var cY = y1;
+
+ var yIntervals = [];
+
+ if (Math.abs(aY) < 0.0001) {
+ var topParam = (boxMinY - y1) / bY;
+ var bottomParam = (boxMaxY - y1) / bY;
+
+ yIntervals.push(topParam, bottomParam);
+ } else {
+ var discriminantY1 = bY * bY - 4 * aY * (cY - boxMinY);
+
+ var tY1, tY2;
+ if (discriminantY1 > 0) {
+ var sqrt = Math.sqrt(discriminantY1);
+ tY1 = (-bY + sqrt) / (2 * aY);
+ tY2 = (-bY - sqrt) / (2 * aY);
+
+ yIntervals.push(tY1, tY2);
+ }
+
+ var discriminantY2 = bY * bY - 4 * aY * (cY - boxMaxY);
+
+ var tY3, tY4;
+ if (discriminantY2 > 0) {
+ var sqrt = Math.sqrt(discriminantY2);
+ tY3 = (-bY + sqrt) / (2 * aY);
+ tY4 = (-bY - sqrt) / (2 * aY);
+
+ yIntervals.push(tY3, tY4);
+ }
+ }
+
+ yIntervals.sort(function(a, b) { return a - b; });
+
+ for (var index = 0; index < xIntervals.length; index += 2) {
+ for (var yIndex = 1; yIndex < yIntervals.length; yIndex += 2) {
+
+ // Check if there exists values for the Bezier curve
+ // parameter between 0 and 1 where both the curve's
+ // x and y coordinates are within the bounds specified by the box
+ if (xIntervals[index] < yIntervals[yIndex]
+ && yIntervals[yIndex] >= 0.0
+ && xIntervals[index] <= 1.0
+ && xIntervals[index + 1] > yIntervals[yIndex - 1]
+ && yIntervals[yIndex - 1] <= 1.0
+ && xIntervals[index + 1] >= 0.0) {
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ };
+
+ $$.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, toleranceSquared) {
+
+ var bb = {
+ x1: Math.min( x1, x3, x2 ),
+ x2: Math.max( x1, x3, x2 ),
+ y1: Math.min( y1, y3, y2 ),
+ y2: Math.max( y1, y3, y2 )
+ };
+
+ // 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];
+ }
+ }
+
+ /*
+ debugStats.clickX = x;
+ debugStats.clickY = y;
+
+ debugStats.closestX = Math.pow(1.0 - closestParam, 2.0) * x1
+ + 2.0 * (1.0 - closestParam) * closestParam * x2
+ + closestParam * closestParam * x3;
+
+ debugStats.closestY = Math.pow(1.0 - closestParam, 2.0) * y1
+ + 2.0 * (1.0 - closestParam) * closestParam * y2
+ + closestParam * closestParam * y3;
+ */
+
+ // debug("given: "
+ // + "( " + x + ", " + y + "), "
+ // + "( " + x1 + ", " + y1 + "), "
+ // + "( " + x2 + ", " + y2 + "), "
+ // + "( " + x3 + ", " + y3 + ")");
+
+
+ // debug("roots: " + roots);
+ // debug("params: " + params);
+ // debug("closest param: " + closestParam);
+ 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.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 = Math.asin(direction[1] / (Math.sqrt(direction[0] * direction[0]
+ + direction[1] * direction[1])));
+
+ if (direction[0] < 0) {
+ angle = angle + Math.PI / 2;
+ } else {
+ angle = -angle - Math.PI / 2;
+ }
+
+ 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;
+ }
+
+ 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];
+ }
+
+//* console.log("line from (" + x1 + ", " + y1 + ") to (" + x2 + ", " + y2 + ")");
+
+//& console.log(x1, x, x2);
+
+ 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++;
+ }
+
+//* console.log(y3, y);
+
+ } else {
+//* console.log('22');
+ continue;
+ }
+
+ }
+
+//* console.log("up: " + up + ", down: " + down);
+
+ if (up % 2 === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+ $$.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];
+ };
+
+ $$.math.dotProduct = function(
+ vec1, vec2) {
+
+ if (vec1.length != 2 || vec2.length != 2) {
+ throw 'dot product: arguments are not vectors';
+ }
+
+ return (vec1[0] * vec2[0] + vec1[1] * vec2[1]);
+ };
+
+ // 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 [];
+ }
+ }
+ };
+
+ // (boxMinX, boxMinY, boxMaxX, boxMaxY, padding,
+ // cornerRadius * 2, cornerRadius * 2, vBoxTopLeftX + padding, hBoxTopLeftY + padding)) {
+
+ $$.math.boxIntersectEllipse = function(
+ x1, y1, x2, y2, padding, width, height, centerX, centerY) {
+
+ if (x2 < x1) {
+ var oldX1 = x1;
+ x1 = x2;
+ x2 = oldX1;
+ }
+
+ if (y2 < y1) {
+ var oldY1 = y1;
+ y1 = y2;
+ y2 = oldY1;
+ }
+
+ // 4 ortho extreme points
+ var west = [centerX - width / 2 - padding, centerY];
+ var east = [centerX + width / 2 + padding, centerY];
+ var north = [centerX, centerY - height / 2 - padding];
+ var south = [centerX, centerY + height / 2 + padding];
+
+ // out of bounds: return false
+ if (x2 < west[0]) {
+ return false;
+ }
+
+ if (x1 > east[0]) {
+ return false;
+ }
+
+ if (y1 > south[1]) {
+ return false;
+ }
+
+ if (y2 < north[1]) {
+ return false;
+ }
+
+ // 1 of 4 ortho extreme points in box: return true
+ if (x1 <= east[0] && east[0] <= x2
+ && y1 <= east[1] && east[1] <= y2) {
+ return true;
+ }
+
+ if (x1 <= west[0] && west[0] <= x2
+ && y1 <= west[1] && west[1] <= y2) {
+ return true;
+ }
+
+ if (x1 <= north[0] && north[0] <= x2
+ && y1 <= north[1] && north[1] <= y2) {
+ return true;
+ }
+
+ if (x1 <= south[0] && south[0] <= x2
+ && y1 <= south[1] && south[1] <= y2) {
+ return true;
+ }
+
+ // box corner in ellipse: return true
+ x1 = (x1 - centerX) / (width / 2 + padding);
+ x2 = (x2 - centerX) / (width / 2 + padding);
+
+ y1 = (y1 - centerY) / (height / 2 + padding);
+ y2 = (y2 - centerY) / (height / 2 + padding);
+
+ if (x1 * x1 + y1 * y1 <= 1) {
+ return true;
+ }
+
+ if (x2 * x2 + y1 * y1 <= 1) {
+ return true;
+ }
+
+ if (x2 * x2 + y2 * y2 <= 1) {
+ return true;
+ }
+
+ if (x1 * x1 + y2 * y2 <= 1) {
+ return true;
+ }
+
+ return false;
+ };
+
+ $$.math.boxIntersectPolygon = function(
+ x1, y1, x2, y2, basePoints, width, height, centerX, centerY, direction, padding) {
+
+// console.log(arguments);
+
+ if (x2 < x1) {
+ var oldX1 = x1;
+ x1 = x2;
+ x2 = oldX1;
+ }
+
+ if (y2 < y1) {
+ var oldY1 = y1;
+ y1 = y2;
+ y2 = oldY1;
+ }
+
+ var transformedPoints = new Array(basePoints.length);
+
+ // Gives negative of angle
+ var angle = Math.asin(direction[1] / (Math.sqrt(direction[0] * direction[0]
+ + direction[1] * direction[1])));
+
+ if (direction[0] < 0) {
+ angle = angle + Math.PI / 2;
+ } else {
+ angle = -angle - Math.PI / 2;
+ }
+
+ var cos = Math.cos(-angle);
+ var sin = Math.sin(-angle);
+
+ 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;
+ }
+
+ // Assume transformedPoints.length > 0, and check if intersection is possible
+ var minTransformedX = transformedPoints[0];
+ var maxTransformedX = transformedPoints[0];
+ var minTransformedY = transformedPoints[1];
+ var maxTransformedY = transformedPoints[1];
+
+ for (var i = 1; i < transformedPoints.length / 2; i++) {
+ if (transformedPoints[i * 2] > maxTransformedX) {
+ maxTransformedX = transformedPoints[i * 2];
+ }
+
+ if (transformedPoints[i * 2] < minTransformedX) {
+ minTransformedX = transformedPoints[i * 2];
+ }
+
+ if (transformedPoints[i * 2 + 1] > maxTransformedY) {
+ maxTransformedY = transformedPoints[i * 2 + 1];
+ }
+
+ if (transformedPoints[i * 2 + 1] < minTransformedY) {
+ minTransformedY = transformedPoints[i * 2 + 1];
+ }
+ }
+
+ if (x2 < minTransformedX - padding) {
+ return false;
+ }
+
+ if (x1 > maxTransformedX + padding) {
+ return false;
+ }
+
+ if (y2 < minTransformedY - padding) {
+ return false;
+ }
+
+ if (y1 > maxTransformedY + padding) {
+ return false;
+ }
+
+ // Continue checking with padding-corrected points
+ var points;
+
+ if (padding > 0) {
+ var expandedLineSet = $$.math.expandPolygon(
+ transformedPoints,
+ -padding);
+
+ points = $$.math.joinLines(expandedLineSet);
+ } else {
+ points = transformedPoints;
+ }
+
+ // Check if a point is in box
+ for (var i = 0; i < transformedPoints.length / 2; i++) {
+ if (x1 <= transformedPoints[i * 2]
+ && transformedPoints[i * 2] <= x2) {
+
+ if (y1 <= transformedPoints[i * 2 + 1]
+ && transformedPoints[i * 2 + 1] <= y2) {
+
+ return true;
+ }
+ }
+ }
+
+
+ // Check for intersections with the selection box
+ for (var i = 0; i < points.length / 2; i++) {
+
+ var currentX = points[i * 2];
+ var currentY = points[i * 2 + 1];
+ var nextX;
+ var nextY;
+
+ 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 with top of selection box
+ if ($$.math.finiteLinesIntersect(currentX, currentY, nextX, nextY, x1, y1, x2, y1, false).length > 0) {
+ return true;
+ }
+
+ // Intersection with bottom of selection box
+ if ($$.math.finiteLinesIntersect(currentX, currentY, nextX, nextY, x1, y2, x2, y2, false).length > 0) {
+ return true;
+ }
+
+ // Intersection with left side of selection box
+ if ($$.math.finiteLinesIntersect(currentX, currentY, nextX, nextY, x1, y1, x1, y2, false).length > 0) {
+ return true;
+ }
+
+ // Intersection with right side of selection box
+ if ($$.math.finiteLinesIntersect(currentX, currentY, nextX, nextY, x2, y1, x2, y2, false).length > 0) {
+ return true;
+ }
+ }
+
+ /*
+ // Check if box corner in the polygon
+ if ($$.math.pointInsidePolygon(
+ x1, y1, points, 0, 0, 1, 1, 0, direction)) {
+
+ return true;
+ } else if ($$.math.pointInsidePolygon(
+ x1, y2, points, 0, 0, 1, 1, 0, direction)) {
+
+ return true;
+ } else if ($$.math.pointInsidePolygon(
+ x2, y2, points, 0, 0, 1, 1, 0, direction)) {
+
+ return true;
+ } else if ($$.math.pointInsidePolygon(
+ x2, y1, points, 0, 0, 1, 1, 0, direction)) {
+
+ return true;
+ }
+ */
+ return false;
+ };
+
+ $$.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);
+ };
+
+})( cytoscape );
+
+// type testing utility functions
+
+;(function($$){ 'use strict';
+
+ // list of ids with other metadata assoc'd with it
+ $$.instances = [];
+ $$.instanceCounter = 0;
+ $$.lastInstanceTime = undefined;
+
+ $$.registerInstance = function( instance, domElement ){
+ var cy;
+
+ if( $$.is.core(instance) ){
+ cy = instance;
+ } else if( $$.is.domElement(instance) ){
+ domElement = instance;
+ }
+
+ // if we have an old reg that is empty (no cy), then
+ var oldReg = $$.getRegistrationForInstance(instance, domElement);
+ if( oldReg ){
+ if( !oldReg.cy ){
+ oldReg.cy = instance;
+ oldReg.domElement = domElement;
+ } else {
+ $$.util.error('Tried to register on a pre-existing registration');
+ }
+
+ return oldReg;
+
+ // otherwise, just make a new registration
+ } else {
+ var time = +new Date();
+ var suffix;
+
+ // add a suffix in case instances collide on the same time
+ if( !$$.lastInstanceTime || $$.lastInstanceTime === time ){
+ $$.instanceCounter = 0;
+ } else {
+ ++$$.instanceCounter;
+ }
+ $$.lastInstanceTime = time;
+ suffix = $$.instanceCounter;
+
+ var id = 'cy-' + time + '-' + suffix;
+
+ // create the registration object
+ var registration = {
+ id: id,
+ cy: cy,
+ domElement: domElement,
+ readies: [] // list of bound ready functions before calling init
+ };
+
+ // put the registration object in the pool
+ $$.instances.push( registration );
+ $$.instances[ id ] = registration;
+
+ return registration;
+ }
+ };
+
+ $$.removeRegistrationForInstance = function(instance, domElement){
+ var cy;
+
+ if( $$.is.core(instance) ){
+ cy = instance;
+ } else if( $$.is.domElement(instance) ){
+ domElement = instance;
+ }
+
+ if( $$.is.core(cy) ){
+ var id = cy._private.instanceId;
+ delete $$.instances[ id ];
+ $$.instances.splice(id, 1);
+
+ } else if( $$.is.domElement(domElement) ){
+ for( var i = 0; i < $$.instances.length; i++ ){
+ var reg = $$.instances[i];
+
+ if( reg.domElement === domElement ){
+ delete $$.instances[ reg.id ];
+ $$.instances.splice(i, 1);
+ i--;
+ }
+ }
+ }
+ };
+
+ $$.getRegistrationForInstance = function( instance, domElement ){
+ var cy;
+
+ if( $$.is.core(instance) ){
+ if( instance.registered() ){ // only want it if it's registered b/c if not it has no reg'd id
+ cy = instance;
+ }
+ } else if( $$.is.domElement(instance) ){
+ domElement = instance;
+ }
+
+ if( $$.is.core(cy) ){
+ var id = cy._private.instanceId;
+ return $$.instances[ id ];
+
+ } else if( $$.is.domElement(domElement) ){
+ for( var i = $$.instances.length - 1; i >= 0; i-- ){ // look backwards, since most recent is the one we want
+ var reg = $$.instances[i];
+
+ if( reg.domElement === domElement ){
+ return reg;
+ }
+ }
+ }
+ };
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ // registered extensions to cytoscape, indexed by name
+ var extensions = {};
+ $$.extensions = extensions;
+
+ // registered modules for extensions, indexed by name
+ var modules = {};
+ $$.modules = modules;
+
+ function setExtension(type, name, registrant){
+ var impl = {};
+ impl[name] = registrant;
+
+ switch( type ){
+ case 'core':
+ case 'collection':
+ $$.fn[type]( impl );
+ }
+
+ return $$.util.setMap({
+ map: extensions,
+ keys: [ type, name ],
+ value: registrant
+ });
+ }
+
+ 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 ]
+ });
+ }
+
+ $$.extension = function(){
+ // e.g. $$.extension('renderer', 'svg')
+ if( arguments.length == 2 ){
+ return getExtension.apply(this, arguments);
+ }
+
+ // e.g. $$.extension('renderer', 'svg', { ... })
+ else if( arguments.length == 3 ){
+ return setExtension.apply(this, arguments);
+ }
+
+ // e.g. $$.extension('renderer', 'svg', 'nodeShape', 'ellipse')
+ else if( arguments.length == 4 ){
+ return getModule.apply(this, arguments);
+ }
+
+ // e.g. $$.extension('renderer', 'svg', 'nodeShape', 'ellipse', { ... })
+ else if( arguments.length == 5 ){
+ return setModule.apply(this, arguments);
+ }
+
+ else {
+ $.error('Invalid extension access syntax');
+ }
+
+ };
+
+})( cytoscape );
+
+;(function($, $$){ 'use strict';
+
+ if( !$ ){ return; } // no jquery => don't need this
+
+ // 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' ){
+ var reg = $$.getRegistrationForInstance( $this[0] );
+ return reg.cy;
+ }
+
+ // bind to ready
+ else if( $$.is.fn(opts) ){
+ //debugger;
+
+ var ready = opts;
+ var domEle = $this[0];
+ var reg = $$.getRegistrationForInstance( domEle );
+
+ if( !reg ){
+ reg = $$.registerInstance( domEle );
+ }
+
+ if( reg && reg.cy && reg.cy.ready() ){
+ // already ready so just trigger now
+ reg.cy.trigger('ready', [], ready);
+
+ } else {
+ // not yet ready, so add to readies list
+
+ reg.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);
+ });
+ }
+
+ // proxy a function call
+ else {
+ var domEle = $this[0];
+ var rets = [];
+ var args = [];
+ for(var i = 1; i < arguments.length; i++){
+ args[i - 1] = arguments[i];
+ }
+
+ $this.each(function(){
+ var reg = $$.getRegistrationForInstance( domEle );
+ var cy = reg.cy;
+ var fnName = opts;
+
+ if( cy && $$.is.fn( cy[fnName] ) ){
+ var ret = cy[fnName].apply(cy, args);
+ rets.push(ret);
+ }
+ });
+
+ // if only one instance, don't need to return array
+ if( rets.length === 1 ){
+ rets = rets[0];
+ } else if( rets.length === 0 ){
+ rets = $(this);
+ }
+
+ return rets;
+ }
+
+ };
+
+ // 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;
+ }
+
+})(typeof jQuery !== 'undefined' ? jQuery : null , cytoscape);
+
+;(function($$){ 'use strict';
+
+ // shamelessly taken from jQuery
+ // https://github.com/jquery/jquery/blob/master/src/event.js
+
+ $$.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 ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ $$.util.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || +new Date();
+ };
+
+ function returnFalse() {
+ return false;
+ }
+ function returnTrue() {
+ return true;
+ }
+
+ // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+ // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+ $$.Event.prototype = {
+ 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
+ };
+
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ // metaprogramming makes me happy
+
+ // use this module to cherry pick functions into your prototype
+ // (useful for functions shared between the core and collections, for example)
+
+ // e.g.
+ // $$.fn.collection({
+ // foo: $$.define.foo({ /* params... */ })
+ // });
+
+ $$.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
+
+ batchData: function( params ){
+ var defaults = {
+ field: 'data',
+ event: 'data',
+ triggerFnName: 'trigger',
+ immutableKeys: {}, // key => true if immutable
+ updateStyle: false
+ };
+ var p = $$.util.extend({}, defaults, params);
+
+ return function batchDataImpl( map ){
+ var self = this;
+ var selfIsArrayLike = self.length !== undefined;
+ var eles = selfIsArrayLike ? self : self._private.elements;
+
+ if( eles.length === 0 ){ return self; }
+ var cy = selfIsArrayLike ? eles[0]._private.cy : self; // NB must have at least 1 ele to get cy
+
+ for( var i = 0; i < eles.length; i++ ){
+ var ele = eles[i];
+ var id = ele._private.data.id;
+ var mapData = map[id];
+
+ if( mapData !== undefined && mapData !== null ){
+ var obj = mapData;
+ var k, v;
+
+ // set the (k, v) pairs from the map
+ for( k in obj ){
+ v = obj[ k ];
+
+ var valid = !p.immutableKeys[k];
+ if( valid ){
+ ele._private[ p.field ][ k ] = v;
+ }
+ }
+ } // if
+ } // for
+
+ // update mappers if asked
+ var coln = new $$.Collection(cy, eles);
+ if( p.updateStyle ){ coln.updateStyle(); }
+
+ coln[ p.triggerFnName ]( p.event );
+
+ return self; // chaining
+ };
+ },
+
+ // 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++ ){
+ delete all[ i_a ]._private[ p.field ][ key ];
+ }
+ }
+ }
+
+ 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 ){
+ delete _privateFields[ key ];
+ }
+ }
+ }
+
+ 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++ ){
+ all[j]._private.listeners.push( listener );
+ }
+ }
+ } // for events array
+ } // for events map
+
+ return self; // maintain chaining
+ }; // function
+ }, // on
+
+ 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;
+
+ 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 || this;
+
+ 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;
+ var triggererIsElement = $$.is.element(triggerer);
+ var bubbleUp = triggererIsElement;
+
+ // 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;
+ evt.namespace = evt.namespace || evtObj.namespace;
+
+ } else { // then we have to make one
+ evt = new $$.Event( evtObj, {
+ cyTarget: triggerer,
+ cy: cy,
+ namespace: evtObj.namespace
+ } );
+ }
+
+ // 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.filter(evt.cyTarget).length > 0 ) : (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 = triggerer.parent();
+ var hasParent = 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
+
+ }; // define
+
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.fn.selector = function(map, options){
+ for( var name in map ){
+ var fn = map[name];
+ $$.Selector.prototype[ name ] = fn;
+ }
+ };
+
+ $$.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.element( selector ) ){
+ var collection = new $$.Collection(self.cy(), [ selector ]);
+
+ self[0] = newQuery();
+ self[0].collection = collection;
+ self.length = 1;
+
+ } else if( $$.is.collection( selector ) ){
+ self[0] = newQuery();
+ self[0].collection = selector;
+ 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;
+ }
+
+ // 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 = {
+ group: {
+ query: true,
+ regex: '(node|edge|\\*)',
+ populate: function( group ){
+ this.group = group == "*" ? group : group + 's';
+ }
+ },
+
+ state: {
+ query: true,
+ regex: '(:selected|:unselected|:locked|:unlocked|:visible|:hidden|:transparent|:grabbed|:free|:removed|:inside|:grabbable|:ungrabbable|:animated|:unanimated|:selectable|:unselectable|:parent|:child|:loop|:simple|:active|:inactive|:touch)',
+ populate: function( state ){
+ this.colonSelectors.push( state );
+ }
+ },
+
+ id: {
+ query: true,
+ regex: '\\#('+ tokens.id +')',
+ populate: function( id ){
+ this.ids.push( cleanMetaChars(id) );
+ }
+ },
+
+ className: {
+ query: true,
+ regex: '\\.('+ tokens.className +')',
+ populate: function( className ){
+ this.classes.push( cleanMetaChars(className) );
+ }
+ },
+
+ dataExists: {
+ query: true,
+ regex: '\\[\\s*('+ tokens.variable +')\\s*\\]',
+ populate: function( variable ){
+ this.data.push({
+ field: cleanMetaChars(variable)
+ });
+ }
+ },
+
+ 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
+ });
+ }
+ },
+
+ dataBool: {
+ query: true,
+ regex: '\\[\\s*('+ tokens.boolOp +')\\s*('+ tokens.variable +')\\s*\\]',
+ populate: function( boolOp, variable ){
+ this.data.push({
+ field: cleanMetaChars(variable),
+ operator: boolOp
+ });
+ }
+ },
+
+ 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)
+ });
+ }
+ },
+
+ nextQuery: {
+ separator: true,
+ regex: tokens.separator,
+ populate: function(){
+ // go on to next query
+ self[++i] = newQuery();
+ currentSubject = null;
+ }
+ },
+
+ 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;
+ }
+ },
+
+ 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;
+ }
+ },
+
+ 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;
+ }
+
+ }
+ };
+
+ var j = 0;
+ for( var name in exprs ){
+ exprs[j] = exprs[name];
+ exprs[j].name = name;
+
+ j++;
+ }
+ exprs.length = j;
+
+ 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(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;
+
+ };
+
+ $$.selfn = $$.Selector.prototype;
+
+ $$.selfn.size = function(){
+ return this.length;
+ };
+
+ $$.selfn.eq = function(i){
+ return this[i];
+ };
+
+ // get elements from the core and then filter them
+ $$.selfn.find = function(){
+ // TODO impl if we decide to use a DB for storing elements
+ };
+
+ // 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 new $$.Collection( cy );
+ }
+
+ var queryMatches = function(query, element){
+ // check group
+ if( query.group != null && query.group != '*' && query.group != element._private.group ){
+ return false;
+ }
+
+ // 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.children().nonempty();
+ break;
+ case ':child':
+ allColonSelectorsMatch = element.parent().nonempty();
+ 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 = window && document && (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch);
+ 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.charAt(0) == '@' ){
+ fieldStr = fieldStr.toLowerCase();
+ valStr = valStr.toLowerCase();
+
+ operator = operator.substring(1);
+ caseInsensitive = 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 = fieldVal > value;
+ break;
+ case '>=':
+ matches = fieldVal >= value;
+ break;
+ case '<':
+ matches = fieldVal < value;
+ break;
+ case '<=':
+ matches = fieldVal <= value;
+ 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( !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;
+ 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.size(); i++){
+ if( queryMatches( query, elements.eq(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
+
+ 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
+
+ // ith query to string
+ $$.selfn.toString = $$.selfn.selector = function(){
+
+ var str = '';
+
+ var clean = function(obj){
+ if( $$.is.string(obj) ){
+ return obj;
+ }
+ return '';
+ };
+
+ var queryToString = function(query){
+ var 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) + ']';
+ } 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) + ']]';
+ }
+
+ 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[i];
+ 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;
+ };
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.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: {}
+ };
+
+ this.length = 0;
+
+ this.addDefaultStylesheet();
+ };
+
+ // nice-to-have aliases
+ $$.style = $$.Style;
+ $$.styfn = $$.Style.prototype;
+
+ // define functions in the Style prototype
+ $$.fn.style = function( fnMap, options ){
+ for( var fnName in fnMap ){
+ var fn = fnMap[ fnName ];
+ $$.Style.prototype = fn;
+ }
+ };
+
+ (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 ){ return '^' + prefix + '\\s*\\(([\\w\\.]+)\\s*\\,\\s*(' + number + ')\\s*\\,\\s*(' + number + ')\\s*,\\s*(' + number + '|\\w+|' + rgba + '|' + hsla + '|' + hex3 + '|' + hex6 + ')\\s*\\,\\s*(' + number + '|\\w+|' + rgba + '|' + hsla + '|' + hex3 + '|' + hex6 + ')\\)$'; };
+
+ // each visual style property has a type and needs to be validated according to it
+ $$.style.types = {
+ time: { number: true, min: 0, units: 's' },
+ percent: { number: true, min: 0, max: 100, units: '%' },
+ 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 },
+ size: { number: true, min: 0, enums: ['auto'] },
+ bgSize: { number: true, min: 0, allowPercent: true },
+ 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 },
+ lineStyle: { enums: ['solid', 'dotted', 'dashed'] },
+ curveStyle: { enums: ['bezier', 'haystack'] },
+ 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', 'capitalize', 'uppercase', 'lowercase'] },
+ nodeShape: { enums: ['rectangle', 'roundrectangle', 'ellipse', 'triangle',
+ 'square', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'star'] },
+ arrowShape: { enums: ['tee', 'triangle', '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'] },
+ cursor: { enums: ['auto', 'crosshair', 'default', 'e-resize', 'n-resize', 'ne-resize', 'nw-resize', 'pointer', 'progress', 's-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'grab', 'grabbing'] },
+ text: { string: true },
+ data: { mapping: true, regex: data('data') },
+ layoutData: { mapping: true, regex: data('layoutData') },
+ mapData: { mapping: true, regex: mapData('mapData') },
+ mapLayoutData: { mapping: true, regex: mapData('mapLayoutData') },
+ url: { regex: '^url\\s*\\(\\s*([^\\s]+)\\s*\\s*\\)|none|(.+)$' },
+ propList: { propList: true }
+ };
+
+ // define visual style properties
+ var t = $$.style.types;
+ $$.style.properties = [
+ // these are for elements
+ { name: 'text-valign', type: t.valign },
+ { name: 'text-halign', type: t.halign },
+ { name: 'color', type: t.color },
+ { name: 'content', 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-decoration', type: t.textDecoration },
+ { name: 'text-transform', type: t.textTransform },
+ { name: 'font-family', type: t.fontFamily },
+ { name: 'font-style', type: t.fontStyle },
+ { name: 'font-variant', type: t.fontVariant },
+ { name: 'font-weight', type: t.fontWeight },
+ { name: 'font-size', type: t.size },
+ { name: 'min-zoomed-font-size', type: t.size },
+ { name: 'display', type: t.display },
+ { name: 'visibility', type: t.visibility },
+ { name: 'opacity', type: t.zeroOneNumber },
+ { name: 'z-index', type: t.nonNegativeInt },
+ { name: 'overlay-padding', type: t.size },
+ { name: 'overlay-color', type: t.color },
+ { name: 'overlay-opacity', type: t.zeroOneNumber },
+ { name: 'transition-property', type: t.propList },
+ { name: 'transition-duration', type: t.time },
+ { name: 'transition-delay', type: t.time },
+
+ // these are just for nodes
+ { name: 'background-blacken', type: t.nOneOneNumber },
+ { name: 'background-color', type: t.color },
+ { name: 'background-opacity', type: t.zeroOneNumber },
+ { name: 'background-image', type: t.url },
+ { 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: 'pie-size', type: t.bgSize },
+ { name: 'pie-1-background-color', type: t.color },
+ { name: 'pie-2-background-color', type: t.color },
+ { name: 'pie-3-background-color', type: t.color },
+ { name: 'pie-4-background-color', type: t.color },
+ { name: 'pie-5-background-color', type: t.color },
+ { name: 'pie-6-background-color', type: t.color },
+ { name: 'pie-7-background-color', type: t.color },
+ { name: 'pie-8-background-color', type: t.color },
+ { name: 'pie-9-background-color', type: t.color },
+ { name: 'pie-10-background-color', type: t.color },
+ { name: 'pie-11-background-color', type: t.color },
+ { name: 'pie-12-background-color', type: t.color },
+ { name: 'pie-13-background-color', type: t.color },
+ { name: 'pie-14-background-color', type: t.color },
+ { name: 'pie-15-background-color', type: t.color },
+ { name: 'pie-16-background-color', type: t.color },
+ { name: 'pie-1-background-size', type: t.percent },
+ { name: 'pie-2-background-size', type: t.percent },
+ { name: 'pie-3-background-size', type: t.percent },
+ { name: 'pie-4-background-size', type: t.percent },
+ { name: 'pie-5-background-size', type: t.percent },
+ { name: 'pie-6-background-size', type: t.percent },
+ { name: 'pie-7-background-size', type: t.percent },
+ { name: 'pie-8-background-size', type: t.percent },
+ { name: 'pie-9-background-size', type: t.percent },
+ { name: 'pie-10-background-size', type: t.percent },
+ { name: 'pie-11-background-size', type: t.percent },
+ { name: 'pie-12-background-size', type: t.percent },
+ { name: 'pie-13-background-size', type: t.percent },
+ { name: 'pie-14-background-size', type: t.percent },
+ { name: 'pie-15-background-size', type: t.percent },
+ { name: 'pie-16-background-size', type: t.percent },
+ { name: 'border-color', type: t.color },
+ { name: 'border-opacity', type: t.zeroOneNumber },
+ { name: 'border-width', type: t.size },
+ { name: 'border-style', type: t.lineStyle },
+ { name: 'height', type: t.size },
+ { name: 'width', type: t.size },
+ { name: 'padding-left', type: t.size },
+ { name: 'padding-right', type: t.size },
+ { name: 'padding-top', type: t.size },
+ { name: 'padding-bottom', type: t.size },
+ { name: 'shape', type: t.nodeShape },
+
+ // these are just for edges
+ { name: 'source-arrow-shape', type: t.arrowShape },
+ { name: 'target-arrow-shape', type: t.arrowShape },
+ { name: 'source-arrow-color', type: t.color },
+ { name: 'target-arrow-color', type: t.color },
+ { name: 'source-arrow-fill', type: t.arrowFill },
+ { name: 'target-arrow-fill', type: t.arrowFill },
+ { name: 'line-style', type: t.lineStyle },
+ { name: 'line-color', type: t.color },
+ { name: 'control-point-step-size', type: t.size },
+ { name: 'control-point-distance', type: t.size },
+ { name: 'control-point-weight', type: t.zeroOneNumber },
+ { name: 'curve-style', type: t.curveStyle },
+
+ // 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: 'panning-cursor', type: t.cursor },
+ { 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 }
+ ];
+
+ // allow access of properties by name ( e.g. $$.style.properties.height )
+ var props = $$.style.properties;
+ for( var i = 0; i < props.length; i++ ){
+ var prop = props[i];
+
+ props[ prop.name ] = prop; // allow lookup by name
+ }
+
+ // because the pie properties are numbered, give access to a constant N (for renderer use)
+ $$.style.pieBackgroundN = 16;
+ })();
+
+ // adds the default stylesheet to the current style
+ $$.styfn.addDefaultStylesheet = function(){
+ // to be nice, we build font related style properties from the core container
+ // so that cytoscape matches the style of its container by default
+ //
+ // unfortunately, this doesn't seem work consistently and can grab the default stylesheet values
+ // instead of the developer's values so let's just make it explicit for the dev for now
+ //
+ // delaying the read of these val's is not an opt'n: that would delay init'l load time
+ var fontFamily = 'Helvetica' || this.containerPropertyAsString('font-family') || 'sans-serif';
+ var fontStyle = 'normal' || this.containerPropertyAsString('font-style') || 'normal';
+ var fontVariant = 'normal' || this.containerPropertyAsString('font-variant') || 'normal';
+ var fontWeight = 'normal' || this.containerPropertyAsString('font-weight') || 'normal';
+ var color = '#000' || this.containerPropertyAsString('color') || '#000';
+ var textTransform = 'none' || this.containerPropertyAsString('text-transform') || 'none';
+ var fontSize = 16 || this.containerPropertyAsString('font-size') || 16;
+
+ // fill the style with the default stylesheet
+ this
+ .selector('node, edge') // common properties
+ .css({
+ 'text-valign': 'top',
+ 'text-halign': 'center',
+ 'color': color,
+ 'text-outline-color': '#000',
+ 'text-outline-width': 0,
+ 'text-outline-opacity': 1,
+ 'text-opacity': 1,
+ 'text-decoration': 'none',
+ 'text-transform': textTransform,
+ 'font-family': fontFamily,
+ 'font-style': fontStyle,
+ 'font-variant': fontVariant,
+ 'font-weight': fontWeight,
+ 'font-size': fontSize,
+ 'min-zoomed-font-size': 0,
+ 'visibility': 'visible',
+ 'display': 'element',
+ 'opacity': 1,
+ 'z-index': 0,
+ 'content': '',
+ 'overlay-opacity': 0,
+ 'overlay-color': '#000',
+ 'overlay-padding': 10,
+ 'transition-property': 'none',
+ 'transition-duration': 0,
+ 'transition-delay': 0,
+
+ // node props
+ 'background-blacken': 0,
+ 'background-color': '#888',
+ 'background-opacity': 1,
+ 'background-image': 'none',
+ 'background-position-x': '50%',
+ 'background-position-y': '50%',
+ 'background-repeat': 'no-repeat',
+ 'background-fit': 'none',
+ 'background-clip': 'node',
+ 'border-color': '#000',
+ 'border-opacity': 1,
+ 'border-width': 0,
+ 'border-style': 'solid',
+ 'height': 30,
+ 'width': 30,
+ 'padding-top': 0,
+ 'padding-bottom': 0,
+ 'padding-left': 0,
+ 'padding-right': 0,
+ 'shape': 'ellipse',
+ 'pie-size': '100%',
+ 'pie-1-background-color': 'black',
+ 'pie-1-background-size': '0%',
+ 'pie-2-background-color': 'black',
+ 'pie-2-background-size': '0%',
+ 'pie-3-background-color': 'black',
+ 'pie-3-background-size': '0%',
+ 'pie-4-background-color': 'black',
+ 'pie-4-background-size': '0%',
+ 'pie-5-background-color': 'black',
+ 'pie-5-background-size': '0%',
+ 'pie-6-background-color': 'black',
+ 'pie-6-background-size': '0%',
+ 'pie-7-background-color': 'black',
+ 'pie-7-background-size': '0%',
+ 'pie-8-background-color': 'black',
+ 'pie-8-background-size': '0%',
+ 'pie-9-background-color': 'black',
+ 'pie-9-background-size': '0%',
+ 'pie-10-background-color': 'black',
+ 'pie-10-background-size': '0%',
+ 'pie-11-background-color': 'black',
+ 'pie-11-background-size': '0%',
+ 'pie-12-background-color': 'black',
+ 'pie-12-background-size': '0%',
+ 'pie-13-background-color': 'black',
+ 'pie-13-background-size': '0%',
+ 'pie-14-background-color': 'black',
+ 'pie-14-background-size': '0%',
+ 'pie-15-background-color': 'black',
+ 'pie-15-background-size': '0%',
+ 'pie-16-background-color': 'black',
+ 'pie-16-background-size': '0%',
+
+ // edge props
+ 'source-arrow-shape': 'none',
+ 'target-arrow-shape': 'none',
+ 'source-arrow-color': '#bbb',
+ 'target-arrow-color': '#bbb',
+ 'source-arrow-fill': 'filled',
+ 'target-arrow-fill': 'filled',
+ 'line-style': 'solid',
+ 'line-color': '#bbb',
+ 'control-point-step-size': 40,
+ 'control-point-weight': 0.5,
+ 'curve-style': 'bezier'
+ })
+ .selector('$node > node') // compound (parent) node properties
+ .css({
+ 'width': 'auto',
+ 'height': 'auto',
+ 'shape': 'rectangle',
+ 'background-opacity': 0.5,
+ '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,
+ 'panning-cursor': 'grabbing',
+ 'active-bg-color': 'black',
+ 'active-bg-opacity': 0.15,
+ 'active-bg-size': $$.is.touch() ? 40 : 15,
+ 'outside-texture-bg-color': '#000',
+ 'outside-texture-bg-opacity': 0.125
+ })
+ ;
+ };
+
+ // remove all contexts
+ $$.styfn.clear = function(){
+ this._private.newStyle = true;
+
+ for( var i = 0; i < this.length; i++ ){
+ delete this[i];
+ }
+ this.length = 0;
+
+ 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;
+ };
+
+ // 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
+ $$.styfn.parse = function( name, value, propIsBypass, propIsFlat ){
+
+ name = $$.util.camel2dash( name ); // make sure the property name is in dash form (e.g. 'property-name' not 'propertyName')
+ var property = $$.style.properties[ name ];
+ var passedValue = value;
+
+ if( !property ){ return null; } // return null on property of unknown name
+ if( value === undefined || value === null ){ return null; } // can't assign null
+
+ var valueIsString = $$.is.string(value);
+ if( valueIsString ){ // trim the value to make parsing easier
+ value = $$.util.trim( value );
+ }
+
+ 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 mapped
+ var data, mapData, layoutData, mapLayoutData;
+ if( !valueIsString || propIsFlat ){
+ // then don't bother to do the expensive regex checks
+
+ } else if(
+ ( data = new RegExp( $$.style.types.data.regex ).exec( value ) ) ||
+ ( layoutData = new RegExp( $$.style.types.layoutData.regex ).exec( value ) )
+ ){
+ var isLayout = layoutData !== undefined;
+ data = data || layoutData;
+
+ return {
+ name: name,
+ value: data,
+ strValue: '' + value,
+ mapped: isLayout ? $$.style.types.layoutData : $$.style.types.data,
+ field: data[1],
+ bypass: propIsBypass
+ };
+
+ } else if(
+ ( mapData = new RegExp( $$.style.types.mapData.regex ).exec( value ) ) ||
+ ( mapLayoutData = new RegExp( $$.style.types.mapLayoutData.regex ).exec( value ) )
+ ){
+ var isLayout = mapLayoutData !== undefined;
+ mapData = mapData || mapLayoutData;
+
+ // 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: isLayout ? $$.style.types.mapLayoutData : $$.style.types.mapData,
+ field: mapData[1],
+ fieldMin: parseFloat( mapData[2] ), // min & max are numeric
+ fieldMax: parseFloat( mapData[3] ),
+ valueMin: valueMin.value,
+ valueMax: valueMax.value,
+ bypass: propIsBypass
+ };
+ }
+
+ // check the type and return the appropriate object
+ if( type.number ){
+ var units;
+ var implicitUnit = 'px'; // not set => px
+
+ if( type.units ){ // use specified units if set
+ units = type.units;
+ }
+
+ 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] || implicitUnit;
+ }
+
+ } else if( !units ) {
+ units = implicitUnit; // 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;
+
+ 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; // failed on enum after failing on number
+ }
+
+ // 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
+ };
+
+ if( type.unitless || (units !== 'px' && units !== 'em') ){
+ // then pxValue does not apply
+ } else {
+ ret.pxValue = ( units === 'px' || !units ? (value) : (this.getEmSizeInPixels() * value) );
+ }
+
+ 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 = $$.util.trim( propsSplit[i] );
+
+ if( $$.style.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
+ };
+
+ } else if( type.enums ){
+ 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;
+
+ } else if( type.regex ){
+ var regex = new RegExp( type.regex ); // make a regex from the type
+ var m = regex.exec( value );
+
+ if( m ){ // regex matches
+ return {
+ name: name,
+ value: m,
+ strValue: '' + value,
+ bypass: propIsBypass
+ };
+ } else { // regex doesn't match
+ return null; // didn't match the regex so the value is bogus
+ }
+
+ } else if( type.string ){
+ // just return
+ return {
+ name: name,
+ value: value,
+ strValue: '' + value,
+ bypass: propIsBypass
+ };
+
+ } else {
+ return null; // not a type we can handle
+ }
+
+ };
+
+ // 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: []
+ };
+
+ return this; // chaining
+ };
+
+ // add one or many css rules to the current context
+ $$.styfn.css = function(){
+ var args = arguments;
+
+ switch( args.length ){
+ case 1:
+ var map = args[0];
+
+ for( var i = 0; i < $$.style.properties.length; i++ ){
+ var prop = $$.style.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
+ };
+
+ // 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 );
+
+ // add to core style if necessary
+ var currentSelectorIsCore = !this[i].selector;
+ if( currentSelectorIsCore ){
+ this._private.coreStyle[ property.name ] = property;
+ }
+ }
+
+ return this; // chaining
+ };
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ // (potentially expensive calculation)
+ // apply the style to the element based on
+ // - its bypass
+ // - what selectors match it
+ $$.styfn.apply = function( eles ){
+ var self = this;
+
+ for( var ie = 0; ie < eles.length; ie++ ){
+ var ele = eles[ie];
+ var _p = ele._private;
+ var addedCxts = [];
+ var removedCxts = [];
+
+ if( self._private.newStyle ){
+ _p.styleCxts = [];
+ _p.style = {};
+ }
+
+ // console.log('APPLYING STYLESHEET\n--\n');
+
+ // apply the styles
+ for( var i = 0; i < this.length; i++ ){
+ var context = this[i];
+ var contextSelectorMatches = context.selector && context.selector.filter( ele ).length > 0; // NB: context.selector may be null for 'core'
+ var props = context.properties;
+ var newCxt = !_p.styleCxts[i];
+
+ // console.log(i + ' : looking at selector: ' + context.selector);
+
+ if( contextSelectorMatches ){ // then apply its properties
+
+ // apply the properties in the context
+
+ for( var j = 0; j < props.length; j++ ){ // for each prop
+ var prop = props[j];
+ var currentEleProp = _p.style[prop.name];
+ var propIsFirstInEle = currentEleProp && currentEleProp.context === context;
+ var needToUpdateCxtMapping = prop.mapped && propIsFirstInEle;
+
+ //if(prop.mapped) debugger;
+
+ if( newCxt || needToUpdateCxtMapping ){
+ // console.log(i + ' + MATCH: applying property: ' + prop.name);
+ this.applyParsedProperty( ele, prop, context );
+ }
+ }
+
+ // keep a note that this context matches
+ ele._private.styleCxts[i] = context;
+
+ if( self._private.newStyle === false && newCxt ){
+ addedCxts.push( context );
+ }
+
+ } else {
+
+ // roll back style cxts that don't match now
+ if( _p.styleCxts[i] ){
+ // console.log(i + ' x MISS: rolling back context');
+ this.rollBackContext( ele, context );
+ removedCxts.push( context );
+
+ delete _p.styleCxts[i];
+ }
+ }
+ } // for context
+
+ if( addedCxts.length > 0 || removedCxts.length > 0 ){
+ this.updateTransitions( ele, addedCxts, removedCxts );
+ }
+
+ self.updateStyleHints( ele );
+
+ } // for elements
+
+ self._private.newStyle = false;
+ };
+
+ $$.styfn.updateStyleHints = function(ele){
+ var _p = ele._private;
+
+ // set whether has pie or not; for greater efficiency
+ var hasPie = false;
+ if( _p.group === 'nodes' ){
+ for( var i = 1; i <= $$.style.pieBackgroundN; i++ ){ // 1..N
+ var size = _p.style['pie-' + i + '-background-size'].value;
+
+ if( size > 0 ){
+ hasPie = true;
+ break;
+ }
+ }
+ }
+
+ _p.hasPie = hasPie;
+
+ var transform = _p.style['text-transform'].strValue;
+ var content = _p.style['content'].strValue;
+ var fStyle = _p.style['font-style'].strValue;
+ var size = _p.style['font-size'].pxValue + 'px';
+ var family = _p.style['font-family'].strValue;
+ var variant = _p.style['font-variant'].strValue;
+ var weight = _p.style['font-weight'].strValue;
+ _p.labelKey = fStyle +'$'+ size +'$'+ family +'$'+ variant +'$'+ weight +'$'+ content +'$'+ transform;
+ _p.fontKey = fStyle +'$'+ weight +'$'+ size +'$'+ family;
+ };
+
+ // when a context's selector no longer matches the ele, roll back the context on the ele
+ // (cheaper than having to recalc from the beginning)
+ $$.styfn.rollBackContext = function( ele, context ){
+ for( var j = 0; j < context.properties.length; j++ ){ // for each prop
+ var prop = context.properties[j];
+ var eleProp = ele._private.style[ prop.name ];
+
+ // because bypasses do not store prevs, look at the bypassed property
+ if( eleProp.bypassed ){
+ eleProp = eleProp.bypassed;
+ }
+
+ var first = true;
+ var lastEleProp;
+ var l = 0;
+ while( eleProp.prev ){
+ var prev = eleProp.prev;
+
+ if( eleProp.context === context ){
+
+ if( first ){
+ ele._private.style[ prop.name ] = prev;
+ } else if( lastEleProp ){
+ lastEleProp.prev = prev;
+ }
+
+ }
+
+ lastEleProp = eleProp;
+ eleProp = prev;
+ first = false;
+ l++;
+
+ // in case we have a problematic prev list
+ // if( l >= 100 ){
+ // debugger;
+ // }
+ }
+ }
+ };
+
+ // 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, context ){
+ parsedProp = $$.util.clone( parsedProp ); // copy b/c the same parsedProp may be applied to many elements, BUT
+ // the instances put in each element should be unique to avoid overwriting other the lists of other elements
+
+ var prop = parsedProp;
+ var style = ele._private.style;
+ var fieldVal, flatProp;
+ var type = $$.style.properties[ prop.name ].type;
+ var propIsBypass = prop.bypass;
+ var origProp = style[ prop.name ];
+ var origPropIsBypass = origProp && origProp.bypass;
+
+ // can't apply auto to width or height unless it's a parent node
+ if( (parsedProp.name === 'height' || parsedProp.name === 'width') && parsedProp.value === 'auto' && ele.isNode() && !ele.isParent() ){
+ return false;
+ }
+
+ // 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
+ }
+ }
+
+ // put the property in the style objects
+ switch( prop.mapped ){ // flatten the property if mapped
+ case $$.style.types.mapData:
+ case $$.style.types.mapLayoutData:
+
+ var isLayout = prop.mapped === $$.style.types.mapLayoutData;
+
+ // flatten the field (e.g. data.foo.bar)
+ var fields = prop.field.split(".");
+ var fieldVal = isLayout ? ele._private.layoutData : ele._private.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: 'rgba(' + clr[0] + ", " + clr[1] + ", " + clr[2] + ", " + clr[3]
+ };
+
+ } 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 );
+ }
+
+ flatProp.mapping = prop; // keep a reference to the mapping
+ prop = flatProp; // the flattened (mapped) property is the one we want
+
+ break;
+
+ // direct mapping
+ case $$.style.types.data:
+ case $$.style.types.layoutData:
+
+ var isLayout = prop.mapped === $$.style.types.layoutData;
+
+ // flatten the field (e.g. data.foo.bar)
+ var fields = prop.field.split(".");
+ var fieldVal = isLayout ? ele._private.layoutData : ele._private.data;
+ for( var i = 0; i < fields.length && fieldVal; 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
+ flatProp = this.parse( prop.name, origProp.strValue, 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
+ var prevProp;
+
+ if( origPropIsBypass ){ // then keep the orig prop (since it's a bypass) and link to the new prop
+ prevProp = origProp.bypassed;
+
+ origProp.bypassed = prop;
+ } else { // then just replace the old prop with the new one
+ prevProp = style[ prop.name ];
+
+ style[ prop.name ] = prop;
+ }
+
+ if( prevProp && prevProp.mapping && prop.mapping && prevProp.context === context ){
+ prevProp = prevProp.prev;
+ }
+
+ if( prevProp && prevProp !== prop ){
+ prop.prev = prevProp;
+ }
+ }
+
+ prop.context = context;
+
+ 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 ){
+ 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 < $$.style.properties.length; j++ ){ // for each prop
+ var prop = $$.style.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 );
+ }
+ };
+
+ $$.styfn.updateTransitions = function( ele, addedCxts, removedCxts ){
+ var self = this;
+ var style = ele._private.style;
+
+ var props = style['transition-property'].value;
+ var duration = style['transition-duration'].value * 1000;
+ var delay = style['transition-delay'].value * 1000;
+ 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 fromProp = styProp.prev;
+ var toProp = style[ prop ];
+ var diff = false;
+ var fromAddedCxt = false;
+ var fromRemovedCxt = false;
+
+ // see if the prop was added from one of the contexts
+ for( var j = 0; j < addedCxts.length; j++ ){
+ var cxt = addedCxts[j];
+
+ if( cxt === toProp.context ){
+ fromAddedCxt = true;
+ break;
+ }
+ }
+
+ // see if the prop was removed from one of the contexts
+ for( var j = removedCxts.length - 1; j >= 0; j-- ){ // reverse order b/c last has precedence
+ var cxt = removedCxts[j];
+
+ for( var k = 0; k < cxt.properties.length; k++ ){
+ var cProp = cxt.properties[k];
+
+ if( cProp.name === prop ){
+ fromRemovedCxt = true;
+ fromProp = cProp;
+ break;
+ }
+ }
+
+ if( fromRemovedCxt ){ break; }
+ }
+
+ // if not from changed context, then it's not a state transition but just an overriding part of the stylesheet
+ if( !fromAddedCxt && !fromRemovedCxt ){ continue; }
+
+ // consider px values
+ if( $$.is.number( fromProp.pxValue ) && $$.is.number( toProp.pxValue ) ){
+ diff = fromProp.pxValue !== toProp.pxValue;
+
+ // consider numerical values
+ } else if( $$.is.number( fromProp.value ) && $$.is.number( toProp.value ) ){
+ diff = fromProp.value !== toProp.value;
+
+ // 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]
+ ;
+ }
+
+ // 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, fromProp.strValue); // from val
+ anyPrev = true;
+ }
+
+ } // end if props allow ani
+
+ // can't transition if there's nothing previous to transition from
+ if( !anyPrev ){ return; }
+
+ ele._private.transitioning = true;
+
+ ele.stop();
+
+ if( delay > 0 ){
+ ele.delay( delay );
+ }
+
+ ele.animate({
+ css: css
+ }, {
+ duration: duration,
+ queue: false,
+ complete: function(){
+ self.removeAllBypasses( ele );
+
+ ele._private.transitioning = false;
+ }
+ });
+
+ } else if( ele._private.transitioning ){
+ ele.stop();
+
+ this.removeAllBypasses( ele );
+
+ ele._private.transitioning = false;
+ }
+ };
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ // 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 ){
+ var props = [];
+
+ // 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 < $$.style.properties.length; i++ ){
+ var prop = $$.style.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;
+
+ for( var i = 0; i < $$.style.properties.length; i++ ){
+ var prop = $$.style.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];
+
+ for( var j = 0; j < props.length; j++ ){ // for each prop
+ var prop = props[j];
+
+ ret = this.applyParsedProperty( ele, prop ) || ret;
+ }
+ }
+
+ return ret;
+ };
+
+ $$.styfn.removeAllBypasses = function( eles ){
+ for( var i = 0; i < $$.style.properties.length; i++ ){
+ var prop = $$.style.properties[i];
+ var name = prop.name;
+ var value = ''; // empty => remove bypass
+
+ var parsedProp = this.parse(name, value, true);
+
+ for( var j = 0; j < eles.length; j++ ){
+ var ele = eles[j];
+ this.applyParsedProperty(ele, parsedProp);
+ }
+ }
+ };
+
+})( cytoscape );
+;(function($$, window){ 'use strict';
+
+ // gets what an em size corresponds to in pixels relative to a dom element
+ $$.styfn.getEmSizeInPixels = function(){
+ var cy = this._private.cy;
+ var domElement = cy.container();
+
+ if( window && domElement && window.getComputedStyle ){
+ var pxAsStr = window.getComputedStyle(domElement).getPropertyValue('font-size');
+ var px = parseFloat( pxAsStr );
+ return px;
+ } else {
+ return 1; // in case we're running outside of the browser
+ }
+ };
+
+ // 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 );
+ }
+ };
+
+ $$.styfn.containerProperty = function( propName ){
+ var propStr = this.containerCss( propName );
+ var prop = this.parse( propName, propStr );
+ return prop;
+ };
+
+ $$.styfn.containerPropertyAsString = function( propName ){
+ var prop = this.containerProperty( propName );
+
+ if( prop ){
+ return prop.strValue;
+ }
+ };
+
+})( cytoscape, typeof window === 'undefined' ? null : window );
+;(function($$){ 'use strict';
+
+ // gets the rendered style for an element
+ $$.styfn.getRenderedStyle = function( ele ){
+ var ele = ele[0]; // insure it's an element
+
+ if( ele ){
+ var rstyle = {};
+ var style = ele._private.style;
+ var cy = this._private.cy;
+ var zoom = cy.zoom();
+
+ for( var i = 0; i < $$.style.properties.length; i++ ){
+ var prop = $$.style.properties[i];
+ var styleProp = style[ prop.name ];
+
+ if( styleProp ){
+ var val = styleProp.unitless ? styleProp.strValue : (styleProp.pxValue * zoom) + 'px';
+ rstyle[ prop.name ] = val;
+ rstyle[ $$.util.dash2camel(prop.name) ] = val;
+ }
+ }
+
+ return rstyle;
+ }
+ };
+
+ // gets the raw style for an element
+ $$.styfn.getRawStyle = function( ele ){
+ var ele = ele[0]; // insure it's an element
+
+ if( ele ){
+ var rstyle = {};
+ var style = ele._private.style;
+
+ for( var i = 0; i < $$.style.properties.length; i++ ){
+ var prop = $$.style.properties[i];
+ var styleProp = style[ prop.name ];
+
+ if( styleProp ){
+ rstyle[ prop.name ] = styleProp.strValue;
+ rstyle[ $$.util.dash2camel(prop.name) ] = styleProp.strValue;
+ }
+ }
+
+ return rstyle;
+ }
+ };
+
+ // gets the value style for an element (useful for things like animations)
+ $$.styfn.getValueStyle = function( ele ){
+ var rstyle, style;
+
+ if( $$.is.element(ele) ){
+ rstyle = {};
+ style = ele._private.style;
+ } else {
+ rstyle = {};
+ style = ele; // just passed the style itself
+ }
+
+ if( style ){
+ for( var i = 0; i < $$.style.properties.length; i++ ){
+ var prop = $$.style.properties[i];
+ var styleProp = style[ prop.name ] || style[ $$.util.dash2camel(prop.name) ];
+
+ if( styleProp !== undefined && !$$.is.plainObject( styleProp ) ){ // then make a prop of it
+ styleProp = this.parse(prop.name, styleProp);
+ }
+
+ if( styleProp ){
+ var val = styleProp.value === undefined ? styleProp : styleProp.value;
+
+ rstyle[ prop.name ] = val;
+ rstyle[ $$.util.dash2camel(prop.name) ] = val;
+ }
+ }
+ }
+
+ return rstyle;
+ };
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ $$.style.applyFromJson = function( style, json ){
+ for( var i = 0; i < json.length; i++ ){
+ var context = json[i];
+ var selector = context.selector;
+ var props = context.css;
+
+ style.selector(selector); // apply selector
+
+ for( var name in props ){
+ var value = props[name];
+
+ style.css( name, value ); // apply property
+ }
+ }
+
+ return style;
+ };
+
+ // static function
+ $$.style.fromJson = function( cy, json ){
+ var style = new $$.Style(cy);
+
+ $$.style.applyFromJson( style, json );
+
+ return style;
+ };
+
+ // accessible cy.style() function
+ $$.styfn.fromJson = function( json ){
+ var style = this;
+
+ style.resetToDefault();
+
+ $$.style.applyFromJson( style, json );
+
+ return style;
+ };
+
+ // get json from cy.style() api
+ $$.styfn.json = function(){
+ var json = [];
+
+ for( var i = 0; 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(),
+ css: css
+ });
+ }
+
+ return json;
+ };
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ $$.style.applyFromString = function( style, string ){
+ 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];
+ 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 = $$.style.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;
+ };
+
+ $$.style.fromString = function( cy, string ){
+ var style = new $$.Style(cy);
+
+ $$.style.applyFromString( style, string );
+
+ return style;
+ };
+
+ $$.styfn.fromString = function( string ){
+ var style = this;
+
+ style.resetToDefault();
+
+ $$.style.applyFromString( style, string );
+
+ return style;
+ };
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ // a dummy stylesheet object that doesn't need a reference to the core
+ // (useful for init)
+ $$.stylesheet = $$.Stylesheet = function(){
+ if( !(this instanceof $$.Stylesheet) ){
+ return new $$.Stylesheet();
+ }
+
+ this.length = 0;
+ };
+
+ // just store the selector to be parsed later
+ $$.Stylesheet.prototype.selector = function( selector ){
+ var i = this.length++;
+
+ this[i] = {
+ selector: selector,
+ properties: []
+ };
+
+ return this; // chaining
+ };
+
+ // just store the property to be parsed later
+ $$.Stylesheet.prototype.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
+ };
+
+ // generate a real style object from the dummy stylesheet
+ $$.Stylesheet.prototype.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;
+ };
+
+})( cytoscape );
+;(function($$, window){ 'use strict';
+
+ var isTouch = $$.is.touch();
+
+ var defaults = {
+ hideEdgesOnViewport: false
+ };
+
+ var origDefaults = $$.util.copy( defaults );
+
+ $$.defaults = function( opts ){
+ defaults = $$.util.extend({}, origDefaults, opts);
+ };
+
+ $$.fn.core = function( fnMap, options ){
+ for( var name in fnMap ){
+ var fn = fnMap[name];
+ $$.Core.prototype[ name ] = fn;
+ }
+ };
+
+ $$.Core = function( opts ){
+ if( !(this instanceof $$.Core) ){
+ return new $$.Core(opts);
+ }
+ var cy = this;
+
+ opts = $$.util.extend({}, defaults, opts);
+
+ var container = opts.container;
+ var reg = $$.getRegistrationForInstance(cy, container);
+ if( reg && reg.cy ){
+ reg.domElement.innerHTML = '';
+ reg.cy.notify({ type: 'destroy' }); // destroy the renderer
+
+ $$.removeRegistrationForInstance(reg.cy, reg.domElement);
+ }
+
+ reg = $$.registerInstance( cy, container );
+ var readies = reg.readies;
+
+ var options = opts;
+ options.layout = $$.util.extend( { name: window && container ? 'grid' : 'null' }, options.layout );
+ options.renderer = $$.util.extend( { name: window && container ? 'canvas' : 'null' }, options.renderer );
+
+ // TODO determine whether we need a check like this even though we allow running headless now
+ //
+ // if( !$$.is.domElement(options.container) ){
+ // $$.util.error("Cytoscape.js must be called on an element");
+ // return;
+ // }
+
+ var _p = this._private = {
+ ready: false, // whether ready has been triggered
+ initrender: false, // has initrender has been triggered
+ instanceId: reg.id, // the registered instance id
+ options: options, // cached options
+ elements: [], // array of elements
+ id2index: {}, // element id => index in elements array
+ listeners: [], // list of listeners
+ aniEles: [], // array of 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: options.zoomingEnabled === undefined ? true : options.zoomingEnabled,
+ userZoomingEnabled: options.userZoomingEnabled === undefined ? true : options.userZoomingEnabled,
+ panningEnabled: options.panningEnabled === undefined ? true : options.panningEnabled,
+ userPanningEnabled: options.userPanningEnabled === undefined ? true : options.userPanningEnabled,
+ boxSelectionEnabled: options.boxSelectionEnabled === undefined ? true : options.boxSelectionEnabled,
+ autolockNodes: false,
+ autoungrabifyNodes: options.autoungrabifyNodes === undefined ? false : options.autoungrabifyNodes,
+ 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
+ },
+ hasCompoundNodes: false
+ };
+
+ // set selection type
+ var selType = options.selectionType;
+ if( selType === undefined || (selType !== 'additive' && selType !== 'single') ){
+ // then set default
+
+ if( isTouch ){
+ _p.selectionType = 'additive';
+ } else {
+ _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;
+ }
+
+ // init style
+
+ if( $$.is.stylesheet(options.style) ){
+ _p.style = options.style.generateStyle(this);
+ } else if( $$.is.array(options.style) ) {
+ _p.style = $$.style.fromJson(this, options.style);
+ } else if( $$.is.string(options.style) ){
+ _p.style = $$.style.fromString(this, options.style);
+ } else {
+ _p.style = new $$.Style( cy );
+ }
+
+ // create the renderer
+ cy.initRenderer( $$.util.extend({
+ hideEdgesOnViewport: options.hideEdgesOnViewport,
+ hideLabelsOnViewport: options.hideLabelsOnViewport,
+ textureOnViewport: options.textureOnViewport
+ }, options.renderer) );
+
+ // trigger the passed function for the `initrender` event
+ if( options.initrender ){
+ cy.on('initrender', options.initrender);
+ cy.on('initrender', function(){
+ cy._private.initrender = true;
+ });
+ }
+
+ // initial load
+ cy.load(options.elements, function(){ // onready
+ cy.startAnimationLoop();
+ cy._private.ready = true;
+
+ // if a ready callback is specified as an option, the bind it
+ if( $$.is.fn( options.ready ) ){
+ cy.bind('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.bind('ready', fn);
+ }
+ 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);
+ };
+
+ $$.corefn = $$.Core.prototype; // short alias
+
+
+ $$.fn.core({
+ ready: function(){
+ return this._private.ready;
+ },
+
+ initrender: function(){
+ return this._private.initrender;
+ },
+
+ registered: function(){
+ if( this._private && this._private.instanceId != null ){
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ registeredId: function(){
+ return this._private.instanceId;
+ },
+
+ getElementById: function( id ){
+ var index = this._private.id2index[ id ];
+ if( index !== undefined ){
+ return this._private.elements[ index ];
+ }
+
+ // worst case, return an empty collection
+ return new $$.Collection( this );
+ },
+
+ selectionType: function(){
+ return this._private.selectionType;
+ },
+
+ hasCompoundNodes: function(){
+ return this._private.hasCompoundNodes;
+ },
+
+ 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 ){
+ delete this._private.id2index[ id ];
+ 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 ]--;
+ }
+ }
+ }
+ },
+
+ container: function(){
+ return this._private.options.container;
+ },
+
+ options: function(){
+ return $$.util.copy( this._private.options );
+ },
+
+ json: function(params){
+ var json = {};
+ var cy = this;
+
+ 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() );
+ });
+
+ json.style = cy.style().json();
+ json.scratch = cy.scratch();
+ 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 = cy._private.pan;
+ json.boxSelectionEnabled = cy._private.boxSelectionEnabled;
+ json.layout = cy._private.options.layout;
+ json.renderer = cy._private.options.renderer;
+ json.hideEdgesOnViewport = cy._private.options.hideEdgesOnViewport;
+ json.hideLabelsOnViewport = cy._private.options.hideLabelsOnViewport;
+ json.textureOnViewport = cy._private.options.textureOnViewport;
+
+ return json;
+ }
+
+ });
+
+})( cytoscape, typeof window === 'undefined' ? null : window );
+
+(function($$, window){ 'use strict';
+
+ function ready(f) {
+ var fn = ( document && (document.readyState === 'interactive' || document.readyState === 'complete') ) ? f : ready;
+
+ setTimeout(fn, 9, f);
+ }
+
+ $$.fn.core({
+ 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 = elesArray[j];
+
+ var mjson = $$.util.extend({}, json, { group: group });
+ jsons.push( mjson );
+ }
+ }
+ }
+
+ 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;
+
+ // remove old elements
+ var oldEles = cy.elements();
+ if( oldEles.length > 0 ){
+ oldEles.remove();
+ }
+
+ cy.notifications(false);
+
+ if( elements != null ){
+ if( $$.is.plainObject(elements) || $$.is.array(elements) ){
+ cy.add( elements );
+ }
+ }
+
+ function callback(){
+ 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');
+ });
+
+ cy.layout( cy._private.options.layout );
+
+ }
+
+ if( window ){
+ ready( callback );
+ } else {
+ callback();
+ }
+
+ return this;
+ }
+ });
+
+})( cytoscape, typeof window === 'undefined' ? null : window );
+
+;(function($$, window){ 'use strict';
+
+ $$.fn.core({
+
+ addToAnimationPool: function( eles ){
+ var cy = this;
+ var aniEles = cy._private.aniEles;
+ var aniElesHas = [];
+
+ for( var i = 0; i < aniEles.length; i++ ){
+ var id = aniEles[i]._private.data.id;
+ aniElesHas[ id ] = true;
+ }
+
+ for( var i = 0; i < eles.length; i++ ){
+ var ele = eles[i];
+ var id = ele._private.data.id;
+
+ if( !aniElesHas[id] ){
+ aniEles.push( ele );
+ }
+ }
+ },
+
+ startAnimationLoop: function(){
+ var cy = this;
+ var stepDelay = 1000/60;
+ var useTimeout = false;
+ var useRequestAnimationFrame = true;
+
+ // don't execute the animation loop in headless environments
+ if( !window ){
+ return;
+ }
+
+ // initialise the list
+ cy._private.aniEles = [];
+
+ var requestAnimationFrame = $$.util.requestAnimationFrame;
+
+ if( requestAnimationFrame == null || !useRequestAnimationFrame ){
+ requestAnimationFrame = function(fn){
+ window.setTimeout(function(){
+ fn(+new Date());
+ }, stepDelay);
+ };
+ }
+
+ var containerDom = cy.container();
+
+ function globalAnimationStep(){
+ function exec(){
+ requestAnimationFrame(function(now){
+ handleElements(now);
+ globalAnimationStep();
+ }, containerDom);
+ }
+
+ if( useTimeout ){
+ setTimeout(function(){
+ exec();
+ }, stepDelay);
+ } else {
+ exec();
+ }
+ }
+
+ globalAnimationStep(); // first call
+
+ function handleElements(now){
+ now = +new Date();
+
+ var eles = cy._private.aniEles;
+ for( var e = 0; e < eles.length; e++ ){
+ var ele = eles[e];
+
+ // we might have errors if we edit animation.queue and animation.current
+ // for ele (i.e. by stopping)
+ // try{
+
+ var current = ele._private.animation.current;
+ var queue = ele._private.animation.queue;
+
+ // if nothing currently animating, get something from the queue
+ if( current.length === 0 ){
+ var q = queue;
+ var next = q.length > 0 ? q.shift() : null;
+
+ if( next != null ){
+ next.callTime = +new Date(); // was queued, so update call time
+ current.push( next );
+ }
+ }
+
+ // step and remove if done
+ var completes = [];
+ for(var i = 0; i < current.length; i++){
+ var ani = current[i];
+ step( ele, ani, now );
+
+ if( current[i].done ){
+ completes.push( ani );
+
+ // remove current[i]
+ current.splice(i, 1);
+ i--;
+ }
+ }
+
+ // call complete callbacks
+ for( var i = 0; i < completes.length; i++ ){
+ var ani = completes[i];
+ var complete = ani.params.complete;
+
+ if( $$.is.fn(complete) ){
+ complete.apply( ele, [ now ] );
+ }
+ }
+
+ // } catch(e){
+ // // do nothing
+ // }
+
+ } // each element
+
+
+ // notify renderer
+ if( eles.length > 0 ){
+ cy.notify({
+ type: 'draw',
+ collection: eles
+ });
+ }
+
+ // remove elements from list of currently animating if its queues are empty
+ for( var i = 0; i < eles.length; i++ ){
+ var ele = eles[i];
+ var queue = ele._private.animation.queue;
+ var current = ele._private.animation.current;
+ var keepEle = current.length > 0 || queue.length > 0;
+
+ if( !keepEle ){ // then remove from the array
+ eles.splice(i, 1);
+ i--;
+ }
+ }
+
+ } // handleElements
+
+ function step( self, animation, now ){
+ var style = cy._private.style;
+ var properties = animation.properties;
+ var params = animation.params;
+ var startTime = animation.callTime;
+ var percent;
+
+ if( animation.duration === 0 ){
+ percent = 1;
+ } else {
+ percent = Math.min(1, (now - startTime)/animation.duration);
+ }
+
+ if( percent < 0 ){
+ percent = 0;
+ } else if( percent > 1 ){
+ percent = 1;
+ }
+
+ if( properties.delay == null ){ // then update the position
+ var startPos = animation.startPosition;
+ var endPos = properties.position;
+ var pos = self._private.position;
+ if( endPos ){
+ if( valid( startPos.x, endPos.x ) ){
+ pos.x = ease( startPos.x, endPos.x, percent );
+ }
+
+ if( valid( startPos.y, endPos.y ) ){
+ pos.y = ease( startPos.y, endPos.y, percent );
+ }
+ }
+
+ if( properties.css ){
+ var props = $$.style.properties;
+ for( var i = 0; i < props.length; i++ ){
+ var name = props[i].name;
+ var end = properties.css[ name ];
+
+ if( end !== undefined ){
+ var start = animation.startStyle[ name ];
+ var easedVal = ease( start, end, percent );
+
+ style.applyBypass( self, name, easedVal );
+ }
+ } // for props
+ } // if
+ }
+
+ if( $$.is.fn(params.step) ){
+ params.step.apply( self, [ now ] );
+ }
+
+ if( percent >= 1 ){
+ animation.done = true;
+ }
+
+ 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;
+ }
+
+ function ease(start, end, percent){
+ if( percent < 0 ){
+ percent = 0;
+ } else if( percent > 1 ){
+ percent = 1;
+ }
+
+ if( $$.is.number(start) && $$.is.number(end) ){
+ return start + (end - start) * percent;
+
+ } else if( $$.is.number(start[0]) && $$.is.number(end[0]) ){ // then assume a colour
+ var c1 = start;
+ var c2 = end;
+
+ var ch = function(ch1, ch2){
+ var diff = ch2 - ch1;
+ var min = ch1;
+ return Math.round( percent * diff + min );
+ };
+
+ var r = ch( c1[0], c2[0] );
+ var g = ch( c1[1], c2[1] );
+ var b = ch( c1[2], c2[2] );
+
+ return 'rgb(' + r + ', ' + g + ', ' + b + ')';
+ }
+
+ return undefined;
+ }
+
+ }
+
+ });
+
+})( cytoscape, typeof window === 'undefined' ? null : window );
+
+
+
+
+;(function($$){ 'use strict';
+
+ $$.fn.core({
+ data: $$.define.data({
+ field: 'data',
+ bindingEvent: 'data',
+ allowBinding: true,
+ allowSetting: true,
+ settingEvent: 'data',
+ settingTriggersEvent: true,
+ triggerFnName: 'trigger',
+ allowGetting: true
+ }),
+
+ removeData: $$.define.removeData({
+ field: 'data',
+ event: 'data',
+ triggerFnName: 'trigger',
+ triggerEvent: true
+ }),
+
+ batchData: $$.define.batchData({
+ field: 'data',
+ event: 'data',
+ triggerFnName: 'trigger',
+ immutableKeys: {
+ 'id': true,
+ 'source': true,
+ 'target': true,
+ 'parent': true
+ },
+ updateStyle: true
+ }),
+
+ scratch: $$.define.data({
+ field: 'scratch',
+ allowBinding: false,
+ allowSetting: true,
+ settingTriggersEvent: false,
+ allowGetting: true
+ }),
+
+ removeScratch: $$.define.removeData({
+ field: 'scratch',
+ triggerEvent: false
+ })
+ });
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.fn.core({
+ 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] )
+ });
+
+ // aliases for those folks who like old stuff:
+ $$.corefn.bind = $$.corefn.on;
+ $$.corefn.unbind = $$.corefn.off;
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.fn.core({
+
+ png: function( options ){
+ var renderer = this._private.renderer;
+ options = options || {};
+
+ return renderer.png( options );
+ }
+
+ });
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ $$.fn.core({
+
+ layout: function( params ){
+ var cy = this;
+
+ if( this._private.layoutRunning ){ // don't run another layout if one's already going
+ return this;
+ }
+
+ // if no params, use the previous ones
+ if( params == null ){
+ params = this._private.options.layout;
+ }
+
+ this.initLayout( params );
+
+ cy.trigger('layoutstart');
+
+ this._private.layoutRunning = true;
+ this.one('layoutstop', function(){
+ cy._private.layoutRunning = false;
+ });
+
+ this._private.layout.run();
+
+ return this;
+
+ },
+
+ initLayout: function( options ){
+ if( options == null ){
+ $$.util.error('Layout options must be specified to run a layout');
+ return;
+ }
+
+ if( options.name == null ){
+ $$.util.error('A `name` must be specified to run a layout');
+ return;
+ }
+
+ var name = options.name;
+ var LayoutProto = $$.extension('layout', name);
+
+ if( LayoutProto == null ){
+ $$.util.error('Can not apply layout: No such layout `%s` found; did you include its JS file?', name);
+ return;
+ }
+
+ this._private.layout = new LayoutProto( $$.util.extend({}, options, {
+ renderer: this._private.renderer,
+ cy: this
+ }) );
+ this._private.options.layout = options; // save options
+ }
+
+ });
+
+})( cytoscape );
+(function($$){ 'use strict';
+
+ $$.fn.core({
+ notify: function( params ){
+ if( !this._private.notificationsEnabled ){ return; } // exit on disabled
+
+ var renderer = this.renderer();
+ var cy = this;
+
+ // normalise params.collection
+ if( $$.is.element(params.collection) ){ // make collection from element
+ var element = params.collection;
+ params.collection = new $$.Collection(cy, [ element ]);
+
+ } else if( $$.is.array(params.collection) ){ // make collection from elements array
+ var elements = params.collection;
+ params.collection = new $$.Collection(cy, elements);
+ }
+
+ 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);
+ }
+ });
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.fn.core({
+
+ 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'
+ });
+
+ return this;
+ },
+
+ initRenderer: function( options ){
+ var cy = this;
+
+ var RendererProto = $$.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;
+ }
+
+ this._private.renderer = new RendererProto(
+ $$.util.extend({}, options, {
+ cy: cy,
+ style: cy._private.style
+ })
+ );
+
+ },
+
+ recalculateRenderedStyle: function(){
+ var renderer = this.renderer();
+
+ if( renderer.recalculateRenderedStyle ){
+ renderer.recalculateRenderedStyle();
+ }
+ }
+
+ });
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ $$.fn.core({
+
+ // 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 ){
+
+ if( $$.is.string(eles) ){
+ return this.$( eles );
+ } else if( $$.is.elementOrCollection(eles) ){
+ return eles.collection();
+ }
+
+ return new $$.Collection( this );
+ },
+
+ nodes: function( selector ){
+ var nodes = this.$('node');
+
+ if( selector ){
+ return nodes.filter( selector );
+ }
+
+ return nodes;
+ },
+
+ edges: function( selector ){
+ var edges = this.$('edge');
+
+ 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.$;
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.fn.core({
+
+ style: function(val){
+ return this._private.style;
+ }
+ });
+
+})( cytoscape );
+
+
+;(function($$){ 'use strict';
+
+ $$.fn.core({
+
+ autolockNodes: function(bool){
+ if( bool !== undefined ){
+ this._private.autolockNodes = bool ? true : false;
+ } else {
+ return this._private.autolockNodes;
+ }
+
+ return this; // chaining
+ },
+
+ autoungrabifyNodes: function(bool){
+ if( bool !== undefined ){
+ this._private.autoungrabifyNodes = bool ? true : false;
+ } else {
+ return this._private.autoungrabifyNodes;
+ }
+
+ 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( !this._private.panningEnabled ){
+ return this;
+
+ } else if( $$.is.string( args[0] ) ){ // .pan('x')
+ dim = args[0];
+ return pan[ dim ];
+
+ } else if( $$.is.plainObject( args[0] ) ) { // .pan({ 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');
+ }
+ 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');
+ 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');
+ }
+ 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');
+ break;
+
+ default:
+ break; // invalid
+ }
+
+ this.notify({ // notify the renderer that the viewport changed
+ type: 'viewport'
+ });
+
+ return this; // chaining
+ },
+
+ fit: function( elements, padding ){
+ if( $$.is.number(elements) && padding === undefined ){ // elements is optional
+ padding = elements;
+ elements = undefined;
+ }
+
+ if( !this._private.panningEnabled || !this._private.zoomingEnabled ){
+ return this;
+ }
+
+ if( $$.is.string(elements) ){
+ var sel = elements;
+ elements = this.$( sel );
+ } else if( !$$.is.elementOrCollection(elements) ){
+ elements = this.elements();
+ }
+
+ var bb = elements.boundingBox();
+ var style = this.style();
+
+ var w = parseFloat( style.containerCss('width') );
+ var h = parseFloat( style.containerCss('height') );
+ var zoom;
+ padding = $$.is.number(padding) ? padding : 0;
+
+ if( !isNaN(w) && !isNaN(h) && elements.length > 0 ){
+ zoom = this._private.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;
+
+ this._private.pan = { // now pan to middle
+ x: (w - zoom*( bb.x1 + bb.x2 ))/2,
+ y: (h - zoom*( bb.y1 + bb.y2 ))/2
+ };
+
+ this.trigger('pan zoom');
+
+ this.notify({ // notify the renderer that the viewport changed
+ type: 'viewport'
+ });
+ }
+
+ return this; // chaining
+ },
+
+ 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' : '') );
+
+ } else { // just set the zoom
+ this._private.zoom = zoom;
+ this.trigger('zoom');
+ }
+
+ 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 && _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 ){
+ this.trigger( events.join(' ') );
+ }
+
+ this.notify({
+ type: 'viewport'
+ });
+
+ return this; // chaining
+ },
+
+ // get the bounding box of the elements (in raw model position)
+ boundingBox: function( selector ){
+ var eles = this.$( selector );
+
+ return eles.boundingBox();
+ },
+
+ renderedBoundingBox: function( selector ){
+ var eles = this.$( selector );
+
+ return eles.renderedBoundingBox();
+ },
+
+ center: function(elements){
+ if( !this._private.panningEnabled || !this._private.zoomingEnabled ){
+ return this;
+ }
+
+ if( $$.is.string(elements) ){
+ var selector = elements;
+ elements = cy.elements( selector );
+ } else if( !$$.is.elementOrCollection(elements) ){
+ elements = cy.elements();
+ }
+
+ var bb = elements.boundingBox();
+ var style = this.style();
+ var w = parseFloat( style.containerCss('width') );
+ var h = parseFloat( style.containerCss('height') );
+ var zoom = this._private.zoom;
+
+ this.pan({ // now pan to middle
+ x: (w - zoom*( bb.x1 + bb.x2 ))/2,
+ y: (h - zoom*( bb.y1 + bb.y2 ))/2
+ });
+
+ this.trigger('pan');
+
+ this.notify({ // notify the renderer that the viewport changed
+ type: 'viewport'
+ });
+
+ return this; // chaining
+ },
+
+ reset: function(){
+ if( !this._private.panningEnabled || !this._private.zoomingEnabled ){
+ return this;
+ }
+
+ this.pan({ x: 0, y: 0 });
+
+ if( this._private.maxZoom > 1 && this._private.minZoom < 1 ){
+ this.zoom(1);
+ }
+
+ this.notify({ // notify the renderer that the viewport changed
+ type: 'viewport'
+ });
+
+ return this; // chaining
+ }
+ });
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ // Use this interface to define functions for collections/elements.
+ // This interface is good, because it forces you to think in terms
+ // of the collections case (more than 1 element), so we don't need
+ // notification blocking nonsense everywhere.
+ //
+ // Other collection-*.js files depend on this being defined first.
+ // It's a trade off: It simplifies the code for Collection and
+ // Element integration so much that it's worth it to create the
+ // JS dependency.
+ //
+ // Having this integration guarantees that we can call any
+ // collection function on an element and vice versa.
+
+ // e.g. $$.fn.collection({ someFunc: function(){ /* ... */ } })
+ $$.fn.collection = $$.fn.eles = function( fnMap, options ){
+ for( var name in fnMap ){
+ var fn = fnMap[name];
+
+ $$.Collection.prototype[ name ] = fn;
+ }
+ };
+
+ // factory for generating edge ids when no id is specified for a new element
+ var idFactory = {
+ prefix: {
+ nodes: 'n',
+ edges: 'e'
+ },
+ id: {
+ nodes: 0,
+ edges: 0
+ },
+ generate: function(cy, element, tryThisId){
+ var json = $$.is.element( element ) ? element._private : element;
+ var group = json.group;
+ var id = tryThisId != null ? tryThisId : this.prefix[group] + this.id[group];
+
+ if( cy.getElementById(id).empty() ){
+ this.id[group]++; // 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[group] + ( ++this.id[group] );
+ }
+ }
+
+ return id;
+ }
+ };
+
+ // Element
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ // represents a node or an edge
+ $$.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;
+ }
+
+ // validate group
+ if( params.group !== 'nodes' && params.group !== 'edges' ){
+ $$.util.error('An element must be of type `nodes` or `edges`; you specified `' + params.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
+ layoutData: {}, // place for layouts to put calculated stats etc for mappers
+ position: params.position || {}, // fields x, y, etc (could be 3d or radial coords; renderer decides)
+ 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: params.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: {}, // 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.css ){
+ cy.style().applyBypass( this, params.css );
+ }
+
+ if( restore === undefined || restore ){
+ this.restore();
+ }
+
+ };
+
+
+ // Collection
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ // represents a set of nodes, edges, or both together
+ $$.Collection = function(cy, elements){
+ if( !(this instanceof $$.Collection) ){
+ return new $$.Collection(cy, elements);
+ }
+
+ if( cy === undefined || !$$.is.core(cy) ){
+ $$.util.error('A collection must have a reference to the core');
+ return;
+ }
+
+ var ids = {};
+ 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( !ids[ id ] ){
+ ids[ id ] = element;
+
+ this[ this.length ] = element;
+ this.length++;
+ }
+ }
+
+ this._private = {
+ cy: cy,
+ ids: ids
+ };
+
+ // 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
+ $$.elefn = $$.elesfn = $$.Element.prototype = $$.Collection.prototype;
+
+ $$.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.json = function(){
+ var ele = this.element();
+ if( ele == null ){ return undefined; }
+
+ var p = ele._private;
+
+ var json = $$.util.copy({
+ data: p.data,
+ position: p.position,
+ group: p.group,
+ bypass: p.bypass,
+ removed: p.removed,
+ selected: p.selected,
+ selectable: p.selectable,
+ locked: p.locked,
+ grabbed: p.grabbed,
+ grabbable: p.grabbable,
+ classes: ''
+ });
+
+ var classes = [];
+ for( var cls in p.classes ){
+ classes.push(cls);
+ }
+
+ for( var i = 0; i < classes.length; i++ ){
+ var cls = classes[i];
+ json.classes += cls + ( i < classes.length - 1 ? ' ' : '' );
+ }
+
+ 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.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.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.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( 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;
+
+ var parentId = node._private.data.parent;
+ var specifiedParent = parentId != null;
+
+ if( specifiedParent ){
+ var parent = cy.getElementById( parentId );
+
+ if( parent.empty() ){
+ // non-existant parent; just remove it
+ delete data.parent;
+ } else {
+ var selfAsParent = false;
+ var ancestor = parent;
+ while( !ancestor.empty() ){
+ if( node.same(ancestor) ){
+ // mark self as parent and remove from data
+ selfAsParent = true;
+ delete data.parent; // remove parent reference
+
+ // exit or we loop forever
+ break;
+ }
+
+ ancestor = ancestor.parent();
+ }
+
+ if( !selfAsParent ){
+ // connect with children
+ parent[0]._private.children.push( node );
+
+ // 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 this;
+ };
+
+})( cytoscape );
+
+
+;(function($$){ 'use strict';
+
+ // search, spanning trees, etc
+ $$.fn.eles({
+
+ // do a breadth first search from the nodes in the collection
+ // from pseudocode on wikipedia
+ breadthFirstSearch: function( roots, fn, directed ){
+ directed = arguments.length === 2 && !$$.is.fn(fn) ? fn : directed;
+ fn = $$.is.fn(fn) ? fn : function(){};
+ var cy = this._private.cy;
+ var v = $$.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] );
+ V[ v[i].id() ] = true;
+
+ connectedNodes.push( v[i] );
+ id2depth[ v[i].id() ] = 0;
+ }
+ }
+
+ while( Q.length !== 0 ){
+ var v = Q.shift();
+ var depth = id2depth[ v.id() ];
+ var ret = fn.call(v, j++, depth);
+
+ 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 );
+ V[ w.id() ] = true;
+
+ id2depth[ w.id() ] = id2depth[ v.id() ] + 1;
+
+ connectedNodes.push( w );
+ connectedBy[ w.id() ] = e;
+ }
+ }
+
+ }
+
+ 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: new $$.Collection( cy, connectedEles ),
+ found: new $$.Collection( cy, found )
+ };
+ },
+
+ // do a depth first search on the nodes in the collection
+ // from pseudocode on wikipedia (iterative impl)
+ depthFirstSearch: function( roots, fn, directed ){
+ directed = arguments.length === 2 && !$$.is.fn(fn) ? fn : directed;
+ fn = $$.is.fn(fn) ? fn : function(){};
+ var cy = this._private.cy;
+ var v = $$.is.string(roots) ? this.filter(roots) : roots;
+ var S = [];
+ var connectedNodes = [];
+ var connectedBy = {};
+ var id2depth = {};
+ var discovered = {};
+ var j = 0;
+ var found;
+ var edges = this.edges();
+ var nodes = this.nodes();
+
+ // push v
+ for( var i = 0; i < v.length; i++ ){
+ if( v[i].isNode() ){
+ S.push( v[i] );
+
+ connectedNodes.push( v[i] );
+ id2depth[ v[i].id() ] = 0;
+ }
+ }
+
+ while( S.length !== 0 ){
+ var v = S.pop();
+
+ if( !discovered[ v.id() ] ){
+ discovered[ v.id() ] = true;
+
+ var depth = id2depth[ v.id() ];
+
+ var ret = fn.call(v, j++, depth);
+
+ 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 && !discovered[ w.id() ] ){
+ w = w[0];
+
+ S.push( w );
+
+ id2depth[ w.id() ] = id2depth[ v.id() ] + 1;
+
+ connectedNodes.push( w );
+ connectedBy[ w.id() ] = e;
+ }
+ }
+ }
+ }
+
+ 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: new $$.Collection( cy, connectedEles ),
+ found: new $$.Collection( cy, found )
+ };
+ },
+
+ // kruskal's algorithm (finds min spanning tree, assuming undirected graph)
+ // implemented from pseudocode from wikipedia
+ kruskal: function( weightFn ){
+ 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 = new $$.Collection(this._private.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);
+ var weightB = weightFn.call(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 cy = this._private.cy;
+ directed = !$$.is.fn(weightFn) ? weightFn : directed;
+ 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).eq(0) : root.eq(0);
+ var dist = {};
+ var prev = {};
+ var knownDist = {};
+
+ var edges = this.edges().filter(function(){ return !this.isLoop(); });
+ var nodes = this.nodes();
+ var Q = [];
+
+ for( var i = 0; i < nodes.length; i++ ){
+ dist[ nodes[i].id() ] = nodes[i].same( source ) ? 0 : Infinity;
+ Q.push( nodes[i] );
+ }
+
+ var valueFn = function(node) {
+ return dist[ node.id() ];
+ };
+
+ Q = new $$.Collection(cy, Q);
+
+ var heap = $$.Minheap(cy, Q, valueFn);
+
+ 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.call(edge);
+
+ if( weight < smallestDistance || !smallestEdge ){
+ smallestDistance = weight;
+ smallestEdge = edge;
+ }
+ }
+
+ return {
+ edge: smallestEdge,
+ dist: smallestDistance
+ };
+ };
+
+ while(heap.size() > 0){
+ var smallestEl = heap.pop(),
+ smalletsDist = smallestEl.value,
+ uid = smallestEl.id,
+ u = cy.getElementById(uid);
+
+ 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 < heap.getValueById(vid) ){
+ heap.edit(vid, alt);
+ prev[ vid ] = {
+ node: u,
+ edge: vDist.edge
+ };
+ }
+ } // for
+ } // while
+
+ return {
+ distanceTo: function(node){
+ var target = $$.is.string(node) ? nodes.filter(node).eq(0) : node.eq(0);
+
+ return knownDist[ target.id() ];
+ },
+
+ pathTo: function(node){
+ var target = $$.is.string(node) ? nodes.filter(node).eq(0) : node.eq(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 new $$.Collection( cy, S );
+ }
+ };
+ }
+ });
+
+ // nice, short mathemathical alias
+ $$.elesfn.bfs = $$.elesfn.breadthFirstSearch;
+ $$.elesfn.dfs = $$.elesfn.depthFirstSearch;
+
+})( cytoscape );
+;(function( $$ ){ 'use strict';
+
+ $$.fn.eles({
+ animated: function(){
+ var ele = this[0];
+
+ if( ele ){
+ return ele._private.animation.current.length > 0;
+ }
+ },
+
+ clearQueue: function(){
+ for( var i = 0; i < this.length; i++ ){
+ var ele = this[i];
+ ele._private.animation.queue = [];
+ }
+
+ return this;
+ },
+
+ delay: function( time, complete ){
+ this.animate({
+ delay: time
+ }, {
+ duration: time,
+ complete: complete
+ });
+
+ return this;
+ },
+
+ animate: function( properties, params ){
+ var callTime = +new Date();
+ var cy = this._private.cy;
+ var style = cy.style();
+ var q;
+
+ if( params === undefined ){
+ params = {};
+ }
+
+ if( params.duration === undefined ){
+ params.duration = 400;
+ }
+
+ switch( params.duration ){
+ case 'slow':
+ params.duration = 600;
+ break;
+ case 'fast':
+ params.duration = 200;
+ break;
+ }
+
+ if( properties == null || (properties.position == null && properties.renderedPosition == null && properties.css == null && properties.delay == null) ){
+ return this; // nothing to animate
+ }
+
+ if( properties.css ){
+ properties.css = style.getValueStyle( properties.css );
+ }
+
+ if( properties.renderedPosition ){
+ 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
+ };
+ }
+
+ for( var i = 0; i < this.length; i++ ){
+ var self = this[i];
+
+ var pos = self._private.position;
+ var startPosition = {
+ x: pos.x,
+ y: pos.y
+ };
+ var startStyle = style.getValueStyle( self );
+
+ if( self.animated() && (params.queue === undefined || params.queue) ){
+ q = self._private.animation.queue;
+ } else {
+ q = self._private.animation.current;
+ }
+
+ q.push({
+ properties: properties,
+ duration: params.duration,
+ params: params,
+ callTime: callTime,
+ startPosition: startPosition,
+ startStyle: startStyle
+ });
+ }
+
+ cy.addToAnimationPool( this );
+
+ return this; // chaining
+ }, // animate
+
+ stop: function(clearQueue, jumpToEnd){
+ for( var i = 0; i < this.length; i++ ){
+ var self = this[i];
+ var anis = self._private.animation.current;
+
+ for( var j = 0; j < anis.length; j++ ){
+ var animation = anis[j];
+ if( jumpToEnd ){
+ // next iteration of the animation loop, the animation
+ // will go straight to the end and be removed
+ animation.duration = 0;
+ }
+ }
+
+ // clear the queue of future animations
+ if( clearQueue ){
+ self._private.animation.queue = [];
+ }
+
+ if( !jumpToEnd ){
+ self._private.animation.current = [];
+ }
+ }
+
+ // we have to notify (the animation loop doesn't do it for us on `stop`)
+ this.cy().notify({
+ collection: this,
+ type: 'draw'
+ });
+
+ return this;
+ }
+ });
+
+})( cytoscape );
+
+;(function( $$ ){ 'use strict';
+
+ $$.fn.eles({
+ addClass: function(classes){
+ classes = classes.split(/\s+/);
+ var self = this;
+ var changed = [];
+
+ for( var i = 0; i < classes.length; i++ ){
+ var cls = classes[i];
+ if( $$.is.emptyString(cls) ){ continue; }
+
+ for( var j = 0; j < self.length; j++ ){
+ var ele = self[j];
+ var hasClass = ele._private.classes[cls];
+ ele._private.classes[cls] = true;
+
+ if( !hasClass ){ // if didn't already have, add to list of changed
+ changed.push( ele );
+ }
+ }
+ }
+
+ // trigger update style on those eles that had class changes
+ if( changed.length > 0 ){
+ new $$.Collection(this._private.cy, changed).updateStyle();
+ }
+
+ self.trigger('class');
+ return self;
+ },
+
+ hasClass: function(className){
+ var ele = this[0];
+ return ( ele != null && ele._private.classes[className] ) ? true : false;
+ },
+
+ toggleClass: function(classesStr, toggle){
+ var classes = classesStr.split(/\s+/);
+ var self = this;
+ var changed = []; // eles who had classes changed
+
+ for( var i = 0, il = self.length; i < il; i++ ){
+ var ele = self[i];
+
+ for( var j = 0; j < classes.length; j++ ){
+ var cls = classes[j];
+
+ if( $$.is.emptyString(cls) ){ continue; }
+
+ var hasClass = ele._private.classes[cls];
+ var shouldAdd = toggle || (toggle === undefined && !hasClass);
+
+ if( shouldAdd ){
+ ele._private.classes[cls] = true;
+
+ if( !hasClass ){ changed.push(ele); }
+ } else { // then remove
+ ele._private.classes[cls] = false;
+
+ if( hasClass ){ changed.push(ele); }
+ }
+
+ } // for j classes
+ } // for i eles
+
+ // trigger update style on those eles that had class changes
+ if( changed.length > 0 ){
+ new $$.Collection(this._private.cy, changed).updateStyle();
+ }
+
+ self.trigger('class');
+ return self;
+ },
+
+ removeClass: function(classes){
+ classes = classes.split(/\s+/);
+ var self = this;
+ var changed = [];
+
+ for( var i = 0; i < self.length; i++ ){
+ var ele = self[i];
+
+ for( var j = 0; j < classes.length; j++ ){
+ var cls = classes[j];
+ if( !cls || cls === '' ){ continue; }
+
+ var hasClass = ele._private.classes[cls];
+ delete ele._private.classes[cls];
+
+ if( hasClass ){ // then we changed its set of classes
+ changed.push( ele );
+ }
+ }
+ }
+
+ // trigger update style on those eles that had class changes
+ if( changed.length > 0 ){
+ new $$.Collection(self._private.cy, changed).updateStyle();
+ }
+
+ self.trigger('class');
+ return self;
+ }
+ });
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.fn.eles({
+ allAre: function(selector){
+ return this.filter(selector).length === this.length;
+ },
+
+ is: function(selector){
+ return this.filter(selector).length > 0;
+ },
+
+ 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;
+ }
+ });
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ // Compound functions
+ /////////////////////
+
+ $$.fn.eles({
+ 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 new $$.Collection( cy, parents ).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 new $$.Collection( this.cy(), parents ).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 new $$.Collection( this.cy(), children ).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 new $$.Collection( this.cy(), elements ).filter( selector );
+ }
+ });
+
+ // aliases
+ $$.elesfn.ancestors = $$.elesfn.parents;
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ var borderWidthMultiplier = 2 * 0.5;
+ var borderWidthAdjustment = 0;
+
+ $$.fn.eles({
+
+ 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
+ }),
+
+ batchData: $$.define.batchData({
+ field: 'data',
+ event: 'data',
+ triggerFnName: 'trigger',
+ immutableKeys: {
+ 'id': true,
+ 'source': true,
+ 'target': true,
+ 'parent': true
+ },
+ updateStyle: true
+ }),
+
+ scratch: $$.define.data({
+ field: 'scratch',
+ allowBinding: false,
+ allowSetting: true,
+ settingTriggersEvent: false,
+ allowGetting: true
+ }),
+
+ removeScratch: $$.define.removeData({
+ field: 'scratch',
+ triggerEvent: false
+ }),
+
+ 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;
+ }
+ },
+
+ 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();
+ }
+ }),
+
+ // 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();
+ }
+ }),
+
+ 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() ){
+ var elePos = ele._private.position;
+ elePos.x = pos.x;
+ elePos.y = pos.y;
+ }
+ }
+
+ var updatedEles = this.updateCompoundBounds();
+
+ var toTrigger = this.add( updatedEles );
+
+ if( silent ){
+ toTrigger.trigger('position');
+ } else {
+ toTrigger.rtrigger('position');
+ }
+ }
+
+ return this; // chaining
+ },
+
+ updateCompoundBounds: function(){
+ var cy = this.cy();
+
+ if( !cy.hasCompoundNodes() ){ return cy.collection(); } // save cycles for non compound graphs
+
+ var updated = [];
+
+ function update( parent ){
+ var children = parent.children();
+ var style = parent._private.style;
+ var bb = children.boundingBox({ includeLabels: false, includeEdges: false });
+ var padding = {
+ top: style['padding-top'].pxValue,
+ bottom: style['padding-bottom'].pxValue,
+ left: style['padding-left'].pxValue,
+ right: style['padding-right'].pxValue
+ };
+ var pos = parent._private.position;
+ var didUpdate = false;
+
+ if( style['width'].value === 'auto' ){
+ parent._private.autoWidth = bb.w + padding.left + padding.right;
+ pos.x = (bb.x1 + bb.x2 - padding.left + padding.right)/2;
+ didUpdate = true;
+ }
+
+ if( style['height'].value === 'auto' ){
+ parent._private.autoHeight = bb.h + padding.top + padding.bottom;
+ 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 new $$.Collection( cy, updated );
+ },
+
+ // 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
+ },
+
+ // convenience function to get a numerical value for the width of the node/edge
+ width: function(){
+ var ele = this[0];
+
+ if( ele ){
+ var w = ele._private.style.width;
+ return w.strValue === 'auto' ? ele._private.autoWidth : w.pxValue;
+ }
+ },
+
+ outerWidth: function(){
+ var ele = this[0];
+
+ if( ele ){
+ var style = ele._private.style;
+ var width = style.width.strValue === 'auto' ? ele._private.autoWidth : style.width.pxValue;
+ var border = style['border-width'] ? style['border-width'].pxValue * borderWidthMultiplier + borderWidthAdjustment : 0;
+
+ return width + border;
+ }
+ },
+
+ renderedWidth: function(){
+ var ele = this[0];
+
+ if( ele ){
+ var width = ele.width();
+ return width * this.cy().zoom();
+ }
+ },
+
+ renderedOuterWidth: function(){
+ var ele = this[0];
+
+ if( ele ){
+ var owidth = ele.outerWidth();
+ return owidth * this.cy().zoom();
+ }
+ },
+
+ // convenience function to get a numerical value for the height of the node
+ height: function(){
+ var ele = this[0];
+
+ if( ele && ele.isNode() ){
+ var h = ele._private.style.height;
+ return h.strValue === 'auto' ? ele._private.autoHeight : h.pxValue;
+ }
+ },
+
+ outerHeight: function(){
+ var ele = this[0];
+
+ if( ele ){
+ var style = ele._private.style;
+ var height = style.height.strValue === 'auto' ? ele._private.autoHeight : style.height.pxValue;
+ var border = style['border-width'] ? style['border-width'].pxValue * borderWidthMultiplier + borderWidthAdjustment : 0;
+
+ return height + border;
+ }
+ },
+
+ renderedHeight: function(){
+ var ele = this[0];
+
+ if( ele ){
+ var height = ele.height();
+ return height * this.cy().zoom();
+ }
+ },
+
+ renderedOuterHeight: function(){
+ var ele = this[0];
+
+ if( ele ){
+ var oheight = ele.outerHeight();
+ return oheight * this.cy().zoom();
+ }
+ },
+
+ 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
+ };
+ },
+
+ // get the bounding box of the elements (in raw model position)
+ boundingBox: function( options ){
+ var eles = this;
+
+ options = $$.util.extend({
+ includeNodes: true,
+ includeEdges: true,
+ includeLabels: true
+ }, options);
+
+ // recalculate projections etc
+ this.cy().recalculateRenderedStyle();
+
+ 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 ex1, ex2, ey1, ey2, x, y;
+ var includedEle = false;
+
+ if( ele.css('display') === 'none' ){ continue; } // then ele doesn't take up space
+
+ if( ele.isNode() && options.includeNodes ){
+ includedEle = true;
+
+ var pos = ele._private.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() && options.includeEdges ){
+ includedEle = true;
+
+ var n1pos = ele.source()[0]._private.position;
+ var n2pos = ele.target()[0]._private.position;
+
+ // handle edge dimensions (rough box estimate)
+ //////////////////////////////////////////////
+
+ var rstyle = ele._private.rstyle || {};
+
+ 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;
+ }
+
+ 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)
+ //////////////////////////////////////////
+
+ var bpts = rstyle.bezierPts || [];
+ var w = ele._private.style['width'].pxValue;
+ for( var j = 0; j < bpts.length; j++ ){
+ var bpt = bpts[j];
+
+ x1 = bpt.x - w < x1 ? bpt.x - w : x1;
+ x2 = bpt.x + w > x2 ? bpt.x + w : x2;
+ y1 = bpt.y - w < y1 ? bpt.y - w : y1;
+ y2 = bpt.y + w > y2 ? bpt.y + w : y2;
+ }
+
+ }
+
+ // handle label dimensions
+ //////////////////////////
+
+ var style = ele._private.style;
+ var rstyle = ele._private.rstyle;
+ var label = style['content'].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;
+
+ if( includedEle && options.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( ele.isEdge() ){
+ lx1 = labelX - lw/2;
+ lx2 = labelX + lw/2;
+ ly1 = labelY - lh/2;
+ ly2 = labelY + lh/2;
+ } 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;
+ }
+ } // for
+
+ return {
+ x1: x1,
+ x2: x2,
+ y1: y1,
+ y2: y2,
+ w: x2 - x1,
+ h: y2 - y1
+ };
+ }
+ });
+
+
+})( cytoscape );
+
+;(function( $$ ){ 'use strict';
+
+ // Regular degree functions (works on single element)
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ 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;
+ }
+ };
+ }
+
+ $$.fn.eles({
+ 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;
+ }
+ })
+ });
+
+
+ // Collection degree stats
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ 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;
+ };
+ }
+
+ $$.fn.eles({
+ 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;
+ })
+ });
+
+ $$.fn.eles({
+ 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;
+ }
+ });
+
+})( cytoscape );
+
+
+;(function($$){ 'use strict';
+
+ // Functions for binding & triggering events
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ $$.fn.eles({
+ 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 unless removed
+ this.cy().notify({
+ type: event,
+ collection: this.filter(function(){
+ return !this.removed();
+ })
+ });
+
+ this.trigger(event, extraParams);
+ return this;
+ }
+ });
+
+ // aliases for those folks who like old stuff:
+ $$.elesfn.bind = $$.elesfn.on;
+ $$.elesfn.unbind = $$.elesfn.off;
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.fn.eles({
+ 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){
+ var cy = this._private.cy;
+
+ 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 new $$.Collection(cy, elements);
+
+ } else if( $$.is.string(filter) || $$.is.elementOrCollection(filter) ){
+ return new $$.Selector(filter).filter(this);
+
+ } else if( filter === undefined ){
+ return this;
+ }
+
+ return new $$.Collection( cy ); // if not handled by above, give 'em an empty collection
+ },
+
+ not: function(toRemove){
+ var cy = this._private.cy;
+
+ 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 new $$.Collection( cy, elements );
+ }
+
+ },
+
+ intersect: function( other ){
+ var cy = this._private.cy;
+
+ // if a selector is specified, then filter by it
+ 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;
+
+ for( var id in ids1 ){
+ var ele = ids2[ id ];
+
+ if( ele ){
+ elements.push( ele );
+ }
+ }
+
+ return new $$.Collection( cy, elements );
+ },
+
+ 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 new $$.Collection(cy, elements);
+ }
+ });
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ $$.fn.eles({
+ isNode: function(){
+ return this.group() === 'nodes';
+ },
+
+ isEdge: function(){
+ return this.group() === 'edges';
+ },
+
+ isLoop: function(){
+ return this.isEdge() && this.source().id() === this.target().id();
+ },
+
+ group: function(){
+ var ele = this[0];
+
+ if( ele ){
+ return ele._private.group;
+ }
+ }
+ });
+
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ // Functions for iterating over collections
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ $$.fn.eles({
+ 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;
+ },
+
+ 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 new $$.Collection(this.cy(), array);
+ },
+
+ size: function(){
+ return this.length;
+ },
+
+ eq: function(i){
+ return this[i] || new $$.Collection( this.cy() );
+ },
+
+ empty: function(){
+ return this.length === 0;
+ },
+
+ nonempty: function(){
+ return !this.empty();
+ },
+
+ sort: function( sortFn ){
+ if( !$$.is.fn( sortFn ) ){
+ return this;
+ }
+
+ var cy = this.cy();
+ var sorted = this.toArray().sort( sortFn );
+
+ return new $$.Collection(cy, sorted);
+ },
+
+ sortByZIndex: function(){
+ return this.sort( $$.Collection.zIndexSort );
+ }
+ });
+
+
+ $$.Collection.zIndexSort = function(a, b) {
+ var elementDepth = function(ele) {
+ if (ele._private.group === 'nodes')
+ {
+ return ele._private.data.parent ? ele.parents().size() : 0;
+ }
+ else if (ele._private.group === 'edges')
+ {
+ var source = ele._private.source;
+ var target = ele._private.target;
+
+ var sourceDepth = source._private.data.parent ? source.parents().size() : 0;
+ var targetDepth = target._private.data.parent ? target.parents().size() : 0;
+
+ return Math.max(sourceDepth, targetDepth);
+ }
+ else
+ {
+ return 0;
+ }
+ };
+
+ var result = a._private.style['z-index'].value - b._private.style['z-index'].value;
+
+ var depthA = 0;
+ var depthB = 0;
+
+ // no need to calculate element depth if there is no compound node
+ if ( a.cy().hasCompoundNodes() )
+ {
+ depthA = elementDepth(a);
+ depthB = elementDepth(b);
+ }
+
+ // if both elements has same depth,
+ // then edges should be drawn first
+ if (depthA - depthB === 0)
+ {
+ // 'a' is a node, it should be drawn later
+ if (a._private.group === 'nodes'
+ && b._private.group === 'edges')
+ {
+ return 1;
+ }
+
+ // 'a' is an edge, it should be drawn first
+ else if (a._private.group === 'edges'
+ && b._private.group === 'nodes')
+ {
+ return -1;
+ }
+
+ // both nodes or both edges
+ else
+ {
+ if( result === 0 ){ // same z-index => compare indices in the core (order added to graph w/ last on top)
+ return a._private.index - b._private.index;
+ } else {
+ return result;
+ }
+ }
+ }
+
+ // elements on different level
+ else
+ {
+ // deeper element should be drawn later
+ return depthA - depthB;
+ }
+
+ // return zero if z-index values are not the same
+ return 0;
+ };
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.fn.eles({
+
+ // fully updates (recalculates) the style for the elements
+ updateStyle: function( notifyRenderer ){
+ var cy = this._private.cy;
+ var style = cy.style();
+ notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;
+
+ style.apply( this );
+
+ var updatedCompounds = this.updateCompoundBounds();
+
+ if( notifyRenderer ){
+ this.add( updatedCompounds ).rtrigger('style'); // let renderer know we changed style
+ } else {
+ this.add( updatedCompounds ).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;
+
+ style.updateMappers( this );
+
+ var updatedCompounds = this.updateCompoundBounds();
+
+ if( notifyRenderer ){
+ this.add( updatedCompounds ).rtrigger('style'); // let renderer know we changed style
+ } else {
+ this.add( updatedCompounds ).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 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 style = this.cy().style();
+
+ if( $$.is.plainObject(name) ){ // then extend the bypass
+ var props = name;
+ style.applyBypass( this, props );
+
+ var updatedCompounds = this.updateCompoundBounds();
+ this.add( updatedCompounds ).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 ele._private.style[ name ].strValue;
+ } else { // empty collection => can't get any value
+ return;
+ }
+
+ } else { // then set the bypass with the property value
+ style.applyBypass( this, name, value );
+
+ var updatedCompounds = this.updateCompoundBounds();
+ this.add( updatedCompounds ).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(){
+ var style = this.cy().style();
+ var eles = this;
+
+ for( var i = 0; i < eles.length; i++ ){
+ var ele = eles[i];
+
+ style.removeAllBypasses( ele );
+ }
+
+ var updatedCompounds = this.updateCompoundBounds();
+ this.add( updatedCompounds ).rtrigger('style');
+ },
+
+ show: function(){
+ this.css('display', 'element');
+ return this; // chaining
+ },
+
+ hide: function(){
+ this.css('display', 'none');
+ return this; // chaining
+ },
+
+ visible: function(){
+ var ele = this[0];
+
+ if( ele ){
+ var style = ele._private.style;
+
+ if(
+ style['visibility'].value !== 'visible'
+ || style['display'].value !== 'element'
+ ){
+ return false;
+ }
+
+ if( ele._private.group === 'nodes' ){
+ 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 ele = this[0];
+
+ if( ele ){
+ var parentOpacity = ele._private.style.opacity.value;
+ var parents = !ele._private.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 ele = this[0];
+
+ if( ele ){
+ return ele.effectiveOpacity() === 0;
+ }
+ },
+
+ isFullAutoParent: function(){
+ 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;
+ }
+ }
+
+ });
+
+
+ $$.elesfn.style = $$.elesfn.css;
+ $$.elesfn.renderedStyle = $$.elesfn.renderedCss;
+ $$.elesfn.removeStyle = $$.elesfn.removeCss;
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ // Collection functions that toggle a boolean value
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+ function defineSwitchFunction(params){
+ return function(){
+ var args = arguments;
+
+ // 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];
+
+ if( !params.ableField || ele._private[params.ableField] ){
+ ele._private[params.field] = params.value;
+ }
+ }
+ this.updateStyle(); // change of state => possible change of style
+ this.trigger(params.event);
+ }
+
+ return this;
+ };
+ }
+
+ function defineSwitchSet( params ){
+ $$.elesfn[ params.field ] = function(){
+ var ele = this[0];
+ if( ele ){
+ if( params.altFieldFn ){
+ var val = params.altFieldFn(ele);
+
+ if( val !== undefined ){
+ return val;
+ }
+ }
+
+ return ele._private[ params.field ];
+ }
+ };
+
+ $$.elesfn[ params.on ] = defineSwitchFunction({
+ event: params.on,
+ field: params.field,
+ ableField: params.ableField,
+ value: true
+ });
+
+ $$.elesfn[ params.off ] = defineSwitchFunction({
+ event: params.off,
+ field: params.field,
+ ableField: params.ableField,
+ value: false
+ });
+ }
+
+ defineSwitchSet({
+ field: 'locked',
+ altFieldFn: function(ele){
+ return ele.cy().autolockNodes() ? true : undefined;
+ },
+ on: 'lock',
+ off: 'unlock'
+ });
+
+ defineSwitchSet({
+ field: 'grabbable',
+ altFieldFn: function(ele){
+ return ele.cy().autoungrabifyNodes() ? false : undefined;
+ },
+ on: 'grabify',
+ off: 'ungrabify'
+ });
+
+ defineSwitchSet({
+ field: 'selected',
+ ableField: 'selectable',
+ on: 'select',
+ off: 'unselect'
+ });
+
+ defineSwitchSet({
+ field: 'selectable',
+ on: 'selectify',
+ off: 'unselectify'
+ });
+
+ $$.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;
+ }
+ };
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ $$.fn.eles({
+ // 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;
+
+ (function(){
+ hasEdgesPointingIn = ele.connectedEdges(function(){
+ return this.data('target') === ele.id() && this.data('source') !== ele.id();
+ }).length > 0;
+ })();
+
+ if( !hasEdgesPointingIn ){
+ roots.push( ele );
+ }
+ }
+
+ return new $$.Collection( this._private.cy, roots ).filter( selector );
+ },
+
+ // normally called children in graph theory
+ // these nodes =edges=> forward nodes
+ forwardNodes: function( selector ){
+ var eles = this;
+ var fNodes = [];
+
+ 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 ){
+ fNodes.push( edge.target()[0] );
+ }
+ }
+ }
+
+ return new $$.Collection( this._private.cy, fNodes ).filter( selector );
+ },
+
+ // normally called parents in graph theory
+ // these nodes <=edges= backward nodes
+ backwardNodes: function( selector ){
+ var eles = this;
+ var bNodes = [];
+
+ 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 ){
+ bNodes.push( edge.source()[0] );
+ }
+ }
+ }
+
+ return new $$.Collection( this._private.cy, bNodes ).filter( selector );
+ }
+ });
+
+
+ // Neighbourhood functions
+ //////////////////////////
+
+ $$.fn.eles({
+ neighborhood: function(selector){
+ var elements = [];
+ var cy = this._private.cy;
+ 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 otherNode = edge.connectedNodes().not(node);
+
+ // 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 ( new $$.Collection( cy, elements ) ).filter( selector );
+ },
+
+ closedNeighborhood: function(selector){
+ return this.neighborhood().add(this).filter(selector);
+ },
+
+ openNeighborhood: function(selector){
+ return this.neighborhood(selector);
+ }
+ });
+
+
+ // Edge functions
+ /////////////////
+
+ $$.fn.eles({
+ source: function( selector ){
+ var ele = this[0];
+ var src;
+
+ if( ele ){
+ src = ele._private.source;
+ }
+
+ return new $$.Collection( this.cy(), src ).filter( selector );
+ },
+
+ target: function( selector ){
+ var ele = this[0];
+ var tgt;
+
+ if( ele ){
+ tgt = ele._private.target;
+ }
+
+ return new $$.Collection( this.cy(), tgt ).filter( selector );
+ },
+
+ sources: defineSourceFunction({
+ attr: 'source'
+ }),
+
+ targets: defineSourceFunction({
+ attr: 'target'
+ })
+ });
+
+ function defineSourceFunction( params ){
+ return function( selector ){
+ var sources = [];
+ var cy = this._private.cy;
+
+ for( var i = 0; i < this.length; i++ ){
+ var ele = this[i];
+ var src = ele._private[ params.attr ];
+
+ if( src ){
+ sources.push( src );
+ }
+ }
+
+ return new $$.Collection( cy, sources ).filter( selector );
+ };
+ }
+
+ $$.fn.eles({
+ edgesWith: defineEdgesWithFunction(),
+
+ edgesTo: defineEdgesWithFunction({
+ thisIs: 'source'
+ })
+ });
+
+ function defineEdgesWithFunction( params ){
+
+ return function(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 edges = otherNodes.connectedEdges();
+ var thisIds = this._private.ids;
+
+ for( var i = 0; i < edges.length; i++ ){
+ var edge = edges[i];
+ var foundId;
+ var edgeData = edge._private.data;
+
+ if( p.thisIs ){
+ var idToFind = edgeData[ p.thisIs ];
+ foundId = thisIds[ idToFind ];
+ } else {
+ foundId = thisIds[ edgeData.source ] || thisIds[ edgeData.target ];
+ }
+
+ if( foundId ){
+ elements.push( edge );
+ }
+ }
+
+ return new $$.Collection( cy, elements );
+ };
+ }
+
+ $$.fn.eles({
+ connectedEdges: function( selector ){
+ var retEles = [];
+ var cy = this._private.cy;
+
+ 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 new $$.Collection( cy, retEles ).filter( selector );
+ },
+
+ connectedNodes: function( selector ){
+ var retEles = [];
+ var cy = this._private.cy;
+
+ 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 new $$.Collection( cy, retEles ).filter( selector );
+ },
+
+ parallelEdges: defineParallelEdgesFunction(),
+
+ codirectedEdges: defineParallelEdgesFunction({
+ codirected: true
+ })
+ });
+
+ function defineParallelEdgesFunction(params){
+ var defaults = {
+ codirected: false
+ };
+ params = $$.util.extend({}, defaults, params);
+
+ return function( selector ){
+ var cy = this._private.cy;
+ 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 new $$.Collection( cy, elements ).filter( selector );
+ };
+
+ }
+
+
+})( cytoscape );
+
+;(function ($$) {
+ "use strict";
+
+ /* Min and Max heap predefaults */
+
+ $$.Minheap = function (cy, eles, valueFn) {
+ return new $$.Heap(cy, eles, $$.Heap.minHeapComparator, valueFn);
+ };
+
+ $$.Maxheap = function (cy, eles, valueFn) {
+ return new $$.Heap(cy, eles, $$.Heap.maxHeapComparator, valueFn);
+ };
+
+ $$.Heap = function (cy, eles, comparator, valueFn) {
+ if (typeof comparator === "undefined" || typeof eles === "undefined") {
+ return;
+ }
+
+ if (typeof valueFn === "undefined") {
+ valueFn = $$.Heap.idFn;
+ }
+
+ var sourceHeap = [],
+ pointers = {},
+ elements = [],
+ i = 0,
+ id,
+ heap,
+ elesLen;
+
+ eles = this.getArgumentAsCollection(eles, cy);
+ elesLen = eles.length;
+
+ for (i = 0; i < elesLen; i += 1) {
+ sourceHeap.push(valueFn.call(cy, eles[i], i, eles));
+
+ id = eles[i].id();
+
+ if (pointers.hasOwnProperty(id)) {
+ throw "ERROR: Multiple items with the same id found: " + id;
+ }
+
+ pointers[id] = i;
+ elements.push(id);
+ }
+
+ this._private = {
+ cy: cy,
+ heap: sourceHeap,
+ pointers: pointers,
+ elements: elements,
+ comparator: comparator,
+ extractor: valueFn,
+ length: elesLen
+ };
+
+ for (i = Math.floor(elesLen / 2); i >= 0; i -= 1) {
+ heap = this.heapify(i);
+ }
+
+ return heap;
+ };
+
+ /* static methods */
+ $$.Heap.idFn = function (node) {
+ return node.id();
+ };
+
+ $$.Heap.minHeapComparator = function (a, b) {
+ return a >= b;
+ };
+
+ $$.Heap.maxHeapComparator = function (a, b) {
+ return a <= b;
+ };
+
+ $$.fn.heap = function( fnMap, options ){
+ for( var name in fnMap ){
+ var fn = fnMap[name];
+ $$.Heap.prototype[ name ] = fn;
+ }
+ };
+
+ $$.heapfn = $$.Heap.prototype; // short alias
+
+ /* object methods */
+ $$.heapfn.size = function () {
+ return this._private.length;
+ };
+
+ $$.heapfn.getArgumentAsCollection = function (eles, cy) {
+ var result;
+ if(typeof cy === "undefined") {
+ cy = this._private.cy;
+ }
+
+ if ($$.is.elementOrCollection(eles)) {
+ result = eles;
+
+ } else {
+ var resultArray = [],
+ sourceEles = [].concat.apply([], [eles]);
+
+ for (var i = 0; i < sourceEles.length; i++) {
+ var id = sourceEles[i],
+ ele = cy.getElementById(id);
+
+ if(ele.length > 0) {
+ resultArray.push(ele);
+ }
+ }
+
+ result = new $$.Collection(cy, resultArray);
+ }
+
+ return result;
+ };
+
+ $$.heapfn.isHeap = function () {
+ var array = this._private.heap,
+ arrlen = array.length,
+ i,
+ left,
+ right,
+ lCheck,
+ rCheck,
+ comparator = this._private.comparator;
+
+ for (i = 0; i < arrlen; i += 1) {
+ left = 2 * i + 1;
+ right = left + 1;
+ lCheck = left < arrlen ? comparator(array[left], array[i]) : true;
+ rCheck = right < arrlen ? comparator(array[right], array[i]) : true;
+
+ if (!lCheck || !rCheck) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ $$.heapfn.heapSwap = function (i, j) {
+ var heap = this._private.heap,
+ pointers = this._private.pointers,
+ elements = this._private.elements,
+ swapValue = heap[i],
+ swapElems = elements[i],
+ idI = elements[i],
+ idJ = elements[j];
+
+ heap[i] = heap[j];
+ elements[i] = elements[j];
+
+ pointers[idI] = j;
+ pointers[idJ] = i;
+
+ heap[j] = swapValue;
+ elements[j] = swapElems;
+ };
+
+ $$.heapfn.heapify = function (i, rootToLeaf) {
+ var treeLen = 0,
+ condHeap = false,
+ array,
+ current,
+ left,
+ right,
+ best,
+ comparator,
+ parent;
+
+ if (typeof rootToLeaf === "undefined") {
+ rootToLeaf = true;
+ }
+
+ array = this._private.heap;
+ treeLen = array.length;
+ comparator = this._private.comparator;
+ current = i;
+
+ while (!condHeap) {
+
+ if (rootToLeaf) {
+ left = 2 * current + 1;
+ right = left + 1;
+ best = current;
+
+ if (left < treeLen && !comparator(array[left], array[best])) {
+ best = left;
+ }
+
+ if (right < treeLen && !comparator(array[right], array[best])) {
+ best = right;
+ }
+
+ condHeap = best === current;
+
+ if (!condHeap) {
+ this.heapSwap(best, current);
+ current = best;
+ }
+
+ } else {
+ parent = Math.floor((current - 1) / 2);
+ best = current;
+ condHeap = parent < 0 || comparator(array[best], array[parent]);
+
+ if (!condHeap) {
+ this.heapSwap(best, parent);
+ current = parent;
+ }
+ }
+
+ } // while
+ };
+
+ /* collectionOrElement */
+ $$.heapfn.insert = function (eles) {
+ var elements = this.getArgumentAsCollection(eles),
+ elsize = elements.length,
+ element,
+ elindex,
+ elvalue,
+ elid,
+ i;
+
+ for (i = 0; i < elsize; i += 1) {
+ element = elements[i];
+ elindex = this._private.heap.length;
+ elvalue = this._private.extractor(element);
+ elid = element.id();
+
+ if (this._private.pointers.hasOwnProperty(elid)) {
+ throw "ERROR: Multiple items with the same id found: " + elid;
+ }
+
+ this._private.heap.push(elvalue);
+ this._private.elements.push(elid);
+ this._private.pointers[elid] = elindex;
+ this.heapify(elindex, false);
+ }
+
+ this._private.length = this._private.heap.length;
+ };
+
+ $$.heapfn.getValueById = function (elementId) {
+ if (this._private.pointers.hasOwnProperty(elementId)) {
+ var elementIndex = this._private.pointers[elementId];
+
+ return this._private.heap[elementIndex];
+ }
+ };
+
+ $$.heapfn.contains = function (eles) {
+ var elements = this.getArgumentAsCollection(eles);
+
+ for (var i = 0; i < elements.length; i += 1) {
+ var elementId = elements[i].id();
+
+ if(!this._private.pointers.hasOwnProperty(elementId)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ $$.heapfn.top = function () {
+ if (this._private.length > 0) {
+
+ return {
+ value: this._private.heap[0],
+ id: this._private.elements[0]
+ };
+
+ }
+ };
+
+ $$.heapfn.pop = function () {
+ if (this._private.length > 0) {
+ var top = this.top(),
+ lastIndex = this._private.length - 1,
+ removeCandidate,
+ removeValue,
+ remId;
+
+ this.heapSwap(0, lastIndex);
+
+ removeCandidate = this._private.elements[lastIndex];
+ removeValue = this._private.heap[lastIndex];
+ remId = removeCandidate;
+
+ this._private.heap.pop();
+ this._private.elements.pop();
+ this._private.length = this._private.heap.length;
+ delete this._private.pointers[remId];
+
+ this.heapify(0);
+ return top;
+ }
+ };
+
+ $$.heapfn.findDirectionHeapify = function (index) {
+ var parent = Math.floor((index - 1) / 2),
+ array = this._private.heap,
+ condHeap = parent < 0 || this._private.comparator(array[index], array[parent]);
+
+ this.heapify(index, condHeap);
+ };
+
+ /* edit is a new value or function */
+ // only values in heap are updated. elements themselves are not!
+ $$.heapfn.edit = function (eles, edit) {
+ var elements = this.getArgumentAsCollection(eles);
+
+ for (var i = 0; i < elements.length; i += 1) {
+ var elementId = elements[i].id(),
+ elementIndex = this._private.pointers[elementId],
+ elementValue = this._private.heap[elementIndex];
+
+ if ($$.is.number(edit)) {
+ this._private.heap[elementIndex] = edit;
+
+ } else if ($$.is.fn(edit)) {
+ this._private.heap[elementIndex] = edit.call(this._private.cy, elementValue, elementIndex);
+ }
+
+ this.findDirectionHeapify(elementIndex);
+ }
+ };
+
+ $$.heapfn.delete = function (eles) {
+ var elements = this.getArgumentAsCollection(eles);
+
+ for (var i = 0; i < elements.length; i += 1) {
+ var elementId = elements[i].id(),
+ elementIndex = this._private.pointers[elementId],
+ lastIndex = this._private.length - 1,
+ removeCandidate,
+ removeValue,
+ remId;
+
+ if (elementIndex !== lastIndex) {
+ this.heapSwap(elementIndex, lastIndex);
+ }
+
+ removeCandidate = this._private.elements[lastIndex];
+ removeValue = this._private.heap[lastIndex];
+ remId = removeCandidate;
+
+ this._private.heap.pop();
+ this._private.elements.pop();
+ this._private.length = this._private.heap.length;
+ delete this._private.pointers[remId];
+
+ this.findDirectionHeapify(elementIndex);
+ }
+
+ return removeValue;
+ };
+
+})(cytoscape);
+/*
+ The canvas renderer was written by Yue Dong.
+
+ Modifications tracked on Github.
+*/
+
+(function($$) { 'use strict';
+
+ function CanvasRenderer(options) {
+
+ CanvasRenderer.CANVAS_LAYERS = 5;
+ CanvasRenderer.SELECT_BOX = 0;
+ CanvasRenderer.DRAG = 2;
+ CanvasRenderer.NODE = 4;
+ CanvasRenderer.TEXTURE_BUFFER = 0;
+ CanvasRenderer.BUFFER_COUNT = 2;
+
+ this.options = options;
+
+ this.data = {
+
+ select: [undefined, undefined, undefined, undefined, 0], // Coordinates for selection box, plus enabled flag
+ renderer: this, cy: options.cy, container: options.cy.container(),
+
+ canvases: new Array(CanvasRenderer.CANVAS_LAYERS),
+ contexts: new Array(CanvasRenderer.CANVAS_LAYERS),
+ canvasNeedsRedraw: new Array(CanvasRenderer.CANVAS_LAYERS),
+
+ bufferCanvases: new Array(CanvasRenderer.BUFFER_COUNT),
+ bufferContexts: new Array(CanvasRenderer.CANVAS_LAYERS)
+
+ };
+
+ //--Pointer-related data
+ this.hoverData = {down: null, last: null,
+ downTime: null, triggerMode: null,
+ dragging: false,
+ initialPan: [null, null], capture: false};
+
+ this.timeoutData = {panTimeout: null};
+
+ this.dragData = {possibleDragElements: []};
+
+ this.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] };
+ //--
+
+ //--Wheel-related data
+ this.zoomData = {freeToZoom: false, lastPointerX: null};
+ //--
+
+ this.redraws = 0;
+
+ this.bindings = [];
+
+ this.data.canvasContainer = document.createElement('div');
+ var containerStyle = this.data.canvasContainer.style;
+ containerStyle.position = 'absolute';
+ containerStyle.zIndex = '0';
+ containerStyle.overflow = 'hidden';
+
+ this.data.container.appendChild( this.data.canvasContainer );
+
+ for (var i = 0; i < CanvasRenderer.CANVAS_LAYERS; i++) {
+ this.data.canvases[i] = document.createElement('canvas');
+ this.data.contexts[i] = this.data.canvases[i].getContext('2d');
+ this.data.canvases[i].style.position = 'absolute';
+ this.data.canvases[i].setAttribute('data-id', 'layer' + i);
+ this.data.canvases[i].style.zIndex = String(CanvasRenderer.CANVAS_LAYERS - i);
+ this.data.canvasContainer.appendChild(this.data.canvases[i]);
+
+ this.data.canvasNeedsRedraw[i] = false;
+ }
+
+ this.data.canvases[CanvasRenderer.NODE].setAttribute('data-id', 'layer' + CanvasRenderer.NODE + '-node');
+ this.data.canvases[CanvasRenderer.SELECT_BOX].setAttribute('data-id', 'layer' + CanvasRenderer.SELECT_BOX + '-selectbox');
+ this.data.canvases[CanvasRenderer.DRAG].setAttribute('data-id', 'layer' + CanvasRenderer.DRAG + '-drag');
+
+ for (var i = 0; i < CanvasRenderer.BUFFER_COUNT; i++) {
+ this.data.bufferCanvases[i] = document.createElement('canvas');
+ this.data.bufferContexts[i] = this.data.bufferCanvases[i].getContext('2d');
+ this.data.bufferCanvases[i].style.position = 'absolute';
+ this.data.bufferCanvases[i].setAttribute('data-id', 'buffer' + i);
+ this.data.bufferCanvases[i].style.zIndex = String(-i - 1);
+ this.data.bufferCanvases[i].style.visibility = 'hidden';
+ this.data.canvasContainer.appendChild(this.data.bufferCanvases[i]);
+ }
+
+ this.hideEdgesOnViewport = options.hideEdgesOnViewport;
+ this.hideLabelsOnViewport = options.hideLabelsOnViewport;
+ this.textureOnViewport = options.textureOnViewport;
+
+ this.load();
+ }
+
+ CanvasRenderer.panOrBoxSelectDelay = 400;
+ CanvasRenderer.isTouch = $$.is.touch();
+
+ // whether to use Path2D caching for drawing
+ var pathsImpld = typeof Path2D !== 'undefined';
+ CanvasRenderer.usePaths = function(){
+ return pathsImpld;
+ };
+
+ CanvasRenderer.prototype.notify = function(params) {
+ switch( params.type ){
+
+ case 'destroy':
+ this.destroy();
+ return;
+
+ case 'add':
+ case 'remove':
+ case 'load':
+ this.updateNodesCache();
+ this.updateEdgesCache();
+ break;
+
+ case 'viewport':
+ this.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+ break;
+
+ case 'style':
+ this.updateCachedZSortedEles();
+ break;
+ }
+
+ if( params.type === 'load' || params.type === 'resize' ){
+ this.invalidateContainerClientCoordsCache();
+ this.matchCanvasSize(this.data.container);
+ }
+
+ this.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+
+ this.redraw();
+ };
+
+ CanvasRenderer.prototype.destroy = function(){
+ this.destroyed = true;
+
+ 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);
+ }
+ };
+
+
+
+ // copy the math functions into the renderer prototype
+ // unfortunately these functions are used interspersed t/o the code
+ // and this makes sure things work just in case a ref was missed in refactoring
+ // TODO remove this eventually
+ for( var fnName in $$.math ){
+ CanvasRenderer.prototype[ fnName ] = $$.math[ fnName ];
+ }
+
+
+ $$('renderer', 'canvas', CanvasRenderer);
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ var CanvasRenderer = $$('renderer', 'canvas');
+ var rendFunc = CanvasRenderer.prototype;
+ var arrowShapes = CanvasRenderer.arrowShapes = {};
+
+ CanvasRenderer.arrowShapeHeight = 0.3;
+
+ // 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, centerX, centerY, width, height, direction, padding){
+ var x1 = centerX - width/2;
+ var x2 = centerX + width/2;
+ var y1 = centerY - height/2;
+ var y2 = centerY + height/2;
+
+ return (x1 <= x && x <= x2) && (y1 <= y && y <= y2);
+ };
+
+ arrowShapes['arrow'] = {
+ _points: [
+ -0.15, -0.3,
+ 0, 0,
+ 0.15, -0.3
+ ],
+
+ collide: function(x, y, centerX, centerY, width, height, direction, padding) {
+ var points = arrowShapes['arrow']._points;
+
+// console.log("collide(): " + direction);
+
+ return $$.math.pointInsidePolygon(
+ x, y, points, centerX, centerY, width, height, direction, padding);
+ },
+
+ roughCollide: bbCollide,
+
+ draw: function(context) {
+ var points = arrowShapes['arrow']._points;
+
+ for (var i = 0; i < points.length / 2; i++) {
+ context.lineTo(points[i * 2], points[i * 2 + 1]);
+ }
+ },
+
+ spacing: function(edge) {
+ return 0;
+ },
+
+ gap: function(edge) {
+ return edge._private.style['width'].pxValue * 2;
+ }
+ };
+
+ arrowShapes['triangle'] = arrowShapes['arrow'];
+
+ arrowShapes['none'] = {
+ collide: function(x, y, centerX, centerY, width, height, direction, padding) {
+ return false;
+ },
+
+ roughCollide: function(x, y, centerX, centerY, width, height, direction, padding) {
+ return false;
+ },
+
+ draw: function(context) {
+ },
+
+ spacing: function(edge) {
+ return 0;
+ },
+
+ gap: function(edge) {
+ return 0;
+ }
+ };
+
+ arrowShapes['circle'] = {
+ _baseRadius: 0.15,
+
+ collide: function(x, y, centerX, centerY, width, height, direction, padding) {
+ // Transform x, y to get non-rotated ellipse
+
+ if (width != height) {
+ var aspectRatio = (height + padding) / (width + padding);
+ y /= aspectRatio;
+ centerY /= aspectRatio;
+
+ return (Math.pow(centerX - x, 2)
+ + Math.pow(centerY - y, 2) <= Math.pow((width + padding)
+ * arrowShapes['circle']._baseRadius, 2));
+ } else {
+ return (Math.pow(centerX - x, 2)
+ + Math.pow(centerY - y, 2) <= Math.pow((width + padding)
+ * arrowShapes['circle']._baseRadius, 2));
+ }
+ },
+
+ roughCollide: bbCollide,
+
+ draw: function(context) {
+ context.arc(0, 0, arrowShapes['circle']._baseRadius, 0, Math.PI * 2, false);
+ },
+
+ spacing: function(edge) {
+ return rendFunc.getArrowWidth(edge._private.style['width'].pxValue)
+ * arrowShapes['circle']._baseRadius;
+ },
+
+ gap: function(edge) {
+ return edge._private.style['width'].pxValue * 2;
+ }
+ };
+
+ arrowShapes['inhibitor'] = {
+ _points: [
+ -0.25, 0,
+ -0.25, -0.1,
+ 0.25, -0.1,
+ 0.25, 0
+ ],
+
+ collide: function(x, y, centerX, centerY, width, height, direction, padding) {
+ var points = arrowShapes['inhibitor']._points;
+
+ return $$.math.pointInsidePolygon(
+ x, y, points, centerX, centerY, width, height, direction, padding);
+ },
+
+ roughCollide: bbCollide,
+
+ draw: function(context) {
+ var points = arrowShapes['inhibitor']._points;
+
+ for (var i = 0; i < points.length / 2; i++) {
+ context.lineTo(points[i * 2], points[i * 2 + 1]);
+ }
+ },
+
+ spacing: function(edge) {
+ return 4;
+ },
+
+ gap: function(edge) {
+ return 4;
+ }
+ };
+
+ arrowShapes['tee'] = arrowShapes['inhibitor'];
+
+ arrowShapes['square'] = {
+ _points: [
+ -0.12, 0.00,
+ 0.12, 0.00,
+ 0.12, -0.24,
+ -0.12, -0.24
+ ],
+
+ collide: function(x, y, centerX, centerY, width, height, direction, padding) {
+ var points = arrowShapes['square']._points;
+
+ return $$.math.pointInsidePolygon(
+ x, y, points, centerX, centerY, width, height, direction, padding);
+ },
+
+ roughCollide: bbCollide,
+
+ draw: function(context) {
+ var points = arrowShapes['square']._points;
+
+ for (var i = 0; i < points.length / 2; i++) {
+ context.lineTo(points[i * 2], points[i * 2 + 1]);
+ }
+ },
+
+ spacing: function(edge) {
+ return 0;
+ },
+
+ gap: function(edge) {
+ return edge._private.style['width'].pxValue * 2;
+ }
+ };
+
+ arrowShapes['diamond'] = {
+ _points: [
+ -0.14, -0.14,
+ 0, -0.28,
+ 0.14, -0.14,
+ 0, 0
+ ],
+
+ collide: function(x, y, centerX, centerY, width, height, direction, padding) {
+ var points = arrowShapes['diamond']._points;
+
+ return $$.math.pointInsidePolygon(
+ x, y, points, centerX, centerY, width, height, direction, padding);
+ },
+
+ roughCollide: bbCollide,
+
+ draw: function(context) {
+ context.lineTo(-0.14, -0.14);
+ context.lineTo(0, -0.28);
+ context.lineTo(0.14, -0.14);
+ context.lineTo(0, 0.0);
+ },
+
+ spacing: function(edge) {
+ return 0;
+ },
+
+ gap: function(edge) {
+ return edge._private.style['width'].pxValue * 2;
+ }
+ };
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ var CanvasRenderer = $$('renderer', 'canvas');
+
+ CanvasRenderer.prototype.getCachedNodes = function() {
+ var data = this.data; var cy = this.data.cy;
+
+ if (data.cache == null) {
+ data.cache = {};
+ }
+
+ if (data.cache.cachedNodes == null) {
+ data.cache.cachedNodes = cy.nodes();
+ }
+
+ return data.cache.cachedNodes;
+ };
+
+ CanvasRenderer.prototype.updateNodesCache = function() {
+ var data = this.data; var cy = this.data.cy;
+
+ if (data.cache == null) {
+ data.cache = {};
+ }
+
+ data.cache.cachedNodes = cy.nodes();
+ };
+
+ CanvasRenderer.prototype.getCachedEdges = function() {
+ var data = this.data; var cy = this.data.cy;
+
+ if (data.cache == null) {
+ data.cache = {};
+ }
+
+ if (data.cache.cachedEdges == null) {
+ data.cache.cachedEdges = cy.edges();
+ }
+
+ return data.cache.cachedEdges;
+ };
+
+ CanvasRenderer.prototype.updateEdgesCache = function() {
+ var data = this.data; var cy = this.data.cy;
+
+ if (data.cache == null) {
+ data.cache = {};
+ }
+
+ data.cache.cachedEdges = cy.edges();
+ };
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ var CanvasRenderer = $$('renderer', 'canvas');
+
+ // Project mouse
+ CanvasRenderer.prototype.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.data.cy.pan().x; y -= this.data.cy.pan().y; x /= this.data.cy.zoom(); y /= this.data.cy.zoom();
+ return [x, y];
+ };
+
+ CanvasRenderer.prototype.findContainerClientCoords = function() {
+ var container = this.data.container;
+
+ var bb = this.containerBB = this.containerBB || container.getBoundingClientRect();
+
+ return [bb.left, bb.top, bb.right - bb.left, bb.bottom - bb.top];
+ };
+
+ CanvasRenderer.prototype.invalidateContainerClientCoordsCache = function(){
+ this.containerBB = null;
+ };
+
+ // Find nearest element
+ CanvasRenderer.prototype.findNearestElement = function(x, y, visibleElementsOnly){
+ var self = this;
+ var eles = this.getCachedZSortedEles();
+ var near = [];
+ var isTouch = CanvasRenderer.isTouch;
+ var zoom = this.data.cy.zoom();
+ var hasCompounds = this.data.cy.hasCompoundNodes();
+ var edgeThreshold = (isTouch ? 256 : 32) / zoom;
+ var nodeThreshold = (isTouch ? 16 : 0) / zoom;
+
+ function checkNode(node){
+ var shape = CanvasRenderer.nodeShapes[ self.getNodeShape(node) ];
+ var borderWO = node._private.style['border-width'].pxValue / 2;
+ var width = self.getNodeWidth( node );
+ var height = self.getNodeHeight( node );
+ var hw = width/2;
+ var hh = height/2;
+ var pos = node._private.position;
+ var visible = node.visible() && !node.transparent();
+
+ // exit early if invisible edge and must be visible
+ if( visibleElementsOnly && !visible ){
+ return;
+ }
+
+ if(
+ pos.x - hw <= x && x <= pos.x + hw // bb check x
+ &&
+ pos.y - hh <= y && y <= pos.y + hh // bb check y
+ &&
+ shape.checkPointRough(x, y, borderWO, width + nodeThreshold, height + nodeThreshold, pos.x, pos.y)
+ &&
+ shape.checkPoint(x, y, borderWO, width + nodeThreshold, height + nodeThreshold, pos.x, pos.y)
+ ){
+ near.push( node );
+ }
+ }
+
+ function checkEdge(edge){
+ var rs = edge._private.rscratch;
+ var style = edge._private.style;
+ var width = style['width'].pxValue;
+ var widthSq = width * width;
+ var width2 = width * 2;
+ var visible = edge.visible() && !edge.transparent();
+ var src = edge._private.source;
+ var tgt = edge._private.target;
+
+ // exit early if invisible edge and must be visible
+ if( visibleElementsOnly && !visible ){
+ return;
+ }
+
+ if (rs.edgeType === 'self') {
+ if(
+ (
+ $$.math.inBezierVicinity(x, y, rs.startX, rs.startY, rs.cp2ax, rs.cp2ay, rs.selfEdgeMidX, rs.selfEdgeMidY, widthSq)
+ &&
+ ( widthSq + edgeThreshold > $$.math.sqDistanceToQuadraticBezier(x, y, rs.startX, rs.startY, rs.cp2ax, rs.cp2ay, rs.selfEdgeMidX, rs.selfEdgeMidY) )
+ )
+ ||
+ (
+ $$.math.inBezierVicinity(x, y, rs.selfEdgeMidX, rs.selfEdgeMidY, rs.cp2cx, rs.cp2cy, rs.endX, rs.endY, widthSq)
+ &&
+ ( widthSq + edgeThreshold > $$.math.sqDistanceToQuadraticBezier(x, y, rs.selfEdgeMidX, rs.selfEdgeMidY, rs.cp2cx, rs.cp2cy, rs.endX, rs.endY) )
+ )
+ ){
+ near.push( edge );
+ }
+
+ } else if (rs.edgeType == 'haystack') {
+ var tgtPos = tgt._private.position;
+ var tgtW = tgt.width();
+ var tgtH = tgt.height();
+ var srcPos = src._private.position;
+ var srcW = src.width();
+ var srcH = src.height();
+
+ var startX = srcPos.x + rs.source.x * srcW;
+ var startY = srcPos.y + rs.source.y * srcH;
+ var endX = tgtPos.x + rs.target.x * tgtW;
+ var endY = tgtPos.y + rs.target.y * tgtH;
+
+ if(
+ $$.math.inLineVicinity(x, y, startX, startY, endX, endY, width2)
+ &&
+ widthSq + edgeThreshold > $$.math.sqDistanceToFiniteLine( x, y, startX, startY, endX, endY )
+ ){
+ near.push( edge );
+ }
+
+ } else if (rs.edgeType === 'straight') {
+ if(
+ $$.math.inLineVicinity(x, y, rs.startX, rs.startY, rs.endX, rs.endY, width2)
+ &&
+ widthSq + edgeThreshold > $$.math.sqDistanceToFiniteLine(x, y, rs.startX, rs.startY, rs.endX, rs.endY)
+ ){
+ near.push( edge );
+ }
+
+ } else if (rs.edgeType === 'bezier') {
+ if(
+ $$.math.inBezierVicinity(x, y, rs.startX, rs.startY, rs.cp2x, rs.cp2y, rs.endX, rs.endY, widthSq)
+ &&
+ (widthSq + edgeThreshold > $$.math.sqDistanceToQuadraticBezier(x, y, rs.startX, rs.startY, rs.cp2x, rs.cp2y, rs.endX, rs.endY))
+ ){
+ near.push( edge );
+ }
+ }
+
+ if( near.length === 0 || near[near.length - 1] !== edge ){
+ var srcShape = CanvasRenderer.arrowShapes[ style['source-arrow-shape'].value ];
+ var tgtShape = CanvasRenderer.arrowShapes[ style['target-arrow-shape'].value ];
+
+ var src = src || edge._private.source;
+ var tgt = tgt || edge._private.target;
+
+ var tgtPos = tgt._private.position;
+ var srcPos = src._private.position;
+
+ var srcArW = self.getArrowWidth( style['width'].pxValue );
+ var srcArH = self.getArrowHeight( style['width'].pxValue );
+
+ var tgtArW = srcArW;
+ var tgtArH = srcArH;
+
+ if(
+ (
+ srcShape.roughCollide(x, y, rs.arrowStartX, rs.arrowStartY, srcArW, srcArH, [rs.arrowStartX - srcPos.x, rs.arrowStartY - srcPos.y], 0)
+ &&
+ srcShape.collide(x, y, rs.arrowStartX, rs.arrowStartY, srcArW, srcArH, [rs.arrowStartX - srcPos.x, rs.arrowStartY - srcPos.y], 0)
+ )
+ ||
+ (
+ tgtShape.roughCollide(x, y, rs.arrowEndX, rs.arrowEndY, tgtArW, tgtArH, [rs.arrowEndX - tgtPos.x, rs.arrowEndY - tgtPos.y], 0)
+ &&
+ tgtShape.collide(x, y, rs.arrowEndX, rs.arrowEndY, tgtArW, tgtArH, [rs.arrowEndX - tgtPos.x, rs.arrowEndY - tgtPos.y], 0)
+ )
+ ){
+ near.push( edge );
+ }
+ }
+
+ // 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 );
+ }
+ }
+
+ for( var i = eles.length - 1; i >= 0; i-- ){ // reverse order for precedence
+ var ele = eles[i];
+
+ if( near.length > 0 ){ break; } // since we check in z-order, first found is top and best result => exit early
+
+ if( ele._private.group === 'nodes' ){
+ checkNode( eles[i] );
+
+ } else { // then edge
+ checkEdge( eles[i] );
+ }
+
+ }
+
+
+ if( near.length > 0 ){
+ return near[ near.length - 1 ];
+ } else {
+ return null;
+ }
+ };
+
+ // 'Give me everything from this box'
+ CanvasRenderer.prototype.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 heur;
+
+ for ( var i = 0; i < nodes.length; i++ ){
+ var pos = nodes[i]._private.position;
+ var nShape = this.getNodeShape(nodes[i]);
+ var w = this.getNodeWidth(nodes[i]);
+ var h = this.getNodeHeight(nodes[i]);
+ var border = nodes[i]._private.style['border-width'].pxValue / 2;
+ var shapeObj = CanvasRenderer.nodeShapes[ nShape ];
+
+ if ( shapeObj.intersectBox(x1, y1, x2, y2, w, h, pos.x, pos.y, border) ){
+ box.push(nodes[i]);
+ }
+ }
+
+ for ( var i = 0; i < edges.length; i++ ){
+ var rs = edges[i]._private.rscratch;
+
+ if (edges[i]._private.rscratch.edgeType == 'self') {
+ if ((heur = $$.math.boxInBezierVicinity(x1, y1, x2, y2,
+ rs.startX, rs.startY,
+ rs.cp2ax, rs.cp2ay,
+ rs.endX, rs.endY, edges[i]._private.style['width'].pxValue))
+ &&
+ (heur == 2 || (heur == 1 && $$.math.checkBezierInBox(x1, y1, x2, y2,
+ rs.startX, rs.startY,
+ rs.cp2ax, rs.cp2ay,
+ rs.endX, rs.endY, edges[i]._private.style['width'].pxValue)))
+ ||
+ (heur = $$.math.boxInBezierVicinity(x1, y1, x2, y2,
+ rs.startX, rs.startY,
+ rs.cp2cx, rs.cp2cy,
+ rs.endX, rs.endY, edges[i]._private.style['width'].pxValue))
+ &&
+ (heur == 2 || (heur == 1 && $$.math.checkBezierInBox(x1, y1, x2, y2,
+ rs.startX, rs.startY,
+ rs.cp2cx, rs.cp2cy,
+ rs.endX, rs.endY, edges[i]._private.style['width'].pxValue)))
+ )
+ { box.push(edges[i]); }
+ }
+
+ if (rs.edgeType == 'bezier' &&
+ (heur = $$.math.boxInBezierVicinity(x1, y1, x2, y2,
+ rs.startX, rs.startY,
+ rs.cp2x, rs.cp2y,
+ rs.endX, rs.endY, edges[i]._private.style['width'].pxValue))
+ &&
+ (heur == 2 || (heur == 1 && $$.math.checkBezierInBox(x1, y1, x2, y2,
+ rs.startX, rs.startY,
+ rs.cp2x, rs.cp2y,
+ rs.endX, rs.endY, edges[i]._private.style['width'].pxValue))))
+ { box.push(edges[i]); }
+
+ if (rs.edgeType == 'straight' &&
+ (heur = $$.math.boxInBezierVicinity(x1, y1, x2, y2,
+ rs.startX, rs.startY,
+ rs.startX * 0.5 + rs.endX * 0.5,
+ rs.startY * 0.5 + rs.endY * 0.5,
+ rs.endX, rs.endY, edges[i]._private.style['width'].pxValue))
+ && /* console.log('test', heur) == undefined && */
+ (heur == 2 || (heur == 1 && $$.math.checkStraightEdgeInBox(x1, y1, x2, y2,
+ rs.startX, rs.startY,
+ rs.endX, rs.endY, edges[i]._private.style['width'].pxValue))))
+ { box.push(edges[i]); }
+
+
+ if (rs.edgeType == 'haystack'){
+ var tgt = edges[i].target()[0];
+ var tgtPos = tgt.position();
+ var src = edges[i].source()[0];
+ var srcPos = src.position();
+
+ var startX = srcPos.x + rs.source.x;
+ var startY = srcPos.y + rs.source.y;
+ var endX = tgtPos.x + rs.target.x;
+ var endY = tgtPos.y + rs.target.y;
+
+ var startInBox = (x1 <= startX && startX <= x2) && (y1 <= startY && startY <= y2);
+ var endInBox = (x1 <= endX && endX <= x2) && (y1 <= endY && endY <= y2);
+
+ if( startInBox && endInBox ){
+ box.push( edges[i] );
+ }
+ }
+
+ }
+
+ return box;
+ };
+
+
+ /**
+ * Returns the width of the given node. If the width is set to auto,
+ * returns the value of the autoWidth field.
+ *
+ * @param node a node
+ * @return {number} width of the node
+ */
+ CanvasRenderer.prototype.getNodeWidth = function(node)
+ {
+ return node.width();
+ };
+
+ /**
+ * Returns the height of the given node. If the height is set to auto,
+ * returns the value of the autoHeight field.
+ *
+ * @param node a node
+ * @return {number} width of the node
+ */
+ CanvasRenderer.prototype.getNodeHeight = function(node)
+ {
+ return node.height();
+ };
+
+ /**
+ * 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
+ */
+ CanvasRenderer.prototype.getNodeShape = function(node)
+ {
+ // TODO only allow rectangle for a compound node?
+// if (node._private.style['width'].value == 'auto' ||
+// node._private.style['height'].value == 'auto')
+// {
+// return 'rectangle';
+// }
+
+ var shape = node._private.style['shape'].value;
+
+ if( node.isParent() ){
+ if( shape === 'rectangle' || shape === 'roundrectangle' ){
+ return shape;
+ } else {
+ return 'rectangle';
+ }
+ }
+
+ return shape;
+ };
+
+
+ CanvasRenderer.prototype.getNodePadding = function(node)
+ {
+ var left = node._private.style['padding-left'].pxValue;
+ var right = node._private.style['padding-right'].pxValue;
+ var top = node._private.style['padding-top'].pxValue;
+ var bottom = node._private.style['padding-bottom'].pxValue;
+
+ if (isNaN(left))
+ {
+ left = 0;
+ }
+
+ if (isNaN(right))
+ {
+ right = 0;
+ }
+
+ if (isNaN(top))
+ {
+ top = 0;
+ }
+
+ if (isNaN(bottom))
+ {
+ bottom = 0;
+ }
+
+ return {left : left,
+ right : right,
+ top : top,
+ bottom : bottom};
+ };
+
+ CanvasRenderer.prototype.zOrderSort = $$.Collection.zIndexSort;
+
+ CanvasRenderer.prototype.updateCachedZSortedEles = function(){
+ this.getCachedZSortedEles( true );
+ };
+
+ CanvasRenderer.prototype.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++ ){
+ eles.push( nodes[i] );
+ }
+
+ for( var i = 0; i < edges.length; i++ ){
+ eles.push( edges[i] );
+ }
+
+ eles.sort( this.zOrderSort );
+ 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;
+ };
+
+ CanvasRenderer.prototype.projectBezier = function(edge){
+ var qbezierAt = $$.math.qbezierAt;
+ var rs = edge._private.rscratch;
+ var bpts = edge._private.rstyle.bezierPts = [];
+
+ function pushBezierPts(pts){
+ 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 )
+ });
+ }
+
+ if( rs.edgeType === 'self' ){
+ pushBezierPts( [rs.startX, rs.startY, rs.cp2ax, rs.cp2ay, rs.selfEdgeMidX, rs.selfEdgeMidY] );
+ pushBezierPts( [rs.selfEdgeMidX, rs.selfEdgeMidY, rs.cp2cx, rs.cp2cy, rs.endX, rs.endY] );
+ } else if( rs.edgeType === 'bezier' ){
+ pushBezierPts( [rs.startX, rs.startY, rs.cp2x, rs.cp2y, rs.endX, rs.endY] );
+ }
+ };
+
+ CanvasRenderer.prototype.recalculateNodeLabelProjection = function( node ){
+ var content = node._private.style['content'].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 );
+ };
+
+ CanvasRenderer.prototype.recalculateEdgeLabelProjection = function( edge ){
+ var content = edge._private.style['content'].strValue;
+ if( !content || content.match(/^\s+$/) ){ return; }
+
+ var textX, textY;
+ var edgeCenterX, edgeCenterY;
+ var rs = edge._private.rscratch;
+ var rstyle = edge._private.rstyle;
+
+ if (rs.edgeType == 'self') {
+ edgeCenterX = rs.selfEdgeMidX;
+ edgeCenterY = rs.selfEdgeMidY;
+ } else if (rs.edgeType == 'straight') {
+ edgeCenterX = (rs.startX + rs.endX) / 2;
+ edgeCenterY = (rs.startY + rs.endY) / 2;
+ } else if (rs.edgeType == 'bezier') {
+ edgeCenterX = $$.math.qbezierAt( rs.startX, rs.cp2x, rs.endX, 0.5 );
+ edgeCenterY = $$.math.qbezierAt( rs.startY, rs.cp2y, rs.endY, 0.5 );
+ } else if (rs.edgeType == 'haystack') {
+ var srcPos = edge.source().position();
+ var tgtPos = edge.target().position();
+
+ edgeCenterX = (srcPos.x + rs.source.x + tgtPos.x + rs.target.x)/2;
+ edgeCenterY = (srcPos.y + rs.source.y + tgtPos.y + rs.target.y)/2;
+ }
+
+ textX = edgeCenterX;
+ textY = edgeCenterY;
+
+ // 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 );
+ };
+
+ CanvasRenderer.prototype.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;
+ };
+
+ CanvasRenderer.prototype.getLabelText = function( ele ){
+ var style = ele._private.style;
+ var text = ele._private.style['content'].strValue;
+ var textTransform = style['text-transform'].value;
+
+ if (textTransform == 'none') {
+ } else if (textTransform == 'uppercase') {
+ text = text.toUpperCase();
+ } else if (textTransform == 'lowercase') {
+ text = text.toLowerCase();
+ }
+
+ return text;
+ };
+
+ CanvasRenderer.prototype.calculateLabelDimensions = function( ele, text ){
+ var style = ele._private.style;
+ var fStyle = style['font-style'].strValue;
+ var size = style['font-size'].pxValue + 'px';
+ var family = style['font-family'].strValue;
+ var variant = style['font-variant'].strValue;
+ var weight = style['font-weight'].strValue;
+
+ var rscratch = ele._private.rscratch;
+ var cacheKey = ele._private.labelKey;
+ var cache = rscratch.labelDimCache || (rscratch.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.padding = '0';
+ ds.lineHeight = '1';
+
+ // put label content in div
+ div.innerText = text;
+
+ cache[cacheKey] = {
+ width: div.clientWidth,
+ height: div.clientHeight
+ };
+
+ return cache[cacheKey];
+ };
+
+ CanvasRenderer.prototype.recalculateRenderedStyle = function(){
+ this.recalculateEdgeProjections();
+ this.recalculateLabelProjections();
+ };
+
+ CanvasRenderer.prototype.recalculateLabelProjections = function(){
+ var nodes = this.getCachedNodes();
+ for( var i = 0; i < nodes.length; i++ ){
+ this.recalculateNodeLabelProjection( nodes[i] );
+ }
+
+ var edges = this.getCachedEdges();
+ for( var i = 0; i < edges.length; i++ ){
+ this.recalculateEdgeLabelProjection( edges[i] );
+ }
+ };
+
+ CanvasRenderer.prototype.recalculateEdgeProjections = function(){
+ var edges = this.getCachedEdges();
+
+ this.findEdgeControlPoints( edges );
+ };
+
+
+ // Find edge control points
+ CanvasRenderer.prototype.findEdgeControlPoints = function(edges) {
+ var hashTable = {}; var cy = this.data.cy;
+ var pairIds = [];
+ var haystackEdges = [];
+
+ // 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 style = edge._private.style;
+
+ // ignore edges who are not to be displayed
+ // they shouldn't take up space
+ if( style.display.value === 'none' ){
+ continue;
+ }
+
+ if( style['curve-style'].value === 'haystack' ){
+ haystackEdges.push( edge );
+ continue;
+ }
+
+ var srcId = edge._private.data.source;
+ var tgtId = edge._private.data.target;
+
+ pairId = srcId > tgtId ?
+ tgtId + '-' + srcId :
+ srcId + '-' + tgtId ;
+
+ if (hashTable[pairId] == null) {
+ hashTable[pairId] = [];
+ }
+
+ hashTable[pairId].push( edge );
+ pairIds.push( pairId );
+ }
+
+ var src, tgt, srcPos, tgtPos, srcW, srcH, tgtW, tgtH, srcShape, tgtShape, srcBorder, tgtBorder;
+ 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];
+
+ src = cy.getElementById( hashTable[pairId][0]._private.data.source );
+ tgt = cy.getElementById( hashTable[pairId][0]._private.data.target );
+
+ srcPos = src._private.position;
+ tgtPos = tgt._private.position;
+
+ srcW = this.getNodeWidth(src);
+ srcH = this.getNodeHeight(src);
+
+ tgtW = this.getNodeWidth(tgt);
+ tgtH = this.getNodeHeight(tgt);
+
+ srcShape = CanvasRenderer.nodeShapes[ this.getNodeShape(src) ];
+ tgtShape = CanvasRenderer.nodeShapes[ this.getNodeShape(tgt) ];
+
+ srcBorder = src._private.style['border-width'].pxValue;
+ tgtBorder = tgt._private.style['border-width'].pxValue;
+
+ badBezier = false;
+
+
+ if (hashTable[pairId].length > 1 && src !== tgt) {
+
+ // 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,
+ srcBorder / 2
+ );
+
+ // 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,
+ tgtBorder / 2
+ );
+
+ 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], tgtBorder/2, tgtW, tgtH, tgtPos.x, tgtPos.y ) ||
+ srcShape.checkPoint( tgtOutside[0], tgtOutside[1], srcBorder/2, srcW, srcH, srcPos.x, srcPos.y )
+ ){
+ vectorNormInverse = {};
+ badBezier = true;
+ }
+
+ }
+
+ var edge;
+ var rs;
+
+ for (var i = 0; i < hashTable[pairId].length; i++) {
+ edge = hashTable[pairId][i];
+ rs = edge._private.rscratch;
+
+ var edgeIndex1 = rs.lastEdgeIndex;
+ var edgeIndex2 = i;
+
+ var numEdges1 = rs.lastNumEdges;
+ var numEdges2 = hashTable[pairId].length;
+
+ 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();
+
+ 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
+ && edgeIndex1 === edgeIndex2 && numEdges1 === numEdges2 ){
+ // 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;
+ // console.log('edge ctrl pt cache MISS')
+ }
+
+ var eStyle = edge._private.style;
+ var stepSize = eStyle['control-point-step-size'].pxValue;
+ var stepDist = eStyle['control-point-distance'] !== undefined ? eStyle['control-point-distance'].pxValue : undefined;
+ var stepWeight = eStyle['control-point-weight'].value;
+
+ // Self-edge
+ if ( src.id() == tgt.id() ) {
+
+ rs.edgeType = 'self';
+
+ // New -- fix for large nodes
+ rs.cp2ax = srcPos.x;
+ rs.cp2ay = srcPos.y - (1 + Math.pow(srcH, 1.12) / 100) * stepSize * (i / 3 + 1);
+
+ rs.cp2cx = src._private.position.x - (1 + Math.pow(srcW, 1.12) / 100) * stepSize * (i / 3 + 1);
+ rs.cp2cy = srcPos.y;
+
+ rs.selfEdgeMidX = (rs.cp2ax + rs.cp2cx) / 2.0;
+ rs.selfEdgeMidY = (rs.cp2ay + rs.cp2cy) / 2.0;
+
+ // Straight edge
+ } else if (hashTable[pairId].length % 2 == 1
+ && i == Math.floor(hashTable[pairId].length / 2)) {
+
+ rs.edgeType = 'straight';
+
+ // Bezier edge
+ } else {
+ var normStepDist = (0.5 - hashTable[pairId].length / 2 + i) * stepSize;
+ var manStepDist = stepDist !== undefined ? $$.math.signum( normStepDist ) * stepDist : undefined;
+ var distanceFromMidpoint = manStepDist !== undefined ? manStepDist : normStepDist;
+
+ var adjustedMidpt = {
+ x: midptSrcPts.x1 * (1 - stepWeight) + midptSrcPts.x2 * stepWeight,
+ y: midptSrcPts.y1 * (1 - stepWeight) + midptSrcPts.y2 * stepWeight
+ };
+
+ rs.edgeType = 'bezier';
+
+ rs.cp2x = adjustedMidpt.x + vectorNormInverse.x * distanceFromMidpoint;
+ rs.cp2y = adjustedMidpt.y + vectorNormInverse.y * distanceFromMidpoint;
+
+ // console.log(edge, midPointX, displacementX, 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( edge._private.style['width'].pxValue ) * CanvasRenderer.arrowShapeHeight;
+ var minCpADist = minCpADistFactor * arrowW;
+ var startACpDist = $$.math.distance( { x: rs.cp2x, y: rs.cp2y }, { x: rs.startX, y: rs.startY } );
+ var closeStartACp = startACpDist < minCpADist;
+ var endACpDist = $$.math.distance( { x: rs.cp2x, y: rs.cp2y }, { x: rs.endX, y: rs.endY } );
+ var closeEndACp = endACpDist < minCpADist;
+
+ if( rs.edgeType === 'bezier' ){
+ 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.cp2x - srcPos.x,
+ y: rs.cp2y - 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.cp2x + cpM.x * 2 * radius,
+ y: rs.cp2y + cpM.y * 2 * radius
+ };
+
+ var srcCtrlPtIntn = srcShape.intersectLine(
+ srcPos.x,
+ srcPos.y,
+ srcW,
+ srcH,
+ cpProj.x,
+ cpProj.y,
+ srcBorder / 2
+ );
+
+ if( closeStartACp ){
+ rs.cp2x = rs.cp2x + cpM.x * (minCpADist - startACpDist);
+ rs.cp2y = rs.cp2y + cpM.y * (minCpADist - startACpDist);
+ } else {
+ rs.cp2x = srcCtrlPtIntn[0] + cpM.x * minCpADist;
+ rs.cp2y = 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.cp2x - tgtPos.x,
+ y: rs.cp2y - 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.cp2x + cpM.x * 2 * radius,
+ y: rs.cp2y + cpM.y * 2 * radius
+ };
+
+ var tgtCtrlPtIntn = tgtShape.intersectLine(
+ tgtPos.x,
+ tgtPos.y,
+ tgtW,
+ tgtH,
+ cpProj.x,
+ cpProj.y,
+ tgtBorder / 2
+ );
+
+ if( closeEndACp ){
+ rs.cp2x = rs.cp2x + cpM.x * (minCpADist - endACpDist);
+ rs.cp2y = rs.cp2y + cpM.y * (minCpADist - endACpDist);
+ } else {
+ rs.cp2x = tgtCtrlPtIntn[0] + cpM.x * minCpADist;
+ rs.cp2y = tgtCtrlPtIntn[1] + cpM.y * minCpADist;
+ }
+
+ }
+
+ if( overlapping ){
+ // recalc endpts
+ this.findEndpoints( edge );
+ }
+ }
+
+ // project the edge into rstyle
+ this.projectBezier( edge );
+
+ }
+ }
+
+ for( var i = 0; i < haystackEdges.length; i++ ){
+ var edge = haystackEdges[i];
+ var rscratch = edge._private.rscratch;
+ var rFactor = 0.8;
+
+ if( !rscratch.haystack ){
+ var srcR = rFactor * 0.5;
+ var angle = Math.random() * 2 * Math.PI;
+
+ rscratch.source = {
+ x: srcR * Math.cos(angle),
+ y: srcR * Math.sin(angle)
+ };
+
+ var tgtR = rFactor * 0.5;
+ var angle = Math.random() * 2 * Math.PI;
+
+ rscratch.target = {
+ x: tgtR * Math.cos(angle),
+ y: tgtR * Math.sin(angle)
+ };
+
+ rscratch.edgeType = 'haystack';
+ rscratch.haystack = true;
+ }
+ }
+
+ return hashTable;
+ };
+
+ CanvasRenderer.prototype.findEndpoints = function(edge) {
+ var intersect;
+
+ var source = edge.source()[0];
+ var target = edge.target()[0];
+
+ var tgtArShape = edge._private.style['target-arrow-shape'].value;
+ var srcArShape = edge._private.style['source-arrow-shape'].value;
+
+ var tgtBorderW = target._private.style['border-width'].pxValue;
+ var srcBorderW = source._private.style['border-width'].pxValue;
+
+ var rs = edge._private.rscratch;
+
+ if (edge._private.rscratch.edgeType == 'self') {
+
+ var cp = [rs.cp2cx, rs.cp2cy];
+
+ intersect = CanvasRenderer.nodeShapes[this.getNodeShape(target)].intersectLine(
+ target._private.position.x,
+ target._private.position.y,
+ this.getNodeWidth(target),
+ this.getNodeHeight(target),
+ cp[0],
+ cp[1],
+ tgtBorderW / 2
+ );
+
+ var arrowEnd = $$.math.shortenIntersection(intersect, cp,
+ CanvasRenderer.arrowShapes[tgtArShape].spacing(edge));
+ var edgeEnd = $$.math.shortenIntersection(intersect, cp,
+ CanvasRenderer.arrowShapes[tgtArShape].gap(edge));
+
+ rs.endX = edgeEnd[0];
+ rs.endY = edgeEnd[1];
+
+ rs.arrowEndX = arrowEnd[0];
+ rs.arrowEndY = arrowEnd[1];
+
+ var cp = [rs.cp2ax, rs.cp2ay];
+
+ intersect = CanvasRenderer.nodeShapes[this.getNodeShape(source)].intersectLine(
+ source._private.position.x,
+ source._private.position.y,
+ this.getNodeWidth(source),
+ this.getNodeHeight(source),
+ cp[0], //halfPointX,
+ cp[1], //halfPointY
+ srcBorderW / 2
+ );
+
+ var arrowStart = $$.math.shortenIntersection(intersect, cp,
+ CanvasRenderer.arrowShapes[srcArShape].spacing(edge));
+ var edgeStart = $$.math.shortenIntersection(intersect, cp,
+ CanvasRenderer.arrowShapes[srcArShape].gap(edge));
+
+ rs.startX = edgeStart[0];
+ rs.startY = edgeStart[1];
+
+
+ rs.arrowStartX = arrowStart[0];
+ rs.arrowStartY = arrowStart[1];
+
+ } else if (rs.edgeType == 'straight') {
+
+ intersect = CanvasRenderer.nodeShapes[this.getNodeShape(target)].intersectLine(
+ target._private.position.x,
+ target._private.position.y,
+ this.getNodeWidth(target),
+ this.getNodeHeight(target),
+ source.position().x,
+ source.position().y,
+ tgtBorderW / 2);
+
+ if (intersect.length === 0) {
+ rs.noArrowPlacement = true;
+ // return;
+ } else {
+ rs.noArrowPlacement = false;
+ }
+
+ var arrowEnd = $$.math.shortenIntersection(intersect,
+ [source.position().x, source.position().y],
+ CanvasRenderer.arrowShapes[tgtArShape].spacing(edge));
+ var edgeEnd = $$.math.shortenIntersection(intersect,
+ [source.position().x, source.position().y],
+ CanvasRenderer.arrowShapes[tgtArShape].gap(edge));
+
+ rs.endX = edgeEnd[0];
+ rs.endY = edgeEnd[1];
+
+ rs.arrowEndX = arrowEnd[0];
+ rs.arrowEndY = arrowEnd[1];
+
+ intersect = CanvasRenderer.nodeShapes[this.getNodeShape(source)].intersectLine(
+ source._private.position.x,
+ source._private.position.y,
+ this.getNodeWidth(source),
+ this.getNodeHeight(source),
+ target.position().x,
+ target.position().y,
+ srcBorderW / 2);
+
+ if (intersect.length === 0) {
+ rs.noArrowPlacement = true;
+ // return;
+ } else {
+ rs.noArrowPlacement = false;
+ }
+
+ /*
+ console.log("1: "
+ + CanvasRenderer.arrowShapes[srcArShape],
+ srcArShape);
+ */
+ var arrowStart = $$.math.shortenIntersection(intersect,
+ [target.position().x, target.position().y],
+ CanvasRenderer.arrowShapes[srcArShape].spacing(edge));
+ var edgeStart = $$.math.shortenIntersection(intersect,
+ [target.position().x, target.position().y],
+ CanvasRenderer.arrowShapes[srcArShape].gap(edge));
+
+ rs.startX = edgeStart[0];
+ rs.startY = edgeStart[1];
+
+ rs.arrowStartX = arrowStart[0];
+ rs.arrowStartY = arrowStart[1];
+
+ } else if (rs.edgeType == 'bezier') {
+ // if( window.badArrow) debugger;
+ var cp = [rs.cp2x, rs.cp2y];
+
+ intersect = CanvasRenderer.nodeShapes[
+ this.getNodeShape(target)].intersectLine(
+ target._private.position.x,
+ target._private.position.y,
+ this.getNodeWidth(target),
+ this.getNodeHeight(target),
+ cp[0], //halfPointX,
+ cp[1], //halfPointY
+ tgtBorderW / 2
+ );
+
+ /*
+ console.log("2: "
+ + CanvasRenderer.arrowShapes[srcArShape],
+ srcArShape);
+ */
+ var arrowEnd = $$.math.shortenIntersection(intersect, cp,
+ CanvasRenderer.arrowShapes[tgtArShape].spacing(edge));
+ var edgeEnd = $$.math.shortenIntersection(intersect, cp,
+ CanvasRenderer.arrowShapes[tgtArShape].gap(edge));
+
+ rs.endX = edgeEnd[0];
+ rs.endY = edgeEnd[1];
+
+ rs.arrowEndX = arrowEnd[0];
+ rs.arrowEndY = arrowEnd[1];
+
+ intersect = CanvasRenderer.nodeShapes[
+ this.getNodeShape(source)].intersectLine(
+ source._private.position.x,
+ source._private.position.y,
+ this.getNodeWidth(source),
+ this.getNodeHeight(source),
+ cp[0], //halfPointX,
+ cp[1], //halfPointY
+ srcBorderW / 2
+ );
+
+ var arrowStart = $$.math.shortenIntersection(
+ intersect,
+ cp,
+ CanvasRenderer.arrowShapes[srcArShape].spacing(edge)
+ );
+ var edgeStart = $$.math.shortenIntersection(
+ intersect,
+ cp,
+ CanvasRenderer.arrowShapes[srcArShape].gap(edge)
+ );
+
+ rs.startX = edgeStart[0];
+ rs.startY = edgeStart[1];
+
+ rs.arrowStartX = arrowStart[0];
+ rs.arrowStartY = arrowStart[1];
+
+ // if( isNaN(rs.startX) || isNaN(rs.startY) ){
+ // debugger;
+ // }
+
+ } else if (rs.isArcEdge) {
+ return;
+ }
+ };
+
+ // Find adjacent edges
+ CanvasRenderer.prototype.findEdges = function(nodeSet) {
+
+ var edges = this.getCachedEdges();
+
+ var hashTable = {};
+ var adjacentEdges = [];
+
+ for (var i = 0; i < nodeSet.length; i++) {
+ hashTable[nodeSet[i]._private.data.id] = nodeSet[i];
+ }
+
+ for (var i = 0; i < edges.length; i++) {
+ if (hashTable[edges[i]._private.data.source]
+ || hashTable[edges[i]._private.data.target]) {
+
+ adjacentEdges.push(edges[i]);
+ }
+ }
+
+ return adjacentEdges;
+ };
+
+ CanvasRenderer.prototype.getArrowWidth = function(edgeWidth) {
+ return Math.max(Math.pow(edgeWidth * 13.37, 0.9), 29);
+ };
+
+ CanvasRenderer.prototype.getArrowHeight = function(edgeWidth) {
+ return Math.max(Math.pow(edgeWidth * 13.37, 0.9), 29);
+ };
+
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ var CanvasRenderer = $$('renderer', 'canvas');
+
+// Draw edge
+ CanvasRenderer.prototype.drawEdge = function(context, edge, drawOverlayInstead) {
+
+ if( !edge.visible() ){
+ return;
+ }
+
+ var rs = edge._private.rscratch;
+
+ // if bezier ctrl pts can not be calculated, then die
+ if( rs.badBezier ){
+ return;
+ }
+
+ var style = edge._private.style;
+
+ // Edge line width
+ if (style['width'].pxValue <= 0) {
+ return;
+ }
+
+ var overlayPadding = style['overlay-padding'].pxValue;
+ 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( edge._private.rscratch.edgeType == 'self'){
+ 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 startNode, endNode, source, target;
+ source = startNode = edge._private.source;
+ target = endNode = edge._private.target;
+
+ var targetPos = target._private.position;
+ var targetW = target.width();
+ var targetH = target.height();
+ var sourcePos = source._private.position;
+ var sourceW = source.width();
+ var sourceH = source.height();
+
+
+ var edgeWidth = style['width'].pxValue + (drawOverlayInstead ? 2 * overlayPadding : 0);
+ var lineStyle = drawOverlayInstead ? 'solid' : style['line-style'].value;
+ context.lineWidth = edgeWidth;
+
+ if( rs.edgeType !== 'haystack' ){
+ this.findEndpoints(edge);
+ }
+
+ if( rs.edgeType === 'haystack' ){
+ this.drawStyledEdge(
+ edge,
+ context,
+ [rs.source.x * sourceW + sourcePos.x, rs.source.y * sourceH + sourcePos.y, rs.target.x * targetW + targetPos.x, rs.target.y * targetH + targetPos.y],
+ lineStyle,
+ edgeWidth
+ );
+ } else if (rs.edgeType === 'self') {
+
+ var details = edge._private.rscratch;
+ this.drawStyledEdge(edge, context, [details.startX, details.startY, details.cp2ax,
+ details.cp2ay, details.selfEdgeMidX, details.selfEdgeMidY],
+ lineStyle,
+ edgeWidth);
+
+ this.drawStyledEdge(edge, context, [details.selfEdgeMidX, details.selfEdgeMidY,
+ details.cp2cx, details.cp2cy, details.endX, details.endY],
+ lineStyle,
+ edgeWidth);
+
+ } else if (rs.edgeType === 'straight') {
+
+ var nodeDirectionX = endNode._private.position.x - startNode._private.position.x;
+ var nodeDirectionY = endNode._private.position.y - startNode._private.position.y;
+
+ var edgeDirectionX = rs.endX - rs.startX;
+ var edgeDirectionY = rs.endY - rs.startY;
+
+ if (nodeDirectionX * edgeDirectionX
+ + nodeDirectionY * edgeDirectionY < 0) {
+
+ rs.straightEdgeTooShort = true;
+ } else {
+
+ var details = rs;
+ this.drawStyledEdge(edge, context, [details.startX, details.startY,
+ details.endX, details.endY],
+ lineStyle,
+ edgeWidth);
+
+ rs.straightEdgeTooShort = false;
+ }
+ } else {
+
+ var details = rs;
+
+ this.drawStyledEdge(edge, context, [details.startX, details.startY,
+ details.cp2x, details.cp2y, details.endX, details.endY],
+ lineStyle,
+ edgeWidth);
+
+ }
+
+ if ( rs.edgeType !== 'haystack' && rs.noArrowPlacement !== true && rs.startX !== undefined ){
+ this.drawArrowheads(context, edge, drawOverlayInstead);
+ }
+
+ };
+
+ var _genPoints = function(pt, spacing, even) {
+
+ var approxLen = Math.sqrt(Math.pow(pt[4] - pt[0], 2) + Math.pow(pt[5] - pt[1], 2));
+ approxLen += Math.sqrt(Math.pow((pt[4] + pt[0]) / 2 - pt[2], 2) + Math.pow((pt[5] + pt[1]) / 2 - pt[3], 2));
+
+ var pts = Math.ceil(approxLen / spacing);
+ var pz;
+
+ if (pts > 0) {
+ pz = new Array(pts * 2);
+ } else {
+ return null;
+ }
+
+ for (var i = 0; i < pts; i++) {
+ var cur = i / pts;
+ pz[i * 2] = pt[0] * (1 - cur) * (1 - cur) + 2 * (pt[2]) * (1 - cur) * cur + pt[4] * (cur) * (cur);
+ pz[i * 2 + 1] = pt[1] * (1 - cur) * (1 - cur) + 2 * (pt[3]) * (1 - cur) * cur + pt[5] * (cur) * (cur);
+ }
+
+ return pz;
+ };
+
+ var _genStraightLinePoints = function(pt, spacing, even) {
+
+ var approxLen = Math.sqrt(Math.pow(pt[2] - pt[0], 2) + Math.pow(pt[3] - pt[1], 2));
+
+ var pts = Math.ceil(approxLen / spacing);
+ var pz;
+
+ if (pts > 0) {
+ pz = new Array(pts * 2);
+ } else {
+ return null;
+ }
+
+ var lineOffset = [pt[2] - pt[0], pt[3] - pt[1]];
+ for (var i = 0; i < pts; i++) {
+ var cur = i / pts;
+ pz[i * 2] = lineOffset[0] * cur + pt[0];
+ pz[i * 2 + 1] = lineOffset[1] * cur + pt[1];
+ }
+
+ return pz;
+ };
+
+ var calls = 0;
+ var time = 0;
+ var avg = 0;
+
+ CanvasRenderer.prototype.drawStyledEdge = function(
+ edge, context, pts, type, width) {
+
+ var start = +new Date();
+
+ // 3 points given -> assume Bezier
+ // 2 -> assume straight
+
+ var cy = this.data.cy;
+ var zoom = cy.zoom();
+ var rs = edge._private.rscratch;
+ var canvasCxt = context;
+ var path;
+ var pathCacheHit = false;
+ var usePaths = CanvasRenderer.usePaths();
+
+
+
+
+ // Adjusted edge width for dotted
+// width = Math.max(width * 1.6, 3.4) * zoom;
+
+ // console.log('w', width);
+
+ if (type === 'solid') {
+
+ if( usePaths ){
+
+ var pathCacheKey = pts;
+ var keyLengthMatches = rs.pathCacheKey && pathCacheKey.length === rs.pathCacheKey.length;
+ var keyMatches = keyLengthMatches;
+
+ for( var i = 0; keyMatches && i < pathCacheKey.length; i++ ){
+ if( rs.pathCacheKey[i] !== pathCacheKey[i] ){
+ keyMatches = false;
+ }
+ }
+
+ if( keyMatches ){
+ path = context = rs.pathCache;
+ pathCacheHit = true;
+ } else {
+ path = context = new Path2D();
+ rs.pathCacheKey = pathCacheKey;
+ rs.pathCache = path;
+ }
+
+ }
+
+ if( !pathCacheHit ){
+ if( context.beginPath ){ context.beginPath(); }
+ context.moveTo(pts[0], pts[1]);
+ if (pts.length == 3 * 2) {
+ context.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5]);
+ } else {
+ context.lineTo(pts[2], pts[3]);
+ }
+ }
+
+ context = canvasCxt;
+ if( usePaths ){
+ context.stroke( path );
+ } else {
+ context.stroke();
+ }
+
+ } else if (type === 'dotted') {
+
+ var pt;
+ if (pts.length == 3 * 2) {
+ pt = _genPoints(pts, 16, true);
+ } else {
+ pt = _genStraightLinePoints(pts, 16, true);
+ }
+
+ if (!pt) { return; }
+
+ var dotRadius = Math.max(width * 1.6, 3.4) * zoom;
+ var bufW = dotRadius * 2, bufH = dotRadius * 2;
+ bufW = Math.max(bufW, 1);
+ bufH = Math.max(bufH, 1);
+
+ var buffer = this.createBuffer(bufW, bufH);
+
+ var context2 = buffer[1];
+// console.log(buffer);
+// console.log(bufW, bufH);
+
+ // Draw on buffer
+ context2.setTransform(1, 0, 0, 1, 0, 0);
+ context2.clearRect(0, 0, bufW, bufH);
+
+ context2.fillStyle = context.strokeStyle;
+ context2.beginPath();
+ context2.arc(bufW/2, bufH/2, dotRadius * 0.5, 0, Math.PI * 2, false);
+ context2.fill();
+
+ // Now use buffer
+ context.beginPath();
+ //context.save();
+
+ for (var i=0; i 0) {
+ context.lineWidth = lineWidth;
+ context.strokeText(text, textX, textY);
+ }
+
+ context.fillText(text, textX, textY);
+ }
+ };
+
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ var CanvasRenderer = $$('renderer', 'canvas');
+
+ // Draw node
+ CanvasRenderer.prototype.drawNode = function(context, node, drawOverlayInstead) {
+
+ var r = this;
+ var nodeWidth, nodeHeight;
+ var style = node._private.style;
+ var rs = node._private.rscratch;
+
+ if ( !node.visible() ) {
+ return;
+ }
+
+ var usePaths = CanvasRenderer.usePaths();
+ var canvasContext = context;
+ var path;
+ var pathCacheHit = false;
+
+ var overlayPadding = style['overlay-padding'].pxValue;
+ 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 = this.getNodeWidth(node);
+ nodeHeight = this.getNodeHeight(node);
+
+ context.lineWidth = style['border-width'].pxValue;
+
+ if( drawOverlayInstead === undefined || !drawOverlayInstead ){
+
+ // Node color & opacity
+
+ var bgColor = style['background-color'].value;
+ var borderColor = style['border-color'].value;
+
+ this.fillStyle(context, bgColor[0], bgColor[1], bgColor[2], style['background-opacity'].value * style['opacity'].value * parentOpacity);
+
+ this.strokeStyle(context, borderColor[0], borderColor[1], borderColor[2], style['border-opacity'].value * style['opacity'].value * parentOpacity);
+
+ context.lineJoin = 'miter'; // so borders are square with the node shape
+
+ //var image = this.getCachedImage('url');
+
+ var url = style['background-image'].value[2] ||
+ style['background-image'].value[1];
+
+ var styleShape = style['shape'].strValue;
+
+ var pos = node._private.position;
+
+ 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
+ };
+ }
+
+ CanvasRenderer.nodeShapes[this.getNodeShape(node)].drawPath(
+ context,
+ npos.x,
+ npos.y,
+ nodeWidth,
+ nodeHeight);
+ }
+
+ context = canvasContext;
+
+ if( usePaths ){
+ context.fill( path );
+ } else {
+ context.fill();
+ }
+
+ if (url !== undefined) {
+
+ // get image, and if not loaded then ask to redraw when later loaded
+ var image = this.getCachedImage(url, function(){
+ r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+ r.data.canvasNeedsRedraw[CanvasRenderer.DRAG] = true;
+
+ r.redraw();
+ });
+
+ if( image.complete ){
+ this.drawInscribedImage(context, image, node);
+ }
+
+ }
+
+ var darkness = style['background-blacken'].value;
+
+ if( this.hasPie(node) ){
+ this.drawPie(context, node);
+
+ // redraw path for blacken and border
+ if( darkness !== 0 ){
+
+ if( !usePaths ){
+ CanvasRenderer.nodeShapes[this.getNodeShape(node)].drawPath(
+ 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 (style['border-width'].pxValue > 0) {
+
+ if( usePaths ){
+ context.stroke( path );
+ } else {
+ context.stroke();
+ }
+
+ }
+
+ if( usePaths ){
+ context.translate( -pos.x, -pos.y );
+ }
+
+ // draw the overlay
+ } else {
+
+ if( overlayOpacity > 0 ){
+ this.fillStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity);
+
+ CanvasRenderer.nodeShapes['roundrectangle'].drawPath(
+ 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?
+ CanvasRenderer.prototype.hasPie = function(node){
+ node = node[0]; // ensure ele ref
+
+ return node._private.hasPie;
+ };
+
+ CanvasRenderer.prototype.drawPie = function(context, node){
+ node = node[0]; // ensure ele ref
+
+ var pieSize = node._private.style['pie-size'];
+ var nodeW = this.getNodeWidth( node );
+ var nodeH = this.getNodeHeight( node );
+ var x = node._private.position.x;
+ var y = node._private.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]
+
+ if( pieSize.units === '%' ){
+ radius = radius * pieSize.value / 100;
+ } else if( pieSize.pxValue !== undefined ){
+ radius = pieSize.pxValue / 2;
+ }
+
+ for( var i = 1; i <= $$.style.pieBackgroundN; i++ ){ // 1..N
+ var size = node._private.style['pie-' + i + '-background-size'].value;
+ var color = node._private.style['pie-' + i + '-background-color'].value;
+ var percent = size / 100; // map integer range [0, 100] to [0, 1]
+ 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], 1);
+
+ context.fill();
+
+ lastPercent += percent;
+ }
+
+ };
+
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ var CanvasRenderer = $$('renderer', 'canvas');
+
+ var isFirefox = typeof InstallTrigger !== 'undefined';
+
+ CanvasRenderer.prototype.getPixelRatio = function(){
+ var context = this.data.contexts[0];
+
+ var backingStore = context.backingStorePixelRatio ||
+ context.webkitBackingStorePixelRatio ||
+ context.mozBackingStorePixelRatio ||
+ context.msBackingStorePixelRatio ||
+ context.oBackingStorePixelRatio ||
+ context.backingStorePixelRatio || 1;
+
+ //console.log(window.devicePixelRatio, backingStore);
+
+ if( isFirefox ){ // because ff can't scale canvas properly
+ return 1;
+ }
+
+ return (window.devicePixelRatio || 1) / backingStore;
+ };
+
+ CanvasRenderer.prototype.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;
+ };
+
+ CanvasRenderer.prototype.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;
+ // }
+ };
+
+ CanvasRenderer.prototype.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;
+ // }
+ };
+
+ // Resize canvas
+ CanvasRenderer.prototype.matchCanvasSize = function(container) {
+ var data = this.data;
+ var width = container.clientWidth;
+ var height = container.clientHeight;
+ var pixelRatio = this.getPixelRatio();
+ var canvasWidth = width * pixelRatio;
+ var canvasHeight = height * pixelRatio;
+ var canvas;
+
+ if( canvasWidth === this.canvasWidth && canvasHeight === this.canvasHeight ){
+ return; // save cycles if same
+ }
+
+ this.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 < CanvasRenderer.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 < CanvasRenderer.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';
+ }
+ }
+
+ this.textureMult = 1;
+ if( pixelRatio <= 1 ){
+ canvas = data.bufferCanvases[ CanvasRenderer.TEXTURE_BUFFER ];
+
+ this.textureMult = 2;
+ canvas.width = canvasWidth * this.textureMult;
+ canvas.height = canvasHeight * this.textureMult;
+ }
+
+ this.canvasWidth = canvasWidth;
+ this.canvasHeight = canvasHeight;
+
+ };
+
+ CanvasRenderer.prototype.renderTo = function( cxt, zoom, pan, pxRatio ){
+ this.redraw({
+ forcedContext: cxt,
+ forcedZoom: zoom,
+ forcedPan: pan,
+ drawAllLayers: true,
+ forcedPxRatio: pxRatio
+ });
+ };
+
+ CanvasRenderer.prototype.timeToRender = function(){
+ return this.redrawTotalTime / this.redrawCount;
+ };
+
+ CanvasRenderer.minRedrawLimit = 1000/60; // people can't see much better than 60fps
+ CanvasRenderer.maxRedrawLimit = 1000; // don't cap max b/c it's more important to be responsive than smooth
+
+ // Redraw frame
+ CanvasRenderer.prototype.redraw = function( options ) {
+ options = options || {};
+
+ // console.log('redraw');
+
+ 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.data.cy; var data = r.data;
+ var needDraw = data.canvasNeedsRedraw;
+
+ if( this.redrawTimeout ){
+ clearTimeout( this.redrawTimeout );
+ }
+ this.redrawTimeout = null;
+
+ if( this.averageRedrawTime === undefined ){ this.averageRedrawTime = 0; }
+
+ var minRedrawLimit = CanvasRenderer.minRedrawLimit;
+ var maxRedrawLimit = CanvasRenderer.maxRedrawLimit;
+
+ var redrawLimit = this.averageRedrawTime; // estimate the ideal redraw limit based on how fast we can draw
+ redrawLimit = minRedrawLimit > redrawLimit ? minRedrawLimit : redrawLimit;
+ redrawLimit = redrawLimit < maxRedrawLimit ? redrawLimit : maxRedrawLimit;
+
+ //console.log('--\nideal: %i; effective: %i', this.averageRedrawTime, redrawLimit);
+
+ if( this.lastDrawTime === undefined ){ this.lastDrawTime = 0; }
+
+ var nowTime = +new Date();
+ var timeElapsed = nowTime - this.lastDrawTime;
+ var callAfterLimit = timeElapsed >= redrawLimit;
+
+ if( !forcedContext ){
+ if( !callAfterLimit || this.currentlyDrawing ){
+ // console.log('-- skip');
+
+ // we have new things to draw but we're busy, so try again when possibly free
+ this.redrawTimeout = setTimeout(function(){
+ r.redraw();
+ }, redrawLimit);
+ return;
+ }
+
+ this.lastDrawTime = nowTime;
+ this.currentlyDrawing = true;
+ }
+
+
+ var startTime;
+
+ //console.log('-- redraw --')
+
+
+ function drawToContext(){
+ startTime = +new Date();
+ 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
+ };
+
+ if( forcedPan ){
+ effectivePan = forcedPan;
+ }
+
+ // apply pixel ratio
+
+ effectiveZoom *= pixelRatio;
+ effectivePan.x *= pixelRatio;
+ effectivePan.y *= pixelRatio;
+
+ var eles = {
+ drag: {
+ nodes: [],
+ edges: []
+ },
+ nondrag: {
+ nodes: [],
+ edges: []
+ }
+ };
+
+ function setContextTransform(context, clear){
+ context.setTransform(1, 0, 0, 1, 0, 0);
+
+ if( !forcedContext && (clear === undefined || clear) ){
+ context.clearRect(0, 0, r.canvasWidth, r.canvasHeight);
+ }
+
+ if( !drawAllLayers ){
+ context.translate(effectivePan.x, effectivePan.y);
+ context.scale(effectiveZoom, effectiveZoom);
+ }
+ if( forcedPan ){
+ context.translate(forcedPan.x, forcedPan.y);
+ }
+ if( forcedZoom ){
+ context.scale(forcedZoom, forcedZoom);
+ }
+ }
+
+ var textureDraw = r.textureOnViewport && !forcedContext && (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming);
+
+ if( textureDraw ){
+
+ var bb;
+
+ if( !r.textureCache ){
+ r.textureCache = {};
+
+ bb = r.textureCache.bb = cy.boundingBox();
+
+ r.textureCache.texture = r.data.bufferCanvases[ CanvasRenderer.TEXTURE_BUFFER ];
+
+ var cxt = r.data.bufferContexts[ CanvasRenderer.TEXTURE_BUFFER ];
+
+ cxt.setTransform(1, 0, 0, 1, 0, 0);
+ cxt.clearRect(0, 0, r.canvasWidth * r.textureMult, r.canvasHeight * r.textureMult);
+
+ r.redraw({
+ 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[CanvasRenderer.DRAG] = false;
+ needDraw[CanvasRenderer.NODE] = false;
+
+ var context = data.contexts[CanvasRenderer.NODE];
+
+ var texture = r.textureCache.texture;
+ var texture = r.textureCache.texture;
+ var vp = r.textureCache.viewport;
+ bb = r.textureCache.bb;
+
+ context.setTransform(1, 0, 0, 1, 0, 0);
+ 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[CanvasRenderer.DRAG] || needDraw[CanvasRenderer.NODE] || drawAllLayers || drawOnlyNodeLayer) {
+ //NB : VERY EXPENSIVE
+
+ if( hideEdges ){
+ } else {
+ r.findEdgeControlPoints(edges);
+ }
+
+ var zEles = r.getCachedZSortedEles();
+
+ for (var i = 0; i < zEles.length; i++) {
+ var ele = zEles[i];
+ var list;
+
+ if ( ele._private.rscratch.inDragLayer ) {
+ list = eles.drag;
+ } else {
+ list = eles.nondrag;
+ }
+
+ list[ ele._private.group ].push( ele );
+ }
+
+ }
+
+
+ function drawElements( list, context ){
+ var edges = list.edges;
+ var nodes = list.nodes;
+
+ for (var i = 0; i < edges.length && !hideEdges; i++) {
+ ele = edges[i];
+
+ r.drawEdge(context, ele);
+ }
+
+ for (var i = 0; i < edges.length && !hideEdges && !hideLabels; i++) {
+ ele = edges[i];
+
+ r.drawEdgeText(context, ele);
+ }
+
+ for (var i = 0; i < edges.length && !hideEdges; i++) {
+ ele = edges[i];
+
+ r.drawEdge(context, ele, true);
+ }
+
+ for( var i = 0; i < nodes.length; i++ ){
+ var ele = nodes[i];
+
+ r.drawNode(context, ele);
+
+ if( !hideLabels ){
+ r.drawNodeText(context, ele);
+ }
+
+ r.drawNode(context, ele, true);
+ }
+
+ }
+
+
+ if (needDraw[CanvasRenderer.NODE] || drawAllLayers || drawOnlyNodeLayer) {
+ // console.log('redrawing node layer');
+
+ var context = forcedContext || data.contexts[CanvasRenderer.NODE];
+
+ setContextTransform( context );
+ drawElements(eles.nondrag, context);
+
+ if( !drawAllLayers ){
+ needDraw[CanvasRenderer.NODE] = false;
+ }
+ }
+
+ if ( !drawOnlyNodeLayer && (needDraw[CanvasRenderer.DRAG] || drawAllLayers) ) {
+
+ var context = forcedContext || data.contexts[CanvasRenderer.DRAG];
+
+ setContextTransform( context );
+ drawElements(eles.drag, context);
+
+ if( !drawAllLayers ){
+ needDraw[CanvasRenderer.DRAG] = false;
+ }
+ }
+
+ if ( !drawOnlyNodeLayer && (needDraw[CanvasRenderer.SELECT_BOX] && !drawAllLayers) ) {
+ // console.log('redrawing selection box');
+
+ var context = forcedContext || data.contexts[CanvasRenderer.SELECT_BOX];
+
+ setContextTransform( context );
+
+ if (data.select[4] == 1) {
+ var zoom = data.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(
+ data.select[0],
+ data.select[1],
+ data.select[2] - data.select[0],
+ data.select[3] - data.select[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(
+ data.select[0],
+ data.select[1],
+ data.select[2] - data.select[0],
+ data.select[3] - data.select[1]);
+ }
+ }
+
+ if( data.bgActivePosistion ){
+ var zoom = data.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'].pxValue / zoom, 0, 2 * Math.PI);
+ context.fill();
+ }
+
+ if( !drawAllLayers ){
+ needDraw[CanvasRenderer.SELECT_BOX] = false;
+ }
+ }
+
+
+ var endTime = +new Date();
+
+ if( r.averageRedrawTime === undefined ){
+ r.averageRedrawTime = endTime - startTime;
+ }
+
+ if( r.redrawCount === undefined ){
+ r.redrawCount = 0;
+ }
+
+ r.redrawCount++;
+
+ if( r.redrawTotalTime === undefined ){
+ r.redrawTotalTime = 0;
+ }
+
+ r.redrawTotalTime += endTime - startTime;
+
+ // use a weighted average with a bias from the previous average so we don't spike so easily
+ r.averageRedrawTime = r.averageRedrawTime/2 + (endTime - startTime)/2;
+ //console.log('actual: %i, average: %i', endTime - startTime, this.averageRedrawTime);
+
+ r.currentlyDrawing = false;
+ } // draw to context
+
+ if( !forcedContext ){
+ $$.util.requestAnimationFrame(drawToContext); // makes direct renders to screen a bit more responsive
+ } else {
+ drawToContext();
+ }
+
+ if( !forcedContext && !r.initrender ){
+ r.initrender = true;
+ cy.trigger('initrender');
+ }
+
+ };
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ var CanvasRenderer = $$('renderer', 'canvas');
+
+ // @O Polygon drawing
+ CanvasRenderer.prototype.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();
+ };
+
+ CanvasRenderer.prototype.drawPolygon = function(
+ context, x, y, width, height, points) {
+
+ // Draw path
+ this.drawPolygonPath(context, x, y, width, height, points);
+
+ // Fill path
+ context.fill();
+ };
+
+ // Round rectangle drawing
+ CanvasRenderer.prototype.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();
+ };
+
+ CanvasRenderer.prototype.drawRoundRectangle = function(
+ context, x, y, width, height, radius) {
+
+ this.drawRoundRectanglePath(context, x, y, width, height, radius);
+
+ context.fill();
+ };
+
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ var CanvasRenderer = $$('renderer', 'canvas');
+
+ CanvasRenderer.prototype.createBuffer = function(w, h) {
+ var buffer = document.createElement('canvas');
+ buffer.width = w;
+ buffer.height = h;
+
+ return [buffer, buffer.getContext('2d')];
+ };
+
+ CanvasRenderer.prototype.bufferCanvasImage = function( options ){
+ var data = this.data;
+ var cy = data.cy;
+ var bb = cy.boundingBox();
+ var width = options.full ? Math.ceil(bb.w) : this.data.container.clientWidth;
+ var height = options.full ? Math.ceil(bb.h) : this.data.container.clientHeight;
+ var scale = 1;
+
+ if( options.full && options.scale !== undefined ){
+ width *= options.scale;
+ height *= options.scale;
+
+ scale = options.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.redraw({
+ forcedContext: buffCxt,
+ drawAllLayers: true,
+ forcedZoom: scale,
+ forcedPan: { x: -bb.x1, y: -bb.y1 },
+ forcedPxRatio: 1
+ });
+ } else { // draw the current view
+ this.redraw({
+ forcedContext: buffCxt,
+ drawAllLayers: true,
+ forcedZoom: cy.zoom(),
+ forcedPan: cy.pan(),
+ forcedPxRatio: 1
+ });
+ }
+ }
+
+ return buffCanvas;
+ };
+
+ CanvasRenderer.prototype.png = function( options ){
+ return this.bufferCanvasImage( options ).toDataURL('image/png');
+ };
+
+})( cytoscape );
+;(function($$){ 'use strict';
+
+ var CanvasRenderer = $$('renderer', 'canvas');
+
+ CanvasRenderer.prototype.registerBinding = function(target, event, handler, useCapture){
+ this.bindings.push({
+ target: target,
+ event: event,
+ handler: handler,
+ useCapture: useCapture
+ });
+
+ target.addEventListener(event, handler, useCapture);
+ };
+
+ CanvasRenderer.prototype.load = function() {
+ var r = this;
+
+ // 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, addSelected, dragElements) {
+ if (!addSelected)
+ {
+ var parents = node.parents();
+
+ // do not process descendants for this node,
+ // because those will be handled for the topmost selected parent
+ for (var i=0; i < parents.size(); i++)
+ {
+ if (parents[i]._private.selected)
+ {
+ return;
+ }
+ }
+ }
+
+ var innerNodes = node.descendants();
+
+ // TODO do not drag hidden children & children of hidden children?
+ for (var i=0; i < innerNodes.size(); i++)
+ {
+ // if addSelected is true, then add node in any case,
+ // if not, then add only non-selected nodes
+ if ( (addSelected || !innerNodes[i]._private.selected) )
+ {
+ innerNodes[i]._private.rscratch.inDragLayer = true;
+ //innerNodes[i].trigger(new $$.Event(e, {type: 'grab'}));
+ //innerNodes[i].trigger(event);
+ dragElements.push(innerNodes[i]);
+
+ for (var j=0; j < innerNodes[i]._private.edges.length; j++)
+ {
+ innerNodes[i]._private.edges[j]._private.rscratch.inDragLayer = true;
+ }
+ }
+ }
+ };
+
+ // adds the given nodes, and its edges to the drag layer
+ var addNodeToDrag = function(node, dragElements) {
+ node._private.grabbed = true;
+ node._private.rscratch.inDragLayer = true;
+
+ dragElements.push(node);
+
+ for (var i=0;i containerPageCoords[0] && e.clientX < containerPageCoords[0] + r.canvasWidth
+ && e.clientY > containerPageCoords[1] && e.clientY < containerPageCoords[1] + r.canvasHeight) {
+
+ } else {
+ return;
+ }
+ }
+
+ var cy = r.data.cy;
+ var pos = r.projectIntoViewport(e.clientX, e.clientY);
+ var select = r.data.select;
+
+ var near = null;
+ if( !r.hoverData.draggingEles ){
+ near = r.findNearestElement(pos[0], pos[1], true);
+ }
+ 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;
+
+
+ preventDefault = true;
+
+ // Mousemove event
+ {
+ if (near != null) {
+ near
+ .trigger(new $$.Event(e, {
+ type: 'mousemove',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ .trigger(new $$.Event(e, {
+ type: 'vmousemove',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ .trigger(new $$.Event(e, {
+ type: 'tapdrag',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ ;
+
+ } else if (near == null) {
+ cy
+ .trigger(new $$.Event(e, {
+ type: 'mousemove',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ .trigger(new $$.Event(e, {
+ type: 'vmousemove',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ .trigger(new $$.Event(e, {
+ type: 'tapdrag',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ ;
+ }
+
+ }
+
+
+ // trigger context drag if rmouse down
+ if( r.hoverData.which === 3 ){
+ var cxtEvt = new $$.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( new $$.Event(e, {
+ type: 'cxtdragout',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) );
+
+ // console.log('cxtdragout ' + r.hoverData.cxtOver.id());
+ }
+
+ r.hoverData.cxtOver = near;
+
+ if( near ){
+ near.trigger( new $$.Event(e, {
+ type: 'cxtdragover',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) );
+
+ // console.log('cxtdragover ' + near.id());
+ }
+
+ }
+
+ // Check if we are drag panning the entire graph
+ } else if (r.hoverData.dragging) {
+ preventDefault = true;
+
+ if( cy.panningEnabled() && cy.userPanningEnabled() ){
+ var deltaP = {x: disp[0] * cy.zoom(), y: disp[1] * cy.zoom()};
+
+ cy.panBy( deltaP );
+ }
+
+ // 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())
+ && ( !cy.boxSelectionEnabled() || +new Date() - r.hoverData.downTime >= CanvasRenderer.panOrBoxSelectDelay )
+ && (Math.abs(select[3] - select[1]) + Math.abs(select[2] - select[0]) < 4)
+ && cy.panningEnabled() && cy.userPanningEnabled() ) {
+
+ r.hoverData.dragging = true;
+ select[4] = 0;
+
+ } else {
+ // deactivate bg on box selection
+ if (cy.boxSelectionEnabled() && Math.pow(select[2] - select[0], 2) + Math.pow(select[3] - select[1], 2) > 7 && select[4]){
+ clearTimeout( r.bgActiveTimeout );
+ r.data.bgActivePosistion = undefined;
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+ r.redraw();
+ }
+
+ if( down && down.isEdge() && down.active() ){ down.unactivate(); }
+
+ if (near != last) {
+
+ if (last) {
+ last.trigger( new $$.Event(e, {
+ type: 'mouseout',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) );
+
+ last.trigger( new $$.Event(e, {
+ type: 'tapdragout',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) );
+ }
+
+ if (near) {
+ near.trigger( new $$.Event(e, {
+ type: 'mouseover',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) );
+
+ near.trigger( new $$.Event(e, {
+ type: 'tapdragover',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) );
+ }
+
+ r.hoverData.last = near;
+ }
+
+ if ( down && down.isNode() && r.nodeIsDraggable(down) ) {
+ if( !r.dragData.didDrag ) {
+ r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+ }
+
+ r.dragData.didDrag = true; // indicate that we actually did drag the node
+
+ r.hoverData.draggingEles = true;
+
+ var toTrigger = [];
+
+ for( var i = 0; i < draggedElements.length; i++ ){
+ var dEle = draggedElements[i];
+
+ // Locked nodes not draggable, as well as non-visible nodes
+ if (dEle.isNode() && r.nodeIsDraggable(dEle)) {
+ var dPos = dEle._private.position;
+
+ toTrigger.push( dEle );
+
+ if( $$.is.number(disp[0]) && $$.is.number(disp[1]) ){
+ dPos.x += disp[0];
+ dPos.y += disp[1];
+ }
+
+ }
+ }
+
+ var tcol = (new $$.Collection(cy, toTrigger));
+
+ tcol.updateCompoundBounds();
+ tcol.trigger('position drag');
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.DRAG] = true;
+ r.redraw();
+ }
+
+ // 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;
+ }
+ }, 1000/30), false);
+
+ r.registerBinding(window, 'mouseup', function(e) {
+ // console.log('--\nmouseup', e)
+
+ var capture = r.hoverData.capture;
+ if (!capture) { return; }
+ r.hoverData.capture = false;
+
+ var cy = r.data.cy; var pos = r.projectIntoViewport(e.clientX, e.clientY); var select = r.data.select;
+ var near = r.findNearestElement(pos[0], pos[1], true);
+ var draggedElements = r.dragData.possibleDragElements; var down = r.hoverData.down;
+ var shiftDown = e.shiftKey;
+
+ if( r.data.bgActivePosistion ){
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+ r.redraw();
+ }
+
+ r.data.bgActivePosistion = undefined; // not active bg now
+ clearTimeout( r.bgActiveTimeout );
+
+ r.hoverData.cxtStarted = false;
+ r.hoverData.draggingEles = false;
+
+ if( down ){
+ down.unactivate();
+ }
+
+ if( r.hoverData.which === 3 ){
+ var cxtEvt = new $$.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 = new $$.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;
+
+ // if not right mouse
+ } else {
+
+ // 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
+ && !(Math.pow(select[2] - select[0], 2) + Math.pow(select[3] - select[1], 2) > 7 && select[4]) // not box selection
+ && !r.hoverData.dragging // not panning
+ ) {
+
+ // console.log('unselect all from bg');
+
+ //++clock+unselect
+ // var a = time();
+ cy.$(':selected').unselect();
+
+ //++clock+unselect
+ // console.log('unselect', time() - a);
+
+ if (draggedElements.length > 0) {
+ r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+ }
+
+ r.dragData.possibleDragElements = draggedElements = [];
+ }
+
+
+ // Mouseup event
+ {
+ // console.log('trigger mouseup et al');
+
+ if (near != null) {
+ near
+ .trigger(new $$.Event(e, {
+ type: 'mouseup',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ .trigger(new $$.Event(e, {
+ type: 'tapend',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ .trigger(new $$.Event(e, {
+ type: 'vmouseup',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ ;
+ } else if (near == null) {
+ cy
+ .trigger(new $$.Event(e, {
+ type: 'mouseup',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ .trigger(new $$.Event(e, {
+ type: 'tapend',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ .trigger(new $$.Event(e, {
+ type: 'vmouseup',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }))
+ ;
+ }
+ }
+
+ // Click event
+ {
+ // console.log('trigger click et al');
+
+ if (Math.pow(select[2] - select[0], 2) + Math.pow(select[3] - select[1], 2) === 0) {
+ if (near != null) {
+ near
+ .trigger( new $$.Event(e, {
+ type: 'click',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) )
+ .trigger( new $$.Event(e, {
+ type: 'tap',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) )
+ .trigger( new $$.Event(e, {
+ type: 'vclick',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) )
+ ;
+ } else if (near == null) {
+ cy
+ .trigger( new $$.Event(e, {
+ type: 'click',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) )
+ .trigger( new $$.Event(e, {
+ type: 'tap',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) )
+ .trigger( new $$.Event(e, {
+ type: 'vclick',
+ cyPosition: { x: pos[0], y: pos[1] }
+ }) )
+ ;
+ }
+ }
+ }
+
+ // Single selection
+ if (near == down && !r.dragData.didDrag) {
+ if (near != null && near._private.selectable) {
+
+ // console.log('single selection')
+
+ if( cy.selectionType() === 'additive' || shiftDown ){
+ if( near.selected() ){
+ near.unselect();
+ } else {
+ near.select();
+ }
+ } else {
+ if( !shiftDown ){
+ cy.$(':selected').not( near ).unselect();
+ near.select();
+ }
+ }
+
+
+ updateAncestorsInDragLayer(near, false);
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+
+ }
+ // Ungrab single drag
+ } else if (near == down) {
+ if (near != null && near._private.grabbed) {
+ // console.log('ungrab single drag')
+
+ var grabbedEles = cy.$(':grabbed');
+
+ for(var i = 0; i < grabbedEles.length; i++){
+ var ele = grabbedEles[i];
+
+ ele._private.grabbed = false;
+
+ var sEdges = ele._private.edges;
+ for (var j=0;j 7 && select[4] ) {
+ var newlySelected = [];
+ var box = r.getAllInBox( select[0], select[1], select[2], select[3] );
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+
+ if( box.length > 0 ) {
+ r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+ }
+
+ for( var i = 0; i < box.length; i++ ){
+ if( box[i]._private.selectable ){
+ newlySelected.push( box[i] );
+ }
+ }
+
+ var newlySelCol = new $$.Collection( cy, newlySelected );
+
+ if( cy.selectionType() === 'additive' ){
+ newlySelCol.select();
+ } else {
+ if( !shiftDown ){
+ cy.$(':selected').not( newlySelCol ).unselect();
+ }
+
+ newlySelCol.select();
+ }
+
+ if( newlySelected.length === 0 ){
+ r.redraw();
+ }
+
+ }
+
+ // Cancel drag pan
+ if( r.hoverData.dragging ){
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+ r.redraw();
+ }
+
+ r.hoverData.dragging = false;
+
+ if (!select[4]) {
+ // console.log('free at end', draggedElements)
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.DRAG] = true;
+ r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+
+ for (var i=0; i 250) {
+ if (r.touchData.start) {
+ r.touchData.start.trigger( new $$.Event(e, {
+ type: 'taphold',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+ } else {
+ r.data.cy.trigger( new $$.Event(e, {
+ type: 'taphold',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+
+ cy.$(':selected').unselect();
+ }
+
+// console.log('taphold');
+ }
+ }, 1000);
+ }
+
+ //r.redraw();
+
+ }, false);
+
+// console.log = function(m){ $('#console').append(''+m+'
'); };
+
+ r.registerBinding(window, 'touchmove', $$.util.throttle(function(e) {
+
+ var select = r.data.select;
+ var capture = r.touchData.capture; //if (!capture) { return; };
+ if( capture ){ e.preventDefault(); }
+
+ var cy = r.data.cy;
+ 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]; }
+ var disp = []; for (var j=0;j= 1.5 || distance2 >= 150 ){
+ r.touchData.cxt = false;
+ if( r.touchData.start ){ r.touchData.start.unactivate(); r.touchData.start = null; }
+ r.data.bgActivePosistion = undefined;
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+
+ var cxtEvt = new $$.Event(e, {
+ type: 'cxttapend',
+ cyPosition: { x: now[0], y: now[1] }
+ });
+ if( r.touchData.start ){
+ r.touchData.start.trigger( cxtEvt );
+ } else {
+ cy.trigger( cxtEvt );
+ }
+ }
+
+ }
+
+ if( capture && r.touchData.cxt ){
+ var cxtEvt = new $$.Event(e, {
+ type: 'cxtdrag',
+ cyPosition: { x: now[0], y: now[1] }
+ });
+ r.data.bgActivePosistion = undefined;
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = 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;
+
+ //console.log('cxtdrag')
+
+ var near = r.findNearestElement(now[0], now[1], true);
+
+ if( !r.touchData.cxtOver || near !== r.touchData.cxtOver ){
+
+ if( r.touchData.cxtOver ){
+ r.touchData.cxtOver.trigger( new $$.Event(e, {
+ type: 'cxtdragout',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+
+ // console.log('cxtdragout');
+ }
+
+ r.touchData.cxtOver = near;
+
+ if( near ){
+ near.trigger( new $$.Event(e, {
+ type: 'cxtdragover',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+
+ // console.log('cxtdragover');
+ }
+
+ }
+
+ } else if( capture && e.touches[2] && cy.boxSelectionEnabled() ){
+ r.data.bgActivePosistion = undefined;
+ clearTimeout( this.threeFingerSelectTimeout );
+ this.lastThreeTouch = +new Date();
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = 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.redraw();
+
+ } else if ( capture && e.touches[1] && cy.zoomingEnabled() && cy.panningEnabled() && cy.userZoomingEnabled() && cy.userPanningEnabled() ) { // two fingers => pinch to zoom
+ r.data.bgActivePosistion = undefined;
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+
+ var draggedEles = r.dragData.touchDragEles;
+ if( draggedEles ){
+ r.data.canvasNeedsRedraw[CanvasRenderer.DRAG] = true;
+
+ for( var i = 0; i < draggedEles.length; i++ ){
+ draggedEles[i]._private.grabbed = false;
+ draggedEles[i]._private.rscratch.inDragLayer = false;
+ }
+ }
+
+ // console.log('touchmove ptz');
+
+ // (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;
+
+ // console.log( f1x2, f1y2 )
+ // console.log( f2x2, f2y2 )
+
+ var distance2 = distance( f1x2, f1y2, f2x2, f2y2 );
+ var factor = distance2 / distance1;
+
+ // console.log(distance2)
+ // console.log(factor)
+
+ if( factor != 1 && twoFingersStartInside){
+
+ // console.log(factor)
+ // console.log(distance2 + ' / ' + distance1);
+ // console.log('--');
+
+ // 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
+ };
+
+ // console.log(pan2);
+ // console.log(zoom2);
+
+ // remove dragged eles
+ if( r.touchData.start ){
+ var draggedEles = r.dragData.touchDragEles;
+
+ if( draggedEles ){ for( var i = 0; i < draggedEles.length; i++ ){
+ draggedEles[i]._private.grabbed = false;
+ draggedEles[i]._private.rscratch.inDragLayer = false;
+ } }
+
+ r.touchData.start._private.active = false;
+ r.touchData.start._private.grabbed = false;
+ r.touchData.start._private.rscratch.inDragLayer = false;
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.DRAG] = true;
+
+ r.touchData.start
+ .trigger('free')
+ .trigger('unactivate')
+ ;
+ }
+
+ cy.viewport({
+ zoom: zoom2,
+ pan: pan2
+ });
+
+ 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);;
+
+ if ( start != null && start._private.group == 'nodes' && r.nodeIsDraggable(start)) {
+ var draggedEles = r.dragData.touchDragEles;
+
+ for( var k = 0; k < draggedEles.length; k++ ){
+ var draggedEle = draggedEles[k];
+
+ if( r.nodeIsDraggable(draggedEle) ){
+ r.dragData.didDrag = true;
+ var dPos = draggedEle._private.position;
+
+ dPos.x += disp[0];
+ dPos.y += disp[1];
+ }
+ }
+
+ var tcol = new $$.Collection(cy, draggedEle);
+
+ tcol.updateCompoundBounds();
+ tcol.trigger('position drag');
+
+ r.hoverData.draggingEles = true;
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.DRAG] = true;
+
+ if (r.touchData.startPosition[0] == earlier[0]
+ && r.touchData.startPosition[1] == earlier[1]) {
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+ }
+
+ r.redraw();
+ }
+
+ // Touchmove event
+ {
+
+ if (start != null) {
+ start.trigger( new $$.Event(e, {
+ type: 'touchmove',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+
+ start.trigger( new $$.Event(e, {
+ type: 'tapdrag',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+
+ start.trigger( new $$.Event(e, {
+ type: 'vmousemove',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+ }
+
+ if (start == null) {
+
+ if (near != null) {
+ near.trigger( new $$.Event(e, {
+ type: 'touchmove',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+
+ near.trigger( new $$.Event(e, {
+ type: 'tapdrag',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+
+ near.trigger( new $$.Event(e, {
+ type: 'vmousemove',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+ }
+
+ if (near == null) {
+ cy.trigger( new $$.Event(e, {
+ type: 'touchmove',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+
+ cy.trigger( new $$.Event(e, {
+ type: 'tapdrag',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+
+ cy.trigger( new $$.Event(e, {
+ type: 'vmousemove',
+ cyPosition: { x: now[0], y: now[1] }
+ }) );
+ }
+ }
+
+ if (near != last) {
+ if (last) { last.trigger(new $$.Event(e, { type: 'tapdragout', cyPosition: { x: now[0], y: now[1] } })); }
+ if (near) { near.trigger(new $$.Event(e, { type: 'tapdragover', cyPosition: { x: now[0], y: now[1] } })); }
+ }
+
+ r.touchData.last = near;
+ }
+
+ // Check to cancel taphold
+ for (var i=0;i 4) {
+
+ r.touchData.singleTouchMoved = true;
+ }
+ }
+
+ if ( capture && (start == null || start.isEdge()) && cy.panningEnabled() && cy.userPanningEnabled() ) {
+ if( start ){
+ start.unactivate();
+
+ if( !r.data.bgActivePosistion ){
+ r.data.bgActivePosistion = {
+ x: now[0],
+ y: now[1]
+ };
+ }
+
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+
+ r.touchData.start = null;
+ }
+
+ cy.panBy({x: disp[0] * cy.zoom(), y: disp[1] * cy.zoom()});
+ r.swipePanning = true;
+
+ // 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.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+ } else {
+ r.redraw();
+ }
+
+ //}, 100);
+ }
+
+ if( e.touches.length < 2 ){
+ r.pinching = false;
+ r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true;
+ 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.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+ } else if (e.touches[1]) {
+
+ } else if (e.touches[0]) {
+
+ // Last touch released
+ } else if (!e.touches[0]) {
+
+ r.data.bgActivePosistion = undefined;
+ r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true;
+
+ if (start != null ) {
+
+ if( start._private.grabbed ){
+ start._private.grabbed = false;
+ start.trigger('free');
+ start._private.rscratch.inDragLayer = false;
+ }
+
+ var sEdges = start._private.edges;
+ for (var j=0;j 0 && options.stableEnergy(energy) ){
+ sys.stop();
+ return;
+ }
+
+ clearTimeout(doneTimeout);
+ doneTimeout = setTimeout(doneHandler, doneTime);
+
+ var movedNodes = [];
+
+ sys.eachNode(function(n, point){
+ var data = n.data;
+ var node = data.element;
+
+ if( node == null ){
+ return;
+ }
+
+ if( !node.locked() && !node.grabbed() ){
+ node.silentPosition({
+ x: simBB.x1 + point.x,
+ y: simBB.y1 + point.y
+ });
+
+ movedNodes.push( node );
+ }
+ });
+
+
+ var timeToDraw = (+new Date() - lastDraw) >= 16;
+ if( options.liveUpdate && movedNodes.length > 0 && timeToDraw ){
+ new $$.Collection(cy, movedNodes).rtrigger('position');
+ lastDraw = +new Date();
+ }
+
+
+ if( !ready ){
+ ready = true;
+ cy.one('layoutready', options.ready);
+ cy.trigger('layoutready');
+ }
+ }
+
+ };
+ sys.renderer = sysRenderer;
+ sys.screenSize( width, height );
+ sys.screenPadding( options.padding[0], options.padding[1], options.padding[2], options.padding[3] );
+ sys.screenStep( options.stepSize );
+
+ function calculateValueForElement(element, value){
+ if( value == null ){
+ return undefined;
+ } else if( typeof value == typeof function(){} ){
+ return value.apply(element, [element._private.data, {
+ nodes: nodes.length,
+ edges: edges.length,
+ element: element
+ }]);
+ } else {
+ return value;
+ }
+ }
+
+ // TODO we're using a hack; sys.toScreen should work :(
+ function fromScreen(pos){
+ var x = pos.x;
+ var y = pos.y;
+ var w = width;
+ var h = height;
+
+ var left = -2;
+ var right = 2;
+
+ var d = 4;
+
+ return {
+ x: x/w * d + left,
+ y: y/h * d + right
+ };
+ }
+
+ var grabHandler = function(e){
+ var pos = sys.fromScreen( this.position() );
+ var p = arbor.Point(pos.x, pos.y);
+ this.scratch().arbor.p = p;
+
+ switch( e.type ){
+ case 'grab':
+ this.scratch().arbor.fixed = true;
+ break;
+ case 'free':
+ this.scratch().arbor.fixed = false;
+ //this.scratch().arbor.tempMass = 1000;
+ break;
+ }
+ };
+ nodes.bind('grab drag free', grabHandler);
+
+ nodes.each(function(i, node){
+ if( this.isFullAutoParent() ){ return; } // they don't exist in the sim
+
+ var id = this._private.data.id;
+ var mass = calculateValueForElement(this, options.nodeMass);
+ var locked = this._private.locked;
+
+ if( node.isFullAutoParent() ){
+ return;
+ }
+
+ var pos = fromScreen({
+ x: node.position().x,
+ y: node.position().y
+ });
+
+ if( node.locked() ){
+ return;
+ }
+
+ this.scratch().arbor = sys.addNode(id, {
+ element: this,
+ mass: mass,
+ fixed: locked,
+ x: locked ? pos.x : undefined,
+ y: locked ? pos.y : undefined
+ });
+ });
+
+ edges.each(function(){
+ var src = this.source().id();
+ var tgt = this.target().id();
+ var length = calculateValueForElement(this, options.edgeLength);
+
+ this.scratch().arbor = sys.addEdge(src, tgt, {
+ length: length
+ });
+ });
+
+ function packToCenter(callback){
+ // TODO implement this for IE :(
+
+ if( options.fit ){
+ cy.fit();
+ }
+ callback();
+ }
+
+ var grabbableNodes = nodes.filter(":grabbable");
+ // disable grabbing if so set
+ if( options.ungrabifyWhileSimulating ){
+ grabbableNodes.ungrabify();
+ }
+
+ var doneHandler = function(){
+ if( window.isIE ){
+ packToCenter(function(){
+ done();
+ });
+ } else {
+ done();
+ }
+
+ function done(){
+ if( !options.liveUpdate ){
+ if( options.fit ){
+ cy.reset();
+ }
+
+ cy.nodes().rtrigger('position');
+ }
+
+ // unbind handlers
+ nodes.unbind('grab drag free', grabHandler);
+
+ // enable back grabbing if so set
+ if( options.ungrabifyWhileSimulating ){
+ grabbableNodes.grabify();
+ }
+
+ cy.one('layoutstop', options.stop);
+ cy.trigger('layoutstop');
+ }
+ };
+
+ sys.start();
+ if( options.maxSimulationTime != null && options.maxSimulationTime > 0 && options.maxSimulationTime !== Infinity ){
+ setTimeout(function(){
+ sys.stop();
+ }, options.maxSimulationTime);
+ }
+
+ };
+
+ ArborLayout.prototype.stop = function(){
+ if( this.system != null ){
+ system.stop();
+ }
+ };
+
+ $$('layout', 'arbor', ArborLayout);
+
+
+})(cytoscape);
+
+;(function($$){ 'use strict';
+
+ var defaults = {
+ fit: true, // whether to fit the viewport to the graph
+ ready: undefined, // callback on layoutready
+ stop: undefined, // callback on layoutstop
+ 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
+ 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)
+ };
+
+ 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 nodes = cy.nodes();
+ var edges = cy.edges();
+ var graph = nodes.add(edges);
+ var container = cy.container();
+
+ var width = container.clientWidth;
+ var height = container.clientHeight;
+
+ 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 = new $$.Collection( cy, rootsArray );
+ } else if( $$.is.string(options.roots) ){
+ roots = cy.$( options.roots );
+
+ } else {
+ if( options.directed ){
+ roots = nodes.roots();
+ } else {
+ var maxDegree = nodes.maxDegree( false );
+ roots = nodes.filter(function(){
+ return this.degree() === maxDegree;
+ });
+ }
+ }
+
+
+ var depths = [];
+ var foundByBfs = {};
+ var id2depth = {};
+
+ // find the depths of the nodes
+ graph.bfs(roots, function(i, depth){
+ var ele = this[0];
+
+ if( !depths[depth] ){
+ depths[depth] = [];
+ }
+
+ depths[depth].push( ele );
+ foundByBfs[ ele.id() ] = true;
+ id2depth[ ele.id() ] = depth;
+ }, options.directed);
+
+ // 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.BreadthFirstLayout = {
+ 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.BreadthFirstLayout;
+ 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.BreadthFirstLayout;
+
+ 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.BreadthFirstLayout;
+ 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.BreadthFirstLayout;
+ var intEle = info.intEle;
+ var intInfo = intEle._private.scratch.BreadthFirstLayout;
+
+ 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;
+ for( var i = 0; i < nodes.length; i++ ){
+ var w = nodes[i].outerWidth();
+ var h = nodes[i].outerHeight();
+
+ minDistance = Math.max(minDistance, w, h);
+ }
+ minDistance *= 1.75; // 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.BreadthFirstLayout.depth;
+ var neighbors = ele.neighborhood().nodes();
+ var percent = 0;
+ var samples = 0;
+
+ for( var i = 0; i < neighbors.length; i++ ){
+ var neighbor = neighbors[i];
+ var index = neighbor._private.scratch.BreadthFirstLayout.index;
+ var depth = neighbor._private.scratch.BreadthFirstLayout.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 center = {
+ x: width/2,
+ y: height/2
+ };
+ nodes.positions(function(){
+ var ele = this[0];
+ var info = ele._private.scratch.BreadthFirstLayout;
+ var depth = info.depth;
+ var index = info.index;
+ var depthSize = depths[depth].length;
+
+ var distanceX = Math.max( width / (depthSize + 1), minDistance );
+ var distanceY = Math.max( height / (depths.length + 1), minDistance );
+ var radiusStepSize = Math.min( width / 2 / depths.length, height / 2 / depths.length );
+ radiusStepSize = Math.max( radiusStepSize, minDistance );
+
+ 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
+ };
+ }
+
+ });
+
+ if( params.fit ){
+ cy.fit( options.padding );
+ }
+
+ cy.one('layoutready', params.ready);
+ cy.trigger('layoutready');
+
+ cy.one('layoutstop', params.stop);
+ cy.trigger('layoutstop');
+ };
+
+ BreadthFirstLayout.prototype.stop = function(){
+ // not a continuous layout
+ };
+
+ $$('layout', 'breadthfirst', BreadthFirstLayout);
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ var defaults = {
+ fit: true, // whether to fit the viewport to the graph
+ ready: undefined, // callback on layoutready
+ stop: undefined, // callback on layoutstop
+ rStepSize: 10, // the step size for increasing the radius if the nodes don't fit on screen
+ padding: 30, // the padding on fit
+ startAngle: 3/2 * Math.PI, // the position of the first node
+ counterclockwise: false // whether the layout should go counterclockwise (true) or clockwise (false)
+ };
+
+ 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 nodes = cy.nodes().filter(function(){
+ return !this.isFullAutoParent();
+ });
+ var container = cy.container();
+
+ var width = container.clientWidth;
+ var height = container.clientHeight;
+
+ var center = {
+ x: width/2,
+ y: height/2
+ };
+
+ var theta = options.startAngle;
+ var dTheta = 2 * Math.PI / nodes.length;
+ var maxNodeSize = 0;
+
+ for( var i = 0; i < nodes.length; i++ ){
+ var node = nodes[i];
+
+ maxNodeSize = Math.max( node.outerWidth(), node.outerHeight() );
+ }
+
+ var r = width/2 - maxNodeSize;
+
+ function distanceBetweenNodes(){
+ var t1 = 0;
+ var t2 = dTheta;
+
+ var p1 = {
+ x: center.x + r * Math.cos(t1),
+ y: center.y + r * Math.sin(t1)
+ };
+
+ var p2 = {
+ x: center.x + r * Math.cos(t2),
+ y: center.y + r * Math.sin(t2)
+ };
+
+ var dist = Math.sqrt( (p2.x - p1.x)*(p2.x - p1.x) + (p2.y - p1.y)*(p2.y - p1.y) );
+
+ return dist;
+ }
+
+ while( distanceBetweenNodes() < maxNodeSize && nodes.length >= 2 ){
+ r += options.rStepSize;
+ }
+
+
+ var i = 0;
+ nodes.positions(function(){
+ var rx = r * Math.cos( theta );
+ var ry = r * Math.sin( theta );
+ var pos = {
+ x: center.x + rx,
+ y: center.y + ry
+ };
+
+ i++;
+ theta = options.counterclockwise ? theta - dTheta : theta + dTheta;
+ return pos;
+ });
+
+ if( params.fit ){
+ cy.fit( options.padding );
+ }
+
+ cy.one('layoutready', params.ready);
+ cy.trigger('layoutready');
+
+ cy.one('layoutstop', params.stop);
+ cy.trigger('layoutstop');
+ };
+
+ CircleLayout.prototype.stop = function(){
+ // not a continuous layout
+ };
+
+ $$('layout', 'circle', CircleLayout);
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ var defaults = {
+ fit: true, // whether to fit the viewport to the graph
+ ready: undefined, // callback on layoutready
+ stop: undefined, // callback on layoutstop
+ padding: 30, // the padding on fit
+ startAngle: 3/2 * Math.PI, // the position of the first node
+ counterclockwise: false, // whether the layout should go counterclockwise (true) or clockwise (false)
+ minNodeSpacing: 10, // min spacing between outside of nodes (used for radius adjustment)
+ height: undefined, // height of layout area (overrides container height)
+ width: undefined, // width of layout area (overrides container width)
+ concentric: function(){ // returns numeric value for each node, placing higher nodes in levels towards the centre
+ return this.degree();
+ },
+ levelWidth: function(nodes){ // the variation of concentric values in each level
+ return nodes.maxDegree() / 4;
+ }
+ };
+
+ function ConcentricLayout( options ){
+ this.options = $$.util.extend({}, defaults, options);
+ }
+
+ ConcentricLayout.prototype.run = function(){
+ var params = this.options;
+ var options = params;
+
+ var cy = params.cy;
+ var nodes = cy.nodes().filter(function(){
+ return !this.isFullAutoParent();
+ });
+ var container = cy.container();
+
+ var width = options.width !== undefined ? options.width : container.clientWidth;
+ var height = options.height !== undefined ? options.height : container.clientHeight;
+
+ var center = {
+ x: width/2,
+ y: height/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.call(node);
+ nodeValues.push({
+ value: value,
+ node: node
+ });
+
+ // for style mapping
+ node._private.layoutData.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];
+
+ maxNodeSize = Math.max( maxNodeSize, node.outerWidth(), node.outerHeight() );
+ }
+
+ // 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 pos = {}; // id => position
+ var r = 0;
+ var minDist = maxNodeSize + options.minNodeSpacing; // min dist between nodes
+
+ for( var i = 0; i < levels.length; i++ ){
+ var level = levels[i];
+ var dTheta = 2 * Math.PI / level.length;
+
+ // calculate the radius
+ if( level.length > 1 ){ // 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 );
+ }
+
+ for( var j = 0; j < level.length; j++ ){
+ var val = level[j];
+ var theta = options.startAngle + (options.counterclockwise ? 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;
+ }
+
+ r += minDist;
+
+ }
+
+ // position the nodes
+ nodes.positions(function(){
+ var id = this.id();
+
+ return pos[id];
+ });
+
+ if( params.fit ){
+ cy.fit( options.padding );
+ }
+
+ cy.one('layoutready', params.ready);
+ cy.trigger('layoutready');
+
+ cy.one('layoutstop', params.stop);
+ cy.trigger('layoutstop');
+ };
+
+ ConcentricLayout.prototype.stop = function(){
+ // not a continuous layout
+ };
+
+ $$('layout', 'concentric', ConcentricLayout);
+
+})( cytoscape );
+
+/*
+ The CoSE layout was written by Gerardo Huck.
+
+ Modifications tracked on Github.
+*/
+
+;(function($$) { 'use strict';
+
+ var DEBUG;
+
+ /**
+ * @brief : default layout options
+ */
+ var defaults = {
+ // Called on `layoutready`
+ ready : function() {},
+
+ // Called on `layoutstop`
+ stop : function() {},
+
+ // Number of iterations between consecutive screen positions update (0 -> only updated on the end)
+ refresh : 0,
+
+ // Whether to fit the network view after when done
+ fit : true,
+
+ // Padding on fit
+ padding : 30,
+
+
+ // Whether to randomize node positions on the beginning
+ randomize : true,
+
+ // Whether to use the JS console to print debug messages
+ debug : false,
+
+ // Node repulsion (non overlapping) multiplier
+ nodeRepulsion : 10000,
+
+ // Node repulsion (overlapping) multiplier
+ nodeOverlap : 10,
+
+ // Ideal edge (non nested) length
+ idealEdgeLength : 10,
+
+ // Divisor to compute edge forces
+ edgeElasticity : 100,
+
+ // Nesting factor (multiplier) to compute ideal edge length for nested edges
+ nestingFactor : 5,
+
+ // Gravity force (constant)
+ gravity : 250,
+
+ // Maximum number of iterations to perform
+ numIter : 100,
+
+ // 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
+ };
+
+
+ /**
+ * @brief : constructor
+ * @arg options : object containing layout options
+ */
+ function CoseLayout(options) {
+ this.options = $$.util.extend({}, defaults, options);
+ }
+
+
+ /**
+ * @brief : runs the layout
+ */
+ CoseLayout.prototype.run = function() {
+ var options = this.options;
+ var cy = options.cy;
+
+ // Set DEBUG - Global variable
+ if (true === options.debug) {
+ DEBUG = true;
+ } else {
+ DEBUG = false;
+ }
+
+ // Get start time
+ var startTime = new Date();
+
+ // Initialize layout info
+ var layoutInfo = createLayoutInfo(cy, options);
+
+ // Show LayoutInfo contents if debugging
+ if (DEBUG) {
+ printLayoutInfo(layoutInfo);
+ }
+
+ // If required, randomize node positions
+ if (true === options.randomize) {
+ randomizePositions(layoutInfo, cy);
+
+ if (0 < options.refresh) {
+ refreshPositions(layoutInfo, cy, options);
+ }
+ }
+
+ updatePositions(layoutInfo, cy, options);
+
+ // Main loop
+ for (var i = 0; i < options.numIter; i++) {
+ // Do one step in the phisical simulation
+ step(layoutInfo, cy, options, i);
+
+ // If required, update positions
+ if (0 < options.refresh && 0 === (i % options.refresh)) {
+ refreshPositions(layoutInfo, cy, options);
+ }
+
+ // 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);
+ break;
+ }
+ }
+
+ refreshPositions(layoutInfo, cy, options);
+
+ // Fit the graph if necessary
+ if (true === options.fit) {
+ cy.fit( options.padding );
+ }
+
+ // Get end time
+ var endTime = new Date();
+
+ console.info('Layout took ' + (endTime - startTime) + ' ms');
+
+ // Layout has finished
+ cy.one('layoutstop', options.stop);
+ cy.trigger('layoutstop');
+ };
+
+
+ /**
+ * @brief : called on continuous layouts to stop them before they finish
+ */
+ CoseLayout.prototype.stop = function(){
+ var options = this.options;
+
+ cy.one('layoutstop', options.stop);
+ cy.trigger('layoutstop');
+ };
+
+
+ /**
+ * @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, options) {
+ var layoutInfo = {
+ layoutNodes : [],
+ idToIndex : {},
+ nodeSize : cy.nodes().size(),
+ graphSet : [],
+ indexToGraph : [],
+ layoutEdges : [],
+ edgeSize : cy.edges().size(),
+ temperature : options.initialTemp,
+ clientWidth : cy.container().clientWidth,
+ clientHeight : cy.container().clientHeight
+ };
+
+ // Shortcut
+ var nodes = cy.nodes();
+
+ // Iterate over all nodes, creating layout nodes
+ for (var i = 0; i < layoutInfo.nodeSize; i++) {
+ var tempNode = {};
+ tempNode.id = nodes[i].data('id');
+ tempNode.parentId = nodes[i].data('parent');
+ tempNode.children = [];
+ tempNode.positionX = nodes[i].position('x');
+ tempNode.positionY = nodes[i].position('y');
+ tempNode.offsetX = 0;
+ tempNode.offsetY = 0;
+ tempNode.height = nodes[i].height();
+ tempNode.width = nodes[i].width();
+ 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 = nodes[i]._private.style['padding-left'].pxValue;
+ tempNode.padRight = nodes[i]._private.style['padding-right'].pxValue;
+ tempNode.padTop = nodes[i]._private.style['padding-top'].pxValue;
+ tempNode.padBottom = nodes[i]._private.style['padding-bottom'].pxValue;
+
+ // 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;
+ }
+ }
+
+ // Shortcut
+ var edges = cy.edges();
+
+ // 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 = options.idealEdgeLength;
+
+ // 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 === $.inArray(tempNode.id, lcaGraph)) {
+ tempNode = layoutInfo.layoutNodes[layoutInfo.idToIndex[tempNode.parentId]];
+ depth++;
+ }
+
+ // Target depth
+ tempNode = layoutInfo.layoutNodes[targetIx];
+ while (-1 === $.inArray(tempNode.id, lcaGraph)) {
+ 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;
+
+ 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 < $.inArray(node1, graph) && -1 < $.inArray(node2, graph)) {
+ 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) {
+ 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;
+ };
+
+
+ /**
+ * @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
+ if (true || 0 === n.children.length) {
+ 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);
+
+ cy.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);
+ 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;
+ cy.one('layoutready', options.ready);
+ cy.trigger('layoutready');
+ }
+ };
+
+
+ /**
+ * @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, cy, options, step) {
+ var s = "\n\n###############################";
+ s += "\nSTEP: " + step;
+ s += "\n###############################\n";
+ logDebug(s);
+
+ // Calculate node repulsions
+ calculateNodeForces(layoutInfo, cy, options);
+ // Calculate edge forces
+ calculateEdgeForces(layoutInfo, cy, options);
+ // Calculate gravity forces
+ calculateGravityForces(layoutInfo, cy, options);
+ // Propagate forces from parent to child
+ propagateForces(layoutInfo, cy, options);
+ // Update positions based on calculated forces
+ updatePositions(layoutInfo, cy, options);
+ };
+
+
+ /**
+ * @brief : Computes the node repulsion forces
+ */
+ var calculateNodeForces = function(layoutInfo, cy, 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, cy, options);
+ }
+ }
+ }
+ };
+
+
+ /**
+ * @brief : Compute the node repulsion forces between a pair of nodes
+ */
+ var nodeRepulsion = function(node1, node2, layoutInfo, cy, options) {
+ var s = "Node repulsion. Node1: " + node1.id + " Node2: " + node2.id;
+
+ // 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
+ }
+
+ 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 = options.nodeRepulsion / distanceSqr;
+ var forceX = force * distanceX / distance;
+ var forceY = force * distanceY / distance;
+ }
+
+ // Apply force
+ node1.offsetX -= forceX;
+ node1.offsetY -= forceY;
+ node2.offsetX += forceX;
+ node2.offsetY += forceY;
+
+ s += "\nForceX: " + forceX + " ForceY: " + forceY;
+ logDebug(s);
+
+ return;
+ };
+
+
+ /**
+ * @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;
+ var W = node.width;
+ 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 : 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 : Calculates all edge forces
+ */
+ var calculateEdgeForces = function(layoutInfo, cy, 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) / options.edgeElasticity;
+
+ 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
+ source.offsetX += forceX;
+ source.offsetY += forceY;
+ 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, cy, options) {
+ 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;
+ var dx = centerX - node.positionX;
+ var dy = centerY - node.positionY;
+ var d = Math.sqrt(dx * dx + dy * dy);
+ if (d > 1.0) { // TODO: Use global variable for distance threshold
+ 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, cy, 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) {
+ 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, cy, 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) {
+ // No need to set compound 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.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;
+ };
+
+
+ /**
+ * @brief : Logs a debug message in JS console, if DEBUG is ON
+ */
+ var logDebug = function(text) {
+ if (DEBUG) {
+ console.debug(text);
+ }
+ };
+
+
+ // register the layout
+ $$('layout', 'cose', CoseLayout);
+
+})(cytoscape);
+;(function($$){ 'use strict';
+
+ var defaults = {
+ fit: true, // whether to fit the viewport to the graph
+ padding: 30, // padding used on fit
+ rows: undefined, // force num of rows in the grid
+ columns: undefined, // force num of cols in the grid
+ position: function( node ){}, // returns { row, col } for element
+ 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 nodes = cy.nodes();
+ var container = cy.container();
+
+ var width = container.clientWidth;
+ var height = container.clientHeight;
+
+ if( height === 0 || width === 0){
+ nodes.positions(function(){
+ return { x: 0, y: 0 };
+ });
+
+ } else {
+
+ // width/height * splits^2 = cells where splits is number of times to split width
+ var cells = nodes.size();
+ var splits = Math.sqrt( cells * height/width );
+ var rows = Math.round( splits );
+ var cols = Math.round( width/height * 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;
+ }
+ }
+ };
+
+ // if rows or columns were set in options, use those values
+ if( options.rows != null && options.columns != null ){
+ rows = options.rows;
+ cols = options.columns;
+ } else if( options.rows != null && options.columns == null ){
+ rows = options.rows;
+ cols = Math.ceil( cells / rows );
+ } else if( options.rows == null && options.columns != null ){
+ cols = options.columns;
+ 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 = width / cols;
+ var cellHeight = height / rows;
+
+ for( var i = 0; i < nodes.length; i++ ){
+ var node = nodes[i];
+ var w = node.outerWidth();
+ var h = node.outerHeight();
+
+ 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 );
+ }
+ }
+
+ nodes.positions(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;
+ y = rcPos.row * cellHeight + cellHeight/2;
+
+ } else { // otherwise set automatically
+
+ while( used(row, col) ){
+ moveToNextCell();
+ }
+
+ x = col * cellWidth + cellWidth/2;
+ y = row * cellHeight + cellHeight/2;
+ use( row, col );
+
+ moveToNextCell();
+ }
+
+ return { x: x, y: y };
+
+ });
+ }
+
+ if( params.fit ){
+ cy.fit( options.padding );
+ }
+
+ cy.one('layoutready', params.ready);
+ cy.trigger('layoutready');
+
+ cy.one('layoutstop', params.stop);
+ cy.trigger('layoutstop');
+ };
+
+ GridLayout.prototype.stop = function(){
+ // not a continuous layout
+ };
+
+ $$('layout', 'grid', GridLayout);
+
+})( cytoscape );
+
+;(function($$){ 'use strict';
+
+ // 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(true, {}, defaults, options);
+ }
+
+ // runs the layout
+ NullLayout.prototype.run = function(){
+ var options = this.options;
+ var cy = options.cy; // cy is automatically populated for us in the constructor
+
+ // puts all nodes at (0, 0)
+ cy.nodes().positions(function(){
+ return {
+ x: 0,
+ y: 0
+ };
+ });
+
+ // trigger layoutready when each node has had its position set at least once
+ cy.one('layoutready', options.ready);
+ cy.trigger('layoutready');
+
+ // trigger layoutstop when the layout stops (e.g. finishes)
+ cy.one('layoutstop', options.stop);
+ cy.trigger('layoutstop');
+ };
+
+ // called on continuous layouts to stop them before they finish
+ NullLayout.prototype.stop = function(){
+ var options = this.options;
+ var cy = options.cy;
+
+ cy.one('layoutstop', options.stop);
+ cy.trigger('layoutstop');
+ };
+
+ // register the layout
+ $$('layout', 'null', NullLayout);
+
+})(cytoscape);
+;(function($$){ 'use strict';
+
+ var defaults = {
+ fit: true, // whether to fit to viewport
+ ready: undefined, // callback on layoutready
+ stop: undefined, // callback on layoutstop
+ positions: undefined, // map of (node id) => (position obj)
+ 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)
+ padding: 30 // padding on fit
+ };
+
+ function PresetLayout( options ){
+ this.options = $$.util.extend(true, {}, defaults, options);
+ }
+
+ PresetLayout.prototype.run = function(){
+ var options = this.options;
+ var cy = options.cy;
+ var nodes = cy.nodes();
+
+ function getPosition(node){
+ if( options.positions == null ){
+ return null;
+ }
+
+ if( options.positions[node._private.data.id] == null ){
+ return null;
+ }
+
+ return options.positions[node._private.data.id];
+ }
+
+ nodes.positions(function(i, node){
+ var position = getPosition(node);
+
+ if( node.locked() || position == null ){
+ return false;
+ }
+
+ return position;
+ });
+
+ if( options.pan != null ){
+ cy.pan( options.pan );
+ }
+
+ if( options.zoom != null ){
+ cy.zoom( options.zoom );
+ }
+
+ cy.one('layoutready', options.ready);
+ cy.trigger('layoutready');
+
+ if( options.fit ){
+ cy.fit( options.padding );
+ }
+
+ cy.one('layoutstop', options.stop);
+ cy.trigger('layoutstop');
+ };
+
+ $$('layout', 'preset', PresetLayout);
+
+ $$('core', 'presetLayout', function(){
+ var cy = this;
+ var layout = {};
+ var elements = {};
+
+ cy.nodes().each(function(i, ele){
+ elements[ ele.data('id') ] = ele.position();
+ });
+
+ layout.positions = elements;
+ layout.name = 'preset';
+ layout.zoom = cy.zoom();
+ layout.pan = cy.pan();
+
+ return layout;
+ });
+
+})(cytoscape);
+
+;(function($$){ 'use strict';
+
+ var defaults = {
+ ready: undefined, // callback on layoutready
+ stop: undefined, // callback on layoutstop
+ fit: true, // whether to fit to viewport
+ padding: 30 // fit padding
+ };
+
+ function RandomLayout( options ){
+ this.options = $$.util.extend(true, {}, defaults, options);
+ }
+
+ RandomLayout.prototype.run = function(){
+ var options = this.options;
+ var cy = options.cy;
+ var nodes = cy.nodes();
+ var container = cy.container();
+
+ var width = container.clientWidth;
+ var height = container.clientHeight;
+
+
+ nodes.positions(function(i, element){
+
+ if( element.locked() ){
+ return false;
+ }
+
+ return {
+ x: Math.round( Math.random() * width ),
+ y: Math.round( Math.random() * height )
+ };
+ });
+
+ // layoutready should be triggered when the layout has set each node's
+ // position at least once
+ cy.one('layoutready', options.ready);
+ cy.trigger('layoutready');
+
+ if( options.fit ){
+ cy.fit( options.padding );
+ }
+
+ // layoutstop should be triggered when the layout stops running
+ cy.one('layoutstop', options.stop);
+ cy.trigger('layoutstop');
+ };
+
+ RandomLayout.prototype.stop = function(){
+ // stop the layout if it were running continuously
+ };
+
+ // register the layout
+ $$(
+ 'layout', // we're registering a layout
+ 'random', // the layout name
+ RandomLayout // the layout prototype
+ );
+
+})(cytoscape);
+
+;(function($$){ 'use strict';
+
+ var defaults = {
+ maxSimulationTime: 1000,
+ ungrabifyWhileSimulating: true,
+ fit: true,
+ random: false
+ };
+
+ function SpringyLayout( options ){
+ this.options = $$.util.extend(true, {}, defaults, options);
+ }
+
+ SpringyLayout.prototype.run = function(){
+ var self = this;
+ var options = this.options;
+
+ var cy = options.cy;
+ var nodes = cy.nodes();
+ var edges = cy.edges();
+
+ var container = cy.container();
+ var width = container.clientWidth;
+ var height = container.clientHeight;
+
+ // make a new graph
+ var graph = new Springy.Graph();
+
+ // make some nodes
+ nodes.each(function(i, node){
+ node.scratch('springy', {
+ model: graph.newNode({
+ element: node
+ })
+ });
+ });
+
+ // connect them with edges
+ edges.each(function(i, edge){
+ var fdSrc = edge.source().scratch('springy').model;
+ var fdTgt = edge.target().scratch('springy').model;
+
+ edge.scratch('springy', {
+ model: graph.newEdge(fdSrc, fdTgt, {
+ element: edge
+ })
+ });
+ });
+
+ var layout = new Springy.Layout.ForceDirected(graph, 400.0, 400.0, 0.5);
+
+ var currentBB = layout.getBoundingBox();
+ // var targetBB = {bottomleft: new Springy.Vector(-2, -2), topright: new Springy.Vector(2, 2)};
+
+ // convert to/from screen coordinates
+ var toScreen = function(p) {
+ var size = currentBB.topright.subtract(currentBB.bottomleft);
+ var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * width;
+ var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * height;
+ return new Springy.Vector(sx, sy);
+ };
+
+ var fromScreen = function(s) {
+ var size = currentBB.topright.subtract(currentBB.bottomleft);
+ var px = (s.x / width) * size.x + currentBB.bottomleft.x;
+ var py = (s.y / height) * size.y + currentBB.bottomleft.y;
+ return new Springy.Vector(px, py);
+ };
+
+ var movedNodes = cy.collection();
+
+ var numNodes = cy.nodes().size();
+ var drawnNodes = 1;
+ var fdRenderer = new Springy.Renderer(layout,
+ function clear() {
+ // code to clear screen
+ },
+ function drawEdge(edge, p1, p2) {
+ // draw an edge
+ },
+ function drawNode(node, p) {
+ var v = toScreen(p);
+ var element = node.data.element;
+
+ window.p = p;
+ window.n = node;
+
+ if( !element.locked() ){
+ element._private.position = {
+ x: v.x,
+ y: v.y
+ };
+ movedNodes = movedNodes.add(element);
+ } else {
+ setLayoutPositionForElement(element);
+ }
+
+ if( drawnNodes == numNodes ){
+ cy.one('layoutready', options.ready);
+ cy.trigger('layoutready');
+ }
+
+ drawnNodes++;
+
+
+ }
+ );
+
+ // set initial node points
+ nodes.each(function(i, ele){
+ if( !options.random ){
+ setLayoutPositionForElement(ele);
+ }
+ });
+
+ // update actual node positions every once in a while
+ setInterval(function(){
+ if( movedNodes.size() > 0 ){
+ movedNodes.rtrigger('position');
+ movedNodes = cy.collection();
+ }
+ }, 50);
+
+ // update node positions when dragging
+ nodes.bind('drag', function(){
+ setLayoutPositionForElement(this);
+ });
+
+ function setLayoutPositionForElement(element){
+ var fdId = element.scratch('springy').model.id;
+ var fdP = fdRenderer.layout.nodePoints[fdId].p;
+ var pos = element.position();
+ var positionInFd = (pos.x != null && pos.y != null) ? fromScreen(element.position()) : {
+ x: Math.random() * 4 - 2,
+ y: Math.random() * 4 - 2
+ };
+
+ fdP.x = positionInFd.x;
+ fdP.y = positionInFd.y;
+ }
+
+ var grabbableNodes = nodes.filter(":grabbable");
+
+ function start(){
+ // disable grabbing if so set
+ if( options.ungrabifyWhileSimulating ){
+ grabbableNodes.ungrabify();
+ }
+
+ fdRenderer.start();
+ }
+
+ function stop(callback){
+ graph.filterNodes(function(){
+ return false; // remove all nodes
+ });
+
+ setTimeout(function(){
+ if( options.ungrabifyWhileSimulating ){
+ grabbableNodes.grabify();
+ }
+
+ callback();
+ }, 100);
+ }
+
+ var stopSystem = self.stopSystem = function(){
+ stop(function(){
+ if( options.fit ){
+ cy.fit();
+ }
+
+ cy.one('layoutstop', options.stop);
+ cy.trigger('layoutstop');
+
+ self.stopSystem = null;
+ });
+ };
+
+ start();
+ setTimeout(function(){
+ stopSystem();
+ }, options.maxSimulationTime);
+
+ };
+
+ SpringyLayout.prototype.stop = function(){
+ if( this.stopSystem != null ){
+ this.stopSystem();
+ }
+ };
+
+ $$('layout', 'springy', SpringyLayout);
+
+
+})(cytoscape);
+
+;(function($$){ 'use strict';
+
+ function NullRenderer(options){
+ this.options = options;
+ }
+
+ NullRenderer.prototype.notify = function(){
+ // the null renderer does nothing
+ };
+
+ $$('renderer', 'null', NullRenderer);
+
+})( cytoscape );
diff --git a/ajax/libs/cytoscape/2.2.9/cytoscape.min.js b/ajax/libs/cytoscape/2.2.9/cytoscape.min.js
new file mode 100644
index 000000000..6be28cd9f
--- /dev/null
+++ b/ajax/libs/cytoscape/2.2.9/cytoscape.min.js
@@ -0,0 +1,23 @@
+/*!
+ * This file is part of cytoscape.js 2.2.9.
+ *
+ * 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 .
+ */
+var cytoscape;!function(e){"use strict";var t=cytoscape=function(){return cytoscape.init.apply(cytoscape,arguments)};t.init=function(e){return void 0===e&&(e={}),t.is.plainObject(e)?new t.Core(e):t.is.string(e)?t.extension.apply(t.extension,arguments):void 0},t.fn={},"undefined"!=typeof exports&&(exports=module.exports=cytoscape),"undefined"!=typeof define&&define("cytoscape",function(){return cytoscape}),e&&(e.cytoscape=cytoscape)}("undefined"==typeof window?null:window),function(e,t){"use strict";e.is={string:function(e){return null!=e&&"string"==typeof e},fn:function(e){return null!=e&&"function"==typeof e},array:function(e){return null!=e&&e instanceof Array},plainObject:function(t){return null!=t&&typeof t==typeof{}&&!e.is.array(t)&&t.constructor===Object},number:function(e){return null!=e&&"number"==typeof e&&!isNaN(e)},integer:function(t){return e.is.number(t)&&Math.floor(t)===t},color:function(e){return null!=e&&"string"==typeof e&&""!==$.Color(e).toString()},bool:function(e){return null!=e&&typeof e==typeof!0},elementOrCollection:function(t){return e.is.element(t)||e.is.collection(t)},element:function(t){return t instanceof e.Element&&t._private.single},collection:function(t){return t instanceof e.Collection&&!t._private.single},core:function(t){return t instanceof e.Core},style:function(t){return t instanceof e.Style},stylesheet:function(t){return t instanceof e.Stylesheet},event:function(t){return t instanceof e.Event},emptyString:function(t){return t?e.is.string(t)&&(""===t||t.match(/^\s+$/))?!0:!1:!0},nonemptyString:function(t){return t&&e.is.string(t)&&""!==t&&!t.match(/^\s+$/)?!0:!1},domElement:function(e){return"undefined"==typeof HTMLElement?!1:e instanceof HTMLElement},touch:function(){return t&&("ontouchstart"in t||t.DocumentTouch&&document instanceof DocumentTouch)}}}(cytoscape,"undefined"==typeof window?null:window),function(e,t){"use strict";e.util={extend:function(){var t,r,n,i,a,o,s=arguments[0]||{},l=1,c=arguments.length,u=!1;for("boolean"==typeof s&&(u=s,s=arguments[1]||{},l=2),"object"==typeof s||e.is.fn(s)||(s={}),c===l&&(s=this,--l);c>l;l++)if(null!=(t=arguments[l]))for(r in t)n=s[r],i=t[r],s!==i&&(u&&i&&(e.is.plainObject(i)||(a=e.is.array(i)))?(a?(a=!1,o=n&&e.is.array(n)?n:[]):o=n&&e.is.plainObject(n)?n:{},s[r]=e.util.extend(u,o,i)):void 0!==i&&(s[r]=i));return s},throttle:function(t,r,n){var i=!0,a=!0;return n===!1?i=!1:e.is.plainObject(n)&&(i="leading"in n?n.leading:i,a="trailing"in n?n.trailing:a),n=n||{},n.leading=i,n.maxWait=r,n.trailing=a,e.util.debounce(t,r,n)},now:function(){return+new Date},debounce:function(t,r,n){var i,a,o,s,l,c,u,d=0,p=!1,h=!0;if(e.is.fn(t)){if(r=Math.max(0,r)||0,n===!0){var g=!0;h=!1}else e.is.plainObject(n)&&(g=n.leading,p="maxWait"in n&&(Math.max(r,n.maxWait)||0),h="trailing"in n?n.trailing:h);var v=function(){var n=r-(e.util.now()-s);if(0>=n){a&&clearTimeout(a);var p=u;a=c=u=void 0,p&&(d=e.util.now(),o=t.apply(l,i),c||a||(i=l=null))}else c=setTimeout(v,n)},f=function(){c&&clearTimeout(c),a=c=u=void 0,(h||p!==r)&&(d=e.util.now(),o=t.apply(l,i),c||a||(i=l=null))};return function(){if(i=arguments,s=e.util.now(),l=this,u=h&&(c||!g),p===!1)var n=g&&!c;else{a||g||(d=s);var y=p-(s-d),m=0>=y;m?(a&&(a=clearTimeout(a)),d=s,o=t.apply(l,i)):a||(a=setTimeout(f,y))}return m&&c?c=clearTimeout(c):c||r===p||(c=setTimeout(v,r)),n&&(m=!0,o=t.apply(l,i)),!m||c||a||(i=l=null),o}}},error:function(e){if(!console)throw e;if(console.error)console.error(e);else{if(!console.log)throw e;console.log(e)}},clone:function(e){var t={};for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);return t},copy:function(t){return null==t?t:e.is.array(t)?t.slice():e.is.plainObject(t)?e.util.clone(t):t},mapEmpty:function(e){var t=!0;if(null!=e)for(var r in e){t=!1;break}return t},pushMap:function(t){var r=e.util.getMap(t);null==r?e.util.setMap($.extend({},t,{value:[t.value]})):r.push(t.value)},setMap:function(t){for(var r,n=t.map,i=t.keys,a=i.length,o=0;a>o;o++){var r=i[o];e.is.plainObject(r)&&e.util.error("Tried to set map with object key"),oa;a++){var o=n[a];if(e.is.plainObject(o)&&e.util.error("Tried to get map with object key"),r=r[o],null==r)return r}return r},deleteMap:function(t){for(var r=t.map,n=t.keys,i=n.length,a=t.keepChildren,o=0;i>o;o++){var s=n[o];e.is.plainObject(s)&&e.util.error("Tried to delete map with object key");var l=o===t.keys.length-1;if(l)if(a)for(var c in r)a[c]||delete r[c];else delete r[s];else r=r[s]}},capitalize:function(t){return e.is.emptyString(t)?t:t.charAt(0).toUpperCase()+t.substring(1)},camel2dash:function(e){for(var t=[],r=0;rt&&" "===e[r];r--);return e.substring(t,r+1)},hex2tuple:function(e){if((4===e.length||7===e.length)&&"#"===e[0]){var t,r,n,i=4===e.length,a=16;return i?(t=parseInt(e[1]+e[1],a),r=parseInt(e[2]+e[2],a),n=parseInt(e[3]+e[3],a)):(t=parseInt(e[1]+e[2],a),r=parseInt(e[3]+e[4],a),n=parseInt(e[5]+e[6],a)),[t,r,n]}},hsl2tuple:function(t){function r(e,t,r){return 0>r&&(r+=1),r>1&&(r-=1),1/6>r?e+6*(t-e)*r:.5>r?t:2/3>r?e+(t-e)*(2/3-r)*6:e}var n,i,a,o,s,l,c,u,d=new RegExp("^"+e.util.regex.hsla+"$").exec(t);if(d){if(i=parseInt(d[1]),0>i?i=(360- -1*i%360)%360:i>360&&(i%=360),i/=360,a=parseFloat(d[2]),0>a||a>100)return;if(a/=100,o=parseFloat(d[3]),0>o||o>100)return;if(o/=100,s=d[4],void 0!==s&&(s=parseFloat(s),0>s||s>1))return;if(0===a)l=c=u=Math.round(255*o);else{var p=.5>o?o*(1+a):o+a-o*a,h=2*o-p;l=Math.round(255*r(h,p,i+1/3)),c=Math.round(255*r(h,p,i)),u=Math.round(255*r(h,p,i-1/3))}n=[l,c,u,s]}return n},rgb2tuple:function(t){var r,n=new RegExp("^"+e.util.regex.rgba+"$").exec(t);if(n){r=[];for(var i=[],a=1;3>=a;a++){var o=n[a];if("%"===o[o.length-1]&&(i[a]=!0),o=parseFloat(o),i[a]&&(o=o/100*255),0>o||o>255)return;r.push(Math.floor(o))}var s=i[1]||i[2]||i[3],l=i[1]&&i[2]&&i[3];if(s&&!l)return;var c=n[4];if(void 0!==c){if(c=parseFloat(c),0>c||c>1)return;r.push(c)}}return r},colorname2tuple:function(t){return e.util.colors[t.toLowerCase()]},color2tuple:function(t){return e.util.colorname2tuple(t)||e.util.hex2tuple(t)||e.util.rgb2tuple(t)||e.util.hsl2tuple(t)},tuple2hex:function(e){function t(e){var t=e.toString(16);return 1===t.length&&(t="0"+t),t}var r=e[0],n=e[1],i=e[2];return"#"+t(r)+t(n)+t(i)},colors:{transparent:[0,0,0,0],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]}},e.util.regex={},e.util.regex.number="(?:[-]?\\d*\\.\\d+|[-]?\\d+|[-]?\\d*\\.\\d+[eE]\\d+)",e.util.regex.rgba="rgb[a]?\\(("+e.util.regex.number+"[%]?)\\s*,\\s*("+e.util.regex.number+"[%]?)\\s*,\\s*("+e.util.regex.number+"[%]?)(?:\\s*,\\s*("+e.util.regex.number+"))?\\)",e.util.regex.rgbaNoBackRefs="rgb[a]?\\((?:"+e.util.regex.number+"[%]?)\\s*,\\s*(?:"+e.util.regex.number+"[%]?)\\s*,\\s*(?:"+e.util.regex.number+"[%]?)(?:\\s*,\\s*(?:"+e.util.regex.number+"))?\\)",e.util.regex.hsla="hsl[a]?\\(("+e.util.regex.number+")\\s*,\\s*("+e.util.regex.number+"[%])\\s*,\\s*("+e.util.regex.number+"[%])(?:\\s*,\\s*("+e.util.regex.number+"))?\\)",e.util.regex.hslaNoBackRefs="hsl[a]?\\((?:"+e.util.regex.number+")\\s*,\\s*(?:"+e.util.regex.number+"[%])\\s*,\\s*(?:"+e.util.regex.number+"[%])(?:\\s*,\\s*(?:"+e.util.regex.number+"))?\\)",e.util.regex.hex3="\\#[0-9a-fA-F]{3}",e.util.regex.hex6="\\#[0-9a-fA-F]{6}";var r=t?t.requestAnimationFrame||t.mozRequestAnimationFrame||t.webkitRequestAnimationFrame||t.msRequestAnimationFrame:null;r=r||function(e){e&&setTimeout(e,1e3/60)},e.util.requestAnimationFrame=function(e){r(e)}}(cytoscape,"undefined"==typeof window?null:window),function(e){"use strict";e.math={},e.math.signum=function(e){return e>0?1:0>e?-1:0},e.math.distance=function(e,t){var r=t.x-e.x,n=t.y-e.y;return Math.sqrt(r*r+n*n)},e.math.qbezierAt=function(e,t,r,n){return(1-n)*(1-n)*e+2*(1-n)*n*t+n*n*r},e.math.qbezierPtAt=function(t,r,n,i){return{x:e.math.qbezierAt(t.x,r.x,n.x,i),y:e.math.qbezierAt(t.y,r.y,n.y,i)}},e.math.roundRectangleIntersectLine=function(e,t,r,n,i,a,o){var s,l=this.getRoundRectangleRadius(i,a),c=i/2,u=a/2,d=r-c+l-o,p=n-u-o,h=r+c-l+o,g=p;if(s=this.finiteLinesIntersect(e,t,r,n,d,p,h,g,!1),s.length>0)return s;var v=r+c+o,f=n-u+l-o,y=v,m=n+u-l+o;if(s=this.finiteLinesIntersect(e,t,r,n,v,f,y,m,!1),s.length>0)return s;var b=r-c+l-o,x=n+u+o,w=r+c-l+o,_=x;if(s=this.finiteLinesIntersect(e,t,r,n,b,x,w,_,!1),s.length>0)return s;var E=r-c-o,S=n-u+l-o,P=E,k=n+u-l+o;if(s=this.finiteLinesIntersect(e,t,r,n,E,S,P,k,!1),s.length>0)return s;var C,D=r-c+l,N=n-u+l;if(C=this.intersectLineCircle(e,t,r,n,D,N,l+o),C.length>0&&C[0]<=D&&C[1]<=N)return[C[0],C[1]];var T=r+c-l,M=n-u+l;if(C=this.intersectLineCircle(e,t,r,n,T,M,l+o),C.length>0&&C[0]>=T&&C[1]<=M)return[C[0],C[1]];var I=r+c-l,z=n+u-l;if(C=this.intersectLineCircle(e,t,r,n,I,z,l+o),C.length>0&&C[0]>=I&&C[1]>=z)return[C[0],C[1]];var B=r-c+l,R=n+u-l;return C=this.intersectLineCircle(e,t,r,n,B,R,l+o),C.length>0&&C[0]<=B&&C[1]>=R?[C[0],C[1]]:[]},e.math.roundRectangleIntersectBox=function(e,t,r,n,i,a,o,s,l){var c=this.getRoundRectangleRadius(i,a),u=o-i/2-l,d=s-a/2+c-l,p=o+i/2+l,h=s+a/2-c+l,g=o-i/2+c-l,v=s-a/2-l,f=o+i/2-c+l,y=s+a/2+l,m=Math.min(e,r),b=Math.max(e,r),x=Math.min(t,n),w=Math.max(t,n);return u>b?!1:m>p?!1:v>w?!1:x>y?!1:u>=m&&b>=u&&d>=x&&w>=d?!0:p>=m&&b>=p&&d>=x&&w>=d?!0:p>=m&&b>=p&&h>=x&&w>=h?!0:u>=m&&b>=u&&h>=x&&w>=h?!0:m>=u&&p>=m&&x>=d&&h>=x?!0:b>=u&&p>=b&&x>=d&&h>=x?!0:b>=u&&p>=b&&w>=d&&h>=w?!0:m>=u&&p>=m&&w>=d&&h>=w?!0:g>=m&&b>=g&&v>=x&&w>=v?!0:f>=m&&b>=f&&v>=x&&w>=v?!0:f>=m&&b>=f&&y>=x&&w>=y?!0:g>=m&&b>=g&&y>=x&&w>=y?!0:m>=g&&f>=m&&x>=v&&y>=x?!0:b>=g&&f>=b&&x>=v&&y>=x?!0:b>=g&&f>=b&&w>=v&&y>=w?!0:m>=g&&f>=m&&w>=v&&y>=w?!0:this.boxIntersectEllipse(m,x,b,w,l,2*c,2*c,g+l,d+l)?!0:this.boxIntersectEllipse(m,x,b,w,l,2*c,2*c,f-l,d+l)?!0:this.boxIntersectEllipse(m,x,b,w,l,2*c,2*c,f-l,h-l)?!0:this.boxIntersectEllipse(m,x,b,w,l,2*c,2*c,g+l,h-l)?!0:!1},e.math.checkInBoundingCircle=function(e,t,r,n,i,a,o,s){return e=(e-o)/(i+n),t=(t-s)/(a+n),r>=e*e+t*t},e.math.checkInBoundingBox=function(e,t,r,n,i,a,o,s){for(var l=r[0],c=r[1],u=r[0],d=r[1],p=1;pu&&(u=r[2*p]),r[2*p+1]d&&(d=r[2*p+1]);return e-=o,t-=s,e/=i,t/=a,l>e?!1:e>u?!1:c>t?!1:t>d?!1:!0},e.math.boxInBezierVicinity=function(e,t,r,n,i,a,o,s,l,c,u){var d=.25*i+.5*o+.25*l,p=.25*a+.5*s+.25*c,h=Math.min(e,r)-u,g=Math.min(t,n)-u,v=Math.max(e,r)+u,f=Math.max(t,n)+u;if(i>=h&&v>=i&&a>=g&&f>=a)return 1;if(l>=h&&v>=l&&c>=g&&f>=c)return 1;if(d>=h&&v>=d&&p>=g&&f>=p)return 1;if(o>=h&&v>=o&&s>=g&&f>=s)return 1;var y=Math.min(i,d,l),m=Math.min(a,p,c),b=Math.max(i,d,l),x=Math.max(a,p,c);return y>v||h>b||m>f||g>x?0:1},e.math.checkBezierInBox=function(t,r,n,i,a,o,s,l,c,u){function d(d){var p=e.math.qbezierAt(a,s,c,d),h=e.math.qbezierAt(o,l,u,d);return p>=t&&n>=p&&h>=r&&i>=h}for(var p=0;1>=p;p+=.25)if(!d(p))return!1;return!0},e.math.checkStraightEdgeInBox=function(e,t,r,n,i,a,o,s){return i>=e&&r>=i&&o>=e&&r>=o&&a>=t&&n>=a&&s>=t&&n>=s},e.math.checkStraightEdgeCrossesBox=function(e,t,r,n,i,a,o,s,l){var c,u,d=Math.min(e,r)-l,p=Math.min(t,n)-l,h=Math.max(e,r)+l,g=Math.max(t,n)+l,v=o-i,f=i,y=s-a,m=a;if(Math.abs(v)<1e-4)return i>=d&&h>=i&&Math.min(a,s)<=p&&Math.max(a,s)>=g;var b=(d-f)/v;if(b>0&&1>=b&&(c=y*b+m,c>=p&&g>=c))return!0;var x=(h-f)/v;if(x>0&&1>=x&&(c=y*x+m,c>=p&&g>=c))return!0;var w=(p-m)/y;if(w>0&&1>=w&&(u=v*w+f,u>=d&&h>=u))return!0;var _=(g-m)/y;return _>0&&1>=_&&(u=v*_+f,u>=d&&h>=u)?!0:!1},e.math.checkBezierCrossesBox=function(e,t,r,n,i,a,o,s,l,c,u){var d=Math.min(e,r)-u,p=Math.min(t,n)-u,h=Math.max(e,r)+u,g=Math.max(t,n)+u;if(i>=d&&h>=i&&a>=p&&g>=a)return!0;if(l>=d&&h>=l&&c>=p&&g>=c)return!0;var v=i-2*o+l,f=-2*i+2*o,y=i,m=[];if(Math.abs(v)<1e-4){var b=(d-i)/f,x=(h-i)/f;m.push(b,x)}else{var w,_,E=f*f-4*v*(y-d);if(E>0){var S=Math.sqrt(E);w=(-f+S)/(2*v),_=(-f-S)/(2*v),m.push(w,_)}var P,k,C=f*f-4*v*(y-h);if(C>0){var S=Math.sqrt(C);P=(-f+S)/(2*v),k=(-f-S)/(2*v),m.push(P,k)}}m.sort(function(e,t){return e-t});var D=a-2*s+c,N=-2*a+2*s,T=a,M=[];if(Math.abs(D)<1e-4){var I=(p-a)/N,z=(g-a)/N;M.push(I,z)}else{var B,R,X=N*N-4*D*(T-p);if(X>0){var S=Math.sqrt(X);B=(-N+S)/(2*D),R=(-N-S)/(2*D),M.push(B,R)}var L,O,Y=N*N-4*D*(T-g);if(Y>0){var S=Math.sqrt(Y);L=(-N+S)/(2*D),O=(-N-S)/(2*D),M.push(L,O)}}M.sort(function(e,t){return e-t});for(var V=0;V=0&&m[V]<=1&&m[V+1]>M[A-1]&&M[A-1]<=1&&m[V+1]>=0)return!0;return!1},e.math.inLineVicinity=function(e,t,r,n,i,a,o){var s=o,l=Math.min(r,i),c=Math.max(r,i),u=Math.min(n,a),d=Math.max(n,a);return e>=l-s&&c+s>=e&&t>=u-s&&d+s>=t},e.math.inBezierVicinity=function(e,t,r,n,i,a,o,s){var l={x1:Math.min(r,o,i),x2:Math.max(r,o,i),y1:Math.min(n,s,a),y2:Math.max(n,s,a)};return el.x2||tl.y2?!1:!0},e.math.solveCubic=function(e,t,r,n,i){t/=e,r/=e,n/=e;var a,o,s,l,c,u,d,p;return o=(3*r-t*t)/9,s=-(27*n)+t*(9*r-2*t*t),s/=54,a=o*o*o+s*s,i[1]=0,d=t/3,a>0?(c=s+Math.sqrt(a),c=0>c?-Math.pow(-c,1/3):Math.pow(c,1/3),u=s-Math.sqrt(a),u=0>u?-Math.pow(-u,1/3):Math.pow(u,1/3),i[0]=-d+c+u,d+=(c+u)/2,i[4]=i[2]=-d,d=Math.sqrt(3)*(-u+c)/2,i[3]=d,void(i[5]=-d)):(i[5]=i[3]=0,0===a?(p=0>s?-Math.pow(-s,1/3):Math.pow(s,1/3),i[0]=-d+2*p,void(i[4]=i[2]=-(p+d))):(o=-o,l=o*o*o,l=Math.acos(s/Math.sqrt(l)),p=2*Math.sqrt(o),i[0]=-d+p*Math.cos(l/3),i[2]=-d+p*Math.cos((l+2*Math.PI)/3),void(i[4]=-d+p*Math.cos((l+4*Math.PI)/3))))},e.math.sqDistanceToQuadraticBezier=function(e,t,r,n,i,a,o,s){var l=1*r*r-4*r*i+2*r*o+4*i*i-4*i*o+o*o+n*n-4*n*a+2*n*s+4*a*a-4*a*s+s*s,c=9*r*i-3*r*r-3*r*o-6*i*i+3*i*o+9*n*a-3*n*n-3*n*s-6*a*a+3*a*s,u=3*r*r-6*r*i+r*o-r*e+2*i*i+2*i*e-o*e+3*n*n-6*n*a+n*s-n*t+2*a*a+2*a*t-s*t,d=1*r*i-r*r+r*e-i*e+n*a-n*n+n*t-a*t,p=[];this.solveCubic(l,c,u,d,p);for(var h=1e-7,g=[],v=0;6>v;v+=2)Math.abs(p[v+1])=0&&p[v]<=1&&g.push(p[v]);g.push(1),g.push(0);for(var f,y,m,b,x=-1,w=0;w=0?x>b&&(x=b,f=g[w]):(x=b,f=g[w]);return x},e.math.sqDistanceToFiniteLine=function(e,t,r,n,i,a){var o=[e-r,t-n],s=[i-r,a-n],l=s[0]*s[0]+s[1]*s[1],c=o[0]*o[0]+o[1]*o[1],u=o[0]*s[0]+o[1]*s[1],d=u*u/l;return 0>u?c:d>l?(e-i)*(e-i)+(t-a)*(t-a):c-d},e.math.pointInsidePolygon=function(e,t,r,n,i,a,o,s,l){var c=new Array(r.length),u=Math.asin(s[1]/Math.sqrt(s[0]*s[0]+s[1]*s[1]));s[0]<0?u+=Math.PI/2:u=-u-Math.PI/2;for(var d=Math.cos(-u),p=Math.sin(-u),h=0;h0){var v=this.expandPolygon(c,-l);g=this.joinLines(v)}else g=c;for(var f,y,m,b,x,w=0,_=0,h=0;h=e&&e>=m||e>=f&&m>=e))continue;x=(e-f)/(m-f)*(b-y)+y,x>t&&w++,t>x&&_++}return w%2===0?!1:!0},e.math.joinLines=function(e){for(var t,r,n,i,a,o,s,l,c=new Array(e.length/2),u=0;uc)return[];var u=c/l;return[(r-e)*u+e,(n-t)*u+t]},e.math.dotProduct=function(e,t){if(2!=e.length||2!=t.length)throw"dot product: arguments are not vectors";return e[0]*t[0]+e[1]*t[1]},e.math.intersectLineCircle=function(e,t,r,n,i,a,o){var s=[r-e,n-t],l=[i,a],c=[e-i,t-a],u=s[0]*s[0]+s[1]*s[1],d=2*(c[0]*s[0]+c[1]*s[1]),l=c[0]*c[0]+c[1]*c[1]-o*o,p=d*d-4*u*l;if(0>p)return[];var h=(-d+Math.sqrt(p))/(2*u),g=(-d-Math.sqrt(p))/(2*u),v=Math.min(h,g),f=Math.max(h,g),y=[];if(v>=0&&1>=v&&y.push(v),f>=0&&1>=f&&y.push(f),0===y.length)return[];var m=y[0]*s[0]+e,b=y[0]*s[1]+t;if(y.length>1){if(y[0]==y[1])return[m,b];var x=y[1]*s[0]+e,w=y[1]*s[1]+t;return[m,b,x,w]}return[m,b]},e.math.findCircleNearPoint=function(e,t,r,n,i){var a=n-e,o=i-t,s=Math.sqrt(a*a+o*o),l=a/s,c=o/s;return[e+l*r,t+c*r]},e.math.findMaxSqDistanceToOrigin=function(e){for(var t,r=1e-6,n=0;nr&&(r=t);return r},e.math.finiteLinesIntersect=function(e,t,r,n,i,a,o,s,l){var c=(o-i)*(t-a)-(s-a)*(e-i),u=(r-e)*(t-a)-(n-t)*(e-i),d=(s-a)*(r-e)-(o-i)*(n-t);if(0!==d){var p=c/d,h=u/d;return p>=0&&1>=p&&h>=0&&1>=h?[e+p*(r-e),t+p*(n-t)]:l?[e+p*(r-e),t+p*(n-t)]:[]}return 0===c||0===u?[e,r,o].sort()[1]===o?[o,s]:[e,r,i].sort()[1]===i?[i,a]:[i,o,r].sort()[1]===r?[r,n]:[]:[]},e.math.boxIntersectEllipse=function(e,t,r,n,i,a,o,s,l){if(e>r){var c=e;e=r,r=c}if(t>n){var u=t;t=n,n=u}var d=[s-a/2-i,l],p=[s+a/2+i,l],h=[s,l-o/2-i],g=[s,l+o/2+i];return rp[0]?!1:t>g[1]?!1:n=e*e+t*t?!0:1>=r*r+t*t?!0:1>=r*r+n*n?!0:1>=e*e+n*n?!0:!1)},e.math.boxIntersectPolygon=function(t,r,n,i,a,o,s,l,c,u,d){if(t>n){var p=t;t=n,n=p}if(r>i){var h=r;r=i,i=h}var g=new Array(a.length),v=Math.asin(u[1]/Math.sqrt(u[0]*u[0]+u[1]*u[1]));u[0]<0?v+=Math.PI/2:v=-v-Math.PI/2;for(var f=Math.cos(-v),y=Math.sin(-v),m=0;mx&&(x=g[2*m]),g[2*m]_&&(_=g[2*m+1]),g[2*m+1]n)return!1;if(t>x+d)return!1;if(w-d>i)return!1;if(r>_+d)return!1;var E;if(d>0){var S=e.math.expandPolygon(g,-d);E=e.math.joinLines(S)}else E=g;for(var m=0;m0)return!0;if(e.math.finiteLinesIntersect(C,D,P,k,t,i,n,i,!1).length>0)return!0;if(e.math.finiteLinesIntersect(C,D,P,k,t,r,t,i,!1).length>0)return!0;if(e.math.finiteLinesIntersect(C,D,P,k,n,r,n,i,!1).length>0)return!0}return!1},e.math.polygonIntersectLine=function(t,r,n,i,a,o,s,l){for(var c,u=[],d=new Array(n.length),p=0;p0){var g=e.math.expandPolygon(d,-l);h=e.math.joinLines(g)}else h=d;for(var v,f,y,m,p=0;pa&&(a=1e-5),[t[0]+a*n[0],t[1]+a*n[1]]},e.math.generateUnitNgonPointsFitToSquare=function(t,r){var n=e.math.generateUnitNgonPoints(t,r);return n=e.math.fitPolygonToSquare(n)},e.math.fitPolygonToSquare=function(e){for(var t,r,n=e.length/2,i=1/0,a=1/0,o=-1/0,s=-1/0,l=0;n>l;l++)t=e[2*l],r=e[2*l+1],i=Math.min(i,t),o=Math.max(o,t),a=Math.min(a,r),s=Math.max(s,r);for(var c=2/(o-i),u=2/(s-a),l=0;n>l;l++)t=e[2*l]=e[2*l]*c,r=e[2*l+1]=e[2*l+1]*u,i=Math.min(i,t),o=Math.max(o,t),a=Math.min(a,r),s=Math.max(s,r);if(-1>a)for(var l=0;n>l;l++)r=e[2*l+1]=e[2*l+1]+(-1-a);return e},e.math.generateUnitNgonPoints=function(e,t){var r=1/e*2*Math.PI,n=e%2===0?Math.PI/2+r/2:Math.PI/2;n+=t;for(var i,a,o,s=new Array(2*e),l=0;e>l;l++)i=l*r+n,a=s[2*l]=Math.cos(i),o=s[2*l+1]=Math.sin(-i);return s},e.math.getRoundRectangleRadius=function(e,t){return Math.min(e/4,t/4,8)}}(cytoscape),function(e){"use strict";e.instances=[],e.instanceCounter=0,e.lastInstanceTime=void 0,e.registerInstance=function(t,r){var n;e.is.core(t)?n=t:e.is.domElement(t)&&(r=t);var i=e.getRegistrationForInstance(t,r);if(i)return i.cy?e.util.error("Tried to register on a pre-existing registration"):(i.cy=t,i.domElement=r),i;var a,o=+new Date;e.lastInstanceTime&&e.lastInstanceTime!==o?++e.instanceCounter:e.instanceCounter=0,e.lastInstanceTime=o,a=e.instanceCounter;var s="cy-"+o+"-"+a,l={id:s,cy:n,domElement:r,readies:[]};return e.instances.push(l),e.instances[s]=l,l},e.removeRegistrationForInstance=function(t,r){var n;if(e.is.core(t)?n=t:e.is.domElement(t)&&(r=t),e.is.core(n)){var i=n._private.instanceId;delete e.instances[i],e.instances.splice(i,1)}else if(e.is.domElement(r))for(var a=0;a=0;a--){var o=e.instances[a];if(o.domElement===r)return o}}}(cytoscape),function(e){"use strict";function t(t,r,n){var i={};switch(i[r]=n,t){case"core":case"collection":e.fn[t](i)}return e.util.setMap({map:a,keys:[t,r],value:n})}function r(t,r){return e.util.getMap({map:a,keys:[t,r]})}function n(t,r,n,i,a){return e.util.setMap({map:o,keys:[t,r,n,i],value:a})}function i(t,r,n,i){return e.util.getMap({map:o,keys:[t,r,n,i]})}var a={};e.extensions=a;var o={};e.modules=o,e.extension=function(){return 2==arguments.length?r.apply(this,arguments):3==arguments.length?t.apply(this,arguments):4==arguments.length?i.apply(this,arguments):5==arguments.length?n.apply(this,arguments):void $.error("Invalid extension access syntax")}}(cytoscape),function(e,t){"use strict";e&&(e.fn.cytoscape=function(r){var n=e(this);if("get"===r){var i=t.getRegistrationForInstance(n[0]);return i.cy}if(!t.is.fn(r)){if(t.is.plainObject(r))return n.each(function(){var t=e.extend({},r,{container:e(this)[0]});cytoscape(t)});for(var a=n[0],o=[],s=[],l=1;ld;d++)i.canSet(s[d])&&(s[d]._private[i.field][r]=n);i.updateStyle&&a.updateStyle(),i.onSet(a),i.settingTriggersEvent&&a[i.triggerFnName](i.settingEvent)}}}else if(i.allowSetting&&e.is.plainObject(r)){var h,g,v=r;for(h in v){g=v[h];var u=!i.immutableKeys[h];if(u)for(var d=0,p=s.length;p>d;d++)i.canSet(s[d])&&(s[d]._private[i.field][h]=g)}i.updateStyle&&a.updateStyle(),i.onSet(a),i.settingTriggersEvent&&a[i.triggerFnName](i.settingEvent)}else if(i.allowBinding&&e.is.fn(r)){var f=r;a.bind(i.bindingEvent,f)}else if(i.allowGetting&&void 0===r){var c;return l&&(c=l._private[i.field]),c}return a}},batchData:function(t){var r={field:"data",event:"data",triggerFnName:"trigger",immutableKeys:{},updateStyle:!1},n=e.util.extend({},r,t);return function(t){var r=this,i=void 0!==r.length,a=i?r:r._private.elements;if(0===a.length)return r;for(var o=i?a[0]._private.cy:r,s=0;sc;c++){var u=s[c];if(!e.is.emptyString(u)){var d=!n.immutableKeys[u];if(d)for(var p=0,h=o.length;h>p;p++)delete o[p]._private[n.field][u]}}n.triggerEvent&&i[n.triggerFnName](n.event)}else if(void 0===r){for(var p=0,h=o.length;h>p;p++){var g=o[p]._private[n.field];for(var u in g){var v=!n.immutableKeys[u];v&&delete g[u]}}n.triggerEvent&&i[n.triggerFnName](n.event)}return i}},event:{regex:/(\w+)(\.\w+)?/,optionalTypeRegex:/(\w+)?(\.\w+)?/,falseCallback:function(){return!1}},on:function(t){var r={unbindSelfOnTrigger:!1,unbindAllBindersOnTrigger:!1};return t=e.util.extend({},r,t),function(r,n,i,a){var o=this,s=void 0!==o.length,l=s?o:[o],c=e.is.string(r),u=t;if(e.is.plainObject(n)?(a=i,i=n,n=void 0):(e.is.fn(n)||n===!1)&&(a=n,i=void 0,n=void 0),(e.is.fn(i)||i===!1)&&(a=i,i=void 0),!e.is.fn(a)&&a!==!1&&c)return o;if(c){var d={};d[r]=a,r=d}for(var p in r)if(a=r[p],a===!1&&(a=e.define.event.falseCallback),e.is.fn(a)){p=p.split(/\s+/);for(var h=0;h0:!0,I=N&&T&&M;if(I){var z=[h];if(z=z.concat(r),h.data=D.data?D.data:void 0,(D.unbindSelfOnTrigger||D.unbindAllBindersOnTrigger)&&(w.splice(C,1),C--),D.unbindAllBindersOnTrigger)for(var B=D.binders,R=0;R\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]",comparatorOp:"=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=",boolOp:"\\?|\\!|\\^",string:'"(?:\\\\"|[^"])+"|'+"'(?:\\\\'|[^'])+'",number:e.util.regex.number,meta:"degree|indegree|outdegree",separator:"\\s*,\\s*",descendant:"\\s+",child:"\\s+>\\s+",subject:"\\$"};s.variable="(?:[\\w-]|(?:\\\\"+s.metaChar+"))+",s.value=s.string+"|"+s.number,s.className=s.variable,s.id=s.variable;for(var l=function(e){return e.replace(new RegExp("\\\\("+s.metaChar+")","g"),function(e,t){return t})},c=s.comparatorOp.split("|"),u=0;u=0;break;case"$=":a=null!=new RegExp(p+"$").exec(d);break;case"^=":a=null!=new RegExp("^"+p).exec(d);break;case"=":a=u===l;break;case"!=":a=u!==l;break;case">":a=u>l;break;case">=":a=u>=l;break;case"<":a=l>u;break;case"<=":a=l>=u;break;default:a=!1}}else if(null!=s)switch(s){case"?":a=r.fieldTruthy(c);break;case"!":a=!r.fieldTruthy(c);break;case"^":a=r.fieldUndefined(c)}else a=!r.fieldUndefined(c);if(!a){n=!1;break}}return n},h=p({name:"data",fieldValue:function(e){return r._private.data[e]},fieldRef:function(e){return"element._private.data."+e},fieldUndefined:function(e){return void 0===r._private.data[e]},fieldTruthy:function(e){return r._private.data[e]?!0:!1}});if(!h)return!1;var g=p({name:"meta",fieldValue:function(e){return r[e]()},fieldRef:function(e){return"element."+e+"()"},fieldUndefined:function(e){return null==r[e]()},fieldTruthy:function(e){return r[e]()?!0:!1}});if(!g)return!1;if(null!=t.collection){var v=null!=t.collection._private.ids[r.id()];if(!v)return!1}if(null!=t.filter&&0===r.collection().filter(t.filter).size())return!1;var f=function(e,t){if(null!=e){var r=!1;t=t();for(var n=0;n "+t),null!=e.ancestor&&(t=n(e.ancestor)+" "+t),null!=e.child&&(t+=" > "+n(e.child)),null!=e.descendant&&(t+=" "+n(e.descendant)),t},i=0;i1&&i node").css({width:"auto",height:"auto",shape:"rectangle","background-opacity":.5,"padding-top":10,"padding-right":10,"padding-left":10,"padding-bottom":10}).selector("edge").css({width:1}).selector(":active").css({"overlay-color":"black","overlay-padding":10,"overlay-opacity":.25}).selector("core").css({"selection-box-color":"#ddd","selection-box-opacity":.65,"selection-box-border-color":"#aaa","selection-box-border-width":1,"panning-cursor":"grabbing","active-bg-color":"black","active-bg-opacity":.15,"active-bg-size":e.is.touch()?40:15,"outside-texture-bg-color":"#000","outside-texture-bg-opacity":.125})},e.styfn.clear=function(){this._private.newStyle=!0;for(var e=0;el.max)return null;var P={name:t,value:r,strValue:""+r+(b?b:""),units:b,bypass:n};return l.unitless||"px"!==b&&"em"!==b||(P.pxValue="px"!==b&&b?this.getEmSizeInPixels()*r:r),P}if(l.propList){var k=[],C=""+r;if("none"===C);else{for(var D=C.split(","),E=0;E0,u=l.properties,d=!i.styleCxts[s];if(c){for(var p=0;p0||o.length>0)&&this.updateTransitions(n,a,o),t.updateStyleHints(n)}t._private.newStyle=!1},e.styfn.updateStyleHints=function(t){var r=t._private,n=!1;if("nodes"===r.group)for(var i=1;i<=e.style.pieBackgroundN;i++){var a=r.style["pie-"+i+"-background-size"].value;if(a>0){n=!0;break}}r.hasPie=n;var o=r.style["text-transform"].strValue,s=r.style.content.strValue,l=r.style["font-style"].strValue,a=r.style["font-size"].pxValue+"px",c=r.style["font-family"].strValue,u=r.style["font-variant"].strValue,d=r.style["font-weight"].strValue;r.labelKey=l+"$"+a+"$"+c+"$"+u+"$"+d+"$"+s+"$"+o,r.fontKey=l+"$"+d+"$"+a+"$"+c},e.styfn.rollBackContext=function(e,t){for(var r=0;ry?y=0:y>1&&(y=1),l.color){var m=o.valueMin[0],b=o.valueMax[0],x=o.valueMin[1],w=o.valueMax[1],_=o.valueMin[2],E=o.valueMax[2],S=null==o.valueMin[3]?1:o.valueMin[3],P=null==o.valueMax[3]?1:o.valueMax[3],k=[Math.round(m+(b-m)*y),Math.round(x+(w-x)*y),Math.round(_+(E-_)*y),Math.round(S+(P-S)*y)];a={bypass:o.bypass,name:o.name,value:k,strValue:"rgba("+k[0]+", "+k[1]+", "+k[2]+", "+k[3]}}else{if(!l.number)return!1;var C=o.valueMin+(o.valueMax-o.valueMin)*y;a=this.parse(o.name,C,o.bypass,!0)}a||(a=this.parse(o.name,u.strValue,o.bypass,!0)),a.mapping=o,o=a;break;case e.style.types.data:case e.style.types.layoutData:for(var h=o.mapped===e.style.types.layoutData,g=o.field.split("."),i=h?t._private.layoutData:t._private.data,v=0;v0&&s>0){for(var u=!1,d=0;d=0;b--){for(var x=n[b],w=0;w0&&t.delay(l),t.animate({css:c},{duration:s,queue:!1,complete:function(){i.removeAllBypasses(t),t._private.transitioning=!1}})}else t._private.transitioning&&(t.stop(),this.removeAllBypasses(t),t._private.transitioning=!1)}}(cytoscape),function(e){"use strict";e.styfn.applyBypass=function(t,r,n){var i=[];if("*"===r||"**"===r){if(void 0!==n)for(var a=0;aa.length?l.substr(a.length):""}function i(){o=o.length>s.length?o.substr(s.length):""}var a,o,s,l=""+r;for(l=l.replace(/[/][*](\s|.)+?[*][/]/g,"");;){var c=l.match(/^\s*$/);if(c)break;var u=l.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/);if(!u){e.util.error("Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: "+l);break}a=u[0];var d=u[1],p=new e.Selector(d);if(p._private.invalid)e.util.error("Skipping parsing of block: Invalid selector found in string stylesheet: "+d),n();else{var h=u[2],g=!1;o=h;for(var v=[];;){var c=o.match(/^\s*$/);if(c)break;var f=o.match(/^\s*(.+?)\s*:\s*(.+?)\s*;/);if(!f){e.util.error("Skipping parsing of block: Invalid formatting of style property and value definitions found in:"+h),g=!0;break}s=f[0];var y=f[1],m=f[2],b=e.style.properties[y];if(b){var x=t.parse(y,m);x?(v.push({name:y,val:m}),i()):(e.util.error("Skipping property: Invalid property definition in: "+s),i())
+}else e.util.error("Skipping property: Invalid property name in: "+s),i()}if(g){n();break}t.selector(d);for(var w=0;wo;o++){var d=c[o],p=l[d];if(e.is.array(p))for(var h=0,g=p.length;g>h;h++){var v=p[h],f=e.util.extend({},v,{group:d});a.push(f)}}r=new e.Collection(n,a)}else{var v=t;r=new e.Element(n,v).collection()}return r},remove:function(t){if(e.is.elementOrCollection(t))t=t;else if(e.is.string(t)){var r=t;t=this.$(r)}return t.remove()},load:function(n,i,a){function o(){s.one("layoutready",function(e){s.notifications(!0),s.trigger(e),s.notify({type:"load",collection:s.elements()}),s.one("load",i),s.trigger("load")}).one("layoutstop",function(){s.one("done",a),s.trigger("done")}),s.layout(s._private.options.layout)}var s=this,l=s.elements();return l.length>0&&l.remove(),s.notifications(!1),null!=n&&(e.is.plainObject(n)||e.is.array(n))&&s.add(n),t?r(o):o(),this}})}(cytoscape,"undefined"==typeof window?null:window),function(e,t){"use strict";e.fn.core({addToAnimationPool:function(e){for(var t=this,r=t._private.aniEles,n=[],i=0;i0?c.shift():null;null!=u&&(u.callTime=+new Date,o.push(u))}for(var d=[],p=0;p0&&s.notify({type:"draw",collection:r});for(var p=0;p0||l.length>0;v||(r.splice(p,1),p--)}}function i(t,r,n){var i,l=s._private.style,c=r.properties,u=r.params,d=r.callTime;if(i=0===r.duration?1:Math.min(1,(n-d)/r.duration),0>i?i=0:i>1&&(i=1),null==c.delay){var p=r.startPosition,h=c.position,g=t._private.position;if(h&&(a(p.x,h.x)&&(g.x=o(p.x,h.x,i)),a(p.y,h.y)&&(g.y=o(p.y,h.y,i))),c.css)for(var v=e.style.properties,f=0;f=1&&(r.done=!0),i}function a(t,r){return null==t||null==r?!1:e.is.number(t)&&e.is.number(r)?!0:t&&r?!0:!1}function o(t,r,n){if(0>n?n=0:n>1&&(n=1),e.is.number(t)&&e.is.number(r))return t+(r-t)*n;if(e.is.number(t[0])&&e.is.number(r[0])){var i=t,a=r,o=function(e,t){var r=t-e,i=e;return Math.round(n*r+i)},s=o(i[0],a[0]),l=o(i[1],a[1]),c=o(i[2],a[2]);return"rgb("+s+", "+l+", "+c+")"}return void 0}var s=this,l=1e3/60,c=!1,u=!0;if(t){s._private.aniEles=[];var d=e.util.requestAnimationFrame;null!=d&&u||(d=function(e){t.setTimeout(function(){e(+new Date)},l)});var p=s.container();r()}}})}(cytoscape,"undefined"==typeof window?null:window),function(e){"use strict";e.fn.core({data:e.define.data({field:"data",bindingEvent:"data",allowBinding:!0,allowSetting:!0,settingEvent:"data",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0}),removeData:e.define.removeData({field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!0}),batchData:e.define.batchData({field:"data",event:"data",triggerFnName:"trigger",immutableKeys:{id:!0,source:!0,target:!0,parent:!0},updateStyle:!0}),scratch:e.define.data({field:"scratch",allowBinding:!1,allowSetting:!0,settingTriggersEvent:!1,allowGetting:!0}),removeScratch:e.define.removeData({field:"scratch",triggerEvent:!1})})}(cytoscape),function(e){"use strict";e.fn.core({on:e.define.on(),one:e.define.on({unbindSelfOnTrigger:!0}),once:e.define.on({unbindAllBindersOnTrigger:!0}),off:e.define.off(),trigger:e.define.trigger()}),e.corefn.bind=e.corefn.on,e.corefn.unbind=e.corefn.off}(cytoscape),function(e){"use strict";e.fn.core({png:function(e){var t=this._private.renderer;return e=e||{},t.png(e)}})}(cytoscape),function(e){"use strict";e.fn.core({layout:function(e){var t=this;return this._private.layoutRunning?this:(null==e&&(e=this._private.options.layout),this.initLayout(e),t.trigger("layoutstart"),this._private.layoutRunning=!0,this.one("layoutstop",function(){t._private.layoutRunning=!1}),this._private.layout.run(),this)},initLayout:function(t){if(null==t)return void e.util.error("Layout options must be specified to run a layout");if(null==t.name)return void e.util.error("A `name` must be specified to run a layout");var r=t.name,n=e.extension("layout",r);return null==n?void e.util.error("Can not apply layout: No such layout `%s` found; did you include its JS file?",r):(this._private.layout=new n(e.util.extend({},t,{renderer:this._private.renderer,cy:this})),void(this._private.options.layout=t))}})}(cytoscape),function(e){"use strict";e.fn.core({notify:function(t){if(this._private.notificationsEnabled){var r=this.renderer(),n=this;if(e.is.element(t.collection)){var i=t.collection;t.collection=new e.Collection(n,[i])}else if(e.is.array(t.collection)){var a=t.collection;t.collection=new e.Collection(n,a)}r.notify(t)}},notifications:function(e){var t=this._private;return void 0===e?t.notificationsEnabled:void(t.notificationsEnabled=e?!0:!1)},noNotifications:function(e){this.notifications(!1),e(),this.notifications(!0)}})}(cytoscape),function(e){"use strict";e.fn.core({renderTo:function(e,t,r,n){var i=this._private.renderer;return i.renderTo(e,t,r,n),this},renderer:function(){return this._private.renderer},forceRender:function(){return this.notify({type:"draw"}),this},resize:function(){return this.notify({type:"resize"}),this},initRenderer:function(t){var r=this,n=e.extension("renderer",t.name);return null==n?void e.util.error("Can not initialise: No such renderer `%s` found; did you include its JS file?",t.name):void(this._private.renderer=new n(e.util.extend({},t,{cy:r,style:r._private.style})))},recalculateRenderedStyle:function(){var e=this.renderer();e.recalculateRenderedStyle&&e.recalculateRenderedStyle()}})}(cytoscape),function(e){"use strict";e.fn.core({collection:function(t){return e.is.string(t)?this.$(t):e.is.elementOrCollection(t)?t.collection():new e.Collection(this)},nodes:function(e){var t=this.$("node");return e?t.filter(e):t},edges:function(e){var t=this.$("edge");return e?t.filter(e):t},$:function(t){var r=new e.Collection(this,this._private.elements);return t?r.filter(t):r}}),e.corefn.elements=e.corefn.filter=e.corefn.$}(cytoscape),function(e){"use strict";e.fn.core({style:function(){return this._private.style}})}(cytoscape),function(e){"use strict";e.fn.core({autolockNodes:function(e){return void 0===e?this._private.autolockNodes:(this._private.autolockNodes=e?!0:!1,this)},autoungrabifyNodes:function(e){return void 0===e?this._private.autoungrabifyNodes:(this._private.autoungrabifyNodes=e?!0:!1,this)},panningEnabled:function(e){return void 0===e?this._private.panningEnabled:(this._private.panningEnabled=e?!0:!1,this)},userPanningEnabled:function(e){return void 0===e?this._private.userPanningEnabled:(this._private.userPanningEnabled=e?!0:!1,this)},zoomingEnabled:function(e){return void 0===e?this._private.zoomingEnabled:(this._private.zoomingEnabled=e?!0:!1,this)},userZoomingEnabled:function(e){return void 0===e?this._private.userZoomingEnabled:(this._private.userZoomingEnabled=e?!0:!1,this)},boxSelectionEnabled:function(e){return void 0===e?this._private.boxSelectionEnabled:(this._private.boxSelectionEnabled=e?!0:!1,this)},pan:function(){var t,r,n,i,a,o=arguments,s=this._private.pan;switch(o.length){case 0:return s;case 1:if(!this._private.panningEnabled)return this;if(e.is.string(o[0]))return t=o[0],s[t];e.is.plainObject(o[0])&&(n=o[0],i=n.x,a=n.y,e.is.number(i)&&(s.x=i),e.is.number(a)&&(s.y=a),this.trigger("pan"));break;case 2:if(!this._private.panningEnabled)return this;t=o[0],r=o[1],"x"!==t&&"y"!==t||!e.is.number(r)||(s[t]=r),this.trigger("pan")}return this.notify({type:"viewport"}),this},panBy:function(){var t,r,n,i,a,o=arguments,s=this._private.pan;if(!this._private.panningEnabled)return this;switch(o.length){case 1:e.is.plainObject(o[0])&&(n=o[0],i=n.x,a=n.y,e.is.number(i)&&(s.x+=i),e.is.number(a)&&(s.y+=a),this.trigger("pan"));break;case 2:t=o[0],r=o[1],"x"!==t&&"y"!==t||!e.is.number(r)||(s[t]+=r),this.trigger("pan")}return this.notify({type:"viewport"}),this},fit:function(t,r){if(e.is.number(t)&&void 0===r&&(r=t,t=void 0),!this._private.panningEnabled||!this._private.zoomingEnabled)return this;if(e.is.string(t)){var n=t;t=this.$(n)}else e.is.elementOrCollection(t)||(t=this.elements());var i,a=t.boundingBox(),o=this.style(),s=parseFloat(o.containerCss("width")),l=parseFloat(o.containerCss("height"));return r=e.is.number(r)?r:0,!isNaN(s)&&!isNaN(l)&&t.length>0&&(i=this._private.zoom=Math.min((s-2*r)/a.w,(l-2*r)/a.h),i=i>this._private.maxZoom?this._private.maxZoom:i,i=ithis._private.maxZoom?this._private.maxZoom:n,n=nr.maxZoom||!r.zoomingEnabled?o=!0:(r.zoom=l,a.push("zoom"))}if(i&&!o&&r.panningEnabled){var c=t.pan;e.is.number(c.x)&&(r.pan.x=c.x,s=!1),e.is.number(c.y)&&(r.pan.y=c.y,s=!1),s||a.push("pan")}return a.length>0&&this.trigger(a.join(" ")),this.notify({type:"viewport"}),this},boundingBox:function(e){var t=this.$(e);return t.boundingBox()},renderedBoundingBox:function(e){var t=this.$(e);return t.renderedBoundingBox()},center:function(t){if(!this._private.panningEnabled||!this._private.zoomingEnabled)return this;if(e.is.string(t)){var r=t;t=cy.elements(r)}else e.is.elementOrCollection(t)||(t=cy.elements());var n=t.boundingBox(),i=this.style(),a=parseFloat(i.containerCss("width")),o=parseFloat(i.containerCss("height")),s=this._private.zoom;return this.pan({x:(a-s*(n.x1+n.x2))/2,y:(o-s*(n.y1+n.y2))/2}),this.trigger("pan"),this.notify({type:"viewport"}),this},reset:function(){return this._private.panningEnabled&&this._private.zoomingEnabled?(this.pan({x:0,y:0}),this._private.maxZoom>1&&this._private.minZoom<1&&this.zoom(1),this.notify({type:"viewport"}),this):this}})}(cytoscape),function(e){"use strict";e.fn.collection=e.fn.eles=function(t){for(var r in t){var n=t[r];e.Collection.prototype[r]=n}};var t={prefix:{nodes:"n",edges:"e"},id:{nodes:0,edges:0},generate:function(t,r,n){var i=e.is.element(r)?r._private:r,a=i.group,o=null!=n?n:this.prefix[a]+this.id[a];if(t.getElementById(o).empty())this.id[a]++;else for(;!t.getElementById(o).empty();)o=this.prefix[a]+ ++this.id[a];return o}};e.Element=function(t,r,n){if(!(this instanceof e.Element))return new e.Element(t,r,n);var i=this;if(n=void 0===n||n?!0:!1,void 0===t||void 0===r||!e.is.core(t))return void e.util.error("An element must have a core reference and parameters set");if("nodes"!==r.group&&"edges"!==r.group)return void e.util.error("An element must be of type `nodes` or `edges`; you specified `"+r.group+"`");if(this.length=1,this[0]=this,this._private={cy:t,single:!0,data:r.data||{},layoutData:{},position:r.position||{},autoWidth:void 0,autoHeight:void 0,listeners:[],group:r.group,style:{},rstyle:{},styleCxts:[],removed:!0,selected:r.selected?!0:!1,selectable:void 0===r.selectable?!0:r.selectable?!0:!1,locked:r.locked?!0:!1,grabbed:!1,grabbable:void 0===r.grabbable?!0:r.grabbable?!0:!1,active:!1,classes:{},animation:{current:[],queue:[]},rscratch:{},scratch:{},edges:[],children:[]},r.renderedPosition){var a=r.renderedPosition,o=t.pan(),s=t.zoom();this._private.position={x:(a.x-o.x)/s,y:(a.y-o.y)/s}}if(e.is.string(r.classes))for(var l=r.classes.split(/\s+/),c=0,u=l.length;u>c;c++){var d=l[c];d&&""!==d&&(i._private.classes[d]=!0)}r.css&&t.style().applyBypass(this,r.css),(void 0===n||n)&&this.restore()},e.Collection=function(r,n){if(!(this instanceof e.Collection))return new e.Collection(r,n);if(void 0===r||!e.is.core(r))return void e.util.error("A collection must have a reference to the core");var i={},a=!1;if(n){if(n.length>0&&e.is.plainObject(n[0])&&!e.is.element(n[0])){a=!0;for(var o=[],s={},l=0,c=n.length;c>l;l++){var u=n[l];null==u.data&&(u.data={});var d=u.data;if(null==d.id)d.id=t.generate(r,u);else if(0!==r.getElementById(d.id).length||s[d.id])continue;var p=new e.Element(r,u,!1);o.push(p),s[d.id]=!0}n=o}}else n=[];this.length=0;for(var l=0,c=n.length;c>l;l++){var h=n[l];if(h){var g=h._private.data.id;i[g]||(i[g]=h,this[this.length]=h,this.length++)}}this._private={cy:r,ids:i},a&&this.restore()},e.elefn=e.elesfn=e.Element.prototype=e.Collection.prototype,e.elesfn.cy=function(){return this._private.cy},e.elesfn.element=function(){return this[0]},e.elesfn.collection=function(){return e.is.collection(this)?this:new e.Collection(this._private.cy,[this])},e.elesfn.json=function(){var t=this.element();if(null==t)return void 0;var r=t._private,n=e.util.copy({data:r.data,position:r.position,group:r.group,bypass:r.bypass,removed:r.removed,selected:r.selected,selectable:r.selectable,locked:r.locked,grabbed:r.grabbed,grabbable:r.grabbable,classes:""}),i=[];for(var a in r.classes)i.push(a);for(var o=0;od;d++){var h=n[d];h.isNode()?(s.push(h),c++):(l.push(h),u++)}o=s.concat(l);for(var d=0,p=o.length;p>d;d++){var h=o[d];if(h.removed()){var g=h._private,v=g.data;if(void 0===v.id)v.id=t.generate(a,h);else{if(e.is.emptyString(v.id)||!e.is.string(v.id)){e.util.error("Can not create element with invalid string ID `"+v.id+"`");continue}if(0!==a.getElementById(v.id).length){e.util.error("Can not create second element with ID `"+v.id+"`");continue}}var f=v.id;if(h.isEdge()){for(var y=h,m=["source","target"],b=m.length,x=!1,w=0;b>w;w++){var _=m[w],E=v[_];null==E||""===E?(e.util.error("Can not create edge `"+f+"` with unspecified "+_),x=!0):a.getElementById(E).empty()&&(e.util.error("Can not create edge `"+f+"` with nonexistant "+_+" `"+E+"`"),x=!0)}if(x)continue;var S=a.getElementById(v.source),P=a.getElementById(v.target);S._private.edges.push(y),P._private.edges.push(y),y._private.source=S,y._private.target=P}g.ids={},g.ids[f]=h,g.removed=!1,a.addToPool(h),i.push(h)}}for(var d=0;c>d;d++){var k=o[d],v=k._private.data,C=k._private.data.parent,D=null!=C;if(D){var N=a.getElementById(C);if(N.empty())delete v.parent;else{for(var T=!1,M=N;!M.empty();){if(k.same(M)){T=!0,delete v.parent;break}M=M.parent()}T||(N[0]._private.children.push(k),a._private.hasCompoundNodes=!0)}}}if(i=new e.Collection(a,i),i.length>0){var I=i.add(i.connectedNodes()).add(i.parent());I.updateStyle(r),r?i.rtrigger("add"):i.trigger("add")}return n},e.elesfn.removed=function(){var e=this[0];return e&&e._private.removed},e.elesfn.inside=function(){var e=this[0];return e&&!e._private.removed},e.elesfn.remove=function(t){function r(e){for(var t=e._private.edges,r=0;rp;p++){var g=s[p];i(g)}for(var p=0;p0&&(t&&this.cy().notify({type:"remove",collection:b}),b.trigger("remove"));for(var x={},p=0;pu||!i)&&(o=u,i=l)}return{edge:i,dist:o}};g.size()>0;){var f=g.pop(),y=f.value,m=f.id,b=i.getElementById(m);if(l[m]=y,y===Math.Infinite)break;for(var x=b.neighborhood().intersect(u),p=0;p0)for(n.unshift(r);s[a.id()];){var o=s[a.id()];n.unshift(o.edge),n.unshift(o.node),a=o.node}return new e.Collection(i,n)}}}}),e.elesfn.bfs=e.elesfn.breadthFirstSearch,e.elesfn.dfs=e.elesfn.depthFirstSearch}(cytoscape),function(e){"use strict";e.fn.eles({animated:function(){var e=this[0];return e?e._private.animation.current.length>0:void 0},clearQueue:function(){for(var e=0;e0&&new e.Collection(this._private.cy,n).updateStyle(),r.trigger("class"),r},hasClass:function(e){var t=this[0];return null!=t&&t._private.classes[e]?!0:!1},toggleClass:function(t,r){for(var n=t.split(/\s+/),i=this,a=[],o=0,s=i.length;s>o;o++)for(var l=i[o],c=0;c0&&new e.Collection(this._private.cy,a).updateStyle(),i.trigger("class"),i},removeClass:function(t){t=t.split(/\s+/);for(var r=this,n=[],i=0;i0&&new e.Collection(r._private.cy,n).updateStyle(),r.trigger("class"),r}})}(cytoscape),function(e){"use strict";e.fn.eles({allAre:function(e){return this.filter(e).length===this.length},is:function(e){return this.filter(e).length>0},same:function(e){return e=this.cy().collection(e),this.length!==e.length?!1:this.intersect(e).length===this.length},anySame:function(e){return e=this.cy().collection(e),this.intersect(e).length>0},allAreNeighbors:function(e){return e=this.cy().collection(e),this.neighborhood().intersect(e).length===e.length}})}(cytoscape),function(e){"use strict";e.fn.eles({parent:function(t){for(var r=[],n=this._private.cy,i=0;i0&&r.push(o)}return new e.Collection(n,r).filter(t)},parents:function(t){for(var r=[],n=this.parent();n.nonempty();){for(var i=0;il?l:n,i=c>i?c:i,a=a>u?u:a,o=d>o?d:o}else if(g.isEdge()&&t.includeEdges){v=!0;var w=g.source()[0]._private.position,_=g.target()[0]._private.position,E=g._private.rstyle||{};if(l=w.x,c=_.x,u=w.y,d=_.y,l>c){var S=l;l=c,c=S}if(u>d){var S=u;u=d,d=S}n=n>l?l:n,i=c>i?c:i,a=a>u?u:a,o=d>o?d:o;for(var P=E.bezierPts||[],y=g._private.style.width.pxValue,k=0;ki?C.x+y:i,a=C.y-yo?C.y+y:o}}var D=g._private.style,E=g._private.rstyle,N=D.content.strValue,T=D["font-size"],M=D["text-halign"],I=D["text-valign"],z=E.labelWidth,B=E.labelHeight,R=E.labelX,X=E.labelY;if(v&&t.includeLabels&&N&&T&&null!=B&&null!=z&&null!=R&&null!=X&&M&&I){var L,O,Y,V,A=B,F=z;if(g.isEdge())L=R-F/2,O=R+F/2,Y=X-A/2,V=X+A/2;else{switch(M.value){case"left":L=R-F,O=R;break;case"center":L=R-F/2,O=R+F/2;break;case"right":L=R,O=R+F}switch(I.value){case"top":Y=X-A,V=X;break;case"center":Y=X-A/2,V=X+A/2;break;case"bottom":Y=X,V=X+A}}n=n>L?L:n,i=O>i?O:i,a=a>Y?Y:a,o=V>o?V:o}}}return{x1:n,x2:i,y1:a,y2:o,w:i-n,h:o-a}}})}(cytoscape),function(e){"use strict";function t(e){return function(t){var r=this;if(void 0===t&&(t=!0),0!==r.length&&r.isNode()&&!r.removed()){for(var n=0,i=r[0],a=i._private.edges,o=0;oe}),maxDegree:r("degree",function(e,t){return e>t}),minIndegree:r("indegree",function(e,t){return t>e}),maxIndegree:r("indegree",function(e,t){return e>t}),minOutdegree:r("outdegree",function(e,t){return t>e}),maxOutdegree:r("outdegree",function(e,t){return e>t})}),e.fn.eles({totalDegree:function(e){for(var t=0,r=this.nodes(),n=0;nt&&(t=i+t),0>r&&(r=i+r);for(var a=t;a>=0&&r>a&&i>a;a++)n.push(this[a]);return new e.Collection(this.cy(),n)},size:function(){return this.length},eq:function(t){return this[t]||new e.Collection(this.cy())},empty:function(){return 0===this.length},nonempty:function(){return!this.empty()},sort:function(t){if(!e.is.fn(t))return this;var r=this.cy(),n=this.toArray().sort(t);return new e.Collection(r,n)},sortByZIndex:function(){return this.sort(e.Collection.zIndexSort)}}),e.Collection.zIndexSort=function(e,t){var r=function(e){if("nodes"===e._private.group)return e._private.data.parent?e.parents().size():0;if("edges"===e._private.group){var t=e._private.source,r=e._private.target,n=t._private.data.parent?t.parents().size():0,i=r._private.data.parent?r.parents().size():0;return Math.max(n,i)}return 0},n=e._private.style["z-index"].value-t._private.style["z-index"].value,i=0,a=0;return e.cy().hasCompoundNodes()&&(i=r(e),a=r(t)),i-a===0?"nodes"===e._private.group&&"edges"===t._private.group?1:"edges"===e._private.group&&"nodes"===t._private.group?-1:0===n?e._private.index-t._private.index:n:i-a}}(cytoscape),function(e){"use strict";e.fn.eles({updateStyle:function(e){var t=this._private.cy,r=t.style();e=e||void 0===e?!0:!1,r.apply(this);var n=this.updateCompoundBounds();return e?this.add(n).rtrigger("style"):this.add(n).trigger("style"),this},updateMappers:function(e){var t=this._private.cy,r=t.style();e=e||void 0===e?!0:!1,r.updateMappers(this);var n=this.updateCompoundBounds();return e?this.add(n).rtrigger("style"):this.add(n).trigger("style"),this},renderedCss:function(e){var t=this[0];if(t){var r=t.cy().style().getRenderedStyle(t);return void 0===e?r:r[e]}},css:function(t,r){var n=this.cy().style();if(e.is.plainObject(t)){var i=t;n.applyBypass(this,i);var a=this.updateCompoundBounds();this.add(a).rtrigger("style")}else if(e.is.string(t)){if(void 0===r){var o=this[0];return o?o._private.style[t].strValue:void 0}n.applyBypass(this,t,r);var a=this.updateCompoundBounds();this.add(a).rtrigger("style")}else if(void 0===t){var o=this[0];return o?n.getRawStyle(o):void 0}return this},removeCss:function(){for(var e=this.cy().style(),t=this,r=0;r0}(),o||n.push(a)}}return new e.Collection(this._private.cy,n).filter(t)},forwardNodes:function(t){for(var r=this,n=[],i=0;i0&&r.push(u[0]),r.push(c[0])}return new e.Collection(n,r).filter(t)},closedNeighborhood:function(e){return this.neighborhood().add(this).filter(e)},openNeighborhood:function(e){return this.neighborhood(e)}}),e.fn.eles({source:function(t){var r,n=this[0];return n&&(r=n._private.source),new e.Collection(this.cy(),r).filter(t)},target:function(t){var r,n=this[0];return n&&(r=n._private.target),new e.Collection(this.cy(),r).filter(t)},sources:t({attr:"source"}),targets:t({attr:"target"})}),e.fn.eles({edgesWith:r(),edgesTo:r({thisIs:"source"})}),e.fn.eles({connectedEdges:function(t){for(var r=[],n=this._private.cy,i=this,a=0;ad;d+=1){if(l.push(i.call(t,r[d],d,r)),a=r[d].id(),c.hasOwnProperty(a))throw"ERROR: Multiple items with the same id found: "+a;c[a]=d,u.push(a)}for(this._private={cy:t,heap:l,pointers:c,elements:u,comparator:n,extractor:i,length:s},d=Math.floor(s/2);d>=0;d-=1)o=this.heapify(d);return o}},e.Heap.idFn=function(e){return e.id()},e.Heap.minHeapComparator=function(e,t){return e>=t},e.Heap.maxHeapComparator=function(e,t){return t>=e},e.fn.heap=function(t){for(var r in t){var n=t[r];e.Heap.prototype[r]=n}},e.heapfn=e.Heap.prototype,e.heapfn.size=function(){return this._private.length},e.heapfn.getArgumentAsCollection=function(t,r){var n;if("undefined"==typeof r&&(r=this._private.cy),e.is.elementOrCollection(t))n=t;else{for(var i=[],a=[].concat.apply([],[t]),o=0;o0&&i.push(l)}n=new e.Collection(r,i)}return n},e.heapfn.isHeap=function(){var e,t,r,n,i,a=this._private.heap,o=a.length,s=this._private.comparator;for(e=0;o>e;e+=1)if(t=2*e+1,r=t+1,n=o>t?s(a[t],a[e]):!0,i=o>r?s(a[r],a[e]):!0,!n||!i)return!1;return!0},e.heapfn.heapSwap=function(e,t){var r=this._private.heap,n=this._private.pointers,i=this._private.elements,a=r[e],o=i[e],s=i[e],l=i[t];r[e]=r[t],i[e]=i[t],n[s]=t,n[l]=e,r[t]=a,i[t]=o},e.heapfn.heapify=function(e,t){var r,n,i,a,o,s,l,c=0,u=!1;for("undefined"==typeof t&&(t=!0),r=this._private.heap,c=r.length,s=this._private.comparator,n=e;!u;)t?(i=2*n+1,a=i+1,o=n,c>i&&!s(r[i],r[o])&&(o=i),c>a&&!s(r[a],r[o])&&(o=a),u=o===n,u||(this.heapSwap(o,n),n=o)):(l=Math.floor((n-1)/2),o=n,u=0>l||s(r[o],r[l]),u||(this.heapSwap(o,l),n=l))},e.heapfn.insert=function(e){var t,r,n,i,a,o=this.getArgumentAsCollection(e),s=o.length;for(a=0;s>a;a+=1){if(t=o[a],r=this._private.heap.length,n=this._private.extractor(t),i=t.id(),this._private.pointers.hasOwnProperty(i))throw"ERROR: Multiple items with the same id found: "+i;this._private.heap.push(n),this._private.elements.push(i),this._private.pointers[i]=r,this.heapify(r,!1)}this._private.length=this._private.heap.length},e.heapfn.getValueById=function(e){if(this._private.pointers.hasOwnProperty(e)){var t=this._private.pointers[e];return this._private.heap[t]}},e.heapfn.contains=function(e){for(var t=this.getArgumentAsCollection(e),r=0;r0?{value:this._private.heap[0],id:this._private.elements[0]}:void 0},e.heapfn.pop=function(){if(this._private.length>0){var e,t,r,n=this.top(),i=this._private.length-1;return this.heapSwap(0,i),e=this._private.elements[i],t=this._private.heap[i],r=e,this._private.heap.pop(),this._private.elements.pop(),this._private.length=this._private.heap.length,delete this._private.pointers[r],this.heapify(0),n}},e.heapfn.findDirectionHeapify=function(e){var t=Math.floor((e-1)/2),r=this._private.heap,n=0>t||this._private.comparator(r[e],r[t]);this.heapify(e,n)},e.heapfn.edit=function(t,r){for(var n=this.getArgumentAsCollection(t),i=0;i=o&&s>=e&&t>=l&&c>=t};n.arrow={_points:[-.15,-.3,0,0,.15,-.3],collide:function(t,r,i,a,o,s,l,c){var u=n.arrow._points;return e.math.pointInsidePolygon(t,r,u,i,a,o,s,l,c)},roughCollide:i,draw:function(e){for(var t=n.arrow._points,r=0;re.math.sqDistanceToQuadraticBezier(r,n,l.startX,l.startY,l.cp2ax,l.cp2ay,l.selfEdgeMidX,l.selfEdgeMidY)||e.math.inBezierVicinity(r,n,l.selfEdgeMidX,l.selfEdgeMidY,l.cp2cx,l.cp2cy,l.endX,l.endY,g)&&g+h>e.math.sqDistanceToQuadraticBezier(r,n,l.selfEdgeMidX,l.selfEdgeMidY,l.cp2cx,l.cp2cy,l.endX,l.endY))&&c.push(o);else if("haystack"==l.edgeType){var b=m._private.position,x=m.width(),w=m.height(),_=y._private.position,E=y.width(),S=y.height(),P=_.x+l.source.x*E,k=_.y+l.source.y*S,C=b.x+l.target.x*x,D=b.y+l.target.y*w;e.math.inLineVicinity(r,n,P,k,C,D,v)&&g+h>e.math.sqDistanceToFiniteLine(r,n,P,k,C,D)&&c.push(o)}else"straight"===l.edgeType?e.math.inLineVicinity(r,n,l.startX,l.startY,l.endX,l.endY,v)&&g+h>e.math.sqDistanceToFiniteLine(r,n,l.startX,l.startY,l.endX,l.endY)&&c.push(o):"bezier"===l.edgeType&&e.math.inBezierVicinity(r,n,l.startX,l.startY,l.cp2x,l.cp2y,l.endX,l.endY,g)&&g+h>e.math.sqDistanceToQuadraticBezier(r,n,l.startX,l.startY,l.cp2x,l.cp2y,l.endX,l.endY)&&c.push(o);if(0===c.length||c[c.length-1]!==o){var N=t.arrowShapes[u["source-arrow-shape"].value],T=t.arrowShapes[u["target-arrow-shape"].value],y=y||o._private.source,m=m||o._private.target,b=m._private.position,_=y._private.position,M=s.getArrowWidth(u.width.pxValue),I=s.getArrowHeight(u.width.pxValue),z=M,B=I;(N.roughCollide(r,n,l.arrowStartX,l.arrowStartY,M,I,[l.arrowStartX-_.x,l.arrowStartY-_.y],0)&&N.collide(r,n,l.arrowStartX,l.arrowStartY,M,I,[l.arrowStartX-_.x,l.arrowStartY-_.y],0)||T.roughCollide(r,n,l.arrowEndX,l.arrowEndY,z,B,[l.arrowEndX-b.x,l.arrowEndY-b.y],0)&&T.collide(r,n,l.arrowEndX,l.arrowEndY,z,B,[l.arrowEndX-b.x,l.arrowEndY-b.y],0))&&c.push(o)}p&&c.length>0&&c[c.length-1]===o&&(a(y),a(m))}}for(var s=this,l=this.getCachedZSortedEles(),c=[],u=t.isTouch,d=this.data.cy.zoom(),p=this.data.cy.hasCompoundNodes(),h=(u?256:32)/d,g=(u?16:0)/d,v=l.length-1;v>=0;v--){var f=l[v];if(c.length>0)break;"nodes"===f._private.group?a(l[v]):o(l[v])}return c.length>0?c[c.length-1]:null},t.prototype.getAllInBox=function(r,n,i,a){var o=this.getCachedNodes(),s=this.getCachedEdges(),l=[],c=Math.min(r,i),u=Math.max(r,i),d=Math.min(n,a),p=Math.max(n,a);r=c,i=u,n=d,a=p;for(var h,g=0;g=r&&i>=k&&C>=n&&a>=C,M=D>=r&&i>=D&&N>=n&&a>=N;T&&M&&l.push(s[g])}}return l},t.prototype.getNodeWidth=function(e){return e.width()},t.prototype.getNodeHeight=function(e){return e.height()},t.prototype.getNodeShape=function(e){var t=e._private.style.shape.value;return e.isParent()?"rectangle"===t||"roundrectangle"===t?t:"rectangle":t},t.prototype.getNodePadding=function(e){var t=e._private.style["padding-left"].pxValue,r=e._private.style["padding-right"].pxValue,n=e._private.style["padding-top"].pxValue,i=e._private.style["padding-bottom"].pxValue;return isNaN(t)&&(t=0),isNaN(r)&&(r=0),isNaN(n)&&(n=0),isNaN(i)&&(i=0),{left:t,right:r,top:n,bottom:i}},t.prototype.zOrderSort=e.Collection.zIndexSort,t.prototype.updateCachedZSortedEles=function(){this.getCachedZSortedEles(!0)
+},t.prototype.getCachedZSortedEles=function(e){var t=this.lastZOrderCachedNodes,r=this.lastZOrderCachedEdges,n=this.getCachedNodes(),i=this.getCachedEdges(),a=[];if(!e&&t&&r&&t===n&&r===i)a=this.cachedZSortedEles;else{for(var o=0;op?p+"-"+d:d+"-"+p,null==i[n]&&(i[n]=[]),i[n].push(c),o.push(n)}else s.push(c)}for(var h,g,v,f,y,m,b,x,w,_,E,S,P,k,C=0;C1&&h!==g){var D=w.intersectLine(v.x,v.y,y,m,f.x,f.y,E/2),N=_.intersectLine(f.x,f.y,b,x,v.x,v.y,S/2),T={x1:D[0],x2:N[0],y1:D[1],y2:N[1]},M=N[1]-D[1],I=N[0]-D[0],z=Math.sqrt(I*I+M*M),B={x:I,y:M},R={x:B.x/z,y:B.y/z};P={x:-R.y,y:R.x},(_.checkPoint(D[0],D[1],S/2,b,x,f.x,f.y)||w.checkPoint(N[0],N[1],E/2,y,m,v.x,v.y))&&(P={},k=!0)}for(var c,X,l=0;lmt,xt=e.math.distance({x:X.cp2x,y:X.cp2y},{x:X.endX,y:X.endY}),wt=yt>xt;if("bezier"===X.edgeType){var _t=!1;if(dt||pt||bt){_t=!0;var Et={x:X.cp2x-v.x,y:X.cp2y-v.y},St=Math.sqrt(Et.x*Et.x+Et.y*Et.y),Pt={x:Et.x/St,y:Et.y/St},kt=Math.max(y,m),Ct={x:X.cp2x+2*Pt.x*kt,y:X.cp2y+2*Pt.y*kt},Dt=w.intersectLine(v.x,v.y,y,m,Ct.x,Ct.y,E/2);bt?(X.cp2x=X.cp2x+Pt.x*(yt-mt),X.cp2y=X.cp2y+Pt.y*(yt-mt)):(X.cp2x=Dt[0]+Pt.x*yt,X.cp2y=Dt[1]+Pt.y*yt)}if(ht||gt||wt){_t=!0;var Et={x:X.cp2x-f.x,y:X.cp2y-f.y},St=Math.sqrt(Et.x*Et.x+Et.y*Et.y),Pt={x:Et.x/St,y:Et.y/St},kt=Math.max(y,m),Ct={x:X.cp2x+2*Pt.x*kt,y:X.cp2y+2*Pt.y*kt},Nt=_.intersectLine(f.x,f.y,b,x,Ct.x,Ct.y,S/2);wt?(X.cp2x=X.cp2x+Pt.x*(yt-xt),X.cp2y=X.cp2y+Pt.y*(yt-xt)):(X.cp2x=Nt[0]+Pt.x*yt,X.cp2y=Nt[1]+Pt.y*yt)}_t&&this.findEndpoints(c)}this.projectBezier(c)}}}for(var l=0;l_*S+E*P)n.straightEdgeTooShort=!0;else{var w=n;this.drawStyledEdge(t,e,[w.startX,w.startY,w.endX,w.endY],x,b),n.straightEdgeTooShort=!1}}else{var w=n;this.drawStyledEdge(t,e,[w.startX,w.startY,w.cp2x,w.cp2y,w.endX,w.endY],x,b)}"haystack"!==n.edgeType&&n.noArrowPlacement!==!0&&void 0!==n.startX&&this.drawArrowheads(e,t,r)}}}};var r=function(e,t){var r=Math.sqrt(Math.pow(e[4]-e[0],2)+Math.pow(e[5]-e[1],2));r+=Math.sqrt(Math.pow((e[4]+e[0])/2-e[2],2)+Math.pow((e[5]+e[1])/2-e[3],2));var n,i=Math.ceil(r/t);if(!(i>0))return null;n=new Array(2*i);for(var a=0;i>a;a++){var o=a/i;n[2*a]=e[0]*(1-o)*(1-o)+2*e[2]*(1-o)*o+e[4]*o*o,n[2*a+1]=e[1]*(1-o)*(1-o)+2*e[3]*(1-o)*o+e[5]*o*o}return n},n=function(e,t){var r,n=Math.sqrt(Math.pow(e[2]-e[0],2)+Math.pow(e[3]-e[1],2)),i=Math.ceil(n/t);if(!(i>0))return null;r=new Array(2*i);for(var a=[e[2]-e[0],e[3]-e[1]],o=0;i>o;o++){var s=o/i;r[2*o]=a[0]*s+e[0],r[2*o+1]=a[1]*s+e[1]}return r},i=0,a=0,o=0;t.prototype.drawStyledEdge=function(e,s,l,c,u){var d,p=+new Date,h=this.data.cy,g=h.zoom(),v=e._private.rscratch,f=s,y=!1,m=t.usePaths();if("solid"===c){if(m){for(var b=l,x=v.pathCacheKey&&b.length===v.pathCacheKey.length,w=x,_=0;w&&_o?l+=Math.PI/2:l=-(Math.PI/2+l),e.translate(i,a),e.moveTo(0,0),e.rotate(-l);var c=this.getArrowWidth(e.lineWidth);e.scale(c,c),e.beginPath(),t.arrowShapes[n].draw(e),e.closePath(),"hollow"===r?(e.lineWidth=1/c,e.stroke()):e.fill(),e.scale(1/c,1/c),e.rotate(l),e.translate(-i,-a)}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.getCachedImage=function(e,t){var r=this,n=r.imageCache=r.imageCache||{};if(n[e]&&n[e].image)return n[e].image;var i=n[e]=n[e]||{},a=i.image=new Image;return a.addEventListener("load",t),a.src=e,a},t.prototype.drawInscribedImage=function(e,r,n){var i=this,a=n._private.position.x,o=n._private.position.y,s=n._private.style,l=s["background-fit"].value,c=s["background-position-x"],u=s["background-position-y"],d=s["background-repeat"].value,p=n.width(),h=n.height(),g=n._private.rscratch,v=s["background-clip"].value,f="node"===v,y=r.width,m=r.height;if("contain"===l){var b=Math.min(p/y,h/m);y*=b,m*=b}else if("cover"===l){var b=Math.max(p/y,h/m);y*=b,m*=b}var x=a-p/2;x+="%"===c.units?(p-y)*c.value/100:c.pxValue;var w=o-h/2;if(w+="%"===u.units?(h-m)*u.value/100:u.pxValue,g.pathCache&&(x-=a,w-=o,a=0,o=0),"no-repeat"===d)f&&(e.save(),g.pathCache?e.clip(g.pathCache):(t.nodeShapes[i.getNodeShape(n)].drawPath(e,a,o,p,h),e.clip())),e.drawImage(r,0,0,r.width,r.height,x,w,y,m),f&&e.restore();else{var _=e.createPattern(r,d);e.fillStyle=_,t.nodeShapes[i.getNodeShape(n)].drawPath(e,a,o,p,h),e.translate(x,w),e.fill(),e.translate(-x,-w)}}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.drawEdgeText=function(e,t){var r=t._private.style.content.strValue;if(t.visible()&&r&&!r.match(/^\s+$/)&&(!this.hideEdgesOnViewport||!(this.dragData.didDrag||this.pinching||this.hoverData.dragging||this.data.wheel||this.swipePanning))){var n=t._private.style["font-size"].pxValue*t.cy().zoom(),i=t._private.style["min-zoomed-font-size"].pxValue;if(!(i>n)){e.textAlign="center",e.textBaseline="middle",this.recalculateEdgeLabelProjection(t);var a=t._private.rscratch;this.drawText(e,t,a.labelX,a.labelY)}}},t.prototype.drawNodeText=function(e,t){var r=t._private.style.content.strValue;if(t.visible()&&r&&!r.match(/^\s+$/)){var n=t._private.style["font-size"].pxValue*t.cy().zoom(),i=t._private.style["min-zoomed-font-size"].pxValue;if(!(i>n)){this.recalculateNodeLabelProjection(t);var a=t._private.style["text-halign"].strValue,o=t._private.style["text-valign"].strValue,s=t._private.rscratch;switch(a){case"left":e.textAlign="right";break;case"right":e.textAlign="left";break;default:e.textAlign="center"}switch(o){case"top":e.textBaseline="bottom";break;case"bottom":e.textBaseline="top";break;default:e.textBaseline="middle"}this.drawText(e,t,s.labelX,s.labelY)}}},t.prototype.getFontCache=function(e){var t;this.fontCaches=this.fontCaches||[];for(var r=0;r0&&(e.lineWidth=s,e.strokeText(o,r,n)),e.fillText(o,r,n)}}}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.drawNode=function(e,r,n){var i,a,o=this,s=r._private.style,l=r._private.rscratch;if(r.visible()){var c,u=t.usePaths(),d=e,p=!1,h=s["overlay-padding"].pxValue,g=s["overlay-opacity"].value,v=s["overlay-color"].value;if(!n||0!==g){var f=r.effectiveOpacity();if(0!==f)if(i=this.getNodeWidth(r),a=this.getNodeHeight(r),e.lineWidth=s["border-width"].pxValue,void 0!==n&&n)g>0&&(this.fillStyle(e,v[0],v[1],v[2],g),t.nodeShapes.roundrectangle.drawPath(e,r._private.position.x,r._private.position.y,i+2*h,a+2*h),e.fill());else{var y=s["background-color"].value,m=s["border-color"].value;this.fillStyle(e,y[0],y[1],y[2],s["background-opacity"].value*s.opacity.value*f),this.strokeStyle(e,m[0],m[1],m[2],s["border-opacity"].value*s.opacity.value*f),e.lineJoin="miter";var b=s["background-image"].value[2]||s["background-image"].value[1],x=s.shape.strValue,w=r._private.position;if(u){var _=x+"$"+i+"$"+a;e.translate(w.x,w.y),l.pathCacheKey===_?(c=e=l.pathCache,p=!0):(c=e=new Path2D,l.pathCacheKey=_,l.pathCache=c)}if(!p){var E=w;u&&(E={x:0,y:0}),t.nodeShapes[this.getNodeShape(r)].drawPath(e,E.x,E.y,i,a)}if(e=d,u?e.fill(c):e.fill(),void 0!==b){var S=this.getCachedImage(b,function(){o.data.canvasNeedsRedraw[t.NODE]=!0,o.data.canvasNeedsRedraw[t.DRAG]=!0,o.redraw()});S.complete&&this.drawInscribedImage(e,S,r)}var P=s["background-blacken"].value;this.hasPie(r)&&(this.drawPie(e,r),0!==P&&(u||t.nodeShapes[this.getNodeShape(r)].drawPath(e,w.x,w.y,i,a))),P>0?(this.fillStyle(e,0,0,0,P),u?e.fill(c):e.fill()):0>P&&(this.fillStyle(e,255,255,255,-P),u?e.fill(c):e.fill()),s["border-width"].pxValue>0&&(u?e.stroke(c):e.stroke()),u&&e.translate(-w.x,-w.y)}}}},t.prototype.hasPie=function(e){return e=e[0],e._private.hasPie},t.prototype.drawPie=function(t,r){r=r[0];var n=r._private.style["pie-size"],i=this.getNodeWidth(r),a=this.getNodeHeight(r),o=r._private.position.x,s=r._private.position.y,l=Math.min(i,a)/2,c=0;"%"===n.units?l=l*n.value/100:void 0!==n.pxValue&&(l=n.pxValue/2);for(var u=1;u<=e.style.pieBackgroundN;u++){var d=r._private.style["pie-"+u+"-background-size"].value,p=r._private.style["pie-"+u+"-background-color"].value,h=d/100,g=1.5*Math.PI+2*Math.PI*c,v=2*Math.PI*h,f=g+v;0===d||c>=1||c+h>1||(t.beginPath(),t.moveTo(o,s),t.arc(o,s,l,g,f),t.closePath(),this.fillStyle(t,p[0],p[1],p[2],1),t.fill(),c+=h)}}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas"),r="undefined"!=typeof InstallTrigger;t.prototype.getPixelRatio=function(){var e=this.data.contexts[0],t=e.backingStorePixelRatio||e.webkitBackingStorePixelRatio||e.mozBackingStorePixelRatio||e.msBackingStorePixelRatio||e.oBackingStorePixelRatio||e.backingStorePixelRatio||1;return r?1:(window.devicePixelRatio||1)/t},t.prototype.paintCache=function(e){for(var t,r=this.paintCaches=this.paintCaches||[],n=!0,i=0;i=o&&(r=n.bufferCanvases[t.TEXTURE_BUFFER],this.textureMult=2,r.width=s*this.textureMult,r.height=l*this.textureMult),this.canvasWidth=s,this.canvasHeight=l}},t.prototype.renderTo=function(e,t,r,n){this.redraw({forcedContext:e,forcedZoom:t,forcedPan:r,drawAllLayers:!0,forcedPxRatio:n})},t.prototype.timeToRender=function(){return this.redrawTotalTime/this.redrawCount},t.minRedrawLimit=1e3/60,t.maxRedrawLimit=1e3,t.prototype.redraw=function(r){function n(){function e(e,t){e.setTransform(1,0,0,1,0,0),i||void 0!==t&&!t||e.clearRect(0,0,c.canvasWidth,c.canvasHeight),a||(e.translate(m.x,m.y),e.scale(f,f)),l&&e.translate(l.x,l.y),s&&e.scale(s,s)}function r(e,t){for(var r=e.edges,n=e.nodes,i=0;i0&&(P.strokeStyle="rgba("+g["selection-box-border-color"].value[0]+","+g["selection-box-border-color"].value[1]+","+g["selection-box-border-color"].value[2]+","+g["selection-box-opacity"].value+")",P.strokeRect(p.select[0],p.select[1],p.select[2]-p.select[0],p.select[3]-p.select[1]))}if(p.bgActivePosistion){var v=p.cy.zoom(),L=p.bgActivePosistion;P.fillStyle="rgba("+g["active-bg-color"].value[0]+","+g["active-bg-color"].value[1]+","+g["active-bg-color"].value[2]+","+g["active-bg-opacity"].value+")",P.beginPath(),P.arc(L.x,L.y,g["active-bg-size"].pxValue/v,0,2*Math.PI),P.fill()}a||(h[t.SELECT_BOX]=!1)}var O=+new Date;void 0===c.averageRedrawTime&&(c.averageRedrawTime=O-x),void 0===c.redrawCount&&(c.redrawCount=0),c.redrawCount++,void 0===c.redrawTotalTime&&(c.redrawTotalTime=0),c.redrawTotalTime+=O-x,c.averageRedrawTime=c.averageRedrawTime/2+(O-x)/2,c.currentlyDrawing=!1}r=r||{};var i=r.forcedContext,a=r.drawAllLayers,o=r.drawOnlyNodeLayer,s=r.forcedZoom,l=r.forcedPan,c=this,u=void 0===r.forcedPxRatio?this.getPixelRatio():r.forcedPxRatio,d=c.data.cy,p=c.data,h=p.canvasNeedsRedraw;this.redrawTimeout&&clearTimeout(this.redrawTimeout),this.redrawTimeout=null,void 0===this.averageRedrawTime&&(this.averageRedrawTime=0);var g=t.minRedrawLimit,v=t.maxRedrawLimit,f=this.averageRedrawTime;f=g>f?g:f,f=v>f?f:v,void 0===this.lastDrawTime&&(this.lastDrawTime=0);var y=+new Date,m=y-this.lastDrawTime,b=m>=f;if(!i){if(!b||this.currentlyDrawing)return void(this.redrawTimeout=setTimeout(function(){c.redraw()},f));this.lastDrawTime=y,this.currentlyDrawing=!0}var x;i?n():e.util.requestAnimationFrame(n),i||c.initrender||(c.initrender=!0,d.trigger("initrender"))}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.drawPolygonPath=function(e,t,r,n,i,a){var o=n/2,s=i/2;e.beginPath&&e.beginPath(),e.moveTo(t+o*a[0],r+s*a[1]);for(var l=1;l0&&a>0&&(l.clearRect(0,0,i,a),e.bg&&(l.fillStyle=e.bg,l.rect(0,0,i,a),l.fill()),l.globalCompositeOperation="source-over",this.redraw(e.full?{forcedContext:l,drawAllLayers:!0,forcedZoom:o,forcedPan:{x:-n.x1,y:-n.y1},forcedPxRatio:1}:{forcedContext:l,drawAllLayers:!0,forcedZoom:r.zoom(),forcedPan:r.pan(),forcedPxRatio:1})),s},t.prototype.png=function(e){return this.bufferCanvasImage(e).toDataURL("image/png")}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.registerBinding=function(e,t,r,n){this.bindings.push({target:e,event:t,handler:r,useCapture:n}),e.addEventListener(t,r,n)},t.prototype.load=function(){var r=this,n=function(e,t,r){if(!t)for(var n=e.parents(),i=0;io[0]&&n.clientXo[1]&&n.clientY=t.panOrBoxSelectDelay)&&Math.abs(c[3]-c[1])+Math.abs(c[2]-c[0])<4&&s.panningEnabled()&&s.userPanningEnabled())r.hoverData.dragging=!0,c[4]=0;else{if(s.boxSelectionEnabled()&&Math.pow(c[2]-c[0],2)+Math.pow(c[3]-c[1],2)>7&&c[4]&&(clearTimeout(r.bgActiveTimeout),r.data.bgActivePosistion=void 0,r.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,r.redraw()),p&&p.isEdge()&&p.active()&&p.unactivate(),u!=d&&(d&&(d.trigger(new e.Event(n,{type:"mouseout",cyPosition:{x:l[0],y:l[1]}})),d.trigger(new e.Event(n,{type:"tapdragout",cyPosition:{x:l[0],y:l[1]}}))),u&&(u.trigger(new e.Event(n,{type:"mouseover",cyPosition:{x:l[0],y:l[1]}})),u.trigger(new e.Event(n,{type:"tapdragover",cyPosition:{x:l[0],y:l[1]}}))),r.hoverData.last=u),p&&p.isNode()&&r.nodeIsDraggable(p)){r.dragData.didDrag||(r.data.canvasNeedsRedraw[t.NODE]=!0),r.dragData.didDrag=!0,r.hoverData.draggingEles=!0;for(var y=[],m=0;m7&&l[4]||r.hoverData.dragging||(o.$(":selected").unselect(),u.length>0&&(r.data.canvasNeedsRedraw[t.NODE]=!0),r.dragData.possibleDragElements=u=[]),null!=c?c.trigger(new e.Event(n,{type:"mouseup",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(n,{type:"tapend",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(n,{type:"vmouseup",cyPosition:{x:s[0],y:s[1]}})):null==c&&o.trigger(new e.Event(n,{type:"mouseup",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(n,{type:"tapend",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(n,{type:"vmouseup",cyPosition:{x:s[0],y:s[1]}})),Math.pow(l[2]-l[0],2)+Math.pow(l[3]-l[1],2)===0&&(null!=c?c.trigger(new e.Event(n,{type:"click",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(n,{type:"tap",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(n,{type:"vclick",cyPosition:{x:s[0],y:s[1]}})):null==c&&o.trigger(new e.Event(n,{type:"click",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(n,{type:"tap",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(n,{type:"vclick",cyPosition:{x:s[0],y:s[1]}}))),c!=d||r.dragData.didDrag){if(c==d&&null!=c&&c._private.grabbed){for(var v=o.$(":grabbed"),f=0;f7&&l[4]){var x=[],w=r.getAllInBox(l[0],l[1],l[2],l[3]);r.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,w.length>0&&(r.data.canvasNeedsRedraw[t.NODE]=!0);for(var f=0;f=0&&b>=u&&p>=0&&b>=p&&d>=0&&x>=d&&h>=0&&x>=h;var D=s.pan(),N=s.zoom();if(g=_(u,d,p,h),v=[(u+p)/2,(d+h)/2],f=[(v[0]-D.x)/N,(v[1]-D.y)/N],200>g&&!o.touches[2]){var T=r.findNearestElement(E[0],E[1],!0),M=r.findNearestElement(E[2],E[3],!0);return T&&T.isNode()?(T.activate().trigger(new e.Event(o,{type:"cxttapstart",cyPosition:{x:E[0],y:E[1]}})),r.touchData.start=T):M&&M.isNode()?(M.activate().trigger(new e.Event(o,{type:"cxttapstart",cyPosition:{x:E[0],y:E[1]}})),r.touchData.start=M):(s.trigger(new e.Event(o,{type:"cxttapstart",cyPosition:{x:E[0],y:E[1]}})),r.touchData.start=null),r.touchData.start&&(r.touchData.start._private.grabbed=!1),r.touchData.cxt=!0,r.touchData.cxtDragged=!1,r.data.bgActivePosistion=void 0,void r.redraw()}}if(o.touches[2]);else if(o.touches[1]);else if(o.touches[0]){var I=r.findNearestElement(E[0],E[1],!0);if(null!=I){if(I.activate(),r.touchData.start=I,"nodes"==I._private.group&&r.nodeIsDraggable(I)){var z=r.dragData.touchDragEles=[];if(i(I,z),I.trigger("grab"),r.data.canvasNeedsRedraw[t.NODE]=!0,r.data.canvasNeedsRedraw[t.DRAG]=!0,I.selected()){z=r.dragData.touchDragEles=[];for(var B=s.$("node:selected"),R=0;R250&&(r.touchData.start?r.touchData.start.trigger(new e.Event(o,{type:"taphold",cyPosition:{x:E[0],y:E[1]}})):(r.data.cy.trigger(new e.Event(o,{type:"taphold",cyPosition:{x:E[0],y:E[1]}})),s.$(":selected").unselect()))},1e3)}},!1),r.registerBinding(window,"touchmove",e.util.throttle(function(n){var i=r.data.select,a=r.touchData.capture;a&&n.preventDefault();var o=r.data.cy,s=r.touchData.now,l=r.touchData.earlier;if(n.touches[0]){var c=r.projectIntoViewport(n.touches[0].clientX,n.touches[0].clientY);s[0]=c[0],s[1]=c[1]}if(n.touches[1]){var c=r.projectIntoViewport(n.touches[1].clientX,n.touches[1].clientY);s[2]=c[0],s[3]=c[1]}if(n.touches[2]){var c=r.projectIntoViewport(n.touches[2].clientX,n.touches[2].clientY);s[4]=c[0],s[5]=c[1]}for(var v=[],b=0;b=1.5||k>=150){r.touchData.cxt=!1,r.touchData.start&&(r.touchData.start.unactivate(),r.touchData.start=null),r.data.bgActivePosistion=void 0,r.data.canvasNeedsRedraw[t.SELECT_BOX]=!0;var D=new e.Event(n,{type:"cxttapend",cyPosition:{x:s[0],y:s[1]}});r.touchData.start?r.touchData.start.trigger(D):o.trigger(D)}}if(a&&r.touchData.cxt){var D=new e.Event(n,{type:"cxtdrag",cyPosition:{x:s[0],y:s[1]}});r.data.bgActivePosistion=void 0,r.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,r.touchData.start?r.touchData.start.trigger(D):o.trigger(D),r.touchData.start&&(r.touchData.start._private.grabbed=!1),r.touchData.cxtDragged=!0;var N=r.findNearestElement(s[0],s[1],!0);r.touchData.cxtOver&&N===r.touchData.cxtOver||(r.touchData.cxtOver&&r.touchData.cxtOver.trigger(new e.Event(n,{type:"cxtdragout",cyPosition:{x:s[0],y:s[1]}})),r.touchData.cxtOver=N,N&&N.trigger(new e.Event(n,{type:"cxtdragover",cyPosition:{x:s[0],y:s[1]}})))}else if(a&&n.touches[2]&&o.boxSelectionEnabled())r.data.bgActivePosistion=void 0,clearTimeout(this.threeFingerSelectTimeout),this.lastThreeTouch=+new Date,r.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,i&&0!==i.length&&void 0!==i[0]?(i[2]=(s[0]+s[2]+s[4])/3,i[3]=(s[1]+s[3]+s[5])/3):(i[0]=(s[0]+s[2]+s[4])/3,i[1]=(s[1]+s[3]+s[5])/3,i[2]=(s[0]+s[2]+s[4])/3+1,i[3]=(s[1]+s[3]+s[5])/3+1),i[4]=1,r.redraw();else if(a&&n.touches[1]&&o.zoomingEnabled()&&o.panningEnabled()&&o.userZoomingEnabled()&&o.userPanningEnabled()){r.data.bgActivePosistion=void 0,r.data.canvasNeedsRedraw[t.SELECT_BOX]=!0;var T=r.dragData.touchDragEles;if(T){r.data.canvasNeedsRedraw[t.DRAG]=!0;for(var M=0;M4&&(r.touchData.singleTouchMoved=!0);if(a&&(null==j||j.isEdge())&&o.panningEnabled()&&o.userPanningEnabled()){j&&(j.unactivate(),r.data.bgActivePosistion||(r.data.bgActivePosistion={x:s[0],y:s[1]}),r.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,r.touchData.start=null),o.panBy({x:v[0]*o.zoom(),y:v[1]*o.zoom()}),r.swipePanning=!0;var c=r.projectIntoViewport(n.touches[0].clientX,n.touches[0].clientY);s[0]=c[0],s[1]=c[1]}}for(var b=0;b0?r.data.canvasNeedsRedraw[t.NODE]=!0:r.redraw()}n.touches.length<2&&(r.pinching=!1,r.data.canvasNeedsRedraw[t.NODE]=!0,r.redraw());var m=!1;if(null!=i&&(i._private.active=!1,m=!0,i.unactivate()),n.touches[2])r.data.bgActivePosistion=void 0,r.data.canvasNeedsRedraw[t.SELECT_BOX]=!0;else if(n.touches[1]);else if(n.touches[0]);else if(!n.touches[0]){if(r.data.bgActivePosistion=void 0,r.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,null!=i){i._private.grabbed&&(i._private.grabbed=!1,i.trigger("free"),i._private.rscratch.inDragLayer=!1);for(var b=i._private.edges,x=0;xu;u++)d[4*u]=p[2*u],d[4*u+1]=p[2*u+1],d[4*u+2]=h[2*u],d[4*u+3]=h[2*u+1];d=e.math.fitPolygonToSquare(d),i.star5=i.star={points:d,draw:function(e,t,n,a,o){r.drawPolygon(e,t,n,a,o,i.star5.points)},drawPath:function(e,t,n,a,o){r.drawPolygonPath(e,t,n,a,o,i.star5.points)},intersectLine:function(e,t,n,a,o,s,l){return r.polygonIntersectLine(o,s,i.star5.points,e,t,n/2,a/2,l)},intersectBox:function(e,t,n,a,o,s,l,c,u){var d=i.star5.points;return r.boxIntersectPolygon(e,t,n,a,d,o,s,l,c,[0,-1],u)},checkPointRough:function(t,r,n,a,o,s,l){return e.math.checkInBoundingBox(t,r,i.star5.points,n,a,o,s,l)},checkPoint:function(t,r,n,a,o,s,l){return e.math.pointInsidePolygon(t,r,i.star5.points,s,l,a,o,[0,-1],n)}}}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend({},r,t)}var r={liveUpdate:!0,ready:void 0,stop:void 0,maxSimulationTime:4e3,fit:!0,padding:[50,50,50,50],simulationBounds:void 0,ungrabifyWhileSimulating:!0,repulsion:void 0,stiffness:void 0,friction:void 0,gravity:!0,fps:void 0,precision:void 0,nodeMass:void 0,edgeLength:void 0,stepSize:1,stableEnergy:function(e){var t=e;return t.max<=.5||t.mean<=.3}};t.prototype.run=function(){function t(e,t){return null==t?void 0:"function"==typeof t?t.apply(e,[e._private.data,{nodes:o.length,edges:s.length,element:e}]):t}function r(e){var t=e.x,r=e.y,n=c,i=u,a=-2,o=2,s=4;return{x:t/n*s+a,y:r/i*s+o}}function n(e){i.fit&&a.fit(),e()}var i=this.options,a=i.cy,o=a.nodes(),s=a.edges(),l=a.container(),c=l.clientWidth,u=l.clientHeight,d=i.simulationBounds;i.simulationBounds?(c=d[2]-d[0],u=d[3]-d[1]):i.simulationBounds=[0,0,c,u];var p=i.simulationBounds;if(p.x1=p[0],p.y1=p[1],p.x2=p[2],p.y2=p[3],a.nodes().size()<=1)return i.fit&&a.reset(),a.nodes().position({x:Math.round((p.x1+p.x2)/2),y:Math.round((p.y1+p.y2)/2)}),a.one("layoutready",i.ready),a.trigger("layoutready"),a.one("layoutstop",i.stop),void a.trigger("layoutstop");var h=this.system=arbor.ParticleSystem(i.repulsion,i.stiffness,i.friction,i.gravity,i.fps,i.dt,i.precision);this.system=h,i.liveUpdate&&i.fit&&a.reset();var g,v=250,f=!1,y=+new Date,m={init:function(){},redraw:function(){var t=h.energy();if(null!=i.stableEnergy&&null!=t&&t.n>0&&i.stableEnergy(t))return void h.stop();clearTimeout(g),g=setTimeout(w,v);var r=[];h.eachNode(function(e,t){var n=e.data,i=n.element;null!=i&&(i.locked()||i.grabbed()||(i.silentPosition({x:p.x1+t.x,y:p.y1+t.y}),r.push(i)))});var n=+new Date-y>=16;i.liveUpdate&&r.length>0&&n&&(new e.Collection(a,r).rtrigger("position"),y=+new Date),f||(f=!0,a.one("layoutready",i.ready),a.trigger("layoutready"))}};h.renderer=m,h.screenSize(c,u),h.screenPadding(i.padding[0],i.padding[1],i.padding[2],i.padding[3]),h.screenStep(i.stepSize);var b=function(e){var t=h.fromScreen(this.position()),r=arbor.Point(t.x,t.y);switch(this.scratch().arbor.p=r,e.type){case"grab":this.scratch().arbor.fixed=!0;
+break;case"free":this.scratch().arbor.fixed=!1}};o.bind("grab drag free",b),o.each(function(e,n){if(!this.isFullAutoParent()){var a=this._private.data.id,o=t(this,i.nodeMass),s=this._private.locked;if(!n.isFullAutoParent()){var l=r({x:n.position().x,y:n.position().y});n.locked()||(this.scratch().arbor=h.addNode(a,{element:this,mass:o,fixed:s,x:s?l.x:void 0,y:s?l.y:void 0}))}}}),s.each(function(){var e=this.source().id(),r=this.target().id(),n=t(this,i.edgeLength);this.scratch().arbor=h.addEdge(e,r,{length:n})});var x=o.filter(":grabbable");i.ungrabifyWhileSimulating&&x.ungrabify();var w=function(){function e(){i.liveUpdate||(i.fit&&a.reset(),a.nodes().rtrigger("position")),o.unbind("grab drag free",b),i.ungrabifyWhileSimulating&&x.grabify(),a.one("layoutstop",i.stop),a.trigger("layoutstop")}window.isIE?n(function(){e()}):e()};h.start(),null!=i.maxSimulationTime&&i.maxSimulationTime>0&&1/0!==i.maxSimulationTime&&setTimeout(function(){h.stop()},i.maxSimulationTime)},t.prototype.stop=function(){null!=this.system&&system.stop()},e("layout","arbor",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend({},r,t)}var r={fit:!0,ready:void 0,stop:void 0,directed:!1,padding:30,circle:!1,roots:void 0,maximalAdjustments:0};t.prototype.run=function(){var t,r=this.options,n=r,i=r.cy,a=i.nodes(),o=i.edges(),s=a.add(o),l=i.container(),c=l.clientWidth,u=l.clientHeight;if(e.is.elementOrCollection(n.roots))t=n.roots;else if(e.is.array(n.roots)){for(var d=[],p=0;pw;){for(var _=b.shift(),E=_.neighborhood().nodes(),S=!1,p=0;pp;p++)for(var P=f[p],M=P.length,I=0;M>I;I++){var g=P[I],z=g._private.scratch.BreadthFirstLayout,B=C(g);B&&(z.intEle=B,T.push(g))}for(var p=0;pf.length-1;)f.push([]);f[X].push(g),z.depth=X,z.index=f[X].length-1}k()}for(var L=0,p=0;pl||0===t)&&(n+=s/c,i++)}return i=Math.max(1,i),n/=i,0===i&&(n=void 0),V[e.id()]=n,n},F=function(e,t){var r=A(e),n=A(t);return r-n},q=0;3>q;q++){for(var p=0;p0&&f[0].length<=3?l/2:0),p=2*Math.PI/f[r].length*i;return 0===r&&1===f[0].length&&(d=1),{x:j.x+d*Math.cos(p),y:j.y+d*Math.sin(p)}}return{x:j.x+(i+1-(a+1)/2)*o,y:(r+1)*s}}),r.fit&&i.fit(n.padding),i.one("layoutready",r.ready),i.trigger("layoutready"),i.one("layoutstop",r.stop),i.trigger("layoutstop")},t.prototype.stop=function(){},e("layout","breadthfirst",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend({},r,t)}var r={fit:!0,ready:void 0,stop:void 0,rStepSize:10,padding:30,startAngle:1.5*Math.PI,counterclockwise:!1};t.prototype.run=function(){function e(){var e=0,t=u,r={x:l.x+g*Math.cos(e),y:l.y+g*Math.sin(e)},n={x:l.x+g*Math.cos(t),y:l.y+g*Math.sin(t)},i=Math.sqrt((n.x-r.x)*(n.x-r.x)+(n.y-r.y)*(n.y-r.y));return i}for(var t=this.options,r=t,n=t.cy,i=n.nodes().filter(function(){return!this.isFullAutoParent()}),a=n.container(),o=a.clientWidth,s=a.clientHeight,l={x:o/2,y:s/2},c=r.startAngle,u=2*Math.PI/i.length,d=0,p=0;p=2;)g+=r.rStepSize;var p=0;i.positions(function(){var e=g*Math.cos(c),t=g*Math.sin(c),n={x:l.x+e,y:l.y+t};return p++,c=r.counterclockwise?c-u:c+u,n}),t.fit&&n.fit(r.padding),n.one("layoutready",t.ready),n.trigger("layoutready"),n.one("layoutstop",t.stop),n.trigger("layoutstop")},t.prototype.stop=function(){},e("layout","circle",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend({},r,t)}var r={fit:!0,ready:void 0,stop:void 0,padding:30,startAngle:1.5*Math.PI,counterclockwise:!1,minNodeSpacing:10,height:void 0,width:void 0,concentric:function(){return this.degree()},levelWidth:function(e){return e.maxDegree()/4}};t.prototype.run=function(){for(var e=this.options,t=e,r=e.cy,n=r.nodes().filter(function(){return!this.isFullAutoParent()}),i=r.container(),a=void 0!==t.width?t.width:i.clientWidth,o=void 0!==t.height?t.height:i.clientHeight,s={x:a/2,y:o/2},l=[],c=t.startAngle,u=0,d=0;d0){var m=Math.abs(f[0].value-y.value);m>=g&&(f=[],v.push(f))}f.push(y)}for(var b={},x=0,w=u+t.minNodeSpacing,d=0;d1){var S=Math.cos(E)-Math.cos(0),P=Math.sin(E)-Math.sin(0),k=Math.sqrt(w*w/(S*S+P*P));x=Math.max(k,x)}for(var C=0;C<_.length;C++){var y=_[C],c=t.startAngle+(t.counterclockwise?1:-1)*E*C,D={x:s.x+x*Math.cos(c),y:s.y+x*Math.sin(c)};b[y.node.id()]=D}x+=w}n.positions(function(){var e=this.id();return b[e]}),e.fit&&r.fit(t.padding),r.one("layoutready",e.ready),r.trigger("layoutready"),r.one("layoutstop",e.stop),r.trigger("layoutstop")},t.prototype.stop=function(){},e("layout","concentric",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend({},n,t)}var r,n={ready:function(){},stop:function(){},refresh:0,fit:!0,padding:30,randomize:!0,debug:!1,nodeRepulsion:1e4,nodeOverlap:10,idealEdgeLength:10,edgeElasticity:100,nestingFactor:5,gravity:250,numIter:100,initialTemp:200,coolingFactor:.95,minTemp:1};t.prototype.run=function(){var e=this.options,t=e.cy;r=!0===e.debug?!0:!1;var n=new Date,a=i(t,e);r&&s(a),!0===e.randomize&&(l(a,t),0=l;){var h=s[l++],g=r.idToIndex[h],v=r.layoutNodes[g],f=v.children;if(f.length>0){r.graphSet.push(f);for(var i=0;in.count?0:n.graph},o=function(e,t,r,n){var i=n.graphSet[r];if(-1<$.inArray(e,i)&&-1<$.inArray(t,i))return{count:2,graph:r};for(var a=0,s=0;ss;s++)for(var l=e.layoutNodes[e.idToIndex[a[s]]],c=s+1;o>c;c++){var u=e.layoutNodes[e.idToIndex[a[c]]];p(l,u,e,t,r)}}},p=function(e,t,r,n,i){var a="Node repulsion. Node1: "+e.id+" Node2: "+t.id,o=t.positionX-e.positionX,s=t.positionY-e.positionY;if(a+="\ndirectionX: "+o+", directionY: "+s,0===o&&0===s)return void(a+="\nNodes have the same position.");var l=g(e,t,o,s);if(l>0){a+="\nNodes DO overlap.",a+="\nOverlap: "+l;var c=i.nodeOverlap*l,u=Math.sqrt(o*o+s*s);a+="\nDistance: "+u;var d=c*o/u,p=c*s/u}else{a+="\nNodes do NOT overlap.";var v=h(e,o,s),f=h(t,-1*o,-1*s),y=f.x-v.x,m=f.y-v.y,b=y*y+m*m,u=Math.sqrt(b);a+="\nDistance: "+u;var c=i.nodeRepulsion/b,d=c*y/u,p=c*m/u}e.offsetX-=d,e.offsetY-=p,t.offsetX+=d,t.offsetY+=p,a+="\nForceX: "+d+" ForceY: "+p,w(a)},h=function(e,t,r){var n=e.positionX,i=e.positionY,a=e.height,o=e.width,s=r/t,l=a/o,c="Computing clipping point of node "+e.id+" . Height: "+a+", Width: "+o+"\nDirection "+t+", "+r,u={};do{if(0===t&&r>0){u.x=n,c+="\nUp direction",u.y=i+a/2;break}if(0===t&&0>r){u.x=n,u.y=i+a/2,c+="\nDown direction";break}if(t>0&&s>=-1*l&&l>=s){u.x=n+o/2,u.y=i+o*r/2/t,c+="\nRightborder";break}if(0>t&&s>=-1*l&&l>=s){u.x=n-o/2,u.y=i-o*r/2/t,c+="\nLeftborder";break}if(r>0&&(-1*l>=s||s>=l)){u.x=n+a*t/2/r,u.y=i+a/2,c+="\nTop border";break}if(0>r&&(-1*l>=s||s>=l)){u.x=n-a*t/2/r,u.y=i-a/2,c+="\nBottom border";break}}while(!1);return c+="\nClipping point found at "+u.x+", "+u.y,w(c),u},g=function(e,t,r,n){if(r>0)var i=e.maxX-t.minX;else var i=t.maxX-e.minX;if(n>0)var a=e.maxY-t.minY;else var a=t.maxY-e.minY;return i>=0&&a>=0?Math.sqrt(i*i+a*a):0},v=function(e,t,r){for(var n=0;nd;d++){var p=e.layoutNodes[e.idToIndex[a[d]]];n="Node: "+p.id;var h=s-p.positionX,g=l-p.positionY,v=Math.sqrt(h*h+g*g);if(v>1){var f=r.gravity*h/v,y=r.gravity*g/v;p.offsetX+=f,p.offsetY+=y,n+=": Applied force: "+f+", "+y}else n+=": skypped since it's too close to center";w(n)}}},y=function(e){var t=[],r=0,n=-1;for(w("propagateForces"),t.push.apply(t,e.graphSet[0]),n+=e.graphSet[0].length;n>=r;){var i=t[r++],a=e.idToIndex[i],o=e.layoutNodes[a],s=o.children;if(0r)var a={x:r*e/i,y:r*t/i};else var a={x:e,y:t};return n+=".\nResult: ("+a.x+", "+a.y+")",w(n),a},x=function(e,t){var r="Propagating new position/size of node "+e.id,n=e.parentId;if(null==n)return r+=". No parent node.",void w(r);var i=t.layoutNodes[t.idToIndex[n]],a=!1;return(null==i.maxX||e.maxX+i.padRight>i.maxX)&&(i.maxX=e.maxX+i.padRight,a=!0,r+="\nNew maxX for parent node "+i.id+": "+i.maxX),(null==i.minX||e.minX-i.padLefti.maxY)&&(i.maxY=e.maxY+i.padBottom,a=!0,r+="\nNew maxY for parent node "+i.id+": "+i.maxY),(null==i.minY||e.minY-i.padTops){var h=d(),g=p();(h-1)*g>=s?d(h-1):(g-1)*h>=s&&p(g-1)}else for(;s>u*c;){var h=d(),g=p();(g+1)*h>=s?p(g+1):d(h+1)}for(var v=a/u,f=o/c,y=0;y=u&&(P=0,S++)},C={},y=0;y0&&(f.rtrigger("position"),f=a.collection())},50),o.bind("drag",function(){e(this)});var x=o.filter(":grabbable"),w=n.stopSystem=function(){r(function(){i.fit&&a.fit(),a.one("layoutstop",i.stop),a.trigger("layoutstop"),n.stopSystem=null})};t(),setTimeout(function(){w()},i.maxSimulationTime)},t.prototype.stop=function(){null!=this.stopSystem&&this.stopSystem()},e("layout","springy",t)}(cytoscape),function(e){"use strict";function t(e){this.options=e}t.prototype.notify=function(){},e("renderer","null",t)}(cytoscape);
\ No newline at end of file
diff --git a/ajax/libs/cytoscape/package.json b/ajax/libs/cytoscape/package.json
index f4322f24c..82599d1ae 100644
--- a/ajax/libs/cytoscape/package.json
+++ b/ajax/libs/cytoscape/package.json
@@ -9,7 +9,7 @@
]
}
],
- "version": "2.2.8",
+ "version": "2.2.9",
"filename": "cytoscape.min.js",
"description": "A JavaScript graph library for analysis and visualisation",
"homepage": "http://cytoscape.github.io/cytoscape.js",