From 3b0fdf68a553adbb57ec58b6282de2f3dc6a09f3 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 15 Apr 2016 14:20:39 -0700 Subject: [PATCH 01/11] add ability to scale coloring to numeric values --- .../support_files/js/color-view-controller.js | 104 +++++++++++------- 1 file changed, 67 insertions(+), 37 deletions(-) diff --git a/emperor/support_files/js/color-view-controller.js b/emperor/support_files/js/color-view-controller.js index 39580314..c798e4f6 100644 --- a/emperor/support_files/js/color-view-controller.js +++ b/emperor/support_files/js/color-view-controller.js @@ -56,6 +56,11 @@ define([ var SLICK_WIDTH = 25, scope = this; var name, value, colorItem; + // Create checkbox for whether using scalar data or not + this.$colorScale = $(""); + this.$scaled = $(""); + this.$scaledLabel = $("") + // this class uses a colormap selector, so populate it before calling super // because otherwise the categorySelectionCallback will be called before the // data is populated @@ -72,46 +77,62 @@ define([ } // Build the options dictionary - var options = {'valueUpdatedCallback':function(e, args) { - var val = args.item.category, color = args.item.value; - var group = args.item.plottables; - var element = scope.decompViewDict[scope.getActiveDecompViewKey()]; - ColorViewController.setPlottableAttributes(element, color, group); - }, - 'categorySelectionCallback':function(evt, params) { - // we re-use this same callback regardless of whether the - // color or the metadata category changed, maybe we can do - // something better about this - var category = scope.$select.val(); - var colorScheme = scope.$colormapSelect.val(); - - var k = scope.getActiveDecompViewKey(); - var decompViewDict = scope.decompViewDict[k]; - - // getting all unique values per categories - var uniqueVals = decompViewDict.decomp.getUniqueValuesByCategory(category); - // getting color for each uniqueVals - var attributes = ColorViewController.getColorList(uniqueVals, colorScheme); - // fetch the slickgrid-formatted data - var data = decompViewDict.setCategory(attributes, ColorViewController.setPlottableAttributes, category); - - scope.setSlickGridDataset(data); - }, - 'slickGridColumn':{id: 'title', name: '', field: 'value', + var options = { + 'valueUpdatedCallback': + function(e, args) { + var val = args.item.category, color = args.item.value; + var group = args.item.plottables; + var element = scope.decompViewDict[scope.getActiveDecompViewKey()]; + ColorViewController.setPlottableAttributes(element, color, group); + }, + 'categorySelectionCallback': + function(evt, params) { + // we re-use this same callback regardless of whether the + // color or the metadata category changed, maybe we can do + // something better about this + var category = scope.$select.val(); + var scaled = scope.$scaled.is(':checked'); + var colorScheme = scope.$colormapSelect.val(); + + var k = scope.getActiveDecompViewKey(); + var decompViewDict = scope.decompViewDict[k]; + + // getting all unique values per categories + var uniqueVals = decompViewDict.decomp.getUniqueValuesByCategory(category); + // getting color for each uniqueVals + var colorInfo = ColorViewController.getColorList(uniqueVals, colorScheme, scaled); + var attributes = colorInfo[0]; + // fetch the slickgrid-formatted data + var data = decompViewDict.setCategory(attributes, ColorViewController.setPlottableAttributes, category); + if(scaled) { + scope.setSlickGridDataset({}); + this.$colorScale.html(colorInfo[1]); + } else { + scope.setSlickGridDataset(data); + } + }, + 'slickGridColumn': { + id: 'title', name: '', field: 'value', sortable: false, maxWidth: SLICK_WIDTH, minWidth: SLICK_WIDTH, autoEdit: true, editor: ColorEditor, - formatter: ColorFormatter}}; + formatter: ColorFormatter + } + }; EmperorAttributeABC.call(this, container, title, helpmenu, decompViewDict, options); this.$header.append(this.$colormapSelect); + this.$header.append(this.$scaled); + this.$header.append(this.$scaledLabel); + this.$body.append(this.$colorScale); // the chosen select can only be set when the document is ready $(function() { scope.$colormapSelect.chosen({width: "100%", search_contains: true}); scope.$colormapSelect.chosen().change(options.categorySelectionCallback); + scope.$scaled.on('change', options.categorySelectionCallback); }); return this; @@ -128,15 +149,18 @@ define([ * category in a given metadata column. * @param {map} name of the color map to use, see * ColorViewController.Colormaps + * @param {scaled} Whether to use a scaled colormap or equidistant colors for + * each value. * * * This function will generate a list of coloring values depending on the * coloring scheme that the system is currently using (discrete or * continuous). */ - ColorViewController.getColorList = function(values, map) { + ColorViewController.getColorList = function(values, map, scaled) { var colors = {}, numColors = values.length-1, counter=0, discrete = false; var interpolator; + var scaled = scaled || false; if (ColorViewController.Colormaps.indexOf(map) === -1) { throw new Error("Could not find "+map+" in the available colormaps"); @@ -153,23 +177,30 @@ define([ return colors; } - if (discrete === false){ + if (discrete === false) { map = chroma.brewer[map]; interpolator = chroma.bezier([map[0], map[3], map[4], map[5], map[8]]); + if (scaled === true) { + //assuming we have numeric since scaled called + var numericValues = _.map(values, Number); + interpolator = interpolator.scale().domain([_.min(numericValues), _.max(numericValues)]); + } } - for(var index in values){ - if(discrete){ + for (var index in values) { + if (discrete) { // get the next available color colors[values[index]] = ColorViewController.getDiscreteColor(index, map); } - else{ - colors[values[index]] = interpolator(counter/numColors).hex(); + else if (scaled === true) { + colors[values[index]] = interpolator(Number(values[index])).hex(); + } else { + colors[values[index]] = interpolator(counter / numColors).hex(); counter = counter + 1; } } - return colors; + return [colors, interpolator]; }; /** @@ -188,9 +219,8 @@ define([ * */ ColorViewController.getDiscreteColor = function(index, map){ - if (map === undefined){ - map = 'discrete-coloring'; - } + var map = map || 'discrete-coloring'; + if (_.has(ColorViewController._discreteColormaps, map) === false){ throw new Error("Could not find "+map+" as a discrete colormap."); } From bea10c89d63cf18d686cdc0e839c1e4dd5ea8f9e Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 15 Apr 2016 15:39:03 -0700 Subject: [PATCH 02/11] make body a container div for the slickgrid --- emperor/support_files/css/emperor.css | 38 +++++++++++++++++++ .../support_files/js/color-view-controller.js | 33 ++++++++++++---- emperor/support_files/js/view-controller.js | 9 ++++- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/emperor/support_files/css/emperor.css b/emperor/support_files/css/emperor.css index a4373582..6c3e3c3b 100644 --- a/emperor/support_files/css/emperor.css +++ b/emperor/support_files/css/emperor.css @@ -306,6 +306,44 @@ a.media-button:hover img{ display: none; } +.gradient { + width: 85%; + white-space: nowrap; + position: relative; + display: inline-block; + top: 4px; + padding-bottom: 15px; +} + + +.gradient .domain-min { + position: absolute; + left: 0; + font-size: 11px; + bottom: 3px; +} +.gradient .domain-med { + position: absolute; + right: 25%; + left: 25%; + text-align: center; + font-size: 11px; + bottom: 3px; +} +.gradient .domain-max { + position: absolute; + right: 0; + font-size: 11px; + bottom: 3px; +} + + +.grad-step { + display: inline-block; + height: 20px; + width: 1%; +} + /* * SlickGrid specific * diff --git a/emperor/support_files/js/color-view-controller.js b/emperor/support_files/js/color-view-controller.js index c798e4f6..50d6414f 100644 --- a/emperor/support_files/js/color-view-controller.js +++ b/emperor/support_files/js/color-view-controller.js @@ -57,7 +57,7 @@ define([ var name, value, colorItem; // Create checkbox for whether using scalar data or not - this.$colorScale = $(""); + this.$colorScale = $("
"); this.$scaled = $(""); this.$scaledLabel = $("") @@ -104,9 +104,10 @@ define([ var attributes = colorInfo[0]; // fetch the slickgrid-formatted data var data = decompViewDict.setCategory(attributes, ColorViewController.setPlottableAttributes, category); - if(scaled) { + if (scaled) { scope.setSlickGridDataset({}); - this.$colorScale.html(colorInfo[1]); + scope.$gridDiv.hide(); + scope.$colorScale.html(colorInfo[1]); } else { scope.setSlickGridDataset(data); } @@ -127,6 +128,7 @@ define([ this.$header.append(this.$scaled); this.$header.append(this.$scaledLabel); this.$body.append(this.$colorScale); + console.log(this.$body); // the chosen select can only be set when the document is ready $(function() { @@ -159,7 +161,7 @@ define([ */ ColorViewController.getColorList = function(values, map, scaled) { var colors = {}, numColors = values.length-1, counter=0, discrete = false; - var interpolator; + var interpolator, scaleInterpolator, min, max; var scaled = scaled || false; if (ColorViewController.Colormaps.indexOf(map) === -1) { @@ -183,7 +185,10 @@ define([ if (scaled === true) { //assuming we have numeric since scaled called var numericValues = _.map(values, Number); - interpolator = interpolator.scale().domain([_.min(numericValues), _.max(numericValues)]); + min = _.min(numericValues); + max = _.max(numericValues); + interpolator = interpolator.scale(); + scaleInterpolator = interpolator.domain([min, max]); } } @@ -193,14 +198,28 @@ define([ colors[values[index]] = ColorViewController.getDiscreteColor(index, map); } else if (scaled === true) { - colors[values[index]] = interpolator(Number(values[index])).hex(); + colors[values[index]] = scaleInterpolator(Number(values[index])).hex(); } else { colors[values[index]] = interpolator(counter / numColors).hex(); counter = counter + 1; } } - return [colors, interpolator]; + //if scaled, generate the scale and add to div. + if (scaled) { + var min = _.min(numericValues); + var max = _.max(numericValues); + var mid = (min + max) / 2; + var gradient = ''; + for (var s = 0; s <= 1; s += 0.01) { + gradient += ""; + } + gradient += "" + min + "" + gradient += "" + mid + "" + gradient += "" + max + "" + } + + return [colors, gradient]; }; /** diff --git a/emperor/support_files/js/view-controller.js b/emperor/support_files/js/view-controller.js index 5ec16902..bd498159 100644 --- a/emperor/support_files/js/view-controller.js +++ b/emperor/support_files/js/view-controller.js @@ -83,7 +83,7 @@ define([ this.$header.css('margin', '0 auto'); this.$header.css('width', '100%'); - this.$body = $('
'); + this.$body = $('
'); this.$body.css('margin', '0 auto'); this.$body.css('width', '100%'); @@ -243,6 +243,11 @@ define([ throw Error('The decomposition view dictionary cannot be empty'); } this.decompViewDict = decompViewDict; + this.$gridDiv = $('
'); + this.$gridDiv.css('margin', '0 auto'); + this.$gridDiv.css('width', '100%'); + this.$gridDiv.css('height', '100%'); + this.$body.append(this.$gridDiv); this.metadataField = null; this.activeViewKey = null; @@ -356,7 +361,7 @@ define([ columns.unshift(options.slickGridColumn); } - this.bodyGrid = new Slick.Grid(this.$body, [], columns, gridOptions); + this.bodyGrid = new Slick.Grid(this.$gridDiv, [], columns, gridOptions); // subscribe to events when a cell is changed this.bodyGrid.onCellChange.subscribe(options.valueUpdatedCallback); From 0e5eb81a89b154c686da634e40517ca54423a9e2 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 15 Apr 2016 16:01:19 -0700 Subject: [PATCH 03/11] rename checkbox --- emperor/support_files/js/color-view-controller.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/emperor/support_files/js/color-view-controller.js b/emperor/support_files/js/color-view-controller.js index 50d6414f..7b41900c 100644 --- a/emperor/support_files/js/color-view-controller.js +++ b/emperor/support_files/js/color-view-controller.js @@ -59,7 +59,7 @@ define([ // Create checkbox for whether using scalar data or not this.$colorScale = $("
"); this.$scaled = $(""); - this.$scaledLabel = $("") + this.$scaledLabel = $("") // this class uses a colormap selector, so populate it before calling super // because otherwise the categorySelectionCallback will be called before the @@ -107,9 +107,12 @@ define([ if (scaled) { scope.setSlickGridDataset({}); scope.$gridDiv.hide(); + scope.$colorScale.show(); scope.$colorScale.html(colorInfo[1]); } else { scope.setSlickGridDataset(data); + scope.$colorScale.hide(); + scope.$gridDiv.show(); } }, 'slickGridColumn': { @@ -161,7 +164,7 @@ define([ */ ColorViewController.getColorList = function(values, map, scaled) { var colors = {}, numColors = values.length-1, counter=0, discrete = false; - var interpolator, scaleInterpolator, min, max; + var interpolator, min, max; var scaled = scaled || false; if (ColorViewController.Colormaps.indexOf(map) === -1) { @@ -187,8 +190,7 @@ define([ var numericValues = _.map(values, Number); min = _.min(numericValues); max = _.max(numericValues); - interpolator = interpolator.scale(); - scaleInterpolator = interpolator.domain([min, max]); + interpolator = interpolator.scale().domain([min, max]); } } @@ -198,7 +200,7 @@ define([ colors[values[index]] = ColorViewController.getDiscreteColor(index, map); } else if (scaled === true) { - colors[values[index]] = scaleInterpolator(Number(values[index])).hex(); + colors[values[index]] = interpolator(Number(values[index])).hex(); } else { colors[values[index]] = interpolator(counter / numColors).hex(); counter = counter + 1; @@ -210,8 +212,9 @@ define([ var min = _.min(numericValues); var max = _.max(numericValues); var mid = (min + max) / 2; + step = (max - min) / 100; var gradient = ''; - for (var s = 0; s <= 1; s += 0.01) { + for (var s = min; s <= max; s += step) { gradient += ""; } gradient += "" + min + "" From f1fd3f6e134d09a205de32f46f9329197a54e6c8 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 15 Apr 2016 16:37:12 -0700 Subject: [PATCH 04/11] show scale bar as svg --- emperor/support_files/css/emperor.css | 38 ------------------- .../support_files/js/color-view-controller.js | 25 ++++++++---- 2 files changed, 17 insertions(+), 46 deletions(-) diff --git a/emperor/support_files/css/emperor.css b/emperor/support_files/css/emperor.css index 6c3e3c3b..a4373582 100644 --- a/emperor/support_files/css/emperor.css +++ b/emperor/support_files/css/emperor.css @@ -306,44 +306,6 @@ a.media-button:hover img{ display: none; } -.gradient { - width: 85%; - white-space: nowrap; - position: relative; - display: inline-block; - top: 4px; - padding-bottom: 15px; -} - - -.gradient .domain-min { - position: absolute; - left: 0; - font-size: 11px; - bottom: 3px; -} -.gradient .domain-med { - position: absolute; - right: 25%; - left: 25%; - text-align: center; - font-size: 11px; - bottom: 3px; -} -.gradient .domain-max { - position: absolute; - right: 0; - font-size: 11px; - bottom: 3px; -} - - -.grad-step { - display: inline-block; - height: 20px; - width: 1%; -} - /* * SlickGrid specific * diff --git a/emperor/support_files/js/color-view-controller.js b/emperor/support_files/js/color-view-controller.js index 7b41900c..f7a3067c 100644 --- a/emperor/support_files/js/color-view-controller.js +++ b/emperor/support_files/js/color-view-controller.js @@ -57,7 +57,9 @@ define([ var name, value, colorItem; // Create checkbox for whether using scalar data or not - this.$colorScale = $("
"); + this.$scaleDiv = $('
') + this.$colorScale = $(""); + this.$scaleDiv.append(this.$colorScale); this.$scaled = $(""); this.$scaledLabel = $("") @@ -107,10 +109,12 @@ define([ if (scaled) { scope.setSlickGridDataset({}); scope.$gridDiv.hide(); + scope.$scaleDiv.show(); scope.$colorScale.show(); scope.$colorScale.html(colorInfo[1]); } else { scope.setSlickGridDataset(data); + scope.$scaleDiv.hide(); scope.$colorScale.hide(); scope.$gridDiv.show(); } @@ -131,7 +135,6 @@ define([ this.$header.append(this.$scaled); this.$header.append(this.$scaledLabel); this.$body.append(this.$colorScale); - console.log(this.$body); // the chosen select can only be set when the document is ready $(function() { @@ -213,16 +216,22 @@ define([ var max = _.max(numericValues); var mid = (min + max) / 2; step = (max - min) / 100; - var gradient = ''; + var stopColors = []; for (var s = min; s <= max; s += step) { - gradient += ""; + stopColors.push(interpolator(s).hex()); } - gradient += "" + min + "" - gradient += "" + mid + "" - gradient += "" + max + "" + + var gradientSVG = '' + for (pos in stopColors) { + gradientSVG += ''; + } + gradientSVG += '' + gradientSVG += '' + min + '' + gradientSVG += '' + mid + '' + gradientSVG += '' + max + '' } - return [colors, gradient]; + return [colors, gradientSVG]; }; /** From 26199c91440a06ff257b07ffb040546386ba7555 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Fri, 15 Apr 2016 19:20:04 -0700 Subject: [PATCH 05/11] add all colormaps. Fix #440 --- .../support_files/js/color-view-controller.js | 131 +++++++++++------- 1 file changed, 81 insertions(+), 50 deletions(-) diff --git a/emperor/support_files/js/color-view-controller.js b/emperor/support_files/js/color-view-controller.js index f7a3067c..d9281ed0 100644 --- a/emperor/support_files/js/color-view-controller.js +++ b/emperor/support_files/js/color-view-controller.js @@ -10,7 +10,6 @@ define([ // we only use the base attribute class, no need to get the base class var EmperorAttributeABC = ViewControllers.EmperorAttributeABC; var ColorEditor = Color.ColorEditor, ColorFormatter = Color.ColorFormatter; - /** * @name ColorViewController * @@ -67,16 +66,24 @@ define([ // because otherwise the categorySelectionCallback will be called before the // data is populated this.$colormapSelect = $(""); + this.$scaled = $(""); this.$scaledLabel = $("") // this class uses a colormap selector, so populate it before calling super @@ -69,17 +76,18 @@ define([ var currType = ColorViewController.Colormaps[0].type; var selectOpts = $('').attr('label', currType); - for (var i = 0; i < ColorViewController.Colormaps.length; i++) { + for (var i = 1; i < ColorViewController.Colormaps.length; i++) { var colormap = ColorViewController.Colormaps[i]; - //Check if we are in a new optgroup - if (colormap.type != currType) { + // Check if we are in a new optgroup + if (colormap.hasOwnProperty('type')) { currType = colormap.type; scope.$colormapSelect.append(selectOpts); selectOpts = $('').attr('label', currType); + continue; } var colorItem = $('