7df99a27a400ec5627663c3a0692e9ab7d04796c max Fri May 9 09:25:54 2025 -0700 fix: null on recent genes, split screen titles, scroll bar coming up without reason, zooming on split screen, checkbox on both sides updating on change event, hide checkbox when not in split screen mode diff --git src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js index 6276394..c543433 100644 --- src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js +++ src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js @@ -3419,82 +3419,87 @@ function colorByNothing() { /* color by nothing, rarely needed */ renderer.setColors([cNullColor]); var cellCount = db.conf.sampleCount; renderer.setColorArr(new Uint8Array(cellCount)); gLegend.rows = []; gLegend.title = "Nothing selected"; gLegend.subTitle = ""; gLegend.rows.push( { color:cNullColor, defColor:null, label:"No Value", count:cellCount, intKey:0, strKey:null } ); buildLegendBar(); } - function buildWatermark(myRend) { + function buildWatermark(myRend, showWatermark) { /* update the watermark behind the image */ if (myRend===undefined) myRend = renderer; - if (!myRend.isSplit()) { + if (!myRend.isSplit() && !showWatermark) { myRend.setWatermark(""); return; } let prefix = ""; if (db.conf.coords.length!==1) - prefix = renderer.coords.coordInfo.shortLabel+": "; + prefix = myRend.coords.coordInfo.shortLabel+": "; let labelStr; if (gLegend.type==="expr") labelStr = prefix+gLegend.geneSym; else labelStr = prefix+gLegend.metaInfo.label; let waterLabel; if (db.isAtacMode()) waterLabel= labelStr.split("|").length + " peak(s)"; else waterLabel = labelStr; myRend.setWatermark(waterLabel); } function colorByLocus(locusStr, onDone, locusLabel) { - /* color by a gene or peak, load the array into the renderer and call onDone or just redraw + /* colorByGene: color by a gene or peak, load the array into the renderer and call onDone or just redraw * peak can be in format: +chr1:1-1000 * gene can be in format: geneSym or geneSym=geneId * */ if (onDone===undefined || onDone===null) onDone = function() { renderer.drawDots(); }; function gotGeneVec(exprArr, decArr, locusStr, geneDesc, binInfo) { /* called when the expression vector has been loaded and binning is done */ if (decArr===null) return; console.log("Received expression vector, for "+locusStr+", desc: "+geneDesc); // update the URL and possibly the gene combo box if (locusStr.indexOf("|") > -1) { if (locusStr.length < 600) // this is rare, so just completely skip this URL change now changeUrl({"locus":locusStr, "meta":null}); } else changeUrl({"gene":locusStr, "meta":null}); makeLegendExpr(locusStr, geneDesc, binInfo, exprArr, decArr); renderer.setColors(legendGetColors(gLegend.rows)); renderer.setColorArr(decArr); + if (renderer.childPlot && document.getElementById("splitJoinBox").checked) { + renderer.childPlot.setColors(legendGetColors(gLegend.rows)); + renderer.childPlot.setColorArr(decArr); + buildWatermark(renderer.childPlot); + } buildWatermark(renderer); buildLegendBar(); onDone(); // update the "recent genes" div for (var i = 0; i < gRecentGenes.length; i++) { // remove previous gene entry with the same symbol if (gRecentGenes[i][0]===locusStr || gRecentGenes[i][1]===locusStr) { // match symbol or ID gRecentGenes.splice(i, 1); break; } } // make sure that recent genes table has symbol and Id @@ -3555,30 +3560,31 @@ for (var i = 0; i < clusterMids.length; i++) { origLabels.push(clusterMids[i][2]); } renderer.origLabels = origLabels; } if (clusterInfo && clusterInfo.lines) { opts["lines"] = clusterInfo.lines; opts["lineWidth"] = db.conf.lineWidth; opts["lineColor"] = db.conf.lineColor; opts["lineAlpha"] = db.conf.lineAlpha; } renderer.setCoords(coords, clusterMids, info, opts); + buildWatermark(renderer); } function computeAndSetLabels(values, metaInfo) { /* recompute the label positions and redraw everything. Updates the dropdown. */ var labelCoords; var coords = renderer.coords.orig; var names = null; if (metaInfo.type !== "float" && metaInfo.type !== "int") { var names = metaInfo.ui.shortLabels; } console.time("cluster centers"); var calc = renderer.calcMedian(coords, values, names, metaInfo.origVals); @@ -3843,36 +3849,41 @@ else renderer.drawDots(); // this requires coordinates to be loaded if (getVar("cell")!==undefined) { selectCellsById([getVar("cell")], false, null) } //if (db.conf.multiModal && db.conf.multiModal.splitPrefix) //renderer.split(); if (db.conf.split) { let splitOpts = db.conf.split; //configureRenderer(splitOpts[0]); //renderer.drawDots(); //buildWatermark(); + //buildWatermark(); activateSplit(); configureRenderer(splitOpts); + $("#splitJoinDiv").show(); + $("#splitJoinBox").prop("checked", true); //buildWatermark(); //renderer.drawDots(); changeUrl({"layout":null, "meta":null, "gene":null}); renderer.drawDots(); + } else { + $("#splitJoinDiv").hide(); } } } function guessRadiusAlpha(dotCount) { /* return reasonable radius and alpha values for a number of dots */ if (dotCount<3000) return [4, 0.7]; if (dotCount<6000) return [4, 0.6]; if (dotCount<10000) return [3, 0.5]; if (dotCount<35000) return [2, 0.3]; if (dotCount<80000) @@ -4722,31 +4733,31 @@ htmls.push('<div style="margin-top:6px" class="tpHint">'); htmls.push(subtitle); htmls.push('</div>'); } htmls.push("</div>"); // divId_title } if (doUpdate) { $('#'+divId).empty(); } htmls.push("<div id='"+divId+"'>"); if (geneInfos===undefined || geneInfos===null || geneInfos.length===0) { - if (noteStr!==undefined) + if (noteStr!==undefined && noteStr!==null) htmls.push("<div style='font-style:80%'>"+noteStr+"</div>"); htmls.push("</div>"); return; } var i = 0; while (i < geneInfos.length) { var geneInfo = geneInfos[i]; var geneIdOrSym = geneInfo[0]; var mouseOver = geneInfo[1]; // geneIdOrSym can be just the symbol (if we all we have is symbols) or geneId|symbol var internalId; var label; if (geneIdOrSym.indexOf("|")!==-1) { @@ -5511,31 +5522,31 @@ var field = metaFieldInfo[i]; var fieldName = field.label; var isNumeric = (field.type==="int" || field.type==="float"); var hasTooManyVals = (field.diffValCount>MAXCOLORCOUNT); if ((optStr==="noNums" && isNumeric) || (optStr=='doLabels' && (isNumeric || hasTooManyVals))) { continue; } entries.push( ["tpMetaVal_"+i, fieldName] ); if (selectedField == fieldName) { selIdx = i-1; // -1 because the first element was skipped } } - buildComboBox(htmls, id, entries, selIdx, "select a field...", metaBarWidth+50); + buildComboBox(htmls, id, entries, selIdx, "select a field...", 50); htmls.push('</div>'); } function getGeneLabel() { /* some datasets have data not on genes, but on other things e.g. "lipids". The config can * define a label for the rows in the expression matrix */ var geneLabel = "Gene"; if (db.conf.atacSearch) geneLabel = "Range"; if (db.conf.geneLabel) geneLabel = db.conf.geneLabel; return geneLabel; } function splitButtonLabel(state) { @@ -7375,71 +7386,73 @@ buildMetaFieldCombo(htmls, "tpLabelComboBox", "tpLabelCombo", 0, db.conf.labelField, "doLabels"); htmls.push('<div style="padding-top:4px; padding-bottom: 4px; padding-left:2px" id="tpHoverHint" class="tpHint">Hover over a '+gSampleDesc+' to update data below</div>'); htmls.push('<div style="padding-top:4px; padding-bottom: 4px; padding-left:2px; display: none" id="tpSelectHint" class="tpHint">Cells are selected. No update on hover.</div>'); htmls.push("<div id='tpMetaPanel'>"); buildMetaPanel(htmls); htmls.push("</div>"); // tpMetaPanel htmls.push("</div>"); // tpAnnotTab htmls.push("<div id='tpGeneTab'>"); buildGeneCombo(htmls, "tpGeneCombo", 0, metaBarWidth-10); - if (db.conf.split) - htmls.push('<input type="checkbox" id="splitJoinBox" name="splitJoin" value="splitJoin" /> <label for="splitJoinBox">Show on both screens</label>'); - // var myGenes = loadMyGenes(); + htmls.push('<div id="splitJoinDiv"><input class="form-check-input" type="checkbox" id="splitJoinBox" name="splitJoin" value="splitJoin" /> <label for="splitJoinBox">Show on both sides</label></div>'); if (db.conf.atacSearch) buildPeakList(htmls); var geneLabel = getGeneLabel(); var recentHelp = "Shown below are the 10 most recently searched genes. Click any gene to color the plot on the right-hand side by the gene."; buildGeneTable(htmls, "tpRecentGenes", "Recent "+geneLabel+"s", "Hover or select cells to update colors here<br>Click to color by gene", gRecentGenes, null, recentHelp); - - var noteStr = "No genes or peaks defined. Use the setting quickGenesFile in " - "cellbrowser.conf to add a file with gene symbols or peaks that will be shown here"; + var noteStr = "No genes or peaks defined: Use quickGenesFile in cellbrowser.conf."; var geneHelp = "The dataset genes were defined by the dataset submitter, publication author or data wrangler at UCSC. " + "Click any of them to color the plot on the right hand side by the gene."; buildGeneTable(htmls, "tpGenes", "Dataset "+geneLabel+"s", null, db.conf.quickGenes, noteStr, geneHelp); htmls.push("</div>"); // tpGeneTab htmls.push("<div id='tpLayoutTab'>"); buildLayoutCombo(db.conf.coordLabel, htmls, db.conf.coords, "tpLayoutCombo", 0, 2); htmls.push("</div>"); // tpLayoutTab htmls.push("</div>"); // tpLeftSidebar $(document.body).append(htmls.join("")); resizeGeneTableDivs("tpRecentGenes"); resizeGeneTableDivs("tpGenes"); activateTooltip('.hasTooltip'); $("#tpResetColors").click ( function() { removeSplit(renderer); colorByDefaultField(undefined, true) activateTab(); }); + $("#splitJoinBox").on("change", function() { + renderer.childPlot.activatePlot(); + var colorBy = getVar("gene"); + colorByLocus(colorBy); + }); + $("#tpSplitOnGene").click ( function() { if (renderer.isSplit()) { removeSplit(renderer); } else { $("#tpSplitOnGene").text(splitButtonLabel(false)); activateSplit(); colorByDefaultField(undefined, true) renderer.childPlot.activatePlot(); } }); $("#tpMultiGene").click ( onMultiGeneClick ); $("#tpLeftTabs").tabs(); @@ -8284,31 +8297,31 @@ var rows = gLegend.rows; var legTitle = gLegend.title; var subTitle = gLegend.subTitle; htmls.push('<span id="tpLegendTitle" title="' +gLegend.titleHover+'">'+legTitle+"</span>"); if (subTitle) htmls.push('<div id="tpLegendSubTitle" >'+subTitle+"</div>"); htmls.push('<div class="tpHint">Click buttons to select '+gSampleDesc+'s</small></div>'); htmls.push("<small><button id='tpLegendAll'>All</button>"); htmls.push("<button id='tpLegendNone'>None</button>"); htmls.push("<button id='tpLegendInvert'>Invert</button></small>"); htmls.push("<button id='tpLegendNotNull'>> 0</button></small>"); - let buttonText = "Color only checked"; + let buttonText = "Recolor only checked"; if (gLegend.isColorOnlyChecked===true) { buttonText = "Reset colors"; } htmls.push("<button id='tpLegendColorChecked'>"+buttonText+"</button></small>"); htmls.push("</div>"); // title htmls.push('<div id="tpLegendHeader"><span id="tpLegendCol1"></span><span id="tpLegendCol2"></span></div>'); htmls.push('<div id="tpLegendRows">'); // get the sum of all, to calculate frequency var sum = 0; for (var i = 0; i < rows.length; i++) { let count = rows[i].count; sum += count; @@ -8910,39 +8923,40 @@ if (!renderer.childPlot && !renderer.parentPlot) return; if (!renderer.isMain) { // make sure the left renderer is the active one renderer = renderer.childPlot; renderer.activatePlot(); } renderer.unsplit(); $("#tpSplitMenuEntry").text("Split Screen"); renderer.drawDots(); $("#tpSplitOnGene").text(splitButtonLabel(true)); } function activateSplit() { // nothing is split yet -> start the split - buildWatermark(renderer); + $("#splitJoinDiv").show(); + buildWatermark(renderer, true); renderer.onActiveChange = onActRendChange; var currCoordIdx = $("#tpLayoutCombo").val(); renderer.legend = gLegend; renderer.isMain = true; let rend2 = renderer.split(); - buildWatermark(rend2); + buildWatermark(rend2, true); renderer.childPlot.legend = gLegend; $("#tpSplitMenuEntry").text("Unsplit Screen"); $("#mpCloseButton").click( function() { removeSplit(renderer);} ); $("#tpSplitOnGene").text(splitButtonLabel(false)); } function onSplitClick() { /* user clicked on View > Split Screen */ if (!renderer.childPlot && !renderer.parentPlot) { activateSplit(); } else { removeSplit(renderer);