UNPKG

sbgn-renderer

Version:

A library that renders SBGN

1,937 lines (1,516 loc) 2.35 MB
(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 =