From da5b859697ff9397c599ccfe130e2ba3eb3c0716 Mon Sep 17 00:00:00 2001 From: Julian Matschinske <ge93nar@mytum.de> Date: Mon, 6 Apr 2020 21:19:23 +0200 Subject: [PATCH] Adapt Analysis Window --- src/app/analysis.service.ts | 4 + .../analysis-window.component.html | 2 +- .../analysis-window.component.ts | 248 ++++++++---------- .../launch-analysis.component.ts | 5 +- .../select-dataset.component.html | 2 +- .../select-dataset.component.ts | 2 - .../task-list/task-list.component.html | 49 ++-- .../task-list/task-list.component.scss | 10 + src/app/interfaces.ts | 4 +- .../explorer-page.component.html | 8 +- .../explorer-page/explorer-page.component.ts | 26 +- 11 files changed, 181 insertions(+), 179 deletions(-) diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index e90aade5..f1e5a289 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 f6e4f608..ba091706 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 e89bce3a..e9f18dd9 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 65c54814..a0a884f1 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 9c0a7789..b34ccf14 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 4b669c79..d2f32719 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 7dfbe838..eb672041 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 e69de29b..77578f19 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 b8e87b54..6a5341ed 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 80a425d9..b9d22f7d 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 92db3b0f..6c2f6c1b 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); } } -- GitLab