diff --git a/empress/support_files/css/empress.css b/empress/support_files/css/empress.css index 718026fd..9d2b5e81 100644 --- a/empress/support_files/css/empress.css +++ b/empress/support_files/css/empress.css @@ -609,7 +609,7 @@ p.side-header button:hover, #legend-main { margin: 20px; min-width: 150px; - max-width: 33vw; + max-width: 30vw; min-height: 30px; max-height: 85vh; padding-bottom: 0.1px; diff --git a/empress/support_files/js/barplot-layer.js b/empress/support_files/js/barplot-layer.js index f0c0119e..196c5e95 100644 --- a/empress/support_files/js/barplot-layer.js +++ b/empress/support_files/js/barplot-layer.js @@ -3,9 +3,9 @@ define([ "underscore", "spectrum", "Colorer", - "Legend", + "BarplotLegend", "util", -], function ($, _, spectrum, Colorer, Legend, util) { +], function ($, _, spectrum, Colorer, BarplotLegend, util) { /** * * @class BarplotLayer @@ -634,14 +634,14 @@ define([ this.colorLegendDiv.classList.add("hidden"); this.colorLegendDiv.classList.add("legend"); this.colorLegendDiv.classList.add("barplot-layer-legend"); - this.colorLegend = new Legend(this.colorLegendDiv); + this.colorLegend = new BarplotLegend(this.colorLegendDiv); this.layerDiv.appendChild(this.colorLegendDiv); this.lengthLegendDiv = document.createElement("div"); this.lengthLegendDiv.classList.add("hidden"); this.lengthLegendDiv.classList.add("legend"); this.lengthLegendDiv.classList.add("barplot-layer-legend"); - this.lengthLegend = new Legend(this.lengthLegendDiv); + this.lengthLegend = new BarplotLegend(this.lengthLegendDiv); this.layerDiv.appendChild(this.lengthLegendDiv); // TODO: if possible, making the legend text selectable (overriding @@ -757,7 +757,7 @@ define([ * effect) will be excluded from the Array. However, if all legends are * active, the order in the Array will always be color then length. * - * @return {Array} Array of Legend objects + * @return {Array} Array of BarplotLegend objects */ BarplotLayer.prototype.getLegends = function () { var containedLegends = [this.colorLegend, this.lengthLegend]; diff --git a/empress/support_files/js/barplot-legend.js b/empress/support_files/js/barplot-legend.js new file mode 100644 index 00000000..db93b60d --- /dev/null +++ b/empress/support_files/js/barplot-legend.js @@ -0,0 +1,35 @@ +define(["Legend"], function (Legend) { + /** + * @class SampleFeatureColorLegend + */ + function BarplotLegend(container) { + // call Legend constructor + Legend.call(this, container); + + /** + * @type {String} + * Text to display at the bottom of the continuous legend when some + * values in a field are either missing or non-numeric. + */ + this.continuousMissingNonNumericWarning = + "Some value(s) in this field were missing and/or not numeric. " + + "These value(s) have been left out of the gradient, and no " + + "bar(s) have been drawn for them."; + + /** + * @type {String} + * Short version of the above warning, shown for the same legends when + * exported to SVG + */ + this.continuousMissingNonNumericWarningShort = + "Missing / non-numeric value(s) omitted."; + } + + // inherit Legend functions + BarplotLegend.prototype = Object.create(Legend.prototype); + + // set BarplotLegend's constructor + BarplotLegend.prototype.constructor = BarplotLegend; + + return BarplotLegend; +}); diff --git a/empress/support_files/js/empress.js b/empress/support_files/js/empress.js index fc803003..a9005fa6 100644 --- a/empress/support_files/js/empress.js +++ b/empress/support_files/js/empress.js @@ -6,7 +6,7 @@ define([ "VectorOps", "CanvasEvents", "BarplotPanel", - "Legend", + "SampleFeatureColorLegend", "util", "chroma", "LayoutsUtil", @@ -20,7 +20,7 @@ define([ VectorOps, CanvasEvents, BarplotPanel, - Legend, + SampleFeatureColorLegend, util, chroma, LayoutsUtil, @@ -159,11 +159,13 @@ define([ } /** - * @type {Legend} + * @type {SampleFeatureColorLegend} * Legend describing the way the tree is colored. * @private */ - this._legend = new Legend(document.getElementById("legend-main")); + this._legend = new SampleFeatureColorLegend( + document.getElementById("legend-main") + ); /** * @type {BiomTable} @@ -2489,14 +2491,19 @@ define([ * @param{Boolean} reverse Defaults to false. If true, the color scale * will be reversed, with respect to its default * orientation. - * + * @param{Boolean} continuous Defaults to false. If true, the colorer will + * try to use a gradient color scale. + * @param{Function} continousFailedFunc The function to call if continuous + * coloring failed. * @return {Object} Maps unique values in this f. metadata column to colors */ Empress.prototype.colorByFeatureMetadata = function ( cat, color, method, - reverse = false + reverse = false, + continuous = false, + continousFailedFunc = null ) { var fmInfo = this.getUniqueFeatureMetadataInfo(cat, method); var sortedUniqueValues = fmInfo.sortedUniqueValues; @@ -2509,26 +2516,70 @@ define([ obs[uniqueVal] = new Set(uniqueValueToFeatures[uniqueVal]); }); - // assign colors to unique values - var colorer = new Colorer( - color, - sortedUniqueValues, - undefined, - undefined, - reverse - ); - + var colorer; + try { + // assign colors to unique values + colorer = new Colorer( + color, + sortedUniqueValues, + continuous, + // Colorer will create a special gradient ID using the number + // we pass into this parameter. This allows empress to display + // multiple gradients at the same time without them overriding + // each other. Currently, the barplots are set up to start at + // 0. So, we set this value to -1 here to avoid conflict with + // the barplot gradients; this allows us to display the + // feature metadata gradient alongside the + // barplot gradients. + continuous ? -1 : undefined, + reverse + ); + } catch (err) { + // If the Colorer construction failed (should only have + // happened if the user asked for continuous values but the + // selected field doesn't have at least 2 unique numeric + // values), then we open a toast message about this error and + // use discrete coloring instead. + continuous = false; + var msg = + 'Error with assigning colors: the feature metadata field "' + + cat + + '" has less than 2 unique numeric values, so it cannot be ' + + "used for continuous coloring. " + + "Using discrete coloring instead."; + util.toastMsg("Feature metadata coloring error", msg, 5000); + // assign colors to unique values + colorer = new Colorer( + color, + sortedUniqueValues, + continuous, + undefined, + reverse + ); + continousFailedFunc(); + } // colors for drawing the tree var cm = colorer.getMapRGB(); // colors for the legend - var keyInfo = colorer.getMapHex(); + var keyInfo; + if (continuous) { + keyInfo = colorer.getGradientInfo(); + } else { + keyInfo = colorer.getMapHex(); + } // if the tree has been sheared then categories in obs maybe empty. // getUniqueFeatureMetadataInfo() does not filter out those categories // so that the same color can be assigned to each value in obs. util.removeEmptyArrayKeys(keyInfo, uniqueValueToFeatures); + // In the case of continuous coloring, non-numeric values will not be + // added to cm and is the only such case where the keys in obs (after + // projectObservations has been called) and keys in cm will differ. + // Thus, we need to remove the non-numeric keys from obs. + obs = _.pick(obs, Object.keys(cm)); + // Do upwards propagation only if the coloring method is "tip" if (method === "tip") { obs = this._projectObservations(obs, false); @@ -2540,7 +2591,12 @@ define([ // color tree this._colorTree(obs, cm); - this.updateLegendCategorical(cat, keyInfo); + this.resizeLegend(); + if (continuous) { + this._legend.addContinuousKey(cat, keyInfo); + } else { + this.updateLegendCategorical(cat, keyInfo); + } return keyInfo; }; @@ -2554,7 +2610,7 @@ define([ * * 2) Assigns each internal node to a group if all of its children belong * to the same group. - *@t + * * 3) Remove empty groups from return object. * * Note: All tips that are not passed into obs are considered to belong to @@ -3030,7 +3086,7 @@ define([ // project groups up tree // Note: if _projectObservations was called, then if an internal node // belongs to a group, all of its descendants will belong to the - // same group. However, this is not guaranteed if _projectOBservations + // same group. However, this is not guaranteed if _projectObservations // was not called. Thus, this loop is used to guarantee that if an // internal node belongs to a group then all of its descendants belong // to the same group. diff --git a/empress/support_files/js/legend.js b/empress/support_files/js/legend.js index b10e3ae2..a0b81e7b 100644 --- a/empress/support_files/js/legend.js +++ b/empress/support_files/js/legend.js @@ -1,6 +1,7 @@ define(["jquery", "underscore", "util"], function ($, _, util) { /** * + * @Abstract * @class Legend * * Creates a legend within a given HTML element. (You'll need to call @@ -11,7 +12,6 @@ define(["jquery", "underscore", "util"], function ($, _, util) { * will be added. * * @return {Legend} - * @constructs Legend */ function Legend(container) { /** @@ -102,6 +102,25 @@ define(["jquery", "underscore", "util"], function ($, _, util) { */ this._minLengthVal = null; this._maxLengthVal = null; + + /** + * @type {String} + * Text to display at the bottom of the continuous legend when some + * values in a field are either missing or non-numeric. + */ + this.continuousMissingNonNumericWarning = null; + + /** + * @type {String} + * Short version of the above warning, shown for the same legends when + * exported to SVG + */ + this.continuousMissingNonNumericWarningShort = null; + + // make Legend an abstract class + if (this.constructor === Legend) { + throw new Error("Abstract class Legend can not be instantiated."); + } } /** @@ -179,14 +198,28 @@ define(["jquery", "underscore", "util"], function ($, _, util) { "svg" ); containerSVG.setAttribute("width", "100%"); - containerSVG.setAttribute("height", "100%"); + containerSVG.setAttribute("height", "80%"); containerSVG.setAttribute("style", "display: block; margin: auto;"); // just kinda plop the combined SVG code into containerSVG's HTML containerSVG.innerHTML = totalHTMLSVG; - this._container.appendChild(containerSVG); + + // We need to put the svg container inside a div: otherwise, some unwanted + // behavior will occur when users resize the legend. + // The gradient bar's height is set to be 80% of the legend's + // height. This works for most cases; however, some issues come up when + // the user resizes the legend -- the height of the gradient color + // bar will continuously be set to 80% of the legend's height, which + // can hide the warning message(s) that appear beneath the gradient. + // By putting the svg container inside a div, the gradient color bar + // will be set to 80% of the initial size of the legend and will remain + // fixed when users resize the legend. + var fixSizeDiv = document.createElement("div"); + fixSizeDiv.appendChild(containerSVG); + this._container.appendChild(fixSizeDiv); if (this._missingNonNumericWarningShown) { + var missingText = this.getMissingNonNumericWarning(); var warningP = document.createElement("p"); - warningP.innerText = Legend.CONTINUOUS_MISSING_NON_NUMERIC_WARNING; + warningP.innerText = missingText.full; warningP.classList.add("side-panel-notes"); // All legends have white-space: nowrap; set to prevent color // labels from breaking onto the next line (which would look @@ -220,6 +253,12 @@ define(["jquery", "underscore", "util"], function ($, _, util) { * their assigned color, expressed in hex format. */ Legend.prototype.addCategoricalKey = function (name, info) { + if (_.isEmpty(info)) { + throw new Error( + "Can't create a categorical legend when there are no " + + "categories in the info" + ); + } this.clear(); this.addTitle(name); this._sortedCategories = util.naturalSort(_.keys(info)); @@ -533,6 +572,7 @@ define(["jquery", "underscore", "util"], function ($, _, util) { // But first, let's add a warning about missing / non-numeric values if // needed. if (this._missingNonNumericWarningShown) { + var missingText = this.getMissingNonNumericWarning(); // We use a hanging baseline to add some extra vertical space // between the gradient minimum value and the warning text. This // seems to look nice. @@ -542,9 +582,9 @@ define(["jquery", "underscore", "util"], function ($, _, util) { '" y="' + (gradientTopY + gradientHeight + Legend.HALF_LINE_HEIGHT) + '" dominant-baseline="hanging">' + - Legend.CONTINUOUS_MISSING_NON_NUMERIC_WARNING_SHORT + + missingText.short + "\n"; - texts.push(Legend.CONTINUOUS_MISSING_NON_NUMERIC_WARNING_SHORT); + texts.push(missingText.short); } _.each(texts, function (text) { maxLineWidth = Math.max( @@ -733,17 +773,22 @@ define(["jquery", "underscore", "util"], function ($, _, util) { }; }; - // Shown at the bottom of continuous legends in the page when some values - // in a continuous field can't be represented on a gradient - Legend.CONTINUOUS_MISSING_NON_NUMERIC_WARNING = - "Some value(s) in this field were missing and/or not numeric. " + - "These value(s) have been left out of the gradient, and no bar(s) " + - "have been drawn for them."; - - // Short version of the above warning, shown for the same legends when - // exported to SVG - Legend.CONTINUOUS_MISSING_NON_NUMERIC_WARNING_SHORT = - "Missing / non-numeric value(s) omitted."; + /** + * Returns the full and short versions of the continuous missing + * non-numeric warning messages. + * + * @return {Object} This object is formatted as follows: + * { + * full: messageString, + * short: messageString + * } + */ + Legend.prototype.getMissingNonNumericWarning = function () { + return { + full: this.continuousMissingNonNumericWarning, + short: this.continuousMissingNonNumericWarningShort, + }; + }; // Various SVG attributes stored here since they're used every time the // export function is called diff --git a/empress/support_files/js/sample-feature-color-legend.js b/empress/support_files/js/sample-feature-color-legend.js new file mode 100644 index 00000000..e0e94afc --- /dev/null +++ b/empress/support_files/js/sample-feature-color-legend.js @@ -0,0 +1,36 @@ +define(["Legend"], function (Legend) { + /** + * @class SampleFeatureColorLegend + */ + function SampleFeatureColorLegend(container) { + // call Legend constructor + Legend.call(this, container); + + /** + * @type {String} + * Text to display at the bottom of the continuous legend when some + * values in a continuous are either missing or non-numeric. + */ + this.continuousMissingNonNumericWarning = + "Some value(s) in this field were missing and/or not numeric. " + + "These value(s) have been left out of the gradient, and the " + + "corresponding nodes have been set to the default color."; + + /** + * @type {String} + * Short version of the above warning, shown for the same legends when + * exported to SVG + */ + this.continuousMissingNonNumericWarningShort = + "Missing / non-numeric value(s)' associated nodes left as " + + "default color."; + } + + // inherit Legend functions + SampleFeatureColorLegend.prototype = Object.create(Legend.prototype); + + // set SampleFeatureColorLegend's constructor + SampleFeatureColorLegend.prototype.constructor = SampleFeatureColorLegend; + + return SampleFeatureColorLegend; +}); diff --git a/empress/support_files/js/side-panel-handler.js b/empress/support_files/js/side-panel-handler.js index 5a50128e..63d0e771 100644 --- a/empress/support_files/js/side-panel-handler.js +++ b/empress/support_files/js/side-panel-handler.js @@ -74,6 +74,10 @@ define(["underscore", "Colorer", "util"], function (_, Colorer, util) { this.fCollapseCladesChk = document.getElementById( "feature-collapse-chk" ); + this.fContinuousDiv = document.getElementById("feature-continous-div"); + this.fContinuousChk = document.getElementById( + "feature-continuous-color-chk" + ); this.fLineWidth = document.getElementById("feature-line-width"); this.fUpdateBtn = document.getElementById("feature-update"); this.fUpdateBtnP = document.getElementById("fm-update-container"); @@ -250,6 +254,7 @@ define(["underscore", "Colorer", "util"], function (_, Colorer, util) { fLineWidth: { value: 0 }, fMethodChk: { checked: true }, fCollapseCladesChk: { checked: false }, + fContinuousChk: { checked: false }, }, [this.fAddOpts, this.fUpdateBtnP] ); @@ -337,15 +342,23 @@ define(["underscore", "Colorer", "util"], function (_, Colorer, util) { * Colors the tree based on the feature metadata coloring settings. */ SidePanel.prototype._colorFeatureTree = function () { + var scope = this; var colBy = this.fSel.value; var col = this.fColor.value; var coloringMethod = this.fMethodChk.checked ? "tip" : "all"; var reverse = this.fReverseColor.checked; + var continuous = + !this.fContinuousDiv.classList.contains("hidden") && + this.fContinuousChk.checked; var keyInfo = this.empress.colorByFeatureMetadata( colBy, col, coloringMethod, - reverse + reverse, + continuous, + () => { + scope.fContinuousChk.checked = false; + } ); if (_.isEmpty(keyInfo)) { util.toastMsg( @@ -636,10 +649,16 @@ define(["underscore", "Colorer", "util"], function (_, Colorer, util) { }; var showUpdateBtn = function () { + if (Colorer.isColorMapDiscrete(scope.fColor.value)) { + scope.fContinuousDiv.classList.add("hidden"); + } else { + scope.fContinuousDiv.classList.remove("hidden"); + } scope.fUpdateBtnP.classList.remove("hidden"); }; this.fSel.onchange = showUpdateBtn; this.fColor.onchange = showUpdateBtn; + this.fContinuousChk.onchange = showUpdateBtn; this.fReverseColor.onchange = showUpdateBtn; this.fLineWidth.onchange = showUpdateBtn; this.fMethodChk.onchange = function () { diff --git a/empress/support_files/templates/empress-template.html b/empress/support_files/templates/empress-template.html index f7172fc9..f82c9b40 100644 --- a/empress/support_files/templates/empress-template.html +++ b/empress/support_files/templates/empress-template.html @@ -115,6 +115,8 @@
+ + +
+
Sample Presence Information
'LayoutsUtil' : './support_files/js/layouts-util',
'ExportUtil' : './support_files/js/export-util',
'TreeController' : './support_files/js/tree-controller',
+ 'SampleFeatureColorLegend': './support_files/js/sample-feature-color-legend',
+ 'BarplotLegend': './support_files/js/barplot-legend',
'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-animation-panel-handler.js b/tests/test-animation-panel-handler.js
index 60bd5d6e..80e873cb 100644
--- a/tests/test-animation-panel-handler.js
+++ b/tests/test-animation-panel-handler.js
@@ -5,7 +5,7 @@ require([
"BPTree",
"Empress",
"BiomTable",
- "Legend",
+ "SampleFeatureColorLegend",
"util",
"Colorer",
"EnableDisableAnimationTab",
@@ -16,7 +16,7 @@ require([
BPTree,
Empress,
BiomTable,
- Legend,
+ SampleFeatureColorLegend,
util,
Colorer,
EnableDisableAnimationTab
@@ -115,7 +115,7 @@ require([
var empress = new Empress({ size: 0 }, biom, [], [], {}, {}, null);
var animator = new Animator(
empress,
- new Legend(
+ new SampleFeatureColorLegend(
document.createElement("div"),
document.createElement("div"),
document.createElement("div")
diff --git a/tests/test-empress.js b/tests/test-empress.js
index 10ed7598..d8cfde75 100644
--- a/tests/test-empress.js
+++ b/tests/test-empress.js
@@ -353,6 +353,128 @@ require([
}
});
+ test("Test colorByFeatureMetadata, continuous", function () {
+ // get color map
+ var cm = this.empress.colorByFeatureMetadata(
+ "f1",
+ "Viridis",
+ "tip",
+ false,
+ true
+ );
+ var properties = [
+ "gradientID",
+ "gradientSVG",
+ "maxValStr",
+ "midValStr",
+ "minValStr",
+ "missingNonNumerics",
+ "pageSVG",
+ ];
+
+ // make sure color gradient returns correct keyInfo
+ var resultProperties = util.naturalSort(Object.keys(cm));
+ deepEqual(resultProperties, properties);
+
+ deepEqual(cm.gradientID, "Gradient-1");
+ deepEqual(cm.maxValStr, "2");
+ deepEqual(cm.midValStr, "1.5");
+ deepEqual(cm.minValStr, "1");
+ deepEqual(cm.missingNonNumerics, false);
+
+ // make sure nodes were assigned correct color
+ var node;
+ var group1 = new Set([2, 3, 4]);
+ var group2 = new Set([1, 6]);
+ for (node = 1; node <= 7; node++) {
+ if (group1.has(node)) {
+ deepEqual(
+ this.empress.getNodeInfo(node, "color"),
+ 5505348,
+ "node: " + node
+ );
+ } else if (group2.has(node)) {
+ deepEqual(
+ this.empress.getNodeInfo(node, "color"),
+ 2484478,
+ "node: " + node
+ );
+ } else {
+ deepEqual(this.empress.getNodeInfo(node, "color"), 3289650);
+ }
+ }
+ });
+
+ test("Test colorByFeatureMetadata, continuous", function () {
+ // get color map
+ var cm = this.empress.colorByFeatureMetadata(
+ "f1",
+ "Viridis",
+ "tip",
+ false,
+ true
+ );
+ var properties = [
+ "gradientID",
+ "gradientSVG",
+ "maxValStr",
+ "midValStr",
+ "minValStr",
+ "missingNonNumerics",
+ "pageSVG",
+ ];
+
+ // make sure color gradient returns correct keyInfo
+ var resultProperties = util.naturalSort(Object.keys(cm));
+ deepEqual(resultProperties, properties);
+
+ // make sure nodes were assigned correct color
+ var node;
+ var group1 = new Set([2, 3, 4]);
+ var group2 = new Set([1, 6]);
+ for (node = 1; node <= 7; node++) {
+ if (group1.has(node)) {
+ deepEqual(
+ this.empress.getNodeInfo(node, "color"),
+ 5505348,
+ "node: " + node
+ );
+ } else if (group2.has(node)) {
+ deepEqual(
+ this.empress.getNodeInfo(node, "color"),
+ 2484478,
+ "node: " + node
+ );
+ } else {
+ deepEqual(this.empress.getNodeInfo(node, "color"), 3289650);
+ }
+ }
+ });
+
+ test("Test colorByFeatureMetadata, continuous: failure due to no numeric values", function () {
+ // hack to add a metadata column with no numeric data
+ this.empress._featureMetadataColumns = ["f1", "f2", "f3"];
+ this.empress._tipMetadata = {
+ 1: ["2", "2", "n1"],
+ 2: ["1", "2", "n2"],
+ 3: ["1", "2", "n3"],
+ 6: ["2", "2", "n4"],
+ };
+ var failedFunctionCalled = false;
+ var failedFunction = () => {
+ failedFunctionCalled = true;
+ };
+ var cm = this.empress.colorByFeatureMetadata(
+ "f3",
+ "Viridis",
+ "tip",
+ false,
+ true,
+ failedFunction
+ );
+ ok(failedFunctionCalled, "continousFailedFunc was called.");
+ });
+
test("Test _projectObservations, all tips in obs", function () {
var obs = {
g1: new Set([2, 3]),
diff --git a/tests/test-legend.js b/tests/test-legend.js
index 9eb88c2f..437c2ef1 100644
--- a/tests/test-legend.js
+++ b/tests/test-legend.js
@@ -2,15 +2,32 @@ require([
"jquery",
"chroma",
"UtilitiesForTesting",
- "Legend",
+ "BarplotLegend",
+ "SampleFeatureColorLegend",
"Colorer",
-], function ($, chroma, UtilitiesForTesting, Legend, Colorer) {
+], function (
+ $,
+ chroma,
+ UtilitiesForTesting,
+ BarplotLegend,
+ SampleFeatureColorLegend,
+ Colorer
+) {
$(document).ready(function () {
- module("Legend", {
+ module("Legends", {
// Create and destroy the container HTML element within the test,
// to avoid having to directly mess around with the test HTML file.
// (I can't find the exact source, but I read about this on a
// StackOverflow post somewhere.)
+ // Currently there are two different legend classes that need
+ // to be tested: BarplotLegend and SampleFeatureColorLegend. They
+ // both inherit the Legend abstract class and thus behave almost
+ // identically. The only difference between the two legends are
+ // the missing non-numeric warning messages. Thus, for the majority
+ // of the tests, we only need to use one of the legend types
+ // (testing both would likely be redundant, at least now). The
+ // only test where we need to explicitly test both legends is the
+ // 'addContinuousKey (with non-numeric warning)' test.
setup: function () {
this.containerEle = document.createElement("div");
this.validateRefSVG = function (obsContainerSVG, expSVG) {
@@ -47,22 +64,21 @@ require([
// addContinuousKey() is called (there isn't really an important
// reason for this, it's just how things work; the caller is
// responsible for making sure Legends aren't created in already-
- // populated containers, at least when initially constructing a
- // Legend)
+ // populated containers, at least on initial construction)
var funkyP = this.containerEle.appendChild(
document.createElement("p")
);
funkyP.innerText = "asdfasdfasdf";
- var legend = new Legend(this.containerEle);
+ var legend = new BarplotLegend(this.containerEle);
equal(this.containerEle.firstChild, funkyP);
});
test('On initialization, legendType is null and title is ""', function () {
- var legend = new Legend(this.containerEle);
+ var legend = new BarplotLegend(this.containerEle);
equal(legend.legendType, null);
equal(legend.title, "");
});
test("addCategoricalKey", function () {
- var legend = new Legend(this.containerEle);
+ var legend = new BarplotLegend(this.containerEle);
var colorInfo = {
"Thing 1": "#ff0000",
"Thing 2": "#00ff00",
@@ -120,7 +136,7 @@ require([
// Now, check that the legend is as expected.
equal(cellsInRow[1].innerText, expectedKey);
});
- // Legend should be visible
+ // BarplotLegend should be visible
notOk(this.containerEle.classList.contains("hidden"));
// Check that _sortedCategories and _category2color are defined
@@ -134,7 +150,7 @@ require([
deepEqual(legend._category2color, colorInfo);
});
test("addCategoricalKey (just 1 color)", function () {
- var legend = new Legend(this.containerEle);
+ var legend = new BarplotLegend(this.containerEle);
var darkBrown = "#52330b";
var colorInfo = { hjkl: darkBrown };
legend.addCategoricalKey("Single-color test", colorInfo);
@@ -156,8 +172,14 @@ require([
deepEqual(legend._sortedCategories, ["hjkl"]);
deepEqual(legend._category2color, colorInfo);
});
+ test("addCategoricalKey (error: no categories)", function () {
+ var legend = new BarplotLegend(this.containerEle);
+ throws(function () {
+ legend.addCategoricalKey("oops", {});
+ }, /Can't create a categorical legend when there are no categories in the info/);
+ });
test("addContinuousKey", function () {
- var legend = new Legend(this.containerEle);
+ var legend = new BarplotLegend(this.containerEle);
var colorer = new Colorer("Viridis", ["0", "4"], true);
var refSVGs = UtilitiesForTesting.getReferenceSVGs();
legend.addContinuousKey(
@@ -184,9 +206,11 @@ require([
// just concatenate these strings together to get the expected SVG
// here)
var cSVG = this.containerEle.children[1];
- this.validateRefSVG(cSVG, refSVGs[0] + refSVGs[1]);
+ // svg container should be inside a div element
+ equal(cSVG.tagName, "DIV");
+ this.validateRefSVG(cSVG.children[0], refSVGs[0] + refSVGs[1]);
- // Legend should be visible
+ // BarplotLegend should be visible
notOk(this.containerEle.classList.contains("hidden"));
// Check SVG exporting attributes are set ok
@@ -198,12 +222,12 @@ require([
notOk(legend._missingNonNumericWarningShown);
});
test("addContinuousKey (with non-numeric warning)", function () {
- var legend = new Legend(this.containerEle);
+ var barplotLegend = new BarplotLegend(this.containerEle);
var colorer = new Colorer("Viridis", ["0", ">:D", "4"], true);
var refSVGs = UtilitiesForTesting.getReferenceSVGs();
- legend.addContinuousKey("howdy", colorer.getGradientInfo());
+ barplotLegend.addContinuousKey("howdy", colorer.getGradientInfo());
- equal(legend.legendType, "continuous");
+ equal(barplotLegend.legendType, "continuous");
// There's a third top-level child element now -- a warning
// message shown to the user.
@@ -211,40 +235,62 @@ require([
// 1. Check title
this.validateTitleEle(this.containerEle.children[0], "howdy");
- equal(legend.title, "howdy");
+ equal(barplotLegend.title, "howdy");
- // 2. Check SVG
+ // 2. A "container SVG" element containing the gradient SVG and
+ // has white-space: normal; set so it
// has line breaks, like normal text
equal($(warning).css("white-space"), "normal");
- // Legend should be visible
+ // BarplotLegend should be visible
notOk(this.containerEle.classList.contains("hidden"));
- // Check that legend._gradientSVG and
- // legend._nonNumericWarningShown are properly set
+ // Check that barplotLegend._gradientSVG and
+ // barplotLegend._nonNumericWarningShown are properly set
// (The gradientSVG check is extremely cursory -- this just
// verifies that it kinda looks like a gradient. The actual
// gradient SVG being correct is tested in test-colorer.js.)
- ok(legend._gradientSVG.includes("