cytoscape
Version:
Graph theory (a.k.a. network) library for analysis and visualisation
425 lines (323 loc) • 10.7 kB
JavaScript
import * as util from '../util';
import * as is from '../is';
import cache from './cache-traversal-call';
let elesfn = {};
// DAG functions
////////////////
let defineDagExtremity = function( params ){
return function dagExtremityImpl( selector ){
let eles = this;
let ret = [];
for( let i = 0; i < eles.length; i++ ){
let ele = eles[ i ];
if( !ele.isNode() ){
continue;
}
let disqualified = false;
let edges = ele.connectedEdges();
for( let j = 0; j < edges.length; j++ ){
let edge = edges[j];
let src = edge.source();
let tgt = edge.target();
if(
( params.noIncomingEdges && tgt === ele && src !== ele )
|| ( params.noOutgoingEdges && src === ele && tgt !== ele )
){
disqualified = true;
break;
}
}
if( !disqualified ){
ret.push( ele );
}
}
return this.spawn( ret, true ).filter( selector );
};
};
let defineDagOneHop = function( params ){
return function( selector ){
let eles = this;
let oEles = [];
for( let i = 0; i < eles.length; i++ ){
let ele = eles[ i ];
if( !ele.isNode() ){ continue; }
let edges = ele.connectedEdges();
for( let j = 0; j < edges.length; j++ ){
let edge = edges[ j ];
let src = edge.source();
let tgt = edge.target();
if( params.outgoing && src === ele ){
oEles.push( edge );
oEles.push( tgt );
} else if( params.incoming && tgt === ele ){
oEles.push( edge );
oEles.push( src );
}
}
}
return this.spawn( oEles, true ).filter( selector );
};
};
let defineDagAllHops = function( params ){
return function( selector ){
let eles = this;
let sEles = [];
let sElesIds = {};
for( ;; ){
let next = params.outgoing ? eles.outgoers() : eles.incomers();
if( next.length === 0 ){ break; } // done if none left
let newNext = false;
for( let i = 0; i < next.length; i++ ){
let n = next[ i ];
let nid = n.id();
if( !sElesIds[ nid ] ){
sElesIds[ nid ] = true;
sEles.push( n );
newNext = true;
}
}
if( !newNext ){ break; } // done if touched all outgoers already
eles = next;
}
return this.spawn( sEles, true ).filter( selector );
};
};
elesfn.clearTraversalCache = function( ){
for( let i = 0; i < this.length; i++ ){
this[i]._private.traversalCache = null;
}
};
util.extend( elesfn, {
// get the root nodes in the DAG
roots: defineDagExtremity({ noIncomingEdges: true }),
// get the leaf nodes in the DAG
leaves: defineDagExtremity({ noOutgoingEdges: true }),
// normally called children in graph theory
// these nodes =edges=> outgoing nodes
outgoers: cache( defineDagOneHop({ outgoing: true }) , 'outgoers' ),
// aka DAG descendants
successors: defineDagAllHops({ outgoing: true }),
// normally called parents in graph theory
// these nodes <=edges= incoming nodes
incomers: cache( defineDagOneHop({ incoming: true }), 'incomers' ),
// aka DAG ancestors
predecessors: defineDagAllHops({ incoming: true })
} );
// Neighbourhood functions
//////////////////////////
util.extend( elesfn, {
neighborhood: cache(function( selector ){
let elements = [];
let nodes = this.nodes();
for( let i = 0; i < nodes.length; i++ ){ // for all nodes
let node = nodes[ i ];
let connectedEdges = node.connectedEdges();
// for each connected edge, add the edge and the other node
for( let j = 0; j < connectedEdges.length; j++ ){
let edge = connectedEdges[ j ];
let src = edge.source();
let tgt = edge.target();
let otherNode = node === src ? tgt : src;
// need check in case of loop
if( otherNode.length > 0 ){
elements.push( otherNode[0] ); // add node 1 hop away
}
// add connected edge
elements.push( edge[0] );
}
}
return ( this.spawn( elements, true ) ).filter( selector );
}, 'neighborhood'),
closedNeighborhood: function( selector ){
return this.neighborhood().add( this ).filter( selector );
},
openNeighborhood: function( selector ){
return this.neighborhood( selector );
}
} );
// aliases
elesfn.neighbourhood = elesfn.neighborhood;
elesfn.closedNeighbourhood = elesfn.closedNeighborhood;
elesfn.openNeighbourhood = elesfn.openNeighborhood;
// Edge functions
/////////////////
util.extend( elesfn, {
source: cache(function sourceImpl( selector ){
let ele = this[0];
let src;
if( ele ){
src = ele._private.source || ele.cy().collection();
}
return src && selector ? src.filter( selector ) : src;
}, 'source'),
target: cache(function targetImpl( selector ){
let ele = this[0];
let tgt;
if( ele ){
tgt = ele._private.target || ele.cy().collection();
}
return tgt && selector ? tgt.filter( selector ) : tgt;
}, 'target'),
sources: defineSourceFunction( {
attr: 'source'
} ),
targets: defineSourceFunction( {
attr: 'target'
} )
} );
function defineSourceFunction( params ){
return function sourceImpl( selector ){
let sources = [];
for( let i = 0; i < this.length; i++ ){
let ele = this[ i ];
let src = ele._private[ params.attr ];
if( src ){
sources.push( src );
}
}
return this.spawn( sources, true ).filter( selector );
};
}
util.extend( elesfn, {
edgesWith: cache( defineEdgesWithFunction(), 'edgesWith' ),
edgesTo: cache( defineEdgesWithFunction( {
thisIsSrc: true
} ), 'edgesTo' )
} );
function defineEdgesWithFunction( params ){
return function edgesWithImpl( otherNodes ){
let elements = [];
let cy = this._private.cy;
let p = params || {};
// get elements if a selector is specified
if( is.string( otherNodes ) ){
otherNodes = cy.$( otherNodes );
}
for( let h = 0; h < otherNodes.length; h++ ){
let edges = otherNodes[ h ]._private.edges;
for( let i = 0; i < edges.length; i++ ){
let edge = edges[ i ];
let edgeData = edge._private.data;
let thisToOther = this.hasElementWithId( edgeData.source ) && otherNodes.hasElementWithId( edgeData.target );
let otherToThis = otherNodes.hasElementWithId( edgeData.source ) && this.hasElementWithId( edgeData.target );
let edgeConnectsThisAndOther = thisToOther || otherToThis;
if( !edgeConnectsThisAndOther ){ continue; }
if( p.thisIsSrc || p.thisIsTgt ){
if( p.thisIsSrc && !thisToOther ){ continue; }
if( p.thisIsTgt && !otherToThis ){ continue; }
}
elements.push( edge );
}
}
return this.spawn( elements, true );
};
}
util.extend( elesfn, {
connectedEdges: cache(function( selector ){
let retEles = [];
let eles = this;
for( let i = 0; i < eles.length; i++ ){
let node = eles[ i ];
if( !node.isNode() ){ continue; }
let edges = node._private.edges;
for( let j = 0; j < edges.length; j++ ){
let edge = edges[ j ];
retEles.push( edge );
}
}
return this.spawn( retEles, true ).filter( selector );
}, 'connectedEdges'),
connectedNodes: cache(function( selector ){
let retEles = [];
let eles = this;
for( let i = 0; i < eles.length; i++ ){
let edge = eles[ i ];
if( !edge.isEdge() ){ continue; }
retEles.push( edge.source()[0] );
retEles.push( edge.target()[0] );
}
return this.spawn( retEles, true ).filter( selector );
}, 'connectedNodes'),
parallelEdges: cache( defineParallelEdgesFunction(), 'parallelEdges' ),
codirectedEdges: cache( defineParallelEdgesFunction( {
codirected: true
} ), 'codirectedEdges' )
} );
function defineParallelEdgesFunction( params ){
let defaults = {
codirected: false
};
params = util.extend( {}, defaults, params );
return function parallelEdgesImpl( selector ){ // micro-optimised for renderer
let elements = [];
let edges = this.edges();
let p = params;
// look at all the edges in the collection
for( let i = 0; i < edges.length; i++ ){
let edge1 = edges[ i ];
let edge1_p = edge1._private;
let src1 = edge1_p.source;
let srcid1 = src1._private.data.id;
let tgtid1 = edge1_p.data.target;
let srcEdges1 = src1._private.edges;
// look at edges connected to the src node of this edge
for( let j = 0; j < srcEdges1.length; j++ ){
let edge2 = srcEdges1[ j ];
let edge2data = edge2._private.data;
let tgtid2 = edge2data.target;
let srcid2 = edge2data.source;
let codirected = tgtid2 === tgtid1 && srcid2 === srcid1;
let oppdirected = srcid1 === tgtid2 && tgtid1 === srcid2;
if( (p.codirected && codirected) || (!p.codirected && (codirected || oppdirected)) ){
elements.push( edge2 );
}
}
}
return this.spawn( elements, true ).filter( selector );
};
}
// Misc functions
/////////////////
util.extend( elesfn, {
components: function(root){
let self = this;
let cy = self.cy();
let visited = cy.collection();
let unvisited = root == null ? self.nodes() : root.nodes();
let components = [];
if( root != null && unvisited.empty() ){ // root may contain only edges
unvisited = root.sources(); // doesn't matter which node to use (undirected), so just use the source sides
}
let visitInComponent = ( node, component ) => {
visited.merge( node );
unvisited.unmerge( node );
component.merge( node );
};
if( unvisited.empty() ){ return self.spawn(); }
do { // each iteration yields a component
let cmpt = cy.collection();
components.push( cmpt );
let root = unvisited[0];
visitInComponent( root, cmpt );
self.bfs({
directed: false,
roots: root,
visit: v => visitInComponent( v, cmpt )
} );
cmpt.forEach(node => {
node.connectedEdges().forEach(e => { // connectedEdges() usually cached
if( self.has(e) && cmpt.has(e.source()) && cmpt.has(e.target()) ){ // has() is cheap
cmpt.merge(e); // forEach() only considers nodes -- sets N at call time
}
});
});
} while( unvisited.length > 0 );
return components;
},
component: function(){
let ele = this[0];
return ele.cy().mutableElements().components( ele )[0];
}
} );
elesfn.componentsOf = elesfn.components;
export default elesfn;