Here’s an example:
<!DOCTYPE html>
<html>
<head>
<title>Simple Shifting of Nodes to Avoid Overlapping with Groups</title>
<!-- Copyright 1998-2021 by Northwoods Software Corporation. -->
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
After each layout, the <code>shiftNodes</code> function is called on each group to make sure no
nodes overlap with the group. The function moves overlapping nodes rightwards or downwards or both.
A layout normally happens whenever any node changes size, so groups do not need to be expanded
in order for any overlapping nodes to be shifted.
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
<script src="https://unpkg.com/gojs"></script>
<script id="code">
function init() {
const $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"LayoutCompleted": e => { e.diagram.findTopLevelGroups().each(shiftNodes); },
"undoManager.isEnabled": true,
"ModelChanged": e => {
if (e.isTransactionFinished) document.getElementById("mySavedModel").textContent = e.model.toJson();
}
});
function makePort(name, spot) {
return $(go.Shape, "Circle",
{ width: 6, height: 6, strokeWidth: 0, fill: "green",
alignment: spot, alignmentFocus: spot.opposite() },
{ portId: name, fromLinkable: true, toLinkable: true,
fromSpot: spot, toSpot: spot });
}
myDiagram.nodeTemplate =
$(go.Node, "Spot",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Panel, "Auto",
{ width: 100, height: 80 },
$(go.Shape, { fill: "lightgreen" }),
$(go.TextBlock, { editable: true },
new go.Binding("text").makeTwoWay())
),
makePort("T", go.Spot.Top),
makePort("R", go.Spot.Right),
makePort("B", go.Spot.Bottom),
makePort("L", go.Spot.Left),
);
myDiagram.groupTemplate =
$(go.Group, "Vertical", // title above Placeholder
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
new go.Binding("isSubGraphExpanded").makeTwoWay(),
{ background: "transparent" },
$(go.Panel, "Horizontal", // button next to TextBlock
{ stretch: go.GraphObject.Horizontal },
$("SubGraphExpanderButton",
{ alignment: go.Spot.Right, margin: 5 }),
$(go.TextBlock,
{
alignment: go.Spot.Left,
editable: true,
margin: 5
},
new go.Binding("text").makeTwoWay())
), // end Horizontal Panel
$(go.Panel, "Auto",
{ alignment: go.Spot.Left },
$(go.Shape, { fill: "whitesmoke" }),
$(go.Placeholder,
{ padding: 10 })
)
);
myDiagram.linkTemplate =
$(go.Link,
{ routing: go.Link.AvoidsNodes },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
// This function moves any nodes that overlap with the given Part, either rightwards or downwards.
// Currently it is only called on Groups, but it could work for any Node.
function shiftNodes(part) {
const diagram = part.diagram;
if (diagram === null) return;
part.ensureBounds();
const b = part.actualBounds;
const overlaps = diagram.findObjectsIn(b,
x => { const p = x.part; return (p.isTopLevel && p instanceof go.Node) ? p : null; },
node => node !== part && !node.isMemberOf(part),
true);
let dx = 0;
let dy = 0;
const shiftsXY = new go.Set();
const shiftsX = new go.Set();
const shiftsY = new go.Set();
overlaps.each(node => {
const r = node.actualBounds;
if (r.contains(b.right, b.bottom)) {
dx = Math.max(dx, b.right - r.left);
dy = Math.max(dy, b.bottom - r.top);
shiftsXY.add(node);
} else if (b.contains(r.left, r.bottom)) {
dx = Math.max(dx, b.right - r.left);
shiftsX.add(node);
} else if (b.contains(r.right, r.top)) {
dy = Math.max(dy, b.bottom - r.top);
shiftsY.add(node);
}
});
if (dx > 0) diagram.moveParts(shiftsX, new go.Point(dx+10, 0), false);
if (dy > 0) diagram.moveParts(shiftsY, new go.Point(0, dy+10), false);
if (dx > 0 && dy > 0) diagram.moveParts(shiftsXY, new go.Point(dx+10, dy+10), false);
}
myDiagram.model = $(go.GraphLinksModel,
{
linkFromPortIdProperty: "fpid",
linkToPortIdProperty: "tpid",
nodeDataArray:
[
{ key: 0, isGroup: true, text: "Group", isSubGraphExpanded: false, loc: "0 0" },
{ key: 1, text: "Alpha", group: 0, loc: "10 10" },
{ key: 2, text: "Beta", group: 0, loc: "180 40" },
{ key: 3, text: "Gamma", group: 0, loc: "60 130" },
{ key: 4, text: "Delta", loc: "200 70" } // in the way when the group is expanded
],
linkDataArray:
[
{ from: 1, fpid: "R", to: 2, tpid: "L" },
{ from: 1, fpid: "B", to: 3, tpid: "T" }
]
});
}
window.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>