Select Git revision
-
Jettka, Daniel authoredJettka, Daniel authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
analysis-panel.component.ts 20.57 KiB
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild,
} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {environment} from '../../../environments/environment';
import {algorithmNames, AnalysisService} from '../../services/analysis/analysis.service';
import {
Drug,
EdgeType,
NodeAttributeMap,
getDrugNodeId,
getProteinNodeId,
getWrapperFromNode,
LegendContext,
Node,
Task,
Tissue,
Wrapper,
NodeInteraction,
} from '../../interfaces';
import domtoimage from 'dom-to-image';
import {NetworkSettings} from '../../network-settings';
import {NetexControllerService} from 'src/app/services/netex-controller/netex-controller.service';
import {defaultConfig, IConfig} from 'src/app/config';
import {mapCustomEdge, mapCustomNode} from 'src/app/main-network';
import {downLoadFile, pieChartContextRenderer, removeDuplicateObjectsFromList} from 'src/app/utils';
import {DrugstoneConfigService} from 'src/app/services/drugstone-config/drugstone-config.service';
import {NetworkHandlerService} from 'src/app/services/network-handler/network-handler.service';
import {LegendService} from 'src/app/services/legend-service/legend-service.service';
import { LoadingScreenService } from 'src/app/services/loading-screen/loading-screen.service';
declare var vis: any;
interface Scored {
score: number; // Normalized or unnormalized (whichever user selects, will be displayed in the table)
rawScore: number; // Unnormalized (kept to restore unnormalized value)
}
interface Seeded {
isSeed: boolean;
}
@Component({
selector: 'app-analysis-panel',
templateUrl: './analysis-panel.component.html',
styleUrls: ['./analysis-panel.component.scss'],
})
export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit {
@ViewChild('networkWithLegend', {static: false}) networkWithLegendEl: ElementRef;
@Input() token: string | null = null;
@Output() tokenChange = new EventEmitter<string | null>();
@Output() showDetailsChange = new EventEmitter<Wrapper>();
@Output() setInputNetwork = new EventEmitter<any>();
@Output() visibleItems = new EventEmitter<[any[], [Node[], Tissue], NodeInteraction[]]>();
public task: Task | null = null;
public result: any = null;
public fullscreen = false;
public network: any;
public nodeData: { nodes: any, edges: any } = {nodes: null, edges: null};
// private drugNodes: any[] = [];
// private drugEdges: any[] = [];
public tab: 'meta' | 'network' | 'table' = 'table';
// public adjacentDrugs = false;
// public adjacentDrugList: Node[] = [];
// public adjacentDrugEdgesList: Node[] = [];
//
// public adjacentDisordersProtein = false;
// public adjacentDisordersDrug = false;
//
// public adjacentProteinDisorderList: Node[] = [];
// public adjacentProteinDisorderEdgesList: Node[] = [];
//
// public adjacentDrugDisorderList: Node[] = [];
// public adjacentDrugDisorderEdgesList: Node[] = [];
private proteins: any;
public effects: any;
public tableDrugs: Array<Drug & Scored> = [];
public tableProteins: Array<Node & Scored & Seeded> = [];
public tableSelectedProteins: Array<Node & Scored & Seeded> = [];
public tableNormalize = false;
public tableHasScores = false;
public LegendContext: LegendContext = 'drugTarget';
public expressionExpanded = false;
public selectedTissue: Tissue | null = null;
public algorithmNames = algorithmNames;
public tableDrugScoreTooltip = '';
public tableProteinScoreTooltip = '';
public expressionMap: NodeAttributeMap;
public loading = false;
constructor(public legendService: LegendService, public networkHandler: NetworkHandlerService, public drugstoneConfig: DrugstoneConfigService, private http: HttpClient, public analysis: AnalysisService, public netex: NetexControllerService, public loadingScreen: LoadingScreenService) {
}
async ngOnInit() {
}
ngAfterViewInit() {
this.networkHandler.setActiveNetwork('analysis');
}
async ngOnChanges(changes: SimpleChanges) {
await this.refresh();
}
private async refresh() {
if (this.token) {
this.loadingScreen.stateUpdate(true);
this.task = await this.getTask(this.token);
this.analysis.switchSelection(this.token);
if (this.task.info.algorithm === 'degree') {
this.tableDrugScoreTooltip =
'Normalized number of direct interactions of the drug with the seeds. ' +
'The higher the score, the more relevant the drug.';
this.tableProteinScoreTooltip =
'Normalized number of direct interactions of the protein with the seeds. ' +
'The higher the score, the more relevant the protein.';
} else if (this.task.info.algorithm === 'closeness' || this.task.info.algorithm === 'quick' || this.task.info.algorithm === 'super') {
this.tableDrugScoreTooltip =
'Normalized inverse mean distance of the drug to the seeds. ' +
'The higher the score, the more relevant the drug.';
this.tableProteinScoreTooltip =
'Normalized inverse mean distance of the protein to the seeds. ' +
'The higher the score, the more relevant the protein.';
} else if (this.task.info.algorithm === 'trustrank') {
this.tableDrugScoreTooltip =
'Amount of ‘trust’ on the drug at termination of the algorithm. ' +
'The higher the score, the more relevant the drug.';
this.tableProteinScoreTooltip =
'Amount of ‘trust’ on the protein at termination of the algorithm. ' +
'The higher the score, the more relevant the protein.';
} else if (this.task.info.algorithm === 'proximity') {
this.tableDrugScoreTooltip =
'Empirical z-score of mean minimum distance between the drug’s targets and the seeds. ' +
'The lower the score, the more relevant the drug.';
this.tableProteinScoreTooltip =
'Empirical z-score of mean minimum distance between the drug’s targets and the seeds. ' +
'The lower the score, the more relevant the drug.';
}
if (this.task && this.task.info.done) {
this.loading = true;
this.netex.getTaskResult(this.token).then(result => {
this.drugstoneConfig.set_analysisConfig(result.parameters.config);
this.result = result;
if (this.result.parameters.target === 'drug') {
this.legendService.add_to_context('drug');
} else {
this.legendService.add_to_context('drugTarget');
}
const nodeAttributes = this.result.nodeAttributes || {};
this.networkHandler.activeNetwork.seedMap = nodeAttributes.isSeed || {};
// Reset
this.nodeData = {nodes: null, edges: null};
this.networkHandler.activeNetwork.networkEl.nativeElement.innerHTML = '';
this.networkHandler.activeNetwork.networkInternal = null;
// Create
this.createNetwork(this.result).then(nw => {
const nodes = nw.nodes;
const edges = nw.edges;
this.networkHandler.activeNetwork.inputNetwork = {nodes: nodes, edges: edges};
this.nodeData.nodes = new vis.DataSet(nodes);
this.nodeData.edges = new vis.DataSet(edges);
const container = this.networkHandler.activeNetwork.networkEl.nativeElement;
const isBig = nodes.length > 100 || edges.length > 100;
const options = NetworkSettings.getOptions(isBig ? 'analysis-big' : 'analysis', this.drugstoneConfig.currentConfig());
// @ts-ignore
options.groups = this.drugstoneConfig.currentConfig().nodeGroups;
// @ts-ignore
for (const g of Object.values(options.groups)) {
// @ts-ignore
delete g.renderer;
}
this.drugstoneConfig.config.physicsOn = !isBig;
this.networkHandler.activeNetwork.networkInternal = new vis.Network(container, this.nodeData, options);
this.networkHandler.activeNetwork.networkInternal.on('stabilizationIterationsDone', () => {
if (!this.drugstoneConfig.config.physicsOn) {
this.networkHandler.activeNetwork.updatePhysicsEnabled(false);
}
this.networkHandler.updateAdjacentNodes();
});
this.tableDrugs = nodes.filter(e => e.drugstoneId && e.drugstoneType === 'drug');
this.tableDrugs.forEach((r) => {
r.rawScore = r.score;
});
this.tableProteins = nodes.filter(e => e.drugstoneId && e.drugstoneType === 'protein');
this.tableSelectedProteins = [];
this.tableProteins.forEach((r) => {
r.rawScore = r.score;
r.isSeed = this.networkHandler.activeNetwork.seedMap[r.id];
const wrapper = getWrapperFromNode(r);
if (this.analysis.inSelection(wrapper)) {
this.tableSelectedProteins.push(r);
}
});
this.tableHasScores = ['trustrank', 'closeness', 'degree', 'betweenness', 'quick', 'super']
.indexOf(this.task.info.algorithm) !== -1;
if (this.tableHasScores) {
this.toggleNormalization(true);
}
this.networkHandler.activeNetwork.networkInternal.setData({nodes: undefined, edge: undefined});
setTimeout(() => {
this.networkHandler.activeNetwork.networkInternal.setData(this.nodeData);
}, 1000);
this.networkHandler.activeNetwork.networkInternal.on('deselectNode', (properties) => {
this.showDetailsChange.emit(null);
});
this.networkHandler.activeNetwork.networkInternal.on('doubleClick', (properties) => {
const nodeIds: Array<string> = properties.nodes;
if (nodeIds.length > 0) {
const nodeId = nodeIds[0];
const node = this.nodeData.nodes.get(nodeId);
if (node.drugstoneId === undefined || node.nodeType === 'drug' || node.drugstoneType !== 'protein') {
this.analysis.unmappedNodeToast();
return;
}
const wrapper = getWrapperFromNode(node);
if (this.analysis.inSelection(wrapper)) {
this.analysis.removeItems([wrapper]);
this.analysis.getCount();
} else {
this.analysis.addItems([wrapper]);
this.analysis.getCount();
}
}
});
this.networkHandler.activeNetwork.networkInternal.on('click', (properties) => {
const selectedNodes = this.nodeData.nodes.get(properties.nodes);
if (selectedNodes.length > 0) {
this.showDetailsChange.emit(getWrapperFromNode(selectedNodes[0]));
} else {
this.showDetailsChange.emit(null);
}
});
this.analysis.subscribeList((items, selected) => {
// return if analysis panel is closed or no nodes are loaded
if (!this.token) {
return;
}
if (selected !== null) {
const updatedNodes: Node[] = [];
for (const item of items) {
const node = this.nodeData.nodes.get(item.id);
if (!node) {
continue;
}
const pos = this.networkHandler.activeNetwork.networkInternal.getPositions([item.id]);
node.x = pos[item.id].x;
node.y = pos[item.id].y;
const isSeed = this.networkHandler.activeNetwork.highlightSeeds ? this.networkHandler.activeNetwork.seedMap[node.id] : false;
const nodeStyled = NetworkSettings.getNodeStyle(
node,
this.drugstoneConfig.currentConfig(),
isSeed,
selected,
this.networkHandler.activeNetwork.getGradient(item.id),
this.networkHandler.activeNetwork.nodeRenderer
);
updatedNodes.push(nodeStyled);
}
this.nodeData.nodes.update(updatedNodes);
const proteinSelection = this.tableSelectedProteins;
for (const item of items) {
// TODO: Refactor!
const found = proteinSelection.findIndex((i) => getProteinNodeId(i) === item.id);
const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.id);
if (selected && found === -1 && tableItem) {
proteinSelection.push(tableItem);
}
if (!selected && found !== -1 && tableItem) {
proteinSelection.splice(found, 1);
}
}
this.tableSelectedProteins = [...proteinSelection];
} else {
// else: selected is null
const updatedNodes = [];
this.nodeData.nodes.forEach((node) => {
const isSeed = this.networkHandler.activeNetwork.highlightSeeds ? this.networkHandler.activeNetwork.seedMap[node.id] : false;
if (!isSeed) {
return;
}
const nodeStyled = NetworkSettings.getNodeStyle(
node,
this.drugstoneConfig.currentConfig(),
isSeed,
selected,
this.networkHandler.activeNetwork.getGradient(node.id),
this.networkHandler.activeNetwork.nodeRenderer
);
updatedNodes.push(nodeStyled);
});
this.nodeData.nodes.update(updatedNodes);
const proteinSelection = [];
for (const item of items) {
const tableItem = this.tableProteins.find((i) => getProteinNodeId(i) === item.id);
if (tableItem) {
proteinSelection.push(tableItem);
}
}
this.tableSelectedProteins = [...proteinSelection];
}
});
this.emitVisibleItems(true);
});
this.loadingScreen.stateUpdate(false);
});
}
}
}
public emitVisibleItems(on: boolean) {
if (on) {
this.visibleItems.emit([this.nodeData.nodes, [this.proteins, this.selectedTissue], this.nodeData.edges]);
} else {
this.visibleItems.emit(null);
}
}
private async getTask(token: string): Promise<any> {
return await this.http.get(`${this.netex.getBackend()}task/?token=${token}`).toPromise();
}
close() {
this.networkHandler.activeNetwork.gradientMap = {};
this.drugstoneConfig.remove_analysisConfig();
this.expressionExpanded = false;
this.expressionMap = undefined;
this.networkHandler.activeNetwork.seedMap = {};
this.networkHandler.activeNetwork.highlightSeeds = false;
this.analysis.switchSelection('main');
this.token = null;
this.tokenChange.emit(this.token);
this.legendService.remove_from_context('drug');
this.legendService.remove_from_context('drugTarget');
this.emitVisibleItems(false);
}
public toggleNormalization(normalize: boolean) {
this.tableNormalize = normalize;
const normalizeFn = (table) => {
let max = 0;
table.forEach(i => {
if (i.rawScore > max) {
max = i.rawScore;
}
});
table.forEach(i => {
i.score = i.rawScore / max;
});
};
const unnormalizeFn = (table) => {
table.forEach(i => {
i.score = i.rawScore;
});
};
if (normalize) {
normalizeFn(this.tableProteins);
if (this.task.info.target === 'drug') {
normalizeFn(this.tableDrugs);
}
} else {
unnormalizeFn(this.tableProteins);
if (this.task.info.target === 'drug') {
unnormalizeFn(this.tableDrugs);
}
}
}
public downloadLink(view: string): string {
return `${this.netex.getBackend()}task_result/?token=${this.token}&view=${view}&fmt=csv`;
}
/**
* Maps analysis result returned from database to valid Vis.js network input
*
* @param result
* @returns
*/
public async createNetwork(result: any): Promise<{ edges: any[]; nodes: any[]; }> {
const identifier = this.drugstoneConfig.currentConfig().identifier;
// add drugGroup and foundNodesGroup for added nodes
// these groups can be overwritten by the user
const nodes = [];
let edges = [];
const attributes = result.nodeAttributes || {};
this.proteins = [];
this.effects = [];
const network = result.network;
network.nodes = [...new Set<string>(network.nodes)];
const details = attributes.details || {};
const nodeIdMap = {};
// @ts-ignore
Object.entries(details).filter(e => e[1].drugstoneType === 'protein').forEach(e => {
// @ts-ignore
e[1].drugstoneId.forEach(id => {
nodeIdMap[id] = e[1][identifier][0];
});
});
for (const nodeId of network.nodes) {
if (details[nodeId]) {
const nodeDetails = details[nodeId];
nodeDetails.id = nodeDetails.id ? nodeDetails.id : (typeof nodeDetails.drugstoneId === 'string' ? nodeDetails.drugstoneId : nodeDetails.drugstoneId[0]);
if (nodeDetails.drugstoneId && nodeDetails.drugstoneType === 'protein') {
// node is protein from database, has been mapped on init to backend protein from backend
// or was found during analysis
// FIXME connectorNodes are not visualized correctly
nodeDetails.group = result.targetNodes && result.targetNodes.indexOf(nodeId) !== -1 ? 'foundNode' : (nodeDetails.group ? nodeDetails.group : 'connectorNode');
nodeDetails.label = nodeDetails.label ? nodeDetails.label : nodeDetails[identifier];
nodeDetails.id = nodeDetails[identifier][0] ? nodeDetails[identifier][0] : nodeDetails.id;
this.proteins.push(nodeDetails);
} else if (nodeDetails.drugstoneId && nodeDetails.drugstoneType === 'drug') {
// node is drug, was found during analysis
nodeDetails.type = 'Drug';
nodeDetails.group = 'foundDrug';
} else {
// node is custom input from user, could not be mapped to backend protein
nodeDetails.group = nodeDetails.group ? nodeDetails.group : 'default';
nodeDetails.label = nodeDetails.label ? nodeDetails.label : nodeDetails[identifier];
}
// further analysis and the button function can be used to highlight seeds
// option to use scores[node] as gradient, but sccores are very small
nodes.push(NetworkSettings.getNodeStyle(nodeDetails as Node, this.drugstoneConfig.currentConfig(), false, false, 1, this.networkHandler.activeNetwork.nodeRenderer));
} else {
console.log('Missing details for ' + nodeId);
}
}
const uniqEdges = [];
for (const edge of network.edges) {
const e = mapCustomEdge(edge, this.drugstoneConfig.currentConfig());
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;
if (uniqEdges.indexOf(hash) === -1) {
uniqEdges.push(hash);
edges.push(e);
}
}
// remove self-edges/loops
if (!this.drugstoneConfig.currentConfig().selfReferences) {
edges = edges.filter(el => el.from !== el.to);
}
return {
nodes,
edges,
};
}
getResultNodes() {
if (this.nodeData && this.nodeData['nodes']) {
return this.nodeData['nodes'].get();
}
return [];
}
getResultEdges() {
if (this.nodeData && this.nodeData['edges']) {
return this.nodeData['edges'].get().filter(e => !e.id || !e.groupName || (typeof e.from === 'string' && typeof e.to === 'string'));
}
return [];
}
public tableProteinSelection = (e): void => {
const oldSelection = [...this.tableSelectedProteins];
this.tableSelectedProteins = e;
const addItems = [];
const removeItems = [];
for (const i of this.tableSelectedProteins) {
const wrapper = getWrapperFromNode(i);
if (oldSelection.indexOf(i) === -1) {
addItems.push(wrapper);
}
}
for (const i of oldSelection) {
const wrapper = getWrapperFromNode(i);
if (this.tableSelectedProteins.indexOf(i) === -1) {
removeItems.push(wrapper);
}
}
this.analysis.addItems(addItems);
this.analysis.removeItems(removeItems);
};
public toggleFullscreen() {
this.fullscreen = !this.fullscreen;
this.loadingScreen.fullscreenUpdate(this.fullscreen)
}
}