1e682bf4a5e1776f9752d67e8becb2092926b5d5
max
  Sat Jul 12 02:46:18 2025 -0700
moving over Nan code, but knowing that there is a problem with the psi dataset

diff --git src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js
index cc6416c..5706a1d 100644
--- src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js
+++ src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js
@@ -40,30 +40,31 @@
 
     // last 10 genes
     var gRecentGenes = [];
 
     // -- CONSTANTS
     var gTitle = "UCSC Cell Browser";
     var COL_PREFIX = "col_";
 
     var gOpenDataset = null; // while navigating the open dataset dialog, this contains the current name
         // it's a global variable as the dialog is not a class (yet?) and it's the only piece of data
         // it is a subset of dataset.json , e.g. name, description, cell count, etc.
 
     // depending on the type of data, single cell or bulk RNA-seq, we call a circle a
     // "sample" or a "cell". This will adapt help menus, menus, etc.
     var gSampleDesc = "cell";
+    var gFeatDesc = "gene";
 
     // width of left meta bar in pixels
     var metaBarWidth = 250;
     // margin between left meta bar and drawing canvas
     var metaBarMargin = 0;
     // width of legend, pixels
     var legendBarWidth = 200;
     var legendBarMargin = 0;
     // width of the metaBar tooltip (histogram)
     var metaTipWidth = 400;
     // height of pull-down menu bar at the top, in pixels
     var menuBarHeight = null; // defined at runtime by div.height
     // height of the toolbar, in pixels
     var toolBarHeight = 28;
     // position of first combobox in toolbar from left, in pixels
@@ -75,30 +76,31 @@
     // width of a single gene cell in the meta gene bar tables
     //var gGeneCellWidth = 66;
 
     // height of the trace viewer at the bottom of the screen
     var traceHeight = 100;
 
     // height of bottom gene bar
     var geneBarHeight = 100;
     var geneBarMargin = 5;
     // color for missing value when coloring by expression value
     //var cNullColor = "CCCCCC";
     //const cNullColor = "DDDDDD";
     //const cNullColor = "95DFFF"; //= light blue, also tried e1f6ff
     //const cNullColor = "e1f6ff"; //= light blue
     const cNullColor = "AFEFFF"; //= light blue
+    const nanColor = "DDDDDD"; // light grey
 
     const cDefGradPalette = "magma";  // default legend gradient palette for gene expression
     // this is a special palette, tol-sq with the first entry being a light blue, so 0 stands out a bit more
     const cDefGradPaletteHeat = "magma";  // default legend gradient palette for the heatmap
     const cDefQualPalette  = "rainbow"; // default legend palette for categorical values
 
     var datasetGradPalette = cDefGradPalette;
     var datasetQualPalette = cDefQualPalette;
 
 
     const exprBinCount = 10; //number of expression bins for genes
     // has to match cbData.js.exprBinCount - TODO - share the constant between these two files
 
     var HIDELABELSNAME = "Hide labels";
     var SHOWLABELSNAME = "Show labels";
@@ -318,31 +320,31 @@
         }
         return trgArr;
     }
 
     function keys(o) {
     /* return all keys of object as an array */
         var allKeys = [];
         for(var k in o) allKeys.push(k);
         return allKeys;
     }
 
     function trackEvent(eventName, eventLabel) {
     /* send an event to google analytics */
         if (typeof gtag !== 'function')
             return;
-        gtag('event', eventName, eventLabel);
+        gtag('event', eventName, {"name": eventLabel});
     }
 
     function trackEventObj(eventName, obj) {
     /* send an event obj to google analytics */
         if (typeof gtag !== 'function')
             return;
         gtag('event', obj);
     }
 
     function classAddListener(className, type, listener) {
         /* add an event listener for all elements of a class */
         var els = document.getElementsByClassName(className);
         for (let el of els) {
             el.addEventListener(type, listener);
         }
@@ -3471,56 +3473,59 @@
             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));
