diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index 30b5b4f3ba7799a3dc22f0de04560c5306e95611..57170bab89a556cc9716fe9dfc46ce0bb9fcb91c 100644 --- a/src/app/analysis.service.ts +++ b/src/app/analysis.service.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {Protein} from './pages/protein-network'; +import {Protein, Task} from './interfaces'; import {Subject} from 'rxjs'; import {HttpClient} from '@angular/common/http'; import {environment} from '../environments/environment'; @@ -13,9 +13,8 @@ export class AnalysisService { private selectedProteins = new Map<string, Protein>(); private selectSubject = new Subject<{ protein: Protein, selected: boolean }>(); - public tokens: any[] = []; - private stats: any; - public tasks: any[] = []; + public tokens: string[] = []; + public tasks: Task[] = []; private intervalId: any; @@ -64,27 +63,6 @@ export class AnalysisService { }); } - getTask(token): any { - this.tasks.forEach((task) => { - if (task.token === token) { - return task; - } - }); - } - - reset() { - this.tokens = null; - this.tasks = null; - this.stats = null; - if (this.intervalId) { - clearInterval(this.intervalId); - } - } - - getStats(): any { - return this.stats; - } - async startAnalysis(algorithm, parameters) { const resp = await this.http.post<any>(`${environment.backend}task/`, { algorithm, @@ -96,11 +74,13 @@ export class AnalysisService { } startWatching() { - this.intervalId = setInterval(async () => { + const watch = async () => { if (this.tokens.length > 0) { this.tasks = await this.getTasks(); } - }, 1000); + }; + watch(); + this.intervalId = setInterval(watch, 5000); } } diff --git a/src/app/api.service.ts b/src/app/api.service.ts deleted file mode 100644 index 8e8ead34ef7538f8feda11eee02c9cbed1b33374..0000000000000000000000000000000000000000 --- a/src/app/api.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; -import { environment } from '../environments/environment'; - - -const networkUrl = `${environment.backend}` + 'network/'; -@Injectable({ - providedIn: 'root' -}) -export class ApiService { - - constructor(private http: HttpClient) { } - - async getNetwork(dataset: Array<[string, string]>) { - const data = JSON.stringify(dataset); - const params = new HttpParams().set('data', data); - return this.http.get(networkUrl, {params}).toPromise(); - } - -} diff --git a/src/app/components/analysis-window/analysis-window.component.html b/src/app/components/analysis-window/analysis-window.component.html index f8f3f26ce5515a0f1153e30a754e96ae1918331e..681e71e2f0fea3c28fe3ef13ebf2f5b5328a9165 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="info && info.done"> + <div class="card analysis" *ngIf="task && task.info.done"> <header class="card-header"> <p class="card-header-title"> <span class="icon"> @@ -25,7 +25,7 @@ </footer> </div> - <div class="card analysis" *ngIf="info && !info.startedAt"> + <div class="card analysis" *ngIf="task && !task.info.startedAt"> <header class="card-header"> <p class="card-header-title"> <span class="icon"> @@ -49,7 +49,7 @@ </footer> </div> - <div class="card analysis" *ngIf="info && info.startedAt && !info.done"> + <div class="card analysis" *ngIf="task && task.info.startedAt && !task.info.done"> <header class="card-header"> <p class="card-header-title"> <span class="icon"> @@ -73,7 +73,7 @@ </footer> </div> - <div class="card analysis" *ngIf="!info"> + <div class="card analysis" *ngIf="!task"> <header class="card-header"> <p class="card-header-title"> <span class="icon"> diff --git a/src/app/components/analysis-window/analysis-window.component.ts b/src/app/components/analysis-window/analysis-window.component.ts index 2516905c582fb7547b69f751f2749f20166cd890..0c5551c6691d2e2ae81954192fbe701d49421152 100644 --- a/src/app/components/analysis-window/analysis-window.component.ts +++ b/src/app/components/analysis-window/analysis-window.component.ts @@ -1,8 +1,8 @@ import {Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {environment} from '../../../environments/environment'; -import {Edge, Effect, getDatasetFilename, Protein, ProteinNetwork} from '../../pages/protein-network'; import {AnalysisService} from '../../analysis.service'; +import {Task} from '../../interfaces'; declare var vis: any; @@ -16,8 +16,7 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { @Input() token: string | null = null; @Output() tokenChange = new EventEmitter<string | null>(); - public info: any = null; - public stats: any = null; + public task: Task | null = null; @ViewChild('network', {static: false}) networkEl: ElementRef; @@ -33,13 +32,18 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { } async ngOnChanges(changes: SimpleChanges) { + await this.refresh(); + } + + private async refresh() { if (this.token) { - const {info, stats} = await this.getTask(this.token); - this.info = info; - this.stats = stats; + this.task = await this.getTask(this.token); - if (this.info && this.info.done) { + if (this.task && this.task.info.done) { const result = await this.http.get<any>(`${environment.backend}result/?token=${this.token}`).toPromise(); + this.networkEl.nativeElement.innerHTML = ''; + this.network = null; + this.nodeData = {nodes: null, edges: null}; this.createNetwork(result); } } @@ -75,6 +79,10 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { }; this.network = new vis.Network(container, this.nodeData, options); + + this.network.on('select', () => { + // TODO + }); } private mapProteinToNode(protein: any): any { diff --git a/src/app/components/query/query.component.ts b/src/app/components/query/query.component.ts index 086fada3a0b33b31f78bfa0ed73fd902303a4d58..7426d3d9ad0b5cf0c48e582d2ba78831a3e1c405 100644 --- a/src/app/components/query/query.component.ts +++ b/src/app/components/query/query.component.ts @@ -1,5 +1,5 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; -import {Protein} from '../../pages/protein-network'; +import {Protein} from '../../interfaces'; @Component({ selector: 'app-query-component', diff --git a/src/app/components/select-dataset/select-dataset.component.ts b/src/app/components/select-dataset/select-dataset.component.ts index 877eed0df4c905cf8967c4497a5af123924818da..28223fbd55feeabf350f4b7cb62835bf9c32664a 100644 --- a/src/app/components/select-dataset/select-dataset.component.ts +++ b/src/app/components/select-dataset/select-dataset.component.ts @@ -1,6 +1,4 @@ import {Component, EventEmitter, Input, Output} from '@angular/core'; -import {DataSet} from 'vis-data'; -import {Edge, Effect, Protein} from '../../pages/protein-network'; @Component({ selector: 'app-select-dataset', diff --git a/src/app/components/task-list/task-list.component.html b/src/app/components/task-list/task-list.component.html index 470df2baf919bb9d194cecc31e3b7a668d2e8fc0..a776237931000f2df630f7bcd841f2a630c01040 100644 --- a/src/app/components/task-list/task-list.component.html +++ b/src/app/components/task-list/task-list.component.html @@ -1,20 +1,19 @@ <div class="content"> <div class="list is-hoverable"> - <a *ngFor="let task of analysis.tasks" class="list-item"> - <div *ngIf="!task.info.startedAt"> - <a (click)="open(task.token)"><b>Algorithm: {{task.info.algorithm}}</b></a><br> + <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)"> + <b>Algorithm: {{task.info.algorithm}}</b><br> Queue Length: {{task.stats.queueLength}}<br> Queue Position:{{task.stats.queuePosition}} </div> - <div *ngIf="task.info.startedAt && !task.info.done"> - <a (click)="open(task.token)">Algorithm: {{task.info.algorithm}}</a><br> + <div *ngIf="task.info.startedAt && !task.info.done" (click)="open(task.token)"> + <a>Algorithm: {{task.info.algorithm}}</a><br> <progress class="progress is-primary" [value]="task.info.progress * 100" max="100"></progress> </div> - <div *ngIf="task.info.done"> - <a (click)="open(task.token)"><span>Algorithm: {{task.info.algorithm}}</span> - <span class="icon is-success"><i class="fas fa-check is-success" - aria-hidden="true"></i> - </span></a> + <div *ngIf="task.info.done" (click)="open(task.token)"> + <span>Algorithm: {{task.info.algorithm}}</span> + <span class="icon is-success"><i class="fas fa-check is-success" aria-hidden="true"></i> + </span> </div> </a> </div> diff --git a/src/app/components/task-list/task-list.component.ts b/src/app/components/task-list/task-list.component.ts index 0ada658a9185ad41c0fe51195b935e787c1b0893..8e3591562167dadd60652d60e9aa38b32e44820f 100644 --- a/src/app/components/task-list/task-list.component.ts +++ b/src/app/components/task-list/task-list.component.ts @@ -10,8 +10,8 @@ import {AnalysisService} from '../../analysis.service'; export class TaskListComponent implements OnInit { - - @Output() token: EventEmitter<any> = new EventEmitter(); + @Input() token: string; + @Output() tokenChange: EventEmitter<string> = new EventEmitter(); constructor(public analysis: AnalysisService) { } @@ -20,7 +20,8 @@ export class TaskListComponent implements OnInit { } open(token) { - this.token.emit(token); + this.token = token; + this.tokenChange.emit(token); } } diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts new file mode 100644 index 0000000000000000000000000000000000000000..845b04589ee0a4fc15df9cec4147a2902a79719b --- /dev/null +++ b/src/app/interfaces.ts @@ -0,0 +1,48 @@ +export interface Protein { + name: string; + proteinAc: string; + effects?: ViralProtein[]; + x?: number; + y?: number; +} + +export interface ViralProtein { + effectName: string; + virusName: string; + datasetName: string; + proteins?: Protein[]; + x?: number; + y?: number; +} + +export interface ProteinViralInteraction { + effectName: string; + virusName: string; + datasetName: string; + proteinAc: string; +} + +export interface Task { + token: string; + info: { + algorithm: string; + parameters?: { [key: string]: any }; + + workerId?: string; + jobId?: string; + + progress: number; + status: string; + + createdAt: string; + startedAt: string; + finishedAt: string; + + done: boolean; + failed: boolean; + }; + stats: { + queuePosition: number; + queueLength: number; + }; +} diff --git a/src/app/pages/protein-network.ts b/src/app/main-network.ts similarity index 77% rename from src/app/pages/protein-network.ts rename to src/app/main-network.ts index efa08b44ddd7a7918784ce09f77e1298a784a2b3..b0595cd5c5c0bd5dae4188ebbb6cbfc853225df7 100644 --- a/src/app/pages/protein-network.ts +++ b/src/app/main-network.ts @@ -1,28 +1,5 @@ import {HttpClient} from '@angular/common/http'; - -export interface Protein { - name: string; - proteinAc: string; - effects?: Effect[]; - x?: number; - y?: number; -} - -export interface Effect { - effectName: string; - virusName: string; - datasetName: string; - proteins?: Protein[]; - x?: number; - y?: number; -} - -export interface Edge { - effectName: string; - virusName: string; - datasetName: string; - proteinAc: string; -} +import {ProteinViralInteraction, ViralProtein, Protein} from './interfaces'; export function getDatasetFilename(dataset: Array<[string, string]>): string { return `network-${JSON.stringify(dataset).replace(/[\[\]\",]/g, '')}.json`; @@ -30,7 +7,7 @@ export function getDatasetFilename(dataset: Array<[string, string]>): string { export class ProteinNetwork { - constructor(public proteins: Protein[], public effects: Effect[], public edges: Edge[]) { + constructor(public proteins: Protein[], public effects: ViralProtein[], public edges: ProteinViralInteraction[]) { } public async loadPositions(http: HttpClient, dataset: Array<[string, string]>) { @@ -55,7 +32,7 @@ export class ProteinNetwork { return this.proteins.find((p) => p.proteinAc === ac); } - public getEffect(name: string, virus: string, dataset: string): Effect | undefined { + public getEffect(name: string, virus: string, dataset: string): ViralProtein | undefined { return this.effects.find((eff) => eff.effectName === name && eff.virusName === virus && eff.datasetName === dataset); } diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 5a75d80cec96652b3500416b8fddef135953b272..3893ce6bf6ff16431e5a3f95b0aae83d2f197007 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -203,7 +203,7 @@ </p> </header> <div class="card-content"> - <app-task-list (token)="selectedAnalysisToken = $event"></app-task-list> + <app-task-list [(token)]="selectedAnalysisToken"></app-task-list> </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 3f900b5355863f1352ca7c2901985931b3cc6739..159066e9a6186f461c8d45f7ee564214a83bc3b1 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -3,15 +3,15 @@ import { Component, ElementRef, OnInit, - ViewChild, - HostListener + ViewChild } from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; -import {Edge, Effect, getDatasetFilename, Protein, ProteinNetwork} from '../protein-network'; -import {HttpClient} from '@angular/common/http'; -import {ApiService} from '../../api.service'; +import {ProteinViralInteraction, ViralProtein, Protein} from '../../interfaces'; +import {ProteinNetwork, getDatasetFilename} from '../../main-network'; +import {HttpClient, HttpParams} from '@angular/common/http'; import {AnalysisService} from '../../analysis.service'; import html2canvas from 'html2canvas'; +import {environment} from '../../../environments/environment'; declare var vis: any; @@ -28,9 +28,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public geneNames: Array<string> = []; public proteinNames: Array<string> = []; public proteinAcs: Array<string> = []; - public watcher = 0; - public viralProteinCheckboxes: Array<{ checked: boolean; data: Effect }> = []; + public viralProteinCheckboxes: Array<{ checked: boolean; data: ViralProtein }> = []; public proteinData: ProteinNetwork; @@ -41,8 +40,6 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { private network: any; private nodeData: { nodes: any, edges: any } = {nodes: null, edges: null}; - private seed = 1; // TODO: Remove this - private dumpPositions = false; public physicsEnabled = false; @@ -63,33 +60,9 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { @ViewChild('network', {static: false}) networkEl: ElementRef; - @HostListener('window:keydown', ['$event']) - handleKeyboardEvent1(event: KeyboardEvent) { - - const keyName = event.key; - - if (keyName === 'Control') { - this.watcher = 1; - // console.log(this.watcher); - - } - } - - @HostListener('window:keyup', ['$event']) - handleKeyboardEvent(event: KeyboardEvent) { - - const keyName1 = event.key; - if (keyName1 === 'Control') { - this.watcher = 0; - // console.log(this.watcher); - - } - } - constructor(private http: HttpClient, private route: ActivatedRoute, private router: Router, - private api: ApiService, public analysis: AnalysisService) { this.geneNames.push('IFI16'); this.proteinNames.push('Gamma-interface-inducible protein 16'); @@ -127,6 +100,9 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.analysis.subscribe((protein, selected) => { const nodeId = `pg_${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; @@ -156,7 +132,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { private async getNetwork(dataset: Array<[string, string]>) { this.currentDataset = dataset; - const data: any = await this.api.getNetwork(dataset); + const params = new HttpParams().set('data', JSON.stringify(dataset)); + const data = await this.http.get<any>(`${environment.backend}network/`, {params}).toPromise(); this.proteins = data.proteins; this.effects = data.effects; this.edges = data.edges; @@ -237,25 +214,18 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }; this.network = new vis.Network(container, this.nodeData, options); - this.network.on('click', (properties) => { + this.network.on('select', (properties) => { const id: Array<string> = properties.nodes; - // TODO use groupID if (id.length > 0) { if (id[0].startsWith('pg_')) { const protein = this.proteinData.getProtein(id[0].substr(3)); this.openSummary(protein, false); - // tslint:disable-next-line:no-console - console.log(this.currentProteinAc); - if (this.watcher === 1) { + if (properties.event.srcEvent.ctrlKey) { if (this.inSelection(protein.proteinAc) === true) { - // tslint:disable-next-line:no-console - console.log(this.removeFromSelection(protein.proteinAc)); + this.removeFromSelection(protein.proteinAc); } else { - // tslint:disable-next-line:no-console - console.log(this.addToSelection(protein.proteinAc)); - // console.log(this.removeFromSelection(this.currentProteinAc)); - // tslint:disable-next-line:no-console - console.log(this.analysis.getCount()); + this.addToSelection(protein.proteinAc); + this.analysis.getCount(); } } } else { @@ -291,7 +261,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { const connectedProteinAcs = new Set<string>(); this.viralProteinCheckboxes.forEach((cb) => { - const effects: Array<Effect> = []; + const effects: Array<ViralProtein> = []; this.proteinData.effects.forEach((effect) => { if (effect.effectName === cb.data.effectName) { effects.push(effect); @@ -325,11 +295,9 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { if (!found) { const node = this.mapProteinToNode(protein); - // this.nodeData.nodes.add(node); addNodes.set(node.id, node); } } else if (found) { - // this.nodeData.nodes.remove(nodeId); removeIds.add(nodeId); } } @@ -365,7 +333,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }; } - private mapEffectToNode(effect: Effect): any { + private mapEffectToNode(effect: ViralProtein): any { return { id: `eff_${effect.effectName}_${effect.virusName}_${effect.datasetName}`, label: `${effect.effectName} (${effect.virusName}, ${effect.datasetName})`, @@ -375,7 +343,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }; } - private mapEdge(edge: Edge): any { + private mapEdge(edge: ProteinViralInteraction): any { return { from: `pg_${edge.proteinAc}`, to: `eff_${edge.effectName}_${edge.virusName}_${edge.datasetName}`, @@ -441,8 +409,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public toCanvas() { this.array.forEach((key, index) => { const elem = document.getElementById(index.toString()); - // tslint:disable-next-line:only-arrow-functions - html2canvas(elem).then(function(canvas) { + html2canvas(elem).then((canvas) => { const generatedImage = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); const a = document.createElement('a'); a.href = generatedImage; @@ -452,6 +419,4 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }); } - - } diff --git a/src/styles.scss b/src/styles.scss index 3124c5321156b285f1bed68933802cbd68890de6..f241c27e9d54278bfb2230ed5007f2b0a414fadb 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -88,12 +88,12 @@ div.card.bar-medium { div.card.bar-large { margin-bottom: 15px; - height: 600px; + max-height: 600px; } div.card-content.overflow { overflow: auto; - height: 500px; + max-height: 500px; } div.covex.left-window { diff --git a/tslint.json b/tslint.json index 8f6066bace2e689e1ebcebaf493d2578ac93f1c3..e102758ef9e8b00d21d0ab1294cfec7f93c770b5 100644 --- a/tslint.json +++ b/tslint.json @@ -73,6 +73,8 @@ "no-output-on-prefix": true, "no-output-rename": true, "no-outputs-metadata-property": true, + "no-unused-expression": true, + "no-unused-variable": true, "template-banana-in-box": true, "template-no-negated-async": true, "use-lifecycle-interface": true,