diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/GraphLayoutServiceImpl.java b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/GraphLayoutServiceImpl.java index a26641578f87cf33ca20d1216e71e0959b5098a5..8126c89cb6b97ee8c1f82f7535fd304523371433 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/GraphLayoutServiceImpl.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/GraphLayoutServiceImpl.java @@ -17,7 +17,11 @@ package kieker.webgui.service.impl; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import org.springframework.stereotype.Service; @@ -29,7 +33,6 @@ import org.eclipse.emf.common.util.EList; import de.cau.cs.kieler.core.alg.BasicProgressMonitor; import de.cau.cs.kieler.core.alg.IKielerProgressMonitor; import de.cau.cs.kieler.core.kgraph.KEdge; -import de.cau.cs.kieler.core.kgraph.KLabeledGraphElement; import de.cau.cs.kieler.core.kgraph.KNode; import de.cau.cs.kieler.core.kgraph.KPort; import de.cau.cs.kieler.kiml.AbstractLayoutProvider; @@ -58,10 +61,97 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { // No code necessary } + /* + * (non-Javadoc) + * + * @see kieker.webgui.service.impl.IGraphLayoutService#layoutGraph(java.lang.String, java.lang.String) + */ + @Override + public String layoutGraph(final String nodes, final String edges) { + try { + final LayoutInformation layoutInformation = GraphLayoutServiceImpl.assembleLayoutInformation(nodes, edges); + + // Create a progress monitor + final IKielerProgressMonitor progressMonitor = new BasicProgressMonitor(); + + // Create the layout provider + final AbstractLayoutProvider layoutProvider = new LayeredLayoutProvider(); + + // Perform layout on the created graph + layoutProvider.doLayout(layoutInformation.getGraph(), progressMonitor); + + // convert layouted graph positions to String + final String layoutedGraph = GraphLayoutServiceImpl.getPositions(layoutInformation); + final String bendPoints = GraphLayoutServiceImpl.getBendPoints(layoutInformation); + + final StringBuilder sb = new StringBuilder("autoLayout#"); + sb.append(layoutedGraph); + sb.append("#"); + sb.append(bendPoints); + + return sb.toString(); + } catch (final GraphLayoutException e) { + e.printStackTrace(); + return "Error#Empty Graph!"; + } catch (final NumberFormatException e) { + e.printStackTrace(); + return "Error#Invalid Graph Information String!"; + } catch (final Exception e) { // NOCS + e.printStackTrace(); + return "Error#Unknown Error!"; + } + } + + /** + * Assembles the necessary layout information using the given parameters. + * + * @param nodesStr + * The string containing the information about the nodes. + * @param edgesStr + * The string containing the information about the edges. + * + * @return An object containing the layout information for the graph. + */ + private static LayoutInformation assembleLayoutInformation(final String nodesStr, final String edgesStr) throws NumberFormatException, GraphLayoutException { + final int firstSemicolon = nodesStr.indexOf(';'); + + // if there is no semicolon, we got an empty graph + if (firstSemicolon < 0) { + throw new GraphLayoutException(); + } + + final int scale = Integer.parseInt(nodesStr.substring(0, firstSemicolon)); + + final String[] nodeInfo = GraphLayoutServiceImpl.correctEmptyArray(nodesStr.substring(firstSemicolon + 1).split(";")); + final String[] edgeInfo = GraphLayoutServiceImpl.correctEmptyArray(edgesStr.split(";")); + + final Map<String, List<Object>> children = new HashMap<String, List<Object>>(); + final Object[][] edges = new Object[edgeInfo.length][5]; + final LayoutInformation layoutInformation = new LayoutInformation(KimlUtil.createInitializedNode(), children, edges); + + GraphLayoutServiceImpl.addNodes(scale, nodeInfo, layoutInformation); + GraphLayoutServiceImpl.addEdges(edgeInfo, layoutInformation); + return layoutInformation; + } + + /** + * Parses a String array, building a graph with nodes and fixed ports. + * + * @param scale + * The scale factor of the graph. + * @param nodeInfo + * Information about the nodes: isItCustom?, width, height, parent, portInformation + * @param layoutInformation + * The necessary information about the graph. + * + * @throws NumberFormatException + * The nodeInfo should mostly be space separated numbers. If it is however invalid, this + * Exception will be thrown + */ private static void addNodes(final int scale, final String[] nodeInfo, final LayoutInformation layoutInformation) throws NumberFormatException { - // define node and port list - final List<List<KLabeledGraphElement>> nodesAndPorts = layoutInformation.getNodesAndPorts(); - List<KLabeledGraphElement> nodeAndPortList; + // define node HashMap port list + final Map<String, List<Object>> nodesAndPorts = layoutInformation.getNodesAndPorts(); + List<Object> nodeAndPortList; // retrieve size modifier final int dimHalf = scale; @@ -70,7 +160,7 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { // loop variables // String[] nodeProps; KNode node; - int parentIndex; + String parentId; KNode parent; KPort port; KShapeLayout layout; @@ -89,54 +179,46 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { int lp; final int l = nodeInfo.length; - // -- 1. initialize empty nodes -- - for (n = 0; n < l; n++) { - // init node - node = KimlUtil.createInitializedNode(); + // nodeID : KNode, ParentID - // create a list that will contain the node itself - // as well as its ports (should they exist) - nodeAndPortList = new ArrayList<KLabeledGraphElement>(); - nodeAndPortList.add(node); - - // add the list to the global 2 dimensional list - nodesAndPorts.add(n, nodeAndPortList); - } - - // -- 2. Define nodes by the properties, given in the string -- + // -- 1. Define nodes by the properties, given in the string -- for (n = 0; n < l; n++) { - nodeProps = nodeInfo[n].split(" "); - - // get Node from global list - node = (KNode) nodesAndPorts.get(n).get(0); // i is our property array-pointer i = 0; + nodeProps = nodeInfo[n].split(" "); - // retrieve node information - isNodeFamily = "f".equals(nodeProps[i++]); - width = Integer.parseInt(nodeProps[i++]); - height = Integer.parseInt(nodeProps[i++]); + // add entry to map + nodeAndPortList = new ArrayList<Object>(); + nodesAndPorts.put(nodeProps[i++], nodeAndPortList); + // create empty node + node = KimlUtil.createInitializedNode(); + nodeAndPortList.add(node); - // determine the parent of the node - parentIndex = Integer.parseInt(nodeProps[i++]); - if (parentIndex > 0) { - parent = (KNode) nodesAndPorts.get(parentIndex).get(0); - node.setParent(parent); - } else { + // get parentID + parentId = nodeProps[i++]; + if ("-1".equals(parentId)) { + parentId = ""; node.setParent(layoutInformation.getGraph()); } + nodeAndPortList.add(parentId); + + // get width and height + width = Integer.parseInt(nodeProps[i++]); + height = Integer.parseInt(nodeProps[i++]); + + // get Custom Node tag + isNodeFamily = "f".equals(nodeProps[i++]); - // set node dimensions + // set width and height of KNode layout = node.getData(KShapeLayout.class); layout.setWidth(width); layout.setHeight(height); - // parse port data if it is a NodeFamily + // if node is a NodeFamily, go on and look for ports if (isNodeFamily) { - // let the node size be fixed for now - // (resizable nodeFamilies may be implemented in the future) + // nodeFamily size is fixed layout.setProperty(LayoutOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_POS); portX = -dimHalf; @@ -155,7 +237,7 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { // create port port = KimlUtil.createInitializedPort(); port.setNode(node); - nodesAndPorts.get(n).add(port); + nodeAndPortList.add(port); // set layout for Port layout = port.getData(KShapeLayout.class); @@ -171,26 +253,61 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { } } } + + // -- 2. resolve stored parentIDs to actual parents + final Iterator<Entry<String, List<Object>>> it = nodesAndPorts.entrySet().iterator(); + while (it.hasNext()) { + nodeAndPortList = (List<Object>) ((Entry) it.next()).getValue(); + + // get KNode and its parentID + node = (KNode) nodeAndPortList.get(0); + parentId = nodeAndPortList.get(1).toString(); + + // set other KNode as parent + if (!parentId.isEmpty()) { + parent = (KNode) nodesAndPorts.get(parentId).get(0); + node.setParent(parent); + } + } } + /** + * Parses an array which must contain quadruples of numbers.<br/> + * The first number must be the index of the children-array, where + * the source node is located.<br/> + * The second number must be the index of the children-array, where + * the target node is located.<br/> + * The third number must be the index of the source node List, where + * the source port is located.<br/> + * The fourth number must be the index of the target node List, where + * the target port is located. + * + * @param edgeInfo + * The single elements for the edges. + * @param layoutInformation + * The necessary information about the graph. + * @throws UninitializedGraphException + * If the graph has not yet been initialized. + */ private static void addEdges(final String[] edgeInfo, final LayoutInformation layoutInformation) throws NumberFormatException { - final List<List<KLabeledGraphElement>> nodesAndPorts = layoutInformation.getNodesAndPorts(); + final Map<String, List<Object>> nodesAndPorts = layoutInformation.getNodesAndPorts(); // set up loop variables String[] edgeProps; KEdge edge; - int id; + String id; boolean hasSourcePort; + Object[] edgeList; KNode sourceNode; int sourcePortIndex; KPort sourcePort = null; - List<KLabeledGraphElement> sourceList; + List<Object> sourceList; int targetPortIndex; KPort targetPort; - List<KLabeledGraphElement> targetList; + List<Object> targetList; int ea = 0; int p; @@ -200,48 +317,59 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { for (final String element : edgeInfo) { // get edge info of a single source + edgeList = layoutInformation.getEdges()[ea++]; edgeProps = element.split(" "); p = 0; // get source index and port - id = Integer.parseInt(edgeProps[p++]); + id = edgeProps[p++]; sourceList = nodesAndPorts.get(id); sourceNode = (KNode) sourceList.get(0); - sourcePortIndex = Integer.parseInt(edgeProps[p++]) + 1; - hasSourcePort = sourcePortIndex != 0; + sourcePortIndex = Integer.parseInt(edgeProps[p++]); + edgeList[2] = sourcePortIndex; + + hasSourcePort = ++sourcePortIndex != 0; if (hasSourcePort) { - sourcePort = (KPort) sourceList.get(sourcePortIndex); + sourcePort = (KPort) sourceList.get(++sourcePortIndex); } // get target ports epl = edgeProps.length; while (p < epl) { + // init edge + edge = KimlUtil.createInitializedEdge(); + edgeList[0] = edge; + edgeList[1] = id; + // get target index and port - id = Integer.parseInt(edgeProps[p++]); + id = edgeProps[p++]; + edgeList[3] = id; targetList = nodesAndPorts.get(id); - // define edge - edge = KimlUtil.createInitializedEdge(); + // select connected nodes edge.setSource(sourceNode); edge.setTarget((KNode) targetList.get(0)); - targetPortIndex = Integer.parseInt(edgeProps[p++]) + 1; - if (targetPortIndex != 0) { - targetPort = (KPort) targetList.get(targetPortIndex); + targetPortIndex = Integer.parseInt(edgeProps[p++]); + edgeList[4] = targetPortIndex; + + if (++targetPortIndex != 0) { + targetPort = (KPort) targetList.get(++targetPortIndex); // set target port of edge edge.setTargetPort(targetPort); targetPort.getEdges().add(edge); } + if (hasSourcePort) { // set source port of edge edge.setSourcePort(sourcePort); sourcePort.getEdges().add(edge); } - layoutInformation.getEdges().add(ea++, edge); + } } @@ -255,90 +383,45 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { } } - private static LayoutInformation assembleLayoutInformation(final String nodesStr, final String edgesStr) throws GraphLayoutException { - final int firstSemicolon = nodesStr.indexOf(';'); - - // if there is no semicolon, we got an empty graph - if (firstSemicolon < 0) { - throw new GraphLayoutException("Empty graph"); - } - - final int scale = Integer.parseInt(nodesStr.substring(0, firstSemicolon)); - - final String[] nodeInfo = GraphLayoutServiceImpl.correctEmptyArray(nodesStr.substring(firstSemicolon + 1).split(";")); - final String[] edgeInfo = GraphLayoutServiceImpl.correctEmptyArray(edgesStr.split(";")); - - final List<List<KLabeledGraphElement>> children = new ArrayList<List<KLabeledGraphElement>>(nodeInfo.length - 1); - final List<KEdge> edges = new ArrayList<KEdge>(edgeInfo.length); - final LayoutInformation layoutInformation = new LayoutInformation(KimlUtil.createInitializedNode(), children, edges); - - GraphLayoutServiceImpl.addNodes(scale, nodeInfo, layoutInformation); - GraphLayoutServiceImpl.addEdges(edgeInfo, layoutInformation); - - return layoutInformation; - } - - /* - * (non-Javadoc) + /** + * Reads the (layouted) positions of a constructed KGraph and writes them as integers to a string, seperating each number with a space. * - * @see kieker.webgui.service.impl.IGraphLayoutService#layoutGraph(java.lang.String, java.lang.String) + * @param layoutInformation + * The object containing the necessary information about the graph. + * + * @return The constructed string. */ - @Override - public String layoutGraph(final String nodes, final String edges) { - final String layoutedGraph; - final String bendPoints; - - try { - final LayoutInformation layoutInformation = GraphLayoutServiceImpl.assembleLayoutInformation(nodes, edges); - - // Create a progress monitor - final IKielerProgressMonitor progressMonitor = new BasicProgressMonitor(); - - // Create the layout provider - final AbstractLayoutProvider layoutProvider = new LayeredLayoutProvider(); - - // Perform layout on the created graph - layoutProvider.doLayout(layoutInformation.getGraph(), progressMonitor); - - // convert layouted graph positions to String - layoutedGraph = GraphLayoutServiceImpl.getPositions(layoutInformation); - bendPoints = GraphLayoutServiceImpl.getBendPoints(layoutInformation); - - } catch (final NumberFormatException e) { - throw new GraphLayoutException("Invalid Graph Information String", e); - } - - return layoutedGraph + "#" + bendPoints; - } - private static String getPositions(final LayoutInformation layoutInformation) { - - final List<List<KLabeledGraphElement>> nodesAndPorts = layoutInformation.getNodesAndPorts(); + final Map<String, List<Object>> nodesAndPorts = layoutInformation.getNodesAndPorts(); final StringBuilder sb = new StringBuilder(); - KShapeLayout layout; - KNode node; - int x; - int y; // position of node - int width; - int height; - for (final List<KLabeledGraphElement> list : nodesAndPorts) { - node = (KNode) list.get(0); - layout = node.getData(KShapeLayout.class); - x = Math.round(layout.getXpos() + (layout.getWidth() / 2)); + // Run through all available entries + for (final Map.Entry<String, List<Object>> entry : nodesAndPorts.entrySet()) { + final String id = entry.getKey(); + final List<Object> nodeAndPortList = entry.getValue(); - // hotFix: wrong nodeFamily position: needs to be half the height lower - if (list.size() > 1) { - y = Math.round(layout.getYpos() + (layout.getHeight())); + final KShapeLayout layout = ((KNode) nodeAndPortList.get(0)).getData(KShapeLayout.class); + + // Get the position of the node + final int posX = Math.round(layout.getXpos() + (layout.getWidth() / 2)); + final int posY; + // HotFix: wrong nodeFamily position: needs to be half the height lower + if (nodeAndPortList.size() > 2) { + posY = Math.round(layout.getYpos() + (layout.getHeight())); } else { - y = Math.round(layout.getYpos() + (layout.getHeight() / 2)); + posY = Math.round(layout.getYpos() + (layout.getHeight() / 2)); } - width = Math.round(layout.getWidth()); - height = Math.round(layout.getHeight()); - sb.append(x); + // Get the size of the node + final int width = Math.round(layout.getWidth()); + final int height = Math.round(layout.getHeight()); + + // Put everything into the string + sb.append(id); sb.append(" "); - sb.append(y); + sb.append(posX); + sb.append(" "); + sb.append(posY); sb.append(" "); sb.append(width); sb.append(" "); @@ -349,21 +432,46 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { return sb.toString(); } + /** + * This method iterates through all edges of the given object and creates a semicolon separated String containing bend points of all edges. + * + * @param layoutInformation + * The object containing the information about the graph and the layout. + * @return The string containing the bend points. + */ private static String getBendPoints(final LayoutInformation layoutInformation) { final StringBuilder sb = new StringBuilder(); + final Object[][] edges = layoutInformation.getEdges(); + + KEdge edge; + int e; + + final int l = edges.length; + final int el = 4; // iterate edges - for (final KEdge edge : layoutInformation.getEdges()) { + for (int i = 0; i < l; i++) { + e = 0; + edge = (KEdge) edges[i][e]; final KEdgeLayout layout = edge.getData(KEdgeLayout.class); final EList<KPoint> bendPoints = layout.getBendPoints(); + // add IDs and portIndices + while (++e < el) { + sb.append(edges[i][e].toString()); + sb.append(" "); + } + sb.append(edges[i][4].toString()); + // iterate bend points final int bl = bendPoints.size(); KPoint point; if (bl > 0) { + point = bendPoints.get(0); + sb.append(" "); sb.append(Math.round(point.getX())); sb.append(" "); sb.append(Math.round(point.getY())); @@ -391,8 +499,14 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { private static class LayoutInformation { private final KNode graph; - private final List<List<KLabeledGraphElement>> children; - private final List<KEdge> edges; + /** + * An array containing the child node and the ports from left to right. + */ + private final Map<String, List<Object>> children; + /** + * An array containing edges. This way we can return edge information in the same order. + */ + private final Object[][] edges; /** * Creates a new instance of this class. @@ -404,7 +518,7 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { * @param edges * The edges of the graph. */ - public LayoutInformation(final KNode graph, final List<List<KLabeledGraphElement>> children, final List<KEdge> edges) { + public LayoutInformation(final KNode graph, final Map<String, List<Object>> children, final Object[][] edges) { // NOPMD (array) this.graph = graph; this.children = children; this.edges = edges; @@ -416,16 +530,31 @@ public final class GraphLayoutServiceImpl implements IGraphLayoutService { } + /** + * Getter for the property {@link LayoutInformation#graph}. + * + * @return The current value of the property. + */ public KNode getGraph() { return this.graph; } - public List<List<KLabeledGraphElement>> getNodesAndPorts() { + /** + * Getter for the property {@link LayoutInformation#children}. + * + * @return The current value of the property. + */ + public Map<String, List<Object>> getNodesAndPorts() { return this.children; } - public List<KEdge> getEdges() { - return this.edges; + /** + * Getter for the property {@link LayoutInformation#edges}. + * + * @return The current value of the property. + */ + public Object[][] getEdges() { + return this.edges; // NOPMD (array) } } diff --git a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js index 803f22fb73d1d2859a29d6fc12d6f522ba5c9e76..0a086156a9a2a7c00ac0e5927595e307e12209fe 100644 --- a/Kieker.WebGUI/src/main/webapp/js/flowEditor.js +++ b/Kieker.WebGUI/src/main/webapp/js/flowEditor.js @@ -48,8 +48,6 @@ var Log = { }; -var testVar = "no"; - function GraphFlow(){ ///////////////////////////////////////////// // VARIABLES // @@ -62,14 +60,14 @@ function GraphFlow(){ /** Colors */ var nodeFillColor = []; - nodeFillColor['nodeFamily'] = "#DEDEDE"; + nodeFillColor['nodeFamily'] = "#DEDEDE"; var nodeStrokeColor = []; - nodeStrokeColor['inputPort'] = "#AA0000"; - nodeStrokeColor['outputPort'] = "#AA0000"; - nodeStrokeColor['repositoryPort'] = "#AA0000"; - nodeStrokeColor['nodeFamily'] = "#4D4D4D"; - nodeStrokeColor['crossBox'] = "#4D4D4D"; + nodeStrokeColor['inputPort'] = "#AA0000"; + nodeStrokeColor['outputPort'] = "#AA0000"; + nodeStrokeColor['repositoryPort'] = "#AA0000"; + nodeStrokeColor['nodeFamily'] = "#4D4D4D"; + nodeStrokeColor['crossBox'] = "#4D4D4D"; var nodeColorFocus = "#0098BE", edgeColor = "#114270", @@ -137,25 +135,44 @@ function GraphFlow(){ This information is needed to correctly draw the Grid and may come in handy in future functions. */ - var navi = { // screen position - 'centerX' : 0, - 'centerY' : 0, - // these determine when the screen should move with a dragged node - 'borderLeft' : 0, - 'borderRight' : 0, - 'borderTop' : 0, - 'borderBottom' : 0, - // memorize where we touch a node upon dragging - 'nodeOffsetX' : 0, - 'nodeOffsetY' : 0, - // how far a node will be dragged - 'dragX' : null, - 'dragY' : null}; + var navi = { + // screen position + 'centerX' : 0, + 'centerY' : 0, + // these determine when the screen should move with a dragged node + /* 'borderLeft' : 0, + 'borderRight' : 0, + 'borderTop' : 0, + 'borderBottom' : 0, + */ + // how far a the screen is dragged / where a dragged node is touched + 'dragX' : null, + 'dragY' : null}; + + var animation = { + 'enabled' : true, + 'duration' : 1000 + }; ///////////////////////////////////////////// // FUNCTIONS // ///////////////////////////////////////////// + + /** + * Enables / Disables graph animation. + * + * @param enabled - if true, animation is enabled + * @param duration - the duration of animations + */ + this.setAnimation = function(enabled, duration){ + + animation.enabled = enabled; + if(duration){ + animation.duration = duration; + } + } + /** * Measures the width of a text on the canvas * @@ -165,11 +182,13 @@ function GraphFlow(){ */ this.getTextWidth = function(text, font){ + /* // validate arguments if(!validArg("getTextWidth()", text, "string") || !validArg("getTextWidth()", font, "string")){ return; } + */ // get canvas context var ctx = fd.canvas.getCtx(), @@ -203,12 +222,17 @@ function GraphFlow(){ this.addCustomNode = function(xPosition, yPosition, node, type, forced){ // validate arguments - if(!validArg("addCustomNode()", xPosition, "number") || - !validArg("addCustomNode()", yPosition, "number") || - !validArg("addCustomNode()", type, "string") || - !validArgNode("addCustomNode()", node)){ + var myName = "addCustomNode()"; + + /* + if(!validArg(myName, xPosition, "number") || + !validArg(myName, yPosition, "number") || + !validArg(myName, type, "string") || + !validArgNode(myName, node) || + !validArgID(myName, node.id)){ return false; } + */ var nodeType = 'Custom'; @@ -262,12 +286,14 @@ function GraphFlow(){ */ this.zoom = function(factor){ + /* // check argument if(!validArg("zoom()", factor, "number")){ return; }else if(factor < 0){ callListener("onError", ["Error in function zoom(): Argument factor must be a positive number!"]); } + */ // zoom var canvas = fd.canvas; @@ -310,8 +336,6 @@ function GraphFlow(){ } } - - /** Changes one or more properties of an edge immediately. Converts label strings to arrays. @@ -322,11 +346,13 @@ function GraphFlow(){ */ this.setEdgeData = function(sourceID, targetID, properties){ + /* // check arguments if(!validArg("setEdgeData()", sourceID, "string") || !validArg("setEdgeData()", targetID, "string")){ return; } + */ var edge = fd.graph.getAdjacence(sourceID, targetID); setObjectData(edge, properties); @@ -341,10 +367,13 @@ function GraphFlow(){ must start with a $. */ this.setNodeData = function(nodeID, properties){ + + /* // check argument if(!validArg("setNodeData()", nodeID, "string")){ return; } + */ var node = fd.graph.getNode(nodeID); setObjectData(node, properties); @@ -511,14 +540,171 @@ function GraphFlow(){ */ this.autoLayout = function(){ - // link node IDs to their index in the graph - var indexMap = {}, - index = 0, - c; - // link port IDs to their family index - var portMap = {}; + var c, id, children, portMap = {}; + + // list all parents of custom nodes + var data, parentMap = {}; + iterateAllNodes(function(node){ + + id = node.id; + data = node.data; + + if(data.$custom){ + children = data.$children; + for(c in children){ + c = children[c]; + parentMap[c.id] = id; + } + } + }); + + var nodes = [vertexLabelSize]; + var edge, edgeMap = {}; + var nf, parentID, srcId; + var nodeY, height; + // map nodeIDs to indices + iterateAllNodes(function(node){ + + data = node.data; + height = data.$height; + c = data.$custom; + nf = data.$type == 'nodeFamily'; + + if(nf || c){ + id = node.id; + + // check for parent + if(!(parentID = parentMap[id])){ + parentID = -1; + } + localArr = [id, + parentID, + data.$width, + height]; + + // is Custom Node + if(c){ + localArr.push('c'); // custom tag + + // create entry in edges + if(!(edge = edgeMap[id])){ + edgeMap[id] = [id, -1]; + }else{ + edge[0] = id; + edge[1] = -1; + } + + // save edge info in edges-object + adja = node.adjacencies; + + for(a in adja){ + a = adja[a].data.$direction; + srcId = a[0]; + + // fill in info for all edges that have this node + // as a target + if(a[1] == id){ + + if(!edgeMap[srcId]){ + edgeMap[srcId] = ["", -1] + } + // add target info to edge + edgeMap[srcId].push(id, -1); + } + } + } + + // is NodeFamily + else{ + localArr.push('f'); // family tag + // inputCount, inputYPos, repoCount... + localArr.push(0,0, 0,0, 0,0); + + // iterate ports and children + children = data.$children; + nodeY = node.pos.y; + pIndex = -1; + + for(c in children){ + child = children[c]; + + // increment node count and get relative y position of the topmost ports + switch(child.data.$type){ + case "inputPort" : + if(localArr[5]++ == 0){ + localArr[6] = Math.round(child.pos.y - nodeY + height); + } + break; + + case "repositoryPort" : + if(localArr[7]++ == 0){ + localArr[8] = Math.round(child.pos.y - nodeY + height); + } + break; + + case "outputPort" : + if(localArr[9]++ == 0){ + localArr[10] = Math.round(child.pos.y - nodeY + height); + } + break; + + default : break; + } + + if(pIndex != -1){ + // create entry in edges + if(!(edge = edgeMap[child.id])){ + edgeMap[child.id] = [id, pIndex]; + }else{ + edge[0] = id; + edge[1] = pIndex; + } + + // save edge info in edges-object + adja = child.adjacencies; + + for(a in adja){ + a = adja[a].data.$direction; + srcId = a[0]; + + // fill in info for all edges that have this port + // as a target + if(a[1] == child.id){ + + if(!edgeMap[srcId]){ + edgeMap[srcId] = ["", -1] + } + // add target info to edge + edgeMap[srcId].push(id, pIndex); + } + } + } + pIndex++; + } + } + // convert node data to string and push it to Array + nodes.push(localArr.join(" ")); + } + }); + + // merge arrays to strings + var edges = []; + var t, source; + for(c in edgeMap){ + c = edgeMap[c]; + + if(c.length > 2){ + source = c[0] + " " + c[1]; + t = 2; + while(t < c.length){ + target = " " + c[t++] + " " + c[t++]; + edges.push(source + target); + } + } + } + /* // map nodeIDs to indices iterateAllNodes(function(node){ @@ -680,7 +866,7 @@ function GraphFlow(){ // convert node data to string and push it to Array nodes.push(localArr.join(" ")); - } + }*/ // fuse nodes array to a single string nodes = nodes.join(";"); @@ -704,136 +890,188 @@ function GraphFlow(){ this.loadPositions = function(input){ console.log(input); + // check if animation is in progress + var isAnimated = animation.enabled; + var usePortIndex; var edgeInfo, nodeInfo; + var p = 0; + { var inputSplit = input.split("#"); - // catch Java Errors here if(inputSplit[0] == "Error"){ callListener("onError", ["Error in GraphFlowLayouter.jar : " + inputSplit[1]]); return; } - nodeInfo = inputSplit[0].split(";"); + // check to see if we got the layout string from + // the autolayouter, because it looks a bit different + usePortIndex = inputSplit[0] == "autoLayout"; + if(usePortIndex){ + p++; + } + nodeInfo = inputSplit[p++].split(";"); // bend points are optional - if(inputSplit[1] && inputSplit[1].length > 0){ - edgeInfo = inputSplit[1].split(";"); + if(inputSplit[p] && inputSplit[p].length > 0){ + edgeInfo = inputSplit[p].split(";"); } } - // link node IDs to their index in the graph - var indexMap = {}, - index = 0, - c; - - // map indices to nodeIDs - iterateAllNodes(function(node){ - - c = node.data.$custom; - if(c || node.data.$type == 'nodeFamily'){ - indexMap[index] = [c, node]; - index++; - } - }); - // prepare loop variables - var node, data, adja, x, y, p = 0; + var node, data, adja, x, y; - var width, height; + var left = Number.POSITIVE_INFINITY, + right = Number.NEGATIVE_INFINITY, + top = Number.POSITIVE_INFINITY, + bottom = Number.NEGATIVE_INFINITY; + var id, width, height; var xOffset = -grid.data.$width/2, yOffset = -grid.data.$height/2; var nodeProps; - var gn, n, l; + var n, l; + + var endPos = new $jit.Complex(0,0); + var g = fd.graph; - // iterate nodefamiles + // iterate node positions for(n = 0, l = nodeInfo.length - 1; n < l; n++){ - // catch a layout String which is too long - if(!indexMap[n]){ - callListener("onError", ["Error in loadGraphPositions() : Non-existent Nodes in layout string!"]); - fd.plot(); - return; - } - nodeProps = nodeInfo[n].split(" "); p = 0; - // extract positions from string array - x = parseFloat(nodeProps[p++]) + xOffset; - y = parseFloat(nodeProps[p++]) + yOffset; - width = parseFloat(nodeProps[p++]); - height = parseFloat(nodeProps[p]); + // lookup node in graph + id = nodeProps[p++]; + node = g.getNode(id); - // find node in graph and move/resize it - node = indexMap[n][1]; - - // is custom node - if(indexMap[n][0]){ - node.pos.setc(x, y); - node.setData('width', width, 'end'); - node.setData('height', height, 'end'); + if(!node){ + callListener("onError", ["Error in loadGraphPositions() : Node with ID '"+ id + "' does not exist!"]); } - // is nodeFamily else{ - moveNode(node, x, y); - } + // extract positions from string array + x = parseFloat(nodeProps[p++]) + xOffset; + y = parseFloat(nodeProps[p++]) + yOffset; + width = parseFloat(nodeProps[p++]); + height = parseFloat(nodeProps[p]); + + // calculate new center of graph + left = Math.min(left, x - width / 2); + right = Math.max(right, x + width / 2); + top = Math.min(top, y - height / 2); + bottom = Math.max(bottom, y + height / 2); + + // is custom node + if(node.data.$custom){ + + // set new position and dimensions + if(isAnimated){ + endPos.x = x; + endPos.y = y; + node.setPos(endPos, "end"); + node.setData('width', width, 'end'); + node.setData('height', height, 'end'); + }else{ + node.pos.setc(x, y); + node.data.$width = width; + node.data.$height = height; + } + } + // is nodeFamily + else{ + moveNode(node, x, y, isAnimated); + } + } } if(edgeInfo){ - var adja, a; - - var e = 0; - var b, bendArray, bendPoints; - var el = edgeInfo.length; - - // iterate all nodes, looking for adjacencies - iterateAllNodes(function(node){ + var adja; + var from, to; + var bl, b, bendArray, bendPoints; + var portIndex; + + for(n = 0, l = edgeInfo.length - 1; n < l; n++){ + bendArray = edgeInfo[n].split(" "); + bl = bendArray.length; + b = 0; - // stop if we have more edges than - // defined by out layout string - if(e >= el){ - fd.plot(); - return; - } + // get portIDs, using the familyID and the portIndex + if(usePortIndex){ + node = g.getNode(bendArray[b++]); + data = node.data; + if(data.$custom){ + from = node.id; + }else{ + portIndex = parseInt(bendArray[b]); + from = data.$children[++portIndex].id + } + b++; + + node = g.getNode(bendArray[b++]); + data = node.data; + if(data.$custom){ + to = node.id; + }else{ + portIndex = parseInt(bendArray[b]); + to = data.$children[++portIndex].id + } + b++; - adja = node.adjacencies; + // simply read the exact portIDs + }else{ + from = bendArray[b++]; + to = bendArray[b++]; + } - for(a in adja){ - a = adja[a].data.$direction; - if(a[0] == node.id){ - if(edgeInfo[e] != "" ){ - // read from bend point string - bendArray = edgeInfo[e].split(" "); - bendPoints = []; - b = 0; - l = bendArray.length; - - while(b < l && bendArray[b] != "" ){ - x = parseFloat(bendArray[b++]) + xOffset; - y = parseFloat(bendArray[b++]) + yOffset; - bendPoints.push({'x':x, 'y':y}); - } - setBendPoints(a[0], a[1], bendPoints); - }else{ - setBendPoints(a[0], a[1], null); + adja = g.getAdjacence(from, to); + if(!adja){ + callListener("onError", ["Error in loadGraphPositions() : Edge between '" + + from + "' and '" + to + "' does not exist!"]); + }else{ + // new bend points exist + if(b < bl){ + + // read from bend point string + bendPoints = []; + while(b < bl){ + x = parseFloat(bendArray[b++]) + xOffset; + y = parseFloat(bendArray[b++]) + yOffset; + bendPoints.push({'x':x, 'y':y}); } - e++; + adja.data.$bendPoints = bendPoints; + } + // remove bend points + else{ + adja.data.$bendPoints = null; } } - }); + } + } - /* - fd.animate({ - modes: ['node-property:width:height'], - transition: $jit.Trans.Cubic.easeInOut, - duration: 1000 - }); */ - fd.plot(); + // translation values + var scale = fd.canvas.scaleOffsetX, + midX = - (left + right) / 2, + midY = - (top + bottom) / 2; + + if(isAnimated){ + fd.canvas.translateOffsetXEnd = midX * scale; + fd.canvas.translateOffsetYEnd = midY * scale; + + fd.animate({ + modes: ['node-property:width:height', 'linear', 'canvas'], + transition: $jit.Trans.Cubic.easeInOut, + duration: animation.duration + }); + } + else{ + midX -= fd.canvas.translateOffsetX/scale; + midY -= fd.canvas.translateOffsetY/scale; + fd.canvas.translate(midX, midY); + fd.plot(); + } return; } @@ -862,6 +1100,7 @@ function GraphFlow(){ nodePos = node.pos.getc(true); nodeProps = [ + node.id, Math.round(nodePos.x), Math.round(nodePos.y), data.$width, @@ -876,8 +1115,13 @@ function GraphFlow(){ data = adja[a].data; if(data.$direction[0] == node.id){ - // extract bendPoints edgeProps = []; + + // save connected ids + edgeProps.push(node.id); + edgeProps.push(data.$direction[1]); + + // extract bendPoints bendPoints = data.$bendPoints; for(b in bendPoints){ @@ -885,7 +1129,9 @@ function GraphFlow(){ edgeProps.push(bendPoints[b].y); } - edgeLayout.push(edgeProps.join(" ")); + if(edgeProps.length > 2){ + edgeLayout.push(edgeProps.join(" ")); + } } } }); @@ -910,8 +1156,8 @@ function GraphFlow(){ @returns - false if the string is not a well formed color string */ this.validArgColor = function(callFunction, color){ - var valid = false; + var valid = false; // is it a string at all ? if(typeof color == "string"){ // does the string match "#RRGGBB" ? @@ -937,11 +1183,13 @@ function GraphFlow(){ */ this.validArgNode = function(callFunction, node){ var valid = false; + var id = node.id; // is it an object at all ? + if(node && typeof node == "object"){ // does the object have an id - valid = (typeof node.id != "undefined"); + valid = (typeof id == "string"); } if(!valid){ @@ -951,7 +1199,13 @@ function GraphFlow(){ return valid; } - var id = node.id; + valid = id[0] != "#"; + if(!valid){ + callListener("onError", ["Error in function "+ callFunction + + ": Invalid NodeID '"+ id + + "'. IDs may not start with #."]); + return valid; + } return valid; } @@ -968,7 +1222,6 @@ 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 + "."]); @@ -976,16 +1229,44 @@ function GraphFlow(){ return valid; } + + /** + Checks if a node ID is correct and non-existent in the graph. + Sends an onError-event if the argument ID not valid. + @param callFunction - the function where the argument needs to be checked + @param id - the id which is checked + @returns - false if the id is not a string or exists already + */ + this.validArgID = function(callFunction, id){ + + // check if id is a string first + if (!validArg(callFunction, id, "string")){ + return false; + } + + // check if node id is occupied + var nodeExists = fd.graph.getNode(id); + if(nodeExists){ + callListener("onError", ["Error in function "+ callFunction + + ": Duplicate Node ID '" + + id + "'."]); + } + return !nodeExists; + } + /** Changes the color of the grid. @param newColor - the new color of the grid */ this.setGridColor = function(newColor){ - if(validArgColor('setGridColor()', newColor)){ - - grid.data.$color = newColor; - fd.plot(); + /* + if(!validArgColor('setGridColor()', newColor)){ + return; } + */ + + grid.data.$color = newColor; + fd.plot(); } /** @@ -993,10 +1274,14 @@ function GraphFlow(){ @param newSize - the new size of the grid */ this.setGridSize = function(newSize){ - if(validArg('setGridSize()', newSize, 'number')){ - grid.data.$dim = newSize; - fd.plot(); + /* + if(!validArg('setGridSize()', newSize, 'number')){ + return; } + */ + grid.data.$dim = newSize; + fd.plot(); + } /** @@ -1012,8 +1297,29 @@ function GraphFlow(){ @param visibility - must be true to display the graph */ this.setGridVisible = function(visibility){ - grid.data.$visible = visibility; - fd.plot(); + + isAnimated = animation.enabled; + var setter = 'current'; + + if(isAnimated){ + setter = 'end'; + } + + if(visibility){ + grid.setData('alpha', 1, setter); + }else{ + grid.setData('alpha', 0, setter); + } + + if(isAnimated){ + fd.animate({ + modes: ['node-property:alpha'], + transition: $jit.Trans.Cubic.easeInOut, + duration: animation.duration + }); + }else{ + fd.plot(); + } } /** @@ -1021,81 +1327,82 @@ function GraphFlow(){ in which it is drawn. */ this.scaleToFit = function(){ - // do nothing if the graph is empty - if(fd.graph.nodes.length == 1){ - return; - } - - // init loop variables - var data, - width = 0, - height = 0; - - var left = Number.POSITIVE_INFINITY, - right = Number.NEGATIVE_INFINITY, - top = Number.POSITIVE_INFINITY, - bottom = Number.NEGATIVE_INFINITY; - - // init the outer bounds of the displayed graph - var leftMost = left, - rightMost = right, - topMost = top, - bottomMost = bottom; - - // check nodeFamilies for position - iterateAllNodes(function(node){ - data = node.data; + // do nothing if the graph is empty + if(fd.graph.nodes.length == 2){ + return; + } - if(data.$custom || data.$type == "nodeFamily"){ + // init loop variables + var data, + width = 0, + height = 0; - width = data.$width/2; - height = data.$height/2; + var left = Number.POSITIVE_INFINITY, + right = Number.NEGATIVE_INFINITY, + top = Number.POSITIVE_INFINITY, + bottom = Number.NEGATIVE_INFINITY; - left = node.pos.x - width, - right = node.pos.x + width, - top = node.pos.y - height, - bottom = node.pos.y + height; + // init the outer bounds of the displayed graph + var leftMost = left, + rightMost = right, + topMost = top, + bottomMost = bottom; + + // check nodeFamilies for position + iterateAllNodes(function(node){ + data = node.data; - // update bounds - if(left < leftMost){ - leftMost = left; - } - if(right > rightMost){ - rightMost = right; - } - if(top < topMost){ - topMost = top; - } - if(bottom > bottomMost){ - bottomMost = bottom; + if(data.$custom || data.$type == "nodeFamily"){ + + width = data.$width/2; + height = data.$height/2; + + left = node.pos.x - width, + right = node.pos.x + width, + top = node.pos.y - height, + bottom = node.pos.y + height; + + // update bounds + if(left < leftMost){ + leftMost = left; + } + if(right > rightMost){ + rightMost = right; + } + if(top < topMost){ + topMost = top; + } + if(bottom > bottomMost){ + bottomMost = bottom; + } } - } - }); - - // scaling values - var canvas = fd.canvas, - oldScale = canvas.scaleOffsetX, - scaleX = grid.data.$width / (rightMost - leftMost + 4*vertexLabelSize), - scaleY = grid.data.$height / (bottomMost - topMost + 4*vertexLabelSize) - scale = Math.min(scaleX, scaleY) / oldScale; + }); - // translation values - var midX = (leftMost + rightMost) / 2, - midY = (topMost + bottomMost) / 2 - vertexLabelSize; + // scaling values + var canvas = fd.canvas, + oldScale = canvas.scaleOffsetX, + scaleX = grid.data.$width / (rightMost - leftMost + 4*vertexLabelSize), + scaleY = grid.data.$height / (bottomMost - topMost + 4*vertexLabelSize) + scale = Math.min(scaleX, scaleY) / oldScale; + + // translation values + var midX = (leftMost + rightMost) / 2, + midY = (topMost + bottomMost) / 2; + - - // translate to origin, scale, and translate to the center of the graph - canvas.translate( -canvas.translateOffsetX/oldScale, -canvas.translateOffsetY/oldScale); - canvas.scale(scale, scale); - canvas.translate( -midX, -midY); - - // show text only if it is big enough - canvas.showLabels = (canvas.scaleOffsetX > canvas.labelThreshold); - - fd.plot(); + // translate to origin, scale, and translate to the center of the graph + canvas.translate( -canvas.translateOffsetX/oldScale, -canvas.translateOffsetY/oldScale); + canvas.scale(scale, scale); + canvas.translate( -midX, -midY); + + // show text only if it is big enough + canvas.showLabels = (canvas.scaleOffsetX > canvas.labelThreshold); + + fd.plot(); } + /** Assigns an iconPath to a type of node. @param nodeType - the type of the node. Typically 'Repository', @@ -1109,9 +1416,11 @@ function GraphFlow(){ */ this.setNodeIcon = function(nodeType, path, resizeImmediately){ // TODO: give refrence instead of icon to nodes + /* if(!validArg('setNodeIcon()', nodeType, 'string')){ return; } + */ if(path == null){ delete nodeIcons[nodeType]; @@ -1133,9 +1442,11 @@ function GraphFlow(){ return; } + /* if(!validArg('setNodeIcon()', path, 'string')){ return; } + */ var prevImage = nodeIcons[nodeType]; @@ -1194,8 +1505,13 @@ function GraphFlow(){ this.setMouseCursor = function(newCursor){ if(newCursor == null){ fd.canvas.getElement().style.cursor = ''; - - }else if(validArg('setMouseCursor()', newCursor, 'string')){ + } + /* + else if(!validArg('setMouseCursor()', newCursor, 'string')){ + return; + } + */ + else{ fd.canvas.getElement().style.cursor = newCursor; } } @@ -1222,11 +1538,13 @@ function GraphFlow(){ @param listenerFunction - a function that may alter the graph */ this.addListener = function(eventName, listenerFunction){ + /* // check arguments if(!validArg('addListener()', eventName, 'string') || !validArg('addListener()', listenerFunction, 'function')){ return; } + */ if(listener[eventName] == undefined){ listener[eventName] = []; @@ -1313,15 +1631,16 @@ function GraphFlow(){ @param nodeFunction - a single parameter function, called on a node. */ this.iterateAllNodes = function(nodeFunction){ - + /* // check arguments if(!validArg('iterateAllNodes()', nodeFunction, 'function')){ return; } + */ // filter out dummy nodes fd.graph.eachNode(function(node){ - if(node && node.id != "#DUMMY_MOUSE_NODE" && node.id != "#DUMMY_GRID_NODE"){ + if(node && node.id[0] != "#"){ nodeFunction(node); } }); @@ -1337,20 +1656,20 @@ function GraphFlow(){ @param highlightColor - the of highlighted objects. If null, restore Edge Color from global Array. */ this.setNodeStyle = function(nodeType, fillColor, strokeColor, edgeColor, focusColor){ - if(validArg('setNodeStyle()', nodeType, 'string')){ - - if(fillColor && validArgColor('setNodeStyle()', fillColor)){ - nodeFillColor[nodeType] = fillColor; - } - if(strokeColor && validArgColor('setNodeStyle()', strokeColor)){ - nodeStrokeColor[nodeType] = strokeColor; - } + if(!validArg('setNodeStyle()', nodeType, 'string')){ + return; } - if(edgeColor && validArgColor('setNodeStyle()', edgeColor)){ + if(fillColor /*&& validArgColor('setNodeStyle()', fillColor)*/){ + nodeFillColor[nodeType] = fillColor; + } + if(strokeColor /*&& validArgColor('setNodeStyle()', strokeColor)*/){ + nodeStrokeColor[nodeType] = strokeColor; + } + if(edgeColor /*&& validArgColor('setNodeStyle()', edgeColor)*/){ edgeColor = edgeColor; } - if(focusColor && validArgColor('setNodeStyle()', focusColor)){ + if(focusColor /*&& validArgColor('setNodeStyle()', focusColor)*/){ edgeColorFocus = focusColor; nodeColorFocus = focusColor; } @@ -1427,25 +1746,26 @@ function GraphFlow(){ if(repositoryPorts){ countRepo = repositoryPorts.length; // check if it is an array - if(!validArg(caller, repositoryPorts, 'object') || !countRepo){ + if(/*!validArg(caller, repositoryPorts, 'object') ||*/ !countRepo){ countRepo = 0; } } if(inputPorts){ countInput = inputPorts.length; // check if it is an array - if(!validArg(caller, inputPorts, 'object') || !countInput){ + if(/*!validArg(caller, inputPorts, 'object') ||*/ !countInput){ countInput = 0; } } if(outputPorts){ countOutput = outputPorts.length; // check if it is an array - if(!validArg(caller, outputPorts, 'object') || !countOutput){ + if(/*!validArg(caller, outputPorts, 'object') ||*/ !countOutput){ countOutput = 0; } } + /* // check for other illegal arguments var caller = 'addNode()'; if(!validArg(caller, xPosition, 'number')){ @@ -1460,6 +1780,10 @@ function GraphFlow(){ if(!validArgNode(caller, nodeFamily)){ return; } + if(!validArgID(caller, nodeFamily.id)){ + return; + } + */ var maxPorts = Math.max(countInput, countRepo+countOutput+0.5), size = vertexLabelSize*2; @@ -1541,7 +1865,7 @@ function GraphFlow(){ // set node positions newNode.pos.setc(xPosition, yPosition); - closeButton.pos.setc(xPosition + width/2, yPosition - height/2); + closeButton.pos.setc(xPosition + (width - vertexLabelSize) / 2 , yPosition - height / 2 + size); newNode.data.$children.push(closeButton); // Loop Info: @@ -1589,13 +1913,14 @@ function GraphFlow(){ x = xPosition + relativeX; y = yPosition + relativeY; - + var port, portID; for(var p=0; p < loopPorts.length; p++){ - var port = loopPorts[p]; + port = loopPorts[p]; - // only attempt to add a node if it has an id - if(validArgNode(caller, port)){ + // only attempt to add a node if it has a valid id + portID = nodeFamily.id+"."+port.id; + //if(validArgNode(caller, port) && validArgID(caller, portID)){ var newPort ={ "adjacencies": [], "data": { @@ -1606,20 +1931,16 @@ function GraphFlow(){ "$tooltip": port.tooltip, "$movable" : false, "$symbol": port.symbol}, - "id": nodeFamily.id+"."+port.id, + "id": portID, "name": port.name}; - - /*if(p == 0){ - newPort.data.$relativeX= relativeX; - newPort.data.$relativeY= relativeY; - }*/ + g.addNode(newPort); newPort = g.getNode(newPort.id); newPort.pos.setc(x, y); newNode.data.$children.push(newPort); y += size; - } + //} } } } @@ -1640,11 +1961,14 @@ function GraphFlow(){ event listeners will be called */ this.removeNode = function(nodeFamily, removeChildren, forced){ - + + /* // check for valid arguments if(!validArgNode("removeNode()", nodeFamily)){ return; } + */ + // call listener if(!forced && !callListener("onRemoveNode", [nodeFamily])){ return; @@ -1679,9 +2003,12 @@ function GraphFlow(){ @param node - the node to be moved @param xPos - the horizontal position of the moved node. 0 is the center of the canvas' starting position @param yPos - the vertical position of the moved node. 0 is the center of the canvas' starting position + @param smoothMove - used for smooth animation in autoLayout */ - function moveNode(node, x, y){ + function moveNode(node, x, y, smoothMove){ + var isAnimated = smoothMove; + // snap to grid if(grid.data.$snap){ var gridDim = grid.data.$dim, @@ -1747,9 +2074,12 @@ function GraphFlow(){ deltaY = y - pos.y; // move parent - node.pos.setc(x, y); + if(isAnimated){ + node.setPos(new $jit.Complex(x, y), "end"); + }else{ + 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, @@ -1759,8 +2089,7 @@ function GraphFlow(){ child = children[c]; pos = child.pos.getc(true); - moveNode(child, pos.x + deltaX, pos.y + deltaY); - //child.pos.setc(pos.x + deltaX, pos.y + deltaY); + moveNode(child, pos.x + deltaX, pos.y + deltaY, isAnimated); } } } @@ -1777,6 +2106,7 @@ function GraphFlow(){ * @return true - if the edge was successfully added */ this.addEdge = function(sourceID, targetID, edgeLabel, edgeData, forced){ + /* // check for valid arguments if(!validArg("addEdge()", sourceID, "string")){ return false; @@ -1784,6 +2114,11 @@ function GraphFlow(){ if(!validArg("addEdge()", targetID, "string")){ return false; } + */ + if(edgeLabel && typeof edgeLabel == "string"){ + edgeLabel = edgeLabel.split('\n'); + } + /* if(edgeLabel){ if(typeof edgeLabel == 'object'){ // do nothing @@ -1793,6 +2128,7 @@ function GraphFlow(){ edgeLabel = null; } } + */ // look up the nodes which are to be connected var g = fd.graph; @@ -1940,6 +2276,7 @@ function GraphFlow(){ @param bendPoints - an array of {"x":..., "y":...} objects */ this.setBendPoints = function(sourceID, targetID, bendPoints){ + /* // check for valid arguments if(!validArg("setBendPoints()", sourceID, "string")){ return false; @@ -1947,6 +2284,7 @@ function GraphFlow(){ if(!validArg("setBendPoints()", targetID, "string")){ return false; } + */ // change data fd.graph.getAdjacence(sourceID, targetID).data.$bendPoints = bendPoints; @@ -1963,6 +2301,7 @@ function GraphFlow(){ does not trigger a listener event */ this.removeEdge = function(sourceID, targetID, forced){ + /* // check for valid arguments if(!validArg("removeEdge()", sourceID, "string")){ return; @@ -1970,6 +2309,7 @@ function GraphFlow(){ if(!validArg("removeEdge()", targetID, "string")){ return; } + */ // look up the source node and the nodeFamilies of both nodes var g = fd.graph, @@ -2023,11 +2363,12 @@ function GraphFlow(){ @return - the node object or null if the node does not exist */ this.getNode = function(nodeID){ - + /* // check for valid arguments if(!validArg("getNode()", nodeID, "string")){ return; } + */ return fd.graph.getNode(nodeID); } @@ -2038,30 +2379,38 @@ function GraphFlow(){ Highlights exactly one node or edge, and un-highlights the previously highlighted node or edge. @param node - the node or edge object which will be highlighted + @param noAnim - does not animate the highlight. Used by loadPositions() */ - function setHighlight(node){ + function setHighlight(node, noAnim){ + + var setter = 'current'; + var isAnimated = animation.enabled; + if(isAnimated){ + setter = 'end'; + } + // animation parameters var trans = $jit.Trans.Quint.easeOut, - dur= 200; + dur = animation.duration / 4; // clean up the old highlight if(hover){ if(hover.nodeFrom){ - hover.setData('lineWidth', 1, 'end'); - hover.setData('color', edgeColor, 'end'); + hover.setData('lineWidth', 1, setter); + hover.setData('color', edgeColor, setter); } else{ var type = hover.data.$type; if(markedNode != null && hover.id == markedNode.node.id){ - hover.setData('color', markedNode.strokeColor, 'end'); + hover.setData('color', markedNode.strokeColor, setter); } else{ - hover.setData('color', nodeStrokeColor[type], 'end'); + hover.setData('color', nodeStrokeColor[type], setter); } // decrease size of ports and cross button if(type == 'inputPort' || type == 'outputPort' || type == 'repositoryPort'){ - hover.setData('dim', vertexLabelSize*2, 'end'); + hover.setData('dim', vertexLabelSize*2, setter); } } @@ -2071,33 +2420,42 @@ function GraphFlow(){ // do we highlight something new? if(node != null){ if(node.nodeFrom != undefined){ - node.setData('color', edgeColorFocus, 'end'); - node.setData('lineWidth', 2, 'end'); + node.setData('color', edgeColorFocus, setter); + node.setData('lineWidth', 2, setter); }else{ - node.setData('color', nodeColorFocus, 'end'); + node.setData('color', nodeColorFocus, setter); // increase size of ports and cross button var type = node.data.$type; if(type == 'inputPort' || type == 'outputPort' || type == 'repositoryPort'){ - node.setData('dim', vertexLabelSize*3, 'end'); + node.setData('dim', vertexLabelSize*3, setter); } } hover = node; }else{ // slower fade away transition trans = $jit.Trans.Quint.easeIn; - dur = 400; + dur *= 2; + } + + if(noAnim){ + return; } // Animations overwrite each other. If we let this animation // execute while we delete an edge, the onComplete() function // of the edge animation will not be called - fd.animate({ - modes: ['edge-property:lineWidth:color', - 'node-property:color:dim'], - transition: trans, - duration: dur - }); + if(isAnimated){ + fd.animate({ + modes: ['edge-property:lineWidth:color', + 'node-property:color:dim'], + transition: trans, + duration: dur + }); + }else{ + fd.plot(); + } + } /** @@ -2112,7 +2470,14 @@ function GraphFlow(){ */ this.markNode = function(node, strokeColor, fillColor){ + var isAnimated = animation.enabled; + var setter = 'current'; + if(isAnimated){ + setter = 'end'; + } + // check for valid arguments + /* if(node && !validArgNode("markNode()", node)){ return; } @@ -2122,6 +2487,7 @@ function GraphFlow(){ if(fillColor && !validArgColor("markNode()", fillColor)){ return; } + */ // clean up the old marking if(markedNode){ @@ -2131,11 +2497,11 @@ function GraphFlow(){ } else{ var type = markedNode.node.data.$type; - markedNode.node.setData('color', nodeStrokeColor[type], 'end'); + markedNode.node.setData('color', nodeStrokeColor[type], setter); // change FillColor only if it exists if(nodeFillColor[type]){ - markedNode.node.setData('fillColor', nodeFillColor[type], 'end'); + markedNode.node.setData('fillColor', nodeFillColor[type], setter); } } markedNode = null; @@ -2148,24 +2514,28 @@ function GraphFlow(){ // ignore edges }else{ if(strokeColor != null){ - node.setData('color', strokeColor, 'end'); + node.setData('color', strokeColor, setter); } // change FillColor only if it exists if(fillColor != null && nodeFillColor[node.data.$type]){ - node.setData('fillColor', fillColor, 'end'); + node.setData('fillColor', fillColor, setter); } } markedNode = { 'strokeColor' : strokeColor, 'fillColor' : fillColor, 'node' : node}; } - // we need a 0-frame animation to change the colors - fd.animate({ - modes: ['edge-property:color', - 'node-property:color'], - transition: $jit.Trans.Quint.easeOut, - duration: 0 - }); + // animate + if(isAnimated){ + fd.animate({ + modes: ['edge-property:color', + 'node-property:color'], + transition: $jit.Trans.Quint.easeOut, + duration: animation.duration / 2 + }); + }else{ + fd.plot(); + } } /** @@ -2210,29 +2580,32 @@ function GraphFlow(){ json.push({ "adjacencies": [], "data": { - "$dim": 0, - "$type": "none", - "&xPos": 0, - "&typeSelected" : null, - "&yPos": 0 + "$type": "none" }, - "id": "#DUMMY_MOUSE_NODE", - "name": "", - "alpha": 0 - }); + "id": "#DUMMY_MOUSE_NODE" + }, // define gridNode - json.push({ - 'data': { - '$dim' : vertexLabelSize*4, - '$type' : 'grid', - '$color' : '#AAAAFF', - '$width' : document.getElementById(visContainer).clientWidth, - '$height': document.getElementById(visContainer).clientHeight, - '$visible' : false, - '$snap' : false - }, - 'id': '#DUMMY_GRID_NODE', + { + 'data': { + '$dim' : vertexLabelSize*4, + '$type' : 'grid', + '$color' : '#AAAAFF', + '$width' : document.getElementById(visContainer).clientWidth, + '$height': document.getElementById(visContainer).clientHeight, + '$alpha' : 0, + '$snap' : false + }, + 'id': '#DUMMY_GRID_NODE' + }, + + // define canvas node + { + 'data': { + '$dim' : vertexLabelSize*4, + '$type' : 'none' + }, + 'id': '#DUMMY_NAV_NODE' }); @@ -2294,6 +2667,7 @@ function GraphFlow(){ } } }, + // Add node events Events: { enable: true, @@ -2375,7 +2749,7 @@ function GraphFlow(){ var mousePos = eventInfo.getPos(); // dragging the canvas - if(node == false || node.id == mouseNode.id || + if(!node || node.id == mouseNode.id || !node.data.$movable){ navi.dragX = e.layerX; //e.pageX; @@ -2391,8 +2765,8 @@ function GraphFlow(){ var nodePos = node.pos.getc(true); // 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; + navi.dragX = nodePos.x - mousePos.x; + navi.dragY = nodePos.y - mousePos.y; /* // memorize the node position before node movement @@ -2419,8 +2793,8 @@ function GraphFlow(){ // move node var pos = eventInfo.getPos(), - x = pos.x + navi.nodeOffsetX, - y = pos.y + navi.nodeOffsetY; + x = pos.x + navi.dragX, + y = pos.y + navi.dragY; moveNode(node, x, y); // move screen if near edge @@ -2591,7 +2965,6 @@ function GraphFlow(){ // load JSON data. fd.loadJSON(json, 0); - // set pointers to grid and mouse node mouseNode = fd.graph.getNode("#DUMMY_MOUSE_NODE"); grid = fd.graph.getNode("#DUMMY_GRID_NODE"); @@ -2600,6 +2973,7 @@ function GraphFlow(){ fd.canvas.labelThreshold = 1/vertexLabelSize; fd.canvas.showLabels = true; + console.log(fd.canvas); console.log(fd.graph); } diff --git a/Kieker.WebGUI/src/main/webapp/js/jit.js b/Kieker.WebGUI/src/main/webapp/js/jit.js index 16a120d2e10205cc91e9d42de407d63ff57efe83..f4626464f35bd6f3c73e2207400e39b61fbbb33c 100644 --- a/Kieker.WebGUI/src/main/webapp/js/jit.js +++ b/Kieker.WebGUI/src/main/webapp/js/jit.js @@ -816,7 +816,7 @@ var Animation = new Class( { initialize: function(options){ this.setOptions(options); }, - + setOptions: function(options){ var opt = { duration: 2500, @@ -2905,6 +2905,7 @@ var Canvas; this.canvases[i].translate(x, y, disablePlot); } }, + /* Method: scale @@ -4622,6 +4623,7 @@ Graph.Node = new Class({ */ setPos: function(value, type) { type = type || "current"; + var pos; if(type == "current") { pos = this.pos; @@ -16978,6 +16980,12 @@ $jit.GraphFlow = new Class( { this.json = null; this.busy = false; + this.animationFix = { + 'duration' : 0, + 'busy' : false, + 'map' : undefined + }; + //overwrite faulty click function this.Classes.Events.prototype.onMouseDown = function(e, win, event) { var evt = $.event.get(e, win); @@ -16989,10 +16997,30 @@ $jit.GraphFlow = new Class( { } this.config.onDragStart(this.pressed, event, evt); }; + + // extend Interpolator for new values + var that = this; + var iMap = this.fx.Interpolator.map; + + this.fx.Interpolator.canvas = + function(c, delta){ + var scale = c.scaleOffsetX; + var fromX = c.translateOffsetX/scale, + fromY = c.translateOffsetY/scale; + var toX = c.translateOffsetXEnd/scale, + toY = c.translateOffsetYEnd/scale; + + var newX = that.fx.Interpolator.compute(fromX, toX, delta), + newY = that.fx.Interpolator.compute(fromY, toY, delta); + + c.translate(newX-fromX, newY-fromY); + }; + + iMap.fillColor = "color"; + // "number", "array-number" // initialize extras this.initializeExtras(); - }, /* @@ -17113,7 +17141,7 @@ $jit.GraphFlow = new Class( { $jit.GraphFlow.$extend = true; (function(GraphFlow) { - + /* Class: GraphFlow.Op @@ -17156,32 +17184,56 @@ $jit.GraphFlow.$extend = true; */ animate: function(opt, versor) { opt = $.merge(this.viz.config, opt || {}); + var that = this, viz = this.viz, + animFix = viz.animationFix, graph = viz.graph, interp = this.Interpolator, animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation; - //prepare graph values - var m = this.prepare(opt.modes); + // merge animation properties + if(animFix.busy){ + animFix.map = $.merge(animFix.map, this.prepare(opt.modes)); + + }else{ + animFix.map = this.prepare(opt.modes); + animFix.busy = true; + } + + //prepare graph values + var m = animFix.map; + var moveCanvas = !!m.canvas; + var iterNodes = !!Object.keys(m).length; + delete m.canvas; + //animate animation.setOptions($.merge(opt, { $animating: false, compute: function(delta) { - graph.eachNode(function(node) { - for(var p in m) { - interp[p](node, m[p], delta, versor); - } - }); + + if(iterNodes){ + graph.eachNode(function(node) { + for(var p in m) { + interp[p](node, m[p], delta, versor); + } + }); + } viz.canvas.clear(); + if(moveCanvas){ + interp.canvas(viz.canvas, delta); + } + that.plot(opt, this.$animating, delta); this.$animating = true; }, + link: 'cancel', complete: function() { viz.canvas.clear(); that.plot(opt); opt.onComplete(); opt.onAfterCompute(); + animFix.busy = false; } })).start(); }, @@ -17777,7 +17829,7 @@ $jit.GraphFlow.$extend = true; 'grid': { 'render': function(grid, canvas){ - if(!grid.data.$visible){ + if(!grid.data.$alpha){ return; } var scale = 1/canvas.scaleOffsetX, @@ -17863,6 +17915,7 @@ $jit.GraphFlow.$extend = true; if(bend){ for(var b = 0, l = bend.length; b < l; b++){ toBend = bend[b]; + //if (EdgeHelper.line.contains(from, toBend, pos, dim)){ if (GraphFlow.EdgeHelper.isOverEdge(from, toBend, pos, dim)){ return true; } @@ -17870,6 +17923,7 @@ $jit.GraphFlow.$extend = true; } } + //return EdgeHelper.line.contains(from, to, pos, dim); return GraphFlow.EdgeHelper.isOverEdge(from, to, pos, dim); },