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