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 cells');
+ htmls.push('Reset background cells');
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 update selection and redraw
if (clickedCellIds!==null && self.onCellClick!==null) {
- self.selCells = clickedCellIds;
+ self.selectClear();
+ for (var i = 0; i < clickedCellIds.length; i++) {
+ self.selCells.add(clickedCellIds[i]);
+ }
self.drawDots();
self.onCellClick(clickedCellIds, ev);
diff --git a/src/cbPyLib/cellbrowser/cellbrowser.py b/src/cbPyLib/cellbrowser/cellbrowser.py
index 267259e1..0b173b20 100755
--- a/src/cbPyLib/cellbrowser/cellbrowser.py
+++ b/src/cbPyLib/cellbrowser/cellbrowser.py
@@ -17,6 +17,7 @@
from collections import namedtuple, OrderedDict
from os.path import join, basename, dirname, isfile, isdir, relpath, abspath, getsize, getmtime, expanduser
from time import gmtime, strftime
+import csv
try:
# python3
@@ -2462,10 +2463,10 @@ def parseMarkerTable(filename, geneToSym):
otherHeaders = headers[otherStart:otherEnd]
logging.debug("Other headers: %s" % otherHeaders)
+ reader = csv.reader(ifh, delimiter=sep, quotechar='"')
data = defaultdict(list)
otherColumns = defaultdict(list)
- for line in ifh:
- row = line.rstrip("\r\n").split(sep)
+ for row in reader:
clusterName = row[clusterIdx]
geneId = row[geneIdx]
scoreVal = float(row[scoreIdx])
@@ -4165,8 +4166,9 @@ def findRoot(inDir=None):
def resolveOutDir(outDir):
""" user can define mapping e.g. {"alpha" : "/usr/local/apache/htdocs-cells"} in ~/.cellbrowser.conf """
confDirs = getConfig("outDirs")
- if outDir in confDirs:
- outDir = confDirs[outDir]
+ if confDirs:
+ if outDir in confDirs:
+ outDir = confDirs[outDir]
return outDir
def build(confFnames, outDir, port=None, doDebug=False, devMode=False, redo=None):