diff --git a/CS202210140.json b/CS202210140.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/CS202210140.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/CS202210140_project_config.yaml b/CS202210140_project_config.yaml new file mode 100755 index 00000000..9219dd59 --- /dev/null +++ b/CS202210140_project_config.yaml @@ -0,0 +1,14 @@ +id: CS202210140 +title: Human Brain Cell Atlas v1.0 (Neuronal) +description: "Atlas of human primary motor cortex (M1), developed in collaboration with the BRAIN Initiative Cell Census Network (BICCN).\nFirst draft atlas of human brain transcriptomic cell types: The human brain directs a wide range of complex behaviors ranging from fine motor skills to abstract intelligence and emotion. This broad range of functions is supported by an exceptionally complex cellular and circuit architecture. To create a first draft human brain cell atlas, high-throughput single-nucleus RNA sequencing was used to systematically survey cells across the entire adult human brain in three postmortem donors. Over three million nuclei were sampled from approximately 100 dissections across the forebrain, midbrain, and hindbrain. Analysis of these data showed regional diversity in that cellular organization exhibited regional diversity at multiple scales, identifying 30 superclusters, 461 clusters and 3313 subclusters. As the first single-cell transcriptomic census of the entire human brain, this atlas provides a resource for understanding the molecular diversity of the human brain in health and disease. The Human Brain Cell Atlas v1.0 is presented for visualization and data mining through the Chan Zuckerberg Initiative’s CellxGene application, with the following biologically meaningful partitions: 1. Neuronal and non-neuronal cell types 2. Supercluster-specific groupings (`Supercluster: `) 3.Brain region-specific groupings (`Dissection: `), ordered by the adult human brain anatomical reference atlas ontology in Ding et al. (2016)" +matrix_file_id: CellXGene_dataset:8e10f1c4-8e98-41e5-b65f-8cd89a887122 +github_org: hkir-dev +repo: human-brain-cell-atlas_v1_neurons +author: https://orcid.org/0000-0001-7620-8973 +accession_id_prefix: CS202210140_ +citation: '' +creators: + - https://orcid.org/0000-0001-7258-9596 + - https://orcid.org/0000-0002-3315-2794 + + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..45478e39 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +WORKSPACE=/tools +NANOBOT := build/nanobot +NANOBOTDB := build/nanobot.db +EXPORT := build/export.py +IMPORT := $(WORKSPACE)/scripts/import.py +CONFIGURATIONS := $(WORKSPACE)/scripts/configurations.py + +build/: + mkdir -p $@ + +build/nanobot: | build/ + curl -L -o $@ "https://github.com/ontodev/nanobot.rs/releases/download/v2023-06-30/nanobot-x86_64-unknown-linux-musl" + chmod +x $@ + +build/export.py: | build/ + curl -L -o $@ "https://github.com/ontodev/valve.rs/raw/main/scripts/export.py" + +.PHONY: build_nomenclature_tables +build_nomenclature_tables: + Rscript $(WORKSPACE)/dendR/nomenclature_builder.R + +.PHONY: load_data +load_data: + python3 $(IMPORT) import-data --input input_data/ --schema src/schema/ --curation_tables curation_tables/ + +.PHONY: runR +runR: + Rscript dendR/nomenclature_builder.R + +$(NANOBOTDB): | $(NANOBOT) + $(NANOBOT) init + +.PHONY: load +load: clean | $(NANOBOT) + $(NANOBOT) init + +.PHONY: +save: $(EXPORT) $(NANOBOTDB) + python3 $(EXPORT) data $(NANOBOTDB) src/schema/ table column datatype + python3 $(EXPORT) data $(NANOBOTDB) curation_tables/ $(foreach t,$(wildcard curation_tables/*.tsv), $(basename $(notdir $t))) + #python3 $(EXPORT) data $(NANOBOTDB) curation_tables/ $$(cut -f1 src/schema/table.tsv | grep CCN2 | tr '\n' ' ') + +.PHONY: serve +serve: $(NANOBOTDB) + python3 $(CONFIGURATIONS) configure-git --root_folder ./ + $(NANOBOT) serve + +.PHONY: clean +clean: + rm -rf build/ diff --git a/README.md b/README.md new file mode 100644 index 00000000..2deee920 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Human Brain Cell Atlas v1.0 (Neuronal) (CS202210140) + +Atlas of human primary motor cortex (M1), developed in collaboration with the BRAIN Initiative Cell Census Network (BICCN). +First draft atlas of human brain transcriptomic cell types: The human brain directs a wide range of complex behaviors ranging from fine motor skills to abstract intelligence and emotion. This broad range of functions is supported by an exceptionally complex cellular and circuit architecture. To create a first draft human brain cell atlas, high-throughput single-nucleus RNA sequencing was used to systematically survey cells across the entire adult human brain in three postmortem donors. Over three million nuclei were sampled from approximately 100 dissections across the forebrain, midbrain, and hindbrain. Analysis of these data showed regional diversity in that cellular organization exhibited regional diversity at multiple scales, identifying 30 superclusters, 461 clusters and 3313 subclusters. As the first single-cell transcriptomic census of the entire human brain, this atlas provides a resource for understanding the molecular diversity of the human brain in health and disease. The Human Brain Cell Atlas v1.0 is presented for visualization and data mining through the Chan Zuckerberg Initiative’s CellxGene application, with the following biologically meaningful partitions: 1. Neuronal and non-neuronal cell types 2. Supercluster-specific groupings (`Supercluster: `) 3.Brain region-specific groupings (`Dissection: `), ordered by the adult human brain anatomical reference atlas ontology in Ding et al. (2016) + +Curate your taxonomy in 3 simple steps: + +1. [Get Taxonomy Development Tools](#get-taxonomy-development-tools) +1. [Load your data](#load-your-data) +1. [Browse](#browse) + +## Get Taxonomy Development Tools + +Pull the latest TDT docker image via following the steps defined in the project [GitHub Container Registry](https://github.com/brain-bican/taxonomy-development-tools/pkgs/container/taxonomy-development-tools). + +``` +docker pull ghcr.io/brain-bican/taxonomy-development-tools:latest +``` + +## Load your data + +Place your data (ex. [AIT115_annotation_sheet.tsv](https://github.com/brain-bican/taxonomy-development-tools/tree/main/examples/nhp_basal_ganglia/AIT115_annotation_sheet.tsv)) and configuration file (ex. [ingestion_config.yaml](https://github.com/brain-bican/taxonomy-development-tools/tree/main/examples/nhp_basal_ganglia/ingestion_config.yaml)) into your project's `input_data` folder. + +Run following command in your project root folder to ingest your data files: + +``` +bash ./run.sh make load_data +``` + +## Browse + +Run following command in your project root folder to run the online data editor: +``` +bash ./run.sh make serve +``` + +This command will print a set of logs including a log like `nanobot::serve: listening on 0.0.0.0:3000`. This means your web editor is ready, and you can start editing your data. + +You can start browsing web taxonomy editor from: [http://localhost:3000/table](http://localhost:3000/table) + +_For further details see [Taxonomy Development Tools Documentation](https://brain-bican.github.io/taxonomy-development-tools/)_ \ No newline at end of file diff --git a/curation_tables/README.md b/curation_tables/README.md new file mode 100644 index 00000000..bbb561fb --- /dev/null +++ b/curation_tables/README.md @@ -0,0 +1,3 @@ +# Curation Tables + +CCN2 taxonomy curation tables. \ No newline at end of file diff --git a/input_data/README.md b/input_data/README.md new file mode 100644 index 00000000..44428088 --- /dev/null +++ b/input_data/README.md @@ -0,0 +1,3 @@ +# Input Files + +Place your taxonomy data files and configuration files into this folder. \ No newline at end of file diff --git a/nanobot.toml b/nanobot.toml new file mode 100644 index 00000000..a96cabf1 --- /dev/null +++ b/nanobot.toml @@ -0,0 +1,128 @@ +[nanobot] +config_version = 1 + +[logging] +level = "DEBUG" + +[database] +connection = "build/nanobot.db" + +[valve] +path = "src/schema/table.tsv" + +[assets] +path = "src/assets/" + +[templates] +path = "src/resources/" + +[actions.save] +label = "Save" +commands = [ + ["make", "save"], +] + +[actions.status] +label = "Status" +commands = [ + ["git", "-c", "color.ui=always", "status"], +] + +[actions.diff] +label = "Diff" +commands = [ + ["git", "-c", "color.ui=always", "diff"], +] + +[actions.fetch] +label = "Fetch" +commands = [ + ["git", "fetch"], +] + +[actions.pull] +label = "Pull" +commands = [ + ["git", "pull"], +] + +[actions.branch] +label = "Branch" +inputs = [ + { name = "branch_name", label = "Branch Name", default = "{username}-{today}", validate = "\\w+" } +] +commands = [ + ["git", "checkout", "main"], + ["git", "pull"], + ["git", "checkout", "-b", "{branch_name}"], + ["git", "push", "--set-upstream", "origin", "{branch_name}"], +] + +[actions.commit] +label = "Commit" +inputs = [ + { name = "message", label = "Commit Message" } +] +commands = [ + ["git", "add", "--update"], + ["git", "-c", "color.ui=always", "status"], + ["git", "commit", "--message", "'{message}'"], +] + +[actions.commitandpush] +label = "Commit and Push" +inputs = [ + { name = "message", label = "Commit Message" } +] +commands = [ + ["git", "add", "--update"], + ["git", "-c", "color.ui=always", "status"], + ["git", "commit", "--message", "'{message}'"], + ["git", "push"], +] + +[actions.push] +label = "Push" +commands = [ + ["git", "push"], +] + +[actions.pr] +label = "Pull Request" +inputs = [ + { name = "title", label = "Title" }, + { name = "body", label = "Body" }, +] +commands = [ + ["gh", "pr", "create", "--title", "'{title}'", "--body", "'{body}'"], +] + +[actions.release] +label = "Release" +inputs = [ + { name = "tag", label = "Release Tag", default = "v{today}" }, +] +commands = [ + ["git", "checkout", "main"], + ["git", "pull"], + ["git", "-c", "color.ui=always", "status"], + ["gh", "release", "create", "--tag", "'{tag}'"], +] + +[actions.purl] +label = "Publish PURL" +commands = [ + ["tdta", "purl-publish", "-i", "./", "-t", "CS202210140", "-u", "'{username}'"] +] + +[actions.cas_save] +label = "Save CAS" +commands = [ + ["tdta", "export", "-db", "./build/nanobot.db", "-o", "./CS202210140.json", "-c", "/tdt_datasets"] +] + +[actions.export_anndata] +label = "Export AnnData" +commands = [ + ["tdta", "anndata", "-db", "./build/nanobot.db", "-j", "./CS202210140.json", "-o", "/tdt_datasets/CS202210140.h5ad", "-c", "/tdt_datasets"] +] \ No newline at end of file diff --git a/purl/CS202210140.yml b/purl/CS202210140.yml new file mode 100644 index 00000000..339e938d --- /dev/null +++ b/purl/CS202210140.yml @@ -0,0 +1,22 @@ +# PURL configuration for https://purl.bican.org/taxonomy/CS202210140 + +idspace: CS202210140 +base_url: /taxonomy/CS202210140 + +products: +- CS202210140.json: https://raw.githubusercontent.com/hkir-dev/human-brain-cell-atlas_v1_neurons/main/CS202210140.json + +base_redirect: https://github.com/hkir-dev/human-brain-cell-atlas_v1_neurons + +entries: + +# https://purl.bican.org/taxonomy/CS202210140/CS202210140.json +- exact: /CS202210140.json + replacement: https://raw.githubusercontent.com/hkir-dev/human-brain-cell-atlas_v1_neurons/main/CS202210140.json + +# https://purl.bican.org/taxonomy/CS202210140/releases/2023-09-24/CS202210140.json +- prefix: /releases/ + replacement: https://raw.githubusercontent.com/hkir-dev/human-brain-cell-atlas_v1_neurons/v + tests: + - from: /releases/2023-09-24/ + to: https://raw.githubusercontent.com/hkir-dev/human-brain-cell-atlas_v1_neurons/v2023-09-24/ diff --git a/purl/README.md b/purl/README.md new file mode 100644 index 00000000..5ecc3d24 --- /dev/null +++ b/purl/README.md @@ -0,0 +1,3 @@ +# PURL Configuration + +BICAN Permanent URLs sample configuration file. Please make a pull request to place this file in [BICAN PURLs taxonomy configuration folder](https://github.com/hkir-dev/purl.bican.org/tree/main/config/taxonomy) via TDT 'Publish PURL' action. \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 00000000..682c6287 --- /dev/null +++ b/run.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# Wrapper script for docker. +# +# This is used primarily for wrapping the GNU Make workflow. +# Instead of typing "make TARGET", type "./run.sh make TARGET". +# This will run the make workflow within a docker container. +# +# The assumption is that you are working in the project root folder; +# we therefore map the whole repo (../..) to a docker volume. +# +# See README-editors.md for more details. + +IMAGE=${IMAGE:-taxonomy-development-tools} +TDT_DEBUG=${TDT_DEBUG:-no} + +TIMECMD= +if [ x$TDT_DEBUG = xyes ]; then + # If you wish to change the format string, take care of using + # non-breaking spaces (U+00A0) instead of normal spaces, to + # prevent the shell from tokenizing the format string. + TIMECMD="/usr/bin/time -f ### DEBUG STATS ###\nElapsed time: %E\nPeak memory: %M kb" +fi + +GITHUB_USER=$(git config user.name) +GITHUB_EMAIL=$(git config user.email) + +mkdir -p "$HOME/tdt_datasets" + +docker run -v "$PWD:/work" -v "$HOME/tdt_datasets:/tdt_datasets" -w /work --rm -ti -p 3000:3000 -p 8000:8000 -e "GITHUB_AUTH_TOKEN=$GH_TOKEN" --env "GITHUB_USER=$GITHUB_USER" --env "GITHUB_EMAIL=$GITHUB_EMAIL" ghcr.io/brain-bican/$IMAGE $TIMECMD "$@" \ No newline at end of file diff --git a/src/assets/bstreeview.css b/src/assets/bstreeview.css new file mode 100644 index 00000000..ce1a18a8 --- /dev/null +++ b/src/assets/bstreeview.css @@ -0,0 +1,59 @@ +/* + @preserve + bstreeview.css + Version: 1.2.0 + Authors: Sami CHNITER + Copyright 2020 + License: Apache License 2.0 + Project: https://github.com/chniter/bstreeview +*/ +.bstreeview { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0,0,0,.125); + border-radius: .25rem; + + padding: 0; + overflow: hidden; +} + +.bstreeview .list-group { + margin-bottom: 0; +} + +.bstreeview .list-group-item { + border-radius: 0; + border-width: 0px 0 0 0; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + cursor: pointer; + color: #428bca; +} + +.bstreeview .search-result { + color: #D9534F; +} + +.bstreeview .list-group-item:hover { + background-color:#dee2e6; +} + +.bstreeview > .list-group-item:first-child { + border-top-width: 0; +} + +.bstreeview .state-icon { + margin-right: 8px; + width: 12px; + text-align: center; +} +.bstreeview .item-icon { + margin-right: 5px; +} diff --git a/src/assets/bstreeview.js b/src/assets/bstreeview.js new file mode 100644 index 00000000..1ff72bec --- /dev/null +++ b/src/assets/bstreeview.js @@ -0,0 +1,342 @@ +/*! @preserve + * bstreeview.js + * Version: 1.2.1 + * Authors: Sami CHNITER + * Copyright 2020 + * License: Apache License 2.0 + * + * Project: https://github.com/jonmiles/bootstrap-treeview (bootstrap 3) + * : https://jonmiles.github.io/bootstrap-treeview/ + * Project: https://github.com/chniter/bstreeview (bootstrap 4) + * Project: https://github.com/nhmvienna/bs5treeview (bootstrap 5) (nanobot uses this version) + */ +; (function ($, window, document, undefined) { + "use strict"; + /** + * Default bstreeview options. + */ + var pluginName = "bstreeview", + defaults = { + expandIcon: 'fa fa-angle-down fa-fw', + collapseIcon: 'fa fa-angle-right fa-fw', + expandClass: 'show', + indent: 1.25, + parentsMarginLeft: '1.25rem', + openNodeLinkOnNewTab: true + + }; + /** + * bstreeview HTML templates. + */ + var templates = { + treeview: '
', + treeviewItem: '
', + treeviewGroupItem: '
', + treeviewItemStateIcon: '', + treeviewItemIcon: '' + }; + /** + * BsTreeview Plugin constructor. + * @param {*} element + * @param {*} options + */ + function bstreeView(element, options) { + this.element = element; + this.itemIdPrefix = element.id + "-item-"; + this.settings = $.extend({}, defaults, options); + this.init(); + } + /** + * Avoid plugin conflict. + */ + $.extend(bstreeView.prototype, { + /** + * bstreeview intialize. + */ + init: function () { + this.tree = []; + this.nodes = []; + // Retrieve bstreeview Json Data. + if (this.settings.data) { + if (this.settings.data.isPrototypeOf(String)) { + this.settings.data = $.parseJSON(this.settings.data); + } + this.tree = $.extend(true, [], this.settings.data); + delete this.settings.data; + } + // Set main bstreeview class to element. + $(this.element).addClass('bstreeview'); + + this.initData({ nodes: this.tree }); + var _this = this; + + this.build($(this.element), this.tree, 0); + // Update angle icon on collapse + $(this.element).on('click', '.list-group-item', function (e) { + $('.state-icon', this) + .toggleClass(_this.settings.expandIcon) + .toggleClass(_this.settings.collapseIcon); + // navigate to href if present + if (e.target.hasAttribute('href')) { + if (_this.settings.openNodeLinkOnNewTab) { + window.open(e.target.getAttribute('href'), '_blank'); + } + else { + window.location = e.target.getAttribute('href'); + } + } + else + { + // Toggle the data-bs-target. Issue with Bootstrap toggle and dynamic code + $($(this).attr("data-bs-target")).collapse('toggle'); + } + }); + }, + /** + * Initialize treeview Data. + * @param {*} node + */ + initData: function (node) { + if (!node.nodes) return; + var parent = node; + var _this = this; + $.each(node.nodes, function checkStates(index, node) { + + node.nodeId = _this.nodes.length; + node.parentId = parent.nodeId; + + node.state = node.state || {}; + + _this.nodes.push(node); + + if (node.nodes) { + _this.initData(node); + } + }); + }, + /** + * Build treeview. + * @param {*} parentElement + * @param {*} nodes + * @param {*} depth + */ + build: function (parentElement, nodes, depth) { + var _this = this; + // Calculate item padding. + var leftPadding = _this.settings.parentsMarginLeft; + + if (depth > 0) { + leftPadding = (_this.settings.indent + depth * _this.settings.indent).toString() + "rem;"; + } + depth += 1; + // Add each node and sub-nodes. + $.each(nodes, function addNodes(id, node) { + // Main node element. + var treeItem = $(templates.treeviewItem) + .attr('data-bs-target', "#" + _this.itemIdPrefix + node.nodeId) + .attr('style', 'padding-left:' + leftPadding) + .attr('aria-level', depth); + // Set Expand and Collapse icones. + if (node.nodes) { + var treeItemStateIcon = $(templates.treeviewItemStateIcon) + .addClass((node.expanded)?_this.settings.expandIcon:_this.settings.collapseIcon); + treeItem.append(treeItemStateIcon); + } + + // highlight searched node + if (node.searchResult) { + treeItem.addClass('search-result'); + treeItem.focus(); + } + + // set node icon if exist. + if (node.icon) { + var treeItemIcon = $(templates.treeviewItemIcon) + .addClass(node.icon); + treeItem.append(treeItemIcon); + } + // Set node Text. + treeItem.append(node.text); + // Reset node href if present + if (node.href) { + treeItem.attr('href', node.href); + } + // Add class to node if present + if (node.class) { + treeItem.addClass(node.class); + } + // Add custom id to node if present + if (node.id) { + treeItem.attr('id', node.id); + } + // Attach node to parent. + parentElement.append(treeItem); + // Build child nodes. + if (node.nodes) { + // Node group item. + var treeGroup = $(templates.treeviewGroupItem) + .attr('id', _this.itemIdPrefix + node.nodeId); + parentElement.append(treeGroup); + _this.build(treeGroup, node.nodes, depth); + if (node.expanded) { + treeGroup.addClass(_this.settings.expandClass); + } + } + }); + }, + + search: function (pattern) { + this.clearSearch(); + + // pattern is accession id now + var results = this.findNodes(pattern, 'gi', 'text'); + // Add searchResult property to all matching nodes + // This will be used to apply custom styles + // and when identifying result to be cleared + $.each(results, function (index, node) { + node.searchResult = true; + }); + this.revealNode(results); + + return results; + }, + + clearSearch : function (options) { + var results = $.each(this.findNodes('true', 'g', 'searchResult'), function (index, node) { + node.searchResult = false; + node.expanded = false; + }); + }, + + findNodes: function (pattern, modifier, attribute) { + modifier = modifier || 'g'; + attribute = attribute || 'text'; + + var _this = this; + return $.grep(this.nodes, function (node) { + var val = _this.getNodeValue(node, attribute); + if (typeof val === 'string') { + //return val.match(new RegExp(pattern, modifier)); + return val == pattern; + } + }); + }, + + identifyNode : function (identifier) { + return ((typeof identifier) === 'number') ? + this.nodes[identifier] : + identifier; + }, + + getNodeValue: function (obj, attr) { + var index = attr.indexOf('.'); + if (index > 0) { + var _obj = obj[attr.substring(0, index)]; + var _attr = attr.substring(index + 1, attr.length); + return this.getNodeValue(_obj, _attr); + } + else { + if (obj.hasOwnProperty(attr)) { + return obj[attr].toString(); + } + else { + return undefined; + } + } + }, + + revealNode : function (identifiers, options) { + this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) { + var parentNode = this.getParent(node); + while (parentNode) { + this.setExpandedState(parentNode, true, options); + parentNode = this.getParent(parentNode); + }; + }, this)); + + this.render(); + }, + + forEachIdentifier : function (identifiers, options, callback) { + options = {}; + + if (!(identifiers instanceof Array)) { + identifiers = [identifiers]; + } + + $.each(identifiers, $.proxy(function (index, identifier) { + callback(this.identifyNode(identifier), options); + }, this)); + }, + + getParent : function (identifier) { + var node = this.identifyNode(identifier); + return this.nodes[node.parentId]; + }, + + identifyNode : function (identifier) { + return ((typeof identifier) === 'number') ? + this.nodes[identifier] : + identifier; + }, + + setExpandedState : function (node, state, options) { + //if (state === node.state.expanded) return; + if (state === node.expanded) return; + + if (state && node.nodes) { + + // Expand a node + //node.state.expanded = true; + node.expanded = true; + } + else if (!state) { + + // Collapse a node + //node.state.expanded = false; + node.expanded = false; + + // Collapse child nodes + if (node.nodes && !options.ignoreChildren) { + $.each(node.nodes, $.proxy(function (index, node) { + this.setExpandedState(node, false, options); + }, this)); + } + } + }, + + render : function () { +// if (!this.initialized) { +// +// // Setup first time only components +// this.$element.addClass(pluginName); +// this.$wrapper = $(this.template.list); +// +// this.injectStyle(); +// +// this.initialized = true; +// } + + //this.$element.empty().append(this.$wrapper.empty()); + $(this.element).empty(); + // Build tree + this.build($(this.element), this.tree, 0); + } + + + + + + }); + + // A really lightweight plugin wrapper around the constructor, + // preventing against multiple instantiations + $.fn[pluginName] = function (options) { + return this.each(function () { + //if (!$.data(this, "plugin_" + pluginName)) { + $.data(this, "plugin_" + + pluginName, new bstreeView(this, options)); + //} + }); + }; +})(jQuery, window, document); diff --git a/src/assets/ols-autocomplete.css b/src/assets/ols-autocomplete.css new file mode 100644 index 00000000..863447cf --- /dev/null +++ b/src/assets/ols-autocomplete.css @@ -0,0 +1,36 @@ +.ontology-source{ + background-color: #5FBDCE; + padding:2px; + padding-right:4px; + color: white; + /*font-size: larger;*/ + border-radius: 3px; + display: inline-block; + margin-right: 4px; + vertical-align: middle; +} + +a .ontology-source { + background-color: #5FBDCE; + padding:2px; + padding-right:4px; + color: white; + /*font-size: larger;*/ + border-radius: 3px; + display: inline-block; + margin-right: 4px; + vertical-align: middle; + border-bottom: none; +} + +.term-source{ + background-color: #ffac1b; + padding:2px; + padding-right:4px; + color: white; + /*font-size: larger;*/ + border-radius: 3px; + display: inline-block; + margin-right: 4px; + vertical-align: middle; +} \ No newline at end of file diff --git a/src/assets/ols-autocomplete.js b/src/assets/ols-autocomplete.js new file mode 100644 index 00000000..d25f2698 --- /dev/null +++ b/src/assets/ols-autocomplete.js @@ -0,0 +1,177 @@ +require = function t(e, o, i) { + function a(r, s) { + if (!o[r]) { + if (!e[r]) { + var l = "function" == typeof require && require; + if (!s && l) return l(r, !0); + if (n) return n(r, !0); + var u = new Error("Cannot find module '" + r + "'"); + throw u.code = "MODULE_NOT_FOUND", u + } + var d = o[r] = { + exports: {} + }; + e[r][0].call(d.exports, function(t) { + var o = e[r][1][t]; + return a(o ? o : t) + }, d, d.exports, t, e, o, i) + } + return o[r].exports + } + for (var n = "function" == typeof require && require, r = 0; r < i.length; r++) a(i[r]); + return a +}({ + "ols-autocomplete": [function(t, e, o) { + e.exports = autocomplete = function() { + function t(t) { + var e = "terms"; + return "property" == t ? e = "properties" : "individual" == t ? e = "individuals" : "ontology" == t && (e = "ontology"), e + } + + function e(t, e, o, i, a) { + if (void 0 != o && void 0 != i) { + var n = encodeURIComponent(i); + window.location.href = t + "ontologies/" + e + "/" + o + "?iri=" + n + } else window.location.href = t + "ontologies/" + suggestion_ontoloy + } + + function o(e, o, i, a) { + e.bind("typeahead:select", function(e, i) { + if (void 0 != i.data) { + var a = t(i.data.type); + "ontology" == a ? options.action.call(this, o, i.data.ontology, a, i.data.iri, i.data, i.value) : options.action.call(this, o, i.data.ontology, a, i.data.iri, i.data, i.value) + // autocomplete selection done + // now automatically fill ID field based on the selection + autocomplete_input_name = e.currentTarget.name; + // get the matching 'id' input field's name + // classifying_ontology_term_name -> classifying_ontology_term_id + // target_input_name = autocomplete_input_name.replace("_name", "_id"); + // cell_ontology_term -> cell_ontology_term_id + target_input_name = autocomplete_input_name + "_id"; + if ($('input[name="' + target_input_name + '"]').length > 0) { + $('input[name="' + target_input_name + '"]')[0].value = i.data.shortForm; + } + } else e.target.form.submit() + }).typeahead({ + hint: !1, + highlight: !0, + minLength: 2, + limit: 4, + async: !0 + }, i) + } + + function i(e) { + return { + header: '
' + e + "
", + suggestion: function(e) { + var o = e.data.label, + i = ""; + "" != e.data.synonym && (o = e.data.synonym, i = "
synonym for " + e.value + "
"); + var a = "
" + e.data.prefix + "
", + n = t(e.data.type); + return "ontology" != n && (a += " 
" + e.data.shortForm + "
"), "
" + o + "
" + i + "
" + a + "
" + }, + footer: Handlebars.compile('
Search EBI APIs
') + } + } + + function a(t, e, o) { + var i = ""; + return e && (i = "&ontology=" + e), o && (i += "&type=" + o), new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.whitespace, + queryTokenizer: Bloodhound.tokenizers.whitespace, + identify: function(t) { + return t.id + }, + remote: { + url: t + "api/select?q=%QUERY" + i, + wildcard: "%QUERY", + transform: function(t) { + return r(t) + } + } + }) + } + + function n(t, e) { + var o = ""; + return e && (o = "&ontology=" + e), new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.whitespace, + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: t + "api/suggest?q=%QUERY" + o, + wildcard: "%QUERY", + transform: function(t) { + return jQuery.map(t.response.docs, function(t) { + return { + value: t.autosuggest + } + }) + } + } + }) + } + + function r(t) { + var e = t.responseHeader.params.q; + return jQuery.map(t.response.docs, function(o) { + var i = o.id, + a = o.label, + n = "", + r = !0; + void 0 != t.highlighting[i].label_autosuggest ? (a = t.highlighting[i].label_autosuggest[0], r = !1) : void 0 != t.highlighting[i].label && (a = t.highlighting[i].label[0], r = !1), r && (void 0 != t.highlighting[i].synonym_autosuggest ? n = t.highlighting[i].synonym_autosuggest[0] : void 0 != t.highlighting[i].synonym && (n = t.highlighting[i].synonym[0])); + var s = o.obo_id; + return void 0 == s && (s = o.short_form), { + id: i, + // display label of the selected element + value: o.label, + data: { + ontology: o.ontology_name, + prefix: o.ontology_prefix, + iri: o.iri, + label: a, + synonym: n, + shortForm: s, + type: o.type + }, + query: e + } + }) + } + options = { + action: e + }, autocomplete.prototype.start = function(t) { + options = jQuery.extend(!0, {}, options, t), jQuery("input[data-olswidget='multisearch']").each(function() { + var t = jQuery(this).data("selectpath") ? jQuery(this).data("selectpath") : "", + e = jQuery(this).data("olsontology") ? jQuery(this).data("olsontology") : "", + r = jQuery(this).data("olstype") ? jQuery(this).data("olstype") : "", + s = jQuery(this).data("suggest-header") ? jQuery(this).data("suggest-header") : "", + l = [{ + name: "suggestion", + source: n(t, e), + display: "value" + }, { + name: "selection", + source: a(t, e, r), + display: "value", + templates: i(s) + }]; + o(jQuery(this), t, l) + }), jQuery("input[data-olswidget='select']").each(function() { + var t = jQuery(this).data("selectpath") ? jQuery(this).data("selectpath") : "", + e = jQuery(this).data("olsontology") ? jQuery(this).data("olsontology") : "", + n = jQuery(this).data("olstype") ? jQuery(this).data("olstype") : "", + r = jQuery(this).data("suggest-header") ? jQuery(this).data("suggest-header") : "", + s = [{ + name: "selection", + source: a(t, e, n), + display: "value", + templates: i(r) + }]; + o(jQuery(this), t, s) + }) + } + } + }, {}] +}, {}, []); \ No newline at end of file diff --git a/src/assets/styles.css b/src/assets/styles.css new file mode 100644 index 00000000..0616693e --- /dev/null +++ b/src/assets/styles.css @@ -0,0 +1,25 @@ +.form-group.required .col-form-label:after { + color: #d00; + content: "*"; + position: absolute; + margin-left: 8px; +} +#sourceAccessionIdDiv .tt-menu { + max-height: 200px; + overflow-y: auto; +} + +#targetAccessionIdDiv .tt-menu { + max-height: 200px; + overflow-y: auto; +} + +#sourceNameDiv .tt-menu { + max-height: 200px; + overflow-y: auto; +} + +#targetNameDiv .tt-menu { + max-height: 200px; + overflow-y: auto; +} \ No newline at end of file diff --git a/src/resources/cross_taxonomy.html b/src/resources/cross_taxonomy.html new file mode 100644 index 00000000..e2f9f660 --- /dev/null +++ b/src/resources/cross_taxonomy.html @@ -0,0 +1,147 @@ +{% extends "page.html" %} + +{% block head_end %} + + + + +{% endblock %} + +{% block content %} + +{% if messages %} +{% if "success" in messages %} +{% for msg in messages["success"] %} +
+
+ +
+
+{% endfor %} +{% endif %} + +{% if "error" in messages %} +{% for msg in messages["error"] %} +
+
+ +
+
+{% endfor %} +{% endif %} + +{% if "warn" in messages %} +{% for msg in messages["warn"] %} +
+
+ +
+
+{% endfor %} +{% endif %} + +{% if "info" in messages %} +{% for msg in messages["info"] %} +
+
+ +
+
+{% endfor %} +{% endif %} +{% endif %} + +
+
+
+
+
+
+
+
+ {{ form_map["cell_set_accession"]|safe }} + {{ form_map["cell_type_name"]|safe }} +
+
+ {{ form_map["mapped_cell_set_accession"]|safe }} + {{ form_map["mapped_cell_type_name"]|safe }} +
+
+ {{ form_map["evidence_comment"]|safe }} + {{ form_map["similarity_score"]|safe }} + {{ form_map["provenance"]|safe }} +
+ + + Cancel +
+
+
+{% endblock %} + +{% block body_end %} + + +{% endblock %} \ No newline at end of file diff --git a/src/resources/ols_form.html b/src/resources/ols_form.html new file mode 100644 index 00000000..1ee916a1 --- /dev/null +++ b/src/resources/ols_form.html @@ -0,0 +1,130 @@ +{% extends "form.html" %} + +{% block head_end %} + + +{% endblock %} + +{% block body_end %} + + + +{% endblock %} \ No newline at end of file diff --git a/src/resources/table.html b/src/resources/table.html new file mode 100644 index 00000000..14f98350 --- /dev/null +++ b/src/resources/table.html @@ -0,0 +1,362 @@ +{# +## This template is derived from https://github.com/ontodev/nanobot.rs/blob/main/src/resources/table.html +## check the 'extend-start' 'extend-end' comment lines +#} + +{% extends "page.html" %} +{% block content %} +

{{ table.table }}

+ + + +{% for name, value in column|items -%} + +{% endfor %} + +
+
+ + + {# range #} + + Showing + {{ table.start }}-{{ table.end }} of + {% if table.counts.count == 1 %} + 1 row + {% else %} + {{ table.counts.count }} + {% endif %} + {% if table.counts.total > table.counts.count %} + rows filtered from {{ table.counts.total }}. + {% else %} + rows. + {% endif %} + + + {# message_rows #} + + {% if table.counts.message_row == 1 %} + 1 + row + has + {% elif table.counts.message_row > 1 %} + {{ table.counts.message_row }} + rows + have + {% endif %} + + + {# message counts #} + + {% if table.counts.message == 1 %} + 1 + message: + {% elif table.counts.message > 1 %} + {{ table.counts.message }} + messages: + {% endif %} + + + {# message types #} + + {% if table.counts.message %} + {% if table.counts.error %} + {{ table.counts.error }} + + {% endif %} + {% if table.counts.warn %} + {{ table.counts.warn }} + + {% endif %} + {% if table.counts.info %} + {{ table.counts.info}} + + {% endif %} + {% if table.counts.update %} + {{ table.counts.update}} + + {% endif %} + {% endif %} + + + Reset + + + + + + + {% if table.table != "message" %} + Add row + {% endif %} + + {# extend-start #} + {% if "_annotation" in table.table and "_labelset" not in table.table and "_metadata" not in table.table and "_annotation_transfer" not in table.table %} + Taxonomy View + {% endif %} + {# extend-end #} + +
+
+ + + + + + {% endfor %} + + + + {% for r in row -%} + + {% for column, cell in r|items -%} + {% if column == "row_number" %} + + {% else %} + + {% endif %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
+ {% for name, value in column|items -%} + + + {{ value.label or name }} + + {% if value.filtered_operator %} + + {% elif value.sorted == "asc" %} + + {% elif value.sorted == "desc" %} + + {% endif %} +
+ + + {% if cell.nulltype %} + {% elif cell.href %} + {{ cell.value }} + {% else %} + {{ cell.value }} + {% endif %} + {% if cell.messages %} + +
+{% endblock %} \ No newline at end of file diff --git a/src/resources/taxonomy_view.html b/src/resources/taxonomy_view.html new file mode 100644 index 00000000..5e7d0910 --- /dev/null +++ b/src/resources/taxonomy_view.html @@ -0,0 +1,73 @@ +{% extends "page.html" %} + +{% block head_end %} + + + + +{% endblock %} + +{% block content %} +
+

{{ table_name }} Taxonomy

+ + Table View +
+ +
+ +{% endblock %} + +{% block body_end %} + + +{% endblock %} \ No newline at end of file diff --git a/src/schema/column.tsv b/src/schema/column.tsv new file mode 100644 index 00000000..80af3e90 --- /dev/null +++ b/src/schema/column.tsv @@ -0,0 +1,20 @@ +table column label nulltype datatype structure description +table table table_name unique name of this table +table path path path to the TSV file for this table, relative to the table.tsv file +table type empty table_type type of this table, used for tables with special meanings +table edit_view empty word the name of a custom view for editing this table +table description empty description a description of this table +column table table_name from(table.table) the table that this column belongs to +column column column_name the name of this column +column label empty label the name of this column +column nulltype empty datatype_name from(datatype.datatype) the datatype for NULL values in this column +column datatype datatype_name from(datatype.datatype) the datatype for this column +column structure empty label schema information for this column +column description empty description a description of this column +datatype datatype datatype_name primary the name of this datatype +datatype parent empty datatype_name tree(datatype) the parent datatype +datatype condition empty datatype_condition +datatype description empty text a description of this datatype +datatype SQLite type empty sql_type the SQL type for representing this data in SQLite +datatype PostgreSQL type empty sql_type the SQL type for representing this data in PostgreSQL +datatype HTML type empty html_type the HTML type for viewing and editing this data \ No newline at end of file diff --git a/src/schema/datatype.tsv b/src/schema/datatype.tsv new file mode 100644 index 00000000..efb35b7e --- /dev/null +++ b/src/schema/datatype.tsv @@ -0,0 +1,43 @@ +datatype parent transform condition structure description SQLite type PostgreSQL type HTML type +CURIE nonspace match(/\w+:\w+/) a Compact URI +IRI nonspace exclude(/\s/) an Internationalized Resource Identifier +OBI_ID CURIE match(/OBI:\S+/) concat(prefix, ":", suffix) an OBI ID +boolean word lowercase in('true', 'false') a boolean: true or false radio +class_type word in('equivalent', 'subclass') an OWL class type: equivalent or subclass search +column_name trimmed_line a column name +curation_status ontology_label in('example to be eventually removed', 'metadata complete', 'metadata incomplete', 'organizational term', 'pending final vetting', 'ready for release', 'requires discussion', 'to be replaced with external ontology term', 'uncurated') the IAO curation status for a term search +datatype_condition line exclude(/\\\\\\\n/) a datatype condition specification +datatype_name word exclude(/\W/) a datatype name +description trimmed_text a brief description textarea +empty text equals('') the empty string +sql_type word in('TEXT', 'INTEGER', 'REAL', 'NUMERIC') an HTML form type search +html_type word in('text', 'textarea', 'search', 'radio', 'number', 'select') an HTML form type search +intermediate_entities word in('all', 'none') radio +label trimmed_line match(/[^\s]+.+[^\s]/) text that does not begin or end with whitespace +line text exclude(/\\\\\\\n/) a line of text text +manchester trimmed_line a Manchester syntax expression +nonnegative_integer nonspace match(/\d+/) a non-negative integer INTEGER INTEGER number +nonspace trimmed_line exclude(/\s/) text without whitespace +obsolescence_reason ontology_label in('failed exploratory term', 'placeholder removed', 'term imported', 'term split', 'terms merged') the IAO obsolescence reason for term deprecation search +obsolete_class ontology_label in('Obsolete Class') the 'Obsolete Class' parent for all deprecated terms search +ontology_id nonspace match(/\w+:\w+/) CURIE that does not begin or end with whitespace +ontology_label trimmed_text match(/[^\s]+.+[^\s]/) label that does not begin or end with whitespace text +owl_type word in('owl:AnnotationProperty', 'owl:Class', 'owl:ObjectProperty') an OWL entity type: owl:AnnotationProperty, owl:Class, or owl:ObjectProperty search +path line exclude(/\\\\\\\n/) a path to a file +positive_integer nonspace match(/\d+/) a positive integer INTEGER INTEGER number +prefix word exclude(/\W/) a prefix for a CURIE +related_entities word in('ancestors', 'children', 'descendants', 'parents') search +source nonspace in('IPD', 'GenBank', 'UniProt', 'IMGT/HLA', 'PDB') a sequence source: IPD, GenBank, or UniProt search +split_manchester manchester one or more Manchester syntax expressions separated by a pipe character +split_ontology_label ontology_label one or more labels of ontology terms separated by a pipe character +suffix word exclude(/\W/) a suffix for a CURIE +table word from(table.table) search +table_name word exclude(/\W/) a table name +table_type word lowercase in('table', 'column', 'datatype', 'index') a table type search +text any text TEXT TEXT textarea +trimmed_line line exclude(/^\s+|\s+$/) a line of text that does not begin or end with whitespace +trimmed_text text exclude(/^\s+|\s+$/) text that does not begin or end with whitespace +word nonspace exclude(/\W/) a single word: letters, numbers, underscore +cell_type word in('gross', 'family', 'leaf_node', 'None') a cell type search +autocomplete_cl line exclude(/^\s+|\s+$/) a cell type +autocomplete_uberon line exclude(/^\s+|\s+$/) a cell type diff --git a/src/schema/table.tsv b/src/schema/table.tsv new file mode 100644 index 00000000..a305b596 --- /dev/null +++ b/src/schema/table.tsv @@ -0,0 +1,4 @@ +table path type edit_view description +table src/schema/table.tsv table All of the user-editable tables in this project. +column src/schema/column.tsv column Columns for all of the tables. +datatype src/schema/datatype.tsv datatype Datatypes for all of the columns \ No newline at end of file