From eaa3420419fc7687d9805da9b3850da5d887447d Mon Sep 17 00:00:00 2001
From: Nils Christian Ehmke <nie@informatik.uni-kiel.de>
Date: Sat, 16 Mar 2013 17:11:49 +0100
Subject: [PATCH] Updated the flow editor

---
 .../webgui/persistence/IProjectDAO.java       |   4 +
 .../persistence/impl/FSProjectDAOImpl.java    |  15 +
 .../webgui/service/IProjectService.java       |   3 +
 .../service/impl/ProjectServiceImpl.java      |   9 +
 .../view/CurrentAnalysisEditorGraphBean.java  |   7 +-
 .../src/main/webapp/js/flowEditor.js          | 451 +++++++++++-------
 Kieker.WebGUI/src/main/webapp/js/jit.js       | 145 +++---
 .../webapp/pages/AnalysisEditorPage.xhtml     |   5 +
 .../webapp/templates/CommonTemplate.xhtml     |   2 +-
 9 files changed, 384 insertions(+), 257 deletions(-)

diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/persistence/IProjectDAO.java b/Kieker.WebGUI/src/main/java/kieker/webgui/persistence/IProjectDAO.java
index ea79960e..9d80b506 100644
--- a/Kieker.WebGUI/src/main/java/kieker/webgui/persistence/IProjectDAO.java
+++ b/Kieker.WebGUI/src/main/java/kieker/webgui/persistence/IProjectDAO.java
@@ -55,6 +55,9 @@ public interface IProjectDAO {
 	@PreAuthorize("hasAnyRole('User', 'Administrator')")
 	public abstract void addProject(String projectName, final String username) throws ProjectAlreadyExistingException, IOException;
 
+	@PreAuthorize("hasAnyRole('User', 'Administrator')")
+	public abstract void delProject(String projectName);
+
 	/**
 	 * This method imports an existing kax-file into the application. If the given project name does already exist, the application will not try to upload it in the
 	 * first place.
@@ -286,4 +289,5 @@ public interface IProjectDAO {
 	 */
 	@PreAuthorize("isAuthenticated()")
 	public abstract String getLayout(String projectName);
+
 }
diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/persistence/impl/FSProjectDAOImpl.java b/Kieker.WebGUI/src/main/java/kieker/webgui/persistence/impl/FSProjectDAOImpl.java
index 2e6704b8..182249a5 100644
--- a/Kieker.WebGUI/src/main/java/kieker/webgui/persistence/impl/FSProjectDAOImpl.java
+++ b/Kieker.WebGUI/src/main/java/kieker/webgui/persistence/impl/FSProjectDAOImpl.java
@@ -783,6 +783,21 @@ public class FSProjectDAOImpl implements IProjectDAO, ReleaseListener {
 		}
 	}
 
