docs: function( roots, fn, directed ){
        directed = arguments.length === 1 && !$$.is.fn(fn) ? fn : directed;
        fn = $$.is.fn(fn) ? fn : function(){};
        var cy = this._private.cy;
        var v = $$.is.string(roots) ? this.filter(roots) : roots;
        var Q = [];
        var connectedEles = [];
        var connectedFrom = {};
        var id2depth = {};
        var V = {};
        var j = 0;
        var found;
        var nodes = this.nodes();
        var edges = this.edges();
  
        // enqueue v
        for( var i = 0; i < v.length; i++ ){
          if( v[i].isNode() ){
            Q.unshift( v[i] );
            V[ v[i].id() ] = true; 
  
            connectedEles.push( v[i] );
            id2depth[ v[i].id() ] = 0;
          }
        }
  
        while( Q.length !== 0 ){
          var v = Q.shift();
          var depth = id2depth[ v.id() ];
          var ret = fn.call(v, j++, depth);
  
          if( ret === true ){
            found = v;
            break;
          }
  
          if( ret === false ){
            break;
          }
  
          var vwEdges = v.connectedEdges(directed ? '[source = "' + v.id() + '"]' : undefined).intersect( edges );
          for( var i = 0; i < vwEdges.length; i++ ){
            var e = vwEdges[i];
            var w = e.connectedNodes('[id != "' + v.id() + '"]').intersect( nodes );
  
            if( w.length !== 0 && !V[ w.id() ] ){
              w = w[0];
  
              Q.push( w );
              V[ w.id() ] = true;
  
              id2depth[ w.id() ] = id2depth[ v.id() ] + 1;
  
              connectedEles.push( w );
              connectedEles.push( e );
            }
          }
          
        }
  
        return {
          path: new $$.Collection( cy, connectedEles ),
          found: new $$.Collection( cy, found )
        };
      }