Skip to content

Commit 83da4e4

Browse files
authored
Merge pull request #78 from daisybio/dev
Add latest dev features
2 parents 22e9bbe + e2295be commit 83da4e4

24 files changed

+1221
-745
lines changed

package-lock.json

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@ngx-dropzone/cdk": "^19.0.0",
2727
"@ngx-dropzone/material": "^19.0.0",
2828
"@sigma/node-square": "^3.0.0",
29+
"@visa-ge/ng-igv": "^0.0.9",
2930
"file-saver": "^2.0.5",
3031
"graphology": "^0.25.4",
3132
"graphology-layout-force": "^0.2.4",

src/app/components/gene-modal/gene-modal.component.html

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<h1 mat-dialog-title>{{ gene.gene_symbol || gene.ensg_number }}</h1>
22
<mat-dialog-content>
3-
<mat-tab-group>
3+
<mat-tab-group [(selectedIndex)]="activeTab$" preserveContent>
44
<mat-tab label="General information">
55
<mat-accordion>
66
<mat-expansion-panel expanded>
@@ -125,6 +125,10 @@ <h1 mat-dialog-title>{{ gene.gene_symbol || gene.ensg_number }}</h1>
125125

126126
</mat-tab>
127127
}
128+
<mat-tab label="Genome view">
129+
<igv [location]="this.location$()" [reference]="IGV_REFGENOME" [refresh]="activeTab$()"
130+
[tracks]="miRNAtracks$()"></igv>
131+
</mat-tab>
128132

129133
</mat-tab-group>
130134
</mat-dialog-content>
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
1-
import {AfterViewInit, Component, effect, inject, model, resource, ResourceRef, viewChild} from '@angular/core';
2-
import {Gene, GOTerm} from "../../interfaces";
3-
import {MAT_DIALOG_DATA, MatDialog, MatDialogModule} from "@angular/material/dialog";
4-
import {MatButtonModule} from "@angular/material/button";
5-
import {VersionsService} from "../../services/versions.service";
6-
import {BackendService} from "../../services/backend.service";
7-
import {MatTabsModule} from "@angular/material/tabs";
8-
import {MatExpansionModule} from "@angular/material/expansion";
9-
import {MatTableDataSource, MatTableModule} from "@angular/material/table";
10-
import {MatPaginator, MatPaginatorModule} from "@angular/material/paginator";
11-
import {MatFormFieldModule} from "@angular/material/form-field";
12-
import {MatInputModule} from "@angular/material/input";
13-
import {FormsModule} from "@angular/forms";
14-
import {MatChip, MatChipSet} from "@angular/material/chips";
15-
import {MatProgressSpinner} from "@angular/material/progress-spinner";
16-
import {TranscriptModalComponent} from "../transcript-modal/transcript-modal.component";
17-
import {AS_DESCRIPTIONS} from "../../constants";
18-
import {MatTooltip} from "@angular/material/tooltip";
1+
import {
2+
AfterViewInit,
3+
Component,
4+
computed,
5+
effect,
6+
inject,
7+
model,
8+
resource,
9+
ResourceRef,
10+
Signal,
11+
viewChild,
12+
} from '@angular/core';
13+
import { Gene, GOTerm } from '../../interfaces';
14+
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
15+
import { MatButtonModule } from '@angular/material/button';
16+
import { VersionsService } from '../../services/versions.service';
17+
import { BackendService } from '../../services/backend.service';
18+
import { MatTabsModule } from '@angular/material/tabs';
19+
import { MatExpansionModule } from '@angular/material/expansion';
20+
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
21+
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
22+
import { MatFormFieldModule } from '@angular/material/form-field';
23+
import { MatInputModule } from '@angular/material/input';
24+
import { FormsModule } from '@angular/forms';
25+
import { MatChip, MatChipSet } from '@angular/material/chips';
26+
import { MatProgressSpinner } from '@angular/material/progress-spinner';
27+
import { AS_DESCRIPTIONS, IGV_REFGENOME } from '../../constants';
28+
import { MatTooltip } from '@angular/material/tooltip';
29+
import { Igv, Location, Track } from '@visa-ge/ng-igv';
30+
import { ModalsService } from '../modals-service/modals.service';
31+
import { BrowseService } from '../../services/browse.service';
1932

