UNPKG

@gmod/jbrowse

Version:

JBrowse - client-side genome browser

201 lines (179 loc) 7.77 kB
/** * Store that encapsulates another store, which is expected to have * features in it that have CIGAR and MD attributes. Produces * features that include SNP allele frequencies. */ define([ 'dojo/_base/declare', 'dojo/_base/array', 'JBrowse/Util', 'JBrowse/Store/SeqFeature', 'JBrowse/Model/NestedFrequencyTable', 'JBrowse/Model/CoverageFeature', './_MismatchesMixin' ], function( declare, array, Util, SeqFeatureStore, NestedFrequencyTable, CoverageFeature, MismatchesMixin ) { return declare( [ SeqFeatureStore, MismatchesMixin ], { constructor: function( args ) { this.store = args.store; this.filter = args.filter || function() { return true; }; }, getGlobalStats: function( callback, errorCallback ) { callback( {} ); }, _defaultConfig: function() { return Util.deepUpdate( dojo.clone( this.inherited(arguments) ), { mismatchScale: 1/10 } ); }, getFeatures: function( query, featureCallback, finishCallback, errorCallback ) { var thisB = this; var leftBase = query.start; var rightBase = query.end; var scale = query.scale || query.basesPerSpan && 1/query.basesPerSpan || 10; // px/bp var widthBp = rightBase-leftBase; var widthPx = widthBp * scale; var binWidth = function() { var bpPerPixel = 1/scale; if( bpPerPixel <= 1/thisB.config.mismatchScale ) { return 1; } else { return Math.ceil( bpPerPixel ); } }(); function binNumber( bp ) { return Math.floor( (bp-leftBase) / binWidth ); }; // init coverage bins var binMax = Math.ceil( (rightBase-leftBase)/binWidth ); var coverageBins = new Array( binMax ); for( var i = 0; i < binMax; i++ ) { coverageBins[i] = new NestedFrequencyTable(); if( binWidth == 1 ) coverageBins[i].snpsCounted = true; } function forEachBin( start, end, callback ) { var s = (start - leftBase)/binWidth; var e = (end - 1 - leftBase)/binWidth; var sb = Math.floor(s), eb = Math.floor(e); if( sb >= binMax || eb < 0 ) return; // does not overlap this block // enforce 0 <= bin < binMax if( sb < 0 ) s = sb = 0; if( eb >= binMax ) { eb = binMax-1; e = binMax; } // now iterate if( sb == eb ) // if in the same bin, just one call callback( sb, e-s ); else { // if in different bins, two or more calls callback( sb, sb+1-s ); for( var i = sb+1; i < eb; i++ ) callback( i, 1 ); callback( eb, e-eb ); } }; thisB.store.getFeatures( query, function( feature ) { if( ! thisB.filter( feature ) ) return; var strand = { '-1': '-', '1': '+' }[ ''+feature.get('strand') ] || 'unstranded'; // increment start and end partial-overlap bins by proportion of overlap forEachBin( feature.get('start'), feature.get('end'), function( bin, overlap ) { coverageBins[bin].getNested('reference').increment( strand, overlap ); }); // Calculate SNP coverage if( binWidth == 1 ) { var mismatches = thisB._getMismatches( feature ); // loops through mismatches and updates coverage variables accordingly. for (var i = 0; i < mismatches.length; i++) { var mismatch = mismatches[i]; forEachBin( feature.get('start')+mismatch.start, feature.get('start')+mismatch.start+mismatch.length, function( binNumber, overlap ) { // Note: we decrement 'reference' so that total of the score is the total coverage var bin = coverageBins[binNumber]; bin.getNested('reference').decrement( strand, overlap ); var base = mismatch.base; if( mismatch.type == 'insertion' ) base = 'ins '+base; else if( mismatch.type == 'skip' ) base = 'skip'; bin.getNested( base ).increment( strand, overlap ); }); } } }, function ( args ) { var makeFeatures = function() { // make fake features from the coverage for( var i = 0; i < coverageBins.length; i++ ) { var bpOffset = leftBase+binWidth*i; featureCallback( new CoverageFeature({ start: bpOffset, end: bpOffset+binWidth, score: coverageBins[i] })); } finishCallback( args ); // optional arguments may change callback behaviour (e.g. add masking) }; // if we are zoomed to base level, try to fetch the // reference sequence for this region and record each // of the bases in the coverage bins if( binWidth == 1 ) { var sequence; thisB.browser.getStore( 'refseqs', function( refSeqStore ) { if( refSeqStore ) { refSeqStore.getFeatures( query, function(f) { sequence = f.get('seq'); }, function() { if( sequence ) { for( var base = leftBase; base <= rightBase; base++ ) { var bin = binNumber( base ); if (coverageBins[bin]) coverageBins[bin].refBase = sequence[bin]; } } makeFeatures(); }, makeFeatures ); } else { makeFeatures(); } }); } else { makeFeatures(); } } , errorCallback ); }, saveStore: function() { return { urlTemplate: this.config.bam.url, baiUrlTemplate: this.config.bai.url }; } }); });