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+"> "+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 );