diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000..08e3f018 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,6 @@ +reviews: + auto_review: + base_branches: + - main + - code-review + drafts: true diff --git a/NGCHM/WebContent/css/NGCHM.css b/NGCHM/WebContent/css/NGCHM.css index f29263d1..7a3642c5 100644 --- a/NGCHM/WebContent/css/NGCHM.css +++ b/NGCHM/WebContent/css/NGCHM.css @@ -188,10 +188,6 @@ div.paneHeader { padding: 5px 5px 5px 5px; } -div.paneHeader.activePane { - /* background-color: #ccccff; */ -} - div.paneTitle { margin: 2px; display: inline-block; diff --git a/NGCHM/WebContent/javascript/CovariateCommands.js b/NGCHM/WebContent/javascript/CovariateCommands.js index 9eac9768..6602c938 100644 --- a/NGCHM/WebContent/javascript/CovariateCommands.js +++ b/NGCHM/WebContent/javascript/CovariateCommands.js @@ -33,7 +33,7 @@ output.write(`${UTIL.toTitleCase(axis)} covariates:`); output.write(); output.indent(); - const order = req.heatMap.getAxisCovariateOrder(axis); + const order = req.heatMap.getCovariateOrder(axis); order.forEach((key) => output.write(key)); output.unindent(); output.write(); @@ -205,7 +205,7 @@ EXEC.noMoreParams, function (req, res, next) { // Check value appropriate for covariate data type. - const order = req.heatMap.getAxisCovariateOrder(req.axis); + const order = req.heatMap.getCovariateOrder(req.axis); if (order.includes(req.name)) { throw `${UTIL.toTitleCase(req.axis)} ${req.name} already exists`; } @@ -234,7 +234,7 @@ EXEC.reqCovariate, reqIndex, function (req, res) { - const order = req.heatMap.getAxisCovariateOrder(req.axis); + const order = req.heatMap.getCovariateOrder(req.axis); if (req.index > order.length) { throw `index must be between zero and the number of covariates, inclusive`; } @@ -253,7 +253,7 @@ order.splice(oldIndex, 1); const insertIndex = Math.min(req.index, order.length); order.splice(insertIndex, 0, req.covariateName); - req.heatMap.setAxisCovariateOrder(req.axis, order); + req.heatMap.setCovariateOrder(req.axis, order); res.output.write(`Reordered ${req.axis} covariates`); } ] @@ -275,7 +275,7 @@ EXEC.getHeatMap, EXEC.reqAxis, function (req, res) { - const order = req.heatMap.getAxisCovariateOrder(req.axis); + const order = req.heatMap.getCovariateOrder(req.axis); res.value = order.slice(); } ] diff --git a/NGCHM/WebContent/javascript/DetailHeatMapEvent.js b/NGCHM/WebContent/javascript/DetailHeatMapEvent.js index 339fef87..d8a1e3c6 100644 --- a/NGCHM/WebContent/javascript/DetailHeatMapEvent.js +++ b/NGCHM/WebContent/javascript/DetailHeatMapEvent.js @@ -263,7 +263,7 @@ const rowClassBars = heatMap.getRowClassificationData(); const rowClassConfig = heatMap.getRowClassificationConfig(); pixelInfo.rowCovariates = heatMap - .getRowClassificationOrder() + .getCovariateOrder("row") .filter((key) => rowClassConfig[key].show === "Y") .map((key) => ({ name: key, @@ -272,7 +272,7 @@ const colClassBars = heatMap.getColClassificationData(); const colClassConfig = heatMap.getColClassificationConfig(); pixelInfo.colCovariates = heatMap - .getColClassificationOrder() + .getCovariateOrder("column") .filter((key) => colClassConfig[key].show === "Y") .map((key) => ({ name: key, diff --git a/NGCHM/WebContent/javascript/DetailHeatMapManager.js b/NGCHM/WebContent/javascript/DetailHeatMapManager.js index f33e260a..dba0bb7d 100644 --- a/NGCHM/WebContent/javascript/DetailHeatMapManager.js +++ b/NGCHM/WebContent/javascript/DetailHeatMapManager.js @@ -335,7 +335,7 @@ // Set a detail panel for heatMap as primary, if possible. DMM.setPrimaryForHeatmap = function setPrimaryForHeatmap (heatMap) { - const maps = DVW.detailMaps.filter(mapItem => mapItem.heatMap == heatMap); + const maps = DVW.detailMaps.filter(mapItem => mapItem.heatMap === heatMap); DMM.switchToPrimary(maps.length > 0 ? maps[0] : null); }; @@ -622,6 +622,7 @@ document.getElementById("primary_btn" + mapNumber).dataset.version = isPrimary ? "P" : "S"; PANE.setPaneTitle(loc, "Heat Map Detail"); + PANE.setPaneDecor(loc, mapItem.heatMap.decor); PANE.registerPaneEventHandler(loc.pane, "empty", emptyDetailPane); PANE.registerPaneEventHandler(loc.pane, "resize", resizeDetailPane); DET.setDrawDetailTimeout(mapItem, 0, false); diff --git a/NGCHM/WebContent/javascript/DetailHeatMapViews.js b/NGCHM/WebContent/javascript/DetailHeatMapViews.js index ffdfa0af..fad3da8b 100644 --- a/NGCHM/WebContent/javascript/DetailHeatMapViews.js +++ b/NGCHM/WebContent/javascript/DetailHeatMapViews.js @@ -86,11 +86,11 @@ }; /********************************************************************************************* - * FUNCTION: anyVisible - Return true if any Detail View is visible. + * FUNCTION: anyVisible - Return true if any Detail View is visible for the specified heatMap. *********************************************************************************************/ - DVW.anyVisible = function anyVisible() { - for (let i = 0; i < DVW.detailMaps.length; i++) { - if (DVW.detailMaps[i].isVisible()) { + DVW.anyVisible = function anyVisible(heatMap) { + for (const mapItem of DVW.detailMaps) { + if (mapItem.heatMap === heatMap && mapItem.isVisible()) { return true; } } diff --git a/NGCHM/WebContent/javascript/HeatMapClass.js b/NGCHM/WebContent/javascript/HeatMapClass.js index 1e13570e..468fcd0b 100644 --- a/NGCHM/WebContent/javascript/HeatMapClass.js +++ b/NGCHM/WebContent/javascript/HeatMapClass.js @@ -72,7 +72,7 @@ this.endColTile = endColTile; this.numColumnTiles = endColTile - startColTile + 1; this.totalTiles = (endRowTile - startRowTile + 1) * this.numColumnTiles; - this.tiles = new Array({ length: this.totalTiles }); + this.tiles = new Array(this.totalTiles); // Get hard references to tile data if it's already in the TileCache. // Set _allTilesAvailable to false if any tile's data is not available. @@ -558,8 +558,9 @@ this.currentTileRequests = []; // Tiles we are currently reading this.pendingTileRequests = []; // Tiles we want to read this.searchOptions = []; // Search options specific to this heatMap. + this.decor = { hue: null, avatar: null }; // UI decorations for distinguishing one heatMap from another. - this.updatedOnLoad = false; + this.mapUpdatedOnLoad = false; this.onready = onready; this.initEventListeners(updateCallbacks); // Initialize this.eventListeners this.initTileWindows(); // Initialize this.tileWindowListeners and this.tileWindowRefs @@ -753,12 +754,6 @@ } }; - HeatMap.prototype.getAxisCovariateOrder = function (axis) { - return MAPREP.isRow(axis) - ? this.getRowClassificationOrder() - : this.getColClassificationOrder(); - }; - HeatMap.prototype.getRowClassificationConfig = function () { return this.mapConfig.row_configuration.classifications; }; @@ -803,7 +798,7 @@ // HeatMap.prototype.genAllCovars = function* genAllCovars() { for (const axis of ["row", "col"]) { - const covariates = this.getAxisCovariateOrder(axis); + const covariates = this.getCovariateOrder(axis); for (const key of covariates) { yield { axis, key }; } @@ -832,59 +827,40 @@ } } - HeatMap.prototype.getRowClassificationOrder = function (showOnly) { - var rowClassBarsOrder = this.mapConfig.row_configuration.classifications_order; - // If configuration not found, create order from classifications config - if (typeof rowClassBarsOrder === "undefined") { - rowClassBarsOrder = []; - for (key in this.mapConfig.row_configuration.classifications) { - rowClassBarsOrder.push(key); - } - } - // Filter order for ONLY shown bars (if requested) - if (typeof showOnly === "undefined") { - return rowClassBarsOrder; - } else { - const filterRowClassBarsOrder = []; - for (var i = 0; i < rowClassBarsOrder.length; i++) { - var newKey = rowClassBarsOrder[i]; - var currConfig = this.mapConfig.row_configuration.classifications[newKey]; - if (currConfig.show == "Y") { - filterRowClassBarsOrder.push(newKey); - } - } - return filterRowClassBarsOrder; - } + // Return the order in which the covariates on the specified axis should be + // displayed. + // If showOnly is specified, return only the visible covariates. + HeatMap.prototype.getCovariateOrder = function (axis, showOnly) { + const axisConfig = this.getAxisConfig(axis); + // If barOrder not defined, create order from classifications config. + const barOrder = Array.isArray(axisConfig.classifications_order) + ? axisConfig.classifications_order.slice() + : Object.keys(axisConfig.classifications); + // Filter order for only shown bars (if requested) + return showOnly === true || showOnly === "show" + ? barOrder.filter((key) => axisConfig.classifications[key].show == "Y") + : barOrder; }; - HeatMap.prototype.setRowClassificationOrder = function () { + // Set the order in which the covariates on the specified axis should be + // displayed. + // If order is not specified and there is no existing order, set a + // default order. + HeatMap.prototype.setCovariateOrder = function (axis, order) { if (this.mapConfig !== null) { - this.mapConfig.row_configuration.classifications_order = this.getRowClassificationOrder(); - } - }; - - HeatMap.prototype.getColClassificationOrder = function (showOnly) { - var colClassBarsOrder = this.mapConfig.col_configuration.classifications_order; - // If configuration not found, create order from classifications config - if (typeof colClassBarsOrder === "undefined") { - colClassBarsOrder = []; - for (key in this.mapConfig.col_configuration.classifications) { - colClassBarsOrder.push(key); - } - } - // Filter order for ONLY shown bars (if requested) - if (typeof showOnly === "undefined") { - return colClassBarsOrder; - } else { - const filterColClassBarsOrder = []; - for (var i = 0; i < colClassBarsOrder.length; i++) { - var newKey = colClassBarsOrder[i]; - var currConfig = this.mapConfig.col_configuration.classifications[newKey]; - if (currConfig.show == "Y") { - filterColClassBarsOrder.push(newKey); + const axisConfig = this.getAxisConfig(axis); + if (order) { + if (!Array.isArray(order)) { + console.error("setCovariateOrder: if specified, order must be an array", { axis, order }); + return; } + axisConfig.classifications_order = order.slice(); + this.setUnAppliedChanges(true); + return; + } + if (typeof axisConfig.classifications_order === "undefined") { + axisConfig.classifications_order = Object.keys(axisConfig.classifications); } - return filterColClassBarsOrder; } }; @@ -965,22 +941,6 @@ ); }; - HeatMap.prototype.setColClassificationOrder = function () { - if (this.mapConfig !== null) { - this.mapConfig.col_configuration.classifications_order = this.getColClassificationOrder(); - } - }; - - // Persist the axis covariate order and mark the heatmap as changed. - HeatMap.prototype.setAxisCovariateOrder = function (axis, order) { - if (!Array.isArray(order)) { - console.error("setAxisCovariateOrder requires an order array", { axis, order }); - return; - } - this.getAxisConfig(axis).classifications_order = order.slice(); - this.setUnAppliedChanges(true); - }; - HeatMap.prototype.getMapInformation = function () { return this.mapConfig.data_configuration.map_information; }; @@ -1340,66 +1300,62 @@ /*********** Methods for accessing datalevels ****************/ - //Return the total number of detail rows - HeatMap.prototype.getTotalRows = function () { - return this.datalevels[MAPREP.DETAIL_LEVEL].totalRows; - }; - - //Return the summary row ratio + // Return the summary row ratio. HeatMap.prototype.getSummaryRowRatio = function () { - if (this.datalevels[MAPREP.SUMMARY_LEVEL] !== null) { + if (this.datalevels[MAPREP.SUMMARY_LEVEL]) { return this.datalevels[MAPREP.SUMMARY_LEVEL].rowSummaryRatio; } else { return this.datalevels[MAPREP.THUMBNAIL_LEVEL].rowSummaryRatio; } }; - //Return the summary row ratio + // Return the summary column ratio. HeatMap.prototype.getSummaryColRatio = function () { - if (this.datalevels[MAPREP.SUMMARY_LEVEL] !== null) { + if (this.datalevels[MAPREP.SUMMARY_LEVEL]) { return this.datalevels[MAPREP.SUMMARY_LEVEL].colSummaryRatio; } else { - return this.datalevels[MAPREP.THUMBNAIL_LEVEL].col_summaryRatio; + return this.datalevels[MAPREP.THUMBNAIL_LEVEL].colSummaryRatio; } }; - //Return the total number of detail rows + + // Return the total number of detail rows. HeatMap.prototype.getTotalRows = function () { return this.datalevels[MAPREP.DETAIL_LEVEL].totalRows; }; - //Return the total number of detail rows + // Return the total number of detail columns. HeatMap.prototype.getTotalCols = function () { return this.datalevels[MAPREP.DETAIL_LEVEL].totalColumns; }; - //Return the total number of rows/columns on the specified axis. + // Return the total number of rows/columns on the specified axis. HeatMap.prototype.getTotalElementsForAxis = function (axis) { const level = this.datalevels[MAPREP.DETAIL_LEVEL]; return MAPREP.isRow(axis) ? level.totalRows : level.totalColumns; }; - //Return the number of rows or columns for the given level + // Return the number of rows or columns for the given level HeatMap.prototype.getNumAxisElements = function (axis, level) { const l = this.datalevels[level]; return MAPREP.isRow(axis) ? l.totalRows : l.totalColumns; }; - //Return the number of rows for a given level + // Return the number of rows for a given level. HeatMap.prototype.getNumRows = function (level) { return this.datalevels[level].totalRows; }; - //Return the number of columns for a given level + // Return the number of columns for a given level. HeatMap.prototype.getNumColumns = function (level) { return this.datalevels[level].totalColumns; }; - //Return the row summary ratio for a given level + // Return the row summary ratio for a given level. HeatMap.prototype.getRowSummaryRatio = function (level) { return this.datalevels[level].rowSummaryRatio; }; - //Return the column summary ratio for a given level + // Return the column summary ratio for a given level. HeatMap.prototype.getColSummaryRatio = function (level) { return this.datalevels[level].colSummaryRatio; }; diff --git a/NGCHM/WebContent/javascript/HeatMapCommands.js b/NGCHM/WebContent/javascript/HeatMapCommands.js index a6e4477a..78f153cc 100644 --- a/NGCHM/WebContent/javascript/HeatMapCommands.js +++ b/NGCHM/WebContent/javascript/HeatMapCommands.js @@ -108,7 +108,7 @@ output.write(); for (const heatMap of MMGR.getAllHeatMaps()) { const info = heatMap.getMapInformation(); - const flag = heatMap == currentHeatmap ? "(*) " : ""; + const flag = heatMap === currentHeatmap ? "(*) " : ""; output.write (`${flag}${info.name}`); } output.unindent(); diff --git a/NGCHM/WebContent/javascript/Linkout.js b/NGCHM/WebContent/javascript/Linkout.js index 35d47b73..9535a6d2 100644 --- a/NGCHM/WebContent/javascript/Linkout.js +++ b/NGCHM/WebContent/javascript/Linkout.js @@ -408,9 +408,9 @@ var linkoutsVersion = "undefined"; } }; - // Returns TRUE iff there is a matrix linkout with the specified name. - LNK.isMatrixLinkout = function isMatrixLinkout (name) { - return matrixLinkouts.map(linkout => linkout.name).includes(name); + // Returns TRUE iff there is a matrix linkout with the specified title. + LNK.isMatrixLinkout = function isMatrixLinkout (title) { + return matrixLinkouts.some(linkout => linkout.title === title); }; // Return the labels from heatMap required by the specified linkout (and axis if applicable). @@ -446,7 +446,7 @@ var linkoutsVersion = "undefined"; } else { const srchResults = SRCHSTATE.getAxisSearchResults(axis); if (axis.includes("Covar")) { - const labels = heatMap.getAxisCovariateOrder(axis.replace("Covar","")); + const labels = heatMap.getCovariateOrder(axis.replace("Covar","")); return srchResults.map(idx => generateLinkoutLabel(labels[idx], formatIndex)); } else { const labels = heatMap.getAxisLabels(axis).labels; @@ -1575,6 +1575,7 @@ var linkoutsVersion = "undefined"; if (restoreInfo) { pluginRestoreInfo[loc.pane.id] = restoreInfo; } + PANE.setPaneDecor(loc, null); switchToPlugin(loc, plugin.name); MMGR.getHeatMap().setUnAppliedChanges(true); const params = plugin.params; @@ -2520,7 +2521,7 @@ var linkoutsVersion = "undefined"; thisAxis = axis; if (debug) console.log({ m: "setAxis", axis, params }); axis1Config = heatMap.getAxisCovariateConfig(axis); - const axis1cvOrder = heatMap.getAxisCovariateOrder(axis); + const axis1cvOrder = heatMap.getCovariateOrder(axis); otherAxis = MAPREP.isRow(axis) ? "Column" : "Row"; if (plugin.hasOwnProperty("specialCoordinates") && plugin.specialCoordinates.hasOwnProperty("name")){ defaultCoord = plugin.specialCoordinates.name + ".coordinate."; @@ -4317,9 +4318,15 @@ var linkoutsVersion = "undefined"; .map((x) => x.replace(/\.coordinate\.\d+$/, "")) /* remove the ".coordinate." suffix */ let uniqueRowCoords = [...new Set(specialRowCoords)] /* remove duplicates */ .map((x) => ({name: x, rowOrColumn: "row"})); /* create object with name and rowOrColumn properties */ - let specialCoords = uniqueColumnCoords.concat(uniqueRowCoords); - specialCoords = [...new Set(specialCoords)]; /* remove duplicates */ - return specialCoords; + + // Remove any duplicates. + const seen = new Set(); + const unique = []; + for (const sc of uniqueColumnCoords.concat(uniqueRowCoords)) { + const key = sc.name + "|" + sc.rowOrColumn; + if (!seen.has(key)) { seen.add(key); unique.push(sc); } + } + return unique; } CUST.waitForPlugins(() => { diff --git a/NGCHM/WebContent/javascript/MatrixManager.js b/NGCHM/WebContent/javascript/MatrixManager.js index 4201362e..5e2cea3a 100644 --- a/NGCHM/WebContent/javascript/MatrixManager.js +++ b/NGCHM/WebContent/javascript/MatrixManager.js @@ -541,7 +541,7 @@ // Add the modified config data. promise = addTextContents( entry.filename, - JSON.stringify(heatMap == map ? (mapConf || heatMap.mapConfig) : map.mapConfig), + JSON.stringify(heatMap === map ? (mapConf || heatMap.mapConfig) : map.mapConfig), ); } else if (keyVal.indexOf("/mapData.json") >= 0) { // Add the potentially modified data. @@ -906,6 +906,15 @@ function saveMaps (callback) { return function onMapsLoaded (maps) { allHeatMaps = maps; + if (maps.length > 0) { + // Define map-specific decorations to help distinguish them in the UI. + for (let ii = 0; ii < maps.length; ii++) { + const map = maps[ii]; + // Spread out hues evenly as much as possible. + map.decor.hue = 360 * ii / maps.length; + map.decor.avatar = UTIL.genAvatar (map.decor.hue); + } + } MMGR.setHeatMap(allHeatMaps[0]); callback(); }; diff --git a/NGCHM/WebContent/javascript/NGCHM_Util.js b/NGCHM/WebContent/javascript/NGCHM_Util.js index 6cc645f7..74048605 100644 --- a/NGCHM/WebContent/javascript/NGCHM_Util.js +++ b/NGCHM/WebContent/javascript/NGCHM_Util.js @@ -162,6 +162,64 @@ return decorateElement(button, names, classes, attrs, [], fn); } + // Create an "avatar" of the specified hue. + // + UTIL.genAvatar = genAvatar; + function genAvatar (hue) { + const alines = [ "00100", "01010", "01110" ]; + const blines = [ "10001", "10101", "11011" ]; + const guid = (window.crypto && typeof crypto.randomUUID === "function") + ? crypto.randomUUID() + : UTIL.getNewNonce(); + const nums = guid.split("").filter(x=>x!='-').map(x => parseInt(x,16)); + let d = ""; + const brow = nums[0] % 5; + for (let ii = 0; ii < 5; ii++) { + const chars = pickline(ii).split(""); + for (let jj = 0; jj < 5; jj++) { + if (chars[jj] == "1") { + d += B(jj,ii); + } + } + } + const sat = 100; + const val = 50 + Math.floor(Math.random() * 30); + const col = UTIL.hsvToRgb(hue,sat,val); + return ` + + `; + // Helper function. + function pickline (idx) { + let lines = blines.slice(); + if (idx != brow) { + lines = lines.concat(alines); + } + return lines[nums[idx+1] % lines.length]; + } + // Helper function. + // Return an SVG path element for a box at coordinates x, y. + function B(x,y) { + const scale = 3; + x *= scale; + y *= scale; + return `M${x} ${y} L${x+scale} ${y} L${x+scale} ${y+scale} L${x} ${y+scale}z`; + } + } + + // Create a button element containing the specified avatar. + // + UTIL.newAvatarButton = newAvatarButton; + function newAvatarButton(spec, avatar, attrs, fn) { + const classes = spec.split("."); + const names = classes.shift().split("#"); + attrs = Object.assign({ type: "button", "aria-label": "Heat map avatar" }, attrs); + const button = UTIL.newElement("BUTTON"); + button.innerHTML = avatar; + return decorateElement(button, names, classes, attrs, [], fn); + } + + // Create a button element containing the specified SVG icon(s). + // UTIL.newSvgMenuItem = newSvgMenuItem; function newSvgMenuItem(iconIds, attrs, fn) { const classes = iconIds.split("."); diff --git a/NGCHM/WebContent/javascript/PaneControl.js b/NGCHM/WebContent/javascript/PaneControl.js index 29f58c41..9297e8b6 100644 --- a/NGCHM/WebContent/javascript/PaneControl.js +++ b/NGCHM/WebContent/javascript/PaneControl.js @@ -622,13 +622,16 @@ const pane = UTIL.newElement("DIV.pane", { style, id: paneid }); pane.addEventListener("paneresize", resizeHandler); if (title) { - const h = UTIL.newElement("DIV.paneHeader.activePane"); + const h = UTIL.newElement("DIV.paneHeader"); if (!PANE.showPaneHeader) h.classList.add("hide"); pane.appendChild(h); const sc = UTIL.newElement("DIV.paneScreenMode"); h.appendChild(sc); + const av = UTIL.newElement("DIV.paneAvatar"); + h.appendChild(av); + const t = UTIL.newElement("DIV.paneTitle"); t.innerText = title; h.appendChild(t); @@ -659,6 +662,67 @@ return pane; } + // Exported function. + // Set the heatMap-specific decorative UI elements. + PANE.setPaneDecor = setPaneDecor; + function setPaneDecor (loc, decor) { + // Remove previous custom decor. + loc.paneHeader.style.backgroundColor = ""; + const av = loc.paneHeader.getElementsByClassName("paneAvatar")[0]; + while (av.firstChild) av.firstChild.remove(); + if (!decor) { + // Quit if no custom decor. + return; + } + if (decor.hasOwnProperty('hue')) { + const bgColor = UTIL.hsvToRgb (decor.hue, 10, 90); + loc.paneHeader.style.backgroundColor = bgColor; + } + if (decor.avatar) { + const avatar = + UTIL.newAvatarButton( + ".ngchm-avatar", + decor.avatar, + { + dataset: { + tooltip: + "Avatar button", + title: "avatar-button", + intro: + "Long avatar button text", + }, + }, + (el) => { + el.onmousedown = (ev) => { + let button = ev.target; + while (button && button.tagName.toLowerCase() != "button") { + button = button.parentElement; + } + if (button) { + button.dataset.mouseDownTime = "" + performance.now(); + } + console.log ("Avatar mousedown", { button }); + }; + el.onclick = (ev) => { + ev.stopPropagation(); + let button = ev.target; + while (button && button.tagName.toLowerCase() != "button") { + button = button.parentElement; + } + if (button) { + const paneLoc = PANE.findPaneLocation(ev.target); + console.log ("Avatar button click", { button, paneLoc }); + } else { + console.log ("Avatar click"); + } + }; + return el; + }, + ); + av.appendChild(avatar); + } + } + // Exported function. // Add a group of icons to the pane header. // Spec is an object with the following entries: @@ -761,7 +825,6 @@ if (debug) console.log({ m: "splitPane", vertical, loc }); if (!loc.pane || !loc.container) return; const verticalContainer = loc.container.classList.contains("vertical"); - if (loc.paneHeader) loc.paneHeader.classList.remove("activePane"); const style = {}; style[vertical ? "height" : "width"] = "calc(50% - 5px)"; const divider = UTIL.newElement("DIV.resizerHelper"); @@ -1476,6 +1539,8 @@ setPaneTitle(loc, "empty"); removePanelMenuGroupIcons(loc); PANE.setPaneClientIcons(loc, {}); + // Clear any panel decoration. + PANE.setPaneDecor(loc, null); MMGR.getHeatMap().removePaneInfoFromMapConfig(loc.pane.id); // Return remaining client elements to caller. return clientElements; diff --git a/NGCHM/WebContent/javascript/PdfGenerator.js b/NGCHM/WebContent/javascript/PdfGenerator.js index 32b36c03..88bd21f0 100644 --- a/NGCHM/WebContent/javascript/PdfGenerator.js +++ b/NGCHM/WebContent/javascript/PdfGenerator.js @@ -48,7 +48,7 @@ * button on the menu bar. The PDF preferences panel is then launched **********************************************************************************/ PDF.canGeneratePdf = function () { - return SUM.isVisible() || DVW.anyVisible(); + return SUM.isVisible() || DVW.anyVisible(MMGR.getHeatMap()); }; PDF.pdfDialogClosed = function () { @@ -64,7 +64,7 @@ let whyDisabled = "Cannot open the PDF dialog since it's already open"; if (prefspanel.classList.contains("hide")) { whyDisabled = - "Cannot generate a PDF when the Summary and all Detail heat map panels are closed."; + "Cannot generate a PDF when the Summary and all Detail heat map panels for the current heat map are closed."; } UHM.systemMessage("NG-CHM PDF Generator", whyDisabled); return; @@ -74,21 +74,15 @@ const sumButton = document.getElementById("pdfInputSummaryMap"); const detButton = document.getElementById("pdfInputDetailMap"); const bothButton = document.getElementById("pdfInputBothMaps"); - if (SUM.isVisible() && !DVW.anyVisible()) { + sumButton.disabled = !SUM.isVisible(); + detButton.disabled = !DVW.anyVisible(MMGR.getHeatMap()); + bothButton.disabled = sumButton.disabled || detButton.disabled; + if (!bothButton.disabled) { + bothButton.checked = true; + } else if (!sumButton.disabled) { sumButton.checked = true; - sumButton.disabled = false; - detButton.disabled = true; - bothButton.disabled = true; - } else if (DVW.anyVisible() && !SUM.isVisible()) { + } else if (!detButton.disabled) { detButton.checked = true; - sumButton.disabled = true; - detButton.disabled = false; - bothButton.disabled = true; - } else if (SUM.isVisible() && DVW.anyVisible()) { - bothButton.checked = true; - sumButton.disabled = false; - detButton.disabled = false; - bothButton.disabled = false; } else { // Should not happen. UHM.systemMessage( @@ -265,7 +259,7 @@ } if (includeDetailMaps) { DVW.detailMaps - .filter((mapItem) => mapItem.isVisible()) + .filter((mapItem) => mapItem.heatMap === heatMap && mapItem.isVisible()) .forEach((mapItem) => { drawJobs.push({ job: "detail", mapItem }); }); @@ -752,10 +746,10 @@ barsInfo.rowBarsToDraw = []; barsInfo.colBarsToDraw = []; if (isChecked("pdfInputColumn")) { - barsInfo.colBarsToDraw = heatMap.getColClassificationOrder("show"); + barsInfo.colBarsToDraw = heatMap.getCovariateOrder("column", "show"); } if (isChecked("pdfInputRow")) { - barsInfo.rowBarsToDraw = heatMap.getRowClassificationOrder("show"); + barsInfo.rowBarsToDraw = heatMap.getCovariateOrder("row", "show"); } barsInfo.topOff = pdfDoc.paddingTop + barsInfo.classBarTitleSize + 5; barsInfo.leftOff = 20; @@ -1824,13 +1818,13 @@ function isLastClassBarToBeDrawn(heatMap, classBar, type) { var isItLast = false; if (isChecked("pdfInputColumn")) { - var colBars = heatMap.getColClassificationOrder("show"); + var colBars = heatMap.getCovariateOrder("column", "show"); if (type === "col" && classBar === colBars[colBars.length - 1]) { isItLast = true; } } if (isChecked("pdfInputRow")) { - var rowBars = heatMap.getRowClassificationOrder("show"); + var rowBars = heatMap.getCovariateOrder("row", "show"); if (type === "row" && classBar === rowBars[rowBars.length - 1]) { isItLast = true; } @@ -2120,12 +2114,12 @@ // Draw the 'green' boundary rectangles that outline the detail map views. if (showDetailViewBounds) { - const color = heatMap.getCurrentDataLayer().selection_color; - pdfDoc.doc.setDrawColor(color); + const sel = CMM.hexToRgba(heatMap.getCurrentDataLayer().selection_color); + pdfDoc.doc.setDrawColor(sel.r, sel.g, sel.b); const yScale = sumMapH / heatMap.getNumRows(MAPREP.DETAIL_LEVEL); const xScale = sumMapW / heatMap.getNumColumns(MAPREP.DETAIL_LEVEL); DVW.detailMaps.forEach((mapItem) => { - if (mapItem.isVisible()) { + if (mapItem.heatMap === heatMap && mapItem.isVisible()) { const left = (mapItem.currentCol - 1) * xScale; const top = (mapItem.currentRow - 1) * yScale; const width = mapItem.dataPerRow * xScale; diff --git a/NGCHM/WebContent/javascript/SearchManager.js b/NGCHM/WebContent/javascript/SearchManager.js index d2fb6ff3..795a00bc 100644 --- a/NGCHM/WebContent/javascript/SearchManager.js +++ b/NGCHM/WebContent/javascript/SearchManager.js @@ -536,7 +536,7 @@ const searchInterface = new SearchInterface(); SRCH.heatMapListener = function heatMapListener (heatMap, event) { - if (searchInterface.heatMap == heatMap && event == HEAT.Event_PLUGINS) { + if (searchInterface.heatMap === heatMap && event == HEAT.Event_PLUGINS) { SRCH.configSearchInterface (heatMap); } }; @@ -562,7 +562,7 @@ // Helper function. // Add a search option for every covariate on the specified axis. function addCovariateOptions(axis) { - for (const key of heatMap.getAxisCovariateOrder(axis)) { + for (const key of heatMap.getCovariateOrder(axis)) { searchInterface.addSearchOption({ type: heatMap.getCovariateType(axis, key), axis, diff --git a/NGCHM/WebContent/javascript/SummaryHeatMapDisplay.js b/NGCHM/WebContent/javascript/SummaryHeatMapDisplay.js index 59d6a800..b8994615 100644 --- a/NGCHM/WebContent/javascript/SummaryHeatMapDisplay.js +++ b/NGCHM/WebContent/javascript/SummaryHeatMapDisplay.js @@ -92,6 +92,10 @@ SUM.colTopItemsWidth = 0; SUM.rowTopItemsHeight = 0; SUM.initSummaryData(); + if (SUM.chmElement) { + const loc = PANE.findPaneLocation(SUM.chmElement); + PANE.setPaneDecor (loc, SUM.heatMap.decor); + } }; // Callback that is notified every time there is an update to the heat map @@ -1163,7 +1167,7 @@ var rowCanvas = document.getElementById("summary_row_top_items_canvas"); var sumCanvas = document.getElementById("summary_canvas"); var classBarsConfig = heatMap.getRowClassificationConfig(); - var classBarConfigOrder = heatMap.getRowClassificationOrder(); + var classBarConfigOrder = heatMap.getCovariateOrder("row"); var totalHeight = 0; var matrixWidth = colCanvas.width; //Calc total width of all covariate bars @@ -1214,7 +1218,7 @@ const heatMap = SUM.heatMap; SUM.removeColClassBarLabels(); var classBarsConfig = heatMap.getColClassificationConfig(); - var classBarConfigOrder = heatMap.getColClassificationOrder(); + var classBarConfigOrder = heatMap.getCovariateOrder("column"); var classBarsData = heatMap.getColClassificationData(); var prevHeight = 0; for (var i = 0; i < classBarConfigOrder.length; i++) { @@ -1274,7 +1278,7 @@ SUM.drawColClassBarLegends = function () { const heatMap = SUM.heatMap; var classBarsConfig = heatMap.getColClassificationConfig(); - var classBarConfigOrder = heatMap.getColClassificationOrder(); + var classBarConfigOrder = heatMap.getCovariateOrder("column"); var classBarsData = heatMap.getColClassificationData(); var totalHeight = 0; for (var i = 0; i < classBarConfigOrder.length; i++) { @@ -1469,7 +1473,7 @@ SUM.drawRowClassBarLegends = function () { const heatMap = SUM.heatMap; var classBarsConfig = heatMap.getRowClassificationConfig(); - var classBarConfigOrder = heatMap.getRowClassificationOrder(); + var classBarConfigOrder = heatMap.getCovariateOrder("row"); var classBarsData = heatMap.getRowClassificationData(); var totalHeight = 0; for (var i = 0; i < classBarConfigOrder.length; i++) { diff --git a/NGCHM/WebContent/javascript/SummaryHeatMapManager.js b/NGCHM/WebContent/javascript/SummaryHeatMapManager.js index ba89fae6..1b76df68 100644 --- a/NGCHM/WebContent/javascript/SummaryHeatMapManager.js +++ b/NGCHM/WebContent/javascript/SummaryHeatMapManager.js @@ -352,6 +352,7 @@ PANE.emptyPaneLocation(loc); initializeSummaryPanel(loc.pane); PANE.setPaneTitle(loc, "Heat Map Summary"); + PANE.setPaneDecor (loc, SUM.heatMap.decor); PANE.registerPaneEventHandler(loc.pane, "empty", emptySummaryPane); PANE.registerPaneEventHandler(loc.pane, "resize", resizeSummaryPane); loc.pane.dataset.title = "Summary Heat Map"; diff --git a/NGCHM/WebContent/javascript/UI-Manager.js b/NGCHM/WebContent/javascript/UI-Manager.js index 86f2c987..42fb37ea 100644 --- a/NGCHM/WebContent/javascript/UI-Manager.js +++ b/NGCHM/WebContent/javascript/UI-Manager.js @@ -84,8 +84,8 @@ function autoSaveHeatMap(heatMap) { let success = true; if (!srcInfo.embedded) { - heatMap.setRowClassificationOrder(); - heatMap.setColClassificationOrder(); + heatMap.setCovariateOrder("row"); + heatMap.setCovariateOrder("column"); switch (MMGR.getSource()) { case MMGR.API_SOURCE: // FIXME: BMB. Verify this does what it's required to do. diff --git a/NGCHM/WebContent/javascript/UserPreferenceManager.js b/NGCHM/WebContent/javascript/UserPreferenceManager.js index ab1e031a..d265316d 100644 --- a/NGCHM/WebContent/javascript/UserPreferenceManager.js +++ b/NGCHM/WebContent/javascript/UserPreferenceManager.js @@ -270,10 +270,19 @@ if (prefspanel.style.display !== "none") { // The user clicked to open the preferences panel, but we believe it is already open. - // Nothing to do, but we reshow the preferences panel just in case it's not visible. - prefspanel.style.display = ""; - locatePrefsPanel(); - return; + if (UPM.heatMap === heatMap) { + // Nothing to do, but we reshow the preferences panel just in case it's not visible. + prefspanel.style.display = ""; + locatePrefsPanel(); + return; + } else { + // Close preferences manager for old heatMap to allow for new one. + closePreferencesManager(); + // Let the display update before opening the new manager, so the + // user sees the preferences manager flash. + setTimeout (() => UPM.openPreferencesManager(heatMap), 100); + return; + } } // Set the heatMap we are editing.