UNPKG

@gmod/jbrowse

Version:

JBrowse - client-side genome browser

207 lines (178 loc) 7.36 kB
define([ 'dojo/_base/declare', 'dojo/_base/array', 'dojo/Deferred', 'dojo/when', 'dojo/promise/all', 'JBrowse/Store/SeqFeature', 'JBrowse/Store/DeferredStatsMixin', 'JBrowse/Store/DeferredFeaturesMixin', 'JBrowse/Store/SeqFeature/GlobalStatsEstimationMixin', 'JBrowse/Util', 'JBrowse/Model/BinaryTreeNode' ], function( declare, array, Deferred, when, all, SeqFeatureStore, DeferredStatsMixin, DeferredFeaturesMixin, GlobalStatsEstimationMixin, Util, TreeNode ) { // Helper object that wraps a feature and which store it comes from var featureWrapper = Util.fastDeclare( { get: function( arg ) { return this.feature.get(arg); }, id: function() { return this.feature.id()+this.storeName; }, parent: function() { return this.feature.parent(); }, children: function() { return this.feature.children(); }, tags: function() { return this.feature.tags(); }, constructor: function( feat, storeName ) { this.feature = feat; this.storeName = storeName; this.source = feat ? feat.source : undefined; } } ); return declare([SeqFeatureStore, DeferredFeaturesMixin, DeferredStatsMixin, GlobalStatsEstimationMixin], { // The base class for combination stores. A combination store is one that pulls feature data from other stores // and combines it according to a binary tree of operations in order to produce new features. constructor: function( args ) { // Objects can access this to know if a given store is a combination store of some kind this.isCombinationStore = true; this.defaultOp = args.op; // If constructed with an opTree already included, might as well try to get all the store info from that opTree. if(args.opTree) { this.reload(args.opTree); } }, // Loads an operation tree (opTree). reload: function( optree ) { this._deferred.features = new Deferred(); this._deferred.stats = new Deferred(); var refSeq; // Load in opTree if( !optree) { optree = new TreeNode({ Value: this.defaultOp}); } this.opTree = optree; this.stores = optree.getLeaves() || []; // If any of the stores doesn't have a name, then something weird is happening... for(var store in this.stores) { if(!this.stores[store].name) { this.stores = []; } } var thisB = this; this._deferred.features.resolve(true); delete this._regionStatsCache; this._estimateGlobalStats().then( dojo.hitch( this, function( stats ) { this.globalStats = stats; this._deferred.stats.resolve({success:true}); } ), dojo.hitch( this, '_failAllDeferred' ) ); }, // Filters the featureArrays to return the list of features for the query, and then calls finish() to pass to the callback _getFeatures: function( query, featCallback, doneCallback, errorCallback ) { var thisB = this; if(this.stores.length == 1) { this.stores[0].getFeatures( query, featCallback, doneCallback, errorCallback); return; } if(this.regionLoaded) { var spans = array.filter(this.regionLoaded.spans, function(span) { return span.start <= query.end && span.end >= query.start; }); var features = this.createFeatures(spans); this.finish(features, spans, featCallback, doneCallback); return; } // featureArrays will be a map from the names of the stores to an array of each store's features var featureArrays = {}; // Generate map var fetchAllFeatures = thisB.stores.map( function (store) { var d = new Deferred(); if ( !featureArrays[store.name] ) { featureArrays[store.name] = []; store.getFeatures( query, dojo.hitch( this, function( feature ) { var feat = new featureWrapper( feature, store.name ); featureArrays[store.name].push( feat ); }), function(){d.resolve( featureArrays[store.name] ); }, function(){d.reject("Error fetching features for store " + store.name);} ); } else { d.resolve(featureArrays[store.name], true); } d.then(function(){}, errorCallback); // Makes sure that none of the rejected deferred promises keep propagating return d.promise; } ); // Once we have all features, combine them according to the operation tree and create new features based on them. when( all( fetchAllFeatures ), function() { // Create a set of spans based on the evaluation of the operation tree var spans = thisB.evalTree(featureArrays, thisB.opTree, query); var features = thisB.createFeatures(spans); thisB.finish(features, spans, featCallback, doneCallback); }, errorCallback); }, // Evaluate (recursively) an operation tree to create a list of spans (essentially pseudo-features) evalTree: function(featureArrays, tree, query) { if(!tree) { return false; } else if(tree.isLeaf()) { return this.toSpan(featureArrays[tree.get().name], query); } else if(!tree.hasLeft()) { return this.toSpan(featureArrays[tree.right().get().name], query); } else if(!tree.hasRight()) { return this.toSpan(featureArrays[tree.left().get().name], query); } return this.opSpan( tree.get(), this.evalTree(featureArrays, tree.left(), query), this.evalTree(featureArrays, tree.right(), query), query ); }, // Passes the list of combined features to the getFeatures() callbacks finish: function( features, spans, featCallback, doneCallback ) { /* Pass features to the track's original featCallback, and pass spans to the doneCallback. */ for ( var key in features ) { if ( features.hasOwnProperty(key) ) { featCallback( features[key] ); } } doneCallback( { spans: spans} ); }, // These last four functions are stubbed out because each derived class should have its own implementation of them. // Converts a list of spans into a list of features. createFeatures: function(spans) {}, // Transforms a set of features into a set of spans toSpan: function(features, query) {}, // Defines the various operations that may occur and assigns each to a span-making function. opSpan: function(op, span1, span2, query) {} }); });