diff --git a/Kieker.WebGUI/bin/data/Bookstore-Example/Bookstore-Example.kax b/Kieker.WebGUI/bin/data/Bookstore-Example/Bookstore-Example.kax index 27ecb97ae1b328afa3058cf967266c4439de0d97..4d4bfb3ca66f304cdb3567c36041527d4a69af98 100644 --- a/Kieker.WebGUI/bin/data/Bookstore-Example/Bookstore-Example.kax +++ b/Kieker.WebGUI/bin/data/Bookstore-Example/Bookstore-Example.kax @@ -1,18 +1,18 @@ <?xml version="1.0" encoding="UTF-8"?> <Project xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="platform:/resource/Kieker/model/AnalysisMetaModel.ecore"> <plugins xsi:type="Reader" name="FSReader" classname="kieker.analysis.plugin.reader.filesystem.FSReader"> - <properties name="inputDirs" value="testdata"/> - <properties name="ignoreUnknownRecordTypes" value="false"/> - <outputPorts name="monitoringRecords" subscribers="//@plugins.1/@inputPorts.0 //@plugins.4/@inputPorts.0"/> + <properties name="inputDirs" value="testdata" description="The name of the input dirs used to read data (multiple dirs are separated by |)."/> + <properties name="ignoreUnknownRecordTypes" value="false" description="Ignore unknown records? Aborts if encountered and value is false."/> + <outputPorts name="monitoringRecords" subscribers="//@plugins.1/@inputPorts.0 //@plugins.5/@inputPorts.0"/> </plugins> <plugins xsi:type="Filter" name="MyResponseTimeFilter" classname="kieker.examples.userguide.ch3and4bookstore.MyResponseTimeFilter"> - <properties name="thresholdNanos" value="1900000"/> - <outputPorts name="validResponseTimes" subscribers="//@plugins.5/@inputPorts.0 //@plugins.2/@inputPorts.0"/> - <outputPorts name="invalidResponseTimes" subscribers="//@plugins.6/@inputPorts.0 //@plugins.3/@inputPorts.0"/> + <properties name="thresholdNanos" value="1900000" description=""/> + <outputPorts name="validResponseTimes" subscribers="//@plugins.4/@inputPorts.0 //@plugins.6/@inputPorts.0"/> + <outputPorts name="invalidResponseTimes" subscribers="//@plugins.2/@inputPorts.0 //@plugins.3/@inputPorts.0"/> <inputPorts name="newResponseTime"/> </plugins> - <plugins xsi:type="Filter" name="Valid Printer" classname="kieker.examples.userguide.ch3and4bookstore.MyResponseTimeOutputPrinter"> - <properties name="validOutput" value="true"/> + <plugins xsi:type="Filter" name="Invalid Printer" classname="kieker.examples.userguide.ch3and4bookstore.MyResponseTimeOutputPrinter"> + <properties name="validOutput" value="false" description=""/> <inputPorts name="newEvent"/> </plugins> <plugins xsi:type="Filter" name="Invalid Counter" classname="kieker.analysis.plugin.filter.forward.CountingFilter"> @@ -21,27 +21,22 @@ <displays name="Counter Display"/> <inputPorts name="inputEvents"/> </plugins> - <plugins xsi:type="Filter" name="Global Counter" classname="kieker.analysis.plugin.filter.forward.CountingFilter"> + <plugins xsi:type="Filter" name="Valid Counter" classname="kieker.analysis.plugin.filter.forward.CountingFilter"> <outputPorts name="relayedEvents"/> <outputPorts name="currentEventCount"/> <displays name="Counter Display"/> <inputPorts name="inputEvents"/> </plugins> - <plugins xsi:type="Filter" name="Valid Counter" classname="kieker.analysis.plugin.filter.forward.CountingFilter"> + <plugins xsi:type="Filter" name="Global Counter" classname="kieker.analysis.plugin.filter.forward.CountingFilter"> <outputPorts name="relayedEvents"/> <outputPorts name="currentEventCount"/> <displays name="Counter Display"/> <inputPorts name="inputEvents"/> </plugins> - <plugins xsi:type="Filter" name="Invalid Printer" classname="kieker.examples.userguide.ch3and4bookstore.MyResponseTimeOutputPrinter"> - <properties name="validOutput" value="false"/> + <plugins xsi:type="Filter" name="Valid Printer" classname="kieker.examples.userguide.ch3and4bookstore.MyResponseTimeOutputPrinter"> + <properties name="validOutput" value="true" description=""/> <inputPorts name="newEvent"/> </plugins> <dependencies filePath="BookstoreApplication.jar"/> <dependencies filePath="commons-cli-1.2.jar"/> - <views name="My View" description="Some amazing view."> - <displayConnectors name="Valid Counter Display" display="//@plugins.5/@displays.0"/> - <displayConnectors name="Global Counter Display" display="//@plugins.4/@displays.0"/> - <displayConnectors name="Invalid Counter Display" display="//@plugins.3/@displays.0"/> - </views> </Project> diff --git a/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar b/Kieker.WebGUI/lib/kieker-1.6-SNAPSHOT_emf.jar index f0843203d315bb79a3cb532099ccad9ba42a78a3..be77cbabac36c423e26af3534303762c2d940bef 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/CurrentAnalysisEditorBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java index 0b435a3e3ad0ef71faac3e2c0002d7765df6ec1d..dc089c4ccded00a38555ac5af83301316ef17739 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorBean.java @@ -53,7 +53,6 @@ import kieker.analysis.model.analysisMetaModel.MIRepositoryConnector; import kieker.analysis.model.analysisMetaModel.impl.MAnalysisMetaModelFactory; import kieker.analysis.plugin.AbstractPlugin; import kieker.analysis.plugin.annotation.Property; -import kieker.analysis.plugin.reader.AbstractReaderPlugin; import kieker.analysis.repository.AbstractRepository; import kieker.common.logging.Log; import kieker.common.logging.LogFactory; @@ -811,7 +810,7 @@ public final class CurrentAnalysisEditorBean { mProperty.setName((String) this.classAndMethodContainer.getPropertyNameMethod().invoke(property, new Object[0])); mProperty.setValue((String) this.classAndMethodContainer.getPropertyDefaultValueMethod().invoke(property, new Object[0])); - + mProperty.setDescription((String) this.classAndMethodContainer.getPropertyDescriptionMethod().invoke(property, new Object[0])); plugin.getProperties().add(mProperty); } @@ -1007,7 +1006,7 @@ public final class CurrentAnalysisEditorBean { this.currentAnalysisEditorGraphBean.setCurrentAnalysisEditorBean(this); // Initialize the graph - this.currentAnalysisEditorGraphBean.initGraph(); + this.currentAnalysisEditorGraphBean.declareGraph(); // Initialize the reader, filter and repositories for (final MIPlugin plugin : this.project.getPlugins()) { @@ -1017,6 +1016,7 @@ public final class CurrentAnalysisEditorBean { this.currentAnalysisEditorGraphBean.addFilter((MIFilter) plugin); } } + this.currentAnalysisEditorGraphBean.initGraph(); for (final MIRepository repository : this.project.getRepositories()) { this.currentAnalysisEditorGraphBean.addRepository(repository); } @@ -1030,6 +1030,12 @@ public final class CurrentAnalysisEditorBean { } } + // TODO Connections between filters and repositories + + this.currentAnalysisEditorGraphBean.initListeners(); + + // This command is here necessary because of a current bug + // Repaint the graph this.currentAnalysisEditorGraphBean.refreshGraph(); } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorGraphBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorGraphBean.java index b75cfa927331b2fbad5cd685569bb1a1aa2c546f..d311e64a91a601f2e3865f1c3690746d3b1d7da7 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorGraphBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/beans/view/CurrentAnalysisEditorGraphBean.java @@ -59,10 +59,14 @@ public class CurrentAnalysisEditorGraphBean { * This is the log for errors, exceptions etc. */ private static final Log LOG = LogFactory.getLog(CurrentAnalysisEditorGraphBean.class); + /** + * This is the javascript code to declare the visual graph variable. + */ + private static final String JS_CMD_CREATE_GRAPH_VAR = "var graph = GraphFlow();"; /** * This is the javascript code to initialize the visual graph. */ - private static final String JS_CMD_INIT_GRAPH = "var graph = GraphFlow(); graph.initGraph(null);"; + private static final String JS_CMD_INIT_GRAPH = "graph.initGraph(null);"; /** * This is the javasscript code to add the click listener to the graph. */ @@ -131,15 +135,29 @@ public class CurrentAnalysisEditorGraphBean { // No code necessary } + /** + * Declares the the graph variable. + */ + public void declareGraph() { + RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_CREATE_GRAPH_VAR); + } + /** * Initializes the graph. */ public void initGraph() { RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_INIT_GRAPH); + + } + + /** + * Initializes the listeners for the graph + */ + public void initListeners() { RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_CLICK_LISTENER); RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_REMOVE_NODE_LISTENER); RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_CREATE_EDGE_LISTENER); - // RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_REMOVE_EDGE_LISTENER); + RequestContext.getCurrentInstance().execute(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_REMOVE_EDGE_LISTENER); } /** @@ -182,7 +200,8 @@ public class CurrentAnalysisEditorGraphBean { public void addRepository(final MIRepository repository) { final String repoPort = String.format(CurrentAnalysisEditorGraphBean.JS_CMD_PORT, CurrentAnalysisEditorGraphBean.JS_CMD_PORT_TYPE_INPUT, CurrentAnalysisEditorGraphBean.REPOSITORY_INPUT_PORT, "N/A"); - RequestContext.getCurrentInstance().execute(String.format(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_REPOSITORY, 0, 0, this.assembleGraphString(repository), + RequestContext.getCurrentInstance().execute(String.format(CurrentAnalysisEditorGraphBean.JS_CMD_ADD_REPOSITORY, 0, 0, + this.assembleGraphString(repository), repoPort)); } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/common/ClassAndMethodContainer.java b/Kieker.WebGUI/src/main/java/kieker/webgui/common/ClassAndMethodContainer.java index 209d63b2dea726cf5318bac525cd0da44f98d248..a9fc87687d650a49b71a7bf2bbd48c9b39e2febe 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/common/ClassAndMethodContainer.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/common/ClassAndMethodContainer.java @@ -214,6 +214,10 @@ public final class ClassAndMethodContainer { * This is the getState()-method of the class equivalence of {@link AnalysisController}. */ private Method analysisControllerGetState; + /** + * This is the description()-method of the class equivalence of {@link Property}. + */ + private final Method propertyDescriptionMethod; /** * This is the constructor for {@link AnalysisControllerThread}, which gets an instance of {@link AnalysisController}. */ @@ -268,6 +272,7 @@ public final class ClassAndMethodContainer { this.analysisControllerThreadStart = this.analysisControllerThreadClass.getMethod("start", new Class<?>[0]); this.analysisControllerThreadTerminate = this.analysisControllerThreadClass.getMethod("terminate", new Class<?>[0]); this.analysisControllerGetState = this.analysisControllerClass.getMethod("getState", new Class<?>[0]); + this.propertyDescriptionMethod = this.propertyAnnotationClass.getMethod("description", new Class<?>[0]); // This is a special case as we need to load some additional classes to search for the correct method final Class<?> miProjectClass = classLoader.loadClass(MIProject.class.getName()); @@ -633,6 +638,15 @@ public final class ClassAndMethodContainer { return this.analysisControllerGetState; } + /** + * The getter-method for the field {@link ClassAndMethodContainer#propertyDescriptionMethod}. + * + * @return The current value for the field. + */ + public Method getPropertyDescriptionMethod() { + return this.propertyDescriptionMethod; + } + /** * The getter-method for the field {@link ClassAndMethodContainer#analysisControllerThreadConstructor}. * diff --git a/Kieker.WebGUI/src/main/resources/kieker-1.6-SNAPSHOT_emf.jar b/Kieker.WebGUI/src/main/resources/kieker-1.6-SNAPSHOT_emf.jar index f0843203d315bb79a3cb532099ccad9ba42a78a3..be77cbabac36c423e26af3534303762c2d940bef 100644 Binary files a/Kieker.WebGUI/src/main/resources/kieker-1.6-SNAPSHOT_emf.jar and b/Kieker.WebGUI/src/main/resources/kieker-1.6-SNAPSHOT_emf.jar differ diff --git a/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml b/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml index bc81a429ff58670b1bfecf14fd7024a29f59ef8e..4d0ea6b1d48b7b26422276b3570988e38a20d30c 100644 --- a/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml +++ b/Kieker.WebGUI/src/main/webapp/AnalysisEditor.xhtml @@ -120,21 +120,27 @@ <p:dataTable editable="true" value="#{currentAnalysisEditorBean.advancedPluginProperties}" var="property" rowIndexVar="rowIndex" emptyMessage="No properties available" rendered="#{not empty currentAnalysisEditorBean.selectedPlugin}"> <p:column headerText="Property" style="width:125px"> <!-- The first property is always the classname, the second one always the normal name. After that, other properties can follow. --> - <h:outputText value="ClassName" rendered="#{rowIndex == 0}"/> - <h:outputText value="Name" rendered="#{rowIndex == 1}"/> - <h:outputText value="#{property.name}" rendered="#{rowIndex > 1}"/> + <h:outputText id="classNameProperty" value="ClassName" rendered="#{rowIndex == 0}"/> + <h:outputText id="nameProperty" value="Name" rendered="#{rowIndex == 1}"/> + <h:outputText id="normalProperty" value="#{property.name}" rendered="#{rowIndex > 1}"/> + <p:tooltip for="classNameProperty" value="The class name of this component." rendered="#{rowIndex == 0}"/> + <p:tooltip for="nameProperty" value="The name of this component." rendered="#{rowIndex == 1}"/> + <p:tooltip for="normalProperty" value="#{property.description}" rendered="#{rowIndex > 1}"/> </p:column> <!-- The classname is not editable, the name is editable with a specific target, other properies are editable normally. --> <p:column headerText="Value" style="width:125px"> - <h:outputText value="#{currentAnalysisEditorBean.selectedPlugin.classname}" rendered="#{rowIndex == 0}"/> - <p:inplace editor="true" rendered="#{rowIndex == 1}" > + <h:outputText id="className" value="#{currentAnalysisEditorBean.selectedPlugin.classname}" rendered="#{rowIndex == 0}"/> + <p:inplace id="nameEditor" editor="true" rendered="#{rowIndex == 1}" > <p:inputText value="#{currentAnalysisEditorBean.selectedPlugin.name}" /> <p:ajax event="save" listener="#{currentAnalysisEditorGraphBean.renameNode(currentAnalysisEditorBean.selectedPlugin, currentAnalysisEditorBean.selectedPlugin.name)}" /> </p:inplace> - <p:inplace editor="true" rendered="#{rowIndex > 1}"> + <p:inplace id="normalEditor" editor="true" rendered="#{rowIndex > 1}"> <p:inputText value="#{property.value}" /> </p:inplace> + <p:tooltip for="className" value="The class name of this component." rendered="#{rowIndex == 0}"/> + <p:tooltip for="nameEditor" value="The name of this component." rendered="#{rowIndex == 1}"/> + <p:tooltip for="normalEditor" value="#{property.description}" rendered="#{rowIndex > 1}"/> </p:column> </p:dataTable> </h:form> diff --git a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js index 2a4d90e24ca209347b13b62142fb837c7f8dff8f..d6940388c279c241d2dd62bbd3405e4b6fbc0bc6 100644 --- a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js +++ b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js @@ -46,19 +46,14 @@ function GraphFlow(){ /** Color Palettes */ var nodeFillColor = []; - //nodeFillColor['Filter'] = "#DEDEDE"; - //nodeFillColor['Repository'] = "#EFAC6A"; - //nodeFillColor['Reader'] = "#6AEF73"; nodeFillColor['nodeFamily'] = "#DEDEDE"; var nodeStrokeColor = []; - //nodeStrokeColor['Filter'] = "#4D4D4D"; - //nodeStrokeColor['Repository'] = "#735332"; - //nodeStrokeColor['Reader'] = "#327337"; nodeStrokeColor['inputPort'] = "#AA0000"; nodeStrokeColor['outputPort'] = "#AA0000"; nodeStrokeColor['repositoryPort'] = "#AA0000"; nodeStrokeColor['nodeFamily'] = "#4D4D4D"; + nodeStrokeColor['crossBox'] = "#4D4D4D"; var nodeColorFocus = "#0098BE", edgeColor = "#114270", @@ -76,7 +71,8 @@ function GraphFlow(){ /** This array stores all Nodes and is initialized with a dummy, used for drawing edges between a node and the mousepointer. */ - var json = [ { + var json = [ null, null, null, null, null, null, null, null, null, null, + { "adjacencies": [], "data": { "$dim": 0, @@ -89,6 +85,13 @@ function GraphFlow(){ "name": "", "alpha": 0 }]; + + /** Used for managing space for node creation/deletion + since the array is divided into to parts, adding and removing nodes + would cause many costly splices. We a similar approach to ArrayLists. + */ + var jsonCapacity = {'occupied' : 0, + 'max' : 10}; /** selected node: {id : <the id of the selected node, from : <the data.$type of the selected node>} @@ -281,9 +284,7 @@ function GraphFlow(){ */ this.refresh = function(){ // reload graph - fd.loadJSON(json); - // restore old node positions - restoreNodePositions(); + fd.loadGraphFlowJSON(json, jsonCapacity.max); fd.plot(); } @@ -366,8 +367,9 @@ function GraphFlow(){ this.addEdgeConstraints = function(){ addListener("onCreateEdge", function(sourceFamily, targetFamily, sourcePort, targetPort){ - // remove Edge if it leads to itself - if( sourcePort.id == targetPort.id){ + + // remove Edge if it does not lead to an inputPort + if(targetPort.data.$type != "inputPort"){ return false; } @@ -407,7 +409,12 @@ function GraphFlow(){ */ this.iterateAllNodes = function(nodeFunction){ var node; - for(var n=1; n < json.length; n++){ + for(var n=0, l=jsonCapacity.occupied; n < l; n++){ + node = json[n]; + nodeFunction(node); + } + alert(json.length); + for(var n=jsonCapacity.max+1, l= json.length; n < l; n++){ node = json[n]; nodeFunction(node); } @@ -423,18 +430,19 @@ function GraphFlow(){ nodeStrokeColor['outputPort'] = portColor; nodeStrokeColor['repositoryPort'] = portColor; - var node, type; - for(var n = 1; n < json.length; n++){ - node = json[n]; - type = node.data.$type; - - node.data.$color = nodeStrokeColor[type]; - - if(type == "nodeFamily" ||type == "crossBox"){ - node.data.$fillColor = nodeFillColor["nodeFamily"]; - node.data.$color = nodeStrokeColor["nodeFamily"]; - }else{ - node.data.$color = nodeStrokeColor[type]; + var data; + // change family colors + for(var n = 0, l = jsonCapacity.occupied; n < l; n++){ + data = json[n].data; + data.$fillColor = nodeFillColor["nodeFamily"]; + data.$color = nodeStrokeColor["nodeFamily"]; + } + // change port colors + for(var n = jsonCapacity.max+1, l = json.length; n < l; n++){ + data = json[n].data; + data.$color = nodeStrokeColor[data.$type]; + if(data.$type == "crossBox"){ + data.$fillColor = nodeFillColor["nodeFamily"]; } } refresh(); @@ -465,20 +473,6 @@ function GraphFlow(){ }*/ - /** - Moves all nodes to their respective positions, which are stored within - their data. We need this function to dynamically remove graph elements - without losing the node positions. - */ - function restoreNodePositions(){ - for (var n = 0; n < json.length; n++){ - var x = json[n].data.$xPos, - y = json[n].data.$yPos, - node = fd.graph.getNode(json[n].id); - node.pos.setc(x,y); - } - } - /** Adds a repository node to the graph. @param nodeFamily - describes some properties of the node: (id, name, nodeClass, tooltip) @@ -516,7 +510,22 @@ function GraphFlow(){ @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, nodeFamily, repositoryPorts, inputPorts, outputPorts, nodeType){ + this.addNode = function(xPosition, yPosition, nodeFamily, repositoryPorts, inputPorts, outputPorts, nodeType, forced){ + + // check if we have enough space reserved + var max = jsonCapacity.max; + var occ = jsonCapacity.occupied; + if(occ == max){ + var families = json.slice(0, max); + var ports = json.slice(max); + + for(var n = 0; n < max; n++){ + families.push(null); + } + max *= 2; + jsonCapacity.max = max; + json = families.concat(ports); + } var countRepo = 0, countInput = 0, @@ -557,8 +566,9 @@ function GraphFlow(){ "data": { "$dim": size, "$type": "nodeFamily", - "$jsonIndex": json.length, + "$jsonIndex": occ, "$portCount": countRepo+countInput+countOutput, + "$portIndex": json.length - max, "$width": width, "$height": height, "$color": strokeColor, @@ -572,7 +582,7 @@ function GraphFlow(){ "id": nodeFamily.id, "name": nodeFamily.name }; - json.push(newNode); + json[occ] = newNode; // add closeButton var closeButton ={ @@ -662,50 +672,53 @@ function GraphFlow(){ } } - // call listener - callListener("onCreateNode", [newNode]); + // call listener + if(!forced){ + callListener("onCreateNode", [newNode]); + } + jsonCapacity.occupied++; } /** Removes a Node and all its edges from the graph. @param nodeFamily - the node which is to be removed */ - this.removeNode = function(nodeFamily){ + this.removeNode = function(nodeFamily, forced){ // call listener - var deletionValid = callListener("onRemoveNode", [nodeFamily]); - - if(!deletionValid){ + if(!forced && !callListener("onRemoveNode", [nodeFamily])){ return; } - var deleteFrom = nodeFamily.data.$jsonIndex, - deleteUntil = deleteFrom+ nodeFamily.data.$portCount+1; - + var deleteFrom = nodeFamily.data.$portIndex + jsonCapacity.max, + deleteUntil = deleteFrom+ nodeFamily.data.$portCount, + familyIndex = nodeFamily.data.$jsonIndex, + occ = --jsonCapacity.occupied; // decrement occupied spaces - // delete nodeFamily and closeButton + // delete nodeFamily and close button + delete json[familyIndex]; delete json[deleteFrom]; - delete json[deleteFrom+1]; // delete ports - for(var n = deleteFrom+2; n <= deleteUntil; n++){ + for(var n = deleteFrom+1; n <= deleteUntil; n++){ var portID = json[n].id; // remove all incoming edges for(var e = 0; e < json.length; e++){ var incNode = json[e]; if(incNode != undefined){ - removeEdge(incNode.id, portID); + removeEdge(incNode.id, portID, true); } } delete json[n]; } - // collapse json array - var deletionSum = deleteUntil-deleteFrom+1; + // remove space that was used for ports + var deletionSum = deleteUntil-deleteFrom+1; json.splice(deleteFrom, deletionSum); selectedNode = null; + /* // update nodeFamily indices for(var n = deleteUntil-deletionSum+1; n < json.length; n++){ var familyIndex = json[n].data.$jsonIndex; @@ -713,7 +726,31 @@ function GraphFlow(){ json[n].data.$jsonIndex = n; } } + */ + + // update nodeFamily indices and remove the deletion gap + // in the array space by shifting all following families up + var data; + for(var n = familyIndex; n < occ; n++){ + json[n] = json[n+1]; + data = json[n].data; + data.$jsonIndex-- + data.$portIndex-= deletionSum; + + } + json[occ] = null; + + // check if we should remove some space + var max = jsonCapacity.max; + if( (occ*4) < max && max > 10){ + var ports = json.slice(max); + + max /= 2; + jsonCapacity.max = max; + var families = json.slice(0, max); + json = families.concat(ports); + } return; } @@ -724,20 +761,20 @@ function GraphFlow(){ @param y - desired y-Position */ function moveNode(nodeFamily, x, y){ - var moveFrom = nodeFamily.data.$jsonIndex, - moveUntil = moveFrom+ nodeFamily.data.$portCount+1, + var familyIndex = nodeFamily.data.$jsonIndex, + moveFrom = nodeFamily.data.$portIndex+ jsonCapacity.max, + moveUntil = moveFrom+ nodeFamily.data.$portCount, dim = nodeFamily.data.$dim; // set pos of family box nodeFamily.pos.setc(x, y); - json[moveFrom].data.$xPos = x; - json[moveFrom].data.$yPos = y; - moveFrom++; + json[familyIndex].data.$xPos = x; + json[familyIndex].data.$yPos = y; // set pos of close button var node = fd.graph.getNode(json[moveFrom].id), - nodeX = x+ nodeFamily.data.$width/2, - nodeY = y- nodeFamily.data.$height/2; + nodeX = x + nodeFamily.data.$width/2, + nodeY = y - nodeFamily.data.$height/2; node.pos.setc(nodeX, nodeY); json[moveFrom].data.$xPos = nodeX; @@ -766,27 +803,40 @@ function GraphFlow(){ * @param targetID the id of the node where the edge ends * @return true if the edge was successfully added */ - this.addEdge = function(sourceID, targetID, edgeLabel){ + this.addEdge = function(sourceID, targetID, edgeLabel, forced){ // look up the source node and the nodeFamilys of both nodes - var source, sourceFamily, targetFamily, lastFamily, target; - for(var n = 0; n< json.length; n++){ - var loopNode = json[n]; - if(loopNode.data.$type == "nodeFamily"){ - lastFamily = loopNode; + var source, sourceFamily, targetFamily, target; + + var sourceFamilyID = sourceID.substring(0, sourceID.indexOf(".")), + targetFamilyID = targetID.substring(0, targetID.indexOf(".")), + loopNode; + // look for nodefamilies + for(var n = 0, l = jsonCapacity.occupied; n < l; n++){ + + loopNode = json[n]; + + if(loopNode.id == sourceFamilyID){ + sourceFamily = loopNode } - else if(loopNode.id == sourceID){ + if(loopNode.id == targetFamilyID){ + targetFamily = loopNode + } + } + + // look for ports + for(var n = jsonCapacity.max, l = json.length; n < l; n++){ + loopNode = json[n]; + + if(loopNode.id == sourceID){ source = loopNode; - sourceFamily = lastFamily; } - else if(loopNode.id == targetID){ + if(loopNode.id == targetID){ target = loopNode; - targetFamily = lastFamily; } } - // call listener if the edge is not connected to the dummy node and check for permission - if(sourceID != mouseNode.id && targetID != mouseNode.id){ + if(!forced && sourceID != mouseNode.id && targetID != mouseNode.id){ if (!callListener("onCreateEdge", [sourceFamily, targetFamily, source, target])){ return false; } @@ -830,32 +880,46 @@ function GraphFlow(){ @param source the source Node @param targetID the id of the target Node */ - this.removeEdge = function(sourceID, targetID){ - var source, target; + this.removeEdge = function(sourceID, targetID, forced){ + // look up the source node and the nodeFamilys of both nodes + var source, sourceFamily, targetFamily, target; + + var sourceFamilyID = sourceID.substring(0, sourceID.indexOf(".")), + targetFamilyID = targetID.substring(0, targetID.indexOf(".")), + loopNode; - // look up nodeFamily IDs - var sourceFamily, targetFamily, lastFamily; - 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"){ - lastFamily = loopNode; - } - else if(loopNode.id == sourceID){ - sourceFamily = lastFamily; - source = loopNode; - } - else if(loopNode.id == targetID){ - targetFamily = lastFamily; - target = loopNode; - } + // look for ports + for(var n = jsonCapacity.max, l = json.length; n < l; n++){ + loopNode = json[n]; + if(loopNode != undefined){ + if(loopNode.id == sourceID){ + source = loopNode; + } + if(loopNode.id == targetID){ + target = loopNode; } } + } - // call listener and ask for permission to delete - if(sourceID != mouseNode.id && targetID != mouseNode.id){ - if(!callListener("onRemoveEdge", [sourceFamily, targetFamily, source, target])){ - return; + if(!forced){ + // look for nodefamilies + for(var n = 0, l = jsonCapacity.occupied; n < l; n++){ + + loopNode = json[n]; + + if(loopNode.id == sourceFamilyID){ + sourceFamily = loopNode + } + if(loopNode.id == targetFamilyID){ + targetFamily = loopNode + } + } + + // call listener and ask for permission to delete + if(sourceID != mouseNode.id && targetID != mouseNode.id){ + if(!callListener("onRemoveEdge", [sourceFamily, targetFamily, source, target])){ + return; + } } } @@ -896,18 +960,6 @@ function GraphFlow(){ } } - /** - saves the given x, y position of a node - @param nodeID - the id of the target node - @param x - x-position of the node - @param y - y-position of the node - */ - function saveNodePosition(nodeID, x, y){ - var node = getNode(nodeID); - node.data.$xPos = x; - node.data.$yPos = y; - } - /** returns a jsonNode by id @param nodeID the id of the desired node @@ -940,13 +992,7 @@ function GraphFlow(){ } else{ var type = hover.data.$type; - - if(type == "crossBox"){ - hover.setData('color', nodeStrokeColor["nodeFamily"], 'end'); - } - else{ - hover.setData('color', nodeStrokeColor[type], 'end'); - } + hover.setData('color', nodeStrokeColor[type], 'end'); } hover = null; } @@ -1154,8 +1200,8 @@ function GraphFlow(){ var pos = eventInfo.getPos(); moveNode(node, - pos.x+mouseNode.data.$dragX, - pos.y+mouseNode.data.$dragY); + pos.x+mouseNode.data.$dragX, + pos.y+mouseNode.data.$dragY); callListener("onDragMove", [node, eventInfo, e]); fd.plot(); @@ -1208,7 +1254,7 @@ function GraphFlow(){ // add Edge if the selectedNode differs from the clickedNode var newEdgeAdded; if(selectedNode.from == "inputPort"){ - var label = mouseNode.adjacencies[0].data.$label; + var label = selectedNode.label; newEdgeAdded = addEdge(node.id, selectedNode.id, label); } else{ @@ -1241,9 +1287,9 @@ function GraphFlow(){ if(selectedNode != null){ if(selectedNode.from == "inputPort"){ - removeEdge(mouseNode.id, selectedNode.id); + removeEdge(mouseNode.id, selectedNode.id, true); }else{ - removeEdge(selectedNode.id, mouseNode.id); + removeEdge(selectedNode.id, mouseNode.id, true); } selectedNode = null; @@ -1269,9 +1315,6 @@ function GraphFlow(){ return; } - // save node position - saveNodePosition(node.id, node.pos.x, node.pos.y); - domElement.innerHTML = node.name; // set style for name @@ -1317,7 +1360,7 @@ function GraphFlow(){ }); // load JSON data. - fd.loadJSON(json,0); + fd.loadGraphFlowJSON(json,0); // compute positions refresh(); // end @@ -1330,9 +1373,18 @@ function GraphFlow(){ // test data function init(){ var graph = GraphFlow(); + var nodecount=0; + graph.addListener("onRightClick", function(node, info, e){ + var node2 = {"id":"Node"+(nodecount++), + "name":"Supi", + "nodeClass":"Repository", + "tooltip":"look at me, i'm another node!"}; + graph.addRepository(100, 0, node2,{"name":"inputPort", "id":"ip1"}); + graph.refresh(); + }); /** These listeners add a "new"-flag to a freshly added node. New nodes cannot be deleted until saved with a right click **/ - + /* graph.addListener("onCreateNode", function(node){ node.data.$saved = false; }); @@ -1352,6 +1404,7 @@ function init(){ } return true; }); + */ // adds a listener that removes invalid Edges graph.addEdgeConstraints(); @@ -1391,6 +1444,7 @@ function init(){ graph.addReader(0, 100, node3,[{"name":"repoPort", "id":"rp1"}], [{"name":"outputPort", "id":"op1"}]); graph.addEdge("superNode1.rp1", "superNode2.ip1", "I'm a label!"); + // 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 09d7cb39abcae2b5cf61a8c4b94e1939029b79c0..951e6d3c33d1fa0966b4a5c055d4ba3b18512906 100644 --- a/Kieker.WebGUI/src/main/webapp/js/jit.js +++ b/Kieker.WebGUI/src/main/webapp/js/jit.js @@ -3986,7 +3986,7 @@ $jit.Graph = new Class({ <Graph.Node> */ - addNode: function(obj) { + addNode: function(obj) { if(!this.nodes[obj.id]) { var edges = this.edges[obj.id] = {}; this.nodes[obj.id] = new Graph.Node($.extend({ @@ -4003,6 +4003,15 @@ $jit.Graph = new Class({ return this.nodes[obj.id]; }, + /** A slight extension of addNode. Restores the node's positions for GraphFlow graphs */ + addGraphFlowNode: function(obj) { + var node = this.addNode(obj); + var x = node.data.$xPos, + y = node.data.$yPos; + node.pos.setc(x,y); + return node; + }, + /* Method: addAdjacence @@ -7806,7 +7815,7 @@ var Loader = { ans.addAdjacence(json[i], getNode(node), data); } } - } + } })(ans, json); return ans; @@ -7941,6 +7950,58 @@ var Loader = { this.root = json[i? i : 0].id; } }, + + /** Shamelessly copied from construct(), but accepts empty nodes as well*/ + constructGraphFlow: function(json) { + var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label); + //make graph + (function (ans, json) { + var getNode = function(id) { + for(var i=0, l=json.length; i<l; i++) { + if(json[i] != null && json[i].id == id) { + return json[i]; + } + } + // The node was not defined in the JSON + // Let's create it + var newNode = { + "id" : id, + "name" : id + }; + return ans.addGraphFlowNode(newNode); + }; + + for(var i=0, l=json.length; i<l; i++) { + if(json[i] != null){ + ans.addGraphFlowNode(json[i]); + var adj = json[i].adjacencies; + if (adj) { + for(var j=0, lj=adj.length; j<lj; j++) { + var node = adj[j], data = {}; + if(typeof adj[j] != 'string') { + data = $.merge(node.data, {}); + node = node.nodeTo; + } + ans.addAdjacence(json[i], getNode(node), data); + } + } + } + } + })(ans, json); + + return ans; + }, + + /** A specialized version that accepts null nodes */ + loadGraphFlowJSON: function(json, i) { + this.json = json; + //if they're canvas labels erase them. + if(this.labels && this.labels.clearLabels) { + this.labels.clearLabels(true); + } + this.graph = this.constructGraphFlow(json); + this.root = json[i].id; + }, /* Method: toJSON @@ -17261,7 +17322,7 @@ $jit.FlowGraph = new Class( { // initialize extras this.initializeExtras(); }, - + /* Method: refresh