How to prevent node doesn't exceed the rack and doesn't overlap with other nodes?

I do not know what function or atribut is used to limit node. would you give me a few sample? thanks

Thanks walter, i have added dragComputation: avoidNodeOverlap inside myDiagram.nodeTemplate but it doesn’t look as expected. when an item inside rack, the item can’t be moved. any suggestion ?

here’s my code

function init() {
	var n = getQueryVariable("total");
	
	var $ = go.GraphObject.make;
	//================================================================================================
	function isUnoccupied(r, node) {
        var diagram = node.diagram;

        // nested function used by Layer.findObjectsIn, below
        // only consider Parts, and ignore the given Node and any Links
        function navig(obj) {
		var part = obj.part;
		if (part === node) return null;
		if (part instanceof go.Link) return null;
		return part;
        }

        // only consider non-temporary Layers
        var lit = diagram.layers;
        while (lit.next()) {
		var 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
    function avoidNodeOverlap(node, pt, gridpt) {
		
        // this assumes each node is fully rectangular
        var bnds = node.actualBounds;
        var loc = node.location;
		
        // see if the area at the proposed location is unoccupied
        // use PT instead of GRIDPT if you want to ignore any grid snapping behavior
        var x = gridpt.x - (loc.x - bnds.x);
        var y = gridpt.y - (loc.y - bnds.y);
        var r = new go.Rect(x, y, bnds.width, bnds.height);
		
        // maybe inflate R if you want some space between the node and any other nodes
        if (isUnoccupied(r, node)) return pt;  // OK
        return loc;  // give up -- don't allow the node to be moved to the new location
    }
	//================================================================================================
	
	myDiagram = $(go.Diagram, "myDiagramDiv", {
		initialDocumentSpot: go.Spot.Center,
		initialViewportSpot: go.Spot.Center,
		initialAutoScale: go.Diagram.Uniform,

		grid: $(go.Panel, "Grid", {
				gridCellSize: CellSize
			}, $(go.Shape, "LineH", {
				stroke: "lightgray"
			}), $(go.Shape, "LineV", {
				stroke: "lightgray"
			})
		),
		"draggingTool.isGridSnapEnabled": true,
		"draggingTool.gridSnapCellSpot": go.Spot.Center,
		allowDrop: true, 
		"ModelChanged": function(e) {},
		"animationManager.isEnabled": true,
		"undoManager.isEnabled": true,
	});
	
	myDiagram.nodeTemplate = $(go.Node, "Auto", {
		locationSpot: new go.Spot(0, 0, CellSize.width / 2, CellSize.height / 2),
		dragComputation: avoidNodeOverlap,
		mouseDragEnter: function(e, node) {
			e.handled = true;
			//node.findObject("SHAPE").fill = "red";
			highlightGroup(node.containingGroup, false);
		},
		mouseDragLeave: function(e, node) {
			node.updateTargetBindings();
		},
		mouseDrop: function(e, node) { // disallow dropping anything onto an "item"
			node.diagram.currentTool.doCancel(); 
		}
	},
	// always save/load the point that is the top-left corner of the node, not the location
		new go.Binding("position", "pos", go.Point.parse).makeTwoWay(go.Point.stringify),	
		new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(),
		
		$(go.Shape, "Rectangle", { 
			name: "SHAPE",
			stroke: null,
            fill: "rgba(255,255,255,.2)",
            minSize: CellSize,
            desiredSize: CellSize  // initially 1x1 cell
        },
		
        new go.Binding("fill", "color"),
        new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)),
		
		$(go.Picture, { margin: 10, width: 30, height: 30, background: null, imageStretch: go.GraphObject.Fill },
		new go.Binding("source")),

		$(go.TextBlock, {
			alignment: go.Spot.Center,
			font: 'normal 5px Segoe UI',
			stroke: "#000",
			background: "rgba(255,255,255,.9)",
		}, new go.Binding("text", "key"))
	); // end Node
	
	function highlightGroup(grp, show) {
		if (!grp) return;
		if (show) { // check that the drop may really happen into the Group
			var tool = grp.diagram.toolManager.draggingTool;
			var map = tool.draggedParts || tool.copiedParts; // this is a Map
			if (grp.canAddMembers(map.toKeySet())) {
				grp.isHighlighted = true;
				return;
			}
		}
		grp.isHighlighted = false;
	}
	
	var groupFill = "rgba(155,155,155,0.2)";
	var groupStroke = "#222";
	var dropFill = "rgba(155,155,155,0.2)";
	var dropStroke = "red";
	
	myDiagram.groupTemplate = $(go.Group, {
			layerName: "Background",
			rotatable: true, rotateObjectName: "SHAPE", 
			movable: false,
			// because the gridSnapCellSpot is Center, offset the Group's location
			locationSpot: new go.Spot(0, 0, CellSize.width / 2, CellSize.height / 2)
		},
		
		// always save/load the point that is the top-left corner of the node, not the location
		new go.Binding("position", "pos", go.Point.parse).makeTwoWay(go.Point.stringify), { // what to do when a drag-over or a drag-drop occurs on a Group
			mouseDragEnter: function(e, grp, prev) {
				highlightGroup(grp, true);
			},
			mouseDragLeave: function(e, grp, next) {
				highlightGroup(grp, false);
			},
			mouseDrop: function(e, grp) {
				var ok = grp.addMembers(grp.diagram.selection, true);
				if (!ok) grp.diagram.currentTool.doCancel();
			}
		}, $(go.Shape, "Rectangle", {
				name: "SHAPE",
				fill: groupFill,
				stroke: groupStroke,
				minSize: new go.Size(CellSize.width * 2, CellSize.height * 2)
			}, new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify), new go.Binding("fill", "isHighlighted", function(h) {
				return h ? dropFill : groupFill;
			}).ofObject(), new go.Binding("stroke", "isHighlighted", function(h) {
				return h ? dropStroke : groupStroke;
			}).ofObject())
		
	);
			
	// decide what kinds of Parts can be added to a Group
	myDiagram.commandHandler.memberValidation = function(grp, node) {
		if (grp instanceof go.Group && node instanceof go.Group) return false; 
		return true;
	};
	// what to do when a drag-drop occurs in the Diagram's background
	myDiagram.mouseDragOver = function(e) {
		if (!AllowTopLevel) {
			if (!e.diagram.selection.all(function(p) {
					return p instanceof go.Group;
				})) {
				e.diagram.currentCursor = "not-allowed";
			}
		}
	};
	
	myDiagram.mouseDrop = function(e) {
		if (AllowTopLevel) {
			if (!e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true)) {
				e.diagram.currentTool.doCancel();
			}
		} else {
			if (!e.diagram.selection.all(function(p) {
					return p instanceof go.Group;
				})) {
				e.diagram.currentTool.doCancel();
			}
		}
	};
	var space_node = 0;
	var a = [];
	for (i = 1; i <= n; i++) {
		space_node = space_node + 100;
		var b = { key: "G", isGroup: true, pos:""+ space_node +" 0", size: "100 100" };
		a.push(b);
		
	}
	myDiagram.model = new go.GraphLinksModel(a);
	
	myPalette = $(go.Palette, "myPalette", { 
		nodeTemplate: myDiagram.nodeTemplate,
		groupTemplate: myDiagram.groupTemplate,
		layout: $(go.GridLayout)
	});
	
	var item = [];
		<?php while($row = db_fetch($result)) { ?>''
	               var c = {key: "g", size: "100 100", source: ""+<?php echo $row['image']?>+""}
	               item.push(c);
	      <?php } ?>
	myPalette.model = new go.GraphLinksModel(item);
	myDiagram.findNodeForKey(1).isSelected = true;
	
	function make_image(){
		myDiagram.makeImage();
	}
}

