Try this:
<!DOCTYPE html>
<html>
<head>
<title>Finding an empty spot within an area</title>
<!-- Copyright 1998-2020 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 });
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, { fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock, { margin: 8 },
new go.Binding("text"))
);
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" }
],
[
{ from: 1, to: 2 },
{ from: 1, to: 3 },
{ from: 2, to: 2 },
{ from: 3, to: 4 },
{ from: 4, to: 1 }
]);
}
function test() {
myDiagram.commit(function(diag) {
var data = { text: "NEW " + diag.nodes.count };
// search in a band starting with the current documentBounds up to 1000
// in the direction of ANGLE
var bounds = diag.documentBounds.copy().subtractMargin(diag.padding);
var angle = 0;
if (angle === 0) bounds.width = 1000;
else if (angle === 90) bounds.height = 1000;
else if (angle === 180) { bounds.width = 1000; bounds.x -= 1000; }
else if (angle === 270) { bounds.height = 1000; bounds.y -= 1000; }
// temporarily add the data/node just so we can find out how big it really is
var oldskips = myDiagram.skipsUndoManager;
diag.skipsUndoManager = true; // ignore all side-effects from adding the node temporarily
diag.model.addNodeData(data); // add the node data temporarily
var node = diag.findNodeForData(data); // find the corresponding Node
node.position = bounds.position; // temporarily put it within the document bounds
node.ensureBounds(); // make sure the node has been measured and arranged
var nodesize = node.actualBounds.size; // and remember its size
diag.model.removeNodeData(data); // remove the temporary node
diag.skipsUndoManager = oldskips;
var found = findUnoccupiedRect(myDiagram, bounds, angle, nodesize);
if (found) { // found an empty area?
// now add the node for real, and position it in the empty area
diag.model.addNodeData(data);
node = diag.findNodeForData(data);
node.position = found.position;
}
});
}
// BOUNDS specifies the area to search for empty space (no Avoidable Nodes) of at least size SIZE.
// ANGLE specifies the direction (0, 90, 180, 270) in which to search the area.
// SKIP is an optional Node that should be ignored when checking for existing Avoidable Nodes in the area.
function findUnoccupiedRect(diag, bounds, angle, size, skip) {
if (!diag) return null;
if (skip === undefined) skip = null;
var r = new go.Rect(bounds.position, size);
var a, b, ma, mb, da, db;
if (angle === 0) {
a = bounds.left; ma = bounds.right - size.width; da = 8;
b = bounds.top; mb = bounds.bottom - size.height; db = 8;
} else if (angle === 90) {
a = bounds.top; ma = bounds.bottom - size.height; da = 8;
b = bounds.left; ma = bounds.right - size.width; db = 8;
} else if (angle === 180) {
a = bounds.right - size.width; ma = bounds.left; da = -8;
b = bounds.top; mb = bounds.bottom - size.height; db = 8;
} else if (angle === 270) {
a = bounds.bottom - size.height; ma = bounds.top; da = -8;
b = bounds.left; ma = bounds.right - size.width; db = 8;
} else {
throw new Error("unknown angle for findUnoccupiedRect: " + angle);
}
var s = b;
for (; (da > 0) ? a < ma : a > ma; a += da) {
if (angle === 0 || angle === 180) {
r.x = a;
} else {
r.y = a;
}
for (; (db > 0) ? b < mb : b > mb; b += db) {
if (angle === 0 || angle === 180) {
r.y = b;
} else {
r.x = b;
}
var empty = diag.isUnoccupied(r, skip);
if (empty) return r;
}
b = s;
}
return null;
}
</script>
</head>
<body onload="init()">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:500px"></div>
<button onclick="test()">Test</button>
</body>
</html>