diff --git a/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar b/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar index cc37c36997b46247562a736d8e1268d68a6a8101..2b90350faeb6f5183920c7db2904a0c67012dc01 100644 Binary files a/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar and b/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar differ diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBeanV2.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBeanV2.java index fbd46a17020f977b28d5c3728e569ce2324713c0..38f6afeb7ee9953080c092ed53f878eea6e83ad9 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBeanV2.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBeanV2.java @@ -48,6 +48,7 @@ import kieker.analysis.model.analysisMetaModel.MIPlugin; import kieker.analysis.model.analysisMetaModel.MIPort; import kieker.analysis.model.analysisMetaModel.MIProject; import kieker.analysis.model.analysisMetaModel.MIProperty; +import kieker.analysis.model.analysisMetaModel.MIReader; import kieker.analysis.model.analysisMetaModel.MIRepository; import kieker.analysis.model.analysisMetaModel.MIRepositoryConnector; import kieker.analysis.model.analysisMetaModel.impl.MAnalysisMetaModelFactory; @@ -98,9 +99,37 @@ public final class CurrentAnalysisEditorBeanV2 { */ private static final String JS_CMD_INIT_GRAPH = "var graph = GraphFlow(); graph.initGraph(null);"; /** - * This is the javascript command to add a node to the graph. + * This is the javascript command for a node object. */ - private static final String JS_CMD_ADD_NODE = "graph.addNode(%d, %d, \"%s\", [%s], [%s]);"; + private static final String JS_CMD_NODE = "{\"id\":\"%s\", \"name\":\"%s\", \"nodeClass\":\"%s\", \"tooltip\":\"%s\"}"; + /** + * This is the javascript command for a port object. + */ + private static final String JS_CMD_PORT = "{\"name\":\"%s\",\"id\":\"%s\", \"tooltip\":\"%s\"}"; + /** + * This is the javascript type name for a repository port. + */ + private static final String JS_CMD_PORT_TYPE_REPOSITORY = "repoPort"; + /** + * This is the javascript type name for an input port. + */ + private static final String JS_CMD_PORT_TYPE_INPUT = "inputPort"; + /** + * This is the javascript type name for an output port. + */ + private static final String JS_CMD_PORT_TYPE_OUTPUT = "outputPort"; + /** + * This is the javascript command to add a filter to the graph. + */ + private static final String JS_CMD_ADD_FILTER = "graph.addFilter(%d, %d, %s,[%s],[%s],[%s]);"; + /** + * This is the javascript command to add a reader to the graph. + */ + private static final String JS_CMD_ADD_READER = "graph.addReader(%d, %d, %s,[%s],[%s]);"; + /** + * This is the javascript command to add a repository to the graph. + */ + private static final String JS_CMD_ADD_REPOSITORY = "graph.addRepository(%d, %d, %s,%s);"; /** * This is the javascript command to add an edge to the graph. */ @@ -621,7 +650,7 @@ public final class CurrentAnalysisEditorBeanV2 { } /** - * This method initializes the modified jit-grpah by delivering the necessary javascript commands to paint the repository components. + * This method initializes the modified jit-graph by delivering the necessary javascript commands to paint the repository components. */ private void initializeGraphRepositories() { final RequestContext context = RequestContext.getCurrentInstance(); @@ -629,22 +658,26 @@ public final class CurrentAnalysisEditorBeanV2 { int posCounter = 0; for (final MIRepository repository : this.project.getRepositories()) { final int pos = (posCounter * 100) - 250; - context.execute(String.format(CurrentAnalysisEditorBeanV2.JS_CMD_ADD_NODE, 0, pos, this.assembleGraphString(repository), "\"ip\"", "")); + final String repoPort = String.format(CurrentAnalysisEditorBeanV2.JS_CMD_PORT, CurrentAnalysisEditorBeanV2.JS_CMD_PORT_TYPE_INPUT, "ip1", "N/A"); + context.execute(String.format(CurrentAnalysisEditorBeanV2.JS_CMD_ADD_REPOSITORY, 0, pos, this.assembleGraphString(repository), repoPort)); posCounter++; } } /** - * This method initializes the modified jit-grpah by delivering the necessary javascript commands to paint the plugin components. + * This method initializes the modified jit-graph by delivering the necessary javascript commands to paint the plugin components. */ private void initializeGraphPlugins() { final RequestContext context = RequestContext.getCurrentInstance(); int posCounter = 0; for (final MIPlugin plugin : this.project.getPlugins()) { - final int pos = (posCounter * 100) - 250; - context.execute(String.format(CurrentAnalysisEditorBeanV2.JS_CMD_ADD_NODE, -250, pos, this.assembleGraphString(plugin), - this.assembleInputPortsGraphString(plugin), this.assembleOutputPortsGraphString(plugin))); + final int pos = posCounter * 100; + if (plugin instanceof MIReader) { + context.execute(String.format(CurrentAnalysisEditorBeanV2.JS_CMD_ADD_READER, pos, pos, this.assembleGraphString(plugin), "", "")); + } else { + context.execute(String.format(CurrentAnalysisEditorBeanV2.JS_CMD_ADD_FILTER, pos, pos, this.assembleGraphString(plugin), "", "", "")); + } posCounter++; } } @@ -659,7 +692,7 @@ public final class CurrentAnalysisEditorBeanV2 { * @return The ID for the port within the graph */ private String assembleGraphPortID(final MIPlugin plugin, final MIPort port) { - return this.assembleGraphString(plugin) + "_" + this.portMap.get(port); + return this.pluginMap.get(plugin) + "_" + this.portMap.get(port); } /** @@ -718,7 +751,7 @@ public final class CurrentAnalysisEditorBeanV2 { * @return A human readable ID. */ private String assembleGraphString(final MIRepository repository) { - return "Repository" + this.repositoryMap.get(repository) + "(" + repository.getName() + ")"; + return String.format(CurrentAnalysisEditorBeanV2.JS_CMD_NODE, this.repositoryMap.get(repository), repository.getName(), repository.getClassname(), "N/A"); } /** @@ -729,7 +762,7 @@ public final class CurrentAnalysisEditorBeanV2 { * @return A human readable ID. */ private String assembleGraphString(final MIPlugin plugin) { - return "Plugin" + this.pluginMap.get(plugin) + "(" + plugin.getName() + ")"; + return String.format(CurrentAnalysisEditorBeanV2.JS_CMD_NODE, this.pluginMap.get(plugin), plugin.getName(), plugin.getClassname(), "N/A"); } /** diff --git a/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml b/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml index 4796358c757fd1b522ead048995d309301047b2d..81c83b3452508fa1034d2c7da4d4a2b905588958 100644 --- a/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml +++ b/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml @@ -198,7 +198,7 @@ <ui:repeat value="#{currentAnalysisEditorBean.availableRepositories}" var="repository"> <p:commandLink id="repositoryLink" value="#{repository.simpleName}" action="#{currentAnalysisEditorBean.addRepository(repository)}" update=":centerForm :messages"/><br/> <p:tooltip for="repositoryLink"> - <b><h:outputText value="#{repository.simpleName} repositoryfilter.name})"/></b> + <b><h:outputText value="#{repository.simpleName} (#{repository.name})"/></b> <br/> <h:outputText value="#{currentAnalysisEditorBean.getDescription(repository)}"/> </p:tooltip> diff --git a/Kieker.WebGUI/src/main/webapp/AnalysisEditor_V2.xhtml b/Kieker.WebGUI/src/main/webapp/AnalysisEditor_V2.xhtml index 0e42c7eb972b1aac35b9d466062fa6a834d97325..6761853592a4ca0db3092348c18f593d2ddf4953 100644 --- a/Kieker.WebGUI/src/main/webapp/AnalysisEditor_V2.xhtml +++ b/Kieker.WebGUI/src/main/webapp/AnalysisEditor_V2.xhtml @@ -15,10 +15,10 @@ <h:head> <title>Kieker.WebGUI</title> - <link type="text/css" href="css/base.css" rel="stylesheet" /> - <link type="text/css" href="css/FlowEditor.css" rel="stylesheet" /> - <link rel="stylesheet" type="text/css" href="../css/Common.css" /> - <link rel="stylesheet" type="text/css" href="../css/AnalysisEditor.css" /> + <link rel="stylesheet" type="text/css" href="css/base.css" /> + <link rel="stylesheet" type="text/css" href="css/FlowEditor.css" /> + <link rel="stylesheet" type="text/css" href="css/Common.css" /> + <link rel="stylesheet" type="text/css" href="css/AnalysisEditor.css" /> <script language="javascript" type="text/javascript" src="js/jit.js"></script> <script language="javascript" type="text/javascript" src="js/flowEditor.js"></script> diff --git a/Kieker.WebGUI/src/main/webapp/css/base.css b/Kieker.WebGUI/src/main/webapp/css/base.css index 398da055806ed1f71f2912dd84867a6e79812fef..4e05001708d0c99e615008a16c7b683c63144370 100644 --- a/Kieker.WebGUI/src/main/webapp/css/base.css +++ b/Kieker.WebGUI/src/main/webapp/css/base.css @@ -14,3 +14,18 @@ overflow:hidden; } +.tip { + color: #111; + width: 139px; + background-color: white; + border:1px solid #ccc; + -moz-box-shadow:#555 2px 2px 8px; + -webkit-box-shadow:#555 2px 2px 8px; + -o-box-shadow:#555 2px 2px 8px; + box-shadow:#555 2px 2px 8px; + opacity:0.9; + filter:alpha(opacity=90); + font-size:10px; + font-family:Verdana, Geneva, Arial, Helvetica, sans-serif; + padding:7px; +} \ No newline at end of file diff --git a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js index 3bfde4fb7451db64921a3037a0b7275e2abf2fc9..b411673eb0ca53b8c2d6e20bb1ff189efe263c76 100644 --- a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js +++ b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js @@ -44,18 +44,26 @@ function GraphFlow(){ //var that = this; - /** edge properties */ - var edgeColor = "#114270", - edgeColorFocus = "#FF00FF"; - - /** node properties */ - var vertexLabelSize = 12, // this value modifies the text size and with it the node size - nodeColor = "#909090", - nodeColorFocus = "#FF00FF"; - //vertexLabelColor = "#0F0F0F", - //vertexSelectColor = "#B2E5F2"; - - var longestEdge = 0; + /** Color Palettes */ + var nodeFillColor = []; + nodeFillColor['Filter'] = "#DEDEDE"; + nodeFillColor['Repository'] = "#EFAC6A"; + nodeFillColor['Reader'] = "#6AEF73"; + + var nodeStrokeColor = []; + nodeStrokeColor['Filter'] = "#4D4D4D"; + nodeStrokeColor['Repository'] = "#735332"; + nodeStrokeColor['Reader'] = "#327337"; + nodeStrokeColor['inputPort'] = "#AA0000"; + nodeStrokeColor['outputPort'] = "#AA0000"; + nodeStrokeColor['repositoryPort'] = "#AA0000"; + + var nodeColorFocus = "#0098BE", + edgeColor = "#114270", + edgeColorFocus = "#0098BE"; + + /** Overall size modifier: the width of node labels in pixels */ + var vertexLabelSize = 12; /** FlowGraph Object */ var fd; @@ -64,7 +72,7 @@ function GraphFlow(){ var hover = null; /** This array stores all Nodes and is initialized with a dummy, - used for beautiful edge dragging. + used for drawing edges between a node and the mousepointer. */ var json = [ { "adjacencies": [], @@ -130,6 +138,11 @@ function GraphFlow(){ onDragMove, onDragCancel, onDragEnd + Or one of these events: + onCreateEdge(sourceNodeID, targetNodeID, sourcePortID, targetPortID), + onCreateNode(nodeID), + onRemoveEdge(sourceNodeID, targetNodeID, sourcePortID, targetPortID), + onRemoveNode(nodeID) When altering the graph, do not forget to refresh() it at the end! @param event - the name of the mouse event ( see list above) @@ -147,21 +160,111 @@ function GraphFlow(){ listener[event].push(listenerFunction); } + /** + Adds a listener that forbids edge creation if... + ... an edge with the same source and target exists. + ... the target node is the source node. + ... the source and target node are of the same port type. + ... a repository node is not connected to a repository and + vice versa. + */ + this.addEdgeConstraints = function(){ + addListener("onCreateEdge", + function(sourceFamilyID, targetFamilyID, sourcePortID, targetPortID){ + + // remove Edge if it leads to itself + if( sourcePortID == targetPortID){ + removeEdge(sourcePortID, targetPortID); + return; + } + + var sourcePort = getNode(sourcePortID), + targetPort = getNode(targetPortID); + + // remove Edge if the connected nodes share the same type + if(sourcePort.data.$type == targetPort.data.$type){ + removeEdge(sourcePortID, targetPortID); + return; + } + + var target = getNode(targetFamilyID), + goesToRepo = (target.data.$nodeType == "Repository"), + isRepoPort = (sourcePort.data.$type == "repositoryPort"); + + // remove Edge if it is falsely connected to repository ports + if(goesToRepo != isRepoPort){ + removeEdge(sourcePortID, targetPortID); + return; + } + + // remove duplicate Edge + var adja = sourcePort.adjacencies, + duplicate = false; + for(var a=0; a < adja.length; a++){ + if(adja[a].nodeTo == targetPortID){ + if(duplicate){ + removeEdge(sourcePortID, targetPortID); + return; + } + else{ + duplicate = true; + } + } + } + }); + } + + /** + Changes all colors of the graph. Colors that should not be changed are given null as argument. + */ + /* + this.setColors = function(nodeFocus, edgeFocus, edge, filterFill, filterStroke, readerFill, readerStroke, repoFill, repoSroke){ + + var nodeFillColor = []; + nodeFillColor['Filter'] = "#DEDEDE"; + nodeFillColor['Repository'] = "#EFAC6A"; + nodeFillColor['Reader'] = "#6AEF73"; + + var nodeStrokeColor = []; + nodeStrokeColor['Filter'] = "#4D4D4D"; + nodeStrokeColor['Repository'] = "#735332"; + nodeStrokeColor['Reader'] = "#327337"; + nodeStrokeColor['inputPort'] = "#AA0000"; + nodeStrokeColor['outputPort'] = "#AA0000"; + nodeStrokeColor['repositoryPort'] = "#AA0000"; + + var nodeColorFocus = "#0098BE", + edgeColor = "#114270", + edgeColorFocus = "#0098BE"; + + }*/ + /** Calls all listener that are registered under the eventName. @param eventName - see 'addListener()' - @param node - the node which was clicked - @param eventInfo - an object containing useful methods like *getPos* to get the mouse position relative to the canvas - @param e - the grabbed event (should return the native event in a cross-browser manner) + @param arguments - an array of arguments. For mouseEvents it is [node, eventInfo, e], + for nodeEvents it is [nodeID], + and for edgeEvents it is [sourceNodeID, targetNodeID, sourcePortID, targetPortID] */ - this.callListener = function(eventName, node, eventInfo, e){ + function callListener(eventName, arguments){ + //Log.write("call "+eventName+" "+arguments.length + " ("+Math.random()+")"); var lArr = listener[eventName]; if(lArr == undefined){ return; } // iterate through all listener this event concerns - for(var l=0; l < lArr.length; l++){ - lArr[l](node, eventInfo, e); + if(arguments.length == 3){ + for(var l=0; l < lArr.length; l++){ + lArr[l](arguments[0], arguments[1], arguments[2]); + } + }else if(arguments.length == 1){ + for(var l=0; l < lArr.length; l++){ + lArr[l](arguments[0]); + } + }else if(arguments.length == 4){ + for(var l=0; l < lArr.length; l++){ + lArr[l](arguments[0], arguments[1], arguments[2], arguments[3]); + } } } @@ -179,133 +282,185 @@ function GraphFlow(){ } } + /** + Adds a repository node to the graph. + @param nodeFamily - describes some properties of the node: (id, name, nodeClass, tooltip) + @param outputPort - properties of the single output port(id, name, tooltip) + */ + this.addRepository = function(xPosition, yPosition, nodeFamily, inputPort){ + addNode(xPosition, yPosition, nodeFamily, null, [inputPort], null, "Repository"); + } + + /** + Adds a reader node to the graph. + @param nodeFamily - describes some properties of the node: (id, name, nodeClass, tooltip) + @param repositoryPorts - an array of properties for the repository ports (id, name, tooltip) + @param outputPorts - an array of properties for the output ports (id, name, tooltip) + */ + this.addReader = function(xPosition, yPosition, nodeFamily, repositoryPorts, outputPorts){ + addNode(xPosition, yPosition, nodeFamily, repositoryPorts, null, outputPorts, "Reader"); + } + + /** + Adds a filter node to the graph. + @param nodeFamily - describes some properties of the node: (id, name, nodeClass, tooltip) + @param outputPort - properties of the single output port(id, name, tooltip) + @param repositoryPorts - an array of properties for the repository ports (id, name, tooltip) + @param inputPorts - an array of properties for the input ports (id, name, tooltip) + @param outputPorts - an array of properties for the output ports (id, name, tooltip) + */ + this.addFilter = function(xPosition, yPosition, nodeFamily, repositoryPorts, inputPorts, outputPorts){ + addNode(xPosition, yPosition, nodeFamily, repositoryPorts, inputPorts, outputPorts, "Filter"); + } + /** Adds a Node to the graph. - @param familyID - the ID and displayname of the node - @param inputPortIDs - an array of IDs for the input ports - @param outputPortIDs - an array of IDs for the output ports + @param nodeFamily - describes some properties of the node: (id, name, nodeClass, tooltip) + @param inputPortIDs - an array of properties for the input ports (id, name, tooltip) + @param outputPortIDs - an array of properties for the output ports (id, name, tooltip) */ - this.addNode = function(xPosition, yPosition, familyID, inputPortIDs, outputPortIDs){ + function addNode(xPosition, yPosition, nodeFamily, repositoryPorts, inputPorts, outputPorts, nodeType){ - var inputPorts = [], - outputPorts = [], - maxPorts = Math.max(inputPortIDs.length, outputPortIDs.length), - size = vertexLabelSize*2; + var countRepo = 0, + countInput = 0, + countOutput = 0; - var width = (familyID.length+4)*vertexLabelSize, - height = 1*size + - Math.max( 2*size, size/2 + maxPorts * size); - - // for fast position computation, calculate the relative position - // of the first port to the nodeFamily-box - var ipRelativeX = -width/2, - ipRelativeY = (size - inputPortIDs.length * size)/2, - opRelativeX = width/2, - opRelativeY = (size - outputPortIDs.length * size)/2; - - // set position of the ports in world coordinates - var x = xPosition+ ipRelativeX, - y = yPosition+ ipRelativeY; - - // define inputPorts - for(var p=0; p < inputPortIDs.length; p++){ - var inputPort = - { - "adjacencies": [], - "data": { - "$dim": size, - "$type": "inputPort", - "$xPos": x, - "$yPos": y, - "$color": "#AA0000", - "$unfocusColor" : "#AA0000" - }, - "id": familyID+"_"+inputPortIDs[p], - "name": inputPortIDs[p], - }; - - inputPorts.push(inputPort); - y += size; + if(repositoryPorts != null){ + countRepo= repositoryPorts.length; } - - if(inputPorts.length != 0){ - inputPorts[0].data.$relativeX= ipRelativeX; - inputPorts[0].data.$relativeY= ipRelativeY; + if(inputPorts != null){ + countInput= inputPorts.length; + } + if(outputPorts != null){ + countOutput= outputPorts.length; } - // set position of the ports in world coordinates - x = xPosition+ opRelativeX, - y = yPosition+ opRelativeY; - - // define outputPorts - for(var p=0; p < outputPortIDs.length; p++){ - var outputPort = - { - "adjacencies": [], - "data": { - "$dim": size, - "$type": "outputPort", - "$xPos": x, - "$yPos": y, - "$color": "#0000AA", - "$unfocusColor" : "#0000AA" - }, - "id": familyID+"_"+outputPortIDs[p], - "name": outputPortIDs[p], - }; + var maxPorts = Math.max(countInput, countRepo+countOutput), + size = vertexLabelSize*2; - outputPorts.push(outputPort); - y += size; + if(maxPorts >= countInput && countRepo > 0 && countInput > 0){ + maxPorts++; } - if(outputPorts.length != 0){ - outputPorts[0].data.$relativeX= opRelativeX; - outputPorts[0].data.$relativeY= opRelativeY; + + var width = (nodeFamily.name.length+4)*vertexLabelSize, + height = 1*size + + Math.max( 2*size, size/2 + maxPorts * size); + + // the classname may be longer than the diplayed name + if( nodeFamily.nodeClass != undefined){ + width = Math.max(width, (nodeFamily.nodeClass.length+6)*(vertexLabelSize-2)); } - var nodeFamily = - { - //"adjacencies": [], + + // add big node box + // change node color, depending on its type + var nodeColor = nodeFillColor[nodeType], + strokeColor = nodeStrokeColor[nodeType]; + var newNode = { "data": { "$dim": size, "$type": "nodeFamily", "$jsonIndex": json.length, - "$portCount": inputPortIDs.length+ outputPortIDs.length, + "$portCount": countRepo+countInput+countOutput, "$width": width, "$height": height, - "$color": "#909090", - "$unfocusColor" : "#909090", - "$fillColor": "#F0F0F0", + "$color": strokeColor, + "$fillColor": nodeColor, + "$nodeClass": nodeFamily.nodeClass, + "$nodeType": nodeType, "$xPos": xPosition, - "$yPos": yPosition + "$yPos": yPosition, + "$tooltip": nodeFamily.tooltip }, - "id": familyID, - "name": familyID, - }; - - // set position of the close button in world coordinates - x = xPosition+ width/2, - y = yPosition- height/2; + "id": nodeFamily.id, + "name": nodeFamily.name + }; + json.push(newNode); - var closeButton = - { + // add closeButton + var closeButton ={ "adjacencies": [], "data": { "$dim": size, "$type": "crossBox", - "$family": nodeFamily, - "$xPos": x, - "$yPos": y, - "$color": "#CC0909", - "$unfocusColor" : "#CC0909", + "$family": newNode, + "$xPos": xPosition+ width/2, + "$yPos": yPosition- height/2, + "$color": strokeColor, + "$fillColor": nodeColor, }, - "id": familyID+"_close", - "name": "x", - }; - - // add all components - json.push(nodeFamily); + "id": nodeFamily.id+"_close", + "name": "x" + }; json.push(closeButton); - json= json.concat(inputPorts); - json= json.concat(outputPorts); + + // Loop Info: + // This loop adds all port types to the temporary port array. + // For fast position computation, the relative position + // of the first port to the nodeFamily-box is computed for each port type + // it is stored in relativeX, relativeY + + for(var portType = 0; portType < 3; portType++){ + var x, y, relativeX, relativeY, + loopPorts, loopType; + switch(portType){ + case 0: // repository ports + var repoSpace = 0; + if(countInput == 0){ + repoSpace = 1; + } + relativeX = width/2, + relativeY = ((0.5+repoSpace-countOutput-countRepo) * size)/2, + loopPorts = repositoryPorts; + loopType = "repositoryPort"; + break; + + case 1: // input ports + relativeX = -width/2, + relativeY = ( (1.5-countInput)*size)/2, + loopPorts = inputPorts; + loopType = "inputPort"; + break; + + case 2: // output ports + relativeX = width/2, + relativeY = ((1.5 + countRepo - countOutput) * size)/2; + loopPorts = outputPorts; + loopType = "outputPort"; + break; + } + + // continue only if ports exist + if(loopPorts != null && loopPorts.length > 0){ + + x = xPosition+ relativeX; + y = yPosition+ relativeY; + + for(var p=0; p < loopPorts.length; p++){ + var port = loopPorts[p]; + var newPort ={ + "adjacencies": [], + "data": { + "$dim": size, + "$type": loopType, + "$xPos": x, + "$yPos": y, + "$color": nodeStrokeColor[loopType], + "$tooltip": port.tooltip}, + "id": nodeFamily.id+"_"+port.id, + "name": port.name}; + + if(p == 0){ + newPort.data.$relativeX= relativeX; + newPort.data.$relativeY= relativeY; + } + json.push(newPort); + y += size; + } + } + } + + // call listener + callListener("onCreateNode", [nodeFamily.id]); } /** @@ -315,7 +470,8 @@ function GraphFlow(){ this.removeNode = function(nodeFamily){ var deleteFrom = nodeFamily.data.$jsonIndex, - deleteUntil = deleteFrom+ nodeFamily.data.$portCount+1; + deleteUntil = deleteFrom+ nodeFamily.data.$portCount+1, + familyID = nodeFamily.id; // delete nodeFamily and closeButton delete json[deleteFrom]; @@ -329,7 +485,7 @@ function GraphFlow(){ for(var e = 0; e < json.length; e++){ var incNode = json[e]; if(incNode != undefined){ - removeEdge(incNode, portID); + removeEdge(incNode.id, portID); } } @@ -348,7 +504,10 @@ function GraphFlow(){ json[n].data.$jsonIndex = n; } } - + + // call listener + callListener("onRemoveNode", [familyID]); + return; } @@ -378,35 +537,18 @@ function GraphFlow(){ json[moveFrom].data.$xPos = nodeX; json[moveFrom].data.$yPos = nodeY; moveFrom++; - - // set pos of inputs - nodeX = x+ json[moveFrom].data.$relativeX; - nodeY = y+ json[moveFrom].data.$relativeY; + // set pos of ports for(var i= moveFrom; i<= moveUntil; i++){ node = fd.graph.getNode(json[i].id); - if(node.data.$type == "inputPort"){ - node.pos.setc(nodeX,nodeY); - json[i].data.$xPos = nodeX; - json[i].data.$yPos = nodeY; - nodeY+= dim; - } - else{ - moveFrom = i; - break; + + if(node.data.$relativeX != undefined){ + nodeX = x+ node.data.$relativeX; + nodeY = y+ node.data.$relativeY; } - } - - // set pos of outputs - nodeX = x+ json[moveFrom].data.$relativeX; - nodeY = y+ json[moveFrom].data.$relativeY; - - for(var o= moveFrom; o <= moveUntil; o++){ - node = fd.graph.getNode(json[o].id); node.pos.setc(nodeX,nodeY); - json[o].data.$xPos = nodeX; - json[o].data.$yPos = nodeY; - + json[i].data.$xPos = nodeX; + json[i].data.$yPos = nodeY; nodeY+= dim; } } @@ -419,40 +561,46 @@ function GraphFlow(){ * @return true if the edge was successfully added */ this.addEdge = function(sourceID, targetID){ - // edge from node to itself? - if(sourceID == targetID){ - Log.write("Error: Cannot connect Node to itself!"); - return false; - } - // look up the source node - var source = getNodeFromJSON(sourceID), - adja = source.adjacencies; + // look up the source node and the nodeFamilys of both nodes + var source, sourceFamilyID, targetFamilyID, lastFamilyID; + for(var n=0; n< json.length; n++){ + var loopNode = json[n]; + if(loopNode.data.$type == "nodeFamily"){ + lastFamilyID = loopNode.id + } + else if(loopNode.id == sourceID){ + source = loopNode; + sourceFamilyID = lastFamilyID; + } + else if(loopNode.id == targetID){ + targetFamilyID = lastFamilyID; + } + } + var adja = source.adjacencies; // source node does not exist? if(adja == undefined){ Log.write("Error: "+sourceID+" does not exist!"); return false; } - for(var a=0; a < adja.length; a++){ - // does the edge exist already? - if(adja[a].nodeTo == targetID){ - Log.write("Error: Edge("+ adja[a].nodeFrom+", "+adja[a].nodeTo+") exists already!" ); - return false; - } - } var edge ={ "nodeTo": targetID, "nodeFrom": sourceID, "data": {"$direction" : [ sourceID, targetID ] } }; + adja.push(edge); + if(sourceID == mouseNode.id || targetID == mouseNode.id){ edge.data.$type = "mouseArrow"; + return true; } - adja.push(edge); + // call listener (only if the edge is not connected to the dummy node!) + callListener("onCreateEdge", [sourceFamilyID, targetFamilyID, sourceID, targetID]); + return true; } @@ -461,13 +609,32 @@ function GraphFlow(){ @param source the source Node @param targetID the id of the target Node */ - this.removeEdge = function(source, targetID){ - var adja = source.adjacencies; + this.removeEdge = function(sourceID, targetID){ + var source = getNode(sourceID), + adja = source.adjacencies; // nothing to delete? if(adja == undefined){ return; } + + // look up nodeFamily IDs + var sourceFamilyID, targetFamilyID, lastFamilyID; + for(var n=0; n< json.length; n++){ + var loopNode = json[n]; + if(loopNode != undefined){ // <- this happens on Node Deletion + if(loopNode.data.$type == "nodeFamily"){ + lastFamilyID = loopNode.id + } + else if(loopNode.id == sourceID){ + sourceFamilyID = lastFamilyID; + } + else if(loopNode.id == targetID){ + targetFamilyID = lastFamilyID; + } + } + } + for(var a=0; a< adja.length; a++){ if(adja[a].nodeTo == targetID){ delete adja[a]; @@ -475,6 +642,11 @@ function GraphFlow(){ break; } } + + // call listener + if(sourceID != mouseNode.id && targetID != mouseNode.id){ + callListener("onRemoveEdge", [sourceFamilyID, targetFamilyID, sourceID, targetID]); + } } /** @@ -505,7 +677,7 @@ function GraphFlow(){ @param y - y-position of the node */ function saveNodePosition(nodeID, x, y){ - var node =getNodeFromJSON(nodeID); + var node = getNode(nodeID); node.data.$xPos = x; node.data.$yPos = y; } @@ -514,9 +686,10 @@ function GraphFlow(){ returns a jsonNode by id @param nodeID the id of the desired node */ - function getNodeFromJSON(nodeID){ + this.getNode = function(nodeID){ for(var n=0; n< json.length; n++){ - if(json[n].id == nodeID){ + var node = json[n]; + if(node != undefined && node.id == nodeID){ return json[n]; } } @@ -541,7 +714,16 @@ function GraphFlow(){ hover.setData('color', edgeColor, 'end'); } else{ - hover.setData('color', hover.data.$unfocusColor, 'end'); + var type = hover.data.$type; + if(type == "nodeFamily"){ + hover.setData('color', nodeStrokeColor[hover.data.$nodeType], 'end'); + } + else if(type == "crossBox"){ + hover.setData('color', nodeStrokeColor[hover.data.$family.data.$nodeType], 'end'); + } + else{ + hover.setData('color', nodeStrokeColor[type], 'end'); + } } hover = null; } @@ -601,7 +783,7 @@ function GraphFlow(){ // JSON structure. Node: { overridable: true, - color: nodeColor, + //color: "#FFFFFF", CanvasStyles: { lineWidth: 2 } @@ -616,10 +798,27 @@ function GraphFlow(){ }, //Native canvas text styling Label: { - type: "HTML", //Native or HTML + type: 'HTML', //Native or HTML size: 10, style: 'bold' - }, + }, + //Add Tips + Tips: { + enable: true, + type: 'Native', + onShow: function(tip, node) { + var message = node.data.$tooltip; + + // do not show a tooltip if it is undefined + if(message == undefined){ + tip.style.visibility = "hidden"; + } + else{ + tip.innerHTML = "<div class=\"tip-text\">" + message + "</div>"; + tip.style.visibility = "visible"; + } + } + }, // Add node events Events: { enable: true, @@ -630,21 +829,21 @@ function GraphFlow(){ * EVENT: OnRightClick */ onRightClick: function(node, eventInfo, e) { - callListener("onRightClick", node, eventInfo, e); + callListener("onRightClick", [node, eventInfo, e]); }, /** * EVENT: OnDragEnd */ onDragEnd: function(node, eventInfo, e) { - callListener("onDragEnd", node, eventInfo, e); + callListener("onDragEnd", [node, eventInfo, e]); }, /** * EVENT: OnDragCancel */ onDragCancel: function(node, eventInfo, e) { - callListener("onDragCancel", node, eventInfo, e); + callListener("onDragCancel", [node, eventInfo, e]); }, /** @@ -657,7 +856,9 @@ function GraphFlow(){ // ignore some highlights if we are dragging an edge if(selectedNode != null && - (node.data.$type != "inputPort" && node.data.$type != "outputPort" )){ + (node.data.$type != "inputPort" + && node.data.$type != "outputPort" + && node.data.$type != "repositoryPort")){ return; } @@ -672,7 +873,7 @@ function GraphFlow(){ }else{ fd.canvas.getElement().style.cursor = 'pointer'; } - callListener("onMouseEnter", node, eventInfo, e); + callListener("onMouseEnter", [node, eventInfo, e]); }, /** @@ -685,7 +886,7 @@ function GraphFlow(){ mouseNode.pos.setc(pos.x, pos.y-2); fd.plot(); } - callListener("onMouseMove", node, eventInfo, e); + callListener("onMouseMove", [node, eventInfo, e]); }, /** @@ -697,7 +898,7 @@ function GraphFlow(){ mouseOverEdge = false; setHighlight(null); - callListener("onMouseLeave", node, eventInfo, e); + callListener("onMouseLeave", [node, eventInfo, e]); }, /** @@ -716,7 +917,7 @@ function GraphFlow(){ mouseNode.data.$dragX = nodePos.x - mousePos.x; mouseNode.data.$dragY = nodePos.y - mousePos.y; - callListener("onDragStart", node, eventInfo, e); + callListener("onDragStart", [node, eventInfo, e]); }, /** @@ -732,7 +933,7 @@ function GraphFlow(){ moveNode(node, pos.x+mouseNode.data.$dragX, pos.y+mouseNode.data.$dragY); - callListener("onDragMove", node, eventInfo, e); + callListener("onDragMove", [node, eventInfo, e]); fd.plot(); }, @@ -758,49 +959,37 @@ function GraphFlow(){ removeNode(node.data.$family); refresh(); break; + case "nodeFamily": break; + case "outputPort": + case "inputPort": + case "repositoryPort": // no selection yet if(selectedNode == null){ - selectedNode = {"id": node.id, "from": "outputPort"}; + selectedNode = {"id": node.id, "from": node.data.$type}; // add edge to mouseNode - addEdge(node.id, mouseNode.id); - refresh(); - return; - } - // click outside the edge drag box - else{ - // add Edge if the selectedNode differs from the clickedNode - if(selectedNode.from == "outputPort"){ - Log.write("Error: Cannot connect two ports of the same type!"); - break; + if(selectedNode.from == "inputPort"){ + addEdge(mouseNode.id, node.id); } - var newEdgeAdded = addEdge(node.id, selectedNode.id); - if(newEdgeAdded){ - refresh(); + else{ + addEdge(node.id, mouseNode.id); } - } - break; - case "inputPort": - // no selection yet - if(selectedNode == null){ - selectedNode = {"id": node.id, "from": "inputPort"}; - - // add edge to mouseNode - addEdge(mouseNode.id, node.id); refresh(); return; } // click outside the edge drag box else{ // add Edge if the selectedNode differs from the clickedNode + var newEdgeAdded; if(selectedNode.from == "inputPort"){ - Log.write("Error: Cannot connect two ports of the same type!"); - break; + newEdgeAdded = addEdge(node.id, selectedNode.id); + } + else{ + newEdgeAdded = addEdge(selectedNode.id, node.id); } - var newEdgeAdded = addEdge(selectedNode.id,node.id); if(newEdgeAdded){ refresh(); } @@ -814,11 +1003,8 @@ function GraphFlow(){ if(selectedNode == null){ selectedNode = {"id":node.data.$direction[0], "from":"outputPort"};//nodeTo - var selectedNodeJson = getNodeFromJSON(selectedNode.id); - - Log.write(selectedNode.id+"->"+node.data.$direction[1]); // remove selectedEdge - removeEdge(selectedNodeJson, node.data.$direction[1]); + removeEdge(selectedNode.id, node.data.$direction[1]); // add edge to mouseNode addEdge(selectedNode.id, mouseNode.id); @@ -830,23 +1016,22 @@ function GraphFlow(){ // remove selection and mouse-edge upon clicking anywhere if(selectedNode != null){ - if(selectedNode.from == "outputPort"){ - var selectedNodeJson = getNodeFromJSON(selectedNode.id); - removeEdge(selectedNodeJson, mouseNode.id); + if(selectedNode.from == "inputPort"){ + removeEdge(mouseNode.id, selectedNode.id); }else{ - removeEdge(json[0], selectedNode.id); + removeEdge(selectedNode.id, mouseNode.id); } selectedNode = null; refresh(); } - callListener("onClick", node, eventInfo, e); + callListener("onClick", [node, eventInfo, e]); } }, //Number of iterations for the FD algorithm - iterations: 200, + iterations: 0, //Edge length - levelDistance: (longestEdge+2)*vertexLabelSize, + levelDistance: 2*vertexLabelSize, // This method is only triggered // on label creation and only for DOM labels (not native canvas ones). onCreateLabel: function(domElement, node){ @@ -864,32 +1049,46 @@ function GraphFlow(){ saveNodePosition(node.id, node.pos.x, node.pos.y); domElement.innerHTML = node.name; + + // set style for name var style = domElement.style; - /*var nameContainer = document.createElement('span'), - styleName = nameContainer.style; - - nameContainer.className = 'fTitle'; - nameContainer.innerHTML = node.name; - setLabelUnselectable(nameContainer); - domElement.appendChild(nameContainer);*/ - // style for nameContainer - style.fontSize = 1.65*vertexLabelSize+"px"; - style.left = 0.2*vertexLabelSize+"px"; - style.color = node.data.$color; - + style.fontSize = 1.65* fd.canvas.scaleOffsetX* vertexLabelSize+"px"; + style.left = 0.2*vertexLabelSize-domElement.offsetWidth/2+"px"; + setLabelUnselectable(domElement); }, // Change node styles when DOM labels are placed // or moved. onPlaceLabel: function(domElement, node){ + if(node.data.$type != "nodeFamily"){ + return; + } + var style = domElement.style; var left = parseInt(style.left); var top = parseInt(style.top); - var w = domElement.offsetWidth; - var h = domElement.offsetHeight; - style.left = (left - w / 2) + 'px'; - style.top = (top - h / 2) + 'px'; + var scale = fd.canvas.scaleOffsetX; + var w = scale*vertexLabelSize* node.name.length;//domElement.offsetWidth; + var h = scale*vertexLabelSize*1.65;//domElement.offsetHeight; + + // TODO: store more calculations inside a nodefamily! + var classWidth = scale*(vertexLabelSize-2)* (node.data.$nodeClass.length+2); + if(classWidth > w){ + w = classWidth; + } + + style.fontSize = scale* 1.65 * vertexLabelSize+"px"; + style.fontFamily = "Lucida Console"; + style.left = (left - w/ 2) + 'px'; + style.top = (top - h ) + 'px'; + style.color = node.data.$color; style.display = ''; + // add class name + if(node.data.$nodeClass != undefined){ + domElement.innerHTML = node.name; + var fontSize= scale*1.65*(vertexLabelSize-2); + domElement.innerHTML += "<div style=\"font-size:"+fontSize+"px\"> <"+node.data.$nodeClass+">"; + } } }); @@ -908,15 +1107,59 @@ function GraphFlow(){ function init(){ var addNodeCounter = 0; var graph = GraphFlow(); + // add a new listener: graph.addListener("onRightClick", function(node,info,e){ - graph.addNode(info.getPos().x, info.getPos().y, "lol"+addNodeCounter, ["ip1"], ["op1"]); + var newNode = {"id":"nuNode"+addNodeCounter, + "name":"i am node #"+addNodeCounter, + "nodeClass":"someClassAgain", + "tooltip":"i am new and shiny!"}; + graph.addFilter(info.getPos().x, info.getPos().y, newNode, null, + [{"name":"inputPort","id":"ip1"}], + [{"name":"outputPort", "id":"op1"}]); graph.refresh(); addNodeCounter++; }); + + // adds a listener that removes invalid Edges + graph.addEdgeConstraints(); + + graph.addListener("onRemoveNode", function(nodeID){alert("bye bye, "+ nodeID+".");}); - graph.addNode(0, -100, "superNode1", ["ip1","ip2","ip3","ip4","ip5","ip6"], ["op1"]); - graph.addNode(0, 100, "superNode2", ["ip1"], ["op1","op2"]); + // define graph by adding nodes + var node1 = {"id":"superNode1", + "name":"i am node", + "nodeClass":"someClass", + "tooltip":"look at me, i'm a node!"}; + + graph.addFilter(0, -100, node1,[{"name":"repoPort", "id":"rp1", "tooltip":"repo"}, + {"name":"repoPort", "id":"rp2", "tooltip":"repo"}, + {"name":"repoPort", "id":"rp3", "tooltip":"repo"}], + + [{"name":"inputPort","id":"ip1", "tooltip":"input goes here"}, + {"name":"inputPort","id":"ip2", "tooltip":"input goes here"}, + {"name":"inputPort","id":"ip3", "tooltip":"input goes here"}, + {"name":"inputPort","id":"ip4"}, + {"name":"inputPort","id":"ip5", "tooltip":"input goes here"}, + {"name":"inputPort","id":"ip6"}], + + [{"name":"outputPort", "id":"op1"}]); + + // create graph graph.initGraph(null); - graph.addNode(100, 0, "superNode3", ["ip1"], ["op1","op2"]); + + // add nodes after graph creation + var node2 = {"id":"superNode2", + "name":"i am node", + "nodeType":"Filter", + "nodeClass":"someOtherClass", + "tooltip":"look at me, i'm another node!"}; + graph.addRepository(100, 0, node2,{"name":"outputPort", "id":"op1"}); + var node3 = {"id":"superNode3", + "name":"i am reader", + "nodeType":"Reader", + "nodeClass":"someOtherClass", + "tooltip":"look at me, i'm another node!"}; + graph.addReader(0, 100, node3,[{"name":"repoPort", "id":"rp1"}], [{"name":"outputPort", "id":"op1"}]); + // refresh graph to show the new nodes graph.refresh(); } diff --git a/Kieker.WebGUI/src/main/webapp/js/jit.js b/Kieker.WebGUI/src/main/webapp/js/jit.js index eca35c3efa20c52b391c6aca144349e5b9618f0b..ccb742b49bf747caf95c1e04b80f55a9147d3eec 100644 --- a/Kieker.WebGUI/src/main/webapp/js/jit.js +++ b/Kieker.WebGUI/src/main/webapp/js/jit.js @@ -17672,7 +17672,7 @@ $jit.FlowGraph.$extend = true; pe2 = 2*pe, pe4 = 4*pe; - ctx.fillStyle = "#FFFFFF"; + ctx.fillStyle = node.getData('fillColor'); ctx.beginPath(); ctx.moveTo(bx-pe4, yTop); @@ -17699,6 +17699,64 @@ $jit.FlowGraph.$extend = true; } }, + /** + The port is a 2x1 square. The node.pos marks the position of the top-middle of the rectangle. + */ + 'repositoryPort': { + 'render': function(node, canvas){ + + var pos = node.pos.getc(true), + size = node.getData('dim'), + bx = pos.x, + by = pos.y, + ctx = canvas.getCtx(); + + // draw close-button area + this.nodeHelper.rectangle.render('fill', {x: bx, y: by+size/4}, size, size/2, canvas); + + var grid = size/8, + i = by + 2*grid, + b = by + grid, + h = grid/2, + j = h + grid, + k = by + 3*grid + h, + xh = bx + h, + xmh = bx - h; + + // draw R + ctx.fillStyle = "#FFFFFF"; + ctx.beginPath(); + ctx.moveTo(bx - j , by + h); + ctx.lineTo(xh , by + h); + ctx.lineTo(bx + j , b); + ctx.lineTo(bx + grid, i); + ctx.lineTo(xh , i); + ctx.lineTo(bx + j , k); + ctx.lineTo(xh , k); + ctx.lineTo(xmh , i); + ctx.lineTo(xmh , k); + ctx.lineTo(bx - j , k); + ctx.closePath(); + ctx.fill(); + + ctx.fillStyle = node.getData('color'); + ctx.beginPath(); + ctx.moveTo(xmh , b); + ctx.lineTo(xh + h/2, b); + ctx.lineTo(xh , by + j); + ctx.lineTo(xmh , by + j); + ctx.closePath(); + ctx.fill(); + + }, + 'contains': function(node, pos){ + + var npos = node.pos.getc(true), + size = node.getData('dim')/2; + return Math.abs(pos.x - npos.x) <= size && Math.abs(pos.y - npos.y-size) <= size; + } + }, + /** The port is a 2x1 square. The node.pos marks the position of the top-middle of the rectangle. */