diff --git a/src/app/app.module.ts b/src/app/app.module.ts index aa1e694c31272e41773a8f7103c4ccceeb5fe6e1..b5b88bbf65f208874a7976086556778b8be10377 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -45,6 +45,8 @@ import { FaSolidIconComponent } from './components/fa-solid-icon/fa-solid-icon.c import { FaIconsComponent } from './pages/explorer-page/fa-icons/fa-icons.component'; import { LoadingScreenComponent } from './components/loading-screen/loading-screen.component'; import { PrivacyBannerComponent } from './components/privacy-banner/privacy-banner.component'; +import { ParserWarningComponent } from './components/parser-warning/parser-warning.component'; +import { GroupWarningComponent } from './components/group-warning/group-warning.component'; @NgModule({ @@ -80,6 +82,8 @@ import { PrivacyBannerComponent } from './components/privacy-banner/privacy-bann FaIconsComponent, LoadingScreenComponent, PrivacyBannerComponent, + ParserWarningComponent, + GroupWarningComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/analysis-panel/analysis-panel.component.ts b/src/app/components/analysis-panel/analysis-panel.component.ts index e74a5f044539e671eb331d72ca280f664d3ee628..9426c237c7e7da9ec92e8d0aa46eaf90cb81bc94 100644 --- a/src/app/components/analysis-panel/analysis-panel.component.ts +++ b/src/app/components/analysis-panel/analysis-panel.component.ts @@ -473,7 +473,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit } const uniqEdges = []; for (const edge of network.edges) { - const e = mapCustomEdge(edge, this.drugstoneConfig.currentConfig()); + const e = mapCustomEdge(edge, this.drugstoneConfig.currentConfig(), this.drugstoneConfig); e.from = e.from[0] === 'p' && nodeIdMap[e.from] ? nodeIdMap[e.from] : e.from; e.to = e.to[0] === 'p' && nodeIdMap[e.to] ? nodeIdMap[e.to] : e.to; const hash = e.from + '_' + e.to; diff --git a/src/app/components/network/network.component.ts b/src/app/components/network/network.component.ts index 18bebdfa50e2e24cc8b738a672eb9ba121b35a17..ab5bba9c4dcd87bfc572181e50381c264e6820b5 100644 --- a/src/app/components/network/network.component.ts +++ b/src/app/components/network/network.component.ts @@ -138,7 +138,10 @@ export class NetworkComponent implements OnInit { const proteinMap = this.getProteinMap(); const addedEdge = {}; for (const interaction of response.edges) { - const edge = mapCustomEdge({from: interaction.protein, to: interaction.disorder}, this.drugstoneConfig.config); + const edge = mapCustomEdge({ + from: interaction.protein, + to: interaction.disorder + }, this.drugstoneConfig.config, this.drugstoneConfig); if (proteinMap[edge.from]) { proteinMap[edge.from].forEach(from => { if (addedEdge[from] && addedEdge[from].indexOf(edge.to) !== -1) { @@ -159,7 +162,7 @@ export class NetworkComponent implements OnInit { for (const disorder of response.disorders) { disorder.group = 'defaultDisorder'; disorder.id = disorder.drugstoneId; - this.adjacentProteinDisorderList.push(mapCustomNode(disorder, this.drugstoneConfig.currentConfig())); + this.adjacentProteinDisorderList.push(mapCustomNode(disorder, this.drugstoneConfig.currentConfig(), this.drugstoneConfig)); } this.saveAddNodes(this.adjacentProteinDisorderList); this.nodeData.edges.add(this.adjacentProteinDisorderEdgesList); @@ -187,12 +190,12 @@ export class NetworkComponent implements OnInit { this.netex.adjacentDisorders(this.nodeData.nodes.get(), 'drugs', this.drugstoneConfig.config.indicationDrugDisorder, this.drugstoneConfig.config.licensedDatasets).subscribe(response => { for (const interaction of response.edges) { const edge = {from: interaction.drug, to: interaction.disorder}; - this.adjacentDrugDisorderEdgesList.push(mapCustomEdge(edge, this.drugstoneConfig.currentConfig())); + this.adjacentDrugDisorderEdgesList.push(mapCustomEdge(edge, this.drugstoneConfig.currentConfig(), this.drugstoneConfig)); } for (const disorder of response.disorders) { disorder.group = 'defaultDisorder'; disorder.id = disorder.drugstoneId; - this.adjacentDrugDisorderList.push(mapCustomNode(disorder, this.drugstoneConfig.currentConfig())); + this.adjacentDrugDisorderList.push(mapCustomNode(disorder, this.drugstoneConfig.currentConfig(), this.drugstoneConfig)); } this.saveAddNodes(this.adjacentDrugDisorderList); this.nodeData.edges.add(this.adjacentDrugDisorderEdgesList); @@ -248,7 +251,10 @@ export class NetworkComponent implements OnInit { this.netex.adjacentDrugs(this.drugstoneConfig.config.interactionDrugProtein, this.drugstoneConfig.config.licensedDatasets, this.nodeData.nodes.get()).subscribe(response => { const existingDrugIDs = this.nodeData.nodes.get().filter(n => n.drugstoneId && n.drugstoneType === 'drug').map(n => n.drugstoneId); for (const interaction of response.pdis) { - const edge = mapCustomEdge({from: interaction.protein, to: interaction.drug}, this.drugstoneConfig.currentConfig()); + const edge = mapCustomEdge({ + from: interaction.protein, + to: interaction.drug + }, this.drugstoneConfig.currentConfig(), this.drugstoneConfig); if (proteinMap[edge.from]) { proteinMap[edge.from].forEach(from => { @@ -272,7 +278,7 @@ export class NetworkComponent implements OnInit { drug.id = getDrugNodeId(drug); if (!existingDrugIDs.includes(drug.drugstoneId)) { existingDrugIDs.push(drug.drugstoneId); - this.adjacentDrugList.push(mapCustomNode(drug, this.drugstoneConfig.currentConfig())); + this.adjacentDrugList.push(mapCustomNode(drug, this.drugstoneConfig.currentConfig(), this.drugstoneConfig)); } } this.nodeData.nodes.add(this.adjacentDrugList); diff --git a/src/app/main-network.ts b/src/app/main-network.ts index a403e4fec733e8fc7dd84c3c27a1533b9e890f5b..0d43da780153f2c8acd1d29a2cfc0114a68b34cc 100644 --- a/src/app/main-network.ts +++ b/src/app/main-network.ts @@ -1,6 +1,7 @@ import {defaultConfig, IConfig} from './config'; import {NodeInteraction, Node, getProteinNodeId, NetexInteraction} from './interfaces'; import * as merge from 'lodash/fp/merge'; +import {DrugstoneConfigService} from './services/drugstone-config/drugstone-config.service'; export function getDatasetFilename(dataset: Array<[string, string]>): string { return `network-${JSON.stringify(dataset).replace(/[\[\]\",]/g, '')}.json`; @@ -43,16 +44,16 @@ export class ProteinNetwork { }); } - public mapDataToNetworkInput(config: IConfig): { nodes: Node[], edges: any[]; } { + public mapDataToNetworkInput(config: IConfig, drugstoneConfig: DrugstoneConfigService): { nodes: Node[], edges: any[]; } { const nodes = []; const edges = []; for (const protein of this.proteins) { - nodes.push(mapCustomNode(protein, config)); + nodes.push(mapCustomNode(protein, config, drugstoneConfig)); } for (const edge of this.edges) { - edges.push(mapCustomEdge(edge, config)); + edges.push(mapCustomEdge(edge, config, drugstoneConfig)); } return { @@ -69,9 +70,10 @@ export class ProteinNetwork { * * @param customNode * @param config + * @param drugstoneConfig * @returns */ -export function mapCustomNode(customNode: any, config: IConfig): Node { +export function mapCustomNode(customNode: any, config: IConfig, drugstoneConfig: DrugstoneConfigService): Node { let node; if (customNode.group === undefined) { // fallback to default node @@ -79,13 +81,21 @@ export function mapCustomNode(customNode: any, config: IConfig): Node { node.group = 'default'; } else { if (config.nodeGroups[customNode.group] === undefined) { - throw `Node with id ${customNode.id} has undefined node group ${customNode.group}.` + drugstoneConfig.groupIssue = true; + if (!drugstoneConfig.groupIssueList.includes(customNode.group)) { + drugstoneConfig.groupIssueList.push(customNode.group); + } + node = JSON.parse(JSON.stringify(config.nodeGroups.default)); + node.group = 'default'; + console.error(`Node with id ${customNode.id} has undefined node group ${customNode.group}.`); + } else + // copy + { + node = JSON.parse(JSON.stringify(config.nodeGroups[customNode.group])); } - // copy - node = JSON.parse(JSON.stringify(config.nodeGroups[customNode.group])); } // update the node with custom node properties, including values fetched from backend - node = merge(node, customNode) + node = merge(node, customNode); // label is only used for network visualization node.label = customNode.label ? customNode.label : customNode.id; return node; @@ -97,19 +107,27 @@ export function mapCustomNode(customNode: any, config: IConfig): Node { * * @param customEdge * @param config + * @param drugstoneConfig * @returns */ -export function mapCustomEdge(customEdge: NodeInteraction, config: IConfig): any { +export function mapCustomEdge(customEdge: NodeInteraction, config: IConfig, drugstoneConfig: DrugstoneConfigService): any { let edge; if (customEdge.group === undefined) { // fallback to default node edge = JSON.parse(JSON.stringify(config.edgeGroups.default)); } else { if (config.edgeGroups[customEdge.group] === undefined) { + drugstoneConfig.groupIssue = true; + if (!drugstoneConfig.groupIssueList.includes(customEdge.group)) { + drugstoneConfig.groupIssueList.push(customEdge.group); + } + edge = JSON.parse(JSON.stringify(config.edgeGroups.default)); console.error(`Edge "from ${customEdge.from}" - "to ${customEdge.to}" has undefined edge group ${customEdge.group}.`); + } else + // copy + { + edge = JSON.parse(JSON.stringify(config.edgeGroups[customEdge.group])); } - // copy - edge = JSON.parse(JSON.stringify(config.edgeGroups[customEdge.group])); } edge = { ...edge, @@ -126,16 +144,16 @@ export function mapCustomEdge(customEdge: NodeInteraction, config: IConfig): any * @returns */ export function mapNetexEdge(customEdge: NetexInteraction, config: IConfig, node_map: object): any { - const edges = [] + const edges = []; node_map[customEdge['proteinA']].forEach(from => { node_map[customEdge['proteinB']].forEach(to => { const edge = JSON.parse(JSON.stringify(config.edgeGroups.default)); edge['from'] = from; edge['to'] = to; edge['dataset'] = customEdge['dataset']; - edges.push(edge) - }) - }) + edges.push(edge); + }); + }); return edges; } diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 3b167dd17ff00e95bd1026e24566c613be18708b..3bd0d1ba63f45d188572405cb10c29f81d1b9a37 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -3,6 +3,8 @@ <div class="is-hidden-mobile fullheight" id="appContainer"> <app-fa-icons></app-fa-icons> <app-loading-screen></app-loading-screen> + <app-group-warning></app-group-warning> + <app-parser-warning></app-parser-warning> <app-privacy-banner></app-privacy-banner> <app-license-agreement></app-license-agreement> diff --git a/src/app/pages/explorer-page/explorer-page.component.ts b/src/app/pages/explorer-page/explorer-page.component.ts index b135116cad4db7eaf2d472e6b3ad1d47c92a183a..0b77c6779de1efc7142597072819b94feacce88a 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -77,8 +77,10 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } try { this.networkJSON = JSON.stringify(typeof network === 'string' ? JSON5.parse(network) : network); - } catch { - console.log('ERROR: Failed parsing input network'); + } catch (e) { + this.drugstoneConfig.parsingIssueNetwork = true; + console.error('Failed parsing input network'); + console.error(e); } this.activateConfig(true); } @@ -115,6 +117,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public showThresholdDialog = false; public analysisDialogTarget: 'drug' | 'drug-target'; + public showCustomProteinsDialog = false; public selectedAnalysisToken: string | null = null; @@ -189,8 +192,34 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } public activateConfig(updateNetworkFlag = false) { - let configObj = typeof this._config === 'string' ? this._config.length === 0 ? {} : JSON5.parse(this._config) : this._config; - const groupsObj = typeof this._groups === 'string' ? this._groups.length === 0 ? {} : JSON5.parse(this._groups) : this._groups; + let configObj = {}; + let groupsObj = {}; + try { + if (typeof this._config === 'string') { + if (this._config.length > 0) { + configObj = JSON5.parse(this._config); + } + } else { + configObj = this._config; + } + } catch (e) { + this.drugstoneConfig.parsingIssueConfig = true; + console.error('Error when parsing user defined config JSON. Please check your JSON string for syntax errors.'); + console.error(e); + } + try { + if (typeof this._groups === 'string') { + if (this._groups.length > 0) { + groupsObj = JSON5.parse(this._groups); + } + } else { + groupsObj = this._groups; + } + } catch (e) { + this.drugstoneConfig.parsingIssueGroups = true; + console.error('Error when parsing user defined groups JSON. Please check your JSON string for syntax errors.'); + console.error(e); + } configObj = merge(configObj, groupsObj); if (this.drugstoneConfig.analysisConfig) { this.drugstoneConfig.set_analysisConfig(merge(this.drugstoneConfig.analysisConfig, configObj)); @@ -227,7 +256,13 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { if (this.drugstoneConfig.config.physicsOn) { this.networkHandler.activeNetwork.updatePhysicsEnabled(true); } - this.networkHandler.updateAdjacentNodes().catch(console.error); + this.networkHandler.updateAdjacentNodes().catch(e => { + console.log('also error'); + console.error(e); + }); + }).catch(e => { + console.log('Error'); + console.error(e); }); } @@ -254,6 +289,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { */ public createNetwork(): Promise<any> { return new Promise<any>(async (resolve, reject) => { + this.analysis.resetSelection(); this.networkHandler.activeNetwork.selectedWrapper = null; // getNetwork synchronizes the input network with the database @@ -262,7 +298,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { if (this.networkHandler.activeNetwork.networkPositions) { this.proteinData.updateNodePositions(this.networkHandler.activeNetwork.networkPositions); } - let {nodes, edges} = this.proteinData.mapDataToNetworkInput(this.drugstoneConfig.currentConfig()); + let {nodes, edges} = this.proteinData.mapDataToNetworkInput(this.drugstoneConfig.currentConfig(), this.drugstoneConfig); if (this.drugstoneConfig.config.autofillEdges && nodes.length) { let node_map = {}; nodes.filter(n => n.drugstoneType === 'protein').forEach(node => { @@ -307,12 +343,15 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { if (!this.drugstoneConfig.selfReferences) { edges = edges.filter(el => el.from !== el.to); } + this.nodeData.nodes = new vis.DataSet(nodes); this.nodeData.edges = new vis.DataSet(edges); + const container = this.networkHandler.activeNetwork.networkEl.nativeElement; const options = NetworkSettings.getOptions('main', this.drugstoneConfig.currentConfig()); + this.networkHandler.activeNetwork.networkInternal = new vis.Network(container, this.nodeData, options); this.networkHandler.activeNetwork.networkInternal.on('stabilizationIterationsDone', () => { @@ -321,6 +360,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } }); + // if (!this.drugstoneConfig.config.showSidebar) { // // skip network options for selecting nodes when there are no options to use it // return; @@ -373,6 +413,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { if (this.networkHandler.activeNetwork.selectedWrapper) { this.networkHandler.activeNetwork.networkInternal.selectNodes([this.networkHandler.activeNetwork.selectedWrapper.id]); } + resolve(true); }); } @@ -534,7 +575,14 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { * @param key * @param values */ - public setConfigEdgeGroup(key: string, edgeGroups: { [key: string]: EdgeGroup }) { + public setConfigEdgeGroup(key: string, edgeGroups: { + [key + : + string + ]: + EdgeGroup; + } + ) { // make sure that default-groups are set const defaultNodeGroups = JSON.parse(JSON.stringify(defaultConfig.edgeGroups)); edgeGroups = merge(defaultNodeGroups, edgeGroups); @@ -555,7 +603,9 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.drugstoneConfig.currentConfig()[key] = edgeGroups; } - gProfilerLink(): string { + gProfilerLink() + : + string { // nodes in selection have drugstoneId const queryString = this.analysis.getSelection() .filter(wrapper => wrapper.data.drugstoneType === 'protein') @@ -599,10 +649,12 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { this.openExternal(url); } - //TODO change to access through network service - @ViewChild('analysisPanel') analysisPanel; +//TODO change to access through network service + @ViewChild('analysisPanel') + analysisPanel; - getNodes(): any { + getNodes(): + any { if (this.selectedAnalysisToken && this.analysisPanel) { return this.analysisPanel.getResultNodes(); } @@ -617,7 +669,8 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } - emitTaskEvent(eventObject: object) { + emitTaskEvent(eventObject: object + ) { this.taskEvent.emit(eventObject); } diff --git a/src/app/services/drugstone-config/drugstone-config.service.ts b/src/app/services/drugstone-config/drugstone-config.service.ts index 1fc8ef45235668208b4acf67cae4a11262fe797b..e7feb8ff6fbb7f7e4eaa687a0394a865ba4a004c 100644 --- a/src/app/services/drugstone-config/drugstone-config.service.ts +++ b/src/app/services/drugstone-config/drugstone-config.service.ts @@ -8,6 +8,11 @@ export class DrugstoneConfigService { public config: IConfig = JSON.parse(JSON.stringify(defaultConfig)); public analysisConfig: IConfig = undefined; + public parsingIssueConfig = false; + public parsingIssueNetwork = false; + public parsingIssueGroups = false; + public groupIssue = false; + public groupIssueList = []; public smallStyle = false; constructor() { diff --git a/src/index.html b/src/index.html index 55d3273347e42abdc98f576bfa51ff9c1ccd93ad..67b0d17a8558cdc6ea048e34d0e89e20cf7f5949 100644 --- a/src/index.html +++ b/src/index.html @@ -112,7 +112,7 @@ Export As Graphml Button<br> <div style="max-width: 80vw; width: 1276px; height: 500px"> <drugst-one id="netexp1" - groups='{"edgeGroups":{"default":{"color":"#000000","groupName":"default edge"}}, "nodeGroups" : { "selectedNode": { "borderWidth": 3,"borderWidthSelected": 4,"color": { "border": "#C80BFD","highlight": {"border": "#C80BFD"}},"font": { "color": "#0000000","size": 14 }},"Protein":{"shape":"circle","groupName":"Protein","type":"Protein","color":"#C99AFFFF","font":{"color":"#FFFFFF"}}}}' + groups='{"nodeGroups" : { "selectedNode": { "borderWidth": 3,"borderWidthSelected": 4,"color": { "border": "#C80BFD","highlight": {"border": "#C80BFD"}},"font": { "color": "#0000000","size": 14 }},"Protein":{"shape":"circle","groupName":"Protein","type":"Protein","color":"#C99AFFFF","font":{"color":"#FFFFFF"}}}}' config='{"showAdvAnalysisContent":["enrichment-gprofiler","enrichment-digest"], "activateNetworkMenuButtonAdjacentDisorders": true, "activateNetworkMenuButtonAdjacentDrugs": true, "identifier":"symbol","title":"ROBUST output network", "taskDrugName": "Drug Search", "showLegendNodes": true, "showLegendEdges": true, "showSidebar": "left", "showOverview": true, "legendPos": "left", "legendClass": "legend", "showQuery": true, "showItemSelector": true,"showSimpleAnalysis": false,"showAdvAnalysis": true,"showSelection": true,"showTasks": true,"showNetworkMenu": "right","showLegend": true,"showNetworkMenuButtonExpression": true, "showNetworkMenuButtonScreenshot": true,"showNetworkMenuButtonExportGraphml": true,"showNetworkMenuButtonAdjacentDrugs": true,"showNetworkMenuButtonCenter": true,"showConnectGenes": false,"networkMenuButtonAdjacentDrugsLabel": "Drugs","showNetworkMenuButtonAdjacentDisordersProteins": true,"networkMenuButtonAdjacentDisordersProteinsLabel": "Disorders (protein)","showNetworkMenuButtonAdjacentDisordersDrugs": true,"networkMenuButtonAdjacentDisordersDrugsLabel": "Disorders (drug)","showNetworkMenuButtonAnimation": true,"networkMenuButtonAnimationLabel": "Animation", "autofillEdges": true, "physicsOn": true,"useNedrexLicenced": true,"selfReferences": false, "interactionDrugProtein": "NeDRex", "indicationDrugDisorder": "NeDRex","nodeShadow": true,"edgeShadow": false, "algorithms": {"drug": ["trustrank", "closeness", "degree", "proximity"], "drug-target": ["trustrank", "multisteiner", "keypathwayminer", "degree", "closeness", "betweenness"]}, "associatedProteinDisorder": "NeDRex", "expandNetworkMenu": true}' network='{"nodes": [{"id":"PSEN1","group":"Protein","label":"PSEN1"},{"id":"PSEN2","group":"Protein","label":"PSEN2"},{"id":"APP","group":"Protein","label":"APP"},{"id":"APOE","group":"Protein","label":"APOE"},{"id":"RNF32","group":"Protein","label":"RNF32"},{"id":"STX5","group":"Protein","label":"STX5"},{"id":"TRAF3IP1","group":"Protein","label":"TRAF3IP1"},{"id":"PHB1","group":"Protein","label":"PHB1"},{"id":"MAPT","group":"Protein","label":"MAPT"},{"id":"ESR1","group":"Protein","label":"ESR1"},{"id":"IRF3","group":"Protein","label":"IRF3"},{"id":"DYNC1H1","group":"Protein","label":"DYNC1H1"},{"id":"CUL3","group":"Protein","label":"CUL3"},{"id":"HMGB1","group":"Protein","label":"HMGB1"},{"id":"DNAJC7","group":"Protein","label":"DNAJC7"},{"id":"NEFM","group":"Protein","label":"NEFM"},{"id":"DISC1","group":"Protein","label":"DISC1"},{"id":"PPP5C","group":"Protein","label":"PPP5C"},{"id":"CTNNB1","group":"Protein","label":"CTNNB1"},{"id":"KRAS","group":"Protein","label":"KRAS"}]}' ></drugst-one> @@ -249,7 +249,7 @@ Export As Graphml Button<br> </html> <style> :root { - --drgstn-font-family:Oxygen; + /*--drgstn-font-family:Oxygen;*/ /*--drgstn-panel: #000;*/ /*--drgstn-text-primary: #fff;*/ } diff --git a/src/stylesheets/variables.scss b/src/stylesheets/variables.scss index 634bed1d6ce4c9bf3cb73f3f35586d7d75b49f87..7f30e71ca4507dbdbbb85a48a518ccd2faa52c27 100644 --- a/src/stylesheets/variables.scss +++ b/src/stylesheets/variables.scss @@ -64,6 +64,7 @@ $text-normal-font-size: 12px; $text-small-font-size: 11px; $privacy-banner-z: 40; +$parser-issue-banner-z: 41; $fullscreen-z: 2147483646; $toast-z: 2147483647;