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