Skip to content
Snippets Groups Projects
Commit 5a43b285 authored by Julian Späth's avatar Julian Späth
Browse files

Add analysis window

parent b04a929a
No related branches found
No related tags found
No related merge requests found
...@@ -1464,6 +1464,21 @@ ...@@ -1464,6 +1464,21 @@
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
} }
}, },
"@creativebulma/bulma-collapsible": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@creativebulma/bulma-collapsible/-/bulma-collapsible-1.0.4.tgz",
"integrity": "sha512-aNqSwyuJxshoIc4oD3wJ3eRqOANRweYfpzqmef/fj5tf0Yn7UVx99yh8ovY6vhB8Il31bFcc7f/eRfJvEMNUPw==",
"requires": {
"bulma": "^0.8.1"
},
"dependencies": {
"bulma": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.8.1.tgz",
"integrity": "sha512-Afi2zv4DKmNSYfmx55V+Mtnt8+WfR8Rs65kWArmzEuWP7vNr7dSAEDI+ORZlgOR1gueNZwpKaPdUi4ZiTNwgPA=="
}
}
},
"@fortawesome/angular-fontawesome": { "@fortawesome/angular-fontawesome": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.6.1.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.6.1.tgz",
......
...@@ -15,6 +15,7 @@ import {QueryComponent} from './components/query/query.component'; ...@@ -15,6 +15,7 @@ import {QueryComponent} from './components/query/query.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ProteinAnalysisComponent} from './components/protein-analysis/protein-analysis.component'; import {ProteinAnalysisComponent} from './components/protein-analysis/protein-analysis.component';
import {SelectDatasetComponent} from './components/select-dataset/select-dataset.component'; import {SelectDatasetComponent} from './components/select-dataset/select-dataset.component';
import {AnalysisWindowComponent} from './components/analysis-window/analysis-window.component';
@NgModule({ @NgModule({
...@@ -26,6 +27,7 @@ import { SelectDatasetComponent } from './components/select-dataset/select-datas ...@@ -26,6 +27,7 @@ import { SelectDatasetComponent } from './components/select-dataset/select-datas
QueryComponent, QueryComponent,
ProteinAnalysisComponent, ProteinAnalysisComponent,
SelectDatasetComponent, SelectDatasetComponent,
AnalysisWindowComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
......
<div class="card analysis">
<header class="card-header">
<p class="card-header-title">
<span class="icon">
<i class="fas fa-flask" aria-hidden="true"></i>
</span>
Analysis
</p>
<a (click)="closeAnalysisWindow()" class="card-header-icon" aria-label="more options">
<span class="icon">
<i class="fas fa-times" aria-hidden="true"></i>
</span>
</a>
</header>
<div class="card-content">
<div class="content">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nec iaculis mauris.
<a href="#">@bulmaio</a>. <a href="#">#css</a> <a href="#">#responsive</a>
<br>
<time datetime="2016-1-1">11:09 PM - 1 Jan 2016</time>
</div>
</div>
<footer class="card-footer">
<button [disabled]="true" (click)="export()" class="card-footer-item button is-primary"><span class="icon">
<i class="fas fa-cloud-download-alt" aria-hidden="true"></i>
</span><span>Export</span></button>
<button [disabled]="true" (click)="discard()" class="card-footer-item button is-danger"><span>Discard</span><span class="icon">
<i class="fas fa-trash" aria-hidden="true"></i>
</span></button>
<button (click)="closeAnalysisWindow()" class="card-footer-item button"><span>Close</span> <span class="icon">
<i class="fas fa-times" aria-hidden="true"></i>
</span></button>
</footer>
</div>
.analysis {
position: absolute;
height: 100%;
width: 100%;
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AnalysisWindowComponent } from './analysis-window.component';
describe('AnalysisWindowComponent', () => {
let component: AnalysisWindowComponent;
let fixture: ComponentFixture<AnalysisWindowComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AnalysisWindowComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AnalysisWindowComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
@Component({
selector: 'app-analysis-window',
templateUrl: './analysis-window.component.html',
styleUrls: ['./analysis-window.component.scss']
})
export class AnalysisWindowComponent implements OnInit {
@Input() analysisWindow: boolean;
@Output() closeWindow = new EventEmitter<boolean>();
constructor() { }
ngOnInit(): void {
}
closeAnalysisWindow() {
this.closeWindow.emit(this.analysisWindow);
}
discard() {
}
export() {
}
}
...@@ -2,9 +2,11 @@ ...@@ -2,9 +2,11 @@
<div class="content explorer"> <div class="content explorer">
<div class="content left-window">
<div>
<div class="content bar-left"> <div class="content bar-left">
<div class="card bar"> <div class="card bar-small">
<header class="card-header"> <header class="card-header">
<p class="card-header-title"> <p class="card-header-title">
<span class="icon"> <span class="icon">
...@@ -14,17 +16,18 @@ ...@@ -14,17 +16,18 @@
</header> </header>
<div class="card-content"> <div class="card-content">
<div class="content"> <div class="content">
<app-select-dataset [datasetItems]="datasetItems" (selectDataset)="createNetwork($event)"></app-select-dataset> <app-select-dataset [datasetItems]="datasetItems"
(selectDataset)="createNetwork($event)"></app-select-dataset>
</div> </div>
</div> </div>
</div> </div>
<div class="card bar"> <div class="card bar-medium">
<header class="card-header"> <header class="card-header">
<p class="card-header-title"> <p class="card-header-title">
<span class="icon"> <span class="icon">
<i class="fas fa-info" aria-hidden="true"></i> <i class="fas fa-info" aria-hidden="true"></i>
</span> Info </span> Network Overview
</p> </p>
</header> </header>
<div class="card-content"> <div class="card-content">
...@@ -32,19 +35,19 @@ ...@@ -32,19 +35,19 @@
<nav class="level" *ngIf="proteinData"> <nav class="level" *ngIf="proteinData">
<div class="level-item has-text-centered"> <div class="level-item has-text-centered">
<div> <div>
<p class="heading">Viral Proteins</p> <p class="heading">Viral<br>Proteins</p>
<p class="title"> {{ proteinData.effects.length }}</p> <p class="title"> {{ proteinData.effects.length }}</p>
</div> </div>
</div> </div>
<div class="level-item has-text-centered"> <div class="level-item has-text-centered">
<div> <div>
<p class="heading">Host Proteins</p> <p class="heading">Host<br>Proteins</p>
<p class="title">{{ proteinData.proteins.length }}</p> <p class="title">{{ proteinData.proteins.length }}</p>
</div> </div>
</div> </div>
<div class="level-item has-text-centered"> <div class="level-item has-text-centered">
<div> <div>
<p class="heading">Interactions</p> <p class="heading">Virus-Host<br>Interactions</p>
<p class="title">{{ proteinData.edges.length }}</p> <p class="title">{{ proteinData.edges.length }}</p>
</div> </div>
</div> </div>
...@@ -53,7 +56,7 @@ ...@@ -53,7 +56,7 @@
</div> </div>
</div> </div>
<div class="card bar"> <div class="card bar-small">
<header class="card-header"> <header class="card-header">
<p class="card-header-title"> <p class="card-header-title">
<span class="icon"> <span class="icon">
...@@ -73,7 +76,7 @@ ...@@ -73,7 +76,7 @@
</div> </div>
</div> </div>
<div class="card bar"> <div class="card bar-large">
<header class="card-header"> <header class="card-header">
<p class="card-header-title"> <p class="card-header-title">
<span class="icon"> <span class="icon">
...@@ -100,30 +103,47 @@ ...@@ -100,30 +103,47 @@
</button> </button>
</footer> </footer>
</div> </div>
</div>
<div class="content network">
<div class="card bar"> <div class="card network">
<header class="card-header"> <header class="card-header">
<p class="card-header-title"> <p class="card-header-title">
<span class="icon"> Protein-Protein Interaction Network
<i class="fas fa-cog" aria-hidden="true"></i>
</span> Settings
</p> </p>
</header> </header>
<div class="card-content"> <div class="card-content">
<div class="content"> <div class="card-image" id="0">
<div class="network center" #network>
<button class="button is-loading center">Loading</button>
</div>
</div>
<footer class="card-footer">
<button (click)="toCanvas()" class="card-footer-item button is-primary">
<span class="icon">
<i class="fas fa-cloud-download-alt" aria-hidden="true"></i>
</span>
</button>
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" class="checkbox" [(ngModel)]="physicsEnabled" <input type="checkbox" class="checkbox" [(ngModel)]="physicsEnabled"
(ngModelChange)="physicsEnabled = $event; updatePhysicsEnabled()"> (ngModelChange)="physicsEnabled = $event; updatePhysicsEnabled()">
Physics enabled Physics enabled
</label> </label>
</footer>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="analysis-view" [hidden]="!analysisWindow">
<app-analysis-window (closeWindow)="closeAnalysisWindow()"></app-analysis-window>
</div>
</div>
<div class="content bar-right"> <div class="content bar-right">
<div class="card bar"> <div class="card bar-medium">
<header class="card-header"> <header class="card-header">
<p class="card-header-title"> <p class="card-header-title">
<span class="icon"> <span class="icon">
...@@ -143,22 +163,17 @@ ...@@ -143,22 +163,17 @@
{{ proteinAc }} {{ proteinAc }}
</a> </a>
</p> </p>
<button class="button is-primary" *ngIf="!inSelection(currentProteinAc)"
(click)="addToSelection(currentProteinAc)">Add to Analysis
</button>
<button class="button is-danger" *ngIf="inSelection(currentProteinAc)"
(click)="removeFromSelection(currentProteinAc)">Remove from Analysis
</button>
</div> </div>
<div *ngIf="!showDetails"> <div *ngIf="!showDetails">
Please select a node for further information. Please select a node for further information.
<a (click)="showAnalysisWindow()"> Open Analysis Window </a>
</div> </div>
</div> </div>
</div> </div>
<div class="card bar"> <div class="card bar-medium">
<header class="card-header"> <header class="card-header">
<p class="card-header-title"> <p class="card-header-title">
<span class="icon"> <span class="icon">
...@@ -175,7 +190,7 @@ ...@@ -175,7 +190,7 @@
</div> </div>
</div> </div>
<div class="card bar"> <div class="card bar-medium">
<header class="card-header"> <header class="card-header">
<p class="card-header-title"> <p class="card-header-title">
<span class="icon"> <span class="icon">
...@@ -184,8 +199,13 @@ ...@@ -184,8 +199,13 @@
</p> </p>
</header> </header>
<div class="card-content"> <div class="card-content">
<p>Hold down the CTRL button to select multiple proteins.</p> <button class="button is-success" *ngIf="!inSelection(currentProteinAc)"
(click)="addToSelection(currentProteinAc)">Add to Analysis
</button>
<button class="button is-danger" *ngIf="inSelection(currentProteinAc)"
(click)="removeFromSelection(currentProteinAc)">Remove from Analysis
</button>
<p></p>
<button (click)="showAnalysisDialog = true" <button (click)="showAnalysisDialog = true"
class="button" class="button"
[class.is-info]="!analysis.getTask()" [class.is-info]="!analysis.getTask()"
...@@ -234,29 +254,4 @@ ...@@ -234,29 +254,4 @@
</div> </div>
<div class="content network">
<div class="card network">
<header class="card-header">
<p class="card-header-title">
Protein-Protein Interaction Network
</p>
</header>
<div class="card-content">
<div id= "0" class="card-image">
<div class="network center" #network>
<button class="button is-loading center">Loading</button>
</div>
</div>
<footer class="card-footer">
<button (click)="toCanvas()" class="card-footer-item button is-primary">
<span class="icon">
<i class="fas fa-cloud-download-alt" aria-hidden="true"></i>
</span>
</button>
</footer>
</div>
</div>
</div>
</div> </div>
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild, Output, EventEmitter, HostListener} from '@angular/core'; import {
AfterViewInit,
Component,
ElementRef,
OnInit,
ViewChild,
Output,
EventEmitter,
HostListener
} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {Edge, Effect, getDatasetFilename, Protein, ProteinNetwork} from '../protein-network'; import {Edge, Effect, getDatasetFilename, Protein, ProteinNetwork} from '../protein-network';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
...@@ -9,7 +18,6 @@ import html2canvas from 'html2canvas'; ...@@ -9,7 +18,6 @@ import html2canvas from 'html2canvas';
declare var vis: any; declare var vis: any;
@Component({ @Component({
selector: 'app-explorer-page', selector: 'app-explorer-page',
templateUrl: './explorer-page.component.html', templateUrl: './explorer-page.component.html',
...@@ -17,64 +25,6 @@ declare var vis: any; ...@@ -17,64 +25,6 @@ declare var vis: any;
}) })
export class ExplorerPageComponent implements OnInit, AfterViewInit { export class ExplorerPageComponent implements OnInit, AfterViewInit {
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');
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;
}
// In this case, the URL is `/explorer/<protein>`
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;
}
// 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 = `pg_${protein.proteinAc}`;
const node = this.nodeData.nodes.get(nodeId);
const pos = this.network.getPositions([nodeId]);
node.x = pos[nodeId].x;
node.y = pos[nodeId].y;
if (selected) {
if (node) {
node.color = '#c42eff';
this.nodeData.nodes.update(node);
}
} else {
if (node) {
node.color = '#e2b600';
this.nodeData.nodes.update(node);
}
}
});
}
public showDetails = false; public showDetails = false;
public currentProteinAc = ''; public currentProteinAc = '';
public geneNames: Array<string> = []; public geneNames: Array<string> = [];
...@@ -101,6 +51,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { ...@@ -101,6 +51,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit {
public queryItems = []; public queryItems = [];
public showAnalysisDialog = false; public showAnalysisDialog = false;
public analysisWindow = false;
public currentDataset = []; public currentDataset = [];
private array = [0]; private array = [0];
...@@ -111,25 +63,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { ...@@ -111,25 +63,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit {
{label: 'CoV2', datasets: 'Krogan', data: [['Krogan', 'SARS-CoV2']]}, {label: 'CoV2', datasets: 'Krogan', data: [['Krogan', 'SARS-CoV2']]},
{label: 'CoV2', datasets: 'TUM', data: [['TUM', 'SARS-CoV2']]}]; {label: 'CoV2', datasets: 'TUM', data: [['TUM', 'SARS-CoV2']]}];
@ViewChild('network', {static: false}) networkEl: ElementRef; @ViewChild('network', {static: false}) networkEl: ElementRef;
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) {
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();
});
});
}
@HostListener('window:keydown', ['$event']) @HostListener('window:keydown', ['$event'])
handleKeyboardEvent1(event: KeyboardEvent) { handleKeyboardEvent1(event: KeyboardEvent) {
...@@ -153,6 +88,73 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { ...@@ -153,6 +88,73 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit {
} }
} }
closeAnalysisWindow() {
this.analysisWindow = false;
}
showAnalysisWindow() {
this.analysisWindow = true;
}
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');
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;
}
// In this case, the URL is `/explorer/<protein>`
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;
}
// 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 = `pg_${protein.proteinAc}`;
const node = this.nodeData.nodes.get(nodeId);
const pos = this.network.getPositions([nodeId]);
node.x = pos[nodeId].x;
node.y = pos[nodeId].y;
if (selected) {
if (node) {
node.color = '#c42eff';
this.nodeData.nodes.update(node);
}
} else {
if (node) {
node.color = '#e2b600';
this.nodeData.nodes.update(node);
}
}
});
}
ngOnInit() { ngOnInit() {
} }
...@@ -456,4 +458,20 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { ...@@ -456,4 +458,20 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit {
this.analysis.removeProtein(protein); this.analysis.removeProtein(protein);
} }
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) {
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();
});
});
}
} }
...@@ -7,6 +7,14 @@ ...@@ -7,6 +7,14 @@
$family-sans-serif: "Varela Round", sans-serif; $family-sans-serif: "Varela Round", sans-serif;
@import "~bulma/bulma.sass"; @import "~bulma/bulma.sass";
html {
overflow: hidden;
}
body {
overflow: hidden;
}
.hero.is-primary { .hero.is-primary {
background-color: #118AB2; background-color: #118AB2;
background-image: url("assets/logo.png"), linear-gradient(to left, #f2fcfe, #118AB2); background-image: url("assets/logo.png"), linear-gradient(to left, #f2fcfe, #118AB2);
...@@ -54,35 +62,53 @@ input.checkbox { ...@@ -54,35 +62,53 @@ input.checkbox {
div.content.bar-left { div.content.bar-left {
float: left; float: left;
width: 15%; width: 350px;
height: calc(100vh - 30%); height: calc(100vh - 102px);
} }
div.content.bar-right { div.content.bar-right {
float: right; float: right;
width: 15%; width: 350px;
} }
div.card.bar { div.card.bar-small {
margin-bottom: 21px; margin-bottom: 15px;
word-wrap: break-word; word-wrap: break-word;
height: 130px;
}
div.card.bar-medium {
margin-bottom: 15px;
word-wrap: break-word;
height: 170px;
}
div.card.bar-large {
margin-bottom: 15px;
height: calc(100vh - 660px);
} }
div.content.left-window {
float: left;
width: calc(100vw - 350px - 2 * 20px);
height: 100%;
}
div.content.network { div.content.network {
width: 68%; width: calc(100% - 350px - 40px);
height: calc(100vh - 100px); height: calc(100vh - 100px);
margin-left: auto; margin-left: 20px;
margin-right: auto; margin-right: 20px;
float: right;
} }
div.card.network { div.card.network {
width: 100%; width: 100%;
height: calc(100vh - 80px); height: calc(100vh - 85px);
} }
div.network { div.network {
height: calc(100vh - 200px); height: calc(100vh - 210px);
} }
div.center { div.center {
...@@ -93,9 +119,20 @@ div.center { ...@@ -93,9 +119,20 @@ div.center {
div.content.explorer { div.content.explorer {
height: calc(100vh - 90px);
margin-left: 20px; margin-left: 20px;
margin-right: 20px; margin-right: 20px;
} }
div.analysis-view {
height: 100%;
width: calc(100% - 20px);
position: relative;
margin-top: 0;
/* Just for looks*/
z-index: 1000;
padding: 0;
}
html, body { height: 100%; } html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment