From 84494990b78e56236770a5b98b0e35d1ef3f36b4 Mon Sep 17 00:00:00 2001 From: Eric Weitz Date: Wed, 30 Oct 2024 12:44:40 -0400 Subject: [PATCH 1/6] Fetch dot plot metrics serially, for scalable unlimited gene count --- app/javascript/lib/pathway-expression.js | 169 ++++++++++++++++++----- app/javascript/vite/application.js | 2 +- vite.config.ts | 2 +- 3 files changed, 133 insertions(+), 40 deletions(-) diff --git a/app/javascript/lib/pathway-expression.js b/app/javascript/lib/pathway-expression.js index e70d8bab7b..37d45b032a 100644 --- a/app/javascript/lib/pathway-expression.js +++ b/app/javascript/lib/pathway-expression.js @@ -145,31 +145,72 @@ export async function renderBackgroundDotPlot( /** Get unique genes in pathway diagram, ranked by global interest */ export function getPathwayGenes(ranks) { const dataNodes = Array.from(document.querySelectorAll('#_ideogramPathwayContainer g.DataNode')) - const geneNodes = dataNodes.filter( - dataNode => Array.from(dataNode.classList).some(cls => cls.startsWith('Ensembl_ENS')) - ) + console.log('dataNodes.length', dataNodes.length) + const t0 = performance.now() + let classListTime = 0 + let innerLoopTime = 0 + let numInnerLoopIterations = 0 + const geneNodes = [] + for (let i = 0; i < dataNodes.length; i++) { + const dataNode = dataNodes[i] + const t0a = performance.now() + const classes = dataNode.classList + const t1a = performance.now() + classListTime += t1a - t0a + + const t0b = performance.now() + for (let j = 0; j < classes.length; j++) { + numInnerLoopIterations += 1 + const cls = classes[j] + const isGene = ['geneproduct', 'rna', 'protein'].includes(cls.toLowerCase()) + if (isGene) { + geneNodes.push(dataNode) + break + } + } + const t1b = performance.now() + innerLoopTime += t1b - t0b + } + // const geneNodes = dataNodes.filter( + // dataNode => Array.from(dataNode.classList).some( + // cls => ['geneproduct', 'rna', 'protein'].includes(cls.toLowerCase()) + // ) + // ) + console.log('geneNodes', geneNodes) + const t1 = Date.now() + console.log('Time to get geneNodes:', (t1-t0)/1000) + console.log('Time to get geneNodes, classListTime:', classListTime/1000) + console.log('Time to get geneNodes, innerLoopTime:', innerLoopTime/1000) + console.log('numInnerLoopIterations:', numInnerLoopIterations) const genes = geneNodes.map( node => {return { domId: node.id, name: node.querySelector('text').textContent }} ) + console.log('genes', genes) const rankedGenes = genes .filter(gene => ranks.includes(gene.name)) .sort((a, b) => ranks.indexOf(a.name) - ranks.indexOf(b.name)) + + console.log('rankedGenes', rankedGenes) return rankedGenes } -/** Get up to 50 genes from pathway, including searched gene and interacting gene */ -function getDotPlotGenes(searchedGene, interactingGene, pathwayGenes) { +/** Slice array into batches of a given size */ +function sliceIntoBatches(arr, batchSize) { + const result = [] + for (let i = 0; i < arr.length; i += batchSize) { + result.push(arr.slice(i, i + batchSize)) + } + return result +} + +/** Get genes from pathway, in batches of up to 50 genes */ +function getDotPlotGeneBatches(pathwayGenes) { const genes = pathwayGenes.map(g => g.name) const uniqueGenes = Array.from(new Set(genes)) - const dotPlotGenes = uniqueGenes.slice(0, 50) - if (!dotPlotGenes.includes(searchedGene)) { - dotPlotGenes[dotPlotGenes.length - 2] = searchedGene - } - if (!dotPlotGenes.includes(interactingGene)) { - dotPlotGenes[dotPlotGenes.length - 1] = interactingGene - } - return dotPlotGenes + const dotPlotGeneBatches = sliceIntoBatches(uniqueGenes, 50) + + return dotPlotGeneBatches } /** @@ -307,18 +348,41 @@ function writeLoadingIndicator(loadingCls) { headerLink.insertAdjacentHTML('afterend', loading) } +/** Merge new and old dot plots metrics */ +function mergeDotPlotMetrics(newMetrics, oldMetrics) { + Object.entries(oldMetrics).map(([label, oldGeneMetrics]) => { + const newGeneMetrics = newMetrics[label] + if (!newGeneMetrics) { + return + } + newMetrics[label] = Object.assign(newGeneMetrics, oldGeneMetrics) + }) + + return newMetrics +} + /** Color pathway gene nodes by expression */ -function renderPathwayExpression( +async function renderPathwayExpression( searchedGene, interactingGene, ideogram, dotPlotParams ) { + let allDotPlotMetrics = {} + const ranks = ideogram.geneCache.interestingNames + const t0 = Date.now() const pathwayGenes = getPathwayGenes(ranks) - const dotPlotGenes = getDotPlotGenes(searchedGene, interactingGene, pathwayGenes, ideogram) + const t1 = Date.now() + console.log('Time in getPathwayGenes:' + (t1 - t0)) + console.log('pathwayGenes', pathwayGenes) + const dotPlotGeneBatches = getDotPlotGeneBatches(pathwayGenes) + const t2 = Date.now() + console.log('Time in getDotPlotGeneBatches:' + (t2 - t1)) + const { studyAccession, cluster, annotation } = dotPlotParams let numDraws = 0 + let numRenders = 0 const annotationLabels = getEligibleLabels() @@ -329,63 +393,92 @@ function renderPathwayExpression( function backgroundDotPlotDrawCallback(dotPlot) { // The first render is for uncollapsed cell-x-gene metrics (heatmap), // the second render is for collapsed label-x-gene metrics (dotplot) + numDraws += 1 if (numDraws === 1) {return} + const t3 = Date.now() const dotPlotMetrics = getDotPlotMetrics(dotPlot) + const t4 = Date.now() + console.log('Time in getDotPlotGeneBatches:' + (t4 - t3)) + + if (!dotPlotMetrics) { // Occurs upon resizing window, artifact of internal Morpheus handling // of pre-dot-plot heatmap matrix. No user-facing impact. return } - writePathwayExpressionHeader(loadingCls, dotPlotMetrics, annotationLabels, pathwayGenes) + + if (!annotationLabels.includes(Object.keys(dotPlotMetrics)[0])) { + // Another protection for computing only for dot plots, not heatmaps + return + } + + allDotPlotMetrics = mergeDotPlotMetrics(dotPlotMetrics, allDotPlotMetrics) + + writePathwayExpressionHeader(loadingCls, allDotPlotMetrics, annotationLabels, pathwayGenes) const annotationLabel = annotationLabels[0] - colorPathwayGenesByExpression(pathwayGenes, dotPlotMetrics, annotationLabel) + colorPathwayGenesByExpression(pathwayGenes, allDotPlotMetrics, annotationLabel) + + if (numRenders <= dotPlotGeneBatches.length) { + numRenders += 1 + // Future optimization: render background dot plot one annotation at a time. This would + // speed up initial pathway expression overlay rendering, and increase the practical limit + // on number of genes that could be retrieved via SCP API Morpheus endpoint. + renderBackgroundDotPlot( + studyAccession, dotPlotGeneBatches[numRenders], cluster, annotation, + 'All', annotationLabels, backgroundDotPlotDrawCallback, + '#related-genes-ideogram-container' + ) + } } // Future optimization: render background dot plot one annotation at a time. This would // speed up initial pathway expression overlay rendering, and increase the practical limit // on number of genes that could be retrieved via SCP API Morpheus endpoint. renderBackgroundDotPlot( - studyAccession, dotPlotGenes, cluster, annotation, + studyAccession, dotPlotGeneBatches[0], cluster, annotation, 'All', annotationLabels, backgroundDotPlotDrawCallback, '#related-genes-ideogram-container' ) } +/** Draw pathway diagram */ +function drawPathway(event, dotPlotParams, ideogram) { + // Hide popover instantly upon drawing pathway; don't wait ~2 seconds + const ideoTooltip = document.querySelector('._ideogramTooltip') + ideoTooltip.style.opacity = 0 + ideoTooltip.style.pointerEvents = 'none' + + // Ensure popover for pathway diagram doesn't appear over gene search autocomplete, + // while still appearing over default visualizations. + const container = document.querySelector('#_ideogramPathwayContainer') + container.style.zIndex = 2 + + const details = event.detail + const searchedGene = details.sourceGene + const interactingGene = details.destGene + renderPathwayExpression( + searchedGene, interactingGene, ideogram, + dotPlotParams + ) +} + /** * Add and remove event listeners for Ideogram's `ideogramDrawPathway` event * * This sets up the pathway expression overlay */ export function manageDrawPathway(studyAccession, cluster, annotation, ideogram) { - const flags = getFeatureFlagsWithDefaults() if (!flags?.show_pathway_expression) {return} const dotPlotParams = { studyAccession, cluster, annotation } if (annotation.type === 'group') { - document.removeEventListener('ideogramDrawPathway') + document.removeEventListener('ideogramDrawPathway', drawPathway) document.addEventListener('ideogramDrawPathway', event => { - - // Hide popover instantly upon drawing pathway; don't wait ~2 seconds - const ideoTooltip = document.querySelector('._ideogramTooltip') - ideoTooltip.style.opacity = 0 - ideoTooltip.style.pointerEvents = 'none' - - // Ensure popover for pathway diagram doesn't appear over gene search autocomplete, - // while still appearing over default visualizations. - const container = document.querySelector('#_ideogramPathwayContainer') - container.style.zIndex = 2 - - const details = event.detail - const searchedGene = details.sourceGene - const interactingGene = details.destGene - renderPathwayExpression( - searchedGene, interactingGene, ideogram, - dotPlotParams - ) + drawPathway(event, dotPlotParams, ideogram) }) } } diff --git a/app/javascript/vite/application.js b/app/javascript/vite/application.js index f4dec13a2e..cc2fcb0c6b 100644 --- a/app/javascript/vite/application.js +++ b/app/javascript/vite/application.js @@ -29,7 +29,7 @@ import * as ScpApi from '~/lib/scp-api' window.SCP = window.SCP ? window.SCP : {} // Set up the context for Sentry to log front-end errors -setupSentry() +// setupSentry() // On each page load, check for old SCP caches, delete any found clearOldServiceWorkerCaches() diff --git a/vite.config.ts b/vite.config.ts index 409da0bec7..4c7f4450b7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,7 +13,7 @@ const version = readFileSync('version.txt', { encoding: 'utf8' }) // DISABLE_SENTRY=true bin/vite dev // // otherwise, this evaluates to false and leaves plugin enabled in all other scenarios -const disableSentry = typeof process.env.DISABLE_SENTRY !== 'undefined' && process.env.DISABLE_SENTRY === 'true' +const disableSentry = true // typeof process.env.DISABLE_SENTRY !== 'undefined' && process.env.DISABLE_SENTRY === 'true' export default defineConfig({ 'define': { From 13737412d67c6d2829a21ce4202a37f9292c8eba Mon Sep 17 00:00:00 2001 From: Eric Weitz Date: Fri, 8 Nov 2024 20:36:00 -0500 Subject: [PATCH 2/6] Remove performance debugging --- app/javascript/lib/pathway-expression.js | 28 ++---------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/app/javascript/lib/pathway-expression.js b/app/javascript/lib/pathway-expression.js index 37d45b032a..bfe1eb8d2f 100644 --- a/app/javascript/lib/pathway-expression.js +++ b/app/javascript/lib/pathway-expression.js @@ -145,7 +145,6 @@ export async function renderBackgroundDotPlot( /** Get unique genes in pathway diagram, ranked by global interest */ export function getPathwayGenes(ranks) { const dataNodes = Array.from(document.querySelectorAll('#_ideogramPathwayContainer g.DataNode')) - console.log('dataNodes.length', dataNodes.length) const t0 = performance.now() let classListTime = 0 let innerLoopTime = 0 @@ -171,26 +170,15 @@ export function getPathwayGenes(ranks) { const t1b = performance.now() innerLoopTime += t1b - t0b } - // const geneNodes = dataNodes.filter( - // dataNode => Array.from(dataNode.classList).some( - // cls => ['geneproduct', 'rna', 'protein'].includes(cls.toLowerCase()) - // ) - // ) - console.log('geneNodes', geneNodes) - const t1 = Date.now() - console.log('Time to get geneNodes:', (t1-t0)/1000) - console.log('Time to get geneNodes, classListTime:', classListTime/1000) - console.log('Time to get geneNodes, innerLoopTime:', innerLoopTime/1000) - console.log('numInnerLoopIterations:', numInnerLoopIterations) + const genes = geneNodes.map( node => {return { domId: node.id, name: node.querySelector('text').textContent }} ) - console.log('genes', genes) + const rankedGenes = genes .filter(gene => ranks.includes(gene.name)) .sort((a, b) => ranks.indexOf(a.name) - ranks.indexOf(b.name)) - console.log('rankedGenes', rankedGenes) return rankedGenes } @@ -369,16 +357,8 @@ async function renderPathwayExpression( let allDotPlotMetrics = {} const ranks = ideogram.geneCache.interestingNames - const t0 = Date.now() const pathwayGenes = getPathwayGenes(ranks) - const t1 = Date.now() - console.log('Time in getPathwayGenes:' + (t1 - t0)) - console.log('pathwayGenes', pathwayGenes) const dotPlotGeneBatches = getDotPlotGeneBatches(pathwayGenes) - const t2 = Date.now() - console.log('Time in getDotPlotGeneBatches:' + (t2 - t1)) - - const { studyAccession, cluster, annotation } = dotPlotParams let numDraws = 0 @@ -397,11 +377,7 @@ async function renderPathwayExpression( numDraws += 1 if (numDraws === 1) {return} - const t3 = Date.now() const dotPlotMetrics = getDotPlotMetrics(dotPlot) - const t4 = Date.now() - console.log('Time in getDotPlotGeneBatches:' + (t4 - t3)) - if (!dotPlotMetrics) { // Occurs upon resizing window, artifact of internal Morpheus handling From 7a20714a1e209b418cc7d8f9dd2a36dc07ade065 Mon Sep 17 00:00:00 2001 From: Eric Weitz Date: Tue, 12 Nov 2024 11:35:46 -0500 Subject: [PATCH 3/6] Restore Sentry, remove more performance debugging --- app/javascript/lib/pathway-expression.js | 10 ---------- app/javascript/vite/application.js | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/app/javascript/lib/pathway-expression.js b/app/javascript/lib/pathway-expression.js index bfe1eb8d2f..72a914bcdc 100644 --- a/app/javascript/lib/pathway-expression.js +++ b/app/javascript/lib/pathway-expression.js @@ -145,21 +145,13 @@ export async function renderBackgroundDotPlot( /** Get unique genes in pathway diagram, ranked by global interest */ export function getPathwayGenes(ranks) { const dataNodes = Array.from(document.querySelectorAll('#_ideogramPathwayContainer g.DataNode')) - const t0 = performance.now() - let classListTime = 0 - let innerLoopTime = 0 - let numInnerLoopIterations = 0 const geneNodes = [] for (let i = 0; i < dataNodes.length; i++) { const dataNode = dataNodes[i] - const t0a = performance.now() const classes = dataNode.classList - const t1a = performance.now() - classListTime += t1a - t0a const t0b = performance.now() for (let j = 0; j < classes.length; j++) { - numInnerLoopIterations += 1 const cls = classes[j] const isGene = ['geneproduct', 'rna', 'protein'].includes(cls.toLowerCase()) if (isGene) { @@ -167,8 +159,6 @@ export function getPathwayGenes(ranks) { break } } - const t1b = performance.now() - innerLoopTime += t1b - t0b } const genes = geneNodes.map( diff --git a/app/javascript/vite/application.js b/app/javascript/vite/application.js index cc2fcb0c6b..f4dec13a2e 100644 --- a/app/javascript/vite/application.js +++ b/app/javascript/vite/application.js @@ -29,7 +29,7 @@ import * as ScpApi from '~/lib/scp-api' window.SCP = window.SCP ? window.SCP : {} // Set up the context for Sentry to log front-end errors -// setupSentry() +setupSentry() // On each page load, check for old SCP caches, delete any found clearOldServiceWorkerCaches() From 081b0948ec69de56a61ee8759394e8c921fcd7f3 Mon Sep 17 00:00:00 2001 From: Eric Weitz Date: Wed, 13 Nov 2024 17:09:53 -0500 Subject: [PATCH 4/6] Upgrade to Ideogram 1.48.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 63230c6f9f..52e8a8899f 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "crossfilter2": "^1.5.4", "exifreader": "4.6.0", "fflate": "^0.7.3", - "ideogram": "1.47.0", + "ideogram": "1.48.0", "jquery": "3.5.1", "jquery-ui": "1.13.2", "morpheus-app": "1.0.18", diff --git a/yarn.lock b/yarn.lock index 2ed24b0665..7e8c626337 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5196,10 +5196,10 @@ iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ideogram@1.47.0: - version "1.47.0" - resolved "https://registry.yarnpkg.com/ideogram/-/ideogram-1.47.0.tgz#58889a0212d66f84964779895e06e2f0ed8fd561" - integrity sha512-koCd/An2VofPoKfd41Wo8hyhwTVu4vCB7w4w7M8e4aWxFU4Si0NWo2ECJ8BG5fZ61xJhI68HHzQvmvhhWsCBpw== +ideogram@1.48.0: + version "1.48.0" + resolved "https://registry.yarnpkg.com/ideogram/-/ideogram-1.48.0.tgz#0c4849b01854d2bd2ab784105fbe546aff022a7a" + integrity sha512-qza99AA6nqa+McAU0v5YTypJXleWzNLy29zeyGYL2pwG7k3fg4H0uvmCkM+EARSH1iMAevEYpxHIMqZrkeq06w== dependencies: crossfilter2 "1.5.2" d3-array "^2.8.0" From c93f2862553b3ee07b45d69b4e64a0e4f10b4c1f Mon Sep 17 00:00:00 2001 From: Eric Weitz Date: Wed, 13 Nov 2024 17:53:48 -0500 Subject: [PATCH 5/6] Add test for batching dot plot genes --- app/javascript/lib/pathway-expression.js | 4 +-- test/js/lib/pathway-expression.test-data.js | 3 +++ test/js/lib/pathway-expression.test.js | 30 ++++++++++++++++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/app/javascript/lib/pathway-expression.js b/app/javascript/lib/pathway-expression.js index 72a914bcdc..c8d81fb7b3 100644 --- a/app/javascript/lib/pathway-expression.js +++ b/app/javascript/lib/pathway-expression.js @@ -181,8 +181,8 @@ function sliceIntoBatches(arr, batchSize) { return result } -/** Get genes from pathway, in batches of up to 50 genes */ -function getDotPlotGeneBatches(pathwayGenes) { +/** Get genes from pathway, in batches of up to 50 genes, eliminating duplicates */ +export function getDotPlotGeneBatches(pathwayGenes) { const genes = pathwayGenes.map(g => g.name) const uniqueGenes = Array.from(new Set(genes)) diff --git a/test/js/lib/pathway-expression.test-data.js b/test/js/lib/pathway-expression.test-data.js index 6396ccf693..6c96ce7702 100644 --- a/test/js/lib/pathway-expression.test-data.js +++ b/test/js/lib/pathway-expression.test-data.js @@ -1,3 +1,6 @@ +// eslint-disable-next-line max-len +export const manyPathwayGenes = [{ 'domId': 'fcaa7', 'name': 'TP53' }, { 'domId': 'b463d', 'name': 'TP53' }, { 'domId': 'd856e', 'name': 'IL6' }, { 'domId': 'd0f8b', 'name': 'TNF' }, { 'domId': 'd4795', 'name': 'TNF' }, { 'domId': 'daa5d', 'name': 'VEGFA' }, { 'domId': 'f7f42', 'name': 'IL1B' }, { 'domId': 'd7428', 'name': 'CTNNB1' }, { 'domId': 'e9d3c', 'name': 'CTNNB1' }, { 'domId': 'd867d', 'name': 'PTEN' }, { 'domId': 'a551c', 'name': 'STAT3' }, { 'domId': 'a326b', 'name': 'CCL2' }, { 'domId': 'ade6d', 'name': 'GSK3B' }, { 'domId': 'dde7a', 'name': 'IL10' }, { 'domId': 'c37c1', 'name': 'NFKB1' }, { 'domId': 'f3ad3', 'name': 'NFKB1' }, { 'domId': 'e02c2', 'name': 'NFKB1' }, { 'domId': 'e8b31', 'name': 'BSG' }, { 'domId': 'def2d', 'name': 'SPP1' }, { 'domId': 'e12dd', 'name': 'ADIPOQ' }, { 'domId': 'be664', 'name': 'PARP1' }, { 'domId': 'c2616', 'name': 'HMOX1' }, { 'domId': 'd7c2b', 'name': 'CXCL8' }, { 'domId': 'd6a57', 'name': 'MMP2' }, { 'domId': 'e8a20', 'name': 'MMP9' }, { 'domId': 'de504', 'name': 'ELAVL1' }, { 'domId': 'aaa8f', 'name': 'ELAVL1' }, { 'domId': 'b9d33', 'name': 'ENO1' }, { 'domId': 'a15b5', 'name': 'NR4A1' }, { 'domId': 'bea8c', 'name': 'PTX3' }, { 'domId': 'c2b73', 'name': 'PLOD3' }, { 'domId': 'e0536', 'name': 'TRPM7' }, { 'domId': 'e55cf', 'name': 'ACTA2' }, { 'domId': 'a3ef7', 'name': 'BAX' }, { 'domId': 'bfc32', 'name': 'BAX' }, { 'domId': 'a01e8', 'name': 'BCL2' }, { 'domId': 'f979c', 'name': 'BMP2' }, { 'domId': 'ceefa', 'name': 'GATA1' }, { 'domId': 'e8cac', 'name': 'MMP1' }, { 'domId': 'c43ee', 'name': 'PTGS2' }, { 'domId': 'aab19', 'name': 'IL37' }, { 'domId': 'b5938', 'name': 'CASP8' }, { 'domId': 'bf33c', 'name': 'RUNX2' }, { 'domId': 'de80c', 'name': 'DES' }, { 'domId': 'cd941', 'name': 'IL13' }, { 'domId': 'a1b81', 'name': 'JUN' }, { 'domId': 'fc9c2', 'name': 'CCL5' }, { 'domId': 'a7635', 'name': 'SP1' }, { 'domId': 'dce54', 'name': 'DEK' }, { 'domId': 'ef0db', 'name': 'RAE1' }, { 'domId': 'ced50', 'name': 'BAZ1B' }, { 'domId': 'c2740', 'name': 'CLDN1' }, { 'domId': 'e5634', 'name': 'TOMM40' }, { 'domId': 'af552', 'name': 'MTCH1' }, { 'domId': 'd9dab', 'name': 'ACTA1' }, { 'domId': 'a11be', 'name': 'BIN1' }, { 'domId': 'f37ff', 'name': 'ARF6' }, { 'domId': 'cb179', 'name': 'ATF3' }, { 'domId': 'bceaa', 'name': 'BID' }, { 'domId': 'fcde4', 'name': 'CENPB' }, { 'domId': 'e818a', 'name': 'ECH1' }, { 'domId': 'e1c5c', 'name': 'EPB41' }, { 'domId': 'dd395', 'name': 'EPS8' }, { 'domId': 'fc0ea', 'name': 'FUT1' }, { 'domId': 'f663f', 'name': 'HADH' }, { 'domId': 'e9bf2', 'name': 'HDGF' }, { 'domId': 'e5bdb', 'name': 'IL2RA' }, { 'domId': 'ffe83', 'name': 'IL9' }, { 'domId': 'd9512', 'name': 'IL18' }, { 'domId': 'a884c', 'name': 'IL18' }, { 'domId': 'cd021', 'name': 'KLC1' }, { 'domId': 'e2d40', 'name': 'STMN1' }, { 'domId': 'f5aaf', 'name': 'MMP3' }, { 'domId': 'ce529', 'name': 'MMP14' }, { 'domId': 'cfff0', 'name': 'MYD88' }, { 'domId': 'd9827', 'name': 'MYD88' }, { 'domId': 'a57f2', 'name': 'MYH6' }, { 'domId': 'add7c', 'name': 'MYH7' }, { 'domId': 'e2b2e', 'name': 'NACA' }, { 'domId': 'b1fa2', 'name': 'NPPB' }, { 'domId': 'cace3', 'name': 'TNFRSF11B' }, { 'domId': 'ac57c', 'name': 'PLD1' }, { 'domId': 'd0298', 'name': 'PRCC' }, { 'domId': 'd9f8a', 'name': 'PKN1' }, { 'domId': 'a4a7c', 'name': 'RFX5' }, { 'domId': 'ff239', 'name': 'RPS11' }, { 'domId': 'b857b', 'name': 'SDC4' }, { 'domId': 'b2fad', 'name': 'SNTB1' }, { 'domId': 'd06de', 'name': 'TIMP1' }, { 'domId': 'c3469', 'name': 'TRAF6' }, { 'domId': 'b99e0', 'name': 'MBTPS1' }, { 'domId': 'd8728', 'name': 'CCNB2' }, { 'domId': 'b11df', 'name': 'LRRFIP1' }, { 'domId': 'aa557', 'name': 'SLC4A7' }, { 'domId': 'b1aa3', 'name': 'PPP1R13L' }, { 'domId': 'ba33d', 'name': 'RASA3' }, { 'domId': 'f1cca', 'name': 'LARS2' }, { 'domId': 'b0b2c', 'name': 'PIGT' }, { 'domId': 'ce540', 'name': 'CYCS' }, { 'domId': 'b4918', 'name': 'CYCS' }, { 'domId': 'ee48f', 'name': 'IMP3' }, { 'domId': 'b2c03', 'name': 'IMP3' }, { 'domId': 'c449a', 'name': 'TMEM165' }, { 'domId': 'cff8b', 'name': 'MEPCE' }, { 'domId': 'e1983', 'name': 'UGGT1' }, { 'domId': 'a239e', 'name': 'RPTOR' }, { 'domId': 'ffb8f', 'name': 'SEMA6D' }, { 'domId': 'e56e7', 'name': 'ITM2C' }, { 'domId': 'c6cd0', 'name': 'LONP2' }, { 'domId': 'c3396', 'name': 'ARFGAP2' }, { 'domId': 'ba79b', 'name': 'LMNB2' }, { 'domId': 'a31f8', 'name': 'GPAT4' }, { 'domId': 'f3fb8', 'name': 'B2M' }, { 'domId': 'c50e2', 'name': 'BCL2L1' }, { 'domId': 'edef2', 'name': 'CASP3' }, { 'domId': 'b29ff', 'name': 'CD36' }, { 'domId': 'd65bb', 'name': 'CETP' }, { 'domId': 'd2eb5', 'name': 'GRN' }, { 'domId': 'fa4e6', 'name': 'CXCL3' }, { 'domId': 'ab199', 'name': 'IFNG' }, { 'domId': 'dbf2e', 'name': 'ITGA2B' }, { 'domId': 'b25fd', 'name': 'KCNH2' }, { 'domId': 'd9dca', 'name': 'MEF2A' }, { 'domId': 'ecbe5', 'name': 'MMP13' }, { 'domId': 'bc930', 'name': 'NOS2' }, { 'domId': 'aa7b4', 'name': 'ZC3H12A' }, { 'domId': 'eb8d5', 'name': 'FAS' }, { 'domId': 'd67a4', 'name': 'FAS' }, { 'domId': 'f053e', 'name': 'BAD' }, { 'domId': 'f0db4', 'name': 'CCNA2' }, { 'domId': 'b05b1', 'name': 'CD81' }, { 'domId': 'f67ff', 'name': 'CEBPB' }, { 'domId': 'df415', 'name': 'CLDN3' }, { 'domId': 'bf2b9', 'name': 'CXCL2' }, { 'domId': 'a5107', 'name': 'ICAM1' }, { 'domId': 'ea365', 'name': 'MMP8' }, { 'domId': 'dd7be', 'name': 'NFKBIA' }, { 'domId': 'eb28f', 'name': 'NPPA' }, { 'domId': 'a7343', 'name': 'TACR1' }, { 'domId': 'cad47', 'name': 'TGM2' }, { 'domId': 'e97ac', 'name': 'TIMP3' }, { 'domId': 'eecfb', 'name': 'TNFAIP3' }, { 'domId': 'd48ea', 'name': 'TNFRSF1A' }, { 'domId': 'e4463', 'name': 'PLA2G7' }, { 'domId': 'e9fbd', 'name': 'TNFSF11' }, { 'domId': 'db553', 'name': 'NR1H3' }, { 'domId': 'd7b29', 'name': 'NSMF' }, { 'domId': 'b0e1a', 'name': 'HSPB8' }, { 'domId': 'a1fbe', 'name': 'IRAK4' }, { 'domId': 'b012f', 'name': 'NRN1' }, { 'domId': 'cb5b5', 'name': 'FBXW7' }, { 'domId': 'e4c67', 'name': 'ALS2' }, { 'domId': 'e6b8e', 'name': 'ARG1' }, { 'domId': 'e6044', 'name': 'COL1A2' }, { 'domId': 'c192c', 'name': 'COL3A1' }, { 'domId': 'a4952', 'name': 'FN1' }, { 'domId': 'ace94', 'name': 'GRM7' }, { 'domId': 'b0eda', 'name': 'IRF1' }, { 'domId': 'e6774', 'name': 'RELA' }, { 'domId': 'd02df', 'name': 'RELA' }, { 'domId': 'b623c', 'name': 'CCL4' }, { 'domId': 'deb28', 'name': 'SLC12A3' }, { 'domId': 'd7946', 'name': 'TRAF1' }, { 'domId': 'ec946', 'name': 'FADD' }, { 'domId': 'dc99e', 'name': 'HDAC3' }, { 'domId': 'faffd', 'name': 'IL18BP' }, { 'domId': 'd55ae', 'name': 'KLF2' }, { 'domId': 'b57e9', 'name': 'CXCL16' }, { 'domId': 'bb5b9', 'name': 'REL' }, { 'domId': 'e065d', 'name': 'CCL1' }, { 'domId': 'eacc6', 'name': 'CLDN15' }, { 'domId': 'ddb88', 'name': 'ABCF1' }, { 'domId': 'f69af', 'name': 'ACADS' }, { 'domId': 'de186', 'name': 'BIRC3' }, { 'domId': 'c9fec', 'name': 'FASLG' }, { 'domId': 'b54d6', 'name': 'FASLG' }, { 'domId': 'ed869', 'name': 'COL1A1' }, { 'domId': 'c5de7', 'name': 'CLDN4' }, { 'domId': 'bbea5', 'name': 'CPT1A' }, { 'domId': 'd699d', 'name': 'CRYGC' }, { 'domId': 'a8d0c', 'name': 'EEF2' }, { 'domId': 'b3fde', 'name': 'FOS' }, { 'domId': 'bdeff', 'name': 'HOXD8' }, { 'domId': 'ae53a', 'name': 'HPS1' }, { 'domId': 'e3c2c', 'name': 'IL12B' }, { 'domId': 'dd675', 'name': 'IRF6' }, { 'domId': 'd6f58', 'name': 'NCF2' }, { 'domId': 'ced02', 'name': 'NCF2' }, { 'domId': 'fbb6e', 'name': 'PLCG1' }, { 'domId': 'bb768', 'name': 'PRM1' }, { 'domId': 'e5d1f', 'name': 'PYGB' }, { 'domId': 'f3af6', 'name': 'RANGAP1' }, { 'domId': 'be357', 'name': 'CCL3' }, { 'domId': 'ae3d3', 'name': 'CCL19' }, { 'domId': 'd706b', 'name': 'CCL20' }, { 'domId': 'ffe82', 'name': 'CNTN2' }, { 'domId': 'bbfe1', 'name': 'TF' }, { 'domId': 'c34bb', 'name': 'TMSB4X' }, { 'domId': 'f3efd', 'name': 'TNFAIP2' }, { 'domId': 'd16f3', 'name': 'BTG2' }, { 'domId': 'a4daa', 'name': 'ABHD16A' }, { 'domId': 'b5a67', 'name': 'USP5' }, { 'domId': 'df9ff', 'name': 'ANP32A' }, { 'domId': 'dc80b', 'name': 'NR0B2' }, { 'domId': 'e2af7', 'name': 'IL18RAP' }, { 'domId': 'ff4bf', 'name': 'IL18RAP' }, { 'domId': 'eda58', 'name': 'CFLAR' }, { 'domId': 'cec71', 'name': 'CCN4' }, { 'domId': 'f59d5', 'name': 'SOCS3' }, { 'domId': 'aee6c', 'name': 'CLDN12' }, { 'domId': 'f8f49', 'name': 'CD83' }, { 'domId': 'c183a', 'name': 'ADAMTS5' }, { 'domId': 'ad9ef', 'name': 'CCDC9' }, { 'domId': 'f41a8', 'name': 'NOX1' }, { 'domId': 'c49f6', 'name': 'NCAPH2' }, { 'domId': 'c55bb', 'name': 'ZBTB7A' }, { 'domId': 'dcf78', 'name': 'ARFGAP1' }, { 'domId': 'd4d28', 'name': 'NFKBIZ' }, { 'domId': 'de472', 'name': 'NCF1' }, { 'domId': 'df144', 'name': 'NCF1' }, { 'domId': 'b2e85', 'name': 'ACOD1' }, { 'domId': 'dce63', 'name': 'STK40' }, { 'domId': 'd4c24', 'name': 'SEMA6C' }, { 'domId': 'cf0cd', 'name': 'RUSC1' }, { 'domId': 'c6bcf', 'name': 'RGS16' }, { 'domId': 'b84b0', 'name': 'PTPN7' }, { 'domId': 'a3ce2', 'name': 'NFKB2' }, { 'domId': 'c7889', 'name': 'TRPC2' }, { 'domId': 'bd1f3', 'name': 'ZNF143' }, { 'domId': 'af68b', 'name': 'SPON1' }, { 'domId': 'ee699', 'name': 'PTMS' }, { 'domId': 'f9455', 'name': 'SYT10' }, { 'domId': 'f1582', 'name': 'FAM186B' }, { 'domId': 'a8b5b', 'name': 'KITLG' }, { 'domId': 'eb8ba', 'name': 'HCAR2' }, { 'domId': 'eb9ac', 'name': 'ZNF219' }, { 'domId': 'c99b9', 'name': 'NFATC4' }, { 'domId': 'd265c', 'name': 'FOXN3' }, { 'domId': 'c38c5', 'name': 'APBA2' }, { 'domId': 'e30aa', 'name': 'STOML1' }, { 'domId': 'b7924', 'name': 'KIFC3' }, { 'domId': 'e97ea', 'name': 'ZDHHC7' }, { 'domId': 'ddf5e', 'name': 'CCL18' }, { 'domId': 'ef692', 'name': 'KRT31' }, { 'domId': 'ebde8', 'name': 'RND2' }, { 'domId': 'b0f9d', 'name': 'ARL4D' }, { 'domId': 'b0ac6', 'name': 'TBX21' }, { 'domId': 'fbd6d', 'name': 'TSHZ1' }, { 'domId': 'ae738', 'name': 'PWWP3A' }, { 'domId': 'f6ef6', 'name': 'S1PR4' }, { 'domId': 'cacbf', 'name': 'MAP2K7' }, { 'domId': 'dfe1f', 'name': 'RELB' }, { 'domId': 'be643', 'name': 'CA11' }, { 'domId': 'dc68d', 'name': 'ZNF444' }, { 'domId': 'eac9c', 'name': 'IL18R1' }, { 'domId': 'a8d08', 'name': 'IL18R1' }, { 'domId': 'e86f3', 'name': 'CDK5R2' }, { 'domId': 'e7d8b', 'name': 'FAM110A' }, { 'domId': 'c89df', 'name': 'TRPC4AP' }, { 'domId': 'b90ee', 'name': 'PHF20' }, { 'domId': 'fcd63', 'name': 'IL17RC' }, { 'domId': 'dc1f0', 'name': 'COX17' }, { 'domId': 'f62a2', 'name': 'UGT2B10' }, { 'domId': 'fd450', 'name': 'TNIP3' }, { 'domId': 'eb0c6', 'name': 'NDUFC1' }, { 'domId': 'e320b', 'name': 'TICAM2' }, { 'domId': 'aa153', 'name': 'IER3' }, { 'domId': 'c05c3', 'name': 'LTB' }, { 'domId': 'b8818', 'name': 'PPT2' }, { 'domId': 'f70f7', 'name': 'RXRB' }, { 'domId': 'a1ee9', 'name': 'NFKBIE' }, { 'domId': 'd24d3', 'name': 'ULBP2' }, { 'domId': 'e6943', 'name': 'PTPRZ1' }, { 'domId': 'd19ad', 'name': 'BPGM' }, { 'domId': 'ac349', 'name': 'UCK1' }] + // To reproduce: // 1. Add `console.log('dotPlotMetrics', dotPlotMetrics)` atop `colorPathwayGenesByExpression` // 2. Search CSN2 in "Cellular and transcriptional diversity over the course of human lactation" diff --git a/test/js/lib/pathway-expression.test.js b/test/js/lib/pathway-expression.test.js index 9dfc644634..ded19cdae6 100644 --- a/test/js/lib/pathway-expression.test.js +++ b/test/js/lib/pathway-expression.test.js @@ -1,7 +1,12 @@ -import { getPathwayGenes, colorPathwayGenesByExpression } from '~/lib/pathway-expression' +import { + getPathwayGenes, colorPathwayGenesByExpression, + getDotPlotGeneBatches +} from '~/lib/pathway-expression' import * as UserProvider from '~/providers/UserProvider' -import { pathwayContainerHtml, dotPlotMetrics } from './pathway-expression.test-data.js' +import { + pathwayContainerHtml, dotPlotMetrics, manyPathwayGenes +} from './pathway-expression.test-data.js' describe('Expression overlay for pathway diagram', () => { it('gets objects containing e.g. DOM ID for genes in pathway ', () => { @@ -47,7 +52,26 @@ describe('Expression overlay for pathway diagram', () => { // See color-mix() on MDN: // https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix expect(egfrColor).toBe('color-mix(in oklab, #6800a1 12.350979804992676%, white)') + }) + + it('splits big lists of dot plot genes in batches of 50 or less', () => { + jest + .spyOn(UserProvider, 'getFeatureFlagsWithDefaults') + .mockReturnValue({ + show_pathway_expression: true + }) + + expect(manyPathwayGenes).toHaveLength(275) + const dotPlotGeneBatches = getDotPlotGeneBatches(manyPathwayGenes) - expect(1).toBe(1) + expect(dotPlotGeneBatches).toHaveLength(6) + expect(dotPlotGeneBatches[0]).toHaveLength(50) + + // N.B.: Not 25, because there are 18 duplicate gene names in manyPathwayGenes. + // That's because manyPathwayGenes represents _graphical_ nodes in the diagram, + // whereas entries in dotPlotGeneBatches represent _genes we want metrics for_, + // and those are unique by gene name. + expect(dotPlotGeneBatches.slice(-1)[0]).toHaveLength(7) }) + }) From d60279f1d6dbfe8af1529ccd14a80ed7b89dc2c4 Mon Sep 17 00:00:00 2001 From: Eric Weitz Date: Wed, 13 Nov 2024 20:54:59 -0500 Subject: [PATCH 6/6] Remove debug --- app/javascript/lib/pathway-expression.js | 1 - vite.config.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/javascript/lib/pathway-expression.js b/app/javascript/lib/pathway-expression.js index c8d81fb7b3..05ef93621d 100644 --- a/app/javascript/lib/pathway-expression.js +++ b/app/javascript/lib/pathway-expression.js @@ -150,7 +150,6 @@ export function getPathwayGenes(ranks) { const dataNode = dataNodes[i] const classes = dataNode.classList - const t0b = performance.now() for (let j = 0; j < classes.length; j++) { const cls = classes[j] const isGene = ['geneproduct', 'rna', 'protein'].includes(cls.toLowerCase()) diff --git a/vite.config.ts b/vite.config.ts index 4c7f4450b7..409da0bec7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,7 +13,7 @@ const version = readFileSync('version.txt', { encoding: 'utf8' }) // DISABLE_SENTRY=true bin/vite dev // // otherwise, this evaluates to false and leaves plugin enabled in all other scenarios -const disableSentry = true // typeof process.env.DISABLE_SENTRY !== 'undefined' && process.env.DISABLE_SENTRY === 'true' +const disableSentry = typeof process.env.DISABLE_SENTRY !== 'undefined' && process.env.DISABLE_SENTRY === 'true' export default defineConfig({ 'define': {