diff --git a/Dockerfile.dev b/Dockerfile.dev index b7be260b872c65073a68c4828ac9fd0e078a9b8f..9a8567034ae8c037e0e21985d179718ee4f24907 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -23,7 +23,7 @@ RUN cp -r website/* /usr/share/nginx/html/ RUN mkdir /usr/share/nginx/html/lib RUN cp dist/covid19/bundle-es2015.js /usr/share/nginx/html/lib/ RUN cp dist/covid19/bundle-es5.js /usr/share/nginx/html/lib/ -RUN cp dist/covid19/styles.scss /usr/share/nginx/html/lib/ +RUN cp dist/covid19/styles.css /usr/share/nginx/html/lib/ COPY nginx/default.conf /etc/nginx/conf.d/ COPY nginx/htpasswd /etc/nginx/htpasswd diff --git a/src/app/analysis.service.ts b/src/app/analysis.service.ts index 23ce8084a92007881179f8b55fc341b98426f564..3e679780c7ce3ede41ba5ba925e1e90e70b04892 100644 --- a/src/app/analysis.service.ts +++ b/src/app/analysis.service.ts @@ -1,4 +1,4 @@ -import {Wrapper, Task, getWrapperFromProtein, Protein, Dataset, Tissue} from './interfaces'; +import {Wrapper, Task, getWrapperFromProtein, Node, Dataset, Tissue} from './interfaces'; import {Subject} from 'rxjs'; import {HttpClient} from '@angular/common/http'; import {environment} from '../environments/environment'; @@ -173,7 +173,7 @@ export class AnalysisService { this.selectListSubject.next({items: newSelection, selected: null}); } - public addExpressedHostProteins(nodes, proteins: Protein[], threshold: number): number { + public addExpressedHostProteins(nodes, proteins: Node[], threshold: number): number { const items: Wrapper[] = []; const visibleIds = new Set<string>(nodes.getIds()); for (const protein of proteins) { @@ -188,7 +188,7 @@ export class AnalysisService { return items.length; } - public addVisibleHostProteins(nodes, proteins: Protein[]): number { + public addVisibleHostProteins(nodes, proteins: Node[]): number { const items: Wrapper[] = []; const visibleIds = new Set<string>(nodes.getIds()); for (const protein of proteins) { @@ -224,7 +224,7 @@ export class AnalysisService { return this.selectedItems.has(wrapper.nodeId); } - proteinInSelection(protein: Protein): boolean { + proteinInSelection(protein: Node): boolean { return this.inSelection(getWrapperFromProtein(protein)); } diff --git a/src/app/components/analysis-panel/analysis-panel.component.ts b/src/app/components/analysis-panel/analysis-panel.component.ts index ca204933f123933c33135b994e4ac9593b7a7808..76e741d220fc1d456a0e0db8a8cf3eb79672343f 100644 --- a/src/app/components/analysis-panel/analysis-panel.component.ts +++ b/src/app/components/analysis-panel/analysis-panel.component.ts @@ -13,7 +13,7 @@ import {HttpClient, HttpErrorResponse} from '@angular/common/http'; import {environment} from '../../../environments/environment'; import {AnalysisService, algorithmNames} from '../../analysis.service'; import { - Protein, + Node, Task, Drug, Wrapper, @@ -57,7 +57,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { @Output() tokenChange = new EventEmitter<string | null>(); @Output() showDetailsChange = new EventEmitter<Wrapper>(); - @Output() visibleItems = new EventEmitter<[any[], [Protein[], Tissue]]>(); + @Output() visibleItems = new EventEmitter<[any[], [Node[], Tissue]]>(); public task: Task | null = null; @@ -73,8 +73,8 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { public effects: any; public tableDrugs: Array<Drug & Scored & Baited> = []; - public tableProteins: Array<Protein & Scored & Seeded & Baited> = []; - public tableSelectedProteins: Array<Protein & Scored & Seeded & Baited> = []; + public tableProteins: Array<Node & Scored & Seeded & Baited> = []; + public tableSelectedProteins: Array<Node & Scored & Seeded & Baited> = []; public tableViralProteins: Array<Scored & Seeded> = []; public tableSelectedViralProteins: Array<Scored & Seeded> = []; public tableNormalize = false; @@ -171,7 +171,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { this.tableSelectedProteins = []; this.tableProteins.forEach((r) => { r.rawScore = r.score; - r.isSeed = isSeed[r.proteinAc]; + r.isSeed = isSeed[r.id]; r.closestViralProteins = (r.closestViralProteins as any).split(','); if (this.analysis.proteinInSelection(r)) { this.tableSelectedProteins.push(r); @@ -410,17 +410,17 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { }; } - private mapNode(nodeType: WrapperType, details: Protein | Drug, isSeed?: boolean, score?: number): any { + private mapNode(nodeType: WrapperType, details: Node | Drug, isSeed?: boolean, score?: number): any { let nodeLabel; let wrapper: Wrapper; let drugType; let drugInTrial; if (nodeType === 'protein') { - const protein = details as Protein; + const protein = details as Node; wrapper = getWrapperFromProtein(protein); nodeLabel = protein.name; if (!protein.name) { - nodeLabel = protein.proteinAc; + nodeLabel = protein.id; } } else if (nodeType === 'drug') { const drug = details as Drug; @@ -596,14 +596,14 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { node.wrapper = item; node.gradient = 1.0; protein.expressionLevel = undefined; - (node.wrapper.data as Protein).expressionLevel = undefined; + (node.wrapper.data as Node).expressionLevel = undefined; updatedNodes.push(node); } this.nodeData.nodes.update(updatedNodes); } else { this.selectedTissue = tissue; const minExp = 0.3; - this.http.get<Array<{ protein: Protein, level: number }>>( + this.http.get<Array<{ protein: Node, level: number }>>( `${environment.backend}tissue_expression/?tissue=${tissue.id}&token=${this.token}`) .subscribe((levels) => { const updatedNodes = []; @@ -629,7 +629,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges { node.wrapper = item; node.gradient = gradient; this.proteins.find(prot => getProteinNodeId(prot) === item.nodeId).expressionLevel = lvl.level; - (node.wrapper.data as Protein).expressionLevel = lvl.level; + (node.wrapper.data as Node).expressionLevel = lvl.level; updatedNodes.push(node); } this.nodeData.nodes.update(updatedNodes); diff --git a/src/app/components/info-tile/info-tile.component.html b/src/app/components/info-tile/info-tile.component.html index 9d64bd448f771879cbb64f320225984bfe51196f..ff2286df6d46491dfbe60dc828aac37fb5e3ce93 100644 --- a/src/app/components/info-tile/info-tile.component.html +++ b/src/app/components/info-tile/info-tile.component.html @@ -1,52 +1,20 @@ <div *ngIf="wrapper"> - <div *ngIf="wrapper.type === 'protein'"> + <div> <p> - <b><span>Gene Name: </span></b> + <b><span>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> + <b><span>Access:</span></b> <a href="https://www.uniprot.org/uniprot/{{ wrapper.data.proteinAc }}" target="_blank"> - {{ wrapper.data.proteinAc }} + {{ wrapper.data.access }} </a> </p> - <p> - <b><span>Name: </span></b> - {{ wrapper.data.proteinName }} - </p> <p *ngIf="wrapper.data.expressionLevel"> <b><span>Expression level: </span></b> {{ wrapper.data.expressionLevel|number }} </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 === 'unapproved' "> - <b>Status: </b> Unapproved - <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> - <p *ngIf="wrapper.data.inTrial"> - <b>In trial(s) </b> <span class="icon is-small"><i class="fas fa-check"></i></span> - </p> - <div *ngIf="wrapper.data.inTrial && wrapper.data.trialLinks.length > 0" class="list"> - <div *ngFor="let link of wrapper.data.trialLinks" class="list-item"> - <a [href]="link" target="_blank">{{beautify(link)}}</a> - </div> - <small class="list-item"><i>Links provided by WHO</i></small> - </div> - </div> <div class="field has-addons add-remove-toggle" *ngIf="wrapper.type !== 'drug'"> <app-toggle [value]="analysis.inSelection(wrapper)" diff --git a/src/app/components/query-tile/query-tile.component.ts b/src/app/components/query-tile/query-tile.component.ts index 63997a24984901c9a0c6757117be5c612be0996d..effd4a12bf2201996568f536b9d76e5286be6cea 100644 --- a/src/app/components/query-tile/query-tile.component.ts +++ b/src/app/components/query-tile/query-tile.component.ts @@ -1,5 +1,5 @@ import {Component, Input, Output, EventEmitter} from '@angular/core'; -import {Protein, Wrapper} from '../../interfaces'; +import {Node, Wrapper} from '../../interfaces'; @Component({ selector: 'app-query-tile-component', @@ -14,9 +14,8 @@ export class QueryTileComponent { querySearch(term: string, item: Wrapper) { term = term.toLowerCase(); - const data = item.data as Protein; - return data.name.toLowerCase().indexOf(term) > -1 || data.proteinName.toLowerCase().indexOf(term) > -1 || - item.type.toLowerCase().indexOf(term) > -1; + const data = item.data as Node; + return data.name.toLowerCase().indexOf(term) > -1 || item.type.toLowerCase().indexOf(term) > -1; } select(item) { diff --git a/src/app/config.ts b/src/app/config.ts index 34a5a918cbbb1de0c39d47bc9d762a7bb4b4651f..6495a82bd026cbca80f93910df2b77026c0ed395 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -1,3 +1,14 @@ +// export interface NodeGroup { +// fill: string; +// } +// +// export interface EdgeGroup { +// color: string; +// } + +type NodeGroup = any; +type EdgeGroup = any; + export interface IConfig { legendUrl: string; legendClass: string; @@ -11,6 +22,8 @@ export interface IConfig { showTasks: boolean; showSelection: boolean; showFooter: boolean; + nodeGroups: { [key: string]: NodeGroup }; + edgeGroups: { [key: string]: EdgeGroup }; } export const defaultConfig: IConfig = { @@ -26,4 +39,20 @@ export const defaultConfig: IConfig = { showSelection: true, showTasks: true, showFooter: true, + nodeGroups: { + default: { + color: 'white' + }, + protein: { + color: 'red' + }, + drug: { + color: 'green' + } + }, + edgeGroups: { + default: { + color: 'black' + } + }, }; 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 6b9e198705199d78463c8278e166f904a1126034..13d96d13b30d4e610d6c49fa130365dcc2d00cfc 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 '../../analysis.service'; -import {getWrapperFromProtein, Protein, Tissue} from '../../interfaces'; +import {getWrapperFromProtein, Node, Tissue} from '../../interfaces'; import {environment} from '../../../environments/environment'; import {HttpClient} from '@angular/common/http'; @@ -18,7 +18,7 @@ export class AddExpressedProteinsComponent implements OnChanges { @Input() public visibleNodes: Array<any> = []; @Input() - public currentViewProteins: Array<Protein> = []; + public currentViewProteins: Array<Node> = []; @Input() public selectedTissue: Tissue | null = null; diff --git a/src/app/dialogs/custom-proteins/custom-proteins.component.ts b/src/app/dialogs/custom-proteins/custom-proteins.component.ts index a6994260cddbaba0302cadec4b03dfa1fd9cc1fa..1dd7e86911e0bb8bd254eb43f6073452c402e060 100644 --- a/src/app/dialogs/custom-proteins/custom-proteins.component.ts +++ b/src/app/dialogs/custom-proteins/custom-proteins.component.ts @@ -1,7 +1,7 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {environment} from '../../../environments/environment'; -import {getWrapperFromProtein, Protein, Wrapper} from '../../interfaces'; +import {getWrapperFromProtein, Node, Wrapper} from '../../interfaces'; import {AnalysisService} from '../../analysis.service'; @Component({ @@ -73,7 +73,7 @@ export class CustomProteinsComponent implements OnInit { const proteinItems = []; const items = []; for (const detail of details) { - proteinItems.push(detail as Protein); + proteinItems.push(detail as Node); items.push(getWrapperFromProtein(detail)); } this.itemsFound = items; diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts index 230bb223c1acbabf0ec1b368f7574b048d317332..e03acc016cc4c8846b9407d13bb31bd845f703ec 100644 --- a/src/app/interfaces.ts +++ b/src/app/interfaces.ts @@ -1,10 +1,12 @@ import {AlgorithmType, QuickAlgorithmType} from './analysis.service'; -export interface Protein { +export interface Node { name: string; - proteinAc: string; - proteinName: string; - interactions?: Protein[]; + id: string; + access: string; + group?: string; + + interactions?: Node[]; x?: number; y?: number; expressionLevel?: number; @@ -15,9 +17,10 @@ export interface Tissue { name: string; } -export interface ProteinProteinInteraction { +export interface NodeInteraction { from: string; to: string; + group?: string; } export interface NetworkEdge { @@ -51,15 +54,15 @@ export interface Task { }; } -export function getProteinNodeId(protein: Protein) { - return `p_${protein.proteinAc}`; +export function getProteinNodeId(protein: Node) { + return `p_${protein.id}`; } -export function getProteinBackendId(protein: Protein) { - return protein.proteinAc; +export function getProteinBackendId(protein: Node) { + return protein.id; } -export function getNodeIdsFromI(pvi: ProteinProteinInteraction) { +export function getNodeIdsFromI(pvi: NodeInteraction) { return { from: `p_${pvi.from}`, to: `p_${pvi.to}`, @@ -88,7 +91,7 @@ export function getDrugBackendId(drug: Drug) { return drug.drugId; } -export function getWrapperFromProtein(protein: Protein): Wrapper { +export function getWrapperFromProtein(protein: Node): Wrapper { return { backendId: getProteinBackendId(protein), nodeId: getProteinNodeId(protein), diff --git a/src/app/main-network.ts b/src/app/main-network.ts index 2217f357dedd41e734e5ab41af434aa4d2a55298..27e2f2032ec316ee8c041be6a90f699243b4b867 100644 --- a/src/app/main-network.ts +++ b/src/app/main-network.ts @@ -1,5 +1,5 @@ import {HttpClient} from '@angular/common/http'; -import {ProteinProteinInteraction, Protein, getProteinNodeId} from './interfaces'; +import {NodeInteraction, Node, getProteinNodeId} from './interfaces'; export function getDatasetFilename(dataset: Array<[string, string]>): string { return `network-${JSON.stringify(dataset).replace(/[\[\]\",]/g, '')}.json`; @@ -7,7 +7,7 @@ export function getDatasetFilename(dataset: Array<[string, string]>): string { export class ProteinNetwork { - constructor(public proteins: Protein[], public edges: ProteinProteinInteraction[]) { + constructor(public proteins: Node[], public edges: NodeInteraction[]) { } public async loadPositions(http: HttpClient, dataset: Array<[string, string]>) { @@ -21,8 +21,8 @@ export class ProteinNetwork { }); } - public getProtein(ac: string): Protein | undefined { - return this.proteins.find((p) => p.proteinAc === ac); + public getProtein(ac: string): Node | undefined { + return this.proteins.find((p) => p.id === ac); } public linkNodes() { diff --git a/src/app/pages/explorer-page/explorer-page.component.ts b/src/app/pages/explorer-page/explorer-page.component.ts index 66f901e69948b935e55fae5fb6e4e76465bd78bf..8ac0357a8baac6d41136b34c4716f5dae333206f 100644 --- a/src/app/pages/explorer-page/explorer-page.component.ts +++ b/src/app/pages/explorer-page/explorer-page.component.ts @@ -6,8 +6,8 @@ import { ViewChild } from '@angular/core'; import { - ProteinProteinInteraction, - Protein, + NodeInteraction, + Node, Wrapper, getWrapperFromProtein, Tissue, getNodeIdsFromI @@ -44,6 +44,10 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { const configObj = JSON.parse(config); for (const key of Object.keys(configObj)) { + if (key === 'nodeGroups' || key === 'edgeGroups') { + this.myConfig[key] = {...this.myConfig[key], ...configObj[key]}; + continue; + } this.myConfig[key] = configObj[key]; } } @@ -98,7 +102,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { public currentDataset = []; - public currentViewProteins: Protein[]; + public currentViewProteins: Node[]; public currentViewSelectedTissue: Tissue | null = null; public currentViewNodes: any[]; @@ -133,13 +137,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { node.y = pos[item.nodeId].y; node.x = pos[item.nodeId].x; node.y = pos[item.nodeId].y; - Object.assign(node, NetworkSettings.getNodeStyle( - node.wrapper.type, - true, - selected, - undefined, - undefined, - node.gradient)); + Object.assign(node, this.myConfig.nodeGroups[node.wrapper.data.group]); updatedNodes.push(node); } this.nodeData.nodes.update(updatedNodes); @@ -147,14 +145,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { const updatedNodes = []; this.nodeData.nodes.forEach((node) => { const nodeSelected = this.analysis.idInSelection(node.id); - Object.assign(node, NetworkSettings.getNodeStyle( - node.wrapper.type, - true, - nodeSelected, - undefined, - undefined, - node.gradient)); - updatedNodes.push(node); + Object.assign(node, this.myConfig.nodeGroups[node.wrapper.data.group]); }); this.nodeData.nodes.update(updatedNodes); } @@ -269,7 +260,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { } } - fillQueryItems(hostProteins: Protein[]) { + fillQueryItems(hostProteins: Node[]) { this.queryItems = []; hostProteins.forEach((protein) => { this.queryItems.push(getWrapperFromProtein(protein)); @@ -297,30 +288,32 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }); } - private mapHostProteinToNode(hostProtein: Protein): any { - const wrapper = getWrapperFromProtein(hostProtein); - const node = NetworkSettings.getNodeStyle('protein', true, this.analysis.inSelection(wrapper)); - let nodeLabel = hostProtein.name; - if (hostProtein.name.length === 0) { - nodeLabel = hostProtein.proteinAc; + private mapCustomNode(customNode: Node): any { + let group = customNode.group; + if (typeof group === 'undefined' || typeof this.myConfig.nodeGroups[group] === 'undefined') { + group = 'default'; + } + const node = JSON.parse(JSON.stringify(this.myConfig.nodeGroups[group])); + let nodeLabel = customNode.name; + if (customNode.name.length === 0) { + nodeLabel = customNode.id; } node.label = nodeLabel; - node.id = wrapper.nodeId; - node.x = hostProtein.x; - node.y = hostProtein.y; - node.wrapper = wrapper; + node.id = customNode.id; + node.x = customNode.x; + node.y = customNode.y; return node; } - private mapEdge(edge: ProteinProteinInteraction): any { - const {from, to} = getNodeIdsFromI(edge); - return { - from, to, - color: { - color: NetworkSettings.getColor('edgeHostVirus'), - highlight: NetworkSettings.getColor('edgeHostVirusHighlight') - }, - }; + private mapCustomEdge(customEdge: NodeInteraction): any { + let group = customEdge.group; + if (typeof group === 'undefined' || typeof this.myConfig.edgeGroups[group] === 'undefined') { + group = 'default'; + } + const edge = JSON.parse(JSON.stringify(this.myConfig.edgeGroups[group])); + edge.from = customEdge.from; + edge.to = customEdge.to; + return edge; } private mapDataToNodes(data: ProteinNetwork): { nodes: any[], edges: any[] } { @@ -328,11 +321,11 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { const edges = []; for (const protein of data.proteins) { - nodes.push(this.mapHostProteinToNode(protein)); + nodes.push(this.mapCustomNode(protein)); } for (const edge of data.edges) { - edges.push(this.mapEdge(edge)); + edges.push(this.mapCustomEdge(edge)); } return { @@ -351,7 +344,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { }); } - analysisWindowChanged($event: [any[], [Protein[], Tissue]]) { + analysisWindowChanged($event: [any[], [Node[], Tissue]]) { if ($event) { this.currentViewNodes = $event[0]; this.currentViewProteins = $event[1][0]; @@ -409,7 +402,7 @@ export class ExplorerPageComponent implements OnInit, AfterViewInit { node.wrapper = item; node.gradient = 1.0; protein.expressionLevel = undefined; - (node.wrapper.data as Protein).expressionLevel = undefined; + (node.wrapper.data as Node).expressionLevel = undefined; updatedNodes.push(node); } this.nodeData.nodes.update(updatedNodes); diff --git a/src/index.html b/src/index.html index 5fc741c8cbffb2c5b2794657dd5615369ae5de72..36090d5937e2ef129f38ed292f04da4670226506 100644 --- a/src/index.html +++ b/src/index.html @@ -24,7 +24,7 @@ <button onclick="setNetwork('netexp1')">Add proteins</button> <div> - <network-expander id="netexp1" config='{"legendClass": "my-legend-1", "showQuery": false}' onload="init1()" style="height: 100vh"></network-expander> + <network-expander id="netexp1" config='{"showQuery": false, "nodeGroups": {"default": {"color": "grey"}}, "edgeGroups":{"default": {"color": "grey"}, "custom": {"color": "red"}}}' onload="init1()" style="height: 100vh"></network-expander> </div> <!-- @@ -55,29 +55,32 @@ netexp.setAttribute('network', JSON.stringify({ nodes: [ { - name: "SIRT5", - proteinAc: "Q9NXA8", - proteinName: "NAD-dependent protein deacylase sirtuin-5" + name: "Protein 1", + id: "1", + access: "A", + group: "protein" }, { - name: "RPL36", - proteinAc: "Q9Y3U8", - proteinName: "60S ribosomal protein L36" + name: "Unknown type", + id: "2", + access: "B" }, { - name: "G3BP2", - proteinAc: "Q9UNAA86", - proteinName: "Ras GTPase-activating protein-binding protein 2" + name: "Drug 123", + id: "3", + access: "C", + group: "drug" } ], edges: [ { - from: 'Q9NXA8', - to: 'Q9Y3U8', + from: '1', + to: '2', }, { - from: 'Q9Y3U8', - to: 'Q9UN86', + from: '2', + to: '3', + group: 'custom' } ] }));