Saving the node position/location when creating a group

Hi,
Suppose that i have a node with a fixed position set to (x,y)=(100,200).
Now, i create a group. The group is located by Group.layout algorithm and set, for example to (x,y)=(400,600).
By the way, we can assume that i didn’t use the Group.Layout and locate the node to a fixed position (400,600)
I also add the node to the group.
When expanding the group, the node is located relative to the group location, thus the node location is moved to a position around (400+100, 600+200).
Is there any way to maintain the node position regardless its parent group position ?
Regards,
Tany

There are a lot of variables that you haven’t mentioned. Small before and after screenshots might help.

Generally the Group.layout is responsible for positioning all of its member nodes. Normally by adding a node to a group the group’s layout is invalidated and will be performed again before the end of the transaction. Please read GoJS Layouts -- Northwoods Software about how you can control that layout invalidation.

It looks like the hole location/position is not clear to me.
Below is the the node and group templates, the grouping code and the expand/collapse code i wrote.
As soon as i press a button the nodes are grouped by siteId and located in the center of the diagram.
Then, when i expand one of the groups, it is expanded to some location. I see that its location is derived from its nodes location.
Then, i arrange the nodes inside the group and close the group and then reopen.
At this point, the group is opened to to some location which is decided by go.js without the ability to control the location where to be opened.
In general, i would like to create the group, then, arrange the nodes in it, collapse the group and when i reopen the group i expect it to be opened to the place right before it was closed.
I tried to save the location of the group to origLoc, before opening the group, and then restore its location, but it didnt help.
I’m lost…

defaultNodeTemplate() {
var GO = this.GO;
var x =
GO(go.Node, “Vertical”,
{
groupable: true,
shadowBlur: 15,
shadowColor: “blue”,
fromSpot: go.Spot.RightSide, // coming out from right side
toSpot: go.Spot.LeftSide,
},
new go.Binding(“zOrder”),
GO(go.Panel, “Spot”,
new go.Binding(“itemArray”, “ports”),
GO(go.Panel, “Spot”,
// A Shape that is bigger from the picture, to enable moving ports around the picture
GO(go.Shape, “Rectangle”,
{ width: 70, height: 70, fill: “transparent”, isPanelMain: true, stroke: “transparent” },
new go.Binding(“width”, “rectangleWidth”).makeTwoWay(),
new go.Binding(“height”, “rectangleWidth”).makeTwoWay()
),
GO(go.Picture, { width: 50, opacity: 1.0, height: 50 },
new go.Binding(“source”, “source”, function (source) {
return (NetMapDiagramComponent.CONST_ICON_RELATIVE_PATH + source);
}),
new go.Binding(“width”, “picWidth”),
new go.Binding(“height”, “picHeight”),
new go.Binding(“opacity”, “picOpacity”)
) // Picture
),
{
name: “PortPanel”,
itemTemplate: GO(go.Panel, “Vertical”, // panel to place the textbox (portId) and the port shape (circle) side by side horizontaly
{
toolTip: this.portTooltip(GO),
fromLinkable: true, toLinkable: true,
cursor: “pointer”
},
new go.Binding(“portId”, “portId”),
new go.Binding(“alignment”, “spot”, go.Spot.parse).makeTwoWay(go.Spot.stringify),
this.createPortShape(), // the Shape is under the port Name
this.createPortName(), // port Name
this.createPortAlarm() // port Alarm

        ) // end port dot)  // end itemTemplate
      }
    ), // go.Panel

    GO(go.TextBlock,  // Node alarmMessage
      {
        isMultiline: false, editable: false,
        stroke: GO(go.Brush, { color: CONST_DEFAULT_COLOR }),
        font: "bold 10pt Verdana sans-serif",
        name: "nodeAlarmMessage",
        background: "white"
      },
      new go.Binding("text", "alarmMessage"),
      new go.Binding("stroke", "statusColor"),
      new go.Binding("visible", "text", function (s) { return s != null && s != "" }).ofObject("nodeAlarmMessage")
    ),
    GO(go.Panel, "Vertical", new go.Binding("visible", "isFullId").ofModel(), // Panel for Full ID
      { name: "fullIdPanel" },

      GO(go.TextBlock,  // Device Type
        {
          margin: new go.Margin(0, 0, 0, 0),
          //maxSize: new go.Size(150, 50),
          isMultiline: false, editable: false,
          stroke: GO(go.Brush, { color: CONST_DEFAULT_COLOR }),
          font: "bold 10pt Verdana sans-serif",
        },
        new go.Binding("text", "deviceType")

      ),
      GO(go.TextBlock,  // ShelfId
        {
          margin: new go.Margin(3, 0, 0, 0),
          //maxSize: new go.Size(170, 50),
          isMultiline: false, editable: false,
          stroke: GO(go.Brush, { color: CONST_DEFAULT_COLOR }),
          font: "bold 10pt Verdana sans-serif"
        },
        new go.Binding("text", "shelfId")
      )
    ),
    GO(go.TextBlock,  // ShortId
      {
        margin: new go.Margin(3, 0, 0, 0),
        //maxSize: new go.Size(150, 50),
        isMultiline: false, editable: false,
        stroke: GO(go.Brush, { color: CONST_DEFAULT_COLOR }),
        font: "bold 10pt Verdana sans-serif",
        visible: false,  // invisible by default
      },
      new go.Binding("text", "shortId"),
      new go.Binding("visible", "visible", function (s) { return !s; }).ofObject("fullIdPanel")
    ),  // go.TextBlock

    new go.Binding("position", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),

    GO(go.TextBlock,  // for ipAddress
      {
        margin: new go.Margin(3, 0, 0, 0),
        //maxSize: new go.Size(150, 50),
        isMultiline: false, editable: false,
        stroke: GO(go.Brush, { color: CONST_DEFAULT_COLOR }),
        font: "bold 10pt Verdana sans-serif"
      },
      new go.Binding("text", "ipAddress")
    ),  // go.TextBlock
  );
return x;

}