2033
interface ASEntry {
2134
enst: string;
@@ -37,69 +50,151 @@ interface ASEntry {
3750
MatChipSet,
3851
MatChip,
3952
MatProgressSpinner,
40-
MatTooltip
53+
MatTooltip,
54+
Igv,
4155
],
4256
templateUrl: './gene-modal.component.html',
43-
styleUrl: './gene-modal.component.scss'
57+
styleUrl: './gene-modal.component.scss',
4458
})
4559
export class GeneModalComponent implements AfterViewInit {
4660
goPaginator = viewChild.required<MatPaginator>('goPaginator');
4761
asPaginator = viewChild.required<MatPaginator>('asPaginator');
48-
goColumns = ['symbol', 'description']
49-
asColumns = ['enst', 'events']
50-
goFilter = model<string>('')
51-
dialog = inject(MatDialog);
52-
readonly asDescriptions = AS_DESCRIPTIONS
62+
goColumns = ['symbol', 'description'];
63+
asColumns = ['enst', 'events'];
64+
goFilter = model<string>('');
65+
readonly asDescriptions = AS_DESCRIPTIONS;
66+
modalsService = inject(ModalsService);
5367

68+
browseService = inject(BrowseService);
69+
readonly disease$ = this.browseService.disease$;
5470
readonly gene = inject<Gene>(MAT_DIALOG_DATA);
5571
readonly versionsService = inject(VersionsService);
5672
readonly backend = inject(BackendService);
5773
readonly version$ = this.versionsService.versionReadOnly();
5874
readonly isOpeningTranscript = model<boolean>(false);
75+
readonly activeTab$ = model<number>(0);
5976

6077
goDatasource = new MatTableDataSource<GOTerm>();
6178
asDatasource = new MatTableDataSource<ASEntry>();
6279

80+
edges$ = this.browseService.getEdgesForNode(this.gene);
81+
miRNAs$ = resource({
82+
request: computed(() => {
83+
return {
84+
edges: this.edges$(),
85+
disease: this.disease$(),
86+
version: this.version$(),
87+
};
88+
}),
89+
loader: async (param) => {
90+
const edges = param.request.edges;
91+
const disease = param.request.disease;
92+
const version = param.request.version;
93+
if (!disease) {
94+
return Promise.resolve([]);
95+
}
96+
const miRNAs$ = edges.map((edge) =>
97+
this.backend
98+
.getMiRNAs(
99+
version,
100+
disease,
101+
BrowseService.getInteractionIDs(edge),
102+
'gene',
103+
)
104+
.then((res) => res.map((mirna) => mirna.mirna.hs_nr)),
105+
);
106+
return (await Promise.all(miRNAs$))
107+
.flat()
108+
.filter((miRNA, i, arr) => arr.indexOf(miRNA) === i);
109+
},
110+
});
111+
112+
miRNAtracks$ = computed((): Track[] => {
113+
const miRNAs = this.miRNAs$.value();
114+
if (!miRNAs) {
115+
return [];
116+
}
117+
return miRNAs.map((miRNA) => {
118+
return {
119+
name: miRNA,
120+
url: `https://exbio.wzw.tum.de/sponge-files/miRNA_bed_files/${miRNA}.bed.gz`,
121+
format: 'bed',
122+
type: 'annotation',
123+
height: 30,
124+
displayMode: 'SQUISHED',
125+
indexed: false,
126+
};
127+
});
128+
});
129+
63130
readonly geneInfo$ = resource({
64131
request: this.version$,
65-
loader: async (version) => this.backend.getGeneInfo(version.request, this.gene.ensg_number).then(info => info[0])
66-
})
132+
loader: async (version) =>
133+
this.backend
134+
.getGeneInfo(version.request, this.gene.ensg_number)
135+
.then((info) => info[0]),
136+
});
67137

