
var pwURI = "";

// START PATHWAY GRAPH CONFIGURATION ----------------------------------------------

// colors
var lightGrey = "#E8E8E8";
var grey = "#B2B2B2";

var adOut = "#ff9999";
var adIn = "#007000";

// compounds
var compoundSize = 75; // = diameter of nodes of small compounds
var maxCompoundSize = function(){ return 1.5 * compoundSize }; // limit increased size of larger compounds
var maxCompoundPopupSize = function(){ return 5 * compoundSize };
var compoundImageMargin = 0.15;  // compounds images are rectangles, reduce size to fit into nodes

var compoundFill = lightGrey;
var compoundFillSelected = grey;

var showCompoundOutlines = false;
var compoundStroke = "black";
var compoundStrokeRoot = "red";
var compoundStrokeOpacity = function(d){
    // set opacity according to depth:
    // * depth 0 -> opacity 1
    // * depth == max-depth -> opacity 0
    return (maxNodeDepth - d.depth) / maxNodeDepth;
 };
var compoundStrokeWidth = function(){ return 0.04 * compoundSize };

var compoundTextSize = function(){ return 6 + compoundSize * 0.04 };
var compoundTextYOffset = function(){ return compoundSize * 0.5 };

// links
var linkStroke = grey;
var linkStrokeUp = "red";
var linkStrokeDown = "green";
var linkStrokeOpacity = 1.0;
var linkStrokeOpacitySelected = 1.0;

var linkStrokeWidth = function(){ return 0.02 * compoundSize };
var linkTextSize = function(){ return compoundTextSize() };

// layout settings ----------------------------
// default = pathway !
var treeLayoutEnabled = true;
var mergedView = false;
var ADView = false;

// ROOT in CENTER layout
var centerLayoutLinkDistance = function(){ return 2.5 * compoundSize };
var centerLayoutCharge = -1500; // compounds push each other apart
var centerLayoutRootCharge = centerLayoutCharge * 2;
var centerLayoutGravity = 0.05; // pulls compounds back to graph center

// TREE layout
var treeLayoutLinkDistance = function(){ return 2.5 * compoundSize };
var treeLayoutCharge = -1000; // compounds push each other apart
var treeLayoutPseudoCharge = treeLayoutCharge * 0.1; // relaxes forces on pseudo nodes
var treeLayoutLevelGravity = 0.5; // pulls compounds back to their depth level
var treeLayoutXCenterGravity = 0.03; // pulls compounds to x center
var treeLayoutLevels = function(){ return treeLayoutLinkDistance() }; // distance between depth levels
var treeLayoutXInitMargin = function(){ return treeLayoutLinkDistance() }; // initial x-distance between nodes of same depth

// popup
var popupEnabled = true;
var popupWaitBeforeShow = 1000;
var popushowAtLeast = 1000;

// other
var showCompoundNames = false;
var showReactionNames = false;
var showReactionProb = false;
var selectionEnabled = true;

// this is to not draw the arrow at the end of the line (which is a the compound center)
// but at the beginning of the circle
// MG: dont understand whats going on, depends on stroke-Width!, estimate manually for
// each setting
var arrowOffset = function(target_size){ return target_size * 0.33 };

// END PATHWAY GRAPH CONFIGURATION ----------------------------------------------

var maxNodeDepth = 0;

var educts =[];
var products =[];

//indicates click modes
var mode = 'undefined';

var width;
var height;

var border = "thin solid lightgrey";

var setFix = false;

var markedCompUri;
var markedEdgeUri;

var links;
var nodes;

//backup variable to keep original graph;
var __graph;

var nodeSelected = false;
var edgeSelected = false;

var clickedNode = 'undefined';
var clickedNode_d = 'undefined';
var clickedEdge = 'undefined';
var clickedEdge_d = 'undefined';

var compoundNodeUri = 'undefined';
//Array for links of all paths
var paths = [];
//Array for one path found by dfs
var path = [];
//debug variable
var pathNumeric = [];
//variable for dfs which indicates visited nodes
var greenLinks = [];

var visited;

var svg;
var force;

var modelUri;
var modelName;


function restartPathwayViz(onGraphLoad){

    if (!width) {
        width = document.getElementById("viz").offsetWidth;
        height = width*0.75;
    }

    if (force)
        force.stop();
    d3.select("svg").remove();

    if (treeLayoutEnabled) {
        force = d3.layout.force()
        .charge(function(d) {
            if (d.pseudo)
                return treeLayoutPseudoCharge;
            else
                return treeLayoutCharge;
        })
        .gravity(0)
        .linkDistance(function(d) {
            dist = Math.abs(d.target.treeLevel - d.source.treeLevel) * treeLayoutLinkDistance();
            if (dist == 0) {
                // nodes with depth in brackets: A (0) -> B (1) + C (2)
                // then there is a pseudo node that is on the same level as B
                dist = 0.5 * treeLayoutLinkDistance();
            }
            return dist;
        })
        .linkStrength(function(d) {
            return strength = 1 / d.siblings;
        })
        .size([width, height]);
    }
    else {
        force = d3.layout.force()
            .charge(function(d) {
                return d.depth==0 ? centerLayoutRootCharge : centerLayoutCharge;
            })
            .gravity(centerLayoutGravity)
            .linkDistance(centerLayoutLinkDistance())
            .size([width, height]);
    }

    svg = d3.select("#viz")
        .append("svg")
        .attr("width", "100%")
        .attr("height", height)
        .style("border", border)
           .attr("inline", "block")
        .attr("margin", "auto")
              .attr("pointer-events",    "all")
        .append('svg:g')
        .call(d3.behavior.zoom().on("zoom", redraw))
        .attr("preserveAspectRatio", "xMidYMid meet")
        .append('svg:g');

    pwUrl = pwURI;

    if(mergedView) {
        pwUrl += "?merged=true";
    }

    d3.json(pwUrl, function(error, graph) {
        if (graph) {
            if (graph.pathwaySetting && graph.pathwaySetting.truncationstrategy) {
                modelUri = graph.pathwaySetting.truncationstrategy.modelUri;
                modelName = graph.pathwaySetting.truncationstrategy.modelName;
            }

            if(!graph.isPredicted) {
                $("#toggleADView").parent().remove();
                $("#toggleADView").remove();
            }

            if (onGraphLoad)
                onGraphLoad(graph);
            __graph = graph;
            if (graph.nodes.length==0)
                alert("pathway is empty");
            else
                start(graph);
        }
    });
    }


