diff --git a/package-lock.json b/package-lock.json index 5c6a9b33..9bdc6259 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@ngx-dropzone/cdk": "^19.0.0", "@ngx-dropzone/material": "^19.0.0", "@sigma/node-square": "^3.0.0", + "@visa-ge/ng-igv": "^0.0.9", "file-saver": "^2.0.5", "graphology": "^0.25.4", "graphology-layout-force": "^0.2.4", @@ -5269,6 +5270,18 @@ "@types/node": "*" } }, + "node_modules/@visa-ge/ng-igv": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@visa-ge/ng-igv/-/ng-igv-0.0.9.tgz", + "integrity": "sha512-6BqiqTUKbs81nggy6II5h6A3ZwU5JLtChDwRSsm0QUxogJgeW2eTRrGm0hEc89Cdhf3a3I3nALHIMWjFwZ5YJw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0" + } + }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", diff --git a/package.json b/package.json index 847df9ea..ef345dea 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@ngx-dropzone/cdk": "^19.0.0", "@ngx-dropzone/material": "^19.0.0", "@sigma/node-square": "^3.0.0", + "@visa-ge/ng-igv": "^0.0.9", "file-saver": "^2.0.5", "graphology": "^0.25.4", "graphology-layout-force": "^0.2.4", diff --git a/src/app/components/gene-modal/gene-modal.component.html b/src/app/components/gene-modal/gene-modal.component.html index 11d090b8..012a5db2 100644 --- a/src/app/components/gene-modal/gene-modal.component.html +++ b/src/app/components/gene-modal/gene-modal.component.html @@ -1,6 +1,6 @@

{{ gene.gene_symbol || gene.ensg_number }}

- + @@ -125,6 +125,10 @@

