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