function goFullscreen(id) {

    var element = document.getElementById(id);

    if (element.mozRequestFullScreen) {

        element.mozRequestFullScreen();
    } else if (element.webkitRequestFullScreen) {

        element.webkitRequestFullScreen();
    }
}

// now pathway fun

function reformatLink(string) {
    return string.replace(/\[([^|]+)\|([^\]]+)]/g, "<a target='parent' href=\"$1\">$2</a>");
}

/*function delComp() {
    if (markedCompUri == 'undefined') {
        // console.log("markedComp was undefined");
        return;
    }
    var conf = confirm("Do you really want to delete this Node?");
    if (conf) {
        jQuery.ajax({
            type : "delete",
            url : markedCompUri,
            success : function(response) {
                hide(clickedNode_d);
                click(clickedNode_d);
            },
            error : function(jqXHR, textStatus, errorThrown) {
                alert("Error while deleting Node");
            }

        });
    } else {
        return;
    }
}*/

/*function delEdge() {
    // console.log(markedEdgeUri);
    if (markedEdgeUri == 'undefined') {
        return;
    }
    var conf = confirm("Do you really want to delete this Edge?");
    if (conf) {
        jQuery.ajax({
            type : "delete",
            url : markedEdgeUri,
            success : function(response) {
                linkArray = new Array();
                for (var i = 0; i < links[0].length; i++) {
                    if (links[0][i].__data__.id == clickedEdge_d.id) {
                        linkArray.push(links[0][i]);
                    }
                }
                hideLinks(linkArray);
                edgeClick(clickedEdge_d);
            },
            error : function(jqXHR, textStatus, errorThrown) {
                alert("Error while deleting Edge");
            }

        });
    } else {
        return;
    }

}*/

