      1 define(['underscore'],
      2 function(_) {
      3   /**
      4    * @class MultiModel
      5    *
      6    * The MultiModel wraps a dictionary of DecompositionModel objects and
      7    * supplies methods to compute aggregate metrics over all such model objects
      8    *
      9    * This is used to provide the 'global' dimension ranges of the scene, rather
     10    * than the 'local' dimension ranges of any individual scatter or biplot.
     11    */
     12   function MultiModel(underlyingModelsDict) {
     13     this.models = underlyingModelsDict;
     14
     15     var first = true;
     16     var firstModel = null;
     17
     18     for (var key in underlyingModelsDict) {
     19       if (first) {
     20         first = false;
     21         firstModel = underlyingModelsDict[key];
     22       }
     23       else {
     24         if (underlyingModelsDict[key].dimensions != firstModel.dimensions)
     25           throw 'Underlying models must have same number of dimensions';
     26       }
     27     }
     28
     29     this.dimensionRanges = {'max': [], 'min': []};
     30     this._unionRanges();
     31   }
     32
     33   /**
     34    *
     35    * Utility method to find the union of the ranges in the DecompositionModels
     36    * this method will populate the dimensionRanges attributes.
     37    * @private
     38    *
     39    */
     40   MultiModel.prototype._unionRanges = function() {
     41     var scope = this, computeRanges;
     42
     43     // first check if there's any range data, if there isn't, then we need
     44     // to compute it by looking at all the decompositions
     45     computeRanges = scope.dimensionRanges.max.length === 0;
     46
     47     // if there's range data then check it lies within the global ranges
     48     if (computeRanges === false) {
     49       _.each(this.models, function(decomp, unused) {
     50         for (var i = 0; i < decomp.dimensionRanges.max.length; i++) {
     51           // global
     52           var gMax = scope.dimensionRanges.max[i];
     53           var gMin = scope.dimensionRanges.min[i];
     54
     55           // local
     56           var lMax = decomp.dimensionRanges.max[i];
     57           var lMin = decomp.dimensionRanges.min[i];
     58
     59           // when we detect a point outside the global ranges we break and
     60           // recompute them
     61           if (!(gMin <= lMin && lMin <= gMax) ||
     62               !(gMin <= lMax && lMax <= gMax)) {
     63             computeRanges = true;
     64             break;
     65           }
     66         }
     67       });
     68     }
     69
     70     if (computeRanges === false) {
     71       // If at this point we still don't need to compute the data, it is safe
     72       // to exit because all data still exists within the expected ranges
     73       return;
     74     }
     75     else {
     76       // TODO: If this entire function ever becomes a bottleneck we should only
     77       // update the dimensions that changed.
     78       // See: https://github.com/biocore/emperor/issues/526
     79
     80       // if we have to compute the data, clean up the previously known ranges
     81       this.dimensionRanges.max = [];
     82       this.dimensionRanges.max.length = 0;
     83       this.dimensionRanges.min = [];
     84       this.dimensionRanges.min.length = 0;
     85     }
     86
     87     _.each(this.models, function(decomp, unused) {
     88
     89       if (scope.dimensionRanges.max.length === 0) {
     90         scope.dimensionRanges.max = decomp.dimensionRanges.max.slice();
     91         scope.dimensionRanges.min = decomp.dimensionRanges.min.slice();
     92       }
     93       else {
     94         // when we have more than one decomposition view we need to figure out
     95         // the absolute largest range that views span over
     96         _.each(decomp.dimensionRanges.max, function(value, index) {
     97           var vMax = decomp.dimensionRanges.max[index],
     98               vMin = decomp.dimensionRanges.min[index];
     99
    100           if (vMax > scope.dimensionRanges.max[index]) {
    101             scope.dimensionRanges.max[index] = vMax;
    102           }
    103           if (vMin < scope.dimensionRanges.min[index]) {
    104             scope.dimensionRanges.min[index] = vMin;
    105           }
    106         });
    107       }
    108     });
    109   };
    110
    111   return MultiModel;
    112 });
    113