UNPKG

@gmod/jbrowse

Version:

JBrowse - client-side genome browser

220 lines (192 loc) 7.4 kB
define( [ 'dojo/_base/declare', 'dojo/_base/lang', 'dojo/_base/array', 'dojo/Deferred', 'JBrowse/Util', 'JBrowse/Model/SimpleFeature', 'JBrowse/Store/SeqFeature', 'JBrowse/Store/DeferredFeaturesMixin', 'JBrowse/Store/DeferredStatsMixin', 'JBrowse/Store/SeqFeature/GlobalStatsEstimationMixin', 'JBrowse/Model/XHRBlob', './GTF/Parser' ], function( declare, lang, array, Deferred, Util, SimpleFeature, SeqFeatureStore, DeferredFeatures, DeferredStats, GlobalStatsEstimationMixin, XHRBlob, Parser ) { return declare([ SeqFeatureStore, DeferredFeatures, DeferredStats, GlobalStatsEstimationMixin ], /** * @lends JBrowse.Store.SeqFeature.GTF */ { constructor: function( args ) { this.data = args.blob || new XHRBlob( this.resolveUrl( args.urlTemplate ) ); this.features = []; this._loadFeatures(); }, _loadFeatures: function() { var thisB = this; var features = this.bareFeatures = []; var featuresSorted = true; var seenRefs = this.refSeqs = {}; var parser = new Parser( { featureCallback: function(fs) { array.forEach( fs, function( feature ) { var prevFeature = features[ features.length-1 ]; var regRefName = thisB.browser.regularizeReferenceName( feature.seq_id ); if( regRefName in seenRefs && prevFeature && prevFeature.seq_id != feature.seq_id ) featuresSorted = false; if( prevFeature && prevFeature.seq_id == feature.seq_id && feature.start < prevFeature.start ) featuresSorted = false; if( !( regRefName in seenRefs )) seenRefs[ regRefName ] = features.length; features.push( feature ); }); }, endCallback: function() { if( ! featuresSorted ) { features.sort( thisB._compareFeatureData ); // need to rebuild the refseq index if changing the sort order thisB._rebuildRefSeqs( features ); } thisB._estimateGlobalStats() .then( function( stats ) { thisB.globalStats = stats; thisB._deferred.stats.resolve(); }); thisB._deferred.features.resolve( features ); } }); var fail = lang.hitch( this, '_failAllDeferred' ); // parse the whole file and store it this.data.fetchLines( function( line ) { try { parser.addLine(line); } catch(e) { fail('Error parsing GTF.'); throw e; } }, lang.hitch( parser, 'finish' ), fail ); }, _rebuildRefSeqs: function( features ) { var refs = {}; for( var i = 0; i<features.length; i++ ) { var regRefName = this.browser.regularizeReferenceName( features[i].seq_id ); if( !( regRefName in refs ) ) refs[regRefName] = i; } this.refSeqs = refs; }, _compareFeatureData: function( a, b ) { if( a.seq_id < b.seq_id ) return -1; else if( a.seq_id > b.seq_id ) return 1; return a.start - b.start; }, _getFeatures: function( query, featureCallback, finishedCallback, errorCallback ) { var thisB = this; thisB._deferred.features.then( function() { thisB._search( query, featureCallback, finishedCallback, errorCallback ); }); }, _search: function( query, featureCallback, finishCallback, errorCallback ) { // search in this.features, which are sorted // by ref and start coordinate, to find the beginning of the // relevant range var bare = this.bareFeatures; var converted = this.features; var refName = this.browser.regularizeReferenceName( query.ref ); var i = this.refSeqs[ refName ]; if( !( i >= 0 )) { finishCallback(); return; } var checkEnd = 'start' in query ? function(f) { return f.get('end') >= query.start; } : function() { return true; }; for( ; i<bare.length; i++ ) { // lazily convert the bare feature data to JBrowse features var f = converted[i] || ( converted[i] = function(b,i) { bare[i] = false; return this._formatFeature( b ); }.call( this, bare[i], i ) ); // features are sorted by ref seq and start coord, so we // can stop if we are past the ref seq or the end of the // query region if( f._reg_seq_id != refName || f.get('start') > query.end ) break; if( checkEnd( f ) ) { this.applyFeatureTransforms([f]) .forEach(featureCallback) } } finishCallback(); }, supportsFeatureTransforms: true, _formatFeature: function( data ) { var f = new SimpleFeature({ data: this._featureData( data ), id: (data.attributes.ID||[])[0] }); f._reg_seq_id = this.browser.regularizeReferenceName( data.seq_id ); return f; }, _featureData: function( data ) { var f = lang.mixin( {}, data ); delete f.child_features; delete f.derived_features; delete f.attributes; f.start -= 1; // convert to interbase for( var a in data.attributes ) { f[ a.toLowerCase() ] = data.attributes[a].join(','); } var sub = array.map( Util.flattenOneLevel( data.child_features ), this._featureData, this ); if( sub.length ) f.subfeatures = sub; return f; }, /** * Interrogate whether a store has data for a given reference * sequence. Calls the given callback with either true or false. * * Implemented as a binary interrogation because some stores are * smart enough to regularize reference sequence names, while * others are not. */ hasRefSeq: function( seqName, callback, errorCallback ) { var thisB = this; this._deferred.features.then( function() { callback( thisB.browser.regularizeReferenceName( seqName ) in thisB.refSeqs ); }); }, saveStore: function() { return { urlTemplate: this.config.blob.url }; } }); });