function start(graph) {

    setChildrenAndParents(graph)
    setPseudoLinks(graph)

    maxNodeDepth = 0
    graph.nodes.forEach(function(node, i) {
        if (node.depth > maxNodeDepth)
            maxNodeDepth = node.depth;
    });

    if (treeLayoutEnabled) {
        setTreeLevel(graph)
        var treeLayout = buildTreeLayout(graph)
    }

    force.nodes(graph.nodes).links(graph.links).start();

    computeNodeSizes(graph.nodes);

    $('#name').val(graph.pathwayName);
    $('#description').val(graph.description);

    // Background rectangle to enable zoom function
    // even cursor is not on a node or path
    svg.append('svg:rect').attr('width', 9 * width).attr('height', 9 * height)
            .attr('fill', 'white').attr("x", -4 * width).attr("y", -4 * height)
            .attr("opacity", 0.0);

    function defineArrow(name, color, opacity, target) {
        svg.append("svg:defs").selectAll("marker").data([ name ]).enter()
        .append("svg:marker").attr("id", String).attr("viewBox",
                "0 -5 10 10")
        .attr("refX", 10 + (target ? arrowOffset(target.size) : 0 ))
        .attr("refY", 0).attr(
                "markerWidth", 10).attr("markerHeight", 10).attr("orient",
                "auto").attr("stroke", color).attr("fill", "none").attr(
                "stroke-opacity", opacity).append("svg:path").attr("d",
                "M0,-5L10,0L0,5");
    }
    // hack: define arrow statically for each target and each color (dont know how to change dynamically)
    for (var i = 0; i < graph.links.length; i++) {
        if (graph.links[i].target.size) {
            defineArrow("arrow_"+i,linkStroke,linkStrokeOpacity,graph.links[i].target);
            defineArrow("arrow-green_"+i,linkStrokeDown,linkStrokeOpacitySelected,graph.links[i].target);
            defineArrow("arrow-red_"+i,linkStrokeUp,linkStrokeOpacitySelected,graph.links[i].target);
        }
    }
    defineArrow("arrow",linkStroke,linkStrokeOpacity);
    defineArrow("arrow-green",linkStrokeDown,linkStrokeOpacitySelected);
    defineArrow("arrow-red",linkStrokeUp,linkStrokeOpacitySelected);

    // coordinates are set in tick
    links = svg.selectAll("line").data(graph.links).enter().append("g").append("svg:line")
            .style("stroke", function(d) {
                if(ADView) {
                    if(d.ad) {
                        if(d.ad[0]['passesAD']) {
                            return adIn;
                        } else {
                            return adOut;
                        }
                    } else {
                        return linkStroke
                    }
                } else {
                    return linkStroke
                }
            })
            .style("stroke-opacity", function(d) {
                if(ADView) {
                    if(d.ad) {
                        return 1;
                    } else {
                        return linkStrokeOpacity
                    }
                } else {
                    return linkStrokeOpacity
                }
            })
            .style("stroke-width", function() {
                        return linkStrokeWidth();
                    })
            .attr("class", "link arrow")
            .on("click", edgeClick)
            .attr("marker-end", function(d,i) {
                        if (!d.target.pseudo) {
//                            if (d.multistep === 'true') {
//                                return "";
//                            }
                            return "url(#arrow_"+i+")";
                        } else {
                            if (d.multistep === 'true') {
                                return "url(#arrow)";
                            }
                            return "";
                        }
                    })
            .attr("idx", function(d,i) {return i; })
            .attr("target-pseudo", function(d) {
                        return d.target.pseudo;
            });

    // only for mouse over and clicking (other lines are hard to hit)
    // coordinates are set in tick
    links_invisible = svg.selectAll("g").append("svg:line")
        .style("stroke", "black").style("stroke-opacity", "0").style(
            "stroke-width", function() {
                return linkStrokeWidth() * 10;
            }).attr("class", "link arrow").on("click", edgeClick);

    // coordinates are set in tick
    link_text = svg.selectAll("g")
        .append("text")
        .attr("text-anchor", "middle")
        .style("fill", "#000")
        .style("font-size", function() { return linkTextSize() + "pt"; })
        .style("font-family", "sans-serif");
    updateReactionText();

    // the popup for lines is oriented on the max-right position of the line
    // adjust to center of the line / to the pseudo node
    function popup_offset(d) {
        x = 15;
        y = 0;
        if (d.target.pseudo) {
            y += Math.abs(d.source.y - d.target.y) * 0.5;
            if (d.target.x < d.source.x)
                x -= Math.abs(d.source.x - d.target.x);
        }
        else if (d.source.pseudo) {
            y -= Math.abs(d.source.y - d.target.y) * 0.5;
            if (d.target.x > d.source.x)
                x -= Math.abs(d.source.x - d.target.x);
        }
        else {
            x -= Math.abs(d.source.x - d.target.x) * 0.5;
        }
        return {x: x, y: y};
    }
    if (popupEnabled) {
        function reaction_content(d) {
            content = "<p>Reaction: <a href='"+d.idreaction+"'>"+d.name+"</a></p>";
            var adcontent = "";
            if(d.ad){
                for(i in d.ad) {
                    data = d.ad[i];
                    adcontent = "<p>";
                    if(data["timesTriggered"]) {
                        adcontent += "This rule triggered " + data["timesTriggered"] + " in the training set<br>";
                    }
                    adcontent += "Reliability " + round(data["reliability"]) + " (" + (data["reliability"] > data["reliabilityThreshold"] ? "&gt" : "&lt") + " Reliability Threshold of " +  data["reliabilityThreshold"] + ")<br>";
                    adcontent += "Local Compatibility " + round(data["localCompatibility"]) + " (" + (data["localCompatibility"] > data["localCompatibilityThreshold"] ? "&gt" : "&lt") + " Reliability Threshold of " +  data["localCompatibilityThreshold"] + ")<br>";
                    adcontent += "</p>";
                }                
            }
            content += adcontent;

            if (d.scenarios && d.scenarios.length>0) {

                content += "<p>Scenario/s:"
                for (var sc in d.scenarios) {
                    scen = d.scenarios[sc]
                    content += "<br><a href='"+scen.id+"'>"+scen.name+"</a>";
                    if (scen.modelpredictionprob) {
                        content += "<p>Relative Reasoning: <a href='"+modelUri+"'>"+modelName+"</a>";
                        content += "<br>Probability: "+scen.modelpredictionprob+"</p>";
                    }
                    if (scen.modelbayespredictionprob) {
                        content += "<p>Relative Reasoning: <a href='"+modelUri+"'>"+modelName+"</a>";
                        content += "<br>Bayes Probability: "+scen.modelbayespredictionprob+"</p>";
                    }
                }
                content += "</p>";
            }
            if (d.ecNumbers && d.ecNumbers.length>0) {
                content += "<p>Enzyme/s:";
                for (var ec in d.ecNumbers) {
                    content += "<br><a href=\"http://www.brenda-enzymes.org/enzyme.php?ecno="
                            +  d.ecNumbers[ec].ecNumber + "\" target=\"_blank\">"
                            +  d.ecNumbers[ec].ecName + " (" + d.ecNumbers[ec].ecNumber + ")</a>";
                }
                content += "</p>";
            }
            return content;
        }
        pop_add(links_invisible,"Reaction",function(d){ return reaction_content(d);},popup_offset);
        pop_add(link_text,"Reaction",function(d){ return reaction_content(d);});
    }

    var node_drag = d3.behavior.drag().on("dragstart", dragstart).on("drag",
            dragmove).on("dragend", dragend);

    function dragstart(d, i) {
        pop_hideall();
        d3.event.sourceEvent.stopPropagation();
        force.stop();
    }

    function dragmove(d, i) {
        d3.event.sourceEvent.stopPropagation();
        d.px += d3.event.dx;
        d.py += d3.event.dy;
        d.x += d3.event.dx;
        d.y += d3.event.dy;
        tick();
    }

    function dragend(d, i) {
        pop_show();
        d3.event.sourceEvent.stopPropagation();
        d.fixed = true;
        tick();
        force.resume();
    }

    nodes = svg.selectAll(".node").data(graph.nodes).enter().append("g").attr(
            "class", "node").call(node_drag);

    rootIndices = []
    graph.nodes.forEach(function(node,i) {
        if (node.depth==0) {
            rootIndices.push(i);
          }
      });

    // init node positions
    if (!treeLayoutEnabled) {
        maxTreeSize = -1
        node = null
        graph.nodes.forEach(function(d,i) {
            if (d.depth == 0 && d.treeSize>maxTreeSize) {
                maxTreeSize = d.treeSize;
                node = d;
            }
        });
        x = width/2;
        y = height/2;
        node.x = x;
        node.y = y;
        node.px = x;
        node.py = y;
        node.fixed = true;
    }
    else {
        // positions for tree layout
        // y: set node at treeLevel
        // x: equally distriute according to number of nodes per level (for non-pseudo nodes)
        for (var level in treeLayout) {
            xStart = (width * 0.5) - (treeLayoutXInitMargin() * (treeLayout[level].length-1) * 0.5)
            treeLayout[level].forEach(function (node,j) {
                if (!node.pseudo) {
                    x = xStart + j * treeLayoutXInitMargin();
                    node.x = x;
                    node.px = x;
                }
                y = treeLayoutLevels()*node.treeLevel + compoundSize;
                node.y = y;
                node.py = y;

            });
        }
        // update x for pseudo nodes: set in between parents and children
        for (var level in treeLayout) {
            treeLayout[level].forEach(function (node,j) {
                if (node.pseudo) {
                    sumX = 0;
                    countX = 0;
                    (node.parents.concat(node.children)).forEach(function (n,j) {
                       if (!n.pseudo) {
                           sumX += n.x;
                           countX += 1;
                       }
                    });
                    x = sumX/countX;
                    node.x = x;
                    node.px = x;
                }
            });
        }
    }

     circles = nodes.append("circle").attr("class", "circle").attr("cx", 0).attr(
                        "cy", 0)
     .attr("depth", function(d) {
        return d.depth;
    }).attr("r", function(d) {
        if (!d.pseudo) {
            return d.size * 0.5;
        } else {
            return 0;
        }
    })
    .style("stroke", function(d) {
        if(ADView) {
            if(d.ad){
                if(d.ad['inAD']) {
                    return adIn;
                } else {
                    return adOut;
                }
            }
            return "black";
        } else if (d.depth == 0) {
            return compoundStrokeRoot;
        } else {
            return compoundStroke;
        }
    }).style("stroke-opacity",function(d) {
        if(ADView) {
            return 1;
        } else if (showCompoundOutlines) {
            return compoundStrokeOpacity(d);
        } else {
            return 0.0;
        }
    })
    .style("stroke-width", function(d) {
        if (!d.pseudo) {
            return compoundStrokeWidth() + "px";
        } else {
            return 0;
        }
    })
    .style("fill", function(d) {
        if(d.engineeredIntermediate) {
            return "#dcfcf9"
        }else {
            return compoundFill
        }

    })
    .style("fill-opacity", function(d) {
        if (!d.pseudo) {
            return "1";
        } else {
            return "0.0";
        }
    });

    nodes.append("inAD").attr("inAD", function(d){
        if (!d.pseudo) {
            if(d.ad) {
                return d.ad['inAD'];
            } else {
                return false;
            }
        } else {
            return;
        }
    });

    nodes.append("image").attr("xlink:href", function(d) {
        if (!d.pseudo) {
            if(ADView) {
                return d.image += "&highlight=true";
            }
            return d.image;
        } else {
            return;
        }
    }).attr("x", function(d) {
        if (!d.pseudo) {
            return -0.5*(1.0-compoundImageMargin)*d.size
        }
        else {
            return 0;
        }
    }).attr("y", function(d) {
        if (!d.pseudo) {
            return -0.5*(1.0-compoundImageMargin)*d.size
        }
        else {
            return 0;
        }
    }).on("click",
            function(d) {
                if (!d.pseudo) {
                    return click(d);
                } else {
                    return;
                }
            }).attr("width", function(d) {
        if (!d.pseudo) {
            return d.size * (1.0-compoundImageMargin);
        } else {
            return 0;
        }
    }).attr("height", function(d) {
        if (!d.pseudo) {
            return d.size * (1.0-compoundImageMargin);
        } else {
            return 0;
        }
    });

    function removeObjDel(id) {
        var button = document.getElementById(id);
        button.parentNode.removeChild(button);
    }

    node_text = nodes.append("text").attr("text-anchor", "middle").style("fill", "#000")
            .style("font-size", function() {
                return compoundTextSize() + "pt";
            }).style("font-family", "sans-serif").style("stroke-width", "0")
            .attr("transform", function() {
                return "translate(0, -" + compoundTextYOffset() + ")";
            }).text(function(d) {
                if (!d.pseudo && showCompoundNames) {
                    return d.name;
                } else {
                    return "";
                }
            });

    if (popupEnabled)
        pop_add(nodes, "Compound", function(d) {

            var prop = d.proposed;
            var proposedInfo = '';

            if(d.ad) {

                proposedInfo += "This compound is " + (d.ad['inAD'] ? "inside" : "outside") + " the Applicability Domain derived from the chemical (PCA) space constructed using the training data." + "<br>";
                if(d.ad['uncoveredFG']) {
                    proposedInfo += "Compound contains functional groups not covered by the training set  <br>";
                }

            }

            if(graph.isIncremental && isLeaf(d)) {
                console.log(d);
                proposedInfo += '<a id="aNodeProp'+ d.index + '" class="btn btn-primary" onclick="predictFromNode(\'' + d.index + '\')" href="#">Predict from here</a><br>'
            }

            if(d.properties.length > 0) {
                proposedInfo += "<br>";
                for(x in d.properties) {
                    property = d.properties[x];
                    proposedInfo += property["name"] + ": " + property["value"] + '<br>';
                }
                proposedInfo += "<br>";
            }

            sortByKey(prop, 'scenarioName');
            proposedInfo += "<a href='"+d.id+"'>"+d.name+"</a><br>"
                +"Depth: "+d.depth+"<br><br>"
                +"<img src="+d.image+" width='"+d.popupSize+"'>" + "<br>"
                +"<br><br><p><b>Half-lives and related scenarios:</b></p>";
            for(var i in prop){
                // Get scenario name and uri
                var scenName = prop[i].scenarioName;
                var scenUri = prop[i].scenarioId;
                proposedInfo += "<a href='" + scenUri + "'>" + scenName+"</a><br>";

                // Iterate over all other properties
                for(var key in prop[i]){
                    if (key === 'scenarioName' || key === 'scenarioId') {
                        continue;
                    }
                    proposedInfo += key+": "+prop[i][key]+"<br>";
                }
                proposedInfo += '<br>';
            }
        return proposedInfo;
        });

    // on each iteration: update links and translate compounds
    function tick() {

        links_invisible.attr("x1", function(d) {
            return d.source.x;
        }).attr("y1", function(d) {
            return d.source.y;
        }).attr("x2", function(d) {
            return d.target.x;
        }).attr("y2", function(d) {
            return d.target.y;
        });

        links.attr("x1", function(d) {
            return d.source.x;
        }).attr("y1", function(d) {
            return d.source.y;
        }).attr("x2", function(d) {
            return d.target.x;
        }).attr("y2", function(d) {
            return d.target.y;
        });

        link_text.attr("dx", function(d) {
            if (d.target.pseudo) {
                return d.target.x;
            }
            else if (d.source.pseudo) {
                return d.source.x;
            }
            else {
                return (d.source.x + d.target.x)/2;
            }
        }).attr("dy", function(d) {
            if (d.target.pseudo) {
                return d.target.y;
            }
            else if (d.source.pseudo) {
                return d.source.y;
            }
            else {
                return (d.source.y + d.target.y)/2;
            }
        });

        nodes.attr("transform", function(d) {
            return "translate(" + d.x + "," + d.y + ")";
        });
    }

    // on each iteration: add forces and manual stopping of forces
    force.on("tick", function(e) {

        if (treeLayoutEnabled) {
            // implement treeLayoutGravity
            var ky = treeLayoutLevelGravity * e.alpha;
            var kx = treeLayoutXCenterGravity * e.alpha;
            graph.nodes.forEach(function(d) {
                // pull back to y-level
                yLevel = treeLayoutLevels()*d.treeLevel + compoundSize;
                d.y -= (d.y - yLevel) * ky;

                // pull back to center
                xCenter = width/2;
                d.x -= (d.x - xCenter) * kx;
            });
        }

        // TODO find a better value =0.03...
        if (force.alpha() <= 0.005004) {
            for (var i = 0; i < nodes[0].length; i++) {
                if (!nodes[0][i].__data__.pseudo) {
                    nodes[0][i].__data__.fixed = true;
                }
            }
            setFix = true;
            force.stop();

        } else {
            tick();
        }
    });
}

