diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index cd137b45b705345b6f3a0f0bb76ac32c41a573f5..55306456abd94a12b74600b93f2d85855ac00335 100644 --- a/src/app/analysis.service.ts +++ b/src/app/analysis.service.ts @@ -1,4 +1,4 @@ -import {QueryItem, Task} from './interfaces'; +import {Wrapper, Task, getWrapperFromProtein, getWrapperFromViralProtein} from './interfaces'; import {Subject} from 'rxjs'; import {HttpClient} from '@angular/common/http'; import {environment} from '../environments/environment'; @@ -33,8 +33,8 @@ export const MULTISTEINER: Algorithm = {slug: 'multisteiner', name: algorithmNam }) export class AnalysisService { - private selectedItems = new Map<string, QueryItem>(); - private selectSubject = new Subject<{ item: QueryItem, selected: boolean }>(); + private selectedItems = new Map<string, Wrapper>(); + private selectSubject = new Subject<{ item: Wrapper, selected: boolean }>(); public tokens: string[] = []; public finishedTokens: string[] = []; @@ -76,41 +76,31 @@ export class AnalysisService { }); } - public addItem(item: QueryItem) { - if (!this.inSelection(item.name)) { - this.selectedItems.set(`${item.name}`, item); - this.selectSubject.next({item, selected: true}); + public addItem(wrapper: Wrapper) { + if (!this.inSelection(wrapper)) { + this.selectedItems.set(wrapper.nodeId, wrapper); + this.selectSubject.next({item: wrapper, selected: true}); } } public addAllHostProteins(nodes, proteins) { const visibleIds = new Set<string>(nodes.getIds()); for (const protein of proteins) { - const nodeId = protein.proteinAc; - const found = visibleIds.has(nodeId) || visibleIds.has('p_' + nodeId); - if (found && !this.inSelection(protein.name)) { - this.addItem({ - name: protein.proteinAc, - type: 'Host Protein', - data: protein - }); + const wrapper = getWrapperFromProtein(protein); + const found = visibleIds.has(wrapper.nodeId); + if (found && !this.inSelection(wrapper)) { + this.addItem(wrapper); } } } - public addAllViralProteins(nodes, effects) { + public addAllViralProteins(nodes, viralProteins) { const visibleIds = new Set<string>(nodes.getIds()); - for (const effect of effects) { - const nodeId = effect.effectId; - const found = visibleIds.has(nodeId) || visibleIds.has('eff_' + effect.effectName + '_' + - effect.datasetName + '_' + effect.virusName); - if (found && !this.inSelection(effect.effectName + '_' + - effect.datasetName + '_' + effect.virusName)) { - this.addItem({ - name: effect.effectId, - type: 'Viral Protein', - data: effect - }); + for (const viralProtein of viralProteins) { + const wrapper = getWrapperFromViralProtein(viralProtein); + const found = visibleIds.has(wrapper.nodeId); + if (found && !this.inSelection(wrapper)) { + this.addItem(wrapper); } } } @@ -118,22 +108,22 @@ export class AnalysisService { resetSelection() { const oldSelection = this.selectedItems.values(); for (const item of oldSelection) { - this.removeItem(item.name); + this.removeItem(item); } } - inSelection(itemName: string): boolean { - return this.selectedItems.has(itemName); + inSelection(wrapper: Wrapper): boolean { + return this.selectedItems.has(wrapper.nodeId); } - removeItem(itemName: string) { - const item = this.selectedItems.get(itemName); - if (this.selectedItems.delete(itemName)) { + removeItem(wrapper: Wrapper) { + const item = this.selectedItems.get(wrapper.nodeId); + if (this.selectedItems.delete(wrapper.nodeId)) { this.selectSubject.next({item, selected: false}); } } - getSelection(): QueryItem[] { + getSelection(): Wrapper[] { return Array.from(this.selectedItems.values()); } @@ -141,7 +131,7 @@ export class AnalysisService { return this.selectedItems.size; } - subscribe(cb: (item: QueryItem, selected: boolean) => void) { + subscribe(cb: (item: Wrapper, selected: boolean) => void) { this.selectSubject.subscribe((event) => { cb(event.item, event.selected); }); @@ -165,7 +155,7 @@ export class AnalysisService { algorithm: 'quick', target: 'drug', parameters: { - seeds: this.getSelection().map((i) => i.name), + seeds: this.getSelection().map((i) => i.backendId), }, }).toPromise(); this.tokens.push(resp.token); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index c48a54efeb930cd50579dbd4d02b75a21ac14bde..28e0dc75c971e22fb55d67042e90aabececd9b49 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -18,6 +18,7 @@ import {SelectDatasetComponent} from './components/select-dataset/select-dataset import {AnalysisWindowComponent} from './components/analysis-window/analysis-window.component'; import {TaskListComponent} from './components/task-list/task-list.component'; import {ToggleComponent} from './components/toggle/toggle.component'; +import {InfoBoxComponent} from './components/info-box/info-box.component'; import {AnalysisService} from './analysis.service'; @@ -34,6 +35,7 @@ import {AnalysisService} from './analysis.service'; AnalysisWindowComponent, TaskListComponent, ToggleComponent, + InfoBoxComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/analysis-window/analysis-window.component.html b/src/app/components/analysis-window/analysis-window.component.html index a549721d09e267f61ba44c5c86b25845c6457daa..94a5c4c38a351448b8de5a2fee8bf966fd2db3f5 100644 --- a/src/app/components/analysis-window/analysis-window.component.html +++ b/src/app/components/analysis-window/analysis-window.component.html @@ -128,7 +128,7 @@ </p> </div> - <div *ngIf="tableDrugs.length > 0"> + <div *ngIf="tableDrugs.length > 0" class="table-header"> <h4 class="is-4"> <span class="icon"><i class="fa fa-capsules"></i></span> <span>Drugs</span> @@ -169,7 +169,7 @@ </ng-template> </p-table> - <div *ngIf="tableProteins.length > 0"> + <div *ngIf="tableProteins.length > 0" class="table-header"> <h4 class="is-4"> <span class="icon"><i class="fa fa-dna"></i></span> <span>Proteins</span> @@ -205,7 +205,7 @@ </ng-template> </p-table> - <div *ngIf="tableViralProteins.length > 0"> + <div *ngIf="tableViralProteins.length > 0" class="table-header"> <h4 class="is-4"> <span class="icon"><i class="fa fa-virus"></i></span> <span>Viral Proteins</span> diff --git a/src/app/components/analysis-window/analysis-window.component.scss b/src/app/components/analysis-window/analysis-window.component.scss index 7be4c7dd9cb8ffddb358ae13b9b661e79682ce36..e957daa66c148fb3ae5fcc09f5cd8615ec37e8c7 100644 --- a/src/app/components/analysis-window/analysis-window.component.scss +++ b/src/app/components/analysis-window/analysis-window.component.scss @@ -27,3 +27,7 @@ div.network { margin-top: 60px; } } + +.table-header { + margin-bottom: 50px; +} diff --git a/src/app/components/analysis-window/analysis-window.component.ts b/src/app/components/analysis-window/analysis-window.component.ts index bcec53671663063d14f432ab3035cd92aa84063a..170d0dee39bf8fc740abc361921781f934f706c1 100644 --- a/src/app/components/analysis-window/analysis-window.component.ts +++ b/src/app/components/analysis-window/analysis-window.component.ts @@ -12,9 +12,13 @@ import { import {HttpClient, HttpErrorResponse} from '@angular/common/http'; import {environment} from '../../../environments/environment'; import {AnalysisService, algorithmNames} from '../../analysis.service'; -import {Protein, Task, NodeType, ViralProtein, Drug} from '../../interfaces'; +import { + Protein, Task, ViralProtein, Drug, Wrapper, WrapperType, + getWrapperFromProtein, getWrapperFromDrug, getWrapperFromViralProtein, getNodeIdsFromPPI, getNodeIdsFromPDI +} from '../../interfaces'; import html2canvas from 'html2canvas'; import {toast} from 'bulma-toast'; +import {NetworkSettings} from '../../network-settings'; declare var vis: any; @@ -30,21 +34,17 @@ interface Scored { }) export class AnalysisWindowComponent implements OnInit, OnChanges { - constructor(private http: HttpClient, public analysis: AnalysisService) { - } + @ViewChild('network', {static: false}) networkEl: ElementRef; @Input() token: string | null = null; @Output() tokenChange = new EventEmitter<string | null>(); - @Output() showDetailsChange: EventEmitter<any> = new EventEmitter(); - + @Output() showDetailsChange = new EventEmitter<Wrapper>(); + @Output() visibleItems = new EventEmitter<any>(); public task: Task | null = null; public indexscreenshot = 1; - - @ViewChild('network', {static: false}) networkEl: ElementRef; - private network: any; private nodeData: { nodes: any, edges: any } = {nodes: null, edges: null}; private drugNodes: any[] = []; @@ -54,8 +54,6 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { public physicsEnabled = true; public drugstatus = true; - - private proteins: any; public effects: any; @@ -65,10 +63,11 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { public tableNormalize = false; public tableHasScores = false; - @Output() visibleItems: EventEmitter<any> = new EventEmitter(); - public algorithmNames = algorithmNames; + constructor(private http: HttpClient, public analysis: AnalysisService) { + } + async ngOnInit() { } @@ -95,20 +94,7 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { this.nodeData.edges = new vis.DataSet(edges); const container = this.networkEl.nativeElement; - const options = { - layout: { - improvedLayout: false, - }, - edges: { - smooth: false, - }, - physics: { - enabled: this.physicsEnabled, - stabilization: { - enabled: false, - }, - }, - }; + const options = NetworkSettings.getOptions('analysis'); this.network = new vis.Network(container, this.nodeData, options); @@ -136,78 +122,38 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { } this.network.on('deselectNode', (properties) => { - this.showDetailsChange.emit([false, [null, null, null, null, null, null]]); + this.showDetailsChange.emit(null); }); - this.network.on('selectNode', (properties) => { + this.network.on('click', (properties) => { const selectedNodes = this.nodeData.nodes.get(properties.nodes); if (selectedNodes.length > 0) { - let selectedItem; - let selectedName; - let selectedType; - let selectedId; - let selectedVirusName; - let selectedStatus; - if (selectedNodes[0].nodeType === 'host') { - const protein: Protein = selectedNodes[0].details; - selectedVirusName = null; - selectedStatus = null; - selectedItem = {name: selectedNodes[0].id, type: 'Host Protein', data: protein}; - // TODO use gene name here - selectedName = protein.name; - selectedId = protein.proteinAc; - selectedType = 'Host Protein'; - if (properties.event.srcEvent.ctrlKey) { - if (this.analysis.inSelection(protein.proteinAc)) { - this.analysis.removeItem(protein.proteinAc); - } else { - this.analysis.addItem({name: protein.proteinAc, type: 'Host Protein', data: protein}); - this.analysis.getCount(); - } - } - } else if (selectedNodes[0].nodeType === 'virus') { - const virus: ViralProtein = selectedNodes[0].details; - selectedId = null; - selectedStatus = null; - selectedItem = {name: virus.effectId, type: 'Viral Protein', data: virus}; - selectedVirusName = virus.virusName; - selectedName = virus.effectName; - selectedType = 'Viral Protein'; - if (properties.event.srcEvent.ctrlKey) { - if (this.analysis.inSelection(virus.effectName)) { - this.analysis.removeItem(virus.effectName); - } else { - this.analysis.addItem(selectedItem); - this.analysis.getCount(); - } + const selectedNode = selectedNodes[0]; + const wrapper = selectedNode.wrapper; + + if (properties.event.srcEvent.ctrlKey) { + if (this.analysis.inSelection(wrapper)) { + this.analysis.removeItem(wrapper); + } else { + this.analysis.addItem(wrapper); + this.analysis.getCount(); } - } else if (selectedNodes[0].nodeType === 'drug') { - const drug: Drug = selectedNodes[0].details; - selectedId = drug.drugId; - selectedStatus = drug.status; - selectedName = drug.name; - selectedType = 'Drug'; - selectedItem = {name: drug.name, type: 'Drug', data: drug}; - selectedVirusName = null; } - this.showDetailsChange.emit([true, [selectedItem, selectedName, selectedType, - selectedId, selectedVirusName, selectedStatus]]); + this.showDetailsChange.emit(wrapper); } else { - this.showDetailsChange.emit([false, [null, null, null, null, null, null]]); + this.showDetailsChange.emit(null); } }); this.analysis.subscribe((item, selected) => { - const nodeId = item.name; - const node = this.nodeData.nodes.get(nodeId); + const node = this.nodeData.nodes.get(item.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; + const pos = this.network.getPositions([item.nodeId]); + node.x = pos[item.nodeId].x; + node.y = pos[item.nodeId].y; + Object.assign(node, NetworkSettings.getNodeStyle(node.wrapper.type, node.isSeed, selected)); this.nodeData.nodes.update(node); }); } @@ -273,7 +219,7 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { return `${environment.backend}task_result/?token=${this.token}&view=${view}&fmt=csv`; } - public inferNodeType(nodeId: string): 'host' | 'virus' | 'drug' { + public inferNodeType(nodeId: string): WrapperType { if (nodeId.indexOf('-') !== -1 || nodeId.indexOf('_') !== -1) { return 'virus'; } else if (nodeId.startsWith('DB')) { @@ -302,76 +248,73 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { } else if (nodeTypes[node] === 'virus') { this.effects.push(details[node]); } - nodes.push(this.mapNode(node, nodeTypes[node] || this.inferNodeType(node), isSeed[node], scores[node], details[node])); + nodes.push(this.mapNode(this.inferNodeType(node), details[node], isSeed[node], scores[node])); } for (const edge of network.edges) { - edges.push(this.mapEdge(edge)); + edges.push(this.mapEdge(edge, 'protein-protein')); } + return { nodes, 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; - + private mapNode(nodeType: WrapperType, details: Protein | ViralProtein | Drug, isSeed?: boolean, score?: number): any { + let nodeLabel; + let wrapper: Wrapper; if (nodeType === 'host') { - shape = 'ellipse'; - if (this.analysis.inSelection(nodeId)) { - color = '#c7661c'; - } else { - color = '#e2b600'; + const protein = details as Protein; + wrapper = getWrapperFromProtein(protein); + nodeLabel = protein.name; + if (!protein.name) { + nodeLabel = protein.proteinAc; } - 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; + const drug = details as Drug; + wrapper = getWrapperFromDrug(drug); + nodeLabel = drug.name; + } else if (nodeType === 'virus') { + const viralProtein = details as ViralProtein; + wrapper = getWrapperFromViralProtein(viralProtein); + nodeLabel = viralProtein.effectName; } - if (isSeed) { - color = '#c064c7'; - } + const node = NetworkSettings.getNodeStyle(nodeType, isSeed, this.analysis.inSelection(wrapper)); - return {color, shape, size, font, shadow}; + node.id = wrapper.nodeId; + node.label = nodeLabel; + node.nodeType = nodeType; + node.isSeed = isSeed; + node.wrapper = wrapper; + return node; } - private mapNode(nodeId: any, nodeType?: NodeType, isSeed?: boolean, score?: number, details?): any { - const {shape, color, size, font, shadow} = this.getNodeLooks(nodeId, nodeType, isSeed); - let nodeLabel = nodeId; - if (nodeType === 'host') { - nodeLabel = details.name; - } else if (nodeType === 'drug') { - nodeLabel = details.name; + private mapEdge(edge: any, type: 'protein-protein' | 'to-drug'): any { + let edgeColor; + if (type === 'protein-protein') { + edgeColor = { + color: NetworkSettings.getColor('edgeHostVirus'), + highlight: NetworkSettings.getColor('edgeHostVirusHighlight') + }; + const {from, to} = getNodeIdsFromPPI(edge); + return { + from, to, + color: edgeColor, + }; + } else if (type === 'to-drug') { + edgeColor = { + color: NetworkSettings.getColor('edgeHostDrug'), + highlight: NetworkSettings.getColor('edgeHostDrugHighlight') + }; + const {from, to} = getNodeIdsFromPDI(edge); + return { + from, to, + color: edgeColor, + }; } - return { - id: nodeId, - label: nodeLabel, - size, color, shape, font, shadow, - nodeType, isSeed, details - }; - } - - private mapEdge(edge: any): any { - return { - from: `${edge.from}`, - to: `${edge.to}`, - color: {color: '#afafaf', highlight: '#854141'}, - }; } public async toggleDrugs(bool: boolean) { @@ -413,12 +356,12 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { }); } else { for (const drug of drugs) { - this.drugNodes.push(this.mapNode(drug.drugId, 'drug', false, null, drug)); + this.drugNodes.push(this.mapNode('drug', drug, false, null)); } for (const interaction of edges) { const edge = {from: interaction.proteinAc, to: interaction.drugId}; - this.drugEdges.push(this.mapEdge(edge)); + this.drugEdges.push(this.mapEdge(edge, 'to-drug')); } this.nodeData.nodes.add(Array.from(this.drugNodes.values())); this.nodeData.edges.add(Array.from(this.drugEdges.values())); @@ -450,6 +393,7 @@ export class AnalysisWindowComponent implements OnInit, OnChanges { }); } + public updateshowdrugs(bool) { this.drugstatus = bool; diff --git a/src/app/components/info-box/info-box.component.html b/src/app/components/info-box/info-box.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8deb469e2836c545b9c3f3ff05fdd6737d59dd68 --- /dev/null +++ b/src/app/components/info-box/info-box.component.html @@ -0,0 +1,51 @@ +<div *ngIf="wrapper"> + <div *ngIf="wrapper.type === 'host'"> + <p> + <b><span>Gene Name:</span></b> + {{ wrapper.data.name }} + <span class="icon is-small"><i class="fas fa-dna"></i></span> + </p> + <p> + <b><span>Uniprot AC:</span></b> + <a href="https://www.uniprot.org/uniprot/{{ wrapper.data.proteinAc }}" target="_blank"> + {{ wrapper.data.proteinAc }} + </a> + </p> + </div> + <div *ngIf="wrapper.type === 'virus'"> + <p> + <b><span>Virus:</span></b> + {{ wrapper.data.virusName }} + <span class="icon is-small"><i class="fas fa-virus"></i></span> + </p> + <p> + <b><span>Effect:</span></b> + {{ wrapper.data.effectName }} + </p> + </div> + <div *ngIf="wrapper.type === 'drug'"> + <p> + <b><span>Name:</span></b> + {{ wrapper.data.name }} + <span class="icon is-small"><i class="fas fa-capsules"></i></span> + </p> + <p> + <b>DrugBank ID:</b> + <a href="https://www.drugbank.ca/drugs/{{ wrapper.data.drugId }}" target="_blank"> {{ wrapper.data.drugId }}</a> + </p> + <p *ngIf="wrapper.data.status === 'investigational' "> + <b>Status:</b> Investigational + <span class="icon is-small"><i class="fas fa-search investigational"></i></span> + <p *ngIf="wrapper.data.status === 'approved' "> + <b>Status:</b> Approved + <span class="icon is-small"><i class="fas fa-check"></i></span> + </p> + </div> + + <div class="field has-addons add-remove-toggle" *ngIf="wrapper.type !== 'drug'"> + <app-toggle [value]="!analysis.inSelection(wrapper)" (valueChange)="$event ? analysis.addItem(wrapper) : analysis.removeItem(wrapper)" textOn="Add to Analysis" textOff="Remove" icon="fa-plus"></app-toggle> + </div> +</div> +<div *ngIf="!wrapper"> + Please select a node for further information. +</div> diff --git a/src/app/components/info-box/info-box.component.scss b/src/app/components/info-box/info-box.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/components/info-box/info-box.component.spec.ts b/src/app/components/info-box/info-box.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..90ca0818d9720bc190333bc6323a8abb9f9dae4a --- /dev/null +++ b/src/app/components/info-box/info-box.component.spec.ts @@ -0,0 +1,27 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InfoBoxComponent } from './info-box.component'; +import {HttpClientModule} from '@angular/common/http'; + +describe('InfoBoxComponent', () => { + let component: InfoBoxComponent; + let fixture: ComponentFixture<InfoBoxComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ InfoBoxComponent ], + imports: [HttpClientModule], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InfoBoxComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/info-box/info-box.component.ts b/src/app/components/info-box/info-box.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..b45c8ca5fe64ac733b495841d9a344468d898c1c --- /dev/null +++ b/src/app/components/info-box/info-box.component.ts @@ -0,0 +1,20 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {Wrapper} from '../../interfaces'; +import {AnalysisService} from '../../analysis.service'; + +@Component({ + selector: 'app-info-box', + templateUrl: './info-box.component.html', + styleUrls: ['./info-box.component.scss'] +}) +export class InfoBoxComponent implements OnInit { + + @Input() + public wrapper: Wrapper; + + constructor(public analysis: AnalysisService) { } + + ngOnInit(): void { + } + +} diff --git a/src/app/components/launch-analysis/launch-analysis.component.ts b/src/app/components/launch-analysis/launch-analysis.component.ts index 67e7aad79111eb625ae6791512d521128220229c..7afca91c5693304426f827f17f78806bbdb85742 100644 --- a/src/app/components/launch-analysis/launch-analysis.component.ts +++ b/src/app/components/launch-analysis/launch-analysis.component.ts @@ -56,9 +56,9 @@ export class LaunchAnalysisComponent implements OnInit, OnChanges { public hasBaits; constructor(public analysis: AnalysisService) { - this.hasBaits = !!analysis.getSelection().find((i) => i.type === 'Viral Protein'); + this.hasBaits = !!analysis.getSelection().find((i) => i.type === 'virus'); analysis.subscribe(() => { - this.hasBaits = !!analysis.getSelection().find((i) => i.type === 'Viral Protein'); + this.hasBaits = !!analysis.getSelection().find((i) => i.type === 'virus'); }); } @@ -88,7 +88,7 @@ export class LaunchAnalysisComponent implements OnInit, OnChanges { public async startTask() { const parameters: any = { - seeds: this.analysis.getSelection().map((item) => item.name), + seeds: this.analysis.getSelection().map((item) => item.backendId), }; if (this.algorithm === 'trustrank') { diff --git a/src/app/components/query/query.component.html b/src/app/components/query/query.component.html index c36f4aa6c1f3872edf67c24290f5656e3a95b00f..5a258cb821ce3815554938e2a1ae2127297aa447 100644 --- a/src/app/components/query/query.component.html +++ b/src/app/components/query/query.component.html @@ -1,12 +1,12 @@ <div class="content"> - <ng-select [items]="queryItems" bindLabel="name" bindValue="data" [virtualScroll]="true" class="custom" + <ng-select [items]="queryItems" bindLabel="backendId" bindValue="data" [virtualScroll]="true" class="custom" placeholder="Search..." [hideSelected]="true" [searchFn]="querySearch" (change)="select($event)"> <ng-template ng-option-tmp let-item="item"> - <b *ngIf="item.type == 'Host Protein'"> {{item.data.name}}</b> - <b *ngIf="item.type == 'Viral Protein'"> {{item.data.effectName}}</b> <br/> - <span><small>{{item.type}}</small> | </span> - <span *ngIf="item.type == 'Host Protein'"><small>AC: <b>{{item.data.proteinAc}}</b></small> </span> - <span *ngIf="item.type == 'Viral Protein'"><small><b>{{item.data.virusName}}</b></small></span> + <b *ngIf="item.type == 'host'"> {{item.data.name}}</b> + <b *ngIf="item.type == 'virus'"> {{item.data.effectName}}</b> <br/> + <span><small *ngIf="item.type === 'virus'">Viral Protein</small><small *ngIf="item.type === 'host'">Host Protein</small> | </span> + <span *ngIf="item.type == 'host'"><small>AC: <b>{{item.data.proteinAc}}</b></small> </span> + <span *ngIf="item.type == 'virus'"><small><b>{{item.data.virusName}}</b></small></span> </ng-template> </ng-select> </div> diff --git a/src/app/components/query/query.component.ts b/src/app/components/query/query.component.ts index 56b4bf375f07eaef801ca17a9924676a06dc3560..383e50f9e0598ac12d0f979b517d2dd196808726 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, QueryItem, ViralProtein} from '../../interfaces'; +import {Protein, Wrapper, ViralProtein} from '../../interfaces'; @Component({ selector: 'app-query-component', @@ -10,11 +10,11 @@ export class QueryComponent { @Output() selectItem: EventEmitter<any> = new EventEmitter(); - @Input() queryItems: QueryItem[]; + @Input() queryItems: Wrapper[]; - querySearch(term: string, item: QueryItem) { + querySearch(term: string, item: Wrapper) { term = term.toLowerCase(); - if (item.type === 'Host Protein') { + if (item.type === 'host') { const data = item.data as Protein; return data.name.toLowerCase().indexOf(term) > -1 || data.proteinAc.toLowerCase().indexOf(term) > -1 || item.type.toLowerCase().indexOf(term) > -1; diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts index de7e1169c5a78e8a62a350aa88918b4a1d9b5e9c..4062e89931d347e6caf2f0ede4967b4028fb9a3b 100644 --- a/src/app/interfaces.ts +++ b/src/app/interfaces.ts @@ -25,6 +25,11 @@ export interface ProteinViralInteraction { proteinAc: string; } +export interface NetworkEdge { + from: string; + to: string; +} + export interface Task { token: string; info: { @@ -51,10 +56,97 @@ export interface Task { }; } -export interface QueryItem { - name: string; - type: 'Host Protein' | 'Viral Protein' | 'Drug'; - data: Protein | ViralProtein | Drug; +export function getProteinNodeId(protein: Protein) { + return `p_${protein.proteinAc}`; +} + +export function getProteinBackendId(protein: Protein) { + return protein.proteinAc; +} + +export function getViralProteinNodeId(viralProtein: ViralProtein) { + return `v_${viralProtein.effectName}_${viralProtein.virusName}`; +} + +export function getNodeIdsFromPVI(pvi: ProteinViralInteraction) { + return { + from: `p_${pvi.proteinAc}`, + to: `v_${pvi.effectName}_${pvi.virusName}`, + }; +} + +export function getNodeIdsFromPPI(edge: NetworkEdge) { + return { + from: `p_${edge.from}`, + to: `p_${edge.to}`, + }; +} + +export function getNodeIdsFromPDI(edge: NetworkEdge) { + return { + from: `p_${edge.from}`, + to: `d_${edge.to}`, + }; +} + +export function getViralProteinBackendId(viralProtein: ViralProtein) { + return viralProtein.effectId; +} + +export function getDrugNodeId(drug: Drug) { + return `d_${drug.drugId}`; +} + +export function getDrugBackendId(drug: Drug) { + return drug.drugId; +} + +export function getWrapperFromProtein(protein: Protein): Wrapper { + return { + backendId: getProteinBackendId(protein), + nodeId: getProteinNodeId(protein), + type: 'host', + data: protein, + }; +} + +export function getWrapperFromViralProtein(viralProtein: ViralProtein): Wrapper { + return { + backendId: getViralProteinBackendId(viralProtein), + nodeId: getViralProteinNodeId(viralProtein), + type: 'virus', + data: viralProtein, + }; +} + +export function getWrapperFromDrug(drug: Drug): Wrapper { + return { + backendId: getDrugBackendId(drug), + nodeId: getDrugNodeId(drug), + type: 'drug', + data: drug, + }; +} + +export type WrapperType = 'host' | 'virus' | 'drug'; + +export function getTypeFromNodeId(nodeId: string): WrapperType { + if (nodeId.startsWith('p_')) { + return 'host'; + } + if (nodeId.startsWith('v_')) { + return 'virus'; + } + if (nodeId.startsWith('d_')) { + return 'drug'; + } +} + +export interface Wrapper { + backendId: string; + nodeId: string; + type: 'host' | 'virus' | 'drug'; + data: any; } export interface Drug { diff --git a/src/app/network-settings.ts b/src/app/network-settings.ts new file mode 100644 index 0000000000000000000000000000000000000000..15c54545035cfe49817420f6c0c7bf16218322a5 --- /dev/null +++ b/src/app/network-settings.ts @@ -0,0 +1,180 @@ +import {WrapperType} from './interfaces'; + +export class NetworkSettings { + + // Node color + private static hostColor = '#123456'; + private static virusColor = '#BE093C'; + private static drugColor = '#F8981D'; + private static seedHostColor = '#3070B3'; + private static seedVirusColor = '#3070B3'; + + private static selectedBorderColor = '#F8981D'; + private static selectBorderHighlightColor = '#F8981D'; + private static selectedBorderWidth = 3; + private static selectedBorderWidthSelected = 3.2; + + // Edge color + private static edgeHostVirusColor = '#686868'; + private static edgeHostVirusHighlightColor = '#686868'; + private static edgeHostDrugColor = '#686868'; + private static edgeHostDrugHighlightColor = '#686868'; + + // Node Font + private static hostFontSize = 20; + private static virusFontSize = 50; + private static drugFontSize = 30; + private static hostFontColor = '#FFFFFF'; + private static virusFontColor = '#FFFFFF'; + private static drugFontColor = '#FFFFFF'; + + // Network Layout + private static analysisLayout = { + improvedLayout: true, + }; + private static analysisEdges = { + smooth: false, + length: 400, + }; + private static analysisPhysics = { + enabled: true, + stabilization: { + enabled: true, + }, + }; + + private static mainLayout = { + improvedLayout: false, + }; + private static mainEdges = { + smooth: false, + length: 250, + }; + private static mainPhysics = { + enabled: false, + }; + + // Node size + private static hostSize = 20; + private static virusSize = 30; + private static drugSize = 15; + + // Node shape + private static hostShape = 'ellipse'; + private static virusShape = 'ellipse'; + private static drugShape = 'box'; + + static getNodeSize(wrapperType: WrapperType) { + if (wrapperType === 'host') { + return this.hostSize; + } else if (wrapperType === 'virus') { + return this.virusSize; + } else if (wrapperType === 'drug') { + return this.drugSize; + } + } + + static getNodeShape(wrapperType: WrapperType) { + if (wrapperType === 'host') { + return this.hostShape; + } else if (wrapperType === 'virus') { + return this.virusShape; + } else if (wrapperType === 'drug') { + return this.drugShape; + } + } + + static getOptions(network: 'main' | 'analysis') { + if (network === 'main') { + return { + layout: this.mainLayout, + edges: this.mainEdges, + physics: this.mainPhysics, + }; + } else if (network === 'analysis') { + return { + layout: this.analysisLayout, + edges: this.analysisEdges, + physics: this.analysisPhysics, + }; + } + } + + static getColor(color: 'host' | 'virus' | 'drug' | 'hostFont' | 'virusFont' | 'drugFont' | + 'seedHost' | 'seedVirus' | 'selectedForAnalysis' | 'selectedForAnalysisText' | + 'edgeHostVirus' | 'edgeHostVirusHighlight' | 'edgeHostDrug' | 'edgeHostDrugHighlight') { + if (color === 'host') { + return this.hostColor; + } else if (color === 'virus') { + return this.virusColor; + } else if (color === 'drug') { + return this.drugColor; + } else if (color === 'hostFont') { + return this.hostFontColor; + } else if (color === 'virusFont') { + return this.virusFontColor; + } else if (color === 'drugFont') { + return this.drugFontColor; + } else if (color === 'seedHost') { + return this.seedHostColor; + } else if (color === 'seedVirus') { + return this.seedVirusColor; + } else if (color === 'edgeHostVirus') { + return this.edgeHostVirusColor; + } else if (color === 'edgeHostDrug') { + return this.edgeHostDrugColor; + } else if (color === 'edgeHostVirusHighlight') { + return this.edgeHostVirusHighlightColor; + } else if (color === 'edgeHostDrugHighlight') { + return this.edgeHostDrugHighlightColor; + } + } + + static getFont(wrapperType: WrapperType) { + if (wrapperType === 'host') { + return {color: this.hostFontColor, size: this.hostFontSize}; + } else if (wrapperType === 'virus') { + return {color: this.virusFontColor, size: this.virusFontSize}; + } else if (wrapperType === 'drug') { + return {color: this.drugFontColor, size: this.drugFontSize}; + } + } + + static getNodeStyle(nodeType: WrapperType, isSeed: boolean, isSelected: boolean): any { + let nodeColor; + let nodeShape; + let nodeSize; + let nodeFont; + const nodeShadow = true; + nodeShape = NetworkSettings.getNodeShape(nodeType); + nodeSize = NetworkSettings.getNodeSize(nodeType); + nodeFont = NetworkSettings.getFont(nodeType); + if (nodeType === 'host') { + nodeColor = NetworkSettings.getColor(nodeType); + nodeFont = NetworkSettings.getFont('host'); + if (isSeed) { + nodeColor = NetworkSettings.getColor('seedHost'); + } + } else if (nodeType === 'virus') { + nodeColor = NetworkSettings.getColor(nodeType); + if (nodeType === 'virus') { + nodeFont = NetworkSettings.getFont('virus'); + if (isSeed) { + nodeColor = NetworkSettings.getColor('seedVirus'); + } + } + } else if (nodeType === 'drug') { + nodeColor = NetworkSettings.getColor(nodeType); + } + + const node: any = {size: nodeSize, color: nodeColor, shape: nodeShape, font: nodeFont, shadow: nodeShadow}; + + if (isSelected) { + node.color = {color: node.color, border: this.selectedBorderColor, highlight: {border: this.selectBorderHighlightColor}}; + node.borderWidth = this.selectedBorderWidth; + node.borderWidthSelected = this.selectedBorderWidthSelected; + } + return node; + } +} + diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 38e339c15a74ff718551d49479c6ac04846c5741..e5829e9fbf98f6007be0a2e38650a191393ca3c6 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -28,15 +28,16 @@ (selectedDatasetChange)="selectedDataset = $event; createNetwork($event.data)"> </app-select-dataset> - <div> + <div> <br> <font size="2"> - <p ><span>Virus: SARS-CoV-2</span> - <p ><span>Host/target: Human cell line, HEK-293T kidney cells</span> - <p ><span>Method: AP-MS (affinity purification-mass spectrometry)</span> - <p ><span>Source:<a href="https://www.biorxiv.org/content/10.1101/2020.03.22.002386v3" target="_blank"> Gordon et al., 2020</a></span></font> + <p><span>Virus: <b>SARS-CoV-2</b></span> + <p><span>Host/target: <b>Human</b>, HEK-293T kidney cells</span> + <p><span>Method: <b>AP-MS</b></span> + <p><span>Source:<a href="https://www.biorxiv.org/content/10.1101/2020.03.22.002386v3" target="_blank"> <b> Gordon et al., 2020</b></a></span> + </font> - </div> + </div> </div> </div> @@ -116,15 +117,15 @@ <i class="fas fa-filter" aria-hidden="true"></i> </span> Filter Viral Proteins </p> - <a (click)="collapseDFilter = !collapseDFilter" data-action="collapse" + <a (click)="collapseBaitFilter = !collapseBaitFilter" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> <span class="icon"> - <i *ngIf="collapseDFilter" class="fas fa-angle-up" aria-hidden="true"></i> - <i *ngIf="!collapseDFilter" class="fas fa-angle-down" aria-hidden="true"></i> + <i *ngIf="collapseBaitFilter" class="fas fa-angle-up" aria-hidden="true"></i> + <i *ngIf="!collapseBaitFilter" class="fas fa-angle-down" aria-hidden="true"></i> </span> </a> </header> - <div *ngIf="collapseDFilter"> + <div *ngIf="collapseBaitFilter"> <div class="card-content overflow"> <div *ngFor="let bait of viralProteinCheckboxes"> <label class="checkbox"> @@ -181,9 +182,8 @@ <div class="analysis-view" *ngIf="selectedAnalysisToken"> <app-analysis-window [(token)]="selectedAnalysisToken" - (showDetailsChange)="showDetails = $event[0]; changeInfo($event[1])" - (visibleItems)="analysisWindowChanged($event)" - ></app-analysis-window> + (showDetailsChange)="selectedWrapper = $event" + (visibleItems)="analysisWindowChanged($event)"></app-analysis-window> </div> </div> @@ -193,11 +193,17 @@ <header class="card-header"> <p class="card-header-title"> <span class="icon"> - <i *ngIf="!showDetails" class="fas fa-info" aria-hidden="true"></i> - <i *ngIf="selectedType === 'Host Protein'" class="fas fa-dna" aria-hidden="true"></i> - <i *ngIf="selectedType === 'Viral Protein'" class="fas fa-virus" aria-hidden="true"></i> - <i *ngIf="selectedType === 'Drug'" class="fas fa-capsules" aria-hidden="true"></i> - </span> {{ selectedType }} + <i *ngIf="!selectedWrapper" class="fas fa-info" aria-hidden="true"></i> + <i *ngIf="selectedWrapper && selectedWrapper.type === 'host'" class="fas fa-dna" aria-hidden="true"></i> + <i *ngIf="selectedWrapper && selectedWrapper.type === 'virus'" class="fas fa-virus" aria-hidden="true"></i> + <i *ngIf="selectedWrapper && selectedWrapper.type === 'drug'" class="fas fa-capsules" aria-hidden="true"></i> + </span> + <span *ngIf="!selectedWrapper">No item selected</span> + <span *ngIf="selectedWrapper"> + <span *ngIf="selectedWrapper.type === 'host'">Host</span> + <span *ngIf="selectedWrapper.type === 'virus'">Viral Protein</span> + <span *ngIf="selectedWrapper.type === 'drug'">Drug</span> + </span> </p> <a (click)="collapseDetails = !collapseDetails" data-action="collapse" class="card-header-icon is-hidden-fullscreen" aria-label="more options"> @@ -209,57 +215,7 @@ </header> <div *ngIf="collapseDetails"> <div class="card-content"> - <div *ngIf="showDetails"> - <p *ngIf="selectedName && selectedType=='Drug'"><b><span>Name:</span></b> {{ selectedName }} <span - class="icon is-small"> - <i class="fas fa-capsules"></i> - </span></p> - <p *ngIf="selectedName && selectedType=='Host Protein'"><b><span>Gene Name:</span></b> {{ selectedName }} - <span class="icon is-small"> - <i class="fas fa-dna"></i> - </span></p> - <p *ngIf="selectedVirusName"><b><span>Virus:</span></b> {{ selectedVirusName }} <span class="icon is-small"> - <i class="fas fa-virus"></i> - </span></p> - <p *ngIf="selectedName && selectedType=='Viral Protein'"><b>Effect:</b> {{ selectedName }}</p> - <p *ngIf="selectedId && selectedType == 'Host Protein'"><b>Uniprot AC:</b> - <a href="https://www.uniprot.org/uniprot/{{ selectedId }}" - target="_blank"> {{ selectedId }}</a> - </p> - <p *ngIf="selectedId && selectedType == 'Drug'"><b>DrugBank ID:</b> - <a href="https://www.drugbank.ca/drugs/{{ selectedId }}" - target="_blank"> {{ selectedId }}</a> - </p> - <p *ngIf="selectedStatus === 'investigational' "><b>Status:</b> Investigational <span class="icon is-small"><i class="fas fa-search investigational"></i></span> - <p *ngIf="selectedStatus === 'approved' "><b>Status:</b> Approved <span class="icon is-small"><i class="fas fa-check"></i></span> - </p> - <div class="field has-addons add-remove-toggle"> - <p *ngIf="selectedType !== 'Drug'" class="control"> - <button class="button is-rounded" [class.is-success]="!analysis.inSelection(selectedName)" - [disabled]="analysis.inSelection(selectedName)" - (click)="analysis.addItem(selectedItem)"> - <span class="icon is-small"> - <i class="fas fa-plus"></i> - </span> - <span>Add to Analysis</span> - </button> - </p> - <p *ngIf="selectedType !== 'Drug'" class="control"> - <button class="button is-rounded" [class.is-danger]="analysis.inSelection(selectedName)" - [disabled]="!analysis.inSelection(selectedName)" - (click)="analysis.removeItem(selectedName)"> - <span>Remove</span> - <span class="icon is-small"> - <i class="fas fa-trash"></i> - </span> - </button> - </p> - </div> - </div> - <div *ngIf="!showDetails"> - Please select a node for further information. - <!-- <a (click)="selectedAnalysisToken = 'oy4UsXfBDobTucdQBhN9IUzfnpqKwzqx'"> Open Analysis Window </a>--> - </div> + <app-info-box [wrapper]="selectedWrapper"></app-info-box> </div> </div> </div> @@ -422,16 +378,16 @@ </thead> <tbody> <tr *ngFor="let p of analysis.getSelection()"> - <td> + <td> <span class="icon"> - <i class="fa fa-dna" *ngIf="p.type == 'Host Protein'"></i> - <i class="fa fa-virus" *ngIf="p.type =='Viral Protein'"></i> + <i class="fa fa-dna" *ngIf="p.type == 'host'"></i> + <i class="fa fa-virus" *ngIf="p.type =='virus'"></i> </span> </td> - <td *ngIf="p.type == 'Viral Protein'">{{p.name}}</td> - <td *ngIf="p.type == 'Host Protein'">{{p.data.name}}</td> + <td *ngIf="p.type == 'virus'">{{p.data.effectName}}</td> + <td *ngIf="p.type == 'host'">{{p.data.name}}</td> <td> - <button (click)="analysis.removeItem(p.name)" class="button is-small is-danger is-outlined"> + <button (click)="analysis.removeItem(p)" class="button is-small is-danger is-outlined"> <i class="fa fa-trash"></i> </button> </td> @@ -443,7 +399,8 @@ </i> </div> <footer class="card-footer"> - <a (click)="analysis.addAllHostProteins(currentViewNodes, currentViewProteins)" class="card-footer-item has-text-success"> + <a (click)="analysis.addAllHostProteins(currentViewNodes, currentViewProteins)" + class="card-footer-item has-text-success"> <span class="icon"> <i class="fa fa-plus"></i> </span> @@ -451,7 +408,8 @@ Host Proteins </span> </a> - <a (click)="analysis.addAllViralProteins(currentViewNodes, currentViewEffects)" class="card-footer-item has-text-success"> + <a (click)="analysis.addAllViralProteins(currentViewNodes, currentViewEffects)" + class="card-footer-item has-text-success"> <span class="icon"> <i class="fa fa-plus"></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 3eb3691abaf509b880a15295d39a2e19cb285530..1f3f29c18c927d5d29e56b8afdab693280a259b9 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -5,12 +5,19 @@ import { OnInit, ViewChild } from '@angular/core'; -import {ProteinViralInteraction, ViralProtein, Protein, QueryItem} from '../../interfaces'; +import { + ProteinViralInteraction, + ViralProtein, + Protein, + Wrapper, + getWrapperFromViralProtein, getWrapperFromProtein, getNodeIdsFromPVI, getViralProteinNodeId, getProteinNodeId +} 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'; +import {NetworkSettings} from '../../network-settings'; declare var vis: any; @@ -23,19 +30,14 @@ declare var vis: any; export class ExplorerPageComponent implements OnInit, AfterViewInit { public showDetails = false; - public selectedName = null; - public selectedType = null; - public selectedId = null; - public selectedItem = null; - public selectedVirusName = null; - public selectedStatus = null; + public selectedWrapper: Wrapper | null = null; public collapseAnalysisQuick = true; public collapseAnalysis = false; public collapseDetails = true; public collapseTask = true; public collapseSelection = true; - public collapseDFilter = true; + public collapseBaitFilter = true; public collapseQuery = true; public collapseData = true; public collapseOverview = true; @@ -54,7 +56,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { private dumpPositions = false; public physicsEnabled = false; - public queryItems: QueryItem[] = []; + public queryItems: Wrapper[] = []; public showAnalysisDialog = false; public analysisDialogTarget: 'drug' | 'drug-target'; @@ -68,23 +70,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public currentViewNodes: Node[]; 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']] - // }, - // tslint:disable-next-line:max-line-length {id: 'CoV2 (Gordon et al., 2020)', label: 'CoV2', datasets: 'Gordon et al., 2020', data: [['Krogan', 'SARS-CoV2']]}, - // {id: 'CoV2 (TUM)', label: 'CoV2', datasets: 'TUM', data: [['TUM', 'SARS-CoV2']]} - ]; + ]; public selectedDataset = this.datasetItems[0]; @@ -95,28 +82,14 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.showDetails = false; this.analysis.subscribe((item, selected) => { - let nodeId; - if (item.type === 'Host Protein') { - nodeId = `p_${item.name}`; - } else if (item.type === 'Viral Protein') { - nodeId = `eff_${item.name}`; - } - const node = this.nodeData.nodes.get(nodeId); + const node = this.nodeData.nodes.get(item.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'; - } else { - if (item.type === 'Host Protein') { - node.color = '#e2b600'; - } else if (item.type === 'Viral Protein') { - node.color = '#118AB2'; - } - } + const pos = this.network.getPositions([item.nodeId]); + node.x = pos[item.nodeId].x; + node.y = pos[item.nodeId].y; + Object.assign(node, NetworkSettings.getNodeStyle(node.wrapper.type, false, selected)); this.nodeData.nodes.update(node); }); } @@ -124,11 +97,11 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { ngOnInit() { } - async ngAfterViewInit() { if (!this.network) { this.selectedDataset = this.datasetItems[0]; await this.createNetwork(this.selectedDataset.data); + this.physicsEnabled = false; } } @@ -148,6 +121,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } private zoomToNode(id: string) { + this.nodeData.nodes.getIds(); const coords = this.network.getPositions(id)[id]; if (!coords) { return; @@ -165,47 +139,25 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }); } - public changeInfo(showList: any[]) { - this.selectedItem = showList[0]; - this.selectedName = showList[1]; - this.selectedType = showList[2]; - this.selectedId = showList[3]; - this.selectedVirusName = showList[4]; - this.selectedStatus = showList[5]; + public changeInfo(wrapper: Wrapper | null) { + // this.selectedItem = showList[0]; + // this.selectedName = showList[1]; + // this.selectedType = showList[2]; + // this.selectedId = showList[3]; + // this.selectedVirusName = showList[4]; + // this.selectedStatus = showList[5]; } - public async openSummary(item: QueryItem, zoom: boolean) { - this.selectedId = null; - this.selectedItem = item; - this.selectedType = item.type; - this.selectedName = item.name; - - if (this.selectedType === 'Host Protein') { - const hostProtein = item.data as Protein; - this.selectedId = hostProtein.proteinAc; - this.selectedName = hostProtein.name; - - if (zoom) { - this.zoomToNode(`p_${item.name}`); - } - } else if (item.type === 'Viral Protein') { - const viralProtein = item.data as ViralProtein; - this.selectedName = viralProtein.effectName; - this.selectedVirusName = viralProtein.virusName; - if (zoom) { - this.zoomToNode(`eff_${viralProtein.effectName}_${viralProtein.datasetName}_${viralProtein.virusName}`); - } + public async openSummary(item: Wrapper, zoom: boolean) { + this.selectedWrapper = item; + if (zoom) { + this.zoomToNode(item.nodeId); } this.showDetails = true; } public async closeSummary() { - this.selectedItem = null; - this.selectedName = null; - this.selectedType = null; - this.selectedId = null; - this.selectedVirusName = null; - this.selectedStatus = null; + this.selectedWrapper = null; this.showDetails = false; } @@ -239,51 +191,23 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.nodeData.edges = new vis.DataSet(edges); const container = this.networkEl.nativeElement; - const options = { - layout: { - improvedLayout: false, - }, - edges: { - smooth: false, - }, - physics: { - enabled: this.physicsEnabled, - stabilization: { - enabled: false, - }, - }, - }; + const options = NetworkSettings.getOptions('main'); this.network = new vis.Network(container, this.nodeData, options); - this.network.on('selectNode', (properties) => { - const id: Array<string> = properties.nodes; - if (id.length > 0) { - const nodeId = id[0].split('_'); - let node: QueryItem; - if (nodeId[0].startsWith('p')) { - node = { - name: nodeId[1], - type: 'Host Protein', - data: this.proteinData.getProtein(nodeId[1]) - }; - } else if (nodeId[0].startsWith('e')) { - const effect = this.effects.find((eff) => eff.effectName === nodeId[1] && eff.datasetName === nodeId[2] && - eff.virusName === nodeId[3]); - node = { - name: effect.effectId, - type: 'Viral Protein', - data: effect - }; - } + this.network.on('click', (properties) => { + const nodeIds: Array<string> = properties.nodes; + if (nodeIds.length > 0) { + const nodeId = nodeIds[0]; + const node = this.nodeData.nodes.get(nodeId); + const wrapper = node.wrapper; if (properties.event.srcEvent.ctrlKey) { - if (this.analysis.inSelection(node.name) === true) { - this.analysis.inSelection(node.name); + if (this.analysis.inSelection(wrapper)) { + this.analysis.removeItem(wrapper); } else { - this.analysis.addItem(node); - this.analysis.getCount(); + this.analysis.addItem(wrapper); } - this.openSummary(node, false); + this.openSummary(wrapper, false); } else { - this.openSummary(node, false); + this.openSummary(wrapper, false); } } else { this.closeSummary(); @@ -304,33 +228,25 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.network.stabilize(); } - if (this.selectedItem) { - this.zoomToNode(`p_${this.selectedItem.name}`); + if (this.selectedWrapper) { + this.zoomToNode(this.selectedWrapper.nodeId); } this.queryItems = []; this.fillQueryItems(this.proteins, this.effects); - if (this.selectedItem) { - this.network.selectNodes(['p_' + this.selectedItem.name]); + if (this.selectedWrapper) { + this.network.selectNodes([this.selectedWrapper.nodeId]); } } fillQueryItems(hostProteins: Protein[], viralProteins: ViralProtein[]) { this.queryItems = []; hostProteins.forEach((protein) => { - this.queryItems.push({ - name: protein.name, - type: 'Host Protein', - data: protein - }); + this.queryItems.push(getWrapperFromProtein(protein)); }); - viralProteins.forEach((effect) => { - this.queryItems.push({ - name: effect.effectId, - type: 'Viral Protein', - data: effect - }); + viralProteins.forEach((viralProtein) => { + this.queryItems.push(getWrapperFromViralProtein(viralProtein)); }); this.currentViewNodes = this.nodeData.nodes; @@ -356,7 +272,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } }); viralProteins.forEach((effect) => { - const nodeId = `eff_${effect.effectName}_${effect.datasetName}_${effect.virusName}`; + const nodeId = getViralProteinNodeId(effect); const found = visibleIds.has(nodeId); if ((cb.checked || showAll) && !found) { const node = this.mapViralProteinToNode(effect); @@ -376,7 +292,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }); const filteredProteins = []; for (const protein of this.proteinData.proteins) { - const nodeId = `p_${protein.proteinAc}`; + const nodeId = getProteinNodeId(protein); const contains = connectedProteinAcs.has(protein.proteinAc); const found = visibleIds.has(nodeId); if (contains) { @@ -415,38 +331,40 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } private mapHostProteinToNode(hostProtein: Protein): any { - let color = '#e2b600'; - if (this.analysis.inSelection(hostProtein.name)) { - color = '#48C774'; + const wrapper = getWrapperFromProtein(hostProtein); + const node = NetworkSettings.getNodeStyle('host', false, this.analysis.inSelection(wrapper)); + let nodeLabel = hostProtein.name; + if (hostProtein.name.length === 0) { + nodeLabel = hostProtein.proteinAc; } - return { - id: `p_${hostProtein.proteinAc}`, - label: `${hostProtein.name}`, - size: 10, font: '5px', color, shape: 'ellipse', shadow: false, - x: hostProtein.x, - y: hostProtein.y, - }; + node.label = nodeLabel; + node.id = wrapper.nodeId; + node.x = hostProtein.x; + node.y = hostProtein.y; + node.wrapper = wrapper; + return node; } private mapViralProteinToNode(viralProtein: ViralProtein): any { - let color = '#118AB2'; - if (this.analysis.inSelection(`${viralProtein.effectName}_${viralProtein.datasetName}_${viralProtein.virusName}`)) { - color = '#48C774'; - } - return { - id: `eff_${viralProtein.effectName}_${viralProtein.datasetName}_${viralProtein.virusName}`, - label: `${viralProtein.effectName} (${viralProtein.datasetName}, ${viralProtein.virusName})`, - size: 10, color, shape: 'box', shadow: true, font: {color: '#FFFFFF'}, - x: viralProtein.x, - y: viralProtein.y, - }; + const wrapper = getWrapperFromViralProtein(viralProtein); + const node = NetworkSettings.getNodeStyle('virus', false, this.analysis.inSelection(wrapper)); + node.id = wrapper.nodeId; + node.label = viralProtein.effectName; + node.id = wrapper.nodeId; + node.x = viralProtein.x; + node.y = viralProtein.y; + node.wrapper = wrapper; + return node; } private mapEdge(edge: ProteinViralInteraction): any { + const {from, to} = getNodeIdsFromPVI(edge); return { - from: `p_${edge.proteinAc}`, - to: `eff_${edge.effectName}_${edge.datasetName}_${edge.virusName}`, - color: {color: '#afafaf', highlight: '#854141'}, + from, to, + color: { + color: NetworkSettings.getColor('edgeHostVirus'), + highlight: NetworkSettings.getColor('edgeHostVirusHighlight') + }, }; } diff --git a/src/assets/logo.png b/src/assets/logo.png index 066eb713ee455cebe340dc8504fe6fcf1bfad8d0..ff01400851865ccb47bec645a3cc22895377ffed 100644 Binary files a/src/assets/logo.png and b/src/assets/logo.png differ diff --git a/src/assets/virus.png b/src/assets/virus.png new file mode 100644 index 0000000000000000000000000000000000000000..066eb713ee455cebe340dc8504fe6fcf1bfad8d0 Binary files /dev/null and b/src/assets/virus.png differ diff --git a/src/styles.scss b/src/styles.scss index 84f5472c73082db27cd83428d7b7db2551581331..1e2849298f770ab7d3308ba45cc62b9d43e675d1 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -28,16 +28,10 @@ body { background-image: url("assets/virus_background.png"); background-size: cover; background-repeat: no-repeat; + background-position: 80%; + padding: 50px; } -//.hero.is-primary { -// background-color: $primary; -// background-image: linear-gradient(to left, #f2fcfe, $primary); -// background-repeat: no-repeat; -// background-position: 80%; -// padding: 50px; -//} - div.navbar-menu { margin-left: 5px; }