From 393d5b4ee0f4e09f04b7e38c2871eb33b39125e5 Mon Sep 17 00:00:00 2001 From: a-shilin <147729544+a-shilin@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:02:18 -0500 Subject: [PATCH] Single cell browser component (#819) * 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 * adding layouts + more * single cell plot updates --- .../ResearchSingleCellBrowser.vue | 877 +++++------------- .../researchPortal/ResearchStackedBarPlot.vue | 466 ++++++++++ .../researchPortal/ResearchViolinPlot.vue | 143 ++- .../customComponents/cfdeLanding.vue | 2 +- 4 files changed, 815 insertions(+), 673 deletions(-) create mode 100644 src/components/researchPortal/ResearchStackedBarPlot.vue diff --git a/src/components/researchPortal/ResearchSingleCellBrowser.vue b/src/components/researchPortal/ResearchSingleCellBrowser.vue index 335729001..698a973fb 100644 --- a/src/components/researchPortal/ResearchSingleCellBrowser.vue +++ b/src/components/researchPortal/ResearchSingleCellBrowser.vue @@ -4,7 +4,7 @@ Please Select a Dataset
- Loading + Loading {{ preloadItem }}...
@@ -42,7 +42,7 @@
-
+
@@ -91,66 +91,63 @@ @on-update="handleSelectorUpdate($event, 'a', '1')" @on-hover="handleSelectorHover($event, 'a', '1')" /> - -
-
-
-
-
Cell Count per {{ cellCompositionVars['a'].cellTypeInfo.key }}
- +
+
+
+ +
+
+ {{ label }} +
+
+
@@ -177,7 +174,7 @@ :isLoading="isLoadingData" />
-
+
Gene Search
@@ -186,7 +183,7 @@
-
+
-
+
Gene Expression {{ geneExpressionVars['a'].selectedGene ? `${geneExpressionVars['a'].selectedGene}` : '' }} -
-
+
+
Display
-
+
-
+
Subset by
-
+
- - - -
-
-
-
Color/Subset By
-
- -
-
-
-
-
-
- -
-
- {{ label }} -
-
-
- -
-
-
-
+
+
Marker Genes
@@ -585,7 +516,7 @@
- -
-
- -
-
-
- Cell Composition -
-
- Gene Expression -
-
-
-
-
-
-
- UMAP {{ coordinates.length.toLocaleString() }} cells -
- -
-
- Color By - - - -
-
-
-
-
Cell Count per {{ cellCompositionVars['a'].cellTypeInfo.key }}
- -
-
-
-
-
-
-
- UMAP {{ geneExpressionVars['a'].selectedGene ? `${geneExpressionVars['a'].selectedGene}` : '' }} {{ coordinates.length.toLocaleString() }} cells -
- -
-
- Gene Search -
-
- - -
-
-
-
-
- -
-
{{ gene }}
-
-
-
-
-
-
-
-
-
- Gene Expression {{ geneExpressionVars['a'].selectedGene ? `${geneExpressionVars['a'].selectedGene}` : '' }} -
-
-
Display
-
- -
-
-
-
Subset by
-
- -
-
-
-
-
-
- -
-
- {{ label }} -
-
-
-
- -
-
-
-
-
-
-
-
- Marker Genes -
-
-
Expression
-
-
0.0
3.0
-
-
-
% Cells Expressing
-
-
-
-
-
-
-
-
0
100
-
-
-
- - -
-
-
-
@@ -949,7 +607,8 @@ import keyParams from "@/utils/keyParams"; import EventBus from "@/utils/eventBus" import ResearchUmapPlot from "@/components/researchPortal/ResearchUmapPlot.vue"; - import ResearchBarPlotV2 from "@/components/researchPortal/ResearchBarPlotV2.vue"; + //import ResearchBarPlotV2 from "@/components/researchPortal/ResearchBarPlotV2.vue"; //DELETE + import ResearchStackedBarPlot from "@/components/researchPortal/ResearchStackedBarPlot.vue"; import ResearchDotPlot from "@/components/researchPortal/ResearchDotPlot.vue"; import ResearchViolinPlot from "@/components/researchPortal/ResearchViolinPlot.vue"; import ResearchSingleCellSelector from "@/components/researchPortal/ResearchSingleCellSelector.vue"; @@ -959,7 +618,8 @@ export default Vue.component('research-single-cell-browser', { components: { ResearchUmapPlot, - ResearchBarPlotV2, + //ResearchBarPlotV2, + ResearchStackedBarPlot, ResearchDotPlot, ResearchViolinPlot, ResearchSingleCellSelector @@ -1005,6 +665,20 @@ labelColors: null, colorByOptions: null, //rename? + datasetId: null, + cellTypeField: null, + + geneNames: [], + expressionData: {}, + expressionStatsAll: [], + + dataLoaded: false, + preloadItem: '', + + highlightHoverTimeout: null, + + selectedTabs: {"a":"1", "b":"2"}, + layout: -1, cellCompositionVars: { @@ -1013,17 +687,20 @@ colorByLabel: null, highlightLabel: '', highlightLabels: [], - cellTypeInfo: null + segmentByCounts2: null, + displayByLabel: null, + subsetLabel: "", }, "b": { umapColors: null, colorByLabel: null, highlightLabel: '', highlightLabels: [], - cellTypeInfo: null + segmentByCounts2: null, + displayByLabel: null, + subsetLabel: "", } }, - geneExpressionVars: { "a": { umapGeneColors: null, @@ -1039,25 +716,7 @@ selectedLabel: null, subsetLabel: "", } - }, - - displayByLabel: '', - segmentByLabel: '', - segmentByCounts: null, - - datasetId: null, - cellTypeField: null, - - geneNames: [], - expressionData: {}, - expressionStats: [], - expressionStats2: [], - - dataLoaded: false, - preloadItem: '', - highlightHoverTimeout: null, - - selectedTabs: {"a":"1", "b":"2"}, + } } }, watch: { @@ -1069,14 +728,15 @@ Object.keys(this.expressionData).forEach(gene => { expressionStats.push(...this.getExpressionStats(gene, this.cellTypeField, null, true)); }) - this.expressionStats2 = expressionStats; - console.log('updated expression stats', this.expressionStats2); + this.expressionStatsAll = expressionStats; + console.log('updated expression stats', this.expressionStatsAll); } }, mounted() { //load metadata from renderConfig console.log('renderConfig', this.renderConfig); console.log('data', this.data); + EventBus.$on('on-select',this.handleSelectEvent); this.init(); }, @@ -1113,21 +773,25 @@ clean(){ this.expressionData = {}; this.geneNames = []; - this.expressionStats2 = []; + this.expressionStatsAll = []; this.cellCompositionVars = { "a": { umapColors: null, colorByLabel: null, highlightLabel: '', highlightLabels: [], - cellTypeInfo: null + segmentByCounts2: null, + displayByLabel: null, + subsetLabel: "", }, "b": { umapColors: null, colorByLabel: null, highlightLabel: '', highlightLabels: [], - cellTypeInfo: null + segmentByCounts2: null, + displayByLabel: null, + subsetLabel: "", } }, this.geneExpressionVars = { @@ -1218,31 +882,19 @@ this.cellTypeField = this.presetsConfig?.["cell type label"] || Object.keys(this.rawData["metadata_labels"])[0]; console.log("cellTypeField", this.cellTypeField, this.presetsConfig); - - + + //preset base visualizers to display by cell type this.cellCompositionVars['a'].colorByLabel = this.cellTypeField; this.selectColorBy(this.cellTypeField, 'a'); this.selectColorBy(this.cellTypeField, 'b'); - - this.updateCellsInfo(this.cellTypeField, 'a'); - this.updateCellsInfo(this.cellTypeField, 'b'); + + this.selectSegmentBy(this.cellTypeField, "", 'a'); + this.selectSegmentBy(this.cellTypeField, "", 'b'); this.geneExpressionVars['a'].selectedLabel = this.cellTypeField; this.geneExpressionVars['b'].selectedLabel = this.cellTypeField; - - if(this.showCellProportion){ - if(this.rawData['metadata_labels']['Diabetes Status']) { - this.selectSegmentBy(this.cellTypeField, "Diabetes Status"); - }else{ - this.selectSegmentBy(this.cellTypeField, Object.keys(this.rawData["metadata_labels"])[0]); - } - }else{ - console.log('cell proportion component disabled') - } - - //load gene data from parameters if(this.renderConfig["parameters"]?.gene){ const paramGenes = decodeURIComponent(keyParams[this.renderConfig["parameters"].gene]); @@ -1309,6 +961,7 @@ } }, async fetchGeneExpression(gene){ + //return; const expressionDataPoint = this.renderConfig["data points"].find(x => x.role === "expression"); const expressionUrl = expressionDataPoint.url.replace('$datasetId', this.datasetId).replace('$gene', gene); @@ -1339,9 +992,9 @@ //update query string gene params if(this.renderConfig["parameters"]?.gene){ - const paramGenes = decodeURIComponent(keyParams[this.renderConfig["parameters"].gene]); + let paramGenes = decodeURIComponent(keyParams[this.renderConfig["parameters"].gene]); if(paramGenes){ - const paramGenesArray = paramGenes.toLowerCase().split(','); + const paramGenesArray = paramGenes==='undefined' ? [] : paramGenes.toLowerCase().split(','); console.log(`try adding: ${gene} to ${paramGenesArray}`) if(!paramGenesArray.includes(gene.toLowerCase())){ paramGenesArray.push(gene); @@ -1353,13 +1006,6 @@ } await Vue.nextTick(); - - //this.expressionStats = this.parseGeneExpression(this.cellTypeField); - //this.expressionStats2[gene] = this.getExpressionStats(gene, this.cellTypeField, null, true); - //Vue.set(this.expressionStats2, gene, this.getExpressionStats(gene, this.cellTypeField, null, true)); - //this.expressionStats2 = this.getExpressionStats(gene, this.cellTypeField, null, true); - //console.log(' stats', this.expressionStats); - //console.log(' stats2', this.expressionStats2); if(!this.geneExpressionVars['a'].selectedGene){ this.geneClick(gene, 'a'); @@ -1389,24 +1035,21 @@ console.log('color by:', val); g.colorByLabel = val; g.umapColors = this.getColorsByLabel(g.colorByLabel); - this.updateCellsInfo(g.colorByLabel, group); }, - selectSegmentBy(display, segment){ + selectSegmentBy(display, segment, group){ const displayVal = typeof display === 'object' ? display.target.value : display; const segmentVal = typeof segment === 'object' ? segment.target.value : segment; - console.log('segment by:', {displayVal, segmentVal}); - this.displayByLabel = displayVal - this.segmentByLabel = segmentVal; - //this.segmentByCounts = this.getCountsByLabelSubset(this.labels, this.displayByLabel, this.segmentByLabel); - this.segmentByCounts = this.getCounts(this.displayByLabel, this.segmentByLabel); - console.log('segmentByCounts', this.segmentByCounts); + const g = this.cellCompositionVars[group]; + console.log('segment by:', {displayVal, segmentVal, group}); + g.displayByLabel = displayVal + g.segmentByLabel = segmentVal; + g.segmentByCounts2 = this.getCounts2(g.displayByLabel, g.segmentByLabel); + console.log('segmentByCounts2', g.segmentByCounts2); }, selectExpressionBy(e, group){ const val = typeof e === 'object' ? e.target.value : e; const g = this.geneExpressionVars[group]; console.log('expression by:', val, group, g); - //g.selectedLabel = val; - //g.expressionStats = this.parseGeneExpression(g.selectedLabel); g.expressionStats = this.getExpressionStats(g.selectedGene, g.selectedLabel, g.subsetLabel); }, calcLabelColors(rawData){ @@ -1468,10 +1111,7 @@ geneClick(e, group){ const gene = e; const g = this.geneExpressionVars[group]; - //g.expressionStats = this.parseGeneExpression(g.selectedLabel); - //g.subsetLabel = "bmi__group"; g.expressionStats = this.getExpressionStats(gene, g.selectedLabel, g.subsetLabel); - //console.log(' #####', gene, g) g.selectedGene = gene; g.umapGeneColors = this.getUmapExpressionColors(gene); }, @@ -1504,17 +1144,6 @@ /* cell composition */ - async updateCellsInfo(cellTypeCategory, group){ - const g = this.cellCompositionVars[group]; - g.cellTypeInfo = { - key: cellTypeCategory, - data: {[cellTypeCategory]: this.getCounts(cellTypeCategory)}, - colors: Object.values(this.labelColors[cellTypeCategory]) - }; - console.log(" cellTypeInfo", g.cellTypeInfo); - - return; - }, getCellLabels(rawData, labelKeys){ console.log('get CellAnnotations'); @@ -1580,6 +1209,56 @@ console.log(' >>>', result); return result; }, + getCounts2(primaryKey, subsetKey){ + console.log('getCounts2', primaryKey, subsetKey) + const keys = this.rawData["metadata_labels"]; + const values = this.rawData["metadata"]; + + const primaryLabels = keys[primaryKey]; + const primaryValues = values[primaryKey]; + + const result = []; + + if (!subsetKey) { + // calculate counts by primary key only + primaryLabels.forEach((label, index) => { + const indices = primaryValues + .map((value, i) => (value === index ? i : -1)) + .filter(i => i !== -1); + + result.push({ + [primaryKey]: label, + count: indices.length, + color: this.labelColors[primaryKey][label] + }); + }); + } else { + // calculate counts grouped by primary key and subset key + const subsetValues = values[subsetKey]; + const subsetLabels = keys[subsetKey]; + + primaryLabels.forEach((primaryLabel, primaryIndex) => { + const primaryIndices = primaryValues + .map((value, i) => (value === primaryIndex ? i : -1)) + .filter(i => i !== -1); + + subsetLabels.forEach((subsetLabel, subsetIndex) => { + const subsetIndices = primaryIndices.filter( + i => subsetValues[i] === subsetIndex + ); + result.push({ + [primaryKey]: primaryLabel, + [subsetKey]: subsetLabel, + count: subsetIndices.length, + color: this.labelColors[subsetKey][subsetLabel] + }) + }); + }); + } + + console.log(' >>>', result); + return result; + }, /* gene expression @@ -1592,60 +1271,6 @@ await this.fetchGeneExpression(gene.toUpperCase()); }) }, - parseGeneExpression(category){ - //const categories = side==='left'?this.categoriesLeft:this.categoriesRight; - - console.log('parseGeneExpression'); - - // Get expressinon values for user selected categories - const expressionByCategory = (category) => { - const categoryLabels = this.rawData['metadata_labels'][category];//.slice().sort((a, b) => a.localeCompare(b)); - const categoryData = this.rawData['metadata'][category]; - const geneExpression = {}; - const sumstat = {}; - - //geneNames * 160000 + geneNames * labels - this.geneNames.forEach(gene => { - - if(!geneExpression[gene]) geneExpression[gene] = {} - - categoryData.forEach((labelIdx, cellIdx) => { - const label = categoryLabels[labelIdx]; - if (!geneExpression[gene][label]) { - geneExpression[gene][label] = []; - } - geneExpression[gene][label].push(this.expressionData[gene][cellIdx]); - }); - geneExpression[gene] = geneExpression[gene];//this.sortObjectKeysLocale(geneExpression[gene]); - - categoryLabels.forEach(label => { - //const sortedValues = geneExpression[gene][label] ? geneExpression[gene][label].sort(d3.descending) : [0]; - const exprValues = geneExpression[gene][label] ? geneExpression[gene][label].sort(d3.ascending) : [0]; - const key = label; - const mean = d3.mean(exprValues) - const q1 = d3.quantile(exprValues, .25) - const median = d3.quantile(exprValues, .5) - const q3 = d3.quantile(exprValues, .75) - const interQuantileRange = q3 - q1 - const min = exprValues[0] - const max = exprValues[exprValues.length-1] - const pctExpr = (exprValues.filter(val => val > 0).length / exprValues.length) * 100;//sortedValues.length / 166149; - if(!sumstat[gene]) sumstat[gene] = []; - sumstat[gene].push({ key, mean, q1, median, q3, interQuantileRange, min, max, pctExpr, exprValues }); - - }) - }) - - return sumstat; - } - - const e = expressionByCategory(category); - - console.log(' expressionByCategory', e); - - return [e]; - }, - getExpressionStats(gene, primaryKey, subsetKey, partial=false) { const expression = this.expressionData[gene]; const keys = this.rawData["metadata_labels"]; @@ -1681,15 +1306,11 @@ .map((value, i) => (value === primaryIndex ? i : -1)) .filter(i => i !== -1); - //result[primaryLabel] = {}; - subsetLabels.forEach((subsetLabel, subsetIndex) => { const subsetIndices = primaryIndices.filter( i => subsetValues[i] === subsetIndex ); - const exprValues = subsetIndices.map(i => expression[i]); - //result[primaryLabel][subsetLabel] = this.calculateStats(exprValues); result.push({ gene: gene, [primaryKey]: primaryLabel, @@ -1739,7 +1360,7 @@ const geneData = this.expressionData[gene]; //console.log('---', this.expressionData, this.geneNames, this.expressionData[gene]) const color = d3.scaleSequential(d3.interpolatePlasma) - .domain([d3.max(geneData), 0]); + .domain([d3.max(geneData), 0]); for(var i=0; i +
+
+
+
count
+
pct.
+
+
+
group
+
stack
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/components/researchPortal/ResearchViolinPlot.vue b/src/components/researchPortal/ResearchViolinPlot.vue index 43610124d..0c520e81d 100644 --- a/src/components/researchPortal/ResearchViolinPlot.vue +++ b/src/components/researchPortal/ResearchViolinPlot.vue @@ -49,6 +49,8 @@ }, data() { return { + eventElements: [], + tooltip: null, } }, watch: { @@ -69,6 +71,9 @@ }, beforeDestroy(){ //window.removeEventListener('resize', this.handleResize); + if(this.eventElements.length>0) { + this.removeAllListeners(this.eventElements); + } }, methods: { handleResize(){ @@ -80,13 +85,17 @@ if(!this.data) return; - const tooltip = this.$refs.tooltip; + //clear previous event listeners + if(this.eventElements.length>0) { + this.removeAllListeners(this.eventElements); + this.eventElements = []; + } + + const tooltip = this.tooltip = this.$refs.tooltip; const primaryKey = this.primaryKey; const subsetKey = this.subsetKey; - + const hasSubsetKey = subsetKey; const keys = Array.from(new Set(this.data.map((d) => d[primaryKey]))); - const hasSubsetKey = subsetKey && this.data[0][subsetKey] && this.data[0][subsetKey] !== ""; - const domain = hasSubsetKey ? this.data.map((d) => d[primaryKey] +' - '+d[subsetKey]) : keys; //pre-render x-axis labels to get the their max height //this way we can ensure long labels dont get cut off at the bottom @@ -108,7 +117,7 @@ //calculate sizes and margins const parentWidth = this.$refs.chartWrapper.parentElement.offsetWidth; const labels = { xAxis: this.xAxisLabel?20:0, yAxis: this.yAxisLabel?20:0 } - const margin = { top: 20, right: 10, bottom: labelsHeight + labels.xAxis, left: 40 }; + const margin = { top: 10, right: 10, bottom: labelsHeight + labels.xAxis, left: 40 }; let width = parentWidth; let height = this.height; if(margin.bottom > (height/2)){ @@ -127,6 +136,7 @@ width = plotWidth + margin.left + margin.right; */ + //get absolute min/max values const min = d3.min(this.data, (d) => d.min); const max = d3.max(this.data, (d) => d.max); @@ -135,6 +145,7 @@ .attr('width', width) .attr('height', height) + //rednder axis labels if(this.xAxisLabel){ const label = svg.append('g') .append('text') @@ -155,7 +166,8 @@ } const plot = svg.append("g") - .attr("transform", `translate(${margin.left+labels.xAxis},${margin.top})`); + .attr("transform", `translate(${margin.left+labels.xAxis},${margin.top})`) + .attr("class", 'plot'); const entryKey = (entry) => { if(hasSubsetKey){ @@ -165,6 +177,8 @@ } } + const domain = hasSubsetKey ? this.data.map((d) => d[primaryKey] +' - '+d[subsetKey]) : keys; + // x scale const x = d3.scaleBand() .domain(domain) @@ -200,6 +214,8 @@ const boxWidth = x.bandwidth() * 0.6; + //add background boxes to separate primaryKey sections + //when they have subsetKey items if(hasSubsetKey){ keys.forEach((key, i) => { plot.append('rect') @@ -211,15 +227,19 @@ }) } + //draw the violins this.data.forEach((entry) => { const xCenter = x(entryKey(entry)) + x.bandwidth() / 2; const box = plot.append('g') .attr("width", boxWidth) - .attr("class", "violin-group") - .attr("data-key", entryKey(entry)); + .attr('class', 'bar') + .attr('data-label', `${entry[primaryKey]},${hasSubsetKey ? entry[subsetKey] : ''}`) + /*.attr("class", "violin-group") + .attr("data-key", entryKey(entry));*/ const boxNode = box.node(); + this.addListener(boxNode, entry); // kde const bandwidth = 1; @@ -300,33 +320,6 @@ .attr("height", y(entry.min) - y(entry.max)) .attr("fill", "transparent") .style("pointer-events", "all"); - - - // Tooltip mouseover - boxNode.addEventListener('mouseover', function(e){ - tooltip.innerHTML = ` -
${primaryKey}:
${entry[primaryKey]}
-
${subsetKey}:
${entry[subsetKey]}
-
Gene:
${entry.gene}
-
Max:
${entry.max}
-
Q3:
${entry.q3}
-
Median:
${entry.median.toFixed(4)}
-
Q1:
${entry.q1}
-
Min:
${entry.min}
- `; - tooltip.classList.add('show') - }) - // Tooltip mousemove to follow the cursor - boxNode.addEventListener('mousemove', function(e){ - tooltip.style.top = (e.clientY - 10) + "px"; - tooltip.style.left = (e.clientX + 10) + "px"; - }) - // Tooltip mouseout to hide it - boxNode.addEventListener('mouseout', function(e){ - tooltip.classList.remove('show'); - tooltip.style.top = -1000 + "px"; - tooltip.style.left = -1000 + "px"; - }); }); }, kde(kernel, thresholds, data) { @@ -338,16 +331,72 @@ return Math.abs(u) <= 1 ? 0.75 * (1 - u * u) / bandwidth : 0; }; }, - doHighlight(key){ - const svg = this.$refs.chart; - const violins = svg.querySelectorAll('.violin-group'); - violins.forEach(violin=>{ - if(!key || violin.dataset.key===key){ - violin.style.opacity = '1'; - }else{ - violin.style.opacity = '0.1'; - } + addListener(el, entry){ + const mouseOver = this.mouseOverHandler.bind(this, entry); + const mouseMove = this.mouseMoveHandler.bind(this); + const mouseOut = this.mouseOutHandler.bind(this); + el._listeners = { mouseOver, mouseMove, mouseOut }; + this.eventElements.push(el); + // Tooltip mouseover + el.addEventListener('mouseover', mouseOver); + el.addEventListener('mousemove', mouseMove); + el.addEventListener('mouseout', mouseOut); + }, + removeListener(el){ + if(el._listeners){ + el.addEventListener('mouseover', el._listeners.mouseOver); + el.addEventListener('mousemove', el._listeners.mouseMove); + el.addEventListener('mouseout', el._listeners.mouseOut); + delete el._listeners; + } + }, + removeAllListeners(elsArr){ + console.log(`removing event listeners for ${elsArr.length} elements`); + elsArr.forEach(el=>{ + this.removeListener(el); + }); + }, + mouseOverHandler(entry){ + this.tooltip.innerHTML = `
${this.primaryKey}:
${entry[this.primaryKey]}
+
${this.subsetKey}:
${entry[this.subsetKey]}
+
Gene:
${entry.gene}
+
Max:
${entry.max}
+
Q3:
${entry.q3}
+
Median:
${entry.median.toFixed(4)}
+
Q1:
${entry.q1}
+
Min:
${entry.min}
+ `; + this.tooltip.classList.add('show') + }, + mouseMoveHandler(e){ + this.tooltip.style.top = (e.clientY - 10) + "px"; + this.tooltip.style.left = (e.clientX + 10) + "px"; + }, + mouseOutHandler(e){ + this.tooltip.classList.remove('show'); + this.tooltip.style.top = -1000 + "px"; + this.tooltip.style.left = -1000 + "px"; + }, + doHighlight(label){ + const plot = this.$refs.chart.querySelector(`.plot`); + if(!plot) return; + plot.classList.remove('highlighting'); + const matchingEls = this.$refs.chart.querySelectorAll(`.plot .bar`); + matchingEls.forEach(el => { + el.classList.remove('on'); }) + if(label && label != ''){ + const matchingEls = this.$refs.chart.querySelectorAll(`.bar[data-label*="${label}"]`); + let hasHighlight = false; + matchingEls.forEach(el => { + const elLabels = el.dataset.label.split(','); + if(elLabels.includes(label)){ + el.classList.add('on'); + hasHighlight=true; + } + }) + if(hasHighlight) plot.classList.add('highlighting'); + } } }, }); @@ -370,5 +419,11 @@ .tooltip.show{ opacity: 1; } + ::v-deep .plot.highlighting .bar{ + opacity: 0.2; + } + ::v-deep .plot.highlighting .bar.on{ + opacity: 1; + } \ No newline at end of file diff --git a/src/components/researchPortal/customComponents/cfdeLanding.vue b/src/components/researchPortal/customComponents/cfdeLanding.vue index 24d72dd7c..b2e094f29 100644 --- a/src/components/researchPortal/customComponents/cfdeLanding.vue +++ b/src/components/researchPortal/customComponents/cfdeLanding.vue @@ -386,7 +386,7 @@