+            var colors = makeLegendExpr(locusStr, geneDesc, binInfo, exprArr, decArr);
+            renderer.setColors(colors);
             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
+                var recIntId = gRecentGenes[i][0];
+                if (!db.isAtacMode())
+                    recIntId = recIntId.split("|")[0]; // only keep geneId for comparisons
+                if (recIntId===locusStr || gRecentGenes[i][1]===locusStr) { // match symbol or ID
                     gRecentGenes.splice(i, 1);
                     break;
                 }
             }
 
             // make sure that recent genes table has symbol and Id
             var locusWithSym = locusStr;
             if (db.isAtacMode()) {
-                locusWithSym = shortenRange(locusStr);
+                //locusWithSym = shortenRange(locusStr);
             } else {
                 if (locusStr.indexOf("+")===-1) {
                     let geneInfo = db.getGeneInfo(locusStr);
                     if ((geneInfo.sym!==geneInfo.geneId))
                         locusWithSym = geneInfo.id+"|"+geneInfo.sym;
                 } else { 
                     let geneCount = locusStr.split("+").length;
                     locusWithSym = locusStr+"|Sum of "+geneCount+" genes";
                 }
             }
 
             gRecentGenes.unshift([locusWithSym, geneDesc]); // insert at position 0
             gRecentGenes = gRecentGenes.slice(0, 9); // keep only nine last
             buildGeneTable(null, "tpRecentGenes", null, null, gRecentGenes);
             $('#tpRecentGenes .tpGeneBarCell').click( onGeneClick );
