Here’s some code that I think implements what was originally asked for. It does not use any new functionality in GoJS. Well, at least none that is relevant to the problem.
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2022 by Northwoods Software Corporation. -->
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
<script src="https://unpkg.com/gojs"></script>
<script id="code">
const $ = go.GraphObject.make;
const myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true,
"ModelChanged": e => { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
}
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, { fill: "white" }),
$(go.TextBlock,
{ margin: 8, editable: true },
new go.Binding("text").makeTwoWay())
);
// Assume this is used as a Group.layout
class CenteringGridLayout extends go.GridLayout {
// As an extra step in doLayout, shift all of the Group.memberParts so that the
// whole group remains horizontally centered at the same point in document coordinates
commitLayers(layerRects, offset) {
if (this.group === null) return;
// BUG!? when the first member node is added, the group's actualBounds have been updated already,
// even before doLayout is called. That causes the group's location to shifted before
// this layout can try to maintain the location.x value of the group
const x = this.group.actualBounds.centerX;
const mbnds = this.diagram.computePartsBounds(this.group.memberParts);
if (!isNaN(x) && mbnds.isReal()) {
const dx = x - mbnds.centerX;
this.diagram.moveParts(this.group.memberParts, new go.Point(dx, 0));
}
}
} // end CenteringGridLayout
myDiagram.groupTemplate =
$(go.Group, "Vertical",
{
locationSpot: go.Spot.Top, // must have Spot.x === 0.5
layout: $(CenteringGridLayout, { wrappingColumn: 6 }),
doubleClick: (e, grp) => { // add a node to the group
const ctr = grp.getDocumentPoint(go.Spot.Center); // start new node at center of group
e.diagram.model.commit(m => {
const loc = grp.location.copy();
m.addNodeData({ text: "new node", group: grp.key, loc: go.Point.stringify(ctr) });
// BUG: the next two lines (and remembering the original location, above)
// are necessary to keep the group at the original location,
// for the first 2 additions to the group
grp.ensureBounds();
grp.move(loc, true);
});
}
},
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.TextBlock, new go.Binding("text")),
$(go.Panel, "Auto",
$(go.Shape, { fill: "#00000008", minSize: new go.Size(400, NaN) }),
$(go.Placeholder, { padding: 10, alignment: go.Spot.Top })
)
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 0, text: "Double click to add a member node", isGroup: true, loc: "0 0" },
]);
</script>
</body>
</html>