68138
readonly goTerms$ = resource({
69139
request: this.version$,
70-
loader: async (version) => this.backend.getGOterms(version.request, this.gene.gene_symbol)
71-
})
140+
loader: async (version) =>
141+
this.backend.getGOterms(version.request, this.gene.gene_symbol),
142+
});
72143

73144
readonly hallmarks$ = resource({
74145
request: this.version$,
75-
loader: async (version) => this.backend.getHallmark(version.request, this.gene.gene_symbol)
76-
})
146+
loader: async (version) =>
147+
this.backend.getHallmark(version.request, this.gene.gene_symbol),
148+
});
77149

78150
readonly wikipathways$ = resource({
79151
request: this.version$,
80-
loader: async (version) => this.backend.getWikiPathways(version.request, this.gene.gene_symbol)
81-
})
152+
loader: async (version) =>
153+
this.backend.getWikiPathways(version.request, this.gene.gene_symbol),
154+
});
82155

83156
readonly transcripts$: ResourceRef<ASEntry[]> = resource({
84157
request: this.version$,
85158
loader: async (version) => {
86-
const transcripts = await this.backend.getGeneTranscripts(version.request, this.gene.ensg_number);
87-
const asEvents = await this.backend.getAlternativeSplicingEvents(transcripts);
159+
const transcripts = await this.backend.getGeneTranscripts(
160+
version.request,
161+
this.gene.ensg_number,
162+
);
163+
const asEvents =
164+
await this.backend.getAlternativeSplicingEvents(transcripts);
88165
const transcriptEvents = asEvents.reduce((acc, event) => {
89166
const enst = event.transcript.enst_number;
90167
if (!acc.has(enst)) {
91168
acc.set(enst, new Set<string>());
92169
}
93170
acc.get(enst)!.add(event.event_type);
94171
return acc;
95-
}, new Map<string, Set<string>>())
172+
}, new Map<string, Set<string>>());
96173

97-
return transcripts.map(t => ({
174+
return transcripts.map((t) => ({
98175
enst: t,
99-
events: Array.from(transcriptEvents.get(t) ?? [])
176+
events: Array.from(transcriptEvents.get(t) ?? []),
100177
}));
178+
},
179+
});
180+
181+
readonly location$: Signal<Location> = computed(() => {
182+
const geneInfo = this.geneInfo$.value();
183+
if (!geneInfo) {
184+
return {
185+
chr: 'all',
186+
};
187+
} else {
188+
return {
189+
chr: geneInfo.chromosome_name,
190+
range: {
191+
start: geneInfo.start_pos,
192+
end: geneInfo.end_pos,
193+
},
194+
};
101195
}
102-
})
196+
});
197+
protected readonly IGV_REFGENOME = IGV_REFGENOME;
103198

