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;