diff --git a/README.rst b/README.rst index 9d76ee69..92fd02a6 100644 --- a/README.rst +++ b/README.rst @@ -18,29 +18,34 @@ If you want us to add a single cell dataset to the website http://cells.ucsc.edu please contact us at cells@ucsc.edu. We are happy to add any dataset. This is a viewer for a static, precomputed layout. If you're looking for an interative layout, where you can -move the cells around and run algorithsm interactively, try Chan-Zuckerberg's own cellxgene or Spring. -Another viewer is `Scope `_. +move the cells around and run some algorithms interactively, try Chan-Zuckerberg's own cellxgene or Spring. +A website with both datasets and some analysis is `Scope `_. -Apart from labs at UCSC, UCSF and Gladstone who host their data at -cells.ucsc.edu, other groups have setup their own cell browsers: +Many labs host their data at cells.ucsc.edu by sending it to us, but some groups have setup their own cell browsers: * Alexander Misharin Lab, Northwester University, https://www.nupulmonary.org/resources/ * Accelerating Medicine Partnership Consortium, https://immunogenomics.io/cellbrowser/, used in `Zhang et al. 2018 `_ and `Der et al 2018 `_ -* Sansom Lab, Oxford, https://sansomlab.github.io -* http://caire.ipmc.cnrs.fr/cellbrowser/Differentiation/ Zaragosi group at IPMC CNRS Nice, for the manuscript https://dev.biologists.org/content/early/2019/09/25/dev.177428.abstract +* Sansom Lab, Oxford, https://sansomlab.github.io for `Croft et al, Nature 2019 `_ +* http://caire.ipmc.cnrs.fr/cellbrowser/Differentiation/ (URL has changed, contacted authors) Zaragosi group at IPMC CNRS Nice, for the manuscript https://dev.biologists.org/content/early/2019/09/25/dev.177428.abstract + +These papers have cell browsers made at UCSC: +* organoidatlas: https://www.sciencedirect.com/science/article/pii/S221112472030053X +* dros-brain: https://elifesciences.org/articles/50354 +* kidney-atlas: https://science.sciencemag.org/content/365/6460/1461.abstract +* allen-celltypes/mouse-cortex: https://www.biorxiv.org/content/10.1101/2020.03.30.015214v1.full +* organoidreportcard: https://www.nature.com/articles/s41586-020-1962-0 Additional availability ----------------------- -* The preferred installation is via pip https://pypi.org/project/cellbrowser/0.4.12/, see https://cellbrowser.readthedocs.io -* Bioconda: this tool is available to install via `bioconda `_Note that the conda release is usually a bit outdated relative to the pip release. +* The preferred installation is via pip https://pypi.org/project/cellbrowser/, for documentation see https://cellbrowser.readthedocs.io +* Bioconda: this tool is available to install via `bioconda `_. Note that the conda release is usually a bit outdated relative to the pip release, so use pip if possible. If you cannot use pip, please contact us. * Biocontainers: there is a biocontainer automatically generated from the bioconda package available `here `_ * The Seurat3Wizard, demo at http://nasqar.abudhabi.nyu.edu/SeuratV3Wizard, builds a cell browser as its last step * Galaxy: there is a Galaxy tool for UCSC CellBrowser, which can be installed on any Galaxy instance via its `Galaxy Toolshed entry `_ or it can be directly used by users at the `Human Cell Atlas Galaxy instance `_ or as part of the example workflows, such as the `Human Cell Atlas / Scanpy CellBrowser workflow `_ or the `EBI Single Cell Expression Atlas / Scanpy / CellBrowser workflow `_ - -Funded by the California Institute of Regenerative Medicine and the -Chan-Zuckerberg Initiative https://www.chanzuckerberg.com/. +This project was funded by the California Institute of Regenerative Medicine and the +Chan-Zuckerberg Initiative https://www.chanzuckerberg.com/. In 2020, it is funded through a supplement to the NHGRI Genome Browser grant. This is early research software. You are likely to find bugs. Please open a Github ticket or email us at cells@ucsc.edu, we can usually fix them quickly. diff --git a/docs/cellBrowserLogo.png b/docs/cellBrowserLogo.png new file mode 100644 index 00000000..0d05b701 Binary files /dev/null and b/docs/cellBrowserLogo.png differ diff --git a/docs/cellBrowserLogoBallOnly.png b/docs/cellBrowserLogoBallOnly.png new file mode 100644 index 00000000..701e2cc2 Binary files /dev/null and b/docs/cellBrowserLogoBallOnly.png differ diff --git a/src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js b/src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js index a5bb482f..436c7b23 100644 --- a/src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js +++ b/src/cbPyLib/cellbrowser/cbWeb/js/cellBrowser.js @@ -31,6 +31,8 @@ var cellbrowser = function() { var renderer = null; + var background = null; + // last 10 genes var gRecentGenes = []; @@ -932,7 +934,7 @@ var cellbrowser = function() { let doFaceting = false; let filtList = []; - if (openDsInfo.parents === undefined) { + if (openDsInfo.parents === undefined && openDsInfo.datasets !== undefined) { //noteLines.push("Filter:"); let bodyParts = getBodyParts(openDsInfo.datasets); if (bodyParts.length!==0) { @@ -1055,7 +1057,9 @@ var cellbrowser = function() { changeUrl({"ds":openDatasetName}); }); + var focused = document.activeElement; var scroller = $("#tpDatasetList").overlayScrollbars({ }); + $(focused).focus(); $("#tabLink1").tab("show"); @@ -1074,11 +1078,16 @@ var cellbrowser = function() { } - function onSelChange(cellIds) { + function onSelChange(selection) { /* called each time when the selection has been changed */ + var cellIds = []; + selection.forEach(function(x) {cellIds.push(x)}); + $("#tpSetBackground").parent("li").removeClass("disabled"); + if (cellIds.length===0 || cellIds===null) { clearMetaAndGene(); clearSelectionState(); + $("#tpSetBackground").parent("li").addClass("disabled"); } else if (cellIds.length===1) { var cellId = cellIds[0]; var cellCountBelow = cellIds.length-1; @@ -1093,6 +1102,22 @@ var cellbrowser = function() { if ("geneSym" in gLegend) buildViolinPlot(); + var cols = renderer.col.arr; + var selectedLegends = {}; + for (var i = 0; i < gLegend.rows.length; i++) { + selectedLegends[i] = 0; + } + selection.forEach(function(cellId) { + selectedLegends[cols[cellId]]++; + }); + for (var i = 0; i < gLegend.rows.length; i++) { + if (selectedLegends[i] == gLegend.rows[i].count) { + $("#tpLegendCheckbox_" + i).prop("checked", true); + } else { + $("#tpLegendCheckbox_" + i).prop("checked", false); + } + } + updateLegendGrandCheckbox(); } function onSaveAsClick() { @@ -1506,6 +1531,26 @@ var cellbrowser = function() { return true; } + function onBackgroudSetClick() { +// Tools -> Set cells as background + if ($("#tpSetBackground").parent("li").hasClass("disabled")) { + return; + } + + background = renderer.getSelection(); + $("#tpResetBackground").parent("li").removeClass("disabled"); + if ("geneSym" in gLegend) + buildViolinPlot(); + } + + function onBackgroudResetClick() { +// Tools -> Reset background cells + background = null; + $("#tpResetBackground").parent("li").addClass("disabled"); + if ("geneSym" in gLegend) + buildViolinPlot(); + } + function saveQueryList(queryList) { var queryStr = JSURL.stringify(queryList); changeUrl({'select':queryStr}); @@ -2154,6 +2199,8 @@ var cellbrowser = function() { //htmls.push('
  • Rename clusters...
  • '); htmls.push('
  • Remove all custom annotations
  • '); //htmls.push('
  • Run clustering...
  • '); + htmls.push('
  • Set as background cellsb s
  • '); + htmls.push('
  • Reset background cellsb r
  • '); htmls.push(''); // Tools dropdown-menu htmls.push(''); // Tools dropdown container @@ -2203,6 +2250,8 @@ var cellbrowser = function() { $('#tpRenameClusters').click( onRenameClustersClick ); $('#tpCustomAnnots').click( onCustomAnnotationsClick ); + $('#tpSetBackground').click( onBackgroudSetClick ); + $('#tpResetBackground').click( onBackgroudResetClick ); //$('#tpCluster').click( onRunClusteringClick ); // This version is more like OSX/Windows: @@ -2480,10 +2529,13 @@ var cellbrowser = function() { var labelLines = []; //labelLines.push([labelList[0], dataList[0].length+" cells"]); - labelLines.push([labelList[0], dataList[0].length]); - if (dataList.length > 1) + labelLines[0] = labelList[0].split("\n"); + labelLines[0].push(dataList[0].length); + if (dataList.length > 1) { //labelLines.push([labelList[1], dataList[1].length+" cells"]); - labelLines.push([labelList[1], dataList[1].length]); + labelLines[1] = labelList[1].split("\n"); + labelLines[1].push(dataList[1].length); + } const ctx = document.getElementById("tpViolinCanvas").getContext("2d"); @@ -2566,6 +2618,31 @@ var cellbrowser = function() { var dataList = []; var labelList = []; var selCells = renderer.getSelection(); + + // filter exprVec by background + if (background !== null) { + var ourSelCells = {}; + for (var i = 0; i < selCells.length; i++) { + ourSelCells[selCells[i]] = true; + } + var ourCells = {}; + for (i = 0; i < background.length; i++) { + ourCells[background[i]] = true; + } + + var result = []; + var renamedSelCells = []; + for (i = 0; i < exprVec.length; i++) { + if (i in ourSelCells) { + renamedSelCells.push(result.length); + result.push(exprVec[i]); + } else if (i in ourCells) { + result.push(exprVec[i]); + } + } + exprVec = result; + selCells = renamedSelCells; + } // if we have a violin meta field to split on, make two violin plots, one meta vs, the other meta // restrict the plot to the selected cells, if any if (db.conf.violinField!==undefined) { @@ -2575,12 +2652,20 @@ var cellbrowser = function() { if (selCells.length===0) { // no selection, no violinField: default to a single violin plot dataList = [Array.prototype.slice.call(exprVec)]; - labelList = ['All cells']; + if (background === null) { + labelList = ['All cells']; + } else { + labelList = ['Background\ncells']; + } buildViolinFromValues(labelList, dataList); } else { // cells are selected and no violin field: make two violin plots, selected against other cells dataList = splitExpr(exprVec, selCells); - labelList = ['Selected', 'Others']; + if (background === null) { + labelList = ['Selected', 'Others']; + } else { + labelList = ['Selected', 'Background\nOthers']; + } if (dataList[1].length===0) { dataList = [dataList[0]]; labelList = ['All Selected']; @@ -3296,6 +3381,7 @@ var cellbrowser = function() { gLegend.rowType = "range"; gLegend.exprVec = exprVec; // raw expression values, e.g. floats gLegend.decExprVec = decExprVec; // expression values as deciles, array of bytes + gLegend.selectionDirection = "all"; legendSetPalette(gLegend, "default"); var colors = legendGetColors(legendRows); @@ -3841,6 +3927,7 @@ var cellbrowser = function() { legend.rows = rows; legend.isSortedByName = sortResult.isSortedByName; legend.rowType = "category"; + legend.selectionDirection = "all"; legendSetPalette(legend, "default"); return legend; } @@ -4865,6 +4952,8 @@ var cellbrowser = function() { Mousetrap.bind('s i', onSelectInvertClick); Mousetrap.bind('s s', onSelectNameClick); + Mousetrap.bind('b s', onBackgroudSetClick); + Mousetrap.bind('b r', onBackgroudResetClick); Mousetrap.bind('m', function() {$('#tpMetaCombo').trigger("chosen:open"); return false;}); Mousetrap.bind('d', function() {$('#tpDatasetCombo').trigger("chosen:open"); return false;}); @@ -5080,49 +5169,9 @@ var cellbrowser = function() { function onLegendLabelClick(ev) { /* called when user clicks on legend entry. */ - //function saveLabel() { - ///* save the current labelEl text to the cart and update everything */ - //$(".tooltip").remove(); // not sure why tooltips won't disappear here - //labelEl.removeAttr("contenteditable"); - //var newLabel = labelEl.text(); // = strip the rich text tags possibly added through copy/paste - //var metaInfo = gLegend.metaInfo; - //cartFieldArrayUpdate(db, metaInfo, "shortLabels", legendId, newLabel); - //legendUpdateLabels(gLegend.metaInfo.name); - //rendererUpdateLabels(metaInfo); - //buildLegendBar(); - //renderer.drawDots(); - //} - var legendId = parseInt(ev.target.id.split("_")[1]); var colorIndex = gLegend.rows[legendId].intKey; - - //if (("lastClicked" in gLegend) && gLegend.lastClicked===legendId) { - // user clicked the same entry as before: - //gLegend.lastClicked = null; - //$('#tpLegend_'+legendId).removeClass('tpLegendSelect'); - //renderer.selectClear(); - //} - //else { - // clear the old selection - //if (!ev.shiftKey && !ev.ctrlKey && !ev.metaKey) { - //renderer.selectClear(); - //$('.tpLegend').removeClass('tpLegendSelect'); - //} - $("#tpLegendCheckbox_"+colorIndex).prop("checked", true); - renderer.selectByColor(colorIndex); - //menuBarShow("#tpFilterButton"); - //menuBarShow("#tpOnlySelectedButton"); - //$('#tpLegend_'+legendId).addClass('tpLegendSelect'); - //gLegend.lastClicked=legendId; - clearSelectionState(); - //if (gLegend.type==="meta" && gLegend.metaInfo.type==="enum") { - //let fieldName = gLegend.metaInfo.name; - //let fieldVal = gLegend.metaInfo.valCounts[colorIndex][0]; - //let queryList = [{"m":fieldName, "eq":fieldVal}]; - //saveQueryList(queryList); - //} - //} - renderer.drawDots(); + $("#tpLegendCheckbox_" + colorIndex).click(); } function onSortByClick (ev) { @@ -5179,25 +5228,47 @@ var cellbrowser = function() { function setLegendHeaders(type) { /* set the headers of the right-hand legend */ if (type==="category") { - $('#tpLegendCol1').html(' Name'); + $('#tpLegendCol1').html(' Name'); $('#tpLegendCol2').html(' Frequency'); } else { - $('#tpLegendCol1').html(' Range☑ Range