diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index f1e5a289d6138a1b5b4422cc64ac65071645e4f0..f782af95a6e371be95ad3b1279e9e2fa55c20991 100644 --- a/src/app/analysis.service.ts +++ b/src/app/analysis.service.ts @@ -1,5 +1,5 @@ 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'; @@ -10,8 +10,8 @@ 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 tasks: Task[] = []; @@ -44,45 +44,42 @@ 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); }); } diff --git a/src/app/components/analysis-window/analysis-window.component.ts b/src/app/components/analysis-window/analysis-window.component.ts index e9f18dd98bbbc8518208e6cf0214ebf8f200093d..b90fde8554d8d789621a83e7e71dc7785e28741c 100644 --- a/src/app/components/analysis-window/analysis-window.component.ts +++ b/src/app/components/analysis-window/analysis-window.component.ts @@ -12,7 +12,7 @@ import { import {HttpClient} from '@angular/common/http'; import {environment} from '../../../environments/environment'; import {AnalysisService} from '../../analysis.service'; -import {Protein, Task, NodeType} from '../../interfaces'; +import {Protein, Task, NodeType, ViralProtein, QueryItem} from '../../interfaces'; declare var vis: any; @@ -24,7 +24,16 @@ declare var vis: any; export class AnalysisWindowComponent implements OnInit, OnChanges { @Input() token: string | null = null; + @Input() selectedProteinName: string; + @Input() selectedProteinType: string; + @Input() selectedProteinAc: string; + @Input() selectedProteinItem: QueryItem; + @Input() selectedProteinVirus: string; + @Input() selectedProteinDataset: string; + @Output() tokenChange = new EventEmitter<string | null>(); + @Output() showDetailsChange: EventEmitter<any> = new EventEmitter(); + public task: Task | null = null; @@ -67,25 +76,58 @@ 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) { if (selectedNodes[0].nodeType === 'host') { const protein: Protein = {name: '', proteinAc: selectedNodes[0].id}; + this.selectedProteinName = null; + this.selectedProteinDataset = null; + this.selectedProteinVirus = null; + this.selectedProteinItem = {name: selectedNodes[0].id, type: 'Host Protein', data: protein}; + this.selectedProteinAc = protein.proteinAc; + this.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 = {effectName: selectedNodes[0].id, virusName: null, datasetName: null}; + this.selectedProteinAc = null; + this.selectedProteinDataset = null; + this.selectedProteinVirus = null; + this.selectedProteinItem = {name: virus.effectName, type: 'Viral Protein', data: virus}; + this.selectedProteinName = virus.effectName; + this.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(this.selectedProteinItem); this.analysis.getCount(); } } } + this.showDetailsChange.emit([true, [this.selectedProteinItem, this.selectedProteinName, + this.selectedProteinType, this.selectedProteinAc, this.selectedProteinDataset, this.selectedProteinVirus]]); + } else { + this.selectedProteinItem = null; + this.selectedProteinName = null; + this.selectedProteinType = null; + this.selectedProteinAc = null; + this.selectedProteinDataset = null; + this.selectedProteinVirus = null; + this.showDetailsChange.emit([false, [this.selectedProteinItem, this.selectedProteinName, + this.selectedProteinType, this.selectedProteinAc, this.selectedProteinDataset, this.selectedProteinVirus]]); } }); - 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; @@ -164,7 +206,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.ts b/src/app/components/launch-analysis/launch-analysis.component.ts index a0a884f18e838e6ec2c7fb28001fe9feff4548da..cb1f46e3108eaa6cac883814ed33a33351426fdd 100644 --- a/src/app/components/launch-analysis/launch-analysis.component.ts +++ b/src/app/components/launch-analysis/launch-analysis.component.ts @@ -43,7 +43,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') { diff --git a/src/app/components/task-list/task-list.component.html b/src/app/components/task-list/task-list.component.html index eb672041421659e03b2079856c9a316419cad7bd..1a9c4881f702b73d47f26ffa2a8d55a72cdf561d 100644 --- a/src/app/components/task-list/task-list.component.html +++ b/src/app/components/task-list/task-list.component.html @@ -18,7 +18,8 @@ <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> diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index e9d90ba548828385c705940819ee23a0f0ca7e19..6cc3172c3f5fcad8d1381e4bd232fa0171573034 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -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 class="fas fa-angle-down" aria-hidden="true"></i> </span> @@ -22,7 +23,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> @@ -37,7 +38,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 class="fas fa-angle-down" aria-hidden="true"></i> </span> @@ -69,19 +71,20 @@ </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 class="fas fa-angle-down" aria-hidden="true"></i> </span> - </a> - </header > + </a> + </header> <div *ngIf="collabsQuery"> <div class="card-content"> <div class="field"> @@ -92,7 +95,7 @@ </div> </div> </div> - </div> + </div> <div class="card bar-large"> <header class="card-header"> @@ -101,7 +104,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 class="fas fa-angle-down" aria-hidden="true"></i> </span> @@ -117,19 +121,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"> @@ -179,7 +183,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> @@ -190,9 +195,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 class="fas fa-angle-down" aria-hidden="true"></i> </span> @@ -201,32 +207,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> @@ -246,7 +251,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 class="fas fa-angle-down" aria-hidden="true"></i> </span> @@ -275,7 +281,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 class="fas fa-angle-down" aria-hidden="true"></i> </span> @@ -286,7 +293,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> @@ -306,7 +314,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 class="fas fa-angle-down" aria-hidden="true"></i> </span> @@ -323,9 +332,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> @@ -336,38 +345,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..d27ba75fbc31d99759bf50e62f7aa6bd960b1855 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'; @@ -23,6 +22,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 +36,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 }> = []; @@ -66,7 +68,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 +81,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 +101,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 +156,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 +241,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 +290,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 +338,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 +380,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 +396,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 +459,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/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 {