Try setting Group.avoidable to false.

Still not working walter.

myDiagram.groupTemplate = $(go.Group, {
	layerName: "Background",
	avoidable: false,
	rotatable: true, rotateObjectName: "SHAPE", 
	movable: false,
	// because the gridSnapCellSpot is Center, offset the Group's location
	locationSpot: new go.Spot(0, 0, CellSize.width / 2, CellSize.height / 2)
},

Improve your navig function to check for and ignore Groups.

I have added my code like this. cmiiw

function isUnoccupied(r, node, group) {
     var diagram = node.diagram;

     // nested function used by Layer.findObjectsIn, below
     // only consider Parts, and ignore the given Node and any Links
        function navig(obj) {
		var part = obj.part;
		if (part === node) return null;
		if (part === group) return null;
		if (part instanceof go.Link) return null;
		return part;
        }

        // only consider non-temporary Layers
        var lit = diagram.layers;
        while (lit.next()) {
		var lay = lit.value;
		if (lay.isTemporary) continue;
		if (lay.findObjectsIn(r, navig, null, true).count > 0) return false;
        }
		
        return true;
    }

You must also have changed how isUnoccupied is called.

My suggestion was to test instead for:

                if (part instanceof go.Group) return null;

But maybe you would want to allow nodes to be moved out of groups and into other groups. Depends on what policy you want to implement.

Thanks walter, it almost done. But the item could still overlap the rack. What should i do? I want to something like this. link

Make that navig test for groups be even more specific – if it’s a rack, don’t return null.

I have added my code like this. but still doesn’t work

function navig(obj) {
	var part = obj.part;
	if (part === node) return null;
	if (part instanceof go.Link) return null;
	if (part instanceof go.Group) return null;
			
	if (go.Group === "rack") return part; // <-- doesn't work
        // if (go.Shape === "rack") return part; <-- doesn't work
        // if (part instanceof go.Group.name) return part; <-- doesn't work
        // if (part instanceof go.Shape.name) return part; <-- doesn't work
	return part;
}

and it’s my rack shape inside groupTemplate.

    myDiagram.groupTemplate = $(go.Group, {
    	layerName: "Background", 
    	movable: false,
    	// because the gridSnapCellSpot is Center, offset the Group's location
    	locationSpot: new go.Spot(0, 0, CellSize.width / 2, CellSize.height / 2)
    },
    		
            // always save/load the point that is the top-left corner of the node, not the location
    	new go.Binding("position", "pos", go.Point.parse).makeTwoWay(go.Point.stringify), { // what to do when a drag-over or a drag-drop occurs on a Group
    		mouseDragEnter: function(e, grp, prev) {
    			highlightGroup(grp, true);
    		},
    		mouseDragLeave: function(e, grp, next) {
    			highlightGroup(grp, false);
    		},
    		mouseDrop: function(e, grp) {
    			var ok = grp.addMembers(grp.diagram.selection, true);
    			if (!ok) grp.diagram.currentTool.doCancel();
    		}
    		}, $(go.Shape, "Rectangle", {
    			name: "rack",
    			fill: groupFill,
    			stroke: groupStroke,
    			minSize: new go.Size(CellSize.width * 2, CellSize.height * 2)
    		}, new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify), new go.Binding("fill", "isHighlighted", function(h) {
    			return h ? dropFill : groupFill;
    		}).ofObject(), new go.Binding("stroke", "isHighlighted", function(h) {
    			return h ? dropStroke : groupStroke;
    		}).ofObject())
    		
    );

