diff --git a/package-lock.json b/package-lock.json index df6e4b4acc7307becb13621059e917201d9b3dd3..28e8d6283c98ecadac74aa1ae05a2ca6faea002a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2693,6 +2693,11 @@ "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.8.1.tgz", "integrity": "sha512-Afi2zv4DKmNSYfmx55V+Mtnt8+WfR8Rs65kWArmzEuWP7vNr7dSAEDI+ORZlgOR1gueNZwpKaPdUi4ZiTNwgPA==" }, + "bulma-toast": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/bulma-toast/-/bulma-toast-1.5.4.tgz", + "integrity": "sha512-yLGeupj1WOCgOS6Gtik2eyJ2aA55nFwDyTeLSoTDdRRdWsWbBcMsna3j7ZATiQTZ4o5BI05X4z/qCKc9qMysUA==" + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", diff --git a/package.json b/package.json index 8197d632b3e4d92ffc8580f72a805d964877ddc7..fd54ac8d792e242958ea2635f4082de8c83b6452 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@fortawesome/free-solid-svg-icons": "^5.13.0", "@ng-select/ng-select": "^3.7.3", "bulma": "^0.8.1", + "bulma-toast": "^1.5.4", "html2canvas": "^1.0.0-rc.5", "python": "0.0.4", "python-shell": "^1.0.8", diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index f1e5a289d6138a1b5b4422cc64ac65071645e4f0..d85b8c6734eb64d9ef7aa52d9489a669a189fc3e 100644 --- a/src/app/analysis.service.ts +++ b/src/app/analysis.service.ts @@ -1,8 +1,9 @@ import {Injectable} from '@angular/core'; -import {Protein, Task} from './interfaces'; +import {QueryItem, Task} from './interfaces'; import {Subject} from 'rxjs'; import {HttpClient} from '@angular/common/http'; import {environment} from '../environments/environment'; +import {toast} from 'bulma-toast'; @Injectable({ providedIn: 'root' @@ -10,24 +11,32 @@ import {environment} from '../environments/environment'; export class AnalysisService { - private selectedProteins = new Map<string, Protein>(); - private selectSubject = new Subject<{ protein: Protein, selected: boolean }>(); + private selectedItems = new Map<string, QueryItem>(); + private selectSubject = new Subject<{ item: QueryItem, selected: boolean }>(); public tokens: string[] = []; + public finishedTokens: string[] = []; public tasks: Task[] = []; private intervalId: any; constructor(private http: HttpClient) { const tokens = localStorage.getItem('tokens'); + const finishedTokens = localStorage.getItem('finishedTokens'); + + if (tokens) { this.tokens = JSON.parse(tokens); } + if (finishedTokens) { + this.finishedTokens = JSON.parse(finishedTokens); + } this.startWatching(); } removeTask(token) { this.tokens = this.tokens.filter((item) => item !== token); + this.finishedTokens = this.finishedTokens.filter((item) => item !== token); this.tasks = this.tasks.filter((item) => item.token !== (token)); localStorage.setItem('tokens', JSON.stringify(this.tokens)); } @@ -44,51 +53,49 @@ export class AnalysisService { }); } - addProtein(protein: Protein) { - if (!this.inSelection(protein)) { - this.selectedProteins.set(`${protein.proteinAc}`, protein); - this.selectSubject.next({protein, selected: true}); + public addItem(item: QueryItem) { + if (!this.inSelection(item.name)) { + this.selectedItems.set(`${item.name}`, item); + this.selectSubject.next({item, selected: true}); } } resetSelection() { - const oldSelection = this.selectedProteins.values(); - for (const protein of oldSelection) { - this.removeProtein(protein); + const oldSelection = this.selectedItems.values(); + for (const item of oldSelection) { + this.removeItem(item.name); } } - inSelection(protein: Protein): boolean { - return this.selectedProteins.has(protein.proteinAc); + inSelection(itemName: string): boolean { + return this.selectedItems.has(itemName); } - idInSelection(id: string): boolean { - return this.selectedProteins.has(id); - } - - removeProtein(protein: Protein) { - if (this.selectedProteins.delete(`${protein.proteinAc}`)) { - this.selectSubject.next({protein, selected: false}); + removeItem(itemName: string) { + const item = this.selectedItems.get(itemName); + if (this.selectedItems.delete(itemName)) { + this.selectSubject.next({item, selected: false}); } } - getSelection(): Protein[] { - return Array.from(this.selectedProteins.values()); + getSelection(): QueryItem[] { + return Array.from(this.selectedItems.values()); } getCount(): number { - return this.selectedProteins.size; + return this.selectedItems.size; } - subscribe(cb: (protein: Protein, selected: boolean) => void) { + subscribe(cb: (item: QueryItem, selected: boolean) => void) { this.selectSubject.subscribe((event) => { - cb(event.protein, event.selected); + cb(event.item, event.selected); }); } - async startAnalysis(algorithm, parameters) { + async startAnalysis(algorithm, target: 'drug' | 'drug-target', parameters) { const resp = await this.http.post<any>(`${environment.backend}task/`, { algorithm, + target, parameters, }).toPromise(); this.tokens.push(resp.token); @@ -96,14 +103,59 @@ export class AnalysisService { this.startWatching(); } + showToast(task: Task, status: 'DONE' | 'FAILED') { + let toastMessage; + let toastType; + const startDate = new Date(task.info.startedAt); + const finishedDate = new Date(task.info.finishedAt); + if (status === 'DONE') { + toastMessage = `Computation finished succesfully. + \n- Algorithm: ${task.info.algorithm} + \n- Started At: ${startDate.getHours()}:${startDate.getMinutes()} + \n- Finished At: ${finishedDate.getHours()}:${finishedDate.getMinutes()}`; + toastType = 'is-success'; + } else if (status === 'FAILED') { + toastMessage = `Computation failed. + \n- Algorithm: ${task.info.algorithm} + \n- Started At: ${startDate.getHours()}:${startDate.getMinutes()}`; + toastType = 'is-danger'; + } + + toast({ + message: toastMessage, + duration: 5000, + dismissible: true, + pauseOnHover: true, + type: toastType, + position: 'top-center', + animate: { in: 'fadeIn', out: 'fadeOut' } + }); + } + startWatching() { const watch = async () => { if (this.tokens.length > 0) { this.tasks = await this.getTasks(); + this.tasks.forEach((task) => { + if (this.finishedTokens.find((finishedToken) => finishedToken === task.token)) { + } else { + if (task.info.done) { + this.finishedTokens.push(task.token); + this.showToast(task, 'DONE'); + localStorage.setItem('finishedTokens', JSON.stringify(this.finishedTokens)); + } else if (task.info.failed) { + this.finishedTokens.push(task.token); + this.showToast(task, 'FAILED'); + localStorage.setItem('finishedTokens', JSON.stringify(this.finishedTokens)); + } else { + } + } + }); } }; watch(); this.intervalId = setInterval(watch, 5000); } + } diff --git a/src/app/components/analysis-window/analysis-window.component.html b/src/app/components/analysis-window/analysis-window.component.html index dc41bd1d18eb6a3221fd5774ab9ab4b718539792..51b2a434975d3e5d21698dc90ad65fe626f2f425 100644 --- a/src/app/components/analysis-window/analysis-window.component.html +++ b/src/app/components/analysis-window/analysis-window.component.html @@ -1,5 +1,5 @@ <div *ngIf="token"> - <div class="card analysis" *ngIf="task && task.info.done"> + <div class="card analysis"> <header class="card-header"> <p class="card-header-title"> <span class="icon"> @@ -13,9 +13,36 @@ </span> </a> </header> - <div class="card-content"> - <div class="content"> + <div class="tabs is-centered"> + <ul> + <li [class.is-active]="tab === 'meta'"><a (click)="tab = 'meta'">Meta</a></li> + <li [class.is-active]="tab === 'network'"><a (click)="tab = 'network'">Network</a></li> + <li [class.is-active]="tab === 'table'"><a (click)="tab = 'table'">Table</a></li> + </ul> + </div> + <div class="content tab-content" *ngIf="task && task.info.done" [class.is-visible]="tab === 'meta'"> + <div *ngIf="task"> + <p> + Algorithm: {{task.info.algorithm}} + </p> + <div *ngIf="task.info.algorithm === 'trustrank'"> + <table class="table is-narrow"> + <tbody> + <tr> + <td>Damping Factor</td> + <td>{{task.info.parameters.dampingFactor}}</td> + </tr> + <tr> + <td>Result Size</td> + <td>{{task.info.parameters.resultSize}}</td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + <div class="content tab-content" *ngIf="task && task.info.done" [class.is-visible]="tab === 'network'"> <div class="card-image"> <div class="parent" id="1"> <div class="network center" #network> @@ -73,79 +100,9 @@ </div> </footer> </div> - </div> - - <div class="card analysis" *ngIf="task && !task.info.startedAt"> - <header class="card-header"> - <p class="card-header-title"> - <span class="icon"> - <i class="fas fa-cog" aria-hidden="true"></i> - </span> - Analysis Queued... - </p> - <a (click)="close()" class="card-header-icon" aria-label="close"> - <span class="icon"> - <i class="fas fa-times" aria-hidden="true"></i> - </span> - </a> - </header> - <div class="card-content"> - <div class="content"> - The analysis is queued - <!--TODO: Display queue information--> - </div> - </div> - <footer class="card-footer"> - </footer> - </div> - - <div class="card analysis" *ngIf="task && task.info.startedAt && !task.info.done"> - <header class="card-header"> - <p class="card-header-title"> - <span class="icon"> - <i class="fas fa-cog fa-spin" aria-hidden="true"></i> - </span> - Analysis in Progress... - </p> - <a (click)="close()" class="card-header-icon" aria-label="close"> - <span class="icon"> - <i class="fas fa-times" aria-hidden="true"></i> - </span> - </a> - </header> - <div class="card-content"> - <div class="content"> - The analysis is in progress - <!--TODO: Display analysis progress--> - </div> - </div> - <footer class="card-footer"> - </footer> - </div> - - <div class="card analysis" *ngIf="!task"> - <header class="card-header"> - <p class="card-header-title"> - <span class="icon"> - <i class="fas fa-question" aria-hidden="true"></i> - </span> - Analysis not found - </p> - <a (click)="close()" class="card-header-icon" aria-label="more options"> - <span class="icon"> - <i class="fas fa-times" aria-hidden="true"></i> - </span> - </a> - </header> - <div class="card-content"> - <div class="content"> - <span class="notification is-danger"> - The analysis you were looking for is either gone or never existed. - </span> - </div> + <div class="content tab-content" *ngIf="task && task.info.done" [class.is-visible]="tab === 'table'"> + TABLE </div> - <footer class="card-footer"> - </footer> </div> </div> </div> diff --git a/src/app/components/analysis-window/analysis-window.component.scss b/src/app/components/analysis-window/analysis-window.component.scss index 4888ec899f7bbc18d0f23d308447415d8e5ac075..056832a086a4be55405c315ec225e0365fd83e22 100644 --- a/src/app/components/analysis-window/analysis-window.component.scss +++ b/src/app/components/analysis-window/analysis-window.component.scss @@ -3,3 +3,15 @@ height: 100%; width: 100%; } + +div.network { + height: calc(100vh - 210px - 80px); +} + +.tab-content { + visibility: hidden; + + &.is-visible { + visibility: visible; + } +} diff --git a/src/app/components/analysis-window/analysis-window.component.ts b/src/app/components/analysis-window/analysis-window.component.ts index 62e0b4a128a12dba7e48218214abc1157b7e6b3d..2a7292c366d66a650088751cc26b42a1a35000b9 100644 --- a/src/app/components/analysis-window/analysis-window.component.ts +++ b/src/app/components/analysis-window/analysis-window.component.ts @@ -25,7 +25,10 @@ declare var vis: any; export class AnalysisWindowComponent implements OnInit, OnChanges { @Input() token: string | null = null; + @Output() tokenChange = new EventEmitter<string | null>(); + @Output() showDetailsChange: EventEmitter<any> = new EventEmitter(); + public task: Task | null = null; public indexscreenshot = 1; @@ -37,6 +40,7 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { private nodeData: { nodes: any, edges: any } = {nodes: null, edges: null}; private drugNodes = []; public showDrugs = false; + public tab = 'network'; constructor(private http: HttpClient, public analysis: AnalysisService) { @@ -71,25 +75,57 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { const options = {}; this.network = new vis.Network(container, this.nodeData, options); - this.network.on('select', (properties) => { + this.network.on('selectNode', (properties) => { const selectedNodes = this.nodeData.nodes.get(properties.nodes); if (selectedNodes.length > 0) { + let selectedProteinItem; + let selectedProteinName; + let selectedProteinType; + let selectedProteinAc; + let selectedProteinDataset; + let selectedProteinVirus; if (selectedNodes[0].nodeType === 'host') { const protein: Protein = {name: '', proteinAc: selectedNodes[0].id}; + selectedProteinName = null; + selectedProteinDataset = null; + selectedProteinVirus = null; + selectedProteinItem = {name: selectedNodes[0].id, type: 'Host Protein', data: protein}; + selectedProteinAc = protein.proteinAc; + selectedProteinType = 'Host Protein'; + if (properties.event.srcEvent.ctrlKey) { + if (this.analysis.inSelection(protein.proteinAc)) { + this.analysis.removeItem(protein.proteinAc); + } else { + this.analysis.addItem({name: protein.proteinAc, type: 'Host Protein', data: protein}); + this.analysis.getCount(); + } + } + } else if (selectedNodes[0].nodeType === 'virus') { + const virus: ViralProtein = {viralProteinId: null, effectName: selectedNodes[0].id, virusName: null, datasetName: null}; + selectedProteinAc = null; + selectedProteinDataset = null; + selectedProteinVirus = null; + selectedProteinItem = {name: virus.effectName, type: 'Viral Protein', data: virus}; + selectedProteinName = virus.effectName; + selectedProteinType = 'Viral Protein'; if (properties.event.srcEvent.ctrlKey) { - if (this.analysis.inSelection(protein) === true) { - this.analysis.removeProtein(protein); + if (this.analysis.inSelection(virus.effectName)) { + this.analysis.removeItem(virus.effectName); } else { - this.analysis.addProtein(protein); + this.analysis.addItem(selectedProteinItem); this.analysis.getCount(); } } } + this.showDetailsChange.emit([true, [selectedProteinItem, selectedProteinName, selectedProteinType, selectedProteinAc, + selectedProteinDataset, selectedProteinVirus]]); + } else { + this.showDetailsChange.emit([false, [null, null, null, null, null, null]]); } }); - this.analysis.subscribe((protein, selected) => { - const nodeId = `${protein.proteinAc}`; + this.analysis.subscribe((item, selected) => { + const nodeId = item.name; const node = this.nodeData.nodes.get(nodeId); if (!node) { return; @@ -168,7 +204,7 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { if (nodeType === 'host') { shape = 'ellipse'; - if (this.analysis.idInSelection(nodeId)) { + if (this.analysis.inSelection(nodeId)) { color = '#c7661c'; } else { color = '#e2b600'; diff --git a/src/app/components/launch-analysis/launch-analysis.component.html b/src/app/components/launch-analysis/launch-analysis.component.html index b85a31c9f1ccf8d3e2301785b4486d1d863f9258..54a64d53bca5d2ce661bd806b118d8a301bd3bf1 100644 --- a/src/app/components/launch-analysis/launch-analysis.component.html +++ b/src/app/components/launch-analysis/launch-analysis.component.html @@ -2,35 +2,32 @@ <div class="modal-background"></div> <div class="modal-card"> <header class="modal-card-head"> - <p class="modal-card-title">Launch Analysis</p> + <p class="modal-card-title" *ngIf="target === 'drug'"> + <span class="icon"><i class="fa fa-capsules"></i></span> + Find Drugs + </p> + <p class="modal-card-title" *ngIf="target === 'drug-target'"> + <span class="icon"><i class="fa fa-crosshairs"></i></span> + Find Drug Targets + </p> <button (click)="close()" class="delete" aria-label="close"></button> </header> <section class="modal-card-body"> <div class="tabs is-toggle is-small is-fullwidth is-rounded"> <ul> - <li [class.is-active]="algorithm === 'dummy'"> - <a (click)="algorithm = 'dummy'" class="is-boxed is-medium">Dummy</a> - </li> - <li [class.is-active]="algorithm === 'trustrank'"> - <a (click)="algorithm = 'trustrank'" class="is-boxed is-medium">Trustrank</a> - </li> - <li [class.is-active]="algorithm === 'keypathwayminer'"> - <a (click)="algorithm = 'keypathwayminer'" class="is-boxed is-medium">KeyPathwayMiner</a> - </li> - <li [class.is-active]="algorithm === 'multisteiner'"> - <a (click)="algorithm = 'multisteiner'" class="is-boxed is-medium">Multi-Steiner</a> + <li [class.is-active]="algorithm === algo.slug" *ngFor="let algo of algorithms"> + <a (click)="algorithm = algo.slug" class="is-boxed is-medium">{{algo.name}}</a> </li> </ul> </div> <div *ngIf="algorithm==='trustrank'"> - <div class="field"> + <div class="field" *ngIf="target === 'drug-target'"> <label class="label" for="trustrank-strain">Strain</label> <div class="control"> <div class="select"> <select id="trustrank-strain" [(ngModel)]="trustrankStrain"> <option [ngValue]="'SARS_CoV2'">SARS Coronavirus 2</option> - <option [ngValue]="'drugs'">Drugs</option> </select> </div> </div> diff --git a/src/app/components/launch-analysis/launch-analysis.component.ts b/src/app/components/launch-analysis/launch-analysis.component.ts index a0a884f18e838e6ec2c7fb28001fe9feff4548da..fc743ec71e3aa78cfdd4dfc03a6fbc194b40281b 100644 --- a/src/app/components/launch-analysis/launch-analysis.component.ts +++ b/src/app/components/launch-analysis/launch-analysis.component.ts @@ -1,20 +1,34 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core'; import {AnalysisService} from '../../analysis.service'; +interface Algorithm { + slug: string; + name: string; +} + +const DUMMY: Algorithm = {slug: 'dummy', name: 'Dummy'}; +const TRUSTRANK: Algorithm = {slug: 'trustrank', name: 'Trust-Rank'}; +const KEYPATHWAYMINER: Algorithm = {slug: 'keypathwayminer', name: 'KeyPathwayMiner'}; +const MULTISTEINER: Algorithm = {slug: 'multisteiner', name: 'Multi-Steiner'}; + @Component({ selector: 'app-launch-analysis', templateUrl: './launch-analysis.component.html', styleUrls: ['./launch-analysis.component.scss'] }) -export class LaunchAnalysisComponent implements OnInit { +export class LaunchAnalysisComponent implements OnInit, OnChanges { @Input() public show = false; + @Input() + public target: 'drug' | 'drug-target'; @Output() public showChange = new EventEmitter<boolean>(); public algorithm: 'dummy' | 'trustrank' | 'keypathwayminer' | 'multisteiner'; + public algorithms: Array<Algorithm> = []; + // Trustrank Parameters public trustrankStrain = 'SARS_CoV2'; public trustrankDampingFactor = 0.85; @@ -36,6 +50,16 @@ export class LaunchAnalysisComponent implements OnInit { ngOnInit(): void { } + ngOnChanges(changes: SimpleChanges): void { + if (this.target === 'drug-target') { + this.algorithms = [TRUSTRANK, MULTISTEINER, KEYPATHWAYMINER, DUMMY]; + this.trustrankStrain = 'SARS_CoV2'; + } else if (this.target === 'drug') { + this.algorithms = [TRUSTRANK, DUMMY]; + this.trustrankStrain = 'drugs'; + } + } + public close() { this.show = false; this.showChange.emit(this.show); @@ -43,7 +67,7 @@ export class LaunchAnalysisComponent implements OnInit { public async startTask() { const parameters: any = { - seeds: this.analysis.getSelection().map((protein) => protein.proteinAc), + seeds: this.analysis.getSelection().map((item) => item.name), }; if (this.algorithm === 'dummy') { @@ -60,10 +84,9 @@ export class LaunchAnalysisComponent implements OnInit { } else if (this.algorithm === 'multisteiner') { parameters.strain = this.multisteinerStrain; parameters.num_trees = this.multisteinerNumTrees; - } - await this.analysis.startAnalysis(this.algorithm, parameters); + await this.analysis.startAnalysis(this.algorithm, this.target, parameters); } } diff --git a/src/app/components/task-list/task-list.component.html b/src/app/components/task-list/task-list.component.html index eb672041421659e03b2079856c9a316419cad7bd..292d3cc0db9b88ff1209b7a7a50d86d151a54699 100644 --- a/src/app/components/task-list/task-list.component.html +++ b/src/app/components/task-list/task-list.component.html @@ -3,7 +3,7 @@ <a *ngFor="let task of analysis.tasks" class="list-item" [class.is-active]="task.token === token"> <div *ngIf="!task.info.startedAt" (click)="open(task.token)"> <p> - <span class="is-capitalized">{{task.info.algorithm}}</span> + <span class="is-capitalized"><i class="fa" [class.fa-capsules]="task.info.target === 'drug'" [class.fa-crosshairs]="task.info.target === 'drug-target'"></i> {{task.info.algorithm}}</span> <span class="icon is-pulled-right"><i class="fas fa-pause" aria-hidden="true"></i></span> </p> <p> @@ -12,17 +12,18 @@ </div> <div *ngIf="!task.info.done && !task.info.failed && task.info.startedAt" (click)="open(task.token)"> <p> - <span class="is-capitalized">{{task.info.algorithm}}</span> + <span class="is-capitalized"><i class="fa" [class.fa-capsules]="task.info.target === 'drug'" [class.fa-crosshairs]="task.info.target === 'drug-target'"></i> {{task.info.algorithm}}</span> <span class="icon is-pulled-right"><i class="fas fa-spinner fa-spin" aria-hidden="true"></i></span> </p> <p> <small>Started {{task.info.startedAt | date :'short'}}</small> </p> - <progress class="progress is-primary" [value]="task.info.progress * 100" max="100">Test</progress> + <progress class="progress is-success" [value]="task.info.progress * 100" max="100">Test</progress> + </div> <div *ngIf="task.info.done" (click)="open(task.token)"> <p> - <span class="is-capitalized">{{task.info.algorithm}}</span> + <span class="is-capitalized"><i class="fa" [class.fa-capsules]="task.info.target === 'drug'" [class.fa-crosshairs]="task.info.target === 'drug-target'"></i> {{task.info.algorithm}}</span> <span class="icon is-pulled-right"><i class="fas fa-check" aria-hidden="true"></i></span> </p> <p> @@ -31,7 +32,7 @@ </div> <div *ngIf="task.info.failed && task.info.finishedAt == null"> <p> - <span class="is-capitalized">{{task.info.algorithm}}</span> + <span class="is-capitalized"><i class="fa" [class.fa-capsules]="task.info.target === 'drug'" [class.fa-crosshairs]="task.info.target === 'drug-target'"></i> {{task.info.algorithm}}</span> <span class="icon is-pulled-right"><i class="fas fa-exclamation-triangle" aria-hidden="true"></i></span> </p> <p class="has-text-danger"> diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts index 6a5341edfb8199db014c047fd00534fc836d1593..5ae4c54a0516f6ebd4a0b21cd9ebb2ca60122066 100644 --- a/src/app/interfaces.ts +++ b/src/app/interfaces.ts @@ -9,6 +9,7 @@ export interface Protein { } export interface ViralProtein { + viralProteinId: string; effectName: string; virusName: string; datasetName: string; @@ -27,6 +28,7 @@ export interface ProteinViralInteraction { export interface Task { token: string; info: { + target: 'drug' | 'drug-target', algorithm: 'trustrank' | 'multisteiner' | 'keypathwayminer'; parameters?: { [key: string]: any }; diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 610d7f01f46e5ac4e436fe05c611bcb36696f0d8..e366c8a91e784c86fa57d91407a4671db6c7e1c0 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -1,4 +1,4 @@ -<app-launch-analysis [(show)]="showAnalysisDialog"></app-launch-analysis> +<app-launch-analysis [(show)]="showAnalysisDialog" [target]="analysisDialogTarget"></app-launch-analysis> <div class="covex explorer"> @@ -13,7 +13,8 @@ <i class="fas fa-database" aria-hidden="true"></i> </span> Choose Dataset </p> - <a (click)="collabsData = !collabsData" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> + <a (click)="collabsData = !collabsData" data-action="collapse" class="card-header-icon is-hidden-fullscreen" + aria-label="more options"> <span class="icon"> <i *ngIf="collabsData" class="fas fa-angle-up" aria-hidden="true"></i> <i *ngIf="!collabsData" class="fas fa-angle-down" aria-hidden="true"></i> @@ -23,7 +24,7 @@ <div *ngIf="collabsData"> <div class="card-content"> - <app-select-dataset [datasetItems]="datasetItems" [selectedDataset]="selectedDataset" + <app-select-dataset [datasetItems]="datasetItems" [selectedDataset]="selectedDataset" (selectedDatasetChange)="selectedDataset = $event; createNetwork($event.data)"> </app-select-dataset> @@ -38,7 +39,8 @@ <i class="fas fa-info" aria-hidden="true"></i> </span> Network Overview </p> - <a (click)="collabsOverview= !collabsOverview" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> + <a (click)="collabsOverview= !collabsOverview" data-action="collapse" + class="card-header-icon is-hidden-fullscreen" aria-label="more options"> <span class="icon"> <i *ngIf="collabsOverview" class="fas fa-angle-up" aria-hidden="true"></i> <i *ngIf="!collabsOverview" class="fas fa-angle-down" aria-hidden="true"></i> </span> @@ -70,20 +72,21 @@ </div> </div> - <div class="card bar-large" > + <div class="card bar-large"> <header class="card-header"> <p class="card-header-title"> <span class="icon"> <i class="fas fa-search" aria-hidden="true"></i> </span> Query Protein </p> - <a (click)="collabsQuery = !collabsQuery" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> + <a (click)="collabsQuery = !collabsQuery" data-action="collapse" + class="card-header-icon is-hidden-fullscreen" aria-label="more options"> <span class="icon"> <i *ngIf="collabsQuery" class="fas fa-angle-up" aria-hidden="true"></i> <i *ngIf="!collabsQuery" class="fas fa-angle-down" aria-hidden="true"></i> </span> - </a> - </header > + </a> + </header> <div *ngIf="collabsQuery"> <div class="card-content"> <div class="field"> @@ -94,7 +97,7 @@ </div> </div> </div> - </div> + </div> <div class="card bar-large"> <header class="card-header"> @@ -103,7 +106,8 @@ <i class="fas fa-filter" aria-hidden="true"></i> </span> Filter Viral Proteins </p> - <a (click)="collabsDFilter = !collabsDFilter" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> + <a (click)="collabsDFilter = !collabsDFilter" data-action="collapse" + class="card-header-icon is-hidden-fullscreen" aria-label="more options"> <span class="icon"> <i *ngIf="collabsDFilter" class="fas fa-angle-up" aria-hidden="true"></i> <i *ngIf="!collabsDFilter" class="fas fa-angle-down" aria-hidden="true"></i> @@ -120,19 +124,19 @@ </label> </div> </div> - <footer class="card-footer"> - <a (click)="reset($event);" class="card-footer-item has-text-danger"> + <footer class="card-footer"> + <a (click)="reset($event);" class="card-footer-item has-text-danger"> <span class="icon"> <i class="fa fa-refresh"></i> </span> - <span> + <span> Reset </span> - </a> - </footer> + </a> + </footer> + </div> </div> </div> - </div> <div class="covex network"> <div class="card network"> @@ -182,7 +186,8 @@ </div> <div class="analysis-view" *ngIf="selectedAnalysisToken"> - <app-analysis-window [(token)]="selectedAnalysisToken"></app-analysis-window> + <app-analysis-window [(token)]="selectedAnalysisToken" + (showDetailsChange)="showDetails = $event[0]; changeInfo($event[1])"></app-analysis-window> </div> </div> @@ -193,9 +198,10 @@ <p class="card-header-title"> <span class="icon "> <i class="fas fa-info" aria-hidden="true"></i> - </span> {{currentProteinAc}} + </span> {{ selectedProteinType }} </p> - <a (click)="collabsDetails = !collabsDetails" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> + <a (click)="collabsDetails = !collabsDetails" data-action="collapse" + class="card-header-icon is-hidden-fullscreen" aria-label="more options"> <span class="icon"> <i *ngIf="collabsDetails" class="fas fa-angle-up" aria-hidden="true"></i> <i *ngIf="!collabsDetails" class="fas fa-angle-down" aria-hidden="true"></i> @@ -205,32 +211,31 @@ <div *ngIf="collabsDetails"> <div class="card-content"> <div *ngIf="showDetails"> - <p><b>Protein Name:</b> {{ currentProteinAc }}</p> - <p><b>Protein AC(s):</b> - <a href="https://www.uniprot.org/uniprot/{{proteinAc}}" target="_blank" - *ngFor="let proteinAc of proteinAcs"> - {{ proteinAc }} - </a> + <p *ngIf="selectedProteinName"><b>Name:</b> {{ selectedProteinName }}</p> + <p *ngIf="selectedProteinDataset"><b>Virus:</b> {{ selectedProteinVirus }}</p> + <p *ngIf="selectedProteinAc"><b>Protein AC:</b> + <a href="https://www.uniprot.org/uniprot/{{ selectedProteinAc }}" + target="_blank"> {{ selectedProteinAc }}</a> </p> <div class="field has-addons add-remove-toggle"> <p class="control"> - <button class="button is-rounded" [class.is-success]="!inSelection(currentProteinAc)" - [disabled]="inSelection(currentProteinAc)" - (click)="addToSelection(currentProteinAc)"> - <span class="icon is-small"> - <i class="fas fa-plus"></i> - </span> + <button class="button is-rounded" [class.is-success]="!analysis.inSelection(selectedProteinName)" + [disabled]="analysis.inSelection(selectedProteinName)" + (click)="analysis.addItem(selectedProteinItem)"> + <span class="icon is-small"> + <i class="fas fa-plus"></i> + </span> <span>Add to Analysis</span> </button> </p> <p class="control"> - <button class="button is-rounded" [class.is-danger]="inSelection(currentProteinAc)" - [disabled]="!inSelection(currentProteinAc)" - (click)="removeFromSelection(currentProteinAc)"> + <button class="button is-rounded" [class.is-danger]="analysis.inSelection(selectedProteinName)" + [disabled]="!analysis.inSelection(selectedProteinName)" + (click)="analysis.removeItem(selectedProteinName)"> <span>Remove</span> <span class="icon is-small"> - <i class="fas fa-trash"></i> - </span> + <i class="fas fa-trash"></i> + </span> </button> </p> </div> @@ -250,7 +255,8 @@ <i class="fas fa-flask" aria-hidden="true"></i> </span> Analysis </p> - <a (click)="collabsAnalysis = !collabsAnalysis" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> + <a (click)="collabsAnalysis = !collabsAnalysis" data-action="collapse" + class="card-header-icon is-hidden-fullscreen" aria-label="more options"> <span class="icon"> <i *ngIf="collabsAnalysis" class="fas fa-angle-up" aria-hidden="true"></i> <i *ngIf="!collabsAnalysis" class="fas fa-angle-down" aria-hidden="true"></i> @@ -259,16 +265,34 @@ </header> <div *ngIf="collabsAnalysis"> <div class="card-content"> - <button (click)="showAnalysisDialog = true" - class="button is-primary is-fullwidth is-rounded" - [disabled]="analysis.getCount() === 0"> - <span class="icon"> - <i class="fa fa-rocket"></i> - </span> - <span> - Launch Analysis - </span> - </button> + <div class="field"> + <div class="control"> + <button (click)="analysisDialogTarget = 'drug-target'; showAnalysisDialog = true;" + class="button is-primary is-fullwidth is-rounded" + [disabled]="analysis.getCount() === 0"> + <span class="icon"> + <i class="fa fa-crosshairs"></i> + </span> + <span> + Find Drug Targets + </span> + </button> + </div> + </div> + <div class="field"> + <div class="control"> + <button (click)="analysisDialogTarget = 'drug'; showAnalysisDialog = true;" + class="button is-primary is-fullwidth is-rounded" + [disabled]="analysis.getCount() === 0"> + <span class="icon"> + <i class="fa fa-capsules"></i> + </span> + <span> + Find Drugs + </span> + </button> + </div> + </div> </div> </div> </div> @@ -280,7 +304,8 @@ <i class="fas fa-filter" aria-hidden="true"></i> </span> Tasks </p> - <a (click)="collabsTask = !collabsTask" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> + <a (click)="collabsTask = !collabsTask" data-action="collapse" class="card-header-icon is-hidden-fullscreen" + aria-label="more options"> <span class="icon"> <i *ngIf="collabsSelection" class="fas fa-angle-up" aria-hidden="true"></i> <i *ngIf="!collabsSelection" class="fas fa-angle-down" aria-hidden="true"></i> @@ -292,7 +317,8 @@ <app-task-list [(token)]="selectedAnalysisToken"></app-task-list> </div> <footer class="card-footer"> - <a (click)="analysis.removeAllTasks(); selectedAnalysisToken = null;" class="card-footer-item has-text-danger"> + <a (click)="analysis.removeAllTasks(); selectedAnalysisToken = null;" + class="card-footer-item has-text-danger"> <span class="icon"> <i class="fa fa-trash"></i> </span> @@ -312,7 +338,8 @@ <i class="fas fa-filter" aria-hidden="true"></i> </span> Selection </p> - <a (click)="collabsSelection = !collabsSelection" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> + <a (click)="collabsSelection = !collabsSelection" data-action="collapse" + class="card-header-icon is-hidden-fullscreen" aria-label="more options"> <span class="icon"> <i *ngIf="collabsSelection" class="fas fa-angle-up" aria-hidden="true"></i> <i *ngIf="!collabsSelection" class="fas fa-angle-down" aria-hidden="true"></i> @@ -330,9 +357,9 @@ </thead> <tbody> <tr *ngFor="let p of analysis.getSelection()"> - <td>{{p.proteinAc}}</td> + <td>{{p.name}}</td> <td> - <button (click)="analysis.removeProtein(p)" class="button is-small is-danger is-outlined"> + <button (click)="analysis.removeItem(p.name)" class="button is-small is-danger is-outlined"> <i class="fa fa-trash"></i> </button> </td> @@ -343,38 +370,37 @@ To select proteins, click them while pressing CTRL. </i> </div> - <footer class="card-footer"> - <a (click)="addAllHostProteins()" class="card-footer-item has-text-success"> + <footer class="card-footer"> + <a (click)="addAllHostProteins()" class="card-footer-item has-text-success"> <span class="icon"> <i class="fa fa-plus"></i> </span> - <span> + <span> Host Proteins </span> - </a> - <a class="card-footer-item has-text-grey-light"> + </a> + <a (click)="addAllViralProteins()" class="card-footer-item has-text-success"> <span class="icon"> <i class="fa fa-plus"></i> </span> - <span> + <span> Viral Proteins </span> - </a> - </footer> - <footer class="card-footer"> - <a (click)="analysis.resetSelection()" class="card-footer-item has-text-danger"> + </a> + </footer> + <footer class="card-footer"> + <a (click)="analysis.resetSelection()" class="card-footer-item has-text-danger"> <span class="icon"> <i class="fa fa-refresh"></i> </span> - <span> + <span> Reset </span> - </a> - </footer> - </div> + </a> + </footer> + </div> </div> - </div> </div> diff --git a/src/app/pages/explorer-page/explorer-page.component.ts b/src/app/pages/explorer-page/explorer-page.component.ts index 6c2f6c1b1c42f989443187d4f816fd0897aade95..9d2b19f4f5cc90393cc4c44c2c93acad9e8d6094 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -5,7 +5,6 @@ import { OnInit, ViewChild } from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; import {ProteinViralInteraction, ViralProtein, Protein, QueryItem} from '../../interfaces'; import {ProteinNetwork, getDatasetFilename} from '../../main-network'; import {HttpClient, HttpParams} from '@angular/common/http'; @@ -13,6 +12,7 @@ import {AnalysisService} from '../../analysis.service'; import html2canvas from 'html2canvas'; import {environment} from '../../../environments/environment'; + declare var vis: any; @Component({ @@ -23,6 +23,12 @@ declare var vis: any; export class ExplorerPageComponent implements OnInit, AfterViewInit { public showDetails = false; + public selectedProteinName = null; + public selectedProteinType = null; + public selectedProteinAc = null; + public selectedProteinItem = null; + public selectedProteinVirus = null; + public selectedProteinDataset = null; public collabsAnalysis = true; public collabsDetails = true; public collabsTask = true; @@ -31,10 +37,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public collabsQuery = true; public collabsData = true; public collabsOverview = true; - public currentProteinAc = ''; - public geneNames: Array<string> = []; - public proteinNames: Array<string> = []; - public proteinAcs: Array<string> = []; + public viralProteinCheckboxes: Array<{ checked: boolean; data: ViralProtein }> = []; @@ -52,6 +55,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public queryItems: QueryItem[] = []; public showAnalysisDialog = false; + public analysisDialogTarget: 'drug' | 'drug-target'; public selectedAnalysisToken: string | null = null; @@ -66,7 +70,12 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { data: [['TUM', 'HCoV'], ['TUM', 'SARS-CoV2'], ['Krogan', 'SARS-CoV2']] }, {id: 'HCoV (TUM)', label: 'HCoV', datasets: 'TUM', data: [['TUM', 'HCoV']]}, - {id: 'CoV2 (TUM & Krogan)', label: 'CoV2', datasets: 'TUM & Krogan', data: [['TUM', 'SARS-CoV2'], ['Krogan', 'SARS-CoV2']]}, + { + id: 'CoV2 (TUM & Krogan)', + label: 'CoV2', + datasets: 'TUM & Krogan', + data: [['TUM', 'SARS-CoV2'], ['Krogan', 'SARS-CoV2']] + }, {id: 'CoV2 (Krogan)', label: 'CoV2', datasets: 'Krogan', data: [['Krogan', 'SARS-CoV2']]}, {id: 'CoV2 (TUM)', label: 'CoV2', datasets: 'TUM', data: [['TUM', 'SARS-CoV2']]}]; @@ -74,45 +83,17 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { @ViewChild('network', {static: false}) networkEl: ElementRef; - constructor(private http: HttpClient, - private route: ActivatedRoute, - private router: Router, - public analysis: AnalysisService) { - this.geneNames.push('IFI16'); - this.proteinNames.push('Gamma-interface-inducible protein 16'); - this.proteinAcs.push('Q16666'); - - this.route.queryParams.subscribe(async (params) => { - this.dumpPositions = params.dumpPositions; - this.physicsEnabled = !!this.dumpPositions; - - const protein = params.protein; - if (!protein) { - // In this case, the URL is just `/explorer` - // Therefore, we do not show a modal - this.showDetails = false; - return; - } + constructor(private http: HttpClient, public analysis: AnalysisService) { - // In this case, the URL is `/explorer/<protein>` + this.showDetails = false; - if (this.currentProteinAc === protein) { - // The protein group is the same as before, so we do not need to do anything - // TODO Also highlight node when reloading the page/sharing the URL - return; + this.analysis.subscribe((item, selected) => { + let nodeId; + if (item.type === 'Host Protein') { + nodeId = `p_${item.name}`; + } else if (item.type === 'Viral Protein') { + nodeId = `eff_${item.name}`; } - - // We have a new protein id, so we need to load it and show the modal... - - this.currentProteinAc = protein; - - // TODO: Perform call here for 'protein'... - // this.zoomToNode(protein) - this.showDetails = true; - }); - - this.analysis.subscribe((protein, selected) => { - const nodeId = `p_${protein.proteinAc}`; const node = this.nodeData.nodes.get(nodeId); if (!node) { return; @@ -122,11 +103,14 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { node.y = pos[nodeId].y; if (selected) { node.color = '#48C774'; - this.nodeData.nodes.update(node); } else { - node.color = '#e2b600'; - this.nodeData.nodes.update(node); + if (item.type === 'Host Protein') { + node.color = '#e2b600'; + } else if (item.type === 'Viral Protein') { + node.color = '#118AB2'; + } } + this.nodeData.nodes.update(node); }); } @@ -174,15 +158,45 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }); } - public async openSummary(protein: Protein, zoom: boolean) { - await this.router.navigate(['explorer'], {queryParams: {protein: protein.proteinAc}}); - if (zoom) { - this.zoomToNode(`p_${protein.proteinAc}`); + public changeInfo(showList: any[]) { + this.selectedProteinItem = showList[0]; + this.selectedProteinName = showList[1]; + this.selectedProteinType = showList[2]; + this.selectedProteinAc = showList[3]; + this.selectedProteinDataset = showList[4]; + this.selectedProteinVirus = showList[5]; + } + + public async openSummary(item: QueryItem, zoom: boolean) { + this.selectedProteinAc = null; + this.selectedProteinItem = item; + this.selectedProteinType = item.type; + this.selectedProteinName = item.name; + if (this.selectedProteinType === 'Host Protein') { + const hostProtein = item.data as Protein; + this.selectedProteinAc = hostProtein.proteinAc; + if (zoom) { + this.zoomToNode(`p_${item.name}`); + } + } else if (item.type === 'Viral Protein') { + const viralProtein = item.data as ViralProtein; + this.selectedProteinVirus = viralProtein.virusName; + this.selectedProteinDataset = viralProtein.datasetName; + if (zoom) { + this.zoomToNode(`eff_${viralProtein.effectName}_${viralProtein.datasetName}_${viralProtein.virusName}`); + } } + this.showDetails = true; } public async closeSummary() { - await this.router.navigate(['explorer']); + this.selectedProteinItem = null; + this.selectedProteinName = null; + this.selectedProteinType = null; + this.selectedProteinAc = null; + this.selectedProteinVirus = null; + this.selectedProteinDataset = null; + this.showDetails = false; } public async createNetwork(dataset: Array<[string, string]>) { @@ -229,27 +243,44 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }, }, }; - this.network = new vis.Network(container, this.nodeData, options); - this.network.on('select', (properties) => { + this.network.on('selectNode', (properties) => { const id: Array<string> = properties.nodes; if (id.length > 0) { - if (id[0].startsWith('p_')) { - const protein = this.proteinData.getProtein(id[0].substr(2)); - this.openSummary(protein, false); - if (properties.event.srcEvent.ctrlKey) { - if (this.inSelection(protein.proteinAc) === true) { - this.removeFromSelection(protein.proteinAc); - } else { - this.addToSelection(protein.proteinAc); - this.analysis.getCount(); - } + const nodeId = id[0].split('_'); + let node: QueryItem; + if (nodeId[0].startsWith('p')) { + node = { + name: nodeId[1], + type: 'Host Protein', + data: this.proteinData.getProtein(nodeId[1]) + }; + } else if (nodeId[0].startsWith('e')) { + node = { + name: nodeId[1] + '_' + nodeId[2] + '_' + nodeId[3], + type: 'Viral Protein', + data: this.effects.find((eff) => eff.effectName === nodeId[1] && eff.datasetName === nodeId[2] && eff.virusName === nodeId[3]) + }; + } + if (properties.event.srcEvent.ctrlKey) { + if (this.analysis.inSelection(node.name) === true) { + this.analysis.inSelection(node.name); + } else { + this.analysis.addItem(node); + this.analysis.getCount(); } + this.openSummary(node, false); } else { - this.closeSummary(); + this.openSummary(node, false); } + } else { + this.closeSummary(); } }); + this.network.on('deselectNode', (properties) => { + this.closeSummary(); + }); + if (this.dumpPositions) { this.network.on('stabilizationIterationsDone', () => { @@ -261,15 +292,17 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.network.stabilize(); } - if (this.currentProteinAc) { - this.zoomToNode(`p_${this.currentProteinAc}`); + if (this.selectedProteinItem) { + this.zoomToNode(`p_${this.selectedProteinItem.name}`); } this.queryItems = []; this.fillQueryItems(this.proteins, this.effects); + if (this.selectedProteinItem) { + this.network.selectNodes(['p_' + this.selectedProteinItem.name]); + } } - fillQueryItems(hostProteins: Protein[], viralProteins: ViralProtein[]) { this.queryItems = []; hostProteins.forEach((protein) => { @@ -307,7 +340,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } }); viralProteins.forEach((effect) => { - const nodeId = `eff_${effect.effectName}_${effect.virusName}_${effect.datasetName}`; + const nodeId = `eff_${effect.effectName}_${effect.datasetName}_${effect.effectName}`; const found = visibleIds.has(nodeId); if ((cb.checked || showAll) && !found) { const node = this.mapViralProteinToNode(effect); @@ -349,14 +382,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public queryAction(item: any) { if (item) { - if (item.type === 'Host Protein') { - this.openSummary(item.data, true); - } else if (item.type === 'Viral Protein') { - this.zoomToNode(`eff_${item.data.effectName}_${item.data.virusName}_${item.data.datasetName}` - ); - } + this.openSummary(item, true); } - } public updatePhysicsEnabled(bool) { @@ -371,34 +398,38 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }); } - private mapHostProteinToNode(protein: Protein): any { + private mapHostProteinToNode(hostProtein: Protein): any { let color = '#e2b600'; - if (this.analysis.inSelection(protein)) { + if (this.analysis.inSelection(hostProtein.proteinAc)) { color = '#48C774'; } return { - id: `p_${protein.proteinAc}`, - label: `${protein.proteinAc}`, + id: `p_${hostProtein.proteinAc}`, + label: `${hostProtein.proteinAc}`, size: 10, font: '5px', color, shape: 'ellipse', shadow: false, - x: protein.x, - y: protein.y, + x: hostProtein.x, + y: hostProtein.y, }; } - private mapViralProteinToNode(effect: ViralProtein): any { + private mapViralProteinToNode(viralProtein: ViralProtein): any { + let color = '#118AB2'; + if (this.analysis.inSelection(`${viralProtein.effectName}_${viralProtein.datasetName}_${viralProtein.virusName}`)) { + color = '#48C774'; + } return { - id: `eff_${effect.effectName}_${effect.virusName}_${effect.datasetName}`, - label: `${effect.effectName} (${effect.virusName}, ${effect.datasetName})`, - size: 10, color: '#118AB2', shape: 'box', shadow: true, font: {color: '#FFFFFF'}, - x: effect.x, - y: effect.y, + id: `eff_${viralProtein.effectName}_${viralProtein.datasetName}_${viralProtein.virusName}`, + label: `${viralProtein.effectName} (${viralProtein.datasetName}, ${viralProtein.virusName})`, + size: 10, color, shape: 'box', shadow: true, font: {color: '#FFFFFF'}, + x: viralProtein.x, + y: viralProtein.y, }; } private mapEdge(edge: ProteinViralInteraction): any { return { from: `p_${edge.proteinAc}`, - to: `eff_${edge.effectName}_${edge.virusName}_${edge.datasetName}`, + to: `eff_${edge.effectName}_${edge.datasetName}_${edge.virusName}`, color: {color: '#afafaf', highlight: '#854141'}, }; } @@ -430,43 +461,25 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { for (const protein of this.proteinData.proteins) { const nodeId = `p_${protein.proteinAc}`; const found = visibleIds.has(nodeId); - if (found && !this.analysis.inSelection(protein)) { - this.analysis.addProtein(protein); + if (found && !this.analysis.inSelection(protein.name)) { + this.analysis.addItem({name: protein.proteinAc, type: 'Host Protein', data: protein}); } } } - inSelection(proteinAc: string): boolean { - if (!this.proteinData || !proteinAc) { - return false; - } - const protein = this.proteinData.getProtein(proteinAc); - if (!protein) { - return false; - } - return this.analysis.inSelection(protein); - } - - addToSelection(proteinAc: string) { - if (!this.proteinData || !proteinAc) { - return false; - } - const protein = this.proteinData.getProtein(proteinAc); - if (!protein) { - return false; - } - this.analysis.addProtein(protein); - } - - removeFromSelection(proteinAc: string) { - if (!this.proteinData || !proteinAc) { - return false; - } - const protein = this.proteinData.getProtein(proteinAc); - if (!protein) { - return false; + public addAllViralProteins() { + const visibleIds = new Set<string>(this.nodeData.nodes.getIds()); + for (const effect of this.proteinData.effects) { + const nodeId = `eff_${effect.effectName + '_' + effect.datasetName + '_' + effect.virusName}`; + const found = visibleIds.has(nodeId); + if (found && !this.analysis.inSelection(effect.effectName + '_' + effect.datasetName + '_' + effect.virusName)) { + this.analysis.addItem({ + name: effect.effectName + '_' + effect.datasetName + '_' + effect.virusName, + type: 'Viral Protein', + data: effect + }); + } } - this.analysis.removeProtein(protein); } public toCanvas() { diff --git a/src/index.html b/src/index.html index 1c8b9b651bd9e8067206019a473495af2e120f5f..3a8acea8a766bd1ebf7a4fe6142b918998ab4a81 100644 --- a/src/index.html +++ b/src/index.html @@ -11,6 +11,7 @@ <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css"> </head> <body> <app-root></app-root> diff --git a/src/styles.scss b/src/styles.scss index 9d1dfb452c81588ecc9107cae4bc0370026b7174..642097fe2e3634a41d8658c0961d152bb7d6b6fa 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -49,7 +49,8 @@ img { img.inline { height: 30px; - margin-left: 0px; + align: middle; + } button.i {