Source: network.js

/**
 * @typedef {number} Motif
 **/

/**
 *
 * @enum {Motif}
 */
var MotifEnum = Object.freeze({"CLIQUE":1, "FAN":2, "CONNECTOR":3});
var cliquePolygonPoints = "0,-150 125,-200 200,-125 150,0 200,125 125,200 0,150 -125,200 -200,125 -150,0 -200,-125 -125,-200";
var connectorPolygonPoints = "0,-200 200,0 0,200 -200,0";

/**
 * Represents the network.
 * @constructor
 * @param {Object} graph - Graph object containing the nodes and links of the graph as properties. Nodes and
 * links are arrays of node/link objects.
 * @param {Object} nxGraph - Data structure of the graph as needed for the networkx library to detect cliques.
 * @param {d3.selection} svg - D3.selection containing the svg HTML-Element to draw the graph.
 * @param {number} width - Width of svg.
 * @param {number} height - Height of svg.
 * @param {Function} labeling - Function returning the label of a node object.
 *
 */
function Network(graph, nxGraph, svg, width, height, labeling){
    this.nxGraph = nxGraph;
    this.labeling = labeling; //Name of the node-attribute that should be displayed
    var global_motif_index = 0;
    var motifs = {};
    var motif_properties = {};
    var detectedNodes = [];
    var cliques;
    var fans;
    var connector;

    var graph = graph;
    var svg = svg;
    var svgW = width;
    var svgH = height;

    var prevScale = 1;
    var prevTranslate = [0, 0];

    var transformManual = false;
    var appliedTransform;

    var colorAttr;

    var simulation;
    var color;

    var link;
    var node;
    var labels;
    var legend = [];

    var zoom;

    /**
     * Function to initialize the d3 force simulation and to create html-elements of the nodes, links
     * and labels.
     */
    this.setUpNetwork = function setUpNetwork() {
        zoom = d3.zoom();
        svg.call(zoom.on("zoom", zoomEvent));

        color = d3.scaleOrdinal(d3.schemeCategory20);

        simulation = d3.forceSimulation()
            .force("link", d3.forceLink().id(function(d) { return d.id; }))
            .force("charge", d3.forceManyBody())
            .force("center", d3.forceCenter(svgW / 2, svgH / 2))
            .force("collide", d3.forceCollide().radius( function(d) {
                if(d.radius){
                    return d.radius;
                } else {
                    return 5;
                }
            })
                .iterations(3)
                .strength(0.1));

        link = svg
            .append("g")
                .attr("class", "links")
                .selectAll("line")
                .data(graph.links)
                .enter()
                .append("line")
                    .attr("source_id", function(d) {
                        return "vertex_" + d.source;
                    })
                    .attr("target_id", function(d) {
                        return "vertex_" + d.target;
                    })
                    .attr("stroke-width", function(d) {
                        return Math.sqrt(d.value);
                    })
                    .attr("stroke", "#999");

        var g_nodes = svg
            .append("g")
                .attr("class", "nodes")
                .selectAll("g")
                .data(graph.nodes)
                .enter();

        var g_vertex = g_nodes
            .append("circle")
                .attr("class", "vertex")
                .attr("id", function(d) {
                    return "vertex_" + d.id;
                })
                .attr("r", 5)
                .attr("fill", function(d) {
                    return color(d[colorAttr]);
                })
                .attr("stroke", "#fff")
                .call(d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    .on("end", dragended))
                .on("mouseover", this.showLabel)
                .on("mouseout", this.hideLabel);

        labels = svg
            .append("g")
                .attr("class", "labels")
                .selectAll("text")
                .data(graph.nodes)
                .enter();

        labels
            .append("text")
                .text(function(d) {
                    return this.labeling(d);
                }.bind(this))
                .attr("pointer-events", "none")
                .attr("visibility", "hidden")
                .attr("dx", "-30px")
                .attr("id", function(d) {
                    return "label_" + d.id;
                });

        g_vertex
            .append("title")
                .text(function(d) {
                    return d.id;
                });

        node = d3.selectAll("circle");
        labels = d3.selectAll("text");

        simulation
            .nodes(graph.nodes)
            .on("tick", ticked)
            .on("end", calculateScaleExtent);

        simulation.force("link")
            .links(graph.links);

    };

    /**
     * Sets the visibility of the given label to visible.
     * @param {Object} d - Label object.
     */
    this.showLabel = function showLabel(d) {
        //this is the current dom element
        var id = this.getAttribute("id").substring(7);
        d3.select("#label_" + id).attr("visibility", "visible");
    };

    /**
     * Sets the visibility of the given label to hidden.
     * @param {Object} d - Label object.
     */
    this.hideLabel = function hideLabel(d) {
        var id = this.getAttribute("id").substring(7);
        d3.select("#label_" + id).attr("visibility", "hidden");
    };

    /**
     * Sets the attribute according to which the nodes and glyphs will be colored and recolors
     * the nodes and glyphs.
     * Generates the color legend.
     * @param {string} attribute - Name of the node attribute to color the nodes and glyphs.
     */
    this.setColorAttribute = function setColorAttribute(attribute) {
        legend = [];
        colorAttr = attribute;

        d3.selectAll("circle").attr("fill", function (d) {
            if(!legend[d[colorAttr]]) {
                legend[d[colorAttr]] = color(d[colorAttr]);
            }
            return color(d[colorAttr]);
        });

        d3.selectAll(".nodes g").select(function (d) {
            var properties = calculateGlyphProperties(d.motif_id);

            motif_properties[d.motif_id] = properties;
            motif_properties[d.motif_id]["type"] = d.type;

            if(this.childNodes.length > 1) {
                this.childNodes[1].setAttribute("fill", properties["color"]);
            } else {
                this.childNodes[0].setAttribute("style", "fill:" + properties["color"] + ";stroke:white;stroke-width:10");
            }
        });
    };

    /**
     * Stops the d3 simulation and d3 zoom listener of this network.
     */
    this.clearNetwork = function clearNetwork() {
        simulation.stop();
        svg.call(zoom.on("zoom", null));
    };

    /**
     * Returns the legend according to the current coloring.
     * @returns {Array} Array where the indices represent the labels and the values the corresponding
     * color.
     */
    this.getLegend = function getLegend() {
        return legend;
    };

    /**
     * Function that detects motifs and replaces them by glyphs.
     * @param {Motif} motifType - Type of motif to detect and replace. Either "CLIQUE", "FAN" or
     * "CONNECTOR".
     * @param {number} min - Minimum number of nodes that may be replaced by a glyph of type motifType.
     * @param {number} max - Maximum number of nodes that may be replaced by a glyph of type motifType.
     */
    this.collapseMotifs = function collapseMotifs(motifType, min, max) {
        var tmp = this.initMotifDataStructure(motifType, min, max);
        var collapse = tmp[0];
        var motifs_tmp = tmp[1];

        if(collapse) {
            if(motifs_tmp) {
                if (Object.keys(motifs_tmp.motifs).length > 0) {

                    Object.keys(motifs_tmp.motifs).forEach(function (key) {
                        motifs[global_motif_index] = {};
                        motifs[global_motif_index]["oldNodes"] = [];
                        motifs[global_motif_index]["motifNode"] = [];
                        motifs[global_motif_index]["motifLinks"] = [];
                        motifs[global_motif_index]["adjacentLinks"] = [];
                        motifs[global_motif_index]["type"] = motifType;

                        replaceMotif(motifType, motifs_tmp.motifs[key], global_motif_index);

                        motif_properties[global_motif_index] = calculateGlyphProperties(global_motif_index);
                        motif_properties[global_motif_index]["type"] = motifType;

                        motifs[global_motif_index]["motifNode"][0]["x"] = motif_properties[global_motif_index]["meanX"];
                        motifs[global_motif_index]["motifNode"][0]["y"] = motif_properties[global_motif_index]["meanY"];
                        motifs[global_motif_index]["motifNode"][0]["radius"] = motif_properties[global_motif_index]["radius"];

                        global_motif_index++;
                    });

                    var data = [];
                    Object.keys(motifs_tmp["motifs"]).forEach(function (value) {
                        var d = motifs[value]["motifNode"][0];
                        if (d) {
                            data.push(d);
                        }
                    });

                    appendGlyphs(motifType, data, motif_properties);

                } else {
                    console.log("no motifs to collapse");
                }
            }
        }
    };

    /**
     * Inits the data structure containing the information of the motifs and removes previously
     * detected glyphs if needed.
     * @param {Motif} motifType - Type of motif to detect and replace. Either "CLIQUE", "FAN" or
     * "CONNECTOR".
     * @param {number} min - Minimum number of nodes that may be replaced by a glyph of type motifType.
     * @param {number} max - Maximum number of nodes that may be replaced by a glyph of type motifType.
     * @returns {Array} Array containing a boolean which is true if the given detection query
     * is different to the previus query and false otherwise and the suitable data structure to
     * store motifs according to motifType.
     */
    this.initMotifDataStructure = function initMotifDataStructure(motifType, min, max){
        var collapse = true;
        var tmp;
        var addMotif = true;
        var index = 0;

        switch(motifType) {
            case MotifEnum.CLIQUE:
                if(cliques) {
                    Object.keys(cliques.motifs).forEach(function (key) {
                        removeGlyph(motifs[key]["motifNode"][0].id);
                        delete motifs[key];
                        delete motif_properties[key];
                    });

                    if (min === -1 || max === -1) {
                        cliques = undefined;
                        collapse = false;
                    }

                } else if (min === -1 || max === -1) {
                    collapse = false;
                }

                if(collapse) {
                    cliques = {};
                    cliques["min"] = min;
                    cliques["max"] = max;
                    cliques["index"] = motifs.length;
                    cliques["id"] = global_motif_index;
                    cliques["motifs"] = {};

                    addMotif = true;
                    index = 0;
                    tmp = detectCliques(this, min, max);

                    tmp.forEach(function (value) {
                        value.forEach(function (value2) {
                            if(detectedNodes.indexOf(value2) >= 0) {
                                addMotif = false;
                            }
                        });

                        if(addMotif) {
                            cliques["motifs"][global_motif_index + index] = value;
                            detectedNodes.push.apply(detectedNodes, value);
                            index++;
                        }

                        addMotif = true;
                    });
                }
                tmp = cliques;
                break;
            case MotifEnum.FAN:
                if(fans) {
                    Object.keys(fans.motifs).forEach(function (key) {
                        removeGlyph(motifs[key]["motifNode"][0].id);
                        delete motifs[key];
                        delete motif_properties[key];
                    });

                    if( min === -1 || max === -1 ) {
                        fans = undefined;
                        collapse = false;
                    }

                } else if(min === -1 || max === -1) {
                    collapse = false;
                }

                if(collapse){
                    fans = {};
                    fans["index"] = motifs.length;
                    fans["id"] = global_motif_index;
                    fans["motifs"] = {};

                    index = 0;
                    addMotif = true;
                    tmp = detectFans(this);
                    tmp.forEach(function (value) {
                        value.forEach(function (value2) {
                            if(detectedNodes.indexOf(value2) >= 0) {
                                addMotif = false;
                            }
                        });

                        if(addMotif) {
                            fans["motifs"][global_motif_index + index] = value;
                            detectedNodes.push.apply(detectedNodes, value);
                            index++;
                        }

                        addMotif = true;

                    });
                    tmp = fans;
                }
                break;
            case MotifEnum.CONNECTOR:
                if(connector) {
                    Object.keys(connector.motifs).forEach(function (key) {
                        removeGlyph(motifs[key]["motifNode"][0].id);
                        delete motifs[key];
                        delete motif_properties[key];
                    });

                    if (min === -1 || max === -1) {
                        connector = undefined;
                        collapse = false;
                    }

                } else if( min === -1 || max === -1){
                    collapse = false;
                }

                if(collapse){
                    connector = {};
                    connector["min"] = min;
                    connector["max"] = max;
                    connector["index"] = motifs.length?motifs.length:0;
                    connector["id"] = global_motif_index;
                    connector["motifs"] = {};

                    index = 0;
                    addMotif = true;
                    tmp = detectConnectors(this, min, max);
                    tmp.forEach(function (value) {
                        value.forEach(function (value2) {
                            if(detectedNodes.indexOf(value2) >= 0) {
                                addMotif = false;
                            }
                        });

                        if(addMotif) {
                            connector["motifs"][global_motif_index + index] = value;
                            detectedNodes.push.apply(detectedNodes, value);
                            index++;
                        }

                        addMotif = true;
                    });

                    tmp = connector;
                }
                break;
            default:
                break;
        }

        return [collapse, tmp];
    };

    /**
     * Replaces a motif by its corresponding glyph. Hides the nodes of the motifs and all links
     * between these nodes and adds a new node which is the glyph and modifies incoming links of
     * the nodes of the motif such that they are connected with the glyph.
     * @param {Motif} motifType - Type of motif to detect and replace. Either "CLIQUE", "FAN" or
     * "CONNECTOR".
     * @param {Array} motif - Array containing the ids of the nodes of the motif.
     * @param {number} motif_id - Id of the motif.
     */
    function replaceMotif(motifType, motif, motif_id){
        //find graph data of clique nodes
        graph.nodes.forEach(function (value) {
            if (motif.indexOf(value.id.toString()) >= 0) {
                value["motif_id"] = motif_id;
                motifs[motif_id]["oldNodes"].push(value);
            }
        });

        //find graph data of clique links
        graph.links.forEach(function (value) {
            var sid = value.source.id.toString();
            var tid = value.target.id.toString();

            if (motif.indexOf(sid) >= 0 && motif.indexOf(tid) >= 0) {
                motifs[motif_id]["motifLinks"].push(value);
            } else if (motif.indexOf(sid) >= 0 || motif.indexOf(tid) >= 0) {
                motifs[motif_id]["adjacentLinks"].push(value);
            }
        });

        //hide links between nodes of the motif
        d3.selectAll("line").select(function (d) {
            if ((motif.indexOf(d.source.id.toString()) >= 0) && (motif.indexOf(d.target.id.toString()) >= 0)) {
                this.setAttribute("class", "collapse");
                return this;
            }
        }).attr("motif_id", "motif_" + motif_id);

        //hide Nodes
        motifs[motif_id]["oldNodes"].forEach(function (value) {
            var nodeId = value["id"];

            d3.select("[id='" + 'vertex_' + nodeId + "']")
                .attr("class", function () {
                    return this.getAttribute("class") + " collapse";
                })
                .attr("motif_id", "motif_" + motif_id);

        });

        // add new node
        var newNode = jQuery.extend(true, {}, motifs[motif_id]["oldNodes"][0]);
        newNode["motif_id"] = motif_id;
        newNode["radius"] = 10;
        newNode["type"] = motifType;

        var idx = graph.nodes.push(newNode) - 1;
        graph.nodes[idx]["id"] = idx + "";
        motifs[motif_id]["motifNode"].push(newNode);

        // Modify links
        motifs[motif_id]["adjacentLinks"].forEach(function (value) {
            var sid = value.source.id.toString();
            var tid = value.target.id.toString();
            var mtf_idx;
            var tmp;

            if (!value["motif"] || value["motif"].length === 0) {
                value["motif"] = [];
                value["motif"][0] = newNode;
                mtf_idx = 0;
            } else {
                value["motif"][1] = newNode;
                mtf_idx = 1;
            }

            if (motif.indexOf(sid) >= 0) {
                tmp = value.source;
                value.source = value.motif[mtf_idx];
                value.motif[mtf_idx] = tmp;
            } else if (motif.indexOf(tid) >= 0) {
                tmp = value.target;
                value.target = value.motif[mtf_idx];
                value.motif[mtf_idx] = tmp;
            }

        });
        motifs[motif_id].tStatus = "active";
    }

    /**
     * Scales and positions the network such that all components are visible within the svg.
     */
    function fit() {
        if(appliedTransform) {
            scale = appliedTransform.k;
            translate = [appliedTransform.x, appliedTransform.y];
        } else {
            var bbox = svg.node().getBBox();

            var bboxW = bbox.width / (prevScale);
            var bboxH = bbox.height / (prevScale);
            var bboxX = (bbox.x - prevTranslate[0]) / prevScale;
            var bboxY = (bbox.y - prevTranslate[1]) / prevScale;

            var midX = bboxX + bboxW / 2;
            var midY = bboxY + bboxH / 2;

            if ((bboxW === 0) || (bboxH === 0)) {
                // nothing to fit
                return;
            }

            var scale = Math.sqrt(Math.min(svgW / bboxW, svgH / bboxH)) * 0.8;
            scale = scale * scale;

            var translate = [svgW / 2 - scale * midX, svgH / 2 - scale * midY];
            zoom.scaleExtent([(scale) * 0.5, 5]);

            prevScale = scale;
            prevTranslate = translate;
        }

        transformManual = true;
        svg.call(zoom.transform, d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale));
    }

    /**
     * Function that handles the ticks of the d3 force simulation. Updates the positions and
     * scales of the html-elements.
     */
    function ticked() {
        link
            .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; });

        node
            .attr("cx", function(d) { return d.x; })
            .attr("cy", function(d) { return d.y; });

        node.selectAll("polygon").select(function () {
            var parent = d3.select(this.parentNode)._groups[0][0];
            var str = this.getAttribute("transform");
            str = str.split("(")[2];
            str = str.split(")")[0];

            this.setAttribute("transform", "translate(" + parent.getAttribute("cx") + "," + parent.getAttribute("cy") + ")scale(" + str + ")");

        });

        node.selectAll("g path").select(function (d) {
            var parent = d3.select(this.parentNode)._groups[0][0];
            var str = this.getAttribute("transform");
            str = str.split("(")[2];
            str = str.split(")")[0];

            this.setAttribute("transform", "translate(" + parent.getAttribute("cx") + "," + parent.getAttribute("cy") + ")scale(" + str + ")");
        });

        labels
            .attr("x", function(d) { return d.x; })
            .attr("y", function(d) { return d.y; });

        fit();
    }

    /**
     * Event handler for start of drag action.
     * @param {Object} d - Node object that is dragged.
     */
    function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.15).restart();
        d.fx = d.x;
        d.fy = d.y;
    }

    /**
     * Event handler for drag action.
     * @param {Object} d - Node object that is dragged.
     */
    function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
    }

    /**
     * Event handler for end of drag action.
     * @param {Object} d - Node object that was dragged.
     */
    function dragended(d) {
        if (!d3.event.active) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
    }

    /**
     * Event handler for zoom event.
     * Applies zoom event to links, nodes and labels.
     */
    function zoomEvent() {
        if (!transformManual) {
            appliedTransform = d3.event.transform;
        }
        transformManual = false;

        svg.selectAll("line").attr("transform", d3.event.transform);
        svg.selectAll(".vertex").attr("transform", d3.event.transform);
        svg.selectAll(".labels").attr("transform", d3.event.transform);
    }

    this.toggleGlyph = function toggleGlyph(motif_id) {
        if (motifs[motif_id].tStatus == undefined || motifs[motif_id].tStatus == "active") {
            enlargeGlyph(motifs[motif_id].motifNode[0].id);
        } else {
            showGlyph(motifs[motif_id].motifNode[0].id);
        }
    };

    function showGlyph(index) {
        var glyph = graph.nodes[index];
        var motif_id = glyph["motif_id"];
        var motif = motifs[motif_id];

        //hide links between nodes of the motif
        d3.selectAll("line[motif_id='" + 'motif_' + motif_id + "']").attr("class", function () {
            return this.getAttribute("class") + " collapse";
        });

        //hide Nodes
        d3.selectAll("circle[motif_id='" + 'motif_' + motif_id + "']").attr("class", function () {
            return this.getAttribute("class") + " collapse";
        });

        // Modify links
        motif.adjacentLinks.forEach(function (value) {
            var sid = value.source.id.toString();
            var tid = value.target.id.toString();
            var mtf_idx;
            var tmp;

            mtf_idx = value.motif.indexOf(motif.motifNode[0]);

            if(value.source.motif_id === motif.motifNode[0].motif_id) {
                tmp = value.source;
                value.source = value.motif[mtf_idx];
                value.motif[mtf_idx] = tmp;
            } else if(value.target.motif_id === motif.motifNode[0].motif_id) {
                tmp = value.target;
                value.target = value.motif[mtf_idx];
                value.motif[mtf_idx] = tmp;
            }
        });

        //show glyph
        d3.selectAll("g[motif_id='" + 'motif_' + motif.motifNode[0].motif_id + "']").attr("class", function () {
            return "vertex";
        });

        motif.tStatus = "active";
        restartSimulation(0.1);
    }

    /**
     * Enlarges (hides) a glyph such that the underlying nodes and links are visible.
     * @param {number} index - index of the glyph node inside the graph data structure.
     */
    function enlargeGlyph(index){
        var glyph = graph.nodes[index];
        var gX = glyph.x;
        var gY = glyph.y;
        var motif_id = glyph["motif_id"];
        var tmp;

        console.log("enlarge glyph: " + index);
        simulation.stop();

        //modify Nodes
        motifs[motif_id]["oldNodes"].forEach(function (value) {
            value.x = value.x - (value.x - gX);
            value.y = value.y - (value.y - gY);
        });

        //modify adjacent Links
        motifs[motif_id]["adjacentLinks"].forEach(function (value) {
            var sid = value.source.id.toString();
            var tid = value.target.id.toString();
            var mtf_idx = -1;

            value.motif.forEach(function (value2, idx) {
                if (((value2.motif_id === value.source.motif_id) && (sid === index.toString())) ||
                    ((value2.motif_id === value.target.motif_id) && (tid === index.toString()))) {
                    mtf_idx = idx;
                }
            });

            if(mtf_idx >= 0) {
                if (sid && (sid === index.toString())) {
                    tmp = value.source;
                    value.source = value.motif[mtf_idx];
                    value.motif[mtf_idx] = tmp;

                } else if (tid && (tid === index.toString())) {
                    tmp = value.target;
                    value.target = value.motif[mtf_idx];
                    value.motif[mtf_idx] = tmp;
                }
            } else {
                console.log("something went wrong!");
            }
        });

        motifs[motif_id].tStatus = "passive";

        //hide glyph html
        d3.selectAll(".nodes g[motif_id='" + 'motif_' + motif_id + "']").select(function (d) {
            this.setAttribute("class", this.getAttribute("class") + " collapse");
            return this;
        });

        //show Nodes and motifLinks
        d3.selectAll(".nodes circle[motif_id='" + 'motif_' + motif_id + "']").select(function () {
            this.setAttribute("class", "vertex");
            this.setAttribute("stroke", "black");
            return this;
        });

        d3.selectAll(".links [motif_id='" + 'motif_' + motif_id + "']").select(function () {
            this.setAttribute("class", "");
            this.setAttribute("stroke", "black");
            return this;
        });
        restartSimulation(0.1);
    }

    /**
     * Removes a glyph.
     * @param {number} index - index of the glyph node inside the graph data structure.
     */
    function removeGlyph(index) {
        var glyph = graph.nodes[index];
        var motif_id = glyph["motif_id"];

        enlargeGlyph(index);

        motifs[motif_id]["oldNodes"].forEach(function (value) {
            detectedNodes.splice(detectedNodes.indexOf(value.id,1));
        });

        motifs[motif_id]["adjacentLinks"].forEach(function (value) {
            var tmp = [];

            value.motif.forEach(function (value2) {
                if (value2.id.toString() !== index.toString()) {
                    tmp.push(value2);
                }
            });

            value.motif = tmp;
        });

        //remove glyph HTMLElement
        d3.selectAll(".nodes g[motif_id='" + 'motif_' + motif_id + "']").remove();

        //show Nodes and motifLinks
        d3.selectAll(".nodes circle[motif_id='" + 'motif_' + motif_id + "']").select(function () {
            this.setAttribute("stroke", "white");
            return this;
        });

        d3.selectAll(".links [motif_id='" + 'motif_' + motif_id + "']").select(function () {
            this.setAttribute("stroke", "#999");
            return this;
        });
    }

    /**
     * Calculates the scale extent of the graph according to its size.
     */
    function calculateScaleExtent() {
        var bbox = svg.node().getBBox();

        var bboxW = bbox.width;
        var bboxH = bbox.height;

        if ((bboxW === 0) || (bboxH === 0)) {
            return;
        }

        var scale = Math.sqrt(Math.min(svgW / bboxW, svgH / bboxH)) * 0.8;
        scale = scale * scale;

        if(appliedTransform) {
            scale = scale * appliedTransform.k;
        } else {
            scale = scale * prevScale;
        }

        zoom.scaleExtent([(scale) * 0.5, 5]);
    }

    /**
     * Restarts the d3 force simulation of the graph.
     * @param {number} alpha - Parameter for d3 force simulation.
     */
    function restartSimulation(alpha) {
        node = svg.select(".nodes").selectAll("*").select( function () {
            if(this.tagName === "circle" || this.tagName === "g" ) {
                return this;
            }
        });

        link = svg.selectAll("line");

        simulation
            .nodes(graph.nodes)
            .on("tick", ticked)
            .on("end", calculateScaleExtent);

        simulation.force("link")
            .links(graph.links);

        if(!alpha || alpha < 0) {
            simulation.restart();
        } else {
            simulation.alphaTarget(0);
            simulation.alpha(alpha).restart();
        }
    }

    /**
     * Appends glyphs on the svg html-element.
     * @param {Motif} motifType - Type of motif to detect and replace. Either "CLIQUE", "FAN" or
     * "CONNECTOR".
     * @param {Array} nodes - Array of node objects containing the data of the glyphs to append.
     * @param {{}} properties - Array of properties (color, size, position,...) to visualize
     * the glyph.
     */
    function appendGlyphs(motifType, nodes, properties){
        simulation.stop();

        switch (motifType) {
            case MotifEnum.CLIQUE:
                appendClique(nodes, properties);
                break;
            case MotifEnum.FAN:
                appendFan(nodes, properties);
                break;
            case MotifEnum.CONNECTOR:
                appendConnector(nodes, properties);
                break;
            default:
                break;
        }
    }

    /**
     * Appends clique glyphs on the svg html-element.
     * @param {Array} nodes - Array of node objects containing the data of the glyphs to append.
     * @param {{}} properties - Array of properties (color, size, position,...) to visualize
     * the glyph.
     */
    function appendClique(nodes, properties) {
        var tmp = svg.selectAll(".nodes").selectAll("g")
            .data(nodes, function(d){
                if (d.type === 1) {
                    return d;
                }
            });

        tmp
            .enter()
            .append("g")
                .attr("class", "vertex")
                .attr("id", function(d) {
                    return "vertex_" + d.id;
                })
                .attr("motif_id", function (d) {
                    return "motif_" + d.motif_id;
                })
                .attr("r", function (d) {
                    return properties[d.motif_id]["radius"];
                })
                .attr("transform",function (d) {
                    return properties[d.motif_id]["transform"];
                })
                .attr("cx", function (d) {
                    return properties[d.motif_id]["meanX"];
                })
                .attr("cy", function (d) {
                    return properties[d.motif_id]["meanY"];
                })
                .append("polygon")
                    .attr("points", cliquePolygonPoints)
                    .attr("style", function (d) {
                        return "fill:" + properties[d.motif_id]["color"] + ";stroke:white;stroke-width:10";
                    })
                    .attr("transform",function (d) {
                        var str = properties[d.motif_id]["transform"].split(")")[0];
                        return str + ")scale(" + properties[d.motif_id]["scale"] + ")";
                    })
                    .attr("type", function (d) {
                        return d.type;
                    })
                    .call(d3.drag()
                        .on("start", dragstarted)
                        .on("drag", dragged)
                        .on("end", dragended))
                    .on("mouseover", this.showLabel)
                    .on("mouseout", this.hideLabel);

        restartSimulation(0.1);
    }

    /**
     * Appends fan glyphs on the svg html-element.
     * @param {Array} nodes - Array of node objects containing the data of the glyphs to append.
     * @param {{}} properties - Array of properties (color, size, position,...) to visualize
     * the glyph.
     */
    function appendFan(nodes, properties) {
        var tmp = svg.selectAll(".nodes").selectAll("g")
            .data(nodes, function(d){
                if(d.type === 2) {
                    return d;
                }
            })
            .enter()
            .append("g")
                .attr("class", "vertex")
                .attr("id", function(d) {
                    return "vertex_" + d.id;
                })
                .attr("motif_id", function (d) {
                    return "motif_" + d.motif_id;
                })
                .attr("r", 30)
                .attr("transform",function (d) {
                    return properties[d.motif_id]["transform"];
                })
                .attr("cx", function (d) {
                    return properties[d.motif_id]["meanX"];
                })
                .attr("cy", function (d) {
                    return properties[d.motif_id]["meanY"];
                })
                .call(d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    .on("end", dragended))
                .on("mouseover", this.showLabel)
                .on("mouseout", this.hideLabel);

        tmp
            .append("circle")
            .attr("class", "vertex no-transform")
            .attr("r", 5)
            .attr("fill", function(d) {
                var color = d3.color("white");
                d3.selectAll("circle[id='" + 'vertex_' + motifs[d.motif_id]["oldNodes"][0].id + "']").select(function (d) {
                   color = this.getAttribute("fill");
                });
                return color;
            })
            .attr("stroke", "#fff")
            .attr("type", function (d) {
                return d.type;
            });

        tmp
            .append("path")
            .attr("d", function (d) {
                return calculateFanShape(properties[d.motif_id]["scale"]);

            })
            .attr("fill", function(d) {
                return properties[d.motif_id]["color"];
            })
            .attr("style", function (d) {
                return "stroke:white;stroke-width:6";
            })
            .attr("transform",function (d) {
                var str = properties[d.motif_id]["transform"].split(")")[0];
                return str + ")scale(" + 0.25 + ")";
            })
            .attr("type", function (d) {
                return d.type;
            });

        restartSimulation(0.1);
    }

    /**
     * Appends connector glyphs on the svg html-element.
     * @param {Array} nodes - Array of node objects containing the data of the glyphs to append.
     * @param {{}} properties - Array of properties (color, size, position,...) to visualize
     * the glyph.
     */
    function appendConnector(nodes, properties) {
        var tmp = svg.selectAll(".nodes").selectAll("g")
            .data(nodes, function(d){
                if(d.type === 3) {
                    return d;
                }});

        tmp
            .enter()
            .append("g")
                .attr("class", "vertex")
                .attr("id", function(d) {
                    return "vertex_" + d.id;
                })
                .attr("motif_id", function (d) {
                    return "motif_" + d.motif_id;
                })
                .attr("r", 30)
                .append("polygon")
                    .attr("points", connectorPolygonPoints)
                    .attr("style", function (d) {
                        return "fill:" + properties[d.motif_id]["color"] + ";stroke:white;stroke-width:10";
                    })
                    .attr("transform",function (d) {
                        var str = properties[d.motif_id]["transform"].split(")")[0];
                        return str + ")scale(" + properties[d.motif_id]["scale"] + ")";
                    })
                    .call(d3.drag()
                        .on("start", dragstarted)
                        .on("drag", dragged)
                        .on("end", dragended))
                    .on("mouseover", this.showLabel)
                    .on("mouseout", this.hideLabel);

        restartSimulation(0.1);
    }

    /**
     * Calculates the properties of a glyph based on the nodes which are represented by the glyph.
     * @param {number} motif_id - Id of the motif.
     * @returns {Object} Object containing the properties(meanX, meanY, scale, radius, color, transform)
     * as attributes.
     */
    function calculateGlyphProperties(motif_id) {
        var properties = {};
        var meanPos;
        var meanColor = d3.color("white");
        var width;
        var height;

        motifs[motif_id]["oldNodes"].forEach(function (value) {
            if (!meanPos) {
                meanPos = [];
                meanPos[0] = value["x"];
                meanPos[1] = value["y"];
                meanColor = d3.rgb(color(value[colorAttr]));
            } else {
                meanPos[0] = (meanPos[0] + value["x"]);
                meanPos[1] = (meanPos[1] + value["y"]);
                var tmpColor = d3.rgb(color(value[colorAttr]));
                meanColor.r = (meanColor.r + tmpColor.r);
                meanColor.g = (meanColor.g + tmpColor.g);
                meanColor.b = (meanColor.b + tmpColor.b);
            }
        });
        meanPos /= motifs[motif_id]["oldNodes"].length;
        meanColor.r /= motifs[motif_id]["oldNodes"].length;
        meanColor.g /= motifs[motif_id]["oldNodes"].length;
        meanColor.b /= motifs[motif_id]["oldNodes"].length;

        if(meanPos) {
            properties["meanX"] = meanPos[0];
            properties["meanY"] = meanPos[1];
        }

        var scale = (motifs[motif_id]["oldNodes"].length / graph.nodes.length) * 2;
        scale = scale < (5*3)/200 ? (5*3)/200 : scale;

        if(motifs[motif_id]["type"] === 2) {
            width = 400 * 0.25;
            height = 400 * 0.25;
        } else {
            width = 400 * scale * 1.1;
            height = 400 * scale * 1.1;
        }
        var r = Math.sqrt(width * width + height * height) / 2;

        properties["scale"] = scale;
        properties["radius"] = r;
        properties["color"] = meanColor;

        var transform = "";

        d3.selectAll("[id='" + 'vertex_' + motifs[motif_id]["oldNodes"][0].id + "']").select(function () {
            transform = this.getAttribute("transform");
            return this;
        });

        properties["transform"] = transform;

        return properties;
    }

    /**
     * Converts polar coordinates to cartesian corrdinates of an arc.
     * @param {number} centerX - X position of the center of the arc.
     * @param {number} centerY - Y position of the center of the arc.
     * @param {number} radius - Radius of the arc.
     * @param {number} angleInDegrees - Angle of the arc in degrees.
     * @returns {{x: *, y: *}} Object containing the cartesian x and y coordinates.
     */
    function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
        var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;

        return {
            x: centerX + (radius * Math.cos(angleInRadians)),
            y: centerY + (radius * Math.sin(angleInRadians))
        };
    }

    /**
     * Generates the string for a path html-element to describe an arc.
     * @param {number} x - X coordinate of the center of the arc.
     * @param {number} y - Y coordinate of the center of the arc.
     * @param {number} radius - Radius of the arc.
     * @param {number} startAngle - Degrees where to start the angle.
     * @param {number} endAngle - Degrees where to end the angle.
     * @returns {string} Description of the arc.
     */
    function describeArc(x, y, radius, startAngle, endAngle){

        var start = polarToCartesian(x, y, radius, endAngle);
        var end = polarToCartesian(x, y, radius, startAngle);

        var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";

        return [
            "M", start.x, start.y,
            "A", radius, radius, 0, arcSweep, 0, end.x, end.y,
            "L", x,y,
            "L", start.x, start.y
        ].join(" ");
    }

    /**
     * Returns the description for a fan glyph according to the underlying nodes.
     * @param {number} scale - Scale factor of the fan glyph.
     * @returns {string} Description of the arc.
     */
    function calculateFanShape(scale){
        var radius  = 200;
        var angle = 180 * scale;

        return describeArc(0,0,radius, 0, angle);
    }

    /**
    * Returns all stored cliques
    * @returns {Object} list of cliques
    */
    this.getCliques = function getCliques() {
        return cliques;
    }

    /**
    * Returns all stored fans
    * @returns {Object} list of fans
    */
    this.getFans = function getFans() {
        return fans;
    }

    /**
    * Return all stored connectors
    * @returns {Object} list of connectors
    */
    this.getConnectors = function getConnectors() {
        return connector;
    }

    /**
    * Returns the label of a given node
    * @param {string} nodeid - id of the required node
    * @returns {string} label of the node
    */
    this.getNodeLabel = function getNodeLabel(nodeid) {
        //Node deren ID nodeid ist, nicht index
        for (var i = 0; i < graph.nodes.length; i++) {
            if (graph.nodes[i].id == nodeid) {
                return labeling(graph.nodes[i]);
            }
        }
    };

    /**
    * Returns the color of the glyph of the motif with the given id
    * @param {number} motif_id - id of the required motif
    * @returns {Object} d3-rgb-object resembling the color of the glyph
    */
    this.getMotifColor = function getMotifColor(motif_id) {
        return motif_properties[motif_id].color; //RGB-object
    }

    /**
    * Highlights a glyph in the visualization
    * @param {number} motif_id - id of the motif to highlight
    */
    this.highlightMotif = function highlightMotif(motif_id) {
        var nodeid = motifs[motif_id].motifNode[0].id;
        if (motifs[motif_id].type == MotifEnum.FAN) {
            $("#vertex_" + nodeid + " path").css("stroke", "black");
            $("#vertex_" + nodeid + " path").css("fill", "#ddd");
        } else {
            $("#vertex_" + nodeid + " polygon").css("stroke", "black");
            $("#vertex_" + nodeid + " polygon").css("fill", "#ddd");
        }
    }

    /**
    * Resets the color of a glyph to its original color
    * @param {number} motif_id - id of the motif to color
    */
    this.unlightMotif = function unlightMotif(motif_id) {
        var nodeid = motifs[motif_id].motifNode[0].id;
        var color = motif_properties[motif_id].color;
        if (motifs[motif_id].type == MotifEnum.FAN) {
            $("#vertex_" + nodeid + " path").css("stroke", "white");
            $("#vertex_" + nodeid + " path").css("fill", color);
        }
        else {
            $("#vertex_" + nodeid + " polygon").css("stroke", "white");
            $("#vertex_" + nodeid + " polygon").css("fill", color);
        }

    }

}