siteGroupTemplate() {
var GO = this.GO;
var x =
GO(go.Group, “Vertical”,
{
isSubGraphExpanded: false,
ungroupable: true,
subGraphExpandedChanged: function (e, obj) {
}, // subGraphExpandedChangedHandler,
background: “white”,
isShadowed: true
//resizable: true
// name : “GROUP”
},
new go.Binding(“scale”, “groupPictureScale”).makeTwoWay(),
new go.Binding(“position”, “loc”, go.Point.parse).makeTwoWay(go.Point.stringify),
new go.Binding(“zOrder”),
new go.Binding(“isSubGraphExpanded”).makeTwoWay(),

    // Panel for holding COLLAPSED Group Header info
    GO(go.Panel, "Spot", { opacity: 1.0 },

      new go.Binding("type", "isSubGraphExpanded", function (s) { return !s ? go.Panel.Spot : go.Panel.Auto; }).ofObject(),
      new go.Binding("opacity", "groupMonitoringOpacity"),

      GO(go.Shape, "RoundedRectangle",
        {
          fill: "white",
          stroke: CONST_GROUP_BORDER_COLOR,
          strokeWidth: CONST_GROUP_BORDER_WIDTH
        },
        new go.Binding("stroke", "statusColor")
      ),

      GO(go.Panel, "Vertical",
        // group iconnew
        GO(go.Picture, { scale: 0.9 },
          new go.Binding("visible", "isSubGraphExpanded", function (s) { return !s; }).ofObject(),

          new go.Binding("source", "source", function (source) {
            return (NetMapDiagramComponent.CONST_ICON_RELATIVE_PATH + source);
          })),
        // siteId
        GO(go.TextBlock,
          {
            font: "Bold 11pt Sans-Serif",
            editable: false,
            alignment: go.Spot.Bottom,
          },
          new go.Binding("font", "isSubGraphExpanded", function (s) { return !s ? "Bold 11pt Sans-Serif" : "Bold 14pt Sans-Serif"; }).ofObject(),
          new go.Binding("stroke", "isSubGraphExpanded", function (s) { return !s ? "black" : "#428bca"; }).ofObject(),
          new go.Binding("text", "siteId").makeTwoWay()
        ),
        // group name
        GO(go.TextBlock,
          {
            font: "Bold 11pt Sans-Serif",
            editable: false,
            alignment: go.Spot.Bottom,
            // margin: new go.Margin(10,0,10,0),
          },
          new go.Binding("font", "isSubGraphExpanded", function (s) { return !s ? "Bold 11pt Sans-Serif" : "Bold 14pt Sans-Serif"; }).ofObject(),
          new go.Binding("stroke", "isSubGraphExpanded", function (s) { return !s ? "black" : "#428bca"; }).ofObject(),
          new go.Binding("text", "groupName").makeTwoWay(),
          new go.Binding("editable")
        ),
        GO("SubGraphExpanderButton", {
          alignment: go.Spot.BottomCenter,
          margin: new go.Margin(20, 0, 0, 0)
        },
          new go.Binding("margin", "isSubGraphExpanded", function (s) { return !s ? new go.Margin(20, 0, 0, 0) : new go.Margin(0, 0, 0, 0); }).ofObject()
        )
      )
    ),
    // Panel for holding Group members
    GO(go.Panel, "Auto",
      GO(go.Shape, "Rectangle",
        {
          fill: "transparent",
          stroke: CONST_GROUP_BORDER_COLOR,
          strokeWidth: CONST_GROUP_BORDER_WIDTH
        }
      ),
      GO(go.Placeholder, // represents area for all member parts
        { background: "transparent" })
    )
  );
return x;

}

