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