From 591e5038da1f20436683c5b41c8a424b65755581 Mon Sep 17 00:00:00 2001 From: "Hartung, Michael" <michael.hartung@uni-hamburg.de> Date: Fri, 5 May 2023 19:02:24 +0200 Subject: [PATCH] expression data to network; graphml and json export in frontend --- .../analysis-panel.component.ts | 14 +- .../download-button.component.html | 10 +- .../download-button.component.ts | 78 +++-- src/app/config.ts | 1 + src/app/utils.ts | 331 ++++++++++++++---- 5 files changed, 324 insertions(+), 110 deletions(-) diff --git a/src/app/components/analysis-panel/analysis-panel.component.ts b/src/app/components/analysis-panel/analysis-panel.component.ts index ef9ccbac..fce813c4 100644 --- a/src/app/components/analysis-panel/analysis-panel.component.ts +++ b/src/app/components/analysis-panel/analysis-panel.component.ts @@ -14,7 +14,6 @@ import { HttpClient } from '@angular/common/http'; import { algorithmNames, AnalysisService } from '../../services/analysis/analysis.service'; import { Drug, - NodeAttributeMap, getProteinNodeId, getWrapperFromNode, LegendContext, @@ -32,8 +31,7 @@ import { NetworkHandlerService } from 'src/app/services/network-handler/network- import { LegendService } from 'src/app/services/legend-service/legend-service.service'; import { LoadingScreenService } from 'src/app/services/loading-screen/loading-screen.service'; import { version } from '../../../version'; -import { downloadCSV } from 'src/app/utils'; -import { Observable } from 'rxjs'; +import { downloadCSV, downloadNodeAttributes } from 'src/app/utils'; declare var vis: any; @@ -98,8 +96,6 @@ export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit public versionString = undefined; - public expressionMap: NodeAttributeMap; - public loading = false; @@ -545,7 +541,6 @@ export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit analysisNetwork.gradientMap = {}; this.drugstoneConfig.remove_analysisConfig(); this.expressionExpanded = false; - this.expressionMap = undefined; analysisNetwork.seedMap = {}; analysisNetwork.highlightSeeds = false; this.analysis.switchSelection('main'); @@ -603,9 +598,8 @@ export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit if ('score' in data[0]) { data = data.sort((a, b) => b['score'] - a['score']); } - - const columns = ['label', 'symbol', 'uniprot', 'ensg', 'entrez', 'proteinName', 'isSeed', 'score', 'rank', 'status']; - downloadCSV(data, columns, `drugstone_${view}`); + + downloadCSV(data, downloadNodeAttributes, `drugstone_${view}`); } /** @@ -704,7 +698,7 @@ export class AnalysisPanelComponent implements OnInit, OnChanges, AfterViewInit nodes = nodes.filter(n => !(n.drugstoneId && skippedDrugIds.has(n.drugstoneId))); // if (this.networkHandler.activeNetwork.selectedDrugTargetType) { // } - + this.legendService.networkHasConnector = nodes.filter(node => node.group === 'connectorNode').length > 0; return { diff --git a/src/app/components/network/network-menu/download-button/download-button.component.html b/src/app/components/network/network-menu/download-button/download-button.component.html index dafc0a10..604342e5 100644 --- a/src/app/components/network/network-menu/download-button/download-button.component.html +++ b/src/app/components/network/network-menu/download-button/download-button.component.html @@ -25,25 +25,25 @@ > <div class="dropdown-content"> <a - (click)="downloadLink('graphml')" + (click)="download('graphml')" class="dropdown-item" [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }" >.graphml</a > <!-- <hr class="dropdown-divider" /> --> <a - (click)="downloadLink('json')" + (click)="download('json')" class="dropdown-item" [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }" >.json</a > <!-- <hr class="dropdown-divider" /> --> - <a - (click)="downloadLink('csv')" + <!-- <a + (click)="download('csv')" class="dropdown-item" [ngClass]="{ 'text-normal': drugstoneConfig.smallStyle }" >.csv</a - > + > --> </div> </div> </div> diff --git a/src/app/components/network/network-menu/download-button/download-button.component.ts b/src/app/components/network/network-menu/download-button/download-button.component.ts index 8fd352c1..61857ef8 100644 --- a/src/app/components/network/network-menu/download-button/download-button.component.ts +++ b/src/app/components/network/network-menu/download-button/download-button.component.ts @@ -1,30 +1,68 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { DrugstoneConfigService } from 'src/app/services/drugstone-config/drugstone-config.service'; -import { NetexControllerService } from 'src/app/services/netex-controller/netex-controller.service'; -import { downLoadFile, downloadCSV } from 'src/app/utils'; -import {NetworkHandlerService} from "../../../../services/network-handler/network-handler.service"; +import { Component, Input, OnInit } from "@angular/core"; +import { DrugstoneConfigService } from "src/app/services/drugstone-config/drugstone-config.service"; +import { NetexControllerService } from "src/app/services/netex-controller/netex-controller.service"; +import { + downloadNodeAttributes, + downloadEdgeAttributes, + downloadJSON, + downloadGraphml, +} from "src/app/utils"; +import { NetworkHandlerService } from "../../../../services/network-handler/network-handler.service"; @Component({ - selector: 'app-download-button', - templateUrl: './download-button.component.html', - styleUrls: ['./download-button.component.scss'] + selector: "app-download-button", + templateUrl: "./download-button.component.html", + styleUrls: ["./download-button.component.scss"], }) export class DownloadButtonComponent implements OnInit { - - @Input() nodeData: { nodes: any, edges: any } = {nodes: null, edges: null}; + @Input() nodeData: { nodes: any; edges: any } = { nodes: null, edges: null }; @Input() buttonId: string; - constructor(public drugstoneConfig: DrugstoneConfigService, public netex: NetexControllerService, public networkHandler: NetworkHandlerService) { } + constructor( + public drugstoneConfig: DrugstoneConfigService, + public netex: NetexControllerService, + public networkHandler: NetworkHandlerService + ) {} - ngOnInit(): void { - } + ngOnInit(): void {} - public downloadLink(fmt) { - const data = {nodes: this.nodeData.nodes.get(), edges: this.nodeData.edges.get(), fmt: fmt}; - - this.netex.graphExport(data).subscribe(response => { - return downLoadFile(response, `application/${fmt}`, fmt); - }); - } + public download(fmt) { + let nodes = this.nodeData.nodes.get(); + const edges = this.nodeData.edges.get(); + + const data = { + nodes: this.nodeData.nodes.get(), + edges: this.nodeData.edges.get(), + }; + // add expression data if active + if (Object.keys(this.networkHandler.activeNetwork.expressionMap).length) { + downloadNodeAttributes.push('expression'); + const nodesWithExpression = []; + nodes.forEach(node => { + if (this.networkHandler.activeNetwork.expressionMap.hasOwnProperty(node.id)) { + node.expression = this.networkHandler.activeNetwork.expressionMap[node.id]; + nodesWithExpression.push(node); + } + }); + nodes = nodesWithExpression; + } + + if (fmt === "json") { + downloadJSON( + nodes, + edges, + downloadNodeAttributes, + downloadEdgeAttributes + ); + } else if (fmt === 'graphml') { + downloadGraphml( + nodes, + edges, + downloadNodeAttributes, + downloadEdgeAttributes + ) + } + + } } diff --git a/src/app/config.ts b/src/app/config.ts index 73dff93a..d2f1d1ba 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -1,5 +1,6 @@ import {AlgorithmTarget, AlgorithmType, QuickAlgorithmType} from './interfaces'; + // https://visjs.github.io/vis-network/docs/network/nodes.html export interface NodeGroup { groupName?: string; diff --git a/src/app/utils.ts b/src/app/utils.ts index e39c7891..09d3cbd6 100644 --- a/src/app/utils.ts +++ b/src/app/utils.ts @@ -1,20 +1,23 @@ // From https://stackoverflow.com/a/27709336/3850564 -import {ɵisListLikeIterable} from '@angular/core'; -import {Node} from './interfaces'; - - -export function getGradientColor(startColor: string, endColor: string, percent: number) { +import { ɵisListLikeIterable } from "@angular/core"; +import { NetworkEdge, Node } from "./interfaces"; + +export function getGradientColor( + startColor: string, + endColor: string, + percent: number +) { // strip the leading # if it's there - startColor = startColor.replace(/^\s*#|\s*$/g, ''); - endColor = endColor.replace(/^\s*#|\s*$/g, ''); + startColor = startColor.replace(/^\s*#|\s*$/g, ""); + endColor = endColor.replace(/^\s*#|\s*$/g, ""); // convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF` if (startColor.length === 3) { - startColor = startColor.replace(/(.)/g, '$1$1'); + startColor = startColor.replace(/(.)/g, "$1$1"); } if (endColor.length === 3) { - endColor = endColor.replace(/(.)/g, '$1$1'); + endColor = endColor.replace(/(.)/g, "$1$1"); } // get colors @@ -31,28 +34,34 @@ export function getGradientColor(startColor: string, endColor: string, percent: const diffGreen = endGreen - startGreen; const diffBlue = endBlue - startBlue; - let diffRedStr = `${((diffRed * percent) + startRed).toString(16).split('.')[0]}`; - let diffGreenStr = `${((diffGreen * percent) + startGreen).toString(16).split('.')[0]}`; - let diffBlueStr = `${((diffBlue * percent) + startBlue).toString(16).split('.')[0]}`; + let diffRedStr = `${ + (diffRed * percent + startRed).toString(16).split(".")[0] + }`; + let diffGreenStr = `${ + (diffGreen * percent + startGreen).toString(16).split(".")[0] + }`; + let diffBlueStr = `${ + (diffBlue * percent + startBlue).toString(16).split(".")[0] + }`; // ensure 2 digits by color if (diffRedStr.length === 1) { - diffRedStr = '0' + diffRedStr; + diffRedStr = "0" + diffRedStr; } if (diffGreenStr.length === 1) { - diffGreenStr = '0' + diffGreenStr; + diffGreenStr = "0" + diffGreenStr; } if (diffBlueStr.length === 1) { - diffBlueStr = '0' + diffBlueStr; + diffBlueStr = "0" + diffBlueStr; } - return '#' + diffRedStr + diffGreenStr + diffBlueStr; + return "#" + diffRedStr + diffGreenStr + diffBlueStr; } export function removeUnderscoreFromKeys(obj) { const result = {}; - Object.keys(obj).forEach(x => { - const y = x.replace('_', ''); + Object.keys(obj).forEach((x) => { + const y = x.replace("_", ""); result[y] = obj[x]; }); return result; @@ -60,62 +69,71 @@ export function removeUnderscoreFromKeys(obj) { // https://gist.github.com/whitlockjc/9363016 function trim(str) { - return str.replace(/^\s+|\s+$/gm, ''); + return str.replace(/^\s+|\s+$/gm, ""); } export function rgbaToHex(rgba) { - const inParts = rgba.substring(rgba.indexOf('(')).split(','), + const inParts = rgba.substring(rgba.indexOf("(")).split(","), r = parseInt(trim(inParts[0].substring(1)), 10), g = parseInt(trim(inParts[1]), 10), b = parseInt(trim(inParts[2]), 10), - a: number = parseFloat(parseFloat(trim(inParts[3].substring(0, inParts[3].length - 1))).toFixed(2)); + a: number = parseFloat( + parseFloat(trim(inParts[3].substring(0, inParts[3].length - 1))).toFixed( + 2 + ) + ); const outParts = [ r.toString(16), g.toString(16), b.toString(16), - Math.round(a * 255).toString(16).substring(0, 2) + Math.round(a * 255) + .toString(16) + .substring(0, 2), ]; // Pad single-digit output values - outParts.forEach(function(part, i) { + outParts.forEach(function (part, i) { if (part.length === 1) { - outParts[i] = '0' + part; + outParts[i] = "0" + part; } }); - return ('#' + outParts.join('')); + return "#" + outParts.join(""); } // https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb function componentToHex(c) { const hex = c.toString(16); - return hex.length === 1 ? '0' + hex : hex; + return hex.length === 1 ? "0" + hex : hex; } export function rgbToHex(rgb) { - const inParts = rgb.substring(rgb.indexOf('(')).split(','), + const inParts = rgb.substring(rgb.indexOf("(")).split(","), r = parseInt(trim(inParts[0].substring(1)), 10), g = parseInt(trim(inParts[1]), 10), b = parseInt(trim(inParts[2]), 10); - return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b); + return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } export function rgbaWithoutAToHex(rgb) { - const inParts = rgb.substring(rgb.indexOf('(')).split(','), + const inParts = rgb.substring(rgb.indexOf("(")).split(","), r = parseInt(trim(inParts[0].substring(1)), 10), g = parseInt(trim(inParts[1]), 10), b = parseInt(trim(inParts[2]), 10); - return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b); + return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } // https://stackoverflow.com/questions/1573053/javascript-function-to-convert-color-names-to-hex-codes/47355187#47355187 export function standardizeColor(str) { - var ctx = document.createElement('canvas').getContext('2d'); + var ctx = document.createElement("canvas").getContext("2d"); ctx.fillStyle = str; return ctx.fillStyle.toString(); } -export function removeDuplicateObjectsFromList(nodes: Node[], attribute: string): Node[] { +export function removeDuplicateObjectsFromList( + nodes: Node[], + attribute: string +): Node[] { const seenIds = new Set(); const filteredArray = new Array(); for (const node of nodes) { @@ -133,21 +151,26 @@ export function removeDuplicateObjectsFromList(nodes: Node[], attribute: string) * @param data - Array Buffer data * @param type - type of the document. */ -export function downLoadFile(data: any, type: string, fmt: string, label: string = 'drugstone_network') { - let blob = new Blob([data], {type: type}); - var a = document.createElement('a'); +export function downLoadFile( + data: any, + type: string, + fmt: string, + label: string = "drugstone_network" +) { + let blob = new Blob([data], { type: type }); + var a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `${label}_${new Date().getTime()}.${fmt}`; a.click(); } export function RGBAtoRGBwithoutA(rgbaString) { - const rgbaStringSplit = rgbaString.slice(5, -1).split(','); + const rgbaStringSplit = rgbaString.slice(5, -1).split(","); const RGBA = { red: rgbaStringSplit[0], green: rgbaStringSplit[1], blue: rgbaStringSplit[2], - alpha: rgbaStringSplit[3] + alpha: rgbaStringSplit[3], }; return `rgb(${RGBA.red},${RGBA.green},${RGBA.blue})`; } @@ -166,9 +189,23 @@ function hexToRGBA(hex, alpha) { b = parseInt(hex.slice(5, 7), 16); } if (alpha) { - return 'rgba(' + (isNaN(r) ? 0 : r) + ', ' + (isNaN(g) ? 0 : g) + ', ' + (isNaN(b) ? 0 : b) + ', ' + alpha + ')'; + return ( + "rgba(" + + (isNaN(r) ? 0 : r) + + ", " + + (isNaN(g) ? 0 : g) + + ", " + + (isNaN(b) ? 0 : b) + + ", " + + alpha + + ")" + ); } else { - return 'rgb(' + (isNaN(r) ? 0 : r) + ', ' + isNaN(g) ? 0 : g + ', ' + isNaN(b) ? 0 : b + ')'; + return "rgb(" + (isNaN(r) ? 0 : r) + ", " + isNaN(g) + ? 0 + : g + ", " + isNaN(b) + ? 0 + : b + ")"; } } @@ -178,7 +215,7 @@ export function blendColors(args: any) { let mix; for (let added of args) { added = RGBAtoArray(added); - if (typeof added[3] === 'undefined') { + if (typeof added[3] === "undefined") { added[3] = 1; } // check if both alpha channels exist. @@ -187,12 +224,20 @@ export function blendColors(args: any) { // alpha mix[3] = 1 - (1 - added[3]) * (1 - base[3]); // red - mix[0] = Math.round((added[0] * added[3] / mix[3]) + (base[0] * base[3] * (1 - added[3]) / mix[3])); + mix[0] = Math.round( + (added[0] * added[3]) / mix[3] + + (base[0] * base[3] * (1 - added[3])) / mix[3] + ); // green - mix[1] = Math.round((added[1] * added[3] / mix[3]) + (base[1] * base[3] * (1 - added[3]) / mix[3])); + mix[1] = Math.round( + (added[1] * added[3]) / mix[3] + + (base[1] * base[3] * (1 - added[3])) / mix[3] + ); // blue - mix[2] = Math.round((added[2] * added[3] / mix[3]) + (base[2] * base[3] * (1 - added[3]) / mix[3])); - + mix[2] = Math.round( + (added[2] * added[3]) / mix[3] + + (base[2] * base[3] * (1 - added[3])) / mix[3] + ); } else if (added) { mix = added; } else { @@ -201,26 +246,34 @@ export function blendColors(args: any) { base = mix; } - return 'rgba(' + mix[0] + ', ' + mix[1] + ', ' + mix[2] + ', ' + mix[3] + ')'; + return "rgba(" + mix[0] + ", " + mix[1] + ", " + mix[2] + ", " + mix[3] + ")"; } export function RGBAtoArray(rgbaString) { - const rgbaStringSplit = rgbaString.slice(5, -1).split(','); + const rgbaStringSplit = rgbaString.slice(5, -1).split(","); return [ rgbaStringSplit[0], rgbaStringSplit[1], rgbaStringSplit[2], - rgbaStringSplit[3] + rgbaStringSplit[3], ]; } - -export function pieChartContextRenderer({ctx, x, y, state: {selected, hover}, style, label}) { - ctx.drawPieLabel = function(style, x, y, label) { - ctx.font = 'normal 12px sans-serif'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillStyle = window.getComputedStyle(document.documentElement).getPropertyValue('--drgstn-text-primary'); +export function pieChartContextRenderer({ + ctx, + x, + y, + state: { selected, hover }, + style, + label, +}) { + ctx.drawPieLabel = function (style, x, y, label) { + ctx.font = "normal 12px sans-serif"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = window + .getComputedStyle(document.documentElement) + .getPropertyValue("--drgstn-text-primary"); ctx.fillText(label, x, y + style.size + 12); }; @@ -235,13 +288,13 @@ export function pieChartContextRenderer({ctx, x, y, state: {selected, hover}, st } function colorToHex(color) { - if (color.startsWith('#')) { + if (color.startsWith("#")) { return color; } - if (color.startsWith('rgba')) { + if (color.startsWith("rgba")) { return rgbToHex(color); } - if (color.startsWith('rgb')) { + if (color.startsWith("rgb")) { return rgbToHex(color); } return null; @@ -254,17 +307,22 @@ export function pieChartContextRenderer({ctx, x, y, state: {selected, hover}, st } } - ctx.drawPie = function(style, x, y, state: { selected, hover }) { - const selection = RGBAtoRGBwithoutA(style.borderColor) !== RGBAtoRGBwithoutA(style.color); + ctx.drawPie = function (style, x, y, state: { selected; hover }) { + const selection = + RGBAtoRGBwithoutA(style.borderColor) !== RGBAtoRGBwithoutA(style.color); const bgOpacity = 0.15; const fgOpacity = 0.5; const lineOpacity = 0.6; const fullCircle = 2 * Math.PI; - const fallbackColor = '#FF0000'; - const colorOrFallback = style.color ? colorToHex(style.color) : fallbackColor; + const fallbackColor = "#FF0000"; + const colorOrFallback = style.color + ? colorToHex(style.color) + : fallbackColor; let outerBorderColor = style.borderColor; if (selection) { - outerBorderColor = style.borderColor ? rgbaWithoutAToHex(style.borderColor) : fallbackColor; + outerBorderColor = style.borderColor + ? rgbaWithoutAToHex(style.borderColor) + : fallbackColor; } if (selected) { style.borderColor = style.color; @@ -273,7 +331,17 @@ export function pieChartContextRenderer({ctx, x, y, state: {selected, hover}, st ctx.beginPath(); ctx.arc(x, y, style.size - 1, 0, 2 * Math.PI, false); // fill like background of graph panel - ctx.fillStyle = RGBAtoRGBwithoutA(blendColors([hexToRGBA(window.getComputedStyle(document.documentElement).getPropertyValue('--drgstn-panel'), 1), hexToRGBA(colorOrFallback, bgOpacity)])); + ctx.fillStyle = RGBAtoRGBwithoutA( + blendColors([ + hexToRGBA( + window + .getComputedStyle(document.documentElement) + .getPropertyValue("--drgstn-panel"), + 1 + ), + hexToRGBA(colorOrFallback, bgOpacity), + ]) + ); startShadow(); ctx.fill(); endShadow(); @@ -326,37 +394,150 @@ export function pieChartContextRenderer({ctx, x, y, state: {selected, hover}, st }; } -export const downloadCSV = function(array: any[], columns: string[], label: string = 'drugstone_csv') { +export const downloadEdgeAttributes = ['from', 'to', 'groupName']; +export const downloadNodeAttributes = [ + "label", + "symbol", + "uniprot", + "ensg", + "entrez", + "proteinName", + "isSeed", + "score", + "rank", + "status", + "groupName" +]; + +const _formatNetworkData = function ( + nodes: any[], + edges: string[], + nodeAttrs: string[], + edgeAttrs: string[] +) { + const nodeData = {}; + let iNode = 0; + nodes.forEach((nodeObj) => { + const item = {}; + nodeAttrs.forEach((attr) => { + if (nodeObj.hasOwnProperty(attr)) { + item[attr] = nodeObj[attr]; + } + }); + nodeData[iNode] = item; + iNode++; + }); + + const edgeData = {}; + let iEdge = 0; + edges.forEach((edgeObj) => { + const item = {}; + edgeAttrs.forEach((attr) => { + if (edgeObj.hasOwnProperty(attr)) { + item[attr] = edgeObj[attr]; + } + }); + edgeData[iEdge] = item; + iEdge++; + }); + return { nodes: nodeData, edges: edgeData }; +}; + +export const downloadJSON = function ( + nodes: Node[], + edges: any[], + nodeAttrs: string[], + edgeAttrs: string[], + label: string = "drugstone" +) { + const dataFormatted = _formatNetworkData(nodes, edges, nodeAttrs, edgeAttrs); + const output = JSON.stringify(dataFormatted); + const fmt = "json"; + downLoadFile(output, `application/${fmt}`, fmt, label); +}; + +export const downloadGraphml = function ( + nodes: any[], + edges: any[], + nodeAttrs: string[], + edgeAttrs: string[], + label: string = "drugstone" +) { + const dataFormatted = _formatNetworkData(nodes, edges, nodeAttrs, edgeAttrs); + + const graphml = [ + '<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"> ', + ]; + for (let i = edgeAttrs.length + nodeAttrs.length - 1; i >= nodeAttrs.length ; i--) { + graphml.push(`<key id="d${i}" for="edge" attr.name="${edgeAttrs[i]}" attr.type="string" />`); + } + for (let i = nodeAttrs.length - 1; i >= 0; i--) { + graphml.push(`<key id="d${i}" for="node" attr.name="${nodeAttrs[i]}" attr.type="string" />`); + } + graphml.push('<graph edgedefault="undirected">'); + + nodes.forEach(node => { + graphml.push(`<node id="${node.id}">`); + for (let i=0; i< nodeAttrs.length; i++) { + if (node.hasOwnProperty(nodeAttrs[i])) { + graphml.push(`<data key="d${i}">${node[nodeAttrs[i]]}</data>`) + } + } + graphml.push(`</node>`); + }) + + edges.forEach(edge => { + graphml.push(`<edge source="${edge.from}" target="${edge.to}">`); + for (let i = nodeAttrs.length; i < nodeAttrs.length + edgeAttrs.length; i++) { + if (edge.hasOwnProperty(edgeAttrs[i - nodeAttrs.length])) { + graphml.push(`<data key="d${i}">${edge[edgeAttrs[i - nodeAttrs.length]]}</data>`); + } + } + graphml.push(`</edge>`); + }) + + graphml.push("</graph>"); + graphml.push("</graphml>"); + + const output = graphml.join(''); + const fmt = "graphml"; + downLoadFile(output, `application/${fmt}`, fmt, label); +}; + +export const downloadCSV = function ( + array: any[], + columns: string[], + label: string = "drugstone" +) { // test which columns occur in array elements, function should not fail if columns do not occur const headerColumns = []; columns.forEach((col) => { if (col in array[0]) { headerColumns.push(col); } - }) + }); // headerColumns has all attributes from 'columns' that appear in the elements of 'array' let output = headerColumns.join(",") + "\n"; // fetch data from array, consider only attributes in headerColumns - array.forEach(el => { + array.forEach((el) => { const row = []; - headerColumns.forEach(col => { + headerColumns.forEach((col) => { let value = el[col]; if (value instanceof Array) { - value = value.join('|'); + value = value.join("|"); } - if (!(typeof value == 'string' || value instanceof String)) { + if (!(typeof value == "string" || value instanceof String)) { value = String(value); } - if (value.includes(',')) { + if (value.includes(",")) { value = `"${value}"`; } row.push(value); - }) - output += row.join(','); + }); + output += row.join(","); output += "\n"; }); - const fmt = 'csv'; + const fmt = "csv"; downLoadFile(output, `application/${fmt}`, fmt, label); - -} +}; -- GitLab