diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index e90aade5fb130067e43130be41fb4361bc4682db..f1e5a289d6138a1b5b4422cc64ac65071645e4f0 100644 --- a/src/app/analysis.service.ts +++ b/src/app/analysis.service.ts @@ -62,6 +62,10 @@ export class AnalysisService { return this.selectedProteins.has(protein.proteinAc); } + idInSelection(id: string): boolean { + return this.selectedProteins.has(id); + } + removeProtein(protein: Protein) { if (this.selectedProteins.delete(`${protein.proteinAc}`)) { this.selectSubject.next({protein, selected: false}); diff --git a/src/app/components/analysis-window/analysis-window.component.html b/src/app/components/analysis-window/analysis-window.component.html index f6e4f60846ea9fb1e7ba055a802d95c8b5557888..ba091706768457771946e5dfb4fd0ffdabdcb7c6 100644 --- a/src/app/components/analysis-window/analysis-window.component.html +++ b/src/app/components/analysis-window/analysis-window.component.html @@ -44,7 +44,7 @@ </button> </p> <p class="control"> - <button class="button is-danger is-rounded" (click)="analysis.removeTask(token)"> + <button class="button is-danger is-rounded" (click)="analysis.removeTask(token); close()"> <span class="icon"> <i class="fas fa-trash" aria-hidden="true"></i> </span> diff --git a/src/app/components/analysis-window/analysis-window.component.ts b/src/app/components/analysis-window/analysis-window.component.ts index e89bce3a3a27dd74cfcc03ec973d0257840a8cec..e9f18dd98bbbc8518208e6cf0214ebf8f200093d 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} from '../../interfaces'; +import {Protein, Task, NodeType} from '../../interfaces'; declare var vis: any; @@ -34,7 +34,6 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { private nodeData: { nodes: any, edges: any } = {nodes: null, edges: null}; private drugNodes = []; public showDrugs = false; - private result: any; constructor(private http: HttpClient, public analysis: AnalysisService) { } @@ -51,13 +50,53 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { this.task = await this.getTask(this.token); if (this.task && this.task.info.done) { - const result = await this.http.get<any>(`${environment.backend}result/?token=${this.token}`).toPromise(); - this.result = result; + const result = await this.http.get<any>(`${environment.backend}task_result/?token=${this.token}`).toPromise(); + + // Reset + this.nodeData = {nodes: null, edges: null}; this.networkEl.nativeElement.innerHTML = ''; this.network = null; this.showDrugs = false; - this.nodeData = {nodes: null, edges: null}; - this.createNetwork(result); + + // Create + const {nodes, edges} = this.createNetwork(result); + this.nodeData.nodes = new vis.DataSet(nodes); + this.nodeData.edges = new vis.DataSet(edges); + + const container = this.networkEl.nativeElement; + const options = {}; + + this.network = new vis.Network(container, this.nodeData, options); + this.network.on('select', (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}; + if (properties.event.srcEvent.ctrlKey) { + if (this.analysis.inSelection(protein) === true) { + this.analysis.removeProtein(protein); + } else { + this.analysis.addProtein(protein); + this.analysis.getCount(); + } + } + } + } + }); + + this.analysis.subscribe((protein, selected) => { + const nodeId = `${protein.proteinAc}`; + const node = this.nodeData.nodes.get(nodeId); + if (!node) { + return; + } + const pos = this.network.getPositions([nodeId]); + node.x = pos[nodeId].x; + node.y = pos[nodeId].y; + const {color} = this.getNodeLooks(nodeId, node.nodeType, node.isSeed); + node.color = color; + this.nodeData.nodes.update(node); + }); } } } @@ -79,161 +118,92 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { } - public async createNetwork(result: any) { - const {nodes, edges} = this.mapDataToNodes(result); - this.nodeData.nodes = new vis.DataSet(nodes); - this.nodeData.edges = new vis.DataSet(edges); - - const container = this.networkEl.nativeElement; - const options = { - layout: { - improvedLayout: false, - }, - }; - - this.network = new vis.Network(container, this.nodeData, options); - this.network.on('select', (properties) => { - const id: Array<string> = properties.nodes; - if (id.length > 0) { - if (id[0].startsWith('p_')) { - const protein = this.getProtein(id[0].substr(2)); - if (properties.event.srcEvent.ctrlKey) { - if (this.inSelection(protein.proteinAc) === true) { - this.removeFromSelection(protein.proteinAc); - } else { - this.addToSelection(protein.proteinAc); - this.analysis.getCount(); - } - } - } - } - }); - - this.analysis.subscribe((protein, selected) => { - const nodeId = `p_${protein.proteinAc}`; - const node = this.nodeData.nodes.get(nodeId); - if (!node) { - return; - } - const pos = this.network.getPositions([nodeId]); - node.x = pos[nodeId].x; - node.y = pos[nodeId].y; - if (selected) { - node.color = '#48C774'; - this.nodeData.nodes.update(node); - } else { - node.color = '#e2b600'; - this.nodeData.nodes.update(node); - } - }); - } - - inSelection(proteinAc: string): boolean { - if (!this.result.proteins || !proteinAc) { - return false; + public inferNodeType(nodeId: string): 'host' | 'virus' | 'drug' { + if (nodeId.indexOf('-') !== -1 || nodeId.indexOf('_') !== -1) { + return 'virus'; } - const protein = this.getProtein(proteinAc); - if (!protein) { - return false; - } - return this.analysis.inSelection(protein); + return 'host'; } - addToSelection(proteinAc: string) { - if (!this.result.proteins || !proteinAc) { - return false; - } - const protein = this.getProtein(proteinAc); - if (!protein) { - return false; - } - this.analysis.addProtein(protein); - } + public createNetwork(result: any): { edges: any[], nodes: any[] } { + const nodes = []; + const edges = []; - removeFromSelection(proteinAc: string) { - if (!this.result.proteins || !proteinAc) { - return false; - } - const protein = this.getProtein(proteinAc); - if (!protein) { - return false; - } - this.analysis.removeProtein(protein); - } + const nodeAttributes = result.nodeAttributes || []; - public getProtein(ac: string): Protein | undefined { - return this.result.proteins.find((p) => p.proteinAc === ac); - } + for (let i = 0; i < result.networks.length; i++) { + const network = result.networks[i]; + const attributes = nodeAttributes[i] || {}; + const nodeTypes = attributes.nodeTypes || {}; + const isSeed = attributes.isSeed || {}; + const scores = attributes.scores || {}; - private mapProteinToNode(protein: any): any { - let color = '#e2b600'; - if (this.analysis.inSelection(protein)) { - color = '#48C774'; - } - return { - id: `p_${protein.proteinAc}`, - label: `${protein.proteinAc}`, - size: 10, color, shape: 'ellipse', shadow: false, - }; - } + for (const node of network.nodes) { + nodes.push(this.mapNode(node, nodeTypes[node] || this.inferNodeType(node), isSeed[node], scores[node])); + } - private mapDrugToNode(drug: any): any { - let color = '#ffffff'; - if (drug.status === 'investigational') { - color = '#ffa066'; - } else if (drug.status === 'approved') { - color = '#a0ff66'; + for (const edge of network.edges) { + edges.push(this.mapEdge(edge)); + } } - return { - id: `d_${drug.drugId}`, - label: `${drug.name}`, - size: 10, color, shape: 'ellipse', shadow: true, font: {color: '#000000', size: 5}, - }; - } - private mapProteinProteinInteractionToEdge(edge: any): any { return { - from: `p_${edge.from}`, - to: `p_${edge.to}`, - color: {color: '#afafaf', highlight: '#854141'}, - }; - } - - private mapDrugProteinInteractionToEdge(edge: any): any { - return { - from: `p_${edge.proteinAc}`, - to: `d_${edge.drugId}`, - color: {color: '#afafaf', highlight: '#854141'}, + nodes, + edges, }; } - private mapDataToNodes(result: any): { nodes: any[], edges: any[] } { - const nodes = []; - const edges = []; + private getNodeLooks(nodeId: string, nodeType: NodeType, isSeed: boolean): + { color: string, shape: string, size: number, font: any, shadow: boolean } { + let color = ''; + let shape = ''; + let size = 10; + let font = {}; + let shadow = false; - for (const protein of result.proteins) { - nodes.push(this.mapProteinToNode(protein)); + if (nodeType === 'host') { + shape = 'ellipse'; + if (this.analysis.idInSelection(nodeId)) { + color = '#c7661c'; + } else { + color = '#e2b600'; + } + size = 10; + } else if (nodeType === 'virus') { + shape = 'box'; + color = '#118AB2'; + size = 12; + font = {color: 'white'}; + shadow = true; + } else if (nodeType === 'drug') { + shape = 'ellipse'; + color = '#26b28b'; + size = 6; } - this.drugNodes = []; - for (const drug of result.drugs) { - this.drugNodes.push(this.mapDrugToNode(drug)); + if (isSeed) { + color = '#c064c7'; } - for (const network of result.networks) { - for (const edge of network.ppEdges) { - edges.push(this.mapProteinProteinInteractionToEdge(edge)); - } - } + return {color, shape, size, font, shadow}; + } - for (const edge of result.dpEdges) { - edges.push(this.mapDrugProteinInteractionToEdge(edge)); - } + private mapNode(nodeId: any, nodeType?: NodeType, isSeed?: boolean, score?: number): any { + const {shape, color, size, font, shadow} = this.getNodeLooks(nodeId, nodeType, isSeed); + return { + id: nodeId, + label: nodeId, + size, color, shape, font, shadow, + nodeType, isSeed, + }; + } + private mapEdge(edge: any): any { return { - nodes, - edges, + from: `${edge.from}`, + to: `${edge.to}`, + color: {color: '#afafaf', highlight: '#854141'}, }; } diff --git a/src/app/components/launch-analysis/launch-analysis.component.ts b/src/app/components/launch-analysis/launch-analysis.component.ts index 65c54814c2f01a9f350cbd2ebb054d46a917c217..a0a884f18e838e6ec2c7fb28001fe9feff4548da 100644 --- a/src/app/components/launch-analysis/launch-analysis.component.ts +++ b/src/app/components/launch-analysis/launch-analysis.component.ts @@ -30,7 +30,6 @@ export class LaunchAnalysisComponent implements OnInit { public multisteinerStrain = 'SARS_CoV2'; public multisteinerNumTrees = 5; - constructor(public analysis: AnalysisService) { } @@ -44,13 +43,13 @@ export class LaunchAnalysisComponent implements OnInit { public async startTask() { const parameters: any = { - proteins: this.analysis.getSelection().map((protein) => protein.proteinAc), + seeds: this.analysis.getSelection().map((protein) => protein.proteinAc), }; if (this.algorithm === 'dummy') { // No parameters for dummy } else if (this.algorithm === 'trustrank') { - parameters.strain = this.trustrankStrain; + parameters.strain_or_drugs = this.trustrankStrain; parameters.datasets = []; parameters.ignored_edge_types = []; parameters.damping_factor = this.trustrankDampingFactor; diff --git a/src/app/components/select-dataset/select-dataset.component.html b/src/app/components/select-dataset/select-dataset.component.html index 9c0a778996855f5f47374d6f89d2ba77b6acce82..b34ccf14ee5272f6ebe88f828e2c94d55d674c04 100644 --- a/src/app/components/select-dataset/select-dataset.component.html +++ b/src/app/components/select-dataset/select-dataset.component.html @@ -1,5 +1,5 @@ <div class="content"> - <ng-select [items]="datasetItems" bindLabel="label" [virtualScroll]="true" class="custom" + <ng-select [items]="datasetItems" bindLabel="id" [virtualScroll]="true" class="custom" placeholder="Select..." [ngModel]="selectedDataset" (ngModelChange)="select($event)"> <ng-template ng-option-tmp let-item="item"> {{item.label}} <br/> diff --git a/src/app/components/select-dataset/select-dataset.component.ts b/src/app/components/select-dataset/select-dataset.component.ts index 4b669c79d8126bacd803fd4aeaa94f5303d5f217..d2f327199bba6f1440bef7aa63ebf129ad49f15f 100644 --- a/src/app/components/select-dataset/select-dataset.component.ts +++ b/src/app/components/select-dataset/select-dataset.component.ts @@ -14,10 +14,8 @@ export class SelectDatasetComponent { @Input() datasetItems: Array<{label: string, datasets: string, data: Array<[string, string]>}>; public select(selectionItem) { - // console.log(selectionItem); this.selectedDataset = selectionItem; this.selectedDatasetChange.emit(selectionItem); - } } diff --git a/src/app/components/task-list/task-list.component.html b/src/app/components/task-list/task-list.component.html index 7dfbe838288023320f5091307503d62e7572fb1e..eb672041421659e03b2079856c9a316419cad7bd 100644 --- a/src/app/components/task-list/task-list.component.html +++ b/src/app/components/task-list/task-list.component.html @@ -2,26 +2,41 @@ <div class="list is-hoverable"> <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)"> - <span><b>{{task.info.algorithm}} (Queued)</b></span> - <span class="icon"><i class="fas fa-pause" aria-hidden="true"></i></span><br> - Queue Length: {{task.stats.queueLength}}<br> - Queue Position:{{task.stats.queuePosition}} + <p> + <span class="is-capitalized">{{task.info.algorithm}}</span> + <span class="icon is-pulled-right"><i class="fas fa-pause" aria-hidden="true"></i></span> + </p> + <p> + <small>Queue position: {{task.stats.queuePosition}}/{{task.stats.queueLength}}</small> + </p> </div> - <div *ngIf="task.info.startedAt && !task.info.done && !task.info.failed" (click)="open(task.token)"> - <span><b>{{task.info.algorithm}} ({{task.info.startedAt | date :'short'}})</b></span> - <span class="icon"><i class="fas fa-spinner" aria-hidden="true"></i></span> - <br> - <progress class="progress is-primary" [value]="task.info.progress * 100" max="100"></progress> + <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="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> </div> - <div *ngIf="task.info.failed && task.info.finishedAt == null"> - <span><b>{{task.info.algorithm}} ({{task.info.startedAt | date :'short'}})</b></span> - <span class="icon"><i class="fas fa-exclamation-triangle" aria-hidden="true"></i> - </span> + <div *ngIf="task.info.done" (click)="open(task.token)"> + <p> + <span class="is-capitalized">{{task.info.algorithm}}</span> + <span class="icon is-pulled-right"><i class="fas fa-check" aria-hidden="true"></i></span> + </p> + <p> + <small>Finished {{task.info.finishedAt | date :'short'}}</small> + </p> </div> - <div *ngIf="task.info.done && !task.info.failed" (click)="open(task.token)"> - <span><b>{{task.info.algorithm}} ({{task.info.startedAt | date :'short'}})</b></span> - <span class="icon"><i class="fas fa-check" aria-hidden="true"></i> - </span> + <div *ngIf="task.info.failed && task.info.finishedAt == null"> + <p> + <span class="is-capitalized">{{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"> + <small>{{task.info.status}}</small> + </p> </div> </a> </div> diff --git a/src/app/components/task-list/task-list.component.scss b/src/app/components/task-list/task-list.component.scss index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..77578f190c872199c9f716929eff46e33d49e471 100644 --- a/src/app/components/task-list/task-list.component.scss +++ b/src/app/components/task-list/task-list.component.scss @@ -0,0 +1,10 @@ +.list { + p { + margin-bottom: 0; + } + + progress { + margin-top: 5px; + margin-bottom: 5px; + } +} diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts index b8e87b54867bb3dcb417dcad587f8b7455d6ec18..6a5341edfb8199db014c047fd00534fc836d1593 100644 --- a/src/app/interfaces.ts +++ b/src/app/interfaces.ts @@ -1,3 +1,5 @@ +export type NodeType = 'host' | 'virus' | 'drug'; + export interface Protein { name: string; proteinAc: string; @@ -25,7 +27,7 @@ export interface ProteinViralInteraction { export interface Task { token: string; info: { - algorithm: string; + algorithm: 'trustrank' | 'multisteiner' | 'keypathwayminer'; parameters?: { [key: string]: any }; workerId?: string; diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 80a425d9fd098e76485aebbd1c1d1fea237d6956..b9d22f7d3cf3d81285052068325fd2b11fc44e9b 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -266,9 +266,9 @@ <div class="card bar-large"> <header class="card-header"> <p class="card-header-title"> - <span class="icon"> - <i class="fas fa-filter" aria-hidden="true"></i> - </span> Tasks + <span class="icon"> + <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"> <span class="icon"> @@ -281,7 +281,7 @@ <app-task-list [(token)]="selectedAnalysisToken"></app-task-list> </div> <footer class="card-footer"> - <a (click)="analysis.removeAllTasks();" 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> diff --git a/src/app/pages/explorer-page/explorer-page.component.ts b/src/app/pages/explorer-page/explorer-page.component.ts index 92db3b0fb00451c85d59f4a314fdf98900791ea5..6c2f6c1b1c42f989443187d4f816fd0897aade95 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -58,15 +58,19 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public currentDataset = []; private screenshotArray = [0]; - public datasetItems: Array<{ label: string, datasets: string, data: Array<[string, string]> }> = [ - {label: 'All', datasets: 'TUM & Krogan', data: [['TUM', 'HCoV'], ['TUM', 'SARS-CoV2'], ['Krogan', 'SARS-CoV2']]}, - {label: 'HCoV', datasets: 'TUM', data: [['TUM', 'HCoV']]}, - {label: 'CoV2', datasets: 'TUM & Krogan', data: [['TUM', 'SARS-CoV2'], ['Krogan', 'SARS-CoV2']]}, - {label: 'CoV2', datasets: 'Krogan', data: [['Krogan', 'SARS-CoV2']]}, - {label: 'CoV2', datasets: 'TUM', data: [['TUM', 'SARS-CoV2']]}]; - - public selectedDataset = this.datasetItems[0]; - + public datasetItems: Array<{ id: string, label: string, datasets: string, data: Array<[string, string]> }> = [ + { + id: 'All (TUM & Krogan)', + label: 'All', + datasets: 'TUM & Krogan', + 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 (Krogan)', label: 'CoV2', datasets: 'Krogan', data: [['Krogan', 'SARS-CoV2']]}, + {id: 'CoV2 (TUM)', label: 'CoV2', datasets: 'TUM', data: [['TUM', 'SARS-CoV2']]}]; + + public selectedDataset = this.datasetItems[3]; @ViewChild('network', {static: false}) networkEl: ElementRef; @@ -132,8 +136,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { async ngAfterViewInit() { if (!this.network) { - this.selectedDataset = this.datasetItems[4]; - await this.createNetwork(this.datasetItems[4].data); + this.selectedDataset = this.datasetItems[3]; + await this.createNetwork(this.selectedDataset.data); } }