@@ -4267,65 +4272,94 @@
             else
                 changeUrl({"pal":palName});
             buildLegendBar();
             var colors = legendGetColors(gLegend.rows);
             renderer.setColors(colors);
         }
     }
 
     function legendSetColors(legend, colors, keyName) {
         /* set the colors for all legend rows, keyName can be "color" or "defColor", depending on
          * whether the current row color or the row default color should be changed.
          * colors can also be null to reset all values to null. */
         if (!keyName)
             keyName = "color";
         var rows = legend.rows;
+        var palIdx = 0;
+        var hasNan = false;
         for (let i = 0; i < rows.length; i++) {
             var colorVal = null;
-            if (colors)
-                colorVal = colors[i];
-
             var legendRow = rows[i];
-            if ((legendRow.label == "0" && legend.type=="expr") || (likeEmptyString(legendRow.label) && legend.type=="meta"))
+            if ((i==0 && legendRow.label == "0" && legend.type=="expr" && !hasNan) || 
+                (likeEmptyString(legendRow.label) && legend.type=="meta")) {
+                colorVal = cNullColor;
+            } else if ((legendRow.label == "-12345.00" && legend.type=="expr")) {
+                legendRow.label = "NaN";
                 colorVal = cNullColor;
+                hasNan = true;
+            } else if (colors) {
+                colorVal = colors[palIdx];
+                palIdx++;
+            }
+
             legendRow[keyName] = colorVal;
         }
     }
 
     function legendSetPalette(legend, origPalName) {
     /* update the defColor [1] attribute of all legend rows. pal is an array of hex colors.
      * Will use the predefined colors that are
      * in the legend.metaInfo.colors configuration, if present.
      * */
         var palName = origPalName;
         if (origPalName==="default") {
             if (legend.rowType==="category")
                 palName = datasetQualPalette;
             else
                 palName = datasetGradPalette;
         }
 
         var rows = legend.rows;
-        var n = rows.length;
+
+        // the number of colors needed is not the number of legend rows, because some values
+        // do not get a color from the palette, e.g. "Unknown" and "0" rows
+        //var n = rows.length;
+        var n = legend.rows.length;
+        var hasNan = false;
+        var hasZero = false;
+        for (var row of rows) {
+            if (row.strKey==="noExpr")
+                hasZero = true;
+            if (row.strKey=="nan")
+                hasNan=true;
+        }
+
+        if (hasZero)
+            n--;
+        if (hasNan)
+            n--;
+        if (hasZero && hasNan)
+            n++;
+
         var pal = null;
         var usePredefined = false;
 
-        pal = makeColorPalette(palName, n);
         // if this is a field for which colors were defined manually during the cbBuild, use them
         if (legend.metaInfo!==undefined && legend.metaInfo.colors!==undefined && origPalName==="default") {
             // the order of the color values in the metaInfo object is the same as the order of the order of the values in the
             // JSON file. But the legend has been sorted now, so we cannot just copy over the array as it is
+            pal = makeColorPalette(palName, rows.length);
             var rows = legend.rows;
             var predefColors = legend.metaInfo.colors;
             for (var i=0; i < rows.length; i++) {
                 var origIndex = rows[i].intKey;
                 var col = predefColors[origIndex];
                 if (col !== null)
                     pal[i] = col;
             }
             usePredefined = true;
         } else
             pal = makeColorPalette(palName, n);
 
         if (pal===null) {
             alert("Sorry, palette '"+palName+"' does not have "+rows.length+" different colors");
             return false;
@@ -4407,69 +4441,84 @@
             var binMax = oneBin[1];
             var count  = oneBin[2];
 
             var legendId = binIdx;
 
             var legLabel = labelForBinMinMax(binMin, binMax, isAllInt);
 
             var uniqueKey = legLabel;
 
             // override any color with the color specified in the current URL
             var savKey = COL_PREFIX+legLabel;
             var legColor = getVar(savKey, null);
 
             if (binMin===0 && binMax===0) {
                 uniqueKey = "noExpr";
-                legColor = cNullColor;
+                //legColor = cNullColor;
             }
-            else if (binMin==="Unknown" && binMax==="Unknown") {
-                uniqueKey = "noExpr";
-                legColor = cNullColor;
+            else if (binMin==="Unknown" && binMax==="Unknown" || (binMin===-12345 && binMax==-12345)) {
+                uniqueKey = "nan";
+                //legColor = cNullColor;
             }
             else
                 colIdx++;
 
             legendRows.push( {
                 "color": legColor,
                 "defColor":null,
                 "label":legLabel,
                 "count":count,
                 "intKey":binIdx,
                 "strKey":uniqueKey
             });
         }
         return legendRows;
     }
 
+    function prettifyPeaks(pipePeaks) {
+        /* transform chrom|start|end+chrom2|start2|end2 to chrom:start-end<spc>chrom2:start2-end2 */
+        var outParts = [];
+        var peaks = pipePeaks.split("+");
+        for (var pipePeak of peaks) {
+            var p = pipePeak.split("|");
+            var s = p[0]+":"+p[1]+"-"+p[2];
+            outParts.push(s);
+        }
+        return outParts.join(" ");
+    }
+
     function makeLegendExpr(geneSym, mouseOver, binInfo, exprVec, decExprVec) {
         /* build gLegend object for coloring by expression
          * return the colors as an array of hex codes */
 
         activateTooltip("#tpGeneSym");
 
         var legendRows = makeLegendRowsNumeric(binInfo);
 
         gLegend = {};
         gLegend.type = "expr";
         gLegend.rows = legendRows;
         var subTitle = null;
         if (db.isAtacMode()) {
             let peakCount = geneSym.split("+").length;
-            if (peakCount===1)
-                gLegend.title = "One ATAC peak";
-            else
-                gLegend.title = ("Sum of "+geneSym.split("+").length) + " ATAC peaks";
+            subTitle = prettifyPeaks(geneSym);
+            if (peakCount===1) {
+                gLegend.title = "One "+gFeatDesc;
+            }
+            else {
+                gLegend.title = ("Sum of "+geneSym.split("+").length) + " "+gFeatDesc+"s";
+            }
         }
         else {
             //  make a best effort to find the gene sym and gene ID
             if (geneSym.indexOf("+")===-1) {
                 var geneInfo = db.getGeneInfo(geneSym);
                 geneSym = geneInfo.sym;
                 subTitle = geneInfo.id;
             } else {
                 subTitle = "Sum of "+geneSym.split("+").length+" genes";
             }
             gLegend.title = getGeneLabel()+": "+geneSym;
         }
 
         gLegend.titleHover = mouseOver;
         gLegend.geneSym = geneSym;
@@ -4518,31 +4567,30 @@
         event.stopPropagation();
     }
 
     function showDialogBox(htmlLines, title, options) {
         /* show a dialog box with html in it */
         $('#tpDialog').remove();
 
         if (options===undefined)
             options = {};
 
         var addStr = "";
         if (options.width!==undefined)
             addStr = "max-width:"+options.width+"px;";
         var maxHeight = $(window).height()-200;
         // unshift = insert at pos 0
-        //htmlLines.unshift("<div style='display:none;"+addStr+"max-height:"+maxHeight+"px' id='tpDialog' title='"+title+"'>");
         htmlLines.unshift("<div style='display:none;"+addStr+"' id='tpDialog' title='"+title+"'>");
         htmlLines.push("</div>");
         $(document.body).append(htmlLines.join(""));
 
         var dialogOpts = {modal:true, closeOnEscape:true};
         if (options.width!==undefined)
             dialogOpts["width"] = options.width;
         if (options.height!==undefined)
             dialogOpts["height"] = options.height;
         //dialogOpts["maxHeight"] = maxHeight;
         if (options.buttons!==undefined)
             dialogOpts["buttons"] =  options.buttons;
         else
             dialogOpts["buttons"] =  [];
 
@@ -4690,30 +4738,36 @@
 
         var progressLabel = $( "#tpProgressLabel" );
         $("#tpGeneProgress").progressbar( {
               value: false,
               max  : gLoad_geneList.length
               });
 
     }
 
     function shortenRange(s) {
         /* reformat atac range chr1|start|end to chr1:10Mbp */
         var parts = s.split("|");
         return parts[0]+":"+prettyNumber(parts[1]);
     }
 
+    function humanizeRange(s) {
+        /* reformat atac range chr1|start|end to chr1:xxx-yyy */
+        var parts = s.split("|");
+        return parts[0]+":"+parts[1]+"-"+parts[2];
+    }
+
     function htmlAddInfoIcon(htmls, helpText, placement) {
         /* add an info icon with some text to htmls */
         var iconHtml = '<svg style="width:0.9em" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>';
         var addAttrs = "";
         if (placement!==undefined)
             addAttrs = " data-placement='"+placement+"'"
         htmls.push("<span class='hasTooltip' title='"+helpText+"'"+addAttrs+">&nbsp;"+iconHtml+"</span>");
         return htmls;
     }
 
     function buildGeneTable(htmls, divId, title, subtitle, geneInfos, noteStr, helpText) {
     /* create gene expression info table. if htmls is null, update DIV with divId in-place. 
      * geneInfos is array of [gene, mouseover]. gene can be geneId+"|"+symbol. 
      * You must run activateTooltip(".hasTooltip") after adding the htmls.
      * */
@@ -4755,38 +4809,33 @@
         }
 
         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) {
                 if (db.isAtacMode()) {
                     label = shortenRange(geneIdOrSym);
                     internalId = geneIdOrSym;
-                    //if (mouseOver!==undefined) {
-                        //label = mouseOver.split()[0];
-                        //internalId = geneIdOrSym;
-                    //} else {
-                        // quickGene is a range in format chr|123123|125443
-                        //label = shortenRange(geneIdOrSym);
-                        //internalId = geneIdOrSym;
-                    //}
+                    if (!mouseOver)
+                        mouseOver = humanizeRange(geneIdOrSym);
+                        
                 } else {
                     var parts = geneIdOrSym.split("|");
                     internalId = parts[0];
                     label = parts[1];
                 }
             } else {
                 internalId = geneIdOrSym;
                 label = internalId;
             }
 
             if (mouseOver===undefined)
                 mouseOver = internalId;
 
             htmls.push('<span title="'+mouseOver+'" style="width: fit-content;" data-geneId="'+internalId+'" id="tpGeneBarCell_'+onlyAlphaNum(internalId)+'" class="hasTooltip tpGeneBarCell">'+label+'</span>');
             i++;