function isLeaf(node) {
    return node.children.length == 0;
}

// adds a parents[] and a children[] array for each node
// set treeSize
function setChildrenAndParents(graph)
{
    graph.nodes.forEach(function(node, i) {
        node.parents = []
        node.children = []
    });
    graph.links.forEach(function(link,j) {
        source = graph.nodes[link.source];
        target = graph.nodes[link.target];
           source.children.push(target);
           target.parents.push(source);
    });

    function count(node) {
        node.touch = true;
        c = 1;
        node.children.forEach(function(child, i) {
            if (!child.touch) {
                c += count(child);
            }
        });
        node.count = false;
        return c;
    }
    graph.nodes.forEach(function(node, i) {
        graph.nodes.forEach(function(n, i) {
            n.touch = false;
        });
        node.treeSize = count(node);
    });

    graph.links.forEach(function(link,j) {
        p = graph.nodes[link.source].children.length;
        c = graph.nodes[link.target].parents.length;
        link.siblings = Math.max(p, c);
    });
}


// computes tree level for each node
// similar to depth, but not the shortest path to root
// i.e. if a node has parents of depth 1 and 2, its level is set to 3
// sets level of root compounds to level -1 of their children
// pseudo nodes will have intermedate level of .33 and .66
function setTreeLevel(graph) {

    // init levels
    graph.nodes.forEach(function(node, i) {
        node.treeLevel = 0;
    });

    // recursively set levels
    graph.nodes.forEach(function(node, i) {
        graph.nodes.forEach(function(n, i) {
            n.touch = false;
        });
        if (node.depth==0)
            recursion(node);
    });
    function recursion(node) {
        node.touch = true;
        node.children.forEach(function(child, i) {
            if (!child.touch) {
                d = node.treeLevel;
                if (node.pseudo || child.pseudo)
                    d += 0.33;
                else
                    d += 1;
                if (!child.pseudo)
                    d = Math.round(d);
                if (d > child.treeLevel) {
                    child.treeLevel = d;
                    recursion(child);
                }
            }
        });
        node.touch = false;
    }

    // increase levels of root compounds that have no level=1 child, but children with higher levels
    graph.nodes.forEach(function(node, i) {
        if (node.treeLevel==0 && node.children.length>0) {
            minLevel = 10000;
            node.children.forEach(function(child,j) {
                d = child.treeLevel;
                if (node.pseudo || child.pseudo)
                    d -= 0.33;
                else
                    d -= 1;
                if (!node.pseudo)
                    d = Math.round(d);
                if (d < minLevel)
                    minLevel = d;
            });
            if (minLevel > 0) {
                node.treeLevel = minLevel;
            }
        }
    });

    // pseudo nodes with non-pseudo parent always have 0.33
    // set to 0.5 if they do not have pseudo children
    graph.nodes.forEach(function(node, i) {
        if (node.pseudo && (node.parents.size>1 || !node.parents[0].pseudo)) {
            if (node.children.size>1 || !node.children[0].pseudo) {
                node.treeLevel = (node.children[0].treeLevel + node.parents[0].treeLevel) * 0.5;
            }
        }
    });

}


