UNPKG

@gmod/jbrowse

Version:

JBrowse - client-side genome browser

241 lines (209 loc) 8.55 kB
define([ 'dojo/_base/declare', 'dojo/_base/array', 'dojo/Deferred', 'JBrowse/Model/SimpleFeature', 'JBrowse/Store/SeqFeature/CombinationBase' ], function( declare, array, Deferred, SimpleFeature, CombinationBaseStore ) { return declare([CombinationBaseStore], { // An implementation of CombinationBase that deals with set-type features (without score, as in HTMLFeatures tracks). // Usual operations are things like intersection, union, set subtraction and XOR. // Creates features from spans. Essentially copies the basic span information and adds a feature id. createFeatures: function(spans) { var features = []; //Validate this next time... for(var span in spans) { var id = "comfeat_" + spans[span].start + "." + spans[span].end + "." + spans[span].strand; features.push(new SimpleFeature({data: {start: spans[span].start, end: spans[span].end, strand: spans[span].strand}, id: id})); } return features; }, // Defines the various set-theoretic operations that may occur and assigns each to a span-making function. // Passes the two sets of spans to the appropriate operator function. opSpan: function(op, span1, span2, query) { switch (op) { case "&" : return this.andSpan(span1, span2); case "U" : return this.orSpan(span1, span2); case "X" : return this.andSpan(this.orSpan(span1, span2), this.notSpan(this.andSpan(span1, span2), query)); case "S" : return this.andSpan( span1, this.notSpan(span2, query) ); default : console.error("Invalid boolean operation: "+op); } return undefined; }, // given a set of features, takes the "union" of them and outputs a single set of nonoverlapping spans toSpan: function(features, query) { // strip away extra stuff and keep only the relevant feature data var rawSpans = this._rawToSpan(features,query); // Splits the spans based on which strand they're on, and remove overlap from each strand's spans, recombining at the end. return this._removeOverlap(this._strandFilter(rawSpans, +1)).concat(this._removeOverlap(this._strandFilter(rawSpans, -1))); }, _rawToSpan: function( features, query ) { // given a set of features, makes a set of spans with the // same start and end points (a.k.a. pseudo-features) var spans = []; for (var feature in features) { if (features.hasOwnProperty(feature)) { spans.push( { start: features[feature].get('start'), //Math.max( features[feature].get('start'), query.start ), end: features[feature].get('end'), //Math.min( features[feature].get('end'), query.end ), strand: features[feature].get('strand') } ); } } return spans; }, // Filters an array of spans based on which strand of the reference sequence they are attached to _strandFilter: function( spans, strand ) { return array.filter( spans, function(item) { return item.strand == strand || !item.strand; }).map( function(item) { if(!item.strand) return { start: item.start, end: item.end, strand: strand } // Adds strand to strandless spans else return item; }); }, // converts overlapping spans into their union. Assumes the spans are all on the same strand. _removeOverlap: function( spans ) { if(!spans.length) { return []; } spans.sort(function(a,b) { return a.start - b.start; }); return this._removeOverlapSorted(spans); }, // Given an array of spans sorted by their start bp, converts them into a single non-overlapping set (ie takes their union). _removeOverlapSorted: function( spans ) { var retSpans = []; var i = 0; var strand = spans[0].strand; while(i < spans.length) { var start = spans[i].start; var end = spans[i].end; while(i < spans.length && spans[i].start <= end) { end = Math.max(end, spans[i].end); i++; } retSpans.push( { start: start, end: end, strand: strand}); } return retSpans; }, // given two sets of spans without internal overlap, outputs a set corresponding to their union. orSpan: function( span1, span2 ){ return this._computeUnion(this._strandFilter(span1, 1), this._strandFilter(span2, 1)) .concat(this._computeUnion(this._strandFilter(span1,-1), this._strandFilter(span2,-1))); }, // given two sets of spans without internal overlap, outputs a set corresponding to their intersection andSpan: function( span1, span2){ return this._computeIntersection(this._strandFilter(span1, 1), this._strandFilter(span2,1)) .concat(this._computeIntersection(this._strandFilter(span1,-1), this._strandFilter(span2,-1))); }, // This method should merge two sorted span arrays in O(n) time, which is better // then using span1.concat(span2) and then array.sort(), which takes O(n*log(n)) time. _sortedArrayMerge: function( span1, span2) { var newArray = []; var i = 0; var j = 0; while(i < span1.length && j < span2.length) { if( span1[i].start <= span2[j].start ) { newArray.push(span1[i]); i++; } else { newArray.push(span2[j]); j++; } } if(i < span1.length) { newArray = newArray.concat(span1.slice(i, span1.length)); } else if(j < span2.length) { newArray = newArray.concat(span2.slice(j, span2.length)); } return newArray; }, // A helper method for computing the union of two arrays of spans. _computeUnion: function( span1, span2) { if(!span1.length && !span2.length) { return []; } return this._removeOverlapSorted(this._sortedArrayMerge(span1,span2)); }, // A helper method for computing the intersection of two arrays of spans. _computeIntersection: function( span1, span2) { if(!span1.length || !span2.length) { return []; } var allSpans = this._sortedArrayMerge(span1, span2); var retSpans = []; var maxEnd = allSpans[0].end; var strand = span1[0].strand; // Assumes both span sets contain only features for one specific strand var i = 1; while(i < allSpans.length) { var start = allSpans[i].start; var end = Math.min(allSpans[i].end, maxEnd); if(start < end) { retSpans.push({start: start, end: end, strand: strand}); } maxEnd = Math.max(allSpans[i].end, maxEnd); i++; } return retSpans; }, // Filters span set by strand, inverts the sets represented on each strand, and recombines. notSpan: function( spans, query) { return this._rawNotSpan(this._strandFilter(spans, +1), query, +1).concat(this._rawNotSpan(this._strandFilter(spans, -1), query, -1)); }, // Converts a set of spans into its complement in the reference sequence. _rawNotSpan: function( spans, query, strand ) { var invSpan = []; invSpan[0] = { start: query.start }; var i = 0; for (var span in spans) { if (spans.hasOwnProperty(span)) { span = spans[span]; invSpan[i].strand = strand; invSpan[i].end = span.start; i++; invSpan[i] = { start: span.end }; } } invSpan[i].strand = strand; invSpan[i].end = query.end; if (invSpan[i].end <= invSpan[i].start) { invSpan.splice(i,1); } if (invSpan[0].end <= invSpan[0].start) { invSpan.splice(0,1); } return invSpan; }, loadRegion: function ( region ) { var d = new Deferred(); if(this.stores.length == 1) { d.resolve(this, true); return d.promise; } var thisB = this; var regionLoaded = region; regionLoaded.spans = []; delete this.regionLoaded; this._getFeatures(region, function(){}, function(results){ if(results && results.spans) { regionLoaded.spans = results.spans; thisB.regionLoaded = regionLoaded; } d.resolve(thisB, true); }, function(){ d.reject("cannot load region"); }); return d.promise; } }); });