It appears that you want each “expand” or “collapse” to be laid out in the same way each time. It might be easiest to just define a subclass of ForceDirectedLayout that resets randomNumberGenerator each time.
Regarding the problems with root nodes being far away, that’s because they are still participating in the ForceDirectedLayout each time, until they are at least ForceDirectedLayout.infinityDistance away. One solution would be to use ArrangingLayout to put the singleton nodes in a row or column.
I’ve done all of those changes in a cleaned-up version of your code:
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:800px"></div>
<button onclick="refresh()">Refresh</button>
<button onclick="toggle()">Expand/Collapse</button>
<script src="https://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script>
<script src="https://cdn.jsdelivr.net/npm/create-gojs-kit/dist/extensions/ArrangingLayout.js"></script>
<script id="code">
class RepeatableForceDirectedLayout extends go.ForceDirectedLayout {
constructor(init) {
super();
this.randomNumberGenerator = null;
if (init) Object.assign(this, init);
}
doLayout(coll) {
this.randomNumberGenerator = null;
super.doLayout(coll);
}
}
// one-time initialization
const myDiagram =
new go.Diagram("myDiagramDiv", {
layout:
new ArrangingLayout({
primaryLayout:
new RepeatableForceDirectedLayout({
defaultSpringLength: 30,
defaultElectricalCharge: 200,
prelayoutSpread: 50,
})
}),
"animationManager.isEnabled": false,
autoScale: go.Diagram.Uniform,
"undoManager.isEnabled": true
});
myDiagram.nodeTemplate =
new go.Node("Vertical")
.add(
new go.Shape({ fill: "yellow", width: 100, height: 40 }),
new go.TextBlock({ stretch: go.Stretch.Horizontal, textAlign: "center" })
.bind("text", "name")
);
// end one-time initialization
const additionalNodes = [
{ key: 1, name: "Demo Assembly Component ID-1" },
{ key: 2, name: "Demo Assembly Component ID-2" },
{ key: 3, name: "Demo Assembly Component ID-3" },
{ key: 4, name: "Demo Assembly Component ID-4" },
{ key: 5, name: "Demo Assembly Component ID-5" },
{ key: 6, name: "Demo Assembly Component ID-6" },
{ key: 7, name: "Demo Assembly Component ID-7" },
{ key: 8, name: "Demo Assembly Component ID-8" },
{ key: 9, name: "Demo Assembly Component ID-9" },
{ key: 10, name: "Demo Assembly Component ID-10" },
{ key: 11, name: "Demo Assembly Component ID-11" },
{ key: 12, name: "Demo Assembly Component ID-12" },
{ key: 13, name: "Demo Assembly Component ID-13" },
{ key: 14, name: "Demo Assembly Component ID-14" },
{ key: 15, name: "Demo Assembly Component ID-15" },
{ key: 16, name: "Demo Assembly Component ID-16" },
{ key: 17, name: "Demo Assembly Component ID-17" },
{ key: 18, name: "Demo Assembly Component ID-18" },
{ key: 19, name: "Demo Assembly Component ID-19" },
{ key: 20, name: "Demo Assembly Component ID-20" },
{ key: 21, name: "Demo Assembly Component ID-21" },
{ key: 22, name: "Demo Assembly Component ID-22" },
{ key: 23, name: "Demo Assembly Component ID-23" },
{ key: 24, name: "Demo Assembly Component ID-24" },
{ key: 25, name: "Demo Assembly Component ID-25" },
{ key: 26, name: "Demo Assembly Component ID-26" },
{ key: 27, name: "Demo Assembly Component ID-27" },
{ key: 28, name: "Demo Assembly Component ID-28" },
{ key: 29, name: "Demo Assembly Component ID-29" },
{ key: 30, name: "Demo Assembly Component ID-30" },
{ key: 31, name: "Demo Assembly Component ID-31" },
{ key: 32, name: "Demo Assembly Component ID-32" },
{ key: 33, name: "Demo Assembly Component ID-33" },
{ key: 34, name: "Demo Assembly Component ID-34" },
{ key: 50, name: "Demo Assembly Component ID-35" },
{ key: 51, name: "Demo Assembly Component ID-36" },
{ key: 52, name: "Demo Assembly Component ID-37" },
{ key: 53, name: "Demo Assembly Component ID-38" },
{ key: 54, name: "Demo Assembly Component ID-39" },
{ key: 55, name: "Demo Assembly Component ID-40" },
{ key: 56, name: "Demo Assembly Component ID-41" },
{ key: 57, name: "Demo Assembly Component ID-42" },
{ key: 58, name: "Demo Assembly Component ID-43" },
{ key: 59, name: "Demo Assembly Component ID-44" },
{ key: 60, name: "Demo Assembly Component ID-45" },
{ key: 61, name: "Demo Assembly Component ID-46" },
{ key: 62, name: "Demo Assembly Component ID-47" },
];
const additionalLinks = [
{ from: 0, to: 1 },
{ from: 1, to: 2 },
{ from: 1, to: 3 },
{ from: 0, to: 4 },
{ from: 4, to: 5 },
{ from: 4, to: 6 },
{ from: 4, to: 7 },
{ from: 0, to: 8 },
{ from: 8, to: 9 },
{ from: 9, to: 10 },
{ from: 9, to: 11 },
{ from: 9, to: 12 },
{ from: 8, to: 13 },
{ from: 13, to: 14 },
{ from: 13, to: 15 },
{ from: 13, to: 16 },
{ from: 8, to: 17 },
{ from: 8, to: 18 },
{ from: 8, to: 19 },
{ from: 19, to: 20 },
{ from: 19, to: 21 },
{ from: 19, to: 22 },
{ from: 8, to: 23 },
{ from: 0, to: 24 },
{ from: 24, to: 25 },
{ from: 24, to: 26 },
{ from: 24, to: 27 },
{ from: 24, to: 28 },
{ from: 24, to: 29 },
{ from: 24, to: 30 },
{ from: 24, to: 31 },
{ from: 24, to: 32 },
{ from: 24, to: 33 },
{ from: 0, to: 34 },
{ from: 34, to: 9 },
{ from: 34, to: 13 },
{ from: 34, to: 17 },
{ from: 34, to: 18 },
{ from: 34, to: 19 },
{ from: 34, to: 23 },
{ from: 0, to: 50 },
{ from: 50, to: 51 },
{ from: 50, to: 52 },
{ from: 50, to: 53 },
{ from: 50, to: 54 },
{ from: 50, to: 55 },
{ from: 50, to: 56 },
{ from: 50, to: 57 },
{ from: 50, to: 58 },
{ from: 50, to: 59 },
{ from: 50, to: 60 },
{ from: 50, to: 61 },
{ from: 50, to: 62 },
];
const baseNodes = [
{ key: 0, name: "Demo Assembly Component ID-0" },
{ key: 63, name: "Demo Assembly Component ID-48" },
];
const baseLinks = [
];
function refresh() {
myDiagram.model = new go.GraphLinksModel(
[...baseNodes, ...additionalNodes],
[...baseLinks, ...additionalLinks]
);
}
function toggle() {
if (myDiagram.model.nodeDataArray.length > baseNodes.length) {
myDiagram.model.commit(m => {
m.removeNodeDataCollection(additionalNodes);
m.removeLinkDataCollection(additionalLinks);
});
} else {
myDiagram.model.commit(m => {
m.addNodeDataCollection(additionalNodes);
m.addLinkDataCollection(additionalLinks);
});
}
}
window.addEventListener("DOMContentLoaded", () => refresh());
</script>
</body>
</html>