From 01e9aa0cffe5c0da8a0a779ae449d5751e7d5bee Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Wed, 29 Oct 2014 22:05:24 +0800 Subject: [PATCH] add cytoscape v2.3.4 --- ajax/libs/cytoscape/2.3.4/cytoscape.js | 22935 +++++++++++++++++++ ajax/libs/cytoscape/2.3.4/cytoscape.min.js | 24 + ajax/libs/cytoscape/package.json | 2 +- 3 files changed, 22960 insertions(+), 1 deletion(-) create mode 100644 ajax/libs/cytoscape/2.3.4/cytoscape.js create mode 100644 ajax/libs/cytoscape/2.3.4/cytoscape.min.js diff --git a/ajax/libs/cytoscape/2.3.4/cytoscape.js b/ajax/libs/cytoscape/2.3.4/cytoscape.js new file mode 100644 index 000000000..aad561bab --- /dev/null +++ b/ajax/libs/cytoscape/2.3.4/cytoscape.js @@ -0,0 +1,22935 @@ +/*! + * This file is part of Cytoscape.js 2.3.4. + * + * Cytoscape.js is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Cytoscape.js is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * Cytoscape.js. If not, see . + */ + + +// 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); + }; + + $$.version = '2.3.4'; + + // 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 module !== 'undefined' && module.exports ){ // expose as a commonjs module + module.exports = cytoscape; + } + + if( typeof define !== 'undefined' && define.amd ){ // 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 = { + defined: function(obj){ + return obj != null; // not undefined or null + }, + + string: function(obj){ + return obj != null && typeof obj == typeof ''; + }, + + fn: function(obj){ + return obj != null && typeof obj === typeof function(){}; + }, + + array: function(obj){ + return Array.isArray ? Array.isArray(obj) : 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 ); + }, + + boundingBox: function(obj){ + return $$.is.plainObject(obj) && + $$.is.number(obj.x1) && $$.is.number(obj.x2) && + $$.is.number(obj.y1) && $$.is.number(obj.y2) + ; + } + }; + +})( 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; + }, + + // require that pulls in module from commonjs, amd, or window (falling back until found) + require: function( name, callback, options ){ + var ret; + options = $$.util.extend({ + msgIfNotFound: true + }, options); + + var done = false; + var fulfil = function( ret ){ + done = true; + callback( ret ); + }; + + var checkWindow = function( next ){ + if( window ){ // detected browser/window env + ret = window[ name ]; + } + + if( ret !== undefined ){ fulfil(ret); } + if( next ){ next(); } + }; + var onCheckWindowDone = function(){ + if( !done ){ + checkCommonJs( onCheckCommonJsDone ); + } + }; + + var checkCommonJs = function( next ){ + if( typeof module !== 'undefined' && module.exports && require ){ // detected commonjs env + ret = require( name ); // regular require + } + + if( ret !== undefined ){ fulfil(ret); } + if( next ){ next(); } + }; + var onCheckCommonJsDone = function(){ + if( !done ){ + checkAmd( onCheckAmdDone ); + } + }; + + var checkAmd = function( next ){ + if( typeof define !== 'undefined' && define.amd && require ){ // detected amd env w/ defined module + require([ name ], function( nameImpl ){ + ret = nameImpl; + + if( ret !== undefined ){ fulfil(ret); } + if( next ){ next(); } + }); + } + }; + var onCheckAmdDone = function(){ + if( !done && options.msgIfNotFound ){ + $$.util.error('Cytoscape.js tried to pull in dependency `' + name + '` but no module (i.e. CommonJS, AMD, or window) was found'); + } + }; + + // kick off 1st check: window + checkWindow( onCheckWindowDone ); + + }, + + // multiple requires in one callback + requires: function( names, callback ){ + var impls = []; + var gotImpl = []; + + var checkDone = function(){ + for( var i = 0; i < names.length; i++ ){ // check have all impls + if( !gotImpl[i] ){ return; } + } + + // otherwise, all got all impls => done + callback.apply( callback, impls ); + }; + + for( var i = 0; i < names.length; i++ ){ (function(){ // w/scope + var name = names[i]; + var index = i; + + $$.util.require(name, function(impl){ + impls[index] = impl; + gotImpl[index] = true; + + checkDone(); + }); + })(); } + }, + + // 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.apply( console, arguments ); + } else if( console.log ){ + console.log.apply( console, arguments ); + } 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; + } + }, + + // makes a full bb (x1, y1, x2, y2, w, h) from implicit params + makeBoundingBox: function( bb ){ + if( bb.x1 != null && bb.y1 != null ){ + if( bb.x2 != null && bb.y2 != null && bb.x2 >= bb.x1 && bb.y2 >= bb.y1 ){ + return { + x1: bb.x1, + y1: bb.y1, + x2: bb.x2, + y2: bb.y2, + w: bb.x2 - bb.x1, + h: bb.y2 - bb.y1 + }; + } else if( bb.w != null && bb.h != null && bb.w >= 0 && bb.h >= 0 ){ + return { + x1: bb.x1, + y1: bb.y1, + x2: bb.x1 + bb.w, + y2: bb.y1 + bb.h, + w: bb.w, + h: bb.h + }; + } + } + }, + + // 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] ){ + obj[child] = undefined; + } + } + } else { + obj[key] = undefined; + } + + } 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 ( $$.is.array(color) ? color : null ) + || $$.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.boundingBoxesIntersect = function( bb1, bb2 ){ + // case: one bb to right of other + if( bb1.x1 > bb2.x2 ){ return false; } + if( bb2.x1 > bb1.x2 ){ return false; } + + // case: one bb to left of other + if( bb1.x2 < bb2.x1 ){ return false; } + if( bb2.x2 < bb1.x1 ){ return false; } + + // case: one bb above other + if( bb1.y2 < bb2.y1 ){ return false; } + if( bb2.y2 < bb1.y1 ){ return false; } + + // case: one bb below other + if( bb1.y1 > bb2.y2 ){ return false; } + if( bb2.y1 > bb1.y2 ){ return false; } + + // otherwise, must have some overlap + return true; + }; + + $$.math.inBoundingBox = function( bb, x, y ){ + return bb.x1 <= x && x <= bb.x2 && bb.y1 <= y && y <= bb.y2; + }; + + $$.math.pointInBoundingBox = function( bb, pt ){ + return this.inBoundingBox( bb, pt.x, pt.y ); + }; + + $$.math.roundRectangleIntersectLine = function( + x, y, nodeX, nodeY, width, height, padding) { + + var cornerRadius = this.getRoundRectangleRadius(width, height); + + var halfWidth = width / 2; + var halfHeight = height / 2; + + // Check intersections with straight line segments + var straightLineIntersections; + + // Top segment, left to right + { + var topStartX = nodeX - halfWidth + cornerRadius - padding; + var topStartY = nodeY - halfHeight - padding; + var topEndX = nodeX + halfWidth - cornerRadius + padding; + var topEndY = topStartY; + + straightLineIntersections = this.finiteLinesIntersect( + x, y, nodeX, nodeY, topStartX, topStartY, topEndX, topEndY, false); + + if (straightLineIntersections.length > 0) { + return straightLineIntersections; + } + } + + // Right segment, top to bottom + { + var rightStartX = nodeX + halfWidth + padding; + var rightStartY = nodeY - halfHeight + cornerRadius - padding; + var rightEndX = rightStartX; + var rightEndY = nodeY + halfHeight - cornerRadius + padding; + + straightLineIntersections = this.finiteLinesIntersect( + x, y, nodeX, nodeY, rightStartX, rightStartY, rightEndX, rightEndY, false); + + if (straightLineIntersections.length > 0) { + return straightLineIntersections; + } + } + + // Bottom segment, left to right + { + var bottomStartX = nodeX - halfWidth + cornerRadius - padding; + var bottomStartY = nodeY + halfHeight + padding; + var bottomEndX = nodeX + halfWidth - cornerRadius + padding; + var bottomEndY = bottomStartY; + + straightLineIntersections = this.finiteLinesIntersect( + x, y, nodeX, nodeY, bottomStartX, bottomStartY, bottomEndX, bottomEndY, false); + + if (straightLineIntersections.length > 0) { + return straightLineIntersections; + } + } + + // Left segment, top to bottom + { + var leftStartX = nodeX - halfWidth - padding; + var leftStartY = nodeY - halfHeight + cornerRadius - padding; + var leftEndX = leftStartX; + var leftEndY = nodeY + halfHeight - cornerRadius + padding; + + straightLineIntersections = this.finiteLinesIntersect( + x, y, nodeX, nodeY, leftStartX, leftStartY, leftEndX, leftEndY, false); + + if (straightLineIntersections.length > 0) { + return straightLineIntersections; + } + } + + // Check intersections with arc segments + var arcIntersections; + + // Top Left + { + var topLeftCenterX = nodeX - halfWidth + cornerRadius; + var topLeftCenterY = nodeY - halfHeight + cornerRadius; + arcIntersections = this.intersectLineCircle( + x, y, nodeX, nodeY, + topLeftCenterX, topLeftCenterY, cornerRadius + padding); + + // Ensure the intersection is on the desired quarter of the circle + if (arcIntersections.length > 0 + && arcIntersections[0] <= topLeftCenterX + && arcIntersections[1] <= topLeftCenterY) { + return [arcIntersections[0], arcIntersections[1]]; + } + } + + // Top Right + { + var topRightCenterX = nodeX + halfWidth - cornerRadius; + var topRightCenterY = nodeY - halfHeight + cornerRadius; + arcIntersections = this.intersectLineCircle( + x, y, nodeX, nodeY, + topRightCenterX, topRightCenterY, cornerRadius + padding); + + // Ensure the intersection is on the desired quarter of the circle + if (arcIntersections.length > 0 + && arcIntersections[0] >= topRightCenterX + && arcIntersections[1] <= topRightCenterY) { + return [arcIntersections[0], arcIntersections[1]]; + } + } + + // Bottom Right + { + var bottomRightCenterX = nodeX + halfWidth - cornerRadius; + var bottomRightCenterY = nodeY + halfHeight - cornerRadius; + arcIntersections = this.intersectLineCircle( + x, y, nodeX, nodeY, + bottomRightCenterX, bottomRightCenterY, cornerRadius + padding); + + // Ensure the intersection is on the desired quarter of the circle + if (arcIntersections.length > 0 + && arcIntersections[0] >= bottomRightCenterX + && arcIntersections[1] >= bottomRightCenterY) { + return [arcIntersections[0], arcIntersections[1]]; + } + } + + // Bottom Left + { + var bottomLeftCenterX = nodeX - halfWidth + cornerRadius; + var bottomLeftCenterY = nodeY + halfHeight - cornerRadius; + arcIntersections = this.intersectLineCircle( + x, y, nodeX, nodeY, + bottomLeftCenterX, bottomLeftCenterY, cornerRadius + padding); + + // Ensure the intersection is on the desired quarter of the circle + if (arcIntersections.length > 0 + && arcIntersections[0] <= bottomLeftCenterX + && arcIntersections[1] >= bottomLeftCenterY) { + return [arcIntersections[0], arcIntersections[1]]; + } + } + + return []; // if nothing + }; + + $$.math.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.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 ); + +;(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 ); + } + + // fill in missing layout functions in the prototype + if( type === 'layout' ){ + var layoutProto = registrant.prototype; + var optLayoutFns = ['stop']; + + for( var i = 0; i < optLayoutFns.length; i++ ){ + var fnName = optLayoutFns[i]; + + layoutProto[fnName] = layoutProto[fnName] || function(){ return this; }; + } + + // either .start() or .run() is defined, so autogen the other + if( layoutProto.start && !layoutProto.run ){ + layoutProto.run = function(){ this.start(); return this; }; + } else if( !layoutProto.start && layoutProto.run ){ + layoutProto.start = function(){ this.run(); return this; }; + } + + layoutProto.on = $$.define.on({ layout: true }); + layoutProto.one = $$.define.on({ layout: true, unbindSelfOnTrigger: true }); + layoutProto.once = $$.define.on({ layout: true, unbindAllBindersOnTrigger: true }); + layoutProto.off = $$.define.off({ layout: true }); + layoutProto.trigger = $$.define.trigger({ layout: true }); + + // aliases for those folks who like old stuff: + layoutProto.bind = layoutProto.on; + layoutProto.unbind = layoutProto.off; + } + + 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 { + $$.util.error('Invalid extension access syntax'); + } + + }; + +})( cytoscape ); + +;(function($, $$){ 'use strict'; + + if( !$ ){ return; } // no jquery => don't need this + + var cyReg = function( $ele ){ + var d = $ele[0]._cyreg = $ele[0]._cyreg || {}; + + return d; + }; + + // allow calls on a jQuery selector by proxying calls to $.cytoscape + // e.g. $("#foo").cytoscape(options) => $.cytoscape(options) on #foo + $.fn.cytoscape = function(opts){ + var $this = $(this); + + // get object + if( opts === 'get' ){ + return cyReg( $this ).cy; + } + + // bind to ready + else if( $$.is.fn(opts) ){ + + var ready = opts; + var cy = cyReg( $this ).cy; + + if( cy && cy.isReady() ){ // already ready so just trigger now + cy.trigger('ready', [], ready); + + } else { // not yet ready, so add to readies list + var data = cyReg( $this ); + var readies = data.readies = data.readies || []; + + readies.push( ready ); + } + + } + + // proxy to create instance + else if( $$.is.plainObject(opts) ){ + return $this.each(function(){ + var options = $.extend({}, opts, { + container: $(this)[0] + }); + + cytoscape(options); + }); + } + }; + + // allow access to the global cytoscape object under jquery for legacy reasons + $.cytoscape = cytoscape; + + // use short alias (cy) if not already defined + if( $.fn.cy == null && $.cy == null ){ + $.fn.cy = $.fn.cytoscape; + $.cy = $.cytoscape; + } + +})(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 ); + + // more efficient to manually copy fields we use + this.type = props.type !== undefined ? props.type : this.type; + this.cy = props.cy; + this.cyTarget = props.cyTarget; + this.cyPosition = props.cyPosition; + this.cyRenderedPosition = props.cyRenderedPosition; + this.namespace = props.namespace; + this.layout = props.layout; + this.data = props.data; + } + + // 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'; + + // 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 + + // remove data field + removeData: function( params ){ + var defaults = { + field: 'data', + event: 'data', + triggerFnName: 'trigger', + triggerEvent: false, + immutableKeys: {} // key => true if immutable + }; + params = $$.util.extend({}, defaults, params); + + return function removeDataImpl( names ){ + var p = params; + var self = this; + var selfIsArrayLike = self.length !== undefined; + var all = selfIsArrayLike ? self : [self]; // put in array if not array-like + + // .removeData('foo bar') + if( $$.is.string(names) ){ // then get the list of keys, and delete them + var keys = names.split(/\s+/); + var l = keys.length; + + for( var i = 0; i < l; i++ ){ // delete each non-empty key + var key = keys[i]; + if( $$.is.emptyString(key) ){ continue; } + + var valid = !p.immutableKeys[ key ]; // not valid if immutable + if( valid ){ + for( var i_a = 0, l_a = all.length; i_a < l_a; i_a++ ){ + all[ i_a ]._private[ p.field ][ key ] = undefined; + } + } + } + + if( p.triggerEvent ){ + self[ p.triggerFnName ]( p.event ); + } + + // .removeData() + } else if( names === undefined ){ // then delete all keys + + for( var i_a = 0, l_a = all.length; i_a < l_a; i_a++ ){ + var _privateFields = all[ i_a ]._private[ p.field ]; + + for( var key in _privateFields ){ + var validKeyToDelete = !p.immutableKeys[ key ]; + + if( validKeyToDelete ){ + _privateFields[ key ] = undefined; + } + } + } + + if( p.triggerEvent ){ + self[ p.triggerFnName ]( p.event ); + } + } + + return self; // maintain chaining + }; // function + }, // removeData + + // event function reusable stuff + event: { + regex: /(\w+)(\.\w+)?/, // regex for matching event strings (e.g. "click.namespace") + optionalTypeRegex: /(\w+)?(\.\w+)?/, + falseCallback: function(){ return false; } + }, + + // event binding + on: function( params ){ + var defaults = { + unbindSelfOnTrigger: false, + unbindAllBindersOnTrigger: false + }; + params = $$.util.extend({}, defaults, params); + + return function onImpl(events, selector, data, callback){ + var self = this; + var selfIsArrayLike = self.length !== undefined; + var all = selfIsArrayLike ? self : [self]; // put in array if not array-like + var eventsIsString = $$.is.string(events); + var p = params; + + if( $$.is.plainObject(selector) ){ // selector is actually data + callback = data; + data = selector; + selector = undefined; + } else if( $$.is.fn(selector) || selector === false ){ // selector is actually callback + callback = selector; + data = undefined; + selector = undefined; + } + + if( $$.is.fn(data) || data === false ){ // data is actually callback + callback = data; + data = undefined; + } + + // if there isn't a callback, we can't really do anything + // (can't speak for mapped events arg version) + if( !($$.is.fn(callback) || callback === false) && eventsIsString ){ + return self; // maintain chaining + } + + if( eventsIsString ){ // then convert to map + var map = {}; + map[ events ] = callback; + events = map; + } + + for( var evts in events ){ + callback = events[evts]; + if( callback === false ){ + callback = $$.define.event.falseCallback; + } + + if( !$$.is.fn(callback) ){ continue; } + + evts = evts.split(/\s+/); + for( var i = 0; i < evts.length; i++ ){ + var evt = evts[i]; + if( $$.is.emptyString(evt) ){ continue; } + + var match = evt.match( $$.define.event.regex ); // type[.namespace] + + if( match ){ + var type = match[1]; + var namespace = match[2] ? match[2] : undefined; + + var listener = { + callback: callback, // callback to run + data: data, // extra data in eventObj.data + delegated: selector ? true : false, // whether the evt is delegated + selector: selector, // the selector to match for delegated events + selObj: new $$.Selector(selector), // cached selector object to save rebuilding + type: type, // the event type (e.g. 'click') + namespace: namespace, // the event namespace (e.g. ".foo") + unbindSelfOnTrigger: p.unbindSelfOnTrigger, + unbindAllBindersOnTrigger: p.unbindAllBindersOnTrigger, + binders: all // who bound together + }; + + for( var j = 0; j < all.length; j++ ){ + 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; + var hasCompounds = cy.hasCompoundNodes(); + + 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 || params.layout; + + // create the event for this element from the event object + var evt; + + if( eventsIsEvent ){ // then just get the object + evt = evtObj; + + evt.cyTarget = evt.cyTarget || triggerer; + evt.cy = evt.cy || cy; + + } else { // then we have to make one + evt = new $$.Event( evtObj, { + cyTarget: triggerer, + cy: cy, + namespace: evtObj.namespace + } ); + } + + // if a layout was specified, then put it in the typed event + if( evtObj.layout ){ + evt.layout = evtObj.layout; + } + + // if triggered by layout, put in event + if( params.layout ){ + evt.layout = triggerer; + } + + // create a rendered position based on the passed position + if( evt.cyPosition ){ + var pos = evt.cyPosition; + var zoom = cy.zoom(); + var pan = cy.pan(); + + evt.cyRenderedPosition = { + x: pos.x * zoom + pan.x, + y: pos.y * zoom + pan.y + }; + } + + if( fnToTrigger ){ // then override the listeners list with just the one we specified + listeners = [{ + namespace: evt.namespace, + type: evt.type, + callback: fnToTrigger + }]; + } + + for( var k = 0; k < listeners.length; k++ ){ // check each listener + var lis = listeners[k]; + var nsMatches = !lis.namespace || lis.namespace === evt.namespace; + var typeMatches = lis.type === evt.type; + var targetMatches = lis.delegated ? ( triggerer !== evt.cyTarget && $$.is.element(evt.cyTarget) && lis.selObj.matches(evt.cyTarget) ) : (true); // we're not going to validate the hierarchy; that's too expensive + var listenerMatches = nsMatches && typeMatches && targetMatches; + + if( listenerMatches ){ // then trigger it + var args = [ evt ]; + args = args.concat( extraParams ); // add extra params to args list + + if( lis.data ){ // add on data plugged into binding + evt.data = lis.data; + } else { // or clear it in case the event obj is reused + evt.data = undefined; + } + + if( lis.unbindSelfOnTrigger || lis.unbindAllBindersOnTrigger ){ // then remove listener + listeners.splice(k, 1); + k--; + } + + if( lis.unbindAllBindersOnTrigger ){ // then delete the listener for all binders + var binders = lis.binders; + for( var l = 0; l < binders.length; l++ ){ + var binder = binders[l]; + if( !binder || binder === triggerer ){ continue; } // already handled triggerer or we can't handle it + + var binderListeners = binder._private.listeners; + for( var m = 0; m < binderListeners.length; m++ ){ + var binderListener = binderListeners[m]; + + if( binderListener === lis ){ // delete listener from list + binderListeners.splice(m, 1); + m--; + } + } + } + } + + // run the callback + var context = lis.delegated ? evt.cyTarget : triggerer; + var ret = lis.callback.apply( context, args ); + + if( ret === false || evt.isPropagationStopped() ){ + // then don't bubble + bubbleUp = false; + + if( ret === false ){ + // returning false is a shorthand for stopping propagation and preventing the def. action + evt.stopPropagation(); + evt.preventDefault(); + } + } + } // if listener matches + } // for each listener + + // bubble up event for elements + if( bubbleUp ){ + var parent = hasCompounds ? triggerer._private.parent : null; + var hasParent = parent != null && parent.length !== 0; + + if( hasParent ){ // then bubble up to parent + parent = parent[0]; + parent.trigger(evt); + } else { // otherwise, bubble up to the core + cy.trigger(evt); + } + } + + } // for each of all + } // for each event + + return self; // maintain chaining + }; // function + }, // trigger + + + animated: function( fnParams ){ + var defaults = {}; + fnParams = $$.util.extend({}, defaults, fnParams); + + return function animatedImpl(){ + var self = this; + var selfIsArrayLike = self.length !== undefined; + var all = selfIsArrayLike ? self : [self]; // put in array if not array-like + var cy = this._private.cy || this; + + if( !cy.styleEnabled() ){ return false; } + + var ele = all[0]; + + if( ele ){ + return ele._private.animation.current.length > 0; + } + }; + }, // animated + + clearQueue: function( fnParams ){ + var defaults = {}; + fnParams = $$.util.extend({}, defaults, fnParams); + + return function clearQueueImpl(){ + var self = this; + var selfIsArrayLike = self.length !== undefined; + var all = selfIsArrayLike ? self : [self]; // put in array if not array-like + var cy = this._private.cy || this; + + if( !cy.styleEnabled() ){ return this; } + + for( var i = 0; i < all.length; i++ ){ + var ele = all[i]; + ele._private.animation.queue = []; + } + + return this; + }; + }, // clearQueue + + delay: function( fnParams ){ + var defaults = {}; + fnParams = $$.util.extend({}, defaults, fnParams); + + return function delayImpl( time, complete ){ + var cy = this._private.cy || this; + + if( !cy.styleEnabled() ){ return this; } + + this.animate({ + delay: time + }, { + duration: time, + complete: complete + }); + + return this; + }; + }, // delay + + animate: function( fnParams ){ + var defaults = {}; + fnParams = $$.util.extend({}, defaults, fnParams); + + return function animateImpl( properties, params ){ + var self = this; + var selfIsArrayLike = self.length !== undefined; + var all = selfIsArrayLike ? self : [self]; // put in array if not array-like + var cy = this._private.cy || this; + var isCore = !selfIsArrayLike; + var isEles = !isCore; + + if( !cy.styleEnabled() ){ return this; } + + var callTime = +new Date(); + 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; + } + + var propertiesEmpty = true; + if( properties ){ for( var i in properties ){ + propertiesEmpty = false; + break; + } } + + if( propertiesEmpty ){ + return this; // nothing to animate + } + + if( properties.css && isEles ){ + properties.css = style.getValueStyle( properties.css, { array: true } ); + } + + if( properties.renderedPosition && isEles ){ + var rpos = properties.renderedPosition; + var pan = cy.pan(); + var zoom = cy.zoom(); + + properties.position = { + x: ( rpos.x - pan.x ) /zoom, + y: ( rpos.y - pan.y ) /zoom + }; + } + + // override pan w/ panBy if set + if( properties.panBy && isCore ){ + var panBy = properties.panBy; + var cyPan = cy.pan(); + + properties.pan = { + x: cyPan.x + panBy.x, + y: cyPan.y + panBy.y + }; + } + + // override pan w/ center if set + var center = properties.center || properties.centre; + if( center && isCore ){ + var centerPan = cy.getCenterPan( center.eles ); + + if( centerPan ){ + properties.pan = centerPan; + } + } + + // override pan & zoom w/ fit if set + if( properties.fit && isCore ){ + var fit = properties.fit; + var fitVp = cy.getFitViewport( fit.eles || fit.boundingBox, fit.padding ); + + if( fitVp ){ + properties.pan = fitVp.pan; //{ x: fitVp.pan.x, y: fitVp.pan.y }; + properties.zoom = fitVp.zoom; + } + } + + for( var i = 0; i < all.length; i++ ){ + var ele = all[i]; + + if( ele.animated() && (params.queue === undefined || params.queue) ){ + q = ele._private.animation.queue; + } else { + q = ele._private.animation.current; + } + + q.push({ + properties: properties, + duration: params.duration, + params: params, + callTime: callTime + }); + } + + if( isEles ){ + cy.addToAnimationPool( this ); + } + + return this; // chaining + }; + }, // animate + + stop: function( fnParams ){ + var defaults = {}; + fnParams = $$.util.extend({}, defaults, fnParams); + + return function stopImpl( clearQueue, jumpToEnd ){ + var self = this; + var selfIsArrayLike = self.length !== undefined; + var all = selfIsArrayLike ? self : [self]; // put in array if not array-like + var cy = this._private.cy || this; + + if( !cy.styleEnabled() ){ return this; } + + for( var i = 0; i < all.length; i++ ){ + var ele = all[i]; + var anis = ele._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 ){ + ele._private.animation.queue = []; + } + + if( !jumpToEnd ){ + ele._private.animation.current = []; + } + } + + // we have to notify (the animation loop doesn't do it for us on `stop`) + cy.notify({ + collection: this, + type: 'draw' + }); + + return this; + }; + } // stop + + }; // define + + +})( 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; + } + + // add ! variants to comparatorOp + var ops = tokens.comparatorOp.split('|'); + for( var i = 0; i < ops.length; i++ ){ + var op = ops[i]; + + if( op.indexOf('!') >= 0 ){ continue; } // skip ops that explicitly contain ! + if( op === '=' ){ continue; } // skip = b/c != is explicitly defined + + tokens.comparatorOp += '|\\!' + op; + } + + // NOTE: add new expression syntax here to have it recognised by the parser; + // - a query contains all adjacent (i.e. no separator in between) expressions; + // - the current query is stored in self[i] --- you can use the reference to `this` in the populate function; + // - you need to check the query objects in Selector.filter() for it actually filter properly, but that's pretty straight forward + // - when you add something here, also add to Selector.toString() + var exprs = { + group: { + query: true, + regex: '(node|edge|\\*)', + populate: function( group ){ + this.group = group == "*" ? group : group + 's'; + } + }, + + state: { + query: true, + // NB: if one colon selector is a substring of another from its start, place the longer one first + // e.g. :foobar|:foo + regex: '(:selected|:unselected|:locked|:unlocked|:visible|:hidden|:transparent|:grabbed|:free|:removed|:inside|:grabbable|:ungrabbable|:animated|:unanimated|:selectable|:unselectable|:orphan|:nonorphan|:parent|:child|:loop|:simple|:active|:inactive|:touch)', + 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 + }; + + var queryMatches = function(query, element){ + // check group + if( query.group != null && query.group != '*' && query.group != element._private.group ){ + return false; + } + + var cy = element.cy(); + + // check colon selectors + var allColonSelectorsMatch = true; + for(var k = 0; k < query.colonSelectors.length; k++){ + var sel = query.colonSelectors[k]; + + switch(sel){ + case ':selected': + allColonSelectorsMatch = element.selected(); + break; + case ':unselected': + allColonSelectorsMatch = !element.selected(); + break; + case ':selectable': + allColonSelectorsMatch = element.selectable(); + break; + case ':unselectable': + allColonSelectorsMatch = !element.selectable(); + break; + case ':locked': + allColonSelectorsMatch = element.locked(); + break; + case ':unlocked': + allColonSelectorsMatch = !element.locked(); + break; + case ':visible': + allColonSelectorsMatch = element.visible(); + break; + case ':hidden': + allColonSelectorsMatch = !element.visible(); + break; + case ':transparent': + allColonSelectorsMatch = element.transparent(); + break; + case ':grabbed': + allColonSelectorsMatch = element.grabbed(); + break; + case ':free': + allColonSelectorsMatch = !element.grabbed(); + break; + case ':removed': + allColonSelectorsMatch = element.removed(); + break; + case ':inside': + allColonSelectorsMatch = !element.removed(); + break; + case ':grabbable': + allColonSelectorsMatch = element.grabbable(); + break; + case ':ungrabbable': + allColonSelectorsMatch = !element.grabbable(); + break; + case ':animated': + allColonSelectorsMatch = element.animated(); + break; + case ':unanimated': + allColonSelectorsMatch = !element.animated(); + break; + case ':parent': + allColonSelectorsMatch = element.isNode() && element.children().nonempty(); + break; + case ':child': + case ':nonorphan': + allColonSelectorsMatch = element.isNode() && element.parent().nonempty(); + break; + case ':orphan': + allColonSelectorsMatch = element.isNode() && element.parent().empty(); + break; + case ':loop': + allColonSelectorsMatch = element.isEdge() && element.data('source') === element.data('target'); + break; + case ':simple': + allColonSelectorsMatch = element.isEdge() && element.data('source') !== element.data('target'); + break; + case ':active': + allColonSelectorsMatch = element.active(); + break; + case ':inactive': + allColonSelectorsMatch = !element.active(); + break; + case ':touch': + allColonSelectorsMatch = $$.is.touch(); + break; + } + + if( !allColonSelectorsMatch ) break; + } + if( !allColonSelectorsMatch ) return false; + + // check id + var allIdsMatch = true; + for(var k = 0; k < query.ids.length; k++){ + var id = query.ids[k]; + var actualId = element._private.data.id; + + allIdsMatch = allIdsMatch && (id == actualId); + + if( !allIdsMatch ) break; + } + if( !allIdsMatch ) return false; + + // check classes + var allClassesMatch = true; + for(var k = 0; k < query.classes.length; k++){ + var cls = query.classes[k]; + + allClassesMatch = allClassesMatch && element.hasClass(cls); + + if( !allClassesMatch ) break; + } + if( !allClassesMatch ) return false; + + // generic checking for data/metadata + var operandsMatch = function(params){ + var allDataMatches = true; + for(var k = 0; k < query[params.name].length; k++){ + var data = query[params.name][k]; + var operator = data.operator; + var value = data.value; + var field = data.field; + var matches; + + if( operator != null && value != null ){ + + var fieldVal = params.fieldValue(field); + var fieldStr = !$$.is.string(fieldVal) && !$$.is.number(fieldVal) ? '' : '' + fieldVal; + var valStr = '' + value; + + var caseInsensitive = false; + if( operator.indexOf('@') >= 0 ){ + fieldStr = fieldStr.toLowerCase(); + valStr = valStr.toLowerCase(); + + operator = operator.replace('@', ''); + caseInsensitive = true; + } + + var notExpr = false; + var handledNotExpr = false; + if( operator.indexOf('!') >= 0 ){ + operator = operator.replace('!', ''); + notExpr = true; + } + + // if we're doing a case insensitive comparison, then we're using a STRING comparison + // even if we're comparing numbers + if( caseInsensitive ){ + value = valStr.toLowerCase(); + fieldVal = fieldStr.toLowerCase(); + } + + switch(operator){ + case '*=': + matches = fieldStr.search(valStr) >= 0; + break; + case '$=': + matches = new RegExp(valStr + '$').exec(fieldStr) != null; + break; + case '^=': + matches = new RegExp('^' + valStr).exec(fieldStr) != null; + break; + case '=': + matches = fieldVal === value; + break; + case '!=': + matches = fieldVal !== value; + break; + case '>': + matches = !notExpr ? fieldVal > value : fieldVal <= value; + handledNotExpr = true; + break; + case '>=': + matches = !notExpr ? fieldVal >= value : fieldVal < value; + handledNotExpr = true; + break; + case '<': + matches = !notExpr ? fieldVal < value : fieldVal >= value; + handledNotExpr = true; + break; + case '<=': + matches = !notExpr ? fieldVal <= value : fieldVal > value; + handledNotExpr = true; + break; + default: + matches = false; + break; + + } + } else if( operator != null ){ + switch(operator){ + case '?': + matches = params.fieldTruthy(field); + break; + case '!': + matches = !params.fieldTruthy(field); + break; + case '^': + matches = params.fieldUndefined(field); + break; + } + } else { + matches = !params.fieldUndefined(field); + } + + if( notExpr && !handledNotExpr ){ + matches = !matches; + handledNotExpr = true; + } + + if( !matches ){ + allDataMatches = false; + break; + } + } // for + + return allDataMatches; + }; // operandsMatch + + // check data matches + var allDataMatches = operandsMatch({ + name: 'data', + fieldValue: function(field){ + return element._private.data[field]; + }, + fieldRef: function(field){ + return 'element._private.data.' + field; + }, + fieldUndefined: function(field){ + return element._private.data[field] === undefined; + }, + fieldTruthy: function(field){ + if( element._private.data[field] ){ + return true; + } + return false; + } + }); + + if( !allDataMatches ){ + return false; + } + + // check metadata matches + var allMetaMatches = operandsMatch({ + name: 'meta', + fieldValue: function(field){ + return element[field](); + }, + fieldRef: function(field){ + return 'element.' + field + '()'; + }, + fieldUndefined: function(field){ + return element[field]() == null; + }, + fieldTruthy: function(field){ + if( element[field]() ){ + return true; + } + return false; + } + }); + + if( !allMetaMatches ){ + return false; + } + + // check collection + if( query.collection != null ){ + var matchesAny = query.collection._private.ids[ element.id() ] != null; + + if( !matchesAny ){ + return false; + } + } + + // check filter function + if( query.filter != null && element.collection().filter( query.filter ).size() === 0 ){ + return false; + } + + + // check parent/child relations + var confirmRelations = function( query, elements ){ + if( query != null ){ + var matches = false; + + if( !cy.hasCompoundNodes() ){ + return false; + } + + elements = elements(); // make elements functional so we save cycles if query == null + + // query must match for at least one element (may be recursive) + for(var i = 0; i < elements.length; i++){ + if( queryMatches( query, elements[i] ) ){ + matches = true; + break; + } + } + + return matches; + } else { + return true; + } + }; + + if (! confirmRelations(query.parent, function(){ + return element.parent(); + }) ){ return false; } + + if (! confirmRelations(query.ancestor, function(){ + return element.parents(); + }) ){ return false; } + + if (! confirmRelations(query.child, function(){ + return element.children(); + }) ){ return false; } + + if (! confirmRelations(query.descendant, function(){ + return element.descendants(); + }) ){ return false; } + + // we've reached the end, so we've matched everything for this query + return true; + }; // queryMatches + + // filter an existing collection + $$.selfn.filter = function(collection){ + var self = this; + var cy = collection.cy(); + + // don't bother trying if it's invalid + if( self._private.invalid ){ + return new $$.Collection( cy ); + } + + var selectorFunction = function(i, element){ + for(var j = 0; j < self.length; j++){ + var query = self[j]; + + if( queryMatches(query, element) ){ + return true; + } + } + + return false; + }; + + if( self._private.selectorText == null ){ + selectorFunction = function(){ return true; }; + } + + var filteredCollection = collection.filter( selectorFunction ); + + return filteredCollection; + }; // filter + + // does selector match a single element? + $$.selfn.matches = function(ele){ + var self = this; + + // don't bother trying if it's invalid + if( self._private.invalid ){ + return false; + } + + for(var j = 0; j < self.length; j++){ + var query = self[j]; + + if( queryMatches(query, ele) ){ + return true; + } + } + + return false; + }; // filter + + // ith query to string + $$.selfn.toString = $$.selfn.selector = function(){ + + var str = ''; + + var clean = function(obj){ + 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: {}, + newStyle: true + }; + + 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|ms', implicitUnits: 'ms' }, + 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 }, + position: { enums: ['parent', 'origin'] }, + autoSize: { number: true, min: 0, enums: ['auto'] }, + number: { number: true }, + size: { number: true, min: 0 }, + 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'] }, + borderStyle: { enums: ['solid', 'dotted', 'dashed', 'double'] }, + curveStyle: { enums: ['bezier', 'unbundled-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', 'uppercase', 'lowercase'] }, + nodeShape: { enums: ['rectangle', 'roundrectangle', 'ellipse', 'triangle', 'square', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'star'] }, + arrowShape: { enums: ['tee', 'triangle', 'triangle-tee', 'triangle-backcurve', 'half-triangle-overshot', 'square', 'circle', 'diamond', 'none'] }, + arrowFill: { enums: ['filled', 'hollow'] }, + display: { enums: ['element', 'none'] }, + visibility: { enums: ['hidden', 'visible'] }, + valign: { enums: ['top', 'center', 'bottom'] }, + halign: { enums: ['left', 'center', 'right'] }, + text: { string: true }, + data: { mapping: true, regex: data('data') }, + layoutData: { mapping: true, regex: data('layoutData') }, + mapData: { mapping: true, regex: mapData('mapData') }, + mapLayoutData: { mapping: true, regex: mapData('mapLayoutData') }, + url: { regex: '^url\\s*\\(\\s*([^\\s]+)\\s*\\s*\\)|none|(.+)$' }, + propList: { propList: true }, + angle: { number: true, units: 'deg|rad' } + }; + + // define visual style properties + var t = $$.style.types; + var props = $$.style.properties = [ + // labels + { 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 }, // not supported in canvas + { name: 'text-transform', type: t.textTransform }, + // { name: 'text-rotation', type: t.angle }, // TODO disabled b/c rotation breaks bounding boxes + { name: 'font-family', type: t.fontFamily }, + { name: 'font-style', type: t.fontStyle }, + // { name: 'font-variant', type: t.fontVariant }, // not useful + { name: 'font-weight', type: t.fontWeight }, + { name: 'font-size', type: t.size }, + { name: 'min-zoomed-font-size', type: t.size }, + + // visibility + { name: 'display', type: t.display }, + { name: 'visibility', type: t.visibility }, + { name: 'opacity', type: t.zeroOneNumber }, + { name: 'z-index', type: t.nonNegativeInt }, + + // overlays + { name: 'overlay-padding', type: t.size }, + { name: 'overlay-color', type: t.color }, + { name: 'overlay-opacity', type: t.zeroOneNumber }, + + // transition anis + { name: 'transition-property', type: t.propList }, + { name: 'transition-duration', type: t.time }, + { name: 'transition-delay', type: t.time }, + + // node body + { name: 'height', type: t.autoSize }, + { name: 'width', type: t.autoSize }, + { name: 'shape', type: t.nodeShape }, + { name: 'background-color', type: t.color }, + { name: 'background-opacity', type: t.zeroOneNumber }, + { name: 'background-blacken', type: t.nOneOneNumber }, + + // node border + { name: 'border-color', type: t.color }, + { name: 'border-opacity', type: t.zeroOneNumber }, + { name: 'border-width', type: t.size }, + { name: 'border-style', type: t.borderStyle }, + + // node background images + { name: 'background-image', type: t.url }, + { name: 'background-image-opacity', type: t.zeroOneNumber }, + { name: 'background-position-x', type: t.bgPos }, + { name: 'background-position-y', type: t.bgPos }, + { name: 'background-repeat', type: t.bgRepeat }, + { name: 'background-fit', type: t.bgFit }, + { name: 'background-clip', type: t.bgClip }, + + // compound props + { 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: 'position', type: t.position }, + + // edge line + { 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.number }, + { name: 'control-point-weight', type: t.zeroOneNumber }, + { name: 'curve-style', type: t.curveStyle }, + { name: 'haystack-radius', type: t.zeroOneNumber }, + + // edge arrows + { name: 'source-arrow-shape', type: t.arrowShape }, + { name: 'target-arrow-shape', type: t.arrowShape }, + { name: 'mid-source-arrow-shape', type: t.arrowShape }, + { name: 'mid-target-arrow-shape', type: t.arrowShape }, + { name: 'source-arrow-color', type: t.color }, + { name: 'target-arrow-color', type: t.color }, + { name: 'mid-source-arrow-color', type: t.color }, + { name: 'mid-target-arrow-color', type: t.color }, + { name: 'source-arrow-fill', type: t.arrowFill }, + { name: 'target-arrow-fill', type: t.arrowFill }, + { name: 'mid-source-arrow-fill', type: t.arrowFill }, + { name: 'mid-target-arrow-fill', type: t.arrowFill }, + + // these are just for the core + { name: 'selection-box-color', type: t.color }, + { name: 'selection-box-opacity', type: t.zeroOneNumber }, + { name: 'selection-box-border-color', type: t.color }, + { name: 'selection-box-border-width', type: t.size }, + { name: 'active-bg-color', type: t.color }, + { name: 'active-bg-opacity', type: t.zeroOneNumber }, + { name: 'active-bg-size', type: t.size }, + { name: 'outside-texture-bg-color', type: t.color }, + { name: 'outside-texture-bg-opacity', type: t.zeroOneNumber } + ]; + + // pie backgrounds for nodes + $$.style.pieBackgroundN = 16; // because the pie properties are numbered, give access to a constant N (for renderer use) + props.push({ name: 'pie-size', type: t.bgSize }); + for( var i = 1; i <= $$.style.pieBackgroundN; i++ ){ + props.push({ name: 'pie-'+i+'-background-color', type: t.color }); + props.push({ name: 'pie-'+i+'-background-size', type: t.percent }); + props.push({ name: 'pie-'+i+'-background-opacity', type: t.zeroOneNumber }); + } + + // allow access of properties by name ( e.g. $$.style.properties.height ) + for( var i = 0; i < props.length; i++ ){ + var prop = props[i]; + + props[ prop.name ] = prop; // allow lookup by name + } + })(); + + // 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-image-opacity': 1, + '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, + 'shape': 'ellipse', + + // compound props + 'padding-top': 0, + 'padding-bottom': 0, + 'padding-left': 0, + 'padding-right': 0, + 'position': 'origin', + + + // node pie bg + 'pie-size': '100%', + 'pie-1-background-color': 'black', + 'pie-2-background-color': 'black', + 'pie-3-background-color': 'black', + 'pie-4-background-color': 'black', + 'pie-5-background-color': 'black', + 'pie-6-background-color': 'black', + 'pie-7-background-color': 'black', + 'pie-8-background-color': 'black', + 'pie-9-background-color': 'black', + 'pie-10-background-color': 'black', + 'pie-11-background-color': 'black', + 'pie-12-background-color': 'black', + 'pie-13-background-color': 'black', + 'pie-14-background-color': 'black', + 'pie-15-background-color': 'black', + 'pie-16-background-color': 'black', + 'pie-1-background-size': '0%', + 'pie-2-background-size': '0%', + 'pie-3-background-size': '0%', + 'pie-4-background-size': '0%', + 'pie-5-background-size': '0%', + 'pie-6-background-size': '0%', + 'pie-7-background-size': '0%', + 'pie-8-background-size': '0%', + 'pie-9-background-size': '0%', + 'pie-10-background-size': '0%', + 'pie-11-background-size': '0%', + 'pie-12-background-size': '0%', + 'pie-13-background-size': '0%', + 'pie-14-background-size': '0%', + 'pie-15-background-size': '0%', + 'pie-16-background-size': '0%', + 'pie-1-background-opacity': 1, + 'pie-2-background-opacity': 1, + 'pie-3-background-opacity': 1, + 'pie-4-background-opacity': 1, + 'pie-5-background-opacity': 1, + 'pie-6-background-opacity': 1, + 'pie-7-background-opacity': 1, + 'pie-8-background-opacity': 1, + 'pie-9-background-opacity': 1, + 'pie-10-background-opacity': 1, + 'pie-11-background-opacity': 1, + 'pie-12-background-opacity': 1, + 'pie-13-background-opacity': 1, + 'pie-14-background-opacity': 1, + 'pie-15-background-opacity': 1, + 'pie-16-background-opacity': 1, + + // edge props + 'source-arrow-shape': 'none', + 'mid-source-arrow-shape': 'none', + 'target-arrow-shape': 'none', + 'mid-target-arrow-shape': 'none', + 'source-arrow-color': '#ddd', + 'mid-source-arrow-color': '#ddd', + 'target-arrow-color': '#ddd', + 'mid-target-arrow-color': '#ddd', + 'source-arrow-fill': 'filled', + 'mid-source-arrow-fill': 'filled', + 'target-arrow-fill': 'filled', + 'mid-target-arrow-fill': 'filled', + 'line-style': 'solid', + 'line-color': '#ddd', + 'control-point-step-size': 40, + 'control-point-weight': 0.5, + 'curve-style': 'bezier', + 'haystack-radius': 0.8 + }) + .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, + '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(){ + for( var i = 0; i < this.length; i++ ){ + this[i] = undefined; + } + this.length = 0; + this._private.newStyle = true; + + return this; // chaining + }; + + $$.styfn.resetToDefault = function(){ + this.clear(); + this.addDefaultStylesheet(); + + return this; + }; + + // builds a style object for the 'core' selector + $$.styfn.core = function(){ + return this._private.coreStyle; + }; + + // 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 ) ) + ){ + if( propIsBypass ){ return false; } // mappers not allowed in bypass + + 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, + hasPie: name.match(/pie-(\d+)-background-size/) + }; + + } else if( + ( mapData = new RegExp( $$.style.types.mapData.regex ).exec( value ) ) || + ( mapLayoutData = new RegExp( $$.style.types.mapLayoutData.regex ).exec( value ) ) + ){ + if( propIsBypass ){ return false; } // mappers not allowed in bypass + + 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, + hasPie: name.match(/pie-(\d+)-background-size/) + }; + } + + // check the type and return the appropriate object + if( type.number ){ + var units; + var implicitUnits = 'px'; // not set => px + + if( type.units ){ // use specified units if set + units = type.units; + } + + if( type.implicitUnits ){ + implicitUnits = type.implicitUnits; + } + + if( !type.unitless ){ + if( valueIsString ){ + var unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : ''); + if( units ){ unitsRegex = units; } // only allow explicit units if so set + var match = value.match( '^(' + $$.util.regex.number + ')(' + unitsRegex + ')?' + '$' ); + + if( match ){ + value = match[1]; + units = match[2] || implicitUnits; + } + + } else if( !units || type.implicitUnits ) { + units = implicitUnits; // implicitly px if unspecified + } + } + + value = parseFloat( value ); + + // if not a number and enums not allowed, then the value is invalid + if( isNaN(value) && type.enums === undefined ){ + return null; + } + + // check if this number type also accepts special keywords in place of numbers + // (i.e. `left`, `auto`, etc) + if( isNaN(value) && type.enums !== undefined ){ + value = passedValue; + + 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, + hasPie: name.match(/pie-(\d+)-background-size/) && value != null && value !== 0 && value !== '' + }; + + // normalise value in pixels + if( type.unitless || (units !== 'px' && units !== 'em') ){ + // then pxValue does not apply + } else { + ret.pxValue = ( units === 'px' || !units ? (value) : (this.getEmSizeInPixels() * value) ); + } + + // normalise value in ms + if( units === 'ms' || units === 's' ){ + ret.msValue = units === 'ms' ? value : 1000 * 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: [], + mappedProperties: [], + index: i + }; + + 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 ); + this[i].properties[ property.name ] = property; // allow access by name as well + + if( property.hasPie ){ + this._private.hasPie = true; + } + + if( property.mapped ){ + this[i].mappedProperties.push( property ); + } + + // add to core style if necessary + var currentSelectorIsCore = !this[i].selector; + if( currentSelectorIsCore ){ + this._private.coreStyle[ property.name ] = property; + } + } + + return this; // chaining + }; + +})( 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; + + if( self._private.newStyle ){ // clear style caches + this._private.contextStyles = {}; + this._private.propDiffs = {}; + } + + for( var ie = 0; ie < eles.length; ie++ ){ + var ele = eles[ie]; + var cxtMeta = self.getContextMeta( ele ); + var cxtStyle = self.getContextStyle( cxtMeta ); + var app = self.applyContextStyle( cxtMeta, cxtStyle, ele ); + + self.updateTransitions( ele, app.diffProps ); + self.updateStyleHints( ele ); + + } // for elements + + self._private.newStyle = false; + }; + + $$.styfn.getPropertiesDiff = function( oldCxtKey, newCxtKey ){ + var self = this; + var cache = self._private.propDiffs = self._private.propDiffs || {}; + var dualCxtKey = oldCxtKey + '-' + newCxtKey; + var cachedVal = cache[dualCxtKey]; + + if( cachedVal ){ + return cachedVal; + } + + var diffProps = []; + var addedProp = {}; + + for( var i = 0; i < self.length; i++ ){ + var cxt = self[i]; + var oldHasCxt = oldCxtKey[i] === 't'; + var newHasCxt = newCxtKey[i] === 't'; + var cxtHasDiffed = oldHasCxt !== newHasCxt; + var cxtHasMappedProps = cxt.mappedProperties.length > 0; + + if( cxtHasDiffed || cxtHasMappedProps ){ + var props; + + if( cxtHasDiffed && cxtHasMappedProps ){ + props = cxt.properties; // suffices b/c mappedProperties is a subset of properties + } else if( cxtHasDiffed ){ + props = cxt.properties; // need to check them all + } else if( cxtHasMappedProps ){ + props = cxt.mappedProperties; // only need to check mapped + } + + for( var j = 0; j < props.length; j++ ){ + var prop = props[j]; + var name = prop.name; + + // if a later context overrides this property, then the fact that this context has switched/diffed doesn't matter + // (semi expensive check since it makes this function O(n^2) on context length, but worth it since overall result + // is cached) + var laterCxtOverrides = false; + for( var k = i + 1; k < self.length; k++ ){ + var laterCxt = self[k]; + var hasLaterCxt = newCxtKey[k] === 't'; + + if( !hasLaterCxt ){ continue; } // can't override unless the context is active + + laterCxtOverrides = laterCxt.properties[ prop.name ] != null; + + if( laterCxtOverrides ){ break; } // exit early as long as one later context overrides + } + + if( !addedProp[name] && !laterCxtOverrides ){ + addedProp[name] = true; + diffProps.push( name ); + } + } // for props + } // if + + } // for contexts + + cache[ dualCxtKey ] = diffProps; + return diffProps; + }; + + $$.styfn.getContextMeta = function( ele ){ + var self = this; + var cxtKey = ''; + var diffProps; + var prevKey = ele._private.styleCxtKey || ''; + + if( self._private.newStyle ){ + prevKey = ''; // since we need to apply all style if a fresh stylesheet + } + + // get the cxt key + for( var i = 0; i < self.length; i++ ){ + var context = self[i]; + var contextSelectorMatches = context.selector && context.selector.matches( ele ); // NB: context.selector may be null for 'core' + + if( contextSelectorMatches ){ + cxtKey += 't'; + } else { + cxtKey += 'f'; + } + } // for context + + diffProps = self.getPropertiesDiff( prevKey, cxtKey ); + + ele._private.styleCxtKey = cxtKey; + + return { + key: cxtKey, + diffPropNames: diffProps + }; + }; + + // gets a computed ele style object based on matched contexts + $$.styfn.getContextStyle = function( cxtMeta ){ + var cxtKey = cxtMeta.key; + var self = this; + var cxtStyles = this._private.contextStyles = this._private.contextStyles || {}; + + // if already computed style, returned cached copy + if( cxtStyles[cxtKey] ){ return cxtStyles[cxtKey]; } + + var style = { + _private: { + key: cxtKey + } + }; + + for( var i = 0; i < self.length; i++ ){ + var cxt = self[i]; + var hasCxt = cxtKey[i] === 't'; + + if( !hasCxt ){ continue; } + + for( var j = 0; j < cxt.properties.length; j++ ){ + var prop = cxt.properties[j]; + var styProp = style[ prop.name ] = prop; + + styProp.context = cxt; + } + } + + cxtStyles[cxtKey] = style; + return style; + }; + + $$.styfn.applyContextStyle = function( cxtMeta, cxtStyle, ele ){ + var self = this; + var diffProps = cxtMeta.diffPropNames; + var retDiffProps = {}; + + for( var i = 0; i < diffProps.length; i++ ){ + var diffPropName = diffProps[i]; + var cxtProp = cxtStyle[ diffPropName ]; + var eleProp = ele._private.style[ diffPropName ]; + + // save cycles when the context prop doesn't need to be applied + if( !cxtProp || eleProp === cxtProp ){ continue; } + + var retDiffProp = retDiffProps[ diffPropName ] = { + prev: eleProp + }; + + self.applyParsedProperty( ele, cxtProp ); + + retDiffProp.next = ele._private.style[ diffPropName ]; + + if( retDiffProp.next.bypass ){ + retDiffProp.next = retDiffProp.next.bypassed; + } + } + + return { + diffProps: retDiffProps + }; + }; + + $$.styfn.updateStyleHints = function(ele){ + var _p = ele._private; + var self = this; + var style = _p.style; + + // set whether has pie or not; for greater efficiency + var hasPie = false; + if( _p.group === 'nodes' && self._private.hasPie ){ + 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 = style['text-transform'].strValue; + var content = style['content'].strValue; + 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 valign = style['text-valign'].strValue; + var halign = style['text-valign'].strValue; + var oWidth = style['text-outline-width'].pxValue; + _p.labelKey = fStyle +'$'+ size +'$'+ family +'$'+ weight +'$'+ content +'$'+ transform +'$'+ valign +'$'+ halign +'$'+ oWidth; + _p.fontKey = fStyle +'$'+ weight +'$'+ size +'$'+ family; + + var width = style['width'].pxValue; + var height = style['height'].pxValue; + var borderW = style['border-width'].pxValue; + _p.boundingBoxKey = width +'$'+ height +'$'+ borderW; + + if( ele._private.group === 'edges' ){ + var cpss = style['control-point-step-size'].pxValue; + var cpd = style['control-point-distance'] ? style['control-point-distance'].pxValue : undefined; + var cpw = style['control-point-weight'].value; + var curve = style['curve-style'].strValue; + + _p.boundingBoxKey += '$'+ cpss +'$'+ cpd +'$'+ cpw +'$'+ curve; + } + + _p.styleKey = Date.now(); // probably safe to use applied time and much faster + // for( var i = 0; i < $$.style.properties.length; i++ ){ + // var prop = $$.style.properties[i]; + // var eleProp = _p.style[ prop.name ]; + // var val = eleProp && eleProp.strValue ? eleProp.strValue : 'undefined'; + + // _p.styleKey += '$' + val; + // } + }; + + // apply a property to the style (for internal use) + // returns whether application was successful + // + // now, this function flattens the property, and here's how: + // + // for parsedProp:{ bypass: true, deleteBypass: true } + // no property is generated, instead the bypass property in the + // element's style is replaced by what's pointed to by the `bypassed` + // field in the bypass property (i.e. restoring the property the + // bypass was overriding) + // + // for parsedProp:{ mapped: truthy } + // the generated flattenedProp:{ mapping: prop } + // + // for parsedProp:{ bypass: true } + // the generated flattenedProp:{ bypassed: parsedProp } + $$.styfn.applyParsedProperty = function( ele, parsedProp ){ + var 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: 'rgb(' + clr[0] + ', ' + clr[1] + ', ' + clr[2] + ')' + }; + + } else if( type.number ){ + var calcValue = prop.valueMin + (prop.valueMax - prop.valueMin) * percent; + flatProp = this.parse( prop.name, calcValue, prop.bypass, true ); + + } else { + return false; // can only map to colours and numbers + } + + if( !flatProp ){ // if we can't flatten the property, then use the origProp so we still keep the mapping itself + flatProp = this.parse( prop.name, origProp.strValue, prop.bypass, true ); + } + + 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 + var flatPropVal = origProp ? origProp.strValue : ''; + + flatProp = this.parse( prop.name, flatPropVal, prop.bypass, true ); + } + + flatProp.mapping = prop; // keep a reference to the mapping + prop = flatProp; // the flattened (mapped) property is the one we want + break; + + case undefined: + break; // just set the property + + default: + return false; // not a valid mapping + } + + // if the property is a bypass property, then link the resultant property to the original one + if( propIsBypass ){ + if( origPropIsBypass ){ // then this bypass overrides the existing one + prop.bypassed = origProp.bypassed; // steal bypassed prop from old bypass + } else { // then link the orig prop to the new bypass + prop.bypassed = origProp; + } + + style[ prop.name ] = prop; // and set + + } else { // prop is not bypass + if( origPropIsBypass ){ // then keep the orig prop (since it's a bypass) and link to the new prop + origProp.bypassed = prop; + } else { // then just replace the old prop with the new one + style[ prop.name ] = prop; + } + } + + return true; + }; + + // updates the visual style for all elements (useful for manual style modification after init) + $$.styfn.update = function(){ + var cy = this._private.cy; + var eles = cy.elements(); + + eles.updateStyle(); + }; + + // just update the functional properties (i.e. mappings) in the elements' + // styles (less expensive than recalculation) + $$.styfn.updateMappers = function( eles ){ + 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 ); + } + }; + + // diffProps : { name => { prev, next } } + $$.styfn.updateTransitions = function( ele, diffProps, isBypass ){ + var self = this; + var style = ele._private.style; + + var props = style['transition-property'].value; + var duration = style['transition-duration'].msValue; + var delay = style['transition-delay'].msValue; + var css = {}; + + if( props.length > 0 && duration > 0 ){ + + // build up the style to animate towards + var anyPrev = false; + for( var i = 0; i < props.length; i++ ){ + var prop = props[i]; + var styProp = style[ prop ]; + var diffProp = diffProps[ prop ]; + + if( !diffProp ){ continue; } + + var prevProp = diffProp.prev; + var fromProp = prevProp; + var toProp = diffProp.next != null ? diffProp.next : styProp; + var diff = false; + + if( !fromProp ){ 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(){ + if( !isBypass ){ + self.removeBypasses( ele, props ); + } + + ele._private.transitioning = false; + } + }); + + } else if( ele._private.transitioning ){ + ele.stop(); + + this.removeBypasses( ele, props ); + + 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, updateTransitions ){ + var props = []; + var isBypass = true; + + // put all the properties (can specify one or many) in an array after parsing them + if( name === "*" || name === "**" ){ // apply to all property names + + if( value !== undefined ){ + for( var i = 0; i < $$.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; + updateTransitions = value; + + 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]; + var style = ele._private.style; + var diffProps = {}; + var diffProp; + + for( var j = 0; j < props.length; j++ ){ // for each prop + var prop = props[j]; + + if( updateTransitions ){ + var prevProp = style[ prop.name ]; + diffProp = diffProps[ prop.name ] = { prev: prevProp }; + } + + ret = this.applyParsedProperty( ele, prop ) || ret; + + if( updateTransitions ){ + diffProp.next = style[ prop.name ]; + } + + } // for props + + if( updateTransitions ){ + this.updateTransitions( ele, diffProps, isBypass ); + } + } // for eles + + return ret; + }; + + // only useful in specific cases like animation + $$.styfn.overrideBypass = function( eles, name, value ){ + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + var prop = ele._private.style[ $$.util.camel2dash(name) ]; + + if( !prop.bypass ){ // need a bypass if one doesn't exist + this.applyBypass( ele, name, value ); + continue; + } + + prop.value = value; + prop.pxValue = value; + } + }; + + $$.styfn.removeAllBypasses = function( eles, updateTransitions ){ + var isBypass = true; + + for( var j = 0; j < eles.length; j++ ){ + var ele = eles[j]; + var diffProps = {}; + var style = ele._private.style; + + for( var i = 0; i < $$.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); + var prevProp = style[ prop.name ]; + var diffProp = diffProps[ prop.name ] = { prev: prevProp }; + + this.applyParsedProperty(ele, parsedProp); + + diffProp.next = style[ prop.name ]; + } // for props + + if( updateTransitions ){ + this.updateTransitions( ele, diffProps, isBypass ); + } + } // for eles + }; + + $$.styfn.removeBypasses = function( eles, props, updateTransitions ){ + var isBypass = true; + + for( var j = 0; j < eles.length; j++ ){ + var ele = eles[j]; + var diffProps = {}; + var style = ele._private.style; + + for( var i = 0; i < props.length; i++ ){ + var name = props[i]; + var prop = $$.style.properties[ name ]; + var value = ''; // empty => remove bypass + var parsedProp = this.parse(name, value, true); + var prevProp = style[ prop.name ]; + var diffProp = diffProps[ prop.name ] = { prev: prevProp }; + + this.applyParsedProperty(ele, parsedProp); + + diffProp.next = style[ prop.name ]; + } // for props + + if( updateTransitions ){ + this.updateTransitions( ele, diffProps, isBypass ); + } + } // for eles + }; + +})( 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, opts ){ + opts = opts || {}; + + var rstyle = opts.array ? [] : {}; + var style; + + if( $$.is.element(ele) ){ + style = ele._private.style; + } else { + 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 ){ + if( opts.array ){ + rstyle.push( styleProp ); + } else { + rstyle[ prop.name ] = styleProp; + rstyle[ $$.util.dash2camel(prop.name) ] = styleProp; + } + } + } + } + + 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 && selectorStr !== 'core' ){ + $$.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 = { + }; + + 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 = container ? container._cyreg : null; // e.g. already registered some info (e.g. readies) via jquery + reg = reg || {}; + + if( reg && reg.cy ){ + if( container ){ + while( container.firstChild ){ // clean the container + container.removeChild( container.firstChild ); + } + } + + reg.cy.notify({ type: 'destroy' }); // destroy the renderer + + reg = {}; // old instance => replace reg completely + } + + var readies = reg.readies = reg.readies || []; + + if( container ){ container._cyreg = reg; } // make sure container assoc'd reg points to this cy + reg.cy = cy; + + var head = window !== undefined && container !== undefined && !opts.headless; + var options = opts; + options.layout = $$.util.extend( { name: head ? 'grid' : 'null' }, options.layout ); + options.renderer = $$.util.extend( { name: head ? 'canvas' : 'null' }, options.renderer ); + + var defVal = function( def, val, altVal ){ + if( val !== undefined ){ + return val; + } else if( altVal !== undefined ){ + return altVal; + } else { + return def; + } + }; + + var _p = this._private = { + container: options.container, // html dom ele container + ready: false, // whether ready has been triggered + initrender: false, // has initrender has been triggered + options: options, // cached options + elements: [], // array of elements + id2index: {}, // element id => index in elements array + listeners: [], // list of listeners + aniEles: $$.Collection(this), // elements being animated + scratch: {}, // scratch object for core + layout: null, + renderer: null, + notificationsEnabled: true, // whether notifications are sent to the renderer + minZoom: 1e-50, + maxZoom: 1e50, + zoomingEnabled: defVal(true, options.zoomingEnabled), + userZoomingEnabled: defVal(true, options.userZoomingEnabled), + panningEnabled: defVal(true, options.panningEnabled), + userPanningEnabled: defVal(true, options.userPanningEnabled), + boxSelectionEnabled: defVal(true, options.boxSelectionEnabled), + autolock: defVal(false, options.autolock, options.autolockNodes), + autoungrabify: defVal(false, options.autoungrabify, options.autoungrabifyNodes), + autounselectify: defVal(false, options.autounselectify), + styleEnabled: options.styleEnabled === undefined ? head : options.styleEnabled, + zoom: $$.is.number(options.zoom) ? options.zoom : 1, + pan: { + x: $$.is.plainObject(options.pan) && $$.is.number(options.pan.x) ? options.pan.x : 0, + y: $$.is.plainObject(options.pan) && $$.is.number(options.pan.y) ? options.pan.y : 0 + }, + animation: { // object for currently-running animations + current: [], + queue: [] + }, + hasCompoundNodes: false, + deferredExecQueue: [] + }; + + // set selection type + var selType = options.selectionType; + if( selType === undefined || (selType !== 'additive' && selType !== 'single') ){ + // then set default + + 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( _p.styleEnabled ){ + this.setStyle( options.style ); + } + + // create the renderer + cy.initRenderer( $$.util.extend({ + hideEdgesOnViewport: options.hideEdgesOnViewport, + hideLabelsOnViewport: options.hideLabelsOnViewport, + textureOnViewport: options.textureOnViewport, + wheelSensitivity: $$.is.number(options.wheelSensitivity) && options.wheelSensitivity > 0 ? options.wheelSensitivity : 1, + motionBlur: options.motionBlur, + pixelRatio: $$.is.number(options.pixelRatio) && options.pixelRatio > 0 ? options.pixelRatio : (options.pixelRatio === 'auto' ? undefined : 1) + }, 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.on('ready', options.ready); + } + + // bind all the ready handlers registered before creating this instance + for( var i = 0; i < readies.length; i++ ){ + var fn = readies[i]; + cy.on('ready', fn); + } + if( reg ){ reg.readies = []; } // clear b/c we've bound them all and don't want to keep it around in case a new core uses the same div etc + + cy.trigger('ready'); + }, options.done); + }; + + $$.corefn = $$.Core.prototype; // short alias + + + $$.fn.core({ + isReady: function(){ + return this._private.ready; + }, + + ready: function( fn ){ + if( this.isReady() ){ + this.trigger('ready', [], fn); // just calls fn as though triggered via ready event + } else { + this.on('ready', fn); + } + }, + + initrender: function(){ + return this._private.initrender; + }, + + destroy: function(){ + this.notify({ type: 'destroy' }); // destroy the renderer + + var domEle = this.container(); + var parEle = domEle.parentNode; + if( parEle ){ + parEle.removeChild( domEle ); + } + + return this; + }, + + 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; + }, + + styleEnabled: function(){ + return this._private.styleEnabled; + }, + + addToPool: function( eles ){ + var elements = this._private.elements; + var id2index = this._private.id2index; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + + var id = ele._private.data.id; + var index = id2index[ id ]; + var alreadyInPool = index !== undefined; + + if( !alreadyInPool ){ + index = elements.length; + elements.push( ele ); + id2index[ id ] = index; + ele._private.index = index; + } + } + + return this; // chaining + }, + + removeFromPool: function( eles ){ + var elements = this._private.elements; + var id2index = this._private.id2index; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + + var id = ele._private.data.id; + var index = id2index[ id ]; + var inPool = index !== undefined; + + if( inPool ){ + this._private.id2index[ id ] = undefined; + elements.splice(index, 1); + + // adjust the index of all elements past this index + for( var j = index; j < elements.length; j++ ){ + var jid = elements[j]._private.data.id; + id2index[ jid ]--; + } + } + } + }, + + container: function(){ + return this._private.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() ); + }); + + if( this._private.styleEnabled ){ + json.style = cy.style().json(); + } + + json.zoomingEnabled = cy._private.zoomingEnabled; + json.userZoomingEnabled = cy._private.userZoomingEnabled; + json.zoom = cy._private.zoom; + json.minZoom = cy._private.minZoom; + json.maxZoom = cy._private.maxZoom; + json.panningEnabled = cy._private.panningEnabled; + json.userPanningEnabled = cy._private.userPanningEnabled; + json.pan = 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; + json.wheelSensitivity = cy._private.options.wheelSensitivity; + json.motionBlur = cy._private.options.motionBlur; + + return json; + }, + + // defer execution until not busy and guarantee relative execution order of deferred functions + defer: function( fn ){ + var cy = this; + var _p = cy._private; + var q = _p.deferredExecQueue; + + q.push( fn ); + + if( !_p.deferredTimeout ){ + _p.deferredTimeout = setTimeout(function(){ + while( q.length > 0 ){ + ( q.shift() )(); + } + + _p.deferredTimeout = null; + }, 0); + } + } + + }); + +})( 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]; + json.group = group; + + jsons.push( json ); + } + } + } + + elements = new $$.Collection(cy, jsons); + } + + // specify options for one element + else { + var json = opts; + elements = (new $$.Element( cy, json )).collection(); + } + + return elements; + }, + + remove: function(collection){ + if( $$.is.elementOrCollection(collection) ){ + collection = collection; + } else if( $$.is.string(collection) ){ + var selector = collection; + collection = this.$( selector ); + } + + return collection.remove(); + }, + + load: function(elements, onload, ondone){ + var cy = this; + + // 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'); + }); + + var layoutOpts = $$.util.extend({}, cy._private.options.layout); + layoutOpts.eles = cy.$(); + + cy.layout( layoutOpts ); + + } + + if( window ){ + ready( callback ); + } else { + callback(); + } + + return this; + } + }); + +})( cytoscape, typeof window === 'undefined' ? null : window ); + +;(function($$, window){ 'use strict'; + + $$.fn.core({ + + // pull in animation functions + animated: $$.define.animated(), + clearQueue: $$.define.clearQueue(), + delay: $$.define.delay(), + animate: $$.define.animate(), + stop: $$.define.stop(), + + addToAnimationPool: function( eles ){ + var cy = this; + + if( !cy.styleEnabled() ){ return; } // save cycles when no style used + + cy._private.aniEles.merge( eles ); + }, + + startAnimationLoop: function(){ + var cy = this; + + if( !cy.styleEnabled() ){ return; } // save cycles when no style used + + // don't execute the animation loop in headless environments + if( !window ){ + return; + } + + function globalAnimationStep(){ + $$.util.requestAnimationFrame(function(now){ + handleElements(now); + globalAnimationStep(); + }); + } + + globalAnimationStep(); // first call + + function handleElements(now){ + now = +new Date(); + + var eles = cy._private.aniEles; + var doneEles = []; + + function handleElement( ele, isCore ){ + var current = ele._private.animation.current; + var queue = ele._private.animation.queue; + var ranAnis = false; + + // if nothing currently animating, get something from the queue + if( current.length === 0 ){ + var next = queue.length > 0 ? queue.shift() : null; + + if( next ){ + next.callTime = now; // was queued, so update call time + current.push( next ); + } + } + + // step and remove if done + var completes = []; + for(var i = current.length - 1; i >= 0; i--){ + var ani = current[i]; + + // start if need be + if( !ani.started ){ startAnimation( ele, ani ); } + + step( ele, ani, now, isCore ); + + if( ani.done ){ + completes.push( ani ); + + // remove current[i] + current.splice(i, 1); + } + + ranAnis = true; + } + + // 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 ] ); + } + } + + if( !isCore && current.length === 0 && queue.length === 0 ){ + doneEles.push( ele ); + } + + return ranAnis; + } // handleElements + + // handle all eles + for( var e = 0; e < eles.length; e++ ){ + var ele = eles[e]; + + handleElement( ele ); + } // each element + + var ranCoreAni = handleElement( cy, true ); + + // notify renderer + if( eles.length > 0 || ranCoreAni ){ + var toNotify; + + if( eles.length > 0 ){ + var updatedEles = eles.updateCompoundBounds(); + toNotify = updatedEles.length > 0 ? eles.add( updatedEles ) : eles; + } + + cy.notify({ + type: 'draw', + collection: toNotify + }); + } + + // remove elements from list of currently animating if its queues are empty + eles.unmerge( doneEles ); + + } // handleElements + + function startAnimation( self, ani ){ + var isCore = $$.is.core( self ); + var isEles = !isCore; + var ele = self; + var style = cy._private.style; + + if( isEles ){ + var pos = ele._private.position; + var startPosition = { + x: pos.x, + y: pos.y + }; + var startStyle = style.getValueStyle( ele ); + } + + if( isCore ){ + var pan = cy._private.pan; + var startPan = { + x: pan.x, + y: pan.y + }; + + var startZoom = cy._private.zoom; + } + + ani.started = true; + ani.startTime = Date.now(); + ani.startPosition = startPosition; + ani.startStyle = startStyle; + ani.startPan = startPan; + ani.startZoom = startZoom; + } + + function step( self, animation, now, isCore ){ + var style = cy._private.style; + var properties = animation.properties; + var params = animation.params; + var startTime = animation.startTime; + var percent; + var isEles = !isCore; + + 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 + + var startPos = animation.startPosition; + var endPos = properties.position; + var pos = self._private.position; + if( endPos && isEles ){ + 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 ); + } + } + + var startPan = animation.startPan; + var endPan = properties.pan; + var pan = self._private.pan; + var animatingPan = endPan != null && isCore; + if( animatingPan ){ + if( valid( startPan.x, endPan.x ) ){ + pan.x = ease( startPan.x, endPan.x, percent ); + } + + if( valid( startPan.y, endPan.y ) ){ + pan.y = ease( startPan.y, endPan.y, percent ); + } + + self.trigger('pan'); + } + + var startZoom = animation.startZoom; + var endZoom = properties.zoom; + var animatingZoom = endZoom != null && isCore; + if( animatingZoom ){ + if( valid( startZoom, endZoom ) ){ + self._private.zoom = ease( startZoom, endZoom, percent ); + } + + self.trigger('zoom'); + } + + if( animatingPan || animatingZoom ){ + self.trigger('viewport'); + } + + if( properties.css && isEles ){ + var props = properties.css; + + for( var i = 0; i < props.length; i++ ){ + var name = props[i].name; + var prop = props[i]; + var end = prop; + + var start = animation.startStyle[ name ]; + var easedVal = ease( start, end, percent ); + + style.overrideBypass( 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(startProp, endProp, percent){ + if( percent < 0 ){ + percent = 0; + } else if( percent > 1 ){ + percent = 1; + } + + var start, end; + + if( startProp.pxValue != null || startProp.value != null ){ + start = startProp.pxValue != null ? startProp.pxValue : startProp.value; + } else { + start = startProp; + } + + if( endProp.pxValue != null || endProp.value != null ){ + end = endProp.pxValue != null ? endProp.pxValue : endProp.value; + } else { + end = endProp; + } + + 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 [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 + }), + + 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 layout = this._private.prevLayout = ( params == null ? this._private.prevLayout : this.initLayout( params ) ); + + layout.run(); + + return this; // chaining + }, + + makeLayout: function( params ){ + return this.initLayout( params ); + }, + + initLayout: function( options ){ + if( options == null ){ + $$.util.error('Layout options must be specified to make a layout'); + return; + } + + if( options.name == null ){ + $$.util.error('A `name` must be specified to make a layout'); + return; + } + + var name = options.name; + var LayoutProto = $$.extension('layout', name); + + if( LayoutProto == null ){ + $$.util.error('Can not apply layout: No such layout `' + name + '` found; did you include its JS file?'); + return; + } + + options.eles = options.eles != null ? options.eles : this.$(); + + if( $$.is.string( options.eles ) ){ + options.eles = this.$( options.eles ); + } + + var layout = new LayoutProto( $$.util.extend({}, options, { + cy: this + }) ); + + // make sure layout has _private for use w/ std apis like .on() + if( !$$.is.plainObject(layout._private) ){ + layout._private = {}; + } + + layout._private.cy = this; + layout._private.listeners = []; + + return layout; + } + + }); + +})( cytoscape ); +(function($$){ 'use strict'; + + $$.fn.core({ + notify: function( params ){ + if( this._private.batchingNotify ){ + var bEles = this._private.batchNotifyEles; + var bTypes = this._private.batchNotifyTypes; + + if( params.collection ){ for( var i = 0; i < params.collection.length; i++ ){ + var ele = params.collection[i]; + + if( !bEles.ids[ ele._private.id ] ){ + bEles.push( ele ); + } + } } + + if( !bTypes.ids[ params.type ] ){ + bTypes.push( params.type ); + } + + return; // notifications are disabled during batching + } + + if( !this._private.notificationsEnabled ){ return; } // exit on disabled + + var renderer = this.renderer(); + + renderer.notify(params); + }, + + notifications: function( bool ){ + var p = this._private; + + if( bool === undefined ){ + return p.notificationsEnabled; + } else { + p.notificationsEnabled = bool ? true : false; + } + }, + + noNotifications: function( callback ){ + this.notifications(false); + callback(); + this.notifications(true); + }, + + startBatch: function(){ + var _p = this._private; + + _p.batchingStyle = _p.batchingNotify = true; + _p.batchStyleEles = []; + _p.batchNotifyEles = []; + _p.batchNotifyTypes = []; + + _p.batchStyleEles.ids = {}; + _p.batchNotifyEles.ids = {}; + _p.batchNotifyTypes.ids = {}; + + return this; + }, + + endBatch: function(){ + var _p = this._private; + + // update style for dirty eles + _p.batchingStyle = false; + new $$.Collection(this, _p.batchStyleEles).updateStyle(); + + // notify the renderer of queued eles and event types + _p.batchingNotify = false; + this.notify({ + type: _p.batchNotifyTypes, + collection: _p.batchNotifyEles + }); + + return this; + }, + + batch: function( callback ){ + this.startBatch(); + callback(); + this.endBatch(); + + return this; + }, + + // for backwards compatibility + batchData: function( map ){ + var cy = this; + + return this.batch(function(){ + for( var id in map ){ + var data = map[id]; + var ele = cy.getElementById( id ); + + ele.data( data ); + } + }); + } + }); + +})( 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' + }); + + this.trigger('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 + }) + ); + + } + + }); + +})( 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(); + + } else if( $$.is.array( eles ) ){ + return new $$.Collection( this, eles ); + } + + return new $$.Collection( this ); + }, + + nodes: function( selector ){ + var nodes = this.$(function(){ + return this.isNode(); + }); + + if( selector ){ + return nodes.filter( selector ); + } + + return nodes; + }, + + edges: function( selector ){ + var edges = this.$(function(){ + return this.isEdge(); + }); + + if( selector ){ + return edges.filter( selector ); + } + + return edges; + }, + + // search the graph like jQuery + $: function( selector ){ + var eles = new $$.Collection( this, this._private.elements ); + + if( selector ){ + return eles.filter( selector ); + } + + return eles; + } + + }); + + // aliases + $$.corefn.elements = $$.corefn.filter = $$.corefn.$; + +})( cytoscape ); + +;(function($$){ 'use strict'; + + $$.fn.core({ + + style: function( newStyle ){ + if( newStyle ){ + var s = this.setStyle( newStyle ); + + s.update(); + } + + return this._private.style; + }, + + setStyle: function( style ){ + var _p = this._private; + + if( $$.is.stylesheet(style) ){ + _p.style = style.generateStyle(this); + + } else if( $$.is.array(style) ) { + _p.style = $$.style.fromJson(this, style); + + } else if( $$.is.string(style) ){ + _p.style = $$.style.fromString(this, style); + + } else { + _p.style = new $$.Style( this ); + } + + return _p.style; + } + }); + +})( cytoscape ); + + +;(function($$){ 'use strict'; + + $$.fn.core({ + + autolock: function(bool){ + if( bool !== undefined ){ + this._private.autolock = bool ? true : false; + } else { + return this._private.autolock; + } + + return this; // chaining + }, + + autoungrabify: function(bool){ + if( bool !== undefined ){ + this._private.autoungrabify = bool ? true : false; + } else { + return this._private.autoungrabify; + } + + return this; // chaining + }, + + autounselectify: function(bool){ + if( bool !== undefined ){ + this._private.autounselectify = bool ? true : false; + } else { + return this._private.autounselectify; + } + + return this; // chaining + }, + + panningEnabled: function( bool ){ + if( bool !== undefined ){ + this._private.panningEnabled = bool ? true : false; + } else { + return this._private.panningEnabled; + } + + return this; // chaining + }, + + userPanningEnabled: function( bool ){ + if( bool !== undefined ){ + this._private.userPanningEnabled = bool ? true : false; + } else { + return this._private.userPanningEnabled; + } + + return this; // chaining + }, + + zoomingEnabled: function( bool ){ + if( bool !== undefined ){ + this._private.zoomingEnabled = bool ? true : false; + } else { + return this._private.zoomingEnabled; + } + + return this; // chaining + }, + + userZoomingEnabled: function( bool ){ + if( bool !== undefined ){ + this._private.userZoomingEnabled = bool ? true : false; + } else { + return this._private.userZoomingEnabled; + } + + return this; // chaining + }, + + boxSelectionEnabled: function( bool ){ + if( bool !== undefined ){ + this._private.boxSelectionEnabled = bool ? true : false; + } else { + return this._private.boxSelectionEnabled; + } + + return this; // chaining + }, + + pan: function(){ + var args = arguments; + var pan = this._private.pan; + var dim, val, dims, x, y; + + switch( args.length ){ + case 0: // .pan() + return pan; + + case 1: + + if( $$.is.string( args[0] ) ){ // .pan('x') + dim = args[0]; + return pan[ dim ]; + + } else if( $$.is.plainObject( args[0] ) ) { // .pan({ x: 0, y: 100 }) + if( !this._private.panningEnabled ){ + return this; + } + + dims = args[0]; + x = dims.x; + y = dims.y; + + if( $$.is.number(x) ){ + pan.x = x; + } + + if( $$.is.number(y) ){ + pan.y = y; + } + + this.trigger('pan viewport'); + } + break; + + case 2: // .pan('x', 100) + if( !this._private.panningEnabled ){ + return this; + } + + dim = args[0]; + val = args[1]; + + if( (dim === 'x' || dim === 'y') && $$.is.number(val) ){ + pan[dim] = val; + } + + this.trigger('pan viewport'); + break; + + default: + break; // invalid + } + + this.notify({ // notify the renderer that the viewport changed + type: 'viewport' + }); + + return this; // chaining + }, + + panBy: function(params){ + var args = arguments; + var pan = this._private.pan; + var dim, val, dims, x, y; + + if( !this._private.panningEnabled ){ + return this; + } + + switch( args.length ){ + case 1: + + if( $$.is.plainObject( args[0] ) ) { // .panBy({ x: 0, y: 100 }) + dims = args[0]; + x = dims.x; + y = dims.y; + + if( $$.is.number(x) ){ + pan.x += x; + } + + if( $$.is.number(y) ){ + pan.y += y; + } + + this.trigger('pan viewport'); + } + break; + + case 2: // .panBy('x', 100) + dim = args[0]; + val = args[1]; + + if( (dim === 'x' || dim === 'y') && $$.is.number(val) ){ + pan[dim] += val; + } + + this.trigger('pan viewport'); + break; + + default: + break; // invalid + } + + this.notify({ // notify the renderer that the viewport changed + type: 'viewport' + }); + + return this; // chaining + }, + + fit: function( elements, padding ){ + var viewportState = this.getFitViewport( elements, padding ); + + if( viewportState ){ + var _p = this._private; + _p.zoom = viewportState.zoom; + _p.pan = viewportState.pan; + + this.trigger('pan zoom viewport'); + + this.notify({ // notify the renderer that the viewport changed + type: 'viewport' + }); + } + + return this; // chaining + }, + + getFitViewport: function( elements, padding ){ + if( $$.is.number(elements) && padding === undefined ){ // elements is optional + padding = elements; + elements = undefined; + } + + if( !this._private.panningEnabled || !this._private.zoomingEnabled ){ + return; + } + + var bb; + + if( $$.is.string(elements) ){ + var sel = elements; + elements = this.$( sel ); + + } else if( $$.is.boundingBox(elements) ){ // assume bb + var bbe = elements; + bb = { + x1: bbe.x1, + y1: bbe.y1, + x2: bbe.x2, + y2: bbe.y2 + }; + + bb.w = bb.x2 - bb.x1; + bb.h = bb.y2 - bb.y1; + + } else if( !$$.is.elementOrCollection(elements) ){ + elements = this.elements(); + } + + bb = bb || elements.boundingBox(); + + var w = this.width(); + var h = this.height(); + var zoom; + padding = $$.is.number(padding) ? padding : 0; + + if( !isNaN(w) && !isNaN(h) && w > 0 && h > 0 && !isNaN(bb.w) && !isNaN(bb.h) && bb.w > 0 && bb.h > 0 ){ + zoom = Math.min( (w - 2*padding)/bb.w, (h - 2*padding)/bb.h ); + + // crop zoom + zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom; + zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom; + + var pan = { // now pan to middle + x: (w - zoom*( bb.x1 + bb.x2 ))/2, + y: (h - zoom*( bb.y1 + bb.y2 ))/2 + }; + + return { + zoom: zoom, + pan: pan + }; + } + + return; + }, + + minZoom: function( zoom ){ + if( zoom === undefined ){ + return this._private.minZoom; + } else if( $$.is.number(zoom) ){ + this._private.minZoom = zoom; + } + + return this; + }, + + maxZoom: function( zoom ){ + if( zoom === undefined ){ + return this._private.maxZoom; + } else if( $$.is.number(zoom) ){ + this._private.maxZoom = zoom; + } + + return this; + }, + + zoom: function( params ){ + var pos; // in rendered px + var zoom; + + if( params === undefined ){ // then get the zoom + return this._private.zoom; + + } else if( $$.is.number(params) ){ // then set the zoom + zoom = params; + + } else if( $$.is.plainObject(params) ){ // then zoom about a point + zoom = params.level; + + if( params.position ){ + var p = params.position; + var pan = this._private.pan; + var z = this._private.zoom; + + pos = { // convert to rendered px + x: p.x * z + pan.x, + y: p.y * z + pan.y + }; + } else if( params.renderedPosition ){ + pos = params.renderedPosition; + } + + if( pos && !this._private.panningEnabled ){ + return this; // panning disabled + } + } + + if( !this._private.zoomingEnabled ){ + return this; // zooming disabled + } + + if( !$$.is.number(zoom) || ( pos && (!$$.is.number(pos.x) || !$$.is.number(pos.y)) ) ){ + return this; // can't zoom with invalid params + } + + // crop zoom + zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom; + zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom; + + if( pos ){ // set zoom about position + var pan1 = this._private.pan; + var zoom1 = this._private.zoom; + var zoom2 = zoom; + + var pan2 = { + x: -zoom2/zoom1 * (pos.x - pan1.x) + pos.x, + y: -zoom2/zoom1 * (pos.y - pan1.y) + pos.y + }; + + this._private.zoom = zoom; + this._private.pan = pan2; + + var posChanged = pan1.x !== pan2.x || pan1.y !== pan2.y; + this.trigger(' zoom ' + (posChanged ? ' pan ' : '') + ' viewport ' ); + + } else { // just set the zoom + this._private.zoom = zoom; + this.trigger('zoom viewport'); + } + + this.notify({ // notify the renderer that the viewport changed + type: 'viewport' + }); + + return this; // chaining + }, + + viewport: function( opts ){ + var _p = this._private; + var zoomDefd = true; + var panDefd = true; + var events = []; // to trigger + var zoomFailed = false; + var panFailed = false; + + if( !opts ){ return this; } + if( !$$.is.number(opts.zoom) ){ zoomDefd = false; } + if( !$$.is.plainObject(opts.pan) ){ panDefd = false; } + if( !zoomDefd && !panDefd ){ return this; } + + if( zoomDefd ){ + var z = opts.zoom; + + if( z < _p.minZoom || z > _p.maxZoom || !_p.zoomingEnabled ){ + zoomFailed = true; + + } else { + _p.zoom = z; + + events.push('zoom'); + } + } + + if( panDefd && (!zoomFailed || !opts.cancelOnFailedZoom) && _p.panningEnabled ){ + var p = opts.pan; + + if( $$.is.number(p.x) ){ + _p.pan.x = p.x; + panFailed = false; + } + + if( $$.is.number(p.y) ){ + _p.pan.y = p.y; + panFailed = false; + } + + if( !panFailed ){ + events.push('pan'); + } + } + + if( events.length > 0 ){ + events.push('viewport'); + this.trigger( events.join(' ') ); + + this.notify({ + type: 'viewport' + }); + } + + return this; // chaining + }, + + center: function( elements ){ + var pan = this.getCenterPan( elements ); + + if( pan ){ + this._private.pan = pan; + + this.trigger('pan viewport'); + + this.notify({ // notify the renderer that the viewport changed + type: 'viewport' + }); + } + + return this; // chaining + }, + + getCenterPan: function( elements ){ + if( !this._private.panningEnabled ){ + return; + } + + if( $$.is.string(elements) ){ + var selector = elements; + elements = this.elements( selector ); + } else if( !$$.is.elementOrCollection(elements) ){ + elements = this.elements(); + } + + var bb = elements.boundingBox(); + var w = this.width(); + var h = this.height(); + var zoom = this._private.zoom; + + var pan = { // middle + x: (w - zoom*( bb.x1 + bb.x2 ))/2, + y: (h - zoom*( bb.y1 + bb.y2 ))/2 + }; + + return pan; + }, + + reset: function(){ + if( !this._private.panningEnabled || !this._private.zoomingEnabled ){ + return this; + } + + this.viewport({ + pan: { x: 0, y: 0 }, + zoom: 1 + }); + + return this; // chaining + }, + + width: function(){ + var container = this._private.container; + + if( container ){ + return container.clientWidth; + } + + return 1; // fallback if no container (not 0 b/c can be used for dividing etc) + }, + + height: function(){ + var container = this._private.container; + + if( container ){ + return container.clientHeight; + } + + return 1; // fallback if no container (not 0 b/c can be used for dividing etc) + }, + + extent: function(){ + var pan = this._private.pan; + var zoom = this._private.zoom; + var rb = this.renderedExtent(); + + var b = { + x1: ( rb.x1 - pan.x )/zoom, + x2: ( rb.x2 - pan.x )/zoom, + y1: ( rb.y1 - pan.y )/zoom, + y2: ( rb.y2 - pan.y )/zoom, + }; + + b.w = b.x2 - b.x1; + b.h = b.y2 - b.y1; + + return b; + }, + + renderedExtent: function(){ + var width = this.width(); + var height = this.height(); + + return { + x1: 0, + y1: 0, + x2: width, + y2: height, + w: width, + h: height + }; + } + }); + + // aliases + $$.corefn.centre = $$.corefn.center; + + // backwards compatibility + $$.corefn.autolockNodes = $$.corefn.autolock; + $$.corefn.autoungrabifyNodes = $$.corefn.autoungrabifyNodes; + +})( 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 || {}, // (x, y) position pair + autoWidth: undefined, // width and height of nodes calculated by the renderer when set to special 'auto' value + autoHeight: undefined, + listeners: [], // array of bound listeners + group: 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, options){ + 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 indexes = {}; + var createdElements = false; + + if( !elements ){ + elements = []; + } else if( elements.length > 0 && $$.is.plainObject( elements[0] ) && !$$.is.element( elements[0] ) ){ + createdElements = true; + + // make elements from json and restore all at once later + var eles = []; + var elesIds = {}; + + for( var i = 0, l = elements.length; i < l; i++ ){ + var json = elements[i]; + + if( json.data == null ){ + json.data = {}; + } + + var data = json.data; + + // make sure newly created elements have valid ids + if( data.id == null ){ + data.id = idFactory.generate( cy, json ); + } else if( cy.getElementById( data.id ).length !== 0 || elesIds[ data.id ] ){ + continue; // can't create element if prior id already exists + } + + var ele = new $$.Element( cy, json, false ); + eles.push( ele ); + elesIds[ data.id ] = true; + } + + elements = eles; + } + + this.length = 0; + + for( var i = 0, l = elements.length; i < l; i++ ){ + var element = elements[i]; + if( !element ){ continue; } + + var id = element._private.data.id; + + if( !options || (options.unique && !ids[ id ] ) ){ + ids[ id ] = element; + indexes[ id ] = this.length; + + this[ this.length ] = element; + this.length++; + } + } + + this._private = { + cy: cy, + ids: ids, + indexes: indexes + }; + + // restore the elements if we created them from json + if( createdElements ){ + this.restore(); + } + }; + + + // Functions + //////////////////////////////////////////////////////////////////////////////////////////////////// + + // keep the prototypes in sync (an element has the same functions as a collection) + // and use $$.elefn and $$.elesfn as shorthands to the prototypes + $$.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.unique = function(){ + return new $$.Collection( this._private.cy, this, { unique: true } ); + }; + + $$.elesfn.getElementById = function( id ){ + var cy = this._private.cy; + var ele = this._private.ids[ id ]; + + return ele ? ele : $$.Collection(cy); // get ele or empty collection + }; + + $$.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 ){ + if( p.classes[cls] ){ + 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.clone = function(){ + var cy = this.cy(); + var elesArr = []; + + for( var i = 0; i < this.length; i++ ){ + var ele = this[i]; + var json = ele.json(); + var clone = new $$.Element(cy, json, false); // NB no restore + + elesArr.push( clone ); + } + + return new $$.Collection( cy, elesArr ); + }; + + $$.elesfn.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 + data.parent = undefined; + } else { + var selfAsParent = false; + var ancestor = parent; + while( !ancestor.empty() ){ + if( node.same(ancestor) ){ + // mark self as parent and remove from data + selfAsParent = true; + data.parent = undefined; // remove parent reference + + // exit or we loop forever + break; + } + + ancestor = ancestor.parent(); + } + + if( !selfAsParent ){ + // connect with children + parent[0]._private.children.push( node ); + node._private.parent = parent[0]; + + // let the core know we have a compound graph + cy._private.hasCompoundNodes = true; + } + } // else + } // if specified parent + } // for each node + + restored = new $$.Collection( cy, restored ); + if( restored.length > 0 ){ + + var toUpdateStyle = restored.add( restored.connectedNodes() ).add( restored.parent() ); + toUpdateStyle.updateStyle( notifyRenderer ); + + if( notifyRenderer ){ + restored.rtrigger('add'); + } else { + restored.trigger('add'); + } + } + + return self; // chainability + }; + + $$.elesfn.removed = function(){ + var ele = this[0]; + return ele && ele._private.removed; + }; + + $$.elesfn.inside = function(){ + var ele = this[0]; + return ele && !ele._private.removed; + }; + + $$.elesfn.remove = function( notifyRenderer ){ + var self = this; + var removed = []; + var elesToRemove = []; + var elesToRemoveIds = {}; + var cy = self._private.cy; + + if( notifyRenderer === undefined ){ + notifyRenderer = true; + } + + // add connected edges + function addConnectedEdges(node){ + var edges = node._private.edges; + for( var i = 0; i < edges.length; i++ ){ + add( edges[i] ); + } + } + + + // add descendant nodes + function addChildren(node){ + var children = node._private.children; + + for( var i = 0; i < children.length; i++ ){ + add( children[i] ); + } + } + + function add( ele ){ + var alreadyAdded = elesToRemoveIds[ ele.id() ]; + if( alreadyAdded ){ + return; + } else { + elesToRemoveIds[ ele.id() ] = true; + } + + if( ele.isNode() ){ + elesToRemove.push( ele ); // nodes are removed last + + addConnectedEdges( ele ); + addChildren( ele ); + } else { + elesToRemove.unshift( ele ); // edges are removed first + } + } + + // make the list of elements to remove + // (may be removing more than specified due to connected edges etc) + + for( var i = 0, l = self.length; i < l; i++ ){ + var ele = self[i]; + + add( ele ); + } + + function removeEdgeRef(node, edge){ + var connectedEdges = node._private.edges; + for( var j = 0; j < connectedEdges.length; j++ ){ + var connectedEdge = connectedEdges[j]; + + if( edge === connectedEdge ){ + connectedEdges.splice( j, 1 ); + break; + } + } + } + + function removeChildRef(parent, ele){ + ele = ele[0]; + parent = parent[0]; + var children = parent._private.children; + + for( var j = 0; j < children.length; j++ ){ + if( children[j][0] === ele[0] ){ + children.splice(j, 1); + break; + } + } + } + + for( var i = 0; i < elesToRemove.length; i++ ){ + var ele = elesToRemove[i]; + + // mark as removed + ele._private.removed = true; + + // remove from core pool + cy.removeFromPool( ele ); + + // add to list of removed elements + removed.push( ele ); + + if( ele.isEdge() ){ // remove references to this edge in its connected nodes + var src = ele.source()[0]; + var tgt = ele.target()[0]; + + removeEdgeRef( src, ele ); + removeEdgeRef( tgt, ele ); + + } else { // remove reference to parent + var parent = ele.parent(); + + if( parent.length !== 0 ){ + removeChildRef(parent, ele); + } + } + } + + // check to see if we have a compound graph or not + var elesStillInside = cy._private.elements; + cy._private.hasCompoundNodes = false; + for( var i = 0; i < elesStillInside.length; i++ ){ + var ele = elesStillInside[i]; + + if( ele.isParent() ){ + cy._private.hasCompoundNodes = true; + break; + } + } + + var removedElements = new $$.Collection( this.cy(), removed ); + if( removedElements.size() > 0 ){ + // must manually notify since trigger won't do this automatically once removed + + if( notifyRenderer ){ + this.cy().notify({ + type: 'remove', + collection: removedElements + }); + } + + removedElements.trigger('remove'); + } + + // check for empty remaining parent nodes + var checkedParentId = {}; + for( var i = 0; i < elesToRemove.length; i++ ){ + var ele = elesToRemove[i]; + var isNode = ele._private.group === 'nodes'; + var parentId = ele._private.data.parent; + + if( isNode && parentId !== undefined && !checkedParentId[ parentId ] ){ + checkedParentId[ parentId ] = true; + var parent = cy.getElementById( parentId ); + + if( parent && parent.length !== 0 && !parent._private.removed && parent.children().length === 0 ){ + parent.updateStyle(); + } + } + } + + return this; + }; + + $$.elesfn.move = function( struct ){ + var cy = this._private.cy; + + if( struct.source !== undefined || struct.target !== undefined ){ + var srcId = struct.source; + var tgtId = struct.target; + var srcExists = cy.getElementById(srcId).length > 0; + var tgtExists = cy.getElementById(tgtId).length > 0; + + if( srcExists || tgtExists ){ + var jsons = this.jsons(); + + this.remove(); + + for( var i = 0; i < jsons.length; i++ ){ + var json = jsons[i]; + + if( json.group === 'edges' ){ + if( srcExists ){ json.data.source = srcId; } + if( tgtExists ){ json.data.target = tgtId; } + } + } + + return cy.add( jsons ); + } + + } else if( struct.parent !== undefined ){ // move node to new parent + var parentId = struct.parent; + var parentExists = struct.parent === null || cy.getElementById( parentId ).length > 0; + + if( parentExists ){ + var jsons = this.jsons(); + + this.remove(); + + for( var i = 0; i < this.length; i++ ){ + var json = jsons[i]; + + if( json.group === 'nodes' ){ + json.data.parent = parentId === null ? undefined : parentId; + } + } + } + + return cy.add( jsons ); + } + + return this; // if nothing done + }; + +})( cytoscape ); + + +;(function($$){ 'use strict'; + + // search, spanning trees, etc + $$.fn.eles({ + + // std functional ele first callback style + stdBreadthFirstSearch: function( options ){ + options = $$.util.extend( {}, options, { + std: true + } ); + + return this.breadthFirstSearch( options ); + }, + + // do a breadth first search from the nodes in the collection + // from pseudocode on wikipedia + breadthFirstSearch: function( roots, fn, directed ){ + var options; + var std; + var thisArg; + if( $$.is.plainObject(roots) && !$$.is.elementOrCollection(roots) ){ + options = roots; + roots = options.roots; + fn = options.visit; + directed = options.directed; + std = options.std; + thisArg = options.thisArg; + } + + directed = arguments.length === 2 && !$$.is.fn(fn) ? fn : directed; + fn = $$.is.fn(fn) ? fn : function(){}; + + var cy = this._private.cy; + var v = $$.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 prevEdge = connectedBy[ v.id() ]; + var prevNode = prevEdge == null ? undefined : prevEdge.connectedNodes().not( v )[0]; + var ret; + + if( std ){ + ret = fn.call(thisArg, v, prevEdge, prevNode, j++, depth); + } else { + ret = fn.call(v, j++, depth, v, prevEdge, prevNode); + } + + if( ret === true ){ + found = v; + break; + } + + if( ret === false ){ + break; + } + + var vwEdges = v.connectedEdges(directed ? function(){ return this.data('source') === v.id(); } : undefined).intersect( edges ); + for( var i = 0; i < vwEdges.length; i++ ){ + var e = vwEdges[i]; + var w = e.connectedNodes(function(){ return this.id() !== v.id(); }).intersect( nodes ); + + if( w.length !== 0 && !V[ w.id() ] ){ + w = w[0]; + + Q.push( w ); + 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, { unique: true } ), + found: new $$.Collection( cy, found, { unique: true } ) + }; + }, + + // std functional ele first callback style + stdDepthFirstSearch: function( options ){ + options = $$.util.extend( {}, options, { + std: true + } ); + + return this.depthFirstSearch( options ); + }, + + // do a depth first search on the nodes in the collection + // from pseudocode on wikipedia (iterative impl) + depthFirstSearch: function( roots, fn, directed ){ + var options; + var std; + var thisArg; + if( $$.is.plainObject(roots) && !$$.is.elementOrCollection(roots) ){ + options = roots; + roots = options.roots; + fn = options.visit; + directed = options.directed; + std = options.std; + thisArg = options.thisArg; + } + + directed = arguments.length === 2 && !$$.is.fn(fn) ? fn : directed; + fn = $$.is.fn(fn) ? fn : function(){}; + var cy = this._private.cy; + var v = $$.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 prevEdge = connectedBy[ v.id() ]; + var prevNode = prevEdge == null ? undefined : prevEdge.connectedNodes().not( v )[0]; + var ret; + + if( std ){ + ret = fn.call(thisArg, v, prevEdge, prevNode, j++, depth); + } else { + ret = fn.call(v, j++, depth, v, prevEdge, prevNode); + } + + if( ret === true ){ + found = v; + break; + } + + if( ret === false ){ + break; + } + + var vwEdges = v.connectedEdges(directed ? function(){ return this.data('source') === v.id(); } : undefined).intersect( edges ); + + for( var i = 0; i < vwEdges.length; i++ ){ + var e = vwEdges[i]; + var w = e.connectedNodes(function(){ return this.id() !== v.id(); }).intersect( nodes ); + + if( w.length !== 0 && !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, { unique: true } ), + found: new $$.Collection( cy, found, { unique: true } ) + }; + }, + + // 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, a); + var weightB = weightFn.call(b, b); + + return weightA - weightB; + }); + + for(var i = 0; i < S.length; i++){ + var edge = S[i]; + var u = edge.source()[0]; + var v = edge.target()[0]; + var setU = findSet(u); + var setV = findSet(v); + + if( setU.index !== setV.index ){ + A = A.add( edge ); + + // combine forests for u and v + forest[ setU.index ] = setU.eles.add( setV.eles ); + forest.splice( setV.index, 1 ); + } + } + + return nodes.add( A ); + + }, + + dijkstra: function( root, weightFn, directed ){ + var options; + if( $$.is.plainObject(root) && !$$.is.elementOrCollection(root) ){ + options = root; + root = options.root; + weightFn = options.weight; + directed = options.directed; + } + + var cy = this._private.cy; + 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.apply( edge, [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; + $$.elesfn.stdBfs = $$.elesfn.stdBreadthFirstSearch; + $$.elesfn.stdDfs = $$.elesfn.stdDepthFirstSearch; + +})( cytoscape ); +;(function($$) { + 'use strict'; + + // Additional graph analysis algorithms + $$.fn.eles({ + + // Implemented from pseudocode from wikipedia + + // options => options object + // root // starting node (either element or selector string) + // weight: function( edge ){} // specifies weight to use for `edge`/`this`. If not present, it will be asumed a weight of 1 for all edges + // heuristic: function( node ){} // specifies heuristic value for `node`/`this` + // directed // default false + // goal // target node (either element or selector string). Mandatory. + + // retObj => returned object by function + // found : true/false // whether a path from root to goal has been found + // distance // Distance for the shortest path from root to goal + // path // Array of ids of nodes in shortest path + aStar: function(options) { + options = options || {}; + + var logDebug = function() { + if (debug) { + console.log.apply(console, arguments); + } + }; + + // Reconstructs the path from Start to End, acumulating the result in pathAcum + var reconstructPath = function(start, end, cameFromMap, pathAcum) { + // Base case + if (start == end) { + pathAcum.push( cy.getElementById(end) ); + return pathAcum; + } + + if (end in cameFromMap) { + // We know which node is before the last one + var previous = cameFromMap[end]; + var previousEdge = cameFromEdge[end]; + + pathAcum.push( cy.getElementById(end) ); + pathAcum.push( cy.getElementById(previousEdge) ); + + + return reconstructPath(start, + previous, + cameFromMap, + pathAcum); + } + + // We should not reach here! + return undefined; + }; + + // Returns the index of the element in openSet which has minimum fScore + var findMin = function(openSet, fScore) { + if (openSet.length === 0) { + // Should never be the case + return undefined; + } + var minPos = 0; + var tempScore = fScore[openSet[0]]; + for (var i = 1; i < openSet.length; i++) { + var s = fScore[openSet[i]]; + if (s < tempScore) { + tempScore = s; + minPos = i; + } + } + return minPos; + }; + + // Parse options + // debug - optional + if (options.debug != null) { + var debug = options.debug; + } else { + var debug = false; + } + + logDebug("Starting aStar..."); + var cy = this._private.cy; + + // root - mandatory! + if (options != null && options.root != null) { + var source = $$.is.string(options.root) ? + // use it as a selector, e.g. "#rootID + this.filter(options.root)[0] : + options.root[0]; + logDebug("Source node: %s", source.id()); + } else { + return undefined; + } + + // goal - mandatory! + if (options.goal != null) { + var target = $$.is.string(options.goal) ? + // use it as a selector, e.g. "#goalID + this.filter(options.goal)[0] : + options.goal[0]; + logDebug("Target node: %s", target.id()); + } else { + return undefined; + } + + // Heuristic function - optional + if (options.heuristic != null && $$.is.fn(options.heuristic)) { + var heuristic = options.heuristic; + } else { + var heuristic = function(){ return 0; }; // use constant if unspecified + // $$.util.error("Missing required parameter (heuristic)! Aborting."); + // return; + } + + // Weight function - optional + if (options.weight != null && $$.is.fn(options.weight)) { + var weightFn = options.weight; + } else { + // If not specified, assume each edge has equal weight (1) + var weightFn = function(e) {return 1;}; + } + + // directed - optional + if (options.directed != null) { + var directed = options.directed; + } else { + var directed = false; + } + + var closedSet = []; + var openSet = [source.id()]; + var cameFrom = {}; + var cameFromEdge = {}; + var gScore = {}; + var fScore = {}; + + gScore[source.id()] = 0; + fScore[source.id()] = heuristic(source); + + var edges = this.edges().not(':loop'); + var nodes = this.nodes(); + + // Counter + var steps = 0; + + // Main loop + while (openSet.length > 0) { + var minPos = findMin(openSet, fScore); + var cMin = this.filter("#" + openSet[minPos])[0]; + steps++; + + logDebug("\nStep: %s", steps); + logDebug("Processing node: %s, fScore = %s", cMin.id(), fScore[cMin.id()]); + + // If we've found our goal, then we are done + if (cMin.id() == target.id()) { + logDebug("Found goal node!"); + var rPath = reconstructPath(source.id(), target.id(), cameFrom, []); + rPath.reverse(); + logDebug("Path: %s", rPath); + return { + found : true, + distance : gScore[cMin.id()], + path : new $$.Collection(cy, rPath), + steps : steps + }; + } + + // Add cMin to processed nodes + closedSet.push(cMin.id()); + // Remove cMin from boundary nodes + openSet.splice(minPos, 1); + logDebug("Added node to closedSet, removed from openSet."); + logDebug("Processing neighbors..."); + + // Update scores for neighbors of cMin + // Take into account if graph is directed or not + var vwEdges = cMin.connectedEdges(directed ? '[source = "' + cMin.id() + '"]' + : undefined).intersect(edges); + for (var i = 0; i < vwEdges.length; i++) { + var e = vwEdges[i]; + var w = e.connectedNodes('[id != "' + cMin.id() + '"]').intersect(nodes); + + logDebug(" processing neighbor: %s", w.id()); + // if node is in closedSet, ignore it + if (closedSet.indexOf(w.id()) != -1) { + logDebug(" already in closedSet, ignoring it."); + continue; + } + + // New tentative score for node w + var tempScore = gScore[cMin.id()] + weightFn.apply(e, [e]); + logDebug(" tentative gScore: %d", tempScore); + + // Update gScore for node w if: + // w not present in openSet + // OR + // tentative gScore is less than previous value + + // w not in openSet + if (openSet.indexOf(w.id()) == -1) { + gScore[w.id()] = tempScore; + fScore[w.id()] = tempScore + heuristic(w); + openSet.push(w.id()); // Add node to openSet + cameFrom[w.id()] = cMin.id(); + cameFromEdge[w.id()] = e.id(); + logDebug(" not in openSet, adding it. "); + logDebug(" fScore(%s) = %s", w.id(), tempScore); + continue; + } + // w already in openSet, but with greater gScore + if (tempScore < gScore[w.id()]) { + gScore[w.id()] = tempScore; + fScore[w.id()] = tempScore + heuristic(w); + cameFrom[w.id()] = cMin.id(); + logDebug(" better score, replacing gScore. "); + logDebug(" fScore(%s) = %s", w.id(), tempScore); + } + + } // End of neighbors update + + } // End of main loop + + // If we've reached here, then we've not reached our goal + logDebug("Reached end of computation without finding our goal"); + return { + found : false, + distance : undefined, + path : undefined, + steps : steps + }; + }, // aStar() + + + // Implemented from pseudocode from wikipedia + // options => options object + // weight: function( edge ){} // specifies weight to use for `edge`/`this`. If not present, it will be asumed a weight of 1 for all edges + // directed // default false + // retObj => returned object by function + // pathTo : function(fromId, toId) // Returns the shortest path from node with ID "fromID" to node with ID "toId", as an array of node IDs + // distanceTo: function(fromId, toId) // Returns the distance of the shortest path from node with ID "fromID" to node with ID "toId" + floydWarshall: function(options) { + options = options || {}; + + var logDebug = function() { + if (debug) { + console.log.apply(console, arguments); + } + }; + + // Parse options + // debug - optional + if (options.debug != null) { + var debug = options.debug; + } else { + var debug = false; + } + logDebug("Starting floydWarshall..."); + + var cy = this._private.cy; + + // Weight function - optional + if (options.weight != null && $$.is.fn(options.weight)) { + var weightFn = options.weight; + } else { + // If not specified, assume each edge has equal weight (1) + var weightFn = function(e) {return 1;}; + } + + // directed - optional + if (options.directed != null) { + var directed = options.directed; + } else { + var directed = false; + } + + var edges = this.edges().not(':loop'); + var nodes = this.nodes(); + var numNodes = nodes.length; + + // mapping: node id -> position in nodes array + var id2position = {}; + for (var i = 0; i < numNodes; i++) { + id2position[nodes[i].id()] = i; + } + + // Initialize distance matrix + var dist = []; + for (var i = 0; i < numNodes; i++) { + var newRow = new Array(numNodes); + for (var j = 0; j < numNodes; j++) { + if (i == j) { + newRow[j] = 0; + } else { + newRow[j] = Infinity; + } + } + dist.push(newRow); + } + + // Initialize matrix used for path reconstruction + // Initialize distance matrix + var next = []; + var edgeNext = []; + + var initMatrix = function(next){ + for (var i = 0; i < numNodes; i++) { + var newRow = new Array(numNodes); + for (var j = 0; j < numNodes; j++) { + newRow[j] = undefined; + } + next.push(newRow); + } + }; + + initMatrix(next); + initMatrix(edgeNext); + + // Process edges + for (var i = 0; i < edges.length ; i++) { + var sourceIndex = id2position[edges[i].source().id()]; + var targetIndex = id2position[edges[i].target().id()]; + var weight = weightFn.apply(edges[i], [edges[i]]); + + // Check if already process another edge between same 2 nodes + if (dist[sourceIndex][targetIndex] > weight) { + dist[sourceIndex][targetIndex] = weight; + next[sourceIndex][targetIndex] = targetIndex; + edgeNext[sourceIndex][targetIndex] = edges[i]; + } + } + + // If undirected graph, process 'reversed' edges + if (!directed) { + for (var i = 0; i < edges.length ; i++) { + var sourceIndex = id2position[edges[i].target().id()]; + var targetIndex = id2position[edges[i].source().id()]; + var weight = weightFn.apply(edges[i], [edges[i]]); + + // Check if already process another edge between same 2 nodes + if (dist[sourceIndex][targetIndex] > weight) { + dist[sourceIndex][targetIndex] = weight; + next[sourceIndex][targetIndex] = targetIndex; + edgeNext[sourceIndex][targetIndex] = edges[i]; + } + } + } + + // Main loop + for (var k = 0; k < numNodes; k++) { + for (var i = 0; i < numNodes; i++) { + for (var j = 0; j < numNodes; j++) { + if (dist[i][k] + dist[k][j] < dist[i][j]) { + dist[i][j] = dist[i][k] + dist[k][j]; + next[i][j] = next[i][k]; + } + } + } + } + + // Build result object + var position2id = []; + for (var i = 0; i < numNodes; i++) { + position2id.push(nodes[i].id()); + } + + var res = { + distance: function(from, to) { + if ($$.is.string(from)) { + // from is a selector string + var fromId = (cy.filter(from)[0]).id(); + } else { + // from is a node + var fromId = from.id(); + } + + if ($$.is.string(to)) { + // to is a selector string + var toId = (cy.filter(to)[0]).id(); + } else { + // to is a node + var toId = to.id(); + } + + return dist[id2position[fromId]][id2position[toId]]; + }, + + path: function(from, to) { + var reconstructPathAux = function(from, to, next, position2id, edgeNext) { + if (from === to) { + return cy.getElementById( position2id[from] ); + } + if (next[from][to] === undefined) { + return undefined; + } + + var path = [ cy.getElementById(position2id[from]) ]; + var prev = from; + while (from !== to) { + prev = from; + from = next[from][to]; + + var edge = edgeNext[prev][from]; + path.push( edge ); + + path.push( cy.getElementById(position2id[from]) ); + } + return path; + }; + + if ($$.is.string(from)) { + // from is a selector string + var fromId = (cy.filter(from)[0]).id(); + } else { + // from is a node + var fromId = from.id(); + } + + if ($$.is.string(to)) { + // to is a selector string + var toId = (cy.filter(to)[0]).id(); + } else { + // to is a node + var toId = to.id(); + } + + var pathArr = reconstructPathAux(id2position[fromId], + id2position[toId], + next, + position2id, + edgeNext); + + return new $$.Collection( cy, pathArr ); + }, + }; + + return res; + + }, // floydWarshall + + + // Implemented from pseudocode from wikipedia + // options => options object + // root: starting node (either element or selector string) + // weight: function( edge ){} // specifies weight to use for `edge`/`this`. If not present, it will be asumed a weight of 1 for all edges + // directed // default false + // retObj => returned object by function + // pathTo : function(toId) // Returns the shortest path from root node to node with ID "toId", as an array of node IDs + // distanceTo: function(toId) // Returns the distance of the shortest path from root node to node with ID "toId" + // hasNegativeWeightCycle: true/false (if true, pathTo and distanceTo will be undefined) + bellmanFord: function(options) { + options = options || {}; + + var logDebug = function() { + if (debug) { + console.log.apply(console, arguments); + } + }; + + // Parse options + // debug - optional + if (options.debug != null) { + var debug = options.debug; + } else { + var debug = false; + } + logDebug("Starting bellmanFord..."); + + // Weight function - optional + if (options.weight != null && $$.is.fn(options.weight)) { + var weightFn = options.weight; + } else { + // If not specified, assume each edge has equal weight (1) + var weightFn = function(e) {return 1;}; + } + + // directed - optional + if (options.directed != null) { + var directed = options.directed; + } else { + var directed = false; + } + + // root - mandatory! + if (options.root != null) { + if ($$.is.string(options.root)) { + // use it as a selector, e.g. "#rootID + var source = this.filter(options.root)[0]; + } else { + var source = options.root[0]; + } + logDebug("Source node: %s", source.id()); + } else { + $$.util.error("options.root required"); + return undefined; + } + + var cy = this._private.cy; + var edges = this.edges().not(':loop'); + var nodes = this.nodes(); + var numNodes = nodes.length; + + // mapping: node id -> position in nodes array + var id2position = {}; + for (var i = 0; i < numNodes; i++) { + id2position[nodes[i].id()] = i; + } + + // Initializations + var cost = []; + var predecessor = []; + var predEdge = []; + + for (var i = 0; i < numNodes; i++) { + if (nodes[i].id() === source.id()) { + cost[i] = 0; + } else { + cost[i] = Infinity; + } + predecessor[i] = undefined; + } + + // Edges relaxation + var flag = false; + for (var i = 1; i < numNodes; i++) { + flag = false; + for (var e = 0; e < edges.length; e++) { + var sourceIndex = id2position[edges[e].source().id()]; + var targetIndex = id2position[edges[e].target().id()]; + var weight = weightFn.apply(edges[e], [edges[e]]); + + var temp = cost[sourceIndex] + weight; + if (temp < cost[targetIndex]) { + cost[targetIndex] = temp; + predecessor[targetIndex] = sourceIndex; + predEdge[targetIndex] = edges[e]; + flag = true; + } + + // If undirected graph, we need to take into account the 'reverse' edge + if (!directed) { + var temp = cost[targetIndex] + weight; + if (temp < cost[sourceIndex]) { + cost[sourceIndex] = temp; + predecessor[sourceIndex] = targetIndex; + predEdge[sourceIndex] = edges[e]; + flag = true; + } + } + } + + if (!flag) { + break; + } + } + + if (flag) { + // Check for negative weight cycles + for (var e = 0; e < edges.length; e++) { + var sourceIndex = id2position[edges[e].source().id()]; + var targetIndex = id2position[edges[e].target().id()]; + var weight = weightFn.apply(edges[e], [edges[e]]); + + if (cost[sourceIndex] + weight < cost[targetIndex]) { + $$.util.error("Error: graph contains a negative weigth cycle!"); + return { pathTo: undefined, + distanceTo: undefined, + hasNegativeWeightCycle: true}; + } + } + } + + // Build result object + var position2id = []; + for (var i = 0; i < numNodes; i++) { + position2id.push(nodes[i].id()); + } + + + var res = { + distanceTo : function(to) { + if ($$.is.string(to)) { + // to is a selector string + var toId = (cy.filter(to)[0]).id(); + } else { + // to is a node + var toId = to.id(); + } + + return cost[id2position[toId]]; + }, + + pathTo : function(to) { + + var reconstructPathAux = function(predecessor, fromPos, toPos, position2id, acumPath, predEdge) { + for(;;){ + // Add toId to path + acumPath.push( cy.getElementById(position2id[toPos]) ); + acumPath.push( predEdge[toPos] ); + + if (fromPos === toPos) { + // reached starting node + return acumPath; + } + + // If no path exists, discart acumulated path and return undefined + var predPos = predecessor[toPos]; + if (typeof predPos === "undefined") { + return undefined; + } + + toPos = predPos; + } + + }; + + if ($$.is.string(to)) { + // to is a selector string + var toId = (cy.filter(to)[0]).id(); + } else { + // to is a node + var toId = to.id(); + } + var path = []; + + // This returns a reversed path + var res = reconstructPathAux(predecessor, + id2position[source.id()], + id2position[toId], + position2id, + path, + predEdge); + + // Get it in the correct order and return it + if (res != null) { + res.reverse(); + } + + return new $$.Collection(cy, res); + }, + + hasNegativeWeightCycle: false + }; + + return res; + + }, // bellmanFord + + + // Computes the minimum cut of an undirected graph + // Returns the correct answer with high probability + // options => options object + // + // retObj => returned object by function + // cut : list of IDs of edges in the cut, + // partition1: list of IDs of nodes in one partition + // partition2: list of IDs of nodes in the other partition + kargerStein: function(options) { + options = options || {}; + + var logDebug = function() { + if (debug) { + console.log.apply(console, arguments); + } + }; + + // Function which colapses 2 (meta) nodes into one + // Updates the remaining edge lists + // Receives as a paramater the edge which causes the collapse + var colapse = function(edgeIndex, nodeMap, remainingEdges) { + var edgeInfo = remainingEdges[edgeIndex]; + var sourceIn = edgeInfo[1]; + var targetIn = edgeInfo[2]; + var partition1 = nodeMap[sourceIn]; + var partition2 = nodeMap[targetIn]; + + // Delete all edges between partition1 and partition2 + var newEdges = remainingEdges.filter(function(edge) { + if (nodeMap[edge[1]] === partition1 && nodeMap[edge[2]] === partition2) { + return false; + } + if (nodeMap[edge[1]] === partition2 && nodeMap[edge[2]] === partition1) { + return false; + } + return true; + }); + + // All edges pointing to partition2 should now point to partition1 + for (var i = 0; i < newEdges.length; i++) { + var edge = newEdges[i]; + if (edge[1] === partition2) { // Check source + newEdges[i] = edge.slice(0); + newEdges[i][1] = partition1; + } else if (edge[2] === partition2) { // Check target + newEdges[i] = edge.slice(0); + newEdges[i][2] = partition1; + } + } + + // Move all nodes from partition2 to partition1 + for (var i = 0; i < nodeMap.length; i++) { + if (nodeMap[i] === partition2) { + nodeMap[i] = partition1; + } + } + + return newEdges; + }; + + + // Contracts a graph until we reach a certain number of meta nodes + var contractUntil = function(metaNodeMap, + remainingEdges, + size, + sizeLimit) { + // Stop condition + if (size <= sizeLimit) { + return remainingEdges; + } + + // Choose an edge randomly + var edgeIndex = Math.floor((Math.random() * remainingEdges.length)); + + // Colapse graph based on edge + var newEdges = colapse(edgeIndex, metaNodeMap, remainingEdges); + + return contractUntil(metaNodeMap, + newEdges, + size - 1, + sizeLimit); + }; + + + // Parse options + // debug - optional + if (options != null && options.debug != null) { + var debug = options.debug; + } else { + var debug = false; + } + logDebug("Starting kargerStein..."); + + var cy = this._private.cy; + var edges = this.edges().not(':loop'); + var nodes = this.nodes(); + var numNodes = nodes.length; + var numEdges = edges.length; + var numIter = Math.ceil(Math.pow(Math.log(numNodes) / Math.LN2, 2)); + var stopSize = Math.floor(numNodes / Math.sqrt(2)); + + if (numNodes < 2) { + $$.util.error("At least 2 nodes are required for KargerSteing algorithm!"); + return undefined; + } + + // Create numerical identifiers for each node + // mapping: node id -> position in nodes array + // for reverse mapping, simply use nodes array + var id2position = {}; + for (var i = 0; i < numNodes; i++) { + id2position[nodes[i].id()] = i; + } + + // Now store edge destination as indexes + // Format for each edge (edge index, source node index, target node index) + var edgeIndexes = []; + for (var i = 0; i < numEdges; i++) { + var e = edges[i]; + edgeIndexes.push([i, id2position[e.source().id()], id2position[e.target().id()]]); + } + + // We will store the best cut found here + var minCutSize = Infinity; + var minCut; + + // Initial meta node partition + var originalMetaNode = []; + for (var i = 0; i < numNodes; i++) { + originalMetaNode.push(i); + } + + // Main loop + for (var iter = 0; iter <= numIter; iter++) { + // Create new meta node partition + var metaNodeMap = originalMetaNode.slice(0); + + // Contract until stop point (stopSize nodes) + var edgesState = contractUntil(metaNodeMap, edgeIndexes, numNodes, stopSize); + + // Create a copy of the colapsed nodes state + var metaNodeMap2 = metaNodeMap.slice(0); + + // Run 2 iterations starting in the stop state + var res1 = contractUntil(metaNodeMap, edgesState, stopSize, 2); + var res2 = contractUntil(metaNodeMap2, edgesState, stopSize, 2); + + // Is any of the 2 results the best cut so far? + if (res1.length <= res2.length && res1.length < minCutSize) { + minCutSize = res1.length; + minCut = [res1, metaNodeMap]; + } else if (res2.length <= res1.length && res2.length < minCutSize) { + minCutSize = res2.length; + minCut = [res2, metaNodeMap2]; + } + } // end of main loop + + + // Construct result + var resEdges = (minCut[0]).map(function(e){ return edges[e[0]]; }); + var partition1 = []; + var partition2 = []; + + // traverse metaNodeMap for best cut + var witnessNodePartition = minCut[1][0]; + for (var i = 0; i < minCut[1].length; i++) { + var partitionId = minCut[1][i]; + if (partitionId === witnessNodePartition) { + partition1.push(nodes[i]); + } else { + partition2.push(nodes[i]); + } + } + + var ret = { + cut: new $$.Collection(cy, resEdges), + partition1: new $$.Collection(cy, partition1), + partition2: new $$.Collection(cy, partition2) + }; + + return ret; + }, + + + // + // options => options object + // dampingFactor: optional + // precision: optional + // iterations : optional + // retObj => returned object by function + // rank : function that returns the pageRank of a given node (object or selector string) + pageRank: function(options) { + options = options || {}; + + var normalizeVector = function(vector) { + var length = vector.length; + + // First, get sum of all elements + var total = 0; + for (var i = 0; i < length; i++) { + total += vector[i]; + } + + // Now, divide each by the sum of all elements + for (var i = 0; i < length; i++) { + vector[i] = vector[i] / total; + } + }; + + var logDebug = function() { + if (debug) { + console.log.apply(console, arguments); + } + }; + + // Parse options + // debug - optional + if (options != null && + options.debug != null) { + var debug = options.debug; + } else { + var debug = false; + } + logDebug("Starting pageRank..."); + + // dampingFactor - optional + if (options != null && + options.dampingfactor != null) { + var dampingFactor = options.dampingFactor; + } else { + var dampingFactor = 0.8; // Default damping factor + } + + // desired precision - optional + if (options != null && + options.precision != null) { + var epsilon = options.precision; + } else { + var epsilon = 0.000001; // Default precision + } + + // Max number of iterations - optional + if (options != null && + options.iterations != null) { + var numIter = options.iterations; + } else { + var numIter = 200; // Default number of iterations + } + + // Weight function - optional + if (options != null && + options.weight != null && + $$.is.fn(options.weight)) { + var weightFn = options.weight; + } else { + // If not specified, assume each edge has equal weight (1) + var weightFn = function(e) {return 1;}; + } + + var cy = this._private.cy; + var edges = this.edges().not(':loop'); + var nodes = this.nodes(); + var numNodes = nodes.length; + var numEdges = edges.length; + + // Create numerical identifiers for each node + // mapping: node id -> position in nodes array + // for reverse mapping, simply use nodes array + var id2position = {}; + for (var i = 0; i < numNodes; i++) { + id2position[nodes[i].id()] = i; + } + + // Construct transposed adjacency matrix + // First lets have a zeroed matrix of the right size + // We'll also keep track of the sum of each column + var matrix = []; + var columnSum = []; + var additionalProb = (1 - dampingFactor) / numNodes; + + // Create null matric + for (var i = 0; i < numNodes; i++) { + var newRow = []; + for (var j = 0; j < numNodes; j++) { + newRow.push(0.0); + } + matrix.push(newRow); + columnSum.push(0.0); + } + + // Now, process edges + for (var i = 0; i < numEdges; i++) { + var edge = edges[i]; + var s = id2position[edge.source().id()]; + var t = id2position[edge.target().id()]; + var w = weightFn.apply(edge, [edge]); + + // Update matrix + matrix[t][s] += w; + + // Update column sum + columnSum[s] += w; + } + + // Add additional probability based on damping factor + // Also, take into account columns that have sum = 0 + var p = 1.0 / numNodes + additionalProb; // Shorthand + // Traverse matrix, column by column + for (var j = 0; j < numNodes; j++) { + if (columnSum[j] === 0) { + // No 'links' out from node jth, assume equal probability for each possible node + for (var i = 0; i < numNodes; i++) { + matrix[i][j] = p; + } + } else { + // Node jth has outgoing link, compute normalized probabilities + for (var i = 0; i < numNodes; i++) { + matrix[i][j] = matrix[i][j] / columnSum[j] + additionalProb; + } + } + } + + // Compute dominant eigenvector using power method + var eigenvector = []; + var nullVector = []; + var previous; + + // Start with a vector of all 1's + // Also, initialize a null vector which will be used as shorthand + for (var i = 0; i < numNodes; i++) { + eigenvector.push(1.0); + nullVector.push(0.0); + } + + for (var iter = 0; iter < numIter; iter++) { + // New array with all 0's + var temp = nullVector.slice(0); + + // Multiply matrix with previous result + for (var i = 0; i < numNodes; i++) { + for (var j = 0; j < numNodes; j++) { + temp[i] += matrix[i][j] * eigenvector[j]; + } + } + + normalizeVector(temp); + previous = eigenvector; + eigenvector = temp; + + var diff = 0; + // Compute difference (squared module) of both vectors + for (var i = 0; i < numNodes; i++) { + diff += Math.pow(previous[i] - eigenvector[i], 2); + } + + // If difference is less than the desired threshold, stop iterating + if (diff < epsilon) { + logDebug("Stoped at iteration %s", iter); + break; + } + } + + logDebug("Result:\n" + eigenvector); + + // Construct result + var res = { + rank : function(node) { + if ($$.is.string(node)) { + // is a selector string + var nodeId = (cy.filter(node)[0]).id(); + } else { + // is a node object + var nodeId = node.id(); + } + return eigenvector[id2position[nodeId]]; + } + }; + + + return res; + } // pageRank + + }); // $$.fn.eles + + +}) (cytoscape); +;(function( $$ ){ 'use strict'; + + $$.fn.eles({ + animated: $$.define.animated(), + clearQueue: $$.define.clearQueue(), + delay: $$.define.delay(), + animate: $$.define.animate(), + stop: $$.define.stop() + }); + +})( cytoscape ); + +;(function( $$ ){ 'use strict'; + + $$.fn.eles({ + classes: function(opts){ + var eles = this; + var changed = []; + var fn; + + if( $$.is.fn(opts) ){ + fn = opts; + + } else if( !$$.is.plainObject(opts) ){ + return this; // needs opts or fn + } + + for(var i = 0; i < eles.length; i++){ + var ele = eles[i]; + var eleChanged = false; + + opts = fn ? fn.apply(ele, [i, ele]) : opts; + + // add classes + if( opts.add ){ for( var j = 0; j < opts.add.length; j++ ){ + var cls = opts.add[j]; + var hasClass = ele._private.classes[cls]; + + ele._private.classes[cls] = true; + + if( !hasClass && !eleChanged ){ + changed.push( ele ); + eleChanged = true; + } + } } + + // remove classes + if( opts.remove ){ for( var j = 0; j < opts.remove.length; j++ ){ + var cls = opts.remove[j]; + var hasClass = ele._private.classes[cls]; + + ele._private.classes[cls] = false; + + if( hasClass && !eleChanged ){ + changed.push( ele ); + eleChanged = true; + } + } } + + // toggle classes + if( opts.toggle ){ for( var j = 0; j < opts.toggle.length; j++ ){ + var cls = opts.toggle[j]; + var hasClass = ele._private.classes[cls]; + + ele._private.classes[cls] = !hasClass; + + if( !eleChanged ){ + changed.push( ele ); + eleChanged = true; + } + } } + } + + if( changed.length > 0 ){ + new $$.Collection( this.cy(), changed ) + .updateStyle() + .trigger('class') + ; + } + + return this; + }, + + 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() + .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() + .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]; + ele._private.classes[cls] = undefined; + + 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; + }, + + flashClass: function(classes, duration){ + var self = this; + + if( duration == null ){ + duration = 250; + } else if( duration === 0 ){ + return self; // nothing to do really + } + + self.addClass( classes ); + setTimeout(function(){ + self.removeClass( classes ); + }, duration); + + return self; + } + }); + +})( 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; + }, + + some: function( fn, thisArg ){ + for( var i = 0; i < this.length; i++ ){ + var ret = fn.apply( thisArg, [ this[i], i, this ] ); + + if( ret ){ + return true; + } + } + + return false; + }, + + every: function( fn, thisArg ){ + for( var i = 0; i < this.length; i++ ){ + var ret = fn.apply( thisArg, [ this[i], i, this ] ); + + if( !ret ){ + return false; + } + } + + return true; + }, + + same: function( collection ){ + collection = this.cy().collection( collection ); + + // cheap extra check + if( this.length !== collection.length ){ + return false; + } + + return this.intersect( collection ).length === this.length; + }, + + anySame: function( collection ){ + collection = this.cy().collection( collection ); + + return this.intersect( collection ).length > 0; + }, + + allAreNeighbors: function( collection ){ + collection = this.cy().collection( collection ); + + return this.neighborhood().intersect( collection ).length === collection.length; + } + }); + +})( 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, { unique: true } ).filter( selector ); + }, + + parents: function( selector ){ + var parents = []; + + var eles = this.parent(); + while( eles.nonempty() ){ + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + parents.push( ele ); + } + + eles = eles.parent(); + } + + return new $$.Collection( this.cy(), parents, { unique: true } ).filter( selector ); + }, + + commonAncestors: function( selector ){ + var ancestors; + + for( var i = 0; i < this.length; i++ ){ + var ele = this[i]; + var parents = ele.parents(); + + ancestors = ancestors || parents; + + ancestors = ancestors.intersect( parents ); // current list must be common with current ele parents set + } + + return ancestors.filter( selector ); + }, + + orphans: function( selector ){ + return this.stdFilter(function( ele ){ + return ele.isNode() && ele.parent().empty(); + }).filter( selector ); + }, + + nonorphans: function( selector ){ + return this.stdFilter(function( ele ){ + return ele.isNode() && ele.parent().nonempty(); + }).filter( selector ); + }, + + children: function( selector ){ + var children = []; + + for( var i = 0; i < this.length; i++ ){ + var ele = this[i]; + children = children.concat( ele._private.children ); + } + + return new $$.Collection( this.cy(), children, { unique: true } ).filter( selector ); + }, + + siblings: function( selector ){ + return this.parent().children().not( this ).filter( selector ); + }, + + isParent: function(){ + var ele = this[0]; + + if( ele ){ + return ele._private.children.length !== 0; + } + }, + + isChild: function(){ + var ele = this[0]; + + if( ele ){ + return ele._private.data.parent !== undefined && ele.parent().length !== 0; + } + }, + + descendants: function( selector ){ + var elements = []; + + function add( eles ){ + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + + elements.push( ele ); + + if( ele.children().nonempty() ){ + add( ele.children() ); + } + } + } + + add( this.children() ); + + return new $$.Collection( this.cy(), elements, { unique: true } ).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 + }), + + 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 = updatedEles.length > 0 ? this.add( updatedEles ) : this; + + if( silent ){ + toTrigger.trigger('position'); + } else { + toTrigger.rtrigger('position'); + } + } + + return this; // chaining + }, + + silentPositions: function( pos ){ + return this.positions( pos, true ); + }, + + updateCompoundBounds: function(){ + var cy = this.cy(); + + if( !cy.styleEnabled() || !cy.hasCompoundNodes() ){ return cy.collection(); } // save cycles for non compound graphs or when style disabled + + var updated = []; + + function update( parent ){ + var children = parent.children(); + var style = parent._private.style; + var 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 + }, + + // get/set the position relative to the parent + parentPosition: function( dim, val ){ + var ele = this[0]; + var cy = this.cy(); + var ppos = $$.is.plainObject( dim ) ? dim : undefined; + var setting = ppos !== undefined || ( val !== undefined && $$.is.string(dim) ); + var hasCompoundNodes = cy.hasCompoundNodes(); + + if( ele && ele.isNode() ){ // must have an element and must be a node to return position + if( setting ){ + for( var i = 0; i < this.length; i++ ){ + var ele = this[i]; + var parent = hasCompoundNodes ? ele.parent() : null; + var hasParent = parent && parent.length > 0; + var relativeToParent = hasParent; + + if( hasParent ){ + parent = parent[0]; + } + + var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 }; + + if( val !== undefined ){ // set one dimension + ele._private.position[dim] = val + origin[dim]; + } else if( ppos !== undefined ){ // set whole position + ele._private.position = { + x: ppos.x + origin.x, + y: ppos.y + origin.y, + }; + } + } + + this.rtrigger('position'); + + } else { // getting + var pos = ele._private.position; + var parent = hasCompoundNodes ? ele.parent() : null; + var hasParent = parent && parent.length > 0; + var relativeToParent = hasParent; + + if( hasParent ){ + parent = parent[0]; + } + + var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 }; + + ppos = { + x: pos.x - origin.x, + y: pos.y - origin.y + }; + + if( dim === undefined ){ // then return the whole rendered position + return ppos; + } else { // then return the specified dimension + return ppos[ dim ]; + } + } + } else if( !setting ){ + return undefined; // for empty collection case + } + + return this; // chaining + }, + + // convenience function to get a numerical value for the width of the node/edge + width: function(){ + var ele = this[0]; + var cy = ele._private.cy; + var styleEnabled = cy._private.styleEnabled; + + if( ele ){ + if( styleEnabled ){ + var w = ele._private.style.width; + return w.strValue === 'auto' ? ele._private.autoWidth : w.pxValue; + } else { + return 1; + } + } + }, + + outerWidth: function(){ + var ele = this[0]; + var cy = ele._private.cy; + var styleEnabled = cy._private.styleEnabled; + + if( ele ){ + if( styleEnabled ){ + 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; + } else { + return 1; + } + } + }, + + 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]; + var cy = ele._private.cy; + var styleEnabled = cy._private.styleEnabled; + + if( ele && ele._private.group === 'nodes' ){ + if( styleEnabled ){ + var h = ele._private.style.height; + return h.strValue === 'auto' ? ele._private.autoHeight : h.pxValue; + } else { + return 1; + } + } + }, + + outerHeight: function(){ + var ele = this[0]; + var cy = ele._private.cy; + var styleEnabled = cy._private.styleEnabled; + + if( ele && ele._private.group === 'nodes' ){ + if( styleEnabled ){ + 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; + } else { + return 1; + } + + return height + border; + } + }, + + renderedHeight: function(){ + var ele = this[0]; + + if( ele && ele._private.group === 'nodes' ){ + var height = ele.height(); + return height * this.cy().zoom(); + } + }, + + renderedOuterHeight: function(){ + var ele = this[0]; + + if( ele && ele._private.group === 'nodes' ){ + 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; + var cy = eles._private.cy; + var cy_p = cy._private; + var styleEnabled = cy_p.styleEnabled; + + options = options || {}; + + var includeNodes = options.includeNodes === undefined ? true : options.includeNodes; + var includeEdges = options.includeEdges === undefined ? true : options.includeEdges; + var includeLabels = options.includeLabels === undefined ? true : options.includeLabels; + + // recalculate projections etc + if( styleEnabled ){ + cy_p.renderer.recalculateRenderedStyle( this ); + } + + var x1 = Infinity; + var x2 = -Infinity; + var y1 = Infinity; + var y2 = -Infinity; + + // find bounds of elements + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + var _p = ele._private; + var display = styleEnabled ? _p.style['display'].value : 'element'; + var isNode = _p.group === 'nodes'; + var ex1, ex2, ey1, ey2, x, y; + var includedEle = false; + + if( display === 'none' ){ continue; } // then ele doesn't take up space + + if( isNode && includeNodes ){ + includedEle = true; + + var pos = _p.position; + x = pos.x; + y = pos.y; + var w = ele.outerWidth(); + var halfW = w/2; + var h = ele.outerHeight(); + var halfH = h/2; + + // handle node dimensions + ///////////////////////// + + ex1 = x - halfW; + ex2 = x + halfW; + ey1 = y - halfH; + ey2 = y + halfH; + + x1 = ex1 < x1 ? ex1 : x1; + x2 = ex2 > x2 ? ex2 : x2; + y1 = ey1 < y1 ? ey1 : y1; + y2 = ey2 > y2 ? ey2 : y2; + + } else if( ele.isEdge() && includeEdges ){ + includedEle = true; + + var n1pos = ele._private.source._private.position; + var n2pos = ele._private.target._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) + ////////////////////////////////////////// + + if( styleEnabled ){ + var bpts = rstyle.bezierPts || []; + + var w = ele._private.style['width'].pxValue; + var wHalf = w/2; + + for( var j = 0; j < bpts.length; j++ ){ + var bpt = bpts[j]; + + ex1 = bpt.x - wHalf; + ex2 = bpt.x + wHalf; + ey1 = bpt.y - wHalf; + ey2 = bpt.y + wHalf; + + x1 = ex1 < x1 ? ex1 : x1; + x2 = ex2 > x2 ? ex2 : x2; + y1 = ey1 < y1 ? ey1 : y1; + y2 = ey2 > y2 ? ey2 : y2; + } + } + + } // edges + + // handle label dimensions + ////////////////////////// + + if( styleEnabled ){ + + var 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 && 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; + } + } // style enabled + } // for + + return { + x1: x1, + x2: x2, + y1: y1, + y2: y2, + w: x2 - x1, + h: y2 - y1 + }; + } + }); + + // in case some users want to be explicit + $$.elesfn.modelPosition = $$.elesfn.position; + $$.elesfn.modelPositions = $$.elesfn.positions; + +})( 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 + this.cy().notify({ + type: event, + collection: this + }); + + 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 instead + if( $$.is.string(other) ){ + var selector = other; + return this.filter( selector ); + } + + var elements = []; + var col1 = this; + var col2 = other; + var col1Smaller = this.length < other.length; + // var ids1 = col1Smaller ? col1._private.ids : col2._private.ids; + var ids2 = col1Smaller ? col2._private.ids : col1._private.ids; + var col = col1Smaller ? col1 : col2; + + for( var i = 0; i < col.length; i++ ){ + var id = col[i]._private.data.id; + var ele = ids2[ id ]; + + if( ele ){ + elements.push( ele ); + } + } + + return 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); + }, + + // in place merge on calling collection + merge: function( toAdd ){ + var _p = this._private; + var cy = _p.cy; + + if( !toAdd ){ + return this; + } + + if( $$.is.string(toAdd) ){ + var selector = toAdd; + toAdd = cy.elements(selector); + } + + for( var i = 0; i < toAdd.length; i++ ){ + var toAddEle = toAdd[i]; + var id = toAddEle.id(); + var add = !_p.ids[ id ]; + + if( add ){ + var index = this.length++; + + this[ index ] = toAddEle; + _p.ids[ id ] = toAddEle; + _p.indexes[ id ] = index; + } + } + + return this; // chaining + }, + + // remove single ele in place in calling collection + unmergeOne: function( ele ){ + ele = ele[0]; + + var _p = this._private; + var id = ele.id(); + var i = _p.indexes[ id ]; + + if( i == null ){ + return this; // no need to remove + } + + // remove ele + this[i] = undefined; + _p.ids[ id ] = undefined; + _p.indexes[ id ] = undefined; + + var unmergedLastEle = i === this.length - 1; + + // replace empty spot with last ele in collection + if( this.length > 1 && !unmergedLastEle ){ + var lastEleI = this.length - 1; + var lastEle = this[ lastEleI ]; + + this[ lastEleI ] = undefined; + this[i] = lastEle; + _p.indexes[ lastEle.id() ] = i; + } + + // the collection is now 1 ele smaller + this.length--; + + return this; + }, + + // remove eles in place on calling collection + unmerge: function( toRemove ){ + var cy = this._private.cy; + + if( !toRemove ){ + return this; + } + + if( $$.is.string(toRemove) ){ + var selector = toRemove; + toRemove = cy.elements(selector); + } + + for( var i = 0; i < toRemove.length; i++ ){ + this.unmergeOne( toRemove[i] ); + } + + return this; // chaining + }, + + map: function( mapFn, thisArg ){ + var arr = []; + var eles = this; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + var ret = mapFn.apply( thisArg, [ele, i, eles] ); + + arr.push( ret ); + } + + return arr; + }, + + stdFilter: function( fn, thisArg ){ + var filterEles = []; + var eles = this; + var cy = this._private.cy; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + var include = fn.apply( thisArg, [ele, i, eles] ); + + if( include ){ + filterEles.push( ele ); + } + } + + return new $$.Collection( cy, filterEles ); + }, + + max: function( valFn, thisArg ){ + var max = -Infinity; + var maxEle; + var eles = this; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + var val = valFn.apply( thisArg, [ ele, i, eles ] ); + + if( val > max ){ + max = val; + maxEle = ele; + } + } + + return { + value: max, + ele: maxEle + }; + }, + + min: function( valFn, thisArg ){ + var min = Infinity; + var minEle; + var eles = this; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + var val = valFn.apply( thisArg, [ ele, i, eles ] ); + + if( val < min ){ + min = val; + minEle = ele; + } + } + + return { + value: min, + ele: minEle + }; + } + }); + +})( 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(); + }, + + isSimple: 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; + }, + + forEach: function(fn, thisArg){ + if( $$.is.fn(fn) ){ + + for(var i = 0; i < this.length; i++){ + var ele = this[i]; + var ret = fn.apply( thisArg, [ ele, i, this ] ); + + if( ret === false ){ break; } // exit each early on return false + } + } + + return this; + }, + + toArray: function(){ + var array = []; + + for(var i = 0; i < this.length; i++){ + array.push( this[i] ); + } + + return array; + }, + + slice: function(start, end){ + var array = []; + var thisSize = this.length; + + if( end == null ){ + end = thisSize; + } + + if( start == null ){ + start = 0; + } + + if( start < 0 ){ + start = thisSize + start; + } + + if( end < 0 ){ + end = thisSize + end; + } + + for(var i = start; i >= 0 && i < end && i < thisSize; i++){ + array.push( this[i] ); + } + + return new $$.Collection(this.cy(), array); + }, + + size: function(){ + return this.length; + }, + + eq: function(i){ + return this[i] || new $$.Collection( this.cy() ); + }, + + first: function(){ + return this[0] || new $$.Collection( this.cy() ); + }, + + last: function(){ + return this[ this.length - 1 ] || 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 ); + }, + + zDepth: function(){ + var ele = this[0]; + if( !ele ){ return undefined; } + + var _p = ele._private; + var group = _p.group; + + if( group === 'nodes' ){ + return _p.data.parent ? ele.parents().size() : 0; + } else { + var src = _p.source; + var tgt = _p.target; + var srcDepth = src._private.data.parent ? src.parents().size() : 0; + var tgtDepth = tgt._private.data.parent ? tgt.parents().size() : 0; + + return Math.max( srcDepth - 1, tgtDepth - 1, 0 ) + 0.5; // depth of deepest parent and just a bit above + } + } + }); + + $$.Collection.zIndexSort = function(a, b){ + var cy = a.cy(); + var a_p = a._private; + var b_p = b._private; + var zDiff = a_p.style['z-index'].value - b_p.style['z-index'].value; + var depthA = 0; + var depthB = 0; + var hasCompoundNodes = cy.hasCompoundNodes(); + var aIsNode = a_p.group === 'nodes'; + var aIsEdge = a_p.group === 'edges'; + var bIsNode = b_p.group === 'nodes'; + var bIsEdge = b_p.group === 'edges'; + + // no need to calculate element depth if there is no compound node + if( hasCompoundNodes ){ + depthA = a.zDepth(); + depthB = b.zDepth(); + } + + var depthDiff = depthA - depthB; + var sameDepth = depthDiff === 0; + + if( sameDepth ){ + + if( aIsNode && bIsEdge ){ + return 1; // 'a' is a node, it should be drawn later + + } else if( aIsEdge && bIsNode ){ + return -1; // 'a' is an edge, it should be drawn first + + } else { // both nodes or both edges + if( zDiff === 0 ){ // same z-index => compare indices in the core (order added to graph w/ last on top) + return a_p.index - b_p.index; + } else { + return zDiff; + } + } + + // elements on different level + } else { + return depthDiff; // deeper element should be drawn later + } + + }; + +})( cytoscape ); + +;(function($$){ 'use strict'; + + // Functions for layouts on nodes + //////////////////////////////////////////////////////////////////////////////////////////////////// + + $$.fn.eles({ + + // using standard layout options, apply position function (w/ or w/o animation) + layoutPositions: function( layout, options, fn ){ + var nodes = this.nodes(); + var cy = this.cy(); + + layout.trigger({ type: 'layoutstart', layout: layout }); + + if( options.animate ){ + for( var i = 0; i < nodes.length; i++ ){ + var node = nodes[i]; + var lastNode = i === nodes.length - 1; + + var newPos = fn.call( node, i, node ); + var pos = node.position(); + + if( !$$.is.number(pos.x) || !$$.is.number(pos.y) ){ + node.silentPosition({ x: 0, y: 0 }); + } + + node.animate({ + position: newPos + }, { + duration: options.animationDuration, + step: !lastNode ? undefined : function(){ + if( options.fit ){ + cy.fit( options.padding ); + } + }, + complete: !lastNode ? undefined : function(){ + if( options.zoom != null ){ + cy.zoom( options.zoom ); + } + + if( options.pan ){ + cy.pan( options.pan ); + } + + if( options.fit ){ + cy.fit( options.padding ); + } + + layout.one('layoutstop', options.stop); + layout.trigger({ type: 'layoutstop', layout: layout }); + } + }); + } + + layout.one('layoutready', options.ready); + layout.trigger({ type: 'layoutready', layout: layout }); + } else { + nodes.positions( fn ); + + if( options.fit ){ + cy.fit( options.padding ); + } + + if( options.zoom != null ){ + cy.zoom( options.zoom ); + } + + if( options.pan ){ + cy.pan( options.pan ); + } + + layout.one('layoutready', options.ready); + layout.trigger({ type: 'layoutready', layout: layout }); + + layout.one('layoutstop', options.stop); + layout.trigger({ type: 'layoutstop', layout: layout }); + } + + return this; // chaining + }, + + layout: function( options ){ + var cy = this.cy(); + + cy.layout( $$.util.extend({}, options, { + eles: this + }) ); + + return this; + }, + + makeLayout: function( options ){ + var cy = this.cy(); + + return cy.makeLayout( $$.util.extend({}, options, { + eles: this + }) ); + } + + }); + +})( cytoscape ); + +;(function($$){ 'use strict'; + + $$.fn.eles({ + + // fully updates (recalculates) the style for the elements + updateStyle: function( notifyRenderer ){ + var cy = this._private.cy; + + if( !cy.styleEnabled() ){ return this; } + + if( cy._private.batchingStyle ){ + var bEles = cy._private.batchStyleEles; + + for( var i = 0; i < this.length; i++ ){ + var ele = this[i]; + + if( !bEles.ids[ ele._private.id ] ){ + bEles.push( ele ); + } + } + + return this; // chaining and exit early when batching + } + + var style = cy.style(); + notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false; + + style.apply( this ); + + var updatedCompounds = this.updateCompoundBounds(); + var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this; + + if( notifyRenderer ){ + toNotify.rtrigger('style'); // let renderer know we changed style + } else { + toNotify.trigger('style'); // just fire the event + } + return this; // chaining + }, + + // just update the mappers in the elements' styles; cheaper than eles.updateStyle() + updateMappers: function( notifyRenderer ){ + var cy = this._private.cy; + var style = cy.style(); + notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false; + + if( !cy.styleEnabled() ){ return this; } + + style.updateMappers( this ); + + var updatedCompounds = this.updateCompoundBounds(); + var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this; + + if( notifyRenderer ){ + toNotify.rtrigger('style'); // let renderer know we changed style + } else { + toNotify.trigger('style'); // just fire the event + } + return this; // chaining + }, + + // get the specified css property as a rendered value (i.e. on-screen value) + // or get the whole rendered style if no property specified (NB doesn't allow setting) + renderedCss: function( property ){ + var cy = this.cy(); + if( !cy.styleEnabled() ){ return this; } + + var ele = this[0]; + + if( ele ){ + var renstyle = ele.cy().style().getRenderedStyle( ele ); + + if( property === undefined ){ + return renstyle; + } else { + return renstyle[ property ]; + } + } + }, + + // read the calculated css style of the element or override the style (via a bypass) + css: function( name, value ){ + var cy = this.cy(); + + if( !cy.styleEnabled() ){ return this; } + + var updateTransitions = false; + var style = cy.style(); + + if( $$.is.plainObject(name) ){ // then extend the bypass + var props = name; + style.applyBypass( this, props, updateTransitions ); + + var updatedCompounds = this.updateCompoundBounds(); + var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this; + toNotify.rtrigger('style'); // let the renderer know we've updated style + + } else if( $$.is.string(name) ){ + + if( value === undefined ){ // then get the property from the style + var ele = this[0]; + + if( ele ){ + return 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, updateTransitions ); + + var updatedCompounds = this.updateCompoundBounds(); + var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this; + toNotify.rtrigger('style'); // let the renderer know we've updated style + } + + } else if( name === undefined ){ + var ele = this[0]; + + if( ele ){ + return style.getRawStyle( ele ); + } else { // empty collection => can't get any value + return; + } + } + + return this; // chaining + }, + + removeCss: function( names ){ + var cy = this.cy(); + + if( !cy.styleEnabled() ){ return this; } + + var updateTransitions = false; + var style = cy.style(); + var eles = this; + + if( names === undefined ){ + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + + style.removeAllBypasses( ele, updateTransitions ); + } + } else { + names = names.split(/\s+/); + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + + style.removeBypasses( ele, names, updateTransitions ); + } + } + + var updatedCompounds = this.updateCompoundBounds(); + var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this; + toNotify.rtrigger('style'); // let the renderer know we've updated style + + return this; // chaining + }, + + show: function(){ + this.css('display', 'element'); + return this; // chaining + }, + + hide: function(){ + this.css('display', 'none'); + return this; // chaining + }, + + visible: function(){ + var cy = this.cy(); + if( !cy.styleEnabled() ){ return true; } + + var ele = this[0]; + var hasCompoundNodes = cy.hasCompoundNodes(); + + if( ele ){ + var style = ele._private.style; + + if( + style['visibility'].value !== 'visible' + || style['display'].value !== 'element' + ){ + return false; + } + + if( ele._private.group === 'nodes' ){ + if( !hasCompoundNodes ){ return true; } + + var parents = ele._private.data.parent ? ele.parents() : null; + + if( parents ){ + for( var i = 0; i < parents.length; i++ ){ + var parent = parents[i]; + var pStyle = parent._private.style; + var pVis = pStyle['visibility'].value; + var pDis = pStyle['display'].value; + + if( pVis !== 'visible' || pDis !== 'element' ){ + return false; + } + } + } + + return true; + } else { + var src = ele._private.source; + var tgt = ele._private.target; + + return src.visible() && tgt.visible(); + } + + } + }, + + hidden: function(){ + var ele = this[0]; + + if( ele ){ + return !ele.visible(); + } + }, + + effectiveOpacity: function(){ + var cy = this.cy(); + if( !cy.styleEnabled() ){ return 1; } + + var hasCompoundNodes = cy.hasCompoundNodes(); + var ele = this[0]; + + if( ele ){ + var _p = ele._private; + var parentOpacity = _p.style.opacity.value; + + if( !hasCompoundNodes ){ return parentOpacity; } + + var parents = !_p.data.parent ? null : ele.parents(); + + if( parents ){ + for( var i = 0; i < parents.length; i++ ){ + var parent = parents[i]; + var opacity = parent._private.style.opacity.value; + + parentOpacity = opacity * parentOpacity; + } + } + + return parentOpacity; + } + }, + + transparent: function(){ + var ele = this[0]; + var hasCompoundNodes = ele.cy().hasCompoundNodes(); + + if( ele ){ + if( !hasCompoundNodes ){ + return ele._private.style.opacity === 0; + } else { + return ele.effectiveOpacity() === 0; + } + } + }, + + isFullAutoParent: function(){ + var cy = this.cy(); + if( !cy.styleEnabled() ){ return false; } + + var ele = this[0]; + + if( ele ){ + var autoW = ele._private.style['width'].value === 'auto'; + var autoH = ele._private.style['height'].value === 'auto'; + + return ele.isParent() && autoW && autoH; + } + } + + }); + + + $$.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]; + var able = !params.ableField || ele._private[params.ableField]; + + if( params.overrideAble ){ + var overrideAble = params.overrideAble(ele); + + if( overrideAble !== undefined ){ + able = overrideAble; + + if( !overrideAble ){ return this; } // to save cycles assume not able for all on override + } + } + + if( able ){ + ele._private[params.field] = params.value; + } + } + 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.overrideField ){ + var val = params.overrideField(ele); + + if( val !== undefined ){ + return val; + } + } + + return ele._private[ params.field ]; + } + }; + + $$.elesfn[ params.on ] = defineSwitchFunction({ + event: params.on, + field: params.field, + ableField: params.ableField, + overrideAble: params.overrideAble, + value: true + }); + + $$.elesfn[ params.off ] = defineSwitchFunction({ + event: params.off, + field: params.field, + ableField: params.ableField, + overrideAble: params.overrideAble, + value: false + }); + } + + defineSwitchSet({ + field: 'locked', + overrideField: function(ele){ + return ele.cy().autolock() ? true : undefined; + }, + on: 'lock', + off: 'unlock' + }); + + defineSwitchSet({ + field: 'grabbable', + overrideField: function(ele){ + return ele.cy().autoungrabify() ? false : undefined; + }, + on: 'grabify', + off: 'ungrabify' + }); + + defineSwitchSet({ + field: 'selected', + ableField: 'selectable', + overrideAble: function(ele){ + return ele.cy().autounselectify() ? false : undefined; + }, + on: 'select', + off: 'unselect' + }); + + defineSwitchSet({ + field: 'selectable', + overrideField: function(ele){ + return ele.cy().autounselectify() ? false : undefined; + }, + on: 'selectify', + off: 'unselectify' + }); + + $$.elesfn.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'; + + // DAG functions + ////////////////////////// + + $$.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 = 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, { unique: true } ).filter( selector ); + }, + + // get the leaf nodes in the DAG + leaves: function( selector ){ + var eles = this; + var leaves = []; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + if( !ele.isNode() ){ + continue; + } + + var hasEdgesPointingOut = ele.connectedEdges(function(){ + return this.data('source') === ele.id() && this.data('target') !== ele.id(); + }).length > 0; + + if( !hasEdgesPointingOut ){ + leaves.push( ele ); + } + } + + return new $$.Collection( this._private.cy, leaves, { unique: true } ).filter( selector ); + }, + + // normally called children in graph theory + // these nodes =edges=> outgoing nodes + outgoers: function( selector ){ + var eles = this; + var oEles = []; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + var eleId = ele.id(); + + if( !ele.isNode() ){ continue; } + + var edges = ele._private.edges; + for( var j = 0; j < edges.length; j++ ){ + var edge = edges[j]; + var srcId = edge._private.data.source; + var tgtId = edge._private.data.target; + + if( srcId === eleId && tgtId !== eleId ){ + oEles.push( edge ); + oEles.push( edge.target()[0] ); + } + } + } + + return new $$.Collection( this._private.cy, oEles, { unique: true } ).filter( selector ); + }, + + // aka DAG descendants + successors: function( selector ){ + var eles = this; + var sEles = []; + var sElesIds = {}; + + for(;;){ + var outgoers = eles.outgoers(); + + if( outgoers.length === 0 ){ break; } // done if no outgoers left + + var newOutgoers = false; + for( var i = 0; i < outgoers.length; i++ ){ + var outgoer = outgoers[i]; + var outgoerId = outgoer.id(); + + if( !sElesIds[ outgoerId ] ){ + sElesIds[ outgoerId ] = true; + sEles.push( outgoer ); + newOutgoers = true; + } + } + + if( !newOutgoers ){ break; } // done if touched all outgoers already + + eles = outgoers; + } + + return new $$.Collection( this._private.cy, sEles, { unique: true } ).filter( selector ); + }, + + // normally called parents in graph theory + // these nodes <=edges= incoming nodes + incomers: function( selector ){ + var eles = this; + var oEles = []; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + var eleId = ele.id(); + + if( !ele.isNode() ){ continue; } + + var edges = ele._private.edges; + for( var j = 0; j < edges.length; j++ ){ + var edge = edges[j]; + var srcId = edge._private.data.source; + var tgtId = edge._private.data.target; + + if( tgtId === eleId && srcId !== eleId ){ + oEles.push( edge ); + oEles.push( edge.source()[0] ); + } + } + } + + return new $$.Collection( this._private.cy, oEles, { unique: true } ).filter( selector ); + }, + + // aka DAG ancestors + predecessors: function( selector ){ + var eles = this; + var pEles = []; + var pElesIds = {}; + + for(;;){ + var incomers = eles.incomers(); + + if( incomers.length === 0 ){ break; } // done if no incomers left + + var newIncomers = false; + for( var i = 0; i < incomers.length; i++ ){ + var incomer = incomers[i]; + var incomerId = incomer.id(); + + if( !pElesIds[ incomerId ] ){ + pElesIds[ incomerId ] = true; + pEles.push( incomer ); + newIncomers = true; + } + } + + if( !newIncomers ){ break; } // done if touched all incomers already + + eles = incomers; + } + + return new $$.Collection( this._private.cy, pEles, { unique: true } ).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, { unique: true } ) ).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 src && selector ? src.filter( selector ) : src; + }, + + target: function( selector ){ + var ele = this[0]; + var tgt; + + if( ele ){ + tgt = ele._private.target; + } + + return tgt && selector ? tgt.filter( selector ) : tgt; + }, + + sources: defineSourceFunction({ + attr: 'source' + }), + + targets: defineSourceFunction({ + attr: 'target' + }) + }); + + function defineSourceFunction( params ){ + return function( selector ){ + var sources = []; + 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, { unique: true } ).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, { unique: true } ); + }; + } + + $$.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, { unique: true } ).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, { unique: true } ).filter( selector ); + }, + + parallelEdges: defineParallelEdgesFunction(), + + codirectedEdges: defineParallelEdgesFunction({ + codirected: true + }) + }); + + function defineParallelEdgesFunction(params){ + var defaults = { + codirected: false + }; + params = $$.util.extend({}, defaults, params); + + return function( selector ){ + var 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, { unique: true } ).filter( selector ); + }; + + } + + +})( cytoscape ); + +;(function($$){ 'use strict'; + + $$.fn.eles({ + + fit: function(){}, + center: function(){} + + }); + +})( 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; + this._private.pointers[remId] = undefined; + + 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; + this._private.pointers[remId] = undefined; + + 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.showFps = options.showFps; + + 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.topCanvas = this.data.canvases[0]; + + 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.wheelSensitivity = options.wheelSensitivity; + this.motionBlurEnabled = options.motionBlur === undefined ? true : options.motionBlur; // on by default + this.forcedPixelRatio = options.pixelRatio; + this.motionBlur = true; // for initial kick off + + 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) { + var types; + + if( $$.is.array( params.type ) ){ + types = params.type; + + } else { + types = [ params.type ]; + } + + for( var i = 0; i < types.length; i++ ){ + var type = types[i]; + + switch( type ){ + case 'destroy': + 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( type === 'load' || type === 'resize' ){ + this.invalidateContainerClientCoordsCache(); + this.matchCanvasSize(this.data.container); + } + } // for + + this.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true; + this.data.canvasNeedsRedraw[CanvasRenderer.DRAG] = 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); + }; + + var transform = function(x, y, size, angle, translation){ + angle = -angle; // b/c of notation used in arrow draw fn + + var xRotated = x * Math.cos(angle) - y * Math.sin(angle); + var yRotated = x * Math.sin(angle) + y * Math.cos(angle); + + var xScaled = xRotated * size; + var yScaled = yRotated * size; + + var xTranslated = xScaled + translation.x; + var yTranslated = yScaled + translation.y; + + return { + x: xTranslated, + y: yTranslated + }; + }; + + 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, size, angle, translation) { + var points = arrowShapes['arrow']._points; + + for (var i = 0; i < points.length / 2; i++) { + var pt = transform( points[i * 2], points[i * 2 + 1], size, angle, translation ); + + context.lineTo(pt.x, pt.y); + } + + }, + + spacing: function(edge) { + return 0; + }, + + gap: function(edge) { + return edge._private.style['width'].pxValue * 2; + } + }; + + arrowShapes['triangle'] = arrowShapes['arrow']; + + arrowShapes['triangle-backcurve'] = { + _ctrlPt: [ 0, -0.15 ], + + collide: function(x, y, centerX, centerY, width, height, direction, padding) { + var points = arrowShapes['triangle']._points; + +// console.log("collide(): " + direction); + + return $$.math.pointInsidePolygon( + x, y, points, centerX, centerY, width, height, direction, padding); + }, + + roughCollide: bbCollide, + + draw: function(context, size, angle, translation) { + var points = arrowShapes['triangle']._points; + var firstPt; + + for (var i = 0; i < points.length / 2; i++) { + var pt = transform( points[i * 2], points[i * 2 + 1], size, angle, translation ); + + if( i === 0 ){ + firstPt = pt; + } + + context.lineTo(pt.x, pt.y); + } + + var ctrlPt = this._ctrlPt; + var ctrlPtTrans = transform( ctrlPt[0], ctrlPt[1], size, angle, translation ); + + context.quadraticCurveTo( ctrlPtTrans.x, ctrlPtTrans.y, firstPt.x, firstPt.y ); + }, + + spacing: function(edge) { + return 0; + }, + + gap: function(edge) { + return edge._private.style['width'].pxValue * 2; + } + }; + + + arrowShapes['triangle-tee'] = { + _points: [ + -0.15, -0.3, + 0, 0, + 0.15, -0.3, + -0.15, -0.3 + ], + + _pointsTee: [ + -0.15, -0.4, + -0.15, -0.5, + 0.15, -0.5, + 0.15, -0.4 + ], + + collide: function(x, y, centerX, centerY, width, height, direction, padding) { + var triPts = arrowShapes['triangle-tee']._points; + var teePts = arrowShapes['triangle-tee']._pointsTee; + + var inside = $$.math.pointInsidePolygon(x, y, teePts, centerX, centerY, width, height, direction, padding) + || $$.math.pointInsidePolygon(x, y, triPts, centerX, centerY, width, height, direction, padding); + + return inside; + }, + + roughCollide: bbCollide, + + draw: function(context, size, angle, translation) { + var triPts = arrowShapes['triangle-tee']._points; + for (var i = 0; i < triPts.length / 2; i++){ + var pt = transform( triPts[ i * 2 ], triPts[ i * 2 + 1 ], size, angle, translation ); + + context.lineTo( pt.x, pt.y ); + } + + var teePts = arrowShapes['triangle-tee']._pointsTee; + var firstTeePt = transform( teePts[0], teePts[1], size, angle, translation ); + context.moveTo( firstTeePt.x, firstTeePt.y ); + + for (var i = 0; i < teePts.length / 2; i++){ + var pt = transform( teePts[ i * 2 ], teePts[ i * 2 + 1 ], size, angle, translation ); + + context.lineTo( pt.x, pt.y ); + } + }, + + spacing: function(edge) { + return 0; + }, + + gap: function(edge) { + return edge._private.style['width'].pxValue * 2; + } + }; + + arrowShapes['half-triangle-overshot'] = { + _points: [ + 0, -0.25, + -0.5, -0.25, + 0.5, 0.25 + ], + + leavePathOpen: true, + matchEdgeWidth: true, + + collide: function(x, y, centerX, centerY, width, height, direction, padding) { + var points = this._points; + +// console.log("collide(): " + direction); + + return $$.math.pointInsidePolygon( + x, y, points, centerX, centerY, width, height, direction, padding); + }, + + roughCollide: bbCollide, + + draw: function(context, size, angle, translation) { + var points = this._points; + + for (var i = 0; i < points.length / 2; i++) { + var pt = transform( points[i * 2], points[i * 2 + 1], size, angle, translation ); + + context.lineTo(pt.x, pt.y); + } + }, + + spacing: function(edge) { + return 0; + }, + + gap: function(edge) { + return edge._private.style['width'].pxValue * 2; + } + }; + + 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, size, angle, translation) { + context.arc(translation.x, translation.y, arrowShapes['circle']._baseRadius * size, 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, size, angle, translation) { + var points = arrowShapes['inhibitor']._points; + + for (var i = 0; i < points.length / 2; i++) { + var pt = transform( points[i * 2], points[i * 2 + 1], size, angle, translation ); + + context.lineTo(pt.x, pt.y); + } + }, + + spacing: function(edge) { + return 1; + }, + + gap: function(edge) { + return 1; + } + }; + + arrowShapes['tee'] = arrowShapes['inhibitor']; + + arrowShapes['square'] = { + _points: [ + -0.15, 0.00, + 0.15, 0.00, + 0.15, -0.3, + -0.15, -0.3 + ], + + 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, size, angle, translation) { + var points = arrowShapes['square']._points; + + for (var i = 0; i < points.length / 2; i++) { + var pt = transform( points[i * 2], points[i * 2 + 1], size, angle, translation ); + + context.lineTo(pt.x, pt.y); + } + }, + + spacing: function(edge) { + return 0; + }, + + gap: function(edge) { + return edge._private.style['width'].pxValue * 2; + } + }; + + arrowShapes['diamond'] = { + _points: [ + -0.15, -0.15, + 0, -0.3, + 0.15, -0.15, + 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, size, angle, translation) { + var points = arrowShapes['diamond']._points; + + for (var i = 0; i < points.length / 2; i++) { + var pt = transform( points[i * 2], points[i * 2 + 1], size, angle, translation ); + + context.lineTo(pt.x, pt.y); + } + }, + + spacing: function(edge) { + return 0; + }, + + gap: function(edge) { + return edge._private.style['width'].pxValue; + } + }; + +})( 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 width = node.outerWidth(); + var height = node.outerHeight(); + var hw = width/2; + var hh = height/2; + var pos = node._private.position; + + if( + pos.x - hw <= x && x <= pos.x + hw // bb check x + && + pos.y - hh <= y && y <= pos.y + hh // bb check y + ){ + var visible = !visibleElementsOnly || ( node.visible() && !node.transparent() ); + + // exit early if invisible edge and must be visible + if( visibleElementsOnly && !visible ){ + return; + } + + var shape = CanvasRenderer.nodeShapes[ self.getNodeShape(node) ]; + var borderWO = node._private.style['border-width'].pxValue / 2; + + if( + 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 src = edge._private.source; + var tgt = edge._private.target; + var inEdgeBB = false; + + // exit early if invisible edge and must be visible + var passedVisibilityCheck; + var passesVisibilityCheck = function(){ + if( passedVisibilityCheck !== undefined ){ + return passedVisibilityCheck; + } + + if( !visibleElementsOnly ){ + passedVisibilityCheck = true; + return true; + } + + var visible = edge.visible() && !edge.transparent(); + if( visible ){ + passedVisibilityCheck = true; + return true; + } + + passedVisibilityCheck = false; + return false; + }; + + if (rs.edgeType === 'self') { + if( + ( + (inEdgeBB = $$.math.inBezierVicinity(x, y, rs.startX, rs.startY, rs.cp2ax, rs.cp2ay, rs.selfEdgeMidX, rs.selfEdgeMidY, widthSq)) + && passesVisibilityCheck() && + ( widthSq + edgeThreshold > $$.math.sqDistanceToQuadraticBezier(x, y, rs.startX, rs.startY, rs.cp2ax, rs.cp2ay, rs.selfEdgeMidX, rs.selfEdgeMidY) ) + ) + || + ( + (inEdgeBB = $$.math.inBezierVicinity(x, y, rs.selfEdgeMidX, rs.selfEdgeMidY, rs.cp2cx, rs.cp2cy, rs.endX, rs.endY, widthSq)) + && passesVisibilityCheck() && + ( 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 radius = style['haystack-radius'].value; + var halfRadius = radius/2; // b/c have to half width/height + + 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 * halfRadius; + var startY = srcPos.y + rs.source.y * srcH * halfRadius; + var endX = tgtPos.x + rs.target.x * tgtW * halfRadius; + var endY = tgtPos.y + rs.target.y * tgtH * halfRadius; + + if( + (inEdgeBB = $$.math.inLineVicinity(x, y, startX, startY, endX, endY, width2)) + && passesVisibilityCheck() && + widthSq + edgeThreshold > $$.math.sqDistanceToFiniteLine( x, y, startX, startY, endX, endY ) + ){ + near.push( edge ); + } + + } else if (rs.edgeType === 'straight') { + if( + (inEdgeBB = $$.math.inLineVicinity(x, y, rs.startX, rs.startY, rs.endX, rs.endY, width2)) + && passesVisibilityCheck() && + widthSq + edgeThreshold > $$.math.sqDistanceToFiniteLine(x, y, rs.startX, rs.startY, rs.endX, rs.endY) + ){ + near.push( edge ); + } + + } else if (rs.edgeType === 'bezier') { + if( + (inEdgeBB = $$.math.inBezierVicinity(x, y, rs.startX, rs.startY, rs.cp2x, rs.cp2y, rs.endX, rs.endY, widthSq)) + && passesVisibilityCheck() && + (widthSq + edgeThreshold > $$.math.sqDistanceToQuadraticBezier(x, y, rs.startX, rs.startY, rs.cp2x, rs.cp2y, rs.endX, rs.endY)) + ){ + near.push( edge ); + } + } + + if( inEdgeBB && passesVisibilityCheck() && 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++ ){ + if( nodes[i].visible() && !nodes[i].transparent() ){ + eles.push( nodes[i] ); + } + } + + for( var i = 0; i < edges.length; i++ ){ + if( edges[i].visible() && !edges[i].transparent() ){ + 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 ) + }); + + var mid = { + x: qbezierAt( pts[0], pts[2], pts[4], 0.5 ), + y: qbezierAt( pts[1], pts[3], pts[5], 0.5 ) + }; + + bpts.push( mid ); + + if( rs.edgeType === 'self' ){ + rs.midX = rs.selfEdgeMidX; + rs.midY = rs.selfEdgeMidY; + } else { + rs.midX = mid.x; + rs.midY = mid.y; + } + + 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._private.source._private.position; + var tgtPos = edge._private.target._private.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 r = this; + 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 cacheKey = ele._private.labelKey; + var cache = r.labelDimCache || (r.labelDimCache = {}); + + if( cache[cacheKey] ){ + return cache[cacheKey]; + } + + var div = this.labelCalcDiv; + + if( !div ){ + div = this.labelCalcDiv = document.createElement('div'); + document.body.appendChild( div ); + } + + var ds = div.style; + + // from ele style + ds.fontFamily = family; + ds.fontStyle = fStyle; + ds.fontSize = size; + // ds.fontVariant = variant; + ds.fontWeight = weight; + + // forced style + ds.position = 'absolute'; + ds.left = '-9999px'; + ds.top = '-9999px'; + ds.zIndex = '-1'; + ds.visibility = 'hidden'; + ds.pointerEvents = 'none'; + ds.padding = '0'; + ds.lineHeight = '1'; + + // put label content in div + div.textContent = text; + + cache[cacheKey] = { + width: div.clientWidth, + height: div.clientHeight + }; + + return cache[cacheKey]; + }; + + CanvasRenderer.prototype.recalculateRenderedStyle = function( eles ){ + var edges = []; + var nodes = []; + var handledEdge = {}; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + var _p = ele._private; + var rs = _p.rscratch; + var rstyle = _p.rstyle; + var id = _p.data.id; + var bbStyleSame = rs.boundingBoxKey != null && _p.boundingBoxKey === rs.boundingBoxKey; + var labelStyleSame = rs.labelKey != null && _p.labelKey === rs.labelKey; + var styleSame = bbStyleSame && labelStyleSame; + + if( ele._private.group === 'nodes' ){ + var pos = _p.position; + var posSame = rstyle.nodeX != null && rstyle.nodeY != null && pos.x === rstyle.nodeX && pos.y === rstyle.nodeY; + + if( !posSame || !styleSame ){ + nodes.push( ele ); + } + + rstyle.nodeX = pos.x; + rstyle.nodeY = pos.y; + } else { // edges + + var srcPos = ele._private.source._private.position; + var tgtPos = ele._private.target._private.position; + var srcSame = rstyle.srcX != null && rstyle.srcY != null && srcPos.x === rstyle.srcX && srcPos.y === rstyle.srcY; + var tgtSame = rstyle.tgtX != null && rstyle.tgtY != null && tgtPos.x === rstyle.tgtX && tgtPos.y === rstyle.tgtY; + var positionsSame = srcSame && tgtSame; + + if( !positionsSame || !styleSame ){ + var curveType = _p.style['curve-style'].value; + + if( curveType === 'bezier' ){ + if( !handledEdge[ id ] ){ + edges.push( ele ); + handledEdge[ id ] = true; + + var parallelEdges = ele.parallelEdges(); + for( var i = 0; i < parallelEdges.length; i++ ){ + var pEdge = parallelEdges[i]; + var pId = pEdge._private.data.id; + + if( !handledEdge[ pId ] ){ + edges.push( pEdge ); + handledEdge[ pId ] = true; + } + + } + } + } else { + edges.push( ele ); + } + } // if positions diff + + // update rstyle positions + rstyle.srcX = srcPos.x; + rstyle.srcY = srcPos.y; + rstyle.tgtX = tgtPos.x; + rstyle.tgtY = tgtPos.y; + + } // if edges + + rs.boundingBoxKey = _p.boundingBoxKey; + rs.labelKey = _p.labelKey; + } + + this.recalculateEdgeProjections( edges ); + this.recalculateLabelProjections( nodes, edges ); + }; + + CanvasRenderer.prototype.recalculateLabelProjections = function( nodes, edges ){ + for( var i = 0; i < nodes.length; i++ ){ + this.recalculateNodeLabelProjection( nodes[i] ); + } + + for( var i = 0; i < edges.length; i++ ){ + this.recalculateEdgeLabelProjection( edges[i] ); + } + }; + + CanvasRenderer.prototype.recalculateEdgeProjections = function( edges ){ + this.findEdgeControlPoints( edges ); + }; + + + // Find edge control points + CanvasRenderer.prototype.findEdgeControlPoints = function(edges) { + if( !edges || edges.length === 0 ){ return; } + + var hashTable = {}; + 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; + var edgeIsUnbundled = style['curve-style'].value === 'unbundled-bezier'; + + // 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( edgeIsUnbundled ){ + pairId = 'unbundled' + edge._private.data.id; + } + + if (hashTable[pairId] == null) { + hashTable[pairId] = []; + pairIds.push( pairId ); + } + + hashTable[pairId].push( edge ); + + if( edgeIsUnbundled ){ + hashTable[pairId].hasUnbundled = true; + } + } + + 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]; + var pairEdges = hashTable[pairId]; + + // for each pair id, the edges should be sorted by index + pairEdges.sort(function(edge1, edge2){ + return edge1._private.index - edge2._private.index; + }); + + src = pairEdges[0]._private.source; + tgt = pairEdges[0]._private.target; + + // make sure src/tgt distinction is consistent + // (src/tgt in this case are just for ctrlpts and don't actually have to be true src/tgt) + if( src._private.data.id > tgt._private.data.id ){ + var temp = src; + src = tgt; + tgt = temp; + } + + 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( (pairEdges.length > 1 && src !== tgt) || pairEdges.hasUnbundled ){ + + // pt outside src shape to calc distance/displacement from src to tgt + var srcOutside = srcShape.intersectLine( + srcPos.x, + srcPos.y, + srcW, + srcH, + tgtPos.x, + tgtPos.y, + 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 < pairEdges.length; i++) { + edge = pairEdges[i]; + rs = edge._private.rscratch; + + var edgeIndex1 = rs.lastEdgeIndex; + var edgeIndex2 = i; + + var numEdges1 = rs.lastNumEdges; + var numEdges2 = pairEdges.length; + + 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; + var edgeIsUnbundled = eStyle['curve-style'].value === 'unbundled-bezier'; + + 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) || edgeIsUnbundled) ){ + // console.log('edge ctrl pt cache HIT') + continue; // then the control points haven't changed and we can skip calculating them + } else { + rs.lastSrcCtlPtX = srcX2; + rs.lastSrcCtlPtY = srcY2; + rs.lastSrcCtlPtW = srcW2; + rs.lastSrcCtlPtH = srcH2; + rs.lastTgtCtlPtX = tgtX2; + rs.lastTgtCtlPtY = tgtY2; + rs.lastTgtCtlPtW = tgtW2; + rs.lastTgtCtlPtH = tgtH2; + rs.lastEdgeIndex = edgeIndex2; + rs.lastNumEdges = numEdges2; + // console.log('edge ctrl pt cache MISS') + } + + // Self-edge + if ( src === tgt ) { + + rs.edgeType = 'self'; + + var j = i; + var loopDist = stepSize; + + if( edgeIsUnbundled ){ + j = 0; + loopDist = stepDist; + } + + // New -- fix for large nodes + rs.cp2ax = srcPos.x; + rs.cp2ay = srcPos.y - (1 + Math.pow(srcH, 1.12) / 100) * loopDist * (j / 3 + 1); + + rs.cp2cx = src._private.position.x - (1 + Math.pow(srcW, 1.12) / 100) * loopDist * (j / 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 (pairEdges.length % 2 === 1 + && i === Math.floor(pairEdges.length / 2) + && !edgeIsUnbundled ) { + + rs.edgeType = 'straight'; + + // Bezier edge + } else { + var normStepDist = (0.5 - pairEdges.length / 2 + i) * stepSize; + var manStepDist; + var sign = $$.math.signum( normStepDist ); + + if( edgeIsUnbundled ){ + manStepDist = stepDist; + } else { + manStepDist = stepDist !== undefined ? sign * stepDist : undefined; + } + + var distanceFromMidpoint = manStepDist !== undefined ? manStepDist : normStepDist; + + var w1 = (1 - stepWeight); + var w2 = stepWeight; + + var swappedDirection = edge._private.source !== src; + if( swappedDirection ){ + w1 = stepWeight; + w2 = (1 - stepWeight); + } + + var adjustedMidpt = { + x: midptSrcPts.x1 * w1 + midptSrcPts.x2 * w2, + y: midptSrcPts.y1 * w1 + midptSrcPts.y2 * w2 + }; + + 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 ); + } + } else if( rs.edgeType === 'straight' ){ + rs.midX = ( srcX2 + tgtX2 )/2; + rs.midY = ( srcY2 + tgtY2 )/2; + } + + // 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; + + if( !rscratch.haystack ){ + var angle = Math.random() * 2 * Math.PI; + + rscratch.source = { + x: Math.cos(angle), + y: Math.sin(angle) + }; + + var angle = Math.random() * 2 * Math.PI; + + rscratch.target = { + x: Math.cos(angle), + y: Math.sin(angle) + }; + + 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 = CanvasRenderer.prototype.getArrowHeight = function(edgeWidth) { + var cache = this.arrowWidthCache = this.arrowWidthCache || {}; + + var cachedVal = cache[edgeWidth]; + if( cachedVal ){ + return cachedVal; + } + + cachedVal = Math.max(Math.pow(edgeWidth * 13.37, 0.9), 29); + cache[edgeWidth] = cachedVal; + + return cachedVal; + }; + + +})( cytoscape ); + +;(function($$){ 'use strict'; + + var CanvasRenderer = $$('renderer', 'canvas'); + +// Draw edge + CanvasRenderer.prototype.drawEdge = function(context, edge, drawOverlayInstead) { + var rs = edge._private.rscratch; + var usePaths = CanvasRenderer.usePaths(); + + // if bezier ctrl pts can not be calculated, then die + if( rs.badBezier || ( (rs.edgeType === 'bezier' || rs.edgeType === 'straight') && isNaN(rs.startX)) ){ // extra isNaN() for safari 7.1 b/c it mangles ctrlpt calcs + 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' && !usePaths ){ + context.lineCap = 'butt'; + } + + } else { + var lineColor = style['line-color'].value; + + this.strokeStyle(context, lineColor[0], lineColor[1], lineColor[2], style.opacity.value); + + context.lineCap = 'butt'; + } + + var 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' ){ + var radius = style['haystack-radius'].value; + var halfRadius = radius/2; // b/c have to half width/height + + this.drawStyledEdge( + edge, + context, + rs.haystackPts = [ + rs.source.x * sourceW * halfRadius + sourcePos.x, + rs.source.y * sourceH * halfRadius + sourcePos.y, + rs.target.x * targetW * halfRadius + targetPos.x, + rs.target.y * targetH * halfRadius + targetPos.y + ], + lineStyle, + edgeWidth + ); + } else if (rs.edgeType === 'self') { + + var details = edge._private.rscratch; + var points = [details.startX, details.startY, details.cp2ax, + details.cp2ay, details.selfEdgeMidX, details.selfEdgeMidY, + details.selfEdgeMidX, details.selfEdgeMidY, + details.cp2cx, details.cp2cy, details.endX, details.endY]; + + var details = edge._private.rscratch; + this.drawStyledEdge(edge, context, points, 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' ){ + this.drawArrowheads(context, edge, drawOverlayInstead); + } else if ( rs.noArrowPlacement !== true && rs.startX !== undefined ){ + this.drawArrowheads(context, edge, drawOverlayInstead); + } + + }; + + + CanvasRenderer.prototype.drawStyledEdge = function( + edge, context, pts, type, width) { + + // 3 points given -> assume Bezier + // 2 -> assume straight + + var rs = edge._private.rscratch; + var canvasCxt = context; + var path; + var pathCacheHit = false; + var usePaths = CanvasRenderer.usePaths(); + + + 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( canvasCxt.setLineDash ){ // for very outofdate browsers + switch( type ){ + case 'dotted': + canvasCxt.setLineDash([ 1, 1 ]); + break; + + case 'dashed': + canvasCxt.setLineDash([ 6, 3 ]); + break; + + case 'solid': + canvasCxt.setLineDash([ ]); + break; + } + } + + if( !pathCacheHit ){ + if( context.beginPath ){ context.beginPath(); } + context.moveTo(pts[0], pts[1]); + + if (pts.length === 3 * 2) { // bezier + context.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5]); + } else if( pts.length === 3 * 2 * 2 ){ // double bezier loop + context.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5]); + context.quadraticCurveTo(pts[8], pts[9], pts[10], pts[11]); + } else { // line + context.lineTo(pts[2], pts[3]); + } + } + + context = canvasCxt; + if( usePaths ){ + context.stroke( path ); + } else { + context.stroke(); + } + + // reset any line dashes + if( context.setLineDash ){ // for very outofdate browsers + context.setLineDash([ ]); + } + + }; + + CanvasRenderer.prototype.drawArrowheads = function(context, edge, drawOverlayInstead) { + if( drawOverlayInstead ){ return; } // don't do anything for overlays + + var rs = edge._private.rscratch; + var self = this; + var isHaystack = rs.edgeType === 'haystack'; + + // Displacement gives direction for arrowhead orientation + var dispX, dispY; + var startX, startY, endX, endY; + + var srcPos = edge.source().position(); + var tgtPos = edge.target().position(); + + if( isHaystack ){ + startX = rs.haystackPts[0]; + startY = rs.haystackPts[1]; + endX = rs.haystackPts[2]; + endY = rs.haystackPts[3]; + } else { + startX = rs.arrowStartX; + startY = rs.arrowStartY; + endX = rs.arrowEndX; + endY = rs.arrowEndY; + } + + var style = edge._private.style; + + function drawArrowhead( prefix, x, y, dispX, dispY ){ + var arrowShape = style[prefix + '-arrow-shape'].value; + + if( arrowShape === 'none' ){ + return; + } + + var gco = context.globalCompositeOperation; + + context.globalCompositeOperation = 'destination-out'; + + self.fillStyle(context, 255, 255, 255, 1); + + + var arrowClearFill = style[prefix + '-arrow-fill'].value === 'hollow' ? 'both' : 'filled'; + var arrowFill = style[prefix + '-arrow-fill'].value; + + if( arrowShape === 'half-triangle-overshot' ){ + arrowFill = 'hollow'; + arrowClearFill = 'hollow'; + } + + self.drawArrowShape( edge, prefix, context, + arrowClearFill, style['width'].pxValue, style[prefix + '-arrow-shape'].value, + x, y, dispX, dispY + ); + + context.globalCompositeOperation = gco; + + var color = style[prefix + '-arrow-color'].value; + self.fillStyle(context, color[0], color[1], color[2], style.opacity.value); + + self.drawArrowShape( edge, prefix, context, + arrowFill, style['width'].pxValue, style[prefix + '-arrow-shape'].value, + x, y, dispX, dispY + ); + } + + dispX = startX - srcPos.x; + dispY = startY - srcPos.y; + + if( !isHaystack && !isNaN(startX) && !isNaN(startY) && !isNaN(dispX) && !isNaN(dispY) ){ + drawArrowhead( 'source', startX, startY, dispX, dispY ); + + } else { + // window.badArrow = true; + // debugger; + } + + var midX = rs.midX; + var midY = rs.midY; + + if( isHaystack ){ + midX = ( startX + endX )/2; + midY = ( startY + endY )/2; + } + + dispX = startX - endX; + dispY = startY - endY; + + if( rs.edgeType === 'self' ){ + dispX = 1; + dispY = -1; + } + + if( !isNaN(midX) && !isNaN(midY) ){ + drawArrowhead( 'mid-target', midX, midY, dispX, dispY ); + } + + dispX *= -1; + dispY *= -1; + + if( !isNaN(midX) && !isNaN(midY) ){ + drawArrowhead( 'mid-source', midX, midY, dispX, dispY ); + } + + dispX = endX - tgtPos.x; + dispY = endY - tgtPos.y; + + if( !isHaystack && !isNaN(endX) && !isNaN(endY) && !isNaN(dispX) && !isNaN(dispY) ){ + drawArrowhead( 'target', endX, endY, dispX, dispY ); + } + }; + + // Draw arrowshape + CanvasRenderer.prototype.drawArrowShape = function(edge, arrowType, context, fill, edgeWidth, shape, x, y, dispX, dispY) { + var usePaths = CanvasRenderer.usePaths(); + var rs = edge._private.rscratch; + var pathCacheHit = false; + var path; + var canvasContext = context; + var translation = { x: x, y: y }; + + // Negative of the angle + var angle = Math.asin(dispY / (Math.sqrt(dispX * dispX + dispY * dispY))); + + if (dispX < 0) { + angle = angle + Math.PI / 2; + } else { + angle = - (Math.PI / 2 + angle); + } + + var size = this.getArrowWidth( edgeWidth ); + var shapeImpl = CanvasRenderer.arrowShapes[shape]; + + // context.translate(x, y); + + if( usePaths ){ + var pathCacheKey = size + '$' + shape + '$' + angle + '$' + x + '$' + y; + rs.arrowPathCacheKey = rs.arrowPathCacheKey || {}; + rs.arrowPathCache = rs.arrowPathCache || {}; + + var alreadyCached = rs.arrowPathCacheKey[arrowType] === pathCacheKey; + if( alreadyCached ){ + path = context = rs.arrowPathCache[arrowType]; + pathCacheHit = true; + } else { + path = context = new Path2D(); + rs.arrowPathCacheKey[arrowType] = pathCacheKey; + rs.arrowPathCache[arrowType] = path; + } + } + + if( context.beginPath ){ context.beginPath(); } + + if( !pathCacheHit ){ + shapeImpl.draw(context, size, angle, translation); + } + + if( !shapeImpl.leavePathOpen && context.closePath ){ + context.closePath(); + } + + context = canvasContext; + + if( fill === 'filled' || fill === 'both' ){ + if( usePaths ){ + context.fill( path ); + } else { + context.fill(); + } + } + + if( fill === 'hollow' || fill === 'both' ){ + context.lineWidth = ( shapeImpl.matchEdgeWidth ? edgeWidth : 1 ); + context.lineJoin = 'miter'; + + if( usePaths ){ + context.stroke( path ); + } else { + context.stroke(); + } + + } + + // context.translate(-x, -y); + }; + +})( cytoscape ); +;(function($$){ 'use strict'; + + var CanvasRenderer = $$('renderer', 'canvas'); + + CanvasRenderer.prototype.getCachedImage = function(url, onLoad) { + var r = this; + var imageCache = r.imageCache = r.imageCache || {}; + + if( imageCache[url] && imageCache[url].image ){ + return imageCache[url].image; + } + + var cache = imageCache[url] = imageCache[url] || {}; + + var image = cache.image = new Image(); + image.addEventListener('load', onLoad); + image.src = url; + + return image; + }; + + CanvasRenderer.prototype.drawInscribedImage = function(context, img, node) { + var r = this; + var nodeX = node._private.position.x; + var nodeY = node._private.position.y; + var style = node._private.style; + var fit = style['background-fit'].value; + var xPos = style['background-position-x']; + var yPos = style['background-position-y']; + var repeat = style['background-repeat'].value; + var nodeW = node.width(); + var nodeH = node.height(); + var rs = node._private.rscratch; + var clip = style['background-clip'].value; + var shouldClip = clip === 'node'; + var imgOpacity = style['background-image-opacity'].value; + + var w = img.width; + var h = img.height; + + if( fit === 'contain' ){ + var scale = Math.min( nodeW/w, nodeH/h ); + + w *= scale; + h *= scale; + + } else if( fit === 'cover' ){ + var scale = Math.max( nodeW/w, nodeH/h ); + + w *= scale; + h *= scale; + } + + var x = (nodeX - nodeW/2); // left + if( xPos.units === '%' ){ + x += (nodeW - w) * xPos.value/100; + } else { + x += xPos.pxValue; + } + + var y = (nodeY - nodeH/2); // top + if( yPos.units === '%' ){ + y += (nodeH - h) * yPos.value/100; + } else { + y += yPos.pxValue; + } + + if( rs.pathCache ){ + x -= nodeX; + y -= nodeY; + + nodeX = 0; + nodeY = 0; + } + + var gAlpha = context.globalAlpha; + + context.globalAlpha = imgOpacity; + + if( repeat === 'no-repeat' ){ + + if( shouldClip ){ + context.save(); + + if( rs.pathCache ){ + context.clip( rs.pathCache ); + } else { + CanvasRenderer.nodeShapes[r.getNodeShape(node)].drawPath( + context, + nodeX, nodeY, + nodeW, nodeH); + + context.clip(); + } + } + + context.drawImage( img, 0, 0, img.width, img.height, x, y, w, h ); + + if( shouldClip ){ + context.restore(); + } + } else { + var pattern = context.createPattern( img, repeat ); + context.fillStyle = pattern; + + CanvasRenderer.nodeShapes[r.getNodeShape(node)].drawPath( + context, + nodeX, nodeY, + nodeW, nodeH); + + context.translate(x, y); + context.fill(); + context.translate(-x, -y); + } + + context.globalAlpha = gAlpha; + + }; + + +})( cytoscape ); +;(function($$){ 'use strict'; + + var CanvasRenderer = $$('renderer', 'canvas'); + + // Draw edge text + CanvasRenderer.prototype.drawEdgeText = function(context, edge) { + var text = edge._private.style['content'].strValue; + + if( !text || text.match(/^\s+$/) ){ + return; + } + + if( this.hideEdgesOnViewport && (this.dragData.didDrag || this.pinching || this.hoverData.dragging || this.data.wheel || this.swipePanning) ){ return; } // save cycles on pinching + + var computedSize = edge._private.style['font-size'].pxValue * edge.cy().zoom(); + var minSize = edge._private.style['min-zoomed-font-size'].pxValue; + + if( computedSize < minSize ){ + return; + } + + // Calculate text draw position + + context.textAlign = 'center'; + context.textBaseline = 'middle'; + + // this.recalculateEdgeLabelProjection( edge ); + + var rs = edge._private.rscratch; + this.drawText(context, edge, rs.labelX, rs.labelY); + }; + + // Draw node text + CanvasRenderer.prototype.drawNodeText = function(context, node) { + var text = node._private.style['content'].strValue; + + if ( !text || text.match(/^\s+$/) ) { + return; + } + + var computedSize = node._private.style['font-size'].pxValue * node.cy().zoom(); + var minSize = node._private.style['min-zoomed-font-size'].pxValue; + + if( computedSize < minSize ){ + return; + } + + // this.recalculateNodeLabelProjection( node ); + + var textHalign = node._private.style['text-halign'].strValue; + var textValign = node._private.style['text-valign'].strValue; + var rs = node._private.rscratch; + + switch( textHalign ){ + case 'left': + context.textAlign = 'right'; + break; + + case 'right': + context.textAlign = 'left'; + break; + + default: // e.g. center + context.textAlign = 'center'; + } + + switch( textValign ){ + case 'top': + context.textBaseline = 'bottom'; + break; + + case 'bottom': + context.textBaseline = 'top'; + break; + + default: // e.g. center + context.textBaseline = 'middle'; + } + + this.drawText(context, node, rs.labelX, rs.labelY); + }; + + CanvasRenderer.prototype.getFontCache = function(context){ + var cache; + + this.fontCaches = this.fontCaches || []; + + for( var i = 0; i < this.fontCaches.length; i++ ){ + cache = this.fontCaches[i]; + + if( cache.context === context ){ + return cache; + } + } + + cache = { + context: context + }; + this.fontCaches.push(cache); + + return cache; + }; + + // set up canvas context with font + // returns transformed text string + CanvasRenderer.prototype.setupTextStyle = function( context, element ){ + // Font style + var parentOpacity = element.effectiveOpacity(); + var style = element._private.style; + var labelStyle = style['font-style'].strValue; + var labelSize = style['font-size'].pxValue + 'px'; + var labelFamily = style['font-family'].strValue; + var labelWeight = style['font-weight'].strValue; + var opacity = style['text-opacity'].value * style['opacity'].value * parentOpacity; + var color = style['color'].value; + var outlineColor = style['text-outline-color'].value; + + var fontCacheKey = element._private.fontKey; + var cache = this.getFontCache(context); + + if( cache.key !== fontCacheKey ){ + context.font = labelStyle + ' ' + labelWeight + ' ' + labelSize + ' ' + labelFamily; + + cache.key = fontCacheKey; + } + + var text = String(style['content'].value); + var textTransform = style['text-transform'].value; + + if (textTransform == 'none') { + } else if (textTransform == 'uppercase') { + text = text.toUpperCase(); + } else if (textTransform == 'lowercase') { + text = text.toLowerCase(); + } + + // Calculate text draw position based on text alignment + + // so text outlines aren't jagged + context.lineJoin = 'round'; + + this.fillStyle(context, color[0], color[1], color[2], opacity); + + this.strokeStyle(context, outlineColor[0], outlineColor[1], outlineColor[2], opacity); + + return text; + }; + + // Draw text + CanvasRenderer.prototype.drawText = function(context, element, textX, textY) { + var style = element._private.style; + var parentOpacity = element.effectiveOpacity(); + if( parentOpacity === 0 ){ return; } + + var text = this.setupTextStyle( context, element ); + + if ( text != null && !isNaN(textX) && !isNaN(textY) ) { + + var lineWidth = 2 * style['text-outline-width'].value; // *2 b/c the stroke is drawn centred on the middle + if (lineWidth > 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; + + 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; + var borderStyle = style['border-style'].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 + + if( context.setLineDash ){ // for very outofdate browsers + switch( borderStyle ){ + case 'dotted': + context.setLineDash([ 1, 1 ]); + break; + + case 'dashed': + context.setLineDash([ 4, 2 ]); + break; + + case 'solid': + case 'double': + context.setLineDash([ ]); + break; + } + } + + //var 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; + var borderWidth = style['border-width'].pxValue; + + if( this.hasPie(node) ){ + this.drawPie(context, node); + + // redraw path for blacken and border + if( darkness !== 0 || borderWidth !== 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 (borderWidth > 0) { + + if( usePaths ){ + context.stroke( path ); + } else { + context.stroke(); + } + + if( borderStyle === 'double' ){ + context.lineWidth = style['border-width'].pxValue/3; + + var gco = context.globalCompositeOperation; + context.globalCompositeOperation = 'destination-out'; + + if( usePaths ){ + context.stroke( path ); + } else { + context.stroke(); + } + + context.globalCompositeOperation = gco; + } + + } + + if( usePaths ){ + context.translate( -pos.x, -pos.y ); + } + + // reset in case we changed the border style + if( context.setLineDash ){ // for very outofdate browsers + context.setLineDash([ ]); + } + + // draw the overlay + } else { + + if( overlayOpacity > 0 ){ + this.fillStyle(context, overlayColor[0], overlayColor[1], overlayColor[2], overlayOpacity); + + 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] + var usePaths = CanvasRenderer.usePaths(); + + if( usePaths ){ + x = 0; + y = 0; + } + + 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 opacity = node._private.style['pie-' + i + '-background-opacity'].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], opacity); + + 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]; + + if( this.forcedPixelRatio != null ){ + return this.forcedPixelRatio; + } + + 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 + CanvasRenderer.motionBlurDelay = 100; + + // 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; + var motionBlur = options.motionBlur !== undefined ? options.motionBlur : r.motionBlur; + motionBlur = motionBlur && !forcedContext && r.motionBlurEnabled; + + if( r.motionBlurTimeout ){ + clearTimeout( r.motionBlurTimeout ); + } + + 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 = +new Date(); + + //console.log('-- redraw --') + + + function drawToContext(){ + // startTime = +new Date(); + // console.profile('draw' + startTime) + 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: [], + eles: [] + }, + nondrag: { + nodes: [], + edges: [], + eles: [] + } + }; + + function setContextTransform(context, clear){ + context.setTransform(1, 0, 0, 1, 0, 0); + + if( clear === 'motionBlur' ){ + var gco = context.globalCompositeOperation; + + context.globalCompositeOperation = 'destination-out'; + r.fillStyle( context, 255, 255, 255, 0.666 ); + context.fillRect(0, 0, r.canvasWidth, r.canvasHeight); + + context.globalCompositeOperation = gco; + } else 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.elements().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 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(); + var extent = cy.extent(); + + for (var i = 0; i < zEles.length; i++) { + var ele = zEles[i]; + var list; + var bb = forcedContext ? null : ele.boundingBox(); + var insideExtent = forcedContext ? true : $$.math.boundingBoxesIntersect( extent, bb ); + + if( !insideExtent ){ continue; } // no need to render + + if ( ele._private.rscratch.inDragLayer ) { + list = eles.drag; + } else { + list = eles.nondrag; + } + + list.eles.push( ele ); + } + + } + + + function drawElements( list, context ){ + var eles = list.eles; + + for( var i = 0; i < eles.length; i++ ){ + var ele = eles[i]; + + if( ele.isNode() ){ + r.drawNode(context, ele); + + if( !hideLabels ){ + r.drawNodeText(context, ele); + } + + r.drawNode(context, ele, true); + } else if( !hideEdges ) { + r.drawEdge(context, ele); + + if( !hideLabels ){ + r.drawEdgeText(context, ele); + } + + r.drawEdge(context, ele, true); + } + + + } + + } + + var nodeLayerNeedsMotionClear = needDraw[CanvasRenderer.DRAG] && !needDraw[CanvasRenderer.NODE] && motionBlur && !r.clearedNodeLayerForMotionBlur; + if( nodeLayerNeedsMotionClear ){ r.clearedNodeLayerForMotionBlur = true; } + + if( needDraw[CanvasRenderer.NODE] || drawAllLayers || drawOnlyNodeLayer || nodeLayerNeedsMotionClear ){ + // console.log('redrawing node layer'); + + var context = forcedContext || data.contexts[CanvasRenderer.NODE]; + + setContextTransform( context, motionBlur && !nodeLayerNeedsMotionClear ? 'motionBlur' : undefined ); + 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, motionBlur ? 'motionBlur' : undefined ); + drawElements(eles.drag, context); + + if( !drawAllLayers ){ + needDraw[CanvasRenderer.DRAG] = false; + } + } + + if( r.showFps || (!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(); + } + + var timeToRender = r.averageRedrawTime; + if( r.showFps && timeToRender ){ + timeToRender = Math.round( timeToRender ); + var fps = Math.round(1000/timeToRender); + + context.setTransform(1, 0, 0, 1, 0, 0); + + //context.font = '20px helvetica'; + context.fillStyle = 'rgba(255, 0, 0, 0.75)'; + context.strokeStyle = 'rgba(255, 0, 0, 0.75)'; + context.lineWidth = 1; + context.fillText( '1 frame = ' + timeToRender + ' ms = ' + fps + ' fps', 0, 20); + + var maxFps = 60; + context.strokeRect(0, 30, 250, 20); + context.fillRect(0, 30, 250 * Math.min(fps/maxFps, 1), 20); + } + + if( !drawAllLayers ){ + needDraw[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; + r.lastRedrawTime = 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; + + // console.profileEnd('draw' + startTime) + + if( r.clearingMotionBlur ){ + r.clearingMotionBlur = false; + r.motionBlurCleared = true; + r.motionBlur = true; + } + + if( motionBlur ){ + r.motionBlurTimeout = setTimeout(function(){ + r.motionBlurTimeout = null; + // console.log('motion blur clear'); + + r.clearedNodeLayerForMotionBlur = false; + r.motionBlur = false; + r.clearingMotionBlur = true; + + needDraw[CanvasRenderer.NODE] = true; + needDraw[CanvasRenderer.DRAG] = true; + + r.redraw(); + }, CanvasRenderer.motionBlurDelay); + } + } // 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.elements().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.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*scale, y: -bb.y1*scale }, + forcedPxRatio: 1 + }); + } else { // draw the current view + var cyPan = cy.pan(); + var pan = { + x: cyPan.x * scale, + y: cyPan.y * scale + }; + var zoom = cy.zoom() * scale; + + this.redraw({ + forcedContext: buffCxt, + drawAllLayers: true, + forcedZoom: zoom, + forcedPan: 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.nodeIsDraggable = function(node) { + if (node._private.style['opacity'].value !== 0 + && node._private.style['visibility'].value == 'visible' + && node._private.style['display'].value == 'element' + && !node.locked() + && node.grabbable() ) { + + return true; + } + + return false; + }; + + CanvasRenderer.prototype.load = function() { + var r = this; + + var getDragListIds = function(opts){ + var listHasId; + + if( opts.addToList && r.data.cy.hasCompoundNodes() ){ // only needed for compound graphs + if( !opts.addToList.hasId ){ // build ids lookup if doesn't already exist + opts.addToList.hasId = {}; + + for( var i = 0; i < opts.addToList.length; i++ ){ + var ele = opts.addToList[i]; + + opts.addToList.hasId[ ele.id() ] = true; + } + } + + listHasId = opts.addToList.hasId; + } + + return listHasId || {}; + }; + + // helper function to determine which child nodes and inner edges + // of a compound node to be dragged as well as the grabbed and selected nodes + var addDescendantsToDrag = function(node, opts){ + if( !node._private.cy.hasCompoundNodes() ){ + return; + } + + var listHasId = getDragListIds( opts ); + + var innerNodes = node.descendants(); + + // TODO do not drag hidden children & children of hidden children? + for( var i = 0; i < innerNodes.size(); i++ ){ + var iNode = innerNodes[i]; + var _p = iNode._private; + + if( opts.inDragLayer ){ + _p.rscratch.inDragLayer = true; + } + + if( opts.addToList && !listHasId[ iNode.id() ] ){ + opts.addToList.push( iNode ); + listHasId[ iNode.id() ] = true; + + _p.grabbed = true; + } + + var edges = _p.edges; + for( var j = 0; opts.inDragLayer && j < edges.length; j++ ){ + edges[j]._private.rscratch.inDragLayer = true; + } + } + }; + + // adds the given nodes, and its edges to the drag layer + var addNodeToDrag = function(node, opts){ + + var _p = node._private; + var listHasId = getDragListIds( opts ); + + if( opts.inDragLayer ){ + _p.rscratch.inDragLayer = true; + } + + if( opts.addToList && !listHasId[ node.id() ] ){ + opts.addToList.push( node ); + listHasId[ node.id() ] = true; + + _p.grabbed = true; + } + + var edges = _p.edges; + for( var i = 0; opts.inDragLayer && i < edges.length; i++ ){ + edges[i]._private.rscratch.inDragLayer = true; + } + + addDescendantsToDrag( node, opts ); // always add to drag + + // also add nodes and edges related to the topmost ancestor + updateAncestorsInDragLayer( node, { + inDragLayer: true + } ); + }; + + // helper function to determine which ancestor nodes and edges should go + // to the drag layer (or should be removed from drag layer). + var updateAncestorsInDragLayer = function(node, opts) { + // find top-level parent + var parent = node; + + if( !node._private.cy.hasCompoundNodes() ){ + return; + } + + while( parent.parent().nonempty() ){ + parent = parent.parent()[0]; + } + + // no parent node: no nodes to add to the drag layer + if( parent == node ){ + return; + } + + var nodes = parent + .descendants() + .add( parent ) + .not( node ) + .not( node.descendants() ) + ; + + var edges = nodes.connectedEdges(); + + var listHasId = getDragListIds( opts ); + + for( var i = 0; i < nodes.size(); i++ ){ + if( opts.inDragLayer !== undefined ){ + nodes[i]._private.rscratch.inDragLayer = opts.inDragLayer; + } + + if( opts.addToList && !listHasId[ nodes[i].id() ] ){ + opts.addToList.push( nodes[i] ); + listHasId[ nodes[i].id() ] = true; + + nodes[i]._private.grabbed = true; + } + } + + for( var j = 0; opts.inDragLayer !== undefined && j < edges.length; j++ ) { + edges[j]._private.rscratch.inDragLayer = opts.inDragLayer; + } + }; + + r.registerBinding(r.data.container, 'DOMNodeRemoved', function(e){ + r.destroy(); + }); + + // auto resize + r.registerBinding(window, 'resize', $$.util.debounce( function(e) { + r.invalidateContainerClientCoordsCache(); + + r.matchCanvasSize(r.data.container); + r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true; + r.redraw(); + }, 100 ) ); + + var invalCtnrBBOnScroll = function(domEle){ + r.registerBinding(domEle, 'scroll', function(e){ + r.invalidateContainerClientCoordsCache(); + } ); + }; + + var bbCtnr = r.data.cy.container(); + + for( ;; ){ + + invalCtnrBBOnScroll( bbCtnr ); + + if( bbCtnr.parentNode ){ + bbCtnr = bbCtnr.parentNode; + } else { + break; + } + + } + + // stop right click menu from appearing on cy + r.registerBinding(r.data.container, 'contextmenu', function(e){ + e.preventDefault(); + }); + + var inBoxSelection = function(){ + return r.data.select[4] !== 0; + }; + + // Primary key + r.registerBinding(r.data.container, 'mousedown', function(e) { + e.preventDefault(); + r.hoverData.capture = true; + r.hoverData.which = e.which; + + 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 grabEvent = new $$.Event('grab'); + + // Right click button + if( e.which == 3 ){ + + r.hoverData.cxtStarted = true; + + var cxtEvt = new $$.Event(e, { + type: 'cxttapstart', + cyPosition: { x: pos[0], y: pos[1] } + }); + + if( near ){ + near.activate(); + near.trigger( cxtEvt ); + + r.hoverData.down = near; + } else { + cy.trigger( cxtEvt ); + } + + r.hoverData.downTime = (new Date()).getTime(); + r.hoverData.cxtDragged = false; + + // Primary button + } else if (e.which == 1) { + + if( near ){ + near.activate(); + } + + // Element dragging + { + // If something is under the cursor and it is draggable, prepare to grab it + if (near != null) { + + if( r.nodeIsDraggable(near) ){ + + if ( near.isNode() && !near.selected() ){ + + draggedElements = r.dragData.possibleDragElements = []; + addNodeToDrag( near, { addToList: draggedElements } ); + + near.trigger(grabEvent); + + } else if ( near.isNode() && near.selected() ){ + draggedElements = r.dragData.possibleDragElements = [ ]; + + var selectedNodes = cy.$(function(){ return this.isNode() && this.selected(); }); + + for( var i = 0; i < selectedNodes.length; i++ ){ + + // Only add this selected node to drag if it is draggable, eg. has nonzero opacity + if( r.nodeIsDraggable( selectedNodes[i] ) ){ + addNodeToDrag( selectedNodes[i], { addToList: draggedElements } ); + } + } + + near.trigger( grabEvent ); + } + + r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true; + r.data.canvasNeedsRedraw[CanvasRenderer.DRAG] = true; + + } + + near + .trigger(new $$.Event(e, { + type: 'mousedown', + cyPosition: { x: pos[0], y: pos[1] } + })) + .trigger(new $$.Event(e, { + type: 'tapstart', + cyPosition: { x: pos[0], y: pos[1] } + })) + .trigger(new $$.Event(e, { + type: 'vmousedown', + cyPosition: { x: pos[0], y: pos[1] } + })) + ; + + } else if (near == null) { + cy + .trigger(new $$.Event(e, { + type: 'mousedown', + cyPosition: { x: pos[0], y: pos[1] } + })) + .trigger(new $$.Event(e, { + type: 'tapstart', + cyPosition: { x: pos[0], y: pos[1] } + })) + .trigger(new $$.Event(e, { + type: 'vmousedown', + cyPosition: { x: pos[0], y: pos[1] } + })) + ; + } + + r.hoverData.down = near; + r.hoverData.downTime = (new Date()).getTime(); + + } + + // Selection box + if ( near == null || near.isEdge() ) { + select[4] = 1; + var timeUntilActive = Math.max( 0, CanvasRenderer.panOrBoxSelectDelay - (+new Date() - r.hoverData.downTime) ); + + clearTimeout( r.bgActiveTimeout ); + r.bgActiveTimeout = setTimeout(function(){ + if( near ){ + near.unactivate(); + } + + r.data.bgActivePosistion = { + x: pos[0], + y: pos[1] + }; + + r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true; + + + r.redraw(); + }, timeUntilActive); + + } + + } + + // Initialize selection box coordinates + select[0] = select[2] = pos[0]; + select[1] = select[3] = pos[1]; + + }, false); + + r.registerBinding(window, 'mousemove', $$.util.throttle( function(e) { + var preventDefault = false; + var capture = r.hoverData.capture; + + // save cycles if mouse events aren't to be captured + if ( !capture ){ + var containerPageCoords = r.findContainerClientCoords(); + + if (e.clientX > containerPageCoords[0] && e.clientX < containerPageCoords[0] + r.canvasWidth + && e.clientY > containerPageCoords[1] && e.clientY < containerPageCoords[1] + r.canvasHeight + ) { + // inside container bounds so OK + } else { + return; + } + + var cyContainer = r.data.container; + var target = e.target; + var tParent = target.parentNode; + var containerIsTarget = false; + + while( tParent ){ + if( tParent === cyContainer ){ + containerIsTarget = true; + break; + } + + tParent = tParent.parentNode; + } + + if( !containerIsTarget ){ return; } // if target is outisde cy container, then this event is not for us + } + + var cy = r.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 + + var toTrigger = []; + + for( var i = 0; i < draggedElements.length; i++ ){ + var dEle = draggedElements[i]; + + // now, add the elements to the drag layer if not done already + if( !r.hoverData.draggingEles ){ + addNodeToDrag( dEle, { inDragLayer: true } ); + } + + // Locked nodes not draggable, as well as non-visible nodes + if( dEle.isNode() && r.nodeIsDraggable(dEle) && dEle.grabbed() ){ + var dPos = dEle._private.position; + + toTrigger.push( dEle ); + + if( $$.is.number(disp[0]) && $$.is.number(disp[1]) ){ + dPos.x += disp[0]; + dPos.y += disp[1]; + } + + } + } + + r.hoverData.draggingEles = true; + + 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 + ) { + + cy.$(function(){ + return this.selected(); + }).unselect(); + + 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(); + } + } + + r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true; + + } + + } + + if ( cy.boxSelectionEnabled() && Math.pow(select[2] - select[0], 2) + Math.pow(select[3] - select[1], 2) > 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(); + } + + // always need redraw in case eles unselectable + 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 < draggedElements.length; i++) { + + if(draggedElements[i]._private.group === 'nodes') { + draggedElements[i]._private.rscratch.inDragLayer = false; + draggedElements[i]._private.grabbed = false; + + var sEdges = draggedElements[i]._private.edges; + for( var j = 0; j < sEdges.length; j++ ){ sEdges[j]._private.rscratch.inDragLayer = false; } + + // for compound nodes, also remove related nodes and edges from the drag layer + updateAncestorsInDragLayer(draggedElements[i], { inDragLayer: false }); + + } else if( draggedElements[i]._private.group === 'edges' ){ + draggedElements[i]._private.rscratch.inDragLayer = false; + } + + } + + if( down ){ down.trigger('free'); } + + // draggedElements = r.dragData.possibleDragElements = []; + + } + + } // else not right mouse + + select[4] = 0; r.hoverData.down = null; + + //r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true; + +// console.log('mu', pos[0], pos[1]); +// console.log('ss', select); + + r.dragData.didDrag = false; + + }, false); + + var wheelHandler = function(e) { + if( r.scrollingPage ){ return; } // while scrolling, ignore wheel-to-zoom + + var cy = r.data.cy; + var pos = r.projectIntoViewport(e.clientX, e.clientY); + var rpos = [pos[0] * cy.zoom() + cy.pan().x, + pos[1] * cy.zoom() + cy.pan().y]; + + if( r.hoverData.draggingEles || r.hoverData.dragging || r.hoverData.cxtStarted || inBoxSelection() ){ // if pan dragging or cxt dragging, wheel movements make no zoom + e.preventDefault(); + return; + } + + if( cy.panningEnabled() && cy.userPanningEnabled() && cy.zoomingEnabled() && cy.userZoomingEnabled() ){ + e.preventDefault(); + + r.data.wheelZooming = true; + clearTimeout( r.data.wheelTimeout ); + r.data.wheelTimeout = setTimeout(function(){ + r.data.wheelZooming = false; + + r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true; + r.redraw(); + }, 150); + + var diff = e.wheelDeltaY / 1000 || e.wheelDelta / 1000 || e.detail / -32 || -e.deltaY / 500; + diff = diff * r.wheelSensitivity; + + cy.zoom({ + level: cy.zoom() * Math.pow(10, diff), + renderedPosition: { x: rpos[0], y: rpos[1] } + }); + } + + }; + + // Functions to help with whether mouse wheel should trigger zooming + // -- + r.registerBinding(r.data.container, 'wheel', wheelHandler, true); + + r.registerBinding(r.data.container, 'mousewheel', wheelHandler, true); + + r.registerBinding(r.data.container, 'DOMMouseScroll', wheelHandler, true); + + // TODO is this even needed? + r.registerBinding(r.data.container, 'MozMousePixelScroll', function(e){ + }, false); + + r.registerBinding(window, 'scroll', function(e){ + r.scrollingPage = true; + + clearTimeout( r.scrollingPageTimeout ); + r.scrollingPageTimeout = setTimeout(function(){ + r.scrollingPage = false; + }, 250); + }, true); + + // Functions to help with handling mouseout/mouseover on the Cytoscape container + // Handle mouseout on Cytoscape container + r.registerBinding(r.data.container, 'mouseout', function(e) { + var pos = r.projectIntoViewport(e.clientX, e.clientY); + + r.data.cy.trigger(new $$.Event(e, { + type: 'mouseout', + cyPosition: { x: pos[0], y: pos[1] } + })); + }, false); + + r.registerBinding(r.data.container, 'mouseover', function(e) { + var pos = r.projectIntoViewport(e.clientX, e.clientY); + + r.data.cy.trigger(new $$.Event(e, { + type: 'mouseover', + cyPosition: { x: pos[0], y: pos[1] } + })); + }, false); + + var f1x1, f1y1, f2x1, f2y1; // starting points for pinch-to-zoom + var distance1; // initial distance between finger 1 and finger 2 for pinch-to-zoom + var center1, modelCenter1; // center point on start pinch to zoom + var offsetLeft, offsetTop; + var containerWidth, containerHeight; + var twoFingersStartInside; + + var distance = function(x1, y1, x2, y2){ + return Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ); + }; + + r.registerBinding(r.data.container, 'touchstart', function(e) { + + clearTimeout( this.threeFingerSelectTimeout ); + + if( e.target !== r.data.link ){ + e.preventDefault(); + } + + r.touchData.capture = true; + r.data.bgActivePosistion = undefined; + + var cy = r.data.cy; + var nodes = r.getCachedNodes(); var edges = r.getCachedEdges(); + var now = r.touchData.now; var earlier = r.touchData.earlier; + + if (e.touches[0]) { var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); now[0] = pos[0]; now[1] = pos[1]; } + if (e.touches[1]) { var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); now[2] = pos[0]; now[3] = pos[1]; } + if (e.touches[2]) { var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); now[4] = pos[0]; now[5] = pos[1]; } + + // record starting points for pinch-to-zoom + if( e.touches[1] ){ + + // anything in the set of dragged eles should be released + var release = function( eles ){ + for( var i = 0; i < eles.length; i++ ){ + eles[i]._private.grabbed = false; + eles[i]._private.rscratch.inDragLayer = false; + if( eles[i].active() ){ eles[i].unactivate(); } + } + }; + release(nodes); + release(edges); + + var offsets = r.findContainerClientCoords(); + offsetLeft = offsets[0]; + offsetTop = offsets[1]; + containerWidth = offsets[2]; + containerHeight = offsets[3]; + + f1x1 = e.touches[0].clientX - offsetLeft; + f1y1 = e.touches[0].clientY - offsetTop; + + f2x1 = e.touches[1].clientX - offsetLeft; + f2y1 = e.touches[1].clientY - offsetTop; + + twoFingersStartInside = + 0 <= f1x1 && f1x1 <= containerWidth + && 0 <= f2x1 && f2x1 <= containerWidth + && 0 <= f1y1 && f1y1 <= containerHeight + && 0 <= f2y1 && f2y1 <= containerHeight + ; + + var pan = cy.pan(); + var zoom = cy.zoom(); + + distance1 = distance( f1x1, f1y1, f2x1, f2y1 ); + center1 = [ (f1x1 + f2x1)/2, (f1y1 + f2y1)/2 ]; + modelCenter1 = [ + (center1[0] - pan.x) / zoom, + (center1[1] - pan.y) / zoom + ]; + + // consider context tap + if( distance1 < 200 && !e.touches[2] ){ + + var near1 = r.findNearestElement(now[0], now[1], true); + var near2 = r.findNearestElement(now[2], now[3], true); + + //console.log(distance1) + + if( near1 && near1.isNode() ){ + near1.activate().trigger( new $$.Event(e, { + type: 'cxttapstart', + cyPosition: { x: now[0], y: now[1] } + }) ); + r.touchData.start = near1; + + } else if( near2 && near2.isNode() ){ + near2.activate().trigger( new $$.Event(e, { + type: 'cxttapstart', + cyPosition: { x: now[0], y: now[1] } + }) ); + r.touchData.start = near2; + + } else { + cy.trigger( new $$.Event(e, { + type: 'cxttapstart', + cyPosition: { x: now[0], y: now[1] } + }) ); + r.touchData.start = null; + } + + if( r.touchData.start ){ r.touchData.start._private.grabbed = false; } + r.touchData.cxt = true; + r.touchData.cxtDragged = false; + r.data.bgActivePosistion = undefined; + + //console.log('cxttapstart') + + r.redraw(); + return; + + } + + // console.log(center1); + // console.log('touchstart ptz'); + // console.log(offsetLeft, offsetTop); + // console.log(f1x1, f1y1); + // console.log(f2x1, f2y1); + // console.log(distance1); + // console.log(center1); + } + + // console.log('another tapstart') + + + if (e.touches[2]) { + + } else if (e.touches[1]) { + + } else if (e.touches[0]) { + var near = r.findNearestElement(now[0], now[1], true); + + if (near != null) { + near.activate(); + + r.touchData.start = near; + + if( near.isNode() && r.nodeIsDraggable(near) ){ + + var draggedEles = r.dragData.touchDragEles = []; + + r.data.canvasNeedsRedraw[CanvasRenderer.NODE] = true; + r.data.canvasNeedsRedraw[CanvasRenderer.DRAG] = true; + + if( near.selected() ){ + // reset drag elements, since near will be added again + + var selectedNodes = cy.$(function(){ + return this.isNode() && this.selected(); + }); + + for( var k = 0; k < selectedNodes.length; k++ ){ + var selectedNode = selectedNodes[k]; + + if( r.nodeIsDraggable(selectedNode) ){ + addNodeToDrag( selectedNode, { addToList: draggedEles } ); + } + } + } else { + addNodeToDrag( near, { addToList: draggedEles } ); + } + + near.trigger('grab'); + } + + near + .trigger(new $$.Event(e, { + type: 'touchstart', + cyPosition: { x: now[0], y: now[1] } + })) + .trigger(new $$.Event(e, { + type: 'tapstart', + cyPosition: { x: now[0], y: now[1] } + })) + .trigger(new $$.Event(e, { + type: 'vmousdown', + cyPosition: { x: now[0], y: now[1] } + })) + ; + } if (near == null) { + cy + .trigger(new $$.Event(e, { + type: 'touchstart', + cyPosition: { x: now[0], y: now[1] } + })) + .trigger(new $$.Event(e, { + type: 'tapstart', + cyPosition: { x: now[0], y: now[1] } + })) + .trigger(new $$.Event(e, { + type: 'vmousedown', + cyPosition: { x: now[0], y: now[1] } + })) + ; + + r.data.bgActivePosistion = { + x: pos[0], + y: pos[1] + }; + + r.data.canvasNeedsRedraw[CanvasRenderer.SELECT_BOX] = true; + r.redraw(); + } + + + // Tap, taphold + // ----- + + 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, + cancelOnFailedZoom: true + }); + + distance1 = distance2; + f1x1 = f1x2; + f1y1 = f1y2; + f2x1 = f2x2; + f2y1 = f2y2; + + r.pinching = true; + } + + // Re-project + if (e.touches[0]) { var pos = r.projectIntoViewport(e.touches[0].clientX, e.touches[0].clientY); now[0] = pos[0]; now[1] = pos[1]; } + if (e.touches[1]) { var pos = r.projectIntoViewport(e.touches[1].clientX, e.touches[1].clientY); now[2] = pos[0]; now[3] = pos[1]; } + if (e.touches[2]) { var pos = r.projectIntoViewport(e.touches[2].clientX, e.touches[2].clientY); now[4] = pos[0]; now[5] = pos[1]; } + + } else if (e.touches[0]) { + var start = r.touchData.start; + var last = r.touchData.last; + var near = near || r.findNearestElement(now[0], now[1], true); + + 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) && draggedEle.isNode() && draggedEle.grabbed() ){ + r.dragData.didDrag = true; + var dPos = draggedEle._private.position; + + dPos.x += disp[0]; + dPos.y += disp[1]; + + if( !r.hoverData.draggingEles ){ + addNodeToDrag( draggedEle, { inDragLayer: true } ); + } + } + } + + 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) ){ + layout.stop(); + return; + } + + if( !options.infinite && doneTime != Infinity ){ + clearTimeout(doneTimeout); + doneTimeout = setTimeout(doneHandler, doneTime); + } + + var movedNodes = cy.collection(); + + 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: bb.x1 + point.x, + y: bb.y1 + point.y + }); + + movedNodes.merge( node ); + } + }); + + + if( options.animate && movedNodes.length > 0 ){ + simUpdatingPos = true; + + movedNodes.rtrigger('position'); + + if( options.fit ){ + cy.fit( options.padding ); + } + + lastDraw = +new Date(); + simUpdatingPos = false; + } + + + if( !ready ){ + ready = true; + layout.one('layoutready', options.ready); + layout.trigger({ type: 'layoutready', layout: layout }); + } + } + + }; + sys.renderer = sysRenderer; + sys.screenSize( bb.w, bb.h ); + sys.screenPadding( options.padding, options.padding, options.padding, options.padding ); + 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; + } + } + + var grabHandler; + nodes.on('grab free position', grabHandler = function(e){ + if( simUpdatingPos ){ return; } + + var pos = this.position(); + var apos = sys.fromScreen( pos ); + if( !apos ){ return; } + + var p = arbor.Point(apos.x, apos.y); + var padding = options.padding; + + if( + bb.x1 + padding <= pos.x && pos.x <= bb.x2 - padding && + bb.y1 + padding <= pos.y && pos.y <= bb.y2 - padding + ){ + 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; + } + }); + + var lockHandler; + nodes.on('lock unlock', lockHandler = function(e){ + node.scratch().arbor.fixed = node.locked(); + }); + + var removeHandler; + eles.on('remove', removeHandler = function(e){ return; // TODO enable when layout add/remove api added + // var ele = this; + // var arborEle = ele.scratch().arbor; + + // if( !arborEle ){ return; } + + // if( ele.isNode() ){ + // sys.pruneNode( arborEle ); + // } else { + // sys.pruneEdge( arborEle ); + // } + }); + + var addHandler; + cy.on('add', '*', addHandler = function(){ return; // TODO enable when layout add/remove api added + // var ele = this; + + // if( ele.isNode() ){ + // addNode( ele ); + // } else { + // addEdge( ele ); + // } + }); + + var resizeHandler; + cy.on('resize', resizeHandler = function(){ + if( options.boundingBox == null && layout._private.system != null ){ + var w = cy.width(); + var h = cy.height(); + + sys.screenSize( w, h ); + } + }); + + function addNode( node ){ + if( node.isFullAutoParent() ){ return; } // they don't exist in the sim + + var id = node._private.data.id; + var mass = calculateValueForElement(node, options.nodeMass); + var locked = node._private.locked; + var nPos = node.position(); + + var pos = sys.fromScreen({ + x: nPos.x, + y: nPos.y + }); + + node.scratch().arbor = sys.addNode(id, { + element: node, + mass: mass, + fixed: locked, + x: locked ? pos.x : undefined, + y: locked ? pos.y : undefined + }); + } + + function addEdge( edge ){ + var src = edge.source().id(); + var tgt = edge.target().id(); + var length = calculateValueForElement(edge, options.edgeLength); + + edge.scratch().arbor = sys.addEdge(src, tgt, { + length: length + }); + } + + nodes.each(function(i, node){ + addNode( node ); + }); + + edges.each(function(i, edge){ + addEdge( edge ); + }); + + var grabbableNodes = nodes.filter(":grabbable"); + // disable grabbing if so set + if( options.ungrabifyWhileSimulating ){ + grabbableNodes.ungrabify(); + } + + var doneHandler = layout._private.doneHandler = function(){ + layout._private.doneHandler = null; + + if( !options.animate ){ + if( options.fit ){ + cy.reset(); + } + + nodes.rtrigger('position'); + } + + // unbind handlers + nodes.off('grab free position', grabHandler); + nodes.off('lock unlock', lockHandler); + eles.off('remove', removeHandler); + cy.off('add', '*', addHandler); + cy.off('resize', resizeHandler); + + // enable back grabbing if so set + if( options.ungrabifyWhileSimulating ){ + grabbableNodes.grabify(); + } + + layout.one('layoutstop', options.stop); + layout.trigger({ type: 'layoutstop', layout: layout }); + }; + + sys.start(); + if( !options.infinite && options.maxSimulationTime != null && options.maxSimulationTime > 0 && options.maxSimulationTime !== Infinity ){ + setTimeout(function(){ + layout.stop(); + }, options.maxSimulationTime); + } + + }); // require + + return this; // chaining + }; + + + ArborLayout.prototype.stop = function(){ + if( this._private.system != null ){ + this._private.system.stop(); + } + + if( this._private.doneHandler ){ + this._private.doneHandler(); + } + + return this; // chaining + }; + + $$('layout', 'arbor', ArborLayout); + + +})(cytoscape); + +;(function($$){ 'use strict'; + + var defaults = { + fit: true, // whether to fit the viewport to the graph + directed: false, // whether the tree is directed downwards (or edges can point in any direction if false) + padding: 30, // padding on fit + circle: false, // put depths in concentric circles if true, put depths top down if false + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space + roots: undefined, // the roots of the trees + maximalAdjustments: 0, // how many times to try to position the nodes in a maximal way (i.e. no backtracking) + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + ready: undefined, // callback on layoutready + stop: undefined // callback on layoutstop + }; + + function BreadthFirstLayout( options ){ + this.options = $$.util.extend({}, defaults, options); + } + + BreadthFirstLayout.prototype.run = function(){ + var params = this.options; + var options = params; + + var cy = params.cy; + var eles = options.eles; + var nodes = eles.nodes().not(':parent'); + var graph = eles; + + var bb = $$.util.makeBoundingBox( options.boundingBox ? options.boundingBox : { + x1: 0, y1: 0, w: cy.width(), h: cy.height() + } ); + + var roots; + if( $$.is.elementOrCollection(options.roots) ){ + roots = options.roots; + } else if( $$.is.array(options.roots) ){ + var rootsArray = []; + + for( var i = 0; i < options.roots.length; i++ ){ + var id = options.roots[i]; + var ele = cy.getElementById( id ); + rootsArray.push( ele ); + } + + roots = new $$.Collection( cy, rootsArray ); + } else if( $$.is.string(options.roots) ){ + roots = cy.$( options.roots ); + + } else { + if( options.directed ){ + roots = nodes.roots(); + } else { + var components = []; + var unhandledNodes = nodes; + + while( unhandledNodes.length > 0 ){ + var currComp = cy.collection(); + + eles.bfs({ + roots: unhandledNodes[0], + visit: function(i, depth, node, edge, pNode){ + currComp = currComp.add( node ); + }, + directed: false + }); + + unhandledNodes = unhandledNodes.not( currComp ); + components.push( currComp ); + } + + roots = cy.collection(); + for( var i = 0; i < components.length; i++ ){ + var comp = components[i]; + var maxDegree = comp.maxDegree( false ); + var compRoots = comp.filter(function(){ + return this.degree(false) === maxDegree; + }); + + roots = roots.add( compRoots ); + } + + } + } + + + var depths = []; + var foundByBfs = {}; + var id2depth = {}; + var prevNode = {}; + var prevEdge = {}; + var successors = {}; + + // find the depths of the nodes + graph.bfs({ + roots: roots, + directed: options.directed, + visit: function(i, depth, node, edge, pNode){ + var ele = this[0]; + var id = ele.id(); + + if( !depths[depth] ){ + depths[depth] = []; + } + + depths[depth].push( ele ); + foundByBfs[ id ] = true; + id2depth[ id ] = depth; + prevNode[ id ] = pNode; + prevEdge[ id ] = edge; + + if( pNode ){ + var prevId = pNode.id(); + var succ = successors[ prevId ] = successors[ prevId ] || []; + + succ.push( node ); + } + } + }); + + // check for nodes not found by bfs + var orphanNodes = []; + for( var i = 0; i < nodes.length; i++ ){ + var ele = nodes[i]; + + if( foundByBfs[ ele.id() ] ){ + continue; + } else { + orphanNodes.push( ele ); + } + } + + // assign orphan nodes a depth from their neighborhood + var maxChecks = orphanNodes.length * 3; + var checks = 0; + while( orphanNodes.length !== 0 && checks < maxChecks ){ + var node = orphanNodes.shift(); + var neighbors = node.neighborhood().nodes(); + var assignedDepth = false; + + for( var i = 0; i < neighbors.length; i++ ){ + var depth = id2depth[ neighbors[i].id() ]; + + if( depth !== undefined ){ + depths[depth].push( node ); + assignedDepth = true; + break; + } + } + + if( !assignedDepth ){ + orphanNodes.push( node ); + } + + checks++; + } + + // assign orphan nodes that are still left to the depth of their subgraph + while( orphanNodes.length !== 0 ){ + var node = orphanNodes.shift(); + //var subgraph = graph.bfs( node ).path; + var assignedDepth = false; + + // for( var i = 0; i < subgraph.length; i++ ){ + // var depth = id2depth[ subgraph[i].id() ]; + + // if( depth !== undefined ){ + // depths[depth].push( node ); + // assignedDepth = true; + // break; + // } + // } + + if( !assignedDepth ){ // worst case if the graph really isn't tree friendly, then just dump it in 0 + if( depths.length === 0 ){ + depths.push([]); + } + + depths[0].push( node ); + } + } + + // assign the nodes a depth and index + var assignDepthsToEles = function(){ + for( var i = 0; i < depths.length; i++ ){ + var eles = depths[i]; + + for( var j = 0; j < eles.length; j++ ){ + var ele = eles[j]; + + ele._private.scratch.breadthfirst = { + depth: i, + index: j + }; + } + } + }; + assignDepthsToEles(); + + + var intersectsDepth = function( node ){ // returns true if has edges pointing in from a higher depth + var edges = node.connectedEdges(function(){ + return this.data('target') === node.id(); + }); + var thisInfo = node._private.scratch.breadthfirst; + var highestDepthOfOther = 0; + var highestOther; + for( var i = 0; i < edges.length; i++ ){ + var edge = edges[i]; + var otherNode = edge.source()[0]; + var otherInfo = otherNode._private.scratch.breadthfirst; + + if( thisInfo.depth <= otherInfo.depth && highestDepthOfOther < otherInfo.depth ){ + highestDepthOfOther = otherInfo.depth; + highestOther = otherNode; + } + } + + return highestOther; + }; + + // make maximal if so set by adjusting depths + for( var adj = 0; adj < options.maximalAdjustments; adj++ ){ + + var nDepths = depths.length; + var elesToMove = []; + for( var i = 0; i < nDepths; i++ ){ + var depth = depths[i]; + + var nDepth = depth.length; + for( var j = 0; j < nDepth; j++ ){ + var ele = depth[j]; + var info = ele._private.scratch.breadthfirst; + var intEle = intersectsDepth(ele); + + if( intEle ){ + info.intEle = intEle; + elesToMove.push( ele ); + } + } + } + + for( var i = 0; i < elesToMove.length; i++ ){ + var ele = elesToMove[i]; + var info = ele._private.scratch.breadthfirst; + var intEle = info.intEle; + var intInfo = intEle._private.scratch.breadthfirst; + + depths[ info.depth ].splice( info.index, 1 ); // remove from old depth & index + + // add to end of new depth + var newDepth = intInfo.depth + 1; + while( newDepth > depths.length - 1 ){ + depths.push([]); + } + depths[ newDepth ].push( ele ); + + info.depth = newDepth; + info.index = depths[newDepth].length - 1; + } + + assignDepthsToEles(); + } + + // find min distance we need to leave between nodes + var minDistance = 0; + if( options.avoidOverlap ){ + for( var i = 0; i < nodes.length; i++ ){ + var 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.breadthfirst.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.breadthfirst.index; + var depth = neighbor._private.scratch.breadthfirst.depth; + var nDepth = depths[depth].length; + + if( eleDepth > depth || eleDepth === 0 ){ // only get influenced by elements above + percent += index / nDepth; + samples++; + } + } + + samples = Math.max(1, samples); + percent = percent / samples; + + if( samples === 0 ){ // so lone nodes have a "don't care" state in sorting + percent = undefined; + } + + cachedWeightedPercent[ ele.id() ] = percent; + return percent; + }; + + + // rearrange the indices in each depth level based on connectivity + + var sortFn = function(a, b){ + var apct = getWeightedPercent( a ); + var bpct = getWeightedPercent( b ); + + return apct - bpct; + }; + + for( var times = 0; times < 3; times++ ){ // do it a few times b/c the depths are dynamic and we want a more stable result + + for( var i = 0; i < depths.length; i++ ){ + depths[i] = depths[i].sort( sortFn ); + } + assignDepthsToEles(); // and update + + } + + var biggestDepthSize = 0; + for( var i = 0; i < depths.length; i++ ){ + biggestDepthSize = Math.max( depths[i].length, biggestDepthSize ); + } + + var center = { + x: bb.x1 + bb.w/2, + y: bb.x1 + bb.h/2 + }; + + var getPosition = function( ele, isBottomDepth ){ + var info = ele._private.scratch.breadthfirst; + var depth = info.depth; + var index = info.index; + var depthSize = depths[depth].length; + + if( options.strictHierarchy ){ + depthSize = biggestDepthSize; + } + + var distanceX = Math.max( bb.w / (depthSize + 1), minDistance ); + var distanceY = Math.max( bb.h / (depths.length + 1), minDistance ); + var radiusStepSize = Math.min( bb.w / 2 / depths.length, bb.h / 2 / depths.length ); + radiusStepSize = Math.max( radiusStepSize, minDistance ); + + if( options.strictHierarchy && !options.circle ){ + + var epos = { + x: center.x + (index + 1 - (depthSize + 1)/2) * distanceX, + y: (depth + 1) * distanceY + }; + + if( isBottomDepth ){ + return epos; + } + + var succs = successors[ ele.id() ]; + if( succs ){ + epos.x = 0; + + for( var i = 0 ; i < succs.length; i++ ){ + var spos = pos[ succs[i].id() ]; + + epos.x += spos.x; + } + + epos.x /= succs.length; + } else { + //debugger; + } + + return epos; + + } else { + if( options.circle ){ + var radius = radiusStepSize * depth + radiusStepSize - (depths.length > 0 && depths[0].length <= 3 ? radiusStepSize/2 : 0); + var theta = 2 * Math.PI / depths[depth].length * index; + + if( depth === 0 && depths[0].length === 1 ){ + radius = 1; + } + + return { + x: center.x + radius * Math.cos(theta), + y: center.y + radius * Math.sin(theta) + }; + + } else { + return { + x: center.x + (index + 1 - (depthSize + 1)/2) * distanceX, + y: (depth + 1) * distanceY + }; + } + } + + }; + + // get positions in reverse depth order + var pos = {}; + for( var i = depths.length - 1; i >=0; i-- ){ + var depth = depths[i]; + + for( var j = 0; j < depth.length; j++ ){ + var node = depth[j]; + + pos[ node.id() ] = getPosition( node, i === depths.length - 1 ); + } + } + + nodes.layoutPositions(this, options, function(){ + return pos[ this.id() ]; + }); + + return this; // chaining + }; + + $$('layout', 'breadthfirst', BreadthFirstLayout); + +})( cytoscape ); + +;(function($$){ 'use strict'; + + var defaults = { + fit: true, // whether to fit the viewport to the graph + padding: 30, // the padding on fit + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + avoidOverlap: true, // prevents node overlap, may overflow boundingBox and radius if not enough space + radius: undefined, // the radius of the circle + startAngle: 3/2 * Math.PI, // the position of the first node + counterclockwise: false, // whether the layout should go counterclockwise (true) or clockwise (false) + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + ready: undefined, // callback on layoutready + stop: undefined // callback on layoutstop + }; + + function CircleLayout( options ){ + this.options = $$.util.extend({}, defaults, options); + } + + CircleLayout.prototype.run = function(){ + var params = this.options; + var options = params; + + var cy = params.cy; + var eles = options.eles; + + var nodes = eles.nodes().not(':parent'); + + var bb = $$.util.makeBoundingBox( options.boundingBox ? options.boundingBox : { + x1: 0, y1: 0, w: cy.width(), h: cy.height() + } ); + + var center = { + x: bb.x1 + bb.w/2, + y: bb.y1 + bb.h/2 + }; + + var theta = options.startAngle; + var dTheta = 2 * Math.PI / nodes.length; + var r; + + 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); + } + + if( $$.is.number(options.radius) ){ + r = options.radius; + } else if( nodes.length <= 1 ){ + r = 0; + } else { + r = Math.min( bb.h, bb.w )/2 - minDistance; + } + + // calculate the radius + if( nodes.length > 1 && options.avoidOverlap ){ // but only if more than one node (can't overlap) + minDistance *= 1.75; // just to have some nice spacing + + var dTheta = 2 * Math.PI / nodes.length; + var dcos = Math.cos(dTheta) - Math.cos(0); + var dsin = Math.sin(dTheta) - Math.sin(0); + var rMin = Math.sqrt( minDistance * minDistance / ( dcos*dcos + dsin*dsin ) ); // s.t. no nodes overlapping + r = Math.max( rMin, r ); + } + + var getPos = function( i, ele ){ + var rx = r * Math.cos( theta ); + var ry = r * Math.sin( theta ); + var pos = { + x: center.x + rx, + y: center.y + ry + }; + + theta = options.counterclockwise ? theta - dTheta : theta + dTheta; + return pos; + }; + + nodes.layoutPositions( this, options, getPos ); + + return this; // chaining + }; + + $$('layout', 'circle', CircleLayout); + +})( cytoscape ); + +;(function($$){ 'use strict'; + + // default layout options + var defaults = { + animate: true, // whether to show the layout as it's running + refresh: 1, // number of ticks per frame; higher is faster but more jerky + maxSimulationTime: 4000, // max length in ms to run the layout + ungrabifyWhileSimulating: false, // so you can't drag nodes during layout + fit: true, // on every layout reposition of nodes, fit the viewport + padding: 30, // padding around the simulation + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + + // layout event callbacks + ready: function(){}, // on layoutready + stop: function(){}, // on layoutstop + + // positioning options + randomize: false, // use random node positions at beginning of layout + avoidOverlap: true, // if true, prevents overlap of node bounding boxes + handleDisconnected: true, // if true, avoids disconnected components from overlapping + nodeSpacing: function( node ){ return 10; }, // extra spacing around nodes + flow: undefined, // use DAG/tree flow layout if specified, e.g. { axis: 'y', minSeparation: 30 } + alignment: undefined, // relative alignment constraints on nodes, e.g. function( node ){ return { x: 0, y: 1 } } + + // different methods of specifying edge length + // each can be a constant numerical value or a function like `function( edge ){ return 2; }` + edgeLength: undefined, // sets edge length directly in simulation + edgeSymDiffLength: undefined, // symmetric diff edge length in simulation + edgeJaccardLength: undefined, // jaccard edge length in simulation + + // iterations of cola algorithm; uses default values on undefined + unconstrIter: undefined, // unconstrained initial layout iterations + userConstIter: undefined, // initial layout iterations with user-specified constraints + allConstIter: undefined, // initial layout iterations with all constraints including non-overlap + + // infinite layout options + infinite: false // overrides all other options for a forces-all-the-time mode + }; + + // constructor + // options : object containing layout options + function ColaLayout( options ){ + this.options = $$.util.extend(true, {}, defaults, options); + } + + // runs the layout + ColaLayout.prototype.run = function(){ + var layout = this; + var options = this.options; + + $$.util.require('cola', function(cola){ + + var cy = options.cy; // cy is automatically populated for us in the constructor + var eles = options.eles; + var nodes = eles.nodes(); + var edges = eles.edges(); + var ready = false; + + var bb = $$.util.makeBoundingBox( options.boundingBox ? options.boundingBox : { + x1: 0, y1: 0, w: cy.width(), h: cy.height() + } ); + + var getOptVal = function( val, ele ){ + if( $$.is.fn(val) ){ + var fn = val; + return fn.apply( ele, [ ele ] ); + } else { + return val; + } + }; + + var updateNodePositions = function(){ + var x = { min: Infinity, max: -Infinity }; + var y = { min: Infinity, max: -Infinity }; + + for( var i = 0; i < nodes.length; i++ ){ + var node = nodes[i]; + var scratch = node._private.scratch.cola; + + x.min = Math.min( x.min, scratch.x ); + x.max = Math.max( x.max, scratch.x ); + + y.min = Math.min( y.min, scratch.y ); + y.max = Math.max( y.max, scratch.y ); + } + + nodes.positions(function(i, node){ + var pos = node._private.position; + var scratch = node._private.scratch.cola; + + if( !node.grabbed() ){ + pos.x = bb.x1 + scratch.x - x.min; + pos.y = bb.y1 + scratch.y - y.min; + } + }); + + if( !ready ){ + onReady(); + ready = true; + } + + if( options.fit ){ + cy.fit( options.padding ); + } + }; + + var onDone = function(){ + layout.manuallyStopped = false; + + if( options.ungrabifyWhileSimulating ){ + grabbableNodes.grabify(); + } + + nodes.off('grab free position', grabHandler); + nodes.off('lock unlock', lockHandler); + + // trigger layoutstop when the layout stops (e.g. finishes) + layout.one('layoutstop', options.stop); + layout.trigger({ type: 'layoutstop', layout: layout }); + }; + + var onReady = function(){ + // trigger layoutready when each node has had its position set at least once + layout.one('layoutready', options.ready); + layout.trigger({ type: 'layoutready', layout: layout }); + }; + + var ticksPerFrame = options.refresh; + var tickSkip = 1; // frames until a tick; used to slow down sim for debugging + + if( options.refresh < 0 ){ + tickSkip = Math.abs( options.refresh ); + ticksPerFrame = 1; + } else { + ticksPerFrame = Math.max( 1, ticksPerFrame ); // at least 1 + } + + var adaptor = cola.adaptor({ + trigger: function( e ){ // on sim event + switch( e.type ){ + case 'tick': + if( options.animate ){ + updateNodePositions(); + } + break; + + case 'end': + updateNodePositions(); + if( !options.infinite || layout.manuallyStopped ){ onDone(); } + break; + } + }, + + kick: function( tick ){ // kick off the simulation + var skip = 0; + + var inftick = function(){ + var ret = tick(); + + if( ret && options.infinite ){ // resume layout if done + adaptor.resume(); // resume => new kick + } + + return ret; // allow regular finish b/c of new kick + }; + + var multitick = function(){ // multiple ticks in a row + var ret; + + // skip ticks to slow down layout for debugging + var thisSkip = skip; + skip = (skip + 1) % tickSkip; + if( thisSkip !== 0 ){ + return false; + } + + for( var i = 0; i < ticksPerFrame && !ret; i++ ){ + ret = ret || inftick(); // pick up true ret vals => sim done + } + + return ret; + }; + + if( options.animate ){ + var frame = function(){ + if( multitick() ){ return; } + + $$.util.requestAnimationFrame( frame ); + }; + + $$.util.requestAnimationFrame( frame ); + } else { + while( !inftick() ){} + } + }, + + on: function( type, listener ){}, // dummy; not needed + + drag: function(){} // TODO + }); + layout.adaptor = adaptor; + + // if set no grabbing during layout + var grabbableNodes = nodes.filter(':grabbable'); + if( options.ungrabifyWhileSimulating ){ + grabbableNodes.ungrabify(); + } + + // handle node dragging + var grabHandler; + nodes.on('grab free position', grabHandler = function(e){ + var node = this; + var scrCola = node._private.scratch.cola; + var pos = node._private.position; + + if( node.grabbed() ){ + scrCola.x = pos.x - bb.x1; + scrCola.y = pos.y - bb.y1; + + adaptor.dragstart( scrCola ); + } else if( $$.is.number(scrCola.x) && $$.is.number(scrCola.y) ){ + pos.x = scrCola.x + bb.x1; + pos.y = scrCola.y + bb.y1; + } + + switch( e.type ){ + case 'grab': + adaptor.dragstart( scrCola ); + adaptor.resume(); + break; + case 'free': + adaptor.dragend( scrCola ); + break; + } + + }); + + var lockHandler; + nodes.on('lock unlock', lockHandler = function(e){ + var node = this; + var scrCola = node._private.scratch.cola; + + if( node.locked() ){ + adaptor.dragstart( scrCola ); + } else { + adaptor.dragend( scrCola ); + } + }); + + var nonparentNodes = nodes.stdFilter(function( node ){ + return !node.isParent(); + }); + + // add nodes to cola + adaptor.nodes( nonparentNodes.map(function( node, i ){ + var padding = getOptVal( options.nodeSpacing, node ); + var pos = node.position(); + + var struct = node._private.scratch.cola = { + x: options.randomize ? Math.round( Math.random() * bb.w ) : pos.x, + y: options.randomize ? Math.round( Math.random() * bb.h ) : pos.y, + width: node.outerWidth() + 2*padding, + height: node.outerHeight() + 2*padding, + index: i + }; + + return struct; + }) ); + + if( options.alignment ){ // then set alignment constraints + + var offsetsX = []; + var offsetsY = []; + + nonparentNodes.forEach(function( node ){ + var align = getOptVal( options.alignment, node ); + var scrCola = node._private.scratch.cola; + var index = scrCola.index; + + if( !align ){ return; } + + if( align.x != null ){ + offsetsX.push({ + node: index, + offset: align.x + }); + } + + if( align.y != null ){ + offsetsY.push({ + node: index, + offset: align.y + }); + } + }); + + // add alignment constraints on nodes + var constraints = []; + + if( offsetsX.length > 0 ){ + constraints.push({ + type: 'alignment', + axis: 'x', + offsets: offsetsX + }); + } + + if( offsetsY.length > 0 ){ + constraints.push({ + type: 'alignment', + axis: 'y', + offsets: offsetsY + }); + } + + adaptor.constraints( constraints ); + + } + + // add compound nodes to cola + adaptor.groups( nodes.stdFilter(function( node ){ + return node.isParent(); + }).map(function( node, i ){ // add basic group incl leaf nodes + node._private.scratch.cola = { + index: i, + + leaves: node.children().stdFilter(function( child ){ + return !child.isParent(); + }).map(function( child ){ + return child[0]._private.scratch.cola.index; + }) + }; + + return node; + }).map(function( node ){ // add subgroups + node._private.scratch.cola.groups = node.children().stdFilter(function( child ){ + return child.isParent(); + }).map(function( child ){ + return child._private.scratch.cola.index; + }); + + return node._private.scratch.cola; + }) ); + + // get the edge length setting mechanism + var length; + var lengthFnName; + if( options.edgeLength != null ){ + length = options.edgeLength; + lengthFnName = 'linkDistance'; + } else if( options.edgeSymDiffLength != null ){ + length = options.edgeSymDiffLength; + lengthFnName = 'symmetricDiffLinkLengths'; + } else if( options.edgeJaccardLength != null ){ + length = options.edgeJaccardLength; + lengthFnName = 'jaccardLinkLengths'; + } else { + length = 100; + lengthFnName = 'linkDistance'; + } + + var lengthGetter = function( link ){ + return link.calcLength; + }; + + // add the edges to cola + adaptor.links( edges.stdFilter(function( edge ){ + return !edge.source().isParent() && !edge.target().isParent(); + }).map(function( edge, i ){ + var c = edge._private.scratch.cola = { + source: edge.source()[0]._private.scratch.cola.index, + target: edge.target()[0]._private.scratch.cola.index + }; + + if( length != null ){ + c.calcLength = getOptVal( length, edge ); + } + + return c; + }) ); + + adaptor.size([ bb.w, bb.h ]); + + if( length != null ){ + adaptor[ lengthFnName ]( lengthGetter ); + } + + // set the flow of cola + if( options.flow ){ + var flow; + var defAxis = 'y'; + var defMinSep = 50; + + if( $$.is.string(options.flow) ){ + flow = { + axis: options.flow, + minSeparation: defMinSep + }; + } else if( $$.is.number(options.flow) ){ + flow = { + axis: defAxis, + minSeparation: options.flow + }; + } else if( $$.is.plainObject(options.flow) ){ + flow = options.flow; + + flow.axis = flow.axis || defAxis; + flow.minSeparation = flow.minSeparation != null ? flow.minSeparation : defMinSep; + } else { // e.g. options.flow: true + flow = { + axis: defAxis, + minSeparation: defMinSep + }; + } + + adaptor.flowLayout( flow.axis , flow.minSeparation ); + } + + adaptor + .avoidOverlaps( options.avoidOverlap ) + .handleDisconnected( options.handleDisconnected ) + .start( options.unconstrIter, options.userConstIter, options.allConstIter) + ; + + layout.trigger({ type: 'layoutstart', layout: layout }); + + if( !options.infinite ){ + setTimeout(function(){ + adaptor.stop(); + }, options.maxSimulationTime); + } + + }); // require + + return this; // chaining + }; + + // called on continuous layouts to stop them before they finish + ColaLayout.prototype.stop = function(){ + if( this.adaptor ){ + this.manuallyStopped = true; + this.adaptor.stop(); + } + + return this; // chaining + }; + + // register the layout + $$('layout', 'cola', ColaLayout); + +})(cytoscape); +;(function($$){ 'use strict'; + + var defaults = { + fit: true, // whether to fit the viewport to the graph + 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/anticlockwise (true) or clockwise (false) + minNodeSpacing: 10, // min spacing between outside of nodes (used for radius adjustment) + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space + height: undefined, // height of layout area (overrides container height) + width: undefined, // width of layout area (overrides container width) + concentric: function(){ // 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; + }, + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + ready: undefined, // callback on layoutready + stop: undefined // callback on layoutstop + }; + + function ConcentricLayout( options ){ + this.options = $$.util.extend({}, defaults, options); + } + + ConcentricLayout.prototype.run = function(){ + var params = this.options; + var options = params; + + var cy = params.cy; + + var eles = options.eles; + var nodes = eles.nodes().not(':parent'); + + var bb = $$.util.makeBoundingBox( options.boundingBox ? options.boundingBox : { + x1: 0, y1: 0, w: cy.width(), h: cy.height() + } ); + + var center = { + x: bb.x1 + bb.w/2, + y: bb.y1 + bb.h/2 + }; + + var nodeValues = []; // { node, value } + var theta = options.startAngle; + var maxNodeSize = 0; + + for( var i = 0; i < nodes.length; i++ ){ + var node = nodes[i]; + var value; + + // calculate the node value + value = options.concentric.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 + + if( !options.avoidOverlap ){ // then strictly constrain to bb + var firstLvlHasMulti = levels.length > 0 && levels[0].length > 1; + var maxR = ( Math.min(bb.w, bb.h) / 2 - minDist ); + var rStep = maxR / ( levels.length + firstLvlHasMulti ? 1 : 0 ); + + minDist = Math.min( minDist, rStep ); + } + + 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 && options.avoidOverlap ){ // but only if more than one node (can't overlap) + var dcos = Math.cos(dTheta) - Math.cos(0); + var dsin = Math.sin(dTheta) - Math.sin(0); + var rMin = Math.sqrt( minDist * minDist / ( dcos*dcos + dsin*dsin ) ); // s.t. no nodes overlapping + r = Math.max( rMin, r ); + } + + 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.layoutPositions(this, options, function(){ + var id = this.id(); + + return pos[id]; + }); + + return this; // chaining + }; + + $$('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() {}, + + // Whether to animate while running the layout + animate : true, + + // Number of iterations between consecutive screen positions update (0 -> only updated on the end) + refresh : 4, + + // Whether to fit the network view after when done + fit : true, + + // Padding on fit + padding : 30, + + // Constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + boundingBox : undefined, + + // 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 : 400000, + + // 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.0 + }; + + + /** + * @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; + var layout = this; + + layout.stopped = false; + + layout.trigger({ type: 'layoutstart', layout: layout }); + + // 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, layout, options); + + // Show LayoutInfo contents if debugging + if (DEBUG) { + printLayoutInfo(layoutInfo); + } + + // If required, randomize node positions + if (true === options.randomize) { + randomizePositions(layoutInfo, cy); + } + + updatePositions(layoutInfo, cy, options); + + var mainLoop = function(i){ + if( layout.stopped ){ + logDebug("Layout manually stopped. Stopping computation in step " + i); + return false; + } + + // Do one step in the phisical simulation + step(layoutInfo, cy, options, i); + + // Update temperature + layoutInfo.temperature = layoutInfo.temperature * options.coolingFactor; + logDebug("New temperature: " + layoutInfo.temperature); + + if (layoutInfo.temperature < options.minTemp) { + logDebug("Temperature drop below minimum threshold. Stopping computation in step " + i); + return false; + } + + return true; + }; + + var done = function(){ + 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 + layout.one('layoutstop', options.stop); + layout.trigger({ type: 'layoutstop', layout: layout }); + }; + + if( options.animate ){ + var i = 0; + var frame = function(){ + + var f = 0; + var loopRet; + while( f < options.refresh && i < options.numIter ){ + var loopRet = mainLoop(i); + if( loopRet === false ){ break; } + + f++; + i++; + } + + refreshPositions(layoutInfo, cy, options); + if( options.fit ){ + cy.fit( options.padding ); + } + + if ( loopRet !== false && i + 1 < options.numIter ) { + $$.util.requestAnimationFrame( frame ); + } else { + done(); + } + }; + + $$.util.requestAnimationFrame( frame ); + } else { + for (var i = 0; i < options.numIter; i++) { + if( mainLoop(i) === false ){ break; } + } + + done(); + } + + return this; // chaining + }; + + + /** + * @brief : called on continuous layouts to stop them before they finish + */ + CoseLayout.prototype.stop = function(){ + this.stopped = true; + + return this; // chaining + }; + + + /** + * @brief : Creates an object which is contains all the data + * used in the layout process + * @arg cy : cytoscape.js object + * @return : layoutInfo object initialized + */ + var createLayoutInfo = function(cy, layout, options) { + // Shortcut + var edges = options.eles.edges(); + var nodes = options.eles.nodes(); + + var layoutInfo = { + layout : layout, + layoutNodes : [], + idToIndex : {}, + nodeSize : nodes.size(), + graphSet : [], + indexToGraph : [], + layoutEdges : [], + edgeSize : edges.size(), + temperature : options.initialTemp, + clientWidth : cy.width(), + clientHeight : cy.width(), + boundingBox : $$.util.makeBoundingBox( options.boundingBox ? options.boundingBox : { + x1: 0, y1: 0, w: cy.width(), h: cy.height() + } ) + }; + + // 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; + } + } + + // 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); + + var layout = layoutInfo.layout; + var nodes = options.eles.nodes(); + var bb = layoutInfo.boundingBox; + var coseBB = { x1: Infinity, x2: -Infinity, y1: Infinity, y2: -Infinity }; + + if( options.boundingBox ){ + nodes.forEach(function( node ){ + var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[node.data('id')]]; + + coseBB.x1 = Math.min( coseBB.x1, lnode.positionX ); + coseBB.x2 = Math.max( coseBB.x2, lnode.positionX ); + + coseBB.y1 = Math.min( coseBB.y1, lnode.positionY ); + coseBB.y2 = Math.max( coseBB.y2, lnode.positionY ); + }); + + coseBB.w = coseBB.x2 - coseBB.x1; + coseBB.h = coseBB.y2 - coseBB.y1; + } + + nodes.positions(function(i, ele) { + var lnode = layoutInfo.layoutNodes[layoutInfo.idToIndex[ele.data('id')]]; + s = "Node: " + lnode.id + ". Refreshed position: (" + + lnode.positionX + ", " + lnode.positionY + ")."; + logDebug(s); + + if( options.boundingBox ){ // then add extra bounding box constraint + var pctX = (lnode.positionX - coseBB.x1) / coseBB.w; + var pctY = (lnode.positionY - coseBB.y1) / coseBB.h; + + return { + x: bb.x1 + pctX * bb.w, + y: bb.y1 + pctY * bb.h + }; + } else { + return { + x: lnode.positionX, + y: lnode.positionY + }; + } + }); + + // Trigger layoutReady only on first call + if (true !== layoutInfo.ready) { + s = 'Triggering layoutready'; + logDebug(s); + layoutInfo.ready = true; + layout.one('layoutready', options.ready); + layout.trigger({ type: 'layoutready', layout: this }); + } + }; + + + /** + * @brief : 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'; + + // default layout options + var defaults = { + // dagre algo options, uses default value on undefined + nodeSep: undefined, // the separation between adjacent nodes in the same rank + edgeSep: undefined, // the separation between adjacent edges in the same rank + rankSep: undefined, // the separation between adjacent nodes in the same rank + rankDir: undefined, // 'TB' for top to bottom flow, 'LR' for left to right + minLen: function( edge ){ return 1; }, // number of ranks to keep between the source and target of the edge + + // general layout options + fit: true, // whether to fit to viewport + padding: 30, // fit padding + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + ready: function(){}, // on layoutready + stop: function(){} // on layoutstop + }; + + // constructor + // options : object containing layout options + function DagreLayout( options ){ + this.options = $$.util.extend(true, {}, defaults, options); + } + + // runs the layout + DagreLayout.prototype.run = function(){ + var options = this.options; + var layout = this; + + $$.util.require('dagre', function(dagre){ + + var cy = options.cy; // cy is automatically populated for us in the constructor + var eles = options.eles; + + var bb = $$.util.makeBoundingBox( options.boundingBox ? options.boundingBox : { + x1: 0, y1: 0, w: cy.width(), h: cy.height() + } ); + + var g = new dagre.Digraph(); + + // add nodes to dagre + var nodes = eles.nodes().not(':parent'); + for( var i = 0; i < nodes.length; i++ ){ + var node = nodes[i]; + + g.addNode( node.id(), { + width: node.width(), + height: node.height() + } ); + } + + // add edges to dagre + var edges = eles.edges(); + for( var i = 0; i < edges.length; i++ ){ + var edge = edges[i]; + + g.addEdge( edge.id(), edge.source().id(), edge.target().id(), { + minLen: $$.is.fn(options.minLen) ? options.minLen.apply( edge, [ edge ] ) : options.minLen + } ); + } + + var d = dagre.layout(); + + if( options.nodeSep ){ + d.nodeSep( options.nodeSep ); + } + + if( options.edgeSep ){ + d.edgeSep( options.edgeSep ); + } + + if( options.rankSep ){ + d.rankSep( options.rankSep ); + } + + if( options.rankDir ){ + d.rankDir( options.rankDir ); + } + + d = d.run(g); + + d.eachNode(function(id, n) { + cy.getElementById(id).scratch().dagre = n; + }); + + var dagreBB; + + if( options.boundingBox ){ + dagreBB = { x1: Infinity, x2: -Infinity, y1: Infinity, y2: -Infinity }; + nodes.forEach(function( node ){ + var dModel = node.scratch().dagre; + + dagreBB.x1 = Math.min( dagreBB.x1, dModel.x ); + dagreBB.x2 = Math.max( dagreBB.x2, dModel.x ); + + dagreBB.y1 = Math.min( dagreBB.y1, dModel.y ); + dagreBB.y2 = Math.max( dagreBB.y2, dModel.y ); + }); + + dagreBB.w = dagreBB.x2 - dagreBB.x1; + dagreBB.h = dagreBB.y2 - dagreBB.y1; + } else { + dagreBB = bb; + } + + var constrainPos = function( p ){ + if( options.boundingBox ){ + var xPct = (p.x - dagreBB.x1) / dagreBB.w; + var yPct = (p.y - dagreBB.y1) / dagreBB.h; + + return { + x: bb.x1 + xPct * bb.w, + y: bb.y1 + yPct * bb.h + }; + } else { + return p; + } + }; + + nodes.layoutPositions(layout, options, function(){ + var dModel = this.scratch().dagre; + + return constrainPos({ + x: dModel.x, + y: dModel.y + }); + }); + + }); // require + + return this; // chaining + }; + + // register the layout + $$('layout', 'dagre', DagreLayout); + +})(cytoscape); +;(function($$){ 'use strict'; + + var defaults = { + fit: true, // whether to fit the viewport to the graph + padding: 30, // padding used on fit + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space + 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 + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + ready: undefined, // callback on layoutready + stop: undefined // callback on layoutstop + }; + + function GridLayout( options ){ + this.options = $$.util.extend({}, defaults, options); + } + + GridLayout.prototype.run = function(){ + var params = this.options; + var options = params; + + var cy = params.cy; + var eles = options.eles; + var nodes = eles.nodes().not(':parent'); + + var bb = $$.util.makeBoundingBox( options.boundingBox ? options.boundingBox : { + x1: 0, y1: 0, w: cy.width(), h: cy.height() + } ); + + if( bb.h === 0 || bb.w === 0){ + nodes.layoutPositions(this, options, function(){ + return { x: bb.x1, y: bb.y1 }; + }); + + } else { + + // width/height * splits^2 = cells where splits is number of times to split width + var cells = nodes.size(); + var splits = Math.sqrt( cells * bb.h/bb.w ); + var rows = Math.round( splits ); + var cols = Math.round( bb.w/bb.h * splits ); + + var small = function(val){ + if( val == null ){ + return Math.min(rows, cols); + } else { + var min = Math.min(rows, cols); + if( min == rows ){ + rows = val; + } else { + cols = val; + } + } + }; + + var large = function(val){ + if( val == null ){ + return Math.max(rows, cols); + } else { + var max = Math.max(rows, cols); + if( max == rows ){ + rows = val; + } else { + cols = val; + } + } + }; + + // 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 = bb.w / cols; + var cellHeight = bb.h / rows; + + if( options.avoidOverlap ){ + 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 ); + } + } + + var getPos = function(i, element){ + var x, y; + + if( element.locked() || element.isFullAutoParent() ){ + return false; + } + + // see if we have a manual position set + var rcPos = id2manPos[ element.id() ]; + if( rcPos ){ + x = rcPos.col * cellWidth + cellWidth/2 + bb.x1; + y = rcPos.row * cellHeight + cellHeight/2 + bb.y1; + + } else { // otherwise set automatically + + while( used(row, col) ){ + moveToNextCell(); + } + + x = col * cellWidth + cellWidth/2 + bb.x1; + y = row * cellHeight + cellHeight/2 + bb.y1; + use( row, col ); + + moveToNextCell(); + } + + return { x: x, y: y }; + + }; + + nodes.layoutPositions( this, options, getPos ); + } + + return this; // chaining + + }; + + $$('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 eles = options.eles; // elements to consider in the layout + var layout = this; + + // cy is automatically populated for us in the constructor + var cy = options.cy; // jshint ignore:line + + layout.trigger('layoutstart'); + + // puts all nodes at (0, 0) + eles.nodes().positions(function(){ + return { + x: 0, + y: 0 + }; + }); + + // trigger layoutready when each node has had its position set at least once + layout.one('layoutready', options.ready); + layout.trigger('layoutready'); + + // trigger layoutstop when the layout stops (e.g. finishes) + layout.one('layoutstop', options.stop); + layout.trigger('layoutstop'); + + return this; // chaining + }; + + // called on continuous layouts to stop them before they finish + NullLayout.prototype.stop = function(){ + return this; // chaining + }; + + // register the layout + $$('layout', 'null', NullLayout); + +})(cytoscape); +;(function($$){ 'use strict'; + + var defaults = { + positions: undefined, // map of (node id) => (position obj); or function(node){ return somPos; } + zoom: undefined, // the zoom level to set (prob want fit = false if set) + pan: undefined, // the pan level to set (prob want fit = false if set) + fit: true, // whether to fit to viewport + padding: 30, // padding on fit + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + ready: undefined, // callback on layoutready + stop: undefined // callback on layoutstop + }; + + function PresetLayout( options ){ + this.options = $$.util.extend(true, {}, defaults, options); + } + + PresetLayout.prototype.run = function(){ + var options = this.options; + var eles = options.eles; + + var nodes = eles.nodes(); + var posIsFn = $$.is.fn( options.positions ); + + function getPosition(node){ + if( options.positions == null ){ + return null; + } + + if( posIsFn ){ + return options.positions.apply( node, [ node ] ); + } + + var pos = options.positions[node._private.data.id]; + + if( pos == null ){ + return null; + } + + return pos; + } + + nodes.layoutPositions(this, options, function(i, node){ + var position = getPosition(node); + + if( node.locked() || position == null ){ + return false; + } + + return position; + }); + + return this; // chaining + }; + + $$('layout', 'preset', PresetLayout); + +})(cytoscape); + +;(function($$){ 'use strict'; + + var defaults = { + fit: true, // whether to fit to viewport + padding: 30, // fit padding + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + ready: undefined, // callback on layoutready + stop: undefined // callback on layoutstop + }; + + function RandomLayout( options ){ + this.options = $$.util.extend(true, {}, defaults, options); + } + + RandomLayout.prototype.run = function(){ + var options = this.options; + var cy = options.cy; + var eles = options.eles; + var nodes = eles.nodes().not(':parent'); + + var bb = $$.util.makeBoundingBox( options.boundingBox ? options.boundingBox : { + x1: 0, y1: 0, w: cy.width(), h: cy.height() + } ); + + var getPos = function( i, node ){ + return { + x: bb.x1 + Math.round( Math.random() * bb.w ), + y: bb.y1 + Math.round( Math.random() * bb.h ) + }; + }; + + nodes.layoutPositions( this, options, getPos ); + + return this; // chaining + }; + + // register the layout + $$( + 'layout', // we're registering a layout + 'random', // the layout name + RandomLayout // the layout prototype + ); + +})(cytoscape); + +;(function($$){ 'use strict'; + + var defaults = { + animate: true, // whether to show the layout as it's running + maxSimulationTime: 4000, // max length in ms to run the layout + ungrabifyWhileSimulating: false, // so you can't drag nodes during layout + fit: true, // whether to fit the viewport to the graph + padding: 30, // padding on fit + boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } + random: false, // whether to use random initial positions + infinite: false, // overrides all other options for a forces-all-the-time mode + ready: undefined, // callback on layoutready + stop: undefined, // callback on layoutstop + + // springy forces + stiffness: 400, + repulsion: 400, + damping: 0.5 + }; + + function SpringyLayout( options ){ + this.options = $$.util.extend(true, {}, defaults, options); + } + + SpringyLayout.prototype.run = function(){ + var layout = this; + var self = this; + var options = this.options; + + $$.util.require('Springy', function(Springy){ + + var simUpdatingPos = false; + + var cy = options.cy; + layout.trigger({ type: 'layoutstart', layout: layout }); + + var eles = options.eles; + var nodes = eles.nodes().not(':parent'); + var edges = eles.edges(); + + var bb = $$.util.makeBoundingBox( options.boundingBox ? options.boundingBox : { + x1: 0, y1: 0, w: cy.width(), h: cy.height() + } ); + + // 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 sim = window.sim = new Springy.Layout.ForceDirected(graph, options.stiffness, options.repulsion, options.damping); + + if( options.infinite ){ + sim.minEnergyThreshold = -Infinity; + } + + var currentBB = sim.getBoundingBox(); + // var targetBB = {bottomleft: new Springy.Vector(-2, -2), topright: new Springy.Vector(2, 2)}; + + // convert to/from screen coordinates + var toScreen = function(p) { + currentBB = sim.getBoundingBox(); + + var size = currentBB.topright.subtract(currentBB.bottomleft); + var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * bb.w + bb.x1; + var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * bb.h + bb.x1; + + return new Springy.Vector(sx, sy); + }; + + var fromScreen = function(s) { + currentBB = sim.getBoundingBox(); + + var size = currentBB.topright.subtract(currentBB.bottomleft); + var px = ((s.x - bb.x1) / bb.w) * size.x + currentBB.bottomleft.x; + var py = ((s.y - bb.y1) / bb.h) * 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(sim, + function clear() { + if( movedNodes.length > 0 && options.animate ){ + simUpdatingPos = true; + + movedNodes.rtrigger('position'); + + if( options.fit ){ + cy.fit( options.padding ); + } + + movedNodes = cy.collection(); + + simUpdatingPos = false; + } + }, + + function drawEdge(edge, p1, p2) { + // draw an edge + }, + + function drawNode(node, p) { + var v = toScreen(p); + var element = node.data.element; + + if( !element.locked() && !element.grabbed() ){ + element._private.position = { + x: v.x, + y: v.y + }; + movedNodes.merge(element); + } else { + //setLayoutPositionForElement(element); + } + + if( drawnNodes == numNodes ){ + layout.one('layoutready', options.ready); + layout.trigger({ type: 'layoutready', layout: layout }); + } + + drawnNodes++; + + } + ); + + // set initial node points + nodes.each(function(i, ele){ + if( !options.random ){ + setLayoutPositionForElement(ele); + } + }); + + // update node positions when dragging + var dragHandler; + nodes.on('position', dragHandler = function(){ + if( simUpdatingPos ){ return; } + + 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(); + } + + self.stopSystem = function(){ + graph.filterNodes(function(){ + return false; // remove all nodes + }); + + if( options.ungrabifyWhileSimulating ){ + grabbableNodes.grabify(); + } + + if( options.fit ){ + cy.fit( options.padding ); + } + + nodes.off('drag position', dragHandler); + + layout.one('layoutstop', options.stop); + layout.trigger({ type: 'layoutstop', layout: layout }); + + self.stopSystem = null; + }; + + start(); + if( !options.infinite ){ + setTimeout(function(){ + self.stop(); + }, options.maxSimulationTime); + } + + }); // require + + return this; // chaining + }; + + SpringyLayout.prototype.stop = function(){ + if( this.stopSystem != null ){ + this.stopSystem(); + } + + return this; // chaining + }; + + $$('layout', 'springy', SpringyLayout); + + +})(cytoscape); + +;(function($$){ 'use strict'; + + function NullRenderer(options){ + this.options = options; + } + + NullRenderer.prototype.recalculateRenderedStyle = function(){ + }; + + NullRenderer.prototype.notify = function(){ + // the null renderer does nothing + }; + + $$('renderer', 'null', NullRenderer); + +})( cytoscape ); diff --git a/ajax/libs/cytoscape/2.3.4/cytoscape.min.js b/ajax/libs/cytoscape/2.3.4/cytoscape.min.js new file mode 100644 index 000000000..2c459a52e --- /dev/null +++ b/ajax/libs/cytoscape/2.3.4/cytoscape.min.js @@ -0,0 +1,24 @@ +/*! + * This file is part of Cytoscape.js 2.3.4. + * + * Cytoscape.js is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Cytoscape.js is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * Cytoscape.js. If not, see . + */ +var cytoscape;!function(e){"use strict";var t=cytoscape=function(){return cytoscape.init.apply(cytoscape,arguments)};t.version="2.3.4",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 module&&module.exports&&(module.exports=cytoscape),"undefined"!=typeof define&&define.amd&&define("cytoscape",function(){return cytoscape}),e&&(e.cytoscape=cytoscape)}("undefined"==typeof window?null:window),function(e,t){"use strict";e.is={defined:function(e){return null!=e},string:function(e){return null!=e&&"string"==typeof e},fn:function(e){return null!=e&&"function"==typeof e},array:function(e){return Array.isArray?Array.isArray(e):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)},boundingBox:function(t){return e.is.plainObject(t)&&e.is.number(t.x1)&&e.is.number(t.x2)&&e.is.number(t.y1)&&e.is.number(t.y2)}}}(cytoscape,"undefined"==typeof window?null:window),function(e,t){"use strict";e.util={extend:function(){var t,i,r,n,a,o,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||e.is.fn(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(t=arguments[l]))for(i in t)r=s[i],n=t[i],s!==n&&(c&&n&&(e.is.plainObject(n)||(a=e.is.array(n)))?(a?(a=!1,o=r&&e.is.array(r)?r:[]):o=r&&e.is.plainObject(r)?r:{},s[i]=e.util.extend(c,o,n)):void 0!==n&&(s[i]=n));return s},require:function(i,r,n){var a;n=e.util.extend({msgIfNotFound:!0},n);var o=!1,s=function(e){o=!0,r(e)},l=function(e){t&&(a=t[i]),void 0!==a&&s(a),e&&e()},u=function(){o||c(d)},c=function(e){"undefined"!=typeof module&&module.exports&&require&&(a=require(i)),void 0!==a&&s(a),e&&e()},d=function(){o||p(h)},p=function(e){"undefined"!=typeof define&&define.amd&&require&&require([i],function(t){a=t,void 0!==a&&s(a),e&&e()})},h=function(){!o&&n.msgIfNotFound&&e.util.error("Cytoscape.js tried to pull in dependency `"+i+"` but no module (i.e. CommonJS, AMD, or window) was found")};l(u)},requires:function(t,i){for(var r=[],n=[],a=function(){for(var e=0;e=r){a&&clearTimeout(a);var p=c;a=u=c=void 0,p&&(d=e.util.now(),o=t.apply(l,n),u||a||(n=l=null))}else u=setTimeout(g,r)},f=function(){u&&clearTimeout(u),a=u=c=void 0,(h||p!==i)&&(d=e.util.now(),o=t.apply(l,n),u||a||(n=l=null))};return function(){if(n=arguments,s=e.util.now(),l=this,c=h&&(u||!v),p===!1)var r=v&&!u;else{a||v||(d=s);var y=p-(s-d),m=0>=y;m?(a&&(a=clearTimeout(a)),d=s,o=t.apply(l,n)):a||(a=setTimeout(f,y))}return m&&u?u=clearTimeout(u):u||i===p||(u=setTimeout(g,i)),r&&(m=!0,o=t.apply(l,n)),!m||u||a||(n=l=null),o}}},error:function(e){if(!console)throw e;if(console.error)console.error.apply(console,arguments);else{if(!console.log)throw e;console.log.apply(console,arguments)}},clone:function(e){var t={};for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t},copy:function(t){return null==t?t:e.is.array(t)?t.slice():e.is.plainObject(t)?e.util.clone(t):t},makeBoundingBox:function(e){if(null!=e.x1&&null!=e.y1){if(null!=e.x2&&null!=e.y2&&e.x2>=e.x1&&e.y2>=e.y1)return{x1:e.x1,y1:e.y1,x2:e.x2,y2:e.y2,w:e.x2-e.x1,h:e.y2-e.y1};if(null!=e.w&&null!=e.h&&e.w>=0&&e.h>=0)return{x1:e.x1,y1:e.y1,x2:e.x1+e.w,y2:e.y1+e.h,w:e.w,h:e.h}}},mapEmpty:function(e){var t=!0;if(null!=e)for(var i in e){t=!1;break}return t},pushMap:function(t){var i=e.util.getMap(t);null==i?e.util.setMap($.extend({},t,{value:[t.value]})):i.push(t.value)},setMap:function(t){for(var i,r=t.map,n=t.keys,a=n.length,o=0;a>o;o++){var i=n[o];e.is.plainObject(i)&&e.util.error("Tried to set map with object key"),oa;a++){var o=r[a];if(e.is.plainObject(o)&&e.util.error("Tried to get map with object key"),i=i[o],null==i)return i}return i},deleteMap:function(t){for(var i=t.map,r=t.keys,n=r.length,a=t.keepChildren,o=0;n>o;o++){var s=r[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 u in i)a[u]||(i[u]=void 0);else i[s]=void 0;else i=i[s]}},capitalize:function(t){return e.is.emptyString(t)?t:t.charAt(0).toUpperCase()+t.substring(1)},camel2dash:function(e){for(var t=[],i=0;it&&" "===e[i];i--);return e.substring(t,i+1)},hex2tuple:function(e){if((4===e.length||7===e.length)&&"#"===e[0]){var t,i,r,n=4===e.length,a=16;return n?(t=parseInt(e[1]+e[1],a),i=parseInt(e[2]+e[2],a),r=parseInt(e[3]+e[3],a)):(t=parseInt(e[1]+e[2],a),i=parseInt(e[3]+e[4],a),r=parseInt(e[5]+e[6],a)),[t,i,r]}},hsl2tuple:function(t){function i(e,t,i){return 0>i&&(i+=1),i>1&&(i-=1),1/6>i?e+6*(t-e)*i:.5>i?t:2/3>i?e+(t-e)*(2/3-i)*6:e}var r,n,a,o,s,l,u,c,d=new RegExp("^"+e.util.regex.hsla+"$").exec(t);if(d){if(n=parseInt(d[1]),0>n?n=(360- -1*n%360)%360:n>360&&(n%=360),n/=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=u=c=Math.round(255*o);else{var p=.5>o?o*(1+a):o+a-o*a,h=2*o-p;l=Math.round(255*i(h,p,n+1/3)),u=Math.round(255*i(h,p,n)),c=Math.round(255*i(h,p,n-1/3))}r=[l,u,c,s]}return r},rgb2tuple:function(t){var i,r=new RegExp("^"+e.util.regex.rgba+"$").exec(t);if(r){i=[];for(var n=[],a=1;3>=a;a++){var o=r[a];if("%"===o[o.length-1]&&(n[a]=!0),o=parseFloat(o),n[a]&&(o=o/100*255),0>o||o>255)return;i.push(Math.floor(o))}var s=n[1]||n[2]||n[3],l=n[1]&&n[2]&&n[3];if(s&&!l)return;var u=r[4];if(void 0!==u){if(u=parseFloat(u),0>u||u>1)return;i.push(u)}}return i},colorname2tuple:function(t){return e.util.colors[t.toLowerCase()]},color2tuple:function(t){return(e.is.array(t)?t:null)||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 i=e[0],r=e[1],n=e[2];return"#"+t(i)+t(r)+t(n)},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 i=t?t.requestAnimationFrame||t.mozRequestAnimationFrame||t.webkitRequestAnimationFrame||t.msRequestAnimationFrame:null;i=i||function(e){e&&setTimeout(e,1e3/60)},e.util.requestAnimationFrame=function(e){i(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 i=t.x-e.x,r=t.y-e.y;return Math.sqrt(i*i+r*r)},e.math.qbezierAt=function(e,t,i,r){return(1-r)*(1-r)*e+2*(1-r)*r*t+r*r*i},e.math.qbezierPtAt=function(t,i,r,n){return{x:e.math.qbezierAt(t.x,i.x,r.x,n),y:e.math.qbezierAt(t.y,i.y,r.y,n)}},e.math.boundingBoxesIntersect=function(e,t){return e.x1>t.x2?!1:t.x1>e.x2?!1:e.x2t.y2?!1:t.y1>e.y2?!1:!0},e.math.inBoundingBox=function(e,t,i){return e.x1<=t&&t<=e.x2&&e.y1<=i&&i<=e.y2},e.math.pointInBoundingBox=function(e,t){return this.inBoundingBox(e,t.x,t.y)},e.math.roundRectangleIntersectLine=function(e,t,i,r,n,a,o){var s,l=this.getRoundRectangleRadius(n,a),u=n/2,c=a/2,d=i-u+l-o,p=r-c-o,h=i+u-l+o,v=p;if(s=this.finiteLinesIntersect(e,t,i,r,d,p,h,v,!1),s.length>0)return s;var g=i+u+o,f=r-c+l-o,y=g,m=r+c-l+o;if(s=this.finiteLinesIntersect(e,t,i,r,g,f,y,m,!1),s.length>0)return s;var x=i-u+l-o,b=r+c+o,w=i+u-l+o,_=b;if(s=this.finiteLinesIntersect(e,t,i,r,x,b,w,_,!1),s.length>0)return s;var E=i-u-o,S=r-c+l-o,P=E,k=r+c-l+o;if(s=this.finiteLinesIntersect(e,t,i,r,E,S,P,k,!1),s.length>0)return s;var C,D=i-u+l,N=r-c+l;if(C=this.intersectLineCircle(e,t,i,r,D,N,l+o),C.length>0&&C[0]<=D&&C[1]<=N)return[C[0],C[1]];var T=i+u-l,M=r-c+l;if(C=this.intersectLineCircle(e,t,i,r,T,M,l+o),C.length>0&&C[0]>=T&&C[1]<=M)return[C[0],C[1]];var B=i+u-l,z=r+c-l;if(C=this.intersectLineCircle(e,t,i,r,B,z,l+o),C.length>0&&C[0]>=B&&C[1]>=z)return[C[0],C[1]];var I=i-u+l,L=r+c-l;return C=this.intersectLineCircle(e,t,i,r,I,L,l+o),C.length>0&&C[0]<=I&&C[1]>=L?[C[0],C[1]]:[]},e.math.roundRectangleIntersectBox=function(e,t,i,r,n,a,o,s,l){var u=this.getRoundRectangleRadius(n,a),c=o-n/2-l,d=s-a/2+u-l,p=o+n/2+l,h=s+a/2-u+l,v=o-n/2+u-l,g=s-a/2-l,f=o+n/2-u+l,y=s+a/2+l,m=Math.min(e,i),x=Math.max(e,i),b=Math.min(t,r),w=Math.max(t,r);return c>x?!1:m>p?!1:g>w?!1:b>y?!1:c>=m&&x>=c&&d>=b&&w>=d?!0:p>=m&&x>=p&&d>=b&&w>=d?!0:p>=m&&x>=p&&h>=b&&w>=h?!0:c>=m&&x>=c&&h>=b&&w>=h?!0:m>=c&&p>=m&&b>=d&&h>=b?!0:x>=c&&p>=x&&b>=d&&h>=b?!0:x>=c&&p>=x&&w>=d&&h>=w?!0:m>=c&&p>=m&&w>=d&&h>=w?!0:v>=m&&x>=v&&g>=b&&w>=g?!0:f>=m&&x>=f&&g>=b&&w>=g?!0:f>=m&&x>=f&&y>=b&&w>=y?!0:v>=m&&x>=v&&y>=b&&w>=y?!0:m>=v&&f>=m&&b>=g&&y>=b?!0:x>=v&&f>=x&&b>=g&&y>=b?!0:x>=v&&f>=x&&w>=g&&y>=w?!0:m>=v&&f>=m&&w>=g&&y>=w?!0:this.boxIntersectEllipse(m,b,x,w,l,2*u,2*u,v+l,d+l)?!0:this.boxIntersectEllipse(m,b,x,w,l,2*u,2*u,f-l,d+l)?!0:this.boxIntersectEllipse(m,b,x,w,l,2*u,2*u,f-l,h-l)?!0:this.boxIntersectEllipse(m,b,x,w,l,2*u,2*u,v+l,h-l)?!0:!1},e.math.checkInBoundingCircle=function(e,t,i,r,n,a,o,s){return e=(e-o)/(n+r),t=(t-s)/(a+r),i>=e*e+t*t},e.math.boxInBezierVicinity=function(e,t,i,r,n,a,o,s,l,u,c){var d=.25*n+.5*o+.25*l,p=.25*a+.5*s+.25*u,h=Math.min(e,i)-c,v=Math.min(t,r)-c,g=Math.max(e,i)+c,f=Math.max(t,r)+c;if(n>=h&&g>=n&&a>=v&&f>=a)return 1;if(l>=h&&g>=l&&u>=v&&f>=u)return 1;if(d>=h&&g>=d&&p>=v&&f>=p)return 1;if(o>=h&&g>=o&&s>=v&&f>=s)return 1;var y=Math.min(n,d,l),m=Math.min(a,p,u),x=Math.max(n,d,l),b=Math.max(a,p,u);return y>g||h>x||m>f||v>b?0:1},e.math.checkBezierInBox=function(t,i,r,n,a,o,s,l,u,c){function d(d){var p=e.math.qbezierAt(a,s,u,d),h=e.math.qbezierAt(o,l,c,d);return p>=t&&r>=p&&h>=i&&n>=h}for(var p=0;1>=p;p+=.25)if(!d(p))return!1;return!0},e.math.checkStraightEdgeInBox=function(e,t,i,r,n,a,o,s){return n>=e&&i>=n&&o>=e&&i>=o&&a>=t&&r>=a&&s>=t&&r>=s},e.math.checkStraightEdgeCrossesBox=function(e,t,i,r,n,a,o,s,l){var u,c,d=Math.min(e,i)-l,p=Math.min(t,r)-l,h=Math.max(e,i)+l,v=Math.max(t,r)+l,g=o-n,f=n,y=s-a,m=a;if(Math.abs(g)<1e-4)return n>=d&&h>=n&&Math.min(a,s)<=p&&Math.max(a,s)>=v;var x=(d-f)/g;if(x>0&&1>=x&&(u=y*x+m,u>=p&&v>=u))return!0;var b=(h-f)/g;if(b>0&&1>=b&&(u=y*b+m,u>=p&&v>=u))return!0;var w=(p-m)/y;if(w>0&&1>=w&&(c=g*w+f,c>=d&&h>=c))return!0;var _=(v-m)/y;return _>0&&1>=_&&(c=g*_+f,c>=d&&h>=c)?!0:!1},e.math.checkBezierCrossesBox=function(e,t,i,r,n,a,o,s,l,u,c){var d=Math.min(e,i)-c,p=Math.min(t,r)-c,h=Math.max(e,i)+c,v=Math.max(t,r)+c;if(n>=d&&h>=n&&a>=p&&v>=a)return!0;if(l>=d&&h>=l&&u>=p&&v>=u)return!0;var g=n-2*o+l,f=-2*n+2*o,y=n,m=[];if(Math.abs(g)<1e-4){var x=(d-n)/f,b=(h-n)/f;m.push(x,b)}else{var w,_,E=f*f-4*g*(y-d);if(E>0){var S=Math.sqrt(E);w=(-f+S)/(2*g),_=(-f-S)/(2*g),m.push(w,_)}var P,k,C=f*f-4*g*(y-h);if(C>0){var S=Math.sqrt(C);P=(-f+S)/(2*g),k=(-f-S)/(2*g),m.push(P,k)}}m.sort(function(e,t){return e-t});var D=a-2*s+u,N=-2*a+2*s,T=a,M=[];if(Math.abs(D)<1e-4){var B=(p-a)/N,z=(v-a)/N;M.push(B,z)}else{var I,L,O=N*N-4*D*(T-p);if(O>0){var S=Math.sqrt(O);I=(-N+S)/(2*D),L=(-N-S)/(2*D),M.push(I,L)}var R,X,V=N*N-4*D*(T-v);if(V>0){var S=Math.sqrt(V);R=(-N+S)/(2*D),X=(-N-S)/(2*D),M.push(R,X)}}M.sort(function(e,t){return e-t});for(var Y=0;Y=0&&m[Y]<=1&&m[Y+1]>M[A-1]&&M[A-1]<=1&&m[Y+1]>=0)return!0;return!1},e.math.inLineVicinity=function(e,t,i,r,n,a,o){var s=o,l=Math.min(i,n),u=Math.max(i,n),c=Math.min(r,a),d=Math.max(r,a);return e>=l-s&&u+s>=e&&t>=c-s&&d+s>=t},e.math.inBezierVicinity=function(e,t,i,r,n,a,o,s){var l={x1:Math.min(i,o,n),x2:Math.max(i,o,n),y1:Math.min(r,s,a),y2:Math.max(r,s,a)};return el.x2||tl.y2?!1:!0},e.math.solveCubic=function(e,t,i,r,n){t/=e,i/=e,r/=e;var a,o,s,l,u,c,d,p;return o=(3*i-t*t)/9,s=-(27*r)+t*(9*i-2*t*t),s/=54,a=o*o*o+s*s,n[1]=0,d=t/3,a>0?(u=s+Math.sqrt(a),u=0>u?-Math.pow(-u,1/3):Math.pow(u,1/3),c=s-Math.sqrt(a),c=0>c?-Math.pow(-c,1/3):Math.pow(c,1/3),n[0]=-d+u+c,d+=(u+c)/2,n[4]=n[2]=-d,d=Math.sqrt(3)*(-c+u)/2,n[3]=d,void(n[5]=-d)):(n[5]=n[3]=0,0===a?(p=0>s?-Math.pow(-s,1/3):Math.pow(s,1/3),n[0]=-d+2*p,void(n[4]=n[2]=-(p+d))):(o=-o,l=o*o*o,l=Math.acos(s/Math.sqrt(l)),p=2*Math.sqrt(o),n[0]=-d+p*Math.cos(l/3),n[2]=-d+p*Math.cos((l+2*Math.PI)/3),void(n[4]=-d+p*Math.cos((l+4*Math.PI)/3))))},e.math.sqDistanceToQuadraticBezier=function(e,t,i,r,n,a,o,s){var l=1*i*i-4*i*n+2*i*o+4*n*n-4*n*o+o*o+r*r-4*r*a+2*r*s+4*a*a-4*a*s+s*s,u=9*i*n-3*i*i-3*i*o-6*n*n+3*n*o+9*r*a-3*r*r-3*r*s-6*a*a+3*a*s,c=3*i*i-6*i*n+i*o-i*e+2*n*n+2*n*e-o*e+3*r*r-6*r*a+r*s-r*t+2*a*a+2*a*t-s*t,d=1*i*n-i*i+i*e-n*e+r*a-r*r+r*t-a*t,p=[];this.solveCubic(l,u,c,d,p);for(var h=1e-7,v=[],g=0;6>g;g+=2)Math.abs(p[g+1])=0&&p[g]<=1&&v.push(p[g]);v.push(1),v.push(0);for(var f,y,m,x,b=-1,w=0;w=0?b>x&&(b=x,f=v[w]):(b=x,f=v[w]);return b},e.math.sqDistanceToFiniteLine=function(e,t,i,r,n,a){var o=[e-i,t-r],s=[n-i,a-r],l=s[0]*s[0]+s[1]*s[1],u=o[0]*o[0]+o[1]*o[1],c=o[0]*s[0]+o[1]*s[1],d=c*c/l;return 0>c?u:d>l?(e-n)*(e-n)+(t-a)*(t-a):u-d},e.math.pointInsidePolygon=function(e,t,i,r,n,a,o,s,l){var u=new Array(i.length),c=Math.asin(s[1]/Math.sqrt(s[0]*s[0]+s[1]*s[1]));s[0]<0?c+=Math.PI/2:c=-c-Math.PI/2;for(var d=Math.cos(-c),p=Math.sin(-c),h=0;h0){var g=this.expandPolygon(u,-l);v=this.joinLines(g)}else v=u;for(var f,y,m,x,b,w=0,_=0,h=0;h=e&&e>=m||e>=f&&m>=e))continue;b=(e-f)/(m-f)*(x-y)+y,b>t&&w++,t>b&&_++}return w%2===0?!1:!0},e.math.joinLines=function(e){for(var t,i,r,n,a,o,s,l,u=new Array(e.length/2),c=0;cu)return[];var c=u/l;return[(i-e)*c+e,(r-t)*c+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,i,r,n,a,o){var s=[i-e,r-t],l=[n,a],u=[e-n,t-a],c=s[0]*s[0]+s[1]*s[1],d=2*(u[0]*s[0]+u[1]*s[1]),l=u[0]*u[0]+u[1]*u[1]-o*o,p=d*d-4*c*l;if(0>p)return[];var h=(-d+Math.sqrt(p))/(2*c),v=(-d-Math.sqrt(p))/(2*c),g=Math.min(h,v),f=Math.max(h,v),y=[];if(g>=0&&1>=g&&y.push(g),f>=0&&1>=f&&y.push(f),0===y.length)return[];var m=y[0]*s[0]+e,x=y[0]*s[1]+t;if(y.length>1){if(y[0]==y[1])return[m,x];var b=y[1]*s[0]+e,w=y[1]*s[1]+t;return[m,x,b,w]}return[m,x]},e.math.findCircleNearPoint=function(e,t,i,r,n){var a=r-e,o=n-t,s=Math.sqrt(a*a+o*o),l=a/s,u=o/s;return[e+l*i,t+u*i]},e.math.findMaxSqDistanceToOrigin=function(e){for(var t,i=1e-6,r=0;ri&&(i=t);return i},e.math.finiteLinesIntersect=function(e,t,i,r,n,a,o,s,l){var u=(o-n)*(t-a)-(s-a)*(e-n),c=(i-e)*(t-a)-(r-t)*(e-n),d=(s-a)*(i-e)-(o-n)*(r-t);if(0!==d){var p=u/d,h=c/d;return p>=0&&1>=p&&h>=0&&1>=h?[e+p*(i-e),t+p*(r-t)]:l?[e+p*(i-e),t+p*(r-t)]:[]}return 0===u||0===c?[e,i,o].sort()[1]===o?[o,s]:[e,i,n].sort()[1]===n?[n,a]:[n,o,i].sort()[1]===i?[i,r]:[]:[]},e.math.boxIntersectEllipse=function(e,t,i,r,n,a,o,s,l){if(e>i){var u=e;e=i,i=u}if(t>r){var c=t;t=r,r=c}var d=[s-a/2-n,l],p=[s+a/2+n,l],h=[s,l-o/2-n],v=[s,l+o/2+n];return ip[0]?!1:t>v[1]?!1:r=e*e+t*t?!0:1>=i*i+t*t?!0:1>=i*i+r*r?!0:1>=e*e+r*r?!0:!1)},e.math.boxIntersectPolygon=function(t,i,r,n,a,o,s,l,u,c,d){if(t>r){var p=t;t=r,r=p}if(i>n){var h=i;i=n,n=h}var v=new Array(a.length),g=Math.asin(c[1]/Math.sqrt(c[0]*c[0]+c[1]*c[1]));c[0]<0?g+=Math.PI/2:g=-g-Math.PI/2;for(var f=Math.cos(-g),y=Math.sin(-g),m=0;mb&&(b=v[2*m]),v[2*m]_&&(_=v[2*m+1]),v[2*m+1]r)return!1;if(t>b+d)return!1;if(w-d>n)return!1;if(i>_+d)return!1;var E;if(d>0){var S=e.math.expandPolygon(v,-d);E=e.math.joinLines(S)}else E=v;for(var m=0;m0)return!0;if(e.math.finiteLinesIntersect(C,D,P,k,t,n,r,n,!1).length>0)return!0;if(e.math.finiteLinesIntersect(C,D,P,k,t,i,t,n,!1).length>0)return!0;if(e.math.finiteLinesIntersect(C,D,P,k,r,i,r,n,!1).length>0)return!0}return!1},e.math.polygonIntersectLine=function(t,i,r,n,a,o,s,l){for(var u,c=[],d=new Array(r.length),p=0;p0){var v=e.math.expandPolygon(d,-l);h=e.math.joinLines(v)}else h=d;for(var g,f,y,m,p=0;pa&&(a=1e-5),[t[0]+a*r[0],t[1]+a*r[1]]},e.math.generateUnitNgonPointsFitToSquare=function(t,i){var r=e.math.generateUnitNgonPoints(t,i);return r=e.math.fitPolygonToSquare(r)},e.math.fitPolygonToSquare=function(e){for(var t,i,r=e.length/2,n=1/0,a=1/0,o=-1/0,s=-1/0,l=0;r>l;l++)t=e[2*l],i=e[2*l+1],n=Math.min(n,t),o=Math.max(o,t),a=Math.min(a,i),s=Math.max(s,i);for(var u=2/(o-n),c=2/(s-a),l=0;r>l;l++)t=e[2*l]=e[2*l]*u,i=e[2*l+1]=e[2*l+1]*c,n=Math.min(n,t),o=Math.max(o,t),a=Math.min(a,i),s=Math.max(s,i);if(-1>a)for(var l=0;r>l;l++)i=e[2*l+1]=e[2*l+1]+(-1-a);return e},e.math.generateUnitNgonPoints=function(e,t){var i=1/e*2*Math.PI,r=e%2===0?Math.PI/2+i/2:Math.PI/2;r+=t;for(var n,a,o,s=new Array(2*e),l=0;e>l;l++)n=l*i+r,a=s[2*l]=Math.cos(n),o=s[2*l+1]=Math.sin(-n);return s},e.math.getRoundRectangleRadius=function(e,t){return Math.min(e/4,t/4,8)}}(cytoscape),function(e){"use strict";function t(t,i,r){var n={};switch(n[i]=r,t){case"core":case"collection":e.fn[t](n)}if("layout"===t){for(var o=r.prototype,s=["stop"],l=0;ld;d++)n.canSet(s[d])&&(s[d]._private[n.field][i]=r);n.updateStyle&&a.updateStyle(),n.onSet(a),n.settingTriggersEvent&&a[n.triggerFnName](n.settingEvent)}}}else if(n.allowSetting&&e.is.plainObject(i)){var h,v,g=i;for(h in g){v=g[h];var c=!n.immutableKeys[h];if(c)for(var d=0,p=s.length;p>d;d++)n.canSet(s[d])&&(s[d]._private[n.field][h]=v)}n.updateStyle&&a.updateStyle(),n.onSet(a),n.settingTriggersEvent&&a[n.triggerFnName](n.settingEvent)}else if(n.allowBinding&&e.is.fn(i)){var f=i;a.bind(n.bindingEvent,f)}else if(n.allowGetting&&void 0===i){var u;return l&&(u=l._private[n.field]),u}return a}},removeData:function(t){var i={field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!1,immutableKeys:{}};return t=e.util.extend({},i,t),function(i){var r=t,n=this,a=void 0!==n.length,o=a?n:[n];if(e.is.string(i)){for(var s=i.split(/\s+/),l=s.length,u=0;l>u;u++){var c=s[u];if(!e.is.emptyString(c)){var d=!r.immutableKeys[c];if(d)for(var p=0,h=o.length;h>p;p++)o[p]._private[r.field][c]=void 0}}r.triggerEvent&&n[r.triggerFnName](r.event)}else if(void 0===i){for(var p=0,h=o.length;h>p;p++){var v=o[p]._private[r.field];for(var c in v){var g=!r.immutableKeys[c];g&&(v[c]=void 0)}}r.triggerEvent&&n[r.triggerFnName](r.event)}return n}},event:{regex:/(\w+)(\.\w+)?/,optionalTypeRegex:/(\w+)?(\.\w+)?/,falseCallback:function(){return!1}},on:function(t){var i={unbindSelfOnTrigger:!1,unbindAllBindersOnTrigger:!1};return t=e.util.extend({},i,t),function(i,r,n,a){var o=this,s=void 0!==o.length,l=s?o:[o],u=e.is.string(i),c=t;if(e.is.plainObject(r)?(a=n,n=r,r=void 0):(e.is.fn(r)||r===!1)&&(a=r,n=void 0,r=void 0),(e.is.fn(n)||n===!1)&&(a=n,n=void 0),!e.is.fn(a)&&a!==!1&&u)return o;if(u){var d={};d[i]=a,i=d}for(var p in i)if(a=i[p],a===!1&&(a=e.define.event.falseCallback),e.is.fn(a)){p=p.split(/\s+/);for(var h=0;h0:void 0}},clearQueue:function(t){var i={};return t=e.util.extend({},i,t),function(){var e=this,t=void 0!==e.length,i=t?e:[e],r=this._private.cy||this;if(!r.styleEnabled())return this;for(var n=0;n\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]",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})},u=s.comparatorOp.split("|"),c=0;c=0||"="!==d&&(s.comparatorOp+="|\\!"+d)}var p={group:{query:!0,regex:"(node|edge|\\*)",populate:function(e){this.group="*"==e?e:e+"s"}},state:{query:!0,regex:"(:selected|:unselected|:locked|:unlocked|:visible|:hidden|:transparent|:grabbed|:free|:removed|:inside|:grabbable|:ungrabbable|:animated|:unanimated|:selectable|:unselectable|:orphan|:nonorphan|:parent|:child|:loop|:simple|:active|:inactive|:touch)",populate:function(e){this.colonSelectors.push(e)}},id:{query:!0,regex:"\\#("+s.id+")",populate:function(e){this.ids.push(l(e))}},className:{query:!0,regex:"\\.("+s.className+")",populate:function(e){this.classes.push(l(e))}},dataExists:{query:!0,regex:"\\[\\s*("+s.variable+")\\s*\\]",populate:function(e){this.data.push({field:l(e)})}},dataCompare:{query:!0,regex:"\\[\\s*("+s.variable+")\\s*("+s.comparatorOp+")\\s*("+s.value+")\\s*\\]",populate:function(e,t,i){var r=null!=new RegExp("^"+s.string+"$").exec(i);i=r?i.substring(1,i.length-1):parseFloat(i),this.data.push({field:l(e),operator:t,value:i})}},dataBool:{query:!0,regex:"\\[\\s*("+s.boolOp+")\\s*("+s.variable+")\\s*\\]",populate:function(e,t){this.data.push({field:l(t),operator:e})}},metaCompare:{query:!0,regex:"\\[\\[\\s*("+s.meta+")\\s*("+s.comparatorOp+")\\s*("+s.number+")\\s*\\]\\]",populate:function(e,t,i){this.meta.push({field:l(e),operator:t,value:parseFloat(i)})}},nextQuery:{separator:!0,regex:s.separator,populate:function(){r[++c]=o(),a=null}},child:{separator:!0,regex:s.child,populate:function(){var e=o();e.parent=this,e.subject=a,r[c]=e}},descendant:{separator:!0,regex:s.descendant,populate:function(){var e=o();e.ancestor=this,e.subject=a,r[c]=e}},subject:{modifier:!0,regex:s.subject,populate:function(){return null!=a&&this.subject!=this?(e.util.error("Redefinition of subject in selector `"+i+"`"),!1):(a=this,void(this.subject=this))}}},h=0;for(var v in p)p[h]=p[v],p[h].name=v,h++;p.length=h,r._private.selectorText=i;var g=i,c=0,f=function(t){for(var i,r,n,a=0;a=0&&(d=d.toLowerCase(),p=p.toLowerCase(),s=s.replace("@",""),h=!0);var v=!1,g=!1;switch(s.indexOf("!")>=0&&(s=s.replace("!",""),v=!0),h&&(l=p.toLowerCase(),c=d.toLowerCase()),s){case"*=":a=d.search(p)>=0;break;case"$=":a=null!=new RegExp(p+"$").exec(d);break;case"^=":a=null!=new RegExp("^"+p).exec(d);break;case"=":a=c===l;break;case"!=":a=c!==l;break;case">":a=v?l>=c:c>l,g=!0;break;case">=":a=v?l>c:c>=l,g=!0;break;case"<":a=v?c>=l:l>c,g=!0;break;case"<=":a=v?c>l:l>=c,g=!0;break;default:a=!1}}else if(null!=s)switch(s){case"?":a=t.fieldTruthy(u);break;case"!":a=!t.fieldTruthy(u);break;case"^":a=t.fieldUndefined(u)}else a=!t.fieldUndefined(u);if(v&&!g&&(a=!a,g=!0),!a){r=!1;break}}return r},v=h({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(!v)return!1;var g=h({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!=i.collection){var f=null!=i.collection._private.ids[r.id()];if(!f)return!1}if(null!=i.filter&&0===r.collection().filter(i.filter).size())return!1;var y=function(e,i){if(null!=e){var r=!1;if(!n.hasCompoundNodes())return!1;i=i();for(var a=0;a "+t),null!=e.ancestor&&(t=r(e.ancestor)+" "+t),null!=e.child&&(t+=" > "+r(e.child)),null!=e.descendant&&(t+=" "+r(e.descendant)),t},n=0;n1&&n 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,"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(){for(var e=0;el.max)return null;var P={name:t,value:i,strValue:""+i+(x?x:""),units:x,bypass:r,hasPie:t.match(/pie-(\d+)-background-size/)&&null!=i&&0!==i&&""!==i};return l.unitless||"px"!==x&&"em"!==x||(P.pxValue="px"!==x&&x?this.getEmSizeInPixels()*i:i),("ms"===x||"s"===x)&&(P.msValue="ms"===x?i:1e3*i),P}if(l.propList){var k=[],C=""+i;if("none"===C);else{for(var D=C.split(","),E=0;E0;if(p||h){var v;p&&h?v=u.properties:p?v=u.properties:h&&(v=u.mappedProperties);for(var g=0;g0){a=!0;break}}i.hasPie=a;var l=n["text-transform"].strValue,u=n.content.strValue,c=n["font-style"].strValue,s=n["font-size"].pxValue+"px",d=n["font-family"].strValue,p=n["font-weight"].strValue,h=n["text-valign"].strValue,v=n["text-valign"].strValue,g=n["text-outline-width"].pxValue;i.labelKey=c+"$"+s+"$"+d+"$"+p+"$"+u+"$"+l+"$"+h+"$"+v+"$"+g,i.fontKey=c+"$"+p+"$"+s+"$"+d;var f=n.width.pxValue,y=n.height.pxValue,m=n["border-width"].pxValue;if(i.boundingBoxKey=f+"$"+y+"$"+m,"edges"===t._private.group){var x=n["control-point-step-size"].pxValue,b=n["control-point-distance"]?n["control-point-distance"].pxValue:void 0,w=n["control-point-weight"].value,_=n["curve-style"].strValue;i.boundingBoxKey+="$"+x+"$"+b+"$"+w+"$"+_}i.styleKey=Date.now()},e.styfn.applyParsedProperty=function(t,i){var r,n,a=i,o=t._private.style,s=e.style.properties[a.name].type,l=a.bypass,u=o[a.name],c=u&&u.bypass;if(("height"===i.name||"width"===i.name)&&"auto"===i.value&&t.isNode()&&!t.isParent())return!1;if(l&&a.deleteBypass){var d=o[a.name];return d?d.bypass&&d.bypassed?(o[a.name]=d.bypassed,!0):!1:!0}switch(a.mapped){case e.style.types.mapData:case e.style.types.mapLayoutData:for(var p=a.mapped===e.style.types.mapLayoutData,h=a.field.split("."),r=p?t._private.layoutData:t._private.data,v=0;vf?f=0:f>1&&(f=1),s.color){var y=a.valueMin[0],m=a.valueMax[0],x=a.valueMin[1],b=a.valueMax[1],w=a.valueMin[2],_=a.valueMax[2],E=null==a.valueMin[3]?1:a.valueMin[3],S=null==a.valueMax[3]?1:a.valueMax[3],P=[Math.round(y+(m-y)*f),Math.round(x+(b-x)*f),Math.round(w+(_-w)*f),Math.round(E+(S-E)*f)];n={bypass:a.bypass,name:a.name,value:P,strValue:"rgb("+P[0]+", "+P[1]+", "+P[2]+")"}}else{if(!s.number)return!1;var k=a.valueMin+(a.valueMax-a.valueMin)*f;n=this.parse(a.name,k,a.bypass,!0)}n||(n=this.parse(a.name,u.strValue,a.bypass,!0)),n.mapping=a,a=n;break;case e.style.types.data:case e.style.types.layoutData:for(var p=a.mapped===e.style.types.layoutData,h=a.field.split("."),r=p?t._private.layoutData:t._private.data,v=0;v0&&s>0){for(var c=!1,d=0;d0&&t.delay(l),t.animate({css:u},{duration:s,queue:!1,complete:function(){r||n.removeBypasses(t,o),t._private.transitioning=!1}})}else t._private.transitioning&&(t.stop(),this.removeBypasses(t,o),t._private.transitioning=!1)}}(cytoscape),function(e){"use strict";e.styfn.applyBypass=function(t,i,r,n){var a=[],o=!0;if("*"===i||"**"===i){if(void 0!==r)for(var s=0;sa.length?l.substr(a.length):""}function n(){o=o.length>s.length?o.substr(s.length):""}var a,o,s,l=""+i;for(l=l.replace(/[/][*](\s|.)+?[*][/]/g,"");;){var u=l.match(/^\s*$/);if(u)break;var c=l.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/);if(!c){e.util.error("Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: "+l);break}a=c[0];var d=c[1],p=new e.Selector(d);if(p._private.invalid&&"core"!==d)e.util.error("Skipping parsing of block: Invalid selector found in string stylesheet: "+d),r();else{var h=c[2],v=!1;o=h;for(var g=[];;){var u=o.match(/^\s*$/);if(u)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),v=!0;break}s=f[0];var y=f[1],m=f[2],x=e.style.properties[y];if(x){var b=t.parse(y,m);b?(g.push({name:y,val:m}),n()):(e.util.error("Skipping property: Invalid property definition in: "+s),n())}else e.util.error("Skipping property: Invalid property name in: "+s),n()}if(v){r();break}t.selector(d);for(var w=0;w0?c.wheelSensitivity:1,motionBlur:c.motionBlur,pixelRatio:e.is.number(c.pixelRatio)&&c.pixelRatio>0?c.pixelRatio:"auto"===c.pixelRatio?void 0:1},c.renderer)),c.initrender&&(a.on("initrender",c.initrender),a.on("initrender",function(){a._private.initrender=!0})),a.load(c.elements,function(){a.startAnimationLoop(),a._private.ready=!0,e.is.fn(c.ready)&&a.on("ready",c.ready);for(var t=0;t0;)r.shift()();i.deferredTimeout=null},0))}})}(cytoscape,"undefined"==typeof window?null:window),function(e,t){"use strict";function i(e){var t=!document||"interactive"!==document.readyState&&"complete"!==document.readyState?i:e;setTimeout(t,9,e)}e.fn.core({add:function(t){var i,r=this;if(e.is.elementOrCollection(t)){var n=t;if(n._private.cy===r)i=n.restore();else{for(var a=[],o=0;oo;o++){var d=u[o],p=l[d];if(e.is.array(p))for(var h=0,v=p.length;v>h;h++){var g=p[h];g.group=d,a.push(g)}}i=new e.Collection(r,a)}else{var g=t;i=new e.Element(r,g).collection()}return i},remove:function(t){if(e.is.elementOrCollection(t))t=t;else if(e.is.string(t)){var i=t;t=this.$(i)}return t.remove()},load:function(r,n,a){function o(){s.one("layoutready",function(e){s.notifications(!0),s.trigger(e),s.notify({type:"load",collection:s.elements()}),s.one("load",n),s.trigger("load")}).one("layoutstop",function(){s.one("done",a),s.trigger("done")});var t=e.util.extend({},s._private.options.layout);t.eles=s.$(),s.layout(t)}var s=this,l=s.elements();return l.length>0&&l.remove(),s.notifications(!1),null!=r&&(e.is.plainObject(r)||e.is.array(r))&&s.add(r),t?i(o):o(),this}})}(cytoscape,"undefined"==typeof window?null:window),function(e,t){"use strict";e.fn.core({animated:e.define.animated(),clearQueue:e.define.clearQueue(),delay:e.define.delay(),animate:e.define.animate(),stop:e.define.stop(),addToAnimationPool:function(e){var t=this;t.styleEnabled()&&t._private.aniEles.merge(e)},startAnimationLoop:function(){function i(){e.util.requestAnimationFrame(function(e){r(e),i()})}function r(t){function i(i,r){var s=i._private.animation.current,l=i._private.animation.queue,u=!1;if(0===s.length){var c=l.length>0?l.shift():null;c&&(c.callTime=t,s.push(c))}for(var d=[],p=s.length-1;p>=0;p--){var h=s[p];h.started||n(i,h),a(i,h,t,r),h.done&&(d.push(h),s.splice(p,1)),u=!0}for(var p=0;p0||c){var d;if(r.length>0){var p=r.updateCompoundBounds();d=p.length>0?r.add(p):r}l.notify({type:"draw",collection:d})}r.unmerge(o)}function n(t,i){var r=e.is.core(t),n=!r,a=t,o=l._private.style;if(n)var s=a._private.position,u={x:s.x,y:s.y},c=o.getValueStyle(a);if(r)var d=l._private.pan,p={x:d.x,y:d.y},h=l._private.zoom;i.started=!0,i.startTime=Date.now(),i.startPosition=u,i.startStyle=c,i.startPan=p,i.startZoom=h}function a(t,i,r,n){var a,u=l._private.style,c=i.properties,d=i.params,p=i.startTime,h=!n;if(a=0===i.duration?1:Math.min(1,(r-p)/i.duration),0>a?a=0:a>1&&(a=1),null==c.delay){var v=i.startPosition,g=c.position,f=t._private.position;g&&h&&(o(v.x,g.x)&&(f.x=s(v.x,g.x,a)),o(v.y,g.y)&&(f.y=s(v.y,g.y,a)));var y=i.startPan,m=c.pan,x=t._private.pan,b=null!=m&&n;b&&(o(y.x,m.x)&&(x.x=s(y.x,m.x,a)),o(y.y,m.y)&&(x.y=s(y.y,m.y,a)),t.trigger("pan"));var w=i.startZoom,_=c.zoom,E=null!=_&&n;if(E&&(o(w,_)&&(t._private.zoom=s(w,_,a)),t.trigger("zoom")),(b||E)&&t.trigger("viewport"),c.css&&h)for(var S=c.css,P=0;P=1&&(i.done=!0),a}function o(t,i){return null==t||null==i?!1:e.is.number(t)&&e.is.number(i)?!0:t&&i?!0:!1}function s(t,i,r){0>r?r=0:r>1&&(r=1);var n,a;if(n=null!=t.pxValue||null!=t.value?null!=t.pxValue?t.pxValue:t.value:t,a=null!=i.pxValue||null!=i.value?null!=i.pxValue?i.pxValue:i.value:i,e.is.number(n)&&e.is.number(a))return n+(a-n)*r;if(e.is.number(n[0])&&e.is.number(a[0])){var o=n,s=a,l=function(e,t){var i=t-e,n=e;return Math.round(r*i+n)},u=l(o[0],s[0]),c=l(o[1],s[1]),d=l(o[2],s[2]);return[u,c,d]}return void 0}var l=this;l.styleEnabled()&&t&&i()}})}(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}),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._private.prevLayout=null==e?this._private.prevLayout:this.initLayout(e);return t.run(),this},makeLayout:function(e){return this.initLayout(e)},initLayout:function(t){if(null==t)return void e.util.error("Layout options must be specified to make a layout");if(null==t.name)return void e.util.error("A `name` must be specified to make a layout");var i=t.name,r=e.extension("layout",i);if(null==r)return void e.util.error("Can not apply layout: No such layout `"+i+"` found; did you include its JS file?");t.eles=null!=t.eles?t.eles:this.$(),e.is.string(t.eles)&&(t.eles=this.$(t.eles));var n=new r(e.util.extend({},t,{cy:this}));return e.is.plainObject(n._private)||(n._private={}),n._private.cy=this,n._private.listeners=[],n}})}(cytoscape),function(e){"use strict";e.fn.core({notify:function(e){if(this._private.batchingNotify){var t=this._private.batchNotifyEles,i=this._private.batchNotifyTypes;if(e.collection)for(var r=0;r0&&l>0&&!isNaN(r.w)&&!isNaN(r.h)&&r.w>0&&r.h>0){o=Math.min((s-2*i)/r.w,(l-2*i)/r.h),o=o>this._private.maxZoom?this._private.maxZoom:o,o=othis._private.maxZoom?this._private.maxZoom:r,r=ri.maxZoom||!i.zoomingEnabled?o=!0:(i.zoom=l,a.push("zoom"))}if(n&&(!o||!t.cancelOnFailedZoom)&&i.panningEnabled){var u=t.pan;e.is.number(u.x)&&(i.pan.x=u.x,s=!1),e.is.number(u.y)&&(i.pan.y=u.y,s=!1),s||a.push("pan")}return a.length>0&&(a.push("viewport"),this.trigger(a.join(" ")),this.notify({type:"viewport"})),this},center:function(e){var t=this.getCenterPan(e);return t&&(this._private.pan=t,this.trigger("pan viewport"),this.notify({type:"viewport"})),this},getCenterPan:function(t){if(this._private.panningEnabled){if(e.is.string(t)){var i=t;t=this.elements(i)}else e.is.elementOrCollection(t)||(t=this.elements());var r=t.boundingBox(),n=this.width(),a=this.height(),o=this._private.zoom,s={x:(n-o*(r.x1+r.x2))/2,y:(a-o*(r.y1+r.y2))/2};return s}},reset:function(){return this._private.panningEnabled&&this._private.zoomingEnabled?(this.viewport({pan:{x:0,y:0},zoom:1}),this):this},width:function(){var e=this._private.container;return e?e.clientWidth:1},height:function(){var e=this._private.container;return e?e.clientHeight:1},extent:function(){var e=this._private.pan,t=this._private.zoom,i=this.renderedExtent(),r={x1:(i.x1-e.x)/t,x2:(i.x2-e.x)/t,y1:(i.y1-e.y)/t,y2:(i.y2-e.y)/t};return r.w=r.x2-r.x1,r.h=r.y2-r.y1,r},renderedExtent:function(){var e=this.width(),t=this.height();return{x1:0,y1:0,x2:e,y2:t,w:e,h:t}}}),e.corefn.centre=e.corefn.center,e.corefn.autolockNodes=e.corefn.autolock,e.corefn.autoungrabifyNodes=e.corefn.autoungrabifyNodes}(cytoscape),function(e){"use strict";e.fn.collection=e.fn.eles=function(t){for(var i in t){var r=t[i];e.Collection.prototype[i]=r}};var t={prefix:{nodes:"n",edges:"e"},id:{nodes:0,edges:0},generate:function(t,i,r){var n=e.is.element(i)?i._private:i,a=n.group,o=null!=r?r: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,i,r){if(!(this instanceof e.Element))return new e.Element(t,i,r);var n=this;if(r=void 0===r||r?!0:!1,void 0===t||void 0===i||!e.is.core(t))return void e.util.error("An element must have a core reference and parameters set");if("nodes"!==i.group&&"edges"!==i.group)return void e.util.error("An element must be of type `nodes` or `edges`; you specified `"+i.group+"`");if(this.length=1,this[0]=this,this._private={cy:t,single:!0,data:i.data||{},layoutData:{},position:i.position||{},autoWidth:void 0,autoHeight:void 0,listeners:[],group:i.group,style:{},rstyle:{},styleCxts:[],removed:!0,selected:i.selected?!0:!1,selectable:void 0===i.selectable?!0:i.selectable?!0:!1,locked:i.locked?!0:!1,grabbed:!1,grabbable:void 0===i.grabbable?!0:i.grabbable?!0:!1,active:!1,classes:{},animation:{current:[],queue:[]},rscratch:{},scratch:{},edges:[],children:[]},i.renderedPosition){var a=i.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(i.classes))for(var l=i.classes.split(/\s+/),u=0,c=l.length;c>u;u++){var d=l[u];d&&""!==d&&(n._private.classes[d]=!0)}i.css&&t.style().applyBypass(this,i.css),(void 0===r||r)&&this.restore()},e.Collection=function(i,r,n){if(!(this instanceof e.Collection))return new e.Collection(i,r);if(void 0===i||!e.is.core(i))return void e.util.error("A collection must have a reference to the core");var a={},o={},s=!1;if(r){if(r.length>0&&e.is.plainObject(r[0])&&!e.is.element(r[0])){s=!0;for(var l=[],u={},c=0,d=r.length;d>c;c++){var p=r[c];null==p.data&&(p.data={});var h=p.data;if(null==h.id)h.id=t.generate(i,p);else if(0!==i.getElementById(h.id).length||u[h.id])continue;var v=new e.Element(i,p,!1);l.push(v),u[h.id]=!0}r=l}}else r=[];this.length=0;for(var c=0,d=r.length;d>c;c++){var g=r[c];if(g){var f=g._private.data.id;(!n||n.unique&&!a[f])&&(a[f]=g,o[f]=this.length,this[this.length]=g,this.length++)}}this._private={cy:i,ids:a,indexes:o},s&&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.unique=function(){return new e.Collection(this._private.cy,this,{unique:!0})},e.elesfn.getElementById=function(t){var i=this._private.cy,r=this._private.ids[t];return r?r:e.Collection(i)},e.elesfn.json=function(){var t=this.element();if(null==t)return void 0;var i=t._private,r=e.util.copy({data:i.data,position:i.position,group:i.group,bypass:i.bypass,removed:i.removed,selected:i.selected,selectable:i.selectable,locked:i.locked,grabbed:i.grabbed,grabbable:i.grabbable,classes:""}),n=[];for(var a in i.classes)i.classes[a]&&n.push(a);for(var o=0;od;d++){var h=r[d];h.isNode()?(s.push(h),u++):(l.push(h),c++)}o=s.concat(l);for(var d=0,p=o.length;p>d;d++){var h=o[d];if(h.removed()){var v=h._private,g=v.data;if(void 0===g.id)g.id=t.generate(a,h);else{if(e.is.emptyString(g.id)||!e.is.string(g.id)){e.util.error("Can not create element with invalid string ID `"+g.id+"`");continue}if(0!==a.getElementById(g.id).length){e.util.error("Can not create second element with ID `"+g.id+"`");continue}}var f=g.id;if(h.isEdge()){for(var y=h,m=["source","target"],x=m.length,b=!1,w=0;x>w;w++){var _=m[w],E=g[_];null==E||""===E?(e.util.error("Can not create edge `"+f+"` with unspecified "+_),b=!0):a.getElementById(E).empty()&&(e.util.error("Can not create edge `"+f+"` with nonexistant "+_+" `"+E+"`"),b=!0)}if(b)continue;var S=a.getElementById(g.source),P=a.getElementById(g.target);S._private.edges.push(y),P._private.edges.push(y),y._private.source=S,y._private.target=P}v.ids={},v.ids[f]=h,v.removed=!1,a.addToPool(h),n.push(h)}}for(var d=0;u>d;d++){var k=o[d],g=k._private.data,C=k._private.data.parent,D=null!=C;if(D){var N=a.getElementById(C);if(N.empty())g.parent=void 0;else{for(var T=!1,M=N;!M.empty();){if(k.same(M)){T=!0,g.parent=void 0;break}M=M.parent()}T||(N[0]._private.children.push(k),k._private.parent=N[0],a._private.hasCompoundNodes=!0)}}}if(n=new e.Collection(a,n),n.length>0){var B=n.add(n.connectedNodes()).add(n.parent());B.updateStyle(i),i?n.rtrigger("add"):n.trigger("add")}return r},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 i(e){for(var t=e._private.edges,i=0;ip;p++){var v=s[p];n(v)}for(var p=0;p0&&(t&&this.cy().notify({type:"remove",collection:x}),x.trigger("remove"));for(var b={},p=0;p0,a=t.getElementById(r).length>0;if(n||a){var o=this.jsons();this.remove();for(var s=0;s0;if(c){var o=this.jsons();this.remove();for(var s=0;su||!n)&&(o=u,n=l)}return{edge:n,dist:o}};g.size()>0;){var y=g.pop(),m=y.value,x=y.id,b=a.getElementById(x);if(u[x]=m,m===Math.Infinite)break;for(var w=b.neighborhood().intersect(d),h=0;h0)for(r.unshift(i);l[n.id()];){var o=l[n.id()];r.unshift(o.edge),r.unshift(o.node),n=o.node}return new e.Collection(a,r)}}}}),e.elesfn.bfs=e.elesfn.breadthFirstSearch,e.elesfn.dfs=e.elesfn.depthFirstSearch,e.elesfn.stdBfs=e.elesfn.stdBreadthFirstSearch,e.elesfn.stdDfs=e.elesfn.stdDepthFirstSearch}(cytoscape),function(e){"use strict";e.fn.eles({aStar:function(t){t=t||{};var i=function(){a&&console.log.apply(console,arguments)},r=function(e,t,i,n){if(e==t)return n.push(o.getElementById(t)),n;if(t in i){var a=i[t],s=g[t];return n.push(o.getElementById(t)),n.push(o.getElementById(s)),r(e,a,i,n)}return void 0},n=function(e,t){if(0===e.length)return void 0;for(var i=0,r=t[e[0]],n=1;na&&(r=a,i=n)}return i};if(null!=t.debug)var a=t.debug;else var a=!1;i("Starting aStar...");var o=this._private.cy;if(null==t||null==t.root)return void 0;var s=e.is.string(t.root)?this.filter(t.root)[0]:t.root[0];if(i("Source node: %s",s.id()),null==t.goal)return void 0;var l=e.is.string(t.goal)?this.filter(t.goal)[0]:t.goal[0];if(i("Target node: %s",l.id()),null!=t.heuristic&&e.is.fn(t.heuristic))var u=t.heuristic;else var u=function(){return 0};if(null!=t.weight&&e.is.fn(t.weight))var c=t.weight;else var c=function(){return 1};if(null!=t.directed)var d=t.directed;else var d=!1;var p=[],h=[s.id()],v={},g={},f={},y={};f[s.id()]=0,y[s.id()]=u(s);for(var m=this.edges().not(":loop"),x=this.nodes(),b=0;h.length>0;){var w=n(h,y),_=this.filter("#"+h[w])[0];if(b++,i("\nStep: %s",b),i("Processing node: %s, fScore = %s",_.id(),y[_.id()]),_.id()==l.id()){i("Found goal node!");var E=r(s.id(),l.id(),v,[]);return E.reverse(),i("Path: %s",E),{found:!0,distance:f[_.id()],path:new e.Collection(o,E),steps:b}}p.push(_.id()),h.splice(w,1),i("Added node to closedSet, removed from openSet."),i("Processing neighbors...");for(var S=_.connectedEdges(d?'[source = "'+_.id()+'"]':void 0).intersect(m),P=0;Pd;d++)c[l[d].id()]=d;for(var p=[],d=0;u>d;d++){for(var h=new Array(u),v=0;u>v;v++)h[v]=d==v?0:1/0;p.push(h)}var g=[],f=[],y=function(e){for(var t=0;u>t;t++){for(var i=new Array(u),r=0;u>r;r++)i[r]=void 0;e.push(i)}};y(g),y(f);for(var d=0;db&&(p[m][x]=b,g[m][x]=x,f[m][x]=s[d])}if(!o)for(var d=0;db&&(p[m][x]=b,g[m][x]=x,f[m][x]=s[d])}for(var w=0;u>w;w++)for(var d=0;u>d;d++)for(var v=0;u>v;v++)p[d][w]+p[w][v]d;d++)_.push(l[d].id());var E={distance:function(t,i){if(e.is.string(t))var r=n.filter(t)[0].id();else var r=t.id();if(e.is.string(i))var a=n.filter(i)[0].id();else var a=i.id();return p[c[r]][c[a]]},path:function(t,i){var r=function(e,t,i,r,a){if(e===t)return n.getElementById(r[e]);if(void 0===i[e][t])return void 0;for(var o=[n.getElementById(r[e])],s=e;e!==t;){s=e,e=i[e][t];var l=a[s][e];o.push(l),o.push(n.getElementById(r[e]))}return o};if(e.is.string(t))var a=n.filter(t)[0].id();else var a=t.id();if(e.is.string(i))var o=n.filter(i)[0].id();else var o=i.id();var s=r(c[a],c[o],g,_,f);return new e.Collection(n,s)}};return E},bellmanFord:function(t){t=t||{};var i=function(){r&&console.log.apply(console,arguments)};if(null!=t.debug)var r=t.debug;else var r=!1;if(i("Starting bellmanFord..."),null!=t.weight&&e.is.fn(t.weight))var n=t.weight;else var n=function(){return 1};if(null!=t.directed)var a=t.directed;else var a=!1;if(null==t.root)return void e.util.error("options.root required");if(e.is.string(t.root))var o=this.filter(t.root)[0];else var o=t.root[0];i("Source node: %s",o.id());for(var s=this._private.cy,l=this.edges().not(":loop"),u=this.nodes(),c=u.length,d={},p=0;c>p;p++)d[u[p].id()]=p;for(var h=[],v=[],g=[],p=0;c>p;p++)h[p]=u[p].id()===o.id()?0:1/0,v[p]=void 0;for(var f=!1,p=1;c>p;p++){f=!1;for(var y=0;yp;p++)_.push(u[p].id());var E={distanceTo:function(t){if(e.is.string(t))var i=s.filter(t)[0].id();else var i=t.id();return h[d[i]]},pathTo:function(t){var i=function(e,t,i,r,n,a){for(;;){if(n.push(s.getElementById(r[i])),n.push(a[i]),t===i)return n;var o=e[i];if("undefined"==typeof o)return void 0;i=o}};if(e.is.string(t))var r=s.filter(t)[0].id();else var r=t.id();var n=[],a=i(v,d[o.id()],d[r],_,n,g);return null!=a&&a.reverse(),new e.Collection(s,a)},hasNegativeWeightCycle:!1};return E},kargerStein:function(t){t=t||{};var i=function(){a&&console.log.apply(console,arguments)},r=function(e,t,i){for(var r=i[e],n=r[1],a=r[2],o=t[n],s=t[a],l=i.filter(function(e){return t[e[1]]===o&&t[e[2]]===s?!1:t[e[1]]===s&&t[e[2]]===o?!1:!0}),u=0;u=i)return t;var o=Math.floor(Math.random()*t.length),s=r(o,e,t);return n(e,s,i-1,a)};if(null!=t&&null!=t.debug)var a=t.debug;else var a=!1;i("Starting kargerStein...");var o=this._private.cy,s=this.edges().not(":loop"),l=this.nodes(),u=l.length,c=s.length,d=Math.ceil(Math.pow(Math.log(u)/Math.LN2,2)),p=Math.floor(u/Math.sqrt(2));if(2>u)return void e.util.error("At least 2 nodes are required for KargerSteing algorithm!");for(var h={},v=0;u>v;v++)h[l[v].id()]=v;for(var g=[],v=0;c>v;v++){var f=s[v];g.push([v,h[f.source().id()],h[f.target().id()]])}for(var y,m=1/0,x=[],v=0;u>v;v++)x.push(v);for(var b=0;d>=b;b++){var w=x.slice(0),_=n(w,g,u,p),E=w.slice(0),S=n(w,_,p,2),P=n(E,_,p,2);S.length<=P.length&&S.lengthr;r++)i+=e[r];for(var r=0;t>r;r++)e[r]=e[r]/i},r=function(){n&&console.log.apply(console,arguments)};if(null!=t&&null!=t.debug)var n=t.debug;else var n=!1;if(r("Starting pageRank..."),null!=t&&null!=t.dampingfactor)var a=t.dampingFactor;else var a=.8;if(null!=t&&null!=t.precision)var o=t.precision;else var o=1e-6;if(null!=t&&null!=t.iterations)var s=t.iterations;else var s=200;if(null!=t&&null!=t.weight&&e.is.fn(t.weight))var l=t.weight;else var l=function(){return 1};for(var u=this._private.cy,c=this.edges().not(":loop"),d=this.nodes(),p=d.length,h=c.length,v={},g=0;p>g;g++)v[d[g].id()]=g;for(var f=[],y=[],m=(1-a)/p,g=0;p>g;g++){for(var x=[],b=0;p>b;b++)x.push(0);f.push(x),y.push(0)}for(var g=0;h>g;g++){var w=c[g],_=v[w.source().id()],E=v[w.target().id()],S=l.apply(w,[w]);f[E][_]+=S,y[_]+=S}for(var P=1/p+m,b=0;p>b;b++)if(0===y[b])for(var g=0;p>g;g++)f[g][b]=P;else for(var g=0;p>g;g++)f[g][b]=f[g][b]/y[b]+m;for(var k,C=[],D=[],g=0;p>g;g++)C.push(1),D.push(0);for(var N=0;s>N;N++){for(var T=D.slice(0),g=0;p>g;g++)for(var b=0;p>b;b++)T[g]+=f[g][b]*C[b];i(T),k=C,C=T;for(var M=0,g=0;p>g;g++)M+=Math.pow(k[g]-C[g],2);if(o>M){r("Stoped at iteration %s",N);break}}r("Result:\n"+C);var B={rank:function(t){if(e.is.string(t))var i=u.filter(t)[0].id();else var i=t.id();return C[v[i]]}};return B}})}(cytoscape),function(e){"use strict";e.fn.eles({animated:e.define.animated(),clearQueue:e.define.clearQueue(),delay:e.define.delay(),animate:e.define.animate(),stop:e.define.stop()})}(cytoscape),function(e){"use strict";e.fn.eles({classes:function(t){var i,r=this,n=[];if(e.is.fn(t))i=t;else if(!e.is.plainObject(t))return this;for(var a=0;a0&&new e.Collection(this.cy(),n).updateStyle().trigger("class"),this},addClass:function(t){t=t.split(/\s+/);for(var i=this,r=[],n=0;n0&&new e.Collection(this._private.cy,r).updateStyle().trigger("class"),i},hasClass:function(e){var t=this[0];return null!=t&&t._private.classes[e]?!0:!1},toggleClass:function(t,i){for(var r=t.split(/\s+/),n=this,a=[],o=0,s=n.length;s>o;o++)for(var l=n[o],u=0;u0&&new e.Collection(this._private.cy,a).updateStyle().trigger("class"),n},removeClass:function(t){t=t.split(/\s+/);for(var i=this,r=[],n=0;n0&&new e.Collection(i._private.cy,r).updateStyle(),i.trigger("class"),i},flashClass:function(e,t){var i=this;if(null==t)t=250;else if(0===t)return i;return i.addClass(e),setTimeout(function(){i.removeClass(e)},t),i}})}(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},some:function(e,t){for(var i=0;i0},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 i=[],r=this._private.cy,n=0;n0&&i.push(o)}return new e.Collection(r,i,{unique:!0}).filter(t)},parents:function(t){for(var i=[],r=this.parent();r.nonempty();){for(var n=0;n0?this.add(s):this;i?l.trigger("position"):l.rtrigger("position")}return this},silentPositions:function(e){return this.positions(e,!0)},updateCompoundBounds:function(){function t(e){var t=e.children(),i=e._private.style,n=t.boundingBox({includeLabels:!1,includeEdges:!1}),a={top:i["padding-top"].pxValue,bottom:i["padding-bottom"].pxValue,left:i["padding-left"].pxValue,right:i["padding-right"].pxValue},o=e._private.position,s=!1;"auto"===i.width.value&&(e._private.autoWidth=n.w+a.left+a.right,o.x=(n.x1+n.x2-a.left+a.right)/2,s=!0),"auto"===i.height.value&&(e._private.autoHeight=n.h+a.top+a.bottom,o.y=(n.y1+n.y2-a.top+a.bottom)/2,s=!0),s&&r.push(e)}var i=this.cy();if(!i.styleEnabled()||!i.hasCompoundNodes())return i.collection();for(var r=[],n=this.parent();n.nonempty();){for(var a=0;a0,d=c;c&&(u=u[0]);var p=d?u._private.position:{x:0,y:0};return a={x:l.x-p.x,y:l.y-p.y},void 0===t?a:a[t]}for(var h=0;h0,d=c;c&&(u=u[0]);var p=d?u._private.position:{x:0,y:0};void 0!==i?r._private.position[t]=i+p[t]:void 0!==a&&(r._private.position={x:a.x+p.x,y:a.y+p.y})}this.rtrigger("position")}else if(!o)return void 0;return this},width:function(){var e=this[0],t=e._private.cy,i=t._private.styleEnabled;if(e){if(i){var r=e._private.style.width;return"auto"===r.strValue?e._private.autoWidth:r.pxValue}return 1}},outerWidth:function(){var e=this[0],r=e._private.cy,n=r._private.styleEnabled;if(e){if(n){var a=e._private.style,o="auto"===a.width.strValue?e._private.autoWidth:a.width.pxValue,s=a["border-width"]?a["border-width"].pxValue*t+i:0;return o+s}return 1}},renderedWidth:function(){var e=this[0];if(e){var t=e.width();return t*this.cy().zoom()}},renderedOuterWidth:function(){var e=this[0];if(e){var t=e.outerWidth();return t*this.cy().zoom()}},height:function(){var e=this[0],t=e._private.cy,i=t._private.styleEnabled;if(e&&"nodes"===e._private.group){if(i){var r=e._private.style.height;return"auto"===r.strValue?e._private.autoHeight:r.pxValue}return 1}},outerHeight:function(){var e=this[0],r=e._private.cy,n=r._private.styleEnabled;if(e&&"nodes"===e._private.group){if(!n)return 1;var a=e._private.style,o="auto"===a.height.strValue?e._private.autoHeight:a.height.pxValue,s=a["border-width"]?a["border-width"].pxValue*t+i:0;return o+s}},renderedHeight:function(){var e=this[0];if(e&&"nodes"===e._private.group){var t=e.height();return t*this.cy().zoom()}},renderedOuterHeight:function(){var e=this[0];if(e&&"nodes"===e._private.group){var t=e.outerHeight();return t*this.cy().zoom()}},renderedBoundingBox:function(e){var t=this.boundingBox(e),i=this.cy(),r=i.zoom(),n=i.pan(),a=t.x1*r+n.x,o=t.x2*r+n.x,s=t.y1*r+n.y,l=t.y2*r+n.y;return{x1:a,x2:o,y1:s,y2:l,w:o-a,h:l-s}},boundingBox:function(e){var t=this,i=t._private.cy,r=i._private,n=r.styleEnabled;e=e||{};var a=void 0===e.includeNodes?!0:e.includeNodes,o=void 0===e.includeEdges?!0:e.includeEdges,s=void 0===e.includeLabels?!0:e.includeLabels;n&&r.renderer.recalculateRenderedStyle(this);for(var l=1/0,u=-1/0,c=1/0,d=-1/0,p=0;ph?h:l,u=v>u?v:u,c=c>g?g:c,d=f>d?f:d}else if(x.isEdge()&&o){E=!0;var N=x._private.source._private.position,T=x._private.target._private.position,M=x._private.rstyle||{};if(h=N.x,v=T.x,g=N.y,f=T.y,h>v){var B=h;h=v,v=B}if(g>f){var B=g;g=f,f=B}if(l=l>h?h:l,u=v>u?v:u,c=c>g?g:c,d=f>d?f:d,n)for(var z=M.bezierPts||[],P=x._private.style.width.pxValue,I=P/2,L=0;Lh?h:l,u=v>u?v:u,c=c>g?g:c,d=f>d?f:d}}if(n){var R=x._private.style,M=x._private.rstyle,X=R.content.strValue,V=R["font-size"],Y=R["text-halign"],A=R["text-valign"],q=M.labelWidth,F=M.labelHeight,j=M.labelX,W=M.labelY;if(E&&s&&X&&V&&null!=F&&null!=q&&null!=j&&null!=W&&Y&&A){var H,$,Z,U,G=F,K=q;if(x.isEdge())H=j-K/2,$=j+K/2,Z=W-G/2,U=W+G/2;else{switch(Y.value){case"left":H=j-K,$=j;break;case"center":H=j-K/2,$=j+K/2;break;case"right":H=j,$=j+K}switch(A.value){case"top":Z=W-G,U=W;break;case"center":Z=W-G/2,U=W+G/2;break;case"bottom":Z=W,U=W+G}}l=l>H?H:l,u=$>u?$:u,c=c>Z?Z:c,d=U>d?U:d}}}}return{x1:l,x2:u,y1:c,y2:d,w:u-l,h:d-c}}}),e.elesfn.modelPosition=e.elesfn.position,e.elesfn.modelPositions=e.elesfn.positions}(cytoscape),function(e){"use strict";function t(e){return function(t){var i=this;if(void 0===t&&(t=!0),0!==i.length&&i.isNode()&&!i.removed()){for(var r=0,n=i[0],a=n._private.edges,o=0;oe}),maxDegree:i("degree",function(e,t){return e>t}),minIndegree:i("indegree",function(e,t){return t>e}),maxIndegree:i("indegree",function(e,t){return e>t}),minOutdegree:i("outdegree",function(e,t){return t>e}),maxOutdegree:i("outdegree",function(e,t){return e>t})}),e.fn.eles({totalDegree:function(e){for(var t=0,i=this.nodes(),r=0;r1&&!n){var a=this.length-1,o=this[a];this[a]=void 0,this[r]=o,t.indexes[o.id()]=r}return this.length--,this},unmerge:function(t){var i=this._private.cy;if(!t)return this;if(e.is.string(t)){var r=t;t=i.elements(r)}for(var n=0;nr&&(r=s,i=o)}return{value:r,ele:i}},min:function(e,t){for(var i,r=1/0,n=this,a=0;as&&(r=s,i=o)}return{value:r,ele:i}}})}(cytoscape),function(e){"use strict";e.fn.eles({isNode:function(){return"nodes"===this.group()},isEdge:function(){return"edges"===this.group()},isLoop:function(){return this.isEdge()&&this.source().id()===this.target().id()},isSimple:function(){return this.isEdge()&&this.source().id()!==this.target().id()},group:function(){var e=this[0];return e?e._private.group:void 0}})}(cytoscape),function(e){"use strict";e.fn.eles({each:function(t){if(e.is.fn(t))for(var i=0;it&&(t=n+t),0>i&&(i=n+i);for(var a=t;a>=0&&i>a&&n>a;a++)r.push(this[a]);return new e.Collection(this.cy(),r)},size:function(){return this.length},eq:function(t){return this[t]||new e.Collection(this.cy())},first:function(){return this[0]||new e.Collection(this.cy())},last:function(){return this[this.length-1]||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 i=this.cy(),r=this.toArray().sort(t);return new e.Collection(i,r)},sortByZIndex:function(){return this.sort(e.Collection.zIndexSort)},zDepth:function(){var e=this[0];if(!e)return void 0;var t=e._private,i=t.group;if("nodes"===i)return t.data.parent?e.parents().size():0;var r=t.source,n=t.target,a=r._private.data.parent?r.parents().size():0,o=n._private.data.parent?n.parents().size():0;return Math.max(a-1,o-1,0)+.5}}),e.Collection.zIndexSort=function(e,t){var i=e.cy(),r=e._private,n=t._private,a=r.style["z-index"].value-n.style["z-index"].value,o=0,s=0,l=i.hasCompoundNodes(),u="nodes"===r.group,c="edges"===r.group,d="nodes"===n.group,p="edges"===n.group;l&&(o=e.zDepth(),s=t.zDepth());var h=o-s,v=0===h;return v?u&&p?1:c&&d?-1:0===a?r.index-n.index:a:h}}(cytoscape),function(e){"use strict";e.fn.eles({layoutPositions:function(t,i,r){var n=this.nodes(),a=this.cy();if(t.trigger({type:"layoutstart",layout:t}),i.animate){for(var o=0;o0?this.add(o):this;return e?s.rtrigger("style"):s.trigger("style"),this},updateMappers:function(e){var t=this._private.cy,i=t.style();if(e=e||void 0===e?!0:!1,!t.styleEnabled())return this;i.updateMappers(this);var r=this.updateCompoundBounds(),n=r.length>0?this.add(r):this;return e?n.rtrigger("style"):n.trigger("style"),this},renderedCss:function(e){var t=this.cy();if(!t.styleEnabled())return this;var i=this[0]; +if(i){var r=i.cy().style().getRenderedStyle(i);return void 0===e?r:r[e]}},css:function(t,i){var r=this.cy();if(!r.styleEnabled())return this;var n=!1,a=r.style();if(e.is.plainObject(t)){var o=t;a.applyBypass(this,o,n);var s=this.updateCompoundBounds(),l=s.length>0?this.add(s):this;l.rtrigger("style")}else if(e.is.string(t)){if(void 0===i){var u=this[0];return u?u._private.style[t].strValue:void 0}a.applyBypass(this,t,i,n);var s=this.updateCompoundBounds(),l=s.length>0?this.add(s):this;l.rtrigger("style")}else if(void 0===t){var u=this[0];return u?a.getRawStyle(u):void 0}return this},removeCss:function(e){var t=this.cy();if(!t.styleEnabled())return this;var i=!1,r=t.style(),n=this;if(void 0===e)for(var a=0;a0?this.add(s):this;return l.rtrigger("style"),this},show:function(){return this.css("display","element"),this},hide:function(){return this.css("display","none"),this},visible:function(){var e=this.cy();if(!e.styleEnabled())return!0;var t=this[0],i=e.hasCompoundNodes();if(t){var r=t._private.style;if("visible"!==r.visibility.value||"element"!==r.display.value)return!1;if("nodes"===t._private.group){if(!i)return!0;var n=t._private.data.parent?t.parents():null;if(n)for(var a=0;a0;o||r.push(a)}}return new e.Collection(this._private.cy,r,{unique:!0}).filter(t)},leaves:function(t){for(var i=this,r=[],n=0;n0;o||r.push(a)}}return new e.Collection(this._private.cy,r,{unique:!0}).filter(t)},outgoers:function(t){for(var i=this,r=[],n=0;n0&&i.push(c[0]),i.push(u[0])}return new e.Collection(r,i,{unique:!0}).filter(t)},closedNeighborhood:function(e){return this.neighborhood().add(this).filter(e)},openNeighborhood:function(e){return this.neighborhood(e)}}),e.fn.eles({source:function(e){var t,i=this[0];return i&&(t=i._private.source),t&&e?t.filter(e):t},target:function(e){var t,i=this[0];return i&&(t=i._private.target),t&&e?t.filter(e):t},sources:t({attr:"source"}),targets:t({attr:"target"})}),e.fn.eles({edgesWith:i(),edgesTo:i({thisIs:"source"})}),e.fn.eles({connectedEdges:function(t){for(var i=[],r=this._private.cy,n=this,a=0;ad;d+=1){if(l.push(n.call(t,i[d],d,i)),a=i[d].id(),u.hasOwnProperty(a))throw"ERROR: Multiple items with the same id found: "+a;u[a]=d,c.push(a)}for(this._private={cy:t,heap:l,pointers:u,elements:c,comparator:r,extractor:n,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 i in t){var r=t[i];e.Heap.prototype[i]=r}},e.heapfn=e.Heap.prototype,e.heapfn.size=function(){return this._private.length},e.heapfn.getArgumentAsCollection=function(t,i){var r;if("undefined"==typeof i&&(i=this._private.cy),e.is.elementOrCollection(t))r=t;else{for(var n=[],a=[].concat.apply([],[t]),o=0;o0&&n.push(l)}r=new e.Collection(i,n)}return r},e.heapfn.isHeap=function(){var e,t,i,r,n,a=this._private.heap,o=a.length,s=this._private.comparator;for(e=0;o>e;e+=1)if(t=2*e+1,i=t+1,r=o>t?s(a[t],a[e]):!0,n=o>i?s(a[i],a[e]):!0,!r||!n)return!1;return!0},e.heapfn.heapSwap=function(e,t){var i=this._private.heap,r=this._private.pointers,n=this._private.elements,a=i[e],o=n[e],s=n[e],l=n[t];i[e]=i[t],n[e]=n[t],r[s]=t,r[l]=e,i[t]=a,n[t]=o},e.heapfn.heapify=function(e,t){var i,r,n,a,o,s,l,u=0,c=!1;for("undefined"==typeof t&&(t=!0),i=this._private.heap,u=i.length,s=this._private.comparator,r=e;!c;)t?(n=2*r+1,a=n+1,o=r,u>n&&!s(i[n],i[o])&&(o=n),u>a&&!s(i[a],i[o])&&(o=a),c=o===r,c||(this.heapSwap(o,r),r=o)):(l=Math.floor((r-1)/2),o=r,c=0>l||s(i[o],i[l]),c||(this.heapSwap(o,l),r=l))},e.heapfn.insert=function(e){var t,i,r,n,a,o=this.getArgumentAsCollection(e),s=o.length;for(a=0;s>a;a+=1){if(t=o[a],i=this._private.heap.length,r=this._private.extractor(t),n=t.id(),this._private.pointers.hasOwnProperty(n))throw"ERROR: Multiple items with the same id found: "+n;this._private.heap.push(r),this._private.elements.push(n),this._private.pointers[n]=i,this.heapify(i,!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),i=0;i0?{value:this._private.heap[0],id:this._private.elements[0]}:void 0},e.heapfn.pop=function(){if(this._private.length>0){var e,t,i,r=this.top(),n=this._private.length-1;return this.heapSwap(0,n),e=this._private.elements[n],t=this._private.heap[n],i=e,this._private.heap.pop(),this._private.elements.pop(),this._private.length=this._private.heap.length,this._private.pointers[i]=void 0,this.heapify(0),r}},e.heapfn.findDirectionHeapify=function(e){var t=Math.floor((e-1)/2),i=this._private.heap,r=0>t||this._private.comparator(i[e],i[t]);this.heapify(e,r)},e.heapfn.edit=function(t,i){for(var r=this.getArgumentAsCollection(t),n=0;n=o&&s>=e&&t>=l&&u>=t},a=function(e,t,i,r,n){r=-r;var a=e*Math.cos(r)-t*Math.sin(r),o=e*Math.sin(r)+t*Math.cos(r),s=a*i,l=o*i,u=s+n.x,c=l+n.y;return{x:u,y:c}};r.arrow={_points:[-.15,-.3,0,0,.15,-.3],collide:function(t,i,n,a,o,s,l,u){var c=r.arrow._points;return e.math.pointInsidePolygon(t,i,c,n,a,o,s,l,u)},roughCollide:n,draw:function(e,t,i,n){for(var o=r.arrow._points,s=0;se.math.sqDistanceToQuadraticBezier(i,r,c.startX,c.startY,c.cp2ax,c.cp2ay,c.selfEdgeMidX,c.selfEdgeMidY)||(x=e.math.inBezierVicinity(i,r,c.selfEdgeMidX,c.selfEdgeMidY,c.cp2cx,c.cp2cy,c.endX,c.endY,g))&&b()&&g+h>e.math.sqDistanceToQuadraticBezier(i,r,c.selfEdgeMidX,c.selfEdgeMidY,c.cp2cx,c.cp2cy,c.endX,c.endY))&&u.push(o);else if("haystack"===c.edgeType){var w=d["haystack-radius"].value,_=w/2,E=m._private.position,S=m.width(),P=m.height(),k=y._private.position,C=y.width(),D=y.height(),N=k.x+c.source.x*C*_,T=k.y+c.source.y*D*_,M=E.x+c.target.x*S*_,B=E.y+c.target.y*P*_;(x=e.math.inLineVicinity(i,r,N,T,M,B,f))&&b()&&g+h>e.math.sqDistanceToFiniteLine(i,r,N,T,M,B)&&u.push(o)}else"straight"===c.edgeType?(x=e.math.inLineVicinity(i,r,c.startX,c.startY,c.endX,c.endY,f))&&b()&&g+h>e.math.sqDistanceToFiniteLine(i,r,c.startX,c.startY,c.endX,c.endY)&&u.push(o):"bezier"===c.edgeType&&(x=e.math.inBezierVicinity(i,r,c.startX,c.startY,c.cp2x,c.cp2y,c.endX,c.endY,g))&&b()&&g+h>e.math.sqDistanceToQuadraticBezier(i,r,c.startX,c.startY,c.cp2x,c.cp2y,c.endX,c.endY)&&u.push(o);if(x&&b()&&0===u.length||u[u.length-1]!==o){var z=t.arrowShapes[d["source-arrow-shape"].value],I=t.arrowShapes[d["target-arrow-shape"].value],y=y||o._private.source,m=m||o._private.target,E=m._private.position,k=y._private.position,L=s.getArrowWidth(d.width.pxValue),O=s.getArrowHeight(d.width.pxValue),R=L,X=O;(z.roughCollide(i,r,c.arrowStartX,c.arrowStartY,L,O,[c.arrowStartX-k.x,c.arrowStartY-k.y],0)&&z.collide(i,r,c.arrowStartX,c.arrowStartY,L,O,[c.arrowStartX-k.x,c.arrowStartY-k.y],0)||I.roughCollide(i,r,c.arrowEndX,c.arrowEndY,R,X,[c.arrowEndX-E.x,c.arrowEndY-E.y],0)&&I.collide(i,r,c.arrowEndX,c.arrowEndY,R,X,[c.arrowEndX-E.x,c.arrowEndY-E.y],0))&&u.push(o)}p&&u.length>0&&u[u.length-1]===o&&(a(y),a(m))}for(var s=this,l=this.getCachedZSortedEles(),u=[],c=t.isTouch,d=this.data.cy.zoom(),p=this.data.cy.hasCompoundNodes(),h=(c?256:32)/d,v=(c?16:0)/d,g=l.length-1;g>=0;g--){var f=l[g];if(u.length>0)break;"nodes"===f._private.group?a(l[g]):o(l[g])}return u.length>0?u[u.length-1]:null},t.prototype.getAllInBox=function(i,r,n,a){var o=this.getCachedNodes(),s=this.getCachedEdges(),l=[],u=Math.min(i,n),c=Math.max(i,n),d=Math.min(r,a),p=Math.max(r,a);i=u,n=c,r=d,a=p;for(var h,v=0;v=i&&n>=k&&C>=r&&a>=C,M=D>=i&&n>=D&&N>=r&&a>=N;T&&M&&l.push(s[v])}}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,i=e._private.style["padding-right"].pxValue,r=e._private.style["padding-top"].pxValue,n=e._private.style["padding-bottom"].pxValue;return isNaN(t)&&(t=0),isNaN(i)&&(i=0),isNaN(r)&&(r=0),isNaN(n)&&(n=0),{left:t,right:i,top:r,bottom:n}},t.prototype.zOrderSort=e.Collection.zIndexSort,t.prototype.updateCachedZSortedEles=function(){this.getCachedZSortedEles(!0)},t.prototype.getCachedZSortedEles=function(e){var t=this.lastZOrderCachedNodes,i=this.lastZOrderCachedEdges,r=this.getCachedNodes(),n=this.getCachedEdges(),a=[];if(!e&&t&&i&&t===r&&i===n)a=this.cachedZSortedEles;else{for(var o=0;op?p+"-"+d:d+"-"+p,c&&(r="unbundled"+l._private.data.id),null==n[r]&&(n[r]=[],a.push(r)),n[r].push(l),c&&(n[r].hasUnbundled=!0)}else o.push(l)}for(var h,v,g,f,y,m,x,b,w,_,E,S,P,k,C=0;Cv._private.data.id){var N=h; +h=v,v=N}if(g=h._private.position,f=v._private.position,y=this.getNodeWidth(h),m=this.getNodeHeight(h),x=this.getNodeWidth(v),b=this.getNodeHeight(v),w=t.nodeShapes[this.getNodeShape(h)],_=t.nodeShapes[this.getNodeShape(v)],E=h._private.style["border-width"].pxValue,S=v._private.style["border-width"].pxValue,k=!1,D.length>1&&h!==v||D.hasUnbundled){var T=w.intersectLine(g.x,g.y,y,m,f.x,f.y,E/2),M=_.intersectLine(f.x,f.y,x,b,g.x,g.y,S/2),B={x1:T[0],x2:M[0],y1:T[1],y2:M[1]},z=M[1]-T[1],I=M[0]-T[0],L=Math.sqrt(I*I+z*z),O={x:I,y:z},R={x:O.x/L,y:O.y/L};P={x:-R.y,y:R.x},(_.checkPoint(T[0],T[1],S/2,x,b,f.x,f.y)||w.checkPoint(M[0],M[1],E/2,y,m,g.x,g.y))&&(P={},k=!0)}for(var l,X,s=0;skt,Dt=e.math.distance({x:X.cp2x,y:X.cp2y},{x:X.endX,y:X.endY}),Nt=Pt>Dt;if("bezier"===X.edgeType){var Tt=!1;if(xt||bt||Ct){Tt=!0;var Mt={x:X.cp2x-g.x,y:X.cp2y-g.y},Bt=Math.sqrt(Mt.x*Mt.x+Mt.y*Mt.y),zt={x:Mt.x/Bt,y:Mt.y/Bt},It=Math.max(y,m),Lt={x:X.cp2x+2*zt.x*It,y:X.cp2y+2*zt.y*It},Ot=w.intersectLine(g.x,g.y,y,m,Lt.x,Lt.y,E/2);Ct?(X.cp2x=X.cp2x+zt.x*(Pt-kt),X.cp2y=X.cp2y+zt.y*(Pt-kt)):(X.cp2x=Ot[0]+zt.x*Pt,X.cp2y=Ot[1]+zt.y*Pt)}if(wt||_t||Nt){Tt=!0;var Mt={x:X.cp2x-f.x,y:X.cp2y-f.y},Bt=Math.sqrt(Mt.x*Mt.x+Mt.y*Mt.y),zt={x:Mt.x/Bt,y:Mt.y/Bt},It=Math.max(y,m),Lt={x:X.cp2x+2*zt.x*It,y:X.cp2y+2*zt.y*It},Rt=_.intersectLine(f.x,f.y,x,b,Lt.x,Lt.y,S/2);Nt?(X.cp2x=X.cp2x+zt.x*(Pt-Dt),X.cp2y=X.cp2y+zt.y*(Pt-Dt)):(X.cp2x=Rt[0]+zt.x*Pt,X.cp2y=Rt[1]+zt.y*Pt)}Tt&&this.findEndpoints(l)}else"straight"===X.edgeType&&(X.midX=(Z+it)/2,X.midY=(G+nt)/2);this.projectBezier(l)}}}for(var s=0;sC*N+D*T)n.straightEdgeTooShort=!0;else{var P=n;this.drawStyledEdge(i,e,[P.startX,P.startY,P.endX,P.endY],_,w),n.straightEdgeTooShort=!1}}else{var P=n;this.drawStyledEdge(i,e,[P.startX,P.startY,P.cp2x,P.cp2y,P.endX,P.endY],_,w)}"haystack"===n.edgeType?this.drawArrowheads(e,i,r):n.noArrowPlacement!==!0&&void 0!==n.startX&&this.drawArrowheads(e,i,r)}}},t.prototype.drawStyledEdge=function(e,i,r,n){var a,o=e._private.rscratch,s=i,l=!1,u=t.usePaths();if(u){for(var c=r,d=o.pathCacheKey&&c.length===o.pathCacheKey.length,p=d,h=0;p&&hu?y+=Math.PI/2:y=-(Math.PI/2+y);var m=this.getArrowWidth(a),x=t.arrowShapes[o];if(p){var b=m+"$"+o+"$"+y+"$"+s+"$"+l;h.arrowPathCacheKey=h.arrowPathCacheKey||{},h.arrowPathCache=h.arrowPathCache||{};var w=h.arrowPathCacheKey[i]===b;w?(d=r=h.arrowPathCache[i],v=!0):(d=r=new Path2D,h.arrowPathCacheKey[i]=b,h.arrowPathCache[i]=d)}r.beginPath&&r.beginPath(),v||x.draw(r,m,y,f),!x.leavePathOpen&&r.closePath&&r.closePath(),r=g,("filled"===n||"both"===n)&&(p?r.fill(d):r.fill()),("hollow"===n||"both"===n)&&(r.lineWidth=x.matchEdgeWidth?a:1,r.lineJoin="miter",p?r.stroke(d):r.stroke())}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.getCachedImage=function(e,t){var i=this,r=i.imageCache=i.imageCache||{};if(r[e]&&r[e].image)return r[e].image;var n=r[e]=r[e]||{},a=n.image=new Image;return a.addEventListener("load",t),a.src=e,a},t.prototype.drawInscribedImage=function(e,i,r){var n=this,a=r._private.position.x,o=r._private.position.y,s=r._private.style,l=s["background-fit"].value,u=s["background-position-x"],c=s["background-position-y"],d=s["background-repeat"].value,p=r.width(),h=r.height(),v=r._private.rscratch,g=s["background-clip"].value,f="node"===g,y=s["background-image-opacity"].value,m=i.width,x=i.height;if("contain"===l){var b=Math.min(p/m,h/x);m*=b,x*=b}else if("cover"===l){var b=Math.max(p/m,h/x);m*=b,x*=b}var w=a-p/2;w+="%"===u.units?(p-m)*u.value/100:u.pxValue;var _=o-h/2;_+="%"===c.units?(h-x)*c.value/100:c.pxValue,v.pathCache&&(w-=a,_-=o,a=0,o=0);var E=e.globalAlpha;if(e.globalAlpha=y,"no-repeat"===d)f&&(e.save(),v.pathCache?e.clip(v.pathCache):(t.nodeShapes[n.getNodeShape(r)].drawPath(e,a,o,p,h),e.clip())),e.drawImage(i,0,0,i.width,i.height,w,_,m,x),f&&e.restore();else{var S=e.createPattern(i,d);e.fillStyle=S,t.nodeShapes[n.getNodeShape(r)].drawPath(e,a,o,p,h),e.translate(w,_),e.fill(),e.translate(-w,-_)}e.globalAlpha=E}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.drawEdgeText=function(e,t){var i=t._private.style.content.strValue;if(!(!i||i.match(/^\s+$/)||this.hideEdgesOnViewport&&(this.dragData.didDrag||this.pinching||this.hoverData.dragging||this.data.wheel||this.swipePanning))){var r=t._private.style["font-size"].pxValue*t.cy().zoom(),n=t._private.style["min-zoomed-font-size"].pxValue;if(!(n>r)){e.textAlign="center",e.textBaseline="middle";var a=t._private.rscratch;this.drawText(e,t,a.labelX,a.labelY)}}},t.prototype.drawNodeText=function(e,t){var i=t._private.style.content.strValue;if(i&&!i.match(/^\s+$/)){var r=t._private.style["font-size"].pxValue*t.cy().zoom(),n=t._private.style["min-zoomed-font-size"].pxValue;if(!(n>r)){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 i=0;i0&&(e.lineWidth=s,e.strokeText(o,i,r)),e.fillText(o,i,r)}}}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.drawNode=function(e,i,r){var n,a,o,s=this,l=i._private.style,u=i._private.rscratch,c=t.usePaths(),d=e,p=!1,h=l["overlay-padding"].pxValue,v=l["overlay-opacity"].value,g=l["overlay-color"].value;if(!r||0!==v){var f=i.effectiveOpacity();if(0!==f)if(n=this.getNodeWidth(i),a=this.getNodeHeight(i),e.lineWidth=l["border-width"].pxValue,void 0!==r&&r)v>0&&(this.fillStyle(e,g[0],g[1],g[2],v),t.nodeShapes.roundrectangle.drawPath(e,i._private.position.x,i._private.position.y,n+2*h,a+2*h),e.fill());else{var y=l["background-color"].value,m=l["border-color"].value,x=l["border-style"].value;if(this.fillStyle(e,y[0],y[1],y[2],l["background-opacity"].value*l.opacity.value*f),this.strokeStyle(e,m[0],m[1],m[2],l["border-opacity"].value*l.opacity.value*f),e.lineJoin="miter",e.setLineDash)switch(x){case"dotted":e.setLineDash([1,1]);break;case"dashed":e.setLineDash([4,2]);break;case"solid":case"double":e.setLineDash([])}var b=l["background-image"].value[2]||l["background-image"].value[1],w=l.shape.strValue,_=i._private.position;if(c){var E=w+"$"+n+"$"+a;e.translate(_.x,_.y),u.pathCacheKey===E?(o=e=u.pathCache,p=!0):(o=e=new Path2D,u.pathCacheKey=E,u.pathCache=o)}if(!p){var S=_;c&&(S={x:0,y:0}),t.nodeShapes[this.getNodeShape(i)].drawPath(e,S.x,S.y,n,a)}if(e=d,c?e.fill(o):e.fill(),void 0!==b){var P=this.getCachedImage(b,function(){s.data.canvasNeedsRedraw[t.NODE]=!0,s.data.canvasNeedsRedraw[t.DRAG]=!0,s.redraw()});P.complete&&this.drawInscribedImage(e,P,i)}var k=l["background-blacken"].value,C=l["border-width"].pxValue;if(this.hasPie(i)&&(this.drawPie(e,i),(0!==k||0!==C)&&(c||t.nodeShapes[this.getNodeShape(i)].drawPath(e,_.x,_.y,n,a))),k>0?(this.fillStyle(e,0,0,0,k),c?e.fill(o):e.fill()):0>k&&(this.fillStyle(e,255,255,255,-k),c?e.fill(o):e.fill()),C>0&&(c?e.stroke(o):e.stroke(),"double"===x)){e.lineWidth=l["border-width"].pxValue/3;var D=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",c?e.stroke(o):e.stroke(),e.globalCompositeOperation=D}c&&e.translate(-_.x,-_.y),e.setLineDash&&e.setLineDash([])}}},t.prototype.hasPie=function(e){return e=e[0],e._private.hasPie},t.prototype.drawPie=function(i,r){r=r[0];var n=r._private.style["pie-size"],a=this.getNodeWidth(r),o=this.getNodeHeight(r),s=r._private.position.x,l=r._private.position.y,u=Math.min(a,o)/2,c=0,d=t.usePaths();d&&(s=0,l=0),"%"===n.units?u=u*n.value/100:void 0!==n.pxValue&&(u=n.pxValue/2);for(var p=1;p<=e.style.pieBackgroundN;p++){var h=r._private.style["pie-"+p+"-background-size"].value,v=r._private.style["pie-"+p+"-background-color"].value,g=r._private.style["pie-"+p+"-background-opacity"].value,f=h/100,y=1.5*Math.PI+2*Math.PI*c,m=2*Math.PI*f,x=y+m;0===h||c>=1||c+f>1||(i.beginPath(),i.moveTo(s,l),i.arc(s,l,u,y,x),i.closePath(),this.fillStyle(i,v[0],v[1],v[2],g),i.fill(),c+=f)}}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.getPixelRatio=function(){var e=this.data.contexts[0];if(null!=this.forcedPixelRatio)return this.forcedPixelRatio;var t=e.backingStorePixelRatio||e.webkitBackingStorePixelRatio||e.mozBackingStorePixelRatio||e.msBackingStorePixelRatio||e.oBackingStorePixelRatio||e.backingStorePixelRatio||1;return(window.devicePixelRatio||1)/t},t.prototype.paintCache=function(e){for(var t,i=this.paintCaches=this.paintCaches||[],r=!0,n=0;n=o&&(i=r.bufferCanvases[t.TEXTURE_BUFFER],this.textureMult=2,i.width=s*this.textureMult,i.height=l*this.textureMult),this.canvasWidth=s,this.canvasHeight=l}},t.prototype.renderTo=function(e,t,i,r){this.redraw({forcedContext:e,forcedZoom:t,forcedPan:i,drawAllLayers:!0,forcedPxRatio:r})},t.prototype.timeToRender=function(){return this.redrawTotalTime/this.redrawCount},t.minRedrawLimit=1e3/60,t.maxRedrawLimit=1e3,t.motionBlurDelay=100,t.prototype.redraw=function(i){function r(){function i(e,t){if(e.setTransform(1,0,0,1,0,0),"motionBlur"===t){var i=e.globalCompositeOperation;e.globalCompositeOperation="destination-out",u.fillStyle(e,255,255,255,.666),e.fillRect(0,0,u.canvasWidth,u.canvasHeight),e.globalCompositeOperation=i}else n||void 0!==t&&!t||e.clearRect(0,0,u.canvasWidth,u.canvasHeight);a||(e.translate(b.x,b.y),e.scale(m,m)),l&&e.translate(l.x,l.y),s&&e.scale(s,s)}function r(e,t){for(var i=e.eles,r=0;r0&&(C.strokeStyle="rgba("+f["selection-box-border-color"].value[0]+","+f["selection-box-border-color"].value[1]+","+f["selection-box-border-color"].value[2]+","+f["selection-box-opacity"].value+")",C.strokeRect(p.select[0],p.select[1],p.select[2]-p.select[0],p.select[3]-p.select[1]))}if(p.bgActivePosistion){var y=p.cy.zoom(),q=p.bgActivePosistion;C.fillStyle="rgba("+f["active-bg-color"].value[0]+","+f["active-bg-color"].value[1]+","+f["active-bg-color"].value[2]+","+f["active-bg-opacity"].value+")",C.beginPath(),C.arc(q.x,q.y,f["active-bg-size"].pxValue/y,0,2*Math.PI),C.fill()}var F=u.averageRedrawTime;if(u.showFps&&F){F=Math.round(F);var j=Math.round(1e3/F);C.setTransform(1,0,0,1,0,0),C.fillStyle="rgba(255, 0, 0, 0.75)",C.strokeStyle="rgba(255, 0, 0, 0.75)",C.lineWidth=1,C.fillText("1 frame = "+F+" ms = "+j+" fps",0,20);var W=60;C.strokeRect(0,30,250,20),C.fillRect(0,30,250*Math.min(j/W,1),20)}a||(h[t.SELECT_BOX]=!1)}var H=+new Date;void 0===u.averageRedrawTime&&(u.averageRedrawTime=H-w),void 0===u.redrawCount&&(u.redrawCount=0),u.redrawCount++,void 0===u.redrawTotalTime&&(u.redrawTotalTime=0),u.redrawTotalTime+=H-w,u.lastRedrawTime=H-w,u.averageRedrawTime=u.averageRedrawTime/2+(H-w)/2,u.currentlyDrawing=!1,u.clearingMotionBlur&&(u.clearingMotionBlur=!1,u.motionBlurCleared=!0,u.motionBlur=!0),v&&(u.motionBlurTimeout=setTimeout(function(){u.motionBlurTimeout=null,u.clearedNodeLayerForMotionBlur=!1,u.motionBlur=!1,u.clearingMotionBlur=!0,h[t.NODE]=!0,h[t.DRAG]=!0,u.redraw()},t.motionBlurDelay))}i=i||{};var n=i.forcedContext,a=i.drawAllLayers,o=i.drawOnlyNodeLayer,s=i.forcedZoom,l=i.forcedPan,u=this,c=void 0===i.forcedPxRatio?this.getPixelRatio():i.forcedPxRatio,d=u.data.cy,p=u.data,h=p.canvasNeedsRedraw,v=void 0!==i.motionBlur?i.motionBlur:u.motionBlur;v=v&&!n&&u.motionBlurEnabled,u.motionBlurTimeout&&clearTimeout(u.motionBlurTimeout),this.redrawTimeout&&clearTimeout(this.redrawTimeout),this.redrawTimeout=null,void 0===this.averageRedrawTime&&(this.averageRedrawTime=0);var g=t.minRedrawLimit,f=t.maxRedrawLimit,y=this.averageRedrawTime;y=g>y?g:y,y=f>y?y:f,void 0===this.lastDrawTime&&(this.lastDrawTime=0);var m=+new Date,x=m-this.lastDrawTime,b=x>=y;if(!n){if(!b||this.currentlyDrawing)return void(this.redrawTimeout=setTimeout(function(){u.redraw()},y));this.lastDrawTime=m,this.currentlyDrawing=!0}var w=+new Date;n?r():e.util.requestAnimationFrame(r),n||u.initrender||(u.initrender=!0,d.trigger("initrender"))}}(cytoscape),function(e){"use strict";var t=e("renderer","canvas");t.prototype.drawPolygonPath=function(e,t,i,r,n,a){var o=r/2,s=n/2;e.beginPath&&e.beginPath(),e.moveTo(t+o*a[0],i+s*a[1]);for(var l=1;l0&&a>0)if(l.clearRect(0,0,n,a),e.bg&&(l.fillStyle=e.bg,l.rect(0,0,n,a),l.fill()),l.globalCompositeOperation="source-over",e.full)this.redraw({forcedContext:l,drawAllLayers:!0,forcedZoom:o,forcedPan:{x:-r.x1*o,y:-r.y1*o},forcedPxRatio:1});else{var u=i.pan(),c={x:u.x*o,y:u.y*o},d=i.zoom()*o;this.redraw({forcedContext:l,drawAllLayers:!0,forcedZoom:d,forcedPan:c,forcedPxRatio:1})}return 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,i,r){this.bindings.push({target:e,event:t,handler:i,useCapture:r}),e.addEventListener(t,i,r)},t.prototype.nodeIsDraggable=function(e){return 0!==e._private.style.opacity.value&&"visible"==e._private.style.visibility.value&&"element"==e._private.style.display.value&&!e.locked()&&e.grabbable()?!0:!1},t.prototype.load=function(){var i=this,r=function(e){var t;if(e.addToList&&i.data.cy.hasCompoundNodes()){if(!e.addToList.hasId){e.addToList.hasId={};for(var r=0;rs[0]&&r.clientXs[1]&&r.clientY=t.panOrBoxSelectDelay)&&Math.abs(v[3]-v[1])+Math.abs(v[2]-v[0])<4&&p.panningEnabled()&&p.userPanningEnabled())i.hoverData.dragging=!0,v[4]=0;else{if(p.boxSelectionEnabled()&&Math.pow(v[2]-v[0],2)+Math.pow(v[3]-v[1],2)>7&&v[4]&&(clearTimeout(i.bgActiveTimeout),i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,i.redraw()),y&&y.isEdge()&&y.active()&&y.unactivate(),g!=f&&(f&&(f.trigger(new e.Event(r,{type:"mouseout",cyPosition:{x:h[0],y:h[1]}})),f.trigger(new e.Event(r,{type:"tapdragout",cyPosition:{x:h[0],y:h[1]}}))),g&&(g.trigger(new e.Event(r,{type:"mouseover",cyPosition:{x:h[0],y:h[1]}})),g.trigger(new e.Event(r,{type:"tapdragover",cyPosition:{x:h[0],y:h[1]}}))),i.hoverData.last=g),y&&y.isNode()&&i.nodeIsDraggable(y)){i.dragData.didDrag||(i.data.canvasNeedsRedraw[t.NODE]=!0),i.dragData.didDrag=!0;for(var _=[],E=0;E7&&l[4]||i.hoverData.dragging||(a.$(function(){return this.selected()}).unselect(),c.length>0&&(i.data.canvasNeedsRedraw[t.NODE]=!0),i.dragData.possibleDragElements=c=[]),null!=u?u.trigger(new e.Event(r,{type:"mouseup",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"tapend",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"vmouseup",cyPosition:{x:s[0],y:s[1]}})):null==u&&a.trigger(new e.Event(r,{type:"mouseup",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"tapend",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{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!=u?u.trigger(new e.Event(r,{type:"click",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"tap",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"vclick",cyPosition:{x:s[0],y:s[1]}})):null==u&&a.trigger(new e.Event(r,{type:"click",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"tap",cyPosition:{x:s[0],y:s[1]}})).trigger(new e.Event(r,{type:"vclick",cyPosition:{x:s[0],y:s[1]}}))),u!=d||i.dragData.didDrag||null!=u&&u._private.selectable&&("additive"===a.selectionType()||p?u.selected()?u.unselect():u.select():p||(a.$(":selected").not(u).unselect(),u.select()),i.data.canvasNeedsRedraw[t.NODE]=!0),a.boxSelectionEnabled()&&Math.pow(l[2]-l[0],2)+Math.pow(l[3]-l[1],2)>7&&l[4]){var g=[],f=i.getAllInBox(l[0],l[1],l[2],l[3]);i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,f.length>0&&(i.data.canvasNeedsRedraw[t.NODE]=!0);for(var y=0;y=0&&b>=d&&h>=0&&b>=h&&p>=0&&w>=p&&v>=0&&w>=v;var k=n.pan(),C=n.zoom();if(g=E(d,p,h,v),f=[(d+h)/2,(p+v)/2],y=[(f[0]-k.x)/C,(f[1]-k.y)/C],200>g&&!r.touches[2]){var D=i.findNearestElement(l[0],l[1],!0),N=i.findNearestElement(l[2],l[3],!0);return D&&D.isNode()?(D.activate().trigger(new e.Event(r,{type:"cxttapstart",cyPosition:{x:l[0],y:l[1]}})),i.touchData.start=D):N&&N.isNode()?(N.activate().trigger(new e.Event(r,{type:"cxttapstart",cyPosition:{x:l[0],y:l[1]}})),i.touchData.start=N):(n.trigger(new e.Event(r,{type:"cxttapstart",cyPosition:{x:l[0],y:l[1]}})),i.touchData.start=null),i.touchData.start&&(i.touchData.start._private.grabbed=!1),i.touchData.cxt=!0,i.touchData.cxtDragged=!1,i.data.bgActivePosistion=void 0,void i.redraw()}}if(r.touches[2]);else if(r.touches[1]);else if(r.touches[0]){var T=i.findNearestElement(l[0],l[1],!0);if(null!=T){if(T.activate(),i.touchData.start=T,T.isNode()&&i.nodeIsDraggable(T)){var M=i.dragData.touchDragEles=[];if(i.data.canvasNeedsRedraw[t.NODE]=!0,i.data.canvasNeedsRedraw[t.DRAG]=!0,T.selected())for(var B=n.$(function(){return this.isNode()&&this.selected()}),z=0;z250&&(i.touchData.start?i.touchData.start.trigger(new e.Event(r,{type:"taphold",cyPosition:{x:l[0],y:l[1]}})):(i.data.cy.trigger(new e.Event(r,{type:"taphold",cyPosition:{x:l[0],y:l[1]}})),n.$(":selected").unselect()))},1e3)}},!1),i.registerBinding(window,"touchmove",e.util.throttle(function(r){var n=i.data.select,o=i.touchData.capture;o&&r.preventDefault();var s=i.data.cy,l=i.touchData.now,u=i.touchData.earlier;if(r.touches[0]){var c=i.projectIntoViewport(r.touches[0].clientX,r.touches[0].clientY);l[0]=c[0],l[1]=c[1]}if(r.touches[1]){var c=i.projectIntoViewport(r.touches[1].clientX,r.touches[1].clientY);l[2]=c[0],l[3]=c[1]}if(r.touches[2]){var c=i.projectIntoViewport(r.touches[2].clientX,r.touches[2].clientY);l[4]=c[0],l[5]=c[1]}for(var f=[],b=0;b=1.5||C>=150){i.touchData.cxt=!1,i.touchData.start&&(i.touchData.start.unactivate(),i.touchData.start=null),i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0;var N=new e.Event(r,{type:"cxttapend",cyPosition:{x:l[0],y:l[1]}});i.touchData.start?i.touchData.start.trigger(N):s.trigger(N)}}if(o&&i.touchData.cxt){var N=new e.Event(r,{type:"cxtdrag",cyPosition:{x:l[0],y:l[1]}});i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,i.touchData.start?i.touchData.start.trigger(N):s.trigger(N),i.touchData.start&&(i.touchData.start._private.grabbed=!1),i.touchData.cxtDragged=!0;var T=i.findNearestElement(l[0],l[1],!0);i.touchData.cxtOver&&T===i.touchData.cxtOver||(i.touchData.cxtOver&&i.touchData.cxtOver.trigger(new e.Event(r,{type:"cxtdragout",cyPosition:{x:l[0],y:l[1]}})),i.touchData.cxtOver=T,T&&T.trigger(new e.Event(r,{type:"cxtdragover",cyPosition:{x:l[0],y:l[1]}})))}else if(o&&r.touches[2]&&s.boxSelectionEnabled())i.data.bgActivePosistion=void 0,clearTimeout(this.threeFingerSelectTimeout),this.lastThreeTouch=+new Date,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,n&&0!==n.length&&void 0!==n[0]?(n[2]=(l[0]+l[2]+l[4])/3,n[3]=(l[1]+l[3]+l[5])/3):(n[0]=(l[0]+l[2]+l[4])/3,n[1]=(l[1]+l[3]+l[5])/3,n[2]=(l[0]+l[2]+l[4])/3+1,n[3]=(l[1]+l[3]+l[5])/3+1),n[4]=1,i.redraw();else if(o&&r.touches[1]&&s.zoomingEnabled()&&s.panningEnabled()&&s.userZoomingEnabled()&&s.userPanningEnabled()){i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0;var M=i.dragData.touchDragEles;if(M){i.data.canvasNeedsRedraw[t.DRAG]=!0;for(var B=0;B4&&(i.touchData.singleTouchMoved=!0);if(o&&(null==W||W.isEdge())&&s.panningEnabled()&&s.userPanningEnabled()){W&&(W.unactivate(),i.data.bgActivePosistion||(i.data.bgActivePosistion={x:l[0],y:l[1]}),i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,i.touchData.start=null),s.panBy({x:f[0]*s.zoom(),y:f[1]*s.zoom()}),i.swipePanning=!0;var c=i.projectIntoViewport(r.touches[0].clientX,r.touches[0].clientY);l[0]=c[0],l[1]=c[1]}}for(var b=0;b0?i.data.canvasNeedsRedraw[t.NODE]=!0:i.redraw()}r.touches.length<2&&(i.pinching=!1,i.data.canvasNeedsRedraw[t.NODE]=!0,i.redraw());var m=!1;if(null!=n&&(n._private.active=!1,m=!0,n.unactivate()),r.touches[2])i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0;else if(r.touches[1]);else if(r.touches[0]);else if(!r.touches[0]){if(i.data.bgActivePosistion=void 0,i.data.canvasNeedsRedraw[t.SELECT_BOX]=!0,null!=n){n._private.grabbed&&(n._private.grabbed=!1,n.trigger("free"),n._private.rscratch.inDragLayer=!1);for(var x=n._private.edges,b=0;bc;c++)d[4*c]=p[2*c],d[4*c+1]=p[2*c+1],d[4*c+2]=h[2*c],d[4*c+3]=h[2*c+1];d=e.math.fitPolygonToSquare(d),n.star5=n.star={points:d,draw:function(e,t,r,a,o){i.drawPolygon(e,t,r,a,o,n.star5.points)},drawPath:function(e,t,r,a,o){i.drawPolygonPath(e,t,r,a,o,n.star5.points)},intersectLine:function(e,t,r,a,o,s,l){return i.polygonIntersectLine(o,s,n.star5.points,e,t,r/2,a/2,l)},intersectBox:function(e,t,r,a,o,s,l,u,c){var d=n.star5.points;return i.boxIntersectPolygon(e,t,r,a,d,o,s,l,u,[0,-1],c)},checkPoint:function(t,i,r,a,o,s,l){return e.math.pointInsidePolygon(t,i,n.star5.points,s,l,a,o,[0,-1],r)}}}(cytoscape),function(e){"use strict";function t(t){this._private={},this._private.options=e.util.extend({},i,t)}var i={animate:!0,maxSimulationTime:4e3,fit:!0,padding:30,boundingBox:void 0,ungrabifyWhileSimulating:!1,ready:void 0,stop:void 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},infinite:!1};t.prototype.run=function(){var t=this,i=this._private.options;return e.util.require("arbor",function(r){function n(e,t){return null==t?void 0:"function"==typeof t?t.apply(e,[e._private.data,{nodes:u.length,edges:c.length,element:e}]):t}function a(e){if(!e.isFullAutoParent()){var t=e._private.data.id,r=n(e,i.nodeMass),a=e._private.locked,o=e.position(),s=h.fromScreen({x:o.x,y:o.y});e.scratch().arbor=h.addNode(t,{element:e,mass:r,fixed:a,x:a?s.x:void 0,y:a?s.y:void 0})}}function o(e){var t=e.source().id(),r=e.target().id(),a=n(e,i.edgeLength);e.scratch().arbor=h.addEdge(t,r,{length:a})}var s=i.cy,l=i.eles,u=l.nodes().not(":parent"),c=l.edges(),d=e.util.makeBoundingBox(i.boundingBox?i.boundingBox:{x1:0,y1:0,w:s.width(),h:s.height()}),p=!1;if(t.trigger({type:"layoutstart",layout:t}),void 0!==i.liveUpdate&&(i.animate=i.liveUpdate),s.nodes().size()<=1)return i.fit&&s.reset(),s.nodes().position({x:Math.round((d.x1+d.x2)/2),y:Math.round((d.y1+d.y2)/2)}),t.one("layoutready",i.ready),t.trigger({type:"layoutready",layout:t}),t.one("layoutstop",i.stop),void t.trigger({type:"layoutstop",layout:t});var h=t._private.system=r.ParticleSystem();h.parameters({repulsion:i.repulsion,stiffness:i.stiffness,friction:i.friction,gravity:i.gravity,fps:i.fps,dt:i.dt,precision:i.precision}),i.animate&&i.fit&&s.fit(d,i.padding);var v,g=250,f=!1,y=+new Date,m={init:function(){},redraw:function(){var e=h.energy();if(!i.infinite&&null!=i.stableEnergy&&null!=e&&e.n>0&&i.stableEnergy(e))return void t.stop();i.infinite||1/0==g||(clearTimeout(v),v=setTimeout(P,g));var r=s.collection();h.eachNode(function(e,t){var i=e.data,n=i.element;null!=n&&(n.locked()||n.grabbed()||(n.silentPosition({x:d.x1+t.x,y:d.y1+t.y}),r.merge(n)))}),i.animate&&r.length>0&&(p=!0,r.rtrigger("position"),i.fit&&s.fit(i.padding),y=+new Date,p=!1),f||(f=!0,t.one("layoutready",i.ready),t.trigger({type:"layoutready",layout:t}))}};h.renderer=m,h.screenSize(d.w,d.h),h.screenPadding(i.padding,i.padding,i.padding,i.padding),h.screenStep(i.stepSize);var x;u.on("grab free position",x=function(e){if(!p){var t=this.position(),n=h.fromScreen(t);if(n){var a=r.Point(n.x,n.y),o=i.padding;switch(d.x1+o<=t.x&&t.x<=d.x2-o&&d.y1+o<=t.y&&t.y<=d.y2-o&&(this.scratch().arbor.p=a),e.type){case"grab":this.scratch().arbor.fixed=!0;break;case"free":this.scratch().arbor.fixed=!1}}}});var b;u.on("lock unlock",b=function(){node.scratch().arbor.fixed=node.locked()});var w;l.on("remove",w=function(){});var _;s.on("add","*",_=function(){});var E;s.on("resize",E=function(){if(null==i.boundingBox&&null!=t._private.system){var e=s.width(),r=s.height();h.screenSize(e,r)}}),u.each(function(e,t){a(t)}),c.each(function(e,t){o(t)});var S=u.filter(":grabbable");i.ungrabifyWhileSimulating&&S.ungrabify();var P=t._private.doneHandler=function(){t._private.doneHandler=null,i.animate||(i.fit&&s.reset(),u.rtrigger("position")),u.off("grab free position",x),u.off("lock unlock",b),l.off("remove",w),s.off("add","*",_),s.off("resize",E),i.ungrabifyWhileSimulating&&S.grabify(),t.one("layoutstop",i.stop),t.trigger({type:"layoutstop",layout:t})};h.start(),!i.infinite&&null!=i.maxSimulationTime&&i.maxSimulationTime>0&&1/0!==i.maxSimulationTime&&setTimeout(function(){t.stop()},i.maxSimulationTime)}),this},t.prototype.stop=function(){return null!=this._private.system&&this._private.system.stop(),this._private.doneHandler&&this._private.doneHandler(),this},e("layout","arbor",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend({},i,t)}var i={fit:!0,directed:!1,padding:30,circle:!1,boundingBox:void 0,avoidOverlap:!0,roots:void 0,maximalAdjustments:0,animate:!1,animationDuration:500,ready:void 0,stop:void 0};t.prototype.run=function(){var t,i=this.options,r=i,n=i.cy,a=r.eles,o=a.nodes().not(":parent"),s=a,l=e.util.makeBoundingBox(r.boundingBox?r.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()});if(e.is.elementOrCollection(r.roots))t=r.roots;else if(e.is.array(r.roots)){for(var u=[],c=0;c0;){var g=n.collection();a.bfs({roots:v[0],visit:function(e,t,i){g=g.add(i)},directed:!1}),v=v.not(g),h.push(g)}t=n.collection();for(var c=0;cC;){for(var D=P.shift(),N=D.neighborhood().nodes(),T=!1,c=0;cc;c++)for(var M=x[c],R=M.length,X=0;R>X;X++){var p=M[X],V=p._private.scratch.breadthfirst,Y=z(p);Y&&(V.intEle=Y,O.push(p))}for(var c=0;cx.length-1;)x.push([]);x[q].push(p),V.depth=q,V.index=x[q].length-1}B()}var F=0;if(r.avoidOverlap){for(var c=0;cl||0===t)&&(r+=s/u,n++)}return n=Math.max(1,n),r/=n,0===n&&(r=void 0),H[e.id()]=r,r +},Z=function(e,t){var i=$(e),r=$(t);return i-r},U=0;3>U;U++){for(var c=0;c0&&x[0].length<=3?c/2:0),f=2*Math.PI/x[n].length*a;return 0===n&&1===x[0].length&&(g=1),{x:K.x+g*Math.cos(f),y:K.y+g*Math.sin(f)}}return{x:K.x+(a+1-(o+1)/2)*s,y:(n+1)*u}},Q={},c=x.length-1;c>=0;c--)for(var M=x[c],X=0;X1&&r.avoidOverlap){d*=1.75;var c=2*Math.PI/o.length,g=Math.cos(c)-Math.cos(0),f=Math.sin(c)-Math.sin(0),y=Math.sqrt(d*d/(g*g+f*f));t=Math.max(y,t)}var m=function(){var e=t*Math.cos(u),i=t*Math.sin(u),n={x:l.x+e,y:l.y+i};return u=r.counterclockwise?u-c:u+c,n};return o.layoutPositions(this,r,m),this},e("layout","circle",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend(!0,{},i,t)}var i={animate:!0,refresh:1,maxSimulationTime:4e3,ungrabifyWhileSimulating:!1,fit:!0,padding:30,boundingBox:void 0,ready:function(){},stop:function(){},randomize:!1,avoidOverlap:!0,handleDisconnected:!0,nodeSpacing:function(){return 10},flow:void 0,alignment:void 0,edgeLength:void 0,edgeSymDiffLength:void 0,edgeJaccardLength:void 0,unconstrIter:void 0,userConstIter:void 0,allConstIter:void 0,infinite:!1};t.prototype.run=function(){var t=this,i=this.options;return e.util.require("cola",function(r){var n=i.cy,a=i.eles,o=a.nodes(),s=a.edges(),l=!1,u=e.util.makeBoundingBox(i.boundingBox?i.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()}),c=function(t,i){if(e.is.fn(t)){var r=t;return r.apply(i,[i])}return t},d=function(){for(var e={min:1/0,max:-1/0},t={min:1/0,max:-1/0},r=0;ri&&!e;i++)e=e||n();return e};if(i.animate){var o=function(){a()||e.util.requestAnimationFrame(o)};e.util.requestAnimationFrame(o)}else for(;!n(););},on:function(){},drag:function(){}});t.adaptor=f;var y=o.filter(":grabbable");i.ungrabifyWhileSimulating&&y.ungrabify();var m;o.on("grab free position",m=function(t){var i=this,r=i._private.scratch.cola,n=i._private.position;switch(i.grabbed()?(r.x=n.x-u.x1,r.y=n.y-u.y1,f.dragstart(r)):e.is.number(r.x)&&e.is.number(r.y)&&(n.x=r.x+u.x1,n.y=r.y+u.y1),t.type){case"grab":f.dragstart(r),f.resume();break;case"free":f.dragend(r)}});var x;o.on("lock unlock",x=function(){var e=this,t=e._private.scratch.cola;e.locked()?f.dragstart(t):f.dragend(t)});var b=o.stdFilter(function(e){return!e.isParent()});if(f.nodes(b.map(function(e,t){var r=c(i.nodeSpacing,e),n=e.position(),a=e._private.scratch.cola={x:i.randomize?Math.round(Math.random()*u.w):n.x,y:i.randomize?Math.round(Math.random()*u.h):n.y,width:e.outerWidth()+2*r,height:e.outerHeight()+2*r,index:t};return a})),i.alignment){var w=[],_=[];b.forEach(function(e){var t=c(i.alignment,e),r=e._private.scratch.cola,n=r.index;t&&(null!=t.x&&w.push({node:n,offset:t.x}),null!=t.y&&_.push({node:n,offset:t.y}))});var E=[];w.length>0&&E.push({type:"alignment",axis:"x",offsets:w}),_.length>0&&E.push({type:"alignment",axis:"y",offsets:_}),f.constraints(E)}f.groups(o.stdFilter(function(e){return e.isParent()}).map(function(e,t){return e._private.scratch.cola={index:t,leaves:e.children().stdFilter(function(e){return!e.isParent()}).map(function(e){return e[0]._private.scratch.cola.index})},e}).map(function(e){return e._private.scratch.cola.groups=e.children().stdFilter(function(e){return e.isParent()}).map(function(e){return e._private.scratch.cola.index}),e._private.scratch.cola}));var S,P;null!=i.edgeLength?(S=i.edgeLength,P="linkDistance"):null!=i.edgeSymDiffLength?(S=i.edgeSymDiffLength,P="symmetricDiffLinkLengths"):null!=i.edgeJaccardLength?(S=i.edgeJaccardLength,P="jaccardLinkLengths"):(S=100,P="linkDistance");var k=function(e){return e.calcLength};if(f.links(s.stdFilter(function(e){return!e.source().isParent()&&!e.target().isParent()}).map(function(e){var t=e._private.scratch.cola={source:e.source()[0]._private.scratch.cola.index,target:e.target()[0]._private.scratch.cola.index};return null!=S&&(t.calcLength=c(S,e)),t})),f.size([u.w,u.h]),null!=S&&f[P](k),i.flow){var C,D="y",N=50;e.is.string(i.flow)?C={axis:i.flow,minSeparation:N}:e.is.number(i.flow)?C={axis:D,minSeparation:i.flow}:e.is.plainObject(i.flow)?(C=i.flow,C.axis=C.axis||D,C.minSeparation=null!=C.minSeparation?C.minSeparation:N):C={axis:D,minSeparation:N},f.flowLayout(C.axis,C.minSeparation)}f.avoidOverlaps(i.avoidOverlap).handleDisconnected(i.handleDisconnected).start(i.unconstrIter,i.userConstIter,i.allConstIter),t.trigger({type:"layoutstart",layout:t}),i.infinite||setTimeout(function(){f.stop()},i.maxSimulationTime)}),this},t.prototype.stop=function(){return this.adaptor&&(this.manuallyStopped=!0,this.adaptor.stop()),this},e("layout","cola",t)}(cytoscape),function(e){"use strict";function t(t){this.options=e.util.extend({},i,t)}var i={fit:!0,padding:30,startAngle:1.5*Math.PI,counterclockwise:!1,minNodeSpacing:10,boundingBox:void 0,avoidOverlap:!0,height:void 0,width:void 0,concentric:function(){return this.degree()},levelWidth:function(e){return e.maxDegree()/4},animate:!1,animationDuration:500,ready:void 0,stop:void 0};t.prototype.run=function(){for(var t=this.options,i=t,r=t.cy,n=i.eles,a=n.nodes().not(":parent"),o=e.util.makeBoundingBox(i.boundingBox?i.boundingBox:{x1:0,y1:0,w:r.width(),h:r.height()}),s={x:o.x1+o.w/2,y:o.y1+o.h/2},l=[],u=i.startAngle,c=0,d=0;d0){var m=Math.abs(f[0].value-y.value);m>=v&&(f=[],g.push(f))}f.push(y)}var x={},b=0,w=c+i.minNodeSpacing;if(!i.avoidOverlap){var _=g.length>0&&g[0].length>1,E=Math.min(o.w,o.h)/2-w,S=E/(g.length+_?1:0);w=Math.min(w,S)}for(var d=0;d1&&i.avoidOverlap){var C=Math.cos(k)-Math.cos(0),D=Math.sin(k)-Math.sin(0),N=Math.sqrt(w*w/(C*C+D*D));b=Math.max(N,b)}for(var T=0;T=d;){var f=c[d++],y=s.idToIndex[f],m=s.layoutNodes[y],x=m.children;if(x.length>0){s.graphSet.push(x);for(var l=0;lr.count?0:r.graph},o=function(e,t,i,r){var n=r.graphSet[i];if(-1<$.inArray(e,n)&&-1<$.inArray(t,n))return{count:2,graph:i};for(var a=0,s=0;ss;s++)for(var l=e.layoutNodes[e.idToIndex[a[s]]],u=s+1;o>u;u++){var c=e.layoutNodes[e.idToIndex[a[u]]];p(l,c,e,t,i)}}},p=function(e,t,i,r,n){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=v(e,t,o,s);if(l>0){a+="\nNodes DO overlap.",a+="\nOverlap: "+l;var u=n.nodeOverlap*l,c=Math.sqrt(o*o+s*s);a+="\nDistance: "+c;var d=u*o/c,p=u*s/c}else{a+="\nNodes do NOT overlap.";var g=h(e,o,s),f=h(t,-1*o,-1*s),y=f.x-g.x,m=f.y-g.y,x=y*y+m*m,c=Math.sqrt(x);a+="\nDistance: "+c;var u=n.nodeRepulsion/x,d=u*y/c,p=u*m/c}e.offsetX-=d,e.offsetY-=p,t.offsetX+=d,t.offsetY+=p,a+="\nForceX: "+d+" ForceY: "+p,w(a)},h=function(e,t,i){var r=e.positionX,n=e.positionY,a=e.height,o=e.width,s=i/t,l=a/o,u="Computing clipping point of node "+e.id+" . Height: "+a+", Width: "+o+"\nDirection "+t+", "+i,c={};do{if(0===t&&i>0){c.x=r,u+="\nUp direction",c.y=n+a/2;break}if(0===t&&0>i){c.x=r,c.y=n+a/2,u+="\nDown direction";break}if(t>0&&s>=-1*l&&l>=s){c.x=r+o/2,c.y=n+o*i/2/t,u+="\nRightborder";break}if(0>t&&s>=-1*l&&l>=s){c.x=r-o/2,c.y=n-o*i/2/t,u+="\nLeftborder";break}if(i>0&&(-1*l>=s||s>=l)){c.x=r+a*t/2/i,c.y=n+a/2,u+="\nTop border";break}if(0>i&&(-1*l>=s||s>=l)){c.x=r-a*t/2/i,c.y=n-a/2,u+="\nBottom border";break}}while(!1);return u+="\nClipping point found at "+c.x+", "+c.y,w(u),c},v=function(e,t,i,r){if(i>0)var n=e.maxX-t.minX;else var n=t.maxX-e.minX;if(r>0)var a=e.maxY-t.minY;else var a=t.maxY-e.minY;return n>=0&&a>=0?Math.sqrt(n*n+a*a):0},g=function(e,t,i){for(var r=0;rd;d++){var p=e.layoutNodes[e.idToIndex[a[d]]];r="Node: "+p.id;var h=s-p.positionX,v=l-p.positionY,g=Math.sqrt(h*h+v*v);if(g>1){var f=i.gravity*h/g,y=i.gravity*v/g;p.offsetX+=f,p.offsetY+=y,r+=": Applied force: "+f+", "+y}else r+=": skypped since it's too close to center";w(r)}}},y=function(e){var t=[],i=0,r=-1;for(w("propagateForces"),t.push.apply(t,e.graphSet[0]),r+=e.graphSet[0].length;r>=i;){var n=t[i++],a=e.idToIndex[n],o=e.layoutNodes[a],s=o.children;if(0i)var a={x:i*e/n,y:i*t/n};else var a={x:e,y:t};return r+=".\nResult: ("+a.x+", "+a.y+")",w(r),a},b=function(e,t){var i="Propagating new position/size of node "+e.id,r=e.parentId;if(null==r)return i+=". No parent node.",void w(i);var n=t.layoutNodes[t.idToIndex[r]],a=!1;return(null==n.maxX||e.maxX+n.padRight>n.maxX)&&(n.maxX=e.maxX+n.padRight,a=!0,i+="\nNew maxX for parent node "+n.id+": "+n.maxX),(null==n.minX||e.minX-n.padLeftn.maxY)&&(n.maxY=e.maxY+n.padBottom,a=!0,i+="\nNew maxY for parent node "+n.id+": "+n.maxY),(null==n.minY||e.minY-n.padTops){var h=d(),v=p();(h-1)*v>=s?d(h-1):(v-1)*h>=s&&p(v-1)}else for(;s>c*u;){var h=d(),v=p();(v+1)*h>=s?p(v+1):d(h+1)}var g=o.w/c,f=o.h/u;if(i.avoidOverlap)for(var y=0;y=c&&(P=0,S++)},C={},y=0;y0&&r.animate&&(s=!0,m.rtrigger("position"),r.fit&&l.fit(r.padding),m=l.collection(),s=!1)},function(){},function(e,i){var n=f(i),a=e.data.element;a.locked()||a.grabbed()||(a._private.position={x:n.x,y:n.y},m.merge(a)),b==x&&(t.one("layoutready",r.ready),t.trigger({type:"layoutready",layout:t})),b++});c.each(function(e,t){r.random||a(t)});var _;c.on("position",_=function(){s||a(this)});var E=c.filter(":grabbable");i.stopSystem=function(){h.filterNodes(function(){return!1}),r.ungrabifyWhileSimulating&&E.grabify(),r.fit&&l.fit(r.padding),c.off("drag position",_),t.one("layoutstop",r.stop),t.trigger({type:"layoutstop",layout:t}),i.stopSystem=null},o(),r.infinite||setTimeout(function(){i.stop()},r.maxSimulationTime)}),this},t.prototype.stop=function(){return null!=this.stopSystem&&this.stopSystem(),this},e("layout","springy",t)}(cytoscape),function(e){"use strict";function t(e){this.options=e}t.prototype.recalculateRenderedStyle=function(){},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 08aba6173..82a4636cc 100644 --- a/ajax/libs/cytoscape/package.json +++ b/ajax/libs/cytoscape/package.json @@ -9,7 +9,7 @@ ] } ], - "version": "2.3.3", + "version": "2.3.4", "filename": "cytoscape.min.js", "description": "A JavaScript graph library for analysis and visualisation", "homepage": "http://cytoscape.github.io/cytoscape.js",