Another terminology correction: to avoid confusing the “children” of parent in trees and the “children” of groups, we only refer to nodes in groups as “members”. So “children” only are in trees. And Harry and Hermione are members of Gryffindor.
Try the LimitedDraggingTool in this sample:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Custom Dragging Avoiding Overlapping Groups</title>
<meta name="description" content="Demonstrates a custom DraggingTool that prevents moved or resized Nodes, including unselected Groups, from overlapping unmoving Nodes." />
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="go.js"></script>
<script id="code">
// The custom DraggingTool that disallows moving nodes such that their containing groups
// do not overlap their rectangular bounds with unchanging nodes or groups.
function LimitedDraggingTool() {
go.DraggingTool.call(this);
this._stationary = new go.Set(); // undragged Nodes or Groups that will not change bounds during the drag
this._changing = new go.Set(); // undragged Groups that might change bounds during the drag
this._lastOK = new go.Point(); // in document coordinates
}
go.Diagram.inherit(LimitedDraggingTool, go.DraggingTool);
// Just once for each drag compute the _stationary and _changing Sets.
LimitedDraggingTool.prototype.doActivate = function() {
go.DraggingTool.prototype.doActivate.call(this);
// collect unmoving nodes
// Future optimization: only consider stationary nodes that are "near" to the dragged nodes
var stationary = this._stationary;
stationary.clear();
var dragged = this.draggedParts; // already computed, includes member nodes of moving groups
// NOTE: dragged is a Map<Part, DraggingInfo>, and some of those Parts will be Links
this.diagram.nodes.each(function(n) {
// obviously nodes being dragged aren't stationary
if (dragged.has(n)) return;
// if N is a Group and is an (indirect) container of a dragged Part, N cannot be assumed to be stationary
if (n instanceof go.Group &&
dragged.any(function(kvp) {
return !(kvp.key instanceof go.Link) && kvp.key.isMemberOf(n);
})) {
return;
}
stationary.add(n);
});
// collect groups whose bounds may change due to the drag that are not
// selected or members of selected nodes but might still change bounds
var changing = this._changing;
changing.clear();
this.diagram.nodes.each(function(n) {
// ignore stationary and dragged parts
if (stationary.has(n) || dragged.has(n)) return;
changing.add(n);
});
// assume nodes start are initially not overlapping
this._lastOK = this.diagram.firstInput.documentPoint.copy();
}
// Cleanup
LimitedDraggingTool.prototype.doDeactivate = function() {
go.DraggingTool.prototype.doDeactivate.call(this);
this._stationary.clear();
this._changing.clear();
}
// See if there are now any undesired overlaps.
LimitedDraggingTool.prototype.hasOverlaps = function() {
var sit = this._stationary.iterator;
while (sit.next()) {
var st = sit.value;
var stb = st.actualBounds;
// see if any dragged part intersects with any stationary part
var dit = this.draggedParts.iteratorKeys;
while (dit.next()) {
var dr = dit.value;
if (dr instanceof go.Link) continue;
if (dr.actualBounds.intersectsRect(stb)) {
return true;
}
}
// see if any changing part intersects with any stationary part
var cit = this._changing.iterator;
while (cit.next()) {
var ch = cit.value;
if (!st.isMemberOf(ch) && ch.actualBounds.intersectsRect(stb)) {
return true;
}
}
}
return false;
}
// Try to move normally and see if that causes any overlaps.
// If it does, move back to the last known OK point.
LimitedDraggingTool.prototype.doMouseMove = function() {
go.DraggingTool.prototype.doMouseMove.call(this);
var e = this.diagram.lastInput;
var overlaps = this.hasOverlaps();
if (overlaps) {
e.documentPoint = this._lastOK;
e.viewPoint = e.diagram.transformDocToView(e.documentPoint);
go.DraggingTool.prototype.doMouseMove.call(this);
} else {
this._lastOK = e.documentPoint.copy();
}
}
// Try to move normally, but go back if it causes overlaps.
// Always call the super .doMouseUp method.
LimitedDraggingTool.prototype.doMouseUp = function() {
// first try moving to mouse point
go.DraggingTool.prototype.doMouseMove.call(this);
var e = this.diagram.lastInput;
var overlaps = this.hasOverlaps();
if (overlaps) {
// failed -- move to last known safe point
e.documentPoint = this._lastOK;
e.viewPoint = e.diagram.transformDocToView(e.documentPoint);
go.DraggingTool.prototype.doMouseMove.call(this);
}
go.DraggingTool.prototype.doMouseUp.call(this);
}
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{ // install custom DraggingTool
draggingTool: new LimitedDraggingTool(),
"undoManager.isEnabled": true
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc"),
{
locationSpot: go.Spot.Center,
toEndSegmentLength: 30,
fromEndSegmentLength: 30
},
$(go.Shape, "Rectangle",
{ fill: "white", desiredSize: new go.Size(30, 30) }),
$(go.TextBlock,
{ margin: 4 },
new go.Binding("text", "key"))
);
myDiagram.linkTemplate =
$(go.Link,
{ curve: go.Link.Bezier, toShortLength: 2 },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
myDiagram.groupTemplate =
$(go.Group, "Spot",
{
toSpot: go.Spot.AllSides, // links coming into groups at any side
toEndSegmentLength: 30,
fromEndSegmentLength: 30
},
$(go.Panel, "Auto",
$(go.Shape, "Rectangle",
{ parameter1: 14, fill: "rgba(255,0,0,0.10)" }),
$(go.Placeholder,
{ padding: 16 })
),
$(go.TextBlock,
{
alignment: go.Spot.TopLeft,
alignmentFocus: new go.Spot(0, 0, -4, -4),
font: "Bold 10pt Sans-Serif"
},
new go.Binding("text", "key"))
);
myDiagram.model = new go.GraphLinksModel(
[ // node data
{ key: "A", loc: new go.Point(320, 100) },
{ key: "B", loc: new go.Point(420, 200) },
{ key: "C", group: "Psi", loc: new go.Point(250, 225) },
{ key: "D", group: "Omega", loc: new go.Point(270, 325) },
{ key: "E", group: "Phi", loc: new go.Point(120, 225) },
{ key: "F", group: "Omega", loc: new go.Point(200, 350) },
{ key: "G", loc: new go.Point(180, 450) },
{ key: "Chi", isGroup: true },
{ key: "Psi", isGroup: true, group: "Chi" },
{ key: "Phi", isGroup: true, group: "Psi" },
{ key: "Omega", isGroup: true, group: "Psi" }
],
[ // link data
{ from: "A", to: "B" },
{ from: "A", to: "C" },
{ from: "A", to: "C" },
{ from: "B", to: "B" },
{ from: "B", to: "C" },
{ from: "B", to: "Omega" },
{ from: "C", to: "A" },
{ from: "C", to: "Psi" },
{ from: "C", to: "D" },
{ from: "D", to: "F" },
{ from: "E", to: "F" },
{ from: "F", to: "G" }
]);
}
</script>
</head>
<body onload="init()">
<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px black; height: 560px; display: inline-block; vertical-align: top; width: 70%"></div>
</div>
</body>
</html>
This is just the Navigation sample, Navigation of Graphs, augmented with the custom LimitedDraggingTool.