OK, I just created a sample app that I believe does everything that you just asked for:
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2019 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<script id="code">
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true,
"animationManager.isEnabled": false,
"clickCreatingTool.archetypeNodeData": { text: "Node", color: "white" },
"commandHandler.archetypeGroupData": { isGroup: true, text: "Group" },
"SelectionGrouped": function(e) {
e.subject.layout.isValidLayout = false;
e.diagram.layoutDiagram();
e.subject.isSubGraphExpanded = false;
}
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape,
{ fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8, editable: true },
new go.Binding("text").makeTwoWay())
);
myDiagram.groupTemplate =
$(go.Group, "Vertical",
{
locationSpot: go.Spot.Center,
layout: $(go.CircularLayout, { isInitial: false, isOngoing: false }),
ungroupable: true,
subGraphExpandedChanged: function(group) {
group.type = group.isSubGraphExpanded ? go.Panel.Vertical : go.Panel.Auto;
}
},
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
new go.Binding("isSubGraphExpanded").makeTwoWay(),
$(go.Panel, "Auto",
$(go.Shape, { fill: "lightgray", strokeWidth: 2 },
new go.Binding("stroke", "color")),
$(go.Placeholder, { padding: 5 })
),
$(go.Panel, "Horizontal",
{ margin: 4 },
$(go.TextBlock, { font: "bold 12pt sans-serif" },
new go.Binding("text").makeTwoWay()),
$("SubGraphExpanderButton")
)
);
myDiagram.linkTemplate =
$(go.Link,
{
relinkableFrom: true, relinkableTo: true,
reshapable: true, resegmentable: true
},
$(go.Shape),
$(go.Shape, { toArrow: "OpenTriangle" })
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue" },
{ key: 2, text: "Beta", color: "orange" },
{ key: 3, text: "Gamma", color: "lightgreen" },
{ key: 4, text: "Delta", color: "pink" }
]);
}
function save() {
myDiagram.model.modelData.position = go.Point.stringify(myDiagram.position);
var str = myDiagram.model.toJson();
document.getElementById("mySavedModel").value = str;
}
function load() {
var str = document.getElementById("mySavedModel").value;
myDiagram.model = go.Model.fromJson(str);
var pos = myDiagram.model.modelData.position;
if (pos) myDiagram.initialPosition = go.Point.parse(pos);
}
</script>
</head>
<body onload="init()">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
<button id="loadModel" onclick="load()">Load</button>
<button id="saveModel" onclick="save()">Save</button>
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
</body>
</html>
I guess you’ll have to control-drag-copy (or copy-paste) the existing nodes so that you have more of them that you can group using control-G and ungroup using shift-control-G.