      1 define([
      2     'jquery',
      3     'underscore',
      4     'view',
      5     'slickgrid',
      6     'chosen',
      7     'abcviewcontroller',
      8     'scale-editor',
      9     'util'
     10 ], function($, _, DecompositionView, SlickGrid, Chosen, abc, ScaleEditor,
     11             util) {
     12   EmperorViewControllerABC = abc.EmperorViewControllerABC;
     13
     14   /**
     15    *
     16    * @class EmperorViewController
     17    *
     18    * Base class for view controllers that use a dictionary of decomposition
     19    * views, but that are not controlled by a metadata category, for those
     20    * cases, see `EmperorAttributeABC`.
     21    *
     22    * @param {UIState} uiState The shared state
     23    * @param {Node} container Container node to create the controller in.
     24    * @param {String} title title of the tab.
     25    * @param {String} description helper description.
     26    * @param {Object} decompViewDict This is object is keyed by unique
     27    * identifiers and the values are DecompositionView objects referring to a
     28    * set of objects presented on screen. This dictionary will usually be shared
     29    * by all the tabs in the application. This argument is passed by reference.
     30    *
     31    * @return {EmperorViewController} Returns an instance of the
     32    * EmperorViewController class.
     33    * @constructs EmperorViewController
     34    * @extends EmperorViewControllerABC
     35    *
     36    */
     37   function EmperorViewController(uiState, container, title, description,
     38                                  decompViewDict) {
     39     EmperorViewControllerABC.call(this, uiState, container, title, description);
     40     if (decompViewDict === undefined) {
     41       throw Error('The decomposition view dictionary cannot be undefined');
     42     }
     43     for (var dv in decompViewDict) {
     44       if (!dv instanceof DecompositionView) {
     45         throw Error('The decomposition view dictionary ' +
     46             'can only have decomposition views');
     47       }
     48     }
     49     if (_.size(decompViewDict) <= 0) {
     50       throw Error('The decomposition view dictionary cannot be empty');
     51     }
     52
     53     /**
     54      * @type {Object}
     55      * This is object is keyed by unique identifiers and the values are
     56      * DecompositionView objects referring to a set of objects presented on
     57      * screen. This dictionary will usually be shared by all the tabs in the
     58      * application. This argument is passed by reference.
     59      */
     60     this.decompViewDict = decompViewDict;
     61
     62     /**
     63      * @type {Function}
     64      * Callback to execute when all the elements in the UI for this controller
     65      * have been loaded. Note, that this functionality needs to be implemented
     66      * by subclasses, as EmperorViewController does not have any UI components.
     67      */
     68     this.ready = null;
     69
     70     return this;
     71   }
     72   EmperorViewController.prototype = Object.create(
     73       EmperorViewControllerABC.prototype);
     74   EmperorViewController.prototype.constructor = EmperorViewControllerABC;
     75
     76   /**
     77    *
     78    * Retrieve a view from the controller.
     79    *
     80    * This class does not operate on single decomposition views, hence this
     81    * method retrieves the first available view.
     82    *
     83    */
     84   EmperorViewController.prototype.getView = function() {
     85     // return the first decomposition view available in the dictionary
     86     return this.decompViewDict[Object.keys(this.decompViewDict)[0]];
     87   };
     88
     89   /**
     90    * Check if a metadata field is present
     91    *
     92    * @param {String} m Metadata column to check if is present.
     93    *
     94    * @return {Bool} Whether or not the metadata field is present.
     95    *
     96    */
     97   EmperorViewController.prototype.hasMetadataField = function(m) {
     98     // loop through the metadata headers in the decompositon views
     99     // FIXME: There's no good way to specify the current decomposition name
    100     // this needs to be added to the interface.
    101     var res = _.find(this.decompViewDict, function(view) {
    102       return view.decomp.md_headers.indexOf(m) !== -1;
    103     });
    104
    105     return res !== undefined;
    106   };
    107
    108   /**
    109    *
    110    * @class EmperorAttributeABC
    111    *
    112    * Initializes an abstract tab for attributes i.e. shape, color, size, etc.
    113    * This has to be contained in a DOM object and will use the full size of
    114    * that container.
    115    *
    116    * @param {UIState} uiState the shared state
    117    * @param {Node} container Container node to create the controller in.
    118    * @param {String} title title of the tab.
    119    * @param {String} description helper description.
    120    * @param {Object} decompViewDict This is object is keyed by unique
    121    * identifiers and the values are DecompositionView objects referring to a
    122    * set of objects presented on screen. This dictionary will usually be shared
    123    * by all the tabs in the application. This argument is passed by reference.
    124    * @param {Object} options This is a dictionary of options used to build
    125    * the view controller. Used to set attributes of the slick grid and the
    126    * metadata category drop down. At the moment the constructor only expects
    127    * the following attributes:
    128    *  - categorySelectionCallback: a function object that's called when a new
    129    *  metadata category is selected in the dropdown living in the header.
    130    *  See [change]{@link https://api.jquery.com/change/}.
    131    *  - valueUpdatedCallback: a function object that's called when a metadata
    132    *  visualization attribute is modified (i.e. a change of color).
    133    *  See [onCellChange]{@link
    134    *  https://github.com/mleibman/SlickGrid/wiki/Grid-Events}.
    135    *  - slickGridColumn: a dictionary specifying options to be passed into the
    136    *  slickGrid. For instance, the ColorFormatter and the ColorEditor would be
    137    *  passed here.  For more information, refer to the Slick Grid
    138    *  documentation.
    139    *
    140    * @return {EmperorAttributeABC} Returns an instance of the
    141    * EmperorAttributeABC class.
    142    * @constructs EmperorAttributeABC
    143    * @extends EmperorViewController
    144    *
    145    */
    146   function EmperorAttributeABC(uiState, container, title, description,
    147                                decompViewDict, options) {
    148     EmperorViewController.call(this, uiState, container, title, description,
    149                                decompViewDict);
    150
    151     /**
    152      * @type {Object}
    153      * Dictionary-like object where keys are metadata categories and values are
    154      * lists of metadata columns. This object reflects the data presented in
    155      * the metadata menu.
    156      * @private
    157      */
    158     this._metadata = {};
    159
    160     /**
    161      * @type {Node}
    162      * jQuery element for the div containing the slickgrid of sample information
    163      */
    164     this.$gridDiv = $('<div name="emperor-grid-div"></div>');
    165     this.$gridDiv.css('margin', '0 auto');
    166     this.$gridDiv.css('width', '100%');
    167     this.$gridDiv.css('height', '100%');
    168     this.$gridDiv.attr('title', 'Change the ' + title.toLowerCase() + ' with' +
    169                        ' the left column controls.');
    170     this.$body.append(this.$gridDiv);
    171
    172     var dm = this.getView().decomp;
    173     var scope = this;
    174
    175     // http://stackoverflow.com/a/6602002
    176     this.$select = $('<select>');
    177     this.$header.append(this.$select);
    178
    179     this.$searchBar = $("<input type='search' " +
    180                         "placeholder='Search for a value ...'>"
    181     ).css({
    182       'width': '100%'
    183     });
    184     this.$header.append(this.$searchBar);
    185
    186     // there's a few attributes we can only set on "ready" so list them up here
    187     $(function() {
    188       scope.$searchBar.tooltip({
    189         content: 'No results found!',
    190         disabled: true,
    191         // place the element with a slight offset at the bottom of the input
    192         // so that it doesn't overlap with the "continuous values" elements
    193         position: {my: 'center top+40', at: 'center bottom',
    194                    of: scope.$searchBar},
    195         // prevent the tooltip from disappearing when there's no matches
    196         close: function(event, ui) {
    197           if (scope.bodyGrid.getDataLength() === 0 &&
    198               scope.$searchBar.val() !== '') {
    199             scope.$searchBar.tooltip('open');
    200           }
    201         }
    202       });
    203
    204       var placeholder = 'Select a ' + scope.title + ' Category';
    205
    206       // setup the slick grid
    207       scope._buildGrid(options);
    208
    209       scope.refreshMetadata();
    210
    211       // once this element is ready, it is safe to execute the "ready" callback
    212       // if a subclass needs to wait on other elements, this attribute should
    213       // be changed to null so this callback is effectively cancelled, for an
    214       // example see the constructor of ColorViewController
    215       scope.$select.on('chosen:ready', function() {
    216         if (scope.ready !== null) {
    217           scope.ready();
    218         }
    219       });
    220
    221       // setup chosen
    222       scope.$select.chosen({width: '100%', search_contains: true,
    223                             include_group_label_in_selected: true,
    224                             placeholder_text_single: placeholder});
    225
    226       // only subclasses will provide this callback
    227       if (options.categorySelectionCallback !== undefined) {
    228
    229         // Disable interface controls (except the metadata selector) to
    230         // prevent errors while no metadata category is selected. Once the
    231         // user selects a metadata category, the controls will be enabled
    232         // (see setSlickGridDataset).
    233         scope.setEnabled(false);
    234         scope.$select.val('');
    235         scope.$select.prop('disabled', false).trigger('chosen:updated');
    236
    237         scope.$select.chosen().change(options.categorySelectionCallback);
    238       }
    239
    240       // general events
    241       scope._setupEvents();
    242     });
    243
    244     return this;
    245   }
    246   EmperorAttributeABC.prototype = Object.create(
    247     EmperorViewController.prototype);
    248   EmperorAttributeABC.prototype.constructor = EmperorViewController;
    249
    250   /**
    251    *
    252    * Get the name of the decomposition selected in the metadata menu.
    253    *
    254    */
    255   EmperorAttributeABC.prototype.decompositionName = function(cat) {
    256     return this.$select.find(':selected').parent().attr('label');
    257   };
    258
    259   /**
    260    *
    261    * Get the view that's currently selected by the metadata menu.
    262    *
    263    */
    264   EmperorAttributeABC.prototype.getView = function() {
    265     var view;
    266
    267     try {
    268       view = this.decompViewDict[this.decompositionName()];
    269     }
    270     catch (TypeError) {
    271       view = EmperorViewController.prototype.getView.call(this);
    272     }
    273
    274     return view;
    275   };
    276
    277   /**
    278    *
    279    * Private method to reset the attributes of the controller.
    280    *
    281    * Subclasses should implement this method as a way to reset the visual
    282    * attributes of a given plot.
    283    * @private
    284    *
    285    */
    286   EmperorAttributeABC.prototype._resetAttribute = function() {
    287   };
    288
    289   /**
    290    * Changes the selected value in the metadata menu.
    291    *
    292    * @param {String} m Metadata column name to control. When the category is
    293    * ``null``, the metadata selector is set to an empty value, the body grid
    294    * is emptied, and all the markers are reset to a default state (depends on
    295    * the subclass).
    296    *
    297    * @throws {Error} Argument `m` must be a metadata category in one of the
    298    * decomposition views.
    299    */
    300   EmperorAttributeABC.prototype.setMetadataField = function(m) {
    301     if (m === null) {
    302       this._resetAttribute();
    303
    304       this.$select.val('');
    305       this.setSlickGridDataset([]);
    306
    307       this.setEnabled(false);
    308       this.$select.prop('disabled', false).trigger('chosen:updated');
    309
    310       return;
    311     }
    312
    313     if (!this.hasMetadataField(m)) {
    314       throw Error('Cannot set "' + m + '" as the metadata field, this column' +
    315                   ' is not available in the decomposition views');
    316     }
    317
    318     this.$select.val(m);
    319     this.$select.trigger('chosen:updated');
    320     this.$select.change();
    321   };
    322
    323   /**
    324    *
    325    * Get the name of the selected category in the metadata menu.
    326    *
    327    */
    328   EmperorAttributeABC.prototype.getMetadataField = function() {
    329     return this.$select.val();
    330   };
    331
    332   /**
    333    * Retrieves the underlying data in the slick grid
    334    * @return {Array} Returns an array of objects
    335    * displayed by the body grid.
    336    */
    337   EmperorAttributeABC.prototype.getSlickGridDataset = function() {
    338     return this.bodyGrid.getData().getItems();
    339   };
    340
    341   /**
    342    * Changes the underlying data in the slick grid
    343    *
    344    * @param {Array} data data.
    345    */
    346   EmperorAttributeABC.prototype.setSlickGridDataset = function(data) {
    347
    348     // Accounts for cases where controllers have not been set to a metadata
    349     // category. In these cases all controllers (except for the metadata
    350     // selector) are disabled to prevent interface errors.
    351     if (this.getSlickGridDataset().length === 0 && this.enabled === false) {
    352       this.setEnabled(true);
    353     }
    354
    355     // Re-render the grid on the DOM
    356     this.bodyGrid.getData().beginUpdate();
    357     this.bodyGrid.getData().setItems(data);
    358     this.bodyGrid.getData().endUpdate();
    359     this.bodyGrid.invalidate();
    360     this.bodyGrid.render();
    361   };
    362
    363   /**
    364    * Method in charge of initializing the SlickGrid object
    365    *
    366    * @param {Object} [options] additional options to initialize the slick grid
    367    * of this object.
    368    * @private
    369    *
    370    */
    371   EmperorAttributeABC.prototype._buildGrid = function(options) {
    372     var columns = [{id: 'field1', name: '', field: 'category'}], scope = this;
    373
    374     // autoEdit enables one-click editor trigger on the entire grid, instead
    375     // of requiring users to click twice on a widget.
    376     var gridOptions = {editable: true, enableAddRow: false,
    377       enableCellNavigation: true, forceFitColumns: true,
    378       enableColumnReorder: false, autoEdit: true};
    379
    380     // If there's a custom slickgrid column then add it to the object
    381     if (options.slickGridColumn !== undefined) {
    382       columns.unshift(options.slickGridColumn);
    383     }
    384
    385     var dataView = new Slick.Data.DataView(), searchString = '';
    386
    387     /**
    388      * @type {Slick.Grid}
    389      * Container that lists the metadata categories described under the
    390      * metadata column and the attribute that can be modified.
    391      */
    392     this.bodyGrid = new Slick.Grid(this.$gridDiv, dataView, columns,
    393                                    gridOptions);
    394
    395     this.$searchBar.on('input', function(e) {
    396       dataView.refresh();
    397
    398       // show a message when no results are found
    399       if (scope.bodyGrid.getDataLength() === 0 &&
    400           scope.$searchBar.val() !== '') {
    401         scope.$searchBar.tooltip('option', 'disabled', false);
    402         scope.$searchBar.tooltip('open');
    403       }
    404       else {
    405         scope.$searchBar.tooltip('option', 'disabled', true);
    406         scope.$searchBar.tooltip('close');
    407       }
    408
    409     });
    410
    411     function substringFilter(item, args) {
    412       var val = scope.$searchBar.val();
    413       if (!searchString && val &&
    414          item.category.toLowerCase().indexOf(val.toLowerCase()) === -1) {
    415         return false;
    416       }
    417       return true;
    418     }
    419
    420     dataView.onRowCountChanged.subscribe(function(e, args) {
    421       scope.bodyGrid.updateRowCount();
    422       scope.bodyGrid.render();
    423     });
    424
    425     dataView.onRowsChanged.subscribe(function(e, args) {
    426       scope.bodyGrid.invalidateRows(args.rows);
    427       scope.bodyGrid.render();
    428     });
    429
    430     dataView.setFilter(substringFilter);
    431
    432     // hide the header row of the grid
    433     // http://stackoverflow.com/a/29827664/379593
    434     $(this.$body).find('.slick-header').css('display', 'none');
    435
    436     // subscribe to events when a cell is changed
    437     this.bodyGrid.onCellChange.subscribe(options.valueUpdatedCallback);
    438   };
    439
    440   EmperorAttributeABC.prototype._setupEvents = function() {
    441     var scope = this;
    442
    443     // dispatch an event when the category changes
    444     this.$select.on('change', function() {
    445       scope.dispatchEvent({type: 'category-changed',
    446                            message: {category: scope.getMetadataField(),
    447                                      controller: scope}
    448       });
    449     });
    450
    451     // dispatch an event when a value changes and send the plottable objects
    452     this.bodyGrid.onCellChange.subscribe(function(e, args) {
    453       scope.dispatchEvent({type: 'value-changed',
    454                            message: {category: scope.getMetadataField(),
    455                                      attribute: args.item.value,
    456                                      group: args.item.plottables,
    457                                      controller: scope}
    458       });
    459     });
    460
    461     // dispatch an event when a category is double-clicked
    462     this.bodyGrid.onDblClick.subscribe(function(e, args) {
    463       var item = scope.bodyGrid.getDataItem(args.row);
    464       scope.dispatchEvent({type: 'value-double-clicked',
    465                            message: {category: scope.getMetadataField(),
    466                                      value: item.category,
    467                                      attribute: item.value,
    468                                      group: item.plottables,
    469                                      controller: scope}
    470       });
    471     });
    472   };
    473
    474   /**
    475    * Resizes the container and the individual elements.
    476    *
    477    * Note, the consumer of this class, likely the main controller should call
    478    * the resize function any time a resizing event happens.
    479    *
    480    * @param {Float} width the container width.
    481    * @param {Float} height the container height.
    482    */
    483   EmperorAttributeABC.prototype.resize = function(width, height) {
    484     // call super, most of the header and body resizing logic is done there
    485     EmperorViewController.prototype.resize.call(this, width, height);
    486
    487     // the whole code is asynchronous, so there may be situations where
    488     // bodyGrid doesn't exist yet, so check before trying to modify the object
    489     if (this.bodyGrid !== undefined) {
    490       // make the columns fit the available space whenever the window resizes
    491       // http://stackoverflow.com/a/29835739
    492       this.bodyGrid.setColumns(this.bodyGrid.getColumns());
    493       // Resize the slickgrid canvas for the new body size.
    494       this.bodyGrid.resizeCanvas();
    495     }
    496   };
    497
    498   /**
    499    * Converts the current instance into a JSON object.
    500    *
    501    * @return {Object} base object ready for JSON conversion.
    502    */
    503   EmperorAttributeABC.prototype.toJSON = function() {
    504     var json = {};
    505     json.category = this.getMetadataField();
    506
    507     // Convert SlickGrid list of objects to single object
    508     var gridData = this.getSlickGridDataset();
    509     var jsonData = {};
    510     for (var i = 0; i < gridData.length; i++) {
    511       jsonData[gridData[i].category] = gridData[i].value;
    512     }
    513     json.data = jsonData;
    514     return json;
    515   };
    516
    517   /**
    518    * Decodes JSON string and modifies its own instance variables accordingly.
    519    *
    520    * @param {Object} json Parsed JSON string representation of self.
    521    *
    522    */
    523   EmperorAttributeABC.prototype.fromJSON = function(json) {
    524     this.setMetadataField(json.category);
    525
    526     // if the category is null, then we just reset the controller
    527     if (json.category !== null) {
    528       // fetch and set the SlickGrid-formatted data
    529       var data = this.getView().setCategory(
    530         json.data, this.setPlottableAttributes, json.category);
    531       this.setSlickGridDataset(data);
    532       // set all to needsUpdate
    533       this.getView().needsUpdate = true;
    534     }
    535   };
    536
    537   /**
    538    *
    539    * Update the metadata selection menu.
    540    *
    541    * Performs some additional logic to avoid duplicating decomposition names.
    542    *
    543    * Note that decompositions won't be updated if they have the same name and
    544    * same metadata headers, if the only things changing are coordinates, or
    545    * metadata values, the changes should be performed directly on the objects
    546    * themselves.
    547    *
    548    */
    549   EmperorAttributeABC.prototype.refreshMetadata = function() {
    550     var scope = this, group, hdrs;
    551
    552     _.each(this.decompViewDict, function(view, name) {
    553       // sort alphabetically the metadata headers (
    554       hdrs = _.sortBy(view.decomp.md_headers, function(x) {
    555         return x.toLowerCase();
    556       });
    557
    558       // Before we update the metadata view, we rectify that we don't have that
    559       // information already. The order in this conditional matters as we hope
    560       // to short-circuit if the name is not already present.  If that's not
    561       // the case, we also check to ensure the lists are equivalent.
    562       if (_.contains(_.keys(scope._metadata), name) &&
    563            _.intersection(scope._metadata[name], hdrs).length == hdrs.length &&
    564            scope._metadata[name].length == hdrs.length) {
    565         return;
    566       }
    567
    568       // create the new category
    569       scope._metadata[name] = [];
    570
    571       group = $('<optgroup>').attr('label', name);
    572
    573       scope.$select.append(group);
    574
    575       _.each(hdrs, function(header) {
    576         group.append($('<option>').attr('value', header).text(header));
    577         scope._metadata[name].push(header);
    578       });
    579     });
    580
    581     this.$select.trigger('chosen:updated');
    582   };
    583
    584   /**
    585    * Sets whether or not the tab can be modified or accessed.
    586    *
    587    * @param {Boolean} trulse option to enable tab.
    588    */
    589   EmperorAttributeABC.prototype.setEnabled = function(trulse) {
    590     EmperorViewController.prototype.setEnabled.call(this, trulse);
    591
    592     this.$select.prop('disabled', !trulse).trigger('chosen:updated');
    593     this.bodyGrid.setOptions({editable: trulse});
    594     this.$searchBar.prop('disabled', !trulse);
    595     this.$searchBar.prop('hidden', !trulse);
    596   };
    597
    598   /**
    599    * @class ScalarViewControllerABC
    600    *
    601    * Alters the scale of points displayed on the screen.
    602    *
    603    * @param {UIState} uiState The shared state
    604    * @param {Node} container Container node to create the controller in.
    605    * @param {String} title The name/title of the tab.
    606    * @param {String} helpmenu description helper description.
    607    * @param {Float} min Minimum value for the attribute.
    608    * @param {Float} max Maximum value for the attribute.
    609    * @param {Float} step Size of the step for an attribute slider.
    610    * @param {Object} decompViewDict This object is keyed by unique identifiers
    611    * and the values are DecompositionView objects referring to a set of objects
    612    * presented on screen. This dictionary will usually be shared by all the
    613    * tabs in the application. This argument is passed by reference.
    614    *
    615    * @return {ScalarViewControllerABC}
    616    * @constructs ScalarViewControllerABC
    617    * @extends EmperorAttributeABC
    618    *
    619    **/
    620   function ScalarViewControllerABC(uiState, container, title, helpmenu, min,
    621                                    max, step, decompViewDict) {
    622     // Create checkbox for scaling by values
    623     /**
    624      * jQuery node for checkbox controlling whether to scale by values or not
    625      * @type {Node}
    626      */
    627     this.$scaledValue = $('<input type="checkbox">');
    628     /**
    629      * jQuery node for label of $scaledValues
    630      * @type {Node}
    631      */
    632     this.$scaledLabel = $('<label>Change ' + title.toLowerCase() + ' by ' +
    633                           'values</label>');
    634     this.$scaledLabel.attr('title', 'Samples with lower values will have ' +
    635                            'a decreased ' + title.toLowerCase());
    636
    637     //Create global scale bar
    638     /**
    639      * jQuery node for global scale bar container div
    640      * @type {Node}
    641      */
    642     this.$globalDiv = $('<div style="width:100%;padding:5px;">');
    643     this.$globalDiv.html('<p>Global Scaling</p>');
    644     var $sliderDiv = $('<div style="width:80%;display:inline-block;">');
    645     var $viewval = $('<input type="text" value="1.0" readonly ' +
    646                      'style="border:0;width:25px;' +
    647                      'background-color:rgb(238, 238, 238)">');
    648     /**
    649      * jQuery node for global scale bar
    650      * @type {Node}
    651      */
    652     this.$sliderGlobal = $sliderDiv.slider({
    653       range: 'max',
    654       min: min,
    655       max: max,
    656       value: 1.0,
    657       step: step,
    658       slide: function(event, ui) {
    659         $viewval.val(ui.value);
    660       },
    661       stop: function(event, ui) {
    662         // Update the slickgrid values with the new scalar
    663         var data = scope.getSlickGridDataset();
    664         _.each(data, function(element) {
    665           element.value = ui.value;
    666         });
    667         scope.setSlickGridDataset(data);
    668         scope.setAllPlottableAttributes(ui.value);
    669       }
    670     });
    671     this.$globalDiv.append($viewval);
    672     this.$globalDiv.append($sliderDiv);
    673
    674     // Constant for width in slick-grid
    675     var SLICK_WIDTH = 50, scope = this;
    676
    677     // Build the options dictionary
    678     var options = {
    679     'valueUpdatedCallback': function(e, args) {
    680       var scalar = +args.item.value;
    681       var group = args.item.plottables;
    682       var element = scope.getView();
    683       scope.setPlottableAttributes(element, scalar, group);
    684     },
    685     'categorySelectionCallback': function(evt, params) {
    686       var category = scope.$select.val();
    687       var decompViewDict = scope.getView();
    688       var attributes;
    689
    690       // getting all unique values per categories
    691       var uniqueVals = decompViewDict.decomp.getUniqueValuesByCategory(
    692         category);
    693       // getting a scalar value for each point
    694       var scaled = scope.$scaledValue.is(':checked');
    695       try {
    696         attributes = scope.getScale(uniqueVals, scaled);
    697       }
    698       catch (err) {
    699         scope.$scaledValue.attr('checked', false);
    700         return;
    701       }
    702       if (scaled) {
    703         scope.$globalDiv.hide();
    704       }
    705       else {
    706         scope.$globalDiv.show();
    707       }
    708       scope.resize();
    709
    710       // fetch the slickgrid-formatted data
    711       var data = decompViewDict.setCategory(attributes,
    712                                             scope.setPlottableAttributes,
    713                                             category);
    714
    715       scope.setSlickGridDataset(data);
    716
    717       scope.$sliderGlobal.slider('value', 1);
    718       $viewval.val(1);
    719     },
    720     'slickGridColumn': {id: 'title', name: title, field: 'value',
    721       sortable: false, maxWidth: SLICK_WIDTH,
    722       minWidth: SLICK_WIDTH,
    723       editor: ScaleEditor.ScaleEditor,
    724       formatter: ScaleEditor.ScaleFormatter},
    725     'editorOptions': {'min': min, 'max': max, 'step': step}
    726     };
    727
    728     EmperorAttributeABC.call(this, uiState, container, title, helpmenu,
    729                              decompViewDict, options);
    730
    731     this.$header.append(this.$scaledValue);
    732     this.$header.append(this.$scaledLabel);
    733     this.$body.prepend(this.$globalDiv);
    734
    735     scope.$scaledValue.on('change', options.categorySelectionCallback);
    736
    737     return this;
    738   }
    739   ScalarViewControllerABC.prototype = Object.create(
    740     EmperorAttributeABC.prototype);
    741   ScalarViewControllerABC.prototype.constructor = EmperorAttributeABC;
    742
    743   /**
    744    * Converts the current instance into a JSON string.
    745    *
    746    * @return {Object} JSON ready representation of self.
    747    */
    748   ScalarViewControllerABC.prototype.toJSON = function() {
    749     var json = EmperorAttributeABC.prototype.toJSON.call(this);
    750     json.globalScale = this.$globalDiv.children('input').val();
    751     json.scaleVal = this.$scaledValue.is(':checked');
    752     return json;
    753   };
    754
    755   /**
    756    * Decodes JSON string and modifies its own instance variables accordingly.
    757    *
    758    * @param {Object} Parsed JSON string representation of self.
    759    */
    760   ScalarViewControllerABC.prototype.fromJSON = function(json) {
    761     // Can't call super because select needs to be set first Order here is
    762     // important. We want to set all the extra controller settings before we
    763     // load from json, as they can override the JSON when set
    764
    765     this.setMetadataField(json.category);
    766
    767     // if the category is null, then there's nothing to set about the state
    768     // of the controller
    769     if (json.category === null) {
    770       return;
    771     }
    772
    773     this.$select.val(json.category);
    774     this.$select.trigger('chosen:updated');
    775     this.$sliderGlobal.slider('value', json.globalScale);
    776     this.$scaledValue.prop('checked', json.scaleVal);
    777     this.$scaledValue.trigger('change');
    778
    779     // fetch and set the SlickGrid-formatted data
    780     var data = this.getView().setCategory(json.data,
    781                                           this.setPlottableAttributes,
    782                                           json.category);
    783     this.setSlickGridDataset(data);
    784
    785     // set all to needsUpdate
    786     this.getView().needsUpdate = true;
    787   };
    788
    789   /**
    790    * Resizes the container and the individual elements.
    791    *
    792    * Note, the consumer of this class, likely the main controller should call
    793    * the resize function any time a resizing event happens.
    794    *
    795    * @param {float} width the container width.
    796    * @param {float} height the container height.
    797    */
    798   ScalarViewControllerABC.prototype.resize = function(width, height) {
    799     this.$body.height(this.$canvas.height() - this.$header.height());
    800     this.$body.width(this.$canvas.width());
    801
    802     //scale gridDiv based on whether global scaling available or not
    803     if (this.$scaledValue.is(':checked')) {
    804       this.$gridDiv.css('height', '100%');
    805     }
    806     else {
    807       this.$gridDiv.css(
    808         'height', this.$body.height() - this.$globalDiv.height() - 10);
    809     }
    810
    811     // call super, most of the header and body resizing logic is done there
    812     EmperorAttributeABC.prototype.resize.call(this, width, height);
    813   };
    814
    815   /**
    816    * Sets whether or not elements in the tab can be modified.
    817    *
    818    * @param {Boolean} trulse option to enable elements.
    819    */
    820   ScalarViewControllerABC.prototype.setEnabled = function(trulse) {
    821     EmperorAttributeABC.prototype.setEnabled.call(this, trulse);
    822
    823     var color;
    824
    825     this.$scaledValue.prop('disabled', !trulse);
    826     this.$sliderGlobal.slider('option', 'disabled', !trulse);
    827
    828     if (trulse) {
    829       color = '#70caff';
    830     }
    831     else {
    832       color = '';
    833     }
    834     this.$sliderGlobal.css('background', color);
    835   };
    836
    837   /**
    838    *
    839    * Private method to reset the scale of all the objects to one.
    840    *
    841    * @extends EmperorAttributeABC
    842    * @private
    843    *
    844    */
    845   ScalarViewControllerABC.prototype._resetAttribute = function() {
    846     EmperorAttributeABC.prototype._resetAttribute.call(this);
    847
    848     var scope = this;
    849     this.$scaledValue.prop('checked', false);
    850
    851     _.each(this.decompViewDict, function(view) {
    852       scope.setPlottableAttributes(view, 1, view.decomp.plottable);
    853       view.needsUpdate = true;
    854     });
    855   };
    856
    857   /**
    858    *
    859    * Helper function to set the scale of plottable.
    860    *
    861    * Note, needs to be overriden by the subclass.
    862    *
    863    */
    864   ScalarViewControllerABC.prototype.setPlottableAttributes = function() {
    865   };
    866
    867   /**
    868    *
    869    * Method to do global updates to only the current view
    870    *
    871    * Note, needs to be overriden by the subclass.
    872    *
    873    */
    874   ScalarViewControllerABC.prototype.setAllPlottableAttributes = function() {
    875   };
    876
    877   /**
    878    *
    879    * Scaling function to use when the attribute is based on a metadata
    880    * category (used in getScale).
    881    *
    882    * @param {float} val The metadata value for the current sample.
    883    * @param {float} min The minimum metadata value in the dataset.
    884    * @param {float} range The span of the metadata values.
    885    *
    886    * @return {float} Attribute value
    887    *
    888    */
    889   ScalarViewControllerABC.prototype.scaleValue = function(val, min, range) {
    890     return 1;
    891   };
    892
    893   /**
    894    * Helper function to get the scale for each metadata value
    895    *
    896    * @param {String[]} values The values to get scale for
    897    * @param {Boolean} scaled Whether or not to scale by values or just reset to
    898    * standard scale (1.0)
    899    *
    900    * @throws {Error} No or one numeric value in category and trying to scale by
    901    * value
    902    */
    903   ScalarViewControllerABC.prototype.getScale = function(values, scaled) {
    904     var scale = {}, numbers, val, scope = this;
    905
    906     if (!scaled) {
    907       _.each(values, function(element) {
    908         scale[element] = 1.0;
    909       });
    910     }
    911     else {
    912       //See if we have numeric values, fail if no
    913       var split = util.splitNumericValues(values);
    914
    915       if (split.numeric.length < 2) {
    916         alert('Not enough numeric values in category, can not scale by value!');
    917         throw new Error('no numeric values');
    918       }
    919
    920       // Alert if we have non-numerics and scale them to 0
    921       if (split.nonNumeric.length > 0) {
    922         _.each(split.nonNumeric, function(element) {
    923           scale[element] = 0.0;
    924         });
    925         alert('Non-numeric values detected. These will be hidden!');
    926       }
    927
    928       // convert objects to numbers so we can map them to a color, we keep a
    929       // copy of the untransformed object so we can search the metadata
    930       numbers = _.map(split.numeric, parseFloat);
    931
    932       //scale remaining values between 1 and 5 scale
    933       var min = _.min(numbers);
    934       var max = _.max(numbers);
    935       var range = max - min;
    936
    937       _.each(split.numeric, function(element) {
    938         // note these elements are not numbers
    939         val = parseFloat(element);
    940
    941         // Scale the values, then round to 4 decimal places.
    942         scale[element] = scope.scaleValue(val, min, range);
    943       });
    944     }
    945     return scale;
    946   };
    947
    948   return {'EmperorViewControllerABC': EmperorViewControllerABC,
    949           'EmperorViewController': EmperorViewController,
    950           'EmperorAttributeABC': EmperorAttributeABC,
    951           'ScalarViewControllerABC': ScalarViewControllerABC};
    952 });
    953