<commit_before>(function(self){
'use strict';

let MT = {};

MT.dataSkeleton = () => ({
  nodes: [],
  links: [],
  clusters: [],
  nodeFields: [
    "index",
    "id",
    "selected",
    "cluster",
    "visible",
    "degree",
    "origin"
  ],
  nodeExclusions: [],
  linkFields: [
    "index",
    "source",
    "target",
    "distance",
    "visible",
    "cluster",
    "origin",
    "nn",
    "directed"
  ],
  clusterFields: [
    "id",
    "nodes",
    "links",
    "sum_distances",
    "links_per_node",
    "mean_genetic_distance",
    "visible"
  ],
  reference:
    "CCTCAGGTCACTCTTTGGCAACGACCCCTCGTCACAATAAAGATAGGGGGGCAACTAAAGGAAGCTCTATTAGATACAGGAGCAGATGATACAGTATTAGAAGAAATGAGTTTGCCAGGAAGATGGAAACCAAAAATGATAGGGGGAATTGGAGGTTTTATCAAAGTAAGACAGTATGATCAGATACTCATAGAAATCTGTGGACATAAAGCTATAGGTACAGTATTAGTAGGACCTACACCTGTCAACATAATTGGAAGAAATCTGTTGACTCAGATTGGTTGCACTTTAAATTTTCCCATTAGCCCTATTGAGACTGTACCAGTAAAATTAAAGCCAGGAATGGATGGCCCAAAAGTTAAACAATGGCCATTGACAGAAGAAAAAATAAAAGCATTAGTAGAAATTTGTACAGAGATGGAAAAGGAAGGGAAAATTTCAAAAATTGGGCCTGAAAATCCATACAATACTCCAGTATTTGCCATAAAGAAAAAAGACAGTACTAAATGGAGAAAATTAGTAGATTTCAGAGAACTTAATAAGAGAACTCAAGACTTCTGGGAAGTTCAATTAGGAATACCACATCCCGCAGGGTTAAAAAAGAAAAAATCAGTAACAGTACTGGATGTGGGTGATGCATATTTTTCAGTTCCCTTAGATGAAGACTTCAGGAAGTATACTGCATTTACCATACCTAGTATAAACAATGAGACACCAGGGATTAGATATCAGTACAATGTGCTTCCACAGGGATGGAAAGGATCACCAGCAATATTCCAAAGTAGCATGACAAAAATCTTAGAGCCTTTTAGAAAACAAAATCCAGACATAGTTATCTATCAATACATGGATGATTTGTATGTAGGATCTGACTTAGAAATAGGGCAGCATAGAACAAAAATAGAGGAGCTGAGACAACATCTGTTGAGGTGGGGACTTACCACACCAGACAAAAAACATCAGAAAGAACCTCCATTCCTTTGGATGGGTTATGAACTCCATCCTGATAAATGGACAGTACAGCCTATAGTGCTGCCAGAAAAAGACAGCTGGACTGTCAATGACATACAGAAGTTAGTGGGGAAATTGAATTGGGCAAGTCAGATTTACCCAGGGATTAAAGTAAGGCAATTATGTAAACTCCTTAGAGGAACCAAAGCACTAACAGAAGTAATACCACTAACAGAAGAAGCAGAGCTAGAACTGGCAGAAAACAGAGAGATTCTAAAAGAACCAGTACATGGAGTGTATTATGACCCATCAAAAGACTTAATAGCAGAAATACAGAAGCAGGGGCAAGGCCAATGGACATATCAAATTTATCAAGAGCCATTTAAAAATCTGAAAACAGGAAAATATGCAAGAATGAGGGGTGCCCACACTAATGATGTAAAACAATTAACAGAGGCAGTGCAAAAAATAACCACAGAAAGCATAGTAATATGGGGAAAGACTCCTAAATTTAAACTGCCCATACAAAAGGAAACATGGGAAACATGGTGGACAGAGTATTGGCAAGCCACCTGGATTCCTGAGTGGGAGTTTGTTAATACCCCTCCCTTAGTGAAATTATGGTACCAGTTAGAGAAAGAACCCATAGTAGGAGCAGAAACCTTC"
});

MT.defaultWidgets = {
  "3DNet-link-tooltip-variable": "None",
  "3DNet-link-transparency": 0,
  "3DNet-link-width": 1.6,
  "3DNet-node-tooltip-variable": "id",
  "3DNet-node-radius": 4,
  "3DNet-node-radius-variable": "None",
  "align-sw": false,
  "align-none": true,
  "ambiguity-resolution-strategy": "AVERAGE",
  "ambiguity-threshold": 0.015,
  "background-color": "#ffffff",
  "background-color-contrast": "#000000",
  "bubble-x": "None",
  "bubble-y": "None",
  "bubble-charge": 1.5,
  "bubble-size": 5,
  "choropleth-aggregate-as": "states",
  "choropleth-aggregate-on": "None",
  "choropleth-basemap-show": false,
  "choropleth-color-high": "#800026",
  "choropleth-color-low": "#ffffcc",
  "choropleth-color-medium": "#fd8d3c",
  "choropleth-satellite-show": false,
  "choropleth-transparency": 0.3,
  "cluster-minimum-size": 1,
  "default-view": "2d_network",
  "filtering-epsilon": -8,
  "flow-showNodes": "selected",
  "globe-countries-show": false,
  "globe-field-lat": "None",
  "globe-field-lon": "None",
  "globe-field-tract": "None",
  "globe-field-zipcode": "None",
  "globe-field-county": "None",
  "globe-field-state": "None",
  "globe-field-country": "None",
  "globe-link-show": true,
  "globe-link-transparency": 0,
  "globe-node-jitter": -2,
  "globe-node-show": true,
  "globe-node-transparency": 0,
  "globe-stars-show": true,
  "heatmap-invertX": false,
  "heatmap-invertY": false,
  "heatmap-color-high": "#a50026",
  "heatmap-color-medium": "#ffffbf",
  "heatmap-color-low": "#313695",
  "heatmap-axislabels-show": false,
  "histogram-axis-x": true,
  "histogram-scale-log": false,
  "histogram-variable": "links-distance",
  "link-color": "#a6cee3",
  "link-color-table-counts": true,
  "link-color-table-frequencies": false,
  "link-color-variable": "None",
  "link-directed": false,
  "link-label-variable": "None",
  "link-length": 0.125,
  "link-opacity": 0,
  "link-show-nn": false,
  "link-sort-variable": "distance",
  "link-threshold": 0.015,
  "link-tooltip-variable": "None",
  "link-width": 3,
  "link-width-variable": "None",
  "link-width-reciprocal": true,
  "map-basemap-show": false,
  "map-collapsing-on": true,
  "map-counties-show": false,
  "map-countries-show": true,
  "map-field-lat": "None",
  "map-field-lon": "None",
  "map-field-tract": "None",
  "map-field-zipcode": "None",
  "map-field-county": "None",
  "map-field-state": "None",
  "map-field-country": "None",
  "map-link-show": true,
  "map-link-tooltip-variable": "None",
  "map-link-transparency": 0,
  "map-node-jitter": -2,
  "map-node-show": true,
  "map-node-tooltip-variable": "id",
  "map-node-transparency": 0,
  "map-satellite-show": false,
  "map-states-show": true,
  "network-friction": 0.4,
  "network-gravity": 0.05,
  "node-charge": 200,
  "node-color": "#1f77b4",
  "node-color-table-counts": true,
  "node-color-table-frequencies": false,
  "node-color-variable": "None",
  "node-highlight": false,
  "node-label-size": 16,
  "node-label-variable": "None",
  "node-radius": 250,
  "node-radius-variable": "None",
  "node-symbol": "symbolCircle",
  "node-symbol-table-counts": true,
  "node-symbol-table-frequencies": false,
  "node-symbol-variable": "None",
  "node-tooltip-variable": "id",
  "physics-tree-branch-type": "Straight",
  "physics-tree-charge": 30,
  "physics-tree-friction": 0.05,
  "physics-tree-gravity": 0.05,
  "physics-tree-lateral-strength": 0.025,
  "physics-tree-layout": "Horizontal",
  "physics-tree-node-label-variable": "None",
  "physics-tree-tooltip": "id",
  "physics-tree-type": "tree",
  "reference-source-file": true,
  "reference-source-first": false,
  "reference-source-consensus": false,
  "scatterplot-xVar": "index",
  "scatterplot-yVar": "distance",
  "scatterplot-logScale": false,
  "scatterplot-showNodes": false,
  "search-field": "id",
  "selected-color": "#ff8300",
  "selected-color-contrast": "#000000",
  "timeline-date-field": "None",
  "timeline-noncumulative": true,
  "tree-animation-on": true,
  "tree-branch-distances-hide": true,
  "tree-branch-distance-size": 12,
  "tree-branch-nodes-show": false,
  "tree-horizontal-stretch": 1,
  "tree-layout-vertical": false,
  "tree-layout-horizontal": true,
  "tree-layout-circular": false,
  "tree-labels-align": false,
  "tree-labels-show": false,
  "tree-leaf-label-show": false,
  "tree-leaf-label-size": 12,
  "tree-leaf-node-radius-variable": "None",
  "tree-leaf-node-show": true,
  "tree-leaf-node-size": 9,
  "tree-mode-square": true,
  "tree-mode-smooth": false,
  "tree-mode-straight": false,
  "tree-round-true": false,
  "tree-ruler-show": true,
  "tree-tooltip-show": true,
  "tree-type": "weighted",
  "tree-vertical-stretch": 1
};

MT.sessionSkeleton = () => ({
  data: MT.dataSkeleton(),
  files: [],
  layout: {
    content: [
      {
        type: "files"
      }
    ],
    type: "stack"
  },
  messages: [],
  meta: {
    loadTime: 0,
    readyTime: Date.now(),
    startTime: 0
  },
  network: {
    allPinned: false,
    nodes: []
  },
  state: {
    metrics: ["tn93"],
    timeStart: 0,
    timeEnd: Date.now()
  },
  style: {
    linkAlphas: [1],
    linkColors: d3.schemePaired,
    linkValueNames: {},
    nodeAlphas: [1],
    nodeColors: [d3.schemeCategory10[0]].concat(d3.schemeCategory10.slice(2)),
    nodeSymbols: [
      "symbolCircle",
      "symbolCross",
      "symbolDiamond",
      "symbolSquare",
      "symbolStar",
      "symbolTriangle",
      "symbolWye",
      "symbolTriangleDown",
      "symbolTriangleLeft",
      "symbolTriangleRight",
      "symbolDiamondAlt",
      "symbolDiamondSquare",
      "symbolPentagon",
      "symbolHexagon",
      "symbolHexagonAlt",
      "symbolOctagon",
      "symbolOctagonAlt",
      "symbolX"
    ],
    nodeValueNames: {},
    widgets: MT.defaultWidgets
  },
  warnings: []
});

MT.tempSkeleton = () => ({
  componentCache: {},
  mapData: {},
  matrix: {},
  messageTimeout: null,
  style: {
    linkAlphaMap: () => 1 - session.style.widgets["link-opacity"],
    linkColorMap: () => session.style.widgets["link-color"],
    nodeAlphaMap: () => 1,
    nodeColorMap: () => session.style.widgets["node-color"],
    nodeSymbolMap: () => session.style.widgets["node-symbol"]
  },
  trees: {}
});

MT.defaultNode = () => ({
  index: session.data.nodes.length,
  id: "",
  selected: false,
  cluster: 1,
  visible: true,
  degree: 0,
  origin: []
});

let isNumber = a => typeof a == "number";

MT.addNode = (newNode, check) => {
  if (isNumber(newNode.id)) newNode.id = "" + newNode.id;
  if (session.data.nodeExclusions.indexOf(newNode.id) > -1) return 0;
  if (check) {
    let nodes = session.data.nodes;
    const n = nodes.length;
    for (let i = 0; i < n; i++) {
      let node = nodes[i];
      if (node.id == newNode.id) {
        if (newNode.origin) {
          newNode.origin = newNode.origin.concat(node.origin);
        }
        Object.assign(node, newNode);
        return 0;
      }
    }
  }
  session.data.nodes.push(Object.assign(MT.defaultNode(), newNode));
  return 1;
};

MT.addLink = newLink => {
  if (newLink.source == newLink.target) return 0;
  let linkIsNew = 1;
  let sdlinks = session.data.links;
  if(!temp.matrix[newLink.source]){
    temp.matrix[newLink.source] = {};
  }
  if(!temp.matrix[newLink.target]){
    temp.matrix[newLink.target] = {};
  }
  if(temp.matrix[newLink.source][newLink.target]){
    let oldLink = temp.matrix[newLink.source][newLink.target];
    if (newLink.origin && !oldLink.origin.includes(newLink.origin[0])) {
      newLink.origin = newLink.origin.concat(oldLink.origin);
    }
    Object.assign(oldLink, newLink);
    linkIsNew = 0;
  } else {
    if(temp.matrix[newLink.target][newLink.source]){
      let oldLink = temp.matrix[newLink.target][newLink.source];
      if (newLink.origin && !oldLink.origin.includes(newLink.origin[0])) {
        newLink.origin = newLink.origin.concat(oldLink.origin);
      }
      Object.assign(oldLink, newLink);
      linkIsNew = 0;
    } else {
      newLink = Object.assign({
        index: sdlinks.length,
        source: "",
        target: "",
        visible: false,
        cluster: 1,
        origin: []
      }, newLink);
      temp.matrix[newLink.source][newLink.target] = newLink;
      temp.matrix[newLink.target][newLink.source] = newLink;
      sdlinks.push(newLink);
      linkIsNew = 1;
    }
  }
  return linkIsNew;
};

MT.processSVG = svg => {
  let nodes = [];
  const $xml = $(svg);
  if ($xml.find("#edges").length) {
    $xml.find("#nodes circle").each((i, node) => {
      const $node = $(node);
      const gephid = $node.attr("class");
      nodes.push(gephid);
      MT.addNode(
        {
          id: gephid + "",
          color: $node.attr("fill"),
          size: parseFloat($node.attr("r")),
          origin: ["Scraped Gephi SVG"]
        },
        false
      );
    });
    session.data.nodeFields.push("color");
    session.data.nodeFields.push("size");
    $xml.find("#edges path").each((i, link) => {
      const $link = $(link);
      const coords = $link.attr("class").split(" ");
      let base = {
        source: coords[0] + "",
        target: coords[1] + "",
        color: $link.attr("stroke"),
        origin: ["Scraped MicrobeTrace SVG"]
      };
      session.state.metrics.forEach(metric => base[metric] = 0);
      MT.addLink(base, true);
    });
    session.data.linkFields.push("color");
  } else {
    $xml.find(".nodes g").each((i, node) => {
      nodes.push(
        $(node)
          .attr("transform")
          .slice(10, -1)
          .split(",")
          .map(parseFloat)
      );
      MT.addNode(
        {
          id: i + "",
          origin: ["Scraped SVG"]
        },
        false
      );
    });
    $xml.find("line").each((i, link) => {
      let $l = $(link);
      const source = nodes.findIndex(d => {
        return (
          Math.abs(d[0] - parseFloat($l.attr("x1"))) < 0.0001 &&
          Math.abs(d[1] - parseFloat($l.attr("y1"))) < 0.0001
        );
      });
      const target = nodes.findIndex(d => {
        return (
          Math.abs(d[0] - parseFloat($l.attr("x2"))) < 0.0001 &&
          Math.abs(d[1] - parseFloat($l.attr("y2"))) < 0.0001
        );
      });
      if (source < 0 || target < 0) return;
      let base = {
        source: source + "",
        target: target + "",
        origin: ["Scraped SVG"]
      };
      session.state.metrics.forEach(metric => base[metric] = 0);
      MT.addLink(base, true);
    });
  }
  MT.finishUp();
};

MT.processJSON = (json, extension) => {
  let data;
  try {
    data = JSON.parse(json);
  } catch (error) {
    alertify.error(
      "File Not Recognized! Are you certain this is a MicrobeTrace Session or HIV-TRACE Output File?"
    );
    console.error(error);
    return;
  }
  if (extension == "microbetrace") {
    MT.applySession(data);
  } else {
    if (data.version) {
      MT.applyGHOST(data);
    } else {
      MT.applyHIVTrace(data);
    }
  }
};

MT.applyHIVTrace = hivtrace => {
  self.session = MT.sessionSkeleton();
  session.meta.startTime = Date.now();
  hivtrace["trace_results"]["Nodes"].forEach(node => {
    let newNode = JSON.parse(JSON.stringify(node.patient_attributes));
    newNode.id = node.id;
    newNode.origin = "HIVTRACE Import";
    MT.addNode(newNode, false);
  });
  Object.keys(
    hivtrace["trace_results"]["Nodes"][0]["patient_attributes"]
  ).forEach(key => {
    if (!session.data.nodeFields.includes(key))
      session.data.nodeFields.push(key);
  });
  let n = hivtrace["trace_results"]["Edges"].length;
  let metric = session.state.metrics[0];
  for (let i = 0; i < n; i++) {
    let link = hivtrace["trace_results"]["Edges"][i];
    let newLink = {
      source: "" + link.sequences[0],
      target: "" + link.sequences[1],
      origin: ["HIVTRACE Import"],
      visible: true
    };
    newLink[metric] = parseFloat(link.length);
    MT.addLink(newLink, false);
  }
  session.data.linkFields.push(metric);
  MT.finishUp();
};

MT.applyGHOST = ghost => {
  self.session = MT.sessionSkeleton();
  session.meta.startTime = Date.now();
  ghost["samples"].forEach(node => {
    let newNode = JSON.parse(JSON.stringify(node));
    newNode.origin = "GHOST Import";
    newNode.genotypes = JSON.stringify(newNode.genotypes);
    newNode.id = "" + newNode.id;
    MT.addNode(newNode, false);
  });
  ["genotypes", "group", "id", "name"].forEach(key => {
    if (!session.data.nodeFields.includes(key)){
      session.data.nodeFields.push(key);
    }
  });
  let links = ghost["links"];
  let n = links.length;
  let metric = session.state.metrics[0];
  for (let i = 0; i < n; i++) {
    let link = links[i];
    let newLink = Object.assign({}, link, {
      source: "" + link.source,
      target: "" + link.target,
      dist: parseFloat(link.dist),
      origin: ["GHOST Import"],
      visible: true
    });
    newLink[metric] = newLink.dist;
    if (isNaN(newLink[metric])) {
      newLink[metric] = metric == "tn93" ? 1 : 100;
    }
    MT.addLink(newLink, false);
  }
  [
    metric,
    "density",
    "dist",
    "shared",
    "src_genotype",
    "src_haps",
    "tgt_genotype",
    "tgt_haps"
  ].forEach(key => {
    if (!session.data.linkFields.includes(key))
      session.data.linkFields.push(key);
  });
  MT.finishUp();
};

let decoder = new TextDecoder("utf-8");
MT.decode = x => decoder.decode(x);

MT.parseFASTA = text => {
  return new Promise(resolve => {
    let computer = new Worker("workers/parse-fasta.js");
    computer.onmessage = response => {
      let nodes = JSON.parse(MT.decode(new Uint8Array(response.data.nodes)));
      console.log("FASTA Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      resolve(nodes);
    };
    computer.postMessage(text);
  });
};

MT.parseCSVMatrix = file => {
  return new Promise(resolve => {
    let check = session.files.length > 1;
    const origin = [file.name];
    let nn = 0,
        nl = 0;
    let computer = new Worker("workers/parse-csv-matrix.js");
    computer.onmessage = response => {
      const data = JSON.parse(MT.decode(new Uint8Array(response.data.data)));
      console.log("CSV Matrix Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      const start = Date.now();
      const nodes = data.nodes;
      const tn = nodes.length;
      for (let i = 0; i < tn; i++) {
        nn += MT.addNode({
          id: nodes[i],
          origin: origin
        }, check);
      }
      const links = data.links;
      const tl = links.length;
      for (let j = 0; j < tl; j++) {
        nl += MT.addLink(Object.assign(links[j], { origin: origin }), check);
      }
      console.log("CSV Matrix Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve({ nn, nl, tn, tl });
    };
    computer.postMessage(file.contents);
  })
};

MT.r01 = Math.random;

// ported from https://github.com/CDCgov/SeqSpawnR/blob/91d5857dbda5998839a002fbecae0f494dca960a/R/SequenceSpawner.R
MT.generateSeqs = (idPrefix, count, snps, seed) => {
  let start = Date.now();
  if (!count) count = 1000;
  if (!snps) snps = 100;
  if (!seed) seed = session.data.reference;

  let sampleCodons = [
    "GCA",
    "GCC",
    "GCG",
    "GCT",
    "AAC",
    "AAT",
    "GAC",
    "GAT",
    "TGC",
    "TGT",
    "GAC",
    "GAT",
    "GAA",
    "GAG",
    "TTC",
    "TTT",
    "GGA",
    "GGC",
    "GGG",
    "GGT",
    "CAC",
    "CAT",
    "ATA",
    "ATC",
    "ATT",
    "AAA",
    "AAG",
    "CTA",
    "CTC",
    "CTG",
    "CTT",
    "TTA",
    "TTG",
    "ATG",
    "AAC",
    "AAT",
    "CCA",
    "CCC",
    "CCG",
    "CCT",
    "CAA",
    "CAG",
    "AGA",
    "AGG",
    "CGA",
    "CGC",
    "CGG",
    "CGT",
    "AGC",
    "AGT",
    "TCA",
    "TCC",
    "TCG",
    "TCT",
    "ACA",
    "ACC",
    "ACG",
    "ACT",
    "GTA",
    "GTC",
    "GTG",
    "GTT",
    "TGG",
    "TAC",
    "TAT",
    "CAA",
    "CAG",
    "GAA",
    "GAG"
  ];
  let sampleSNPs = ["A", "C", "G", "T"];

  let sample = (vec, nCodons) => {
    let samples = [];
    for (let x = 0; x < nCodons; x++) {
      let idx = Math.floor(MT.r01() * vec.length);
      samples.push(vec[idx]);
    }
    return samples;
  }

  let seqs = [];

  seqs.push({ id: idPrefix + "0", seq: seed });

  while (seqs.length < count) {
    // number codons to vary
    let nCodons = Math.floor(MT.r01() * 10) + 1;

    // randomly select this many to check for existence
    let randomCodonSet = sample(sampleCodons, nCodons).join("");

    // try again if not present
    if (seqs[seqs.length - 1].seq.indexOf(randomCodonSet) == -1) continue;

    // sequence to mutate
    let oldseed = seqs[Math.floor(MT.r01() * seqs.length)].seq;

    // select codons to replace randomCodonSet
    let replacementCodonSet = sample(sampleCodons, nCodons).join("");

    // replace codon set
    let newseed = oldseed.replace(randomCodonSet, replacementCodonSet);

    // add snp substitutions randomly across entire sequence
    // - randomly sample addedSNP
    // - randomly pick SNPS to replace
    let addedSNPs = Math.floor(MT.r01() * snps);
    for (let j = 0; j < addedSNPs; j++) {
      let randomSNP = sample(sampleSNPs, 1)[0];
      let locOfSNP = Math.floor(MT.r01() * seed.length);
      newseed =
        newseed.substr(0, locOfSNP) + randomSNP + newseed.substr(locOfSNP + 1);
    }

    seqs.push({ id: idPrefix + "" + seqs.length, seq: newseed });
  }
  console.log("Sequence spawn time:", (Date.now() - start).toLocaleString(), 'ms');
  return seqs;
};

MT.align = params => {
  return new Promise(resolve => {
    if (params.aligner == "none") {
      return resolve(params.nodes);
    }
    let n = params.nodes.length;
    let referenceLength = params.reference.length;
    let aligner = new Worker("workers/align-sw.js");
    aligner.onmessage = response => {
      let subset = JSON.parse(MT.decode(new Uint8Array(response.data.nodes)));
      console.log("Alignment transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      let start = Date.now();
      let minPadding = Infinity,
      d = null;
      for (let i = 0; i < n; i++) {
        d = subset[i];
        if (!d.seq) d.seq = "";
        if (minPadding > d.padding) minPadding = d.padding;
      }
      for (let j = 0; j < n; j++) {
        d = subset[j];
        d.seq = "-".repeat(d.padding - minPadding) + d.seq;
        if (d.seq.length > referenceLength){
          d.seq = d.seq.substring(0, referenceLength);
        } else {
          d.seq = d.seq.padEnd(referenceLength, "-");
        }
      }
      console.log("Alignment Padding time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve(subset);
    };
    aligner.postMessage(params);
  });
};

MT.computeConsensus = nodes => {
  if (!nodes) nodes = session.data.nodes.filter(d => d.seq);
  return new Promise(resolve => {
    let computer = new Worker("workers/compute-consensus.js");
    computer.onmessage = response => {
      console.log("Consensus Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      resolve(MT.decode(new Uint8Array(response.data.consensus)));
    };
    computer.postMessage(nodes);
  });
};

MT.computeAmbiguityCounts = () => {
  return new Promise(resolve => {
    let nodes = session.data.nodes;
    let subset = nodes.filter(d => d.seq);
    const subsetLength = subset.length;
    let computer = new Worker("workers/compute-ambiguity-counts.js");
    computer.onmessage = response => {
      console.log("Ambiguity Count Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      const dists = new Float32Array(response.data.counts);
      for (let j = 0; j < subsetLength; j++) {
        nodes[subset[j].index]._ambiguity = dists[j];
      }
      resolve();
    };
    computer.postMessage(subset);
  });
};

MT.computeConsensusDistances = () => {
  return new Promise(resolve => {
    let start = Date.now();
    let nodes = session.data.nodes;
    let nodesLength = nodes.length;
    let subset = [];
    for (let i = 0; i < nodesLength; i++) {
      let node = nodes[i];
      if (node.seq) {
        subset.push({
          index: i,
          seq: node.seq
        });
      } else {
        subset.push({
          index: i,
          seq: ""
        });
      }
    }
    let subsetLength = subset.length;
    let computer = new Worker("workers/compute-consensus-distances.js");
    computer.onmessage = response => {
      let dists = new Uint16Array(response.data.dists);
      console.log("Consensus Difference Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      start = Date.now();
      for (let j = 0; j < subsetLength; j++) {
        nodes[subset[j].index]._diff = dists[j];
      }
      console.log("Consensus Difference Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve();
    };
    computer.postMessage({
      consensus: session.data.consensus,
      subset: subset,
      start: start
    });
  });
};

MT.computeLinks = subset => {
  return new Promise(resolve => {
    let k = 0;
    let computer = new Worker("workers/compute-links.js");
    computer.onmessage = response => {
      let dists = session.state.metrics[0] == 'snps' ?
        new Uint16Array(response.data.links) :
        new Float32Array(response.data.links);
      console.log("Links Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      let start = Date.now();
      let check = session.files.length > 1;
      let n = subset.length;
      let l = 0;
      for (let i = 0; i < n; i++) {
        let source = subset[i];
        for (let j = 0; j < i; j++) {
          let target = subset[j];
          k += MT.addLink({
            source: source['id'],
            target: target['id'],
            distance: dists[l++],
            origin: ['Genetic Distance']
          }, check);
        }
      }
      console.log("Links Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve(k);
    };
    computer.postMessage({
      nodes: subset,
      metrics: session.state.metrics,
      strategy: session.style.widgets["ambiguity-resolution-strategy"],
      threshold: session.style.widgets["ambiguity-threshold"]
    });
  });
};

MT.getDM = () => {
  let start = Date.now();
  return new Promise(resolve => {
    let labels = session.data.nodes.map(d => d.id);
    let metric = session.style.widgets['link-sort-variable'];
    const n = labels.length;
    let dm = new Array(n);
    for(let i = 0; i < n; i++){
      dm[i] = new Array(n);
      dm[i][i] = 0;
      let source = labels[i];
      for(let j = 0; j < i; j++){
        let link = temp.matrix[source][labels[j]];
        if(link){
          dm[i][j] = dm[j][i] = link[metric];
        } else {
          dm[i][j] = dm[j][i] = null;
        }
      }
    }
    console.log("DM Compute time: ", (Date.now() - start).toLocaleString(), "ms");
    resolve(dm);
  });
};

MT.computeTree = () => {
  return new Promise(resolve => {
    let computer = new Worker("workers/compute-tree.js");
    computer.onmessage = response => {
      temp.tree = patristic.parseJSON(MT.decode(new Uint8Array(response.data.tree)));
      console.log("Tree Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      resolve();
    };
    MT.getDM().then(dm => {
      computer.postMessage({
        labels: Object.keys(temp.matrix),
        matrix: dm,
        round: session.style.widgets["tree-round"]
      });
    })
  })
};

MT.computeDirectionality = () => {
  return new Promise(resolve => {
    let computer = new Worker("workers/compute-directionality.js");
    computer.onmessage = response => {
      let flips = new Uint8Array(response.data.output);
      console.log("Directionality Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      let start = Date.now();
      let n = flips.length;
      for (let i = 0; i < n; i++) {
        if (flips[i]) {
          let fliplink = session.data.links[i];
          let fliptemp = fliplink.source;
          fliplink.source = fliplink.target;
          fliplink.target = fliptemp;
          fliplink.directed = true;
        }
      }
      console.log("Directionality Integration time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve();
    };
    computer.postMessage({
      links: session.data.links,
      tree: temp.tree
    });
  });
};

MT.computeNN = () => {
  return new Promise((resolve, reject) => {
    let nnMachine = new Worker("workers/compute-nn.js");
    nnMachine.onmessage = response => {
      if (response.data == "Error") {
        return reject("Nearest Neighbor washed out");
      }
      let output = new Uint8Array(response.data.links);
      console.log("NN Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      const start = Date.now();
      let links = session.data.links;
      const numLinks = links.length;
      for (let i = 0; i < numLinks; i++) {
        links[i].nn = output[i] ? true : false;
      }
      console.log("NN Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve();
    };
    nnMachine.postMessage({
      links: session.data.links,
      matrix: temp.matrix,
      epsilon: session.style.widgets["filtering-epsilon"],
      metric: session.style.widgets['link-sort-variable']
    });
  });
};

MT.computeTriangulation = () => {
  return new Promise((resolve, reject) => {
    const metric = session.style.widgets['link-sort-variable'];
    let machine = new Worker("workers/compute-triangulation.js");
    machine.onmessage = response => {
      if (response.data == "Error") return reject("Triangulation washed out");
      console.log("Triangulation Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      let start = Date.now();
      let matrix = JSON.parse(MT.decode(new Uint8Array(response.data.matrix)));
      let labels = Object.keys(temp.matrix);
      const n = labels.length;
      for(let i = 0; i < n; i++){
        let source = labels[i];
        let row = temp.matrix[source];
        for(let j = 0; j < i; j++){
          let target = labels[j];
          if(!row[target]){
            MT.addLink({
              source: source,
              target: target,
              origin: ['Triangulation'],
              visible: false
            });
          }
          row[target][metric] = matrix[i][j];
        }
      }
      console.log("Triangulation Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve();
    };
    MT.getDM().then(dm => {
      machine.postMessage({
        matrix: dm
      });
    })
  });
};

MT.finishUp = async oldSession => {
  if (!oldSession) {
    if ($('[name="shouldTriangulate"]:checked').attr("id") == "doTriangulate") {
      await MT.computeTriangulation();
    }
    MT.computeNN();
  }
  MT.computeTree().then(MT.computeDirectionality);
  clearTimeout(temp.messageTimeout);
  ["node", "link"].forEach(v => {
    let n = session.data[v + "s"].length;
    let fields = session.data[v + "Fields"];
    for (let i = 0; i < n; i++) {
      let d = session.data[v + "s"][i];
      fields.forEach(field => {
        if (!(field in d)) d[field] = null;
      });
    }
  });
  $("#search-field")
    .html(session.data.nodeFields.map(field => '<option value="' + field + '">' + MT.titleize(field) + "</option>").join("\n"))
    .val(session.style.widgets["search-field"]);
  $("#search-form").css("display", "flex");
  $("#link-sort-variable")
    .html(session.data.linkFields.map(field => '<option value="' + field + '">' + MT.titleize(field) + "</option>").join("\n"))
    .val(session.style.widgets["link-sort-variable"]);
  $("#node-color-variable")
    .html(
      "<option selected>None</option>" +
      session.data.nodeFields.map(field => '<option value="' + field + '">' + MT.titleize(field) + "</option>").join("\n"))
    .val(session.style.widgets["node-color-variable"]);
  $("#link-color-variable")
    .html(
      "<option>None</option>" +
      session.data.linkFields.map(field => '<option value="' + field + '">' + MT.titleize(field) + "</option>").join("\n"))
    .val(session.style.widgets["link-color-variable"]);
  try {
    MT.updateThresholdHistogram();
  } catch (error) {
    console.error(error);
    $("#loading-information-modal").modal("hide");
    alertify
      .error("Something went wrong! Please click here to start a new session and try again.")
      .delay(0)
      .ondismiss(() => window.location.reload());
  }
  MT.setLinkVisibility(true);
  $("#SettingsTab").attr("data-target", "#global-settings-modal");
  session.meta.loadTime = Date.now() - session.meta.startTime;
  console.log("Total load time:", session.meta.loadTime.toLocaleString(), "ms");
  if (oldSession) {
    layout.root.contentItems[0].remove();
    setTimeout(() => MT.loadLayout(session.layout), 80);
  } else {
    MT.launchView($("#default-view").val());
  }
  MT.tagClusters().then(() => {
    MT.setClusterVisibility(true);
    MT.setLinkVisibility(true);
    MT.setNodeVisibility(true);
    ["cluster", "link", "node"].forEach(thing => $window.trigger(thing + "-visibility"));
    MT.updateStatistics();
    $("#network-statistics-wrapper").fadeIn();
  });
  if (localStorage.getItem("stash-auto") == "true") {
    temp.autostash = {
      time: Date.now(),
      interval: setInterval(() => {
        let newTime = Date.now();
        localStorage.setItem("stash-" + newTime + "-autostash", JSON.stringify(session));
        localSorage.removeItem("stash-" + temp.autostash.time + "-autostash");
        temp.autostash.time = newTime;
      }, 60000)
    };
  }
  $(".hideForHIVTrace").css("display", "flex");
  setTimeout(() => {
    let files = layout.contentItems.find(item => item.componentName == "files");
    if (files) files.remove();
    $("#loading-information-modal").modal("hide");<commit_msg>fixed GHOST import<commit_after>(function(self){
'use strict';

let MT = {};

MT.dataSkeleton = () => ({
  nodes: [],
  links: [],
  clusters: [],
  nodeFields: [
    "index",
    "id",
    "selected",
    "cluster",
    "visible",
    "degree",
    "origin"
  ],
  nodeExclusions: [],
  linkFields: [
    "index",
    "source",
    "target",
    "distance",
    "visible",
    "cluster",
    "origin",
    "nn",
    "directed"
  ],
  clusterFields: [
    "id",
    "nodes",
    "links",
    "sum_distances",
    "links_per_node",
    "mean_genetic_distance",
    "visible"
  ],
  reference:
    "CCTCAGGTCACTCTTTGGCAACGACCCCTCGTCACAATAAAGATAGGGGGGCAACTAAAGGAAGCTCTATTAGATACAGGAGCAGATGATACAGTATTAGAAGAAATGAGTTTGCCAGGAAGATGGAAACCAAAAATGATAGGGGGAATTGGAGGTTTTATCAAAGTAAGACAGTATGATCAGATACTCATAGAAATCTGTGGACATAAAGCTATAGGTACAGTATTAGTAGGACCTACACCTGTCAACATAATTGGAAGAAATCTGTTGACTCAGATTGGTTGCACTTTAAATTTTCCCATTAGCCCTATTGAGACTGTACCAGTAAAATTAAAGCCAGGAATGGATGGCCCAAAAGTTAAACAATGGCCATTGACAGAAGAAAAAATAAAAGCATTAGTAGAAATTTGTACAGAGATGGAAAAGGAAGGGAAAATTTCAAAAATTGGGCCTGAAAATCCATACAATACTCCAGTATTTGCCATAAAGAAAAAAGACAGTACTAAATGGAGAAAATTAGTAGATTTCAGAGAACTTAATAAGAGAACTCAAGACTTCTGGGAAGTTCAATTAGGAATACCACATCCCGCAGGGTTAAAAAAGAAAAAATCAGTAACAGTACTGGATGTGGGTGATGCATATTTTTCAGTTCCCTTAGATGAAGACTTCAGGAAGTATACTGCATTTACCATACCTAGTATAAACAATGAGACACCAGGGATTAGATATCAGTACAATGTGCTTCCACAGGGATGGAAAGGATCACCAGCAATATTCCAAAGTAGCATGACAAAAATCTTAGAGCCTTTTAGAAAACAAAATCCAGACATAGTTATCTATCAATACATGGATGATTTGTATGTAGGATCTGACTTAGAAATAGGGCAGCATAGAACAAAAATAGAGGAGCTGAGACAACATCTGTTGAGGTGGGGACTTACCACACCAGACAAAAAACATCAGAAAGAACCTCCATTCCTTTGGATGGGTTATGAACTCCATCCTGATAAATGGACAGTACAGCCTATAGTGCTGCCAGAAAAAGACAGCTGGACTGTCAATGACATACAGAAGTTAGTGGGGAAATTGAATTGGGCAAGTCAGATTTACCCAGGGATTAAAGTAAGGCAATTATGTAAACTCCTTAGAGGAACCAAAGCACTAACAGAAGTAATACCACTAACAGAAGAAGCAGAGCTAGAACTGGCAGAAAACAGAGAGATTCTAAAAGAACCAGTACATGGAGTGTATTATGACCCATCAAAAGACTTAATAGCAGAAATACAGAAGCAGGGGCAAGGCCAATGGACATATCAAATTTATCAAGAGCCATTTAAAAATCTGAAAACAGGAAAATATGCAAGAATGAGGGGTGCCCACACTAATGATGTAAAACAATTAACAGAGGCAGTGCAAAAAATAACCACAGAAAGCATAGTAATATGGGGAAAGACTCCTAAATTTAAACTGCCCATACAAAAGGAAACATGGGAAACATGGTGGACAGAGTATTGGCAAGCCACCTGGATTCCTGAGTGGGAGTTTGTTAATACCCCTCCCTTAGTGAAATTATGGTACCAGTTAGAGAAAGAACCCATAGTAGGAGCAGAAACCTTC"
});

MT.defaultWidgets = {
  "3DNet-link-tooltip-variable": "None",
  "3DNet-link-transparency": 0,
  "3DNet-link-width": 1.6,
  "3DNet-node-tooltip-variable": "id",
  "3DNet-node-radius": 4,
  "3DNet-node-radius-variable": "None",
  "align-sw": false,
  "align-none": true,
  "ambiguity-resolution-strategy": "AVERAGE",
  "ambiguity-threshold": 0.015,
  "background-color": "#ffffff",
  "background-color-contrast": "#000000",
  "bubble-x": "None",
  "bubble-y": "None",
  "bubble-charge": 1.5,
  "bubble-size": 5,
  "choropleth-aggregate-as": "states",
  "choropleth-aggregate-on": "None",
  "choropleth-basemap-show": false,
  "choropleth-color-high": "#800026",
  "choropleth-color-low": "#ffffcc",
  "choropleth-color-medium": "#fd8d3c",
  "choropleth-satellite-show": false,
  "choropleth-transparency": 0.3,
  "cluster-minimum-size": 1,
  "default-view": "2d_network",
  "filtering-epsilon": -8,
  "flow-showNodes": "selected",
  "globe-countries-show": false,
  "globe-field-lat": "None",
  "globe-field-lon": "None",
  "globe-field-tract": "None",
  "globe-field-zipcode": "None",
  "globe-field-county": "None",
  "globe-field-state": "None",
  "globe-field-country": "None",
  "globe-link-show": true,
  "globe-link-transparency": 0,
  "globe-node-jitter": -2,
  "globe-node-show": true,
  "globe-node-transparency": 0,
  "globe-stars-show": true,
  "heatmap-invertX": false,
  "heatmap-invertY": false,
  "heatmap-color-high": "#a50026",
  "heatmap-color-medium": "#ffffbf",
  "heatmap-color-low": "#313695",
  "heatmap-axislabels-show": false,
  "histogram-axis-x": true,
  "histogram-scale-log": false,
  "histogram-variable": "links-distance",
  "link-color": "#a6cee3",
  "link-color-table-counts": true,
  "link-color-table-frequencies": false,
  "link-color-variable": "None",
  "link-directed": false,
  "link-label-variable": "None",
  "link-length": 0.125,
  "link-opacity": 0,
  "link-show-nn": false,
  "link-sort-variable": "distance",
  "link-threshold": 0.015,
  "link-tooltip-variable": "None",
  "link-width": 3,
  "link-width-variable": "None",
  "link-width-reciprocal": true,
  "map-basemap-show": false,
  "map-collapsing-on": true,
  "map-counties-show": false,
  "map-countries-show": true,
  "map-field-lat": "None",
  "map-field-lon": "None",
  "map-field-tract": "None",
  "map-field-zipcode": "None",
  "map-field-county": "None",
  "map-field-state": "None",
  "map-field-country": "None",
  "map-link-show": true,
  "map-link-tooltip-variable": "None",
  "map-link-transparency": 0,
  "map-node-jitter": -2,
  "map-node-show": true,
  "map-node-tooltip-variable": "id",
  "map-node-transparency": 0,
  "map-satellite-show": false,
  "map-states-show": true,
  "network-friction": 0.4,
  "network-gravity": 0.05,
  "node-charge": 200,
  "node-color": "#1f77b4",
  "node-color-table-counts": true,
  "node-color-table-frequencies": false,
  "node-color-variable": "None",
  "node-highlight": false,
  "node-label-size": 16,
  "node-label-variable": "None",
  "node-radius": 250,
  "node-radius-variable": "None",
  "node-symbol": "symbolCircle",
  "node-symbol-table-counts": true,
  "node-symbol-table-frequencies": false,
  "node-symbol-variable": "None",
  "node-tooltip-variable": "id",
  "physics-tree-branch-type": "Straight",
  "physics-tree-charge": 30,
  "physics-tree-friction": 0.05,
  "physics-tree-gravity": 0.05,
  "physics-tree-lateral-strength": 0.025,
  "physics-tree-layout": "Horizontal",
  "physics-tree-node-label-variable": "None",
  "physics-tree-tooltip": "id",
  "physics-tree-type": "tree",
  "reference-source-file": true,
  "reference-source-first": false,
  "reference-source-consensus": false,
  "scatterplot-xVar": "index",
  "scatterplot-yVar": "distance",
  "scatterplot-logScale": false,
  "scatterplot-showNodes": false,
  "search-field": "id",
  "selected-color": "#ff8300",
  "selected-color-contrast": "#000000",
  "timeline-date-field": "None",
  "timeline-noncumulative": true,
  "tree-animation-on": true,
  "tree-branch-distances-hide": true,
  "tree-branch-distance-size": 12,
  "tree-branch-nodes-show": false,
  "tree-horizontal-stretch": 1,
  "tree-layout-vertical": false,
  "tree-layout-horizontal": true,
  "tree-layout-circular": false,
  "tree-labels-align": false,
  "tree-labels-show": false,
  "tree-leaf-label-show": false,
  "tree-leaf-label-size": 12,
  "tree-leaf-node-radius-variable": "None",
  "tree-leaf-node-show": true,
  "tree-leaf-node-size": 9,
  "tree-mode-square": true,
  "tree-mode-smooth": false,
  "tree-mode-straight": false,
  "tree-round-true": false,
  "tree-ruler-show": true,
  "tree-tooltip-show": true,
  "tree-type": "weighted",
  "tree-vertical-stretch": 1
};

MT.sessionSkeleton = () => ({
  data: MT.dataSkeleton(),
  files: [],
  layout: {
    content: [
      {
        type: "files"
      }
    ],
    type: "stack"
  },
  messages: [],
  meta: {
    loadTime: 0,
    readyTime: Date.now(),
    startTime: 0
  },
  network: {
    allPinned: false,
    nodes: []
  },
  state: {
    metrics: ["tn93"],
    timeStart: 0,
    timeEnd: Date.now()
  },
  style: {
    linkAlphas: [1],
    linkColors: d3.schemePaired,
    linkValueNames: {},
    nodeAlphas: [1],
    nodeColors: [d3.schemeCategory10[0]].concat(d3.schemeCategory10.slice(2)),
    nodeSymbols: [
      "symbolCircle",
      "symbolCross",
      "symbolDiamond",
      "symbolSquare",
      "symbolStar",
      "symbolTriangle",
      "symbolWye",
      "symbolTriangleDown",
      "symbolTriangleLeft",
      "symbolTriangleRight",
      "symbolDiamondAlt",
      "symbolDiamondSquare",
      "symbolPentagon",
      "symbolHexagon",
      "symbolHexagonAlt",
      "symbolOctagon",
      "symbolOctagonAlt",
      "symbolX"
    ],
    nodeValueNames: {},
    widgets: MT.defaultWidgets
  },
  warnings: []
});

MT.tempSkeleton = () => ({
  componentCache: {},
  mapData: {},
  matrix: {},
  messageTimeout: null,
  style: {
    linkAlphaMap: () => 1 - session.style.widgets["link-opacity"],
    linkColorMap: () => session.style.widgets["link-color"],
    nodeAlphaMap: () => 1,
    nodeColorMap: () => session.style.widgets["node-color"],
    nodeSymbolMap: () => session.style.widgets["node-symbol"]
  },
  trees: {}
});

MT.defaultNode = () => ({
  index: session.data.nodes.length,
  id: "",
  selected: false,
  cluster: 1,
  visible: true,
  degree: 0,
  origin: []
});

let isNumber = a => typeof a == "number";

MT.addNode = (newNode, check) => {
  if (isNumber(newNode.id)) newNode.id = "" + newNode.id;
  if (session.data.nodeExclusions.indexOf(newNode.id) > -1) return 0;
  if (check) {
    let nodes = session.data.nodes;
    const n = nodes.length;
    for (let i = 0; i < n; i++) {
      let node = nodes[i];
      if (node.id == newNode.id) {
        if (newNode.origin) {
          newNode.origin = newNode.origin.concat(node.origin);
        }
        Object.assign(node, newNode);
        return 0;
      }
    }
  }
  session.data.nodes.push(Object.assign(MT.defaultNode(), newNode));
  return 1;
};

MT.addLink = newLink => {
  if (newLink.source == newLink.target) return 0;
  let linkIsNew = 1;
  let sdlinks = session.data.links;
  if(!temp.matrix[newLink.source]){
    temp.matrix[newLink.source] = {};
  }
  if(!temp.matrix[newLink.target]){
    temp.matrix[newLink.target] = {};
  }
  if(temp.matrix[newLink.source][newLink.target]){
    let oldLink = temp.matrix[newLink.source][newLink.target];
    if (newLink.origin && !oldLink.origin.includes(newLink.origin[0])) {
      newLink.origin = newLink.origin.concat(oldLink.origin);
    }
    Object.assign(oldLink, newLink);
    linkIsNew = 0;
  } else {
    if(temp.matrix[newLink.target][newLink.source]){
      let oldLink = temp.matrix[newLink.target][newLink.source];
      if (newLink.origin && !oldLink.origin.includes(newLink.origin[0])) {
        newLink.origin = newLink.origin.concat(oldLink.origin);
      }
      Object.assign(oldLink, newLink);
      linkIsNew = 0;
    } else {
      newLink = Object.assign({
        index: sdlinks.length,
        source: "",
        target: "",
        visible: false,
        cluster: 1,
        origin: []
      }, newLink);
      temp.matrix[newLink.source][newLink.target] = newLink;
      temp.matrix[newLink.target][newLink.source] = newLink;
      sdlinks.push(newLink);
      linkIsNew = 1;
    }
  }
  return linkIsNew;
};

MT.processSVG = svg => {
  let nodes = [];
  const $xml = $(svg);
  if ($xml.find("#edges").length) {
    $xml.find("#nodes circle").each((i, node) => {
      const $node = $(node);
      const gephid = $node.attr("class");
      nodes.push(gephid);
      MT.addNode(
        {
          id: gephid + "",
          color: $node.attr("fill"),
          size: parseFloat($node.attr("r")),
          origin: ["Scraped Gephi SVG"]
        },
        false
      );
    });
    session.data.nodeFields.push("color");
    session.data.nodeFields.push("size");
    $xml.find("#edges path").each((i, link) => {
      const $link = $(link);
      const coords = $link.attr("class").split(" ");
      let base = {
        source: coords[0] + "",
        target: coords[1] + "",
        color: $link.attr("stroke"),
        origin: ["Scraped MicrobeTrace SVG"]
      };
      session.state.metrics.forEach(metric => base[metric] = 0);
      MT.addLink(base, true);
    });
    session.data.linkFields.push("color");
  } else {
    $xml.find(".nodes g").each((i, node) => {
      nodes.push(
        $(node)
          .attr("transform")
          .slice(10, -1)
          .split(",")
          .map(parseFloat)
      );
      MT.addNode(
        {
          id: i + "",
          origin: ["Scraped SVG"]
        },
        false
      );
    });
    $xml.find("line").each((i, link) => {
      let $l = $(link);
      const source = nodes.findIndex(d => {
        return (
          Math.abs(d[0] - parseFloat($l.attr("x1"))) < 0.0001 &&
          Math.abs(d[1] - parseFloat($l.attr("y1"))) < 0.0001
        );
      });
      const target = nodes.findIndex(d => {
        return (
          Math.abs(d[0] - parseFloat($l.attr("x2"))) < 0.0001 &&
          Math.abs(d[1] - parseFloat($l.attr("y2"))) < 0.0001
        );
      });
      if (source < 0 || target < 0) return;
      let base = {
        source: source + "",
        target: target + "",
        origin: ["Scraped SVG"]
      };
      session.state.metrics.forEach(metric => base[metric] = 0);
      MT.addLink(base, true);
    });
  }
  MT.finishUp();
};

MT.processJSON = (json, extension) => {
  let data;
  try {
    data = JSON.parse(json);
  } catch (error) {
    alertify.error(
      "File Not Recognized! Are you certain this is a MicrobeTrace Session or HIV-TRACE Output File?"
    );
    console.error(error);
    return;
  }
  if (extension == "microbetrace") {
    MT.applySession(data);
  } else {
    if (data.version) {
      MT.applyGHOST(data);
    } else {
      MT.applyHIVTrace(data);
    }
  }
};

MT.applyHIVTrace = hivtrace => {
  self.session = MT.sessionSkeleton();
  session.meta.startTime = Date.now();
  hivtrace["trace_results"]["Nodes"].forEach(node => {
    let newNode = JSON.parse(JSON.stringify(node.patient_attributes));
    newNode.id = node.id;
    newNode.origin = "HIVTRACE Import";
    MT.addNode(newNode, false);
  });
  Object.keys(
    hivtrace["trace_results"]["Nodes"][0]["patient_attributes"]
  ).forEach(key => {
    if (!session.data.nodeFields.includes(key))
      session.data.nodeFields.push(key);
  });
  let n = hivtrace["trace_results"]["Edges"].length;
  let metric = session.state.metrics[0];
  for (let i = 0; i < n; i++) {
    let link = hivtrace["trace_results"]["Edges"][i];
    let newLink = {
      source: "" + link.sequences[0],
      target: "" + link.sequences[1],
      origin: ["HIVTRACE Import"],
      visible: true
    };
    newLink[metric] = parseFloat(link.length);
    MT.addLink(newLink, false);
  }
  session.data.linkFields.push(metric);
  MT.finishUp();
};

MT.applyGHOST = ghost => {
  self.session = MT.sessionSkeleton();
  session.meta.startTime = Date.now();
  ghost["samples"].forEach(node => {
    let newNode = JSON.parse(JSON.stringify(node));
    newNode.origin = ["GHOST Import"];
    newNode.genotypes = JSON.stringify(newNode.genotypes);
    newNode.id = "" + newNode.id;
    MT.addNode(newNode, false);
  });
  ["genotypes", "group", "id", "name"].forEach(key => {
    if (!session.data.nodeFields.includes(key)){
      session.data.nodeFields.push(key);
    }
  });
  let links = ghost["links"];
  let n = links.length;
  for (let i = 0; i < n; i++) {
    let link = links[i];
    let newLink = Object.assign({}, link, {
      source: "" + link.source,
      target: "" + link.target,
      distance: parseFloat(link.dist),
      origin: ["GHOST Import"],
      visible: true
    });
    MT.addLink(newLink, false);
  }
  [
    "density",
    "dist",
    "shared",
    "src_genotype",
    "src_haps",
    "tgt_genotype",
    "tgt_haps"
  ].forEach(key => {
    if (!session.data.linkFields.includes(key))
      session.data.linkFields.push(key);
  });
  MT.finishUp();
};

let decoder = new TextDecoder("utf-8");
MT.decode = x => decoder.decode(x);

MT.parseFASTA = text => {
  return new Promise(resolve => {
    let computer = new Worker("workers/parse-fasta.js");
    computer.onmessage = response => {
      let nodes = JSON.parse(MT.decode(new Uint8Array(response.data.nodes)));
      console.log("FASTA Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      resolve(nodes);
    };
    computer.postMessage(text);
  });
};

MT.parseCSVMatrix = file => {
  return new Promise(resolve => {
    let check = session.files.length > 1;
    const origin = [file.name];
    let nn = 0,
        nl = 0;
    let computer = new Worker("workers/parse-csv-matrix.js");
    computer.onmessage = response => {
      const data = JSON.parse(MT.decode(new Uint8Array(response.data.data)));
      console.log("CSV Matrix Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      const start = Date.now();
      const nodes = data.nodes;
      const tn = nodes.length;
      for (let i = 0; i < tn; i++) {
        nn += MT.addNode({
          id: nodes[i],
          origin: origin
        }, check);
      }
      const links = data.links;
      const tl = links.length;
      for (let j = 0; j < tl; j++) {
        nl += MT.addLink(Object.assign(links[j], { origin: origin }), check);
      }
      console.log("CSV Matrix Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve({ nn, nl, tn, tl });
    };
    computer.postMessage(file.contents);
  })
};

MT.r01 = Math.random;

// ported from https://github.com/CDCgov/SeqSpawnR/blob/91d5857dbda5998839a002fbecae0f494dca960a/R/SequenceSpawner.R
MT.generateSeqs = (idPrefix, count, snps, seed) => {
  let start = Date.now();
  if (!count) count = 1000;
  if (!snps) snps = 100;
  if (!seed) seed = session.data.reference;

  let sampleCodons = [
    "GCA",
    "GCC",
    "GCG",
    "GCT",
    "AAC",
    "AAT",
    "GAC",
    "GAT",
    "TGC",
    "TGT",
    "GAC",
    "GAT",
    "GAA",
    "GAG",
    "TTC",
    "TTT",
    "GGA",
    "GGC",
    "GGG",
    "GGT",
    "CAC",
    "CAT",
    "ATA",
    "ATC",
    "ATT",
    "AAA",
    "AAG",
    "CTA",
    "CTC",
    "CTG",
    "CTT",
    "TTA",
    "TTG",
    "ATG",
    "AAC",
    "AAT",
    "CCA",
    "CCC",
    "CCG",
    "CCT",
    "CAA",
    "CAG",
    "AGA",
    "AGG",
    "CGA",
    "CGC",
    "CGG",
    "CGT",
    "AGC",
    "AGT",
    "TCA",
    "TCC",
    "TCG",
    "TCT",
    "ACA",
    "ACC",
    "ACG",
    "ACT",
    "GTA",
    "GTC",
    "GTG",
    "GTT",
    "TGG",
    "TAC",
    "TAT",
    "CAA",
    "CAG",
    "GAA",
    "GAG"
  ];
  let sampleSNPs = ["A", "C", "G", "T"];

  let sample = (vec, nCodons) => {
    let samples = [];
    for (let x = 0; x < nCodons; x++) {
      let idx = Math.floor(MT.r01() * vec.length);
      samples.push(vec[idx]);
    }
    return samples;
  }

  let seqs = [];

  seqs.push({ id: idPrefix + "0", seq: seed });

  while (seqs.length < count) {
    // number codons to vary
    let nCodons = Math.floor(MT.r01() * 10) + 1;

    // randomly select this many to check for existence
    let randomCodonSet = sample(sampleCodons, nCodons).join("");

    // try again if not present
    if (seqs[seqs.length - 1].seq.indexOf(randomCodonSet) == -1) continue;

    // sequence to mutate
    let oldseed = seqs[Math.floor(MT.r01() * seqs.length)].seq;

    // select codons to replace randomCodonSet
    let replacementCodonSet = sample(sampleCodons, nCodons).join("");

    // replace codon set
    let newseed = oldseed.replace(randomCodonSet, replacementCodonSet);

    // add snp substitutions randomly across entire sequence
    // - randomly sample addedSNP
    // - randomly pick SNPS to replace
    let addedSNPs = Math.floor(MT.r01() * snps);
    for (let j = 0; j < addedSNPs; j++) {
      let randomSNP = sample(sampleSNPs, 1)[0];
      let locOfSNP = Math.floor(MT.r01() * seed.length);
      newseed =
        newseed.substr(0, locOfSNP) + randomSNP + newseed.substr(locOfSNP + 1);
    }

    seqs.push({ id: idPrefix + "" + seqs.length, seq: newseed });
  }
  console.log("Sequence spawn time:", (Date.now() - start).toLocaleString(), 'ms');
  return seqs;
};

MT.align = params => {
  return new Promise(resolve => {
    if (params.aligner == "none") {
      return resolve(params.nodes);
    }
    let n = params.nodes.length;
    let referenceLength = params.reference.length;
    let aligner = new Worker("workers/align-sw.js");
    aligner.onmessage = response => {
      let subset = JSON.parse(MT.decode(new Uint8Array(response.data.nodes)));
      console.log("Alignment transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      let start = Date.now();
      let minPadding = Infinity,
      d = null;
      for (let i = 0; i < n; i++) {
        d = subset[i];
        if (!d.seq) d.seq = "";
        if (minPadding > d.padding) minPadding = d.padding;
      }
      for (let j = 0; j < n; j++) {
        d = subset[j];
        d.seq = "-".repeat(d.padding - minPadding) + d.seq;
        if (d.seq.length > referenceLength){
          d.seq = d.seq.substring(0, referenceLength);
        } else {
          d.seq = d.seq.padEnd(referenceLength, "-");
        }
      }
      console.log("Alignment Padding time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve(subset);
    };
    aligner.postMessage(params);
  });
};

MT.computeConsensus = nodes => {
  if (!nodes) nodes = session.data.nodes.filter(d => d.seq);
  return new Promise(resolve => {
    let computer = new Worker("workers/compute-consensus.js");
    computer.onmessage = response => {
      console.log("Consensus Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      resolve(MT.decode(new Uint8Array(response.data.consensus)));
    };
    computer.postMessage(nodes);
  });
};

MT.computeAmbiguityCounts = () => {
  return new Promise(resolve => {
    let nodes = session.data.nodes;
    let subset = nodes.filter(d => d.seq);
    const subsetLength = subset.length;
    let computer = new Worker("workers/compute-ambiguity-counts.js");
    computer.onmessage = response => {
      console.log("Ambiguity Count Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      const dists = new Float32Array(response.data.counts);
      for (let j = 0; j < subsetLength; j++) {
        nodes[subset[j].index]._ambiguity = dists[j];
      }
      resolve();
    };
    computer.postMessage(subset);
  });
};

MT.computeConsensusDistances = () => {
  return new Promise(resolve => {
    let start = Date.now();
    let nodes = session.data.nodes;
    let nodesLength = nodes.length;
    let subset = [];
    for (let i = 0; i < nodesLength; i++) {
      let node = nodes[i];
      if (node.seq) {
        subset.push({
          index: i,
          seq: node.seq
        });
      } else {
        subset.push({
          index: i,
          seq: ""
        });
      }
    }
    let subsetLength = subset.length;
    let computer = new Worker("workers/compute-consensus-distances.js");
    computer.onmessage = response => {
      let dists = new Uint16Array(response.data.dists);
      console.log("Consensus Difference Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      start = Date.now();
      for (let j = 0; j < subsetLength; j++) {
        nodes[subset[j].index]._diff = dists[j];
      }
      console.log("Consensus Difference Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve();
    };
    computer.postMessage({
      consensus: session.data.consensus,
      subset: subset,
      start: start
    });
  });
};

MT.computeLinks = subset => {
  return new Promise(resolve => {
    let k = 0;
    let computer = new Worker("workers/compute-links.js");
    computer.onmessage = response => {
      let dists = session.state.metrics[0] == 'snps' ?
        new Uint16Array(response.data.links) :
        new Float32Array(response.data.links);
      console.log("Links Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      let start = Date.now();
      let check = session.files.length > 1;
      let n = subset.length;
      let l = 0;
      for (let i = 0; i < n; i++) {
        let source = subset[i];
        for (let j = 0; j < i; j++) {
          let target = subset[j];
          k += MT.addLink({
            source: source['id'],
            target: target['id'],
            distance: dists[l++],
            origin: ['Genetic Distance']
          }, check);
        }
      }
      console.log("Links Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve(k);
    };
    computer.postMessage({
      nodes: subset,
      metrics: session.state.metrics,
      strategy: session.style.widgets["ambiguity-resolution-strategy"],
      threshold: session.style.widgets["ambiguity-threshold"]
    });
  });
};

MT.getDM = () => {
  let start = Date.now();
  return new Promise(resolve => {
    let labels = session.data.nodes.map(d => d.id);
    let metric = session.style.widgets['link-sort-variable'];
    const n = labels.length;
    let dm = new Array(n);
    for(let i = 0; i < n; i++){
      dm[i] = new Array(n);
      dm[i][i] = 0;
      let source = labels[i];
      for(let j = 0; j < i; j++){
        let link = temp.matrix[source][labels[j]];
        if(link){
          dm[i][j] = dm[j][i] = link[metric];
        } else {
          dm[i][j] = dm[j][i] = null;
        }
      }
    }
    console.log("DM Compute time: ", (Date.now() - start).toLocaleString(), "ms");
    resolve(dm);
  });
};

MT.computeTree = () => {
  return new Promise(resolve => {
    let computer = new Worker("workers/compute-tree.js");
    computer.onmessage = response => {
      temp.tree = patristic.parseJSON(MT.decode(new Uint8Array(response.data.tree)));
      console.log("Tree Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      resolve();
    };
    MT.getDM().then(dm => {
      computer.postMessage({
        labels: Object.keys(temp.matrix),
        matrix: dm,
        round: session.style.widgets["tree-round"]
      });
    })
  })
};

MT.computeDirectionality = () => {
  return new Promise(resolve => {
    let computer = new Worker("workers/compute-directionality.js");
    computer.onmessage = response => {
      let flips = new Uint8Array(response.data.output);
      console.log("Directionality Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      let start = Date.now();
      let n = flips.length;
      for (let i = 0; i < n; i++) {
        if (flips[i]) {
          let fliplink = session.data.links[i];
          let fliptemp = fliplink.source;
          fliplink.source = fliplink.target;
          fliplink.target = fliptemp;
          fliplink.directed = true;
        }
      }
      console.log("Directionality Integration time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve();
    };
    computer.postMessage({
      links: session.data.links,
      tree: temp.tree
    });
  });
};

MT.computeNN = () => {
  return new Promise((resolve, reject) => {
    let nnMachine = new Worker("workers/compute-nn.js");
    nnMachine.onmessage = response => {
      if (response.data == "Error") {
        return reject("Nearest Neighbor washed out");
      }
      let output = new Uint8Array(response.data.links);
      console.log("NN Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      const start = Date.now();
      let links = session.data.links;
      const numLinks = links.length;
      for (let i = 0; i < numLinks; i++) {
        links[i].nn = output[i] ? true : false;
      }
      console.log("NN Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve();
    };
    nnMachine.postMessage({
      links: session.data.links,
      matrix: temp.matrix,
      epsilon: session.style.widgets["filtering-epsilon"],
      metric: session.style.widgets['link-sort-variable']
    });
  });
};

MT.computeTriangulation = () => {
  return new Promise((resolve, reject) => {
    const metric = session.style.widgets['link-sort-variable'];
    let machine = new Worker("workers/compute-triangulation.js");
    machine.onmessage = response => {
      if (response.data == "Error") return reject("Triangulation washed out");
      console.log("Triangulation Transit time: ", (Date.now() - response.data.start).toLocaleString(), "ms");
      let start = Date.now();
      let matrix = JSON.parse(MT.decode(new Uint8Array(response.data.matrix)));
      let labels = Object.keys(temp.matrix);
      const n = labels.length;
      for(let i = 0; i < n; i++){
        let source = labels[i];
        let row = temp.matrix[source];
        for(let j = 0; j < i; j++){
          let target = labels[j];
          if(!row[target]){
            MT.addLink({
              source: source,
              target: target,
              origin: ['Triangulation'],
              visible: false
            });
          }
          row[target][metric] = matrix[i][j];
        }
      }
      console.log("Triangulation Merge time: ", (Date.now() - start).toLocaleString(), "ms");
      resolve();
    };
    MT.getDM().then(dm => {
      machine.postMessage({
        matrix: dm
      });
    })
  });
};

MT.finishUp = async oldSession => {
  if (!oldSession) {
    if ($('[name="shouldTriangulate"]:checked').attr("id") == "doTriangulate") {
      await MT.computeTriangulation();
    }
    MT.computeNN();
  }
  MT.computeTree().then(MT.computeDirectionality);
  clearTimeout(temp.messageTimeout);
  ["node", "link"].forEach(v => {
    let n = session.data[v + "s"].length;
    let fields = session.data[v + "Fields"];
    for (let i = 0; i < n; i++) {
      let d = session.data[v + "s"][i];
      fields.forEach(field => {
        if (!(field in d)) d[field] = null;
      });
    }
  });
  $("#search-field")
    .html(session.data.nodeFields.map(field => '<option value="' + field + '">' + MT.titleize(field) + "</option>").join("\n"))
    .val(session.style.widgets["search-field"]);
  $("#search-form").css("display", "flex");
  $("#link-sort-variable")
    .html(session.data.linkFields.map(field => '<option value="' + field + '">' + MT.titleize(field) + "</option>").join("\n"))
    .val(session.style.widgets["link-sort-variable"]);
  $("#node-color-variable")
    .html(
      "<option selected>None</option>" +
      session.data.nodeFields.map(field => '<option value="' + field + '">' + MT.titleize(field) + "</option>").join("\n"))
    .val(session.style.widgets["node-color-variable"]);
  $("#link-color-variable")
    .html(
      "<option>None</option>" +
      session.data.linkFields.map(field => '<option value="' + field + '">' + MT.titleize(field) + "</option>").join("\n"))
    .val(session.style.widgets["link-color-variable"]);
  try {
    MT.updateThresholdHistogram();
  } catch (error) {
    console.error(error);
    $("#loading-information-modal").modal("hide");
    alertify
      .error("Something went wrong! Please click here to start a new session and try again.")
      .delay(0)
      .ondismiss(() => window.location.reload());
  }
  MT.setLinkVisibility(true);
  $("#SettingsTab").attr("data-target", "#global-settings-modal");
  session.meta.loadTime = Date.now() - session.meta.startTime;
  console.log("Total load time:", session.meta.loadTime.toLocaleString(), "ms");
  if (oldSession) {
    layout.root.contentItems[0].remove();
    setTimeout(() => MT.loadLayout(session.layout), 80);
  } else {
    MT.launchView(session.style.widgets['default-view']);
  }
  MT.tagClusters().then(() => {
    MT.setClusterVisibility(true);
    MT.setLinkVisibility(true);
    MT.setNodeVisibility(true);
    ["cluster", "link", "node"].forEach(thing => $window.trigger(thing + "-visibility"));
    MT.updateStatistics();
    $("#network-statistics-wrapper").fadeIn();
  });
  if (localStorage.getItem("stash-auto") == "true") {
    temp.autostash = {
      time: Date.now(),
      interval: setInterval(() => {
        let newTime = Date.now();
        localStorage.setItem("stash-" + newTime + "-autostash", JSON.stringify(session));
        localSorage.removeItem("stash-" + temp.autostash.time + "-autostash");
        temp.autostash.time = newTime;
      }, 60000)
    };
  }
  $(".hideForHIVTrace").css("display", "flex");
  setTimeout(() => {
    let files = layout.contentItems.find(item => item.componentName == "files");
    if (files) files.remove();
    $("#loading-information-modal").modal("hide");
{'fraction_non_alphanumeric': 0.17767243941740585, 'fraction_numerical': 0.009245787769277702, 'mean_word_length': 2.8900925024342747, 'pattern_counts': {'":': 294, '<': 83, '<?xml version=': 0, '>': 209, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 12}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '53347', 'n_tokens_mistral': 23204, 'n_tokens_neox': 22325, 'n_words': 3974}