diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index 7df84dc6fc5a81c44c64da68e2eb9c37ea100beb..1691bab371a7260ebce49ab77dafc3205d6aa73a 100644 --- a/src/app/analysis.service.ts +++ b/src/app/analysis.service.ts @@ -1,4 +1,4 @@ -import {Wrapper, Task, getWrapperFromProtein, getWrapperFromViralProtein} from './interfaces'; +import {Wrapper, Task, getWrapperFromProtein, getWrapperFromViralProtein, Protein, ViralProtein} from './interfaces'; import {Subject} from 'rxjs'; import {HttpClient} from '@angular/common/http'; import {environment} from '../environments/environment'; @@ -121,6 +121,14 @@ export class AnalysisService { return this.selectedItems.has(wrapper.nodeId); } + proteinInSelection(protein: Protein): boolean { + return this.inSelection(getWrapperFromProtein(protein)); + } + + viralProteinInSelection(viralProtein: ViralProtein): boolean { + return this.inSelection(getWrapperFromViralProtein(viralProtein)); + } + removeItem(wrapper: Wrapper) { const item = this.selectedItems.get(wrapper.nodeId); if (this.selectedItems.delete(wrapper.nodeId)) { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 28e0dc75c971e22fb55d67042e90aabececd9b49..7b18458666091942ae82c263d56ea70c0d0b26ae 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -48,8 +48,7 @@ import {AnalysisService} from './analysis.service'; TableModule, ], providers: [AnalysisService], - bootstrap: [AppComponent] - + bootstrap: [AppComponent], }) export class AppModule { } diff --git a/src/app/components/analysis-window/analysis-window.component.html b/src/app/components/analysis-window/analysis-window.component.html index 94a5c4c38a351448b8de5a2fee8bf966fd2db3f5..3fa8e420fd15ecfd1ad60e44fc7025adecaa4c6b 100644 --- a/src/app/components/analysis-window/analysis-window.component.html +++ b/src/app/components/analysis-window/analysis-window.component.html @@ -71,48 +71,40 @@ </div> </div> <footer class="card-footer toolbar"> - <div class="field has-addons"> - <p class="control"> - <button class="button is-rounded is-success is-rounded" [disabled]="true"> - <span class="icon"> - <i class="fas fa-cloud-download-alt" aria-hidden="true"></i> - </span> - <span> - Export Results - </span> - </button> - </p> - <p class="control"> - <button class="button is-primary is-rounded" [disabled]="true"> - <span class="icon"> - <i class="fas fa-camera" aria-hidden="true"></i> - </span> + <div class="field"> + <p class="control footer-buttons"> + <button class="button is-primary is-rounded" (click)="toCanvas()"> + <span class="icon"> + <i class="fas fa-camera" aria-hidden="true"></i> + </span> <span> - Screenshot - </span> + Screenshot + </span> </button> </p> - <p class="control"> + </div> + <div class="field"> + <p class="control footer-buttons"> <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> + <span class="icon"> + <i class="fas fa-trash" aria-hidden="true"></i> + </span> <span> - Delete Analysis - </span> + Delete Analysis + </span> </button> </p> </div> - <app-toggle *ngIf="task.info.target === 'drug-target'" class="footer-toggle-buttons" textOn="Drugs On" textOff="Off" + <app-toggle *ngIf="task.info.target === 'drug-target'" class="footer-buttons" textOn="Drugs On" textOff="Off" [value]="showDrugs" (valueChange)="toggleDrugs($event)" icon="fa-capsules"></app-toggle> - <app-toggle class="footer-toggle-buttons" textOn="Animation On" textOff="Off" + <app-toggle class="footer-buttons" textOn="Animation On" textOff="Off" [value]="physicsEnabled" (valueChange)="updatePhysicsEnabled($event)" icon="fa-wind"></app-toggle> </footer> </div> <div class="content tab-content scrollable" *ngIf="task && task.info.done" [class.is-visible]="tab === 'table'"> - <div class="field has-addons footer-toggle-buttons" *ngIf="tableHasScores"> + <div class="field has-addons" *ngIf="tableHasScores"> <p class="control"> <button class="button is-rounded" [class.is-primary]="tableNormalize" (click)="toggleNormalization(true)"> <span class="icon is-small"> @@ -164,7 +156,7 @@ <td><a href="https://www.drugbank.ca/drugs/{{ e.drugId }}" target="_blank">{{ e.drugId }}</a></td> <td>{{e.name}}</td> <td>{{e.status}}</td> - <td *ngIf="tableHasScores">{{e.score || ''}}</td> + <td *ngIf="tableHasScores">{{e.score ? (e.score | number) : ''}}</td> </tr> </ng-template> </p-table> @@ -179,11 +171,16 @@ <span>Download</span> </a> </div> - <p-table *ngIf="tableProteins.length > 0" [value]="tableProteins"> + <p-table *ngIf="tableProteins.length > 0" selectionMode="multiple" + [value]="tableProteins" [selection]="tableSelectedProteins" dataKey="proteinAc" + (selectionChange)="tableProteinSelection($event)"> <ng-template pTemplate="header"> <tr> + <th class="checkbox-col"> + <p-tableHeaderCheckbox></p-tableHeaderCheckbox> + </th> <th [pSortableColumn]="'proteinAc'"> - AC + UniProt Code <p-sortIcon [field]="'proteinAc'"></p-sortIcon> </th> <th [pSortableColumn]="'name'"> @@ -198,9 +195,12 @@ </ng-template> <ng-template pTemplate="body" let-e> <tr> + <td> + <p-tableCheckbox [value]="e"></p-tableCheckbox> + </td> <td><a href="https://www.uniprot.org/uniprot/{{ e.proteinAc }}" target="_blank">{{ e.proteinAc }}</a></td> <td>{{e.name}}</td> - <td *ngIf="tableHasScores">{{e.score || ''}}</td> + <td *ngIf="tableHasScores">{{e.score ? (e.score | number) : ''}}</td> </tr> </ng-template> </p-table> @@ -215,28 +215,36 @@ <span>Download</span> </a> </div> - <p-table *ngIf="tableViralProteins.length > 0" [value]="tableViralProteins"> + <p-table *ngIf="tableViralProteins.length > 0" selectionMode="multiple" + [value]="tableViralProteins" [selection]="tableSelectedViralProteins" dataKey="effectId" + (selectionChange)="tableViralProteinSelection($event)"> <ng-template pTemplate="header"> <tr> - <td [pSortableColumn]="'effectName'"> + <th class="checkbox-col"> + <p-tableHeaderCheckbox></p-tableHeaderCheckbox> + </th> + <th [pSortableColumn]="'effectName'"> Name <p-sortIcon [field]="'effectName'"></p-sortIcon> - </td> - <td [pSortableColumn]="'virusName'"> + </th> + <th [pSortableColumn]="'virusName'"> Virus Strain <p-sortIcon [field]="'virusName'"></p-sortIcon> - </td> - <td *ngIf="tableHasScores" [pSortableColumn]="'score'"> + </th> + <th *ngIf="tableHasScores" [pSortableColumn]="'score'"> Score <p-sortIcon [field]="'score'"></p-sortIcon> - </td> + </th> </tr> </ng-template> <ng-template pTemplate="body" let-e> <tr> + <td> + <p-tableCheckbox [value]="e"></p-tableCheckbox> + </td> <td>{{e.effectName}}</td> <td>{{e.virusName}}</td> - <td *ngIf="tableHasScores">{{e.score || ''}}</td> + <td *ngIf="tableHasScores">{{e.score ? (e.score | number) : ''}}</td> </tr> </ng-template> </p-table> diff --git a/src/app/components/analysis-window/analysis-window.component.scss b/src/app/components/analysis-window/analysis-window.component.scss index e957daa66c148fb3ae5fcc09f5cd8615ec37e8c7..09f93625591875a8866ef1721c671e11e6babc2f 100644 --- a/src/app/components/analysis-window/analysis-window.component.scss +++ b/src/app/components/analysis-window/analysis-window.component.scss @@ -31,3 +31,7 @@ div.network { .table-header { margin-bottom: 50px; } + +.checkbox-col { + width: 50px; +} diff --git a/src/app/components/analysis-window/analysis-window.component.ts b/src/app/components/analysis-window/analysis-window.component.ts index 8ac5a0e127b39ccbd9100c9e6b55f07b46ff07c4..e167fc61194838cc7c78685efa3e898c96677f44 100644 --- a/src/app/components/analysis-window/analysis-window.component.ts +++ b/src/app/components/analysis-window/analysis-window.component.ts @@ -13,8 +13,19 @@ import {HttpClient, HttpErrorResponse} from '@angular/common/http'; import {environment} from '../../../environments/environment'; import {AnalysisService, algorithmNames} from '../../analysis.service'; import { - Protein, Task, ViralProtein, Drug, Wrapper, WrapperType, - getWrapperFromProtein, getWrapperFromDrug, getWrapperFromViralProtein, getNodeIdsFromPDI, getNodeIdsFromPPI + Protein, + Task, + ViralProtein, + Drug, + Wrapper, + WrapperType, + getWrapperFromProtein, + getWrapperFromDrug, + getWrapperFromViralProtein, + getNodeIdsFromPDI, + getNodeIdsFromPPI, + getViralProteinNodeId, + getProteinNodeId } from '../../interfaces'; import html2canvas from 'html2canvas'; import {toast} from 'bulma-toast'; @@ -59,7 +70,9 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { public tableDrugs: Array<Drug & Scored> = []; public tableProteins: Array<Protein & Scored> = []; + public tableSelectedProteins: Array<Protein & Scored> = []; public tableViralProteins: Array<ViralProtein & Scored> = []; + public tableSelectedViralProteins: Array<ViralProtein & Scored> = []; public tableNormalize = false; public tableHasScores = false; @@ -98,12 +111,17 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { this.network = new vis.Network(container, this.nodeData, options); - const promises: Promise<any>[] = []; promises.push(this.http.get<any>(`${environment.backend}task_result/?token=${this.token}&view=proteins`).toPromise() .then((table) => { this.tableProteins = table; - this.tableProteins.forEach((r) => r.rawScore = r.score); + this.tableSelectedProteins = []; + this.tableProteins.forEach((r) => { + r.rawScore = r.score; + if (this.analysis.proteinInSelection(r)) { + this.tableSelectedProteins.push(r); + } + }); })); promises.push(this.http.get<any>(`${environment.backend}task_result/?token=${this.token}&view=viral_proteins`).toPromise() .then((table) => { @@ -156,8 +174,31 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { } }); - this.analysis.subscribe((item, selected) => { + if (item.type === 'host') { + // TODO: Refactor! + const found = this.tableSelectedProteins.findIndex((i) => getProteinNodeId(i) === item.nodeId); + const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.nodeId); + if (selected && found === -1 && tableItem) { + this.tableSelectedProteins.push(tableItem); + } + if (!selected && found !== -1 && tableItem) { + this.tableSelectedProteins.splice(found, 1); + } + this.tableSelectedProteins = [...this.tableSelectedProteins]; + } else if (item.type === 'virus') { + // TODO: Refactor! + const found = this.tableSelectedViralProteins.findIndex((i) => getViralProteinNodeId(i) === item.nodeId); + const tableItem = this.tableViralProteins.find((i) => getViralProteinNodeId(i) === item.nodeId); + if (selected && found === -1 && tableItem) { + this.tableSelectedViralProteins.push(tableItem); + } + if (!selected && found !== -1 && tableItem) { + this.tableSelectedViralProteins.splice(found, 1); + } + this.tableSelectedViralProteins = [...this.tableSelectedViralProteins]; + } + const node = this.nodeData.nodes.get(item.nodeId); if (!node) { return; @@ -192,10 +233,6 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { this.emitVisibleItems(false); } - export() { - - } - public toggleNormalization(normalize: boolean) { this.tableNormalize = normalize; @@ -351,9 +388,9 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { this.drugNodes = []; this.drugEdges = []; if (this.showDrugs) { - const proteinAcs = this.proteins.map((protein) => protein.proteinAc); - // tslint:disable-next-line:max-line-length - const result = await this.http.get<any>(`${environment.backend}drug_interactions/?proteins=${JSON.stringify(proteinAcs)}`).toPromise().catch((err: HttpErrorResponse) => { + const result = await this.http.get<any>( + `${environment.backend}drug_interactions/?token=${this.token}`).toPromise().catch( + (err: HttpErrorResponse) => { // simple logging, but you can do a lot more, see below toast({ message: 'An error occured while fetching the drugs.', @@ -396,7 +433,6 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { } } - public updatePhysicsEnabled(bool: boolean) { this.physicsEnabled = bool; this.network.setOptions({ @@ -409,21 +445,48 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { }); } - public screenshot() { - const elem = document.getElementById(this.indexscreenshot.toString()); - html2canvas(elem).then((canvas) => { - const generatedImage1 = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); + public toCanvas() { + html2canvas(this.networkEl.nativeElement).then((canvas) => { + const generatedImage = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); const a = document.createElement('a'); - a.href = generatedImage1; - a.download = `Resulting_Network.png`; + a.href = generatedImage; + a.download = `Network.png`; a.click(); - }); } - public updateshowdrugs(bool) { - this.drugstatus = bool; + public tableProteinSelection(e) { + const oldSelection = [...this.tableSelectedProteins]; + this.tableSelectedProteins = e; + for (const i of this.tableSelectedProteins) { + const wrapper = getWrapperFromProtein(i); + if (oldSelection.indexOf(i) === -1) { + this.analysis.addItem(wrapper); + } + } + for (const i of oldSelection) { + const wrapper = getWrapperFromProtein(i); + if (this.tableSelectedProteins.indexOf(i) === -1) { + this.analysis.removeItem(wrapper); + } + } + } + public tableViralProteinSelection(e) { + const oldSelection = [...this.tableSelectedViralProteins]; + this.tableSelectedViralProteins = e; + for (const i of this.tableSelectedViralProteins) { + const wrapper = getWrapperFromViralProtein(i); + if (oldSelection.indexOf(i) === -1) { + this.analysis.addItem(wrapper); + } + } + for (const i of oldSelection) { + const wrapper = getWrapperFromViralProtein(i); + if (this.tableSelectedViralProteins.indexOf(i) === -1) { + this.analysis.removeItem(wrapper); + } + } } } diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index a2ab77cce61ed13afb137d6dbdb21a45ec98e99c..9bde9a0409963ef8a5494dd4b4fd648dba0b08b7 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -148,7 +148,7 @@ </p> </header> <div class="card-content"> - <div class="card-image" id="0"> + <div class="card-image" id="canvas-content"> <div class="parent"> <div class="network center image1" #network> <button class="button is-loading center" alt="Snow">Loading</button> @@ -164,7 +164,7 @@ <i class="fas fa-camera" aria-hidden="true"></i> </span> <span>Screenshot</span> </button> - <app-toggle class="footer-toggle-buttons" textOn="Animation On" textOff="Off" [value]="physicsEnabled" (valueChange)="updatePhysicsEnabled($event)" icon="fa-wind"></app-toggle> + <app-toggle class="footer-buttons" textOn="Animation On" textOff="Off" [value]="physicsEnabled" (valueChange)="updatePhysicsEnabled($event)" icon="fa-wind"></app-toggle> </footer> </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 1f8cb6b1a8036dafea5e91931ed6da503712490b..c9b765d2cef88540d6d01e3b215cdc7440b74f79 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -63,7 +63,6 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public selectedAnalysisToken: string | null = null; public currentDataset = []; - private screenshotArray = [0]; public currentViewProteins: Protein[]; public currentViewEffects: ViralProtein[]; @@ -417,15 +416,12 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } public toCanvas() { - this.screenshotArray.forEach((key, index) => { - const elem = document.getElementById(index.toString()); - html2canvas(elem).then((canvas) => { - const generatedImage = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); - const a = document.createElement('a'); - a.href = generatedImage; - a.download = `Network.png`; - a.click(); - }); + html2canvas(this.networkEl.nativeElement).then((canvas) => { + const generatedImage = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); + const a = document.createElement('a'); + a.href = generatedImage; + a.download = `Network.png`; + a.click(); }); } diff --git a/src/styles.scss b/src/styles.scss index e8385ffee8e46535727d88d1fc3a65307b7b1e81..31c4bbb909994b9a84fc23b4ccc49695621ac651 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -12,7 +12,7 @@ $danger: #EF476F; $link: $primary; $info: $primary; @import "~bulma/bulma.sass"; -@import "~primeng/resources/primeng.css"; +@import "~primeng/resources/primeng.min.css"; @import "~primeicons/primeicons.css"; html { @@ -174,7 +174,7 @@ div.field.has-addons.add-remove-toggle { color: $danger; } -.footer-toggle-buttons { +.footer-buttons { margin-left: 20px; margin-right: 10px; } @@ -192,3 +192,8 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } + + +.ui-chkbox-box { + border: 1px solid black !important; +}