104199
constructor() {
105200
effect(() => {
@@ -112,7 +207,7 @@ export class GeneModalComponent implements AfterViewInit {
112207

113208
effect(() => {
114209
this.asDatasource.data = this.transcripts$.value() ?? [];
115-
})
210+
});
116211
}
117212

118213
ngAfterViewInit(): void {
@@ -123,10 +218,10 @@ export class GeneModalComponent implements AfterViewInit {
123218
async openTranscript(enst: string) {
124219
if (this.isOpeningTranscript()) return;
125220
this.isOpeningTranscript.set(true);
126-
const transcript = (await this.backend.getTranscriptInfo(this.version$(), enst))[0];
127-
this.dialog.open(TranscriptModalComponent, {
128-
data: transcript
129-
});
221+
const transcript = (
222+
await this.backend.getTranscriptInfo(this.version$(), enst)
223+
)[0];
224+
this.modalsService.openNodeDialog(transcript);
130225
this.isOpeningTranscript.set(false);
131226
}
132227
}

src/app/components/interactions-table/interactions-table.component.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
<ng-container matColumnDef="mscor">
4040
<th *matHeaderCellDef mat-header-cell mat-sort-header>
41-
<app-info title="MScor">SPONGE computes multiple miRNA sensitivity correlation values. Note that this is a
41+
<app-info title="mscor">SPONGE computes multiple miRNA sensitivity correlation values. Note that this is a
4242
generalization of sensitivity correlation as defined by <a href="https://www.ncbi.nlm.nih.gov/pubmed/25033876">Paci
4343
et al.</a>. These values capture the joint contribution
4444
of several miRNAs on the ceRNA regulation of two genes while accounting for their cross-correlation.
@@ -51,7 +51,7 @@
5151

5252
<ng-container matColumnDef="padj">
5353
<th *matHeaderCellDef mat-header-cell mat-sort-header>
54-
<app-info title="Adj. p-Value">SPONGE computes a null model to calculate empirical p-values for the ceRNA
54+
<app-info title="Adj. p-value">SPONGE computes a null model to calculate empirical p-values for the ceRNA
5555
interactions. We sampled 1,000,000 datasets to closely estimate the p-values. The interactions were then
5656
FDR-corrected and filtered with a p-value cut-off of 0.01.
5757
</app-info>

src/app/components/interactions-table/interactions-table.component.ts

+4-17
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,13 @@ import {
1818
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
1919
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
2020
import { MatSort, MatSortHeader } from '@angular/material/sort';
21-
import { GeneModalComponent } from '../gene-modal/gene-modal.component';
22-
import { MatDialog } from '@angular/material/dialog';
2321
import { MatButton } from '@angular/material/button';
2422
import { MatTooltip } from '@angular/material/tooltip';
2523
import { BrowseService } from '../../services/browse.service';
26-
import { TranscriptModalComponent } from '../transcript-modal/transcript-modal.component';
27-
import { InteractionModalComponent } from '../interaction-modal/interaction-modal.component';
2824
import { capitalize } from 'lodash';
2925
import { InfoComponent } from '../info/info.component';
3026
import katex from 'katex';
27+
import { ModalsService } from '../modals-service/modals.service';
3128

3229
@Component({
3330
selector: 'app-interactions-table',
@@ -46,9 +43,9 @@ import katex from 'katex';
4643
export class InteractionsTableComponent implements AfterViewInit {
4744
@ViewChild(MatPaginator) paginator!: MatPaginator;
4845
@ViewChild(MatSort) sort!: MatSort;
46+
modalsService = inject(ModalsService);
4947
level$ = input<'gene' | 'transcript'>();
5048
interactions$ = input<(GeneInteraction | TranscriptInteraction)[]>();
51-
readonly dialog = inject(MatDialog);
5249
mscorEquation$ = viewChild<ElementRef<HTMLSpanElement>>('mscorEquation');
5350
columns = ['name_1', 'name_2', 'mirna', 'correlation', 'mscor', 'padj'];
5451
dataSource$ = computed(() => {
@@ -98,20 +95,10 @@ export class InteractionsTableComponent implements AfterViewInit {
9895
}
9996

10097
openMiRNADialog(interaction: GeneInteraction | TranscriptInteraction) {
101-
this.dialog.open(InteractionModalComponent, {
102-
data: interaction,
103-
});
98+
this.modalsService.openMiRNADialog(interaction);
10499
}
105100

106101
openDialog(entity: Gene | Transcript) {
107-
if ('ensg_number' in entity) {
108-
this.dialog.open(GeneModalComponent, {
109-
data: entity,
110-
});
111-
} else {
112-
this.dialog.open(TranscriptModalComponent, {
113-
data: entity,
114-
});
115-
}
102+
this.modalsService.openNodeDialog(entity);
116103
}
117104
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { ModalsService } from './modals.service';
4+
5+
describe('ModalsServiceService', () => {
6+
let service: ModalsService;
7+
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({});
10+
service = TestBed.inject(ModalsService);
11+
});
12+
13+
it('should be created', () => {
14+
expect(service).toBeTruthy();
15+
});
16+
});

0 commit comments

Comments
 (0)