{{ gene.gene_symbol || gene.ensg_number }}

} + + +
diff --git a/src/app/components/gene-modal/gene-modal.component.ts b/src/app/components/gene-modal/gene-modal.component.ts index 94699d29..5b8cc16e 100644 --- a/src/app/components/gene-modal/gene-modal.component.ts +++ b/src/app/components/gene-modal/gene-modal.component.ts @@ -1,21 +1,34 @@ -import {AfterViewInit, Component, effect, inject, model, resource, ResourceRef, viewChild} from '@angular/core'; -import {Gene, GOTerm} from "../../interfaces"; -import {MAT_DIALOG_DATA, MatDialog, MatDialogModule} from "@angular/material/dialog"; -import {MatButtonModule} from "@angular/material/button"; -import {VersionsService} from "../../services/versions.service"; -import {BackendService} from "../../services/backend.service"; -import {MatTabsModule} from "@angular/material/tabs"; -import {MatExpansionModule} from "@angular/material/expansion"; -import {MatTableDataSource, MatTableModule} from "@angular/material/table"; -import {MatPaginator, MatPaginatorModule} from "@angular/material/paginator"; -import {MatFormFieldModule} from "@angular/material/form-field"; -import {MatInputModule} from "@angular/material/input"; -import {FormsModule} from "@angular/forms"; -import {MatChip, MatChipSet} from "@angular/material/chips"; -import {MatProgressSpinner} from "@angular/material/progress-spinner"; -import {TranscriptModalComponent} from "../transcript-modal/transcript-modal.component"; -import {AS_DESCRIPTIONS} from "../../constants"; -import {MatTooltip} from "@angular/material/tooltip"; +import { + AfterViewInit, + Component, + computed, + effect, + inject, + model, + resource, + ResourceRef, + Signal, + viewChild, +} from '@angular/core'; +import { Gene, GOTerm } from '../../interfaces'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { VersionsService } from '../../services/versions.service'; +import { BackendService } from '../../services/backend.service'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { FormsModule } from '@angular/forms'; +import { MatChip, MatChipSet } from '@angular/material/chips'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { AS_DESCRIPTIONS, IGV_REFGENOME } from '../../constants'; +import { MatTooltip } from '@angular/material/tooltip'; +import { Igv, Location, Track } from '@visa-ge/ng-igv'; +import { ModalsService } from '../modals-service/modals.service'; +import { BrowseService } from '../../services/browse.service'; interface ASEntry { enst: string; @@ -37,54 +50,118 @@ interface ASEntry { MatChipSet, MatChip, MatProgressSpinner, - MatTooltip + MatTooltip, + Igv, ], templateUrl: './gene-modal.component.html', - styleUrl: './gene-modal.component.scss' + styleUrl: './gene-modal.component.scss', }) export class GeneModalComponent implements AfterViewInit { goPaginator = viewChild.required('goPaginator'); asPaginator = viewChild.required('asPaginator'); - goColumns = ['symbol', 'description'] - asColumns = ['enst', 'events'] - goFilter = model('') - dialog = inject(MatDialog); - readonly asDescriptions = AS_DESCRIPTIONS + goColumns = ['symbol', 'description']; + asColumns = ['enst', 'events']; + goFilter = model(''); + readonly asDescriptions = AS_DESCRIPTIONS; + modalsService = inject(ModalsService); + browseService = inject(BrowseService); + readonly disease$ = this.browseService.disease$; readonly gene = inject(MAT_DIALOG_DATA); readonly versionsService = inject(VersionsService); readonly backend = inject(BackendService); readonly version$ = this.versionsService.versionReadOnly(); readonly isOpeningTranscript = model(false); + readonly activeTab$ = model(0); goDatasource = new MatTableDataSource(); asDatasource = new MatTableDataSource(); + edges$ = this.browseService.getEdgesForNode(this.gene); + miRNAs$ = resource({ + request: computed(() => { + return { + edges: this.edges$(), + disease: this.disease$(), + version: this.version$(), + }; + }), + loader: async (param) => { + const edges = param.request.edges; + const disease = param.request.disease; + const version = param.request.version; + if (!disease) { + return Promise.resolve([]); + } + const miRNAs$ = edges.map((edge) => + this.backend + .getMiRNAs( + version, + disease, + BrowseService.getInteractionIDs(edge), + 'gene', + ) + .then((res) => res.map((mirna) => mirna.mirna.hs_nr)), + ); + return (await Promise.all(miRNAs$)) + .flat() + .filter((miRNA, i, arr) => arr.indexOf(miRNA) === i); + }, + }); + + miRNAtracks$ = computed((): Track[] => { + const miRNAs = this.miRNAs$.value(); + if (!miRNAs) { + return []; + } + return miRNAs.map((miRNA) => { + return { + name: miRNA, + url: `https://exbio.wzw.tum.de/sponge-files/miRNA_bed_files/${miRNA}.bed.gz`, + format: 'bed', + type: 'annotation', + height: 30, + displayMode: 'SQUISHED', + indexed: false, + }; + }); + }); + readonly geneInfo$ = resource({ request: this.version$, - loader: async (version) => this.backend.getGeneInfo(version.request, this.gene.ensg_number).then(info => info[0]) - }) + loader: async (version) => + this.backend + .getGeneInfo(version.request, this.gene.ensg_number) + .then((info) => info[0]), + }); readonly goTerms$ = resource({ request: this.version$, - loader: async (version) => this.backend.getGOterms(version.request, this.gene.gene_symbol) - }) + loader: async (version) => + this.backend.getGOterms(version.request, this.gene.gene_symbol), + }); readonly hallmarks$ = resource({ request: this.version$, - loader: async (version) => this.backend.getHallmark(version.request, this.gene.gene_symbol) - }) + loader: async (version) => + this.backend.getHallmark(version.request, this.gene.gene_symbol), + }); readonly wikipathways$ = resource({ request: this.version$, - loader: async (version) => this.backend.getWikiPathways(version.request, this.gene.gene_symbol) - }) + loader: async (version) => + this.backend.getWikiPathways(version.request, this.gene.gene_symbol), + }); readonly transcripts$: ResourceRef = resource({ request: this.version$, loader: async (version) => { - const transcripts = await this.backend.getGeneTranscripts(version.request, this.gene.ensg_number); - const asEvents = await this.backend.getAlternativeSplicingEvents(transcripts); + const transcripts = await this.backend.getGeneTranscripts( + version.request, + this.gene.ensg_number, + ); + const asEvents = + await this.backend.getAlternativeSplicingEvents(transcripts); const transcriptEvents = asEvents.reduce((acc, event) => { const enst = event.transcript.enst_number; if (!acc.has(enst)) { @@ -92,14 +169,32 @@ export class GeneModalComponent implements AfterViewInit { } acc.get(enst)!.add(event.event_type); return acc; - }, new Map>()) + }, new Map>()); - return transcripts.map(t => ({ + return transcripts.map((t) => ({ enst: t, - events: Array.from(transcriptEvents.get(t) ?? []) + events: Array.from(transcriptEvents.get(t) ?? []), })); + }, + }); + + readonly location$: Signal = computed(() => { + const geneInfo = this.geneInfo$.value(); + if (!geneInfo) { + return { + chr: 'all', + }; + } else { + return { + chr: geneInfo.chromosome_name, + range: { + start: geneInfo.start_pos, + end: geneInfo.end_pos, + }, + }; } - }) + }); + protected readonly IGV_REFGENOME = IGV_REFGENOME; constructor() { effect(() => { @@ -112,7 +207,7 @@ export class GeneModalComponent implements AfterViewInit { effect(() => { this.asDatasource.data = this.transcripts$.value() ?? []; - }) + }); } ngAfterViewInit(): void { @@ -123,10 +218,10 @@ export class GeneModalComponent implements AfterViewInit { async openTranscript(enst: string) { if (this.isOpeningTranscript()) return; this.isOpeningTranscript.set(true); - const transcript = (await this.backend.getTranscriptInfo(this.version$(), enst))[0]; - this.dialog.open(TranscriptModalComponent, { - data: transcript - }); + const transcript = ( + await this.backend.getTranscriptInfo(this.version$(), enst) + )[0]; + this.modalsService.openNodeDialog(transcript); this.isOpeningTranscript.set(false); } } diff --git a/src/app/components/interactions-table/interactions-table.component.html b/src/app/components/interactions-table/interactions-table.component.html index dd1a06b6..9ef9c1ab 100644 --- a/src/app/components/interactions-table/interactions-table.component.html +++ b/src/app/components/interactions-table/interactions-table.component.html @@ -38,7 +38,7 @@ - SPONGE computes multiple miRNA sensitivity correlation values. Note that this is a + SPONGE computes multiple miRNA sensitivity correlation values. Note that this is a generalization of sensitivity correlation as defined by Paci et al.. These values capture the joint contribution of several miRNAs on the ceRNA regulation of two genes while accounting for their cross-correlation. @@ -51,7 +51,7 @@ - SPONGE computes a null model to calculate empirical p-values for the ceRNA + SPONGE computes a null model to calculate empirical p-values for the ceRNA interactions. We sampled 1,000,000 datasets to closely estimate the p-values. The interactions were then FDR-corrected and filtered with a p-value cut-off of 0.01. diff --git a/src/app/components/interactions-table/interactions-table.component.ts b/src/app/components/interactions-table/interactions-table.component.ts index adbd06a8..df9fc5ef 100644 --- a/src/app/components/interactions-table/interactions-table.component.ts +++ b/src/app/components/interactions-table/interactions-table.component.ts @@ -18,16 +18,13 @@ import { import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatSort, MatSortHeader } from '@angular/material/sort'; -import { GeneModalComponent } from '../gene-modal/gene-modal.component'; -import { MatDialog } from '@angular/material/dialog'; import { MatButton } from '@angular/material/button'; import { MatTooltip } from '@angular/material/tooltip'; import { BrowseService } from '../../services/browse.service'; -import { TranscriptModalComponent } from '../transcript-modal/transcript-modal.component'; -import { InteractionModalComponent } from '../interaction-modal/interaction-modal.component'; import { capitalize } from 'lodash'; import { InfoComponent } from '../info/info.component'; import katex from 'katex'; +import { ModalsService } from '../modals-service/modals.service'; @Component({ selector: 'app-interactions-table', @@ -46,9 +43,9 @@ import katex from 'katex'; export class InteractionsTableComponent implements AfterViewInit { @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; + modalsService = inject(ModalsService); level$ = input<'gene' | 'transcript'>(); interactions$ = input<(GeneInteraction | TranscriptInteraction)[]>(); - readonly dialog = inject(MatDialog); mscorEquation$ = viewChild>('mscorEquation'); columns = ['name_1', 'name_2', 'mirna', 'correlation', 'mscor', 'padj']; dataSource$ = computed(() => { @@ -98,20 +95,10 @@ export class InteractionsTableComponent implements AfterViewInit { } openMiRNADialog(interaction: GeneInteraction | TranscriptInteraction) { - this.dialog.open(InteractionModalComponent, { - data: interaction, - }); + this.modalsService.openMiRNADialog(interaction); } openDialog(entity: Gene | Transcript) { - if ('ensg_number' in entity) { - this.dialog.open(GeneModalComponent, { - data: entity, - }); - } else { - this.dialog.open(TranscriptModalComponent, { - data: entity, - }); - } + this.modalsService.openNodeDialog(entity); } } diff --git a/src/app/components/modals-service/modals-service.service.spec.ts b/src/app/components/modals-service/modals-service.service.spec.ts new file mode 100644 index 00000000..3fc2492e --- /dev/null +++ b/src/app/components/modals-service/modals-service.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ModalsService } from './modals.service'; + +describe('ModalsServiceService', () => { + let service: ModalsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ModalsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/components/modals-service/modals.service.ts b/src/app/components/modals-service/modals.service.ts new file mode 100644 index 00000000..dd58cb70 --- /dev/null +++ b/src/app/components/modals-service/modals.service.ts @@ -0,0 +1,40 @@ +import { inject, Injectable } from '@angular/core'; +import { + Gene, + GeneInteraction, + Transcript, + TranscriptInteraction, +} from '../../interfaces'; +import { GeneModalComponent } from '../gene-modal/gene-modal.component'; +import { TranscriptModalComponent } from '../transcript-modal/transcript-modal.component'; +import { MatDialog } from '@angular/material/dialog'; +import { InteractionModalComponent } from '../interaction-modal/interaction-modal.component'; + +@Injectable({ + providedIn: 'root', +}) +export class ModalsService { + private readonly dialog = inject(MatDialog); + + constructor() {} + + openNodeDialog(entity: Gene | Transcript) { + if ('ensg_number' in entity) { + this.dialog.open(GeneModalComponent, { + data: entity, + minWidth: '60vw', + minHeight: '60vh', + }); + } else { + this.dialog.open(TranscriptModalComponent, { + data: entity, + }); + } + } + + openMiRNADialog(interaction: GeneInteraction | TranscriptInteraction) { + this.dialog.open(InteractionModalComponent, { + data: interaction, + }); + } +} diff --git a/src/app/components/transcript-modal/transcript-modal.component.ts b/src/app/components/transcript-modal/transcript-modal.component.ts index b007fdfa..95b25cd6 100644 --- a/src/app/components/transcript-modal/transcript-modal.component.ts +++ b/src/app/components/transcript-modal/transcript-modal.component.ts @@ -1,22 +1,38 @@ -import {AfterViewInit, Component, computed, effect, inject, resource, viewChild} from '@angular/core'; -import {AlternativeSplicingEvent, Transcript, TranscriptInfoWithChromosome} from "../../interfaces"; -import {MAT_DIALOG_DATA, MatDialog, MatDialogModule} from "@angular/material/dialog"; -import {MatButtonModule} from "@angular/material/button"; -import {VersionsService} from "../../services/versions.service"; -import {BackendService} from "../../services/backend.service"; -import {MatTabsModule} from "@angular/material/tabs"; -import {MatExpansionModule} from "@angular/material/expansion"; -import {MatTableDataSource, MatTableModule} from "@angular/material/table"; -import {MatPaginator, MatPaginatorModule} from "@angular/material/paginator"; -import {MatFormFieldModule} from "@angular/material/form-field"; -import {MatInputModule} from "@angular/material/input"; -import {FormsModule} from "@angular/forms"; -import {BrowseService} from "../../services/browse.service"; -import {GeneModalComponent} from "../gene-modal/gene-modal.component"; -import {MatProgressSpinner} from "@angular/material/progress-spinner"; -import {MatChip} from "@angular/material/chips"; -import {MatTooltip} from "@angular/material/tooltip"; -import {AS_DESCRIPTIONS} from "../../constants"; +import { + AfterViewInit, + Component, + computed, + effect, + inject, + resource, + viewChild, +} from '@angular/core'; +import { + AlternativeSplicingEvent, + Transcript, + TranscriptInfoWithChromosome, +} from '../../interfaces'; +import { + MAT_DIALOG_DATA, + MatDialog, + MatDialogModule, +} from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { VersionsService } from '../../services/versions.service'; +import { BackendService } from '../../services/backend.service'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { FormsModule } from '@angular/forms'; +import { BrowseService } from '../../services/browse.service'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MatChip } from '@angular/material/chips'; +import { MatTooltip } from '@angular/material/tooltip'; +import { AS_DESCRIPTIONS } from '../../constants'; +import { ModalsService } from '../modals-service/modals.service'; interface AsEventWithPsi extends AlternativeSplicingEvent { psi: number; @@ -36,17 +52,18 @@ interface AsEventWithPsi extends AlternativeSplicingEvent { FormsModule, MatProgressSpinner, MatChip, - MatTooltip + MatTooltip, ], templateUrl: './transcript-modal.component.html', - styleUrl: './transcript-modal.component.scss' + styleUrl: './transcript-modal.component.scss', }) export class TranscriptModalComponent implements AfterViewInit { readonly dialog = inject(MatDialog); paginator = viewChild.required(MatPaginator); - columns = ['event_type', 'event_name'] + columns = ['event_type', 'event_name']; - readonly asDescriptions = AS_DESCRIPTIONS + modalsService = inject(ModalsService); + readonly asDescriptions = AS_DESCRIPTIONS; readonly transcript = inject(MAT_DIALOG_DATA); readonly versionsService = inject(VersionsService); readonly backend = inject(BackendService); @@ -56,55 +73,56 @@ export class TranscriptModalComponent implements AfterViewInit { readonly transcriptInfo$ = resource({ request: this.version$, loader: async (version) => { - const transcriptInfoPromise = - this.backend.getTranscriptInfo(version.request, this.transcript.enst_number).then(info => info[0]) + const transcriptInfoPromise = this.backend + .getTranscriptInfo(version.request, this.transcript.enst_number) + .then((info) => info[0]); - const geneInfoPromise = - this.backend.getGeneInfo(version.request, this.transcript.gene.ensg_number).then(info => info[0]); + const geneInfoPromise = this.backend + .getGeneInfo(version.request, this.transcript.gene.ensg_number) + .then((info) => info[0]); - const [transcriptInfo, geneInfo] = await Promise.all([transcriptInfoPromise, geneInfoPromise]); + const [transcriptInfo, geneInfo] = await Promise.all([ + transcriptInfoPromise, + geneInfoPromise, + ]); return { ...transcriptInfo, - chromosome_name: geneInfo.chromosome_name - } as TranscriptInfoWithChromosome - } - }) + chromosome_name: geneInfo.chromosome_name, + } as TranscriptInfoWithChromosome; + }, + }); isCanonical$ = computed(() => { const info = this.transcriptInfo$.value(); - if (info === undefined) return "Unknown"; + if (info === undefined) return 'Unknown'; if (info.canonical_transcript === 0) { - return "No"; + return 'No'; } else if (info.canonical_transcript === 1) { - return "Yes"; + return 'Yes'; } else { - return "Unknown"; + return 'Unknown'; } - }) + }); alternativeSplicingEvents = resource({ loader: () => { - return this.backend.getAlternativeSplicingEvents([this.transcript.enst_number]) - } - }) + return this.backend.getAlternativeSplicingEvents([ + this.transcript.enst_number, + ]); + }, + }); hasAsEvents$ = computed(() => { return (this.alternativeSplicingEvents.value() || []).length > 0; - }) + }); protected readonly BrowseService = BrowseService; constructor() { effect(() => { this.asDatasource.data = this.alternativeSplicingEvents.value() || []; - }) - - effect(() => { - console.log(this.hasAsEvents$()) }); } showGene() { - this.dialog.open(GeneModalComponent, { - data: this.transcript.gene - }) + this.modalsService.openNodeDialog(this.transcript.gene); } ngAfterViewInit(): void { diff --git a/src/app/constants.ts b/src/app/constants.ts index 0f8ce901..85d7d005 100644 --- a/src/app/constants.ts +++ b/src/app/constants.ts @@ -14,3 +14,15 @@ export const AS_DESCRIPTIONS: { [key: string]: string } = { export const SPONGE_EXAMPLE_URL = 'https://exbio.wzw.tum.de/sponge-files/GSE123845_exp_tpm_matrix_processed.csv'; + +export const IGV_REFGENOME = { + id: 'hg38', + name: 'Human (GRCh38/hg38)', + assembly: 'GCA_000001405.15', + taxon: 9606, + fastaURL: + 'https://s3.amazonaws.com/igv.broadinstitute.org/genomes/seq/hg38/hg38.fa', + indexURL: + 'https://s3.amazonaws.com/igv.broadinstitute.org/genomes/seq/hg38/hg38.fa.fai', + refGeneURL: 'https://s3.amazonaws.com/igv.org.genomes/hg38/refGene.txt.gz', +}; diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts index 05ff0b67..7b3e04f9 100644 --- a/src/app/interfaces.ts +++ b/src/app/interfaces.ts @@ -1,294 +1,286 @@ export interface Dataset { - data_origin: string, - dataset_ID: number, - disease_name: string, - disease_subtype: string | null, - disease_type: string, - download_url: string, - sponge_db_version: number + data_origin: string; + dataset_ID: number; + disease_name: string; + disease_subtype: string | null; + disease_type: string; + download_url: string; + sponge_db_version: number; } export interface SpongeRun { - "sponge_run": { - "dataset": { - "data_origin": string, - "dataset_ID": number, - "disease_name": string - }, - "sponge_run_ID": number - } + sponge_run: { + dataset: { + data_origin: string; + dataset_ID: number; + disease_name: string; + }; + sponge_run_ID: number; + }; } export interface RunInfo { - "coefficient_direction": string, - "coefficient_threshold": string, - "dataset": { - "dataset_ID": number, - "disease_name": string - }, - "f_test": boolean, - "f_test_p_adj_threshold": number, - "ks": string, - "log_level": string, - "m_max": number, - "min_corr": number, - "number_of_datasets": number, - "number_of_samples": number, - "sponge_run_ID": number, - "variance_cutoff": string + coefficient_direction: string; + coefficient_threshold: string; + dataset: { + dataset_ID: number; + disease_name: string; + }; + f_test: boolean; + f_test_p_adj_threshold: number; + ks: string; + log_level: string; + m_max: number; + min_corr: number; + number_of_datasets: number; + number_of_samples: number; + sponge_run_ID: number; + variance_cutoff: string; } export interface DatasetInfo { - dataset_ID: number, - disease_name: string, - data_origin: string, - disease_type: string, - download_url: string, - disease_subtype: string, - study_abbreviation: string, - version: number, - number_of_samples: number + dataset_ID: number; + disease_name: string; + data_origin: string; + disease_type: string; + download_url: string; + disease_subtype: string; + study_abbreviation: string; + version: number; + number_of_samples: number; } export interface OverallCounts { - count_interactions: number, - count_interactions_sign: number, - count_shared_miRNAs: number, - disease_name: string, - sponge_run_ID: number + count_interactions: number; + count_interactions_sign: number; + count_shared_miRNAs: number; + disease_name: string; + sponge_run_ID: number; } export enum GeneSorting { - Betweenness = "betweenness", - Degree = "degree", - Eigenvector = "eigenvector" + Betweenness = 'betweenness', + Degree = 'degree', + Eigenvector = 'eigenvector', } export enum InteractionSorting { - pAdj = "adjusted p-value", - mScor = "MScor", - Correlation = "Correlation" + pAdj = 'pValue', + mscor = 'mscor', + Correlation = 'correlation', } export interface Gene { - ensg_number: string, - gene_symbol?: string + ensg_number: string; + gene_symbol?: string; } export interface Transcript { - enst_number: string, - gene: Gene + enst_number: string; + gene: Gene; } export interface GeneNode extends SpongeRun { - betweenness: number, - eigenvector: number, - gene: Gene, - node_degree: number + betweenness: number; + eigenvector: number; + gene: Gene; + node_degree: number; } export interface TranscriptNode extends SpongeRun { - betweenness: number, - eigenvector: number, - transcript: Transcript, - node_degree: number + betweenness: number; + eigenvector: number; + transcript: Transcript; + node_degree: number; } export interface GeneInteraction extends SpongeRun { - "correlation": number, - "gene1": Gene, - "gene2": Gene, - "mscor": number, - "p_value": number, + correlation: number; + gene1: Gene; + gene2: Gene; + mscor: number; + p_value: number; } export interface TranscriptInteraction extends SpongeRun { - "correlation": number, - "mscor": number, - "p_value": number, - "transcript_1": Transcript, - "transcript_2": Transcript + correlation: number; + mscor: number; + p_value: number; + transcript_1: Transcript; + transcript_2: Transcript; } export interface BrowseQuery { - level: 'gene' | 'transcript', - dataset: Dataset, - showOrphans: boolean, - geneSorting: GeneSorting, - maxNodes: number, - minDegree: number, - minBetweenness: number, - minEigen: number, - interactionSorting: InteractionSorting, - maxInteractions: number, - maxPValue: number, - minMScore: number + level: 'gene' | 'transcript'; + dataset: Dataset; + showOrphans: boolean; + geneSorting: GeneSorting; + maxNodes: number; + minDegree: number; + minBetweenness: number; + minEigen: number; + interactionSorting: InteractionSorting; + maxInteractions: number; + maxPValue: number; + minMscor: number; } export interface CeRNA { - betweenness: number, - eigenvector: number, - gene: Gene, - node_degree: number + betweenness: number; + eigenvector: number; + gene: Gene; + node_degree: number; run: { dataset: { - data_origin: string, - dataset_ID: number, - disease_name: string - }, - run_ID: number - } + data_origin: string; + dataset_ID: number; + disease_name: string; + }; + run_ID: number; + }; } export interface CeRNAInteraction { - "correlation": number, - "gene1": Gene, - "gene2": Gene, - "mscor": number, - "p_value": number, - "run": { - "dataset": { - "data_origin": string, - "dataset_ID": number, - "disease_name": string - }, - "run_ID": number - } -} - -export interface CeRNAQuery { - disease: Dataset, - geneSorting: GeneSorting, - maxGenes: number, - minDegree: number, - minBetweenness: number, - minEigen: number, - interactionSorting: InteractionSorting, - maxInteractions: number, - maxPValue: number, - minMScore: number + correlation: number; + gene1: Gene; + gene2: Gene; + mscor: number; + p_value: number; + run: { + dataset: { + data_origin: string; + dataset_ID: number; + disease_name: string; + }; + run_ID: number; + }; +} + +export interface Network { + edges: (GeneInteraction | TranscriptInteraction)[]; + nodes: (GeneNode | TranscriptNode)[]; } export interface CeRNAExpression { - "dataset": string, - "expr_value": number, - "gene": Gene, - "sample_ID": string + dataset: string; + expr_value: number; + gene: Gene; + sample_ID: string; } export interface GeneExpression { - "dataset": string, - "expr_value": number, - "gene": Gene, - "sample_ID": string + dataset: string; + expr_value: number; + gene: Gene; + sample_ID: string; } export interface TranscriptExpression { - "dataset": string, - "expr_value": number, - "sample_ID": string, - "transcript": Transcript + dataset: string; + expr_value: number; + sample_ID: string; + transcript: Transcript; } export interface SurvivalRate { - "dataset": string, - "gene": Gene, - "overexpression": number, - "patient_information": { - "disease_status": number, - "sample_ID": string, - "survival_time": number - } + dataset: string; + gene: Gene; + overexpression: number; + patient_information: { + disease_status: number; + sample_ID: string; + survival_time: number; + }; } export interface SurvivalPValue { - "dataset": string, - "gene": Gene, - "pValue": number + dataset: string; + gene: Gene; + pValue: number; } export interface GeneCount extends SpongeRun { - "count_all": number, - "count_sign": number, - "gene": Gene + count_all: number; + count_sign: number; + gene: Gene; } export interface GeneInfo { - chromosome_name: string, - cytoband: string, - description: string, - end_pos: number, - ensg_number: string, - gene_symbol: string, - gene_type: string, - start_pos: number + chromosome_name: string; + cytoband: string; + description: string; + end_pos: number; + ensg_number: string; + gene_symbol: string; + gene_type: string; + start_pos: number; } export interface TranscriptInfo { - ensg_number: string, - transcript_type: string, - start_pos: number, - end_pos: number, - canonical_transcript: number + ensg_number: string; + transcript_type: string; + start_pos: number; + end_pos: number; + canonical_transcript: number; } export interface TranscriptInfoWithChromosome extends TranscriptInfo { - chromosome_name: string + chromosome_name: string; } export interface GOTerm { - description: string, - gene: Gene, - gene_ontology_symbol: string + description: string; + gene: Gene; + gene_ontology_symbol: string; } export interface Hallmark { - gene: Gene, - hallmark: string + gene: Gene; + hallmark: string; } export interface WikiPathway { - gene: Gene, - wp_key: string + gene: Gene; + wp_key: string; } // from spongEffects // route responses export interface SpongEffectsRun { - spongeEffects_run_ID: number, - m_scor_threshold: number, - p_adjust_threshold: number, - modules_cutoff: number, - bin_size: number, - min_size: number, - max_size: number, - min_expr: number, - method: string, - cv_folds: number - level: string, - sponge_run_ID: number, - m_max: number, - log_level: string, - sponge_db_version: number, - dataset_ID: number, - disease_name: string, - data_origin: string, - disease_type: string, - download_url: string, - disease_subtype: string, + spongeEffects_run_ID: number; + m_scor_threshold: number; + p_adjust_threshold: number; + modules_cutoff: number; + bin_size: number; + min_size: number; + max_size: number; + min_expr: number; + method: string; + cv_folds: number; + level: string; + sponge_run_ID: number; + m_max: number; + log_level: string; + sponge_db_version: number; + dataset_ID: number; + disease_name: string; + data_origin: string; + disease_type: string; + download_url: string; + disease_subtype: string; } export interface RunPerformance { - model_type: string, - split_type: string, - accuracy: number, - kappa: number, - accuracy_lower: number, - accuracy_upper: number, - accuracy_null: number, - accuracy_p_value: number, - mcnemar_p_value: number + model_type: string; + split_type: string; + accuracy: number; + kappa: number; + accuracy_lower: number; + accuracy_upper: number; + accuracy_null: number; + accuracy_p_value: number; + mcnemar_p_value: number; } export interface RunClassPerformance { @@ -371,48 +363,48 @@ export interface PredictCancerType { } export interface ExploreQuery { - selectedCancer: string, - selectedLevel: string + selectedCancer: string; + selectedLevel: string; } // other interfaces for spongEffects export interface Metric { - name: string, - split: string - lower: number, - upper: number, - idx: number + name: string; + split: string; + lower: number; + upper: number; + idx: number; } export interface SelectElement { - value: string, - viewValue: string + value: string; + viewValue: string; } export interface CancerInfo { - text: string[], + text: string[]; link: string; } export interface PlotData { - x: number[], - y: number[] + x: number[]; + y: number[]; } export interface PlotlyData { - data: any, - layout?: any, - config?: any + data: any; + layout?: any; + config?: any; } export interface Tab extends SelectElement { - icon: string + icon: string; } export interface LinearRegression { - slope: number, - x0: number + slope: number; + x0: number; } export interface ExampleExpression { @@ -424,79 +416,78 @@ export interface ExampleExpression { sampleN: number; } - export interface AlternativeSplicingEvent { - alternative_splicing_event_transcripts_ID: number, - event_name: string, - event_type: string, + alternative_splicing_event_transcripts_ID: number; + event_name: string; + event_type: string; transcript: { - enst_number: string - } + enst_number: string; + }; } export interface MiRNA { - hs_nr: string, - mir_ID: string + hs_nr: string; + mir_ID: string; } export interface TranscriptMiRNA extends SpongeRun { - transcript: Transcript, - mirna: MiRNA, - coefficient: number + transcript: Transcript; + mirna: MiRNA; + coefficient: number; } export interface GeneMiRNA extends SpongeRun { - gene: Gene, - mirna: MiRNA, - coefficient: number + gene: Gene; + mirna: MiRNA; + coefficient: number; } export interface NetworkResult { - subtype: {}, + subtype: {}; type: { - "euclidean_distances": { - labels: string[], - x: number[], - y: number[] - }, + euclidean_distances: { + labels: string[]; + x: number[]; + y: number[]; + }; scores: { - labels: string[], - values: number[][] - } - } + labels: string[]; + values: number[][]; + }; + }; } export interface Comparison { - comparison_ID: number, - condition_1: string, - condition_2: string, - dataset_1: Dataset, - dataset_2: Dataset, - gene_transcript: 'gene' | 'transcript' + comparison_ID: number; + condition_1: string; + condition_2: string; + dataset_1: Dataset; + dataset_2: Dataset; + gene_transcript: 'gene' | 'transcript'; } export interface GseaResult { - es: number, - fdr: number, - fwerp: number, - gene_percent: number, - nes: number, - pvalue: number, - tag_percent: string, - term: string, + es: number; + fdr: number; + fwerp: number; + gene_percent: number; + nes: number; + pvalue: number; + tag_percent: string; + term: string; lead_genes: { - gene: Gene, - gsea_lead_genes_ID: number - }[], + gene: Gene; + gsea_lead_genes_ID: number; + }[]; matched_genes: { - gene: Gene, - gsea_matched_genes_ID: number - } + gene: Gene; + gsea_matched_genes_ID: number; + }; } export interface SpongEffectsModule { - ensemblID: string, - symbol: string, - meanGiniDecrease: number, - meanAccuracyDecrease: number, + ensemblID: string; + symbol: string; + meanGiniDecrease: number; + meanAccuracyDecrease: number; } diff --git a/src/app/routes/browse/active-entities/active-entities.component.html b/src/app/routes/browse/active-entities/active-entities.component.html index 1e14e027..98c96953 100644 --- a/src/app/routes/browse/active-entities/active-entities.component.html +++ b/src/app/routes/browse/active-entities/active-entities.component.html @@ -13,7 +13,7 @@ matTooltip="Perform Functional Enrichment Analysis on selected {{level$()}}s">g:Profiler } - +
@for (node of nodes$(); track node) { @@ -73,7 +73,7 @@ {{ interaction.correlation }} - MScor + mscor {{ interaction.mscor }} diff --git a/src/app/routes/browse/active-entities/active-entities.component.ts b/src/app/routes/browse/active-entities/active-entities.component.ts index 27dddccb..aac74e56 100644 --- a/src/app/routes/browse/active-entities/active-entities.component.ts +++ b/src/app/routes/browse/active-entities/active-entities.component.ts @@ -9,11 +9,10 @@ import { } from '../../../interfaces'; import { MatCardModule } from '@angular/material/card'; import { MatDialog } from '@angular/material/dialog'; -import { GeneModalComponent } from '../../../components/gene-modal/gene-modal.component'; import { MatAnchor, MatButton } from '@angular/material/button'; -import { TranscriptModalComponent } from '../../../components/transcript-modal/transcript-modal.component'; import { InteractionModalComponent } from '../../../components/interaction-modal/interaction-modal.component'; import { MatTooltip } from '@angular/material/tooltip'; +import { ModalsService } from '../../../components/modals-service/modals.service'; @Component({ selector: 'app-active-entities', @@ -24,6 +23,7 @@ import { MatTooltip } from '@angular/material/tooltip'; export class ActiveEntitiesComponent { readonly dialog = inject(MatDialog); protected BrowseService = BrowseService; + protected modalsService = inject(ModalsService); protected readonly browseService = inject(BrowseService); nodes$ = this.browseService.activeNodes$; gProfilerUrl = computed(() => @@ -41,14 +41,6 @@ export class ActiveEntitiesComponent { } openModal(entity: Gene | Transcript): void { - if ('ensg_number' in entity) { - this.dialog.open(GeneModalComponent, { - data: entity, - }); - } else { - this.dialog.open(TranscriptModalComponent, { - data: entity, - }); - } + this.modalsService.openNodeDialog(entity); } } diff --git a/src/app/routes/browse/form/form.component.html b/src/app/routes/browse/form/form.component.html index 573131b1..461d4276 100644 --- a/src/app/routes/browse/form/form.component.html +++ b/src/app/routes/browse/form/form.component.html @@ -1,5 +1,5 @@
- + @if (version() > 1) {
@@ -62,8 +62,8 @@ - Min. MScore - + Min. mscor + diff --git a/src/app/routes/browse/form/form.component.ts b/src/app/routes/browse/form/form.component.ts index 9fc77f87..9716e692 100644 --- a/src/app/routes/browse/form/form.component.ts +++ b/src/app/routes/browse/form/form.component.ts @@ -1,16 +1,30 @@ -import {Component, computed, effect, inject, linkedSignal, signal} from '@angular/core'; -import {MatFormFieldModule} from "@angular/material/form-field"; -import {MatSelectModule} from "@angular/material/select"; -import {MatExpansionModule} from "@angular/material/expansion"; -import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms"; -import {MatInputModule} from "@angular/material/input"; -import {BrowseQuery, GeneSorting, InteractionSorting} from "../../../interfaces"; -import {BrowseService} from "../../../services/browse.service"; -import {VersionsService} from "../../../services/versions.service"; -import _ from "lodash"; -import {MatButtonToggle, MatButtonToggleGroup} from "@angular/material/button-toggle"; -import {MatCheckbox} from "@angular/material/checkbox"; -import {DiseaseSelectorComponent} from "../../../components/disease-selector/disease-selector.component"; +import { + Component, + computed, + effect, + inject, + linkedSignal, + signal, +} from '@angular/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { + BrowseQuery, + GeneSorting, + InteractionSorting, +} from '../../../interfaces'; +import { BrowseService } from '../../../services/browse.service'; +import { VersionsService } from '../../../services/versions.service'; +import _ from 'lodash'; +import { + MatButtonToggle, + MatButtonToggleGroup, +} from '@angular/material/button-toggle'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { DiseaseSelectorComponent } from '../../../components/disease-selector/disease-selector.component'; @Component({ selector: 'app-form', @@ -26,14 +40,14 @@ import {DiseaseSelectorComponent} from "../../../components/disease-selector/dis DiseaseSelectorComponent, ], templateUrl: './form.component.html', - styleUrl: './form.component.scss' + styleUrl: './form.component.scss', }) export class FormComponent { versionsService = inject(VersionsService); browseService = inject(BrowseService); version = this.versionsService.versionReadOnly(); - diseases$ = computed(() => this.versionsService.diseases$().value() ?? []) - activeDataset = linkedSignal(() => this.diseases$()[0]) + diseases$ = computed(() => this.versionsService.diseases$().value() ?? []); + activeDataset = linkedSignal(() => this.diseases$()[0]); geneSortings = GeneSorting; interactionSortings = InteractionSorting; formGroup = new FormGroup({ @@ -44,17 +58,19 @@ export class FormComponent { minDegree: new FormControl(1), minBetweenness: new FormControl(0.05), minEigen: new FormControl(0.1), - interactionSorting: new FormControl(this.interactionSortings.pAdj), + interactionSorting: new FormControl( + this.interactionSortings.pAdj, + ), maxInteractions: new FormControl(100), maxPValue: new FormControl(0.05), - minMScore: new FormControl(0.1), - }) + minMscor: new FormControl(0.1), + }); protected readonly capitalize = _.capitalize; constructor() { const formSignal = signal(this.formGroup.value); - this.formGroup.valueChanges.subscribe(val => formSignal.set(val)) + this.formGroup.valueChanges.subscribe((val) => formSignal.set(val)); effect(() => { const config = formSignal(); @@ -62,7 +78,7 @@ export class FormComponent { if (dataset === undefined) return; this.browseService.runQuery({ ...config, - dataset + dataset, } as BrowseQuery); }); } diff --git a/src/app/routes/browse/nodes/nodes.component.ts b/src/app/routes/browse/nodes/nodes.component.ts index e4032c83..e49a96bb 100644 --- a/src/app/routes/browse/nodes/nodes.component.ts +++ b/src/app/routes/browse/nodes/nodes.component.ts @@ -11,10 +11,9 @@ import { MatPaginator } from '@angular/material/paginator'; import { MatSort, MatSortHeader } from '@angular/material/sort'; import { MatButton } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; -import { GeneModalComponent } from '../../../components/gene-modal/gene-modal.component'; import { MatTooltip } from '@angular/material/tooltip'; -import { TranscriptModalComponent } from '../../../components/transcript-modal/transcript-modal.component'; import { InfoComponent } from '../../../components/info/info.component'; +import { ModalsService } from '../../../components/modals-service/modals.service'; @Component({ selector: 'app-nodes', @@ -33,6 +32,7 @@ import { InfoComponent } from '../../../components/info/info.component'; export class NodesComponent implements AfterViewInit { @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; + modalsService = inject(ModalsService); columns = ['identifier', 'betweenness', 'eigenvector', 'node_degree']; dataSource: MatTableDataSource; readonly dialog = inject(MatDialog); @@ -57,14 +57,6 @@ export class NodesComponent implements AfterViewInit { } openDialog(entity: Gene | Transcript) { - if ('ensg_number' in entity) { - this.dialog.open(GeneModalComponent, { - data: entity, - }); - } else { - this.dialog.open(TranscriptModalComponent, { - data: entity, - }); - } + this.modalsService.openNodeDialog(entity); } } diff --git a/src/app/routes/documentation/browse-functionalities/browse-functionalities.component.html b/src/app/routes/documentation/browse-functionalities/browse-functionalities.component.html index 3a5dcd98..194ba8bb 100644 --- a/src/app/routes/documentation/browse-functionalities/browse-functionalities.component.html +++ b/src/app/routes/documentation/browse-functionalities/browse-functionalities.component.html @@ -1,6 +1,6 @@

Browse functionalities

On the browse site you can create different interaction networks and investigate which genes/miRNAs affect each - other. The node sizes and edge thicknes is corresponding to the Mscore and node degree. When loading a new cancer set, + other. The node sizes and edge thickness is corresponding to the mscor and node degree. When loading a new cancer set, also a expression heatmap will be produced.

Network-Features:

diff --git a/src/app/routes/documentation/browse-functionalities/browse-functionalities.component.ts b/src/app/routes/documentation/browse-functionalities/browse-functionalities.component.ts index a0d25ac4..22fae786 100644 --- a/src/app/routes/documentation/browse-functionalities/browse-functionalities.component.ts +++ b/src/app/routes/documentation/browse-functionalities/browse-functionalities.component.ts @@ -1,48 +1,51 @@ -import {Component} from '@angular/core'; -import {MatTableModule} from "@angular/material/table"; +import { Component } from '@angular/core'; +import { MatTableModule } from '@angular/material/table'; @Component({ selector: 'app-browse-functionalities', - imports: [ - MatTableModule - ], + imports: [MatTableModule], templateUrl: './browse-functionalities.component.html', - styleUrl: './browse-functionalities.component.scss' + styleUrl: './browse-functionalities.component.scss', }) export class BrowseFunctionalitiesComponent { - columns = ["name", "description"]; + columns = ['name', 'description']; features = [ { - "name": "Title Banner Select", - "description": "Select one of the cancer sets, which should be used for a network." + name: 'Title Banner Select', + description: + 'Select one of the cancer sets, which should be used for a network.', }, { - "name": "Click on a Node", - "description": "Survival Analysis (Kaplan-Meier Plot) will be displayed for the Node." + name: 'Click on a Node', + description: + 'Survival Analysis (Kaplan-Meier Plot) will be displayed for the Node.', }, { - "name": "Hover over Node/Edge", - "description": "Values such as Mscore, p-value, betweenness, correlation, and gene IDs will be displayed in a side table." + name: 'Hover over Node/Edge', + description: + 'Values such as mscor, p-value, betweenness, correlation, and gene IDs will be displayed in a side table.', }, { - "name": "Search under network", - "description": "Select a ENSG number or interaction of interest (number), and it will be colored and focused on in the network." + name: 'Search under network', + description: + 'Select a ENSG number or interaction of interest (number), and it will be colored and focused on in the network.', }, { - "name": "Save as", - "description": "You can save and download the plot as .jpg or .png." + name: 'Save as', + description: 'You can save and download the plot as .jpg or .png.', }, { - "name": "Reset camera", - "description": "Reset the viewpoint of the network after zooming or focusing on a node." + name: 'Reset camera', + description: + 'Reset the viewpoint of the network after zooming or focusing on a node.', }, { - "name": "Force atlas 2/start layout", - "description": "Start a layout for a better visualization of the network." + name: 'Force atlas 2/start layout', + description: 'Start a layout for a better visualization of the network.', }, { - "name": "Reset Colors", - "description": "Deselect clicked nodes." - } - ] + name: 'Reset Colors', + description: 'Deselect clicked nodes.', + }, + ]; } diff --git a/src/app/routes/documentation/example-script/example-script.component.ts b/src/app/routes/documentation/example-script/example-script.component.ts index ed8fd5d2..d4060a7f 100644 --- a/src/app/routes/documentation/example-script/example-script.component.ts +++ b/src/app/routes/documentation/example-script/example-script.component.ts @@ -18,10 +18,6 @@ export class ExampleScriptComponent { content$ = this.http.getHtmlRequest('./example.py'); - constructor() { - this.content$.then(console.log); - } - async download() { const content = await this.content$; const file = new File([content], 'example.py'); diff --git a/src/app/routes/documentation/home-search-bar/home-search-bar.component.ts b/src/app/routes/documentation/home-search-bar/home-search-bar.component.ts index 5c27d681..60bd490d 100644 --- a/src/app/routes/documentation/home-search-bar/home-search-bar.component.ts +++ b/src/app/routes/documentation/home-search-bar/home-search-bar.component.ts @@ -1,52 +1,57 @@ -import {Component} from '@angular/core'; -import {MatTableModule} from "@angular/material/table"; +import { Component } from '@angular/core'; +import { MatTableModule } from '@angular/material/table'; @Component({ selector: 'app-home-search-bar', - imports: [ - MatTableModule - ], + imports: [MatTableModule], templateUrl: './home-search-bar.component.html', - styleUrl: './home-search-bar.component.scss' + styleUrl: './home-search-bar.component.scss', }) export class HomeSearchBarComponent { - columns = ["name", "description"]; + columns = ['name', 'description']; features = [ { - "name": "Search Box", - "description": "Search for a ENSG number or Gene Symbol. Check the box to only obtain significant results." + name: 'Search Box', + description: + 'Search for a ENSG number or Gene Symbol. Check the box to only obtain significant results.', }, { - "name": "Result Boxes", - "description": "Lists the cancer datasets, which contain interactions with the searched value. The tables visualize values such as mscore, p-value, correlation, and IDs." + name: 'Result Boxes', + description: + 'Lists the cancer datasets, which contain interactions with the searched value. The tables visualize values such as mscore, p-value, correlation, and IDs.', }, { - "name": "Options Button", - "description": "Set a range for the mscore or adjusted p-value. You don't have to submit your entry; it will be applied automatically." + name: 'Options Button', + description: + "Set a range for the mscor or adjusted p-value. You don't have to submit your entry; it will be applied automatically.", }, { - "name": "Hallmarks", - "description": "Associated cancer hallmarks." + name: 'Hallmarks', + description: 'Associated cancer hallmarks.', }, { - "name": "Show as Network Button", - "description": "Exports selected table entries or default ones to create an interaction network." + name: 'Show as Network Button', + description: + 'Exports selected table entries or default ones to create an interaction network.', }, { - "name": "Gene Enrichment Button", - "description": "If no gene is selected, all genes will be used for gene enrichment analysis. Otherwise, the selected entries will be used for a default query on g:Profiler." + name: 'Gene Enrichment Button', + description: + 'If no gene is selected, all genes will be used for gene enrichment analysis. Otherwise, the selected entries will be used for a default query on g:Profiler.', }, { - "name": "WikiPathways", - "description": "Brings you to a pathway visualization on WikiPathways." + name: 'WikiPathways', + description: 'Brings you to a pathway visualization on WikiPathways.', }, { - "name": "GeneCard", - "description": "Displays further information about the gene with the help of GeneCards." + name: 'GeneCard', + description: + 'Displays further information about the gene with the help of GeneCards.', }, { - "name": "Gene Ontology", - "description": "All associating GO numbers will be displayed for the gene and link to their entry on QuickGO." - } - ] + name: 'Gene Ontology', + description: + 'All associating GO numbers will be displayed for the gene and link to their entry on QuickGO.', + }, + ]; } diff --git a/src/app/routes/spongeffects/predict/prediction-results/prediction-results.component.ts b/src/app/routes/spongeffects/predict/prediction-results/prediction-results.component.ts index bb9e05a4..2cbd686e 100644 --- a/src/app/routes/spongeffects/predict/prediction-results/prediction-results.component.ts +++ b/src/app/routes/spongeffects/predict/prediction-results/prediction-results.component.ts @@ -8,24 +8,20 @@ import { resource, viewChild, } from '@angular/core'; -import { PlotlyData, PredictCancerType } from '../../../../interfaces'; +import { PlotlyData } from '../../../../interfaces'; import { ClassPerformancePlotComponent } from '../../explore/plots/class-performance-plot/class-performance-plot.component'; import { PredictFormComponent } from '../form/predict-form.component'; import { PredictService } from '../service/predict.service'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { CommonModule } from '@angular/common'; - declare var Plotly: any; @Component({ selector: 'app-prediction-results', standalone: true, providers: [ClassPerformancePlotComponent, PredictFormComponent], - imports: [ - MatProgressSpinner, - CommonModule, - ], + imports: [MatProgressSpinner, CommonModule], templateUrl: './prediction-results.component.html', styleUrls: ['./prediction-results.component.scss'], }) @@ -33,10 +29,10 @@ export class PredictionResultsComponent { predictService = inject(PredictService); prediction$ = this.predictService.prediction$; predictionResource = this.predictService._prediction$; - typePredictPiePlot = viewChild.required>('typePredictPiePlot'); + typePredictPiePlot = + viewChild.required>('typePredictPiePlot'); refreshSignal$ = input(); - doneLoading = false - + doneLoading = false; predictionMeta$ = computed(() => this.prediction$()?.meta); predictionData$ = computed(() => this.prediction$()?.data); @@ -59,13 +55,10 @@ export class PredictionResultsComponent { loader: async (param) => { const data = param.request.data; if (data === undefined) return; - console.log('resource') - console.log(this.plotTypePredictPieResource.isLoading()) - console.log(this.plotTypePredictPieResource.value()) const plot_data = this.extractPredictions(data); return await this.plotPredictions(plot_data); }, - }) + }); async plotPredictions(plotlyData: PlotlyData): Promise { return Plotly.newPlot( @@ -76,11 +69,9 @@ export class PredictionResultsComponent { ); } - extractPredictions(responseJson: any): PlotlyData { const typeGroups: Map = new Map(); // group predictions by type - console.log(responseJson); responseJson.data.forEach( (entry: { typePrediction: string; subtypePrediction: string }) => { if (typeGroups.has(entry.typePrediction)) { @@ -109,7 +100,6 @@ export class PredictionResultsComponent { (d: { name: string }) => d.name == 'modules', ); const oneMeasure = classPerformanceData[0]; - console.log(oneMeasure); // create map to value const classToMeasure: Map = new Map(); for (let i = 0; i < oneMeasure.x.length; i++) { @@ -174,7 +164,12 @@ export class PredictionResultsComponent { responsive: true, }; const plot_data = { data: data, layout: layout, config: config }; - Plotly.newPlot(this.typePredictPiePlot().nativeElement, data, layout, config); + Plotly.newPlot( + this.typePredictPiePlot().nativeElement, + data, + layout, + config, + ); this.doneLoading = true; return plot_data; } @@ -206,6 +201,4 @@ export class PredictionResultsComponent { // // Additional content validation can be added here // return true; // } - - } diff --git a/src/app/services/backend.service.ts b/src/app/services/backend.service.ts index c23c3e7c..30f8a4de 100644 --- a/src/app/services/backend.service.ts +++ b/src/app/services/backend.service.ts @@ -1,5 +1,5 @@ -import {Injectable} from '@angular/core'; -import {HttpService} from "./http.service"; +import { Injectable } from '@angular/core'; +import { HttpService } from './http.service'; import { AlternativeSplicingEvent, BrowseQuery, @@ -18,6 +18,7 @@ import { GOTerm, GseaResult, Hallmark, + Network, NetworkResult, OverallCounts, PredictCancerType, @@ -36,27 +37,25 @@ import { TranscriptInteraction, TranscriptMiRNA, TranscriptNode, - WikiPathway -} from "../interfaces"; -import {API_BASE} from "../constants"; + WikiPathway, +} from '../interfaces'; +import { API_BASE } from '../constants'; interface Query { [key: string]: any; } @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class BackendService { - - constructor(private http: HttpService) { - } + constructor(private http: HttpService) {} async getDatasets(version: number, diseaseName?: string): Promise { const route = 'datasets'; const query: Query = { - sponge_db_version: version + sponge_db_version: version, }; if (diseaseName) { @@ -68,21 +67,56 @@ export class BackendService { getDatasetInfo(version: number, diseaseName: string): Promise { const route = 'dataset/spongeRunInformation'; - const query: Query = {sponge_db_version: version, disease_name: diseaseName}; + const query: Query = { + sponge_db_version: version, + disease_name: diseaseName, + }; return this.http.getRequest(this.getRequestURL(route, query)); } getOverallCounts(version: number): Promise { const route = 'getOverallCounts'; - const query: Query = {sponge_db_version: version}; - return this.http.getRequest(this.getRequestURL(route, query)); + const query: Query = { sponge_db_version: version }; + return this.http.getRequest( + this.getRequestURL(route, query), + ); + } + + getNetwork(version: number, query: BrowseQuery): Promise { + const level = query.level; + const route = + level == 'gene' + ? 'ceRNAInteraction/getGeneNetwork' + : 'ceRNAInteraction/getTranscriptNetwork'; + + const _query: Query = { + sponge_db_version: version, + dataset_ID: query.dataset.dataset_ID, + minBetweenness: query.minBetweenness, + minNodeDegree: query.minDegree, + minEigenvector: query.minEigen, + maxPValue: query.maxPValue, + minMscor: query.minMscor, + edgeSorting: query.interactionSorting, + nodeSorting: query.geneSorting, + maxNodes: query.maxNodes, + maxEdges: query.maxInteractions, + }; + + return this.http.getRequest(this.getRequestURL(route, _query)); } - getNodes(version: number, query: BrowseQuery): Promise<(GeneNode | TranscriptNode)[]> { + getNodes( + version: number, + query: BrowseQuery, + ): Promise<(GeneNode | TranscriptNode)[]> { const level = query.level; const route = level == 'gene' ? 'findceRNA' : 'findceRNATranscripts'; - if (version != query.dataset.sponge_db_version || (version < 2 && level == 'transcript')) { + if ( + version != query.dataset.sponge_db_version || + (version < 2 && level == 'transcript') + ) { return Promise.resolve([]); } @@ -95,16 +129,27 @@ export class BackendService { minEigenvector: query.minEigen, sorting: query.geneSorting, descending: true, - limit: query.maxNodes + limit: query.maxNodes, }; - return this.http.getRequest<(GeneNode | TranscriptNode)[]>(this.getRequestURL(route, internalQuery)); + return this.http.getRequest<(GeneNode | TranscriptNode)[]>( + this.getRequestURL(route, internalQuery), + ); } - async getGeneInteractionsAll(version: number, disease: Dataset | undefined, maxPValue: number, ensgs: string[]): Promise { + async getGeneInteractionsAll( + version: number, + disease: Dataset | undefined, + maxPValue: number, + ensgs: string[], + ): Promise { const route = 'ceRNAInteraction/findAll'; - if (ensgs.length === 0 || !disease || version != disease.sponge_db_version) { + if ( + ensgs.length === 0 || + !disease || + version != disease.sponge_db_version + ) { return Promise.resolve([]); } @@ -113,21 +158,23 @@ export class BackendService { disease_name: disease.disease_name, dataset_ID: disease.dataset_ID, ensg_number: ensgs.join(','), - pValue: maxPValue - } + pValue: maxPValue, + }; - const results: GeneInteraction[] = [] + const results: GeneInteraction[] = []; const limit = 1000; let offset = 0; let data: GeneInteraction[]; do { - data = await this.http.getRequest(this.getRequestURL(route, { - ...query, - limit, - offset - })); + data = await this.http.getRequest( + this.getRequestURL(route, { + ...query, + limit, + offset, + }), + ); results.push(...data); offset += limit; } while (data.length === limit); @@ -135,8 +182,17 @@ export class BackendService { return results; } - getInteractionsSpecific(version: number, disease: Dataset, maxPValue: number, identifiers: string[], level: 'gene' | 'transcript'): Promise<(GeneInteraction | TranscriptInteraction)[]> { - const route = level == 'gene' ? 'ceRNAInteraction/findSpecific' : 'ceRNAInteraction/findSpecificTranscripts'; + getInteractionsSpecific( + version: number, + disease: Dataset, + maxPValue: number, + identifiers: string[], + level: 'gene' | 'transcript', + ): Promise<(GeneInteraction | TranscriptInteraction)[]> { + const route = + level == 'gene' + ? 'ceRNAInteraction/findSpecific' + : 'ceRNAInteraction/findSpecificTranscripts'; if (identifiers.length === 0) { return Promise.resolve([]); @@ -146,8 +202,8 @@ export class BackendService { sponge_db_version: version, disease_name: disease.disease_name, dataset_ID: disease.dataset_ID, - pValue: maxPValue - } + pValue: maxPValue, + }; if (level == 'gene') { query['ensg_number'] = identifiers.join(','); @@ -155,11 +211,19 @@ export class BackendService { query['enst_number'] = identifiers.join(','); } - return this.http.getRequest<(GeneInteraction | TranscriptInteraction)[]>(this.getRequestURL(route, query)); + return this.http.getRequest<(GeneInteraction | TranscriptInteraction)[]>( + this.getRequestURL(route, query), + ); } - async getExpression(version: number, identifiers: string[], disease: Dataset, level: 'gene' | 'transcript'): Promise<(GeneExpression | TranscriptExpression)[]> { - const route = level == 'gene' ? 'exprValue/getceRNA' : 'exprValue/getTranscriptExpr'; + async getExpression( + version: number, + identifiers: string[], + disease: Dataset, + level: 'gene' | 'transcript', + ): Promise<(GeneExpression | TranscriptExpression)[]> { + const route = + level == 'gene' ? 'exprValue/getceRNA' : 'exprValue/getTranscriptExpr'; if (identifiers.length === 0) { return Promise.resolve([]); @@ -169,7 +233,7 @@ export class BackendService { sponge_db_version: version, dataset_ID: disease.dataset_ID, disease_name: disease.disease_name, - } + }; if (level == 'gene') { query['ensg_number'] = identifiers.join(','); @@ -177,22 +241,29 @@ export class BackendService { query['enst_number'] = identifiers.join(','); } - return await this.http.getRequest<(GeneExpression | TranscriptExpression)[]>(this.getRequestURL(route, query)); + return await this.http.getRequest< + (GeneExpression | TranscriptExpression)[] + >(this.getRequestURL(route, query)); } - getSurvivalRates(version: number, ensgs: string[], disease: Dataset): Promise { + getSurvivalRates( + version: number, + ensgs: string[], + disease: Dataset, + ): Promise { const route = 'survivalAnalysis/getRates'; const query: Query = { sponge_db_version: version, disease_name: disease.disease_name, dataset_ID: disease.dataset_ID, - ensg_number: ensgs.join(',') - } + ensg_number: ensgs.join(','), + }; - return this.http.getRequest(this.getRequestURL(route, query)); + return this.http.getRequest( + this.getRequestURL(route, query), + ); } - getAutocomplete(version: number, query: string): Promise { if (query.length < 2) { return Promise.resolve([]); @@ -201,8 +272,8 @@ export class BackendService { const route = 'stringSearch'; const queryObj: Query = { sponge_db_version: version, - searchString: query - } + searchString: query, + }; try { return this.http.getRequest(this.getRequestURL(route, queryObj)); } catch (e) { @@ -214,17 +285,19 @@ export class BackendService { const route = 'getTranscriptInformation'; const query: Query = { sponge_db_version: version, - enst_number: enst - } - return this.http.getRequest(this.getRequestURL(route, query)); + enst_number: enst, + }; + return this.http.getRequest( + this.getRequestURL(route, query), + ); } getGeneInfo(version: number, ensg: string): Promise { const route = 'getGeneInformation'; const query: Query = { sponge_db_version: version, - ensg_number: ensg - } + ensg_number: ensg, + }; return this.http.getRequest(this.getRequestURL(route, query)); } @@ -237,12 +310,15 @@ export class BackendService { const query: Query = { sponge_db_version: version, - gene_symbol: symbol - } + gene_symbol: symbol, + }; return this.http.getRequest(this.getRequestURL(route, query)); } - async getHallmark(version: number, symbol: string | undefined): Promise { + async getHallmark( + version: number, + symbol: string | undefined, + ): Promise { const route = 'getHallmark'; if (!symbol) { @@ -251,16 +327,21 @@ export class BackendService { const query: Query = { sponge_db_version: version, - gene_symbol: symbol - } - const hallmarks = await this.http.getRequest(this.getRequestURL(route, query)); + gene_symbol: symbol, + }; + const hallmarks = await this.http.getRequest( + this.getRequestURL(route, query), + ); if (!Array.isArray(hallmarks)) { return []; } return hallmarks; } - async getWikiPathways(version: number, symbol: string | undefined): Promise { + async getWikiPathways( + version: number, + symbol: string | undefined, + ): Promise { const route = 'getWikipathway'; if (!symbol) { @@ -269,9 +350,11 @@ export class BackendService { const query: Query = { sponge_db_version: version, - gene_symbol: symbol - } - const wikipathways = await this.http.getRequest(this.getRequestURL(route, query)); + gene_symbol: symbol, + }; + const wikipathways = await this.http.getRequest( + this.getRequestURL(route, query), + ); if (!Array.isArray(wikipathways)) { return []; @@ -279,7 +362,11 @@ export class BackendService { return wikipathways; } - getGeneCount(version: number, ensgs: string[], onlySignificant: boolean): Promise { + getGeneCount( + version: number, + ensgs: string[], + onlySignificant: boolean, + ): Promise { if (ensgs.length === 0) { return Promise.resolve([]); } @@ -287,7 +374,7 @@ export class BackendService { const query: Query = { sponge_db_version: version, ensg_number: ensgs.join(','), - } + }; if (onlySignificant) { query['minCountSign'] = 1; } @@ -298,41 +385,64 @@ export class BackendService { const route = 'getGeneTranscripts'; const query: Query = { sponge_db_version: version, - ensg_number: ensg - } - return this.http.getRequest(this.getRequestURL(route, query)); - } - - async getMiRNAs(version: number, disease: Dataset, identifiers: [string, string], level: 'gene' | 'transcript') { - const route = level == 'gene' ? 'miRNAInteraction/findceRNA' : 'miRNAInteraction/findceRNATranscripts'; + ensg_number: ensg, + }; + return ( + await this.http.getRequest(this.getRequestURL(route, query)) + )[0]; + } + + async getMiRNAs( + version: number, + disease: Dataset, + identifiers: [string, string], + level: 'gene' | 'transcript', + ) { + const route = + level == 'gene' + ? 'miRNAInteraction/findceRNA' + : 'miRNAInteraction/findceRNATranscripts'; const query: Query = { sponge_db_version: version, dataset_ID: disease.dataset_ID, - between: true - } + between: true, + }; if (level == 'gene') { query['ensg_number'] = identifiers.join(','); } else { query['enst_number'] = identifiers.join(','); } - return this.http.getRequest(this.getRequestURL(route, query)); + return this.http.getRequest( + this.getRequestURL(route, query), + ); } - async getAlternativeSplicingEvents(ensts: string[]): Promise { + async getAlternativeSplicingEvents( + ensts: string[], + ): Promise { const route = 'alternativeSplicing/getTranscriptEvents'; const query: Query = { - enst_number: ensts.join(',') - } + enst_number: ensts.join(','), + }; - const resp = await this.http.getRequest(this.getRequestURL(route, query)); + const resp = await this.http.getRequest( + this.getRequestURL(route, query), + ); return 'detail' in resp ? [] : resp; } - getCeRNAInteractionsAll(disease: string, maxPValue: number, ensgs: string[], limit?: number, offset?: number): Promise { - let request = API_BASE + '/ceRNAInteraction/findAll?disease_name=' + disease; + getCeRNAInteractionsAll( + disease: string, + maxPValue: number, + ensgs: string[], + limit?: number, + offset?: number, + ): Promise { + let request = + API_BASE + '/ceRNAInteraction/findAll?disease_name=' + disease; request += `&ensg_number=${ensgs.join(',')}`; request += `&pValue=${maxPValue}`; @@ -346,15 +456,19 @@ export class BackendService { return this.http.getRequest(request); } - getCeRNAInteractionsSpecific(disease: string, maxPValue: number, ensgs: string[]): Promise { - let request = API_BASE + '/ceRNAInteraction/findSpecific?disease_name=' + disease; + getCeRNAInteractionsSpecific( + disease: string, + maxPValue: number, + ensgs: string[], + ): Promise { + let request = + API_BASE + '/ceRNAInteraction/findSpecific?disease_name=' + disease; request += `&ensg_number=${ensgs.join(',')}`; request += `&pValue=${maxPValue}`; return this.http.getRequest(request); } - // getCeRNA(query: CeRNAQuery): Promise { // const sponge_db_version = this.versionService.getCurrentVersion(); // let request = BackendService.API_BASE + '/findceRNA?disease_name=' + query.disease.disease_name + `?sponge_db_version=${sponge_db_version}`; @@ -369,65 +483,114 @@ export class BackendService { // return this.http.getRequest(request); // } - getCeRNAExpression(ensgs: string[], diseaseName: string): Promise { + getCeRNAExpression( + ensgs: string[], + diseaseName: string, + ): Promise { let request = API_BASE + '/exprValue/getceRNA?disease_name=' + diseaseName; request += `&ensg_number=${ensgs.join(',')}`; return this.http.getRequest(request); } - getTranscriptExpression(ensts: string[], disease_name?: string): Promise { - let request = API_BASE + `/exprValue/getTranscript?disease_name=${disease_name}`; + getTranscriptExpression( + ensts: string[], + disease_name?: string, + ): Promise { + let request = + API_BASE + `/exprValue/getTranscript?disease_name=${disease_name}`; request += `&enst_number=${ensts.join(',')}`; return this.http.getRequest(request); } - async getSurvivalPValues(version: number, ensgs: string[], disease: Dataset): Promise { + async getSurvivalPValues( + version: number, + ensgs: string[], + disease: Dataset, + ): Promise { const route = 'survivalAnalysis/getPValues'; const query: Query = { sponge_db_version: version, disease_name: disease.disease_name, dataset_ID: disease.dataset_ID, - ensg_number: ensgs.join(',') - } - - return (await this.http.getRequest(this.getRequestURL(route, query))) ?? []; - } + ensg_number: ensgs.join(','), + }; - getSpongEffectsRuns(version: number, dataset_ID?: number, diseaseName?: string): Promise { - const request = `${API_BASE}/spongEffects/getSpongEffectsRuns?` - + (dataset_ID ? `?dataset_ID=${dataset_ID}` : '') - + (diseaseName ? `&disease_name=${diseaseName}` : '') - + `&sponge_db_version=${version}` + return ( + (await this.http.getRequest( + this.getRequestURL(route, query), + )) ?? [] + ); + } + + getSpongEffectsRuns( + version: number, + dataset_ID?: number, + diseaseName?: string, + ): Promise { + const request = + `${API_BASE}/spongEffects/getSpongEffectsRuns?` + + (dataset_ID ? `?dataset_ID=${dataset_ID}` : '') + + (diseaseName ? `&disease_name=${diseaseName}` : '') + + `&sponge_db_version=${version}`; return this.http.getRequest(request); } - getRunPerformance(version: number, diseaseName: string, level: string): Promise { - const request = API_BASE + '/spongEffects/getRunPerformance' + `?disease_name=${diseaseName}` + `&level=${level}` + `&sponge_db_version=${version}`; + getRunPerformance( + version: number, + diseaseName: string, + level: string, + ): Promise { + const request = + API_BASE + + '/spongEffects/getRunPerformance' + + `?disease_name=${diseaseName}` + + `&level=${level}` + + `&sponge_db_version=${version}`; return this.http.getRequest(request); } + // spongEffects services: -// spongEffects services: - - getRunClassPerformance(version: number, diseaseName: string, level: string): Promise { - const request = API_BASE + '/spongEffects/getRunClassPerformance' + `?disease_name=${diseaseName}` + `&level=${level}` + `&sponge_db_version=${version}`; + getRunClassPerformance( + version: number, + diseaseName: string, + level: string, + ): Promise { + const request = + API_BASE + + '/spongEffects/getRunClassPerformance' + + `?disease_name=${diseaseName}` + + `&level=${level}` + + `&sponge_db_version=${version}`; return this.http.getRequest(request); } - getEnrichmentScoreDistributions(version: number, diseaseName: string, level: string): Promise { + getEnrichmentScoreDistributions( + version: number, + diseaseName: string, + level: string, + ): Promise { const request = `${API_BASE}/spongEffects/enrichmentScoreDistributions?disease_name=${diseaseName}&level=${level}&sponge_db_version=${version}`; return this.http.getRequest(request); } - getSpongEffectsGeneModules(version: number, diseaseName: string): Promise { + getSpongEffectsGeneModules( + version: number, + diseaseName: string, + ): Promise { const request = `${API_BASE}/spongEffects/getSpongEffectsGeneModules?disease_name=${diseaseName}&sponge_db_version=${version}`; return this.http.getRequest(request); } - getSpongEffectsGeneModuleMembers(version: number, diseaseName: string, ensgNumber?: string, geneSymbol?: string): Promise { + getSpongEffectsGeneModuleMembers( + version: number, + diseaseName: string, + ensgNumber?: string, + geneSymbol?: string, + ): Promise { let request = `${API_BASE}/spongEffects/getSpongEffectsGeneModuleMembers?disease_name=${diseaseName}&sponge_db_version=${version}`; if (ensgNumber) { request += `&ensg_number=${ensgNumber}`; @@ -438,12 +601,19 @@ export class BackendService { return this.http.getRequest(request); } - getSpongEffectsTranscriptModules(version: number, diseaseName: string): Promise { + getSpongEffectsTranscriptModules( + version: number, + diseaseName: string, + ): Promise { const request = `${API_BASE}/spongEffects/getSpongEffectsTranscriptModules?disease_name=${diseaseName}&sponge_db_version=${version}`; return this.http.getRequest(request); } - getSpongEffectsTranscriptModuleMembers(version: number, diseaseName: string, enstNumber?: string): Promise { + getSpongEffectsTranscriptModuleMembers( + version: number, + diseaseName: string, + enstNumber?: string, + ): Promise { let request = `${API_BASE}/spongEffects/getSpongEffectsTranscriptModuleMembers?disease_name=${diseaseName}&sponge_db_version=${version}`; if (enstNumber) { request += `&enst_number=${enstNumber}`; @@ -451,7 +621,18 @@ export class BackendService { return this.http.getRequest(request); } - predictCancerType(version: number, file: Blob, subtypes: boolean, log: boolean, mscor: number, fdr: number, minSize: number, maxSize: number, minExpr: number, method: string): Promise { + predictCancerType( + version: number, + file: Blob, + subtypes: boolean, + log: boolean, + mscor: number, + fdr: number, + minSize: number, + maxSize: number, + minExpr: number, + method: string, + ): Promise { const formData = new FormData(); formData.append('file', file); formData.append('subtypes', subtypes.toString()); @@ -470,13 +651,19 @@ export class BackendService { const route = 'comparison'; const query: Query = { - sponge_db_version: version - } + sponge_db_version: version, + }; return this.http.getRequest(this.getRequestURL(route, query)); } - async getGeneSets(version: number, disease1: Dataset | undefined, condition1: string, disease2: Dataset | undefined, condition2: string) { + async getGeneSets( + version: number, + disease1: Dataset | undefined, + condition1: string, + disease2: Dataset | undefined, + condition2: string, + ) { const route = 'gseaSets'; if (!disease1 || !disease2) { @@ -489,13 +676,18 @@ export class BackendService { dataset_ID_2: disease2.dataset_ID, condition_1: condition1, condition_2: condition2, - } + }; - const res = await this.http.getRequest<{ gene_set: string }[]>(this.getRequestURL(route, query)); - return res.map(e => e.gene_set).sort(); + const res = await this.http.getRequest<{ gene_set: string }[]>( + this.getRequestURL(route, query), + ); + return res.map((e) => e.gene_set).sort(); } - async getNetworkResults(version: number, level: 'gene' | 'transcript' | undefined) { + async getNetworkResults( + version: number, + level: 'gene' | 'transcript' | undefined, + ) { const route = 'networkResults'; if (!level || version < 2) { @@ -504,10 +696,12 @@ export class BackendService { const query: Query = { sponge_db_version: version, - level - } + level, + }; - const resp = await this.http.getRequest(this.getRequestURL(route, query)); + const resp = await this.http.getRequest( + this.getRequestURL(route, query), + ); return 'type' in resp ? resp : undefined; } @@ -516,13 +710,20 @@ export class BackendService { const query: Query = { alternative_splicing_event_transcripts_ID: asEventID, - enst_number: enst - } + enst_number: enst, + }; return this.http.getRequest(this.getRequestURL(route, query)); } - async getGSEAterms(version: number, disease1: Dataset | undefined, condition1: string, disease2: Dataset | undefined, condition2: string, geneSet: string | undefined) { + async getGSEAterms( + version: number, + disease1: Dataset | undefined, + condition1: string, + disease2: Dataset | undefined, + condition2: string, + geneSet: string | undefined, + ) { const route = 'gseaTerms'; if (!disease1 || !disease2 || !geneSet) { @@ -534,14 +735,23 @@ export class BackendService { dataset_ID_2: disease2.dataset_ID, condition_1: condition1, condition_2: condition2, - gene_set: geneSet - } + gene_set: geneSet, + }; - const res = await this.http.getRequest<{ term: string }[]>(this.getRequestURL(route, query)); - return res.map(e => e.term).sort(); + const res = await this.http.getRequest<{ term: string }[]>( + this.getRequestURL(route, query), + ); + return res.map((e) => e.term).sort(); } - getGSEAresults(version: number, disease1: Dataset | undefined, condition1: string, disease2: Dataset | undefined, condition2: string, geneSet: string | undefined) { + getGSEAresults( + version: number, + disease1: Dataset | undefined, + condition1: string, + disease2: Dataset | undefined, + condition2: string, + geneSet: string | undefined, + ) { const route = 'gseaResults'; if (!disease1 || !disease2 || !geneSet) { @@ -553,14 +763,16 @@ export class BackendService { dataset_ID_2: disease2.dataset_ID, condition_1: condition1, condition_2: condition2, - gene_set: geneSet - } + gene_set: geneSet, + }; return this.http.getRequest(this.getRequestURL(route, query)); } private stringify(query: Query): string { - return Object.keys(query).map(key => key + '=' + query[key]).join('&'); + return Object.keys(query) + .map((key) => key + '=' + query[key]) + .join('&'); } private getRequestURL(route: string, query: Query): string { diff --git a/src/app/services/browse.service.ts b/src/app/services/browse.service.ts index 5762405a..f4b7ac38 100644 --- a/src/app/services/browse.service.ts +++ b/src/app/services/browse.service.ts @@ -1,26 +1,33 @@ -import {computed, effect, Injectable, resource, ResourceRef, Signal, signal} from '@angular/core'; +import { + computed, + effect, + Injectable, + resource, + ResourceRef, + Signal, + signal, +} from '@angular/core'; import { BrowseQuery, Dataset, Gene, GeneInteraction, GeneNode, - InteractionSorting, NetworkResult, Transcript, TranscriptInteraction, - TranscriptNode -} from "../interfaces"; -import {BackendService} from "./backend.service"; -import Graph from "graphology"; -import {VersionsService} from "./versions.service"; -import ForceSupervisor from "graphology-layout-force/worker"; -import {isEqual} from "lodash"; + TranscriptNode, +} from '../interfaces'; +import { BackendService } from './backend.service'; +import Graph from 'graphology'; +import { VersionsService } from './versions.service'; +import ForceSupervisor from 'graphology-layout-force/worker'; +import { isEqual } from 'lodash'; export enum State { Default, Hover, - Active + Active, } export interface EntityState { @@ -29,35 +36,40 @@ export interface EntityState { } interface NetworkData { - nodes: (GeneNode | TranscriptNode)[], - inverseNodes: (GeneNode | TranscriptNode)[], - interactions: (GeneInteraction | TranscriptInteraction)[], - disease: Dataset | undefined + nodes: (GeneNode | TranscriptNode)[]; + inverseNodes: (GeneNode | TranscriptNode)[]; + edges: (GeneInteraction | TranscriptInteraction)[]; + disease: Dataset | undefined; } @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class BrowseService { readonly physicsEnabled$ = signal(true); - readonly graph$ = computed(() => this.createGraph(this.nodes$(), this.interactions$(), this.inverseNodes$())); - layout = computed(() => new ForceSupervisor(this.graph$(), { - isNodeFixed: (_, attr) => attr['highlighted'], - settings: { - repulsion: 0.001, - attraction: 0.01, - gravity: 0.001, - } - })) + readonly graph$ = computed(() => + this.createGraph(this.nodes$(), this.interactions$(), this.inverseNodes$()), + ); + layout = computed( + () => + new ForceSupervisor(this.graph$(), { + isNodeFixed: (_, attr) => attr['highlighted'], + settings: { + repulsion: 0.001, + attraction: 0.01, + gravity: 0.001, + }, + }), + ); private readonly _query$ = signal(undefined); private readonly _version$: Signal; private readonly _comparisons$ = resource({ request: computed(() => { - return this._version$() + return this._version$(); }), loader: (param) => { return this.backend.getComparisons(param.request); - } + }, }); private readonly _currentData$: ResourceRef; readonly disease$ = computed(() => this._currentData$.value()?.disease); @@ -66,52 +78,85 @@ export class BrowseService { const comparisons = this._comparisons$.value(); if (disease === undefined || comparisons === undefined) return []; return comparisons - .filter(c => c.gene_transcript == this.level$()) - .filter(c => c.dataset_1.dataset_ID === disease.dataset_ID || c.dataset_2.dataset_ID === disease.dataset_ID); - }) + .filter((c) => c.gene_transcript == this.level$()) + .filter( + (c) => + c.dataset_1.dataset_ID === disease.dataset_ID || + c.dataset_2.dataset_ID === disease.dataset_ID, + ); + }); readonly nodes$ = computed(() => this._currentData$.value()?.nodes || []); - readonly inverseNodes$ = computed(() => this._currentData$.value()?.inverseNodes || []); - readonly interactions$ = computed(() => this._currentData$.value()?.interactions || []); + readonly inverseNodes$ = computed( + () => this._currentData$.value()?.inverseNodes || [], + ); + readonly interactions$ = computed( + () => this._currentData$.value()?.edges || [], + ); private readonly _nodeStates$ = signal>({}); activeNodes$ = computed(() => { - const activeNodeIDs = Object.entries(this._nodeStates$()).filter(([_, state]) => state[State.Active]).map(([node, _]) => node); - return this.nodes$().filter(node => activeNodeIDs.includes(BrowseService.getNodeID(node))); + const activeNodeIDs = Object.entries(this._nodeStates$()) + .filter(([_, state]) => state[State.Active]) + .map(([node, _]) => node); + return this.nodes$().filter((node) => + activeNodeIDs.includes(BrowseService.getNodeID(node)), + ); }); private readonly _edgeStates$ = signal>({}); activeInteractions$ = computed(() => { - const activeEdgeIDs = Object.entries(this._edgeStates$()).filter(([_, state]) => state[State.Active]).map(([edge, _]) => edge); - return activeEdgeIDs.map(edgeID => this.getInteractionForEdge(edgeID, this.interactions$(), this.graph$())).flat().filter(interaction => interaction !== undefined); - }) + const activeEdgeIDs = Object.entries(this._edgeStates$()) + .filter(([_, state]) => state[State.Active]) + .map(([edge, _]) => edge); + return activeEdgeIDs + .map((edgeID) => + this.getInteractionForEdge(edgeID, this.interactions$(), this.graph$()), + ) + .flat() + .filter((interaction) => interaction !== undefined); + }); private readonly _networkResults$ = resource({ request: computed(() => { return { version: this._version$(), - level: this.level$() - } + level: this.level$(), + }; }), loader: (param) => { - return this.backend.getNetworkResults(param.request.version, param.request.level); - } + return this.backend.getNetworkResults( + param.request.version, + param.request.level, + ); + }, }); - constructor(private backend: BackendService, versionsService: VersionsService) { + constructor( + private backend: BackendService, + versionsService: VersionsService, + ) { this._version$ = versionsService.versionReadOnly(); this._currentData$ = resource({ request: computed(() => { return { version: this._version$(), - config: this._query$() - } + config: this._query$(), + }; }), - loader: (param) => this.fetchData(param.request.version, param.request.config), - }) + loader: (param) => + this.fetchData(param.request.version, param.request.config), + }); effect(() => { const graph = this.graph$(); - const initialState: EntityState = {[State.Hover]: false, [State.Active]: false}; - this._nodeStates$.set(Object.fromEntries(graph.nodes().map(node => [node, initialState]))); - this._edgeStates$.set(Object.fromEntries(graph.edges().map(edge => [edge, initialState]))); + const initialState: EntityState = { + [State.Hover]: false, + [State.Active]: false, + }; + this._nodeStates$.set( + Object.fromEntries(graph.nodes().map((node) => [node, initialState])), + ); + this._edgeStates$.set( + Object.fromEntries(graph.edges().map((edge) => [edge, initialState])), + ); }); effect(() => { @@ -157,29 +202,39 @@ export class BrowseService { return BrowseService.getFullName(BrowseService.getNodeObject(node)); } - public static getNodeObject(node: GeneNode | TranscriptNode): Gene | Transcript { + public static getNodeObject( + node: GeneNode | TranscriptNode, + ): Gene | Transcript { return 'gene' in node ? node.gene : node.transcript; } - public static getInteractionIDs(interaction: GeneInteraction | TranscriptInteraction): [string, string] { + public static getInteractionIDs( + interaction: GeneInteraction | TranscriptInteraction, + ): [string, string] { const objects = BrowseService.getInteractionObjects(interaction); return objects.map(BrowseService.getID) as [string, string]; } - public static getInteractionFullNames(interaction: GeneInteraction | TranscriptInteraction): [string, string] { + public static getInteractionFullNames( + interaction: GeneInteraction | TranscriptInteraction, + ): [string, string] { const objects = BrowseService.getInteractionObjects(interaction); return objects.map(BrowseService.getFullName) as [string, string]; } - public static getInteractionGeneNames(interaction: GeneInteraction | TranscriptInteraction): [string, string] { + public static getInteractionGeneNames( + interaction: GeneInteraction | TranscriptInteraction, + ): [string, string] { const objects = BrowseService.getInteractionObjects(interaction); return objects.map(BrowseService.getGeneName) as [string, string]; } - public static getInteractionObjects(interaction: GeneInteraction | TranscriptInteraction): [Gene, Gene] | [Transcript, Transcript] { - return 'gene1' in interaction ? - [interaction.gene1, interaction.gene2] : - [interaction.transcript_1, interaction.transcript_2]; + public static getInteractionObjects( + interaction: GeneInteraction | TranscriptInteraction, + ): [Gene, Gene] | [Transcript, Transcript] { + return 'gene1' in interaction + ? [interaction.gene1, interaction.gene2] + : [interaction.transcript_1, interaction.transcript_2]; } public static getFullName(node: Gene | Transcript): string { @@ -190,14 +245,18 @@ export class BrowseService { } } - public static getGProfilerUrlForNodes(nodes: (GeneNode | TranscriptNode)[]): string { - const genes = nodes.map(node => { - if ('gene' in node) { - return node.gene; - } else { - return node.transcript.gene; - } - }).map(gene => gene.gene_symbol || gene.ensg_number); + public static getGProfilerUrlForNodes( + nodes: (GeneNode | TranscriptNode)[], + ): string { + const genes = nodes + .map((node) => { + if ('gene' in node) { + return node.gene; + } else { + return node.transcript.gene; + } + }) + .map((gene) => gene.gene_symbol || gene.ensg_number); return `https://biit.cs.ut.ee/gprofiler/gost?organism=hsapiens&query=${genes.join(' ')}`; } @@ -207,7 +266,9 @@ export class BrowseService { } private static getGeneName(node: Gene | Transcript): string { - return 'ensg_number' in node ? node.gene_symbol || node.ensg_number : node.gene.gene_symbol || node.gene.ensg_number; + return 'ensg_number' in node + ? node.gene_symbol || node.ensg_number + : node.gene.gene_symbol || node.gene.ensg_number; } runQuery(query: BrowseQuery) { @@ -218,112 +279,140 @@ export class BrowseService { return computed(() => this._query$()?.dataset?.download_url); } - async fetchData(version: number, config: BrowseQuery | undefined): Promise { + getEdgesForNode(node: Gene | Transcript) { + const nodeId = BrowseService.getID(node); + return computed(() => { + return this.interactions$().filter((interaction) => { + return BrowseService.getInteractionIDs(interaction).some( + (interactionID) => interactionID == nodeId, + ); + }); + }); + } + + async fetchData( + version: number, + config: BrowseQuery | undefined, + ): Promise { if (config === undefined) { return { nodes: [], inverseNodes: [], - interactions: [], - disease: undefined - } + edges: [], + disease: undefined, + }; } const inverseConfig = { ...config, - level: config.level === 'gene' ? 'transcript' : 'gene' as 'gene' | 'transcript' - } - - const inverseNodes$ = this.backend.getNodes(version, inverseConfig); - let nodes = await this.backend.getNodes(version, config); - // Get gene IDs or transcript IDs respectively - const identifiers = nodes.map(node => 'gene' in node ? node.gene.ensg_number : node.transcript.enst_number); - let interactions = - await this.backend.getInteractionsSpecific(version, config.dataset, config.maxPValue, identifiers, config.level); + level: + config.level === 'gene' + ? 'transcript' + : ('gene' as 'gene' | 'transcript'), + }; + + const inverseNodes$ = this.backend + .getNetwork(version, inverseConfig) + .then((network) => network.nodes); + let { nodes, edges } = await this.backend.getNetwork(version, config); const inverseNodes = await inverseNodes$; - interactions = interactions.filter(interaction => interaction.mscor >= config.minMScore && interaction.p_value <= config.maxPValue); - interactions = interactions.sort((a, b) => { - switch (config.interactionSorting) { - case InteractionSorting.pAdj: - return a.p_value - b.p_value; - case InteractionSorting.mScor: - return a.mscor - b.mscor; - case InteractionSorting.Correlation: - return a.correlation - b.correlation; - } - }); - interactions = interactions.slice(0, config.maxInteractions); - if (!config.showOrphans) { - const interactionNodes = interactions - .map(interaction => BrowseService.getInteractionIDs(interaction)).flat(); - nodes = nodes.filter(node => { + const interactionNodes = edges + .map((interaction) => BrowseService.getInteractionIDs(interaction)) + .flat(); + nodes = nodes.filter((node) => { const nodeObject = BrowseService.getNodeID(node); - return interactionNodes.some(interactionObject => isEqual(interactionObject, nodeObject)); + return interactionNodes.some((interactionObject) => + isEqual(interactionObject, nodeObject), + ); }); } return { nodes, inverseNodes, - interactions, - disease: config.dataset - } + edges, + disease: config.dataset, + }; } - toggleState(id: string, entityType: "node" | "edge", state: State.Active | State.Hover) { - const states = entityType === "node" ? this._nodeStates$ : this._edgeStates$; - states.update(entityStates => { + toggleState( + id: string, + entityType: 'node' | 'edge', + state: State.Active | State.Hover, + ) { + const states = + entityType === 'node' ? this._nodeStates$ : this._edgeStates$; + states.update((entityStates) => { return { ...entityStates, [id]: { ...entityStates[id], - [state]: !entityStates[id][state] - } - } + [state]: !entityStates[id][state], + }, + }; }); } - setState(id: string, entityType: "node" | "edge", state: State, value: boolean) { - const states = entityType === "node" ? this._nodeStates$ : this._edgeStates$; - states.update(entityStates => { + setState( + id: string, + entityType: 'node' | 'edge', + state: State, + value: boolean, + ) { + const states = + entityType === 'node' ? this._nodeStates$ : this._edgeStates$; + states.update((entityStates) => { return { ...entityStates, [id]: { ...entityStates[id], - [state]: value - } - } + [state]: value, + }, + }; }); } - getInteractionForEdge(edgeID: string, interactions: (GeneInteraction | TranscriptInteraction)[], graph: Graph): (GeneInteraction | TranscriptInteraction)[] { + getInteractionForEdge( + edgeID: string, + interactions: (GeneInteraction | TranscriptInteraction)[], + graph: Graph, + ): (GeneInteraction | TranscriptInteraction)[] { const source = graph.source(edgeID); const target = graph.target(edgeID); - return interactions.filter(interaction => { + return interactions.filter((interaction) => { const ids = BrowseService.getInteractionIDs(interaction); - return (ids[0] === source && ids[1] === target) || - (ids[0] === target && ids[1] === source); + return ( + (ids[0] === source && ids[1] === target) || + (ids[0] === target && ids[1] === source) + ); }); } - private createGraph(nodes: (GeneNode | TranscriptNode)[], interactions: (GeneInteraction | TranscriptInteraction)[], inverseNodes: (GeneNode | TranscriptNode)[]): Graph { + private createGraph( + nodes: (GeneNode | TranscriptNode)[], + interactions: (GeneInteraction | TranscriptInteraction)[], + inverseNodes: (GeneNode | TranscriptNode)[], + ): Graph { const graph = new Graph(); - nodes.forEach(node => { + nodes.forEach((node) => { const gene = BrowseService.getNodeGeneName(node); - const hasInverse = inverseNodes.some(inverseNode => BrowseService.getNodeGeneName(inverseNode) === gene); + const hasInverse = inverseNodes.some( + (inverseNode) => BrowseService.getNodeGeneName(inverseNode) === gene, + ); graph.addNode(BrowseService.getNodeID(node), { label: BrowseService.getNodeFullName(node), x: Math.random(), // Coordinates will be overridden by the layout algorithm y: Math.random(), size: Math.log(node.node_degree), forceLabel: true, - type: hasInverse ? 'circle' : 'square' + type: hasInverse ? 'circle' : 'square', }); }); - interactions.forEach(interaction => { + interactions.forEach((interaction) => { const ids = BrowseService.getInteractionIDs(interaction); if (graph.hasEdge(ids[0], ids[1])) { return; diff --git a/src/index.html b/src/index.html index b03df550..74817ad9 100644 --- a/src/index.html +++ b/src/index.html @@ -11,6 +11,7 @@ +