@jocelyn wrote:
Hi,
When I add node from Process(Lane4) lane to another lane, my diagram look like this
Here’s the issues:
- I want my node in same lane can be vertical alignment
- I want my node from same process can be horizontal aligment
- I want node in participant & system lane don’t have the link and I still could get which Process node it comes from
4.When I add node in Process lane it will be like this
Is there any function could let me insert node between two nodes?
My diagram should be like this:
Here’s my code:
>// These parameters need to be set before defining the templates. var MINLENGTH = 200; // this controls the minimum length of any swimlane var MINBREADTH = 20; // this controls the minimum breadth of any non-collapsed swimlane var fromnode = null; // some shared functions // this may be called to force the lanes to be laid out again function relayoutLanes() { myDiagram.nodes.each(function(lane) { if (!(lane instanceof go.Group)) return; if (lane.category === "Pool") return; lane.layout.isValidLayout = false; // force it to be invalid }); myDiagram.layoutDiagram(); } // this is called after nodes have been moved or lanes resized, to layout all of the Pool Groups again function relayoutDiagram() { myDiagram.layout.invalidateLayout(); myDiagram.findTopLevelGroups().each(function(g) { if (g.category === "Pool") g.layout.invalidateLayout(); }); myDiagram.layoutDiagram(); } // compute the minimum size of a Pool Group needed to hold all of the Lane Groups function computeMinPoolSize(pool) { // assert(pool instanceof go.Group && pool.category === "Pool"); var len = MINLENGTH; pool.memberParts.each(function(lane) { // pools ought to only contain lanes, not plain Nodes if (!(lane instanceof go.Group)) return; var holder = lane.placeholder; if (holder !== null) { var sz = holder.actualBounds; len = Math.max(len, sz.height); } }); return new go.Size(NaN, len); } // compute the minimum size for a particular Lane Group function computeLaneSize(lane) { // assert(lane instanceof go.Group && lane.category !== "Pool"); var sz = computeMinLaneSize(lane); if (lane.isSubGraphExpanded) { var holder = lane.placeholder; if (holder !== null) { var hsz = holder.actualBounds; sz.width = Math.max(sz.width, hsz.width); } } // minimum breadth needs to be big enough to hold the header var hdr = lane.findObject("HEADER"); if (hdr !== null) sz.width = Math.max(sz.width, hdr.actualBounds.width); return sz; } // determine the minimum size of a Lane Group, even if collapsed function computeMinLaneSize(lane) { if (!lane.isSubGraphExpanded) return new go.Size(1, MINLENGTH); return new go.Size(MINBREADTH, MINLENGTH); } // define a custom ResizingTool to limit how far one can shrink a lane Group function LaneResizingTool() { go.ResizingTool.call(this); } go.Diagram.inherit(LaneResizingTool, go.ResizingTool); LaneResizingTool.prototype.isLengthening = function() { return (this.handle.alignment === go.Spot.Bottom); }; LaneResizingTool.prototype.computeMinPoolSize = function() { var lane = this.adornedObject.part; // assert(lane instanceof go.Group && lane.category !== "Pool"); var msz = computeMinLaneSize(lane); // get the absolute minimum size if (this.isLengthening()) { // compute the minimum length of all lanes var sz = computeMinPoolSize(lane.containingGroup); msz.height = Math.max(msz.height, sz.height); } else { // find the minimum size of this single lane var sz = computeLaneSize(lane); msz.width = Math.max(msz.width, sz.width); msz.height = Math.max(msz.height, sz.height); } return msz; }; LaneResizingTool.prototype.resize = function(newr) { var lane = this.adornedObject.part; if (this.isLengthening()) { // changing the length of all of the lanes lane.containingGroup.memberParts.each(function(lane) { if (!(lane instanceof go.Group)) return; var shape = lane.resizeObject; if (shape !== null) { // set its desiredSize length, but leave each breadth alone shape.height = newr.height; } }); } else { // changing the breadth of a single lane go.ResizingTool.prototype.resize.call(this, newr); } relayoutDiagram(); // now that the lane has changed size, layout the pool again }; // end LaneResizingTool class // define a custom grid layout that makes sure the length of each lane is the same // and that each lane is broad enough to hold its subgraph function PoolLayout() { go.GridLayout.call(this); this.cellSize = new go.Size(1, 1); this.wrappingColumn = Infinity; this.wrappingWidth = Infinity; this.isRealtime = false; // don't continuously layout while dragging this.alignment = go.GridLayout.Position; // This sorts based on the location of each Group. // This is useful when Groups can be moved up and down in order to change their order. this.comparer = function(a, b) { var ax = a.location.x; var bx = b.location.x; if (isNaN(ax) || isNaN(bx)) return 0; if (ax < bx) return -1; if (ax > bx) return 1; return 0; }; } go.Diagram.inherit(PoolLayout, go.GridLayout); PoolLayout.prototype.doLayout = function(coll) { var diagram = this.diagram; if (diagram === null) return; diagram.startTransaction("PoolLayout"); var pool = this.group; if (pool !== null && pool.category === "Pool") { // make sure all of the Group Shapes are big enough var minsize = computeMinPoolSize(pool); pool.memberParts.each(function(lane) { if (!(lane instanceof go.Group)) return; if (lane.category !== "Pool") { var shape = lane.resizeObject; if (shape !== null) { // change the desiredSize to be big enough in both directions var sz = computeLaneSize(lane); shape.width = (!isNaN(shape.width)) ? Math.max(shape.width, sz.width) : sz.width; shape.height = (isNaN(shape.height) ? minsize.height : Math.max(shape.height, minsize.height)); var cell = lane.resizeCellSize; if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0) shape.width = Math.ceil(shape.width / cell.width) * cell.width; if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0) shape.height = Math.ceil(shape.height / cell.height) * cell.height; } } }); } // now do all of the usual stuff, according to whatever properties have been set on this GridLayout go.GridLayout.prototype.doLayout.call(this, coll); diagram.commitTransaction("PoolLayout"); }; // end PoolLayout class function init() { if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this var $ = go.GraphObject.make; myDiagram = $(go.Diagram, "myDiagramDiv", { // use a custom ResizingTool (along with a custom ResizeAdornment on each Group) resizingTool: new LaneResizingTool(), // use a simple layout that ignores links to stack the top-level Pool Groups next to each other layout: $(PoolLayout), // don't allow dropping onto the diagram's background unless they are all Groups (lanes or pools) "draggingTool.isEnabled": true, mouseDragOver: function(e) { if (!e.diagram.selection.all(function(n) { return n instanceof go.Group; })) { e.diagram.currentCursor = 'not-allowed'; } }, mouseDrop: function(e) { if (!e.diagram.selection.all(function(n) { return n instanceof go.Group; })) { e.diagram.currentTool.doCancel(); } }, // a clipboard copied node is pasted into the original node's group (i.e. lane). "commandHandler.copiesGroupKey": true, // automatically re-layout the swim lanes after dragging the selection "SelectionMoved": relayoutDiagram, // this DiagramEvent listener is "SelectionCopied": relayoutDiagram, // defined above "animationManager.isEnabled": true, // enable undo & redo "undoManager.isEnabled": true, }); // This is the actual HTML context menu: var cxElement = document.getElementById("contextMenu"); // Since we have only one main element, we don't have to declare a hide method, // we can set mainElement and GoJS will hide it automatically var myContextMenu = $(go.HTMLInfo, { show: showContextMenu, mainElement: cxElement },); // this is a Part.dragComputation function for limiting where a Node may be dragged function stayInGroup(part, pt, gridpt) { // don't constrain top-level nodes var grp = part.containingGroup; if (grp === null) return pt; // try to stay within the background Shape of the Group var back = grp.resizeObject; if (back === null) return pt; // allow dragging a Node out of a Group if the Shift key is down if (part.diagram.lastInput.shift) return pt; var p1 = back.getDocumentPoint(go.Spot.TopLeft); var p2 = back.getDocumentPoint(go.Spot.BottomRight); var b = part.actualBounds; var loc = part.location; // find the padding inside the group's placeholder that is around the member parts var m = grp.placeholder.padding; // now limit the location appropriately var x = Math.max(p1.x + m.left, Math.min(pt.x, p2.x - m.right - b.width - 1)) + (loc.x - b.x); var y = Math.max(p1.y + m.top, Math.min(pt.y, p2.y - m.bottom - b.height - 1)) + (loc.y - b.y); return new go.Point(x, y); }
myDiagram.nodeTemplate = $(go.Node, "Auto", { fromSpot: go.Spot.Right, toSpot: go.Spot.Left }, { linkConnected: function(node, link, port) { if (link.fromNode !== null) link.fromNode.invalidateConnectedLinks(); if (link.toNode !== null) link.toNode.invalidateConnectedLinks(); }, linkDisconnected: function(node, link, port) { if (link.fromNode !== null) link.fromNode.invalidateConnectedLinks(); if (link.toNode !== null) link.toNode.invalidateConnectedLinks(); }, }, { contextMenu: myContextMenu }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Shape, "Rectangle", { fill: "white", portId: "", cursor: "pointer", fromLinkable: false, toLinkable: false }), $(go.TextBlock, { margin: 5 }, new go.Binding("text", "key")), { selectionAdornmentTemplate: $(go.Adornment, "Spot", $(go.Panel, "Auto", // this Adornment has a rectangular blue Shape around the selected node $(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }), $(go.Placeholder) ), // and this Adornment has a Button to the right of the selected node // $("Button", // { alignment: go.Spot.Right, alignmentFocus: go.Spot.Left, // click: addNodeAndLink }, // define click behavior for Button in Adornment // $(go.TextBlock, "ADD", // the Button content // { font: "bold 6pt sans-serif" }) // ) ) // end Adornment }, { dragComputation: stayInGroup }); function groupStyle() { // common settings for both Lane and Pool Groups return [ { layerName: "Background", // all pools and lanes are always behind all nodes and links background: "transparent", // can grab anywhere in bounds movable: false, // allows users to re-order by dragging copyable: false, // can't copy lanes or pools avoidable: false, // don't impede AvoidsNodes routed Links minLocation: new go.Point(-Infinity, NaN), // only allow horizontal movement maxLocation: new go.Point(Infinity, NaN) }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify) ]; } // hide links between lanes when either lane is collapsed function updateCrossLaneLinks(group) { group.findExternalLinksConnected().each(function(l) { l.visible = (l.fromNode.isVisible() && l.toNode.isVisible()); }); } // each Group is a "swimlane" with a header on the left and a resizable lane on the right myDiagram.groupTemplate = $(go.Group, "Vertical", groupStyle(), { selectionObjectName: "SHAPE", // selecting a lane causes the body of the lane to be highlit, not the label resizable: true, resizeObjectName: "SHAPE", // the custom resizeAdornmentTemplate only permits two kinds of resizing layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph { isInitial: false, // don't even do initial layout isOngoing: false, // don't invalidate layout when nodes or links are added or removed direction: 90, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }), computesBoundsAfterDrag: true, // needed to prevent recomputing Group.placeholder bounds too soon computesBoundsIncludingLinks: false, // to reduce occurrences of links going briefly outside the lane computesBoundsIncludingLocation: true, // to support empty space at top-left corner of lane handlesDragDropForMembers: true, // don't need to define handlers on member Nodes and Links mouseDrop: function(e, grp) { // dropping a copy of some Nodes and Links onto this Group adds them to this Group if (!e.shift) return; // cannot change groups with an unmodified drag-and-drop // don't allow drag-and-dropping a mix of regular Nodes and Groups if (!e.diagram.selection.any(function(n) { return n instanceof go.Group; })) { var ok = grp.addMembers(grp.diagram.selection, true); if (ok) { updateCrossLaneLinks(grp); } else { grp.diagram.currentTool.doCancel(); } } else { e.diagram.currentTool.doCancel(); } }, subGraphExpandedChanged: function(grp) { var shp = grp.resizeObject; if (grp.diagram.undoManager.isUndoingRedoing) return; if (grp.isSubGraphExpanded) { shp.width = grp._savedBreadth; } else { grp._savedBreadth = shp.width; shp.width = NaN; } updateCrossLaneLinks(grp); } }, new go.Binding("isSubGraphExpanded", "expanded").makeTwoWay(), // the lane header consisting of a Shape and a TextBlock $(go.Panel, "Horizontal", { name: "HEADER", angle: 0, // maybe rotate the header to read sideways going up alignment: go.Spot.Center }, $(go.Panel, "Horizontal", // this is hidden when the swimlane is collapsed new go.Binding("visible", "isSubGraphExpanded").ofObject(), $(go.Shape, "Diamond", { width: 8, height: 8, fill: "white" }, new go.Binding("fill", "color")), $(go.TextBlock, // the lane label { font: "bold 13pt sans-serif", editable: true, margin: new go.Margin(2, 0, 0, 0) }, new go.Binding("text", "text").makeTwoWay()) ), $("SubGraphExpanderButton", { margin: 5 }) // but this remains always visible! ), // end Horizontal Panel $(go.Panel, "Auto", // the lane consisting of a background Shape and a Placeholder representing the subgraph $(go.Shape, "Rectangle", // this is the resized object { name: "SHAPE", fill: "white" }, new go.Binding("fill", "color"), new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)), $(go.Placeholder, { padding: 12, alignment: go.Spot.TopLeft }), $(go.TextBlock, // this TextBlock is only seen when the swimlane is collapsed { name: "LABEL", font: "bold 13pt sans-serif", editable: true, angle: 90, alignment: go.Spot.TopLeft, margin: new go.Margin(4, 0, 0, 2) }, new go.Binding("visible", "isSubGraphExpanded", function(e) { return !e; }).ofObject(), new go.Binding("text", "text").makeTwoWay()) ) // end Auto Panel ); // end Group // define a custom resize adornment that has two resize handles if the group is expanded myDiagram.groupTemplate.resizeAdornmentTemplate = $(go.Adornment, "Spot", $(go.Placeholder), $(go.Shape, // for changing the length of a lane { alignment: go.Spot.Bottom, desiredSize: new go.Size(50, 7), fill: "lightblue", stroke: "dodgerblue", cursor: "row-resize" }, new go.Binding("visible", "", function(ad) { if (ad.adornedPart === null) return false; return ad.adornedPart.isSubGraphExpanded; }).ofObject()), $(go.Shape, // for changing the breadth of a lane { alignment: go.Spot.Right, desiredSize: new go.Size(7, 50), fill: "lightblue", stroke: "dodgerblue", cursor: "col-resize" }, new go.Binding("visible", "", function(ad) { if (ad.adornedPart === null) return false; return ad.adornedPart.isSubGraphExpanded; }).ofObject()) ); myDiagram.groupTemplateMap.add("Pool", $(go.Group, "Auto", groupStyle(), { // use a simple layout that ignores links to stack the "lane" Groups next to each other layout: $(PoolLayout, { spacing: new go.Size(0, 0) }) // no space between lanes }, $(go.Shape, { fill: "white" }, new go.Binding("fill", "color")), $(go.Panel, "Table", { defaultRowSeparatorStroke: "black" }, $(go.Panel, "Horizontal", { row: 0, angle: 0 }, $(go.TextBlock, { font: "bold 16pt sans-serif", editable: true, margin: new go.Margin(2, 0, 0, 0) }, new go.Binding("text").makeTwoWay()) ), $(go.Placeholder, { row: 1 }) ) )); myDiagram.linkTemplate = $(go.Link, { routing: go.Link.AvoidsNodes, corner: 5 }, { relinkableFrom: true, relinkableTo: true }, $(go.Shape), $(go.Shape, { toArrow: "Standard" }) ); // define some sample graphs in some of the lanes myDiagram.model = new go.GraphLinksModel( [ // node data { key: "Pool1", text: "Pool", isGroup: true, category: "Pool" }, { key: "Lane1", text: "Participants", isGroup: true, group: "Pool1", color: "lightblue", category:"Participants" }, { key: "Lane2", text: "System", isGroup: true, group: "Pool1", color: "lightgreen", category:"System" }, { key: "Lane3", text: "Input", isGroup: true, group: "Pool1", color: "lightyellow", category:"Input" }, { key: "Lane4", text: "Process", isGroup: true, group: "Pool1", color: "orange", category:"Process" }, { key: "Lane5", text: "Output", isGroup: true, group: "Pool1", color: "lightskyblue", category:"Output" }, { key: "Start", group: "Lane4"}, { key: "Collect", group: "Lane4"}, { key: "Store", group: "Lane4"}, { key: "Use", group: "Lane4" }, { key: "Transfer", group: "Lane4" }, { key: "Delete", group: "Lane4" }, { key: "End", group: "Lane4" }, ], [ // link data { from: "Start", to: "Collect" }, { from: "Collect", to: "Store" }, { from: "Store", to: "Use" }, { from: "Use", to: "Transfer" }, { from: "Transfer",to: "Delete" }, { from: "Delete", to: "End" }, { from: "Collect", to:"Input"}, ]); myDiagram.contextMenu = myContextMenu; // We don't want the div acting as a context menu to have a (browser) context menu! cxElement.addEventListener("contextmenu", function(e, obj) { e.preventDefault(); return false; }, false); function showContextMenu(obj, diagram, tool) { // Show only the relevant buttons given the current state. var cmd = diagram.commandHandler; let node = { node: obj, diagram: diagram }; fromnode = node; document.getElementById("delete").style.display = cmd.canDeleteSelection() ? "block" : "none"; document.getElementById("add_process").style.display=(obj.part.containingGroup.key == "Lane4" ? "block" : "none"); document.getElementById("add_input").style.display=(obj.part.containingGroup.key =='Lane4' ? "block" : "none"); document.getElementById("add_output").style.display=(obj.part.containingGroup.key =='Lane4' ? "block" : "none"); document.getElementById("add_system").style.display=(obj.part.containingGroup.key =='Lane4' ? "block" : "none"); document.getElementById("add_participants").style.display=(obj.part.containingGroup.key =='Lane4' ? "block" : "none"); // Now show the whole context menu element cxElement.style.display = "block"; // we don't bother overriding positionContextMenu, we just do it here: var mousePt = diagram.lastInput.viewPoint; cxElement.style.left = mousePt.x + "px"; cxElement.style.top = mousePt.y + "px"; } // force all lanes' layouts to be performed relayoutLanes(); load(); } // end init // Show the diagram's model in JSON format function save() { document.getElementById("mySavedModel").value = myDiagram.model.toJson(); myDiagram.isModified = false; jQuery.ajax({ method:'POST', url:'dataflow', data:{'postdata':myDiagram.model.toJson()}, dataType:'json' }); } function load() { var getdata document.getElementById("mySavedModel").value = myDiagram.model.toJson(); // myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value); jQuery.ajax({ method: 'GET', url: 'dataflow_get', data:{getdata}, dataType: 'json', success: function(getdata) { getdata_json = getdata['getdata']; getdata_json = JSON.stringify(getdata_json); console.log(getdata_json); myDiagram.model = go.Model.fromJson(getdata['getdata']); }}); return } function cxcommand(event, val) { if (val === undefined) val = event.currentTarget.id; var diagram = myDiagram; switch (val) { case "delete": diagram.startTransaction("delete node and link"); diagram.commandHandler.deleteSelection(); diagram.commitTransaction("delete node and link"); break; case "add_process": addNodeAndLink(fromnode, "Lane4"); break; case "add_input": addNodeAndLink(fromnode, "Lane3"); break; case "add_output": addNodeAndLink(fromnode, "Lane5"); break; case "add_system": addNodeAndLink(fromnode, "Lane2"); break; case "add_participants": addNodeAndLink(fromnode, "Lane1"); break; } diagram.currentTool.stopTool(); } // A custom command, for changing the color of the selected node(s). function addNodeAndLink(fromnode, lane){ if(!fromnode) return; fromnode.diagram.startTransaction("add node and link"); // have the Model add the node data var newnode = { key: "N", group: lane}; fromnode.diagram.model.addNodeData(newnode); console.log(event); // locate the node initially where the parent node is if (lane == 4) { fromnode.diagram.findNodeForData(newnode).location = fromnode.node.location.y+200; }else { fromnode.diagram.findNodeForData(newnode).location = fromnode.node.location; } if(lane !== "Lane1" || lane !== "Lane2"){ console.log(event); var newlink = { from: fromnode.node.data.key, to: newnode.key }; fromnode.diagram.model.addLinkData(newlink); }else return; // and then add a link data connecting the original node with the new one // finish the transaction -- will automatically perform a layout fromnode.diagram.commitTransaction("add node and link"); fromnode=null; return; } </script>
Cause it is really import to me, if you would like to spend time to answer me, I would be VERY appreciate!!
when I add relayoutLanes(), my diagram look this this
Posts: 1
Participants: 1