diagram.addDiagramListener(“SubGraphCollapsed”, // close the group
function (e) {
diagram.startTransaction(“collapseGroup”);
for (var it = e.subject.iterator; it.next();) {
var group = it.value;
var groupData = group.data;
if (groupData.origLoc !== undefined)
diagram.model.setDataProperty(groupData, “loc”, groupData.origLoc);
for (var itGroup = group.memberParts; itGroup.next();) {
var nodeInGroup = itGroup.value;
nodeInGroup.data.origLoc = nodeInGroup.data.loc;
}
}
groupData.isExpanded = false;
diagram.commitTransaction(“collapseGroup”);
}
);

diagram.addDiagramListener(“SubGraphExpanded”, // open the group
function (e) {
diagram.startTransaction(“expandGroup”);
for (var it = e.subject.iterator; it.next();) {
var group = it.value;
var groupData = group.data;
groupData.origLoc = groupData.loc;
that.zOrder += 100;
diagram.model.setDataProperty(group.data, “zOrder”, that.zOrder);
for (var itGroup = group.memberParts; itGroup.next();) {
var nodeInGroup = itGroup.value;
//diagram.model.setDataProperty(nodeInGroup.data, “loc”, null /nodeInGroup.data.origLoc/);
diagram.model.setDataProperty(nodeInGroup.data, “zOrder”, that.zOrder + 1);
if (nodeInGroup.linksConnected !== undefined)
for (var itLink = nodeInGroup.linksConnected; itLink.next();) {
var link = itLink.value;
diagram.model.setDataProperty(link.data, “zOrder”, that.zOrder + 1);
}
if (nodeInGroup.memberParts !== undefined)
for (var itGroup1 = nodeInGroup.memberParts; itGroup1.next();) {
var nodeInGroup1 = itGroup1.value;
diagram.model.setDataProperty(nodeInGroup1.data, “zOrder”, that.zOrder + 2);
if (nodeInGroup1.linksConnected !== undefined)
for (var itLink1 = nodeInGroup1.linksConnected; itLink1.next();) {
var link1 = itLink1.value;
diagram.model.setDataProperty(link1.data, “zOrder”, that.zOrder + 2);
}
}
}
}
groupData.isExpanded = true;
diagram.commitTransaction(“expandGroup”);
}
);

