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