I honestly cannot tell what you want to do. First you say that you want to allow nodes (items) to be moved on groups (racks). I give you the solution for that, but then you say that you don’t want to allow nodes to move onto groups.

i’m sorry walter, maybe because my bad grammar. I have 2 request here

  1. I want every node (items) inside rack cannot stack with other nodes (items). It has been successfully.
  2. i want, when i drag node (items) inside rack the nodes (items) cannot exceed the rack. in planogram example the nodes still can exceed the rack. look at here

i want to like this link

once again, forgive me for my bad grammar. i hope you understand. thanks

Ah, OK, I understand you now. If you look at the samples, you’ll see that behavior in the Swim Lanes sample, Swim Lanes.

Also, since you want to change the behavior while dragging the selection, it is natural to modify the DraggingTool. If you take a look at its documentation, DraggingTool | GoJS API, you’ll see that it includes an example function stayInGroup that you can adapt for your own purposes and use as the Node.dragComputation in your node template(s). Part | GoJS API

In general all of the most commonly desired appearances and behaviors are discussed in the Introduction pages ( GoJS Introduction -- Northwoods Software ), demonstrated in the samples ( GoJS Sample Diagrams for JavaScript and HTML, by Northwoods Software ) or mentioned in the API documentation ( GoJS API ).

Thanks walter, Its worked. But I have 1 problem here. is it possible to use dragComputation atribut 2 times in node template? because I’ve used dragComputation for avoidNodeOverlap. Any suggestion?

In JavaScript an object property can have only one value, so you need to combine the functions into one function that does what you want.

