How to move nodes (drag and drop) only to particular level?

In the above diagram, Level 1 is ‘Mother’ Level 2 is ‘Father’ and all the children will be placed under ‘Other Juveniles section’. Now i can able to drag a child to father level and able to create a link.

I want to create a link only to father level. Now i can also able to drag and drop the child node to mother level (Level 1). How to restrict this? How to disable the node to drag and drop to level 1 or any other levels except level 2 (Father)?

How are you implementing the behavior of allowing drag-and-drop onto the “Father” node? Did you implement a GraphObject.mouseDrop event handler on the node? If so, don’t implement it on the “Mother” node – instead cancel its operation: e.diagram.currentTool.doCancel().

Also you probably want to do that in the Diagram.mouseDrop event handler, if you don’t want to allow drops in the background of the diagram.

Hi Walter,

How to differentiate ‘Mother’ or ‘Father’ node. Diagram is creating based on the json input we pass. Please find the code below… I’m using ‘mouseDragEnter’, ‘mouseDragLeave’, ‘mouseDrop’ events. In this how to find whether the node is mother (Level1) or father (Level2).

var goJS = go.GraphObject.make;
var myDiagram =
goJS(go.Diagram, “relationshipTreeDiv”,
initialContentAlignment: go.Spot.Center,
allowDragOut: true,
allowDrop: true,
‘animationManager.isEnabled’: false,
“commandHandler.archetypeGroupData”: { isGroup: true },
“ExternalObjectsDropped”: function(e) {
if (juvenileDiagram.commandHandler.canDeleteSelection() &&
!(juvenileDiagram.lastInput.control || juvenileDiagram.lastInput.meta)) {
maxSelectionCount: 1,
validCycle: go.Diagram.CycleDestinationTree,
treeStyle: go.TreeLayout.StyleLastParents,
arrangement: go.TreeLayout.ArrangementHorizontal,
angle: 90,
layerSpacing: 35,
alternateAngle: 90,
alternateLayerSpacing: 35,
alternateAlignment: go.TreeLayout.AlignmentBus,
alternateNodeSpacing: 20
“undoManager.isEnabled”: false
var self = this;
myDiagram.addDiagramListener(“Modified”, function(e) {
var idx = document.title.indexOf(“undefined”);
if (myDiagram.isModified) {;
if (idx < 0) document.title = document.title;
} else {
if (idx >= 0) document.title = document.title.substr(0, idx);

var levelColors = ["#F04F9F", “#377BB9”];
myDiagram.layout.commitNodes = function() {; {
if (v.node) {
var level = v.level % (levelColors.length);
var color = levelColors[level];
var shape = v.node.findObject(“SHAPE”);
if (shape) shape.fill = goJS(go.Brush, “Linear”, { 0: color, 1: go.Brush.lightenBy(color, 0.05), start: go.Spot.Left, end: go.Spot.Right });

function mayWorkFor(node1, node2) {
if (!(node1 instanceof go.Node)) return false;
if (node1 === node2) return false;
if (node2.isInTreeOf(node1)) return false;
return true;

function textStyle() {
return { font: “11pt ‘Open Sans’, Arial, sans-serif”, stroke: “white” };

myDiagram.nodeTemplate =
goJS(go.Node, “Auto”,
{ // handle dragging a Node onto a Node to (maybe) change the reporting relationship
mouseDragEnter: function (e, node, prev) {
var diagram = node.diagram;
var selnode = diagram.selection.first();
if (!mayWorkFor(selnode, node)) return;
var shape = node.findObject(“SHAPE”);
if (shape) {
shape._prevFill = shape.fill; // remember the original brush
shape.fill = “darkred”;
mouseDragLeave: function (e, node, next) {
var shape = node.findObject(“SHAPE”);
if (shape && shape._prevFill) {
shape.fill = shape._prevFill; // restore the original brush
mouseDrop: function (e, node) {
var diagram = node.diagram;
var selnode = diagram.selection.first(); // assume just one Node in selection
if (mayWorkFor(selnode, node)) {
// find any existing link into the selected node
var link = selnode.findTreeParentLink();
if (link !== null) { // reconnect any existing link
link.fromNode = node;
} else { // else create a new link
diagram.toolManager.linkingTool.insertLink(node, node.port, selnode, selnode.port);
{ fromSpot: go.Spot.Right,
toSpot: go.Spot.Left },
new go.Binding(“text”, “PersonName”),
new go.Binding(“layerName”, “isSelected”, function(sel) { return sel ? “Foreground” : “”; }).ofObject(),
goJS(go.Shape, “RoundedRectangle”,
new go.Binding(“fill”, “NodeColor”),
new go.Binding(“stroke”, “white”),
desiredSize: new go.Size(0, 65),
name: “”, fill: “white”, stroke: null,
portId: “”, fromLinkable: false, toLinkable: false, cursor: “pointer”, width:200,
goJS(go.Panel, “Horizontal”,

  goJS(go.Panel, "Table",
      maxSize: new go.Size(180, 70),
      margin: new go.Margin(3, 10, 3, 10),
      defaultAlignment: go.Spot.Left
    goJS(go.RowColumnDefinition, { column: 2, width: 4 }),
    goJS(go.TextBlock, textStyle(),  
        row: 0, column: 0, columnSpan: 4,
        isUnderline : true,
        font: "bold 11pt 'Open Sans', Arial, sans-serif",
        editable: false, isMultiline: true,
        margin: new go.Margin(0, 3, 3, 3)
      new go.Binding("text", "PersonName")),
      goJS(go.TextBlock, textStyle(),
        row: 1, column: 1, columnSpan: 4,
        font: "11pt 'Open Sans', Arial, sans-serif",
        editable: false, isMultiline: true,
        margin: new go.Margin(0, 3, 0, 3)
      new go.Binding("text", "RoleGenderDOB"))


myDiagram.linkTemplate =
  goJS(go.Link, go.Link.Orthogonal,
    { corner: 5, relinkableFrom: true, relinkableTo: true },
    goJS(go.Shape, { strokeWidth: 2, stroke: "#98C0E4" }));  // the link shape

this.diagram = myDiagram;

Maybe you want to change your data to make things easier on yourself. It seems like you have a “RoleGenderDOB” property that’s a string. You could probably separate these into three properties, then you could easily identify mothers and fathers using the “role” property.

ok Thank you. I changed Role as seperate property. How to differentiate and disable the node to drag and drop?

You probably should update the mayWorkFor function so it returns false if the node being dropped on isn’t a “father” role. You’ll probably want to refactor your code to make all the function names more relevant to your app as well.

function mayWorkFor(node1, node2) {
  if (!(node1 instanceof go.Node)) return false;
  if (node1 === node2) return false;
  if (node2.isInTreeOf(node1)) return false;
  // make sure node2 is a father node
  if ( !== "Father") return false;
  return true;

Thank you. This is working good. For example, if user drag and drop in the empty white space it wont form the link but the node will be placed in top right corner. How to restrict that? We should not drag and drop to white space.

Do as Walter mentioned earlier and implement a Diagram.mouseDrop handler which cancels the operation.

Yes I implemented for relationship tree layer.
myDiagram.mouseDrop = function(e) {

1.But if we drag from ‘Relationship Tree’ layer to ‘Other Juveniles’ Layer we have to drag and drop only child. But we should not drag ‘Father’ or ‘Mother’. If we write myDiagram.currentTool.doCancel() then we cant drag child also. How to implement this?

Change the mouseDrop function in the ‘Other Juveniles’ diagram to cancel only if the node’s role is not ‘Child’. You can get the role info via e.diagram.selection.first().data.role.

Thank you so much. Working!

One more question - Selecting any node and by pressing ‘Delete’ button in keyword is deleting that entire node from the tree. How to restrict this?

If you select a Node and then delete it, it removes that Node and the Links connected with that node. That is the default behavior for modifiable diagrams. Are you interested in some other behavior?

Yes. We should not delete a node by pressing on delete button. How to remove delete functionality?

You can disable all deletion by setting Diagram.allowDelete to false.
Read more about permissions at GoJS User Permissions -- Northwoods Software.

Yes I tried that. If we set Diagram.allowDelete to false then we can’t drag and drop from one layer to another. (from other juveniles to Relationship tree layer and viceversa).
GoJS Commands -- Northwoods Software, especially GoJS Commands -- Northwoods Software