@@ -5338,30 +5387,38 @@
         renderer.drawDots();
     }
 
     function showCollectionDialog(collName) {
         /* load collection with given name and open dialog box for it */
         loadCollectionInfo(collName, function(collData) { openDatasetDialog(collData)});
     }
 
     function onConfigLoaded(datasetName) {
         /* dataset config JSON is loaded -> build the entire user interface */
         // this is a collection if it does not have any field information
         if (db.conf.sampleDesc)
             gSampleDesc = db.conf.sampleDesc;
         else
             gSampleDesc = "cell";
+        if (db.conf.featDesc)
+            gFeatDesc = db.conf.featDesc;
+        else
+            if (db.isAtacMode())
+                gFeatDesc = "ATAC peak";
+            else
+                gFeatDesc = "gene";
+
 
         // allow config to override the default palettes
         datasetGradPalette = cDefGradPalette;
         datasetQualPalette = cDefQualPalette;
         if (db.conf.defQuantPal)
             datasetGradPalette = db.conf.defQuantPal;
         if (db.conf.defCatPal)
             datasetQualPalette = db.conf.defCatPal;
 
         if (db.conf.metaBarWidth)
             metaBarWidth = db.conf.metaBarWidth;
         else
             metaBarWidth = 250;
 
         renderer.setPos(null, metaBarWidth+metaBarMargin);
@@ -5534,52 +5591,50 @@
             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...", 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";
+            geneLabel = "peak";
         if (db.conf.geneLabel)
             geneLabel = db.conf.geneLabel;
         return geneLabel;
     }
 
     function splitButtonLabel(state) {
-        let dataType = "gene";
-        if (db.isAtacMode())
-            dataType = "peak";
+        let dataType = getGeneLabel();
         if (state)
-            return "Split on this "+dataType;
+            return "Split on this "+dataType[0].toLowerCase() + dataType.slice(1);
         else
             return "Remove split screen";
     }
 
     function buildGeneCombo(htmls, id, left, width) {
         /* Combobox that allows searching for genes */
         htmls.push('<div class="tpLeftSideItem" style="padding-left: 3px">');
         var title = "Color by "+getGeneLabel();
         if (db.conf.atacSearch)
-            title = "Find peaks at or close to:"
+            title = "Find "+gFeatDesc+" at or close to:"
         htmls.push('<label style="display:block; margin-bottom:8px; padding-top: 8px;" for="'+id+'">'+title+'</label>');
         var geneLabel = getGeneLabel().toLowerCase();
         var boxLabel = 'search for '+geneLabel+'...';
         if (db.conf.atacSearch)
             boxLabel = "enter gene or chrom:start-end";
         htmls.push('<select style="width:'+width+'px" id="'+id+'" placeholder="'+boxLabel+'" class="tpCombo">');
         htmls.push('</select>');
 
         htmls.push('<div><button style="margin-top:4px" id="tpSplitOnGene">'+splitButtonLabel(true)+'</button>');
         htmls.push('<button style="margin-left: 4px" id="tpMultiGene">Multi Gene</button></div>');
         htmls.push('<div><button style="margin-top:4px" id="tpResetColors">Reset to default coloring</button></div>');
         htmls.push('</div>');
     }
 
 
