<commit_before>document.onload = (function ($, d3, saveAs, Blob, undefined) {
    "use strict";

    // define graphcreator object
    var GraphCreator = function (svg, nodes, edges) {
        var self = this;
        self.nextId = 0;

        self.nodes = [];
        nodes.forEach(function (n) {
            self.nodes[n.id] = n;
        });

        self.edges = edges || [];

        self.state = {
            selectedNode: null,
            selectedEdge: null,
            mouseDownNode: null,
            justDragged: false,
            lastKeyDown: -1,
            shiftNodeDrag: false,
            selectedText: null,
            tryReconnect: false,
            lockKeyEvent: false,
            currentLinkSource: null
        };

        self.svg = svg;

        self.defs = svg.append('svg:defs');
        self.defs.append('svg:marker')
            .attr('id', 'end-arrow')
            .attr('viewBox', '0 -5 10 10')
            .attr('refX', "32")
            .attr('markerWidth', 3.5)
            .attr('markerHeight', 3.5)
            .attr('orient', 'auto')
            .append('svg:path')
            .attr('d', 'M0,-5L10,0L0,5');

        // define arrow markers for leading arrow
        self.defs.append('svg:marker')
            .attr('id', 'mark-end-arrow')
            .attr('viewBox', '0 -5 10 10')
            .attr('refX', 7)
            .attr('markerWidth', 3.5)
            .attr('markerHeight', 3.5)
            .attr('orient', 'auto')
            .append('svg:path')
            .attr('d', 'M0,-5L10,0L0,5');

        self.svgGraph = svg.append("g")
            .classed(self.consts.graphClass, true);

        // displayed when dragging between nodes
        self.dragLine = self.svgGraph.append('svg:path')
            .attr('class', 'link dragline hidden')
            .attr('d', 'M0,0L0,0')
            .style('marker-end', 'url(#mark-end-arrow)');

        // svg nodes and edges
        self.paths = self.svgGraph.append("g").selectAll("g");
        self.circles = self.svgGraph.append("g").selectAll("g");

        self.forceDragged = null;

        self.dragHandler = d3.behavior.drag().origin(function (d) {
            return {x: d.x, y: d.y};
        }).on("drag", function (d) {
            self.state.justDragged = true;
            if (self.state.shiftNodeDrag) {
                var position = d3.mouse(self.svgGraph.node());
                self.dragLine.attr('d', 'M' + d.x + ',' + d.y + 'L' + position[0] + ',' + position[1]);
            } else {
                d.x += d3.event.dx;
                d.y += d3.event.dy;
                if (!self.forceDragged) {
                    self.forceNodes.forEach(function (i) {
                        if (i.id == d.id) {
                            self.forceDragged = i;
                        }
                    });
                }
                self.forceDragged.px += d.x - self.forceDragged.x;
                self.forceDragged.py += d.y - self.forceDragged.y;
                self.forceDragged.x = d.x;
                self.forceDragged.y = d.y;

                var average = [d3.mean(self.forceNodes, function (d) {
                    return d.x;
                }) * 2, d3.mean(self.forceNodes, function (d) {
                    return d.y;
                }) * 2];

                self.forceHandler.size(average);

                self.updateGraph();
            }
        }).on("dragend", function () {
            if (self.state.selectedNode) {
                sidebar.update(self.state.selectedNode.id);
            }
            self.forceDragged = null;
            self.trySave();
        });

        self.zoomHandler = d3.behavior.zoom().on("zoom", function () {
            if (d3.event.sourceEvent.shiftKey) {
                return false;
            } else {
                d3.select("." + self.consts.graphClass)
                    .attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
                return true;
            }
        }).on("zoomstart", function () {
            var ael = d3.select("#" + self.consts.activeEditId).node();
            if (ael) {
                ael.blur();
            }
            if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move");
        }).on("zoomend", function () {
            d3.select('body').style("cursor", "auto");
        });

        self.forceNodes = [];
        self.forceEdges = [];
        self.forceSaveLastTime = Date.now();

        self.forceHandler = d3.layout.force()
            .charge(-8000).friction(0.6).gravity(0.4).linkDistance(200)
            .on("tick", function (e) {
                self.forceNodes.forEach(function (i) {
                    self.nodes[i.id].x = i.x;
                    self.nodes[i.id].y = i.y;
                });
                var now = Date.now();
                if (now > self.forceSaveLastTime + 4096) {
                    self.trySave();
                    self.forceSaveLastTime = now;
                }
                self.updateGraph();
            });

        // listen for key events
        d3.select(document).on("keydown", function () {
            if (self.state.lockKeyEvent || self.state.lastKeyDown !== -1) return;

            self.state.lastKeyDown = d3.event.keyCode;

            switch (d3.event.keyCode) {
            case self.consts.BACKSPACE_KEY:
            case self.consts.DELETE_KEY:
                d3.event.preventDefault();
                var oldNode = self.state.selectedNode;
                if (self.state.selectedEdge) {
                    self.delSelectedEdge();
                } else {
                    self.delSelectedNode();
                }
                if (oldNode) {
                    if (graph.nodes.length > 0) {
                        var index = oldNode.id;
                        do {
                            index = (index + 1) % graph.nodes.length;
                        } while (index != oldNode.id && graph.nodes[index] == undefined);
                        graph.setSelectedNode(graph.nodes[index]);
                        graph.centerSelectedNode();
                        sidebar.update(index);
                    } else {
                        graph.setSelectedNode(graph.nodes[graph.nodes.length]);
                        sidebar.update(graph.nodes.length);
                    }
                }
            }
        }).on("keyup", function () {
            self.state.lastKeyDown = -1;
        });

        // listen for mouse events
        svg.on("mousedown", function (d) {
            self.state.graphMouseDown = true;
        }).on("mouseup", function (d) {
            if (self.state.graphMouseDown && d3.event.shiftKey) {
                // clicked not dragged from svg
                var xycoords = d3.mouse(self.svgGraph.node());
                var pre = self.state.selectedNode;
                self.createNodeAndSelect({tax_id: "", gene_id: "", name: "", info: "", title: "", x: xycoords[0], y: xycoords[1]});
                var d = self.state.selectedNode;
                // make title of text immediently editable
                var circles = self.circles.filter(function (dval) {
                    return dval.id === d.id;
                });
                sidebar.editNodeInfo(self.nodes[d.id], function (node) {
                    if (!node) {
                        graph.delSelectedNode();
                        graph.setSelectedNode(pre);
                        sidebar.update(!pre ? graph.nodes.length : pre.id);
                    } else {
                        circles.select('text').remove();
                        graph.setCircleTitle(circles, node.title);
                        sidebar.update(d.id);
                        graph.trySave();
                    }
                });
            } else if (self.state.shiftNodeDrag) {
                // dragged from node
                self.state.shiftNodeDrag = false;
                self.dragLine.classed("hidden", true);
            }
            self.state.graphMouseDown = false;
        }).on("mouseleave", function (d) {
            self.state.shiftNodeDrag = false;
            self.dragLine.classed("hidden", true);
            self.state.graphMouseDown = false;
        });

        // listen for dragging
        svg.call(self.zoomHandler).on("dblclick.zoom", null);

        // listen for resize
        var resizeOld = window.onresize || function () {};
        window.onresize = function () {
            var docEl = document.documentElement, bodyEl = document.body;
            var x = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth;
            var y = window.innerHeight || docEl.clientHeight || bodyEl.clientHeight;
            svg.attr("width", x).attr("height", y);
            resizeOld.apply(this, arguments);
        };

        // update
        self.updateGraph();

        var average = [d3.mean(self.forceNodes, function (d) {
            return d.x;
        }) * 2, d3.mean(self.forceNodes, function (d) {
            return d.y;
        }) * 2];

        self.forceHandler.size(average);

        for (var i in nodes) {
            if (nodes[i] != undefined) {
                self.setSelectedNode(nodes[i]);
                break;
            }
        }
    };

    GraphCreator.prototype.consts = {
        selectedClass: "selected",
        connectClass: "connect-node",
        circleGClass: "conceptG",
        graphClass: "graph",
        activeEditId: "active-editing",
        BACKSPACE_KEY: 8,
        DELETE_KEY: 46,
        ENTER_KEY: 13,
        nodeRadius: 50
    };

    GraphCreator.prototype.deleteGraph = function () {
        this.nodes = [];
        this.edges = [];
        sidebar.update(0);
        this.updateGraph();
        this.trySave();
    };

    /* insert svg line breaks: taken from http://stackoverflow.com/questions/13241475/how-do-i-include-newlines-in-labels-in-d3-charts */
    GraphCreator.prototype.setCircleTitle = function (gEl, title) {
        var words = title.split(/\s+/g);
        var el = gEl.append("text").attr("text-anchor", "middle");
        el.attr("dx", 0);
        for (var i in words) {
            el.append('tspan').text(words[i]).attr('x', 0).attr('dy', function () {
                return i == 0 ? 7 * (2 - words.length) : 14;
            });
        }
    };

    GraphCreator.prototype.createNodeAndSelect = function (nodeData) {
        while (this.nodes[this.nextId] != undefined) {
            this.nextId += 1;
        }
        this.nodes[this.nextId] = {
            "id": this.nextId,
            "type": nodeData.type,
            "tax_id": nodeData.tax_id,
            "gene_id": nodeData.gene_id,
            "name": nodeData.name,
            "info": nodeData.info,
            "title": nodeData.title,
            "x": nodeData.x,
            "y": nodeData.y
        };
        this.updateGraph();
        this.trySave();
        this.setSelectedNode(this.nodes[this.nextId]);
    };

    GraphCreator.prototype.centerSelectedNode = function (duration, callback) {
        duration = duration || 500;
        callback = callback || function () {};
        var thisGraph = this;
        if (thisGraph.state.selectedNode) {
            var svgWrapper = $("#body");
            var x = svgWrapper.width() / 2 - thisGraph.state.selectedNode.x;
            var y = svgWrapper.height() / 2 - thisGraph.state.selectedNode.y;
            d3.select("." + thisGraph.consts.graphClass)
                .transition()
                .duration(duration)
                .attr("transform", function () {
                    return "translate(" + x + ", " + y + ") scale(" + 1 + ")";
                })
                .each("end", function () {
                    callback(thisGraph.state.selectedNode);
                });
            this.zoomHandler.translate([x, y]).scale(1);
        }
    };

    GraphCreator.prototype.setSelectedEdge = function (edgeData) {
        var thisGraph = this, oldEdge = thisGraph.state.selectedEdge, className = thisGraph.consts.selectedClass;
        if (oldEdge !== edgeData) {
            if (oldEdge) {
                thisGraph.paths.filter(function (ed) {
                    return ed === oldEdge;
                }).classed(className, false);
            }
            thisGraph.state.selectedEdge = edgeData;
            if (edgeData) {
                thisGraph.paths.filter(function (ed) {
                    return ed === edgeData;
                }).classed(className, true);
            }
        }
    };

    GraphCreator.prototype.setSelectedNode = function (nodeData) {
        var thisGraph = this, oldNode = thisGraph.state.selectedNode, className = thisGraph.consts.selectedClass;
        if (oldNode !== nodeData) {
            if (oldNode) {
                thisGraph.circles.filter(function (ed) {
                    return ed === oldNode;
                }).classed(className, false);
            }
            thisGraph.state.selectedNode = nodeData;
            if (nodeData) {
                thisGraph.circles.filter(function (ed) {
                    return ed === nodeData;
                }).classed(className, true);
            }
        }
    };

    GraphCreator.prototype.delSelectedEdge = function () {
        var selectedEdge = this.state.selectedEdge;
        if (selectedEdge) {
            this.edges.splice(this.edges.indexOf(selectedEdge), 1);
            this.state.selectedEdge = null;
            this.updateGraph();
            this.trySave();
        }
    };

    GraphCreator.prototype.delSelectedNode = function () {
        var selectedNode = this.state.selectedNode;
        if (selectedNode) {
            this.nodes[selectedNode.id] = undefined;
            this.edges = this.edges.filter(function (l) {
                return l.source != selectedNode.id && l.target != selectedNode.id;
            });
            this.state.selectedNode = null;
            if (selectedNode.id < this.nextId) {
                this.nextId = selectedNode.id;
            }
            this.updateGraph();
            this.trySave();
        }
    };

    GraphCreator.prototype.pathMouseDown = function (d3path, d) {
        var thisGraph = this, state = thisGraph.state;
        d3.event.stopPropagation();
        thisGraph.setSelectedEdge(d);
    };

    // mousedown on node
    GraphCreator.prototype.circleMouseDown = function (d3node, d) {
        var thisGraph = this, state = thisGraph.state;
        d3.event.stopPropagation();
        state.mouseDownNode = d;
        if (d3.event.shiftKey) {
            state.shiftNodeDrag = d3.event.shiftKey;
            // reposition dragged directed edge
            thisGraph.dragLine.classed('hidden', false).attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
        }
    };

    // mouseup on nodes
    GraphCreator.prototype.circleMouseUp = function (d3node, d) {
        var thisGraph = this,
            state = thisGraph.state,
            consts = thisGraph.consts;
        // reset the states
        state.shiftNodeDrag = false;
        d3node.classed(consts.connectClass, false);

        var mouseDownNode = state.currentLinkSource || state.mouseDownNode;

        if (state.currentLinkSource) {
            state.currentLinkSource = null;
        }

        if (!mouseDownNode) return;

        thisGraph.dragLine.classed("hidden", true);

        $('#side-link-to').removeClass('active');

        if ($('#side-path-finder').hasClass('active')) {
            $('#side-path-finder').removeClass('active');
            graph.state.lockKeyEvent = true;
            var nodeDict, paths;
            $('#modal-path-finder').openModal({
                dismissible: true,
                in_duration: 0,
                out_duration: 0,
                ready: function () {
                    nodeDict = [], paths = [];
                    $('#path-finder-main').hide();
                    $('#path-finder-result').empty().hide();
                    $('#path-finder-progress').show();
                    $('#path-finder-k').val('5');
                    $('#path-finder-maxlen').val('10');
                    $('#path-finder-start').click(function () {
                        var k = Number($('#path-finder-k').val());
                        var maxlen = Number($('#path-finder-maxlen').val());
                        $('#path-finder-main').show();
                        sidebar.pathFinder({
                            s: mouseDownNode['gene_id'],
                            t: d['gene_id'],
                            k: isNaN(k) ? 5 : k,
                            maxlen: isNaN(maxlen) ? 10 : maxlen
                        }, function (nodes, p) {
                            paths = p;
                            nodes.forEach(function (i) {
                                nodeDict[i['gene_id']] = i;
                            });
                            var root = $('#path-finder-result').empty().hide();
                            for (var i in paths) {
                                var path = paths[i];
                                var nodeList = $('<div class="collection"></div>');
                                path.forEach(function (id) {
                                    var i = nodeDict[id];
                                    nodeList.append($('<div class="collection-item grey lighten-5 truncate"></div>')
                                        .append($('<span></span>').text('Tax ID: ' + i['tax_id'] + '\t' + 'Gene ID: ' + i['gene_id']))
                                        .append($('<br>'))
                                        .append($('<span></span>').text(i['name'] + ' '))
                                        .append($('<span class="grey-text"></span>').text(i['info'])));
                                });
                                root.append($('<li></li>').append($('<div class="collapsible-header truncate"></div>')
                                    .append($('<span></span>').text('Path ' + (1 + +i) + ' ' + path.map(function (i) {
                                            return nodeDict[i].name;
                                        }).reduce(function (i, j) {
                                            return i + ' -> ' + j;
                                        })))
                                    .append($('<i class="material-icons tiny right">clear</i>').click((function (i) {
                                        return function () {
                                            $(this).parent().parent().remove();
                                            paths[i] = undefined;
                                        };
                                    })(i))))
                                    .append($('<div class="collapsible-body"></div>').append(nodeList)))
                                    .show();
                            }
                            $('#path-finder-progress').hide();
                        });
                    });
                },
                complete: function () {
                    graph.state.lockKeyEvent = false;
                    paths.forEach(function (path) {
                        if (!path) {
                            return;
                        }
                        var previous = mouseDownNode;
                        for (var i = 1; i < path.length; ++i) {
                            var node = nodeDict[path[i]];
                            if (i + 1 < path.length) {
                                graph.createNodeAndSelect({
                                    tax_id: node.tax_id,
                                    gene_id: node.gene_id,
                                    name: node.name,
                                    info: node.info,
                                    title: node.name,
                                    x: previous.x + 100 * Math.random() + 100,
                                    y: previous.y + 100 * Math.random() + 100
                                });
                            } else {
                                graph.setSelectedNode(graph.nodes[d.id]);
                            }
                            var newEdge = {source: previous.id, target: graph.state.selectedNode.id};
                            thisGraph.edges.push(newEdge);
                            thisGraph.updateGraph();
                            previous = graph.state.selectedNode;
                        }
                    });
                    sidebar.update(mouseDownNode.id);
                    thisGraph.setSelectedNode(graph.nodes[mouseDownNode.id]);
                    thisGraph.trySave();
                }
            });
            return;
        }


        if (mouseDownNode !== d) {
            // we're in a different node: create new edge for mousedown edge and add to graph
            var newEdge = {source: mouseDownNode.id, target: d.id};
            var filtRes = thisGraph.paths.filter(function (d) {
                if (d.source === newEdge.target && d.target === newEdge.source) {<commit_msg>Revert "Revert "Change the default link distance of pano""<commit_after>document.onload = (function ($, d3, saveAs, Blob, undefined) {
    "use strict";

    // define graphcreator object
    var GraphCreator = function (svg, nodes, edges) {
        var self = this;
        self.nextId = 0;

        self.nodes = [];
        nodes.forEach(function (n) {
            self.nodes[n.id] = n;
        });

        self.edges = edges || [];

        self.state = {
            selectedNode: null,
            selectedEdge: null,
            mouseDownNode: null,
            justDragged: false,
            lastKeyDown: -1,
            shiftNodeDrag: false,
            selectedText: null,
            tryReconnect: false,
            lockKeyEvent: false,
            currentLinkSource: null
        };

        self.svg = svg;

        self.defs = svg.append('svg:defs');
        self.defs.append('svg:marker')
            .attr('id', 'end-arrow')
            .attr('viewBox', '0 -5 10 10')
            .attr('refX', "32")
            .attr('markerWidth', 3.5)
            .attr('markerHeight', 3.5)
            .attr('orient', 'auto')
            .append('svg:path')
            .attr('d', 'M0,-5L10,0L0,5');

        // define arrow markers for leading arrow
        self.defs.append('svg:marker')
            .attr('id', 'mark-end-arrow')
            .attr('viewBox', '0 -5 10 10')
            .attr('refX', 7)
            .attr('markerWidth', 3.5)
            .attr('markerHeight', 3.5)
            .attr('orient', 'auto')
            .append('svg:path')
            .attr('d', 'M0,-5L10,0L0,5');

        self.svgGraph = svg.append("g")
            .classed(self.consts.graphClass, true);

        // displayed when dragging between nodes
        self.dragLine = self.svgGraph.append('svg:path')
            .attr('class', 'link dragline hidden')
            .attr('d', 'M0,0L0,0')
            .style('marker-end', 'url(#mark-end-arrow)');

        // svg nodes and edges
        self.paths = self.svgGraph.append("g").selectAll("g");
        self.circles = self.svgGraph.append("g").selectAll("g");

        self.forceDragged = null;

        self.dragHandler = d3.behavior.drag().origin(function (d) {
            return {x: d.x, y: d.y};
        }).on("drag", function (d) {
            self.state.justDragged = true;
            if (self.state.shiftNodeDrag) {
                var position = d3.mouse(self.svgGraph.node());
                self.dragLine.attr('d', 'M' + d.x + ',' + d.y + 'L' + position[0] + ',' + position[1]);
            } else {
                d.x += d3.event.dx;
                d.y += d3.event.dy;
                if (!self.forceDragged) {
                    self.forceNodes.forEach(function (i) {
                        if (i.id == d.id) {
                            self.forceDragged = i;
                        }
                    });
                }
                self.forceDragged.px += d.x - self.forceDragged.x;
                self.forceDragged.py += d.y - self.forceDragged.y;
                self.forceDragged.x = d.x;
                self.forceDragged.y = d.y;

                var average = [d3.mean(self.forceNodes, function (d) {
                    return d.x;
                }) * 2, d3.mean(self.forceNodes, function (d) {
                    return d.y;
                }) * 2];

                self.forceHandler.size(average);

                self.updateGraph();
            }
        }).on("dragend", function () {
            if (self.state.selectedNode) {
                sidebar.update(self.state.selectedNode.id);
            }
            self.forceDragged = null;
            self.trySave();
        });

        self.zoomHandler = d3.behavior.zoom().on("zoom", function () {
            if (d3.event.sourceEvent.shiftKey) {
                return false;
            } else {
                d3.select("." + self.consts.graphClass)
                    .attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
                return true;
            }
        }).on("zoomstart", function () {
            var ael = d3.select("#" + self.consts.activeEditId).node();
            if (ael) {
                ael.blur();
            }
            if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move");
        }).on("zoomend", function () {
            d3.select('body').style("cursor", "auto");
        });

        self.forceNodes = [];
        self.forceEdges = [];
        self.forceSaveLastTime = Date.now();

        self.forceHandler = d3.layout.force()
            .friction(0.8).charge(-4000).gravity(0.2).linkDistance(function (d) {
                return Math.sqrt(Math.min(d.source.weight, d.target.weight) * 2 || 2) * 100
            })
            .on("tick", function (e) {
                self.forceNodes.forEach(function (i) {
                    self.nodes[i.id].x = i.x;
                    self.nodes[i.id].y = i.y;
                });
                var now = Date.now();
                if (now > self.forceSaveLastTime + 4096) {
                    self.trySave();
                    self.forceSaveLastTime = now;
                }
                self.updateGraph();
            });

        // listen for key events
        d3.select(document).on("keydown", function () {
            if (self.state.lockKeyEvent || self.state.lastKeyDown !== -1) return;

            self.state.lastKeyDown = d3.event.keyCode;

            switch (d3.event.keyCode) {
            case self.consts.BACKSPACE_KEY:
            case self.consts.DELETE_KEY:
                d3.event.preventDefault();
                var oldNode = self.state.selectedNode;
                if (self.state.selectedEdge) {
                    self.delSelectedEdge();
                } else {
                    self.delSelectedNode();
                }
                if (oldNode) {
                    if (graph.nodes.length > 0) {
                        var index = oldNode.id;
                        do {
                            index = (index + 1) % graph.nodes.length;
                        } while (index != oldNode.id && graph.nodes[index] == undefined);
                        graph.setSelectedNode(graph.nodes[index]);
                        graph.centerSelectedNode();
                        sidebar.update(index);
                    } else {
                        graph.setSelectedNode(graph.nodes[graph.nodes.length]);
                        sidebar.update(graph.nodes.length);
                    }
                }
            }
        }).on("keyup", function () {
            self.state.lastKeyDown = -1;
        });

        // listen for mouse events
        svg.on("mousedown", function (d) {
            self.state.graphMouseDown = true;
        }).on("mouseup", function (d) {
            if (self.state.graphMouseDown && d3.event.shiftKey) {
                // clicked not dragged from svg
                var xycoords = d3.mouse(self.svgGraph.node());
                var pre = self.state.selectedNode;
                self.createNodeAndSelect({tax_id: "", gene_id: "", name: "", info: "", title: "", x: xycoords[0], y: xycoords[1]});
                var d = self.state.selectedNode;
                // make title of text immediently editable
                var circles = self.circles.filter(function (dval) {
                    return dval.id === d.id;
                });
                sidebar.editNodeInfo(self.nodes[d.id], function (node) {
                    if (!node) {
                        graph.delSelectedNode();
                        graph.setSelectedNode(pre);
                        sidebar.update(!pre ? graph.nodes.length : pre.id);
                    } else {
                        circles.select('text').remove();
                        graph.setCircleTitle(circles, node.title);
                        sidebar.update(d.id);
                        graph.trySave();
                    }
                });
            } else if (self.state.shiftNodeDrag) {
                // dragged from node
                self.state.shiftNodeDrag = false;
                self.dragLine.classed("hidden", true);
            }
            self.state.graphMouseDown = false;
        }).on("mouseleave", function (d) {
            self.state.shiftNodeDrag = false;
            self.dragLine.classed("hidden", true);
            self.state.graphMouseDown = false;
        });

        // listen for dragging
        svg.call(self.zoomHandler).on("dblclick.zoom", null);

        // listen for resize
        var resizeOld = window.onresize || function () {};
        window.onresize = function () {
            var docEl = document.documentElement, bodyEl = document.body;
            var x = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth;
            var y = window.innerHeight || docEl.clientHeight || bodyEl.clientHeight;
            svg.attr("width", x).attr("height", y);
            resizeOld.apply(this, arguments);
        };

        // update
        self.updateGraph();

        var average = [d3.mean(self.forceNodes, function (d) {
            return d.x;
        }) * 2, d3.mean(self.forceNodes, function (d) {
            return d.y;
        }) * 2];

        self.forceHandler.size(average);

        for (var i in nodes) {
            if (nodes[i] != undefined) {
                self.setSelectedNode(nodes[i]);
                break;
            }
        }
    };

    GraphCreator.prototype.consts = {
        selectedClass: "selected",
        connectClass: "connect-node",
        circleGClass: "conceptG",
        graphClass: "graph",
        activeEditId: "active-editing",
        BACKSPACE_KEY: 8,
        DELETE_KEY: 46,
        ENTER_KEY: 13,
        nodeRadius: 50
    };

    GraphCreator.prototype.deleteGraph = function () {
        this.nodes = [];
        this.edges = [];
        sidebar.update(0);
        this.updateGraph();
        this.trySave();
    };

    /* insert svg line breaks: taken from http://stackoverflow.com/questions/13241475/how-do-i-include-newlines-in-labels-in-d3-charts */
    GraphCreator.prototype.setCircleTitle = function (gEl, title) {
        var words = title.split(/\s+/g);
        var el = gEl.append("text").attr("text-anchor", "middle");
        el.attr("dx", 0);
        for (var i in words) {
            el.append('tspan').text(words[i]).attr('x', 0).attr('dy', function () {
                return i == 0 ? 7 * (2 - words.length) : 14;
            });
        }
    };

    GraphCreator.prototype.createNodeAndSelect = function (nodeData) {
        while (this.nodes[this.nextId] != undefined) {
            this.nextId += 1;
        }
        this.nodes[this.nextId] = {
            "id": this.nextId,
            "type": nodeData.type,
            "tax_id": nodeData.tax_id,
            "gene_id": nodeData.gene_id,
            "name": nodeData.name,
            "info": nodeData.info,
            "title": nodeData.title,
            "x": nodeData.x,
            "y": nodeData.y
        };
        this.updateGraph();
        this.trySave();
        this.setSelectedNode(this.nodes[this.nextId]);
    };

    GraphCreator.prototype.centerSelectedNode = function (duration, callback) {
        duration = duration || 500;
        callback = callback || function () {};
        var thisGraph = this;
        if (thisGraph.state.selectedNode) {
            var svgWrapper = $("#body");
            var x = svgWrapper.width() / 2 - thisGraph.state.selectedNode.x;
            var y = svgWrapper.height() / 2 - thisGraph.state.selectedNode.y;
            d3.select("." + thisGraph.consts.graphClass)
                .transition()
                .duration(duration)
                .attr("transform", function () {
                    return "translate(" + x + ", " + y + ") scale(" + 1 + ")";
                })
                .each("end", function () {
                    callback(thisGraph.state.selectedNode);
                });
            this.zoomHandler.translate([x, y]).scale(1);
        }
    };

    GraphCreator.prototype.setSelectedEdge = function (edgeData) {
        var thisGraph = this, oldEdge = thisGraph.state.selectedEdge, className = thisGraph.consts.selectedClass;
        if (oldEdge !== edgeData) {
            if (oldEdge) {
                thisGraph.paths.filter(function (ed) {
                    return ed === oldEdge;
                }).classed(className, false);
            }
            thisGraph.state.selectedEdge = edgeData;
            if (edgeData) {
                thisGraph.paths.filter(function (ed) {
                    return ed === edgeData;
                }).classed(className, true);
            }
        }
    };

    GraphCreator.prototype.setSelectedNode = function (nodeData) {
        var thisGraph = this, oldNode = thisGraph.state.selectedNode, className = thisGraph.consts.selectedClass;
        if (oldNode !== nodeData) {
            if (oldNode) {
                thisGraph.circles.filter(function (ed) {
                    return ed === oldNode;
                }).classed(className, false);
            }
            thisGraph.state.selectedNode = nodeData;
            if (nodeData) {
                thisGraph.circles.filter(function (ed) {
                    return ed === nodeData;
                }).classed(className, true);
            }
        }
    };

    GraphCreator.prototype.delSelectedEdge = function () {
        var selectedEdge = this.state.selectedEdge;
        if (selectedEdge) {
            this.edges.splice(this.edges.indexOf(selectedEdge), 1);
            this.state.selectedEdge = null;
            this.updateGraph();
            this.trySave();
        }
    };

    GraphCreator.prototype.delSelectedNode = function () {
        var selectedNode = this.state.selectedNode;
        if (selectedNode) {
            this.nodes[selectedNode.id] = undefined;
            this.edges = this.edges.filter(function (l) {
                return l.source != selectedNode.id && l.target != selectedNode.id;
            });
            this.state.selectedNode = null;
            if (selectedNode.id < this.nextId) {
                this.nextId = selectedNode.id;
            }
            this.updateGraph();
            this.trySave();
        }
    };

    GraphCreator.prototype.pathMouseDown = function (d3path, d) {
        var thisGraph = this, state = thisGraph.state;
        d3.event.stopPropagation();
        thisGraph.setSelectedEdge(d);
    };

    // mousedown on node
    GraphCreator.prototype.circleMouseDown = function (d3node, d) {
        var thisGraph = this, state = thisGraph.state;
        d3.event.stopPropagation();
        state.mouseDownNode = d;
        if (d3.event.shiftKey) {
            state.shiftNodeDrag = d3.event.shiftKey;
            // reposition dragged directed edge
            thisGraph.dragLine.classed('hidden', false).attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
        }
    };

    // mouseup on nodes
    GraphCreator.prototype.circleMouseUp = function (d3node, d) {
        var thisGraph = this,
            state = thisGraph.state,
            consts = thisGraph.consts;
        // reset the states
        state.shiftNodeDrag = false;
        d3node.classed(consts.connectClass, false);

        var mouseDownNode = state.currentLinkSource || state.mouseDownNode;

        if (state.currentLinkSource) {
            state.currentLinkSource = null;
        }

        if (!mouseDownNode) return;

        thisGraph.dragLine.classed("hidden", true);

        $('#side-link-to').removeClass('active');

        if ($('#side-path-finder').hasClass('active')) {
            $('#side-path-finder').removeClass('active');
            graph.state.lockKeyEvent = true;
            var nodeDict, paths;
            $('#modal-path-finder').openModal({
                dismissible: true,
                in_duration: 0,
                out_duration: 0,
                ready: function () {
                    nodeDict = [], paths = [];
                    $('#path-finder-main').hide();
                    $('#path-finder-result').empty().hide();
                    $('#path-finder-progress').show();
                    $('#path-finder-k').val('5');
                    $('#path-finder-maxlen').val('10');
                    $('#path-finder-start').click(function () {
                        var k = Number($('#path-finder-k').val());
                        var maxlen = Number($('#path-finder-maxlen').val());
                        $('#path-finder-main').show();
                        sidebar.pathFinder({
                            s: mouseDownNode['gene_id'],
                            t: d['gene_id'],
                            k: isNaN(k) ? 5 : k,
                            maxlen: isNaN(maxlen) ? 10 : maxlen
                        }, function (nodes, p) {
                            paths = p;
                            nodes.forEach(function (i) {
                                nodeDict[i['gene_id']] = i;
                            });
                            var root = $('#path-finder-result').empty().hide();
                            for (var i in paths) {
                                var path = paths[i];
                                var nodeList = $('<div class="collection"></div>');
                                path.forEach(function (id) {
                                    var i = nodeDict[id];
                                    nodeList.append($('<div class="collection-item grey lighten-5 truncate"></div>')
                                        .append($('<span></span>').text('Tax ID: ' + i['tax_id'] + '\t' + 'Gene ID: ' + i['gene_id']))
                                        .append($('<br>'))
                                        .append($('<span></span>').text(i['name'] + ' '))
                                        .append($('<span class="grey-text"></span>').text(i['info'])));
                                });
                                root.append($('<li></li>').append($('<div class="collapsible-header truncate"></div>')
                                    .append($('<span></span>').text('Path ' + (1 + +i) + ' ' + path.map(function (i) {
                                            return nodeDict[i].name;
                                        }).reduce(function (i, j) {
                                            return i + ' -> ' + j;
                                        })))
                                    .append($('<i class="material-icons tiny right">clear</i>').click((function (i) {
                                        return function () {
                                            $(this).parent().parent().remove();
                                            paths[i] = undefined;
                                        };
                                    })(i))))
                                    .append($('<div class="collapsible-body"></div>').append(nodeList)))
                                    .show();
                            }
                            $('#path-finder-progress').hide();
                        });
                    });
                },
                complete: function () {
                    graph.state.lockKeyEvent = false;
                    paths.forEach(function (path) {
                        if (path) {
                            var previous = mouseDownNode;
                            for (var i = 1; i < path.length; ++i) {
                                var node = nodeDict[path[i]];
                                if (i + 1 < path.length) {
                                    graph.createNodeAndSelect({
                                        tax_id: node.tax_id,
                                        gene_id: node.gene_id,
                                        name: node.name,
                                        info: node.info,
                                        title: node.name,
                                        x: (mouseDownNode.x + d.x + 25 * Math.random()) / 2,
                                        y: (mouseDownNode.y + d.y + 25 * Math.random()) / 2
                                    });
                                } else {
                                    graph.setSelectedNode(graph.nodes[d.id]);
                                }
                                var newEdge = {source: previous.id, target: graph.state.selectedNode.id};
                                thisGraph.edges.push(newEdge);
                                thisGraph.updateGraph();
                                previous = graph.state.selectedNode;
                            }
                        }
                    });
                    sidebar.update(mouseDownNode.id);
                    thisGraph.setSelectedNode(graph.nodes[mouseDownNode.id]);
                    thisGraph.trySave();
                }
            });
            return;
        }


        if (mouseDownNode !== d) {
            // we're in a different node: create new edge for mousedown edge and add to graph
            var newEdge = {source: mouseDownNode.id, target: d.id};
            var filtRes = thisGraph.paths.filter(function (d) {
                if (d.source === newEdge.target && d.target === newEdge.source) {
{'fraction_non_alphanumeric': 0.13522687094873306, 'fraction_numerical': 0.007495580436063642, 'mean_word_length': 1.2552625983414842, 'pattern_counts': {'":': 18, '<': 51, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 78, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5409002', 'n_tokens_mistral': 11577, 'n_tokens_neox': 10952, 'n_words': 2018}