groupNodesBySite() {
this.deleteAutoMaticGroups();
var currentGroupHashMap = {};
var nodesToGroup = [];
for (var i = 0; i < this.diagram.model.nodeDataArray.length; i++) {
var nodeData = this.diagram.model.nodeDataArray[i];
if (nodeData.isGroup) {
currentGroupHashMap[nodeData.key] = true;
}
else {
// node is not a group NOR belong to any other group, add it to the list
if (nodeData.group === undefined)
nodesToGroup.push(nodeData);
}
}
this.groupNodes(nodesToGroup, {});
//document.getElementById(‘diagramPreferences’).classList.remove(“pnlFiltersShow”);
}

/**
*
*/
groupNodes(nodesToGroup, currentGroupHashMap) {
//=================================================================

let diagram = this.diagram;
diagram.startTransaction("groupNodes");
var keyHashMap = {};
var newGroupArray = [];

for (var i = 0; i < diagram.model.nodeDataArray.length; i++) {
  var nodeData = diagram.model.findNodeDataForKey(diagram.model.nodeDataArray[i].key);
  var node = diagram.findNodeForKey(diagram.model.nodeDataArray[i].key);
  if (nodeData.origLoc === undefined)
    nodeData.origLoc = nodeData.loc;
}
// group only nodes that are NOT belong to any other group
var x = 0;
for (i = 0; i < nodesToGroup.length; i++) {
  var nodeData = nodesToGroup[i];
  if (nodeData.deviceLevel === ConfigurationConstants.CONST_DL_PHYSICALDEVICE || nodeData.isGroup) {
    var groupKey, name, siteId, groupName, designation;
    var site = nodeData.site;
    siteId = site.siteId;
    groupKey = site.siteId;
    name = site.siteId;
    groupName = this.htmlDecode(site.name);
    designation = this.htmlDecode(site.designation);
    if (keyHashMap.hasOwnProperty(groupKey) === false) {
      keyHashMap[groupKey] = true;
      if (currentGroupHashMap[groupKey] === undefined) {
          newGroupArray.push(this.setSiteGroupData(nodeData));
      }
    }
    diagram.model.setDataProperty(nodeData, "group", null);
    diagram.model.setDataProperty(nodeData, "group", groupKey);
    diagram.model.setDataProperty(nodeData, "zOrder", 0);
  }
}
// Add the group nodes to the data model
for (i = 0; i < newGroupArray.length; i++) {
  diagram.model.addNodeData(newGroupArray[i]);
}
diagram.alignDocument(go.Spot.Center, go.Spot.Center);
diagram.commitTransaction("groupNodes");

}

That’s a lot of unformatted code. As far as I can tell, your Groups do not specify a Group.layout (which means they are using the default Layout, which is what you want if you want to preserve the user’s manual movements of nodes), and your Groups do have a Placeholder. I think the behavior you are seeking is in fact the default behavior.

To test this, I made a copy of the Grouping sample, Grouping, and removed the assignment of Diagram.layout and Group.layout. The resulting app allowed me as a user to expand a group, move its member nodes around, collapse the group, move the group, and then expand the group at the new location. That group has its member nodes shifted accordingly and maintaining their pre-collapse arrangement relative to each other.

So I think the default behavior is what you want. I don’t know what you are doing in those DiagramEvent listeners, but I suggest you try without them and with other simplifications to see what causes things to stop working in the normal manner.

You are right, i expect to have a normal behavior of groping expanding/collapsing.
i opened you sample. moved one of group aside, expanded it, and then collapsed it.
The group collaped to the original place, namely to the location it was BEFORE i moved it. This is not what i expect to have.
Actually, i’m implementing a diagram editor where the user drag nodes to the diagram, group them and arrange them the way he likes. The user expects that the group will be collapsed right to the place where it was opened.
In your sample, gojs remembers the location of the group before it was dragged and restore the location when collapsed.