@@ -5599,31 +5654,31 @@
             var ds = datasets[i];
             var selStr =  "";
             if (ds.name===db.conf.name)
                 selStr = "selected";
             var val = ds["name"]+"?"+ds["md5"];
             htmls.push('<option value="'+val+'"'+selStr+'>'+ds.shortLabel+'</option>');
         }
         $('#'+id).html(htmls.join(""));
         $("#"+id).trigger("chosen:updated");
     }
 
     /* ----- PEAK LIST START ----- */
 
     function buildPeakList(htmls) {
         /* add a container for the list of peaks to htmls */
-        htmls.push("<div id='tpPeakListTitle'>Peaks found</div>");
+        htmls.push("<div id='tpPeakListTitle'>"+gFeatDesc+" found</div>");
 
         htmls.push("<div id='tpPeakList' style='height: 30%'>");
             htmls.push("<span id='noPeaks'>No genes or ranges found</span>");
         htmls.push("</div>");
 
         htmls.push("<div id='tpPeakListSelector'>");
         //htmls.push("<input id='tpPeakListAuto' style='margin-right: 3px' type='checkbox' checked>");
         htmls.push("<div id='tpPeakListButtonControls' style='margin-left: 4px'>");
         htmls.push('<button title="Select all peaks in the list above" id="tpPeakListAll">All</button>');
         htmls.push('<button title="Select no peaks in the list above" id="tpPeakListNone">None</button>');
         htmls.push('<button title="Select only peaks within a certain distance upstream from the TSS. Click on the field to edit the distance. Click the button to select the peaks." id="tpPeakListUpstream">');
         htmls.push('<input id="tpPeakListAutoDist" type="text" value="2" style="width:2em;border: 0;height: 0.8em;">');
         htmls.push('kbp upstream</button>');
         htmls.push("<div id='tpPeakListButtons'>");
 
@@ -5982,30 +6037,31 @@
                 // URL is an absolute link to a hub.txt URL
                 fullUrl = "https://genome.ucsc.edu/cgi-bin/hgTracks?hubUrl="+hubUrl+"&genome="+ucscDb;
             }
 
             if (geneSym && !geneSym.startsWith("atac-"))
                 fullUrl += "&position="+geneSym+"&singleSearch=knownCanonical";
 
             return fullUrl;
     }
 
     function onGenomeButtonClick(ev) {
         /* run when the user clicks the 'genome browser' button */
         let actSym = null;
         if (gLegend.type==="expr")
             actSym = gLegend.geneSym;
+
         var fullUrl = makeHubUrl(actSym);
         db.gbWin = window.open(fullUrl, 'gbTab');
         return false;
     }
 
     function onXenaButtonClick(ev) {
         /* run when the user clicks the xena button */
         var geneInfos = db.conf.quickGenes;
         var syms  = []
         if (geneInfos!=undefined) {
             for (var i = 0; i < geneInfos.length; i++)
                 syms.push( geneInfos[i][0] ); // make array of symbols
         }
 
         var actSym = null;
@@ -7403,44 +7459,44 @@
 
         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 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);
