One problem is making sure the keys (identifiers) remain unique across your little diagrams
Another problem is making sure that undo and redo work across little diagrams.
So I think it’s better to have a single GoJS Model in a single GoJS Diagram. Each of your little diagrams is implemented by a Group. One can show all of the groups at the same time, or just show one of them. Or I suppose you could show a different subset if you wanted. (I don’t know how variable the number of little diagrams will be.) Each Group can also be shown in a separate Layer, so it’s easy to show or hide them.
In any case, I assume you do not want to allow a link from one node to a node in a different group. That’s achieved by specifying a linkValidation predicate.
The node template and the link template have Part.containingGroupChanged event handler so that they can change their Part.layerName appropriately. I also have the link look different for links between groups than for links between nodes within a group.
It wasn’t clear how many ports you wanted on simple nodes or on groups, so I just decided to have one input and one output port per node or group. You can easily change that decision to do whatever you want. Same goes for the appearance and behavior of the nodes and the groups.
Here is the complete code, HTML and all:
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="https://unpkg.com/gojs"></script>
<script id="code">
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true,
// restrict links to either be within a single group or between groups
"linkingTool.linkValidation": function(fromnode, fromport, tonode, toport) {
return fromnode && tonode && fromnode.containingGroup === tonode.containingGroup;
},
"relinkingTool.linkValidation": function(fromnode, fromport, tonode, toport) {
return fromnode && tonode && fromnode.containingGroup === tonode.containingGroup;
},
"ModelChanged": function(e) { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
}
});
// have each group (i.e. diagram) be shown in a different layer
myDiagram.addLayerBefore($(go.Layer, { name: "g1" }), myDiagram.findLayer(""));
myDiagram.addLayerBefore($(go.Layer, { name: "g2" }), myDiagram.findLayer(""));
myDiagram.nodeTemplate =
$(go.Node, "Auto",
{ // make sure each node that belongs to a group is in the same layer
containingGroupChanged: function(node, oldgrp, newgrp) {
node.layerName = newgrp ? newgrp.key : "";
}
},
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
{ locationSpot: go.Spot.Center },
$(go.Panel, "Spot",
$(go.Panel, "Auto",
$(go.Shape,
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8, editable: true },
new go.Binding("text").makeTwoWay())
),
$(go.Shape, "Circle",
{
alignment: go.Spot.Left, toSpot: go.Spot.Left,
width: 8, height: 8, fill: "white",
portId: "in", toLinkable: true, cursor: "pointer"
}
),
$(go.Shape, "Circle",
{
alignment: go.Spot.Right, fromSpot: go.Spot.Right,
width: 8, height: 8,
portId: "out", fromLinkable: true, cursor: "pointer"
}
)
)
);
myDiagram.groupTemplate =
$(go.Group, "Vertical",
new go.Binding("layerName", "key"),
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
{ locationSpot: go.Spot.Center },
$(go.TextBlock,
{ font: "bold 12pt sans-serif" },
new go.Binding("text")),
$(go.Panel, "Spot",
$(go.Panel, "Auto",
$(go.Shape, { strokeWidth: 2, fill: "lightgray" }),
$(go.Placeholder, { padding: 10 })
),
$(go.Shape, "Circle",
{
alignment: go.Spot.Left, toSpot: go.Spot.Left,
width: 10, height: 10, fill: "white",
portId: "in", toLinkable: true, cursor: "pointer"
}
),
$(go.Shape, "Circle",
{
alignment: go.Spot.Right, fromSpot: go.Spot.Right,
width: 10, height: 10,
portId: "out", fromLinkable: true, cursor: "pointer"
}
)
)
);
myDiagram.linkTemplate =
$(go.Link,
{
fromPortId: "out", toPortId: "in",
relinkableFrom: true, relinkableTo: true,
containingGroupChanged: function(link, oldgrp, newgrp) {
link.path.strokeWidth = newgrp ? 1 : 2; // thicker if between groups
link.layerName = newgrp ? newgrp.key : ""; // make sure link belongs to correct layer
}
},
$(go.Shape, { strokeWidth: 2 }),
$(go.Shape, { toArrow: "OpenTriangle" })
);
myDiagram.model =
$(go.GraphLinksModel,
{
linkKeyProperty: "key",
linkFromPortIdProperty: "fp",
linkToPortIdProperty: "tp",
nodeDataArray:
[
{ key: "g1", text: "Model 1", isGroup: true },
{ key: 1, text: "Alpha", color: "lightblue", model: "1", group: "g1" },
{ key: 2, text: "Beta", color: "orange", group: "g1" },
{ key: "g2", text: "Model 2", isGroup: true },
{ key: 3, text: "Gamma", color: "lightgreen", group: "g2" },
{ key: 4, text: "Delta", color: "pink", group: "g2" },
],
linkDataArray:
[
{ from: 1, to: 2 },
{ from: 3, to: 4 },
{ from: "g1", to: "g2" }
]
});
}
function showGroup(key) {
myDiagram.commit(function(diag) {
diag.layers.each(function(layer) {
layer.visible = (!key || layer.name === key);
})
}, null); // don't record these changes in the UndoManager
}
</script>
</head>
<body onload="init()">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
<button onclick="showGroup(null)">Joint View</button>
<button onclick="showGroup('g1')">Diagram 1</button>
<button onclick="showGroup('g2')">Diagram 2</button>
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
</body>
</html>