diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff2be59c68e3e00449618b5841f20159552d4ece --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,18 @@ +import {Component, Injector, ViewEncapsulation} from '@angular/core'; +import {ExplorerPageComponent} from './pages/explorer-page/explorer-page.component'; +import {createCustomElement} from '@angular/elements'; + +@Component({ + selector: 'app-root', + template: `<div></div>`, + styleUrls: ['./app.component.scss'], + encapsulation: ViewEncapsulation.Emulated, +}) +export class AppComponent { + mobileWindowExpanded = false; + + public toggleMobileMenu() { + this.mobileWindowExpanded = !this.mobileWindowExpanded; + } + +} diff --git a/src/app/dialogs/add-expressed-proteins/add-expressed-proteins.component.ts b/src/app/dialogs/add-expressed-proteins/add-expressed-proteins.component.ts index 05337778e008a6d0260ee065a5b6eb8bd4c3bf79..97e07f2ed7f3b98fcc09092b015dcfe4d0d7a5a4 100644 --- a/src/app/dialogs/add-expressed-proteins/add-expressed-proteins.component.ts +++ b/src/app/dialogs/add-expressed-proteins/add-expressed-proteins.component.ts @@ -1,6 +1,6 @@ import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; import {AnalysisService} from '../../services/analysis/analysis.service'; -import {getWrapperFromNode, Node, Tissue} from '../../interfaces'; +import {getWrapperFromNode, Node, Tissue, ExpressionMap} from '../../interfaces'; import {environment} from '../../../environments/environment'; import {HttpClient} from '@angular/common/http'; @@ -21,6 +21,8 @@ export class AddExpressedProteinsComponent implements OnChanges { public currentViewProteins: Array<Node> = []; @Input() public selectedTissue: Tissue | null = null; + @Input() + public expressionMap: ExpressionMap = undefined; public proteins = []; public threshold = 5; @@ -54,10 +56,10 @@ export class AddExpressedProteinsComponent implements OnChanges { public setThreshold(threshold: number) { this.threshold = threshold; - if (!this.currentViewProteins) { + if (!this.currentViewProteins || this.expressionMap === undefined) { return; } - this.proteins = this.currentViewProteins.filter(p => p.expressionLevel >= threshold); + this.proteins = this.currentViewProteins.filter(p => this.expressionMap[p.netexId] >= threshold); } public close() { diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts index 1dceb3079898cd4589c412dce64e359636ab9c42..b067fe4801212aaa840d14956bf9082c8f27a3bf 100644 --- a/src/app/interfaces.ts +++ b/src/app/interfaces.ts @@ -10,12 +10,13 @@ export interface Node { ensg?: Array<string>; group?: string; groupName?: string; - color?: string; + color?: string | any; // mostly any, but vis js allows detail settings shape?: string; interactions?: Node[]; x?: number; y?: number; - expressionLevel?: number; + borderWidth: number; + borderWidthSelected: number; } export interface Tissue { @@ -23,6 +24,11 @@ export interface Tissue { name: string; } +export interface ExpressionMap { + // node --> expression level + netexId: number; +} + export interface NodeInteraction { from: string; to: string; @@ -80,8 +86,8 @@ export function getNodeIdsFromI(pvi: NodeInteraction) { export function getNodeIdsFromPPI(edge: NetworkEdge, wrappers: { [key: string]: Wrapper }) { return { - from: wrappers[edge.from].nodeId, - to: wrappers[edge.to].nodeId, + from: wrappers[edge.from].id, + to: wrappers[edge.to].id, }; } @@ -129,7 +135,6 @@ export function getWrapperFromCustom(gene: Node): Wrapper { gene.label = gene.label ? gene.label : gene.id return { id: getNodeId(gene), - nodeId: getNodeId(gene), data: gene, }; } @@ -142,7 +147,6 @@ export function getWrapperFromNode(gene: Node): Wrapper { gene.group = gene.group ? gene.group : 'foundNode'; return { id: getNodeId(gene), - nodeId: getNodeId(gene), data: gene, }; } @@ -153,8 +157,7 @@ export function getWrapperFromDrug(drug: Drug): Wrapper { drug.type = 'Drug'; drug.group = 'foundDrug'; return { - id: getDrugBackendId(drug), - nodeId: getDrugNodeId(drug), + id: getDrugNodeId(drug), data: drug, }; } @@ -163,7 +166,6 @@ export type EdgeType = 'protein-protein' | 'protein-drug'; export interface Wrapper { id: string; - nodeId: string; data: { id: string; label: string; @@ -178,6 +180,7 @@ export interface Wrapper { groupName?: string; uniprotAc?: string; expressionLevel?: number; + gradient?: number; x?: number; y?: number; drugId?: string; diff --git a/src/app/main-network.ts b/src/app/main-network.ts index 5382e823a8d367255a7cdc70519648778e9e614e..6e329530d0596d5212ea5622cc83f26c023eee51 100644 --- a/src/app/main-network.ts +++ b/src/app/main-network.ts @@ -1,5 +1,3 @@ -import {HttpClient} from '@angular/common/http'; -import { NGSP_UNICODE } from '@angular/compiler'; import { defaultConfig, IConfig } from './config'; import {NodeInteraction, Node, getProteinNodeId} from './interfaces'; @@ -12,17 +10,6 @@ export class ProteinNetwork { constructor(public proteins: Node[], public edges: NodeInteraction[]) { } - public async loadPositions(http: HttpClient, dataset: Array<[string, string]>) { - const nodePositions = await http.get(`assets/positions/${getDatasetFilename(dataset)}`).toPromise(); - this.proteins.forEach((node) => { - const nodePosition = nodePositions[getProteinNodeId(node)]; - if (nodePosition) { - node.x = nodePosition.x; - node.y = nodePosition.y; - } - }); - } - public updateNodePositions(positions) { this.proteins.forEach((node) => { // take old node position if it is saved. it might only not be saved if node did not exist in old network @@ -68,6 +55,7 @@ export class ProteinNetwork { if (customNode.group === undefined) { // fallback to default node node = JSON.parse(JSON.stringify(defaultConfig.nodeGroups.default)); + node.group = 'default' } else { if (config.nodeGroups[customNode.group] === undefined) { throw `Node with id ${customNode.id} has undefined node group ${customNode.group}.` @@ -119,7 +107,7 @@ export class ProteinNetwork { return edge; } - public mapDataToNetworkInput(config: IConfig): { nodes: any[], edges: any[]; } { + public mapDataToNetworkInput(config: IConfig): { nodes: Node[], edges: any[]; } { const nodes = []; const edges = []; diff --git a/src/app/network-settings.ts b/src/app/network-settings.ts index 3aa6426886298119b562cbf83de30c4ca673ce0c..b7e3794f9daa307cc198e360cfbc97a96380b554 100644 --- a/src/app/network-settings.ts +++ b/src/app/network-settings.ts @@ -1,7 +1,16 @@ import {getGradientColor} from './utils'; +import { + Node, +} from './interfaces'; +import { IConfig } from './config'; export class NetworkSettings { + // colors + private static Grey = '#A0A0A0' + private static White = '#FFFFFF'; + private static Black = '#000000'; + // Node color private static hostColor = '#123456'; private static approvedDrugColor = '#48C774'; @@ -9,8 +18,11 @@ export class NetworkSettings { private static nonSeedHostColor = '#3070B3'; private static nonSeedVirusColor = '#87082c'; - private static selectedBorderColor = '#F8981D'; - private static selectBorderHighlightColor = '#F8981D'; + private static selectedBorderColor = NetworkSettings.Black; + private static selectBorderHighlightColor = NetworkSettings.Black; + + private static seedBorderColor = '#F8981D'; + private static seedBorderHighlightColor = '#F8981D'; // Edge color private static edgeHostVirusColor = '#686868'; @@ -21,19 +33,22 @@ export class NetworkSettings { private static edgeGeneGeneHighlightColor = '#686868'; - // Border width + // Border width for nodes in selection private static selectedBorderWidth = 3; private static selectedBorderWidthSelected = 3; - + // Border width for seed nodes + private static seedBorderWidth = 3; + private static seedBorderWidthSelected = 3; + // Border width private static borderWidth = 1; private static borderWidthSelected = 3; // Node Font private static hostFontSize = 20; private static drugFontSize = 30; - private static hostFontColor = '#FFFFFF'; - private static drugFontColor = '#FFFFFF'; - private static drugInTrialFontColor = 'black'; + private static hostFontColor = NetworkSettings.White; + private static drugFontColor = NetworkSettings.White; + private static drugInTrialFontColor = NetworkSettings.Black; // Network Layout private static analysisLayout = { @@ -160,7 +175,56 @@ export class NetworkSettings { // } // } - // static getNodeStyle(nodeType: WrapperType, + static getNodeStyle( + node: Node, + config: IConfig, + isSeed: boolean, + isSelected: boolean, + drugType?: string, + drugInTrial?: boolean, + gradient?: number): any { + console.log(node) + if (!gradient) { + gradient = 1.0; + } + const nodeGroupObject = config.nodeGroups[node.group]; + // vis js style attributes + const nodeShadow = true; + // const nodeShape = node.shape; + // const nodeSize = 10; + // const nodeFont = node.font; + const nodeColor = nodeGroupObject.color; + if (isSeed) { + node.color = { + background: nodeColor, + border: this.seedBorderColor, + highlight: { + border: this.seedBorderHighlightColor, + background: nodeColor + } + }; + node.borderWidth = this.seedBorderWidth; + node.borderWidthSelected = this.seedBorderWidthSelected; + } else if (isSelected) { + node.color = { + background: nodeColor, + border: this.selectedBorderColor, + highlight: { + border: this.selectBorderHighlightColor, + background: nodeColor + } + }; + node.borderWidth = this.selectedBorderWidth; + node.borderWidthSelected = this.selectedBorderWidthSelected; + } else { + node.color = nodeColor; + node.borderWidth = this.borderWidth; + node.borderWidthSelected = this.borderWidthSelected; + } + return node; + } + + // static getNodeStyleOld(nodeType: WrapperType, // isSeed: boolean, // isSelected: boolean, // drugType?: string, @@ -198,9 +262,9 @@ export class NetworkSettings { // } // if (gradient === -1) { - // nodeColor = '#A0A0A0'; + // nodeColor = NetworkSettings.GREY; // } else { - // nodeColor = getGradientColor('#FFFFFF', nodeColor, gradient); + // nodeColor = getGradientColor(NetworkSettings.WHITE, nodeColor, gradient); // } // const node: any = { @@ -210,24 +274,24 @@ export class NetworkSettings { // shadow: nodeShadow, // }; - // if (isSelected) { - // node.color = { - // background: nodeColor, - // border: this.selectedBorderColor, - // highlight: { - // border: this.selectBorderHighlightColor, - // background: nodeColor, - // }, - // }; - - // node.borderWidth = this.selectedBorderWidth; - // node.borderWidthSelected = this.selectedBorderWidthSelected; - // } else { - // node.color = nodeColor; + // if (isSelected) { + // node.color = { + // background: nodeColor, + // border: this.selectedBorderColor, + // highlight: { + // border: this.selectBorderHighlightColor, + // background: nodeColor, + // }, + // }; - // node.borderWidth = this.borderWidth; - // node.borderWidthSelected = this.borderWidthSelected; - // } + // node.borderWidth = this.selectedBorderWidth; + // node.borderWidthSelected = this.selectedBorderWidthSelected; + // } else { + // node.color = nodeColor; + + // node.borderWidth = this.borderWidth; + // node.borderWidthSelected = this.borderWidthSelected; + // } // 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 c17a7590659f35a4537235b4bc4c13792590aad3..aa6b6173a8b48f4747008407073555a1ea13b6dd 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -13,7 +13,9 @@ <app-add-expressed-proteins [(show)]="showThresholdDialog" [selectedTissue]="currentViewSelectedTissue" [visibleNodes]="currentViewNodes" - [currentViewProteins]="currentViewProteins"> + [currentViewProteins]="currentViewProteins" + [expressionMap]="expressionMap" + > </app-add-expressed-proteins> <!-- Start explorer --> diff --git a/src/app/pages/explorer-page/explorer-page.component.ts b/src/app/pages/explorer-page/explorer-page.component.ts index cb1105801fe04352fe12b8e514ff04ef40fb8b91..98043505c14959bcf7be9e94edbc9e820103077d 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -1,5 +1,18 @@ -import {AfterViewInit, Component, ElementRef, HostListener, Input, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'; -import {getWrapperFromNode, Node, Tissue, Wrapper} from '../../interfaces'; +import { + AfterViewInit, + Component, + ElementRef, HostListener, Input, + OnInit, + ViewChild +} from '@angular/core'; +import { + NodeInteraction, + Node, + Wrapper, + getWrapperFromNode, + Tissue, + ExpressionMap +} from '../../interfaces'; import {ProteinNetwork} from '../../main-network'; import {AnalysisService} from '../../services/analysis/analysis.service'; import {OmnipathControllerService} from '../../services/omnipath-controller/omnipath-controller.service'; @@ -118,8 +131,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public proteinData: ProteinNetwork; - public proteins: any; - public edges: any; + public proteins: Node[]; + public edges: NodeInteraction[]; private networkInternal: any; // this will store the vis Dataset @@ -146,6 +159,9 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public expressionExpanded = false; public selectedTissue: Tissue | null = null; + // keys are node netexIds + public expressionMap: ExpressionMap = undefined; + @Input() public textColor = 'red'; @@ -170,14 +186,20 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } const updatedNodes = []; for (const item of items) { - const node = this.nodeData.nodes.get(item.id); + const node: Node = this.nodeData.nodes.get(item.id); if (!node) { continue; } const pos = this.networkInternal.getPositions([item.id]); node.x = pos[item.id].x; node.y = pos[item.id].y; + // if (node.group == 'default') { + // Object.assign(node, this.myConfig.nodeGroups.default); + // } else { + // Object.assign(node, this.myConfig.nodeGroups[node.group]); + // } Object.assign(node, this.myConfig.nodeGroups[node.group]); + updatedNodes.push(node); } this.nodeData.nodes.update(updatedNodes); @@ -185,7 +207,13 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { const updatedNodes = []; this.nodeData.nodes.forEach((node) => { const nodeSelected = this.analysis.idInSelection(node.id); + // if (node.group == 'default') { + // Object.assign(node, this.myConfig.nodeGroups.default); + // } else { + // Object.assign(node, this.myConfig.nodeGroups[node.group]); + // }; Object.assign(node, this.myConfig.nodeGroups[node.group]); + }); this.nodeData.nodes.update(updatedNodes); } @@ -261,7 +289,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public async openSummary(item: Wrapper, zoom: boolean) { this.selectedWrapper = item; if (zoom) { - this.zoomToNode(item.nodeId); + this.zoomToNode(item.id); } this.showDetails = true; } @@ -331,13 +359,13 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { // this.networkInternal.stabilize(); if (this.selectedWrapper) { - this.zoomToNode(this.selectedWrapper.nodeId); + this.zoomToNode(this.selectedWrapper.id); } this.queryItems = []; this.fillQueryItems(this.proteins); if (this.selectedWrapper) { - this.networkInternal.selectNodes([this.selectedWrapper.nodeId]); + this.networkInternal.selectNodes([this.selectedWrapper.id]); } } @@ -400,7 +428,6 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { delete defaultNodeGroups.default; // if user has not set the return-groups, take the defaults nodeGroups = {...defaultNodeGroups, ...nodeGroups}; - // override default node groups this.myConfig[key] = nodeGroups; } @@ -487,33 +514,45 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } public selectTissue(tissue: Tissue | null) { + console.log("here") this.expressionExpanded = false; if (!tissue) { this.selectedTissue = null; const updatedNodes = []; for (const item of this.proteins) { - const node = this.nodeData.nodes.get(item.nodeId); + console.log(item) + if (item.netexId === undefined) { + // nodes that are not mapped to backend remain untouched + continue; + } + const node: Node = this.nodeData.nodes.get(item.id); if (!node) { continue; } - const pos = this.networkInternal.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, - // this.analysis.inSelection(item), - // undefined, - // undefined, - // 1.0)); - node.wrapper = item; - node.gradient = 1.0; - // protein.expressionLevel = undefined; - (node.wrapper.data as Node).expressionLevel = undefined; + console.log("node") + console.log(node) + const pos = this.networkInternal.getPositions([item.id]); + node.x = pos[item.id].x; + node.y = pos[item.id].y; + Object.assign( + node, + NetworkSettings.getNodeStyle( + node, + this.myConfig, + false, + this.analysis.inSelection(getWrapperFromNode(item)), + undefined, + undefined, + 1.0 + ) + ) + console.log("in selection") + console.log(this.analysis.inSelection(getWrapperFromNode(item))) updatedNodes.push(node); } this.nodeData.nodes.update(updatedNodes); + // delete expression values + this.expressionMap = undefined; } else { this.selectedTissue = tissue; diff --git a/src/index.html b/src/index.html index 80b38a1553d787a974c3cb11fa476aabb96bbe45..47884d34379d914d5cb74a722651d1838a75990e 100644 --- a/src/index.html +++ b/src/index.html @@ -31,12 +31,11 @@ <button onclick="setNetwork('netexp1')">Add nodes</button> - <div style="height: 700px; width: 1300px"> <network-expander id="netexp1" config='{ - "nodeGroups": {"0.5": {"type": "0.5er Instanz", "color": "rgb(204, 255, 51)", "groupName": "0.5", "shape": "hexagon"}, "patientgroup": {"type": "Patient", "detailShowLabel": "true", "color": "red", "groupName": "patient group", "shape": "dot", "size": "50"}, "pugGroup": {"color": "grey", "groupName": "Pug Group", "shape": "triangle", "image": "https://static.raymondcamden.com/images/2016/11/pug.png"}}, + "nodeGroups": {"0.5": {"type": "0.5er Instanz", "color": "rgb(204, 255, 51)", "groupName": "0.5", "shape": "circle"}, "patientgroup": {"type": "Patient", "detailShowLabel": "true", "color": "red", "groupName": "patient group", "shape": "dot", "size": "50"}, "pugGroup": {"type": "woof woof", "color": "grey", "groupName": "Pug Group", "shape": "triangle", "image": "https://static.raymondcamden.com/images/2016/11/pug.png"}}, "edgeGroups": {"dashes": {"color": "black", "groupName": "dashes Group", "dashes": [1, 2]}, "notdashes": {"color": "black", "groupName": "not dashes Group"}}, "identifier": "symbol" }' @@ -47,7 +46,6 @@ {"from": "Patient No. 5","to": "C5","label": "w/o group"} ] }' - style="height: 100%; width: 100vw; display: block;" ></network-expander> </div>