diff --git a/empress/support_files/js/barplot-layer.js b/empress/support_files/js/barplot-layer.js
index f0c0119e..cc73d7f9 100644
--- a/empress/support_files/js/barplot-layer.js
+++ b/empress/support_files/js/barplot-layer.js
@@ -4,8 +4,9 @@ define([
"spectrum",
"Colorer",
"Legend",
+ "ColorOptionsHandler",
"util",
-], function ($, _, spectrum, Colorer, Legend, util) {
+], function ($, _, spectrum, Colorer, Legend, ColorOptionsHandler, util) {
/**
*
* @class BarplotLayer
@@ -96,6 +97,9 @@ define([
this.colorByFMColorMap = null;
this.colorByFMColorReverse = false;
this.colorByFMContinuous = false;
+ this.colorByFMContinuousManualScale = false;
+ this.colorByFMContinuousMin = null;
+ this.colorByFMContinuousMax = null;
this.colorByFMColorMapDiscrete = true;
this.defaultLength = BarplotLayer.DEFAULT_LENGTH;
this.scaleLengthByFM = false;
@@ -295,79 +299,25 @@ define([
colorDetailsDiv.classList.add("indented");
colorDetailsDiv.classList.add("hidden");
- // Add a row for choosing the color map
- var colormapP = colorDetailsDiv.appendChild(
- document.createElement("p")
- );
- var colormapLbl = colormapP.appendChild(
- document.createElement("label")
- );
- colormapLbl.innerText = "Color Map";
- var colormapSC = colormapP.appendChild(document.createElement("label"));
- colormapSC.classList.add("select-container");
- var colormapSelector = document.createElement("select");
- Colorer.addColorsToSelect(colormapSelector);
- colormapSC.appendChild(colormapSelector);
- colormapSelector.id =
- "barplot-layer-" + this.uniqueNum + "-fm-colormap";
- colormapLbl.setAttribute("for", colormapSelector.id);
-
- // Add a row for choosing whether the color scale should
- // be reversed
- var reverseColormapP = colorDetailsDiv.appendChild(
- document.createElement("p")
- );
- var reverseColormapLbl = reverseColormapP.appendChild(
- document.createElement("label")
- );
- reverseColormapLbl.innerText = "Reverse Color Map";
- var reverseColormapCheckbox = reverseColormapP.appendChild(
- document.createElement("input")
- );
- reverseColormapCheckbox.id =
- "barplot-layer-" + this.uniqueNum + "-fmcolor-reverse-chk";
- reverseColormapCheckbox.setAttribute("type", "checkbox");
- reverseColormapCheckbox.classList.add("empress-input");
- reverseColormapLbl.setAttribute("for", reverseColormapCheckbox.id);
-
- // Add a row for choosing the scale type (i.e. whether to use
- // continuous coloring or not)
- // This mimics Emperor's "Continuous values" checkbox
- var continuousValP = colorDetailsDiv.appendChild(
- document.createElement("p")
- );
- var continuousValLbl = continuousValP.appendChild(
- document.createElement("label")
- );
- continuousValLbl.innerText = "Continuous values?";
- var continuousValCheckbox = continuousValP.appendChild(
- document.createElement("input")
- );
- continuousValCheckbox.id =
- "barplot-layer-" + this.uniqueNum + "-fmcolor-continuous-chk";
- continuousValCheckbox.setAttribute("type", "checkbox");
- continuousValCheckbox.classList.add("empress-input");
- continuousValLbl.setAttribute("for", continuousValCheckbox.id);
- // Hide the "Continuous values?" stuff by default, since the default
- // colormap is discrete
- continuousValP.classList.add("hidden");
+ var fColorOptions = new ColorOptionsHandler(colorDetailsDiv, true);
// Initialize defaults to match the UI defaults (e.g. the default
// feature metadata field for coloring is the first in the selector)
+ var colorOptions = fColorOptions.getOptions();
this.colorByFMField = chgColorFMFieldSelector.value;
- this.colorByFMColorMap = colormapSelector.value;
- this.colorByFMColorReverse = reverseColormapCheckbox.checked;
+ this.colorByFMColorMap = colorOptions.color;
+ this.colorByFMColorReverse = colorOptions.reverse;
// Alter visibility of the color-changing details when the "Color
// by..." checkbox is clicked
$(chgColorCheckbox).change(function () {
+ colorOptions = fColorOptions.getOptions();
if (chgColorCheckbox.checked) {
colorDetailsDiv.classList.remove("hidden");
chgColorFMFieldSelector.disabled = false;
scope.colorByFM = true;
scope.colorByFMField = chgColorFMFieldSelector.value;
- scope.colorByFMColorMap = colormapSelector.value;
- scope.colorByFMColorReverse = reverseColormapCheckbox.checked;
- scope.colorByFMContinuous = continuousValCheckbox.checked;
+ scope.colorByFMColorMap = colorOptions.color;
+ scope.colorByFMColorReverse = colorOptions.reverse;
// Hide the default color row (since default colors
// aren't used when f.m. coloring is enabled)
dfltColorP.classList.add("hidden");
@@ -386,24 +336,19 @@ define([
$(chgColorFMFieldSelector).change(function () {
scope.colorByFMField = chgColorFMFieldSelector.value;
});
- $(colormapSelector).change(function () {
- scope.colorByFMColorMap = colormapSelector.value;
- // Hide the "Continuous values?" row based on the selected
- // colormap's type. This matches how Emperor's ColorViewController
- // hides/shows its "Continuous values" elements.
- if (Colorer.isColorMapDiscrete(scope.colorByFMColorMap)) {
- continuousValP.classList.add("hidden");
- scope.colorByFMColorMapDiscrete = true;
- } else {
- continuousValP.classList.remove("hidden");
- scope.colorByFMColorMapDiscrete = false;
- }
- });
- $(reverseColormapCheckbox).change(function () {
- scope.colorByFMColorReverse = reverseColormapCheckbox.checked;
- });
- $(continuousValCheckbox).change(function () {
- scope.colorByFMContinuous = continuousValCheckbox.checked;
+
+ // register color options
+ fColorOptions.registerObserver({
+ colorOptionsUpdate: function (options) {
+ scope.colorByFMColorMap = options.color;
+ scope.colorByFMColorReverse = options.reverse;
+ scope.colorByFMColorMapDiscrete = !options.continuousColoring;
+ scope.colorByFMContinuous = options.continuousColoring;
+ scope.colorByFMContinuousManualScale =
+ options.continuousManualScale;
+ scope.colorByFMContinuousMin = options.min;
+ scope.colorByFMContinuousMax = options.max;
+ },
});
// create default length settings
@@ -561,39 +506,7 @@ define([
chgFieldLbl.setAttribute("for", chgFieldSMFieldSelector.id);
chgFieldSC.appendChild(chgFieldSMFieldSelector);
- // Add a row for choosing the color map
- var colormapP = this.smDiv.appendChild(document.createElement("p"));
- var colormapLbl = colormapP.appendChild(
- document.createElement("label")
- );
- colormapLbl.innerText = "Color Map";
- var colormapSC = colormapP.appendChild(document.createElement("label"));
- colormapSC.classList.add("select-container");
- var colormapSelector = document.createElement("select");
- Colorer.addColorsToSelect(colormapSelector);
- colormapSC.appendChild(colormapSelector);
- colormapSelector.id =
- "barplot-layer-" + this.uniqueNum + "-sm-colormap";
- colormapLbl.setAttribute("for", colormapSelector.id);
-
- // Add a row for choosing whether the color scale should
- // be reversed
- var reverseColormapP = this.smDiv.appendChild(
- document.createElement("p")
- );
- var reverseColormapLbl = reverseColormapP.appendChild(
- document.createElement("label")
- );
- reverseColormapLbl.innerText = "Reverse Color Map";
- var reverseColormapCheckbox = reverseColormapP.appendChild(
- document.createElement("input")
- );
- reverseColormapCheckbox.id =
- "barplot-layer-" + this.uniqueNum + "-smcolor-reverse-chk";
- reverseColormapCheckbox.setAttribute("type", "checkbox");
- reverseColormapCheckbox.classList.add("empress-input");
- reverseColormapLbl.setAttribute("for", reverseColormapCheckbox.id);
-
+ var sColorOptions = new ColorOptionsHandler(this.smDiv);
var lenP = this.smDiv.appendChild(document.createElement("p"));
var lenLbl = lenP.appendChild(document.createElement("label"));
lenLbl.innerText = "Length";
@@ -606,18 +519,21 @@ define([
lenLbl.setAttribute("for", lenInput.id);
// TODO initialize defaults more sanely
+ var options = sColorOptions.getOptions();
this.colorBySMField = chgFieldSMFieldSelector.value;
- this.colorBySMColorMap = colormapSelector.value;
- this.colorBySMColorReverse = reverseColormapCheckbox.checked;
+ this.colorBySMColorMap = options.color;
+ this.colorBySMColorReverse = options.reverse;
$(chgFieldSMFieldSelector).change(function () {
scope.colorBySMField = chgFieldSMFieldSelector.value;
});
- $(colormapSelector).change(function () {
- scope.colorBySMColorMap = colormapSelector.value;
- });
- $(reverseColormapCheckbox).change(function () {
- scope.colorBySMColorReverse = reverseColormapCheckbox.checked;
+ sColorOptions.registerObserver({
+ colorOptionsUpdate: () => {
+ options = sColorOptions.getOptions();
+ scope.colorBySMColorMap = options.color;
+ scope.colorBySMColorReverse = options.reverse;
+ },
});
+
$(lenInput).change(function () {
scope.lengthSM = util.parseAndValidateNum(
lenInput,
diff --git a/empress/support_files/js/color-options-handler.js b/empress/support_files/js/color-options-handler.js
new file mode 100644
index 00000000..c9e06a3b
--- /dev/null
+++ b/empress/support_files/js/color-options-handler.js
@@ -0,0 +1,275 @@
+define(["underscore", "Colorer", "util"], function (_, Colorer, util) {
+ var TotalColorOptionsHandlers = 0;
+
+ function ColorOptionsHandler(container, enableContinuousColoring = false) {
+ // add count
+ TotalColorOptionsHandlers += 1;
+
+ this.container = container;
+ this.observers = [];
+ this.defaultColor = "discrete-coloring-qiime";
+ this.defaultReverseChk = false;
+ // create unique num
+ this.uniqueNum = TotalColorOptionsHandlers;
+ this.enableContinuousColoring = enableContinuousColoring;
+
+ // Add a row for choosing the color map
+ var colormapP = this.container.appendChild(document.createElement("p"));
+ var colormapLbl = colormapP.appendChild(
+ document.createElement("label")
+ );
+ colormapLbl.innerText = "Color Map";
+ var colormapSC = colormapP.appendChild(document.createElement("label"));
+ colormapSC.classList.add("select-container");
+ this.colormapSelector = document.createElement("select");
+ Colorer.addColorsToSelect(this.colormapSelector);
+ colormapSC.appendChild(this.colormapSelector);
+ this.colormapSelector.id =
+ "color-options-handler-" + this.uniqueNum + "-colormap-select";
+ colormapLbl.setAttribute("for", this.colormapSelector.id);
+
+ // Add a row for choosing whether the color scale should
+ // be reversed
+ var reverseColormapP = this.container.appendChild(
+ document.createElement("p")
+ );
+ var reverseColormapLbl = reverseColormapP.appendChild(
+ document.createElement("label")
+ );
+ reverseColormapLbl.innerText = "Reverse Color Map";
+ this.reverseColormapCheckbox = reverseColormapP.appendChild(
+ document.createElement("input")
+ );
+ this.reverseColormapCheckbox.id =
+ "color-options-handler-" + this.uniqueNum + "-reverse-chk";
+ this.reverseColormapCheckbox.setAttribute("type", "checkbox");
+ this.reverseColormapCheckbox.classList.add("empress-input");
+ reverseColormapLbl.setAttribute("for", this.reverseColormapCheckbox.id);
+
+ var scope = this;
+ var notify = function () {
+ var options = scope.getOptions();
+ _.each(scope.observers, function (obs) {
+ obs.colorOptionsUpdate(options);
+ });
+ };
+
+ if (this.enableContinuousColoring) {
+ // add continuous values checkbox
+ var continuousValP = this.container.appendChild(
+ document.createElement("p")
+ );
+ var continuousValLbl = continuousValP.appendChild(
+ document.createElement("label")
+ );
+ continuousValLbl.innerText = "Continuous values?";
+ this.continuousValCheckbox = continuousValP.appendChild(
+ document.createElement("input")
+ );
+ this.continuousValCheckbox.id =
+ "color-options-handler-" + this.uniqueNum + "-continuous-chk";
+ this.continuousValCheckbox.setAttribute("type", "checkbox");
+ this.continuousValCheckbox.classList.add("empress-input");
+ continuousValLbl.setAttribute("for", this.continuousValCheckbox.id);
+ // Hide the "Continuous values?" stuff by default, since the default
+ // colormap is discrete
+ continuousValP.classList.add("hidden");
+
+ // When we're working with a continuous colormap, provide users
+ // the ability to set the min/max of the input manually. See
+ // https://github.com/biocore/empress/pull/521.
+ var continuousManualScaleDiv = this.container.appendChild(
+ document.createElement("div")
+ );
+ continuousManualScaleDiv.classList.add("hidden");
+ continuousManualScaleDiv.classList.add("indented");
+ var continuousManualScaleManualP = continuousManualScaleDiv.appendChild(
+ document.createElement("p")
+ );
+ var continuousManualScaleLbl = continuousManualScaleManualP.appendChild(
+ document.createElement("label")
+ );
+ continuousManualScaleLbl.innerText =
+ "Manually set gradient boundaries?";
+ this.continuousManualScaleCheckbox = continuousManualScaleManualP.appendChild(
+ document.createElement("input")
+ );
+ this.continuousManualScaleCheckbox.id =
+ "color-options-handler-" +
+ this.uniqueNum +
+ "-continuous-scale-chk";
+ this.continuousManualScaleCheckbox.setAttribute("type", "checkbox");
+ this.continuousManualScaleCheckbox.classList.add("empress-input");
+ continuousManualScaleLbl.setAttribute(
+ "for",
+ this.continuousManualScaleCheckbox.id
+ );
+ var continuousMinMaxDiv = continuousManualScaleDiv.appendChild(
+ document.createElement("div")
+ );
+ continuousMinMaxDiv.classList.add("hidden");
+ continuousMinMaxDiv.classList.add("indented");
+
+ // add min scale input
+ var continuousMinP = continuousMinMaxDiv.appendChild(
+ document.createElement("p")
+ );
+ var continuousMinLbl = continuousMinP.appendChild(
+ document.createElement("label")
+ );
+ continuousMinLbl.innerText = "Minimum value";
+ this.continuousMinInput = continuousMinP.appendChild(
+ document.createElement("input")
+ );
+ this.continuousMinInput.setAttribute("type", "number");
+ this.continuousMinInput.classList.add("empress-input");
+ this.continuousMinInput.value = null;
+ this.continuousMinInput.id =
+ "color-options-handler-" +
+ this.uniqueNum +
+ "-continuous-min-input";
+ continuousMinLbl.setAttribute("for", this.continuousMinInput.id);
+
+ // add max scale input
+ var continuousMaxP = continuousMinMaxDiv.appendChild(
+ document.createElement("p")
+ );
+ var continuousMaxLbl = continuousMaxP.appendChild(
+ document.createElement("label")
+ );
+ continuousMaxLbl.innerText = "Maximum value";
+ this.continuousMaxInput = continuousMaxP.appendChild(
+ document.createElement("input")
+ );
+ this.continuousMaxInput.setAttribute("type", "number");
+ this.continuousMaxInput.classList.add("empress-input");
+ this.continuousMaxInput.value = null;
+ this.continuousMaxInput.id =
+ "color-options-handler-" +
+ this.uniqueNum +
+ "-continuous-max-input";
+ continuousMaxLbl.setAttribute("for", this.continuousMaxInput.id);
+
+ var validateNumInput = function (input) {
+ util.parseAndValidateNum(input, null);
+ };
+
+ // add events
+ this.continuousValCheckbox.onchange = () => {
+ if (scope.continuousValCheckbox.checked) {
+ continuousManualScaleDiv.classList.remove("hidden");
+ } else {
+ continuousManualScaleDiv.classList.add("hidden");
+ }
+ notify();
+ };
+ this.continuousManualScaleCheckbox.onchange = () => {
+ if (scope.continuousManualScaleCheckbox.checked) {
+ continuousMinMaxDiv.classList.remove("hidden");
+ } else {
+ continuousMinMaxDiv.classList.add("hidden");
+ }
+ notify();
+ };
+ this.continuousMinInput.onchange = () => {
+ validateNumInput(scope.continuousMinInput, null);
+ notify();
+ };
+ this.continuousMinInput.addEventListener("focusout", () => {
+ validateNumInput(scope.continuousMinInput, null);
+ notify();
+ });
+ this.continuousMaxInput.onchange = () => {
+ validateNumInput(scope.continuousMaxInput, null);
+ notify();
+ };
+ this.continuousMaxInput.addEventListener("focusout", () => {
+ validateNumInput(scope.continuousMaxInput, null);
+ notify();
+ });
+ }
+
+ this.colormapSelector.onchange = () => {
+ if (scope.enableContinuousColoring) {
+ if (Colorer.isColorMapDiscrete(scope.colormapSelector.value)) {
+ scope.continuousValCheckbox.checked = false;
+ continuousValP.classList.add("hidden");
+ continuousManualScaleDiv.classList.add("hidden");
+ } else {
+ continuousValP.classList.remove("hidden");
+ }
+ }
+ notify();
+ };
+ this.reverseColormapCheckbox.onchange = notify;
+ }
+
+ ColorOptionsHandler.prototype.registerObserver = function (obs) {
+ this.observers.push(obs);
+ };
+
+ ColorOptionsHandler.prototype.getOptions = function () {
+ var options = {
+ color: this.colormapSelector.value,
+ reverse: this.reverseColormapCheckbox.checked,
+ };
+ if (this.enableContinuousColoring) {
+ this._getContinuousColoringOptions(options);
+ }
+ return options;
+ };
+
+ ColorOptionsHandler.prototype._getContinuousColoringOptions = function (
+ options
+ ) {
+ options.continuousColoring = this.continuousValCheckbox.checked;
+ options.continuousManualScale = this.continuousManualScaleCheckbox.checked;
+ options.min = this.verifyMinBoundary();
+ options.max = this.verifyMaxBoundary();
+
+ if (!options.continuousColoring) {
+ // set options to not use continuous coloring
+ options.continuousManualScale = false;
+ options.min = null;
+ options.max = null;
+ } else if (!options.continuousManualScale) {
+ // set options to use default continuous scale
+ options.min = null;
+ options.max = null;
+ }
+ };
+
+ ColorOptionsHandler.prototype.reset = function () {
+ this.colormapSelector.value = this.defaultColor;
+ this.reverseColormapCheckbox.checked = this.defaultReverseChk;
+ };
+
+ ColorOptionsHandler.prototype.verifyMinBoundary = function () {
+ var min = parseFloat(this.continuousMinInput.value);
+
+ if (isNaN(min)) {
+ return "Minimum boundary value is missing.";
+ }
+
+ return min;
+ };
+
+ ColorOptionsHandler.prototype.verifyMaxBoundary = function () {
+ var min = parseFloat(this.continuousMinInput.value);
+ var max = parseFloat(this.continuousMaxInput.value);
+
+ if (isNaN(max)) {
+ return "Maximum boundary value is missing.";
+ }
+
+ // It should be noted that if min isNaN that this will always return
+ // false
+ if (max <= min) {
+ return "Maximum boundary must be greater than minimum boundary.";
+ }
+
+ return max;
+ };
+
+ return ColorOptionsHandler;
+});
diff --git a/empress/support_files/js/colorer.js b/empress/support_files/js/colorer.js
index f2c5f0f5..ee90b4c7 100644
--- a/empress/support_files/js/colorer.js
+++ b/empress/support_files/js/colorer.js
@@ -31,6 +31,13 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
* @param{Boolean} reverse Defaults to false. If true, the color scale
* will be reversed, with respect to its default
* orientation.
+ * @param{Array} domain Defaults to null. If this is not null, it is
+ * assumed to be an array of [min, max], where min and
+ * max are Numbers. If useQuantScale is true and the
+ * color map is sequential or diverging (i.e. we are
+ * creating a continuous colorscheme), then min and
+ * max will be used as the domain of the color map.
+ *
* @return{Colorer}
* constructs Colorer
*/
@@ -39,7 +46,8 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
values,
useQuantScale = false,
gradientIDSuffix = 0,
- reverse = false
+ reverse = false,
+ domain = null
) {
var scope = this;
@@ -81,6 +89,26 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
// whether or not to show a warning)
this._missingNonNumerics = false;
+ // Optional custom domain (or this will just be null)
+ this.domain = domain;
+ if (!_.isNull(this.domain)) {
+ // Perform sanity checking on the domain's entries (... or lack
+ // thereof). These problems should already have been caught by the
+ // ColorOptionsHandler, so this is just verifying that the code
+ // hasn't become haunted.
+ if (this.domain.length !== 2) {
+ throw new Error("Custom domain must have exactly 2 entries");
+ }
+ if (!_.isFinite(this.domain[0]) || !_.isFinite(this.domain[1])) {
+ throw new Error("Custom domain entries must be finite nums");
+ }
+ // I think chroma can actually handle this case -- it'll just
+ // flip the numbers. But let's not rely on that.
+ if (this.domain[0] >= this.domain[1]) {
+ throw new Error("Custom domain min must be < max");
+ }
+ }
+
/*** End continuous-scaling-specific things ***/
// Based on the color map, container, type and the value of
@@ -213,6 +241,35 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
}
};
+ /**
+ * Returns the minimum and maximum of a set of values, accounting for
+ * the possibility of a custom domain.
+ *
+ * Usually, this will just return the min and max values of the input
+ * array, but if this.domain has been set then this will just return the
+ * values from that instead.
+ *
+ * @param{Array} nums An array of values to be mapped to colors.
+ *
+ * @return {Object} minAndMax An object containing two keys: min (maps to
+ * the minimum value) and max (maps to the
+ * maximum value).
+ */
+ Colorer.prototype.getContinuousColorRange = function (nums) {
+ var min, max;
+ if (_.isNull(this.domain)) {
+ min = _.min(nums);
+ max = _.max(nums);
+ } else {
+ min = this.domain[0];
+ max = this.domain[1];
+ }
+ return {
+ min: min,
+ max: max,
+ };
+ };
+
/**
* Assigns colors from a sequential or diverging color palette (specified
* by this.color) for every value in this.sortedUniqueValues, taking into
@@ -243,14 +300,16 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
throw new Error("Category has less than 2 unique numeric values.");
}
var nums = _.map(split.numeric, parseFloat);
- var min = _.min(nums);
- var max = _.max(nums);
+ var range = this.getContinuousColorRange(nums);
+ var min = range.min;
+ var max = range.max;
var domain;
if (this.reverse) {
domain = [max, min];
} else {
domain = [min, max];
}
+
var interpolator;
if (this.color === Colorer.__GN_OR_PR) {
interpolator = chroma.scale(Colorer.__gnOrPr).domain(domain);
@@ -266,7 +325,7 @@ define(["chroma", "underscore", "util"], function (chroma, _, util) {
// Create SVG describing the gradient: basically, we sample the color
// map along the domain 101 times, and use these 101 colors to define
- // the
- - -
-- - -
+
- - -
-- - -
+Sample Presence Information 'LayoutsUtil' : './support_files/js/layouts-util', 'ExportUtil' : './support_files/js/export-util', 'TreeController' : './support_files/js/tree-controller', + 'ColorOptionsHandler' : './support_files/js/color-options-handler', 'EnableDisableTab': './support_files/js/enable-disable-tab', 'EnableDisableSidePanelTab': './support_files/js/enable-disable-side-panel-tab', 'EnableDisableAnimationTab': './support_files/js/enable-disable-animation-tab', diff --git a/tests/test-colorer.js b/tests/test-colorer.js index 112d0adc..29e3f2c6 100644 --- a/tests/test-colorer.js +++ b/tests/test-colorer.js @@ -656,5 +656,101 @@ require([ var colorer = new Colorer(colormap, eles, true); }, /Quantitative scales are not supported for custom colormaps/); }); + test("Test custom domains with a valid domain", function () { + var colorer = new Colorer( + "RdBu", + [-2, -1, 0, 1, 2.5], + true, + 0, + false, + [-2.5, 2.5] + ); + hexmap = colorer.getMapHex(); + equal(_.keys(hexmap).length, 5); + // Expected colors determined by trying + // chroma.scale(chroma.brewer.RdBu).domain([-2.5, 2.5])(n); + equal(hexmap["-2"], "#b2182b"); + equal(hexmap["-1"], "#f4a582"); + equal(hexmap["0"], "#f7f7f7"); + equal(hexmap["1"], "#92c5de"); + equal(hexmap["2.5"], "#053061"); + }); + test("Test custom domains that don't overlap values", function () { + // Silly thing that is technically allowed: the custom domain can + // not overlap at all with the values. in that case, the values + // will all either be equal to the lowest color (if the custom + // domain is higher than them) or equal to the highest color (if + // the custom domain is lower than them). + var strVals = ["-2", "-1", "0", "1", "2.5"]; + var numVals = [-2, -1, 0, 1, 2.5]; + + // Case 1: all the values are lower than the custom domain + var colorer = new Colorer("RdBu", numVals, true, 0, false, [5, 10]); + console.log(colorer); + hexmap = colorer.getMapHex(); + equal(_.keys(hexmap).length, 5); + for (i = 0; i < strVals.length; i++) { + equal(hexmap[strVals[i]], "#67001f"); + } + + // Case 2: all the values are higher than the custom domain + colorer = new Colorer("RdBu", numVals, true, 0, false, [-10, -5]); + hexmap = colorer.getMapHex(); + equal(_.keys(hexmap).length, 5); + for (i = 0; i < strVals.length; i++) { + equal(hexmap[strVals[i]], "#053061"); + } + }); + test("Test custom domains with invalid domains", function () { + throws(function () { + new Colorer("RdBu", [-2, -1, 0, 1, 2.5], true, 0, false, [1]); + }, /Custom domain must have exactly 2 entries/); + throws(function () { + new Colorer("RdBu", [-2, -1, 0, 1, 2.5], true, 0, false, []); + }, /Custom domain must have exactly 2 entries/); + throws(function () { + new Colorer("RdBu", [-2, -1, 0, 1, 2.5], true, 0, false, [ + 1, + 2, + 3, + ]); + }, /Custom domain must have exactly 2 entries/); + throws(function () { + new Colorer("RdBu", [-2, -1, 0, 1, 2.5], true, 0, false, [ + NaN, + 2, + ]); + }, /Custom domain entries must be finite nums/); + throws(function () { + new Colorer("RdBu", [-2, -1, 0, 1, 2.5], true, 0, false, [ + 1, + NaN, + ]); + }, /Custom domain entries must be finite nums/); + throws(function () { + new Colorer("RdBu", [-2, -1, 0, 1, 2.5], true, 0, false, [ + null, + null, + ]); + }, /Custom domain entries must be finite nums/); + throws(function () { + new Colorer("RdBu", [-2, -1, 0, 1, 2.5], true, 0, false, [ + null, + 3, + ]); + }, /Custom domain entries must be finite nums/); + throws(function () { + new Colorer("RdBu", [-2, -1, 0, 1, 2.5], true, 0, false, [ + 3, + 1, + ]); + }, /Custom domain min must be < max/); + throws(function () { + new Colorer("RdBu", [-2, -1, 0, 1, 2.5], true, 0, false, [ + 1, + 1, + ]); + }, /Custom domain min must be < max/); + }); }); });