+        buildGeneTable(htmls, "tpQuickGenes", "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");
+        resizeGeneTableDivs("tpQuickGenes");
 
         activateTooltip('.hasTooltip');
 
         $("#tpResetColors").click ( function() { 
             removeSplit(renderer);
             colorByDefaultField(undefined, true) 
             activateTab();
         });
 
         $("#splitJoinBox").on("change", function() {
             renderer.childPlot.activatePlot();
             var colorBy = getVar("gene");
             colorByLocus(colorBy);
         });
 
@@ -7660,50 +7716,48 @@
                 "885578", "FAD09F", "FF8A9A", "D157A0", "BEC459", "456648", "0086ED", "886F4C",
                 "34362D", "B4A8BD", "00A6AA", "452C2C", "636375", "A3C8C9", "FF913F", "938A81",
                 "575329", "00FECF", "B05B6F", "8CD0FF", "3B9700", "04F757", "C8A1A1", "1E6E00",
                 "7900D7", "A77500", "6367A9", "A05837", "6B002C", "772600", "D790FF", "9B9700",
                 "549E79", "FFF69F", "201625", "72418F", "BC23FF", "99ADC0", "3A2465", "922329",
                 "5B4534", "FDE8DC", "404E55", "0089A3", "CB7E98", "A4E804", "324E72", "6A3A4C"];
         return pal.slice(0, n);
     }
 
     function makeColorPalette(palName, n) {
     /* return an array with n color hex strings */
     /* Use Google's palette functions for now, first Paul Tol's colors, if that fails, use the usual HSV rainbow
      * This code understands our special palette, tol-sq-blue
     */
         var pal = [];
-        if (palName==="blues")
+        if (n===2)
+            pal = ["0000FF","FF0000"];
+        else if (palName==="blues")
             pal = makeHslPalette(0.6, n);
         else if (palName==="magma" || palName==="viridis" || palName==="inferno" || palName=="plasma")
             pal = makePercPalette(palName, n);
         else if (palName==="iwanthue")
             pal = iWantHue(n);
         else if (palName==="reds")
             pal = makeHslPalette(0.0, n);
         else if (palName==="tatarize")
             pal = makeTatarizePalette(n);
-        else {
-            if (n===2)
-                pal = ["0000FF","FF0000"];
         else {
             var realPalName = palName.replace("tol-sq-blue", "tol-sq");
             pal = palette(realPalName, n);
             if (palName==="tol-sq-blue")
                 pal[0]='f4f7ff';
         }
-        }
 
         return pal;
     }
 
     function colorByCluster() {
     /* called when meta and coordinates have been loaded: scale data and color by meta field  */
         //setZoomRange();
     }
 
     function loadClusterTsv(fullUrl, func, divName, clusterName) {
     /* load a tsv file relative to baseUrl and call a function when done */
         function conversionDone(data) {
             Papa.parse(data, {
                     complete: function(results, localFile) {
                                 func(results, localFile, divName, clusterName);
@@ -8428,31 +8482,31 @@
         let checkEls = document.getElementsByClassName("tpLegendCheckbox");
         for (let i = 0; i < checkEls.length; i++) {
             checkEls[i].addEventListener("change", onLegendCheckboxClick);
         }
 
         $("#tpLegendClear").click( onLegendClearClick );
         $("#tpExprLimitApply").click( onLegendApplyLimitsClick );
         $("#tpExpColorButton").click( onLegendExportClick );
 
         $("#tpLegendNone").click( function() { legendSetCheckboxes("none"); } );
         $("#tpLegendAll").click( function() { legendSetCheckboxes("all"); } );
         $("#tpLegendInvert").click( function() { legendSetCheckboxes("invert"); } );
         $("#tpLegendNotNull").click( function() { legendSetCheckboxes("notNull"); } );
         $("#tpLegendColorChecked").click( function(ev) { legendColorOnlyChecked(ev); } );
 
-        $('.tpLegendLabel').click( onLegendLabelClick ); // clicking the legend should have the same effect as clicking the checkbox
+        $('.tpLegendLabel').on("mouseup", onLegendLabelClick ); // clicking the legend should have the same effect as clicking the checkbox
         $('.tpLegendLabel').on("mouseover", onLegendHover ); // hovering over the legend should have the same effect hovering over the label
         //$('.tpLegendLabel').attr( "title", "Click to select samples with this value. Shift click to select multiple values.");
         activateTooltip(".tpLegendLabel");
         activateTooltip(".tpLegendCount");
 
         // setup the right-click menu
         //var menuItems = [{name: "Hide "+gSampleDesc+"s with this value"}, {name:"Show only "+gSampleDesc+"s with this value"}];
         //var menuOpt = {
             //selector: ".tpLegend",
             //items: menuItems,
             //className: 'contextmenu-customwidth',
             //callback: onLegendRightClick
         //};
         //$.contextMenu( menuOpt );