// builds a tree layout by iterating through the tree
// * the treeLevel is determined beforehand and remains unchanged
// * puts all nodes into a map with key treeLevel
// * tree layout is the order of the node arrays for each level
// * dfs insertion
// * idea is to insert children first which have children that are already present
function buildTreeLayout(graph) {

    treeLayout = {}

    // start off with the root nodes
    roots = []
    graph.nodes.forEach(function(node, i) {
        if (node.depth==0)
            roots.push(node);
    });
    insertNodes(roots);

    // picks the node with the max-ratio of already inserted children
    function insertNodes(nodes) {
        if (nodes.length==1) {
            insertNode(nodes[0]);
        }
        else if (nodes.length>1) {
            maxIdx = -1;
            nodes.forEach(function(node,j) {
                graph.nodes.forEach(function(n, i) {
                    n.touch = false;
                });
                insertedChildren = findInserted(node, true);
                insertedParents = findInserted(node, false);
                total = insertedChildren.total + insertedParents.total;
                found = insertedChildren.found + insertedParents.found;
                if (total==0)
                    node.foundRatio = 0;
                else
                    node.foundRatio = found/total;
                if (maxIdx==-1 || node.foundRatio>nodes[maxIdx].foundRatio)
                    maxIdx = j;
            });
            // insert max-ratio-node, and remove it from node array
            maxNode = nodes[maxIdx]
            nodes = nodes.slice(); // copy array
            nodes.splice(maxIdx, 1); // remove node
            insertNode(maxNode);
            // recalculate the found-ratio scores for the remaining nodes
            insertNodes(nodes);
        }
    }
    // inserts a single node (if not already included) and then inserts its children
    function insertNode(node) {
        if (!treeLayout[node.treeLevel]) {
            treeLayout[node.treeLevel] = [];
        }
        if ($.inArray(node, treeLayout[node.treeLevel]) == -1) {
            treeLayout[node.treeLevel].push(node);
            insertNodes(node.children);
        }
    }
    // to determine the ratio of already inserted children/parents
    function findInserted(node, downwards) {
        var found = 0;
        var total = 0;
        nodes = downwards ? node.children : node.parents;
        nodes.forEach(function(n,j) {
            if (!n.touch) {
                n.touch = true;
                if (treeLayout[n.treeLevel] && $.inArray(n, treeLayout[n.treeLevel])!=-1)
                    found += 1;
                total += 1;
                newCC = findInserted(n, downwards);
                found += newCC.found;
                total += newCC.total;
            }
        });
        return {found: found, total: total};
    }
    return treeLayout;
}


// sets all links to pseudo that are connected to a pseudo node
function setPseudoLinks(graph) {
    graph.links.forEach(function(link,j) {
        if (graph.nodes[link.source].pseudo || graph.nodes[link.target].pseudo) {
            link.pseudo = true;
        }
        else {
            link.pseudo = false;
        }
    });
}

// compute size of compound-node and store it in <node>.size value
// size of smallest compound will be assigned to 'compoundSize'
// size of larger compounds is limited by 'maxCompoundSize'
function computeNodeSizes(nodes) {

    // step1: determine size of smallest compound
    min_size = 1000000;
    for (var i = 0; i < nodes.length; i++) {
        if (!nodes[i].pseudo) {
            s = nodes[i].imageSize;
            if (s < min_size) {
                min_size = s;
            }
        }
    }

    // step2: compute scaled size and store as .size
    for (var i = 0; i < nodes.length; i++) {
        if (!nodes[i].pseudo) {
            size = nodes[i].imageSize;
            r = size/min_size;
            size = r * compoundSize;
            nodes[i].size = Math.min(size,maxCompoundSize());
            nodes[i].popupSize = Math.min(size,maxCompoundPopupSize());
        }
    }
}

function toggleCompoundOutlines() {
    showCompoundOutlines = !showCompoundOutlines;
    circles.style("stroke-opacity",function(d) {
        if (showCompoundOutlines) {
            return compoundStrokeOpacity(d);
        } else {
            return 0.0;
        }
    });
}

