From ca43e77beefa209a9cbd1c1fa8aa72820e726963 Mon Sep 17 00:00:00 2001 From: a-shilin <147729544+a-shilin@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:05:55 -0500 Subject: [PATCH] Single cell browser component (#805) * starting component * further buildout of components * fixing v-key errors * Single Cell Browser component(s) * adding hover labels to umap buttons and 'colo by' labels * add umap tooltip for zoom/pan * data cleanup fix * starting select component for single cell browser color picker * cfde citation formatting * adding support for query string params --- .../ResearchSingleCellBrowser.vue | 180 +++++++---- .../ResearchSingleCellSelector.vue | 300 ++++++++++++++++++ .../researchPortal/ResearchUmapPlot.vue | 19 +- src/utils/formatters.js | 4 +- 4 files changed, 440 insertions(+), 63 deletions(-) create mode 100644 src/components/researchPortal/ResearchSingleCellSelector.vue diff --git a/src/components/researchPortal/ResearchSingleCellBrowser.vue b/src/components/researchPortal/ResearchSingleCellBrowser.vue index e256502cb..9ef311c00 100644 --- a/src/components/researchPortal/ResearchSingleCellBrowser.vue +++ b/src/components/researchPortal/ResearchSingleCellBrowser.vue @@ -32,6 +32,7 @@
+
-
+
Color By + + +
@@ -107,6 +119,7 @@
+ -->
@@ -162,11 +175,12 @@
-
+
@@ -204,6 +218,7 @@
vs
+
Color By + +
@@ -279,6 +304,7 @@
+ -->
@@ -339,6 +365,7 @@
@@ -377,7 +404,8 @@
-
+
+
Cell Proportion {{ segmentByLabel }} per {{ displayByLabel }}
@@ -442,6 +470,7 @@ />
+
@@ -498,6 +527,7 @@ import ResearchBarPlotV2 from "@/components/researchPortal/ResearchBarPlotV2.vue"; import ResearchDotPlot from "@/components/researchPortal/ResearchDotPlot.vue"; import ResearchViolinPlot from "@/components/researchPortal/ResearchViolinPlot.vue"; + import ResearchSingleCellSelector from "@/components/researchPortal/ResearchSingleCellSelector.vue"; const colors = ["#007bff","#048845","#8490C8","#BF61A5","#EE3124","#FCD700","#5555FF","#7aaa1c","#F88084","#9F78AC","#F5A4C7","#CEE6C1","#cccc00","#6FC7B6","#D5A768","#d4d4d4"] @@ -506,7 +536,8 @@ ResearchUmapPlot, ResearchBarPlotV2, ResearchDotPlot, - ResearchViolinPlot + ResearchViolinPlot, + ResearchSingleCellSelector }, props: { sectionId: { @@ -586,7 +617,6 @@ segmentByCounts: null, datasetId: null, - datasetName: null, cellTypeField: null, geneNames: [], @@ -595,7 +625,6 @@ dataLoaded: false, preloadItem: '', - tissueDisplay: '', highlightHoverTimeout: null, selectedTabs: {"a":"1", "b":"2"}, @@ -637,23 +666,48 @@ if(data.id===this.sectionId){ console.log(this.sectionId, 'Received on-select event:', data); this.datasetId = data.value; + if(this.renderConfig["parameters"]?.datasetId){ + keyParams.set({[this.renderConfig["parameters"]?.datasetId] : this.datasetId}); + } this.init(); } }, - async init(){ - /* - if(this.data.length !== 1){ - if(this.datasetData) { - console.log('--->', this.datasetData); - }else{ - console.log("please select a dataset"); - return; + clean(){ + this.expressionStats = []; + this.cellCompositionVars = { + "a": { + umapColors: null, + colorByLabel: null, + highlightLabel: '', + highlightLabels: [], + cellTypeInfo: null + }, + "b": { + umapColors: null, + colorByLabel: null, + highlightLabel: '', + highlightLabels: [], + cellTypeInfo: null + } + }, + this.geneExpressionVars = { + "a": { + umapGeneColors: null, + selectedGene: null, + expressionStats: [], + selectedLabel: null, + }, + "b": { + umapGeneColors: null, + selectedGene: null, + expressionStats: [], + selectedLabel: null, } - }else{ - this.datasetId = this.data[0].datasetId; } - */ - + }, + async init(){ + //check which components to enable based on cofig options + //all are enabled by default this.componentsConfig = this.renderConfig["components"]; this.showCellInfo = this.componentsConfig?.["cell info"]?.enabled ?? true; this.showCellProportion = this.componentsConfig?.["cell proportion"]?.enabled ?? true; @@ -662,25 +716,38 @@ this.presetsConfig = this.renderConfig["presets"]; - //if user has not selected a dataset from the list + //check for datasetId + /* it can come from multiple places + 1. 'on-select' event from byor + 2. query string param + 3. config preset + */ if(!this.datasetId || this.datasetId === ''){ - if(this.presetsConfig?.datasetId){ + if(keyParams[this.renderConfig["parameters"]?.datasetId]){ + this.datasetId = keyParams[this.renderConfig["parameters"].datasetId]; + }else if(this.presetsConfig?.datasetId){ this.datasetId = this.presetsConfig.datasetId }else{ console.log('select a dataset'); return; } } - - console.log(`loading dataset: ${this.datasetData.datasetId}`); - console.log(' data', this.datasetData); - this.tissueDisplay = this.datasetData["Tissue"]; - this.datasetName = this.datasetData["Name"]; - this.cellTypeField = this.presetsConfig?.["cell type label"]; - console.log("cellTypeField", this.cellTypeField, this.presetsConfig); - this.cellCompositionVars['a'].colorByLabel = this.cellTypeField; + console.log(`requested dataset: ${this.datasetId}`); + + //make sure it exists in the metadata + if(!this.data.find(x => x.datasetId === this.datasetId)){ + console.log('dataset', this.datasetId, 'not in collection'); + this.datasetId = null; + return; + } + console.log(' data', this.datasetData); + + //clear existing data + this.clean(); + + //fetch base data this.dataLoaded = false; this.preloadItem = 'fields'; this.rawData = await this.fetchFields(); @@ -697,10 +764,18 @@ //pre-calculate colors for fields in each category this.labelColors = this.calcLabelColors(this.rawData); + this.colorScalePlasmaColorsArray = d3.range(0, 1.01, 0.1).map(t => this.colorScalePlasma(t)).join(', '); this.getColorByOptions(); + //which label designates cell types + this.cellTypeField = this.presetsConfig?.["cell type label"]; + + console.log("cellTypeField", this.cellTypeField, this.presetsConfig); + + this.cellCompositionVars['a'].colorByLabel = this.cellTypeField; + this.selectColorBy(this.cellTypeField, 'a'); this.selectColorBy(this.cellTypeField, 'b'); @@ -721,17 +796,17 @@ console.log('cell proportion component disabled') } - this.expressionStats = []; - - - if(keyParams.gene){ - this.fetchGeneExpression(keyParams.gene.toUpperCase()); + //load gene data from parameters + if(this.renderConfig["parameters"]?.gene){ + if(keyParams[this.renderConfig["parameters"].gene]){ + this.fetchGeneExpression(keyParams.gene.toUpperCase()); + } } - + //load gene data from config if(this.presetsConfig?.["genes"]){ this.presetsConfig["genes"].forEach(async (gene) => { - await this.fetchGeneExpression(gene.toUpperCase()); + this.fetchGeneExpression(gene.toUpperCase()); }) } @@ -748,22 +823,10 @@ this.colorByOptions = colorByOptions2; }, - updateDataUrl(url, paramaters){ - let updatedUrl = url; - //tmp - //updatedUrl = updatedUrl.replace(`$datasetId`, this.datasetData['Tissue'].toLowerCase()); - - //use below once metadata is fixed - paramaters.forEach(param => { - if(param !== 'gene') - updatedUrl = updatedUrl.replace(`$${param}`, this.datasetData[param]); //.toLowerCase().replaceAll(" ", "_") - }) - return updatedUrl; - }, async fetchFields() { console.log('getting fields'); const fieldsDataPoint = this.renderConfig["data points"].find(x => x.role === "fields"); - const fieldsUrl = this.updateDataUrl(fieldsDataPoint.url, fieldsDataPoint.parameters); + const fieldsUrl = fieldsDataPoint.url.replace('$datasetId', this.datasetId); try { const response = await fetch(fieldsUrl); const rawData = await response.json(); @@ -777,7 +840,7 @@ async fetchCoordinates() { console.log('getting coordinates'); const coordinatesDataPoint = this.renderConfig["data points"].find(x => x.role === "coordinates"); - const coordinatesUrl = this.updateDataUrl(coordinatesDataPoint.url, coordinatesDataPoint.parameters); + const coordinatesUrl = coordinatesDataPoint.url.replace('$datasetId', this.datasetId); try { const response = await fetch(coordinatesUrl); const json = this.utils.dataConvert.tsv2Json(await response.text()); @@ -790,11 +853,11 @@ async fetchGeneExpression(gene){ console.log('fetchGeneExpression', gene); const expressionDataPoint = this.renderConfig["data points"].find(x => x.role === "expression"); - const expressionUrl = this.updateDataUrl(expressionDataPoint.url, expressionDataPoint.parameters); + const expressionUrl = expressionDataPoint.url.replace('$datasetId', this.datasetId).replace('$gene', gene); //this.isLoading = true; await Vue.nextTick(); try{ - const response = await fetch(expressionUrl+','+gene); + const response = await fetch(expressionUrl); const json = await response.json(); const expression = json.data[0]['expression']; this.geneNames.push(gene); @@ -926,6 +989,17 @@ return false; }, + handleSelectorUpdate(e, group, id){ + console.log('selector updated', group, id, e); + this.cellCompositionVars[group].highlightLabels = e.coloredLabels; + this.selectColorBy(e.coloredField, group); + }, + + handleSelectorHover(e, group, id){ + console.log('selector hovered', group, id, e); + this.cellCompositionVars[group].highlightLabel = e.hoveredLabel; + }, + /* cell composition */ diff --git a/src/components/researchPortal/ResearchSingleCellSelector.vue b/src/components/researchPortal/ResearchSingleCellSelector.vue new file mode 100644 index 000000000..3a6872958 --- /dev/null +++ b/src/components/researchPortal/ResearchSingleCellSelector.vue @@ -0,0 +1,300 @@ + + + + + + \ No newline at end of file diff --git a/src/components/researchPortal/ResearchUmapPlot.vue b/src/components/researchPortal/ResearchUmapPlot.vue index 9c2b91f72..6c298fbbc 100644 --- a/src/components/researchPortal/ResearchUmapPlot.vue +++ b/src/components/researchPortal/ResearchUmapPlot.vue @@ -3,12 +3,15 @@ {{ title }}
- - +
@@ -24,7 +27,7 @@ @mouseleave="endPan" > -
+
@@ -221,7 +224,7 @@ this.pointBoundsCalculated = true; } - if (!this.clusterCentersInitialized && this.fields) { + if (this.fields && !this.clusterCentersInitialized) { this.clusterCenters = {}; points.forEach((coord, index) => { @@ -560,15 +563,15 @@ .umapTooltip.hidden{ display:none; } - .tooltip{ +.scb-tooltip{ position:fixed; background: white; padding: 5px 10px; box-shadow: rgba(0, 0, 0, 0.5) -4px 9px 25px -6px; - } - .tooltip.show{ +} +.scb-tooltip.show{ opacity: 1; - } +} button { border: 1px solid rgba(0, 0, 0, .25); diff --git a/src/utils/formatters.js b/src/utils/formatters.js index 1849b465a..e3f3183ef 100644 --- a/src/utils/formatters.js +++ b/src/utils/formatters.js @@ -655,8 +655,8 @@ function BYORColumnFormatter(VALUE, KEY, CONFIG, PMAP, DATA_SCORES) {
${item.authors} ${item.publication}
${item.description}