      1 define([
      2     'jquery',
      3     'underscore',
      4     'util',
      5     'view',
      6     'viewcontroller',
      7     'color-editor',
      8     'chroma',
      9     'three'
     10 ], function($, _, util, DecompositionView, ViewControllers, Color, chroma,
     11             THREE) {
     12
     13   // we only use the base attribute class, no need to get the base class
     14   var EmperorAttributeABC = ViewControllers.EmperorAttributeABC;
     15   var ColorEditor = Color.ColorEditor, ColorFormatter = Color.ColorFormatter;
     16
     17   /**
     18    * @class ColorViewController
     19    *
     20    * Controls the color changing tab in Emperor. Takes care of changes to
     21    * color based on metadata, as well as making colorbars if coloring by a
     22    * numeric metadata category.
     23    *
     24    * @param {UIState} uiState The shared state
     25    * @param {Node} container Container node to create the controller in.
     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 {ColorViewController}
     32    * @constructs ColorViewController
     33    * @extends EmperorAttributeABC
     34    */
     35   function ColorViewController(uiState, container, decompViewDict) {
     36     var helpmenu = 'Change the colors of the attributes on the plot, such as ' +
     37       'spheres, vectors and ellipsoids.';
     38     var title = 'Color';
     39
     40     // Constant for width in slick-grid
     41     var SLICK_WIDTH = 25, scope = this;
     42     var name, value, colorItem;
     43
     44     // Create scale div and checkbox for whether using scalar data or not
     45     /**
     46      * @type {Node}
     47      *  jQuery object holding the colorbar div
     48      */
     49     this.$scaleDiv = $('<div>');
     50     /**
     51      * @type {Node}
     52      *  jQuery object holding the SVG colorbar
     53      */
     54     this.$colorScale = $("<svg width='90%' height='100%' " +
     55                          "style='display:block;margin:auto;'></svg>");
     56     this.$scaleDiv.append(this.$colorScale);
     57     this.$scaleDiv.hide();
     58     /**
     59      * @type {Node}
     60      *  jQuery object holding the continuous value checkbox
     61      */
     62     this.$scaled = $("<input type='checkbox'>");
     63     this.$scaled.prop('hidden', true);
     64     /**
     65      * @type {Node}
     66      *  jQuery object holding the continuous value label
     67      */
     68     this.$scaledLabel = $("<label for='scaled'>Continuous values</label>");
     69     this.$scaledLabel.prop('hidden', true);
     70
     71     // this class uses a colormap selector, so populate it before calling super
     72     // because otherwise the categorySelectionCallback will be called before the
     73     // data is populated
     74     /**
     75      * @type {Node}
     76      *  jQuery object holding the select box for the colormaps
     77      */
     78     this.$colormapSelect = $("<select class='emperor-tab-drop-down'>");
     79     var currType = ColorViewController.Colormaps[0].type;
     80     var selectOpts = $('<optgroup>').attr('label', currType);
     81
     82     for (var i = 0; i < ColorViewController.Colormaps.length; i++) {
     83       var colormap = ColorViewController.Colormaps[i];
     84       // Check if we are in a new optgroup
     85       if (colormap.type !== currType) {
     86         currType = colormap.type;
     87         scope.$colormapSelect.append(selectOpts);
     88         selectOpts = $('<optgroup>').attr('label', currType);
     89       }
     90       var colorItem = $('<option>')
     91         .attr('value', colormap.id)
     92         .attr('data-type', currType)
     93         .text(colormap.name);
     94       selectOpts.append(colorItem);
     95     }
     96     scope.$colormapSelect.append(selectOpts);
     97
     98     // Build the options dictionary
     99     var options = {
    100       'valueUpdatedCallback':
    101         function(e, args) {
    102           var val = args.item.category, color = args.item.value;
    103           var group = args.item.plottables;
    104           var element = scope.getView();
    105           scope.setPlottableAttributes(element, color, group);
    106         },
    107       'categorySelectionCallback':
    108         function(evt, params) {
    109           // we re-use this same callback regardless of whether the
    110           // color or the metadata category changed, maybe we can do
    111           // something better about this
    112           var category = scope.getMetadataField();
    113
    114           var discrete = $('option:selected', scope.$colormapSelect)
    115                            .attr('data-type') == DISCRETE;
    116           var colorScheme = scope.$colormapSelect.val();
    117
    118           var decompViewDict = scope.getView();
    119
    120           if (discrete) {
    121             var palette = ColorViewController.getPaletteColor(colorScheme);
    122             scope.$scaled.prop('checked', false);
    123             scope.$scaled.prop('hidden', true);
    124             scope.$scaledLabel.prop('hidden', true);
    125             scope.bodyGrid.selectionPalette = palette;
    126           } else {
    127             scope.$scaled.prop('hidden', false);
    128             scope.$scaledLabel.prop('hidden', false);
    129             scope.bodyGrid.selectionPalette = undefined;
    130           }
    131           var scaled = scope.$scaled.is(':checked');
    132           // getting all unique values per categories
    133           var uniqueVals = decompViewDict.decomp.getUniqueValuesByCategory(
    134             category);
    135           // getting color for each uniqueVals
    136           var colorInfo = ColorViewController.getColorList(
    137             uniqueVals, colorScheme, discrete, scaled);
    138           var attributes = colorInfo[0];
    139           // fetch the slickgrid-formatted data
    140           var data = decompViewDict.setCategory(
    141             attributes, scope.setPlottableAttributes, category);
    142
    143           if (scaled) {
    144             scope.$searchBar.prop('hidden', true);
    145             plottables = ColorViewController._nonNumericPlottables(
    146               uniqueVals, data);
    147             // Set SlickGrid for color of non-numeric values and show color bar
    148             // for rest if there are non numeric categories
    149             if (plottables.length > 0) {
    150               scope.setSlickGridDataset(
    151                 [{id: 0, category: 'Non-numeric values', value: '#64655d',
    152                   plottables: plottables}]);
    153             }
    154             else {
    155               scope.setSlickGridDataset([]);
    156             }
    157             scope.$scaleDiv.show();
    158             scope.$colorScale.html(colorInfo[1]);
    159           }
    160           else {
    161             scope.$searchBar.prop('hidden', false);
    162             scope.setSlickGridDataset(data);
    163             scope.$scaleDiv.hide();
    164           }
    165           // Call resize to update all methods for new shows/hides/resizes
    166           scope.resize();
    167         },
    168       'slickGridColumn': {
    169         id: 'title', name: '', field: 'value',
    170         sortable: false, maxWidth: SLICK_WIDTH,
    171         minWidth: SLICK_WIDTH,
    172         editor: ColorEditor,
    173         formatter: ColorFormatter
    174       }
    175     };
    176
    177     EmperorAttributeABC.call(this, uiState, container, title, helpmenu,
    178                              decompViewDict, options);
    179
    180     // the base-class will try to execute the "ready" callback, so we prevent
    181     // that by copying the property and setting the property to undefined.
    182     // This controller is not ready until the colormapSelect has signaled that
    183     // it is indeed ready.
    184     var ready = this.ready;
    185     this.ready = undefined;
    186
    187     // account for the searchbar
    188     this.$colormapSelect.insertAfter(this.$select);
    189     this.$header.append(this.$scaled);
    190     this.$header.append(this.$scaledLabel);
    191     this.$body.prepend(this.$scaleDiv);
    192
    193     // the chosen select can only be set when the document is ready
    194     $(function() {
    195       scope.$colormapSelect.on('chosen:ready', function() {
    196         if (ready !== null) {
    197           ready();
    198           scope.ready = ready;
    199         }
    200       });
    201       scope.$colormapSelect.chosen({width: '100%', search_contains: true});
    202       scope.$colormapSelect.chosen().change(options.categorySelectionCallback);
    203       scope.$scaled.on('change', options.categorySelectionCallback);
    204     });
    205
    206     return this;
    207   }
    208   ColorViewController.prototype = Object.create(EmperorAttributeABC.prototype);
    209   ColorViewController.prototype.constructor = EmperorAttributeABC;
    210
    211
    212   /**
    213    * Helper for building the plottables for non-numeric data
    214    *
    215    * @param {String[]} uniqueVals Array of unique values for the category
    216    * @param {Object} data SlickGrid formatted data from setCategory function
    217    *
    218    * @return {Plottable[]} Array of plottables for all non-numeric values
    219    * @private
    220    *
    221    */
    222    ColorViewController._nonNumericPlottables = function(uniqueVals, data) {
    223      // Filter down to only non-numeric data
    224      var split = util.splitNumericValues(uniqueVals);
    225      var plotList = data.filter(function(x) {
    226        return $.inArray(x.category, split.nonNumeric) !== -1;
    227      });
    228      // Build list of plottables and return
    229      var plottables = [];
    230      for (var i = 0; i < plotList.length; i++) {
    231        plottables = plottables.concat(plotList[i].plottables);
    232      }
    233      return plottables;
    234    };
    235
    236   /**
    237    * Sets whether or not elements in the tab can be modified.
    238    *
    239    * @param {Boolean} trulse option to enable elements.
    240    */
    241   ColorViewController.prototype.setEnabled = function(trulse) {
    242     EmperorAttributeABC.prototype.setEnabled.call(this, trulse);
    243
    244     this.$colormapSelect.prop('disabled', !trulse).trigger('chosen:updated');
    245     this.$scaled.prop('disabled', !trulse);
    246   };
    247
    248   /**
    249    *
    250    * Private method to reset the color of all the objects in every
    251    * decomposition view to red.
    252    *
    253    * @extends EmperorAttributeABC
    254    * @private
    255    *
    256    */
    257   ColorViewController.prototype._resetAttribute = function() {
    258     EmperorAttributeABC.prototype._resetAttribute.call(this);
    259
    260     _.each(this.decompViewDict, function(view) {
    261       view.setColor(0xff0000);
    262     });
    263   };
    264
    265   /**
    266    * Method that returns whether or not the coloring is continuous and the
    267    * values have been scaled.
    268    *
    269    * @return {Boolean} True if the coloring is continuous and the data is
    270    * scaled, false otherwise.
    271    */
    272   ColorViewController.prototype.isColoringContinuous = function() {
    273     // the bodygrid can have at most one element (NA values)
    274     return (this.$scaled.is(':checked') &&
    275             this.getSlickGridDataset().length <= 1);
    276   };
    277
    278   /**
    279    *
    280    * Wrapper for generating a list of colors that corresponds to all samples
    281    * in the plot by coloring type requested
    282    *
    283    * @param {String[]} values list of objects to generate a color for, usually a
    284    * category in a given metadata column.
    285    * @param {String} [map = {'discrete-coloring-qiime'|'Viridis'}] name of the
    286    * color map to use, see ColorViewController.Colormaps
    287    * @see ColorViewController.Colormaps
    288    * @param {Boolean} discrete Whether to treat colormap requested as a
    289    * discrete set of colors or use interpolation to create gradient of colors
    290    * @param {Boolean} [scaled = false] Whether to use a scaled colormap or
    291    * equidistant colors for each value
    292    * @see ColorViewController.getDiscreteColors
    293    * @see ColorViewController.getInterpolatedColors
    294    * @see ColorViewController.getScaledColors
    295    *
    296    * @return {Object} colors The object containing the hex colors keyed to
    297    * each sample
    298    * @return {String} gradientSVG The SVG string for the scaled data or null
    299    *
    300    */
    301   ColorViewController.getColorList = function(values, map, discrete, scaled) {
    302     var colors = {}, gradientSVG;
    303     scaled = scaled || false;
    304
    305     if (_.findWhere(ColorViewController.Colormaps, {id: map}) === undefined) {
    306       throw new Error('Could not find ' + map + ' as a colormap.');
    307     }
    308
    309     // 1 color and continuous coloring should return the first element in map
    310     if (values.length == 1 && discrete === false) {
    311       colors[values[0]] = chroma.brewer[map][0];
    312       return [colors, gradientSVG];
    313     }
    314
    315     //Call helper function to create the required colormap type
    316     if (discrete) {
    317       colors = ColorViewController.getDiscreteColors(values, map);
    318     }
    319     else if (scaled) {
    320       try {
    321         var info = ColorViewController.getScaledColors(values, map);
    322       } catch (e) {
    323         alert('Category can not be shown as continuous values. Continuous ' +
    324               'coloration requires at least 2 numeric values in the category.');
    325         throw new Error('non-numeric category');
    326       }
    327       colors = info[0];
    328       gradientSVG = info[1];
    329     }
    330     else {
    331       colors = ColorViewController.getInterpolatedColors(values, map);
    332     }
    333     return [colors, gradientSVG];
    334   };
    335
    336   /**
    337    *
    338    * Retrieve a discrete color set.
    339    *
    340    * @param {String[]} values list of objects to generate a color for, usually a
    341    * category in a given metadata column.
    342    * @param {String} [map = 'discrete-coloring-qiime'] name of the color map to
    343    * use, see ColorViewController.Colormaps
    344    * @see ColorViewController.Colormaps
    345    *
    346    * @return {Object} colors The object containing the hex colors keyed to
    347    * each sample
    348    *
    349    */
    350   ColorViewController.getDiscreteColors = function(values, map) {
    351     map = ColorViewController.getPaletteColor(map);
    352     var size = map.length;
    353     var colors = {};
    354     for (var i = 0; i < values.length; i++) {
    355         mapIndex = i - (Math.floor(i / size) * size);
    356         colors[values[i]] = map[mapIndex];
    357     }
    358     return colors;
    359   };
    360
    361   /**
    362    *
    363    * Retrieve a whole discrete palette color set.
    364    *
    365    * @param {String} [map = 'discrete-coloring-qiime'] name of the color map to
    366    * use, see ColorViewController.Colormaps
    367    * @see ColorViewController.Colormaps
    368    *
    369    * @return {Object} map for selected color palette
    370    *
    371    */
    372   ColorViewController.getPaletteColor = function(map) {
    373     map = map || 'discrete-coloring-qiime';
    374
    375     if (map == 'discrete-coloring-qiime') {
    376       map = ColorViewController._qiimeDiscrete;
    377     } else {
    378       map = chroma.brewer[map];
    379     }
    380
    381     return map;
    382   };
    383
    384   /**
    385    *
    386    * Retrieve a scaled color set.
    387    *
    388    * @param {String[]} values Objects to generate a color for, usually a
    389    * category in a given metadata column.
    390    * @param {String} [map = 'Viridis'] name of the discrete color map to use.
    391    * @param {String} [nanColor = '#64655d'] Color to use for non-numeric values.
    392    *
    393    * @return {Object} colors The object containing the hex colors keyed to
    394    * each sample
    395    * @return {String} gradientSVG The SVG string for the scaled data or null
    396    *
    397    */
    398   ColorViewController.getScaledColors = function(values, map, nanColor) {
    399     map = map || 'Viridis';
    400     nanColor = nanColor || '#64655d';
    401     map = chroma.brewer[map];
    402
    403     // Get list of only numeric values, error if none
    404     var split = util.splitNumericValues(values), numbers;
    405     if (split.numeric.length < 2) {
    406       throw new Error('non-numeric category');
    407     }
    408
    409     // convert objects to numbers so we can map them to a color, we keep a copy
    410     // of the untransformed object so we can search the metadata
    411     numbers = _.map(split.numeric, parseFloat);
    412     min = _.min(numbers);
    413     max = _.max(numbers);
    414
    415     var interpolator = chroma.scale(map).domain([min, max]);
    416     var colors = {};
    417
    418     // Color all the numeric values
    419     _.each(split.numeric, function(element) {
    420       colors[element] = interpolator(+element).hex();
    421     });
    422     // Gray out (or assign a user-specified color for) non-numeric values
    423     _.each(split.nonNumeric, function(element) {
    424       colors[element] = nanColor;
    425     });
    426     // Build the SVG showing the gradient of colors for numeric values
    427     var mid = (min + max) / 2;
    428     // We retrieve 101 colors from along the gradient. This is because we want
    429     // to specify a color for each integer percentage in the range [0%, 100%],
    430     // which contains 101 integers (since we're starting at 0:
    431     // 100 - 0 + 1 = 101). See https://github.com/biocore/emperor/issues/788.
    432     var stopColors = interpolator.colors(101);
    433     var gradientSVG = '<defs>';
    434     gradientSVG += '<linearGradient id="Gradient" x1="0" x2="0" y1="1" y2="0">';
    435     for (var pos = 0; pos < stopColors.length; pos++) {
    436       gradientSVG += '<stop offset="' + pos + '%" stop-color="' +
    437         stopColors[pos] + '"/>';
    438     }
    439     gradientSVG += '</linearGradient></defs><rect id="gradientRect" ' +
    440       'width="20" height="95%" fill="url(#Gradient)"/>';
    441
    442     gradientSVG += '<text x="25" y="12px" font-family="sans-serif" ' +
    443       'font-size="12px" text-anchor="start">' + max + '</text>';
    444     gradientSVG += '<text x="25" y="50%" font-family="sans-serif" ' +
    445       'font-size="12px" text-anchor="start">' + mid + '</text>';
    446     gradientSVG += '<text x="25" y="95%" font-family="sans-serif" ' +
    447       'font-size="12px" text-anchor="start">' + min + '</text>';
    448     return [colors, gradientSVG];
    449   };
    450
    451   /**
    452    *
    453    * Retrieve an interpolatd color set.
    454    *
    455    * @param {String[]} values Objects to generate a color for, usually a
    456    * category in a given metadata column.
    457    * @param {String} [map = 'Viridis'] name of the color map to use.
    458    *
    459    * @return {Object} colors The object containing the hex colors keyed to
    460    * each sample.
    461    *
    462    */
    463   ColorViewController.getInterpolatedColors = function(values, map) {
    464     map = map || 'Viridis';
    465     map = chroma.brewer[map];
    466
    467     var total = values.length;
    468     // Logic here adapted from Colorer.assignOrdinalScaledColors() in Empress'
    469     // codebase
    470     var interpolator = chroma.scale(map).domain([0, values.length - 1]);
    471     var colors = {};
    472     for (var i = 0; i < values.length; i++) {
    473       colors[values[i]] = interpolator(i).hex();
    474     }
    475     return colors;
    476   };
    477
    478   /**
    479    * Converts the current instance into a JSON string.
    480    *
    481    * @return {Object} JSON ready representation of self.
    482    */
    483   ColorViewController.prototype.toJSON = function() {
    484     var json = EmperorAttributeABC.prototype.toJSON.call(this);
    485     json.colormap = this.$colormapSelect.val();
    486     json.continuous = this.$scaled.is(':checked');
    487     return json;
    488   };
    489
    490   /**
    491    * Decodes JSON string and modifies its own instance variables accordingly.
    492    *
    493    * @param {Object} Parsed JSON string representation of self.
    494    */
    495   ColorViewController.prototype.fromJSON = function(json) {
    496     var data;
    497
    498     // NOTE: We do not call super here because of the non-numeric values issue
    499     // Order here is important. We want to set all the extra controller
    500     // settings before we load from json, as they can override the JSON when set
    501     this.setMetadataField(json.category);
    502
    503     this.setEnabled(true);
    504
    505     // if the category is null, then there's nothing to set about the state
    506     // of the controller
    507     if (json.category === null) {
    508       return;
    509     }
    510
    511     this.$colormapSelect.val(json.colormap);
    512     this.$colormapSelect.trigger('chosen:updated');
    513     this.$scaled.prop('checked', json.continuous);
    514     this.$scaled.trigger('change');
    515
    516     // Fetch and set the SlickGrid-formatted data
    517     // Need to take into account the existence of the non-numeric values grid
    518     // information from the continuous data.
    519     var decompViewDict = this.getView();
    520     if (this.$scaled.is(':checked')) {
    521       // Get the current SlickGrid data and update with the saved color
    522       data = this.getSlickGridDataset();
    523       data[0].value = json.data['Non-numeric values'];
    524       this.setPlottableAttributes(
    525         decompViewDict, json.data['Non-numeric values'], data[0].plottables);
    526     }
    527     else {
    528       data = decompViewDict.setCategory(
    529         json.data, this.setPlottableAttributes, json.category);
    530     }
    531
    532     if (!_.isEmpty(data)) {
    533       this.setSlickGridDataset(data);
    534     }
    535   };
    536
    537   /**
    538    * Resizes the container and the individual elements.
    539    *
    540    * Note, the consumer of this class, likely the main controller should call
    541    * the resize function any time a resizing event happens.
    542    *
    543    * @param {Float} width the container width.
    544    * @param {Float} height the container height.
    545    */
    546   ColorViewController.prototype.resize = function(width, height) {
    547     this.$body.height(this.$canvas.height() - this.$header.height());
    548     this.$body.width(this.$canvas.width());
    549
    550     if (this.$scaled.is(':checked')) {
    551       this.$scaleDiv.css('height', (this.$body.height() / 2) + 'px');
    552       this.$gridDiv.css('height', (this.$body.height() / 2 - 20) + 'px');
    553     }
    554     else {
    555       this.$gridDiv.css('height', '100%');
    556     }
    557     // call super, most of the header and body resizing logic is done there
    558     EmperorAttributeABC.prototype.resize.call(this, width, height);
    559   };
    560
    561   /**
    562    * Helper function to set the color of plottable
    563    *
    564    * @param {scope} object , the scope where the plottables exist
    565    * @param {color} string , hexadecimal representation of a color, which will
    566    * be applied to the plottables
    567    * @param {group} array of objects, list of object that should be changed in
    568    * scope
    569    */
    570   ColorViewController.prototype.setPlottableAttributes =
    571   function(scope, color, group) {
    572     scope.setColor(color, group);
    573   };
    574
    575   var DISCRETE = 'Discrete';
    576   var SEQUENTIAL = 'Sequential';
    577   var DIVERGING = 'Diverging';
    578   /**
    579    * @type {Object}
    580    * Color maps available, along with what type of colormap they are.
    581    */
    582   ColorViewController.Colormaps = [
    583     {id: 'discrete-coloring-qiime', name: 'Classic QIIME Colors',
    584      type: DISCRETE},
    585     {id: 'Paired', name: 'Paired', type: DISCRETE},
    586     {id: 'Accent', name: 'Accent', type: DISCRETE},
    587     {id: 'Dark2', name: 'Dark', type: DISCRETE},
    588     {id: 'Set1', name: 'Set1', type: DISCRETE},
    589     {id: 'Set2', name: 'Set2', type: DISCRETE},
    590     {id: 'Set3', name: 'Set3', type: DISCRETE},
    591     {id: 'Pastel1', name: 'Pastel1', type: DISCRETE},
    592     {id: 'Pastel2', name: 'Pastel2', type: DISCRETE},
    593
    594     {id: 'Viridis', name: 'Viridis', type: SEQUENTIAL},
    595     {id: 'Reds', name: 'Reds', type: SEQUENTIAL},
    596     {id: 'RdPu', name: 'Red-Purple', type: SEQUENTIAL},
    597     {id: 'Oranges', name: 'Oranges', type: SEQUENTIAL},
    598     {id: 'OrRd', name: 'Orange-Red', type: SEQUENTIAL},
    599     {id: 'YlOrBr', name: 'Yellow-Orange-Brown', type: SEQUENTIAL},
    600     {id: 'YlOrRd', name: 'Yellow-Orange-Red', type: SEQUENTIAL},
    601     {id: 'YlGn', name: 'Yellow-Green', type: SEQUENTIAL},
    602     {id: 'YlGnBu', name: 'Yellow-Green-Blue', type: SEQUENTIAL},
    603     {id: 'Greens', name: 'Greens', type: SEQUENTIAL},
    604     {id: 'GnBu', name: 'Green-Blue', type: SEQUENTIAL},
    605     {id: 'Blues', name: 'Blues', type: SEQUENTIAL},
    606     {id: 'BuGn', name: 'Blue-Green', type: SEQUENTIAL},
    607     {id: 'BuPu', name: 'Blue-Purple', type: SEQUENTIAL},
    608     {id: 'Purples', name: 'Purples', type: SEQUENTIAL},
    609     {id: 'PuRd', name: 'Purple-Red', type: SEQUENTIAL},
    610     {id: 'PuBuGn', name: 'Purple-Blue-Green', type: SEQUENTIAL},
    611     {id: 'Greys', name: 'Greys', type: SEQUENTIAL},
    612
    613     {id: 'Spectral', name: 'Spectral', type: DIVERGING},
    614     {id: 'RdBu', name: 'Red-Blue', type: DIVERGING},
    615     {id: 'RdYlGn', name: 'Red-Yellow-Green', type: DIVERGING},
    616     {id: 'RdYlBu', name: 'Red-Yellow-Blue', type: DIVERGING},
    617     {id: 'RdGy', name: 'Red-Grey', type: DIVERGING},
    618     {id: 'PiYG', name: 'Pink-Yellow-Green', type: DIVERGING},
    619     {id: 'BrBG', name: 'Brown-Blue-Green', type: DIVERGING},
    620     {id: 'PuOr', name: 'Purple-Orange', type: DIVERGING},
    621     {id: 'PRGn', name: 'Purple-Green', type: DIVERGING}
    622   ];
    623
    624   // taken from the qiime/colors.py module; a total of 24 colors
    625   /** @private */
    626   ColorViewController._qiimeDiscrete = ['#ff0000', '#0000ff', '#f27304',
    627   '#008000', '#91278d', '#ffff00', '#7cecf4', '#f49ac2', '#5da09e', '#6b440b',
    628   '#808080', '#f79679', '#7da9d8', '#fcc688', '#80c99b', '#a287bf', '#fff899',
    629   '#c49c6b', '#c0c0c0', '#ed008a', '#00b6ff', '#a54700', '#808000', '#008080'];
    630
    631   return ColorViewController;
    632 });
    633