function toggleCompoundNames() {
    showCompoundNames = !showCompoundNames;
    node_text.text(function(d) {
      if (!d.pseudo && showCompoundNames) {
          return d.name;
      } else {
          return "";
      }
    });
}

function toggleReactionNames() {
    showReactionNames = !showReactionNames;
    updateReactionText();
}

function toggleReactionProb() {
    showReactionProb = !showReactionProb;
    updateReactionText();
}

function updateReactionText() {
    link_text.text(function(d){

    	s = "";
    	if(showReactionNames){
    		s = d.name;
    	}
    	if(d.rule && showReactionProb){
    		s += " Probability: "+d.modelpredictionprob;
    	}
    	return s.trim();
    });
}

function pop_add(objects, title, contentFunction, offsetFunction) {
  objects.attr("id","pop")
    .attr("data-container","body")
    .attr("data-toggle","popover")
    .attr("data-placement","right")
    .attr("title",title);

  objects.each(function(d,i) {
      options = { trigger: "manual", html: true, animation:false };
      this_ = this;
      var p = $(this).popover(options).on("mouseenter", function () {
          pop_show_e(this);
      });
      p.on("show.bs.popover", function(e){
        // this is to dynamically ajdust the content and bounds of the popup
          p.attr('data-content',contentFunction(d));
          p.data("bs.popover").setContent();
          if (offsetFunction) {
              offset = offsetFunction(d);
              p.data("bs.popover").tip().css({"margin-left": offset["x"]+"px", "margin-top": offset["y"]+"px"});
          }
        p.data("bs.popover").tip().css({"max-width": "1000px"});
      });
  });
}

function pop_hideall() {
    svg.selectAll("#pop").each(function(d,i){$(this).popover("hide")});
}

function pop_show_e(element) {

    var e = element;
    setTimeout(function () {
        if ($(e).is(':hover')) { // if element is still hovered
            $(e).popover("show");

            setTimeout(function () {
                var close = setInterval(function(){
                    if (!$(".popover:hover").length // mouse outside popover
                        && !$(e).is(':hover')) { // mouse outside element
                        $(e).popover('hide');
                        clearInterval(close);
                    }
                }, 100);
            },popushowAtLeast);
        }
    },popupWaitBeforeShow);
}

function pop_show() {
    svg.selectAll("#pop").each(function(d,i){
        pop_show_e(this);
    });
}


function redraw() {
    svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale("
            + d3.event.scale + ")");
}

function hide(node) {
    var nodeArr = new Array();
    var linkArr = new Array();
    var nodeIndices = new Array();

    // The if case handles the if the user wants to hide nodes with
    // a specific atomcount
    if (!node) {
        var limit = document.getElementById("counter").value;

        if (limit.length == 0 || limit == 0) {
            // nothing to hide...
            return;
        }

        // Searching all relevant nodes...
        for (var i = 0; i < nodes[0].length; i++) {
            if (nodes[0][i].__data__.atomCount <= limit) {
                nodeArr.push(nodes[0][i]);
                nodeIndices.push(nodes[0][i].__data__.index);
            }
        }
    }
    // This case handles the deletion of one single node
    else {
        for (var i = 0; i < nodes[0].length; i++) {
            if (nodes[0][i].__data__.id == node.id) {
                nodeArr.push(nodes[0][i]);
                nodeIndices.push(nodes[0][i].__data__.index);
            }
        }
    }

    var tempLinks = new Array();
    // searching relevant links
    for (var i = 0; i < links[0].length; i++) {
        var target = links[0][i].__data__.target.index;
        var source = links[0][i].__data__.source.index;

        if (nodeIndices.indexOf(source) != -1
                || nodeIndices.indexOf(target) != -1) {
            // linkArr.push(links[0][i]);
            tempLinks.push(links[0][i]);
        }
    }

    for (var i = 0; i < links[0].length; i++) {
        for (var j = 0; j < tempLinks.length; j++) {
            if (tempLinks[j].__data__.id == links[0][i].__data__.id) {
                linkArr.push(links[0][i]);
            }
        }
    }

    hideNodes(nodeArr);
    hideLinks(linkArr);

    force.resume();

}

// TODO method hideNodes and hideLinks are equal merge it
function hideNodes(nodeArray) {
    if (detectedBrowser == "Chromium" || detectedBrowser == "Chrome") {
        for (var i = 0; i < nodeArray.length; i++) {
            nodeArray[i].remove();
        }
    }

    else {
        for (var k = 0; k < nodeArray.length; k++) {
            var parentNode = nodeArray[k].parentNode;
            if (parentNode == null) {
                continue;
            }
            var childrens = parentNode.childNodes;
            for (var j = 0; j < childrens.length; j++) {
                if (childrens[j] == nodeArray[k]) {
                    parentNode.removeChild(childrens[j]);
                    break;
                }
            }
        }
    }
}

function hideLinks(linkArray) {

    for (var k = 0; k < linkArray.length; k++) {
        var parentNode = linkArray[k].parentNode;
        if (parentNode == null) {
            continue;
        }
        var childrens = parentNode.childNodes;
        for (var j = 0; j < childrens.length; j++) {
            if (childrens[j] == linkArray[k]) {
                parentNode.removeChild(childrens[j]);
            }
        }
    }
}

function reset() {
    // remove all;
    svg.selectAll("g.node").remove();
    svg.selectAll("rect").remove();
    svg.selectAll("image").remove();
    svg.selectAll("line").remove();
    svg.selectAll("marker").remove();
    svg.selectAll("text").remove();
    svg.selectAll("g").remove();
    svg.selectAll("defs").remove();
    start(__graph);
}

