Group shifts towards top when dragging and reordering its members

I have nested group structure and whenever I drag and reorder any existing members inside it, the group jumps and shifts. Is there a I can affix the group so that it doesn’t change it’s location when the existing members are being moved around.

If your Group has a Placeholder, and if the user moves a member Node up (lower value for node.location.y) the user would want the group to continue surrounding the node.

But perhaps you want to set Group.computesBoundsAfterDrag to true?

Also, the location of the group is ultimately determined by the layout responsible for positioning the group. What layout are you using for the Diagram and for the Group itself?

Already set.


This is group options setup.

I managed to get it almost working using custom stayInGroup (no x-axis movement) only y-axis movement -

Now the group doesn’t jump much but sometimes the drop doesn’t reorder the location.

group.placeholder.actualBounds is not in document coordinates.

You also do not seem to be accounting for the Placeholder.padding, if it is non-zero.

Placeholder paddng is zero. But how do I get the correct upperY limit and lowerY limit then?

The issues are only when I am trying to drag and drop elements on the two extreme ends.

Can you point me to a simple example or bit of documentation that can help me calculate the upper and lower limit of Y values?

I’ll try an example when I get some free time.

Thank you so much.

I hope this explains one way to do what I think you want.

<!DOCTYPE html>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<script id="code">
  // define a custom Layout class for Groups
  function ResortingGridLayout() {;
    this.alignment = go.GridLayout.Position;
    this.sorting = go.GridLayout.Ascending;
    this.comparer = function(a, b) {
      var ax = a.location.x;
      var ay = a.location.y;
      var bx = b.location.x;
      var by = b.location.y;
      if (isNaN(ax) || isNaN(ay) || isNaN(bx) || isNaN(by)) return 0;
      ay = Math.round(ay/40);  //?? how far to move vertically in order to change rows?
      by = Math.round(by/40);
      if (ay < by) return -1;
      if (ay > by) return 1;
      if (ax < bx) return -1;
      if (ax > bx) return 1;
      return 0;
  go.Diagram.inherit(ResortingGridLayout, go.GridLayout);

  // use the saved Group's location from before the drag happened
  ResortingGridLayout.prototype.initialOrigin = function() {
    return this.arrangementOrigin;

  function init() {
    var $ = go.GraphObject.make;

    myDiagram =
      $(go.Diagram, "myDiagramDiv",  // create a Diagram for the DIV HTML element
          "grid.visible": true,
          "draggingTool.doActivate": function() {
            if (this.draggedParts) {
              this.draggedParts.iteratorKeys.each(function(n) {
                if (!(n instanceof go.Node)) return;
                var g = n.containingGroup;
                if (g !== null && g.placeholder !== null) {
                  g.layout.arrangementOrigin = new go.Point(g.location.x + g.placeholder.padding.left,
                                                            g.location.y +;
          "SelectionMoved": function(e) {
            var it = myDiagram.selection.iterator;
            while ( {
              var part = it.value;
              if (part instanceof go.Link) continue;
              if (part instanceof go.Group) {
                part.layout.arrangementOrigin = new go.Point(part.location.x + part.placeholder.padding.left,
                                                             part.location.y +;
                part.layout.isValidLayout = true;
              } else if (part.containingGroup !== null) {
          "undoManager.isEnabled": true

    myDiagram.nodeTemplate =
      $(go.Node, go.Panel.Auto,
        { locationSpot: go.Spot.Center },
          { fill: "white" },
          new go.Binding("fill", "color")),
          { margin: 4 },
          new go.Binding("text", "key")));

    myDiagram.groupTemplate =
      $(go.Group, go.Panel.Vertical,
          layout: $(ResortingGridLayout, { wrappingColumn: 4 }),
          computesBoundsAfterDrag: true,
          selectionObjectName: "SHAPE"
          { font: "bold 12pt sans-serif" },
          new go.Binding("text", "key"),
          new go.Binding("stroke", "color")),
        $(go.Panel, go.Panel.Auto,
          $(go.Shape, "Rectangle",
            { name: "SHAPE", fill: "rgba(128,128,128,0.2)", stroke: "gray", strokeWidth: 2 },
            new go.Binding("stroke", "color")),
          $(go.Placeholder, { padding: 10 })));

    myDiagram.model.nodeDataArray = [
      { key: "Group", isGroup: true, color: "green" },
      { key: "n1", color: "lightblue", group: "Group" },
      { key: "n2", color: "pink", group: "Group" },
      { key: "n3", color: "yellow", group: "Group" },
      { key: "n4", color: "orange", group: "Group" },
      { key: "n5", color: "lightblue", group: "Group" },
      { key: "n6", color: "pink", group: "Group" },
      { key: "n7", color: "yellow", group: "Group" },
      { key: "n8", color: "orange", group: "Group" },
      { key: "Group10", isGroup: true, color: "green" },
      { key: "n11", color: "lightgreen", group: "Group10" },
      { key: "n12", color: "lightgreen", group: "Group10" },
      { key: "n13", color: "lightgreen", group: "Group10" },
      { key: "n14", color: "lightgreen", group: "Group10" },
      { key: "n15", color: "lightgreen", group: "Group10" },
      { key: "n16", color: "lightgreen", group: "Group10" }
<body onload="init()">
  <div id="myDiagramDiv" style="border: solid 1px blue; width:100%; height:500px; min-width: 200px"></div>

It’s not possible for me to do this because the rows aren’t fixed size.

Also I drag and drop the group from an HTML palette and after adding the initialOrigin method on the layout. The group is always being dropped on 0,0 instead of the dropped location like before.

Another thing I notices in the example you shared is sometimes after I dragged the elements inside the group doLayout isn’t triggered and then I have to move the whole group to trigger the layout change. Any idea why that happens?

I had some bugs dealing with groups and their layouts in the “SelectionMoved” DiagramEvent listener, which I have updated above.

Also, you need to set Group.computesBoundsAfterDrag to true.

Thank you so much. Will give this one a try and provide updates here.

I am still facing the issue where the group doesn’t abide by it’s serialized location and always starts at the mid of the canvas viewport on initial load after making these arrangement origin change.

Does it still have the expected position or location? So the problem actually involves how the diagram is initially scrolled? Please read

yea it has the expected location. But not an issue of the initialView because it also happens when I drag and drop a new group from the palette.

When the group is dropped from externally (e.g. from a Palette) does the newly created Group have member Nodes, or is it empty?

You might need to explicitly set the Layout.arrangementOrigin in an “ExternalObjectsDropped” listener.

Yes it does have members. Do I need to set the location value as the arrangementOrigin in the event listener then?

Yes, with the Placeholder.padding offset.