Did you do what I said I did? I removed all settings of layout.

I did all required changes, still when the group is collapsed, i cannot control where to be closed unless i remember the loc when expanding it and restore it when collapsing.
Also, if you play with your sample and grab group0 to some place and expand it, as soon as you collapse it, it returns to its original location and not to the location before it was expanded.
By the way, i noticed that only if i move the group for the second time it retains its position before it was expanded.
Please try to do the following steps:

  1. Move group0 to some location
  2. Expand group0 - It returns to the center - WRONG !
  3. Collapse group0.
  4. Move group0 to to some place
  5. Expand it.
    6.Collapse it - It returns to the place before it was exapnded - OK !!!

Are you not setting the locations of the new member nodes to real Points when you add them to a never-expanded group?

I think what is happening is that those new member nodes haven’t been positioned by the user, so they have no real Node.location. The Group.layout has been invalidated by the addition of the nodes and links, but it hasn’t been performed because the group has always been and still is collapsed.

Then when the group is expanded, the Group.layout is performed and all of the nodes are given real locations. By default those would be near (0,0). Since your Group has a Placeholder, that means the group will be located where its member nodes are – near (0,0).

You can get around this assumption by adding this code at the end of the transaction when you add members to the group when it is expanded for the first time. In that Regrouping sample, at the end of the randomGroup function:

      var groupnode = myDiagram.findNodeForKey(group);
      if (groupnode !== null) {
        groupnode.layout.arrangementOrigin = groupnode.location;
      }
      myDiagram.commitTransaction("addGroupContents");

Hmmm, I’m thinking we should consider this a bug in the default Layout. The other predefined Layouts do not exhibit this behavior. Or at least they should not.

I added the code to the randomGroup function and it works fine.
However, in my code, i use diagramListener(“SubGraphExpanded”).
I added the code there and i see no changes.
Is it the right place to add the code ?

Try the 1.8.7 beta that I just uploaded to GoJS - Build Interactive Diagrams for the Web.

Will try,
In general, should i use the diagramListener(“SubGraphExpanded”) or the SubGraphExpanded function, for expanding/collapsing implementation?

That depends on where you want to put the code, with the template or with the whole diagram.

Group.subGraphExpandedChanged is per Group, when Group.isSubGraphExpanded changes value.

The “SubGraphExpanded” and “SubGraphCollapsed” DiagramEvents are raised only once per call to CommandHandler.expandSubGraph and CommandHandler.collapseSubGraph.

OK,
I did try 1.8.7 without and Layout defintoin.
Looks goos.
Yet, i tried with my code and it looks like it does not work.
I even used your sample template and still when expanding the group is moves aside for the first time
I’m attaching my code :

var GO;
var diagram = null;
var CONST_DEFAULT_COLOR = "black";
var CONST_GROUP_BORDER_WIDTH = 4;
var CONST_GROUP_BORDER_COLOR = "#666666";
var CONST_EXPAND_TREE = true;
var CONST_COLLAPSE_TREE = !CONST_EXPAND_TREE;