+	@Override
+	@PreAuthorize("hasAnyRole('User', 'Administrator')")
+	public void delProject(final String projectName) {
+		if (!this.projectExists(projectName)) {
+			throw new ProjectNotExistingException("A project with the name '" + projectName + "' does not exist.");
+		}
+
+		// Assemble all paths
+		FSProjectDAOImpl.assembleProjectDir(projectName);
+		FSProjectDAOImpl.assembleKaxFile(projectName);
+		FSProjectDAOImpl.assembleMetaFile(projectName);
+		this.assembleLibDir(projectName);
+
+	}
+
 	/*
 	 * (non-Javadoc)
 	 * 
diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/service/IProjectService.java b/Kieker.WebGUI/src/main/java/kieker/webgui/service/IProjectService.java
index ad527a74..9c26f018 100644
--- a/Kieker.WebGUI/src/main/java/kieker/webgui/service/IProjectService.java
+++ b/Kieker.WebGUI/src/main/java/kieker/webgui/service/IProjectService.java
@@ -60,6 +60,9 @@ public interface IProjectService {
 	@PreAuthorize("hasAnyRole('User', 'Administrator')")
 	public void addProject(final String projectName, final String username) throws ProjectAlreadyExistingException, IOException;
 
+	@PreAuthorize("hasAnyRole('User', 'Administrator')")
+	public void delProject(final String projectName) throws ProjectNotExistingException, IOException;
+
 	/**
 	 * This method imports an existing kax-file into the application. If the given project name does already exist, the application will not try to upload it in the
 	 * first place.
diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/ProjectServiceImpl.java b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/ProjectServiceImpl.java
index 843a0741..92a6e540 100644
--- a/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/ProjectServiceImpl.java
+++ b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/ProjectServiceImpl.java
@@ -76,6 +76,15 @@ public final class ProjectServiceImpl implements IProjectService {
 		}
 	}
 
+	@Override
+	public void delProject(final String projectName) throws ProjectNotExistingException, IOException {
+		final Object projectLock = this.getLock(projectName, this.fileSystemLocks);
+
+		synchronized (projectLock) {
+			this.projectDAO.delProject(projectName);
+		}
+	}
+
 	/*
 	 * (non-Javadoc)
 	 * 
diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentAnalysisEditorGraphBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentAnalysisEditorGraphBean.java
index 1fe0b910..4e9110cf 100644
--- a/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentAnalysisEditorGraphBean.java
+++ b/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentAnalysisEditorGraphBean.java
@@ -71,16 +71,15 @@ public final class CurrentAnalysisEditorGraphBean {
 	private static final String JS_CMD_CREATE_GRAPH_VAR = "var graph = GraphFlow()";
 	private static final String JS_CMD_ADD_EDGE_CONSTRAINTS = "graph.addEdgeConstraints()";
 
-	private static final String JS_CMD_SET_FILTER_ICON = "graph.setNodeIcon('Filter', '../img/graphIcons/FilterIcon.png')";
-	private static final String JS_CMD_SET_READER_ICON = "graph.setNodeIcon('Reader', '../img/graphIcons/ReaderIcon.png')";
-	private static final String JS_CMD_SET_REPOSITORY_ICON = "graph.setNodeIcon('Repository', '../img/graphIcons/RepositoryIcon.png')";
+	private static final String JS_CMD_SET_FILTER_ICON = "graph.setNodeIcon('Filter', '../img/graphIcons/FilterIcon.png', true)";
+	private static final String JS_CMD_SET_READER_ICON = "graph.setNodeIcon('Reader', '../img/graphIcons/ReaderIcon.png', true)";
+	private static final String JS_CMD_SET_REPOSITORY_ICON = "graph.setNodeIcon('Repository', '../img/graphIcons/RepositoryIcon.png', true)";
 
 	private static final String JS_CMD_ADD_CLICK_NODE_LISTENER = "graph.addListener('onClick', nodeClickListener)";
 	private static final String JS_CMD_ADD_REMOVE_NODE_LISTENER = "graph.addListener('onRemoveNode', nodeRemoveListener)";
 	private static final String JS_CMD_ADD_CREATE_EDGE_LISTENER = "graph.addListener('onCreateEdge', edgeCreateListener)";
 	private static final String JS_CMD_ADD_REMOVE_EDGE_LISTENER = "graph.addListener('onRemoveEdge', edgeRemoveListener)";
 	private static final String JS_CMD_ADD_AUTO_LAYOUT_LISTENER = "graph.addListener('autoLayout', autoLayoutListener)";
-
 	private static final String JS_CMD_NODE = "{'id':'%s', 'name':'%s', 'nodeClass':'%s', 'tooltip':'%s'}";
 	private static final String JS_CMD_PORT = "{'name':'%s','id':'%s', 'tooltip':'%s'}";
 
diff --git a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js
index a45c120b..803f22fb 100644
--- a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js
+++ b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js
@@ -78,16 +78,16 @@ function GraphFlow(){
 	/** Custom Node Edges **/
 	var customEdgeMap = {
 		'none' : 
-		{'edge' : 'flowArrow', 
-		 'getOffset' : function(){return {'x' : 0, 'y' : -2}}
+		{
+			'getOffset' : function(){return {'x' : 0, 'y' : -2}}
 		},
 		'inputPort' : 
-			{'edge' : 'flowArrow', 
-			 'getOffset' : function(){return {'x' : -vertexLabelSize, 'y' : vertexLabelSize / 2}}
+			{
+			'getOffset' : function(){return {'x' : -vertexLabelSize, 'y' : vertexLabelSize / 2}}
 			},
 		'outputPort' : 
-			{'edge' : 'flowArrow', 
-			 'getOffset' : function(){return {'x' : vertexLabelSize, 'y' : vertexLabelSize / 2}}
+			{
+			'getOffset' : function(){return {'x' : vertexLabelSize, 'y' : vertexLabelSize / 2}}
 			}
 	};
 	customEdgeMap['repositoryPort'] = customEdgeMap['outputPort'];
@@ -207,7 +207,7 @@ function GraphFlow(){
 		   !validArg("addCustomNode()", yPosition, "number") ||
 		   !validArg("addCustomNode()", type, "string") ||
 		   !validArgNode("addCustomNode()", node)){
-			return;
+			return false;
 		}
 		
 		var nodeType = 'Custom';
@@ -244,13 +244,15 @@ function GraphFlow(){
 		
 		// apply changes immediately
 		fd.graph.addNode(newNode);
-		fd.graph.getNode(newNode.id).pos.setc(xPosition, yPosition);
+		newNode = fd.graph.getNode(newNode.id);
+		newNode.pos.setc(xPosition, yPosition);
 		fd.plot();
 		
 		// call listener
 		if(!forced){
 			callListener("onCreateNode", [newNode]);
 		}
+		return newNode;
 	}
 	
 	/**
@@ -312,6 +314,7 @@ function GraphFlow(){
 	
 	/**
 		Changes one or more properties of an edge immediately.
+		Converts label strings to arrays.
 		@param sourceID - the ID of the node where the edge starts
 		@param targetID - the ID of the node where the edge ends
 		@param properties - a data object that may contain any field from
@@ -326,55 +329,49 @@ function GraphFlow(){
 		}
 		
 		var edge = fd.graph.getAdjacence(sourceID, targetID);
-		
-		// iterate through properties object
-		var value;
-		for(var field in properties){
-			value = properties[field];
-			
-			// make changes to data
-			if(field[0] == '$'){
-				
-				if(field == '$type'){
-					// apply new value to data
-					edge.Edge[field] = value;
-				}
-				else{
-					// apply new value to data
-					edge.data[field] = value;
-				}
-			}
-			// make changes to name or id
-			else{
-				edge[field] = value;
-			}
-		}
-		
-		// apply visual changes
-		fd.plot();
+		setObjectData(edge, properties);
 	}
 	
 	/**
-		Changes one or more properties of a node immediately.
-		@nodeID - the ID of the node that needs changing
-		@properties - a data object that may contain any field from
-					  node.data as well as "name" and "id". Data fields
-					  must start with a $.
-	*/
+	Changes one or more properties of a node immediately.
+	Converts displayable strings to arrays and recalculates node size.
+	@param object - id of a node that needs changing
+	@param properties - a data object that may contain any field from
+				  node.data as well as "name" and "id". Data fields
+				  must start with a $.
+	 */
 	this.setNodeData = function(nodeID, properties){
-		
 		// check argument
 		if(!validArg("setNodeData()", nodeID, "string")){
 			return;
 		}
 		
-		var value;
 		var node = fd.graph.getNode(nodeID);
+		setObjectData(node, properties);
+	}
+		
+	/**
+	Changes one or more properties of a node or edge immediately.
+	Converts displayable strings to arrays and recalculates node size.
+	@param object - a node or an edge that needs changing
+	@param properties - a data object that may contain any field from
+				  node.data as well as "name" and "id". Data fields
+				  must start with a $.
+	 */
+	function setObjectData(object, properties){
+		
+		var isEdge = !!object.nodeFrom;
+		var value;
 		
 		// iterate through properties object
 		for(var field in properties){
 			value = properties[field];
 			
+			// convert string to array if it is a displayable name
+			if((field == "name" || field == "$label") && typeof value == "string"){
+				value = value.split("\n");
+			}
+			
 			// make changes to data
 			if(field[0] == '$'){
 				
@@ -385,19 +382,24 @@ function GraphFlow(){
 					}
 					value = "px " + value;
 				}
+				else if(isEdge && field == '$type'){
+					// apply new value to data
+					object.Edge[field] = value;
+				}
+				
 				// apply new value to data
-				node.data[field] = value;
+				object.data[field] = value;
 			}
 			
 			// make changes to name or id
 			else{
-				node[field] = value;
+				object[field] = value;
 			}
 		}
 		
 		// recalculate node dimensions in case we changed the font or name
-		if(!node.data.$custom){
-			updateNodeWidth(node);
+		if(!isEdge && !object.data.$custom){
+			updateNodeDimensions(object);
 		}
 		
 		// apply visual changes
@@ -405,16 +407,20 @@ function GraphFlow(){
 	}
 	
 	/**
-		Recalculates the width of a node and repositions the ports.
-		Applies the change immediately.
-		@param node - the node of the graph
+		Recalculates the width and height of a nodeFamily and repositions the ports.
+		@param node - the nodeFamily of the graph
 	*/
-	function updateNodeWidth(node){
+	function updateNodeDimensions(node){
+		
 		var data = node.data;
 		
+		if(data.$type != 'nodeFamily'){
+			return;
+		}
+		
 		// reserve icon space
 		var iconSpace = 0;
-		if(nodeIcons[data.$nodeType]){iconSpace = 4;}
+		if(data.$icon){iconSpace = 4;}
 		// calculate width of name
 		var width = 0;
 		
@@ -435,7 +441,7 @@ function GraphFlow(){
 		width += (4 + iconSpace) * vertexLabelSize;
 		
 		// check if the width changed at all
-		if(node.data.$width == width){
+		if(data.$width == width){
 			return;
 		}
 		
@@ -447,22 +453,50 @@ function GraphFlow(){
 		width /= 2;
 		
 		// update port positions
+		var portCounter = {
+			'inputPort' : 0,
+			'outputPort' : 0,
+			'repositoryPort' : 0};
+			
 		var children = data.$children,
 			parentX = node.pos.getc(true).x,
-			child,
 			childPos;
 		
 		for(var c in children){
-			child = children[c].pos;
-			childPos = child.getc(true);
+			c = children[c];
+			portCounter[c.data.$type]++;
+			
+			childPos = c.pos.getc(true);
 			
 			if( parentX - childPos.x > 0){
-				child.setc(childPos.x - delta, childPos.y);
+				c.pos.setc(childPos.x - delta, childPos.y);
 			}
 			else{
-				child.setc(childPos.x + delta, childPos.y);
+				c.pos.setc(childPos.x + delta, childPos.y);
 			}
 		}
+		
+		// update height
+		var maxPorts = Math.max(portCounter.inputPort,
+								portCounter.outputPort,
+								portCounter.repositoryPort);
+		var size = vertexLabelSize * 2;
+		
+		var height = size + Math.max( 2 * size, vertexLabelSize + maxPorts * size);
+		
+		if(textArray){
+			height = Math.max(height, (textArray.length + 2) * size);
+		}
+		
+		// update crossBox position
+		delta = (height - node.data.$height) / 2;
+		if(!data.$custom){
+			c = children[0];
+			childPos = c.pos.getc(true);
+			c.pos.setc(childPos.x, childPos.y - delta);
+		}
+		
+		node.data.$height = height;
 	}
 	
 	
@@ -859,7 +893,7 @@ function GraphFlow(){
 		// fuse all information to a single string
 		nodeLayout = nodeLayout.join(";");
 		edgeLayout = edgeLayout.join(";");
-		nodeLayout += "#;" + edgeLayout + ";";
+		nodeLayout += ";#" + edgeLayout + ";";
 		
 		callListener("onSavePositions", [nodeLayout]);
 		
@@ -934,6 +968,7 @@ function GraphFlow(){
 	this.validArg = function(callFunction, arg, type){
 		var valid = (typeof arg == type);
 		if(!valid){
+			console.log(typeof arg);
 			callListener("onError", ["Error in function "+ callFunction 
 										+ ": Invalid argument type for argument '"
 										+ arg + "'. Must be of type " + type + "."]);
@@ -1067,8 +1102,12 @@ function GraphFlow(){
 					  'Filter', or 'Reader'
 	@param path - the path to the image. If null, an existing
 				  icon will be deleted
+	@param resizeImmediately - if true, this function will not wait
+				  until the image is loaded and instead resize all
+				  affected nodes, regardless of if the image is loaded
+				  successfully or not
 	*/
-	this.setNodeIcon = function(nodeType, path){
+	this.setNodeIcon = function(nodeType, path, resizeImmediately){
 		// TODO: give refrence instead of icon to nodes
 		if(!validArg('setNodeIcon()', nodeType, 'string')){
 			return;
@@ -1087,54 +1126,64 @@ function GraphFlow(){
 				// update node width and port positions
 				if(data.$nodeType == nodeType){
 					delete data.$icon;
-					updateNodeWidth(node);
+					updateNodeDimensions(node);
 				}
 			});
+			
+			return;
 		}
-		else{
-			if(!validArg('setNodeIcon()', path, 'string')){
+		
+		if(!validArg('setNodeIcon()', path, 'string')){
+			return;
+		}
+		
+		var prevImage = nodeIcons[nodeType];
+		
+		var icon;
+		icon = new Image();
+		
+		// load error
+		icon.onerror = function(){
+			callListener("onError", ["Error in function setNodeIcon(): Could not load '"+ path +"'."]);
+		};
+		
+		// load success
+		icon.onload = function(){
+			// this may happen on IE, when linking to images via web
+			if(!icon.complete){
+				callListener("onError", ["Error in function setNodeIcon(): Timeout while loading '"+ path +"'. Please try again!"]);
 				return;
 			}
-			var icon;
-			icon = new Image();
-			
-			// load error
-			icon.onerror = function(){
-				callListener("onError", ["Error in function setNodeIcon(): Could not load '"+ path +"'."]);
-			};
-			
-			// load success
-			icon.onload = function(){
-				// this may happen on IE, when linking to images via web
-				if(!icon.complete){
-					callListener("onError", ["Error in function setNodeIcon(): Timeout while loading '"+ path +"'. Please try again!"]);
-					return;
-				}
-				
-				// if nodes were already created, we need to
-				// adjust their width and iconPath
-				var prevImage = nodeIcons[nodeType];
-				nodeIcons[nodeType] = icon;
-				
-				if(!prevImage){
-					var data;
-					iterateAllNodes(function(node){
-						
-						// get data
-						data = node.data;
-						// update node width and port positions
-						if(data.$nodeType == nodeType){
-							data.$icon = icon;
-							updateNodeWidth(node);
-						}
-					});
+			nodeIcons[nodeType] = icon;
+			resizeNodes(icon, !resizeImmediately);
+			fd.plot();
+		};
+		
+		
+		var resizeNodes = function(newIcon, resize){
+			// if nodes were already created, we need to
+			// adjust their width and iconPath
+			var data;
+			iterateAllNodes(function(node){
+				// get data
+				data = node.data;
+				// update node width and port positions
+				if(data.$nodeType == nodeType){
+					data.$icon = newIcon;
+					if(resize && !prevImage){
+						updateNodeDimensions(node);
+					}
 				}
-				fd.plot();
-			};
-			
-			// attempt to load the image
-			icon.src = path;
+			});
+		};
+		
+		if(resizeImmediately){
+			nodeIcons[nodeType] = "emptyImage";
+			resizeNodes("emptyImage", true);
 		}
+		
+		// attempt to load the image
+		icon.src = path;
 	}
 	
 	/**
@@ -1430,17 +1479,20 @@ function GraphFlow(){
 	if(!font){ font = "Lucida Console";}
 	font = "px " + font;
 	
-	var width = 0, height;
+	// calculate node family dimensions
+	var width = 0;	
+	var height = size + Math.max( 2 * size, vertexLabelSize + maxPorts * size);
 	
 	// calculate width of longest text line
-	var textArray = nodeFamily.name.split("\n");
-	for(var t in textArray){
-		width = Math.max(width, graph.getTextWidth(textArray[t], (size * 0.83) + font));
+	if(nodeFamily.name){
+		var textArray = nodeFamily.name.split("\n");
+		for(var t in textArray){
+			width = Math.max(width, graph.getTextWidth(textArray[t], (size * 0.83) + font));
+		}
+		
+		height = Math.max(height, (textArray.length + 2) * vertexLabelSize * 2);
 	}
-	
-	// calculate nodeFamily dimensions
 	width += (4 + iconSpace) * vertexLabelSize;
-	height = size + Math.max( 2 * size, size/2 + maxPorts * size);
 
 	// add big node box
 	// change node color, depending on its type
@@ -1578,6 +1630,7 @@ function GraphFlow(){
 		}
 		fd.plot();
 		
+		return newNode;
 	}
 
 	/**
@@ -1695,6 +1748,8 @@ function GraphFlow(){
 		
 		// move parent
 		node.pos.setc(x, y);
+		
+		//customEdgeMap[sourcePort.data.$type].getOffset(sourcePort, targetPort, true, edgeData)
 		 
 		// move children in relation to their parent
 		var children = node.data.$children,
@@ -1716,11 +1771,12 @@ function GraphFlow(){
    *  @param sourceID - the id of the node from which the edge starts
    *  @param targetID - the id of the node where the edge ends
    *  @param edgeLabel - the label of the edge. Can be null if no label is required
+   *  @param edgeData - a data object which may overwrite the standard edge data
    *  @param forced - if true, creates the edge unconditionally and does not trigger 
 					  a listener event
    *  @return true - if the edge was successfully added
    */
-	this.addEdge = function(sourceID, targetID, edgeLabel, forced){
+	this.addEdge = function(sourceID, targetID, edgeLabel, edgeData, forced){
 		// check for valid arguments
 		if(!validArg("addEdge()", sourceID, "string")){
 			return false;
@@ -1728,8 +1784,14 @@ function GraphFlow(){
 		if(!validArg("addEdge()", targetID, "string")){
 			return false;
 		}
-		if(edgeLabel && !validArg("addEdge()", edgeLabel, "string")){
-			edgeLabel = null;
+		if(edgeLabel){
+			if(typeof edgeLabel == 'object'){
+				// do nothing
+			}else if(validArg("addEdge()", edgeLabel, "string")){
+				edgeLabel = edgeLabel.split('\n');
+			}else{
+				edgeLabel = null;
+			}
 		}
 		
 		// look up the nodes which are to be connected
@@ -1751,11 +1813,18 @@ function GraphFlow(){
 		var data =
 			{"$direction" : [ sourceID, targetID ],
 			 "$color" : edgeColor,
-			 "$type" : customEdgeMap[sourcePort.data.$type].edge,
+			 "$type" : "flowArrow",
 			 "$offsetFrom" : 
-				   customEdgeMap[sourcePort.data.$type].getOffset(sourcePort, targetPort, data),
+				   customEdgeMap[sourcePort.data.$type].getOffset(sourcePort, targetPort, true, edgeData),
 		     "$offsetTo" :
-		    	   customEdgeMap[targetPort.data.$type].getOffset(sourcePort, targetPort, data)};
+		    	   customEdgeMap[targetPort.data.$type].getOffset(sourcePort, targetPort, false, edgeData)};
+		
+		// overwrite data if it was specified
+		if(edgeData){
+			for(var d in edgeData){
+				data[d] = edgeData[d];
+			}
+		}
 		
 		// add label to data
 		if(edgeLabel){
@@ -1769,7 +1838,7 @@ function GraphFlow(){
 			// apply changes
 			fd.graph.addAdjacence(sourcePort, targetPort, data);
 			fd.plot();
-			return true;
+			return fd.graph.getAdjacence(sourcePort, targetPort);
 		}
 		
 		delete data.$isMouseEdge;
@@ -1808,8 +1877,60 @@ function GraphFlow(){
 		// apply changes
 		fd.graph.addAdjacence(sourcePort, targetPort, data);
 		fd.plot();
-		return true;
+		return fd.graph.getAdjacence(sourcePort, targetPort);
 	}
+	
+	 /**
+	   * Creates a dangling edge between a node and the mouse pointer.
+	   * If such an edge exists already, create an edge between both nodes
+	   * that were passed to this function.
+	   * 
+	   * @param node - the node which is to be connected to the mouse pointer
+	   * @param isIncoming - if true, the edge will point towards the node
+	   * @param label - an optional edge label
+	   * @param data - a data object which may overwrite the standard edge data
+	   */
+	  this.connectEdge = function(nodeID, isIncoming, label, data){
+		
+		// second node clicked?
+		if(selectedNode){
+			var label = selectedNode.label;
+			var newEdge = false;
+			
+			// do we create an edge or simply discard it
+			if(nodeID){
+				
+				// determine direction of edge
+				if(selectedNode.isIncoming){
+					newEdge = addEdge(nodeID, selectedNode.id, label, data);
+				}
+				else{
+					newEdge = addEdge(selectedNode.id, nodeID, label, data);
+				}
+			}
+			
+			// remove mouse edge
+			if(selectedNode.isIncoming){
+				removeEdge(mouseNode.id, selectedNode.id, true);
+			}else{
+				removeEdge(selectedNode.id, mouseNode.id, true);
+			}
+			
+			selectedNode = null;
+			return newEdge;
+		}
+		// first node clicked
+		else{
+			selectedNode = {"id": nodeID, "isIncoming" : isIncoming, "label" : label};
+			
+			// determine direction of mouse edge
+			if(isIncoming){
+				return addEdge(mouseNode.id, nodeID, label, data);
+			}else{
+				return addEdge(nodeID, mouseNode.id, label, data);
+			}
+		}
+	  }
   
   
   /**
@@ -2054,54 +2175,6 @@ function GraphFlow(){
 	  return !!selectedNode;
   }
   
-  /**
-   * Creates a dangling edge between a node and the mouse pointer.
-   * If such an edge exists already, create an edge between both nodes
-   * that were passed to this function.
-   * 
-   * @param node - the node which is to be connected to the mouse pointer
-   * @param isIncoming - if true, the edge will point towards the node
-   * @param label - an optional edge label
-   */
-  this.connectEdge = function(nodeID, isIncoming, label){
-	
-	// second node clicked?
-	if(selectedNode){
-		var label = selectedNode.label;
-		
-		// do we create an edge or simply discard it
-		if(nodeID){
-			// determine direction of edge
-			if(selectedNode.isIncoming){
-				addEdge(nodeID, selectedNode.id, label);
-			}
-			else{
-				addEdge(selectedNode.id, nodeID, label);
-			}
-		}
-		
-		// remove mouse edge
-		if(selectedNode.isIncoming){
-			removeEdge(mouseNode.id, selectedNode.id, true);
-		}else{
-			removeEdge(selectedNode.id, mouseNode.id, true);
-		}
-		
-		selectedNode = null;
-	}
-	// first node clicked
-	else{
-		selectedNode = {"id": nodeID, "isIncoming" : isIncoming, "label" : label};
-		
-		// determine direction of mouse edge
-		if(isIncoming){
-			addEdge(mouseNode.id, nodeID, label);
-		}else{
-			addEdge(nodeID, mouseNode.id, label);
-		}
-	}
-  }
-  
   /**
    * Tells the Graph information about a CustomNode, as it may be implemented using Plugins.
    * @param nodeType - the name of the nodeType
@@ -2110,13 +2183,13 @@ function GraphFlow(){
    * @param edgeOffsetFun(ajacency, isSource) - a function which calculates the offset of a connecting edge
    * @param edgeType - the type of edge which is generated when calling connectEdge() on a custom node
    */
-  this.registerCustomNode = function(nodeType, fillColor, strokeColor, edgeOffsetFun, edgeType){
+  this.registerCustomNode = function(nodeType, fillColor, strokeColor, edgeOffsetFun){
 	  
 	    // define the color
 		setNodeStyle(nodeType, fillColor, strokeColor);
 		
 		// define the edge and offset
-		customEdgeMap[nodeType] = {'edge' : edgeType, 'getOffset' : edgeOffsetFun};
+		customEdgeMap[nodeType] = {'getOffset' : edgeOffsetFun};
 	}
   
     
@@ -2316,21 +2389,21 @@ function GraphFlow(){
 				// memorize where we drag the node instead on centering
 				// it to the mouse pointer
 				var	nodePos = node.pos.getc(true);
-				
-				// memorize the node position before node movement
-				navi.dragX = nodePos.x;
-				navi.dragY = nodePos.y;
 				// the offset between the center of the node and the actual position
 				// where we grabbed the node
 				navi.nodeOffsetX = nodePos.x - mousePos.x;
 				navi.nodeOffsetY = nodePos.y - mousePos.y;
 				
+				/*
+				// memorize the node position before node movement
+				navi.dragX = nodePos.x;
+				navi.dragY = nodePos.y;
 				// prepare borders. These determine when to move the canvas with the node
 				navi.borderLeft = (node.data.$width / 2 - navi.nodeOffsetX) * fd.canvas.scaleOffsetX;
 				navi.borderTop = (node.data.$height / 2 - navi.nodeOffsetY) * fd.canvas.scaleOffsetX;
 				navi.borderRight = grid.data.$width - (node.data.$width / 2 + navi.nodeOffsetX) * fd.canvas.scaleOffsetX;
 				navi.borderBottom = grid.data.$height - (node.data.$height / 2 + navi.nodeOffsetY) * fd.canvas.scaleOffsetX;
-				
+				*/
 			}
 			callListener("onDragStart", [node, eventInfo, e]);
 		  },
@@ -2351,6 +2424,7 @@ function GraphFlow(){
 			moveNode(node, x, y);
 			
 			// move screen if near edge
+			/*
 			var screenX = e.layerX,
 				screenY = e.layerY;
 				
@@ -2367,6 +2441,7 @@ function GraphFlow(){
 			else if(screenY > navi.borderBottom && y > navi.dragY){
 				fd.canvas.translate(0,-20);
 			}
+			*/
 			callListener("onDragMove", [node, eventInfo, e]);
 			fd.plot();
 			
@@ -2377,8 +2452,42 @@ function GraphFlow(){
 		   */
 		  onDragEnd: function(node, eventInfo, e) {
 			
-			fd.plot();
 			callListener("onDragEnd", [node, eventInfo, e]);
+			
+			if(node){
+				var children = node.data.$children;
+				var data, source, target;
+				
+				// this function recalculates the edge offset
+				var updateOffset = function(n){
+					n.eachAdjacency(function(adja){
+						
+						data = adja.data;
+						
+						// is the first node the source node?
+						if(adja.nodeFrom.id == data.$direction[0]){
+							source = adja.nodeFrom;
+							target = adja.nodeTo;
+						}
+						else{
+							source = adja.nodeTo;
+							target = adja.nodeFrom;
+						}
+						data.$offsetFrom =
+							customEdgeMap[source.data.$type].getOffset(source, target, true, data);
+						data.$offsetTo =
+							customEdgeMap[target.data.$type].getOffset(source, target, false, data);
+					});
+				}
+				
+				// recalculate the edge offset of the node and its children
+				updateOffset(node);
+				for(var c in children){
+					updateOffset(children[c]);
+				}
+			}
+			
+			fd.plot();
 		  },
 		  
 		  /**
diff --git a/Kieker.WebGUI/src/main/webapp/js/jit.js b/Kieker.WebGUI/src/main/webapp/js/jit.js
index f6159958..16a120d2 100644
--- a/Kieker.WebGUI/src/main/webapp/js/jit.js
+++ b/Kieker.WebGUI/src/main/webapp/js/jit.js
@@ -16977,6 +16977,19 @@ $jit.GraphFlow = new Class( {
     this.op = new $GraphFlow.Op(this);
     this.json = null;
     this.busy = false;
+    
+    //overwrite faulty click function
+    this.Classes.Events.prototype.onMouseDown = function(e, win, event) {
+        var evt = $.event.get(e, win);
+        
+        if(this.hovered){
+      	  this.pressed = this.hovered;
+        }else{
+      	  this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
+        }
+        this.config.onDragStart(this.pressed, event, evt);
+      };
+    
     // initialize extras
     this.initializeExtras();
 	
@@ -17093,7 +17106,7 @@ $jit.GraphFlow = new Class( {
     this.fx.animate($.merge( {
       modes: [ 'linear' ]
     }, opt || {}));
-  }
+  } 
   
 });
 
@@ -17101,68 +17114,6 @@ $jit.GraphFlow.$extend = true;
 
 (function(GraphFlow) {
 
-  /*
-     Method: calculateAnchorPoints
-    
-     Calculates the anchor points for an edge of the old GraphFlow.
-	 Deprecated!
-  */
-  GraphFlow.calculateAnchorPoints = function(nodeFrom, nodeTo){
-			var sizeModifier = 12;
-			if(typeof(vertexLabelSize) != 'undefined'){
-				sizeModifier = vertexLabelSize;
-			}
-					
-			// copy "from" and "to", overwriting input variables
-			var fromPos = nodeFrom.pos.getc(true),
-				toPos = nodeTo.pos.getc(true);
-			var from = 	{"x": fromPos.x, "y": fromPos.y},
-				to = 	{"x": toPos.x, "y": toPos.y};
-			
-			// set anchor-points dynamically, depending on node positions
-			// 4 cases are possible
-			var xDist = to.x - from.x,
-				yDist = to.y - from.y,
-				halfHeight = 2+ 2* sizeModifier;
-			
-			// vertical edge
-			if(Math.abs(yDist) > Math.abs(xDist)){
-				
-				if(yDist > 0){ // target is below source
-					from.y += halfHeight;
-					to.y -= halfHeight;
-					//from.x -= halfHeight/4;
-					//to.x -= halfHeight/4;
-				}
-				else{ // target is above source
-					from.y -= halfHeight;
-					to.y += halfHeight;
-					//from.x += halfHeight/4;
-					//to.x += halfHeight/4;
-				}
-			}
-			// horizontal edge
-			else{
-				var halfWidthFrom = 2+((nodeFrom.id.length+2)*sizeModifier)/2,
-					halfWidthTo = 2+((nodeTo.id.length+2)*sizeModifier)/2;
-				
-				if(xDist > 0){ // target is right of source
-					from.x += halfWidthFrom;
-					to.x -= halfWidthTo;
-					//from.y -= halfHeight/4;
-					//to.y -= halfHeight/4;
-				}
-				else{ // target is left of source
-					from.x -= halfWidthFrom;
-					to.x += halfWidthTo;
-					//from.y += halfHeight/4;
-					//to.y += halfHeight/4;
-				}
-			}
-			
-			return {"from":from, "to":to};
-	  },
-
   /*
      Class: GraphFlow.Op
      
@@ -17448,6 +17399,17 @@ $jit.GraphFlow.$extend = true;
 
   
   GraphFlow.NodeHelper = {
+		  
+		  	/*
+			 * Attempts to draw an image. Does nothing instead if no image
+			 * exists.
+			 */
+			'drawImage' : function(ctx, img, x, y, width, height){
+			  	 if (img && img != "emptyImage"){
+			  		 ctx.drawImage(img, x, y, width, height);
+			  	 }
+			},
+		  
 		  /*
 		  Object: NodeHelper.roundRect
 		  */
@@ -17531,6 +17493,7 @@ $jit.GraphFlow.$extend = true;
 		  }
   };
   
+  
   /*
     Class: GraphFlow.Plot.NodeTypes
 
@@ -17592,37 +17555,39 @@ $jit.GraphFlow.$extend = true;
 					
 					var tX = posX;
 					var img = data.$icon;
-					var imgLoaded = (img && img.width != undefined); // we need this for the crazy Internet Explorer
 					
-					if(imgLoaded){
+					if(img){
 						tX += dim;
 					}
 					
-					// paint name
+					var maxLabelWidth = 0;
 					
+					// paint name label
 					var name = node.name;
-					var midX, midY, textWidth;
-					var textSize = 0.83 * dim;
 					
-					if(canvas.showLabels){
-						if(name){
-							ctx.fillStyle = data.$color;
-							ctx.font = textSize + node.data.$font;
-							textWidth = ctx.measureText(name).width/2;
-							midX = tX - textWidth;
-							midY = posY;
+					if(canvas.showLabels && name){
+						
+						var midX, midY, labelWidth, labelHeight;
+						var labelSize = 0.83 * dim;
+						ctx.fillStyle = data.$color;
+						ctx.font = labelSize + data.$font;
+						midY = pos.y - ((name.length - 2) * dim / 2);
+						
+						for(var t = 0, l = name.length; t < l; t++){
+							labelWidth = ctx.measureText(name[t]).width/2;
+							maxLabelWidth = Math.max(labelWidth, maxLabelWidth);
+							midX = tX - labelWidth;
 							
-							ctx.fillText(name, midX, midY);
+							ctx.fillText(name[t], midX, midY);
+							midY += dim;
 						}
 					}
 					
 					// draw icon
-					if(imgLoaded){
 					
-					var ix = posX - textWidth - dim*1.3,
+					var ix = posX - maxLabelWidth - dim*1.3,
 						iy = posY - dim ;
-					ctx.drawImage(img, ix, iy, dim*2, dim*2);
-					}
+					GraphFlow.NodeHelper.drawImage(ctx,img, ix, iy, dim*2, dim*2);
 					
 	    	      },
 				  
@@ -18260,6 +18225,24 @@ $jit.GraphFlow.$extend = true;
 		
 		// add the label
 		var label = adj.data.$label;
+		
+		if(canvas.showLabels && label){
+			var tX = (from.x + to.x) / 2;
+			
+			ctx.font = (1.23 * dim)+"px Arial";
+			var midX,
+				midY =  (from.y + to.y -dim) / 2; 
+			
+			for(var t = label.length-1; t >= 0; t--){
+				midX = tX - ctx.measureText(label[t]).width/2;
+				
+				ctx.fillText(label[t], midX, midY);
+				midY -= dim;
+			}
+		}
+		
+		/*
+		var label = adj.data.$label;
 		if(label != undefined && canvas.showLabels){
 			ctx.font = (1.23 * dim)+"px Arial";
 				
@@ -18267,7 +18250,7 @@ $jit.GraphFlow.$extend = true;
 				midY = (from.y + to.y -dim) / 2;
 			
 			ctx.fillText(label, midX, midY);
-		}
+		}*/
 		
       },
       'contains': GraphFlow.EdgeHelper.contains
diff --git a/Kieker.WebGUI/src/main/webapp/pages/AnalysisEditorPage.xhtml b/Kieker.WebGUI/src/main/webapp/pages/AnalysisEditorPage.xhtml
index 7a355b3b..0d67ef9e 100644
--- a/Kieker.WebGUI/src/main/webapp/pages/AnalysisEditorPage.xhtml
+++ b/Kieker.WebGUI/src/main/webapp/pages/AnalysisEditorPage.xhtml
@@ -67,12 +67,17 @@
                         preSaveProjectCommand([{name : 'layoutString', value : layoutString}]);
                     }
                     
+                    // "Overwrite" the function in the template
+                    function bodyLoaded() {
+                      //  postInit();
+                    }
                 </script>
             </ui:define>
 
             <ui:define name="furtherForms">
                 <h:form id="hidden" style="display:none">
                     <p:remoteCommand autoRun="true" name="init" action="#{currentAnalysisEditorBean.initializeGraph()}" />
+                    <p:remoteCommand  name="postInit" action="#{currentAnalysisEditorGraphBean.startAutoLayout()}" />
                 </h:form>
                 <h:form id="hiddenNodeProperties" style="display:none"> 
                     <p:remoteCommand name="nodeClickCommand" action="#{currentAnalysisEditorGraphBean.nodeClicked()}" update=":propertiesForm"/>
diff --git a/Kieker.WebGUI/src/main/webapp/templates/CommonTemplate.xhtml b/Kieker.WebGUI/src/main/webapp/templates/CommonTemplate.xhtml
index efced5af..cb3fcded 100644
--- a/Kieker.WebGUI/src/main/webapp/templates/CommonTemplate.xhtml
+++ b/Kieker.WebGUI/src/main/webapp/templates/CommonTemplate.xhtml
@@ -25,7 +25,7 @@
 
         <ui:insert name="js">
             <script>            
-                bodyLoaded = function() {
+                function bodyLoaded() {
                     // Do nothing
                 }
             </script>
-- 
GitLab