function click(d) {

    if (!selectionEnabled)
        return;

    try {
        if (d3.event.defaultPrevented) {
            return;
        }
    } catch (Exception) {
        console.log("mhm");
    }

    for (var i = 0; i < nodes[0].length; i++) {
        if (nodes[0][i].__data__.id == d.id) {
            tempNode = nodes[0][i].childNodes[0];
        }
    }

    if (mode === "educt" || mode === "product") {
        var comp = new Array();
        comp.push(tempNode);
        if (mode === "educt") {
            var list = document.getElementById("eductList");
            appendCompounds(educts, list, comp);
        } else {
            var list = document.getElementById("productList");
            appendCompounds(products, list, comp);
        }
        return;
    }

    if (edgeSelected) {
        edgeClick(clickedEdge_d);
    }

    // variable of child from where dfs should start
    var from = tempNode.parentNode.__data__.index;

    if (clickedNode == 'undefined') {
        setSelectedDropdownValue('#nodeDropdown', d.id);
        setSelectedDropdownValue('#nodeToHl', d.id);
        nodeSelected = true;
        compoundNodeUri = d.idcomp;
        markedCompUri = tempNode.parentNode.__data__.id;
        clickedNode = tempNode;
        clickedNode_d = d;
        mark(clickedNode, nodeSelected);

        resetArrays();
        rootIndices.forEach(function(rootIndex) {
            dfs(from, rootIndex, from, path, pathNumeric);
        });
        for (var i = 0; i < paths.length; i++) {
            paintPath(paths[i],nodeSelected, clickedNode_d);
        }
        findOutgoing(from);
        markOutgoingLinks(greenLinks, nodeSelected);

    } else if (clickedNode == tempNode) {
        setSelectedDropdownValue('#nodeDropdown', "undefined");
        setSelectedDropdownValue('#nodeToHl', "undefined");
        nodeSelected = false;
        old_d = clickedNode_d;
        markedCompUri = "undefined";
        mark(clickedNode, nodeSelected);
        // update sidebar with empty string
        clickedNode = 'undefined';
        clickedNode_d = 'undefined';
        compoundNodeUri = 'undefined';
        // in this case the path is already painted and
        // the node is clicked again -> unpaint
        for (var i = 0; i < paths.length; i++) {
            paintPath(paths[i],nodeSelected, old_d);
        }

        markOutgoingLinks(greenLinks,nodeSelected);
        // initial state
        resetArrays();

    } else {
        setSelectedDropdownValue('#nodeDropdown', d.id);
        setSelectedDropdownValue('#nodeToHl', d.id);
        nodeSelected = true;
        markedCompUri = tempNode.parentNode.__data__.id;

        mark(clickedNode, false);
        mark(tempNode, true);
        clickedNode = tempNode;
        clickedNode_d = d;
        compoundNodeUri = d.idcomp;
        for (var i = 0; i < paths.length; i++) {
            paintPath(paths[i],false, clickedNode_d);
        }
        markOutgoingLinks(greenLinks,false);

        resetArrays();
        rootIndices.forEach(function(rootIndex) {
            dfs(from, rootIndex, from, path, pathNumeric);
        });
        for (var i = 0; i < paths.length; i++) {
            paintPath(paths[i],nodeSelected, clickedNode_d);
        }
        findOutgoing(from);
        markOutgoingLinks(greenLinks,nodeSelected);

    }
}

function appendCompounds(datastore, list, comp) {
    for (var i = 0; i < comp.length; i++) {
        var li = document.createElement("li");
        li.innerHTML = comp[i].__data__.name;
        list.appendChild(li);
        datastore.push([ comp[i].__data__.name, comp[i].__data__.id ]);
    }
}

function clearList(list) {
    list.innerHTML = "";
    educts = [];
    products = [];
}

function edgeClick(d) {

    if (!selectionEnabled)
        return;

    var connectors = new Array();
    var old_connectors = new Array();
    if (nodeSelected) {
        // objectSelected = false;
        click(clickedNode_d);
    }

    var tempEdge = 'undefined';

    // Determine the selected Edge
    for (var i = 0; i < links[0].length; i++) {
        if (links[0][i].__data__.id == d.id) {
            tempEdge = links[0][i];
            connectors.push(tempEdge);
            old_connectors.push(tempEdge);
            break;
        }
    }

    if (tempEdge == 'undefined') {
        return;
    }

    for (var i = 0; i < links[0].length; i++) {
        if (links[0][i].__data__.id == tempEdge.__data__.id) {
            connectors.push(links[0][i]);
        }
        if (clickedEdge != 'undefined') {
            if (links[0][i].__data__.id == clickedEdge.__data__.id) {
                old_connectors.push(links[0][i]);
            }
        }
    }

    var eductsTemp = new Array();
    var productsTemp = new Array();
    for (var i = 0; i < connectors.length; i++) {
        for (var j = 0; j < nodes[0].length; j++) {
            if (connectors[i].__data__.source_byname == nodes[0][j].__data__.name) {
                eductsTemp.push(nodes[0][j]);
            } else if (connectors[i].__data__.target_byname == nodes[0][j].__data__.name) {
                productsTemp.push(nodes[0][j]);
            }
        }
    }
    // console.log(educts);
    // console.log(products);

    if (clickedEdge == 'undefined') {
        setSelectedDropdownValue('#edgeDropdown', d.id);
        edgeSelected = true;
        clickedEdge = tempEdge;
        clickedEdge_d = d;
        markedEdgeUri = clickedEdge.__data__.id;
        paintPath(clickedEdge,edgeSelected, clickedEdge_d);
        for (var i = 0; i < connectors.length; i++) {
            paintPath(connectors[i],edgeSelected, clickedEdge_d);
        }

        // var list = document.getElementById("eductList");
        // appendCompounds(educts,list,eductsTemp);
        // var list = document.getElementById("productList");
        // appendCompounds(products,list,productsTemp);

    } else if (clickedEdge == tempEdge) {
        setSelectedDropdownValue('#edgeDropdown', "undefined");
        old_d = clickedEdge_d;
        edgeSelected = false;
        clickedEdge = 'undefined';
        markedEdgeUri = 'undefined';
        clickedEdge_d = 'undefined';

        for (var i = 0; i < connectors.length; i++) {
            paintPath(connectors[i],edgeSelected, old_d);
        }
        paintPath(tempEdge,edgeSelected, old_d);
        //
        // var list = document.getElementById("eductList");
        // clearList(list);
        // var list = document.getElementById("productList");
        // clearList(list);

    } else {
        setSelectedDropdownValue('#edgeDropdown', d.id);
        // console.log(old_connectors);
        edgeSelected = true;
        // paintPath(clickedEdge);
        clickedEdge = tempEdge;
        clickedEdge_d = d;
        for (var i = 0; i < old_connectors.length; i++) {
            paintPath(old_connectors[i],edgeSelected, clickedEdge_d);
        }
        for (var i = 0; i < connectors.length; i++) {
            paintPath(connectors[i],edgeSelected, clickedEdge_d);
        }
        // paintPath(clickedEdge);
        markedEdgeUri = clickedEdge.__data__.id;

        // var list = document.getElementById("eductList");
        // clearList(list);
        // var list = document.getElementById("productList");
        // clearList(list);

        // var list = document.getElementById("eductList");
        // appendCompounds(educts,list,eductsTemp);
        // var list = document.getElementById("productList");
        // appendCompounds(products,list,productsTemp);
    }
}