var offlineJsonData =
{ "class": "go.GraphLinksModel",  "modelData":
{"isFullId":false, "isAllElementsLocationSaved":false },
    "nodeDataArray":
            [ {"key":3480525, "source":"icons/Router.svg", "deviceId":{"siteId":"0400", "shelfId":"AAA", "cardId":null, "shortId":"AAAAS", "fullId":"AAAAAAAAAA"}, name: "cc", "site":{"id":1182826, "siteId":"0400", "name":"siteA", "designation":null, "icon":"stationary_site", "siteAddress":null, "baseName":"PP", "comments":"Comment", "contacts":null, "managementPhone":null, "status":"exist", "type":"typeA", "area":"aaa", "region":"zzz",    "mannedType":"sss", "securityLevel":"aaa", "additionalName":null, "siteMobility":null, "gisX":null, "gisY":null, "serialZnumber":null, "extrenalName":null}, "siteId":"0400", "siteName":"QQQ", "shelfId":"yyy", "shortId":"yyyy0", "ipAddress":null, "deviceLevel":1, "deviceType":"PPRJS", "toolTip":"", "connectionType":null, "ports":[], "picHeight":50, "picWidth":50, "lastPortPanel":-1, "rectangleWidth":85, "statusColor":"gray", "loc":"-252.00000000000009 -90.00000000000004", "isTreeExpanded":null, "parent":null, "serviceId":null, "networkType":"ccc", "networkTypeKey":2048, "alarmMessage":"", },
                {"key":3467699, "source":"icons/Router.svg", "deviceId":{"siteId":"0400", "shelfId":"BBB", "cardId":null, "shortId":"BBBBS", "fullId":"BBBBB"},
                    "name":"dd", "site":{"id":1182826, "siteId":"0400", "name":"ww", "designation":null, "icon":"stationary_site", "siteAddress":null, "baseName":"",
                    "comments":"ss", "contacts":null, "managementPhone":null, "status":"sdfsdf", "type":"qq", "area":"rr", "region":"rr", "mannedType":"uu", "securityLevel":"", "additionalName":null, "siteMobility":null, "gisX":null, "gisY":null, "serialZnumber":null, "extrenalName":null}, "siteId":"0400", "siteName":"tttrt", "shelfId":"uuuu8", "shortId":"uuuuu", "ipAddress":null, "deviceLevel":1, "deviceType":"TDF", "toolTip":"", "connectionType":null, "ports":[ ], "picHeight":50, "picWidth":50, "lastPortPanel":-1, "rectangleWidth":85, "statusColor":"gray", "loc":"413.99999999999983 324.0000000000001", "isTreeExpanded":null, "parent":null, "serviceId":null, "networkType":"aaaa", "networkTypeKey":2048, "alarmMessage":""},
                {"key":1564515, "source":"icons/Router.svg", "deviceId":{"siteId":"0400", "shelfId":"CCC", "cardId":null, "shortId":"CCCC", "fullId":"CCCCF"},
                    "name":"cc", "site":{"id":1182826, "siteId":"0400", "name":"aa", "designation":null, "icon":"stationary_site", "siteAddress":null, "baseName":"fdfdfd",
                    "comments":"qq", "contacts":null, "managementPhone":null, "status":"קיים", "type":"tty", "area":"uu", "region":"ee", "mannedType":"", "securityLevel":"", "additionalName":null, "siteMobility":null, "gisX":null, "gisY":null, "serialZnumber":null, "extrenalName":null}, "siteId":"0400", "siteName":"", "shelfId":"hhhh", "shortId":"400F1", "ipAddress":"33.44.44.55", "deviceLevel":1, "deviceType":"XDM1000", "toolTip":"", "connectionType":null, "ports":[], "picHeight":50, "picWidth":50, "lastPortPanel":-1, "rectangleWidth":100, "statusColor":"gray", "loc":"359.9999999999998 -125.99999999999996", "isTreeExpanded":null, "parent":null, "serviceId":null, "networkType":"dd", "networkTypeKey":2048, "alarmMessage":""},
                {"key":4465505, "source":"icons/Router.svg", "deviceId":{"siteId":"0100", "shelfId":"DDDD", "cardId":null, "shortId":"DDDD", "fullId":"yyy"}, "name":"yyy", "site":{"id":1152710, "siteId":"0100", "name":"uuu", "designation":"", "icon":"stationary_site", "siteAddress":"", "baseName":"", "comments":"", "contacts":null, "managementPhone":null, "status":"fff", "type":"fgdfgdfg", "area":"fjghjgyh", "region":"jjj", "mannedType":"dd", "securityLevel":"dd", "additionalName":"ereded", "siteMobility":null, "gisX":null, "gisY":null, "serialZnumber":null, "extrenalName":null}, "siteId":"0100", "siteName":"tttt", "shelfId":"eeee", "shortId":"ee", "ipAddress":"1.2.3.4", "deviceLevel":1, "deviceType":"XDM3000", "toolTip":"SiteId : 0100nFullID: 0100/TS-XDM3000-KSDH-A-01nShortID: 100A1nStatus: מאוכלסnSLA: Gold-24X7nColor: שחור", "connectionType":null, "ports":[], "picHeight":50, "picWidth":50, "lastPortPanel":-1, "rectangleWidth":100, "statusColor":"gray", "loc":"575.9999999999999 -72", "isTreeExpanded":null, "parent":null, "serviceId":null, "networkType":"כללי", "networkTypeKey":2048, "alarmMessage":""} ],

    "linkDataArray":[],
    "id":122};

