mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-27 02:38:12 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bedbc87ed6 | ||
|
|
01675a9db9 | ||
|
|
f91935ba0a | ||
|
|
491e643ae2 | ||
|
|
91bac7fef9 | ||
|
|
63631dad37 | ||
|
|
16b19defcf | ||
|
|
98eb91b005 | ||
|
|
e2a3e41867 | ||
|
|
036b5f8c0f | ||
|
|
e6281baa39 | ||
|
|
3aef21f76c | ||
|
|
d821f0eac9 | ||
|
|
3c0b1d45db | ||
|
|
90a4ff7526 | ||
|
|
d5ad823895 | ||
|
|
0e48b335be | ||
|
|
e264724597 | ||
|
|
c7eb319817 | ||
|
|
db559467a2 | ||
|
|
96362b0b98 | ||
|
|
7599af252d | ||
|
|
f07e174a06 | ||
|
|
46d02b1987 | ||
|
|
0b91371ea8 | ||
|
|
c428242878 | ||
|
|
9be086b1e8 | ||
|
|
9a34264f92 | ||
|
|
66e55ba59a | ||
|
|
f700fb7757 | ||
|
|
ae310097dd | ||
|
|
ea70a35a01 | ||
|
|
16718f392f | ||
|
|
ad6b1928e4 | ||
|
|
2cc02a84cb | ||
|
|
f77038ad58 | ||
|
|
2cf55ab819 | ||
|
|
095db02439 | ||
|
|
85fc000418 | ||
|
|
34742b9a94 | ||
|
|
06b88cda20 | ||
|
|
23714bc9f8 | ||
|
|
e892e64685 | ||
|
|
39a1124c35 | ||
|
|
b5856ff9bc | ||
|
|
d87551ce5e | ||
|
|
a5c9ae2432 | ||
|
|
78176f9711 | ||
|
|
b9e132a00c | ||
|
|
3b6c46ba86 | ||
|
|
6a1e5f71be | ||
|
|
727af83152 | ||
|
|
924b518492 | ||
|
|
946a910ef4 | ||
|
|
7e9353c5f1 | ||
|
|
c106ebc525 | ||
|
|
2fa206f2ad | ||
|
|
f41a023418 | ||
|
|
9793398737 | ||
|
|
377e4bccaa | ||
|
|
6a741956e1 | ||
|
|
c9f9549118 | ||
|
|
242546371b | ||
|
|
798976e4c1 | ||
|
|
5325495123 | ||
|
|
beb46bf3df | ||
|
|
86ff48ae36 | ||
|
|
c01a2c72b6 | ||
|
|
6677d0de56 | ||
|
|
c4c48b9a57 | ||
|
|
b5ec43c4b1 | ||
|
|
6c1756f852 | ||
|
|
1c4c501c43 | ||
|
|
cf77a8da9e | ||
|
|
eb108feb37 | ||
|
|
b386ca3425 | ||
|
|
b048fd8d9a | ||
|
|
3718e894ed | ||
|
|
1f9bba6f00 | ||
|
|
8bd4cbd1e5 | ||
|
|
e6c996ca93 | ||
|
|
99152dd5aa | ||
|
|
04a3c3c6b6 | ||
|
|
998fa4762d | ||
|
|
2ffe5ffcae | ||
|
|
4aabb8e092 | ||
|
|
7bee09cd16 | ||
|
|
af3c6e1d8e | ||
|
|
1145f61c78 | ||
|
|
863ca8be03 | ||
|
|
21dfdb91a9 | ||
|
|
374bb11279 | ||
|
|
3e2d7174fd | ||
|
|
a817a94eec | ||
|
|
210042814f | ||
|
|
14fbe0aa77 | ||
|
|
25c272ae8e | ||
|
|
a63bf9d5a3 | ||
|
|
3e9cb0265e | ||
|
|
4f339e89b1 | ||
|
|
9fac6849a3 | ||
|
|
342a9d774c | ||
|
|
6a7ae8bc8e | ||
|
|
4b240920e2 | ||
|
|
a7a4580944 | ||
|
|
3f20f4a1f4 | ||
|
|
5583e91392 | ||
|
|
89b68723cc | ||
|
|
5781879da5 | ||
|
|
0a27b2923a | ||
|
|
437fd5feda | ||
|
|
54b42607ae | ||
|
|
ed865e9a57 | ||
|
|
f8cdb12892 | ||
|
|
ab1b9368a0 | ||
|
|
0a10b3ec2a | ||
|
|
eb05d1cb5c | ||
|
|
9a40b28172 | ||
|
|
65ea1b673f | ||
|
|
5dfe0043c3 | ||
|
|
bc150a8469 | ||
|
|
a5c9ffa880 | ||
|
|
fae9ac9382 | ||
|
|
924aaf9aef | ||
|
|
950be4426d | ||
|
|
053f39d49e | ||
|
|
994c20c094 | ||
|
|
1acb8e4a6f | ||
|
|
298924fef5 | ||
|
|
4ad9f5875a | ||
|
|
508889f74b | ||
|
|
b034055c35 | ||
|
|
263e893b30 | ||
|
|
6a82030558 | ||
|
|
018f653ec6 | ||
|
|
905308e09f | ||
|
|
3c04529523 | ||
|
|
2689d8d2c0 | ||
|
|
1c4bb84f35 | ||
|
|
6848f7a8b2 | ||
|
|
dda530a67c | ||
|
|
a23aaa131d |
+12
-1
@@ -1,5 +1,16 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.16.0
|
current_version = 0.16.7
|
||||||
|
commit = True
|
||||||
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<prerel>rc)\.(?P<prerelversion>\d+))?
|
||||||
|
serialize =
|
||||||
|
{major}.{minor}.{patch}-{prerel}.{prerelversion}
|
||||||
|
{major}.{minor}.{patch}
|
||||||
|
|
||||||
|
[bumpversion:part:prerel]
|
||||||
|
optional_value = release
|
||||||
|
values =
|
||||||
|
rc
|
||||||
|
release
|
||||||
|
|
||||||
[bumpversion:file:setup.py]
|
[bumpversion:file:setup.py]
|
||||||
search = version="{current_version}"
|
search = version="{current_version}"
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL Scan"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ main ]
|
||||||
|
schedule:
|
||||||
|
- cron: '0 8 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'javascript', 'python' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
@@ -25,10 +25,11 @@ jobs:
|
|||||||
cellxgene-main-with-python-and-anndata-versions:
|
cellxgene-main-with-python-and-anndata-versions:
|
||||||
name: python versions x anndata versions
|
name: python versions x anndata versions
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7, 3.8]
|
python-version: [3.6, 3.7] # As of Oct 2020 Anndata is not compatible with 3.8
|
||||||
anndata-version: [0.6.22.post1, 0.7.1]
|
anndata-version: [0.7.0, 0.7.1, 0.7.2, 0.7.3, 0.7.4, 0.7.5]
|
||||||
test-suite: [smoke-test, smoke-test-annotations]
|
test-suite: [smoke-test, smoke-test-annotations]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -67,11 +68,6 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
path: cellxgene
|
path: cellxgene
|
||||||
- name: Checkout tools repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: theislab/anndata
|
|
||||||
path: anndata
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
cd cellxgene
|
cd cellxgene
|
||||||
@@ -82,7 +78,7 @@ jobs:
|
|||||||
# 2. install cellxgene
|
# 2. install cellxgene
|
||||||
pip install --upgrade cellxgene
|
pip install --upgrade cellxgene
|
||||||
# 3. install anndata
|
# 3. install anndata
|
||||||
cd ../anndata && pip install -e .
|
pip install git+https://github.com/theislab/anndata
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cd cellxgene && make unit-test ${{ matrix.test-suite }}
|
run: cd cellxgene && make unit-test ${{ matrix.test-suite }}
|
||||||
|
|
||||||
@@ -102,17 +98,11 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
path: cellxgene
|
path: cellxgene
|
||||||
- name: Checkout tools repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: theislab/anndata
|
|
||||||
path: anndata
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
cd cellxgene
|
cd cellxgene
|
||||||
sed -i -E 's/^anndata[>=]=[0-9]+.[0-9]+.[0-9]+$/anndata/g' server/requirements.txt
|
sed -i -E 's/^anndata[>=]=[0-9]+.[0-9]+.[0-9]+$/anndata/g' server/requirements.txt
|
||||||
make pydist install-dist dev-env
|
make pydist install-dist dev-env
|
||||||
cd ../anndata
|
pip install git+https://github.com/theislab/anndata
|
||||||
pip install -e .
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cd cellxgene && make unit-test ${{ matrix.test-suite }}
|
run: cd cellxgene && make unit-test ${{ matrix.test-suite }}
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install flake8
|
pip install flake8
|
||||||
|
pip install black
|
||||||
cd client
|
cd client
|
||||||
npm install
|
npm install
|
||||||
- name: Lint with flake8
|
- name: Format with black and lint with flake8
|
||||||
run: |
|
run: |
|
||||||
make lint-server
|
make lint-server
|
||||||
- name: Lint src with eslint
|
- name: Lint src with eslint
|
||||||
@@ -73,6 +74,7 @@ jobs:
|
|||||||
|
|
||||||
smoke-tests:
|
smoke-tests:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
@@ -102,6 +104,7 @@ jobs:
|
|||||||
|
|
||||||
smoke-tests-annotations:
|
smoke-tests-annotations:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
name: "Scale test cellxgene APIs for initial loading"
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * Sun"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
locust-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python 3.7
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.7
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r server/test/locust/requirements-locust.txt
|
||||||
|
- name: Dev Scale Test
|
||||||
|
run: |
|
||||||
|
locust -f server/test/locust/locustfile.py --headless -u 30 -r 10 --host https://api.cellxgene.dev.single-cell.czi.technology/cellxgene/e/ --run-time 5m 2>&1 | tee locust_dev_stats.txt
|
||||||
|
- name: Slack success webhook
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||||
|
run: |
|
||||||
|
DEV_STATS=$(tail -n 61 locust_dev_stats.txt)
|
||||||
|
DEV_MSG="\`\`\`CELLXGENE EXPLORER DEV SCALE TEST RESULTS: ${DEV_STATS}\`\`\`"
|
||||||
|
curl -X POST -H 'Content-type: application/json' --data "{'text':'${DEV_MSG}'}" $SLACK_WEBHOOK
|
||||||
|
|
||||||
|
|
||||||
+2
-1
@@ -4,7 +4,8 @@ ENV LC_ALL=C.UTF-8
|
|||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y build-essential libxml2-dev python3-dev python3-pip zlib1g-dev python3-requests && \
|
apt-get install -y build-essential libxml2-dev python3-dev python3-pip zlib1g-dev python3-requests python3-aiohttp && \
|
||||||
|
python3 -m pip install --upgrade pip && \
|
||||||
pip3 install cellxgene
|
pip3 install cellxgene
|
||||||
|
|
||||||
ENTRYPOINT ["cellxgene"]
|
ENTRYPOINT ["cellxgene"]
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013
|
Copyright (c) 2017-2020 Chan Zuckerberg Initiative
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
@@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|||||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ recursive-include server/common/web/static *
|
|||||||
|
|
||||||
include server/requirements.txt
|
include server/requirements.txt
|
||||||
include server/requirements-prepare.txt
|
include server/requirements-prepare.txt
|
||||||
|
include server/converters/schema/hgnc_complete_set.txt.gz
|
||||||
|
include server/converters/schema/schema_definitions/*
|
||||||
|
|||||||
@@ -82,8 +82,9 @@ fmt-py:
|
|||||||
lint: lint-server lint-client
|
lint: lint-server lint-client
|
||||||
|
|
||||||
.PHONY: lint-server
|
.PHONY: lint-server
|
||||||
lint-server:
|
lint-server: fmt-py
|
||||||
flake8 server
|
flake8 server --per-file-ignores='server/test/fixtures/dataset_config_outline.py:F821 server/test/fixtures/server_config_outline.py:F821 server/test/performance/scale_test_annotations.py:E501'
|
||||||
|
|
||||||
|
|
||||||
.PHONY: lint-client
|
.PHONY: lint-client
|
||||||
lint-client:
|
lint-client:
|
||||||
@@ -99,22 +100,32 @@ pydist: build
|
|||||||
|
|
||||||
# RELEASE HELPERS
|
# RELEASE HELPERS
|
||||||
|
|
||||||
# create new version to commit to main
|
# Create new version to commit to main
|
||||||
.PHONY: release-stage-1
|
.PHONY: create-release-candidate
|
||||||
release-stage-1: dev-env bump clean-lite gen-package-lock
|
create-release-candidate: dev-env bump-version clean-lite gen-package-lock
|
||||||
@echo "Version bumped part:$(PART) and client built. Ready to commit and push"
|
@echo "Version bumped part:$(PART) and client built. Ready to commit and push"
|
||||||
|
|
||||||
# build dist and release to dev pypi
|
# Bump the release candidate version if needed (i.e. the previous release candidate had errors).
|
||||||
.PHONY: release-stage-2
|
.PHONY: recreate-release-candidate
|
||||||
release-stage-2: dev-env pydist twine
|
recreate-release-candidate: dev-env bump-release-candidate clean-lite gen-package-lock
|
||||||
|
@echo "Version bumped part:$(PART) and client built. Ready to commit and push"
|
||||||
|
|
||||||
|
# Build dist and release to Test PyPI
|
||||||
|
.PHONY: release-candidate-to-test-pypi
|
||||||
|
release-candidate-to-test-pypi: dev-env pydist twine
|
||||||
@echo "Dist built and uploaded to test.pypi.org"
|
@echo "Dist built and uploaded to test.pypi.org"
|
||||||
@echo "Test the install:"
|
@echo "Test the install:"
|
||||||
@echo " make install-release-test"
|
@echo " make install-release-test"
|
||||||
@echo "Then upload to Pypi prod:"
|
|
||||||
@echo " make twine-prod"
|
|
||||||
|
|
||||||
.PHONY: release-stage-final
|
# Build final dist (gets rid of the rc tag) and release final candidate to TestPyPI
|
||||||
release-stage-final: twine-prod
|
.PHONY: release-final-to-test-pypi
|
||||||
|
release-final-to-test-pypi: dev-env bump-release clean-lite gen-package-lock pydist twine
|
||||||
|
@echo "Final release dist built and uploaded to test.pypi.org"
|
||||||
|
@echo "Test the install:"
|
||||||
|
@echo " make install-release-test"
|
||||||
|
|
||||||
|
.PHONY: release-final
|
||||||
|
release-final: twine-prod
|
||||||
@echo "Release uploaded to pypi.org"
|
@echo "Release uploaded to pypi.org"
|
||||||
|
|
||||||
# DANGER: releases directly to prod
|
# DANGER: releases directly to prod
|
||||||
@@ -136,11 +147,22 @@ dev-env-client:
|
|||||||
dev-env-server:
|
dev-env-server:
|
||||||
pip install -r server/requirements-dev.txt
|
pip install -r server/requirements-dev.txt
|
||||||
|
|
||||||
# give PART=[major, minor, part] as param to make bump
|
# Set PART=[major, minor, patch] as param to make bump.
|
||||||
.PHONY: bump
|
# This will create a release candidate. (i.e. 0.16.1 -> 0.16.2-rc.0 for a patch bump)
|
||||||
bump:
|
.PHONY: bump-version
|
||||||
|
bump-version:
|
||||||
bumpversion --config-file .bumpversion.cfg $(PART)
|
bumpversion --config-file .bumpversion.cfg $(PART)
|
||||||
|
|
||||||
|
# Increments the release candidate version (i.e. 0.16.2-rc.1 -> 0.16.2-rc.2)
|
||||||
|
.PHONY: bump-release-candidate
|
||||||
|
bump-release-candidate:
|
||||||
|
bumpversion --config-file .bumpversion.cfg prerelversion --allow-dirty
|
||||||
|
|
||||||
|
# Finalizes the release candidate by removing the release candidate tag (i.e. 0.16.2-rc.2 -> 0.16.2).
|
||||||
|
.PHONY: bump-release
|
||||||
|
bump-release:
|
||||||
|
bumpversion --config-file .bumpversion.cfg prerel --allow-dirty
|
||||||
|
|
||||||
.PHONY: twine
|
.PHONY: twine
|
||||||
twine:
|
twine:
|
||||||
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
|
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#### Reviewers
|
||||||
|
**Functional:**
|
||||||
|
|
||||||
|
**Readability:**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
- add
|
||||||
|
- remove
|
||||||
|
- modify
|
||||||
@@ -67,37 +67,30 @@ For any errors, [report bugs on Github](https://github.com/chanzuckerberg/cellxg
|
|||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
We warmly welcome contributions from the community! Please see our [contributing guide](https://chanzuckerberg.github.io/cellxgene/posts/contribute) and don't hesitate to open an issue or send a pull request to improve cellxgene.
|
We warmly welcome contributions from the community! Please see our [contributing guide](https://chanzuckerberg.github.io/cellxgene/posts/contribute) and don't hesitate to open an issue or send a pull request to improve cellxgene. Please see the [dev_docs](https://github.com/chanzuckerberg/cellxgene/tree/main/dev_docs) for pull request suggestions, unit test details, local documentation preview, and other development specifics.
|
||||||
|
|
||||||
This project adheres to the Contributor Covenant [code of conduct](https://github.com/chanzuckerberg/.github/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to opensource@chanzuckerberg.com.
|
This project adheres to the Contributor Covenant [code of conduct](https://github.com/chanzuckerberg/.github/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to opensource@chanzuckerberg.com.
|
||||||
|
|
||||||
### Reuse
|
### Reuse
|
||||||
|
|
||||||
This project was started with the sole goal of empowering the scientific community to explore and understand their data. As such, we encourage other scientific tool builders in academia or industry to adopt the patterns, tools, and code from this project, and reach out to us with ideas or questions. All code is freely available for reuse under the [MIT license](https://opensource.org/licenses/MIT).
|
This project was started with the sole goal of empowering the scientific community to explore and understand their data.
|
||||||
|
As such, we encourage other scientific tool builders in academia or industry to adopt the patterns, tools, and code from
|
||||||
|
this project. All code is freely available for reuse under the [MIT license](https://opensource.org/licenses/MIT).
|
||||||
|
|
||||||
|
|
||||||
|
Before extending cellxgene, we encourage you to reach out to us with ideas or questions. It might be possible that an
|
||||||
|
extension could be directly contributed, which would make it available for a wider audience, or that it's on our
|
||||||
|
[roadmap](./docs/posts/roadmap.md) and under active development.
|
||||||
|
|
||||||
|
See the [cellxgene extensions](./docs/posts/extensions.md) section of our documentation for examples of community use and cellxgene extensions.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
If you believe you have found a security issue, we would appreciate notification. Please send email to <security@chanzuckerberg.com>.
|
If you believe you have found a security issue, we would appreciate notification. Please send email to <security@chanzuckerberg.com>.
|
||||||
|
|
||||||
# About
|
# Inspiration
|
||||||
|
|
||||||
### Core team
|
We've been heavily inspired by several other related single-cell visualization projects, including the [UCSC Cell Browser](http://cells.ucsc.edu/), [Cytoscape](http://www.cytoscape.org/), [Xena](https://xena.ucsc.edu/), [ASAP](https://asap.epfl.ch/), [GenePattern](http://genepattern-notebook.org/), and many others. We hope to explore collaborations where useful as this community works together on improving interactive visualization for single-cell data.
|
||||||
|
|
||||||
The current core team:
|
|
||||||
|
|
||||||
- Colin Megill, frontend & product design
|
|
||||||
- Bruce Martin, software engineer
|
|
||||||
- Sidney Bell, computational biologist
|
|
||||||
- Lia Prins, designer
|
|
||||||
- Severiano Badajoz, software engineer
|
|
||||||
|
|
||||||
We would also like to gratefully acknowledge contributions from past core team members:
|
|
||||||
|
|
||||||
- Charlotte Weaver, software engineer
|
|
||||||
|
|
||||||
### Inspiration
|
|
||||||
|
|
||||||
We've been heavily inspired by several other related single-cell visualization projects, including the [UCSC Cell Browswer](http://cells.ucsc.edu/), [Cytoscape](http://www.cytoscape.org/), [Xena](https://xena.ucsc.edu/), [ASAP](https://asap.epfl.ch/), [Gene Pattern](http://genepattern-notebook.org/), and many others. We hope to explore collaborations where useful as this community works together on improving interactive visualization for single-cell data.
|
|
||||||
|
|
||||||
We were inspired by Mike Bostock and the [crossfilter](https://github.com/crossfilter) team for the design of our filtering implementation.
|
We were inspired by Mike Bostock and the [crossfilter](https://github.com/crossfilter) team for the design of our filtering implementation.
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "cellxgene",
|
|
||||||
"description": "An interactive explorer for single-cell transcriptomics data",
|
|
||||||
"repository": "https://github.com/chanzuckerberg/cellxgene",
|
|
||||||
"logo": "https://cellxgene-example-data.czi.technology/favicon.png",
|
|
||||||
"keywords": [
|
|
||||||
"scientific",
|
|
||||||
"visualization",
|
|
||||||
"scrna-seq",
|
|
||||||
"transcriptomics",
|
|
||||||
"dataviz"
|
|
||||||
],
|
|
||||||
"buildpacks": [
|
|
||||||
{
|
|
||||||
"url": "heroku/nodejs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "heroku/python"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stack": "heroku-18",
|
|
||||||
"env": {
|
|
||||||
"DATASET": {
|
|
||||||
"description": "Link to dataset",
|
|
||||||
"value": "https://cellxgene-example-data.czi.technology/pbmc3k.h5ad",
|
|
||||||
"required": "true"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+4
-2
@@ -3,6 +3,8 @@ include ../common.mk
|
|||||||
ANNOTATIONS := $(if $(ANNOTATIONS),$(ANNOTATIONS),../server/test/fixtures/pbmc3k-annotations.csv)
|
ANNOTATIONS := $(if $(ANNOTATIONS),$(ANNOTATIONS),../server/test/fixtures/pbmc3k-annotations.csv)
|
||||||
ANNOTATIONS_FILENAME := $(shell basename $(ANNOTATIONS))
|
ANNOTATIONS_FILENAME := $(shell basename $(ANNOTATIONS))
|
||||||
|
|
||||||
|
CXG_CONFIG := $(if $(CXG_CONFIG), $(CXG_CONFIG), ./__tests__/e2e/test_config.yaml)
|
||||||
|
|
||||||
# Packaging
|
# Packaging
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@@ -31,9 +33,9 @@ start-frontend:
|
|||||||
.PHONY: smoke-test
|
.PHONY: smoke-test
|
||||||
smoke-test:
|
smoke-test:
|
||||||
start_server_and_test \
|
start_server_and_test \
|
||||||
'CXG_OPTIONS="--disable-annotations" $(MAKE) start-server' \
|
'CXG_OPTIONS="--config-file $(CXG_CONFIG)" $(MAKE) start-server' \
|
||||||
$(CXG_SERVER_PORT) \
|
$(CXG_SERVER_PORT) \
|
||||||
'CXG_URL_BASE="http://localhost:$(CXG_SERVER_PORT)" npm run e2e -- --verbose false'
|
'CXG_URL_BASE="http://localhost:$(CXG_SERVER_PORT)" CXG_AUTH_TYPE="test" npm run e2e -- --verbose false'
|
||||||
|
|
||||||
# start an instance of cellxgene and run the end-to-end annotations tests
|
# start an instance of cellxgene and run the end-to-end annotations tests
|
||||||
.PHONY: smoke-test-annotations
|
.PHONY: smoke-test-annotations
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`did launch page launched 1`] = `"<span style=\\"width: 185px; display: flex; overflow: hidden; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">pbm</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">c3k</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">c3k</span></span></span>"`;
|
exports[`did launch page launched 1`] = `"<span style=\\"max-width: 155px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">pbm</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">c3k</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">c3k</span></span></span>"`;
|
||||||
|
|
||||||
exports[`metadata loads categories and values from dataset appear 1`] = `"<div style=\\"display: flex; justify-content: space-between; align-items: baseline;\\"><div style=\\"display: flex; justify-content: flex-start; align-items: flex-start;\\"><label class=\\"bp3-control bp3-checkbox\\" for=\\"category-select-louvain\\"><input id=\\"category-select-louvain\\" data-testclass=\\"category-select\\" data-testid=\\"louvain:category-select\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span role=\\"menuitem\\" tabindex=\\"0\\" data-testclass=\\"category-expand\\" data-testid=\\"louvain:category-expand\\" style=\\"cursor: pointer;\\"><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"louvain:category-label\\" aria-label=\\"louvain\\" class=\\"\\" tabindex=\\"0\\" style=\\"max-width: 265px;\\"><span style=\\"max-width: 265px; display: flex; overflow: hidden; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">lou</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">vain</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">vain</span></span></span></span></span></span><svg stroke=\\"currentColor\\" fill=\\"currentColor\\" stroke-width=\\"0\\" viewBox=\\"0 0 320 512\\" data-testclass=\\"category-expand-is-not-expanded\\" height=\\"1em\\" width=\\"1em\\" xmlns=\\"http://www.w3.org/2000/svg\\" style=\\"font-size: 10px; margin-left: 5px;\\"><path d=\\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\\"></path></svg></span></div><div><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><a role=\\"button\\" data-testclass=\\"colorby\\" data-testid=\\"colorby-louvain\\" class=\\"bp3-button\\" tabindex=\\"0\\"><span icon=\\"tint\\" class=\\"bp3-icon bp3-icon-tint\\"><svg data-icon=\\"tint\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\"><desc>tint</desc><path d=\\"M7.88 1s-4.9 6.28-4.9 8.9c.01 2.82 2.34 5.1 4.99 5.1 2.65-.01 5.03-2.3 5.03-5.13C12.99 7.17 7.88 1 7.88 1z\\" fill-rule=\\"evenodd\\"></path></svg></span></a></span></span></div></div><div style=\\"margin-left: 26px;\\"></div><div></div>"`;
|
exports[`metadata loads categories and values from dataset appear 1`] = `"<div style=\\"display: flex; justify-content: space-between; align-items: baseline;\\"><div style=\\"display: flex; justify-content: flex-start; align-items: flex-start;\\"><label class=\\"bp3-control bp3-checkbox\\" for=\\"category-select-louvain\\"><input id=\\"category-select-louvain\\" data-testclass=\\"category-select\\" data-testid=\\"louvain:category-select\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span role=\\"menuitem\\" tabindex=\\"0\\" data-testclass=\\"category-expand\\" data-testid=\\"louvain:category-expand\\" style=\\"cursor: pointer;\\"><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\" style=\\"max-width: 265px;\\"><span data-testid=\\"louvain:category-label\\" aria-label=\\"louvain\\" class=\\"\\" tabindex=\\"0\\" style=\\"max-width: 265px;\\"><span style=\\"max-width: 265px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">lou</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">vain</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">vain</span></span></span></span></span></span><svg stroke=\\"currentColor\\" fill=\\"currentColor\\" stroke-width=\\"0\\" viewBox=\\"0 0 320 512\\" data-testclass=\\"category-expand-is-not-expanded\\" height=\\"1em\\" width=\\"1em\\" xmlns=\\"http://www.w3.org/2000/svg\\" style=\\"font-size: 10px; margin-left: 5px;\\"><path d=\\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\\"></path></svg></span></div><div><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><a role=\\"button\\" data-testclass=\\"colorby\\" data-testid=\\"colorby-louvain\\" class=\\"bp3-button\\" tabindex=\\"0\\"><span icon=\\"tint\\" class=\\"bp3-icon bp3-icon-tint\\"><svg data-icon=\\"tint\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\"><desc>tint</desc><path d=\\"M7.88 1s-4.9 6.28-4.9 8.9c.01 2.82 2.34 5.1 4.99 5.1 2.65-.01 5.03-2.3 5.03-5.13C12.99 7.17 7.88 1 7.88 1z\\" fill-rule=\\"evenodd\\"></path></svg></span></a></span></span></div></div><div style=\\"margin-left: 26px;\\"></div><div></div>"`;
|
||||||
|
|||||||
@@ -2,22 +2,22 @@
|
|||||||
|
|
||||||
exports[`annotations stacked bar graph renders 1`] = `
|
exports[`annotations stacked bar graph renders 1`] = `
|
||||||
Array [
|
Array [
|
||||||
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||||
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2133</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2133</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`annotations stacked bar graph renders 2`] = `
|
exports[`annotations stacked bar graph renders 2`] = `
|
||||||
Array [
|
Array [
|
||||||
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||||
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas class=\\"bp3-popover-targer\\" width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
"<div class=\\"categorical__value___2RKaC\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" aria-label=\\"unassigned\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 11px; height: 11px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`annotations truncate midpoint whitespace 1`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-123 456\\" data-testclass=\\"categorical-value\\" aria-label=\\"123 456\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 187px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">123</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"> 456</span><span style=\\"position: absolute; right: 0px; color: black;\\"> 456</span></span></span></span>"`;
|
exports[`annotations truncate midpoint whitespace 1`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-123 456\\" data-testclass=\\"categorical-value\\" aria-label=\\"123 456\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">123</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"> 456</span><span style=\\"position: absolute; right: 0px; color: black;\\"> 456</span></span></span></span>"`;
|
||||||
|
|
||||||
exports[`annotations truncate midpoint whitespace 2`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-123 456\\" data-testclass=\\"categorical-value\\" aria-label=\\"123 456\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 187px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">123</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"> 456</span><span style=\\"position: absolute; right: 0px; color: black;\\"> 456</span></span></span></span>"`;
|
exports[`annotations truncate midpoint whitespace 2`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-123 456\\" data-testclass=\\"categorical-value\\" aria-label=\\"123 456\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">123</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"> 456</span><span style=\\"position: absolute; right: 0px; color: black;\\"> 456</span></span></span></span>"`;
|
||||||
|
|
||||||
exports[`annotations truncate single character 1`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-T\\" data-testclass=\\"categorical-value\\" aria-label=\\"T\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 187px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">T</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"></span><span style=\\"position: absolute; right: 0px; color: black;\\"></span></span></span></span>"`;
|
exports[`annotations truncate single character 1`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-T\\" data-testclass=\\"categorical-value\\" aria-label=\\"T\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">T</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"></span><span style=\\"position: absolute; right: 0px; color: black;\\"></span></span></span></span>"`;
|
||||||
|
|
||||||
exports[`annotations truncate single character 2`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-T\\" data-testclass=\\"categorical-value\\" aria-label=\\"T\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 187px; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">T</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"></span><span style=\\"position: absolute; right: 0px; color: black;\\"></span></span></span></span>"`;
|
exports[`annotations truncate single character 2`] = `"<span data-testid=\\"categorical-value-TEST-CATEGORY-T\\" data-testclass=\\"categorical-value\\" aria-label=\\"T\\" class=\\"\\" tabindex=\\"0\\" style=\\"width: 187px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">T</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\"></span><span style=\\"position: absolute; right: 0px; color: black;\\"></span></span></span></span>"`;
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ import {
|
|||||||
getTestClass,
|
getTestClass,
|
||||||
getTestId,
|
getTestId,
|
||||||
isElementPresent,
|
isElementPresent,
|
||||||
|
goToPage,
|
||||||
} from "./puppeteerUtils";
|
} from "./puppeteerUtils";
|
||||||
|
|
||||||
|
import { appUrlBase } from "./config";
|
||||||
|
|
||||||
export async function drag(testId, start, end, lasso = false) {
|
export async function drag(testId, start, end, lasso = false) {
|
||||||
const layout = await waitByID(testId);
|
const layout = await waitByID(testId);
|
||||||
const elBox = await layout.boxModel();
|
const elBox = await layout.boxModel();
|
||||||
@@ -312,4 +315,74 @@ export async function assertCategoryDoesNotExist(categoryName) {
|
|||||||
|
|
||||||
await expect(result).toBe(false);
|
await expect(result).toBe(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function login() {
|
||||||
|
const email = `cellxgene-smoke-test+${process.env.DEPLOYMENT_STAGE}@chanzuckerberg.com`;
|
||||||
|
const password = "Test1111";
|
||||||
|
|
||||||
|
await goToPage(appUrlBase);
|
||||||
|
|
||||||
|
await clickOn("log-in");
|
||||||
|
|
||||||
|
// (thuang): Auth0 form is unstable and unsafe for input until verified
|
||||||
|
await waitUntilFormFieldStable('[name="email"]');
|
||||||
|
|
||||||
|
await expect(page).toFillForm("form", {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({ waitUntil: "networkidle0" }),
|
||||||
|
expect(page).toClick('[name="submit"]'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(page.url()).toContain(appUrlBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logout() {
|
||||||
|
await clickOnUntil("user-info", async () => {
|
||||||
|
await waitByID("log-out");
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({ waitUntil: "networkidle0" }),
|
||||||
|
clickOn("log-out"),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitByID("log-in");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitUntilFormFieldStable(selector) {
|
||||||
|
const MAX_RETRY = 10;
|
||||||
|
const WAIT_FOR_MS = 200;
|
||||||
|
|
||||||
|
const EXPECTED_VALUE = "aaa";
|
||||||
|
|
||||||
|
let retry = 0;
|
||||||
|
|
||||||
|
while (retry < MAX_RETRY) {
|
||||||
|
try {
|
||||||
|
await expect(page).toFill(selector, EXPECTED_VALUE);
|
||||||
|
|
||||||
|
const fieldHandle = await expect(page).toMatchElement(selector);
|
||||||
|
|
||||||
|
const fieldValue = await page.evaluate(
|
||||||
|
(input) => input.value,
|
||||||
|
fieldHandle
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fieldValue).toBe(EXPECTED_VALUE);
|
||||||
|
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
retry += 1;
|
||||||
|
|
||||||
|
await page.waitFor(WAIT_FOR_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retry === MAX_RETRY) {
|
||||||
|
throw Error("clickOnUntil() assertion failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
|
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
goToPage,
|
goToPage,
|
||||||
typeInto,
|
typeInto,
|
||||||
waitByID,
|
waitByID,
|
||||||
|
clickOnUntil,
|
||||||
} from "./puppeteerUtils";
|
} from "./puppeteerUtils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -31,6 +32,8 @@ import {
|
|||||||
runDiffExp,
|
runDiffExp,
|
||||||
selectCategory,
|
selectCategory,
|
||||||
subset,
|
subset,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
} from "./cellxgeneActions";
|
} from "./cellxgeneActions";
|
||||||
|
|
||||||
const data = datasets[DATASET];
|
const data = datasets[DATASET];
|
||||||
@@ -518,4 +521,34 @@ test("lasso moves after pan", async () => {
|
|||||||
|
|
||||||
expect(panCount).toBe(initialCount);
|
expect(panCount).toBe(initialCount);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const describeIfCalledByMakeFileTarget =
|
||||||
|
process.env.CXG_AUTH_TYPE?.toLowerCase() === "test"
|
||||||
|
? describe
|
||||||
|
: describe.skip;
|
||||||
|
|
||||||
|
describeIfCalledByMakeFileTarget("auth buttons", () => {
|
||||||
|
test("login then logout", async () => {
|
||||||
|
await goToPage(appUrlBase);
|
||||||
|
await clickOnUntil("log-in", async () => {
|
||||||
|
await page.waitForNavigation({ waitUntil: "networkidle0" });
|
||||||
|
await waitByID("user-info");
|
||||||
|
});
|
||||||
|
await logout();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const conditionalDescribe =
|
||||||
|
process.env.TEST_AUTH_INTEGRATION === "true" ? describe : describe.skip;
|
||||||
|
|
||||||
|
conditionalDescribe("AuthN Integration", () => {
|
||||||
|
it("logs in", async () => {
|
||||||
|
await login();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs out", async () => {
|
||||||
|
await login();
|
||||||
|
await logout();
|
||||||
|
});
|
||||||
|
});
|
||||||
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
|
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ setDefaultOptions({ timeout: 20 * 1000 });
|
|||||||
|
|
||||||
jest.retryTimes(ENV_DEFAULT.RETRY_ATTEMPTS);
|
jest.retryTimes(ENV_DEFAULT.RETRY_ATTEMPTS);
|
||||||
|
|
||||||
(async () => {
|
beforeEach(async () => {
|
||||||
|
await jestPuppeteer.resetBrowser();
|
||||||
|
|
||||||
const userAgent = await browser.userAgent();
|
const userAgent = await browser.userAgent();
|
||||||
await page.setUserAgent(`${userAgent}bot`);
|
await page.setUserAgent(`${userAgent}bot`);
|
||||||
|
|
||||||
@@ -53,6 +55,4 @@ jest.retryTimes(ENV_DEFAULT.RETRY_ATTEMPTS);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})().catch((error) => {
|
|
||||||
console.error("puppeteer.setup.js error", error);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
server:
|
||||||
|
app:
|
||||||
|
force_https: true
|
||||||
|
|
||||||
|
# By default, cellxgene will serve api requests from the same base url as the webpage.
|
||||||
|
# In general api_base_url and web_base_url will not need to be set.
|
||||||
|
# There are two reasons to set these parameters:
|
||||||
|
# 1. Oauth authentication is used; the oauth server will redirect back to the api_base_url after login,
|
||||||
|
# which then redirects back to the web_base_url. If the web_base_url is not set, it will default to
|
||||||
|
# the api_base_url. If oauth authentication is used, the api_base_url must be set.
|
||||||
|
# For a local test (where the server runs on "http://localhost:<port>"), then the api_base_url may be
|
||||||
|
# set to the string "local".
|
||||||
|
# 2. The cellxgene deploymnent is in an environment where the webpage and api have
|
||||||
|
# different base urls. In this case both api_base_url and web_base_url must be set.
|
||||||
|
# It is up to the server admin to ensure that the networking is setup correctly for this environment.
|
||||||
|
api_base_url: http://localhost:5005
|
||||||
|
web_base_url: http://localhost:3000
|
||||||
|
|
||||||
|
authentication:
|
||||||
|
# The authentication types may be "none", "session", "oauth"
|
||||||
|
# none: No authentication support, features like user_annotations must not be enabled.
|
||||||
|
# session: A session based userid is automatically generated. (no params needed)
|
||||||
|
# oauth: oauth2 is used for authentication; parameters are defined in params_oauth.
|
||||||
|
type: test
|
||||||
|
|
||||||
|
dataset:
|
||||||
|
app:
|
||||||
|
about_legal_tos: null
|
||||||
|
about_legal_privacy: null
|
||||||
|
|
||||||
|
presentation:
|
||||||
|
max_categories: 1000
|
||||||
|
custom_colors: true
|
||||||
|
|
||||||
|
user_annotations:
|
||||||
|
enable: false
|
||||||
|
type: local_file_csv
|
||||||
|
local_file_csv:
|
||||||
|
directory: null
|
||||||
|
file: null
|
||||||
|
ontology:
|
||||||
|
enable: false
|
||||||
|
obo_location: null
|
||||||
|
|
||||||
|
embeddings:
|
||||||
|
names: []
|
||||||
|
enable_reembedding: false
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/* eslint-disable no-bitwise -- unsigned right shift better than Math.round */
|
||||||
|
|
||||||
|
/*
|
||||||
|
test color helpers
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
createColorTable,
|
||||||
|
loadUserColorConfig,
|
||||||
|
} from "../../../src/util/stateManager/colorHelpers";
|
||||||
|
import * as Dataframe from "../../../src/util/dataframe";
|
||||||
|
|
||||||
|
describe("categorical color helpers", () => {
|
||||||
|
/*
|
||||||
|
Primary test constraint for categorical colors is that they are ordered/identified
|
||||||
|
by schema order, NOT by value. Ie,
|
||||||
|
|
||||||
|
scale(schemaIndex) should match rgb[obsOffset]
|
||||||
|
*/
|
||||||
|
|
||||||
|
const schema = indexSchema({
|
||||||
|
annotations: {
|
||||||
|
obs: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: "name_0",
|
||||||
|
type: "string",
|
||||||
|
writable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "continuousColumn",
|
||||||
|
type: "float32",
|
||||||
|
writable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categories: [
|
||||||
|
"CD4 T cells",
|
||||||
|
"CD14+ Monocytes",
|
||||||
|
"B cells",
|
||||||
|
"CD8 T cells",
|
||||||
|
"NK cells",
|
||||||
|
"FCGR3A+ Monocytes",
|
||||||
|
"Dendritic cells",
|
||||||
|
"Megakaryocytes",
|
||||||
|
],
|
||||||
|
name: "categoricalColumn",
|
||||||
|
type: "categorical",
|
||||||
|
writable: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
index: "name_0",
|
||||||
|
},
|
||||||
|
var: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: "name_0",
|
||||||
|
type: "string",
|
||||||
|
writable: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
index: "name_0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataframe: {
|
||||||
|
nObs: 2638,
|
||||||
|
nVar: 1838,
|
||||||
|
type: "float32",
|
||||||
|
},
|
||||||
|
layout: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const catColCategories = schema.annotations.obs.columns[2].categories;
|
||||||
|
const obsDataframe = new Dataframe.Dataframe(
|
||||||
|
[schema.dataframe.nObs, 2],
|
||||||
|
[
|
||||||
|
new Float32Array(schema.dataframe.nObs).map(() => Math.random()),
|
||||||
|
new Array(schema.dataframe.nObs)
|
||||||
|
.fill("")
|
||||||
|
.map(
|
||||||
|
() =>
|
||||||
|
catColCategories[(Math.random() * catColCategories.length) >>> 0]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
null,
|
||||||
|
new Dataframe.KeyIndex(["continuousColumn", "categoricalColumn"])
|
||||||
|
);
|
||||||
|
|
||||||
|
test("default category order", () => {
|
||||||
|
const ct = createColorTable(
|
||||||
|
"color by categorical metadata",
|
||||||
|
"categoricalColumn",
|
||||||
|
obsDataframe,
|
||||||
|
schema
|
||||||
|
);
|
||||||
|
expect(ct).toBeDefined();
|
||||||
|
const data = obsDataframe.col("categoricalColumn").asArray();
|
||||||
|
const cats = schema.annotations.obsByName.categoricalColumn.categories;
|
||||||
|
for (let i = 0; i < schema.dataframe.nObs; i += 1) {
|
||||||
|
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shuffle category order", () => {
|
||||||
|
const schemaClone = indexSchema(JSON.parse(JSON.stringify(schema)));
|
||||||
|
shuffle(schemaClone.annotations.obsByName.categoricalColumn.categories);
|
||||||
|
const ct = createColorTable(
|
||||||
|
"color by categorical metadata",
|
||||||
|
"categoricalColumn",
|
||||||
|
obsDataframe,
|
||||||
|
schemaClone
|
||||||
|
);
|
||||||
|
expect(ct).toBeDefined();
|
||||||
|
const data = obsDataframe.col("categoricalColumn").asArray();
|
||||||
|
const cats = schemaClone.annotations.obsByName.categoricalColumn.categories;
|
||||||
|
for (let i = 0; i < schemaClone.dataframe.nObs; i += 1) {
|
||||||
|
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user defined color order", () => {
|
||||||
|
const cats = schema.annotations.obsByName.categoricalColumn.categories;
|
||||||
|
const shuffleCats = shuffle(
|
||||||
|
Array.from(schema.annotations.obsByName.categoricalColumn.categories)
|
||||||
|
);
|
||||||
|
const userDefinedColorTable = {
|
||||||
|
categoricalColumn: shuffleCats.reduce((acc, label) => {
|
||||||
|
acc[label] = randRGBColor();
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const userColors = loadUserColorConfig(userDefinedColorTable);
|
||||||
|
expect(userColors).toBeDefined();
|
||||||
|
|
||||||
|
const ct = createColorTable(
|
||||||
|
"color by categorical metadata",
|
||||||
|
"categoricalColumn",
|
||||||
|
obsDataframe,
|
||||||
|
schema,
|
||||||
|
userColors
|
||||||
|
);
|
||||||
|
expect(ct).toBeDefined();
|
||||||
|
const data = obsDataframe.col("categoricalColumn").asArray();
|
||||||
|
for (let i = 0; i < schema.dataframe.nObs; i += 1) {
|
||||||
|
expect(makeScale(ct.rgb[i])).toEqual(
|
||||||
|
ct.scale(cats.indexOf(data[i])).toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO:
|
||||||
|
1. mix up category order in schema to make sure it works with varied order
|
||||||
|
2. user defined colors
|
||||||
|
*/
|
||||||
|
|
||||||
|
function indexSchema(schema) {
|
||||||
|
schema.annotations.obsByName = Object.fromEntries(
|
||||||
|
schema.annotations?.obs?.columns?.map((v) => [v.name, v]) ?? []
|
||||||
|
);
|
||||||
|
schema.annotations.varByName = Object.fromEntries(
|
||||||
|
schema.annotations?.var?.columns?.map((v) => [v.name, v]) ?? []
|
||||||
|
);
|
||||||
|
schema.layout.obsByName = Object.fromEntries(
|
||||||
|
schema.layout?.obs?.map((v) => [v.name, v]) ?? []
|
||||||
|
);
|
||||||
|
schema.layout.varByName = Object.fromEntries(
|
||||||
|
schema.layout?.var?.map((v) => [v.name, v]) ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeScale(rgb) {
|
||||||
|
// make a scale string from a rgb float triple
|
||||||
|
return `rgb(${(rgb[0] * 255) >>> 0}, ${(rgb[1] * 255) >>> 0}, ${
|
||||||
|
(rgb[2] * 256) >>> 0
|
||||||
|
})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shuffle(array) {
|
||||||
|
for (let i = array.length - 1; i > 0; i -= 1) {
|
||||||
|
const j = (Math.random() * (i + 1)) >>> 0;
|
||||||
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randHexColor() {
|
||||||
|
const hex = ((Math.random() * 255) >>> 0).toString(16);
|
||||||
|
return `0${hex}`.slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function randRGBColor() {
|
||||||
|
return `#${randHexColor()}${randHexColor()}${randHexColor()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-enable no-bitwise -- unsigned right shift better than Math.round */
|
||||||
@@ -4,6 +4,7 @@ module.exports = {
|
|||||||
extends: [
|
extends: [
|
||||||
"airbnb",
|
"airbnb",
|
||||||
"plugin:eslint-comments/recommended",
|
"plugin:eslint-comments/recommended",
|
||||||
|
"plugin:@blueprintjs/recommended",
|
||||||
"plugin:compat/recommended",
|
"plugin:compat/recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
"prettier/react",
|
"prettier/react",
|
||||||
@@ -39,6 +40,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"react/jsx-no-target-blank": "off",
|
||||||
"eslint-comments/require-description": ["error"],
|
"eslint-comments/require-description": ["error"],
|
||||||
"no-magic-numbers": "off",
|
"no-magic-numbers": "off",
|
||||||
"no-nested-ternary": "off",
|
"no-nested-ternary": "off",
|
||||||
@@ -64,6 +66,12 @@ module.exports = {
|
|||||||
"LabeledStatement",
|
"LabeledStatement",
|
||||||
"WithStatement",
|
"WithStatement",
|
||||||
],
|
],
|
||||||
|
"import/no-extraneous-dependencies": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
devDependencies: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/edge.png"
|
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/edge.png"
|
||||||
style="width: 80px; height: 80px;"
|
style="width: 80px; height: 80px;"
|
||||||
/>
|
/>
|
||||||
<div>Edge ≥ 15</div>
|
<div>Edge ≥ 79</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,7 +31,11 @@ const devConfig = {
|
|||||||
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2|otf)$/i,
|
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2|otf)$/i,
|
||||||
loader: "file-loader",
|
loader: "file-loader",
|
||||||
include: [nodeModules, fonts],
|
include: [nodeModules, fonts],
|
||||||
query: { name: "static/assets/[name].[ext]" },
|
query: {
|
||||||
|
name: "static/assets/[name].[ext]",
|
||||||
|
// (thuang): This is needed to make sure @font url path is '/static/assets/'
|
||||||
|
publicPath: "/",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,7 +45,11 @@ const prodConfig = {
|
|||||||
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2|otf)$/i,
|
test: /\.(jpg|png|gif|eot|svg|ttf|woff|woff2|otf)$/i,
|
||||||
loader: "file-loader",
|
loader: "file-loader",
|
||||||
include: [nodeModules, fonts],
|
include: [nodeModules, fonts],
|
||||||
query: { name: "static/assets/[name]-[contenthash].[ext]" },
|
query: {
|
||||||
|
name: "static/assets/[name]-[contenthash].[ext]",
|
||||||
|
// (thuang): This is needed to make sure @font url path is '../static/assets/'
|
||||||
|
publicPath: "static/",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
|
|||||||
const src = path.resolve("src");
|
const src = path.resolve("src");
|
||||||
const nodeModules = path.resolve("node_modules");
|
const nodeModules = path.resolve("node_modules");
|
||||||
|
|
||||||
const publicPath = "/";
|
const publicPath = "";
|
||||||
|
|
||||||
const rawObsoleteHTMLTemplate = fs.readFileSync(
|
const rawObsoleteHTMLTemplate = fs.readFileSync(
|
||||||
`${__dirname}/obsoleteHTMLTemplate.html`,
|
`${__dirname}/obsoleteHTMLTemplate.html`,
|
||||||
|
|||||||
Generated
+473
-62
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cellxgene",
|
"name": "cellxgene",
|
||||||
"version": "0.16.0",
|
"version": "0.16.7",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4156,6 +4156,216 @@
|
|||||||
"tslib": "~1.10.0"
|
"tslib": "~1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@blueprintjs/eslint-plugin": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@blueprintjs/eslint-plugin/-/eslint-plugin-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-bQEdE4ApEHxCDV8hT9uIxeRbDFKOtRLBT3/Zy3Ku+nowDAYl/8jwZKp6lJuR/nqvsfuIXTnVef6ivwdBEieQfA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@typescript-eslint/experimental-utils": "^4.2.0",
|
||||||
|
"eslint": "^7.9.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/experimental-utils": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-cmmIK8shn3mxmhpKfzMMywqiEheyfXLV/+yPDnOTvQX/ztngx7Lg/OD26J8gTZfkLKUmaEBxO2jYP3keV7h2OQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/json-schema": "^7.0.3",
|
||||||
|
"@typescript-eslint/scope-manager": "4.3.0",
|
||||||
|
"@typescript-eslint/types": "4.3.0",
|
||||||
|
"@typescript-eslint/typescript-estree": "4.3.0",
|
||||||
|
"eslint-scope": "^5.0.0",
|
||||||
|
"eslint-utils": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@typescript-eslint/typescript-estree": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-ZAI7xjkl+oFdLV/COEz2tAbQbR3XfgqHEGy0rlUXzfGQic6EBCR4s2+WS3cmTPG69aaZckEucBoTxW9PhzHxxw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@typescript-eslint/types": "4.3.0",
|
||||||
|
"@typescript-eslint/visitor-keys": "4.3.0",
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"globby": "^11.0.1",
|
||||||
|
"is-glob": "^4.0.1",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"semver": "^7.3.2",
|
||||||
|
"tsutils": "^3.17.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acorn": {
|
||||||
|
"version": "7.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
|
||||||
|
"integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"cross-spawn": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"path-key": "^3.1.0",
|
||||||
|
"shebang-command": "^2.0.0",
|
||||||
|
"which": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslint": {
|
||||||
|
"version": "7.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.10.0.tgz",
|
||||||
|
"integrity": "sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/code-frame": "^7.0.0",
|
||||||
|
"@eslint/eslintrc": "^0.1.3",
|
||||||
|
"ajv": "^6.10.0",
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"cross-spawn": "^7.0.2",
|
||||||
|
"debug": "^4.0.1",
|
||||||
|
"doctrine": "^3.0.0",
|
||||||
|
"enquirer": "^2.3.5",
|
||||||
|
"eslint-scope": "^5.1.1",
|
||||||
|
"eslint-utils": "^2.1.0",
|
||||||
|
"eslint-visitor-keys": "^1.3.0",
|
||||||
|
"espree": "^7.3.0",
|
||||||
|
"esquery": "^1.2.0",
|
||||||
|
"esutils": "^2.0.2",
|
||||||
|
"file-entry-cache": "^5.0.1",
|
||||||
|
"functional-red-black-tree": "^1.0.1",
|
||||||
|
"glob-parent": "^5.0.0",
|
||||||
|
"globals": "^12.1.0",
|
||||||
|
"ignore": "^4.0.6",
|
||||||
|
"import-fresh": "^3.0.0",
|
||||||
|
"imurmurhash": "^0.1.4",
|
||||||
|
"is-glob": "^4.0.0",
|
||||||
|
"js-yaml": "^3.13.1",
|
||||||
|
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||||
|
"levn": "^0.4.1",
|
||||||
|
"lodash": "^4.17.19",
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
|
"natural-compare": "^1.4.0",
|
||||||
|
"optionator": "^0.9.1",
|
||||||
|
"progress": "^2.0.0",
|
||||||
|
"regexpp": "^3.1.0",
|
||||||
|
"semver": "^7.2.1",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"strip-json-comments": "^3.1.0",
|
||||||
|
"table": "^5.2.3",
|
||||||
|
"text-table": "^0.2.0",
|
||||||
|
"v8-compile-cache": "^2.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint-scope": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esrecurse": "^4.3.0",
|
||||||
|
"estraverse": "^4.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslint-visitor-keys": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"espree": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"acorn": "^7.4.0",
|
||||||
|
"acorn-jsx": "^5.2.0",
|
||||||
|
"eslint-visitor-keys": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"esrecurse": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"estraverse": "^5.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"estraverse": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"glob-parent": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-glob": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"version": "12.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
|
||||||
|
"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"type-fest": "^0.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ignore": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"path-key": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "7.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||||
|
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"shebang-command": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"shebang-regex": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shebang-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"which": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@blueprintjs/icons": {
|
"@blueprintjs/icons": {
|
||||||
"version": "3.19.0",
|
"version": "3.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-3.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-3.19.0.tgz",
|
||||||
@@ -4184,6 +4394,76 @@
|
|||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@eslint/eslintrc": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ajv": "^6.12.4",
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"espree": "^7.3.0",
|
||||||
|
"globals": "^12.1.0",
|
||||||
|
"ignore": "^4.0.6",
|
||||||
|
"import-fresh": "^3.2.1",
|
||||||
|
"js-yaml": "^3.13.1",
|
||||||
|
"lodash": "^4.17.19",
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
|
"strip-json-comments": "^3.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": {
|
||||||
|
"version": "7.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
|
||||||
|
"integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"ajv": {
|
||||||
|
"version": "6.12.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz",
|
||||||
|
"integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fast-deep-equal": "^3.1.1",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"json-schema-traverse": "^0.4.1",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslint-visitor-keys": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"espree": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"acorn": "^7.4.0",
|
||||||
|
"acorn-jsx": "^5.2.0",
|
||||||
|
"eslint-visitor-keys": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"version": "12.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
|
||||||
|
"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"type-fest": "^0.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ignore": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@hapi/address": {
|
"@hapi/address": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
||||||
@@ -5137,6 +5417,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nodelib/fs.scandir": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@nodelib/fs.stat": "2.0.3",
|
||||||
|
"run-parallel": "^1.1.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nodelib/fs.stat": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@nodelib/fs.walk": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@nodelib/fs.scandir": "2.1.3",
|
||||||
|
"fastq": "^1.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@npmcli/move-file": {
|
"@npmcli/move-file": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz",
|
||||||
@@ -5489,6 +5795,22 @@
|
|||||||
"eslint-utils": "^2.0.0"
|
"eslint-utils": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@typescript-eslint/scope-manager": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-cTeyP5SCNE8QBRfc+Lgh4Xpzje46kNUhXYfc3pQWmJif92sjrFuHT9hH4rtOkDTo/si9Klw53yIr+djqGZS1ig==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@typescript-eslint/types": "4.3.0",
|
||||||
|
"@typescript-eslint/visitor-keys": "4.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@typescript-eslint/types": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-Cx9TpRvlRjOppGsU6Y6KcJnUDOelja2NNCX6AZwtVHRzaJkdytJWMuYiqi8mS35MRNA3cJSwDzXePfmhU6TANw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@typescript-eslint/typescript-estree": {
|
"@typescript-eslint/typescript-estree": {
|
||||||
"version": "2.34.0",
|
"version": "2.34.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz",
|
||||||
@@ -5512,6 +5834,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@typescript-eslint/visitor-keys": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-xZxkuR7XLM6RhvLkgv9yYlTcBHnTULzfnw4i6+z2TGBLy9yljAypQaZl9c3zFvy7PNI7fYWyvKYtohyF8au3cw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@typescript-eslint/types": "4.3.0",
|
||||||
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint-visitor-keys": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@webassemblyjs/ast": {
|
"@webassemblyjs/ast": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||||
@@ -6387,9 +6727,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bl": {
|
"bl": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
|
||||||
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
|
"integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"buffer": "^5.5.0",
|
"buffer": "^5.5.0",
|
||||||
@@ -6397,16 +6737,6 @@
|
|||||||
"readable-stream": "^3.4.0"
|
"readable-stream": "^3.4.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer": {
|
|
||||||
"version": "5.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz",
|
|
||||||
"integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"base64-js": "^1.0.2",
|
|
||||||
"ieee754": "^1.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||||
@@ -7633,6 +7963,15 @@
|
|||||||
"xdg-basedir": "^3.0.0"
|
"xdg-basedir": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"dot-prop": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-obj": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"make-dir": {
|
"make-dir": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
|
||||||
@@ -8796,6 +9135,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dir-glob": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"path-type": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"doctrine": {
|
"doctrine": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||||
@@ -8935,12 +9283,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dot-prop": {
|
"dot-prop": {
|
||||||
"version": "4.2.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
|
||||||
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
|
"integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-obj": "^1.0.0"
|
"is-obj": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"is-obj": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"duplexer3": {
|
"duplexer3": {
|
||||||
@@ -10187,6 +10543,31 @@
|
|||||||
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
|
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"fast-glob": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@nodelib/fs.stat": "^2.0.2",
|
||||||
|
"@nodelib/fs.walk": "^1.2.3",
|
||||||
|
"glob-parent": "^5.1.0",
|
||||||
|
"merge2": "^1.3.0",
|
||||||
|
"micromatch": "^4.0.2",
|
||||||
|
"picomatch": "^2.2.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"glob-parent": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-glob": "^4.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"fast-json-stable-stringify": {
|
"fast-json-stable-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||||
@@ -10202,6 +10583,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz",
|
"resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz",
|
||||||
"integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw=="
|
"integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw=="
|
||||||
},
|
},
|
||||||
|
"fastq": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"reusify": "^1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"favicons": {
|
"favicons": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/favicons/-/favicons-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/favicons/-/favicons-5.5.0.tgz",
|
||||||
@@ -11159,6 +11549,28 @@
|
|||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
|
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
|
||||||
},
|
},
|
||||||
|
"globby": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"array-union": "^2.1.0",
|
||||||
|
"dir-glob": "^3.0.1",
|
||||||
|
"fast-glob": "^3.1.1",
|
||||||
|
"ignore": "^5.1.4",
|
||||||
|
"merge2": "^1.3.0",
|
||||||
|
"slash": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"array-union": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"got": {
|
"got": {
|
||||||
"version": "6.7.1",
|
"version": "6.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
|
||||||
@@ -11691,6 +12103,12 @@
|
|||||||
"integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
|
"integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"ignore": {
|
||||||
|
"version": "5.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||||
|
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"ignore-walk": {
|
"ignore-walk": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
|
||||||
@@ -11832,9 +12250,9 @@
|
|||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz",
|
||||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
"integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"internal-slot": {
|
"internal-slot": {
|
||||||
@@ -13787,9 +14205,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.19",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||||
},
|
},
|
||||||
"lodash._reinterpolate": {
|
"lodash._reinterpolate": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@@ -14189,6 +14607,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
|
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
|
||||||
},
|
},
|
||||||
|
"merge2": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"methods": {
|
"methods": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
@@ -15059,8 +15483,7 @@
|
|||||||
"pako": {
|
"pako": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"parallel-transform": {
|
"parallel-transform": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
@@ -15251,6 +15674,12 @@
|
|||||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
|
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"path-type": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"pbkdf2": {
|
"pbkdf2": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
|
||||||
@@ -15582,20 +16011,10 @@
|
|||||||
"vendors": "^1.0.0"
|
"vendors": "^1.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dot-prop": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-obj": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-obj": {
|
"is-obj": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
||||||
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"postcss-selector-parser": {
|
"postcss-selector-parser": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
@@ -15682,20 +16101,10 @@
|
|||||||
"postcss-selector-parser": "^3.0.0"
|
"postcss-selector-parser": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dot-prop": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-obj": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-obj": {
|
"is-obj": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
||||||
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"postcss-selector-parser": {
|
"postcss-selector-parser": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
@@ -17065,6 +17474,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
|
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
|
||||||
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
|
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
|
||||||
},
|
},
|
||||||
|
"reusify": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"rgb-regex": {
|
"rgb-regex": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
|
||||||
@@ -17100,6 +17515,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
|
||||||
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA=="
|
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA=="
|
||||||
},
|
},
|
||||||
|
"run-parallel": {
|
||||||
|
"version": "1.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
|
||||||
|
"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"run-queue": {
|
"run-queue": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
|
||||||
@@ -18291,20 +18712,10 @@
|
|||||||
"postcss-selector-parser": "^3.0.0"
|
"postcss-selector-parser": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dot-prop": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-obj": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-obj": {
|
"is-obj": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
||||||
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"postcss-selector-parser": {
|
"postcss-selector-parser": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
@@ -19413,9 +19824,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"urijs": {
|
"urijs": {
|
||||||
"version": "1.19.2",
|
"version": "1.19.5",
|
||||||
"resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.5.tgz",
|
||||||
"integrity": "sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w==",
|
"integrity": "sha512-48z9VGWwdCV5KfizHsE05DWS5fhK6gFlx5MjO7xu0Krc5FGPWzjlXEVV0nPMrdVuP7xmMHiPZ2HoYZwKOFTZOg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"urix": {
|
"urix": {
|
||||||
|
|||||||
+5
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cellxgene",
|
"name": "cellxgene",
|
||||||
"version": "0.16.0",
|
"version": "0.16.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "cellxgene is a web application for the interactive exploration of single cell sequence data.",
|
"description": "cellxgene is a web application for the interactive exploration of single cell sequence data.",
|
||||||
"repository": "https://github.com/chanzuckerberg/cellxgene",
|
"repository": "https://github.com/chanzuckerberg/cellxgene",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"Safari >= 10.1",
|
"Safari >= 10.1",
|
||||||
"iOS >= 10.3",
|
"iOS >= 10.3",
|
||||||
"Firefox >= 60",
|
"Firefox >= 60",
|
||||||
"Edge >= 15",
|
"Edge >= 79",
|
||||||
"not Explorer > 0"
|
"not Explorer > 0"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -52,8 +52,9 @@
|
|||||||
"gl-matrix": "^3.3.0",
|
"gl-matrix": "^3.3.0",
|
||||||
"gl-vec3": "^1.1.3",
|
"gl-vec3": "^1.1.3",
|
||||||
"is-number": "^7.0.0",
|
"is-number": "^7.0.0",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.20",
|
||||||
"memoize-one": "^5.1.1",
|
"memoize-one": "^5.1.1",
|
||||||
|
"pako": "^1.0.11",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-async": "^10.0.1",
|
"react-async": "^10.0.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
@@ -84,6 +85,7 @@
|
|||||||
"@babel/preset-react": "^7.10.4",
|
"@babel/preset-react": "^7.10.4",
|
||||||
"@babel/register": "^7.10.5",
|
"@babel/register": "^7.10.5",
|
||||||
"@babel/runtime": "^7.10.5",
|
"@babel/runtime": "^7.10.5",
|
||||||
|
"@blueprintjs/eslint-plugin": "^0.3.0",
|
||||||
"@sentry/webpack-plugin": "^1.12.0",
|
"@sentry/webpack-plugin": "^1.12.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-jest": "^26.1.0",
|
"babel-jest": "^26.1.0",
|
||||||
|
|||||||
@@ -1,46 +1,59 @@
|
|||||||
/* eslint-disable */
|
const chalk = require("chalk");
|
||||||
// jshint esversion: 6
|
const express = require("express");
|
||||||
var path = require("path");
|
const favicon = require("serve-favicon");
|
||||||
var historyApiFallback = require("connect-history-api-fallback");
|
const webpack = require("webpack");
|
||||||
var chalk = require("chalk");
|
const devMiddleware = require("webpack-dev-middleware");
|
||||||
var express = require("express");
|
const config = require("../configuration/webpack/webpack.config.dev");
|
||||||
var favicon = require("serve-favicon");
|
const utils = require("./utils");
|
||||||
var webpack = require("webpack");
|
|
||||||
var config = require("../configuration/webpack/webpack.config.dev");
|
|
||||||
var utils = require("./utils");
|
|
||||||
|
|
||||||
process.env.NODE_ENV = "development";
|
process.env.NODE_ENV = "development";
|
||||||
|
|
||||||
const CLIENT_PORT = process.env.CXG_CLIENT_PORT;
|
const CLIENT_PORT = process.env.CXG_CLIENT_PORT;
|
||||||
|
const { CXG_SERVER_PORT } = process.env;
|
||||||
|
|
||||||
|
const API = {
|
||||||
|
prefix: `http://localhost:${CXG_SERVER_PORT}/`,
|
||||||
|
};
|
||||||
|
|
||||||
// Set up compiler
|
// Set up compiler
|
||||||
var compiler = webpack(config);
|
const compiler = webpack(config);
|
||||||
|
|
||||||
compiler.plugin("invalid", () => {
|
compiler.hooks.invalid.tap("invalid", () => {
|
||||||
utils.clearConsole();
|
utils.clearConsole();
|
||||||
console.log("Compiling...");
|
console.log("Compiling...");
|
||||||
});
|
});
|
||||||
|
|
||||||
compiler.plugin("done", (stats) => {
|
compiler.hooks.done.tap("done", (stats) => {
|
||||||
utils.formatStats(stats, CLIENT_PORT);
|
utils.formatStats(stats, CLIENT_PORT);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Launch server
|
// Launch server
|
||||||
var app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use(historyApiFallback({ verbose: false }));
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
require("webpack-dev-middleware")(compiler, {
|
devMiddleware(compiler, {
|
||||||
logLevel: "warn",
|
logLevel: "warn",
|
||||||
publicPath: config.output.publicPath,
|
publicPath: config.output.publicPath,
|
||||||
|
index: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
app.use(favicon("./favicon.png"));
|
app.use(favicon("./favicon.png"));
|
||||||
|
|
||||||
app.get("*", (req, res) => {
|
app.get("/login", async (req, res) => {
|
||||||
res.sendFile(path.resolve("index.html"));
|
try {
|
||||||
|
res.redirect(`${API.prefix}login?dataset=http://localhost:${CLIENT_PORT}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/logout", async (req, res) => {
|
||||||
|
try {
|
||||||
|
res.redirect(`${API.prefix}logout?dataset=http://localhost:${CLIENT_PORT}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(CLIENT_PORT, (err) => {
|
app.listen(CLIENT_PORT, (err) => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Action creators for user annotation
|
Action creators for user annotation
|
||||||
*/
|
*/
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import pako from "pako";
|
||||||
import * as globals from "../globals";
|
import * as globals from "../globals";
|
||||||
import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager";
|
import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager";
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@ export const annotationCreateLabelInCategory = (
|
|||||||
assignSelected
|
assignSelected
|
||||||
) => async (dispatch, getState) => {
|
) => async (dispatch, getState) => {
|
||||||
/*
|
/*
|
||||||
Add a new label to a user-defined category. If assignSelected is true, assign
|
Add a new label to a user-defined category. If assignSelected is true, assign
|
||||||
the label to all currently selected cells.
|
the label to all currently selected cells.
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
@@ -347,6 +348,7 @@ export const saveObsAnnotationsAction = () => async (dispatch, getState) => {
|
|||||||
|
|
||||||
const df = await annoMatrix.fetch("obs", writableAnnotations(annoMatrix));
|
const df = await annoMatrix.fetch("obs", writableAnnotations(annoMatrix));
|
||||||
const matrix = MatrixFBS.encodeMatrixFBS(df);
|
const matrix = MatrixFBS.encodeMatrixFBS(df);
|
||||||
|
const compressedMatrix = pako.deflate(matrix);
|
||||||
try {
|
try {
|
||||||
const queryString =
|
const queryString =
|
||||||
!dataCollectionNameIsReadOnly && !!dataCollectionName
|
!dataCollectionNameIsReadOnly && !!dataCollectionName
|
||||||
@@ -358,7 +360,7 @@ export const saveObsAnnotationsAction = () => async (dispatch, getState) => {
|
|||||||
`${globals.API.prefix}${globals.API.version}annotations/obs${queryString}`,
|
`${globals.API.prefix}${globals.API.version}annotations/obs${queryString}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: matrix,
|
body: compressedMatrix,
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
"Content-Type": "application/octet-stream",
|
"Content-Type": "application/octet-stream",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -5,20 +5,23 @@ action creators related to embeddings choice
|
|||||||
import { AnnoMatrixObsCrossfilter } from "../annoMatrix";
|
import { AnnoMatrixObsCrossfilter } from "../annoMatrix";
|
||||||
import { _setEmbeddingSubset } from "../util/stateManager/viewStackHelpers";
|
import { _setEmbeddingSubset } from "../util/stateManager/viewStackHelpers";
|
||||||
|
|
||||||
export async function _switchEmbedding(prevAnnoMatrix, newEmbeddingName) {
|
export async function _switchEmbedding(
|
||||||
|
prevAnnoMatrix,
|
||||||
|
prevCrossfilter,
|
||||||
|
newEmbeddingName
|
||||||
|
) {
|
||||||
/*
|
/*
|
||||||
DRY helper used by this and reembedding action creators
|
DRY helper used by this and reembedding action creators
|
||||||
*/
|
*/
|
||||||
const base = prevAnnoMatrix.base();
|
const base = prevAnnoMatrix.base();
|
||||||
const embeddingDf = await base.fetch("emb", newEmbeddingName);
|
const embeddingDf = await base.fetch("emb", newEmbeddingName);
|
||||||
const annoMatrix = _setEmbeddingSubset(prevAnnoMatrix, embeddingDf);
|
const annoMatrix = _setEmbeddingSubset(prevAnnoMatrix, embeddingDf);
|
||||||
const obsCrossfilter = await new AnnoMatrixObsCrossfilter(annoMatrix).select(
|
const obsCrossfilter = await new AnnoMatrixObsCrossfilter(
|
||||||
"emb",
|
annoMatrix,
|
||||||
newEmbeddingName,
|
prevCrossfilter.obsCrossfilter
|
||||||
{
|
).select("emb", newEmbeddingName, {
|
||||||
mode: "all",
|
mode: "all",
|
||||||
}
|
});
|
||||||
);
|
|
||||||
return [annoMatrix, obsCrossfilter];
|
return [annoMatrix, obsCrossfilter];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,9 +33,13 @@ export const layoutChoiceAction = (newLayoutChoice) => async (
|
|||||||
On layout choice, make sure we have selected all on the previous layout, AND the new
|
On layout choice, make sure we have selected all on the previous layout, AND the new
|
||||||
layout.
|
layout.
|
||||||
*/
|
*/
|
||||||
const { annoMatrix: prevAnnoMatrix } = getState();
|
const {
|
||||||
|
annoMatrix: prevAnnoMatrix,
|
||||||
|
obsCrossfilter: prevCrossfilter,
|
||||||
|
} = getState();
|
||||||
const [annoMatrix, obsCrossfilter] = await _switchEmbedding(
|
const [annoMatrix, obsCrossfilter] = await _switchEmbedding(
|
||||||
prevAnnoMatrix,
|
prevAnnoMatrix,
|
||||||
|
prevCrossfilter,
|
||||||
newLayoutChoice
|
newLayoutChoice
|
||||||
);
|
);
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -41,6 +41,17 @@ async function configFetch(dispatch) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function userInfoFetch(dispatch) {
|
||||||
|
return fetchJson("userinfo").then((response) => {
|
||||||
|
const { userinfo: userInfo } = response || {};
|
||||||
|
dispatch({
|
||||||
|
type: "userInfo load complete",
|
||||||
|
userInfo,
|
||||||
|
});
|
||||||
|
return userInfo;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function prefetchEmbeddings(annoMatrix) {
|
function prefetchEmbeddings(annoMatrix) {
|
||||||
/*
|
/*
|
||||||
prefetch requests for all embeddings
|
prefetch requests for all embeddings
|
||||||
@@ -62,6 +73,7 @@ const doInitialDataLoad = () =>
|
|||||||
configFetch(dispatch),
|
configFetch(dispatch),
|
||||||
schemaFetch(dispatch),
|
schemaFetch(dispatch),
|
||||||
userColorsFetchAndLoad(dispatch),
|
userColorsFetchAndLoad(dispatch),
|
||||||
|
userInfoFetch(dispatch),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const baseDataUrl = `${globals.API.prefix}${globals.API.version}`;
|
const baseDataUrl = `${globals.API.prefix}${globals.API.version}`;
|
||||||
|
|||||||
@@ -79,10 +79,11 @@ export function requestReembed() {
|
|||||||
type: "reembed: request completed",
|
type: "reembed: request completed",
|
||||||
});
|
});
|
||||||
|
|
||||||
const { annoMatrix: prevAnnoMatrix } = getState();
|
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevCrossfilter } = getState();
|
||||||
const base = prevAnnoMatrix.base().addEmbedding(schema);
|
const base = prevAnnoMatrix.base().addEmbedding(schema);
|
||||||
const [annoMatrix, obsCrossfilter] = await _switchEmbedding(
|
const [annoMatrix, obsCrossfilter] = await _switchEmbedding(
|
||||||
base,
|
base,
|
||||||
|
prevCrossfilter,
|
||||||
schema.name
|
schema.name
|
||||||
);
|
);
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -3,17 +3,19 @@ import { connect } from "react-redux";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Tooltip,
|
|
||||||
InputGroup,
|
|
||||||
Dialog,
|
|
||||||
Classes,
|
Classes,
|
||||||
|
Code,
|
||||||
Colors,
|
Colors,
|
||||||
|
Dialog,
|
||||||
|
InputGroup,
|
||||||
|
Tooltip,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null,
|
idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null,
|
||||||
annotations: state.annotations,
|
annotations: state.annotations,
|
||||||
auth: state.config?.authentication,
|
auth: state.config?.authentication,
|
||||||
|
userInfo: state.userInfo,
|
||||||
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
||||||
}))
|
}))
|
||||||
class FilenameDialog extends React.Component {
|
class FilenameDialog extends React.Component {
|
||||||
@@ -91,13 +93,19 @@ class FilenameDialog extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { writableCategoriesEnabled, annotations, idhash, auth } = this.props;
|
const {
|
||||||
|
writableCategoriesEnabled,
|
||||||
|
annotations,
|
||||||
|
idhash,
|
||||||
|
userInfo,
|
||||||
|
} = this.props;
|
||||||
const { filenameText } = this.state;
|
const { filenameText } = this.state;
|
||||||
|
|
||||||
return writableCategoriesEnabled &&
|
return writableCategoriesEnabled &&
|
||||||
|
annotations.promptForFilename &&
|
||||||
!annotations.dataCollectionNameIsReadOnly &&
|
!annotations.dataCollectionNameIsReadOnly &&
|
||||||
!annotations.dataCollectionName &&
|
!annotations.dataCollectionName &&
|
||||||
auth.is_authenticated ? (
|
userInfo.is_authenticated ? (
|
||||||
<Dialog
|
<Dialog
|
||||||
icon="tag"
|
icon="tag"
|
||||||
title="Annotations Collection"
|
title="Annotations Collection"
|
||||||
@@ -138,9 +146,9 @@ class FilenameDialog extends React.Component {
|
|||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Your annotations are stored in this file:
|
Your annotations are stored in this file:
|
||||||
<code className="bp3-code">
|
<Code>
|
||||||
{filenameText}-{idhash}.csv
|
{filenameText}-{idhash}.csv
|
||||||
</code>
|
</Code>
|
||||||
</p>
|
</p>
|
||||||
<p style={{ fontStyle: "italic" }}>
|
<p style={{ fontStyle: "italic" }}>
|
||||||
(We added a unique ID to your filename)
|
(We added a unique ID to your filename)
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ https://bl.ocks.org/SpaceActuary/2f004899ea1b2bd78d6f1dbb2febf771
|
|||||||
https://bl.ocks.org/mbostock/3019563
|
https://bl.ocks.org/mbostock/3019563
|
||||||
*/
|
*/
|
||||||
import React, { useEffect, useRef, useState, useCallback } from "react";
|
import React, { useEffect, useRef, useState, useCallback } from "react";
|
||||||
import { Button, ButtonGroup, Tooltip } from "@blueprintjs/core";
|
import { Button, ButtonGroup, Icon, Tooltip } from "@blueprintjs/core";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import { interpolateCool } from "d3-scale-chromatic";
|
import { interpolateCool } from "d3-scale-chromatic";
|
||||||
import Async from "react-async";
|
import Async from "react-async";
|
||||||
import memoize from "memoize-one";
|
import memoize from "memoize-one";
|
||||||
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import { histogramContinuous } from "../../util/dataframe/histogram";
|
import { histogramContinuous } from "../../util/dataframe/histogram";
|
||||||
@@ -26,7 +27,7 @@ function maybeScientific(x) {
|
|||||||
const _ticks = x.ticks(4);
|
const _ticks = x.ticks(4);
|
||||||
|
|
||||||
if (x.domain().some((n) => Math.abs(n) >= 10000)) {
|
if (x.domain().some((n) => Math.abs(n) >= 10000)) {
|
||||||
/*
|
/*
|
||||||
heuristic: if the last tick d3 wants to render has one significant
|
heuristic: if the last tick d3 wants to render has one significant
|
||||||
digit ie., 2000, render 2e+3, but if it's anything else ie., 42000000 render
|
digit ie., 2000, render 2e+3, but if it's anything else ie., 42000000 render
|
||||||
4.20e+n
|
4.20e+n
|
||||||
@@ -99,7 +100,7 @@ const HistogramFooter = React.memo(
|
|||||||
pvalAdj,
|
pvalAdj,
|
||||||
}) => {
|
}) => {
|
||||||
/*
|
/*
|
||||||
Footer of each histogram. Will render range, title, and optionally
|
Footer of each histogram. Will render range, title, and optionally
|
||||||
differential expression info.
|
differential expression info.
|
||||||
|
|
||||||
Required props:
|
Required props:
|
||||||
@@ -145,7 +146,7 @@ const HistogramFooter = React.memo(
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{logFoldChange && pvalAdj ? (
|
{logFoldChange !== undefined && pvalAdj !== undefined ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -214,10 +215,7 @@ const HistogramHeader = React.memo(
|
|||||||
>
|
>
|
||||||
{onScatterPlotXClick && onScatterPlotYClick ? (
|
{onScatterPlotXClick && onScatterPlotYClick ? (
|
||||||
<span>
|
<span>
|
||||||
<span
|
<Icon icon={IconNames.SCATTER_PLOT} style={{ marginRight: 7 }} />
|
||||||
style={{ marginRight: 7 }}
|
|
||||||
className="bp3-icon-standard bp3-icon-scatter-plot"
|
|
||||||
/>
|
|
||||||
<ButtonGroup style={{ marginRight: 7 }}>
|
<ButtonGroup style={{ marginRight: 7 }}>
|
||||||
<Button
|
<Button
|
||||||
data-testid={`plot-x-${fieldId}`}
|
data-testid={`plot-x-${fieldId}`}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import React, { useRef, useEffect } from "react";
|
import React, { useRef, useEffect } from "react";
|
||||||
import { connect, shallowEqual } from "react-redux";
|
import { connect, shallowEqual } from "react-redux";
|
||||||
import { FaChevronRight, FaChevronDown } from "react-icons/fa";
|
import { FaChevronRight, FaChevronDown } from "react-icons/fa";
|
||||||
import { AnchorButton, Button, Tooltip, Position } from "@blueprintjs/core";
|
import {
|
||||||
|
AnchorButton,
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
Position,
|
||||||
|
Tooltip,
|
||||||
|
} from "@blueprintjs/core";
|
||||||
import { Flipper, Flipped } from "react-flip-toolkit";
|
import { Flipper, Flipped } from "react-flip-toolkit";
|
||||||
import Async from "react-async";
|
import Async from "react-async";
|
||||||
import memoize from "memoize-one";
|
import memoize from "memoize-one";
|
||||||
@@ -301,9 +307,12 @@ const StillLoading = ({ metadataField, checkboxID }) => {
|
|||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<label htmlFor={checkboxID} className="bp3-control bp3-checkbox">
|
<label
|
||||||
|
htmlFor={checkboxID}
|
||||||
|
className={`${Classes.CONTROL} ${Classes.CHECKBOX}`}
|
||||||
|
>
|
||||||
<input disabled id={checkboxID} checked type="checkbox" />
|
<input disabled id={checkboxID} checked type="checkbox" />
|
||||||
<span className="bp3-control-indicator" />
|
<span className={Classes.CONTROL_INDICATOR} />
|
||||||
</label>
|
</label>
|
||||||
<Truncate>
|
<Truncate>
|
||||||
<span
|
<span
|
||||||
@@ -375,7 +384,10 @@ const CategoryHeader = React.memo(
|
|||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<label className="bp3-control bp3-checkbox" htmlFor={checkboxID}>
|
<label
|
||||||
|
className={`${Classes.CONTROL} ${Classes.CHECKBOX}`}
|
||||||
|
htmlFor={checkboxID}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
id={checkboxID}
|
id={checkboxID}
|
||||||
data-testclass="category-select"
|
data-testclass="category-select"
|
||||||
@@ -385,7 +397,7 @@ const CategoryHeader = React.memo(
|
|||||||
checked={selectionState === "all"}
|
checked={selectionState === "all"}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
<span className="bp3-control-indicator" />
|
<span className={Classes.CONTROL_INDICATOR} />
|
||||||
</label>
|
</label>
|
||||||
<span
|
<span
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
@@ -491,19 +503,7 @@ const CategoryRender = React.memo(
|
|||||||
/*
|
/*
|
||||||
Entire category has a single value, special case.
|
Entire category has a single value, special case.
|
||||||
*/
|
*/
|
||||||
const theOneValue = categorySummary.categoryValues[0];
|
return null;
|
||||||
return (
|
|
||||||
<div style={{ marginBottom: 10, marginTop: 4 }}>
|
|
||||||
<Truncate>
|
|
||||||
<span style={{ maxWidth: 150, fontWeight: 700 }}>
|
|
||||||
{metadataField}
|
|
||||||
</span>
|
|
||||||
</Truncate>
|
|
||||||
<Truncate>
|
|
||||||
<span style={{ maxWidth: 150 }}>{`: ${theOneValue}`}</span>
|
|
||||||
</Truncate>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Button } from "@blueprintjs/core";
|
import { AnchorButton, Tooltip, Position } from "@blueprintjs/core";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import Category from "./category";
|
import Category from "./category";
|
||||||
@@ -15,6 +15,7 @@ import actions from "../../actions";
|
|||||||
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
|
||||||
schema: state.annoMatrix?.schema,
|
schema: state.annoMatrix?.schema,
|
||||||
ontology: state.ontology,
|
ontology: state.ontology,
|
||||||
|
userInfo: state.userInfo,
|
||||||
}))
|
}))
|
||||||
class Categories extends React.Component {
|
class Categories extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -127,7 +128,12 @@ class Categories extends React.Component {
|
|||||||
newCategoryText,
|
newCategoryText,
|
||||||
expandedCats,
|
expandedCats,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { writableCategoriesEnabled, schema, ontology } = this.props;
|
const {
|
||||||
|
writableCategoriesEnabled,
|
||||||
|
schema,
|
||||||
|
ontology,
|
||||||
|
userInfo,
|
||||||
|
} = this.props;
|
||||||
const ontologyEnabled = ontology?.enabled ?? false;
|
const ontologyEnabled = ontology?.enabled ?? false;
|
||||||
/* all names, sorted in display order. Will be rendered in this order */
|
/* all names, sorted in display order. Will be rendered in this order */
|
||||||
const allCategoryNames = ControlsHelpers.selectableCategoryNames(
|
const allCategoryNames = ControlsHelpers.selectableCategoryNames(
|
||||||
@@ -179,7 +185,9 @@ class Categories extends React.Component {
|
|||||||
{/* READ ONLY CATEGORICAL FIELDS */}
|
{/* READ ONLY CATEGORICAL FIELDS */}
|
||||||
{/* this is duplicative but flat, could be abstracted */}
|
{/* this is duplicative but flat, could be abstracted */}
|
||||||
{allCategoryNames.map((catName) =>
|
{allCategoryNames.map((catName) =>
|
||||||
!schema.annotations.obsByName[catName].writable ? (
|
!schema.annotations.obsByName[catName].writable &&
|
||||||
|
(schema.annotations.obsByName[catName].categories?.length > 1 ||
|
||||||
|
!schema.annotations.obsByName[catName].categories) ? (
|
||||||
<Category
|
<Category
|
||||||
key={catName}
|
key={catName}
|
||||||
metadataField={catName}
|
metadataField={catName}
|
||||||
@@ -203,15 +211,30 @@ class Categories extends React.Component {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{writableCategoriesEnabled ? (
|
{writableCategoriesEnabled ? (
|
||||||
<div>
|
<Tooltip
|
||||||
<Button
|
content={
|
||||||
|
userInfo.is_authenticated
|
||||||
|
? "Create a new category"
|
||||||
|
: "You must be logged in to create new categorical fields"
|
||||||
|
}
|
||||||
|
position={Position.RIGHT}
|
||||||
|
boundary="viewport"
|
||||||
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
|
modifiers={{
|
||||||
|
preventOverflow: { enabled: false },
|
||||||
|
hide: { enabled: false },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AnchorButton
|
||||||
|
type="button"
|
||||||
data-testid="open-annotation-dialog"
|
data-testid="open-annotation-dialog"
|
||||||
onClick={this.handleEnableAnnoMode}
|
onClick={this.handleEnableAnnoMode}
|
||||||
intent="primary"
|
intent="primary"
|
||||||
|
disabled={!userInfo.is_authenticated}
|
||||||
>
|
>
|
||||||
Create new category
|
Create new category
|
||||||
</Button>
|
</AnchorButton>
|
||||||
</div>
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import * as d3 from "d3";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Classes,
|
||||||
|
Icon,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Popover,
|
Popover,
|
||||||
Position,
|
|
||||||
Icon,
|
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
|
Position,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import * as globals from "../../../globals";
|
import * as globals from "../../../globals";
|
||||||
import styles from "../categorical.css";
|
import styles from "../categorical.css";
|
||||||
@@ -81,7 +82,7 @@ class CategoryValue extends React.Component {
|
|||||||
get shouldRenderStackedBarOrHistogram() {
|
get shouldRenderStackedBarOrHistogram() {
|
||||||
const { colorAccessor, isColorBy, annotations } = this.props;
|
const { colorAccessor, isColorBy, annotations } = this.props;
|
||||||
|
|
||||||
return colorAccessor && !isColorBy && !annotations.isEditingLabelName;
|
return !!colorAccessor && !isColorBy && !annotations.isEditingLabelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteValue = () => {
|
handleDeleteValue = () => {
|
||||||
@@ -173,7 +174,7 @@ class CategoryValue extends React.Component {
|
|||||||
Checks to see if at least one of the following changed:
|
Checks to see if at least one of the following changed:
|
||||||
* world state
|
* world state
|
||||||
* the color accessor (what is currently being colored by)
|
* the color accessor (what is currently being colored by)
|
||||||
* if this catagorical value's selection status has changed
|
* if this categorical value's selection status has changed
|
||||||
* the crossfilter (ie, global selection state)
|
* the crossfilter (ie, global selection state)
|
||||||
|
|
||||||
If and only if true, update the component
|
If and only if true, update the component
|
||||||
@@ -200,6 +201,13 @@ class CategoryValue extends React.Component {
|
|||||||
const newCount = newCategorySummary.categoryValueCounts[newCategoryIndex];
|
const newCount = newCategorySummary.categoryValueCounts[newCategoryIndex];
|
||||||
const countChanged = count !== newCount;
|
const countChanged = count !== newCount;
|
||||||
|
|
||||||
|
// If the user edits an annotation that is currently colored-by, colors may be re-assigned.
|
||||||
|
// This test is conservative - it may cause re-rendering of entire category (all labels)
|
||||||
|
// if any one changes, but only for the currently colored-by category.
|
||||||
|
const colorMightHaveChanged =
|
||||||
|
nextProps.colorAccessor === nextProps.metadataField &&
|
||||||
|
props.categorySummary !== nextProps.categorySummary;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
labelChanged ||
|
labelChanged ||
|
||||||
valueSelectionChange ||
|
valueSelectionChange ||
|
||||||
@@ -207,7 +215,8 @@ class CategoryValue extends React.Component {
|
|||||||
annotationsChange ||
|
annotationsChange ||
|
||||||
editingLabel ||
|
editingLabel ||
|
||||||
dilationChange ||
|
dilationChange ||
|
||||||
countChanged
|
countChanged ||
|
||||||
|
colorMightHaveChanged
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -410,7 +419,6 @@ class CategoryValue extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MiniStackedBar
|
<MiniStackedBar
|
||||||
/* eslint-disable react/jsx-props-no-spreading -- Disable unneeded on next release of eslint-config-airbnb */
|
|
||||||
{...{
|
{...{
|
||||||
colorTable,
|
colorTable,
|
||||||
domainValues,
|
domainValues,
|
||||||
@@ -418,7 +426,6 @@ class CategoryValue extends React.Component {
|
|||||||
domain,
|
domain,
|
||||||
occupancy,
|
occupancy,
|
||||||
}}
|
}}
|
||||||
/* eslint-enable react/jsx-props-no-spreading -- enable */
|
|
||||||
height={VALUE_HEIGHT}
|
height={VALUE_HEIGHT}
|
||||||
width={CHART_WIDTH}
|
width={CHART_WIDTH}
|
||||||
/>
|
/>
|
||||||
@@ -439,7 +446,9 @@ class CategoryValue extends React.Component {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!this.shouldRenderStackedBarOrHistogram ||
|
!this.shouldRenderStackedBarOrHistogram ||
|
||||||
!AnnotationsHelpers.isContinuousAnnotation(schema, colorAccessor)
|
// This function returns true on categorical annotations(when stacked bar should not render),
|
||||||
|
// in cases where the colorAccessor is a gene this function will return undefined since genes do not live on the schema
|
||||||
|
AnnotationsHelpers.isCategoricalAnnotation(schema, colorAccessor) === true
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -459,14 +468,12 @@ class CategoryValue extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MiniHistogram
|
<MiniHistogram
|
||||||
/* eslint-disable react/jsx-props-no-spreading -- Disable unneeded on next release of eslint-config-airbnb */
|
|
||||||
{...{
|
{...{
|
||||||
colorScale,
|
colorScale,
|
||||||
xScale,
|
xScale,
|
||||||
yScale,
|
yScale,
|
||||||
bins,
|
bins,
|
||||||
}}
|
}}
|
||||||
/* eslint-enable react/jsx-props-no-spreading -- enable */
|
|
||||||
obsOrVarContinuousFieldDisplayName={colorAccessor}
|
obsOrVarContinuousFieldDisplayName={colorAccessor}
|
||||||
domainLabel={label}
|
domainLabel={label}
|
||||||
height={VALUE_HEIGHT}
|
height={VALUE_HEIGHT}
|
||||||
@@ -562,7 +569,7 @@ class CategoryValue extends React.Component {
|
|||||||
<div style={{ display: "flex", alignItems: "baseline" }}>
|
<div style={{ display: "flex", alignItems: "baseline" }}>
|
||||||
<label
|
<label
|
||||||
htmlFor={valueToggleLabel}
|
htmlFor={valueToggleLabel}
|
||||||
className="bp3-control bp3-checkbox"
|
className={`${Classes.CONTROL} ${Classes.CHECKBOX}`}
|
||||||
style={{ margin: 0 }}
|
style={{ margin: 0 }}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -574,7 +581,7 @@ class CategoryValue extends React.Component {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="bp3-control-indicator"
|
className={Classes.CONTROL_INDICATOR}
|
||||||
onMouseEnter={this.handleMouseExit}
|
onMouseEnter={this.handleMouseExit}
|
||||||
onMouseLeave={this.handleMouseEnter}
|
onMouseLeave={this.handleMouseEnter}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import React from "react";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import {
|
import {
|
||||||
|
Classes,
|
||||||
Popover,
|
Popover,
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
Position,
|
Position,
|
||||||
Classes,
|
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
@@ -18,8 +18,8 @@ class Occupancy extends React.PureComponent {
|
|||||||
_HEIGHT = 11;
|
_HEIGHT = 11;
|
||||||
|
|
||||||
createHistogram = () => {
|
createHistogram = () => {
|
||||||
/*
|
/*
|
||||||
Knowing that colorScale is based off continous data,
|
Knowing that colorScale is based off continous data,
|
||||||
createHistogram fetches the continous data in relation to the cells releveant to the catagory value.
|
createHistogram fetches the continous data in relation to the cells releveant to the catagory value.
|
||||||
It then seperates that data into 50 bins for drawing the mini-histogram
|
It then seperates that data into 50 bins for drawing the mini-histogram
|
||||||
*/
|
*/
|
||||||
@@ -75,8 +75,8 @@ class Occupancy extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
createOccupancyStack = () => {
|
createOccupancyStack = () => {
|
||||||
/*
|
/*
|
||||||
Knowing that the color scale is based off of catagorical data,
|
Knowing that the color scale is based off of catagorical data,
|
||||||
createOccupancyStack obtains a map showing the number if cells per colored value
|
createOccupancyStack obtains a map showing the number if cells per colored value
|
||||||
Using the colorScale a stack of colored bars is drawn representing the map
|
Using the colorScale a stack of colored bars is drawn representing the map
|
||||||
*/
|
*/
|
||||||
@@ -155,7 +155,7 @@ class Occupancy extends React.PureComponent {
|
|||||||
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
|
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
className="bp3-popover-targer"
|
className={Classes.POPOVER_TARGET}
|
||||||
style={{
|
style={{
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
width: this._WIDTH,
|
width: this._WIDTH,
|
||||||
|
|||||||
@@ -11,20 +11,20 @@ import {
|
|||||||
|
|
||||||
// create continuous color legend
|
// create continuous color legend
|
||||||
// http://bl.ocks.org/syntagmatic/e8ccca52559796be775553b467593a9f
|
// http://bl.ocks.org/syntagmatic/e8ccca52559796be775553b467593a9f
|
||||||
const continuous = (selectorId, colorscale, colorAccessor) => {
|
const continuous = (selectorId, colorScale, colorAccessor) => {
|
||||||
const legendheight = 200;
|
const legendHeight = 200;
|
||||||
const legendwidth = 80;
|
const legendWidth = 80;
|
||||||
const margin = { top: 10, right: 60, bottom: 10, left: 2 };
|
const margin = { top: 10, right: 60, bottom: 10, left: 2 };
|
||||||
|
|
||||||
const canvas = d3
|
const canvas = d3
|
||||||
.select(selectorId)
|
.select(selectorId)
|
||||||
.style("height", `${legendheight}px`)
|
.style("height", `${legendHeight}px`)
|
||||||
.style("width", `${legendwidth}px`)
|
.style("width", `${legendWidth}px`)
|
||||||
.append("canvas")
|
.append("canvas")
|
||||||
.attr("height", legendheight - margin.top - margin.bottom)
|
.attr("height", legendHeight - margin.top - margin.bottom)
|
||||||
.attr("width", 1)
|
.attr("width", 1)
|
||||||
.style("height", `${legendheight - margin.top - margin.bottom}px`)
|
.style("height", `${legendHeight - margin.top - margin.bottom}px`)
|
||||||
.style("width", `${legendwidth - margin.left - margin.right}px`)
|
.style("width", `${legendWidth - margin.left - margin.right}px`)
|
||||||
.style("position", "absolute")
|
.style("position", "absolute")
|
||||||
.style("top", `${margin.top + 1}px`)
|
.style("top", `${margin.top + 1}px`)
|
||||||
.style("left", `${margin.left + 1}px`)
|
.style("left", `${margin.left + 1}px`)
|
||||||
@@ -37,18 +37,18 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
|
|||||||
|
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
const legendscale = d3
|
const legendScale = d3
|
||||||
.scaleLinear()
|
.scaleLinear()
|
||||||
.range([1, legendheight - margin.top - margin.bottom])
|
.range([1, legendHeight - margin.top - margin.bottom])
|
||||||
.domain([
|
.domain([
|
||||||
colorscale.domain()[1],
|
colorScale.domain()[1],
|
||||||
colorscale.domain()[0],
|
colorScale.domain()[0],
|
||||||
]); /* we flip this to make viridis colors dark if high in the color scale */
|
]); /* we flip this to make viridis colors dark if high in the color scale */
|
||||||
|
|
||||||
// image data hackery based on http://bl.ocks.org/mbostock/048d21cf747371b11884f75ad896e5a5
|
// image data hackery based on http://bl.ocks.org/mbostock/048d21cf747371b11884f75ad896e5a5
|
||||||
const image = ctx.createImageData(1, legendheight);
|
const image = ctx.createImageData(1, legendHeight);
|
||||||
d3.range(legendheight).forEach((i) => {
|
d3.range(legendHeight).forEach((i) => {
|
||||||
const c = d3.rgb(colorscale(legendscale.invert(i)));
|
const c = d3.rgb(colorScale(legendScale.invert(i)));
|
||||||
image.data[4 * i] = c.r;
|
image.data[4 * i] = c.r;
|
||||||
image.data[4 * i + 1] = c.g;
|
image.data[4 * i + 1] = c.g;
|
||||||
image.data[4 * i + 2] = c.b;
|
image.data[4 * i + 2] = c.b;
|
||||||
@@ -66,20 +66,20 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
|
|||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const legendaxis = d3
|
const legendAxis = d3
|
||||||
.axisRight(legendscale)
|
.axisRight(legendScale)
|
||||||
.ticks(6)
|
.ticks(6)
|
||||||
.tickFormat(
|
.tickFormat(
|
||||||
d3.format(
|
d3.format(
|
||||||
legendscale.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
|
legendScale.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const svg = d3
|
const svg = d3
|
||||||
.select(selectorId)
|
.select(selectorId)
|
||||||
.append("svg")
|
.append("svg")
|
||||||
.attr("height", `${legendheight}px`)
|
.attr("height", `${legendHeight}px`)
|
||||||
.attr("width", `${legendwidth}px`)
|
.attr("width", `${legendWidth}px`)
|
||||||
.style("position", "absolute")
|
.style("position", "absolute")
|
||||||
.style("left", "0px")
|
.style("left", "0px")
|
||||||
.style("top", "0px");
|
.style("top", "0px");
|
||||||
@@ -89,16 +89,16 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
|
|||||||
.attr("class", "axis")
|
.attr("class", "axis")
|
||||||
.attr(
|
.attr(
|
||||||
"transform",
|
"transform",
|
||||||
`translate(${legendwidth - margin.left - margin.right + 3},${margin.top})`
|
`translate(${legendWidth - margin.left - margin.right + 3},${margin.top})`
|
||||||
)
|
)
|
||||||
.call(legendaxis);
|
.call(legendAxis);
|
||||||
|
|
||||||
// text label for the y axis
|
// text label for the y axis
|
||||||
svg
|
svg
|
||||||
.append("text")
|
.append("text")
|
||||||
.attr("transform", "rotate(-90)")
|
.attr("transform", "rotate(-90)")
|
||||||
.attr("y", 2)
|
.attr("y", 2)
|
||||||
.attr("x", 0 - legendheight / 2)
|
.attr("x", 0 - legendHeight / 2)
|
||||||
.attr("dy", "1em")
|
.attr("dy", "1em")
|
||||||
.style("text-anchor", "middle")
|
.style("text-anchor", "middle")
|
||||||
.style("fill", "white")
|
.style("fill", "white")
|
||||||
@@ -110,24 +110,7 @@ const continuous = (selectorId, colorscale, colorAccessor) => {
|
|||||||
colors: state.colors,
|
colors: state.colors,
|
||||||
}))
|
}))
|
||||||
class ContinuousLegend extends React.Component {
|
class ContinuousLegend extends React.Component {
|
||||||
constructor(props) {
|
async componentDidUpdate(prevProps) {
|
||||||
super(props);
|
|
||||||
this.ref = null;
|
|
||||||
this.state = {
|
|
||||||
colorAccessor: null,
|
|
||||||
colorScale: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.updateState(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
this.updateState(prevProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateState(prevProps) {
|
|
||||||
const { annoMatrix, colors } = this.props;
|
const { annoMatrix, colors } = this.props;
|
||||||
if (!colors || !annoMatrix) return;
|
if (!colors || !annoMatrix) return;
|
||||||
|
|
||||||
@@ -161,35 +144,19 @@ class ContinuousLegend extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
|
||||||
colorAccessor,
|
|
||||||
colorScale: colorTable.scale,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { colorAccessor, colorScale } = this.state;
|
|
||||||
if (
|
|
||||||
colorScale?.domain &&
|
|
||||||
colorScale.domain()[1] === colorScale.domain()[0]
|
|
||||||
) {
|
|
||||||
/* it's a single value, not a distribution, min max are the same */
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="continuous_legend"
|
id="continuous_legend"
|
||||||
ref={(ref) => {
|
|
||||||
this.ref = ref;
|
|
||||||
}}
|
|
||||||
style={{
|
style={{
|
||||||
display: colorAccessor ? "inherit" : "none",
|
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: 8,
|
left: 8,
|
||||||
top: 35,
|
top: 35,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
|
pointerEvents: "none",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import React from "react";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useAsync } from "react-async";
|
import { useAsync } from "react-async";
|
||||||
import {
|
import {
|
||||||
ButtonGroup,
|
|
||||||
Popover,
|
|
||||||
Button,
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
H4,
|
||||||
|
Popover,
|
||||||
|
Position,
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Position,
|
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
@@ -80,7 +81,7 @@ class Embedding extends React.PureComponent {
|
|||||||
width: 400,
|
width: 400,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1>Embedding Choice</h1>
|
<H4>Embedding Choice</H4>
|
||||||
<p style={{ fontStyle: "italic" }}>
|
<p style={{ fontStyle: "italic" }}>
|
||||||
There are {schema?.dataframe?.nObs} cells in the entire dataset.
|
There are {schema?.dataframe?.nObs} cells in the entire dataset.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import fuzzysort from "fuzzysort";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Suggest } from "@blueprintjs/select";
|
import { Suggest } from "@blueprintjs/select";
|
||||||
import {
|
import {
|
||||||
MenuItem,
|
|
||||||
Button,
|
Button,
|
||||||
|
ControlGroup,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
ControlGroup,
|
Intent,
|
||||||
|
MenuItem,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
@@ -278,7 +279,7 @@ class AddGenes extends React.Component {
|
|||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="bp3-button bp3-intent-primary"
|
intent={Intent.PRIMARY}
|
||||||
data-testid="add-gene"
|
data-testid="add-gene"
|
||||||
loading={userDefinedGenesLoading}
|
loading={userDefinedGenesLoading}
|
||||||
onClick={() => this.handleClick(activeItem)}
|
onClick={() => this.handleClick(activeItem)}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function drawPointsRegl(regl) {
|
|||||||
uniform float nPoints;
|
uniform float nPoints;
|
||||||
uniform float minViewportDimension;
|
uniform float minViewportDimension;
|
||||||
|
|
||||||
varying vec4 fragColor;
|
varying lowp vec4 fragColor;
|
||||||
|
|
||||||
const float zBottom = 0.99;
|
const float zBottom = 0.99;
|
||||||
const float zMiddle = 0.;
|
const float zMiddle = 0.;
|
||||||
@@ -27,23 +27,23 @@ export default function drawPointsRegl(regl) {
|
|||||||
${glPointSize}
|
${glPointSize}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
bool isNaN, isSelected, isHighlight;
|
bool isBackground, isSelected, isHighlight;
|
||||||
getFlags(flag, isNaN, isSelected, isHighlight);
|
getFlags(flag, isBackground, isSelected, isHighlight);
|
||||||
|
|
||||||
float size = pointSize(nPoints, minViewportDimension, isSelected, isHighlight);
|
float size = pointSize(nPoints, minViewportDimension, isSelected, isHighlight);
|
||||||
gl_PointSize = size * pow(distance, 0.5);
|
gl_PointSize = size * pow(distance, 0.5);
|
||||||
|
|
||||||
float z = isNaN ? zBottom : (isHighlight ? zTop : zMiddle);
|
float z = isBackground ? zBottom : (isHighlight ? zTop : zMiddle);
|
||||||
vec3 xy = projView * vec3(position, 1.);
|
vec3 xy = projView * vec3(position, 1.);
|
||||||
gl_Position = vec4(xy.xy, z, 1.);
|
gl_Position = vec4(xy.xy, z, 1.);
|
||||||
|
|
||||||
float alpha = isNaN ? 0.9 : 1.0;
|
float alpha = isBackground ? 0.9 : 1.0;
|
||||||
fragColor = vec4(color, alpha);
|
fragColor = vec4(color, alpha);
|
||||||
}`,
|
}`,
|
||||||
|
|
||||||
frag: `
|
frag: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
varying vec4 fragColor;
|
varying lowp vec4 fragColor;
|
||||||
void main() {
|
void main() {
|
||||||
if (length(gl_PointCoord.xy - 0.5) > 0.5) {
|
if (length(gl_PointCoord.xy - 0.5) > 0.5) {
|
||||||
discard;
|
discard;
|
||||||
@@ -67,5 +67,15 @@ export default function drawPointsRegl(regl) {
|
|||||||
count: regl.prop("count"),
|
count: regl.prop("count"),
|
||||||
|
|
||||||
primitive: "points",
|
primitive: "points",
|
||||||
|
|
||||||
|
blend: {
|
||||||
|
enable: true,
|
||||||
|
func: {
|
||||||
|
srcRGB: "src alpha",
|
||||||
|
srcAlpha: 1,
|
||||||
|
dstRGB: 0,
|
||||||
|
dstAlpha: "zero",
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ import CentroidLabels from "./overlays/centroidLabels";
|
|||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import renderThrottle from "../../util/renderThrottle";
|
import renderThrottle from "../../util/renderThrottle";
|
||||||
|
|
||||||
|
import {
|
||||||
|
flagBackground,
|
||||||
|
flagSelected,
|
||||||
|
flagHighlight,
|
||||||
|
} from "../../util/glHelpers";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Simple 2D transforms control all point painting. There are three:
|
Simple 2D transforms control all point painting. There are three:
|
||||||
* model - convert from underlying per-point coordinate to a layout.
|
* model - convert from underlying per-point coordinate to a layout.
|
||||||
@@ -62,10 +68,6 @@ function createModelTF() {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
const flagSelected = 1;
|
|
||||||
const flagNaN = 2;
|
|
||||||
const flagHighlight = 4;
|
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
annoMatrix: state.annoMatrix,
|
annoMatrix: state.annoMatrix,
|
||||||
crossfilter: state.obsCrossfilter,
|
crossfilter: state.obsCrossfilter,
|
||||||
@@ -159,8 +161,9 @@ class Graph extends React.Component {
|
|||||||
const flags = new Float32Array(nObs);
|
const flags = new Float32Array(nObs);
|
||||||
if (colorByData) {
|
if (colorByData) {
|
||||||
for (let i = 0, len = flags.length; i < len; i += 1) {
|
for (let i = 0, len = flags.length; i < len; i += 1) {
|
||||||
if (!Number.isFinite(colorByData[i])) {
|
const val = colorByData[i];
|
||||||
flags[i] = flagNaN;
|
if (typeof val === "number" && !Number.isFinite(val)) {
|
||||||
|
flags[i] = flagBackground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -730,14 +733,28 @@ class Graph extends React.Component {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
updateReglAndRender(asyncProps) {
|
updateReglAndRender(asyncProps, prevAsyncProps) {
|
||||||
const { positions, colors, flags } = asyncProps;
|
const { positions, colors, flags, height, width } = asyncProps;
|
||||||
this.cachedAsyncProps = asyncProps;
|
this.cachedAsyncProps = asyncProps;
|
||||||
const { pointBuffer, colorBuffer, flagBuffer } = this.state;
|
const { pointBuffer, colorBuffer, flagBuffer } = this.state;
|
||||||
pointBuffer({ data: positions, dimension: 2 });
|
let needToRenderCanvas = false;
|
||||||
colorBuffer({ data: colors, dimension: 3 });
|
|
||||||
flagBuffer({ data: flags, dimension: 1 });
|
if (height !== prevAsyncProps?.height || width !== prevAsyncProps?.width) {
|
||||||
this.renderCanvas();
|
needToRenderCanvas = true;
|
||||||
|
}
|
||||||
|
if (positions !== prevAsyncProps?.positions) {
|
||||||
|
pointBuffer({ data: positions, dimension: 2 });
|
||||||
|
needToRenderCanvas = true;
|
||||||
|
}
|
||||||
|
if (colors !== prevAsyncProps?.colors) {
|
||||||
|
colorBuffer({ data: colors, dimension: 3 });
|
||||||
|
needToRenderCanvas = true;
|
||||||
|
}
|
||||||
|
if (flags !== prevAsyncProps?.flags) {
|
||||||
|
flagBuffer({ data: flags, dimension: 1 });
|
||||||
|
needToRenderCanvas = true;
|
||||||
|
}
|
||||||
|
if (needToRenderCanvas) this.renderCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateColorTable(colors, colorDf) {
|
updateColorTable(colors, colorDf) {
|
||||||
@@ -906,7 +923,7 @@ class Graph extends React.Component {
|
|||||||
<Async.Fulfilled>
|
<Async.Fulfilled>
|
||||||
{(asyncProps) => {
|
{(asyncProps) => {
|
||||||
if (regl && !shallowEqual(asyncProps, this.cachedAsyncProps)) {
|
if (regl && !shallowEqual(asyncProps, this.cachedAsyncProps)) {
|
||||||
this.updateReglAndRender(asyncProps);
|
this.updateReglAndRender(asyncProps, this.cachedAsyncProps);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const Lasso = () => {
|
|||||||
let lassoPolygon;
|
let lassoPolygon;
|
||||||
let lassoPath;
|
let lassoPath;
|
||||||
let closePath;
|
let closePath;
|
||||||
|
let lassoInProgress;
|
||||||
|
|
||||||
const polygonToPath = (polygon) =>
|
const polygonToPath = (polygon) =>
|
||||||
`M${polygon.map((d) => d.join(",")).join("L")}`;
|
`M${polygon.map((d) => d.join(",")).join("L")}`;
|
||||||
@@ -25,8 +26,18 @@ const Lasso = () => {
|
|||||||
lassoPolygon = [d3.mouse(svg.node())]; // current x y of mouse within element
|
lassoPolygon = [d3.mouse(svg.node())]; // current x y of mouse within element
|
||||||
|
|
||||||
if (lassoPath) {
|
if (lassoPath) {
|
||||||
|
// If the existing path is in progress
|
||||||
|
if (lassoInProgress) {
|
||||||
|
// cancel the existing lasso
|
||||||
|
handleCancel();
|
||||||
|
// Don't continue with current drag start
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lassoPath.remove();
|
lassoPath.remove();
|
||||||
}
|
}
|
||||||
|
// We're starting a new drag
|
||||||
|
lassoInProgress = true;
|
||||||
|
|
||||||
lassoPath = g
|
lassoPath = g
|
||||||
.append("path")
|
.append("path")
|
||||||
@@ -67,25 +78,33 @@ const Lasso = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
lassoPath.remove();
|
||||||
|
closePath = closePath?.remove();
|
||||||
|
lassoPath = null;
|
||||||
|
lassoPolygon = null;
|
||||||
|
closePath = null;
|
||||||
|
dispatch.call("cancel");
|
||||||
|
};
|
||||||
|
|
||||||
const handleDragEnd = () => {
|
const handleDragEnd = () => {
|
||||||
// remove the close path
|
// remove the close path
|
||||||
closePath.remove();
|
closePath.remove();
|
||||||
closePath = null;
|
closePath = null;
|
||||||
|
|
||||||
// succesfully closed
|
// successfully closed
|
||||||
if (
|
if (
|
||||||
distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) <
|
distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) <
|
||||||
closeDistance
|
closeDistance
|
||||||
) {
|
) {
|
||||||
|
lassoInProgress = false;
|
||||||
|
|
||||||
lassoPath.attr("d", `${polygonToPath(lassoPolygon)}Z`);
|
lassoPath.attr("d", `${polygonToPath(lassoPolygon)}Z`);
|
||||||
dispatch.call("end", lasso, lassoPolygon);
|
dispatch.call("end", lasso, lassoPolygon);
|
||||||
|
|
||||||
// otherwise cancel
|
// otherwise cancel
|
||||||
} else {
|
} else {
|
||||||
lassoPath.remove();
|
handleCancel();
|
||||||
lassoPath = null;
|
|
||||||
lassoPolygon = null;
|
|
||||||
dispatch.call("cancel");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import React, { PureComponent } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Drawer } from "@blueprintjs/core";
|
||||||
|
|
||||||
|
import InfoFormat from "./infoFormat";
|
||||||
|
import { selectableCategoryNames } from "../../util/stateManager/controlsHelpers";
|
||||||
|
|
||||||
|
@connect((state) => {
|
||||||
|
return {
|
||||||
|
schema: state.annoMatrix.schema,
|
||||||
|
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
||||||
|
aboutURL: state.config?.links?.["about-dataset"],
|
||||||
|
isOpen: state.controls.datasetDrawer,
|
||||||
|
dataPortalProps: state.config?.["corpora_props"],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
class InfoDrawer extends PureComponent {
|
||||||
|
handleClose = () => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
|
dispatch({ type: "toggle dataset drawer" });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
position,
|
||||||
|
aboutURL,
|
||||||
|
datasetTitle,
|
||||||
|
schema,
|
||||||
|
isOpen,
|
||||||
|
dataPortalProps,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const allCategoryNames = selectableCategoryNames(schema).sort();
|
||||||
|
const singleValueCategories = new Map();
|
||||||
|
|
||||||
|
allCategoryNames.forEach((catName) => {
|
||||||
|
const isUserAnno = schema?.annotations?.obsByName[catName]?.writable;
|
||||||
|
const colSchema = schema.annotations.obsByName[catName];
|
||||||
|
if (!isUserAnno && colSchema.categories?.length === 1) {
|
||||||
|
singleValueCategories.set(catName, colSchema.categories[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
title="Dataset Overview"
|
||||||
|
onClose={this.handleClose}
|
||||||
|
{...{ isOpen, position }}
|
||||||
|
>
|
||||||
|
<InfoFormat
|
||||||
|
{...{
|
||||||
|
datasetTitle,
|
||||||
|
aboutURL,
|
||||||
|
singleValueCategories,
|
||||||
|
dataPortalProps: dataPortalProps ?? {},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default InfoDrawer;
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { H3, H1, UL, HTMLTable, Classes } from "@blueprintjs/core";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const renderContributors = (contributors, affiliations) => {
|
||||||
|
// eslint-disable-next-line no-constant-condition -- Temp removed contributor section to avoid publishing PII
|
||||||
|
if (!contributors || contributors.length === 0 || true) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3>Contributors</H3>
|
||||||
|
<p>
|
||||||
|
{contributors.map((contributor) => {
|
||||||
|
const { email, name, institution } = contributor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span key={name}>
|
||||||
|
{name}
|
||||||
|
{email && `(${email})`}
|
||||||
|
<sup>{affiliations.indexOf(institution) + 1}</sup>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{renderAffiliations(affiliations)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// generates a list of unique institutions by order of appearance in contributors
|
||||||
|
const buildAffiliations = (contributors = []) => {
|
||||||
|
const affiliations = [];
|
||||||
|
contributors.forEach((contributor) => {
|
||||||
|
const { institution } = contributor;
|
||||||
|
if (affiliations.indexOf(institution) === -1) {
|
||||||
|
affiliations.push(institution);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return affiliations;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderAffiliations = (affiliations) => {
|
||||||
|
if (affiliations.length === 0) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3>Affiliations</H3>
|
||||||
|
<UL>
|
||||||
|
{affiliations.map((item, index) => (
|
||||||
|
<div key={item}>
|
||||||
|
<sup>{index + 1}</sup>
|
||||||
|
{" "}
|
||||||
|
{item}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</UL>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDOILink = (type, doi) => {
|
||||||
|
if (!doi) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3>{type}</H3>
|
||||||
|
<p>
|
||||||
|
<a href={doi} target="_blank" rel="noopener">
|
||||||
|
{doi}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ONTOLOGY_KEY = "ontology_term_id";
|
||||||
|
// Render list of metadata attributes found in categorical field
|
||||||
|
const renderDatasetMetadata = (singleValueCategories, corporaMetadata) => {
|
||||||
|
if (singleValueCategories.size === 0) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3>Dataset Metadata</H3>
|
||||||
|
<HTMLTable
|
||||||
|
striped
|
||||||
|
condensed
|
||||||
|
style={{ display: "block", width: "100%", overflowX: "auto" }}
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Label</th>
|
||||||
|
<th>Ontology ID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Object.entries(corporaMetadata).map(([key, value]) => {
|
||||||
|
return (
|
||||||
|
<tr {...{ key }}>
|
||||||
|
<td>{`${key}:`}</td>
|
||||||
|
<td>{value}</td>
|
||||||
|
<td />
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{Array.from(singleValueCategories).reduce((elems, pair) => {
|
||||||
|
const [category, value] = pair;
|
||||||
|
// If the value is empty skip it
|
||||||
|
if (!value) return elems;
|
||||||
|
|
||||||
|
// If this category is a ontology term, let's add its value to the previous node
|
||||||
|
if (String(category).includes(ONTOLOGY_KEY)) {
|
||||||
|
const prevElem = elems.pop();
|
||||||
|
const newChildren = [...prevElem.props.children];
|
||||||
|
newChildren.splice(2, 1, [<td key="ontology">{value}</td>]);
|
||||||
|
// Props aren't extensible so we must clone and alter the component to append the new child
|
||||||
|
elems.push(
|
||||||
|
React.cloneElement(prevElem, prevElem.props, newChildren)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Create the list item
|
||||||
|
elems.push(
|
||||||
|
<tr key={category}>
|
||||||
|
<td>{`${category}:`}</td>
|
||||||
|
<td>{value}</td>
|
||||||
|
<td />
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return elems;
|
||||||
|
}, [])}
|
||||||
|
</tbody>
|
||||||
|
</HTMLTable>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Renders any links found in the config where link_type is not "SUMMARY"
|
||||||
|
// If there are no links in the config, render the aboutURL
|
||||||
|
const renderLinks = (projectLinks, aboutURL) => {
|
||||||
|
if (!projectLinks && !aboutURL) return null;
|
||||||
|
if (projectLinks)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3>Project Links</H3>
|
||||||
|
<UL>
|
||||||
|
{projectLinks.map((link) => {
|
||||||
|
if (link.link_type === "SUMMARY") return null;
|
||||||
|
return (
|
||||||
|
<li key={link.link_name}>
|
||||||
|
<a href={link.link_url} target="_blank" rel="noopener">
|
||||||
|
{link.link_name}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</UL>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<H3>More Info</H3>
|
||||||
|
<p>
|
||||||
|
<a href={aboutURL} target="_blank" rel="noopener">
|
||||||
|
{aboutURL}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const InfoFormat = React.memo(
|
||||||
|
({ datasetTitle, singleValueCategories, aboutURL, dataPortalProps = {} }) => {
|
||||||
|
if (["1.0.0", "1.1.0"].indexOf(dataPortalProps.version?.["corpora_schema_version"]) === -1) {
|
||||||
|
dataPortalProps = {};
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
publication_doi: doi,
|
||||||
|
preprint_doi: preprintDOI,
|
||||||
|
organism,
|
||||||
|
contributors,
|
||||||
|
project_links: projectLinks,
|
||||||
|
} = dataPortalProps;
|
||||||
|
|
||||||
|
const affiliations = buildAffiliations(contributors);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<H1>{title ?? datasetTitle}</H1>
|
||||||
|
{renderContributors(contributors, affiliations)}
|
||||||
|
{renderDatasetMetadata(singleValueCategories, { organism })}
|
||||||
|
{renderLinks(projectLinks, aboutURL)}
|
||||||
|
{renderDOILink("DOI", doi)}
|
||||||
|
{renderDOILink("Preprint DOI", preprintDOI)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default InfoFormat;
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// jshint esversion: 6
|
||||||
|
import React from "react";
|
||||||
|
import { Button, Menu, MenuItem, Popover, Position } from "@blueprintjs/core";
|
||||||
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
|
|
||||||
|
const InformationMenu = React.memo((props) => {
|
||||||
|
const { libraryVersions, tosURL, privacyURL } = props;
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
content={
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
href="https://chanzuckerberg.github.io/cellxgene/"
|
||||||
|
target="_blank"
|
||||||
|
icon="book"
|
||||||
|
text="Documentation"
|
||||||
|
rel="noopener"
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
href="https://join-cellxgene-users.herokuapp.com/"
|
||||||
|
target="_blank"
|
||||||
|
icon="chat"
|
||||||
|
text="Chat"
|
||||||
|
rel="noopener"
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
href="https://github.com/chanzuckerberg/cellxgene"
|
||||||
|
target="_blank"
|
||||||
|
icon="git-branch"
|
||||||
|
text="Github"
|
||||||
|
rel="noopener"
|
||||||
|
/>
|
||||||
|
<MenuItem target="_blank" text={libraryVersions?.cellxgene || null} />
|
||||||
|
<MenuItem text="MIT License" />
|
||||||
|
{tosURL && (
|
||||||
|
<MenuItem
|
||||||
|
href={tosURL}
|
||||||
|
target="_blank"
|
||||||
|
text="Terms of Service"
|
||||||
|
rel="noopener"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{privacyURL && (
|
||||||
|
<MenuItem
|
||||||
|
href={privacyURL}
|
||||||
|
target="_blank"
|
||||||
|
text="Privacy Policy"
|
||||||
|
rel="noopener"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
position={Position.BOTTOM_RIGHT}
|
||||||
|
modifiers={{
|
||||||
|
preventOverflow: { enabled: false },
|
||||||
|
hide: { enabled: false },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
data-testid="menu"
|
||||||
|
type="button"
|
||||||
|
icon={IconNames.INFO_SIGN}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
verticalAlign: "middle",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InformationMenu;
|
||||||
@@ -1,22 +1,44 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import { Button } from "@blueprintjs/core";
|
||||||
|
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
import Logo from "../framework/logo";
|
import Logo from "../framework/logo";
|
||||||
import Truncate from "../util/truncate";
|
import Truncate from "../util/truncate";
|
||||||
|
import InfoDrawer from "../infoDrawer/infoDrawer";
|
||||||
|
import InformationMenu from "./infoMenu";
|
||||||
|
|
||||||
const DATASET_TITLE_WIDTH = 190;
|
|
||||||
const DATASET_TITLE_FONT_SIZE = 14;
|
const DATASET_TITLE_FONT_SIZE = 14;
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => {
|
||||||
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
const { corpora_props: corporaProps } = state.config;
|
||||||
aboutURL: state.config?.links?.["about-dataset"],
|
const correctVersion =
|
||||||
scatterplotXXaccessor: state.controls.scatterplotXXaccessor,
|
["1.0.0", "1.1.0"].indexOf(corporaProps?.version?.["corpora_schema_version"]) > -1;
|
||||||
scatterplotYYaccessor: state.controls.scatterplotYYaccessor,
|
return {
|
||||||
}))
|
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
||||||
|
libraryVersions: state.config?.["library_versions"],
|
||||||
|
aboutLink: state.config?.links?.["about-dataset"],
|
||||||
|
tosURL: state.config?.parameters?.["about_legal_tos"],
|
||||||
|
privacyURL: state.config?.parameters?.["about_legal_privacy"],
|
||||||
|
title: correctVersion ? corporaProps?.title : undefined,
|
||||||
|
};
|
||||||
|
})
|
||||||
class LeftSideBar extends React.Component {
|
class LeftSideBar extends React.Component {
|
||||||
|
handleClick = () => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
dispatch({ type: "toggle dataset drawer" });
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { datasetTitle, aboutURL } = this.props;
|
const {
|
||||||
|
datasetTitle,
|
||||||
|
libraryVersions,
|
||||||
|
aboutLink,
|
||||||
|
privacyURL,
|
||||||
|
tosURL,
|
||||||
|
dispatch,
|
||||||
|
title,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -26,65 +48,64 @@ class LeftSideBar extends React.Component {
|
|||||||
width: globals.leftSidebarWidth,
|
width: globals.leftSidebarWidth,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
borderBottom: `1px solid ${globals.lighterGrey}`,
|
borderBottom: `1px solid ${globals.lighterGrey}`,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Logo size={30} />
|
<div>
|
||||||
<span
|
<Logo size={28} />
|
||||||
style={{
|
|
||||||
fontSize: 28,
|
|
||||||
position: "relative",
|
|
||||||
top: -6,
|
|
||||||
fontWeight: "bold",
|
|
||||||
marginLeft: 5,
|
|
||||||
color: globals.logoColor,
|
|
||||||
userSelect: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
cell
|
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
position: "relative",
|
|
||||||
top: 1,
|
|
||||||
fontWeight: 300,
|
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
position: "relative",
|
||||||
|
top: -6,
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginLeft: 5,
|
||||||
|
color: globals.logoColor,
|
||||||
|
userSelect: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
×
|
cell
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
top: 1,
|
||||||
|
fontWeight: 300,
|
||||||
|
fontSize: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</span>
|
||||||
|
gene
|
||||||
</span>
|
</span>
|
||||||
gene
|
</div>
|
||||||
</span>
|
<div style={{ marginRight: 5, height: "100%" }}>
|
||||||
<div
|
<Button
|
||||||
style={{
|
minimal
|
||||||
fontSize: DATASET_TITLE_FONT_SIZE,
|
style={{
|
||||||
position: "relative",
|
fontSize: DATASET_TITLE_FONT_SIZE,
|
||||||
top: -6,
|
position: "relative",
|
||||||
display: "inline-block",
|
top: -1,
|
||||||
width: DATASET_TITLE_WIDTH,
|
}}
|
||||||
marginLeft: "7px",
|
onClick={this.handleClick}
|
||||||
height: "1.2em",
|
>
|
||||||
overflow: "hidden",
|
|
||||||
wordBreak: "break-all",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{aboutURL ? (
|
|
||||||
<Truncate>
|
<Truncate>
|
||||||
<a
|
<span style={{ maxWidth: 155 }} data-testid="header">
|
||||||
style={{ width: 185 }}
|
{title ?? datasetTitle}
|
||||||
href={aboutURL}
|
|
||||||
data-testid="header"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{datasetTitle}
|
|
||||||
</a>
|
|
||||||
</Truncate>
|
|
||||||
) : (
|
|
||||||
<Truncate>
|
|
||||||
<span style={{ width: 185 }} data-testid="header">
|
|
||||||
{datasetTitle}
|
|
||||||
</span>
|
</span>
|
||||||
</Truncate>
|
</Truncate>
|
||||||
)}
|
</Button>
|
||||||
|
<InfoDrawer />
|
||||||
|
<InformationMenu
|
||||||
|
{...{
|
||||||
|
libraryVersions,
|
||||||
|
aboutLink,
|
||||||
|
tosURL,
|
||||||
|
privacyURL,
|
||||||
|
dispatch,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,32 +1,175 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { AnchorButton, Tooltip } from "@blueprintjs/core";
|
|
||||||
|
import {
|
||||||
|
AnchorButton,
|
||||||
|
Button,
|
||||||
|
MenuItem,
|
||||||
|
Tooltip,
|
||||||
|
Popover,
|
||||||
|
Menu,
|
||||||
|
Elevation,
|
||||||
|
PopoverPosition,
|
||||||
|
Checkbox,
|
||||||
|
Card,
|
||||||
|
} from "@blueprintjs/core";
|
||||||
|
|
||||||
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
|
|
||||||
import * as globals from "../../globals";
|
import * as globals from "../../globals";
|
||||||
|
|
||||||
import styles from "./menubar.css";
|
import styles from "./menubar.css";
|
||||||
|
|
||||||
|
import { storageGet, storageSet, KEYS } from "../util/localStorage";
|
||||||
|
|
||||||
|
const BASE_EMOJI = [0x1f9d1, 0x1f468, 0x1f469];
|
||||||
|
const SKIN_TONES = [0x1f3fb, 0x1f3fc, 0x1f3fd, 0x1f3fe, 0x1f3ff];
|
||||||
|
const MICROSCOPE = 0x1f52c;
|
||||||
|
const ZERO_WIDTH_JOINER = 0x0200d;
|
||||||
|
|
||||||
|
const LOGIN_PROMPT_OFF = "off";
|
||||||
|
|
||||||
const Auth = React.memo((props) => {
|
const Auth = React.memo((props) => {
|
||||||
const { auth } = props;
|
const [isPromptOpen, setIsPromptOpen] = useState(shouldShowPrompt());
|
||||||
|
|
||||||
if (!auth || (auth && !auth.requires_client_login)) return null;
|
const { auth, userInfo } = props;
|
||||||
|
|
||||||
return (
|
const isAuthenticated = userInfo && userInfo.is_authenticated;
|
||||||
<div className={`bp3-button-group ${styles.menubarButton}`}>
|
|
||||||
<Tooltip
|
window.userInfo = userInfo;
|
||||||
content="Log in or log out of cellxgene"
|
|
||||||
position="bottom"
|
const randomInt = Math.random() * 15;
|
||||||
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
const sexIndex = Math.floor(randomInt / 5);
|
||||||
>
|
const skinToneIndex = Math.floor(randomInt % 5);
|
||||||
<AnchorButton
|
|
||||||
type="button"
|
const scientist = String.fromCodePoint(
|
||||||
data-testid="auth-button"
|
BASE_EMOJI[sexIndex],
|
||||||
disabled={false}
|
SKIN_TONES[skinToneIndex],
|
||||||
icon={!auth.is_authenticated ? "log-in" : "log-out"}
|
ZERO_WIDTH_JOINER,
|
||||||
href={!auth.is_authenticated ? auth.login : auth.logout}
|
MICROSCOPE
|
||||||
>
|
|
||||||
{!auth.is_authenticated ? "Log In" : "Log Out"}
|
|
||||||
</AnchorButton>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!shouldShowAuth()) return null;
|
||||||
|
|
||||||
|
if (isAuthenticated) {
|
||||||
|
const PopoverContent = (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
data-testid="user-email"
|
||||||
|
text={`Logged in as: ${userInfo.email}`}
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
data-testid="log-out"
|
||||||
|
text="Log Out"
|
||||||
|
href={auth.logout}
|
||||||
|
icon={IconNames.LOG_OUT}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover content={PopoverContent}>
|
||||||
|
<Button
|
||||||
|
data-testid="user-info"
|
||||||
|
className={styles.menubarButton}
|
||||||
|
style={{ padding: 0 }}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line no-constant-condition -- disable profile picture until CSP is tweaked */}
|
||||||
|
{userInfo?.picture && false ? (
|
||||||
|
<img alt="profile" size="21px" src={userInfo?.picture} />
|
||||||
|
) : (
|
||||||
|
<span style={{ fontSize: "18px" }}>{scientist}</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginButton = (
|
||||||
|
<Tooltip
|
||||||
|
content="Log in to cellxgene"
|
||||||
|
position="bottom"
|
||||||
|
hoverOpenDelay={globals.tooltipHoverOpenDelay}
|
||||||
|
>
|
||||||
|
<AnchorButton
|
||||||
|
type="button"
|
||||||
|
data-testid="log-in"
|
||||||
|
href={auth.login}
|
||||||
|
className={styles.menubarButton}
|
||||||
|
>
|
||||||
|
Log In
|
||||||
|
</AnchorButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isPromptOpen) {
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
position={PopoverPosition.AUTO_END}
|
||||||
|
isOpen
|
||||||
|
content={<PromptContent setIsPromptOpen={setIsPromptOpen} />}
|
||||||
|
onInteraction={setIsPromptOpen}
|
||||||
|
>
|
||||||
|
{LoginButton}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoginButton;
|
||||||
|
|
||||||
|
function shouldShowAuth() {
|
||||||
|
return auth && auth.requires_client_login;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldShowPrompt() {
|
||||||
|
if (storageGet(KEYS.LOGIN_PROMPT) === LOGIN_PROMPT_OFF) return false;
|
||||||
|
|
||||||
|
return shouldShowAuth && !isAuthenticated;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function PromptContent({ setIsPromptOpen }) {
|
||||||
|
const [isChecked, setIsChecked] = useState(false);
|
||||||
|
|
||||||
|
function handleOKClick() {
|
||||||
|
if (isChecked) {
|
||||||
|
storageSet(KEYS.LOGIN_PROMPT, LOGIN_PROMPT_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPromptOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheckboxChange() {
|
||||||
|
setIsChecked(!isChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ width: "500px" }} elevation={Elevation.TWO}>
|
||||||
|
<p>
|
||||||
|
Logging in will enable you to create your own categories and labels.
|
||||||
|
Logging in later will reset cellxgene to the default view and cause you
|
||||||
|
to lose progress.
|
||||||
|
</p>
|
||||||
|
<Checkbox
|
||||||
|
style={{ width: "230px" }}
|
||||||
|
checked={isChecked}
|
||||||
|
onChange={handleCheckboxChange}
|
||||||
|
data-testid="login-hint-do-not-show-again"
|
||||||
|
>
|
||||||
|
Do not show me this message again
|
||||||
|
</Checkbox>
|
||||||
|
<div
|
||||||
|
style={{ display: "flex", justifyContent: "flex-end", marginTop: 15 }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={handleOKClick}
|
||||||
|
intent="primary"
|
||||||
|
data-testid="login-hint-yes"
|
||||||
|
>
|
||||||
|
Acknowledge
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default Auth;
|
export default Auth;
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
Position,
|
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
ButtonGroup,
|
||||||
NumericInput,
|
|
||||||
Icon,
|
Icon,
|
||||||
|
Intent,
|
||||||
|
NumericInput,
|
||||||
|
Popover,
|
||||||
|
Position,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
|
|
||||||
import { tooltipHoverOpenDelay } from "../../globals";
|
import { tooltipHoverOpenDelay } from "../../globals";
|
||||||
import styles from "./menubar.css";
|
import styles from "./menubar.css";
|
||||||
|
|
||||||
@@ -28,13 +32,13 @@ const Clip = React.memo((props) => {
|
|||||||
pendingClipPercentiles?.clipPercentileMin ?? clipPercentileMin;
|
pendingClipPercentiles?.clipPercentileMin ?? clipPercentileMin;
|
||||||
const clipMax =
|
const clipMax =
|
||||||
pendingClipPercentiles?.clipPercentileMax ?? clipPercentileMax;
|
pendingClipPercentiles?.clipPercentileMax ?? clipPercentileMax;
|
||||||
const activeClipClass =
|
const intent =
|
||||||
clipPercentileMin > 0 || clipPercentileMax < 100
|
clipPercentileMin > 0 || clipPercentileMax < 100
|
||||||
? " bp3-intent-warning"
|
? Intent.INTENT_WARNING
|
||||||
: "";
|
: Intent.NONE;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`bp3-button-group ${styles.menubarButton}`}>
|
<ButtonGroup className={`${styles.menubarButton}`}>
|
||||||
<Popover
|
<Popover
|
||||||
target={
|
target={
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -45,7 +49,8 @@ const Clip = React.memo((props) => {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
data-testid="visualization-settings"
|
data-testid="visualization-settings"
|
||||||
className={`bp3-button bp3-icon-timeline-bar-chart ${activeClipClass}`}
|
intent={intent}
|
||||||
|
icon={IconNames.TIMELINE_BAR_CHART}
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
@@ -126,7 +131,7 @@ const Clip = React.memo((props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</ButtonGroup>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import * as globals from "../../globals";
|
|||||||
import styles from "./menubar.css";
|
import styles from "./menubar.css";
|
||||||
import actions from "../../actions";
|
import actions from "../../actions";
|
||||||
import Clip from "./clip";
|
import Clip from "./clip";
|
||||||
|
|
||||||
import AuthButtons from "./authButtons";
|
import AuthButtons from "./authButtons";
|
||||||
import InformationMenu from "./infoMenu";
|
|
||||||
import Subset from "./subset";
|
import Subset from "./subset";
|
||||||
import UndoRedoReset from "./undoRedo";
|
import UndoRedoReset from "./undoRedo";
|
||||||
import DiffexpButtons from "./diffexpButtons";
|
import DiffexpButtons from "./diffexpButtons";
|
||||||
@@ -42,6 +42,7 @@ import { getEmbSubsetView } from "../../util/stateManager/viewStackHelpers";
|
|||||||
celllist2: state.differential.celllist2,
|
celllist2: state.differential.celllist2,
|
||||||
libraryVersions: state.config?.["library_versions"],
|
libraryVersions: state.config?.["library_versions"],
|
||||||
auth: state.config?.authentication,
|
auth: state.config?.authentication,
|
||||||
|
userInfo: state.userInfo,
|
||||||
undoDisabled: state["@@undoable/past"].length === 0,
|
undoDisabled: state["@@undoable/past"].length === 0,
|
||||||
redoDisabled: state["@@undoable/future"].length === 0,
|
redoDisabled: state["@@undoable/future"].length === 0,
|
||||||
aboutLink: state.config?.links?.["about-dataset"],
|
aboutLink: state.config?.links?.["about-dataset"],
|
||||||
@@ -203,7 +204,6 @@ class MenuBar extends React.PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
dispatch,
|
dispatch,
|
||||||
libraryVersions,
|
|
||||||
disableDiffexp,
|
disableDiffexp,
|
||||||
undoDisabled,
|
undoDisabled,
|
||||||
redoDisabled,
|
redoDisabled,
|
||||||
@@ -211,15 +211,13 @@ class MenuBar extends React.PureComponent {
|
|||||||
clipPercentileMin,
|
clipPercentileMin,
|
||||||
clipPercentileMax,
|
clipPercentileMax,
|
||||||
graphInteractionMode,
|
graphInteractionMode,
|
||||||
aboutLink,
|
|
||||||
showCentroidLabels,
|
showCentroidLabels,
|
||||||
privacyURL,
|
|
||||||
tosURL,
|
|
||||||
categoricalSelection,
|
categoricalSelection,
|
||||||
colorAccessor,
|
colorAccessor,
|
||||||
subsetPossible,
|
subsetPossible,
|
||||||
subsetResetPossible,
|
subsetResetPossible,
|
||||||
enableReembedding,
|
enableReembedding,
|
||||||
|
userInfo,
|
||||||
auth,
|
auth,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { pendingClipPercentiles } = this.state;
|
const { pendingClipPercentiles } = this.state;
|
||||||
@@ -246,13 +244,7 @@ class MenuBar extends React.PureComponent {
|
|||||||
zIndex: 3,
|
zIndex: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AuthButtons auth={auth} />
|
<AuthButtons {...{ auth, userInfo }} />
|
||||||
<InformationMenu
|
|
||||||
libraryVersions={libraryVersions}
|
|
||||||
aboutLink={aboutLink}
|
|
||||||
tosURL={tosURL}
|
|
||||||
privacyURL={privacyURL}
|
|
||||||
/>
|
|
||||||
<UndoRedoReset
|
<UndoRedoReset
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
undoDisabled={undoDisabled}
|
undoDisabled={undoDisabled}
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
|
||||||
import { Button, Popover, Menu, MenuItem, Position } from "@blueprintjs/core";
|
|
||||||
import styles from "./menubar.css";
|
|
||||||
|
|
||||||
const InformationMenu = React.memo((props) => {
|
|
||||||
const { libraryVersions, aboutLink, tosURL, privacyURL } = props;
|
|
||||||
return (
|
|
||||||
<div className={`bp3-button-group ${styles.menubarButton}`}>
|
|
||||||
<Popover
|
|
||||||
content={
|
|
||||||
<Menu>
|
|
||||||
{aboutLink ? (
|
|
||||||
<MenuItem
|
|
||||||
href={aboutLink}
|
|
||||||
target="_blank"
|
|
||||||
icon="document-open"
|
|
||||||
text="About this dataset"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
|
|
||||||
<MenuItem
|
|
||||||
href="https://chanzuckerberg.github.io/cellxgene/"
|
|
||||||
target="_blank"
|
|
||||||
icon="help"
|
|
||||||
text="Help"
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
href="https://join-cellxgene-users.herokuapp.com/"
|
|
||||||
target="_blank"
|
|
||||||
icon="chat"
|
|
||||||
text="Chat"
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
href="https://github.com/chanzuckerberg/cellxgene"
|
|
||||||
target="_blank"
|
|
||||||
icon="git-branch"
|
|
||||||
text="Github"
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
target="_blank"
|
|
||||||
text={
|
|
||||||
libraryVersions && libraryVersions.cellxgene
|
|
||||||
? libraryVersions.cellxgene
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<MenuItem text="MIT License" />
|
|
||||||
{tosURL ? (
|
|
||||||
<MenuItem href={tosURL} target="_blank" text="Terms of Service" />
|
|
||||||
) : null}
|
|
||||||
{privacyURL ? (
|
|
||||||
<MenuItem
|
|
||||||
href={privacyURL}
|
|
||||||
target="_blank"
|
|
||||||
text="Privacy Policy"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Menu>
|
|
||||||
}
|
|
||||||
position={Position.BOTTOM_RIGHT}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
className="bp3-button bp3-icon-info-sign"
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default InformationMenu;
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { AnchorButton, Tooltip } from "@blueprintjs/core";
|
import { AnchorButton, ButtonGroup, Tooltip } from "@blueprintjs/core";
|
||||||
|
import { IconNames } from "@blueprintjs/icons";
|
||||||
import { tooltipHoverOpenDelay } from "../../globals";
|
import { tooltipHoverOpenDelay } from "../../globals";
|
||||||
import styles from "./menubar.css";
|
import styles from "./menubar.css";
|
||||||
|
|
||||||
const UndoRedo = React.memo((props) => {
|
const UndoRedo = React.memo((props) => {
|
||||||
const { undoDisabled, redoDisabled, dispatch } = props;
|
const { undoDisabled, redoDisabled, dispatch } = props;
|
||||||
return (
|
return (
|
||||||
<div className={`bp3-button-group ${styles.menubarButton}`}>
|
<ButtonGroup className={`${styles.menubarButton}`}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content="Undo"
|
content="Undo"
|
||||||
position="bottom"
|
position="bottom"
|
||||||
@@ -14,7 +15,7 @@ const UndoRedo = React.memo((props) => {
|
|||||||
>
|
>
|
||||||
<AnchorButton
|
<AnchorButton
|
||||||
type="button"
|
type="button"
|
||||||
className="bp3-button bp3-icon-undo"
|
icon={IconNames.UNDO}
|
||||||
disabled={undoDisabled}
|
disabled={undoDisabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch({ type: "@@undoable/undo" });
|
dispatch({ type: "@@undoable/undo" });
|
||||||
@@ -32,7 +33,7 @@ const UndoRedo = React.memo((props) => {
|
|||||||
>
|
>
|
||||||
<AnchorButton
|
<AnchorButton
|
||||||
type="button"
|
type="button"
|
||||||
className="bp3-button bp3-icon-redo"
|
icon={IconNames.REDO}
|
||||||
disabled={redoDisabled}
|
disabled={redoDisabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch({ type: "@@undoable/redo" });
|
dispatch({ type: "@@undoable/redo" });
|
||||||
@@ -43,7 +44,7 @@ const UndoRedo = React.memo((props) => {
|
|||||||
data-testid="redo"
|
data-testid="redo"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</ButtonGroup>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export default class MiniHistogram extends React.PureComponent {
|
|||||||
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
|
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
className="bp3-popover-targer"
|
|
||||||
style={{
|
style={{
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
width,
|
width,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// jshint esversion: 6
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default class MiniStackedBar extends React.PureComponent {
|
export default class MiniStackedBar extends React.PureComponent {
|
||||||
@@ -59,7 +58,6 @@ export default class MiniStackedBar extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
className="bp3-popover-targer"
|
|
||||||
style={{
|
style={{
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
width,
|
width,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function drawPointsRegl(regl) {
|
|||||||
uniform float nPoints;
|
uniform float nPoints;
|
||||||
uniform float minViewportDimension;
|
uniform float minViewportDimension;
|
||||||
|
|
||||||
varying vec4 fragColor;
|
varying lowp vec4 fragColor;
|
||||||
|
|
||||||
const float zBottom = 0.99;
|
const float zBottom = 0.99;
|
||||||
const float zMiddle = 0.;
|
const float zMiddle = 0.;
|
||||||
@@ -26,22 +26,22 @@ export default function drawPointsRegl(regl) {
|
|||||||
${glPointSize}
|
${glPointSize}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
bool isNaN, isSelected, isHighlight;
|
bool isBackground, isSelected, isHighlight;
|
||||||
getFlags(flag, isNaN, isSelected, isHighlight);
|
getFlags(flag, isBackground, isSelected, isHighlight);
|
||||||
|
|
||||||
gl_PointSize = pointSize(nPoints, minViewportDimension, isSelected, isHighlight);
|
gl_PointSize = pointSize(nPoints, minViewportDimension, isSelected, isHighlight);
|
||||||
|
|
||||||
float z = isNaN ? zBottom : (isHighlight ? zTop : zMiddle);
|
float z = isBackground ? zBottom : (isHighlight ? zTop : zMiddle);
|
||||||
vec3 xy = projection * vec3(position, 1.);
|
vec3 xy = projection * vec3(position, 1.);
|
||||||
gl_Position = vec4(xy.xy, z, 1.);
|
gl_Position = vec4(xy.xy, z, 1.);
|
||||||
|
|
||||||
float alpha = isNaN ? 0.9 : 1.0;
|
float alpha = isBackground ? 0.9 : 1.0;
|
||||||
fragColor = vec4(color, alpha);
|
fragColor = vec4(color, alpha);
|
||||||
}`,
|
}`,
|
||||||
|
|
||||||
frag: `
|
frag: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
varying vec4 fragColor;
|
varying lowp vec4 fragColor;
|
||||||
void main() {
|
void main() {
|
||||||
if (length(gl_PointCoord.xy - 0.5) > 0.5) {
|
if (length(gl_PointCoord.xy - 0.5) > 0.5) {
|
||||||
discard;
|
discard;
|
||||||
@@ -64,5 +64,15 @@ export default function drawPointsRegl(regl) {
|
|||||||
count: regl.prop("count"),
|
count: regl.prop("count"),
|
||||||
|
|
||||||
primitive: "points",
|
primitive: "points",
|
||||||
|
|
||||||
|
blend: {
|
||||||
|
enable: true,
|
||||||
|
func: {
|
||||||
|
srcRGB: "src alpha",
|
||||||
|
srcAlpha: 1,
|
||||||
|
dstRGB: 0,
|
||||||
|
dstAlpha: "zero",
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ import {
|
|||||||
createColorQuery,
|
createColorQuery,
|
||||||
} from "../../util/stateManager/colorHelpers";
|
} from "../../util/stateManager/colorHelpers";
|
||||||
import renderThrottle from "../../util/renderThrottle";
|
import renderThrottle from "../../util/renderThrottle";
|
||||||
|
import {
|
||||||
const flagSelected = 1;
|
flagBackground,
|
||||||
const flagNaN = 2;
|
flagSelected,
|
||||||
const flagHighlight = 4;
|
flagHighlight,
|
||||||
|
} from "../../util/glHelpers";
|
||||||
|
|
||||||
function createProjectionTF(viewportWidth, viewportHeight) {
|
function createProjectionTF(viewportWidth, viewportHeight) {
|
||||||
/*
|
/*
|
||||||
@@ -135,8 +136,9 @@ class Scatterplot extends React.PureComponent {
|
|||||||
const flags = new Float32Array(nObs);
|
const flags = new Float32Array(nObs);
|
||||||
if (colorByData) {
|
if (colorByData) {
|
||||||
for (let i = 0, len = flags.length; i < len; i += 1) {
|
for (let i = 0, len = flags.length; i < len; i += 1) {
|
||||||
if (!Number.isFinite(colorByData[i])) {
|
const val = colorByData[i];
|
||||||
flags[i] = flagNaN;
|
if (typeof val === "number" && !Number.isFinite(val)) {
|
||||||
|
flags[i] = flagBackground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -528,8 +530,8 @@ class Scatterplot extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<ScatterplotAxis
|
<ScatterplotAxis
|
||||||
minimized={minimized}
|
minimized={minimized}
|
||||||
scatterplotYYaccessor={scatterplotXXaccessor}
|
scatterplotYYaccessor={scatterplotYYaccessor}
|
||||||
scatterplotXXaccessor={scatterplotYYaccessor}
|
scatterplotXXaccessor={scatterplotXXaccessor}
|
||||||
xScale={asyncProps.xScale}
|
xScale={asyncProps.xScale}
|
||||||
yScale={asyncProps.yScale}
|
yScale={asyncProps.yScale}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,26 +8,7 @@ import {
|
|||||||
Colors,
|
Colors,
|
||||||
Icon,
|
Icon,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
|
import { storageGet, storageSet, KEYS } from "../util/localStorage";
|
||||||
const CookieDecision = "cxg.cookieDecision";
|
|
||||||
|
|
||||||
function storageGet(key, defaultValue = null) {
|
|
||||||
try {
|
|
||||||
const val = window.localStorage.getItem(key);
|
|
||||||
if (val === null) return defaultValue;
|
|
||||||
return val;
|
|
||||||
} catch (e) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function storageSet(key, value) {
|
|
||||||
try {
|
|
||||||
window.localStorage.setItem(key, value);
|
|
||||||
} catch {
|
|
||||||
// continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@connect((state) => ({
|
@connect((state) => ({
|
||||||
tosURL: state.config?.parameters?.["about_legal_tos"],
|
tosURL: state.config?.parameters?.["about_legal_tos"],
|
||||||
@@ -37,7 +18,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const { tosURL, privacyURL } = this.props;
|
const { tosURL, privacyURL } = this.props;
|
||||||
const cookieDecision = storageGet(CookieDecision, null);
|
const cookieDecision = storageGet(KEYS.COOKIE_DECISION, null);
|
||||||
const hasDecided = cookieDecision !== null;
|
const hasDecided = cookieDecision !== null;
|
||||||
this.state = {
|
this.state = {
|
||||||
hasDecided,
|
hasDecided,
|
||||||
@@ -55,7 +36,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
|
|
||||||
handleOK = () => {
|
handleOK = () => {
|
||||||
this.setState({ isOpen: false });
|
this.setState({ isOpen: false });
|
||||||
storageSet(CookieDecision, "yes");
|
storageSet(KEYS.COOKIE_DECISION, "yes");
|
||||||
if (window.cookieDecisionCallback instanceof Function) {
|
if (window.cookieDecisionCallback instanceof Function) {
|
||||||
try {
|
try {
|
||||||
window.cookieDecisionCallback();
|
window.cookieDecisionCallback();
|
||||||
@@ -67,7 +48,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
|
|
||||||
handleNo = () => {
|
handleNo = () => {
|
||||||
this.setState({ isOpen: false });
|
this.setState({ isOpen: false });
|
||||||
storageSet(CookieDecision, "no");
|
storageSet(KEYS.COOKIE_DECISION, "no");
|
||||||
};
|
};
|
||||||
|
|
||||||
renderTos() {
|
renderTos() {
|
||||||
@@ -84,7 +65,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
}}
|
}}
|
||||||
href={tosURL}
|
href={tosURL}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener"
|
||||||
>
|
>
|
||||||
terms of service
|
terms of service
|
||||||
</a>
|
</a>
|
||||||
@@ -106,7 +87,7 @@ class TermsPrompt extends React.PureComponent {
|
|||||||
}}
|
}}
|
||||||
href={privacyURL}
|
href={privacyURL}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener"
|
||||||
>
|
>
|
||||||
privacy policy
|
privacy policy
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export const KEYS = {
|
||||||
|
COOKIE_DECISION: "cxg.cookieDecision",
|
||||||
|
LOGIN_PROMPT: "cxg.LOGIN_PROMPT",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function storageGet(key, defaultValue = null) {
|
||||||
|
try {
|
||||||
|
const val = window.localStorage.getItem(key);
|
||||||
|
if (val === null) return defaultValue;
|
||||||
|
return val;
|
||||||
|
} catch (e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function storageSet(key, value) {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(key, value);
|
||||||
|
} catch {
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ const SPLIT_STYLE = {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
|
width: "100%", // There are probably additional styles that we don't want to stack
|
||||||
|
padding: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const FIRST_HALF_STYLE = {
|
const FIRST_HALF_STYLE = {
|
||||||
@@ -40,7 +42,7 @@ export default (props) => {
|
|||||||
) {
|
) {
|
||||||
throw Error("Only pass a single child with text to Truncate");
|
throw Error("Only pass a single child with text to Truncate");
|
||||||
}
|
}
|
||||||
const originalString = children.props.children;
|
const originalString = String(children.props.children);
|
||||||
|
|
||||||
let firstString;
|
let firstString;
|
||||||
let secondString;
|
let secondString;
|
||||||
@@ -58,7 +60,7 @@ export default (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const inheritedColor = children.props.style.color;
|
const inheritedColor = children.props.style?.color;
|
||||||
|
|
||||||
const splitStyle = { ...children.props.style, ...SPLIT_STYLE };
|
const splitStyle = { ...children.props.style, ...SPLIT_STYLE };
|
||||||
const secondHalfContentStyle = {
|
const secondHalfContentStyle = {
|
||||||
@@ -93,6 +95,7 @@ export default (props) => {
|
|||||||
preventOverflow: { enabled: false },
|
preventOverflow: { enabled: false },
|
||||||
hide: { enabled: false },
|
hide: { enabled: false },
|
||||||
}}
|
}}
|
||||||
|
targetProps={{ style: children.props.style }}
|
||||||
>
|
>
|
||||||
{newChildren}
|
{newChildren}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const Annotations = (
|
|||||||
categoryBeingEdited: null,
|
categoryBeingEdited: null,
|
||||||
categoryAddingNewLabel: null,
|
categoryAddingNewLabel: null,
|
||||||
labelEditable: { category: null, label: null },
|
labelEditable: { category: null, label: null },
|
||||||
|
promptForFilename: true,
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -37,10 +38,13 @@ const Annotations = (
|
|||||||
action.config.parameters?.[
|
action.config.parameters?.[
|
||||||
"annotations-data-collection-name-is-read-only"
|
"annotations-data-collection-name-is-read-only"
|
||||||
] ?? false;
|
] ?? false;
|
||||||
|
const promptForFilename =
|
||||||
|
action.config.parameters?.["user_annotation_collection_name_enabled"];
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
dataCollectionNameIsReadOnly,
|
dataCollectionNameIsReadOnly,
|
||||||
dataCollectionName,
|
dataCollectionName,
|
||||||
|
promptForFilename,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+8
@@ -20,6 +20,8 @@ const Controls = (
|
|||||||
scatterplotXXaccessor: null, // just easier to read
|
scatterplotXXaccessor: null, // just easier to read
|
||||||
scatterplotYYaccessor: null,
|
scatterplotYYaccessor: null,
|
||||||
graphRenderCounter: 0 /* integer as <Component key={graphRenderCounter} - a change in key forces a remount */,
|
graphRenderCounter: 0 /* integer as <Component key={graphRenderCounter} - a change in key forces a remount */,
|
||||||
|
|
||||||
|
datasetDrawer: false,
|
||||||
},
|
},
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
@@ -162,6 +164,12 @@ const Controls = (
|
|||||||
scatterplotYYaccessor: null,
|
scatterplotYYaccessor: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**************************
|
||||||
|
Dataset Drawer
|
||||||
|
**************************/
|
||||||
|
case "toggle dataset drawer":
|
||||||
|
return { ...state, datasetDrawer: !state.datasetDrawer };
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import thunk from "redux-thunk";
|
|||||||
import cascadeReducers from "./cascade";
|
import cascadeReducers from "./cascade";
|
||||||
import undoable from "./undoable";
|
import undoable from "./undoable";
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
|
import userInfo from "./userInfo";
|
||||||
import annoMatrix from "./annoMatrix";
|
import annoMatrix from "./annoMatrix";
|
||||||
import obsCrossfilter from "./obsCrossfilter";
|
import obsCrossfilter from "./obsCrossfilter";
|
||||||
import categoricalSelection from "./categoricalSelection";
|
import categoricalSelection from "./categoricalSelection";
|
||||||
@@ -41,6 +42,7 @@ const Reducer = undoable(
|
|||||||
["pointDilation", pointDialation],
|
["pointDilation", pointDialation],
|
||||||
["reembedController", reembedController],
|
["reembedController", reembedController],
|
||||||
["autosave", autosave],
|
["autosave", autosave],
|
||||||
|
["userInfo", userInfo],
|
||||||
]),
|
]),
|
||||||
[
|
[
|
||||||
"annoMatrix",
|
"annoMatrix",
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
const UserInfo = (state = {}, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "initial data load start":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
case "userInfo load complete":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
...action.userInfo,
|
||||||
|
};
|
||||||
|
case "initial data load error":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: action.error,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserInfo;
|
||||||
@@ -8,18 +8,24 @@ PointFlags:
|
|||||||
|
|
||||||
We want a bitmask-like flag structure, but due to webgl limitations
|
We want a bitmask-like flag structure, but due to webgl limitations
|
||||||
must emulate it with floats.
|
must emulate it with floats.
|
||||||
|
|
||||||
|
Supported flags are:
|
||||||
|
|
||||||
|
selected: the point is currently selected
|
||||||
|
highlight: the point is currently highlighted
|
||||||
|
background: the point is background information
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// for JS
|
// for JS
|
||||||
export const flagSelected = 1;
|
export const flagSelected = 1;
|
||||||
export const flagNaN = 2;
|
export const flagBackground = 2;
|
||||||
export const flagHighlight = 4;
|
export const flagHighlight = 4;
|
||||||
|
|
||||||
// for GLSL
|
// for GLSL
|
||||||
export const glPointFlags = `
|
export const glPointFlags = `
|
||||||
|
|
||||||
const float flagSelected = 1.;
|
const float flagSelected = 1.;
|
||||||
const float flagNaN = 2.;
|
const float flagBackground = 2.;
|
||||||
const float flagHighlight = 4.;
|
const float flagHighlight = 4.;
|
||||||
|
|
||||||
bool isLowBitSet(float f) {
|
bool isLowBitSet(float f) {
|
||||||
@@ -32,12 +38,12 @@ export const glPointFlags = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
void getFlags(in float flag,
|
void getFlags(in float flag,
|
||||||
out bool isNaN,
|
out bool isBackground,
|
||||||
out bool isSelected,
|
out bool isSelected,
|
||||||
out bool isHighlight) {
|
out bool isHighlight) {
|
||||||
isSelected = isLowBitSet(flag);
|
isSelected = isLowBitSet(flag);
|
||||||
flag = shiftRightOne(flag);
|
flag = shiftRightOne(flag);
|
||||||
isNaN = isLowBitSet(flag);
|
isBackground = isLowBitSet(flag);
|
||||||
flag = shiftRightOne(flag);
|
flag = shiftRightOne(flag);
|
||||||
isHighlight = isLowBitSet(flag);
|
isHighlight = isLowBitSet(flag);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ create colors scale and RGB array and return as object. Parameters:
|
|||||||
* userColors - optional user color table
|
* userColors - optional user color table
|
||||||
Returns:
|
Returns:
|
||||||
{
|
{
|
||||||
scale: color scale
|
scale: function, mapping label index to color scale
|
||||||
rgb: cell to color mapping
|
rgb: cell label to color mapping
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
function _createColorTable(
|
function _createColorTable(
|
||||||
@@ -70,7 +70,7 @@ function _createColorTable(
|
|||||||
case "color by categorical metadata": {
|
case "color by categorical metadata": {
|
||||||
const data = colorByData.col(colorByAccessor).asArray();
|
const data = colorByData.col(colorByAccessor).asArray();
|
||||||
if (userColors && colorByAccessor in userColors) {
|
if (userColors && colorByAccessor in userColors) {
|
||||||
return createUserColors(data, colorByAccessor, userColors);
|
return createUserColors(data, colorByAccessor, schema, userColors);
|
||||||
}
|
}
|
||||||
return createColorsByCategoricalMetadata(data, colorByAccessor, schema);
|
return createColorsByCategoricalMetadata(data, colorByAccessor, schema);
|
||||||
}
|
}
|
||||||
@@ -91,27 +91,41 @@ function _createColorTable(
|
|||||||
}
|
}
|
||||||
export const createColorTable = memoize(_createColorTable);
|
export const createColorTable = memoize(_createColorTable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create two category label-indexed objects:
|
||||||
|
* - colors: maps label to RGB triplet for that label (used by graph, etc)
|
||||||
|
* - scale: function which given label returns d3 color scale for label
|
||||||
|
* Order doesn't matter - everything is keyed by label value.
|
||||||
|
*/
|
||||||
export function loadUserColorConfig(userColors) {
|
export function loadUserColorConfig(userColors) {
|
||||||
const convertedUserColors = {};
|
const convertedUserColors = {};
|
||||||
Object.keys(userColors).forEach((category) => {
|
Object.keys(userColors).forEach((category) => {
|
||||||
const [colors, scaleMap] = Object.keys(userColors[category]).reduce(
|
const [colors, scaleMap] = Object.keys(userColors[category]).reduce(
|
||||||
(acc, label, i) => {
|
(acc, label) => {
|
||||||
const color = parseRGB(userColors[category][label]);
|
const color = parseRGB(userColors[category][label]);
|
||||||
acc[0][label] = color;
|
acc[0][label] = color;
|
||||||
acc[1][i] = d3.rgb(255 * color[0], 255 * color[1], 255 * color[2]);
|
acc[1][label] = d3.rgb(255 * color[0], 255 * color[1], 255 * color[2]);
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
[{}, {}]
|
[{}, {}]
|
||||||
);
|
);
|
||||||
const scale = (i) => scaleMap[i];
|
const scale = (label) => scaleMap[label];
|
||||||
convertedUserColors[category] = { colors, scale };
|
convertedUserColors[category] = { colors, scale };
|
||||||
});
|
});
|
||||||
return convertedUserColors;
|
return convertedUserColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _createUserColors(data, colorAccessor, userColors) {
|
function _createUserColors(data, colorAccessor, schema, userColors) {
|
||||||
const { colors, scale } = userColors[colorAccessor];
|
const { colors, scale: scaleByLabel } = userColors[colorAccessor];
|
||||||
const rgb = createRgbArray(data, colors);
|
const rgb = createRgbArray(data, colors);
|
||||||
|
|
||||||
|
// color scale function param is INDEX (offset) into schema categories. It is NOT label value.
|
||||||
|
// See createColorsByCategoricalMetadata() for another example.
|
||||||
|
const { categories } = schema.annotations.obsByName[colorAccessor];
|
||||||
|
const categoryMap = new Map();
|
||||||
|
categories.forEach((label, idx) => categoryMap.set(idx, label));
|
||||||
|
const scale = (idx) => scaleByLabel(categoryMap.get(idx));
|
||||||
|
|
||||||
return { rgb, scale };
|
return { rgb, scale };
|
||||||
}
|
}
|
||||||
const createUserColors = memoize(_createUserColors);
|
const createUserColors = memoize(_createUserColors);
|
||||||
|
|||||||
@@ -178,26 +178,26 @@ function promoteTypedArray(o) {
|
|||||||
*/
|
*/
|
||||||
if (isFpTypedArray(o) || Array.isArray(o)) return o;
|
if (isFpTypedArray(o) || Array.isArray(o)) return o;
|
||||||
|
|
||||||
let TyepdArrayCtor;
|
let TypedArrayCtor;
|
||||||
switch (o.constructor) {
|
switch (o.constructor) {
|
||||||
case Int8Array:
|
case Int8Array:
|
||||||
case Uint8Array:
|
case Uint8Array:
|
||||||
case Uint8ClampedArray:
|
case Uint8ClampedArray:
|
||||||
case Int16Array:
|
case Int16Array:
|
||||||
case Uint16Array:
|
case Uint16Array:
|
||||||
TyepdArrayCtor = Float32Array;
|
TypedArrayCtor = Float32Array;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Int32Array:
|
case Int32Array:
|
||||||
case Uint32Array:
|
case Uint32Array:
|
||||||
TyepdArrayCtor = Float64Array;
|
TypedArrayCtor = Float64Array;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error("Unexpected data type returned from server.");
|
throw new Error("Unexpected data type returned from server.");
|
||||||
}
|
}
|
||||||
if (o.constructor === TyepdArrayCtor) return o;
|
if (o.constructor === TypedArrayCtor) return o;
|
||||||
return new TyepdArrayCtor(o);
|
return new TypedArrayCtor(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function matrixFBSToDataframe(arrayBuffers) {
|
export function matrixFBSToDataframe(arrayBuffers) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
## UPDATE (9/30/2020): Starting today, the name Corpora will only be used as the internal project name, with cellxgene Data Portal being the official product name
|
||||||
|
|
||||||
# CXG Data Format Specification
|
# CXG Data Format Specification
|
||||||
|
|
||||||
Document Status: _draft_
|
Document Status: _draft_
|
||||||
|
|||||||
+71
-61
@@ -1,4 +1,4 @@
|
|||||||
# cellxgene release process
|
# cellxgene Release Process
|
||||||
|
|
||||||
_This document defines the release process for cellxgene_
|
_This document defines the release process for cellxgene_
|
||||||
|
|
||||||
@@ -16,71 +16,65 @@ The release process should result in the following side-effects:
|
|||||||
|
|
||||||
Note all release tags pushed to GitHub MUST follow semantic versioning.
|
Note all release tags pushed to GitHub MUST follow semantic versioning.
|
||||||
|
|
||||||
## Recipe
|
## Releasing a Major or Minor Version of cellxgene
|
||||||
|
|
||||||
Follow these steps to create a release.
|
Please scroll down the section below for how to release a patch version. Follow these steps to create a major or minor release.
|
||||||
|
|
||||||
1. Preparation:
|
1. Preparation:
|
||||||
- python3.6 environment, and a cellxgene clone
|
- python3.6 environment, and a cellxgene clone
|
||||||
- Define the release version number, using [semantic versioning](https://semver.org/),
|
- Define the release version number, using [semantic versioning](https://semver.org/), and specifying all three digits (e.g., 0.3.0)
|
||||||
and specifying all three digits (eg, 0.3.0)
|
- Write the release title and release notes and add to [release notes document](https://docs.google.com/document/d/1KnHwkYfhyWO5H8BDcMu7y3ogjvq5Yi4OwpmZ8DB6w0Y/edit)
|
||||||
- Write the release title and release notes and add to
|
2. Create a release branch, eg, `release-version-0.16.0`
|
||||||
[release notes document](https://docs.google.com/document/d/1KnHwkYfhyWO5H8BDcMu7y3ogjvq5Yi4OwpmZ8DB6w0Y/edit)
|
3. In the release branch, run `make create-release-candidate PART=[major | minor | patch]` where you choose major/minor/patch depending on which part of the version is being bumped (e.g., `0.2.9` -> `0.3.0` is minor version bump). This will bump the version and create a release *candidate* version (i.e. `0.3.0-rc.0`).
|
||||||
2. Create a release branch, eg, `release-version`
|
4. Commit and push the new branch. This will trigger tests to ensure that your branch isn't broken.
|
||||||
3. In the release branch:
|
5. Upload the release candidate to Test PyPI by running the command `make release-candidate-to-test-pypi`. (Make sure you are registered for PyPI and Test PyPI and you have write access to the cellxgene PyPI package for both).
|
||||||
- Run `make release-stage-1 PART=[major | minor | patch]` where you choose major/minor/patch depending on which part of the version
|
6. Verify the release candidate in a fresh virtual environment by running `make install-release-test` which installs the cellxgene build you just uploaded the Test PyPI.
|
||||||
is being bumped (eg, 0.2.9->0.3 is minor).
|
7. If you find errors with the release candidate, run `make recreate-release-candidate` to increment the release candidate version (i.e. `0.3.0-rc.0` -> `0.3.0-rc.1`). Then go back to Steps 5 and 6 to re-upload and re-test the new release candidate.
|
||||||
4. Commit and push the new branch
|
8. If everything looks good, push the release to Test PyPI without the release candidate tag by running the command `make release-final-to-test-pypi` (i.e. `0.3.0-rc.1` -> `0.3.0`).
|
||||||
5. Create a PR for the release.
|
- **NOTE:** Once you push the final release version to Test PyPI, you cannot ever re-upload the build again. If you need to make changes to the build, you will have to "burn" the version number and bump the part again and go back to step 1 with a brand new version number. For example, if you upload `0.3.0` to Test PyPI and realize there's a bug, you will have to create a new version `0.4.0` and there will be no `0.3.0` version of cellxgene. This is why testing the release candidate is very important.
|
||||||
- [optional] As needed, conduct PR review.
|
9. Create a PR for the release and conduct a PR review.
|
||||||
6. Merge to the `main` branch
|
10. Merge to the `main` branch.
|
||||||
7. Publish to pypi by performing the following steps (assumes you that you have registered for pypi,
|
11. Publish to PyPI (prod) (assuming you that you have registered for PyPI, and that you have write access to the cellxgene pypi package) by running `make release-final`.
|
||||||
and that you have write access to the cellxgene pypi package):
|
12. Test the installation in a fresh virtual environment by running `pip install --no-cache-dir cellxgene`.
|
||||||
- Build the distribution and upload to test pypi `make release-stage-2`
|
13. Create Github release using the version number and release notes ([instructions](https://help.github.com/articles/creating-releases/)):
|
||||||
- Test the test installation in a fresh virtual environment using `make install-release-test`
|
- Draft new release
|
||||||
- Upload the package to real pypi using `make release-stage-final`
|
- Type version name matching release version number from (1)
|
||||||
- Test the installation in a fresh virtual environment using `pip install cellxgene`
|
- Select `main` as release branch (ensure you merged the release PR)
|
||||||
8. Create Github release using the version number and release notes
|
- Type title `Release {version num}`
|
||||||
([instructions](https://help.github.com/articles/creating-releases/)).
|
- [optional] Check pre-release if this release is not ready for production
|
||||||
- Draft new release
|
- Publish Release
|
||||||
- Type version name matching release version number from (1)
|
|
||||||
- Select `main` as release branch (ensure you merged the release PR)
|
|
||||||
- Type title `Release {version num}`
|
|
||||||
- [optional] Check pre-release if this release is not ready for production
|
|
||||||
- Publish Release
|
|
||||||
|
|
||||||
The optional steps are for testing purposes, and are recommended
|
The optional steps are for testing purposes, and are recommended for publishing any major releases, and any releases that significantly change the packaging (e.g. new bundled files, new dependencies, etc.)
|
||||||
for publishing any major releases, and any releases that significantly
|
|
||||||
change the packaging (e.g. new bundled files, new dependencies, etc.)
|
|
||||||
|
|
||||||
### Point release (special case)
|
### Releasing a Patch Version of cellxgene (special case)
|
||||||
|
|
||||||
To make a bugfix release (a point release) when there are already other changes in `main` we need to do a modified version of our release process. The difference is that instead of using `main` we are going make our release branch off of the tag for the release we want to patch. We cherrypick the commits that we want to include in the patch. Then instead of merging to `main`, we create the release directly off of the branch.
|
To make a bugfix release (a point release/patch release) when there are already other changes in `main` we need to do a modified version of our release process. The difference is that instead of using `main` we are going make our release branch off of the tag for the release we want to patch. We cherrypick the commits that we want to include in the patch. Then instead of merging to `main`, we create the release directly off of the branch.
|
||||||
|
|
||||||
1. (same as above) Preparation:
|
1. (same as above) Preparation:
|
||||||
- python3.6 environment, and a cellxgene clone
|
- python3.6 environment, and a cellxgene clone
|
||||||
- Define the release version number, using [semantic versioning](https://semver.org/),
|
- Define the release version number, using [semantic versioning](https://semver.org/), and specifying all three digits (e.g., 0.3.2) (for this you will update the last digit to represent a bugfix change).
|
||||||
and specifying all three digits (eg, 0.3.0) (for this you will update the last digit to represent a bugfix change)
|
- Write the release title and release notes and add to [release notes document](https://docs.google.com/document/d/1KnHwkYfhyWO5H8BDcMu7y3ogjvq5Yi4OwpmZ8DB6w0Y/edit)
|
||||||
- Write the release title and release notes and add to
|
|
||||||
[release notes document](https://docs.google.com/document/d/1KnHwkYfhyWO5H8BDcMu7y3ogjvq5Yi4OwpmZ8DB6w0Y/edit)
|
|
||||||
2. Create a release branch off of the tag for the release you want to update.
|
2. Create a release branch off of the tag for the release you want to update.
|
||||||
- Checkout the tag for the release you want to fix. ex. if we are fixing 0.9.0: `git checkout 0.9.0`
|
- Checkout the tag for the release you want to fix. For example, if we are fixing 0.9.0: `git checkout 0.9.0`.
|
||||||
- Create a branch from that tag. `git branch release-0.9.1`
|
- Create a branch from that tag. `git branch release-version-0.9.1`
|
||||||
3. Cherrypick the commits that you want included in this patch.
|
3. Cherrypick the commits that you want included in this patch.
|
||||||
- Test that the cherrypicked commits landed and fixed the issue
|
- Test that the cherrypicked commits landed and fixed the issue locally.
|
||||||
- We WILL NOT merge this branch back into `main`, these commits should already exist in `main`.
|
- We **WILL NOT** merge this branch back into `main` as these commits should already exist in `main`.
|
||||||
4. In the release branch:
|
4. In the release branch (i.e. `release-version-0.9.1`), run `make create-release-candidate PART=patch` to bump the patch version and create the first release candidate (i.e. `0.9.1-rc.0`).
|
||||||
- Run `make release-stage-1 PART=patch`.
|
5. Run `make release-candidate-to-test-pypi` to upload the release candidate to Test PyPI.
|
||||||
5. Commit and push the new branch. DO NOT MAKE A PR OR MERGE TO `main`.
|
6. Verify the release candidate in a fresh virtual environment by running `make install-release-test` which installs the cellxgene build you just uploaded the Test PyPI.
|
||||||
- wait for release to pass the tests
|
7. If you find errors with the release candidate, run `make recreate-release-candidate` to increment the release candidate version (i.e. `0.9.1-rc.0` -> `0.9.1-rc.1`). Then go back to Steps 5 and 6 to re-upload and re-test the new release candidate.
|
||||||
6. Publish to pypi by performing the following steps (assumes you that you have registered for pypi,
|
8. If everything looks good, push the final version of the release to Test PyPI without the release candidate tag by running the command `make release-final-to-test-pypi` (i.e. `0.9.1-rc.1` -> `0.9.1`).
|
||||||
and that you have write access to the cellxgene pypi package): - Build the distribution and upload to test pypi `make release-stage-2` - Test the test installation in a fresh virtual environment using `make install-release-test` - Upload the package to real pypi using `make release-stage-final` - Test the installation in a fresh virtual environment using
|
- **NOTE:** Once you push the final release version to Test PyPI, you cannot ever re-upload the build again. If you need to make changes to the build, you will have to "burn" the version number and bump the part again and go back to step 1 with a brand new version number. For example, if you upload `0.9.1` to Test PyPI and realize there's a bug, you will have to create a new version `0.9.2` and there will be no `0.9.1` version of cellxgene. This is why testing the release candidate is very important.
|
||||||
`pip install --no-cache-dir cellxgene`
|
9. Commit and push the new branch. DO NOT MAKE A PR OR MERGE TO `main`.
|
||||||
7. Create Github release using the version number and release notes
|
- Wait for release to pass the tests.
|
||||||
|
10. Publish to PyPI (prod) (assuming you that you have registered for PyPI, and that you have write access to the cellxgene pypi package) by running `make release-final`.
|
||||||
|
11. Test the installation in a fresh virtual environment by running `pip install --no-cache-dir cellxgene`.
|
||||||
|
12. Create Github release using the version number and release notes
|
||||||
([instructions](https://help.github.com/articles/creating-releases/)).
|
([instructions](https://help.github.com/articles/creating-releases/)).
|
||||||
- Draft new release
|
- Draft new release
|
||||||
- Type version name matching release version number from (1)
|
- Type version name matching release version number from (1)
|
||||||
- _Different than above_ Select the release-branch you pushed at step 5 as release branch
|
- [**_Different than above_**] Select the release-branch you pushed at step 5 as release branch
|
||||||
- Type title `Release {version num}`
|
- Type title `Release {version num}`
|
||||||
- [optional] Check pre-release if this release is not ready for production
|
- [optional] Check pre-release if this release is not ready for production
|
||||||
- Publish Release
|
- Publish Release
|
||||||
@@ -93,36 +87,52 @@ _PyPi doesn't allow you to reupload a release with the same version number_
|
|||||||
If you accidentally burned a release number you want to use on prod, you have a few options:
|
If you accidentally burned a release number you want to use on prod, you have a few options:
|
||||||
|
|
||||||
1. OPTION 1: Create distribution `make pydist`; test release locally `pip install dist/<release tarball>`;
|
1. OPTION 1: Create distribution `make pydist`; test release locally `pip install dist/<release tarball>`;
|
||||||
then upload to prod `make release-stage-final`.
|
then upload to prod `make release-final`.
|
||||||
2. OPTION 2: (DANGER) release directly to prod: `make release-directly-to-prod`.
|
2. OPTION 2: (DANGER) release directly to prod: `make release-directly-to-prod`.
|
||||||
3. OPTION 3: If the release was burned on prod as well run from Step 3 again with option
|
3. OPTION 3: If the release was burned on prod as well run from Step 3 again with option PART=patch until you get to an unburned version.
|
||||||
PART=patch until you get to an unburned version.
|
|
||||||
|
|
||||||
### The release doesn't install or fails your tests when you install it
|
### The release doesn't install or fails your tests when you install it
|
||||||
|
|
||||||
Delete it from pypi - Go to pypi.org -> sign in -> go to the cellxgene package -> click manage -> then in the options drop down click delete -> follow the instructions. You will not be able to use that release number again. If it is a minor bug and not a major regression, you can just release a patch.
|
Delete it from pypi - Go to pypi.org -> sign in -> go to the cellxgene package -> click manage -> then in the options drop down click delete -> follow the instructions. You will not be able to use that release number again. If it is a minor bug and not a major regression, you can just release a patch.
|
||||||
|
|
||||||
### If you need to run stage final on a different computer than stage 2
|
### If you need to run the final upload to PyPI (prod) on a different computer than where you ran the command to upload to Test PyPI.
|
||||||
|
|
||||||
If you run stage final without running stage 2 first, the dist will not have been build on the computer running stage final. The solution is to run `make release-directly-to-prod`. This both builds the distribution files and then releases directly to prod pypi.org.
|
If you run `make release-final` without running `make release-final-to-test-pypi` first, the dist will not have been build on the computer running the final PyPI push. The solution is to run `make release-directly-to-prod`. This both builds the distribution files and then releases directly to prod pypi.org.
|
||||||
|
|
||||||
## Stage Details
|
## Command Details
|
||||||
|
|
||||||
### Stage 1 - `make release-stage-1`
|
### Initial creation stage - `make create-release-candidate PART=[major | minor | patch]`
|
||||||
|
|
||||||
1. Pip installs requirements-dev
|
1. Pip installs requirements-dev
|
||||||
2. Bumps version by [PART]
|
2. Bumps version by [PART] and creates the first release candidate.
|
||||||
3. Deletes build directory, client/build, dist and cellxgene.egg-info
|
3. Deletes build directory, client/build, dist and cellxgene.egg-info
|
||||||
4. Creates the package-lock.json
|
4. Creates the package-lock.json
|
||||||
|
|
||||||
### Stage 2 - `make release-stage-2`
|
### Test PyPI upload stage - `make release-candidate-to-test-pypi`
|
||||||
|
|
||||||
1. Pip installs requirements-dev
|
1. Pip installs requirements-dev
|
||||||
2. Builds client and server
|
2. Builds client and server
|
||||||
3. Creates distribution release (sdist)
|
3. Creates distribution release (sdist)
|
||||||
4. Uploads to test.pypi.org
|
4. Uploads to test.pypi.org
|
||||||
|
|
||||||
|
### Recreating release candidate stage(s) - `make recreate-release-candidate`
|
||||||
|
|
||||||
### Stage final - `make release-stage-final`
|
1. Pip installs requirements-dev
|
||||||
|
2. Bumps release candidate version number.
|
||||||
|
3. Deletes build directory, client/build, dist and cellxgene.egg-info
|
||||||
|
4. Creates the package-lock.json
|
||||||
|
|
||||||
|
### Penultimate stage, final release to Test PyPI - `make release-final-to-test-pypi`
|
||||||
|
1. Pip installs requirements-dev
|
||||||
|
2. Removes release candidate tag from the version number.
|
||||||
|
3. Deletes build directory, client/build, dist and cellxgene.egg-info
|
||||||
|
4. Creates the package-lock.json
|
||||||
|
5. Pip installs requirements-dev
|
||||||
|
6. Builds client and server
|
||||||
|
7. Creates distribution release (sdist)
|
||||||
|
8. Uploads to test.pypi.org
|
||||||
|
|
||||||
|
### Final stage - `make release-final`
|
||||||
|
|
||||||
** Does not build distribution **
|
** Does not build distribution **
|
||||||
1. Uploads to pypi.org
|
1. Uploads to pypi.org
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
# Cellxgene Schema Guide
|
||||||
|
|
||||||
|
Datasets included in the [data portal](https://cellxgene.cziscience.com/) and hosted cellxgene need to follow the schema
|
||||||
|
described [here](https://github.com/chanzuckerberg/corpora-data-portal/blob/main/backend/schema/corpora_schema.md). That
|
||||||
|
schema defines some required fields, requirements about feature labels, and some optional fields that mostly help with
|
||||||
|
presentation.
|
||||||
|
|
||||||
|
The number of fields is rather low, and we expect that information needed to populate those fields should either already
|
||||||
|
be present in datasets prepared by a submitter or be easy to obtain. However, this still leaves the task of actually
|
||||||
|
manipulating the dataset so that it follows the schema: adjusting field names, ensuring proper ontologies are used,
|
||||||
|
converting gene symbols to a common set, etc. This can be tedious and error-prone, and at the beginning of the hosted
|
||||||
|
cellxgene project, this was always done with engineering support. As we increase the rate at which we add data, we want
|
||||||
|
to eliminate the need for engineering support so that ultimately submitters themselves can create files that follow the
|
||||||
|
schema.
|
||||||
|
|
||||||
|
## `cellxgene schema apply`
|
||||||
|
|
||||||
|
To enable this, we have a new cellxgene subcommand, `cellxgene schema`, that handles applying and verifying the schema.
|
||||||
|
Its first subcommand, `cellxgene schema apply`, takes three inputs:
|
||||||
|
|
||||||
|
1. A source h5ad file. The input needs to be an AnnData file, so if a submitter has, say, a serialized Seurat or
|
||||||
|
SingleCellExperiment object, it needs to be converted to AnnData first. This can be done with
|
||||||
|
[sceasy](https://github.com/cellgeni/sceasy) or via
|
||||||
|
[Seurat](https://satijalab.org/seurat/v3.1/conversion_vignette.html).
|
||||||
|
2. A configuration yaml file that describes the fields to add and conversions to apply (see below).
|
||||||
|
3. A name for the new h5ad file that should follow the schema.
|
||||||
|
|
||||||
|
### Configuration yaml
|
||||||
|
|
||||||
|
The configuration yaml file describes how to apply the schema. This is an example of a "skeleton" yaml that has all the
|
||||||
|
fields required for the 1.0.0 schema but is not yet filled in with any logic:
|
||||||
|
|
||||||
|
```
|
||||||
|
uns:
|
||||||
|
version:
|
||||||
|
corpora_schema_version: 1.0.0
|
||||||
|
corpora_encoding_version: 0.1.0
|
||||||
|
contributors:
|
||||||
|
title:
|
||||||
|
layer_descriptions:
|
||||||
|
preprint_doi:
|
||||||
|
publication_doi:
|
||||||
|
organism_ontology_term_id:
|
||||||
|
obs:
|
||||||
|
tissue_ontology_term_id:
|
||||||
|
assay_ontology_term_id:
|
||||||
|
disease_ontology_term_id:
|
||||||
|
cell_type_ontology_term_id:
|
||||||
|
sex:
|
||||||
|
ethnicity_ontology_term_id:
|
||||||
|
development_stage_ontology_term_id:
|
||||||
|
fixup_gene_symbols:
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Unstructured metadata
|
||||||
|
The first section is `uns`, which includes metadata fields that describe the whole dataset (see
|
||||||
|
[here](https://anndata.readthedocs.io/en/latest/) for further description of `uns` and `obs`.).
|
||||||
|
|
||||||
|
The first line is `version`, which is required for most of our tooling to work. The schema version is set at
|
||||||
|
1.0.0 in the example above, but of course for future versions that should be changed.
|
||||||
|
|
||||||
|
Next is `contributors` which describes who is adding the dataset to the portal. If you consult the schema, you see that
|
||||||
|
contributors is a list where each element can have `name`, `email`, and `institution`. So when filled out, the
|
||||||
|
`contributors` field should look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
contributors:
|
||||||
|
- name: Mary B. Scientist
|
||||||
|
email: mbs@singlecell.edu
|
||||||
|
institution: Single-Cell University
|
||||||
|
- name: Robert J. Scientist
|
||||||
|
email: rjs@usingle.edu
|
||||||
|
institution: University of Single Cell
|
||||||
|
```
|
||||||
|
|
||||||
|
`title` is the name of the dataset, and is just a string that gets displayed in the portal and cellxgene to identify the
|
||||||
|
dataset.
|
||||||
|
|
||||||
|
`layer_descriptions` is free text descriptions of the different
|
||||||
|
[layers](https://anndata.readthedocs.io/en/latest/anndata.AnnData.layers.html) of the AnnData file. It should look like
|
||||||
|
this when complete, depending on what layers are present:
|
||||||
|
```
|
||||||
|
layer_descriptions:
|
||||||
|
X: CPM and logged
|
||||||
|
raw.X: raw
|
||||||
|
```
|
||||||
|
Note that one of the layers needs to be "raw", that is, the AnnData file must contain raw counts.
|
||||||
|
|
||||||
|
The two DOI fields are optional but can be included if the dataset is associated with a publication or preprint. Note
|
||||||
|
that the DOI should be a full url:
|
||||||
|
```
|
||||||
|
publication_doi: https://doi.org/10.1073%2Fpnas.83.15.5372
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, the `organism_ontology_term_id` field is the species of the donor organism from the NCBITaxon ontology. The
|
||||||
|
value for _Homo sapiens_ is `NCBITaxon:9606`:
|
||||||
|
```
|
||||||
|
organism_ontology_term_id: NCBITaxon:9606
|
||||||
|
```
|
||||||
|
Note that the schema also requires a human-readable `organism` field, but this doesn't need to be included in the yaml.
|
||||||
|
When the `cellxgene schema apply` script encounters an ontology field, it looks up the label for the term(s) and inserts it
|
||||||
|
into the appropriate field.
|
||||||
|
|
||||||
|
|
||||||
|
#### Observation metadata
|
||||||
|
The next section is `obs`, which is metadata than can vary for each observation (and "observation" usually means cell).
|
||||||
|
These fields are all ontology fields except for `sex`, which has its own enumerated set of permitted values.
|
||||||
|
|
||||||
|
There are two ways to fill in the `obs` fields. The first is useful when there is only one value for all the
|
||||||
|
observations in the dataset. This is not uncommon, for example all cells often come from the same assay. In that case
|
||||||
|
just insert the ontology term:
|
||||||
|
```
|
||||||
|
assay_ontology_term_id: EFO:0009922
|
||||||
|
```
|
||||||
|
|
||||||
|
The second is for when there is an existing field in the dataset that needs to be mapped to the schema field. For
|
||||||
|
example, the submitter may have included cell type annotations in a field called `CellType`, and those annotations may
|
||||||
|
just be free text. This doesn't follow the schema because it needs to be in `cell_type_ontology_term_id` and
|
||||||
|
`cell_type`, and it needs ontology terms and labels, not just any text. In that case the field can be a dictionary:
|
||||||
|
|
||||||
|
```
|
||||||
|
cell_type_ontology_term_id:
|
||||||
|
CellType:
|
||||||
|
t-cell: CL:0000084
|
||||||
|
b-cell: CL:0000236
|
||||||
|
```
|
||||||
|
|
||||||
|
This will look at the `obs.CellType` field in the dataset, and where it has the value "t-cell", it will insert
|
||||||
|
`CL:0000084` into `cell_type_ontology_term_id` and its label `T cell` into `cell_type`.
|
||||||
|
|
||||||
|
Now there are often situations where there is no valid ontology term for some field. For example, the dataset may have
|
||||||
|
been produced via an assay not present in `EFO`. Or, a particular cell type may have no entry in `CL`. In that case, a
|
||||||
|
free text description can be used in the `ontology_term_id` field:
|
||||||
|
|
||||||
|
```
|
||||||
|
assay_ontology_term_id: Sci-Plex
|
||||||
|
cell_type_ontology_term_id:
|
||||||
|
CellType:
|
||||||
|
t-cell: CL:0000084
|
||||||
|
b-cell: CL:0000236
|
||||||
|
new cell type: new cell type
|
||||||
|
```
|
||||||
|
|
||||||
|
In these cases, the `cellxgene schema apply` script will leave the ontology field blank and move the free text
|
||||||
|
description into the label field. So the `assay_ontology_term_id` in the new dataset would be `""` but `assay` would be
|
||||||
|
`Sci-Plex`.
|
||||||
|
|
||||||
|
|
||||||
|
#### Gene symbol harmonization
|
||||||
|
|
||||||
|
The last section describes how gene symbol conversion should be applied to each of the layers. This is similar to the
|
||||||
|
`layer_descriptions` field above, but there are only three permitted values: `raw`, `log1p`, and `sqrt`:
|
||||||
|
|
||||||
|
```
|
||||||
|
fixup_gene_symbols:
|
||||||
|
X: log1p
|
||||||
|
raw.X: raw
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells the script how each each layer was transformed from raw values that can be directly summed. `raw` means that
|
||||||
|
the layer contains raw counts or some linear tranformation of raw counts. `log1p` means that the layer has `log(X + 1)`
|
||||||
|
for each the raw `X` values. `sqrt` means `sqrt(X)` (this is not common). For layers produced by Seurat's normalization
|
||||||
|
or SCTransform functions, the correct choice is usually `log1p`.
|
||||||
|
|
||||||
|
|
||||||
|
### `cellxgene schema validate`
|
||||||
|
|
||||||
|
The next `cellxgene schema` subcommand is `cellxgene schema validate`, and it validates that a given h5ad follows a
|
||||||
|
version of the schema. It accepts two parameters:
|
||||||
|
|
||||||
|
1. The h5ad file to check
|
||||||
|
2. The version of the schema to check against.
|
||||||
|
|
||||||
|
If the validation succeeds, the command will have a zero exit code. If it does not, it will have a non-zero exit code
|
||||||
|
and will print validation failure messages.
|
||||||
+4
-2
@@ -13,6 +13,8 @@ nav:
|
|||||||
url: posts/install
|
url: posts/install
|
||||||
- title: Gallery
|
- title: Gallery
|
||||||
url: posts/gallery
|
url: posts/gallery
|
||||||
|
- title: Cellxgene data portal
|
||||||
|
url: https://cellxgene.cziscience.com/
|
||||||
- title: Demo datasets
|
- title: Demo datasets
|
||||||
url: posts/demo-data
|
url: posts/demo-data
|
||||||
- title: Preparing your data
|
- title: Preparing your data
|
||||||
@@ -31,7 +33,7 @@ nav:
|
|||||||
url: posts/roadmap
|
url: posts/roadmap
|
||||||
- title: Contributing (ideas or code)
|
- title: Contributing (ideas or code)
|
||||||
url: posts/contribute
|
url: posts/contribute
|
||||||
|
- title: Extensions
|
||||||
|
url: posts/extensions
|
||||||
- title: Contact & finding help
|
- title: Contact & finding help
|
||||||
url: posts/contact
|
url: posts/contact
|
||||||
- title: cellxgene.cziscience.com
|
|
||||||
url: posts/cellxgene_cziscience_com
|
|
||||||
|
|||||||
@@ -0,0 +1,446 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
|
<title>cellxgene.cziscience.com | cellxgene</title>
|
||||||
|
<meta name="generator" content="Jekyll v3.8.7" />
|
||||||
|
<meta property="og:title" content="cellxgene.cziscience.com" />
|
||||||
|
<meta property="og:locale" content="en_US" />
|
||||||
|
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
||||||
|
<meta property="og:description" content="An interactive explorer for single-cell transcriptomics data" />
|
||||||
|
<link rel="canonical" href="https://chanzuckerberg.github.io/cellxgene/deprecated/cellxgene_cziscience_com.html" />
|
||||||
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/deprecated/cellxgene_cziscience_com.html" />
|
||||||
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{"@type":"WebPage","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"cellxgene.cziscience.com","description":"An interactive explorer for single-cell transcriptomics data","url":"https://chanzuckerberg.github.io/cellxgene/deprecated/cellxgene_cziscience_com.html","@context":"https://schema.org"}</script>
|
||||||
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=f70dffced52a32aada1a22504c841e97e941401a">
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<header>
|
||||||
|
<img src="/cellxgene/cellxgene-logo.png" alt="cellxgene" />
|
||||||
|
|
||||||
|
<p>An interactive explorer for single-cell transcriptomics data</p>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/" class="btn">Quick start</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/install" class="btn">Installation</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/gallery" class="btn">Gallery</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">All other datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/prepare" class="btn">Preparing your data</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/launch" class="btn">Launching cellxgene</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/hosted" class="btn">Hosting cellxgene</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/annotations" class="btn">Annotating data</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/methods" class="btn">Methods</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/troubleshooting" class="btn">Troubleshooting</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/roadmap" class="btn">Roadmap</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/contribute" class="btn">Contributing (ideas or code)</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/extensions" class="btn">Extensions</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1 id="cellxgeneczisciencecom">cellxgene.cziscience.com</h1>
|
||||||
|
|
||||||
|
<p>Chan Zuckerberg has an online repository of public single-cell datasets for exploration with cellxgene.</p>
|
||||||
|
|
||||||
|
<p>If you have a public dataset which you would like hosted for visualization on this site,
|
||||||
|
with a link to embed on your own site, please drop us a note at <a href="mailto:cellxgene@chanzuckerberg.com">cellxgene@chanzuckerberg.com</a>.</p>
|
||||||
|
|
||||||
|
<table class="fixed-layout">
|
||||||
|
<thead style="width: 100%">
|
||||||
|
<tr>
|
||||||
|
<th>cellxgene link</th>
|
||||||
|
<th>More Information</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody style="width: 100%">
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/krasnow_lab_human_lung_cell_atlas_10x-1.cxg/" target="_blank">Krasnow Lab Human Lung Cell Atlas, 10X</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="http://cmgm-new.stanford.edu/krasnow/">Krasnow Lab</a>,
|
||||||
|
<a href="https://github.com/krasnowlab/hlca">HLCA website</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/krasnow_lab_human_lung_cell_atlas_smartseq2-2.cxg/" target="_blank">Krasnow Lab Human Lung Cell Atlas, Smart-seq2</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="http://cmgm-new.stanford.edu/krasnow/">Krasnow Lab</a>,
|
||||||
|
<a href="https://github.com/krasnowlab/hlca">HLCA website</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/human_cell_landscape-3.cxg/" target="_blank">Human Cell Landscape</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://person.zju.edu.cn/en/ggj">Guo Lab</a>,
|
||||||
|
<a href="http://bis.zju.edu.cn/HCL/">HCL website</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/human_fetal_liver_single_cell_transcriptome-13.cxg/" target="_blank">Human fetal liver single cell transcriptome data</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.ebi.ac.uk/arrayexpress/experiments/E-MTAB-7407/">E-MTAB-7407</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/cell_atlas_of_thymic_development-14.cxg/" target="_blank">A cell atlas of human thymic development defines T cell repertoire formation</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.ebi.ac.uk/arrayexpress/experiments/E-MTAB-8581/">E-MTAB-8581</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/cellular_census_of_human_lungs_alveoli_and_parenchyma-15.cxg/" target="_blank">A cellular census of human lungs identifies novel cell states in health and in asthma - parenchyma</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://asthma.cellgeni.sanger.ac.uk/">asthma.cellgeni.sanger.ac.uk</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/cellular_census_of_human_lungs_nasal-16.cxg/" target="_blank">A cellular census of human lungs identifies novel cell states in health and in asthma - nasal</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://asthma.cellgeni.sanger.ac.uk/">asthma.cellgeni.sanger.ac.uk</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/cellular_census_of_human_lungs_bronchi-17.cxg/" target="_blank">A cellular census of human lungs identifies novel cell states in health and in asthma - bronchi</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://asthma.cellgeni.sanger.ac.uk/">asthma.cellgeni.sanger.ac.uk</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/ischaemic_sensitivity_of_human_tissue_by_single_cell_RNA_seq_lung-18.cxg/" target="_blank">Ischaemic sensitivity of human tissue by single cell RNA seq - lung</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://data.humancellatlas.org/explore/projects/c4077b3c-5c98-4d26-a614-246d12c2e5d7">HCA</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/ischaemic_sensitivity_of_human_tissue_by_single_cell_RNA_seq_spleen-19.cxg/" target="_blank">Ischaemic sensitivity of human tissue by single cell RNA seq - spleen</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://data.humancellatlas.org/explore/projects/c4077b3c-5c98-4d26-a614-246d12c2e5d7">HCA</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/ischaemic_sensitivity_of_human_tissue_by_single_cell_RNA_seq_oesophagus-20.cxg/" target="_blank">Ischaemic sensitivity of human tissue by single cell RNA seq - oesophagus</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://data.humancellatlas.org/explore/projects/c4077b3c-5c98-4d26-a614-246d12c2e5d7">HCA</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/spatio_temporal_immune_zonation_of_the_human_kidney-21.cxg/" target="_blank">Spatio-temporal immune zonation of the human kidney</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.kidneycellatlas.org/">www.kidneycellatlas.org</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/fetal_maternal_interface_10x-22.cxg/" target="_blank">Reconstructing the human first trimester fetal-maternal interface using single cell transcriptomics - 10x</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.ebi.ac.uk/arrayexpress/experiments/E-MTAB-6701/">E-MTAB-6701</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/fetal_maternal_interface_smartseq2-23.cxg/" target="_blank">Reconstructing the human first trimester fetal-maternal interface using single cell transcriptomics - SmartSeq2</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.ebi.ac.uk/arrayexpress/experiments/E-MTAB-6701/">E-MTAB-6701</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/gut_cell_atlas-24.cxg/" target="_blank">Gut Cell Atlas</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.gutcellatlas.org/">www.gutcellatlas.org</a>,
|
||||||
|
<a href="https://www.covid19cellatlas.org/">covid19cellatlas.org</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_cell_atlas_of_peripheral_immune_response_to_SARS_CoV_2_infection-25.cxg/" target="_blank">A single-cell atlas of the peripheral immune response to severe COVID-19</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://blishlab.sites.stanford.edu/">Blish Lab</a>,
|
||||||
|
<a href="https://www.medrxiv.org/content/10.1101/2020.04.17.20069930v1">medRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Atlas_of_Healthy_and_SHIV_Infected_Non_Human_Primate_Lung_and_Ileum_ACE2+_Cells_ileum-12.cxg/" target="_blank">Atlas of Healthy and SHIV-Infected Non-Human Primate Lung and Ileum ACE2+ Cells - Ileum</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP807/atlas-of-healthy-and-shiv-infected-non-human-primate-lung-and-ileum-ace2-cells?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Atlas_of_Healthy_and_SHIV_Infected_Non_Human_Primate_Lung_and_Ileum_ACE2+_Cells_lung-11.cxg/" target="_blank">Atlas of Healthy and SHIV-Infected Non-Human Primate Lung and Ileum ACE2+ Cells - Lung</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP807/atlas-of-healthy-and-shiv-infected-non-human-primate-lung-and-ileum-ace2-cells?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Allergic_inflammatory_memory_in_human_respiratory_epithelial_progenitor_cells_epithelial-10.cxg/" target="_blank">Allergic inflammatory memory in human respiratory epithelial progenitor cells - epithelial cells</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP253/allergic-inflammatory-memory-in-human-respiratory-epithelial-progenitor-cells?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Allergic_inflammatory_memory_in_human_respiratory_epithelial_progenitor_cells_scraping-9.cxg/" target="_blank">Allergic inflammatory memory in human respiratory epithelial progenitor cells - nasal scrapings</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP253/allergic-inflammatory-memory-in-human-respiratory-epithelial-progenitor-cells?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Allergic_inflammatory_memory_in_human_respiratory_epithelial_progenitor_cells_surgical-8.cxg/" target="_blank">Allergic inflammatory memory in human respiratory epithelial progenitor cells - surgical</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP253/allergic-inflammatory-memory-in-human-respiratory-epithelial-progenitor-cells?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Allergic_inflammatory_memory_in_human_respiratory_epithelial_progenitor_cells_nasalsss-26.cxg/" target="_blank">Allergic inflammatory memory in human respiratory epithelial progenitor cells - nasal SSS</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP253/allergic-inflammatory-memory-in-human-respiratory-epithelial-progenitor-cells?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/ACE2_and_TMPRSS2_expression_in_human_non_inflamed_terminal_ileum_epithelial-7.cxg/" target="_blank">ACE2 and TMPRSS2 expression in human non-inflamed terminal ileum - epithelial cells</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP812/ace2-and-tmprss2-expression-in-human-non-inflamed-terminal-ileum?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/ACE2_and_TMPRSS2_expression_in_human_non_inflamed_terminal_ileum-6.cxg/" target="_blank">ACE2 and TMPRSS2 expression in human non-inflamed terminal ileum</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP812/ace2-and-tmprss2-expression-in-human-non-inflamed-terminal-ileum?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Human_Lung_HIV_TB_Co_infection_ACE2+_Cells-5.cxg/" target="_blank">Human Lung HIV-TB Co-infection ACE2+ Cells</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP814/human-lung-hiv-tb-co-infection-ace2-cells?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Epithelial_Cells_in_NHP_mTB_Granuloma_and_Uninvolved_Lung-4.cxg/" target="_blank">Epithelial Cells in NHP mTB Granuloma and Uninvolved Lung</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP806/epithelial-cells-in-nhp-mtb-granuloma-and-uninvolved-lung?scpbr=the-alexandria-project">Single Cell Portal</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_EC-49.cxg/
|
||||||
|
" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_SFG-50.cxg/
|
||||||
|
" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease: Superior Frontal Gyrus</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_EC_astrocytes-51.cxg/
|
||||||
|
" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease: Astrocytes in EC</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_EC_excitatoryNeurons-52.cxg/
|
||||||
|
" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease: Excitatory Neurons in EC</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_EC_inhibitoryNeurons-53.cxg/
|
||||||
|
" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease: Inhibitory Neurons in EC</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_EC_microglia-54.cxg/
|
||||||
|
" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease: Microglia in EC</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_SFG_astrocytes-55.cxg/
|
||||||
|
" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease: Astrocytes in SFG</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_SFG_excitatoryNeurons-56.cxg/
|
||||||
|
" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease: Excitatory Neurons in SFG</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_SFG_inhibitoryNeurons-57.cxg/" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease: Inhibitory Neurons in SFG</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/kampmann_lab_human_AD_snRNAseq_SFG_microglia-58.cxg/" target="_blank">Selective Neuronal Vulnerability in Alzheimer's Disease: Microglia in SFG</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://kampmannlab.ucsf.edu/">Kampmann Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.04.04.025825v2">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_cell_gene_expression_profiling_of_SARS_CoV_2_infected_human_cell_lines_H1299-27.cxg/" target="_blank">Single-cell gene expression profiling of SARS-CoV-2 infected human cell lines - H1299</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.mdc-berlin.de/landthaler#t-single-cellsars-cov-2">Landthaler Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.05.05.079194v1">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_cell_gene_expression_profiling_of_SARS_CoV_2_infected_human_cell_lines_Calu_3-28.cxg/" target="_blank">Single-cell gene expression profiling of SARS-CoV-2 infected human cell lines - Calu-3</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.mdc-berlin.de/landthaler#t-single-cellsars-cov-2">Landthaler Lab</a>,
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.05.05.079194v1">BioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_cell_drug_screening_a549-42.cxg/" target="_blank">Single-cell drug screening - A549</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://github.com/cole-trapnell-lab/sci-plex">Trapnell Lab Github</a>,
|
||||||
|
<a href="https://science.sciencemag.org/content/367/6473/45">Science</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_cell_drug_screening_k562-43.cxg/" target="_blank">Single-cell drug screening - K562</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://github.com/cole-trapnell-lab/sci-plex">Trapnell Lab Github</a>,
|
||||||
|
<a href="https://science.sciencemag.org/content/367/6473/45">Science</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_cell_drug_screening_mcf7-44.cxg/" target="_blank">Single-cell drug screening - MCF7</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://github.com/cole-trapnell-lab/sci-plex">Trapnell Lab Github</a>,
|
||||||
|
<a href="https://science.sciencemag.org/content/367/6473/45">Science</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.prod.single-cell.czi.technology/d/Molecular_atlas_of_cell_types_and_zonation_in_the_brain_vasculature-48.cxg/" target="_blank">A molecular atlas of cell types and zonation in the brain vasculature</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="http://betsholtzlab.org/VascularSingleCells/database.html">Betsholtz Lab</a>,
|
||||||
|
<a href="https://www.nature.com/articles/nature25739">Nature</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_soma_transcriptomics_AT8-45.cxg/" target="_blank">Single Soma Transcriptomics - AT8</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.05.11.088591v1">bioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_soma_transcriptomics_MAP2-46.cxg/" target="_blank">Single Soma Transcriptomics - MAP2</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.05.11.088591v1">bioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_soma_transcriptomics_MAP2AT8-47.cxg/" target="_blank">Single Soma Transcriptomics - MAP2AT8</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.05.11.088591v1">bioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://cellxgene.cziscience.com/d/Single_cell_longitudinal_analysis_of_SARS_CoV_2_infection_in_human_bronchial_epithelial_cells-29.cxg/" target="_blank">Single-cell longitudinal analysis of SARS-CoV-2 infection in human bronchial epithelial cells</a></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.biorxiv.org/content/10.1101/2020.05.06.081695v2">bioRxiv preprint</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
|
||||||
|
<p>This project is maintained by <a href="https://github.com/chanzuckerberg">chanzuckerberg</a></p>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<script src="/cellxgene/assets/js/scale.fix.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+10
-6
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","@type":"WebSite","headline":"Index","url":"https://chanzuckerberg.github.io/cellxgene/","name":"cellxgene","@context":"https://schema.org"}</script>
|
{"@type":"WebSite","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"Index","description":"An interactive explorer for single-cell transcriptomics data","url":"https://chanzuckerberg.github.io/cellxgene/","name":"cellxgene","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=f70dffced52a32aada1a22504c841e97e941401a">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -82,13 +86,13 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/extensions" class="btn">Extensions</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>annotations | cellxgene</title>
|
<title>annotations | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="annotations" />
|
<meta property="og:title" content="annotations" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="Creating annotations" />
|
<meta name="description" content="Creating annotations" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/annotations.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/annotations.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Creating annotations","@type":"WebPage","headline":"annotations","url":"https://chanzuckerberg.github.io/cellxgene/posts/annotations.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/annotations.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"annotations","description":"Creating annotations","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>Contact | cellxgene</title>
|
<title>Contact | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="Contact" />
|
<meta property="og:title" content="Contact" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="Contact" />
|
<meta name="description" content="Contact" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/contact.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/contact.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Contact","@type":"WebPage","headline":"Contact","url":"https://chanzuckerberg.github.io/cellxgene/posts/contact.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/contact.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"Contact","description":"Contact","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn"><b>Contact & finding help</b></a><br>
|
<a href="/cellxgene/posts/contact" class="btn"><b>Contact & finding help</b></a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>Code of conduct | cellxgene</title>
|
<title>Code of conduct | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="Code of conduct" />
|
<meta property="og:title" content="Code of conduct" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/contribute.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/contribute.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","headline":"Code of conduct","url":"https://chanzuckerberg.github.io/cellxgene/posts/contribute.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/contribute.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"Code of conduct","description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>demo-data | cellxgene</title>
|
<title>demo-data | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="demo-data" />
|
<meta property="og:title" content="demo-data" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="Demo datasets" />
|
<meta name="description" content="Demo datasets" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/demo-data.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/demo-data.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Demo datasets","@type":"WebPage","headline":"demo-data","url":"https://chanzuckerberg.github.io/cellxgene/posts/demo-data.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/demo-data.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"demo-data","description":"Demo datasets","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn"><b>Demo datasets</b></a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn"><b>Demo datasets</b></a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
|
<title>Extensions | cellxgene</title>
|
||||||
|
<meta name="generator" content="Jekyll v3.8.7" />
|
||||||
|
<meta property="og:title" content="Extensions" />
|
||||||
|
<meta property="og:locale" content="en_US" />
|
||||||
|
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
||||||
|
<meta property="og:description" content="An interactive explorer for single-cell transcriptomics data" />
|
||||||
|
<link rel="canonical" href="https://chanzuckerberg.github.io/cellxgene/posts/extensions.html" />
|
||||||
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/extensions.html" />
|
||||||
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{"@type":"WebPage","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"Extensions","description":"An interactive explorer for single-cell transcriptomics data","url":"https://chanzuckerberg.github.io/cellxgene/posts/extensions.html","@context":"https://schema.org"}</script>
|
||||||
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=f70dffced52a32aada1a22504c841e97e941401a">
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<header>
|
||||||
|
<img src="/cellxgene/cellxgene-logo.png" alt="cellxgene" />
|
||||||
|
|
||||||
|
<p>An interactive explorer for single-cell transcriptomics data</p>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/" class="btn">Quick start</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/install" class="btn">Installation</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/gallery" class="btn">Gallery</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">All other datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/prepare" class="btn">Preparing your data</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/launch" class="btn">Launching cellxgene</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/hosted" class="btn">Hosting cellxgene</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/annotations" class="btn">Annotating data</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/methods" class="btn">Methods</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/troubleshooting" class="btn">Troubleshooting</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/roadmap" class="btn">Roadmap</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/contribute" class="btn">Contributing (ideas or code)</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/extensions" class="btn"><b>Extensions</b></a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1 id="extensions">Extensions</h1>
|
||||||
|
|
||||||
|
<p>This project was started with the sole goal of empowering the scientific community to explore and understand their data.
|
||||||
|
As such, we encourage other scientific tool builders in academia or industry to adopt the patterns, tools, and code from
|
||||||
|
this project. All code is freely available for reuse under the <a href="https://opensource.org/licenses/MIT">MIT license</a>.</p>
|
||||||
|
|
||||||
|
<p>Before extending cellxgene, we encourage you to reach out to us with ideas or questions. It might be possible that an
|
||||||
|
extension could be directly contributed, which would make it available for a wider audience, or that it’s on our
|
||||||
|
<a href="/cellxgene/posts/roadmap.html">roadmap</a> and under active development.</p>
|
||||||
|
|
||||||
|
<p>Please note that cellxgene does not have public APIs. Our development may break extensions. We will document changes to the code base but it is advised that extensions pin the version of cellxgene they develop against.</p>
|
||||||
|
|
||||||
|
<h2 id="example-reuse--extensions">Example Reuse & extensions</h2>
|
||||||
|
|
||||||
|
<h4 id="cellxgene-gateway">cellxgene Gateway</h4>
|
||||||
|
|
||||||
|
<p><a href="https://github.com/Novartis/cellxgene-gateway">cellxgene Gateway</a> allows you to use with multiple datasets. It
|
||||||
|
displays an index of available h5ad (anndata) files. When a user clicks on a file name, it launches a Cellxgene Server
|
||||||
|
instance that loads that particular data file and once it is available proxies requests to that server.</p>
|
||||||
|
|
||||||
|
<h4 id="cellxgene-vip-visualization-in-plugin">cellxgene-VIP (Visualization in Plugin)</h4>
|
||||||
|
|
||||||
|
<p><a href="https://github.com/interactivereport/cellxgene_VIP">cellxgene-VIP</a> enables cellxgene to generate violin, stacked violin, stacked bar, heatmap, volcano, embedding, dot, track, density, 2D density, sankey and dual-gene plot in high-resolution SVG/PNG format. It also performs differential gene expression analysis and provides a Command Line Interface (CLI) for advanced users to perform analysis using python and R.</p>
|
||||||
|
|
||||||
|
<h4 id="galaxy">Galaxy</h4>
|
||||||
|
|
||||||
|
<p><a href="https://galaxyproject.org">Galaxy</a> is an open source, collaborative, web-based platform for data intensive biomedical research.
|
||||||
|
Galaxy provides various tools for <a href="https://singlecell.usegalaxy.eu/">single-cell data analysis</a> and also infrastructure to the
|
||||||
|
<a href="https://humancellatlas.usegalaxy.eu">Galaxy Human Cell Atlas project</a>. cellxgene can be <a href="https://usegalaxy.eu/root?tool_id=interactive_tool_cellxgene">
|
||||||
|
accessed within Galaxy</a> to view analyzed datasets. See also the relevant <a href="https://doi.org/10.1093/gigascience/giaa102">publication</a>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4 id="single-cell-portal">Single Cell Portal</h4>
|
||||||
|
|
||||||
|
<p>The <a href="https://singlecell.broadinstitute.org/single_cell">Single Cell Portal</a> is a data hosting and visualization service. cellxgene can be embedded as an additional view to complement the visualizations provided by the.
|
||||||
|
<a href="https://singlecell.broadinstitute.org/single_cell/study/SCP807/atlas-of-healthy-and-shiv-infected-non-human-primate-lung-and-ileum-ace2-cells">Example</a>.</p>
|
||||||
|
|
||||||
|
<h4 id="fastgenomics">FASTGenomics</h4>
|
||||||
|
|
||||||
|
<p><a href="https://beta.fastgenomics.org/">FASTGenomics</a> is a collaborative research platform that offers easy-to-use data management and reproducible analytics to drive single-cell research forward. Many of the publicly available datasets in FASTGenomics - as well as your private datasets - can be interactively explored with cellxgene.
|
||||||
|
See also this <a href="https://beta.fastgenomics.org/datasets/detail-dataset-952687f71ef34322a850553c4a24e82e#Cellxgene">example</a> for data from <a href="https://beta.fastgenomics.org/p/schulte-schrepping_covid19">Schulte-Schrepping et al. (Cell, 2020)</a>.
|
||||||
|
Note that it is not necessary to create an account, anonymous login is permitted.</p>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
|
||||||
|
<p>This project is maintained by <a href="https://github.com/chanzuckerberg">chanzuckerberg</a></p>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<script src="/cellxgene/assets/js/scale.fix.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Extensions
|
||||||
|
|
||||||
|
This project was started with the sole goal of empowering the scientific community to explore and understand their data.
|
||||||
|
As such, we encourage other scientific tool builders in academia or industry to adopt the patterns, tools, and code from
|
||||||
|
this project. All code is freely available for reuse under the [MIT license](https://opensource.org/licenses/MIT).
|
||||||
|
|
||||||
|
Before extending cellxgene, we encourage you to reach out to us with ideas or questions. It might be possible that an
|
||||||
|
extension could be directly contributed, which would make it available for a wider audience, or that it's on our
|
||||||
|
[roadmap](./roadmap.md) and under active development.
|
||||||
|
|
||||||
|
Please note that cellxgene does not have public APIs. Our development may break extensions. We will document changes to the code base but it is advised that extensions pin the version of cellxgene they develop against.
|
||||||
|
|
||||||
|
## Example Reuse & extensions
|
||||||
|
|
||||||
|
#### cellxgene Gateway
|
||||||
|
|
||||||
|
[cellxgene Gateway](https://github.com/Novartis/cellxgene-gateway) allows you to use with multiple datasets. It
|
||||||
|
displays an index of available h5ad (anndata) files. When a user clicks on a file name, it launches a Cellxgene Server
|
||||||
|
instance that loads that particular data file and once it is available proxies requests to that server.
|
||||||
|
|
||||||
|
#### cellxgene-VIP (Visualization in Plugin)
|
||||||
|
|
||||||
|
[cellxgene-VIP](https://github.com/interactivereport/cellxgene_VIP) enables cellxgene to generate violin, stacked violin, stacked bar, heatmap, volcano, embedding, dot, track, density, 2D density, sankey and dual-gene plot in high-resolution SVG/PNG format. It also performs differential gene expression analysis and provides a Command Line Interface (CLI) for advanced users to perform analysis using python and R.
|
||||||
|
|
||||||
|
#### Galaxy
|
||||||
|
|
||||||
|
[Galaxy](https://singlecell.usegalaxy.eu/) is an open source, web-based platform for data intensive biomedical research. cellxgene can be accessed within Galaxy to view analyzed datasets.
|
||||||
|
See also the relevant [publication](https://www.biorxiv.org/content/10.1101/2020.06.06.137570v1.full.pdf)
|
||||||
|
|
||||||
|
#### Single Cell Portal
|
||||||
|
|
||||||
|
The [Single Cell Portal](https://singlecell.broadinstitute.org/single_cell) is a data hosting and visualization service. cellxgene can be embedded as an additional view to complement the visualizations provided by the.
|
||||||
|
[Example](https://singlecell.broadinstitute.org/single_cell/study/SCP807/atlas-of-healthy-and-shiv-infected-non-human-primate-lung-and-ileum-ace2-cells).
|
||||||
|
|
||||||
|
#### FASTGenomics
|
||||||
|
|
||||||
|
[FASTGenomics](https://beta.fastgenomics.org/) is a collaborative research platform that offers easy-to-use data management and reproducible analytics to drive single-cell research forward. Many of the publicly available datasets in FASTGenomics - as well as your private datasets - can be interactively explored with cellxgene.
|
||||||
|
See also this [example](https://beta.fastgenomics.org/datasets/detail-dataset-952687f71ef34322a850553c4a24e82e#Cellxgene) for data from [Schulte-Schrepping et al. (Cell, 2020)](https://beta.fastgenomics.org/p/schulte-schrepping_covid19).
|
||||||
|
Note that it is not necessary to create an account, anonymous login is permitted.
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>Gallery | cellxgene</title>
|
<title>Gallery | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="Gallery" />
|
<meta property="og:title" content="Gallery" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/gallery.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/gallery.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","headline":"Gallery","url":"https://chanzuckerberg.github.io/cellxgene/posts/gallery.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/gallery.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"Gallery","description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -130,7 +130,7 @@ Check out the cool data that our users are using cellxgene to explore!</p>
|
|||||||
|
|
||||||
<h3 id="melanoma"><a href="https://melanoma.cellgeni.sanger.ac.uk/">Melanoma</a></h3>
|
<h3 id="melanoma"><a href="https://melanoma.cellgeni.sanger.ac.uk/">Melanoma</a></h3>
|
||||||
|
|
||||||
<h3 id="czis-own-cellxgene-site"><a href="cellxgene_cziscience_com">CZI’s own cellxgene site</a></h3>
|
<h3 id="czis-own-cellxgene-site"><a href="https://cellxgene.cziscience.com/">CZI’s own cellxgene site</a></h3>
|
||||||
|
|
||||||
<p><em>Want us to link to your dataset here? <a href="contact">Just send us a note!</a></em></p>
|
<p><em>Want us to link to your dataset here? <a href="contact">Just send us a note!</a></em></p>
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>Hosting cellxgene on the web | cellxgene</title>
|
<title>Hosting cellxgene on the web | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="Hosting cellxgene on the web" />
|
<meta property="og:title" content="Hosting cellxgene on the web" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/hosted.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/hosted.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","headline":"Hosting cellxgene on the web","url":"https://chanzuckerberg.github.io/cellxgene/posts/hosted.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/hosted.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"Hosting cellxgene on the web","description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -139,35 +139,36 @@
|
|||||||
|
|
||||||
<h1 id="deploying-cellxgene-with-heroku">Deploying cellxgene with Heroku</h1>
|
<h1 id="deploying-cellxgene-with-heroku">Deploying cellxgene with Heroku</h1>
|
||||||
|
|
||||||
<h2 id="quickstart">Quickstart</h2>
|
<h2 id="heroku-support">Heroku Support</h2>
|
||||||
|
|
||||||
<p>Clicking on the following button will forward you to Heroku to begin the deployment process:</p>
|
<p>The cellxgene team has decided to end our support for our experimental deploy to Heroku button as we move towards providing a supported method of hosted cellxgene.</p>
|
||||||
|
|
||||||
<p><a href="https://heroku.com/deploy?template=https://github.com/chanzuckerberg/cellxgene">
|
<p>While we no longer directly support Heroku, it is still possible to create a Heroku app via <a href="https://github.com/chanzuckerberg/cellxgene/blob/main/Dockerfile">our provided Dockerfile here</a> and <a href="https://devcenter.heroku.com/articles/build-docker-images-heroku-yml">Heroku’s documentation</a>.</p>
|
||||||
<img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy" />
|
|
||||||
</a></p>
|
|
||||||
|
|
||||||
<p>If not already logged in to Heroku, there you will be prompted to log in or sign up for an account.</p>
|
<p>You may have to tweak the <code class="language-plaintext highlighter-rouge">Dockerfile</code> like so:</p>
|
||||||
|
|
||||||
<p>Once logged in you will be sent to the setup page. Here you can set some of the basic settings for the app:</p>
|
<pre><code class="language-Dockerfile">FROM ubuntu:bionic
|
||||||
|
|
||||||
<h3 id="default-settings">Default settings</h3>
|
ENV LC_ALL=C.UTF-8
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
<ul>
|
RUN apt-get update && \
|
||||||
<li><code class="language-plaintext highlighter-rouge">App name</code>: the unique name for your deployment</li>
|
apt-get install -y build-essential libxml2-dev python3-dev python3-pip zlib1g-dev python3-requests && \
|
||||||
<li>This will also serve as the default URL (e.g. https://cellxgene.herokapp.com/)</li>
|
pip3 install cellxgene
|
||||||
<li><code class="language-plaintext highlighter-rouge">App owner</code>: Who will own this app. Either you personally or an organization/team</li>
|
|
||||||
<li><code class="language-plaintext highlighter-rouge">Region</code>: Location of the server where the app will be deployed (EU or US)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="configuration">Configuration</h3>
|
# ENTRYPOINT ["cellxgene"] # Heroku doesn't work well with ENTRYPOINT
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
<ul>
|
<p>and provide a <code class="language-plaintext highlighter-rouge">heroku.yml</code> file similar to this:</p>
|
||||||
<li><code class="language-plaintext highlighter-rouge">DATASET</code>: A <em>publicly</em> accessible URL pointing to a .h5ad file to view</li>
|
|
||||||
<li>This defaults to pbm3k.h5ad</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>After filling out the settings and pressing the <code class="language-plaintext highlighter-rouge">Deploy app</code> button Heroku will begin building your deployment. This process will take a few minutes, but once completed you will have a personal free hosted version of cellxgene!</p>
|
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">build</span><span class="pi">:</span>
|
||||||
|
<span class="na">docker</span><span class="pi">:</span>
|
||||||
|
<span class="na">web</span><span class="pi">:</span> <span class="s">Dockerfile</span>
|
||||||
|
<span class="na">run</span><span class="pi">:</span>
|
||||||
|
<span class="na">web</span><span class="pi">:</span>
|
||||||
|
<span class="na">command</span><span class="pi">:</span>
|
||||||
|
<span class="pi">-</span> <span class="s">cellxgene launch --host 0.0.0.0 --port $PORT $DATASET</span> <span class="c1"># the DATATSET config var must be defined in your dashboard settings.</span>
|
||||||
|
</code></pre></div></div>
|
||||||
|
|
||||||
<h2 id="what-is-heroku">What is Heroku?</h2>
|
<h2 id="what-is-heroku">What is Heroku?</h2>
|
||||||
|
|
||||||
|
|||||||
+23
-16
@@ -38,31 +38,38 @@ If you know of other solutions, drop us a note and we'll add to this list.
|
|||||||
|
|
||||||
# Deploying cellxgene with Heroku
|
# Deploying cellxgene with Heroku
|
||||||
|
|
||||||
## Quickstart
|
## Heroku Support
|
||||||
|
|
||||||
Clicking on the following button will forward you to Heroku to begin the deployment process:
|
The cellxgene team has decided to end our support for our experimental deploy to Heroku button as we move towards providing a supported method of hosted cellxgene.
|
||||||
|
|
||||||
<a href="https://heroku.com/deploy?template=https://github.com/chanzuckerberg/cellxgene">
|
While we no longer directly support Heroku, it is still possible to create a Heroku app via [our provided Dockerfile here](https://github.com/chanzuckerberg/cellxgene/blob/main/Dockerfile) and [Heroku's documentation](https://devcenter.heroku.com/articles/build-docker-images-heroku-yml).
|
||||||
<img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
If not already logged in to Heroku, there you will be prompted to log in or sign up for an account.
|
You may have to tweak the `Dockerfile` like so:
|
||||||
|
|
||||||
Once logged in you will be sent to the setup page. Here you can set some of the basic settings for the app:
|
```Dockerfile
|
||||||
|
FROM ubuntu:bionic
|
||||||
|
|
||||||
### Default settings
|
ENV LC_ALL=C.UTF-8
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
- `App name`: the unique name for your deployment
|
RUN apt-get update && \
|
||||||
- This will also serve as the default URL (e.g. https://cellxgene.herokapp.com/)
|
apt-get install -y build-essential libxml2-dev python3-dev python3-pip zlib1g-dev python3-requests && \
|
||||||
- `App owner`: Who will own this app. Either you personally or an organization/team
|
pip3 install cellxgene
|
||||||
- `Region`: Location of the server where the app will be deployed (EU or US)
|
|
||||||
|
|
||||||
### Configuration
|
# ENTRYPOINT ["cellxgene"] # Heroku doesn't work well with ENTRYPOINT
|
||||||
|
```
|
||||||
|
|
||||||
- `DATASET`: A _publicly_ accessible URL pointing to a .h5ad file to view
|
and provide a `heroku.yml` file similar to this:
|
||||||
- This defaults to pbm3k.h5ad
|
|
||||||
|
|
||||||
After filling out the settings and pressing the `Deploy app` button Heroku will begin building your deployment. This process will take a few minutes, but once completed you will have a personal free hosted version of cellxgene!
|
```yml
|
||||||
|
build:
|
||||||
|
docker:
|
||||||
|
web: Dockerfile
|
||||||
|
run:
|
||||||
|
web:
|
||||||
|
command:
|
||||||
|
- cellxgene launch --host 0.0.0.0 --port $PORT $DATASET # the DATATSET config var must be defined in your dashboard settings.
|
||||||
|
```
|
||||||
|
|
||||||
## What is Heroku?
|
## What is Heroku?
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>Install | cellxgene</title>
|
<title>Install | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="Install" />
|
<meta property="og:title" content="Install" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/install.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/install.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","headline":"Install","url":"https://chanzuckerberg.github.io/cellxgene/posts/install.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/install.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"Install","description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>demo-data | cellxgene</title>
|
<title>demo-data | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="demo-data" />
|
<meta property="og:title" content="demo-data" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="Demo datasets" />
|
<meta name="description" content="Demo datasets" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/launch.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/launch.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Demo datasets","@type":"WebPage","headline":"demo-data","url":"https://chanzuckerberg.github.io/cellxgene/posts/launch.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/launch.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"demo-data","description":"Demo datasets","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>Methods | cellxgene</title>
|
<title>Methods | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="Methods" />
|
<meta property="og:title" content="Methods" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
<meta name="description" content="An interactive explorer for single-cell transcriptomics data" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/methods.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/methods.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","headline":"Methods","url":"https://chanzuckerberg.github.io/cellxgene/posts/methods.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/methods.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"Methods","description":"An interactive explorer for single-cell transcriptomics data","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>prepare | cellxgene</title>
|
<title>prepare | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="prepare" />
|
<meta property="og:title" content="prepare" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="Preparing your data" />
|
<meta name="description" content="Preparing your data" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/prepare.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/prepare.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Preparing your data","@type":"WebPage","headline":"prepare","url":"https://chanzuckerberg.github.io/cellxgene/posts/prepare.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/prepare.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"prepare","description":"Preparing your data","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>roadmap | cellxgene</title>
|
<title>roadmap | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="roadmap" />
|
<meta property="og:title" content="roadmap" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="Roadmap" />
|
<meta name="description" content="Roadmap" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/roadmap.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/roadmap.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Roadmap","@type":"WebPage","headline":"roadmap","url":"https://chanzuckerberg.github.io/cellxgene/posts/roadmap.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/roadmap.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"roadmap","description":"Roadmap","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
<!-- Begin Jekyll SEO tag v2.6.1 -->
|
||||||
<title>Troubleshooting | cellxgene</title>
|
<title>Troubleshooting | cellxgene</title>
|
||||||
<meta name="generator" content="Jekyll v3.8.7" />
|
<meta name="generator" content="Jekyll v3.9.0" />
|
||||||
<meta property="og:title" content="Troubleshooting" />
|
<meta property="og:title" content="Troubleshooting" />
|
||||||
<meta property="og:locale" content="en_US" />
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta name="description" content="Troubleshooting" />
|
<meta name="description" content="Troubleshooting" />
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/troubleshooting.html" />
|
<meta property="og:url" content="https://chanzuckerberg.github.io/cellxgene/posts/troubleshooting.html" />
|
||||||
<meta property="og:site_name" content="cellxgene" />
|
<meta property="og:site_name" content="cellxgene" />
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Troubleshooting","@type":"WebPage","headline":"Troubleshooting","url":"https://chanzuckerberg.github.io/cellxgene/posts/troubleshooting.html","@context":"https://schema.org"}</script>
|
{"url":"https://chanzuckerberg.github.io/cellxgene/posts/troubleshooting.html","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"headline":"Troubleshooting","description":"Troubleshooting","@type":"WebPage","@context":"https://schema.org"}</script>
|
||||||
<!-- End Jekyll SEO tag -->
|
<!-- End Jekyll SEO tag -->
|
||||||
|
|
||||||
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=6bda27f5542fb7f469425e1cd99f2f37268b095f">
|
<link rel="stylesheet" href="/cellxgene/assets/css/style.css?v=3718e894edc8a8f6e7776946695ab37c5c96ec9f">
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="https://cellxgene.cziscience.com/" class="btn">Cellxgene data portal</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
<a href="/cellxgene/posts/demo-data" class="btn">Demo datasets</a><br>
|
||||||
|
|
||||||
|
|
||||||
@@ -85,10 +89,6 @@
|
|||||||
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
<a href="/cellxgene/posts/contact" class="btn">Contact & finding help</a><br>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="/cellxgene/posts/cellxgene_cziscience_com" class="btn">cellxgene.cziscience.com</a><br>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
<a href="https://github.com/chanzuckerberg/cellxgene" class="btn" target="_blank">Code</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Extensions
|
||||||
|
|
||||||
|
This project was started with the sole goal of empowering the scientific community to explore and understand their data.
|
||||||
|
As such, we encourage other scientific tool builders in academia or industry to adopt the patterns, tools, and code from
|
||||||
|
this project. All code is freely available for reuse under the [MIT license](https://opensource.org/licenses/MIT).
|
||||||
|
|
||||||
|
Before extending cellxgene, we encourage you to reach out to us with ideas or questions. It might be possible that an
|
||||||
|
extension could be directly contributed, which would make it available for a wider audience, or that it's on our
|
||||||
|
[roadmap](./roadmap.md) and under active development.
|
||||||
|
|
||||||
|
Please note that cellxgene does not have public APIs. Our development may break extensions. We will document changes to the code base but it is advised that extensions pin the version of cellxgene they develop against.
|
||||||
|
|
||||||
|
## Example Reuse & extensions
|
||||||
|
|
||||||
|
#### cellxgene Gateway
|
||||||
|
|
||||||
|
[cellxgene Gateway](https://github.com/Novartis/cellxgene-gateway) allows you to use with multiple datasets. It
|
||||||
|
displays an index of available h5ad (anndata) files. When a user clicks on a file name, it launches a Cellxgene Server
|
||||||
|
instance that loads that particular data file and once it is available proxies requests to that server.
|
||||||
|
|
||||||
|
#### cellxgene-VIP (Visualization in Plugin)
|
||||||
|
|
||||||
|
[cellxgene-VIP](https://github.com/interactivereport/cellxgene_VIP) enables cellxgene to generate violin, stacked violin, stacked bar, heatmap, volcano, embedding, dot, track, density, 2D density, sankey and dual-gene plot in high-resolution SVG/PNG format. It also performs differential gene expression analysis and provides a Command Line Interface (CLI) for advanced users to perform analysis using python and R.
|
||||||
|
|
||||||
|
#### Galaxy
|
||||||
|
|
||||||
|
[Galaxy](https://singlecell.usegalaxy.eu/) is an open source, web-based platform for data intensive biomedical research. cellxgene can be accessed within Galaxy to view analyzed datasets.
|
||||||
|
See also the relevant [publication](https://www.biorxiv.org/content/10.1101/2020.06.06.137570v1.full.pdf)
|
||||||
|
|
||||||
|
#### Single Cell Portal
|
||||||
|
|
||||||
|
The [Single Cell Portal](https://singlecell.broadinstitute.org/single_cell) is a data hosting and visualization service. cellxgene can be embedded as an additional view to complement the visualizations provided by the.
|
||||||
|
[Example](https://singlecell.broadinstitute.org/single_cell/study/SCP807/atlas-of-healthy-and-shiv-infected-non-human-primate-lung-and-ileum-ace2-cells).
|
||||||
|
|
||||||
|
#### FASTGenomics
|
||||||
|
|
||||||
|
[FASTGenomics](https://beta.fastgenomics.org/) is a collaborative research platform that offers easy-to-use data management and reproducible analytics to drive single-cell research forward. Many of the publicly available datasets in FASTGenomics - as well as your private datasets - can be interactively explored with cellxgene.
|
||||||
|
See also this [example](https://beta.fastgenomics.org/datasets/detail-dataset-952687f71ef34322a850553c4a24e82e#Cellxgene) for data from [Schulte-Schrepping et al. (Cell, 2020)](https://beta.fastgenomics.org/p/schulte-schrepping_covid19).
|
||||||
|
Note that it is not necessary to create an account, anonymous login is permitted.
|
||||||
@@ -39,6 +39,6 @@ Check out the cool data that our users are using cellxgene to explore!
|
|||||||
|
|
||||||
### [Melanoma](https://melanoma.cellgeni.sanger.ac.uk/)
|
### [Melanoma](https://melanoma.cellgeni.sanger.ac.uk/)
|
||||||
|
|
||||||
### [CZI's own cellxgene site](cellxgene_cziscience_com)
|
### [CZI's own cellxgene site](https://cellxgene.cziscience.com/)
|
||||||
|
|
||||||
_Want us to link to your dataset here? [Just send us a note!](contact)_
|
_Want us to link to your dataset here? [Just send us a note!](contact)_
|
||||||
|
|||||||
+23
-16
@@ -38,31 +38,38 @@ If you know of other solutions, drop us a note and we'll add to this list.
|
|||||||
|
|
||||||
# Deploying cellxgene with Heroku
|
# Deploying cellxgene with Heroku
|
||||||
|
|
||||||
## Quickstart
|
## Heroku Support
|
||||||
|
|
||||||
Clicking on the following button will forward you to Heroku to begin the deployment process:
|
The cellxgene team has decided to end our support for our experimental deploy to Heroku button as we move towards providing a supported method of hosted cellxgene.
|
||||||
|
|
||||||
<a href="https://heroku.com/deploy?template=https://github.com/chanzuckerberg/cellxgene">
|
While we no longer directly support Heroku, it is still possible to create a Heroku app via [our provided Dockerfile here](https://github.com/chanzuckerberg/cellxgene/blob/main/Dockerfile) and [Heroku's documentation](https://devcenter.heroku.com/articles/build-docker-images-heroku-yml).
|
||||||
<img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
If not already logged in to Heroku, there you will be prompted to log in or sign up for an account.
|
You may have to tweak the `Dockerfile` like so:
|
||||||
|
|
||||||
Once logged in you will be sent to the setup page. Here you can set some of the basic settings for the app:
|
```Dockerfile
|
||||||
|
FROM ubuntu:bionic
|
||||||
|
|
||||||
### Default settings
|
ENV LC_ALL=C.UTF-8
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
- `App name`: the unique name for your deployment
|
RUN apt-get update && \
|
||||||
- This will also serve as the default URL (e.g. https://cellxgene.herokapp.com/)
|
apt-get install -y build-essential libxml2-dev python3-dev python3-pip zlib1g-dev python3-requests && \
|
||||||
- `App owner`: Who will own this app. Either you personally or an organization/team
|
pip3 install cellxgene
|
||||||
- `Region`: Location of the server where the app will be deployed (EU or US)
|
|
||||||
|
|
||||||
### Configuration
|
# ENTRYPOINT ["cellxgene"] # Heroku doesn't work well with ENTRYPOINT
|
||||||
|
```
|
||||||
|
|
||||||
- `DATASET`: A _publicly_ accessible URL pointing to a .h5ad file to view
|
and provide a `heroku.yml` file similar to this:
|
||||||
- This defaults to pbm3k.h5ad
|
|
||||||
|
|
||||||
After filling out the settings and pressing the `Deploy app` button Heroku will begin building your deployment. This process will take a few minutes, but once completed you will have a personal free hosted version of cellxgene!
|
```yml
|
||||||
|
build:
|
||||||
|
docker:
|
||||||
|
web: Dockerfile
|
||||||
|
run:
|
||||||
|
web:
|
||||||
|
command:
|
||||||
|
- cellxgene launch --host 0.0.0.0 --port $PORT $DATASET # the DATATSET config var must be defined in your dashboard settings.
|
||||||
|
```
|
||||||
|
|
||||||
## What is Heroku?
|
## What is Heroku?
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
FROM python:3.7
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
RUN pip3 install cellxgene
|
|
||||||
|
|
||||||
expose 5005
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# cellxgene cloud deployment with Heroku
|
|
||||||
|
|
||||||
## Quickstart
|
|
||||||
|
|
||||||
Clicking on the following button will forward you to Heroku to begin the deployment process:
|
|
||||||
|
|
||||||
<a href="https://heroku.com/deploy?template=https://github.com/chanzuckerberg/cellxgene/tree/main">
|
|
||||||
<img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
If not already logged in to Heroku, there you will be prompted to log in or sign up for an account.
|
|
||||||
|
|
||||||
Once logged in you will be sent to the setup page. Here you can set some of the basic settings for the app:
|
|
||||||
|
|
||||||
#### Default settings
|
|
||||||
|
|
||||||
- `App name`: the unique name for your deployment
|
|
||||||
- This will also serve as the default URL (e.g. https://cellxgene.herokapp.com/)
|
|
||||||
- `App owner`: Who will own this app. Either you personally or an organization/team
|
|
||||||
- `Region`: Location of the server where the app will be deployed (EU or US)
|
|
||||||
|
|
||||||
#### Configuration
|
|
||||||
|
|
||||||
- `DATASET`: A _publicly_ accessible URL pointing to a .h5ad file to view
|
|
||||||
- This defaults to pbm3k.h5ad
|
|
||||||
|
|
||||||
After filling out the settings and pressing the `Deploy app` button Heroku will begin building your deployment. This process will take a few minutes, but once completed you will have a personal free hosted version of cellxgene!
|
|
||||||
|
|
||||||
## What is Heroku?
|
|
||||||
|
|
||||||
Heroku is a quick and easy way to host applications on the cloud.
|
|
||||||
|
|
||||||
A Heroku deployment of cellxgene means that the app is not running on your local machine. Instead, the app is installed, configured, and ran on the Heroku servers (read: cloud).
|
|
||||||
|
|
||||||
On Heroku's servers, applications run on a [dyno](https://www.heroku.com/dynos) which are Heroku's implementation and abstraction of containers.
|
|
||||||
|
|
||||||
Heroku is one of many options available for hosting instances of cellxgene on the web.
|
|
||||||
Some other options include: Amazon Web Services, Google Cloud Platform, Digital Ocean, and Microsoft Azure.
|
|
||||||
|
|
||||||
## Why use Heroku to deploy cellxgene?
|
|
||||||
|
|
||||||
What Heroku enables is a quick, non-technical method of setting up a cellxgene instance. No command line knowledge needed. This also allows machines to access the instance via the internet, so sharing a visualized dataset is as simple as sharing a link.
|
|
||||||
|
|
||||||
Because cellxgene currently heavily relies on its Python backend for providing the viewer with the necessary data and tooling, it is currently not possible to host cellxgene as a static webpage.
|
|
||||||
|
|
||||||
This is a good option if you want to quickly deploy an instance of cellxgene to the web. Heroku deployments are free for small datasets up to around 250MBs in size. See below regarding larger datasets.
|
|
||||||
|
|
||||||
## When should I not deploy with Heroku?
|
|
||||||
|
|
||||||
- The default free dyno offered by Heroku is limited in memory to 512 MBs
|
|
||||||
- The amount of memory needed for the dyno is roughly the same size as the h5ad file
|
|
||||||
- Heroku offers tiered paid dynos. More can be found [here](https://www.heroku.com/pricing)
|
|
||||||
- Note that this can get _very_ expensive for larger datasets (\$25+ a month)
|
|
||||||
- On the free dyno, after 30 minutes of inactivity, Heroku will put your app into a hibernation mode. On the next access, Heroku will need time to boot the dyno back online.
|
|
||||||
- Having multiple simultaneous users requires more memory. This means that the free container size is easily overwhelmed by multiple users, even with small datasets; this can be addressed by purchasing a larger container size
|
|
||||||
- For this facilitated Heroku deployment to work, your dataset must be hosted on a publicly accessible URL
|
|
||||||
- By default, Heroku publically shares your instance to anyone with the URL.
|
|
||||||
- There are many ways of securing your instance. One quick and simple way is by installing [wwwhisper](https://elements.heroku.com/addons/wwwhisper), a Heroku addon
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
build:
|
|
||||||
docker:
|
|
||||||
web: experiments/heroku/Dockerfile
|
|
||||||
run:
|
|
||||||
web: cellxgene launch $DATASET --host 0.0.0.0 --port $PORT
|
|
||||||
@@ -39,3 +39,11 @@ create-test-db:
|
|||||||
clean-test-db:
|
clean-test-db:
|
||||||
-docker stop test_db
|
-docker stop test_db
|
||||||
-docker rm test_db
|
-docker rm test_db
|
||||||
|
|
||||||
|
.PHONY: test-annotations-performance
|
||||||
|
test-annotations-performance:
|
||||||
|
python test/performance/performance_test_annotations_backend.py
|
||||||
|
|
||||||
|
.PHONY: test-annotations-scale
|
||||||
|
test-annotations-scale:
|
||||||
|
locust -f test/performance/scale_test_annotations.py --headless -u 30 -r 10 --host https://api.cellxgene.dev.single-cell.czi.technology/cellxgene/e/ --run-time 5m 2>&1 | tee locust_dev_stats.txt
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
from server.common.utils import import_plugins
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
from server.common.utils.utils import import_plugins
|
||||||
|
|
||||||
__version__ = "0.16.0"
|
__version__ = "0.16.7"
|
||||||
display_version = "cellxgene v" + __version__
|
display_version = "cellxgene v" + __version__
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
+112
-33
@@ -1,22 +1,32 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from functools import wraps
|
||||||
|
from http import HTTPStatus
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
|
||||||
from flask import Flask, redirect, current_app, make_response, render_template, abort
|
from flask import (
|
||||||
from flask import Blueprint, request
|
Flask,
|
||||||
|
redirect,
|
||||||
|
current_app,
|
||||||
|
make_response,
|
||||||
|
render_template,
|
||||||
|
abort,
|
||||||
|
Blueprint,
|
||||||
|
request,
|
||||||
|
send_from_directory,
|
||||||
|
)
|
||||||
from flask_restful import Api, Resource
|
from flask_restful import Api, Resource
|
||||||
from server_timing import Timing as ServerTiming
|
from server_timing import Timing as ServerTiming
|
||||||
|
|
||||||
from http import HTTPStatus
|
|
||||||
|
|
||||||
import server.common.rest as common_rest
|
import server.common.rest as common_rest
|
||||||
from server.common.errors import DatasetAccessError, RequestException
|
|
||||||
from server.common.utils import path_join, Float32JSONEncoder
|
|
||||||
from server.common.data_locator import DataLocator
|
from server.common.data_locator import DataLocator
|
||||||
|
from server.common.errors import DatasetAccessError, RequestException
|
||||||
from server.common.health import health_check
|
from server.common.health import health_check
|
||||||
|
from server.common.utils.utils import path_join, Float32JSONEncoder
|
||||||
from server.data_common.matrix_loader import MatrixDataLoader
|
from server.data_common.matrix_loader import MatrixDataLoader
|
||||||
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
webbp = Blueprint("webapp", "server.common.web", template_folder="templates")
|
webbp = Blueprint("webapp", "server.common.web", template_folder="templates")
|
||||||
|
|
||||||
ONE_WEEK = 7 * 24 * 60 * 60
|
ONE_WEEK = 7 * 24 * 60 * 60
|
||||||
@@ -86,23 +96,15 @@ def dataset_index(url_dataroot=None, dataset=None):
|
|||||||
cache_manager = current_app.matrix_data_cache_manager
|
cache_manager = current_app.matrix_data_cache_manager
|
||||||
with cache_manager.data_adaptor(url_dataroot, location, app_config) as data_adaptor:
|
with cache_manager.data_adaptor(url_dataroot, location, app_config) as data_adaptor:
|
||||||
data_adaptor.set_uri_path(f"{url_dataroot}/{dataset}")
|
data_adaptor.set_uri_path(f"{url_dataroot}/{dataset}")
|
||||||
dataset_title = app_config.get_title(data_adaptor)
|
args = {"SCRIPTS": scripts, "INLINE_SCRIPTS": inline_scripts}
|
||||||
return render_template(
|
return render_template("index.html", **args)
|
||||||
"index.html", datasetTitle=dataset_title, SCRIPTS=scripts, INLINE_SCRIPTS=inline_scripts
|
|
||||||
)
|
|
||||||
except DatasetAccessError as e:
|
except DatasetAccessError as e:
|
||||||
return common_rest.abort_and_log(
|
return common_rest.abort_and_log(
|
||||||
e.status_code, f"Invalid dataset {dataset}: {e.message}", loglevel=logging.INFO, include_exc_info=True
|
e.status_code, f"Invalid dataset {dataset}: {e.message}", loglevel=logging.INFO, include_exc_info=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@webbp.route("/health", methods=["GET"])
|
|
||||||
@cache_control_always(no_store=True)
|
|
||||||
def health():
|
|
||||||
config = current_app.app_config
|
|
||||||
return health_check(config)
|
|
||||||
|
|
||||||
|
|
||||||
@webbp.errorhandler(RequestException)
|
@webbp.errorhandler(RequestException)
|
||||||
def handle_request_exception(error):
|
def handle_request_exception(error):
|
||||||
return common_rest.abort_and_log(error.status_code, error.message, loglevel=logging.INFO, include_exc_info=True)
|
return common_rest.abort_and_log(error.status_code, error.message, loglevel=logging.INFO, include_exc_info=True)
|
||||||
@@ -181,9 +183,9 @@ def dataroot_test_index():
|
|||||||
data += f"<p>Logged in as {auth.get_user_id()} / {auth.get_user_name()} / {auth.get_user_email()}</p>"
|
data += f"<p>Logged in as {auth.get_user_id()} / {auth.get_user_name()} / {auth.get_user_email()}</p>"
|
||||||
if auth.requires_client_login():
|
if auth.requires_client_login():
|
||||||
if server_config.auth.is_user_authenticated():
|
if server_config.auth.is_user_authenticated():
|
||||||
data += "<p><a href='/logout'>Logout</a></p>"
|
data += f"<p><a href='{auth.get_logout_url(None)}'>Logout</a></p>"
|
||||||
else:
|
else:
|
||||||
data += "<p><a href='/login'>Login</a></p>"
|
data += f"<p><a href='{auth.get_login_url(None)}'>Login</a></p>"
|
||||||
|
|
||||||
datasets = []
|
datasets = []
|
||||||
for dataroot_dict in server_config.multi_dataset__dataroot.values():
|
for dataroot_dict in server_config.multi_dataset__dataroot.values():
|
||||||
@@ -221,6 +223,13 @@ def dataroot_index():
|
|||||||
return redirect(config.server_config.multi_dataset__index)
|
return redirect(config.server_config.multi_dataset__index)
|
||||||
|
|
||||||
|
|
||||||
|
class HealthAPI(Resource):
|
||||||
|
@cache_control(no_store=True)
|
||||||
|
def get(self):
|
||||||
|
config = current_app.app_config
|
||||||
|
return health_check(config)
|
||||||
|
|
||||||
|
|
||||||
class DatasetResource(Resource):
|
class DatasetResource(Resource):
|
||||||
"""Base class for all Resources that act on datasets."""
|
"""Base class for all Resources that act on datasets."""
|
||||||
|
|
||||||
@@ -230,7 +239,8 @@ class DatasetResource(Resource):
|
|||||||
|
|
||||||
|
|
||||||
class SchemaAPI(DatasetResource):
|
class SchemaAPI(DatasetResource):
|
||||||
@cache_control(public=True, max_age=ONE_WEEK)
|
# TODO @mdunitz separate dataset schema and user schema
|
||||||
|
@cache_control(no_store=True)
|
||||||
@rest_get_data_adaptor
|
@rest_get_data_adaptor
|
||||||
def get(self, data_adaptor):
|
def get(self, data_adaptor):
|
||||||
return common_rest.schema_get(data_adaptor)
|
return common_rest.schema_get(data_adaptor)
|
||||||
@@ -243,8 +253,15 @@ class ConfigAPI(DatasetResource):
|
|||||||
return common_rest.config_get(current_app.app_config, data_adaptor)
|
return common_rest.config_get(current_app.app_config, data_adaptor)
|
||||||
|
|
||||||
|
|
||||||
|
class UserInfoAPI(DatasetResource):
|
||||||
|
@cache_control_always(no_store=True)
|
||||||
|
@rest_get_data_adaptor
|
||||||
|
def get(self, data_adaptor):
|
||||||
|
return common_rest.userinfo_get(current_app.app_config, data_adaptor)
|
||||||
|
|
||||||
|
|
||||||
class AnnotationsObsAPI(DatasetResource):
|
class AnnotationsObsAPI(DatasetResource):
|
||||||
@cache_control(public=True, max_age=ONE_WEEK)
|
@cache_control(public=True, no_store=True)
|
||||||
@rest_get_data_adaptor
|
@rest_get_data_adaptor
|
||||||
def get(self, data_adaptor):
|
def get(self, data_adaptor):
|
||||||
return common_rest.annotations_obs_get(request, data_adaptor)
|
return common_rest.annotations_obs_get(request, data_adaptor)
|
||||||
@@ -301,8 +318,18 @@ class LayoutObsAPI(DatasetResource):
|
|||||||
return common_rest.layout_obs_put(request, data_adaptor)
|
return common_rest.layout_obs_put(request, data_adaptor)
|
||||||
|
|
||||||
|
|
||||||
def get_api_resources(bp_api, url_dataroot=None):
|
def get_api_base_resources(bp_base):
|
||||||
api = Api(bp_api)
|
"""Add resources that are accessed from the api_base_url"""
|
||||||
|
api = Api(bp_base)
|
||||||
|
|
||||||
|
# Diagnostics routes
|
||||||
|
api.add_resource(HealthAPI, "/health")
|
||||||
|
return api
|
||||||
|
|
||||||
|
|
||||||
|
def get_api_dataroot_resources(bp_dataroot, url_dataroot=None):
|
||||||
|
"""Add resources that refer to a dataset"""
|
||||||
|
api = Api(bp_dataroot)
|
||||||
|
|
||||||
def add_resource(resource, url):
|
def add_resource(resource, url):
|
||||||
"""convenience function to make the outer function less verbose"""
|
"""convenience function to make the outer function less verbose"""
|
||||||
@@ -311,6 +338,7 @@ def get_api_resources(bp_api, url_dataroot=None):
|
|||||||
# Initialization routes
|
# Initialization routes
|
||||||
add_resource(SchemaAPI, "/schema")
|
add_resource(SchemaAPI, "/schema")
|
||||||
add_resource(ConfigAPI, "/config")
|
add_resource(ConfigAPI, "/config")
|
||||||
|
add_resource(UserInfoAPI, "/userinfo")
|
||||||
# Data routes
|
# Data routes
|
||||||
add_resource(AnnotationsObsAPI, "/annotations/obs")
|
add_resource(AnnotationsObsAPI, "/annotations/obs")
|
||||||
add_resource(AnnotationsVarAPI, "/annotations/var")
|
add_resource(AnnotationsVarAPI, "/annotations/var")
|
||||||
@@ -323,6 +351,25 @@ def get_api_resources(bp_api, url_dataroot=None):
|
|||||||
return api
|
return api
|
||||||
|
|
||||||
|
|
||||||
|
def handle_api_base_url(app, app_config):
|
||||||
|
"""If an api_base_url is provided, then an inline script is generated to
|
||||||
|
handle the new API prefix"""
|
||||||
|
api_base_url = app_config.server_config.get_api_base_url()
|
||||||
|
if not api_base_url:
|
||||||
|
return
|
||||||
|
|
||||||
|
sha256 = hashlib.sha256(api_base_url.encode()).hexdigest()
|
||||||
|
script_name = f"api_base_url-{sha256}.js"
|
||||||
|
script_path = os.path.join(app.root_path, "../common/web/templates", script_name)
|
||||||
|
with open(script_path, "w") as fout:
|
||||||
|
fout.write("window.CELLXGENE.API.prefix = `" + api_base_url + "${location.pathname}api/`;\n")
|
||||||
|
|
||||||
|
dataset_configs = [app_config.default_dataset_config] + list(app_config.dataroot_config.values())
|
||||||
|
for dataset_config in dataset_configs:
|
||||||
|
inline_scripts = dataset_config.app__inline_scripts
|
||||||
|
inline_scripts.append(script_name)
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _before_adding_routes(app, app_config):
|
def _before_adding_routes(app, app_config):
|
||||||
@@ -330,7 +377,8 @@ class Server:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, app_config):
|
def __init__(self, app_config):
|
||||||
self.app = Flask(__name__, static_folder="../common/web/static")
|
self.app = Flask(__name__, static_folder=None)
|
||||||
|
handle_api_base_url(self.app, app_config)
|
||||||
self._before_adding_routes(self.app, app_config)
|
self._before_adding_routes(self.app, app_config)
|
||||||
self.app.json_encoder = Float32JSONEncoder
|
self.app.json_encoder = Float32JSONEncoder
|
||||||
server_config = app_config.server_config
|
server_config = app_config.server_config
|
||||||
@@ -347,28 +395,59 @@ class Server:
|
|||||||
self.app.register_blueprint(webbp)
|
self.app.register_blueprint(webbp)
|
||||||
|
|
||||||
api_version = "/api/v0.2"
|
api_version = "/api/v0.2"
|
||||||
|
api_base_url = server_config.get_api_base_url()
|
||||||
|
api_path = "/"
|
||||||
|
if api_base_url:
|
||||||
|
parse = urlparse(api_base_url)
|
||||||
|
api_path = parse.path
|
||||||
|
|
||||||
|
bp_base = Blueprint("bp_base", __name__, url_prefix=api_path)
|
||||||
|
base_resources = get_api_base_resources(bp_base)
|
||||||
|
self.app.register_blueprint(base_resources.blueprint)
|
||||||
|
|
||||||
if app_config.is_multi_dataset():
|
if app_config.is_multi_dataset():
|
||||||
# NOTE: These routes only allow the dataset to be in the directory
|
# NOTE: These routes only allow the dataset to be in the directory
|
||||||
# of the dataroot, and not a subdirectory. We may want to change
|
# of the dataroot, and not a subdirectory. We may want to change
|
||||||
# the route format at some point
|
# the route format at some point
|
||||||
for dataroot_dict in server_config.multi_dataset__dataroot.values():
|
for dataroot_dict in server_config.multi_dataset__dataroot.values():
|
||||||
url_dataroot = dataroot_dict["base_url"]
|
url_dataroot = dataroot_dict["base_url"]
|
||||||
bp_api = Blueprint(
|
bp_dataroot = Blueprint(
|
||||||
f"api_dataset_{url_dataroot}", __name__, url_prefix=f"/{url_dataroot}/<dataset>" + api_version
|
f"api_dataset_{url_dataroot}",
|
||||||
|
__name__,
|
||||||
|
url_prefix=f"{api_path}/{url_dataroot}/<dataset>" + api_version,
|
||||||
)
|
)
|
||||||
resources = get_api_resources(bp_api, url_dataroot)
|
dataroot_resources = get_api_dataroot_resources(bp_dataroot, url_dataroot)
|
||||||
self.app.register_blueprint(resources.blueprint)
|
self.app.register_blueprint(dataroot_resources.blueprint)
|
||||||
|
|
||||||
self.app.add_url_rule(
|
self.app.add_url_rule(
|
||||||
f"/{url_dataroot}/<dataset>/",
|
f"/{url_dataroot}/<dataset>",
|
||||||
f"dataset_index_{url_dataroot}",
|
f"dataset_index_{url_dataroot}",
|
||||||
lambda dataset, url_dataroot=url_dataroot: dataset_index(url_dataroot, dataset),
|
lambda dataset, url_dataroot=url_dataroot: dataset_index(url_dataroot, dataset),
|
||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
)
|
)
|
||||||
|
self.app.add_url_rule(
|
||||||
|
f"/{url_dataroot}/<dataset>/",
|
||||||
|
f"dataset_index_{url_dataroot}/",
|
||||||
|
lambda dataset, url_dataroot=url_dataroot: dataset_index(url_dataroot, dataset),
|
||||||
|
methods=["GET"],
|
||||||
|
)
|
||||||
|
self.app.add_url_rule(
|
||||||
|
f"/{url_dataroot}/<dataset>/static/<path:filename>",
|
||||||
|
f"static_assets_{url_dataroot}",
|
||||||
|
view_func=lambda dataset, filename: send_from_directory("../common/web/static", filename),
|
||||||
|
methods=["GET"],
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
bp_api = Blueprint("api", __name__, url_prefix=api_version)
|
bp_api = Blueprint("api", __name__, url_prefix=f"{api_path}{api_version}")
|
||||||
resources = get_api_resources(bp_api)
|
resources = get_api_dataroot_resources(bp_api)
|
||||||
self.app.register_blueprint(resources.blueprint)
|
self.app.register_blueprint(resources.blueprint)
|
||||||
|
self.app.add_url_rule(
|
||||||
|
"/static/<path:filename>",
|
||||||
|
"static_assets",
|
||||||
|
view_func=lambda filename: send_from_directory("../common/web/static", filename),
|
||||||
|
methods=["GET"],
|
||||||
|
)
|
||||||
|
|
||||||
self.app.matrix_data_cache_manager = server_config.matrix_data_cache_manager
|
self.app.matrix_data_cache_manager = server_config.matrix_data_cache_manager
|
||||||
self.app.app_config = app_config
|
self.app.app_config = app_config
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# import the built in auth types so they can be registered
|
# import the built in auth types so they can be registered
|
||||||
|
|
||||||
import server.auth.auth_none # noqa: F401
|
import server.auth.auth_none # noqa: F401
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user