create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
260 lines (226 loc) • 11.7 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
<meta name="description" content="Limit the dragging of nodes to avoid any overlap with other nodes." />
<meta itemprop="description" content="Limit the dragging of nodes to avoid any overlap with other nodes." />
<meta property="og:description" content="Limit the dragging of nodes to avoid any overlap with other nodes." />
<meta name="twitter:description" content="Limit the dragging of nodes to avoid any overlap with other nodes." />
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="../assets/css/style.css">
<!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
<meta itemprop="name" content="Limit Dragging of Nodes to Unoccupied Areas in Diagram" />
<meta property="og:title" content="Limit Dragging of Nodes to Unoccupied Areas in Diagram" />
<meta name="twitter:title" content="Limit Dragging of Nodes to Unoccupied Areas in Diagram" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/dragunoccupied.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/dragunoccupied.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/dragunoccupied.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/dragUnoccupied.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/dragUnoccupied.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Limit Dragging of Nodes to Unoccupied Areas in Diagram | GoJS Diagramming Library
</title>
</head>
<body>
<!-- This top nav is not part of the sample code -->
<nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200">
<div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4">
<a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo"
href="../">
GoJS
</a>
<div class="relative">
<button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="topnavList" class="hidden md:block">
<div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold
md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit">
<a href="../learn/">Learn</a>
<a href="../samples/">Samples</a>
<a href="../intro/">Intro</a>
<a href="../api/">API</a>
<a href="../download.html">Download</a>
<a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a>
<a id="tc" href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
<a id="tb" href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</div>
</div>
</div>
</div>
</nav>
<script>
window.addEventListener("DOMContentLoaded", function () {
// topnav
var topButton = document.getElementById("topnavButton");
var topnavList = document.getElementById("topnavList");
if (topButton && topnavList) {
topButton.addEventListener("click", function (e) {
topnavList
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) {
topButton.click();
}
});
// set active <a> element
var url = window
.location
.href
.toLowerCase();
var aTags = topnavList.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.endsWith('.html'))
lowerhref = lowerhref.slice(0, -5);
if (url.startsWith(lowerhref)) {
aTags[i]
.classList
.add('active');
break;
}
}
}
});
</script>
<div class="flex flex-col prose">
<div class="w-full max-w-screen-xl mx-auto">
<!-- * * * * * * * * * * * * * -->
<!-- Start of GoJS sample code -->
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<div id="allSampleContent" class="p-4 w-full">
<script id="code">
function init() {
// R is a Rect in document coordinates
// NODE is the Node being moved -- ignore when looking for Parts intersecting the Rect
function isUnoccupied(r, node) {
const diagram = node.diagram;
// nested function used by Layer.findObjectsIn, below
// only consider Parts, and ignore the given Node, any Links, and Group members
function navig(obj) {
const part = obj.part;
if (part === node) return null;
if (part instanceof go.Link) return null;
if (part.isMemberOf(node)) return null;
if (node.isMemberOf(part)) return null;
return part;
}
// only consider non-temporary Layers
const lit = diagram.layers;
while (lit.next()) {
const lay = lit.value;
if (lay.isTemporary) continue;
if (lay.findObjectsIn(r, navig, null, true).count > 0) return false;
}
return true;
}
// a Part.dragComputation function that prevents a Part from being dragged to overlap another Part
// use PT instead of GRIDPT if DraggingTool.isGridSnapEnabled but movement should not snap to grid
function avoidNodeOverlap(node, pt, gridpt) {
if (node.diagram instanceof go.Palette) return gridpt;
// this assumes each node is fully rectangular
const bnds = node.actualBounds;
const loc = node.location;
// use PT instead of GRIDPT if you want to ignore any grid snapping behavior
// see if the area at the proposed location is unoccupied
const r = new go.Rect(gridpt.x - (loc.x - bnds.x), gridpt.y - (loc.y - bnds.y), bnds.width, bnds.height);
// maybe inflate R if you want some space between the node and any other nodes
r.inflate(-0.5, -0.5); // by default, deflate to avoid edge overlaps with "exact" fits
// when dragging a node from another Diagram, choose an unoccupied area
if (!(node.diagram.currentTool instanceof go.DraggingTool) && (!node._temp || !node.layer.isTemporary)) {
// in Temporary Layer during external drag-and-drop
node._temp = true; // flag to avoid repeated searches during external drag-and-drop
while (!isUnoccupied(r, node)) {
r.x += 10; // note that this is an unimaginative search algorithm --
r.y += 2; // you can improve the search here to be more appropriate for your app
}
r.inflate(0.5, 0.5); // restore to actual size
// return the proposed new location point
return new go.Point(r.x - (loc.x - bnds.x), r.y - (loc.y - bnds.y));
}
if (isUnoccupied(r, node)) return gridpt; // OK
return loc; // give up -- don't allow the node to be moved to the new location
}
myDiagram = new go.Diagram('myDiagramDiv', {
'animationManager.isEnabled': false,
'undoManager.isEnabled': true
});
// Define the template for Nodes, just some text inside a colored rectangle
myDiagram.nodeTemplate =
new go.Node('Auto', {
dragComputation: avoidNodeOverlap,
minSize: new go.Size(50, 20),
resizable: true
})
.bindTwoWay('desiredSize', 'size', go.Size.parse, go.Size.stringify)
.bindTwoWay('position', 'pos', go.Point.parse, go.Point.stringify)
// temporarily put selected nodes in Foreground layer
.bindObject('layerName', 'isSelected', s => s ? 'Foreground' : '')
.add(
new go.Shape('Rectangle')
.bind('fill', 'color'),
new go.TextBlock()
.bind('text', 'color')
);
myDiagram.model = new go.GraphLinksModel([
{ pos: '-30 0', size: '50 300', color: go.Brush.randomColor() },
{ pos: '120 20', size: '300 50', color: go.Brush.randomColor() },
{ pos: '100 200', size: '300 50', color: go.Brush.randomColor() },
{ pos: '500 50', size: '50 300', color: go.Brush.randomColor() },
{ key: 1, pos: '100 100', size: '50 50', color: 'gray' },
{ key: 2, pos: '200 140', size: '50 50', color: 'gray' }
]);
myDiagram.findNodeForKey(1).isSelected = true;
// initialize the Palette that is on the left side of the page
myPalette = new go.Palette('myPaletteDiv', {
nodeTemplateMap: myDiagram.nodeTemplateMap, // share the templates used by myDiagram
model: new go.GraphLinksModel([
// specify the contents of the Palette
{ size: '50 50', color: go.Brush.randomColor() },
{ size: '60 40', color: go.Brush.randomColor() }
])
});
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div style="width: 100%; display: flex; justify-content: space-between">
<div id="myPaletteDiv" style="width: 100px; background-color: floralwhite; border: solid 1px black; margin-right: 2px"></div>
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 400px"></div>
</div>
<p>
Drag a node around. Notice how you cannot force the dragged node to overlap any other (stationary) node. If you drag more than one node, notice how the
relative positions of the dragged nodes are maintained except when forced to be shifted in order to avoid overlapping other nodes.
</p>
<p>
This functionality is implemented by a custom <a>Part.dragComputation</a> property function, which affects how the <a>DraggingTool</a> can move selected
nodes. You will want to adjust how it finds an empty spot for the dragged node when dragging from another Diagram.
</p>
</div>
</div>
<!-- * * * * * * * * * * * * * -->
<!-- End of GoJS sample code -->
</div>
<div id="allTagDescriptions" class="p-4 w-full max-w-screen-xl mx-auto">
<hr/>
<h3 class="text-xl">GoJS Features in this sample</h3>
</div>
</div>
</body>
<!-- This script is part of the gojs.net website, and is not needed to run the sample -->
<script src="../assets/js/goSamples.js"></script>
</html>