sbgn-renderer
Version:
A library that renders SBGN
1,937 lines (1,516 loc) • 2.35 MB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.sbgnRenderer = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
/*!
Cytoscape.js {{VERSION}} (MIT licensed)
Copyright (c) The Cytoscape Consortium
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
'use strict';
},{}],2:[function(_dereq_,module,exports){
'use strict';
var util = _dereq_( './util' );
var is = _dereq_( './is' );
var Promise = _dereq_( './promise' );
var Animation = function( target, opts, opts2 ){
var _p = this._private = util.extend( {
duration: 1000
}, opts, opts2 );
_p.target = target;
_p.style = _p.style || _p.css;
_p.started = false;
_p.playing = false;
_p.hooked = false;
_p.applying = false;
_p.progress = 0;
_p.completes = [];
_p.frames = [];
if( _p.complete && is.fn( _p.complete ) ){
_p.completes.push( _p.complete );
}
// for future timeline/animations impl
this.length = 1;
this[0] = this;
};
var anifn = Animation.prototype;
util.extend( anifn, {
instanceString: function(){ return 'animation'; },
hook: function(){
var _p = this._private;
if( !_p.hooked ){
// add to target's animation queue
var q;
var tAni = _p.target._private.animation;
if( _p.queue ){
q = tAni.queue;
} else {
q = tAni.current;
}
q.push( this );
// add to the animation loop pool
if( is.elementOrCollection( _p.target ) ){
_p.target.cy().addToAnimationPool( _p.target );
}
_p.hooked = true;
}
return this;
},
play: function(){
var _p = this._private;
// autorewind
if( _p.progress === 1 ){
_p.progress = 0;
}
_p.playing = true;
_p.started = false; // needs to be started by animation loop
_p.stopped = false;
this.hook();
// the animation loop will start the animation...
return this;
},
playing: function(){
return this._private.playing;
},
apply: function(){
var _p = this._private;
_p.applying = true;
_p.started = false; // needs to be started by animation loop
_p.stopped = false;
this.hook();
// the animation loop will apply the animation at this progress
return this;
},
applying: function(){
return this._private.applying;
},
pause: function(){
var _p = this._private;
_p.playing = false;
_p.started = false;
return this;
},
stop: function(){
var _p = this._private;
_p.playing = false;
_p.started = false;
_p.stopped = true; // to be removed from animation queues
return this;
},
rewind: function(){
return this.progress( 0 );
},
fastforward: function(){
return this.progress( 1 );
},
time: function( t ){
var _p = this._private;
if( t === undefined ){
return _p.progress * _p.duration;
} else {
return this.progress( t / _p.duration );
}
},
progress: function( p ){
var _p = this._private;
var wasPlaying = _p.playing;
if( p === undefined ){
return _p.progress;
} else {
if( wasPlaying ){
this.pause();
}
_p.progress = p;
_p.started = false;
if( wasPlaying ){
this.play();
}
}
return this;
},
completed: function(){
return this._private.progress === 1;
},
reverse: function(){
var _p = this._private;
var wasPlaying = _p.playing;
if( wasPlaying ){
this.pause();
}
_p.progress = 1 - _p.progress;
_p.started = false;
var swap = function( a, b ){
var _pa = _p[ a ];
if( _pa == null ){ return; }
_p[ a ] = _p[ b ];
_p[ b ] = _pa;
};
swap( 'zoom', 'startZoom' );
swap( 'pan', 'startPan' );
swap( 'position', 'startPosition' );
// swap styles
if( _p.style ){
for( var i = 0; i < _p.style.length; i++ ){
var prop = _p.style[ i ];
var name = prop.name;
var startStyleProp = _p.startStyle[ name ];
_p.startStyle[ name ] = prop;
_p.style[ i ] = startStyleProp;
}
}
if( wasPlaying ){
this.play();
}
return this;
},
promise: function( type ){
var _p = this._private;
var arr;
switch( type ){
case 'frame':
arr = _p.frames;
break;
default:
case 'complete':
case 'completed':
arr = _p.completes;
}
return new Promise( function( resolve, reject ){
arr.push( function(){
resolve();
} );
} );
}
} );
anifn.complete = anifn.completed;
module.exports = Animation;
},{"./is":91,"./promise":94,"./util":108}],3:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
var elesfn = ({
// Implemented from pseudocode from wikipedia
aStar: function( options ){
var eles = this;
options = options || {};
// 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.unshift( 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.unshift( cy.getElementById( previousEdge ) );
pathAcum.unshift( cy.getElementById( end ) );
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;
};
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];
} 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];
} 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
}
// 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 sid = source.id();
var tid = target.id();
var closedSet = [];
var openSet = [ sid ];
var cameFrom = {};
var cameFromEdge = {};
var gScore = {};
var fScore = {};
gScore[ sid ] = 0;
fScore[ sid ] = heuristic( source );
// Counter
var steps = 0;
// Main loop
while( openSet.length > 0 ){
var minPos = findMin( openSet, fScore );
var cMin = cy.getElementById( openSet[ minPos ] );
var cMinId = cMin.id();
steps++;
// If we've found our goal, then we are done
if( cMinId == tid ){
var rPath = reconstructPath( sid, tid, cameFrom, [] );
return {
found: true,
distance: gScore[ cMinId ],
path: eles.spawn( rPath ),
steps: steps
};
}
// Add cMin to processed nodes
closedSet.push( cMinId );
// Remove cMin from boundary nodes
openSet.splice( minPos, 1 );
// Update scores for neighbors of cMin
// Take into account if graph is directed or not
var vwEdges = cMin._private.edges;
for( var i = 0; i < vwEdges.length; i++ ){
var e = vwEdges[ i ];
// edge must be in set of calling eles
if( !this.hasElementWithId( e.id() ) ){ continue; }
// cMin must be the source of edge if directed
if( directed && e.data('source') !== cMinId ){ continue; }
var wSrc = e.source();
var wTgt = e.target();
var w = wSrc.id() !== cMinId ? wSrc : wTgt;
var wid = w.id();
// node must be in set of calling eles
if( !this.hasElementWithId( wid ) ){ continue; }
// if node is in closedSet, ignore it
if( closedSet.indexOf( wid ) != -1 ){
continue;
}
// New tentative score for node w
var tempScore = gScore[ cMinId ] + weightFn( e );
// Update gScore for node w if:
// w not present in openSet
// OR
// tentative gScore is less than previous value
// w not in openSet
if( openSet.indexOf( wid ) == -1 ){
gScore[ wid ] = tempScore;
fScore[ wid ] = tempScore + heuristic( w );
openSet.push( wid ); // Add node to openSet
cameFrom[ wid ] = cMinId;
cameFromEdge[ wid ] = e.id();
continue;
}
// w already in openSet, but with greater gScore
if( tempScore < gScore[ wid ] ){
gScore[ wid ] = tempScore;
fScore[ wid ] = tempScore + heuristic( w );
cameFrom[ wid ] = cMinId;
}
} // End of neighbors update
} // End of main loop
// If we've reached here, then we've not reached our goal
return {
found: false,
distance: undefined,
path: undefined,
steps: steps
};
}
}); // elesfn
module.exports = elesfn;
},{"../../is":91}],4:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
var util = _dereq_( '../../util' );
var elesfn = ({
// Implemented from pseudocode from wikipedia
bellmanFord: function( options ){
var eles = this;
options = options || {};
// Weight function - optional
if( options.weight != null && is.fn( options.weight ) ){
var weightFn = options.weight;
} else {
// If not specified, assume each edge has equal weight (1)
var weightFn = function( e ){return 1;};
}
// directed - optional
if( options.directed != null ){
var directed = options.directed;
} else {
var directed = false;
}
// root - mandatory!
if( options.root != null ){
if( is.string( options.root ) ){
// use it as a selector, e.g. "#rootID
var source = this.filter( options.root )[0];
} else {
var source = options.root[0];
}
} else {
return undefined;
}
var cy = this._private.cy;
var edges = this.edges().stdFilter( function( e ){ return !e.isLoop(); } );
var nodes = this.nodes();
var numNodes = nodes.length;
// mapping: node id -> position in nodes array
var id2position = {};
for( var i = 0; i < numNodes; i++ ){
id2position[ nodes[ i ].id() ] = i;
}
// Initializations
var cost = [];
var predecessor = [];
var predEdge = [];
for( var i = 0; i < numNodes; i++ ){
if( nodes[ i ].id() === source.id() ){
cost[ i ] = 0;
} else {
cost[ i ] = Infinity;
}
predecessor[ i ] = undefined;
}
// Edges relaxation
var flag = false;
for( var i = 1; i < numNodes; i++ ){
flag = false;
for( var e = 0; e < edges.length; e++ ){
var sourceIndex = id2position[ edges[ e ].source().id() ];
var targetIndex = id2position[ edges[ e ].target().id() ];
var weight = weightFn( 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( edges[ e ] );
if( cost[ sourceIndex ] + weight < cost[ targetIndex ] ){
util.error( 'Graph contains a negative weight cycle for Bellman-Ford' );
return { pathTo: undefined,
distanceTo: undefined,
hasNegativeWeightCycle: true};
}
}
}
// Build result object
var position2id = [];
for( var i = 0; i < numNodes; i++ ){
position2id.push( nodes[ i ].id() );
}
var res = {
distanceTo: function( to ){
if( is.string( to ) ){
// to is a selector string
var toId = (cy.filter( to )[0]).id();
} else {
// to is a node
var toId = to.id();
}
return cost[ id2position[ toId ] ];
},
pathTo: function( to ){
var reconstructPathAux = function( predecessor, fromPos, toPos, position2id, acumPath, predEdge ){
for( ;; ){
// Add toId to path
acumPath.push( cy.getElementById( position2id[ toPos ] ) );
acumPath.push( predEdge[ toPos ] );
if( fromPos === toPos ){
// reached starting node
return acumPath;
}
// If no path exists, discart acumulated path and return undefined
var predPos = predecessor[ toPos ];
if( typeof predPos === 'undefined' ){
return undefined;
}
toPos = predPos;
}
};
if( is.string( to ) ){
// to is a selector string
var toId = (cy.filter( to )[0]).id();
} else {
// to is a node
var toId = to.id();
}
var path = [];
// This returns a reversed path
var res = reconstructPathAux( predecessor,
id2position[ source.id() ],
id2position[ toId ],
position2id,
path,
predEdge );
// Get it in the correct order and return it
if( res != null ){
res.reverse();
}
return eles.spawn( res );
},
hasNegativeWeightCycle: false
};
return res;
} // bellmanFord
}); // elesfn
module.exports = elesfn;
},{"../../is":91,"../../util":108}],5:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
var Heap = _dereq_( '../../heap' );
var elesfn = ({
// Implemented from the algorithm in the paper "On Variants of Shortest-Path Betweenness Centrality and their Generic Computation" by Ulrik Brandes
betweennessCentrality: function( options ){
options = options || {};
// Weight - optional
var weighted, weightFn;
if( is.fn( options.weight ) ){
weightFn = options.weight;
weighted = true;
} else {
weighted = false;
}
// Directed - default false
var directed = options.directed != null ? options.directed : false;
var cy = this._private.cy;
// starting
var V = this.nodes();
var A = {};
var _C = {};
var max = 0;
var C = {
set: function( key, val ){
_C[ key ] = val;
if( val > max ){ max = val; }
},
get: function( key ){ return _C[ key ]; }
};
// A contains the neighborhoods of every node
for( var i = 0; i < V.length; i++ ){
var v = V[ i ];
var vid = v.id();
if( directed ){
A[ vid ] = v.outgoers().nodes(); // get outgoers of every node
} else {
A[ vid ] = v.openNeighborhood().nodes(); // get neighbors of every node
}
C.set( vid, 0 );
}
for( var s = 0; s < V.length; s++ ){
var sid = V[s].id();
var S = []; // stack
var P = {};
var g = {};
var d = {};
var Q = new Heap(function( a, b ){
return d[a] - d[b];
}); // queue
// init dictionaries
for( var i = 0; i < V.length; i++ ){
var vid = V[ i ].id();
P[ vid ] = [];
g[ vid ] = 0;
d[ vid ] = Infinity;
}
g[ sid ] = 1; // sigma
d[ sid ] = 0; // distance to s
Q.push( sid );
while( !Q.empty() ){
var v = Q.pop();
S.push( v );
if( weighted ){
for( var j = 0; j < A[v].length; j++ ){
var w = A[v][j];
var vEle = cy.getElementById( v );
var edge;
if( vEle.edgesTo( w ).length > 0 ){
edge = vEle.edgesTo( w )[0];
} else {
edge = w.edgesTo( vEle )[0];
}
var edgeWeight = weightFn( edge );
w = w.id();
if( d[w] > d[v] + edgeWeight ){
d[w] = d[v] + edgeWeight;
if( Q.nodes.indexOf( w ) < 0 ){ //if w is not in Q
Q.push( w );
} else { // update position if w is in Q
Q.updateItem( w );
}
g[w] = 0;
P[w] = [];
}
if( d[w] == d[v] + edgeWeight ){
g[w] = g[w] + g[v];
P[w].push( v );
}
}
} else {
for( var j = 0; j < A[v].length; j++ ){
var w = A[v][j].id();
if( d[w] == Infinity ){
Q.push( w );
d[w] = d[v] + 1;
}
if( d[w] == d[v] + 1 ){
g[w] = g[w] + g[v];
P[w].push( v );
}
}
}
}
var e = {};
for( var i = 0; i < V.length; i++ ){
e[ V[ i ].id() ] = 0;
}
while( S.length > 0 ){
var w = S.pop();
for( var j = 0; j < P[w].length; j++ ){
var v = P[w][j];
e[v] = e[v] + (g[v] / g[w]) * (1 + e[w]);
if( w != V[s].id() ){
C.set( w, C.get( w ) + e[w] );
}
}
}
}
var ret = {
betweenness: function( node ){
if( is.string( node ) ){
var node = cy.filter( node ).id();
} else {
var node = node.id();
}
return C.get( node );
},
betweennessNormalized: function( node ){
if ( max == 0 )
return 0;
if( is.string( node ) ){
var node = cy.filter( node ).id();
} else {
var node = node.id();
}
return C.get( node ) / max;
}
};
// alias
ret.betweennessNormalised = ret.betweennessNormalized;
return ret;
} // betweennessCentrality
}); // elesfn
// nice, short mathemathical alias
elesfn.bc = elesfn.betweennessCentrality;
module.exports = elesfn;
},{"../../heap":89,"../../is":91}],6:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
var defineSearch = function( params ){
params = {
bfs: params.bfs || !params.dfs,
dfs: params.dfs || !params.bfs
};
// from pseudocode on wikipedia
return function searchFn( roots, fn, directed ){
var options;
if( is.plainObject( roots ) && !is.elementOrCollection( roots ) ){
options = roots;
roots = options.roots || options.root;
fn = options.visit;
directed = options.directed;
}
directed = arguments.length === 2 && !is.fn( fn ) ? fn : directed;
fn = is.fn( fn ) ? fn : function(){};
var cy = this._private.cy;
var v = roots = is.string( roots ) ? this.filter( roots ) : roots;
var Q = [];
var connectedNodes = [];
var connectedBy = {};
var id2depth = {};
var V = {};
var j = 0;
var found;
var nodes = this.nodes();
var edges = this.edges();
// enqueue v
for( var i = 0; i < v.length; i++ ){
if( v[ i ].isNode() ){
Q.unshift( v[ i ] );
if( params.bfs ){
V[ v[ i ].id() ] = true;
connectedNodes.push( v[ i ] );
}
id2depth[ v[ i ].id() ] = 0;
}
}
while( Q.length !== 0 ){
var v = params.bfs ? Q.shift() : Q.pop();
if( params.dfs ){
if( V[ v.id() ] ){ continue; }
V[ v.id() ] = true;
connectedNodes.push( v );
}
var depth = id2depth[ v.id() ];
var prevEdge = connectedBy[ v.id() ];
var prevNode = prevEdge == null ? undefined : prevEdge.connectedNodes().not( v )[0];
var ret;
ret = fn( v, prevEdge, prevNode, j++, depth );
if( ret === true ){
found = v;
break;
}
if( ret === false ){
break;
}
var vwEdges = v.connectedEdges( directed ? function( ele ){ return ele.data( 'source' ) === v.id(); } : undefined ).intersect( edges );
for( var i = 0; i < vwEdges.length; i++ ){
var e = vwEdges[ i ];
var w = e.connectedNodes( function( n ){ return n.id() !== v.id(); } ).intersect( nodes );
if( w.length !== 0 && !V[ w.id() ] ){
w = w[0];
Q.push( w );
if( params.bfs ){
V[ w.id() ] = true;
connectedNodes.push( w );
}
connectedBy[ w.id() ] = e;
id2depth[ w.id() ] = id2depth[ v.id() ] + 1;
}
}
}
var connectedEles = [];
for( var i = 0; i < connectedNodes.length; i++ ){
var node = connectedNodes[ i ];
var edge = connectedBy[ node.id() ];
if( edge ){
connectedEles.push( edge );
}
connectedEles.push( node );
}
return {
path: cy.collection( connectedEles, { unique: true } ),
found: cy.collection( found )
};
};
};
// search, spanning trees, etc
var elesfn = ({
breadthFirstSearch: defineSearch( { bfs: true } ),
depthFirstSearch: defineSearch( { dfs: true } )
});
// nice, short mathemathical alias
elesfn.bfs = elesfn.breadthFirstSearch;
elesfn.dfs = elesfn.depthFirstSearch;
module.exports = elesfn;
},{"../../is":91}],7:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
var elesfn = ({
closenessCentralityNormalized: function( options ){
options = options || {};
var cy = this.cy();
var harmonic = options.harmonic;
if( harmonic === undefined ){
harmonic = true;
}
var closenesses = {};
var maxCloseness = 0;
var nodes = this.nodes();
var fw = this.floydWarshall( { weight: options.weight, directed: options.directed } );
// Compute closeness for every node and find the maximum closeness
for( var i = 0; i < nodes.length; i++ ){
var currCloseness = 0;
for( var j = 0; j < nodes.length; j++ ){
if( i != j ){
var d = fw.distance( nodes[ i ], nodes[ j ] );
if( harmonic ){
currCloseness += 1 / d;
} else {
currCloseness += d;
}
}
}
if( !harmonic ){
currCloseness = 1 / currCloseness;
}
if( maxCloseness < currCloseness ){
maxCloseness = currCloseness;
}
closenesses[ nodes[ i ].id() ] = currCloseness;
}
return {
closeness: function( node ){
if( maxCloseness == 0 ){ return 0; }
if( is.string( node ) ){
// from is a selector string
var node = (cy.filter( node )[0]).id();
} else {
// from is a node
var node = node.id();
}
return closenesses[ node ] / maxCloseness;
}
};
},
// Implemented from pseudocode from wikipedia
closenessCentrality: function( options ){
options = options || {};
// root - mandatory!
if( options.root != null ){
if( is.string( options.root ) ){
// use it as a selector, e.g. "#rootID
var root = this.filter( options.root )[0];
} else {
var root = options.root[0];
}
} else {
return undefined;
}
// weight - optional
if( options.weight != null && is.fn( options.weight ) ){
var weight = options.weight;
} else {
var weight = function(){return 1;};
}
// directed - optional
if( options.directed != null && is.bool( options.directed ) ){
var directed = options.directed;
} else {
var directed = false;
}
var harmonic = options.harmonic;
if( harmonic === undefined ){
harmonic = true;
}
// we need distance from this node to every other node
var dijkstra = this.dijkstra( {
root: root,
weight: weight,
directed: directed
} );
var totalDistance = 0;
var nodes = this.nodes();
for( var i = 0; i < nodes.length; i++ ){
if( nodes[ i ].id() != root.id() ){
var d = dijkstra.distanceTo( nodes[ i ] );
if( harmonic ){
totalDistance += 1 / d;
} else {
totalDistance += d;
}
}
}
return harmonic ? totalDistance : 1 / totalDistance;
} // closenessCentrality
}); // elesfn
// nice, short mathemathical alias
elesfn.cc = elesfn.closenessCentrality;
elesfn.ccn = elesfn.closenessCentralityNormalised = elesfn.closenessCentralityNormalized;
module.exports = elesfn;
},{"../../is":91}],8:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
var util = _dereq_( '../../util' );
var elesfn = ({
degreeCentralityNormalized: function( options ){
options = options || {};
var cy = this.cy();
// directed - optional
if( options.directed != null ){
var directed = options.directed;
} else {
var directed = false;
}
var nodes = this.nodes();
var numNodes = nodes.length;
if( !directed ){
var degrees = {};
var maxDegree = 0;
for( var i = 0; i < numNodes; i++ ){
var node = nodes[ i ];
// add current node to the current options object and call degreeCentrality
var currDegree = this.degreeCentrality( util.extend( {}, options, {root: node} ) );
if( maxDegree < currDegree.degree )
maxDegree = currDegree.degree;
degrees[ node.id() ] = currDegree.degree;
}
return {
degree: function( node ){
if( maxDegree == 0 )
return 0;
if( is.string( node ) ){
// from is a selector string
var node = (cy.filter( node )[0]).id();
} else {
// from is a node
var node = node.id();
}
return degrees[ node ] / maxDegree;
}
};
} else {
var indegrees = {};
var outdegrees = {};
var maxIndegree = 0;
var maxOutdegree = 0;
for( var i = 0; i < numNodes; i++ ){
var node = nodes[ i ];
// add current node to the current options object and call degreeCentrality
var currDegree = this.degreeCentrality( util.extend( {}, options, {root: node} ) );
if( maxIndegree < currDegree.indegree )
maxIndegree = currDegree.indegree;
if( maxOutdegree < currDegree.outdegree )
maxOutdegree = currDegree.outdegree;
indegrees[ node.id() ] = currDegree.indegree;
outdegrees[ node.id() ] = currDegree.outdegree;
}
return {
indegree: function( node ){
if ( maxIndegree == 0 )
return 0;
if( is.string( node ) ){
// from is a selector string
var node = (cy.filter( node )[0]).id();
} else {
// from is a node
var node = node.id();
}
return indegrees[ node ] / maxIndegree;
},
outdegree: function( node ){
if ( maxOutdegree == 0 )
return 0;
if( is.string( node ) ){
// from is a selector string
var node = (cy.filter( node )[0]).id();
} else {
// from is a node
var node = node.id();
}
return outdegrees[ node ] / maxOutdegree;
}
};
}
}, // degreeCentralityNormalized
// Implemented from the algorithm in Opsahl's paper
// "Node centrality in weighted networks: Generalizing degree and shortest paths"
// check the heading 2 "Degree"
degreeCentrality: function( options ){
options = options || {};
var callingEles = this;
// root - mandatory!
if( options != null && options.root != null ){
var root = is.string( options.root ) ? this.filter( options.root )[0] : options.root[0];
} else {
return undefined;
}
// weight - optional
if( options.weight != null && is.fn( options.weight ) ){
var weightFn = options.weight;
} else {
// If not specified, assume each edge has equal weight (1)
var weightFn = function( e ){
return 1;
};
}
// directed - optional
if( options.directed != null ){
var directed = options.directed;
} else {
var directed = false;
}
// alpha - optional
if( options.alpha != null && is.number( options.alpha ) ){
var alpha = options.alpha;
} else {
alpha = 0;
}
if( !directed ){
var connEdges = root.connectedEdges().intersection( callingEles );
var k = connEdges.length;
var s = 0;
// Now, sum edge weights
for( var i = 0; i < connEdges.length; i++ ){
var edge = connEdges[ i ];
s += weightFn( edge );
}
return {
degree: Math.pow( k, 1 - alpha ) * Math.pow( s, alpha )
};
} else {
var incoming = root.connectedEdges( 'edge[target = "' + root.id() + '"]' ).intersection( callingEles );
var outgoing = root.connectedEdges( 'edge[source = "' + root.id() + '"]' ).intersection( callingEles );
var k_in = incoming.length;
var k_out = outgoing.length;
var s_in = 0;
var s_out = 0;
// Now, sum incoming edge weights
for( var i = 0; i < incoming.length; i++ ){
var edge = incoming[ i ];
s_in += weightFn( edge );
}
// Now, sum outgoing edge weights
for( var i = 0; i < outgoing.length; i++ ){
var edge = outgoing[ i ];
s_out += weightFn( edge );
}
return {
indegree: Math.pow( k_in, 1 - alpha ) * Math.pow( s_in, alpha ),
outdegree: Math.pow( k_out, 1 - alpha ) * Math.pow( s_out, alpha )
};
}
} // degreeCentrality
}); // elesfn
// nice, short mathemathical alias
elesfn.dc = elesfn.degreeCentrality;
elesfn.dcn = elesfn.degreeCentralityNormalised = elesfn.degreeCentralityNormalized;
module.exports = elesfn;
},{"../../is":91,"../../util":108}],9:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
var Heap = _dereq_( '../../heap' );
var elesfn = ({
dijkstra: function( root, weightFn, directed ){
var options;
if( is.plainObject( root ) && !is.elementOrCollection( root ) ){
options = root;
root = options.root;
weightFn = options.weight;
directed = options.directed;
}
var cy = this._private.cy;
weightFn = is.fn( weightFn ) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1)
var source = is.string( root ) ? this.filter( root )[0] : root[0];
var dist = {};
var prev = {};
var knownDist = {};
var edges = this.edges().filter( function( ele ){ return !ele.isLoop(); } );
var nodes = this.nodes();
var getDist = function( node ){
return dist[ node.id() ];
};
var setDist = function( node, d ){
dist[ node.id() ] = d;
Q.updateItem( node );
};
var Q = new Heap( function( a, b ){
return getDist( a ) - getDist( b );
} );
for( var i = 0; i < nodes.length; i++ ){
var node = nodes[ i ];
dist[ node.id() ] = node.same( source ) ? 0 : Infinity;
Q.push( node );
}
var distBetween = function( u, v ){
var uvs = ( directed ? u.edgesTo( v ) : u.edgesWith( v ) ).intersect( edges );
var smallestDistance = Infinity;
var smallestEdge;
for( var i = 0; i < uvs.length; i++ ){
var edge = uvs[ i ];
var weight = weightFn( edge );
if( weight < smallestDistance || !smallestEdge ){
smallestDistance = weight;
smallestEdge = edge;
}
}
return {
edge: smallestEdge,
dist: smallestDistance
};
};
while( Q.size() > 0 ){
var u = Q.pop();
var smalletsDist = getDist( u );
var uid = u.id();
knownDist[ uid ] = smalletsDist;
if( smalletsDist === Math.Infinite ){
break;
}
var neighbors = u.neighborhood().intersect( nodes );
for( var i = 0; i < neighbors.length; i++ ){
var v = neighbors[ i ];
var vid = v.id();
var vDist = distBetween( u, v );
var alt = smalletsDist + vDist.dist;
if( alt < getDist( v ) ){
setDist( v, alt );
prev[ vid ] = {
node: u,
edge: vDist.edge
};
}
} // for
} // while
return {
distanceTo: function( node ){
var target = is.string( node ) ? nodes.filter( node )[0] : node[0];
return knownDist[ target.id() ];
},
pathTo: function( node ){
var target = is.string( node ) ? nodes.filter( node )[0] : node[0];
var S = [];
var u = target;
if( target.length > 0 ){
S.unshift( target );
while( prev[ u.id() ] ){
var p = prev[ u.id() ];
S.unshift( p.edge );
S.unshift( p.node );
u = p.node;
}
}
return cy.collection( S );
}
};
}
});
module.exports = elesfn;
},{"../../heap":89,"../../is":91}],10:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
var elesfn = ({
// Implemented from pseudocode from wikipedia
floydWarshall: function( options ){
options = options || {};
var cy = this.cy();
// Weight function - optional
if( options.weight != null && is.fn( options.weight ) ){
var weightFn = options.weight;
} else {
// If not specified, assume each edge has equal weight (1)
var weightFn = function( e ){return 1;};
}
// directed - optional
if( options.directed != null ){
var directed = options.directed;
} else {
var directed = false;
}
var edges = this.edges().stdFilter( function( e ){ return !e.isLoop(); } );
var nodes = this.nodes();
var numNodes = nodes.length;
// mapping: node id -> position in nodes array
var id2position = {};
for( var i = 0; i < numNodes; i++ ){
id2position[ nodes[ i ].id() ] = i;
}
// Initialize distance matrix
var dist = [];
for( var i = 0; i < numNodes; i++ ){
var newRow = new Array( numNodes );
for( var j = 0; j < numNodes; j++ ){
if( i == j ){
newRow[ j ] = 0;
} else {
newRow[ j ] = Infinity;
}
}
dist.push( newRow );
}
// Initialize matrix used for path reconstruction
// Initialize distance matrix
var next = [];
var edgeNext = [];
var initMatrix = function( next ){
for( var i = 0; i < numNodes; i++ ){
var newRow = new Array( numNodes );
for( var j = 0; j < numNodes; j++ ){
newRow[ j ] = undefined;
}
next.push( newRow );
}
};
initMatrix( next );
initMatrix( edgeNext );
// Process edges
for( var i = 0; i < edges.length ; i++ ){
var sourceIndex = id2position[ edges[ i ].source().id() ];
var targetIndex = id2position[ edges[ i ].target().id() ];
var weight = weightFn( 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( edges[ i ] );
// Check if already process another edge between same 2 nodes
if( dist[ sourceIndex ][ targetIndex ] > weight ){
dist[ sourceIndex ][ targetIndex ] = weight;
next[ sourceIndex ][ targetIndex ] = targetIndex;
edgeNext[ sourceIndex ][ targetIndex ] = edges[ i ];
}
}
}
// Main loop
for( var k = 0; k < numNodes; k++ ){
for( var i = 0; i < numNodes; i++ ){
for( var j = 0; j < numNodes; j++ ){
if( dist[ i ][ k ] + dist[ k ][ j ] < dist[ i ][ j ] ){
dist[ i ][ j ] = dist[ i ][ k ] + dist[ k ][ j ];
next[ i ][ j ] = next[ i ][ k ];
}
}
}
}
// Build result object
var position2id = [];
for( var i = 0; i < numNodes; i++ ){
position2id.push( nodes[ i ].id() );
}
var res = {
distance: function( from, to ){
if( is.string( from ) ){
// from is a selector string
var fromId = (cy.filter( from )[0]).id();
} else {
// from is a node
var fromId = from.id();
}
if( is.string( to ) ){
// to is a selector string
var toId = (cy.filter( to )[0]).id();
} else {
// to is a node
var toId = to.id();
}
return dist[ id2position[ fromId ] ][ id2position[ toId ] ];
},
path: function( from, to ){
var reconstructPathAux = function( from, to, next, position2id, edgeNext ){
if( from === to ){
return cy.getElementById( position2id[ from ] );
}
if( next[ from ][ to ] === undefined ){
return undefined;
}
var path = [ cy.getElementById( position2id[ from ] ) ];
var prev = from;
while( from !== to ){
prev = from;
from = next[ from ][ to ];
var edge = edgeNext[ prev ][ from ];
path.push( edge );
path.push( cy.getElementById( position2id[ from ] ) );
}
return path;
};
if( is.string( from ) ){
// from is a selector string
var fromId = (cy.filter( from )[0]).id();
} else {
// from is a node
var fromId = from.id();
}
if( is.string( to ) ){
// to is a selector string
var toId = (cy.filter( to )[0]).id();
} else {
// to is a node
var toId = to.id();
}
var pathArr = reconstructPathAux( id2position[ fromId ],
id2position[ toId ],
next,
position2id,
edgeNext );
return cy.collection( pathArr );
}
};
return res;
} // floydWarshall
}); // elesfn
module.exports = elesfn;
},{"../../is":91}],11:[function(_dereq_,module,exports){
'use strict';
var util = _dereq_( '../../util' );
var elesfn = {};
[
_dereq_( './bfs-dfs' ),
_dereq_( './dijkstra' ),
_dereq_( './kruskal' ),
_dereq_( './a-star' ),
_dereq_( './floyd-warshall' ),
_dereq_( './bellman-ford' ),
_dereq_( './kerger-stein' ),
_dereq_( './page-rank' ),
_dereq_( './degree-centrality' ),
_dereq_( './closeness-centrality' ),
_dereq_( './betweenness-centrality' )
].forEach( function( props ){
util.extend( elesfn, props );
} );
module.exports = elesfn;
},{"../../util":108,"./a-star":3,"./bellman-ford":4,"./betweenness-centrality":5,"./bfs-dfs":6,"./closeness-centrality":7,"./degree-centrality":8,"./dijkstra":9,"./floyd-warshall":10,"./kerger-stein":12,"./kruskal":13,"./page-rank":14}],12:[function(_dereq_,module,exports){
'use strict';
var util = _dereq_( '../../util' );
var elesfn = ({
// Computes the minimum cut of an undirected graph
// Returns the correct answer with high probability
kargerStein: function( options ){
var eles = this;
options = options || {};
// Function which colapses 2 (meta) nodes into one
// Updates the remaining edge lists
// Receives as a paramater the edge which causes the collapse
var colapse = function( edgeIndex, nodeMap, remainingEdges ){
var edgeInfo = remainingEdges[ edgeIndex ];
var sourceIn = edgeInfo[1];
var targetIn = edgeInfo[2];
var partition1 = nodeMap[ sourceIn ];
var partition2 = nodeMap[ targetIn ];
// Delete all edges between partition1 and partition2
var newEdges = remainingEdges.filter( function( edge ){
if( nodeMap[ edge[1] ] === partition1 && nodeMap[ edge[2] ] === partition2 ){
return false;
}
if( nodeMap[ edge[1] ] === partition2 && nodeMap[ edge[2] ] === partition1 ){
return false;
}
return true;
} );
// All edges pointing to partition2 should now point to partition1
for( var i = 0; i < newEdges.length; i++ ){
var edge = newEdges[ i ];
if( edge[1] === partition2 ){ // Check source
newEdges[ i ] = edge.slice( 0 );
newEdges[ i ][1] = partition1;
} else if( edge[2] === partition2 ){ // Check target
newEdges[ i ] = edge.slice( 0 );
newEdges[ i ][2] = partition1;
}
}
// Move all nodes from partition2 to partition1
for( var i = 0; i < nodeMap.length; i++ ){
if( nodeMap[ i ] === partition2 ){
nodeMap[ i ] = partition1;
}
}
return newEdges;
};
// Contracts a graph until we reach a certain number of meta nodes
var contractUntil = function( metaNodeMap,
remainingEdges,
size,
sizeLimit ){
// Stop condition
if( size <= sizeLimit ){
return remainingEdges;
}
// Choose an edge randomly
var edgeIndex = Math.floor( (Math.random() * remainingEdges.length) );
// Colapse graph based on edge
var newEdges = colapse( edgeIndex, metaNodeMap, remainingEdges );
return contractUntil( metaNodeMap,
newEdges,
size - 1,
sizeLimit );
};
var cy = this._private.cy;
var edges = this.edges().stdFilter( function( e ){ return !e.isLoop(); } );
var nodes = this.nodes();
var numNodes = nodes.length;
var numEdges = edges.length;
var numIter = Math.ceil( Math.pow( Math.log( numNodes ) / Math.LN2, 2 ) );
var stopSize = Math.floor( numNodes / Math.sqrt( 2 ) );
if( numNodes < 2 ){
util.error( 'At least 2 nodes are required for Karger-Stein algorithm' );
return undefined;
}
// Create numerical identifiers for each node
// mapping: node id -> position in nodes array
// for reverse mapping, simply use nodes array
var id2position = {};
for( var i = 0; i < numNodes; i++ ){
id2position[ nodes[ i ].id() ] = i;
}
// Now store edge destination as indexes
// Format for each edge (edge index, source node index, target node index)
var edgeIndexes = [];
for( var i = 0; i < numEdges; i++ ){
var e = edges[ i ];
edgeIndexes.push( [ i, id2position[ e.source().id() ], id2position[ e.target().id() ] ] );
}
// We will store the best cut found here
var minCutSize = Infinity;
var minCut;
// Initial meta node partition
var originalMetaNode = [];
for( var i = 0; i < numNodes; i++ ){
originalMetaNode.push( i );
}
// Main loop
for( var iter = 0; iter <= numIter; iter++ ){
// Create new meta node partition
var metaNodeMap = originalMetaNode.slice( 0 );
// Contract until stop point (stopSize nodes)
var edgesState = contractUntil( metaNodeMap, edgeIndexes, numNodes, stopSize );
// Create a copy of the colapsed nodes state
var metaNodeMap2 = metaNodeMap.slice( 0 );
// Run 2 iterations starting in the stop state
var res1 = contractUntil( metaNodeMap, edgesState, stopSize, 2 );
var res2 = contractUntil( metaNodeMap2, edgesState, stopSize, 2 );
// Is any of the 2 results the best cut so far?
if( res1.length <= res2.length && res1.length < minCutSize ){
minCutSize = res1.length;
minCut = [ res1, metaNodeMap ];
} else if( res2.length <= res1.length && res2.length < minCutSize ){
minCutSize = res2.length;
minCut = [ res2, metaNodeMap2 ];
}
} // end of main loop
// Construct result
var resEdges = (minCut[0]).map( function( e ){ return edges[ e[0] ]; } );
var partition1 = [];
var partition2 = [];
// traverse metaNodeMap for best cut
var witnessNodePartition = minCut[1][0];
for( var i = 0; i < minCut[1].length; i++ ){
var partitionId = minCut[1][ i ];
if( partitionId === witnessNodePartition ){
partition1.push( nodes[ i ] );
} else {
partition2.push( nodes[ i ] );
}
}
var ret = {
cut: eles.spawn( cy, resEdges ),
partition1: eles.spawn( partition1 ),
partition2: eles.spawn( partition2 )
};
return ret;
}
}); // elesfn
module.exports = elesfn;
},{"../../util":108}],13:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
// search, spanning trees, etc
var elesfn = ({
// kruskal's algorithm (finds min spanning tree, assuming undirected graph)
// implemented from pseudocode from wikipedia
kruskal: function( weightFn ){
var cy = this.cy();
weightFn = is.fn( weightFn ) ? weightFn : function(){ return 1; }; // if not specified, assume each edge has equal weight (1)
function findSet( ele ){
for( var i = 0; i < forest.length; i++ ){
var eles = forest[ i ];
if( eles.anySame( ele ) ){
return {
eles: eles,
index: i
};
}
}
}
var A = cy.collection( cy, [] );
var forest = [];
var nodes = this.nodes();
for( var i = 0; i < nodes.length; i++ ){
forest.push( nodes[ i ].collection() );
}
var edges = this.edges();
var S = edges.toArray().sort( function( a, b ){
var weightA = weightFn( a );
var weightB = weightFn( 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 );
}
});
module.exports = elesfn;
},{"../../is":91}],14:[function(_dereq_,module,exports){
'use strict';
var is = _dereq_( '../../is' );
var elesfn = ({
pageRank: function( options ){
options = options || {};
var normalizeVector = function( vector ){
var length =