@gmod/jbrowse
Version:
JBrowse - client-side genome browser
201 lines (179 loc) • 7.77 kB
JavaScript
/**
* 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
};
}
});
});