function init() {
    GO = go.GraphObject.make;
    this.diagram =
            GO(go.Diagram, "myDiagramDiv",  // must name or refer to the DIV HTML element
                    {
                        grid: GO(go.Panel, "Grid",
                                GO(go.Shape, "LineH", {stroke: "lightgray", strokeWidth: 0.5}),
                                GO(go.Shape, "LineH", {stroke: "lightgray", strokeWidth: 0.5, interval: 10}),
                                GO(go.Shape, "LineV", {stroke: "lightgray", strokeWidth: 0.5}),
                                GO(go.Shape, "LineV", {stroke: "lightgray", strokeWidth: 0.5, interval: 10}),
                                {gridCellSize: new go.Size(18, 18)}
                        ),
                    });

    this.diagram.nodeTemplate = this.defaultNodeTemplate();
    this.diagram.groupTemplate = this.defaultGroupTemplate();
    this.diagram.model = go.Model.fromJson(offlineJsonData);
}
    /**
     *
     */
function defaultNodeTemplate() {
        var $ = GO;
        var x =
                $(go.Node, "Auto",
                        $(go.TextBlock,
                                { margin: 7, font: "Bold 14px Sans-Serif" },
                                //the text, color, and key are all bound to the same property in the node data
                                new go.Binding("text", "shortId"))
                );
        return x;
 }

    /**
     *
     */
    function  defaultGroupTemplate() {
        var $ = GO;
        var x =
                $(go.Group, "Auto",
                        { 
                            isSubGraphExpanded: false,                                
                        },
                        $(go.Shape, "Rectangle",
                                {fill: null, stroke: "gray", strokeWidth: 2}),
                        $(go.Panel, "Vertical",
                                {defaultAlignment: go.Spot.Left, margin: 4},
                                $(go.Panel, "Horizontal",
                                        {defaultAlignment: go.Spot.Top},

the subGraph
$(“SubGraphExpanderButton”),
$(go.TextBlock,
{font: “Bold 18px Sans-Serif”, margin: 4},
new go.Binding(“text”, “key”))
),
// create a placeholder to represent the area where the contents of the group are
$(go.Placeholder,
{padding: new go.Margin(0, 10)})
) // end Vertical Panel
); // end
return x;
}

    /**
     * collect the currently group nodes into hashmap in order to avoid  duplicate them
     */
    function groupNodesBySite() {
        var currentGroupHashMap = {};
        var nodesToGroup = [];
        for (var i = 0; i < this.diagram.model.nodeDataArray.length; i++) {
            var nodeData = this.diagram.model.nodeDataArray[i];
            if (nodeData.isGroup) {
                currentGroupHashMap[nodeData.key] = true;
            }
            else {
                // node is not a group NOR belong to any other group, add it to the list
                if (nodeData.group === undefined)
                    nodesToGroup.push(nodeData);
            }
        }
        this.groupNodes(nodesToGroup, {});
    }

    function groupNodes(nodesToGroup, currentGroupHashMap) {
        //=================================================================
        diagram.startTransaction("groupNodes");
        var keyHashMap = {};
        var newGroupArray = [];

        for (var i = 0; i < diagram.model.nodeDataArray.length; i++) {
            var nodeData = diagram.model.findNodeDataForKey(diagram.model.nodeDataArray[i].key);
            var node = diagram.findNodeForKey(diagram.model.nodeDataArray[i].key);
            if (nodeData.origLoc === undefined)
                nodeData.origLoc = nodeData.loc;
        }
        // group only nodes that are NOT belong to any other group
        var x = 0;
        for (i = 0; i < nodesToGroup.length; i++) {
            var nodeData = nodesToGroup[i];
                var groupKey, name, siteId, groupName, designation;
                var site = nodeData.site;
                siteId = site.siteId;
                groupKey = site.siteId;
                name = site.siteId;
                groupName = this.htmlDecode(site.name);
                designation = this.htmlDecode(site.designation);
                if (keyHashMap.hasOwnProperty(groupKey) === false) {
                    keyHashMap[groupKey] = true;
                    if (currentGroupHashMap[groupKey] === undefined) {
                        newGroupArray.push(this.setSiteGroupData(nodeData));
                    }
                }
                diagram.model.setDataProperty(nodeData, "group", null);
                diagram.model.setDataProperty(nodeData, "group", groupKey);
                x += 50;
        }
        // Add the group nodes to the data model
        for (i = 0; i < newGroupArray.length; i++) {
            diagram.model.addNodeData(newGroupArray[i]);
        }
        diagram.alignDocument(go.Spot.Center, go.Spot.Center);
        diagram.commitTransaction("groupNodes");
    }

    function setSiteGroupData(node) {

        var siteData = {
            key: node.site.siteId,
            source: "icons/hub.jpg",
            siteId : node.siteId,
            groupName: this.htmlDecode(node.site.name),
            designation: node.site.designation != null ? this.htmlDecode(node.site.designation) : "",
            additionalName: node.site.additionalName != null ? this.htmlDecode(node.site.additionalName) : "",
            isRegularGroupNode: true,
            isSortHeaderGroupNode: false,
            isGroup: true,
            isAutomaticGroup: true,
            isExpanded: false,
        };
        return siteData;
    }
    /**
     *
     */
    function htmlDecode(str) {
        return String(str)
                .replace(/&amp;/g, '&')
                .replace(/&quot;/g, '"')
                .replace(/&#39;/g, "'")
                .replace(/&lt;/g, '<')
                .replace(/&gt;/g, '>');
    }

init();

Walter,
Any progress on this one ? How can i send you the code ?

Sorry, but I didn’t see any explicit or implied question, so I didn’t have anything to say.

I have just retried the Grouping sample without any Diagram.layout or Group.layout, and it behaves as I think you want.

For some reason the code i sent you, is not running as expected although there is no layout involved.
Can you test it ?
Do you want me to send you the code somehow ?

I suggest that you make sure that the member nodes really do have locations or positions where you want them to be, before the group is expanded or collapsed.

As long as the group has a Placeholder, the group will be wherever its member nodes are.

I did set the node location to lie down in an horizontal manner (no use of Layout, only setting the location to x+ " 0" and increasing x by 50) and still i get the same behavior : After the group is created, when expanded for the first time (ONLY for the first time) it moves to some location, and from that point if collapsed or expanded again and again, and the group node maintain its location. So, the problem is only when expanding the group node for the first time.
I must admit, that this strange behavior has become and issue in my project and the customer keeps complaining about it. I have to provide some kind of solution.
Is there any way i can send you the sample code i wrote ?
By the way i used the exact node and group templates from your Grouping sample, so i cannot understand why my code behaves like this ? (using 1.8.8)

After the group is created and seen in the viewport, but before it is expanded, select it and evaluate in the console something like:

var g = myDiagram.selection.first();
g.memberParts(function(p) { if (p instanceof go.Node) console.log(p.location); });

Those node locations should be where you want them to be, not necessarily near the origin.

OK,
I did that, i see the X,Y.
But why the group node is moved to some location. Why it doesn’t stay at its location ?
Why in your Grouping sample, the group retains its position ?