Hi walter, i have joined them to planogram function but still not worked and give error. Here’s my code.

       var pf = [];
       function planogram(node, pt, gridpt) {
              pf.push(avoidNodeOverlap(node, pt, gridpt), stayInGroup(node, pt, gridpt));
	      while(pf.length != 0){
		  return pf.pop();
	      }
        }

          // a Part.dragComputation function that prevents a Part from being dragged to overlap another Part
        function avoidNodeOverlap(node, pt, gridpt) {

            // this assumes each node is fully rectangular
            var bnds = node.actualBounds;
            var locate = node.location;

            // see if the area at the proposed location is unoccupied
            // use PT instead of GRIDPT if you want to ignore any grid snapping behavior
            var x = gridpt.x - (locate.x - bnds.x);
            var y = gridpt.y - (locate.y - bnds.y);
            var r = new go.Rect(x, y, bnds.width, bnds.height);

            // maybe inflate R if you want some space between the node and any other nodes
            if (isUnoccupied(r, node)) return pt;  // OK
            return locate;  // give up -- don't allow the node to be moved to the new location      
        }

        function stayInGroup(part, pt, gridpt) {
            // don't constrain top-level nodes
            var grp = part.containingGroup;
            if (grp === null) return pt;
            // try to stay within the background Shape of the Group
            var back = grp.findObject("rack");
            if (back === null) return pt;
            // allow dragging a Node out of a Group if the Shift key is down
            if (part.diagram.lastInput.shift) return pt;
            var p1 = back.getDocumentPoint(go.Spot.TopLeft);
            var p2 = back.getDocumentPoint(go.Spot.BottomRight);
            var b = part.actualBounds;
            var loc = part.location;
            // find the padding inside the group's placeholder that is around the member parts
            var m = (grp.placeholder !== null ? grp.placeholder.padding : new go.Margin(0));
            // now limit the location appropriately
            var x = Math.max(p1.x + m.left, Math.min(pt.x, p2.x - m.right - b.width - 1)) + (loc.x-b.x);
            var y = Math.max(p1.y + m.top, Math.min(pt.y, p2.y - m.bottom - b.height - 1)) + (loc.y-b.y);
            return new go.Point(x, y);
        }

I also tried to call stayInGroup function inside avoidNodeOverlap function, but stayInGroup function be ignored.

          // a Part.dragComputation function that prevents a Part from being dragged to overlap another Part
        function avoidNodeOverlap(node, pt, gridpt) {
            stayInGroup(part, pt, gridpt);
            // this assumes each node is fully rectangular
            var bnds = node.actualBounds;
            var locate = node.location;

            // see if the area at the proposed location is unoccupied
            // use PT instead of GRIDPT if you want to ignore any grid snapping behavior
            var x = gridpt.x - (locate.x - bnds.x);
            var y = gridpt.y - (locate.y - bnds.y);
            var r = new go.Rect(x, y, bnds.width, bnds.height);

            // maybe inflate R if you want some space between the node and any other nodes
            if (isUnoccupied(r, node)) return pt;  // OK
            return locate;  // give up -- don't allow the node to be moved to the new location      
        }

        function stayInGroup(part, pt, gridpt) {
            // don't constrain top-level nodes
            var grp = part.containingGroup;
            if (grp === null) return pt;
            // try to stay within the background Shape of the Group
            var back = grp.findObject("rack");
            if (back === null) return pt;
            // allow dragging a Node out of a Group if the Shift key is down
            if (part.diagram.lastInput.shift) return pt;
            var p1 = back.getDocumentPoint(go.Spot.TopLeft);
            var p2 = back.getDocumentPoint(go.Spot.BottomRight);
            var b = part.actualBounds;
            var loc = part.location;
            // find the padding inside the group's placeholder that is around the member parts
            var m = (grp.placeholder !== null ? grp.placeholder.padding : new go.Margin(0));
            // now limit the location appropriately
            var x = Math.max(p1.x + m.left, Math.min(pt.x, p2.x - m.right - b.width - 1)) + (loc.x-b.x);
            var y = Math.max(p1.y + m.top, Math.min(pt.y, p2.y - m.bottom - b.height - 1)) + (loc.y-b.y);
            return new go.Point(x, y);
        }

any suggestion?

As the documentation states, Part | GoJS API, the function must return a Point that is the new location.

Your function calls stayInGroup but doesn’t make use of the result to limit the result of the whole function.