function mark(nodeToMark, select) {
    if (select){
        nodeToMark.style.fill = compoundFillSelected;
    }
    else {
        nodeToMark.style.fill = compoundFill;
    }
}

/*
 * Depth first search
 */
function dfs(from, to, orig, path, pathNumeric) {
    if (from == to) {
        // add path to all found paths
        for (var i = 0; i < path.length; i++) {
            if (paths.indexOf(path[i]) == -1) {
                paths.push(path[i]);
            }
        }
        return;
    } else {
        for (var i = 0; i < links[0].length; i++) {
            if (links[0][i].__data__.target.index == nodes[0][from].__data__.index
                    && visited[links[0][i].__data__.source.index] == -1) {
                if (links[0][i].__data__.target.index != links[0][i].__data__.source.index) {
                    path.push(links[0][i]);
                    // @DEBUG pathnumeric stores indices of links
                    // pathNumeric.push(/*links[0][i].__data__.target.index*/
                    // i);
                    var curr = links[0][i].__data__.source.index;
                    visited[from] = links[0][i].__data__.source.index;
                    dfs(curr, to, orig, path, pathNumeric);
                    // Backtracking to find all possible paths
                    path.pop();
                    // pathNumeric.pop();
                    visited[from] = -1;
                }
            }
        }
    }
}

function paintPath(path, select, d) {
    var curr = path.style;

    passes = false;

    if(ADView) {
        if ('ad' in d){
            if(d['id'].includes('node')) {
                passes = d.ad['inAD'];
            } else {
                passes = d.ad[0]['passesAD']
            }
        }
    }

    if (select) {
        curr.stroke = linkStrokeUp;
        curr.strokeOpacity = linkStrokeOpacitySelected;
        if (path.getAttribute("marker-end"))
            if (path.getAttribute("target-pseudo"))
                path.setAttribute("marker-end","url(#arrow-red)");
            else
                path.setAttribute("marker-end","url(#arrow-red_"+path.getAttribute("idx")+")");
    }
    else {
        curr.stroke = ADView ? (passes ? adIn : adOut) : linkStroke
        curr.strokeOpacity = ADView ? (passes ? 1 : 0) : linkStrokeOpacity;
        if (path.getAttribute("marker-end"))
            if (path.getAttribute("target-pseudo"))
                path.setAttribute("marker-end","url(#arrow)");
            else
                path.setAttribute("marker-end","url(#arrow_"+path.getAttribute("idx")+")");
    }
}

function markOutgoingLinks(links, select) {
    for (var i = 0; i < links.length; i++) {
        var curr = links[i].style;
        if (select) {
            curr.stroke = linkStrokeDown;
            curr.strokeOpacity = linkStrokeOpacitySelected;
            if (links[i].getAttribute("marker-end"))
                if (links[i].getAttribute("target-pseudo"))
                    links[i].setAttribute("marker-end","url(#arrow-green)");
                else
                    links[i].setAttribute("marker-end","url(#arrow-green_"+links[i].getAttribute("idx")+")");
        }
        else {
            curr.stroke = linkStroke;
            curr.strokeOpacity = linkStrokeOpacity;
            if (links[i].getAttribute("marker-end"))
                if (links[i].getAttribute("target-pseudo"))
                    links[i].setAttribute("marker-end","url(#arrow)");
                else
                    links[i].setAttribute("marker-end","url(#arrow_"+links[i].getAttribute("idx")+")");
        }
    }
}

function findOutgoing(from) {
    var tempLinks = new Array();

    for (var i = 0; i < links[0].length; i++) {
        if (links[0][i].__data__.source.index == from) {
            // greenLinks.push(links[0][i]);
            tempLinks.push(links[0][i]);
        }
    }

    for (var j = 0; j < tempLinks.length; j++) {
        for (var i = 0; i < links[0].length; i++) {
            if (tempLinks[j].__data__.id == links[0][i].__data__.id) {
                greenLinks.push(links[0][i]);
            }

        }
    }
}

function findConnectedEdges(from) {
    // var res = new Array();
    for (var i = 0; i < links[0].length; i++) {
        if (links[0][i].__data__.source.index == from) {
            for (var j = 0; j < links[0].length; j++) {
                if (links[0][i].__data__.id == links[0][j].__data__.id) {
                    greenLinks.push(links[0][j]);
                }
            }
        }
    }
}

function resetArrays() {
    visited = new Array(nodes[0].length);
    for (var i = 0; i < visited.length; i++) {
        visited[i] = -1;
    }
    path = [];
    paths = [];
    pathNumeric = [];
    greenLinks = [];

}

function uploadReaction() {

    var eductsidlist = [];
    for ( var x in educts) {
        eductsidlist.push(educts[x][1]);
    }

    var productsidlist = [];
    for ( var x in products) {
        productsidlist.push(products[x][1]);
    }

    if (educts.length == 0 || products.length == 0) {
        console.log("Caught empty list");
        return;
    }

    var url = window.location.href + "/edge";

    var multi = $("#multistep").is(":checked");

    var data = {
        products : productsidlist.join(),
        educts : eductsidlist.join(),
        edgeReason : "",
        multistep : multi
    };

    jQuery.ajax({
        type : "post",
        data : data,
        url : url,
        success : function(response) {
            console.log("success");
            location.reload();
        },
        error : function(jqXHR, textStatus, errorThrown) {
            alert("Error while adding Edge");
            console.log(errorThrown);
            console.log(jqXHR);
            console.log(textStatus);
        }

    });
}

function dl(url) {
    var hiddenIFrameID = 'hiddenDownloader', iframe = document
            .getElementById(hiddenIFrameID);
    if (iframe === null) {
        iframe = document.createElement('iframe');
        iframe.id = hiddenIFrameID;
        iframe.style.display = 'none';
        document.body.appendChild(iframe);
    }
    iframe.src = url;
}
function dlCSV() {
    var thresh = $('#thresh').val();
    console.log(thresh);
    if(!thresh){
        thresh = 0.001;
    }
    var uri = '?toCSV=true&threshold='+thresh;
    dl(uri);
}

function dlPW(){
    var uri = "?hidden=download";
    var thresh =  $('#threshold').val();
    if(!thresh){
        thresh = 0.001;
    }
    uri += "&threshold="+thresh;
    var format = $('#format').val();
    uri += "&format="+format;
    if(format === 'custom'){
        var delimiter = $('#delimiter').val();
        if(delimiter){
            uri += "&delimiter="+delimiter;
        }
    }
    dl(uri);
}

