Here’s a start. But it could be much smarter, which I’ll leave for you to implement.
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2021 by Northwoods Software Corporation. -->
<script src="https://unpkg.com/gojs"></script>
<script id="code">
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 },
new go.Binding("text", "key"))
);
myDiagram.groupTemplate =
$(go.Group, "Auto",
$(go.Shape, { fill: "transparent", strokeWidth: 3 },
new go.Binding("stroke", "color"),
new go.Binding("fill", "color", go.Brush.lighten)),
$(go.Placeholder, { alignment: go.Spot.TopLeft, padding: 15 }),
$("SubGraphExpanderButton", { alignment: go.Spot.TopLeft }),
{
minSize: new go.Size(50, 50),
subGraphExpandedChanged: function(grp) {
if (!grp.isSubGraphExpanded) return;
shiftNodes(grp);
},
selectionChanged: function(grp) {
grp.diagram.commit(function(diag) {
var lay = grp.isSelected ? "Foreground" : "";
grp.layerName = lay;
grp.findSubGraphParts().each(function(x) { x.layerName = lay; });
}, null);
}
}
);
function shiftNodes(part) {
part.ensureBounds();
var b = part.actualBounds;
var diagram = part.diagram;
if (diagram === null) return;
var overlaps = diagram.findObjectsIn(b,
function(x) { var p = x.part; return (p.isTopLevel && p instanceof go.Node) ? p : null; },
function(node) { return node !== part && !node.isMemberOf(part); },
true);
var dx = 0;
var dy = 0;
var shiftsXY = new go.Set();
var shiftsX = new go.Set();
var shiftsY = new go.Set();
overlaps.each(function(node) {
var 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 = new go.GraphLinksModel(
[
{ key: 1, color: "lightblue", isGroup: true },
{ key: 2, color: "orange", isGroup: true },
{ key: 3, color: "lightgreen", isGroup: true },
{ key: 4, color: "pink", isGroup: true },
{ group: 1 },
{ group: 1 },
{ group: 1 },
{ group: 1 },
{ group: 2 },
{ group: 2 },
{ group: 2 },
{ group: 3 },
{ group: 3 },
{ group: 4 },
{ group: 4 },
],
[
]);
}
</script>
</head>
<body onload="init()">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
</body>
</html>