Moving a group seems to disregard locationObjectName

When I call the move method on a group, it seems to calculate its rendered position differently than how it was calculated for the initial layout.

As a simple test, I would expect the following to be a no-op:

let loc = go.Point.parse(group.data.loc);
group.move(new go.Point(loc.x, loc.y));

But that causes the rendered position of the group to shift on the canvas.

This is a group deriving from “Spot”, and I suspect move is not honoring locationObjectName and instead blindly calculating based on the group bounding box.

Full example here: GoJS Sample for JSFIDDLE - JSFiddle - Code Playground

When you click the button, the group shifts its rendered location, while I would have expected the group to stay put.

Is this expected behavior? Is there a way I can get move to do the same logic as what happens during the initial layout?

Just edited with a better jsfiddle link: GoJS Sample for JSFIDDLE - JSFiddle - Code Playground

Yes, that is an intentional difference.

Part.move sets the GraphObject.position, not the Part.location.

Okay, and so in my case it seems that I can set group.location directly, like this instead?

var loc = go.Point.parse(group.data.loc); 
group.location = new go.Point(loc.x, loc.y);

Yes, you can. But that will not move the group’s member nodes and links, so if your group has a Placeholder, the group will stay where its members are.

I see. I’m looking for a way to have the member nodes and links follow along as well.

I could imagine a round-about approach where we look at the difference between the Part.location and the GraphObject.position, and then offset the parameters to group.move by that much. But that doesn’t seem to be a great option, since I’m wanting to do this directly in doLayout within a custom Layout, so no GraphObjects have any positions yet.

Can you recommend a better approach? I suppose I would like to do whatever is being done in the doLayout method of the Layout base class. Is that available to see somehow?

Yes, offseting by the difference between the group’s location and position is the natural thing to do. That should work if you are implementing a layout or executing at some other non-initialization time.

But you are right that if group hasn’t had the opportunity to measure and arrange itself, the difference between the location and position might be wrong. That might be a problem is if the group has a Placeholder and the group’s member parts have been resized or moved around.

Still, the whole point of having Part.move is that that is what layouts normally use – they typically want to move nodes to be next to each other (maybe with some spacing or other characteristics) and they want to treat groups just like regular nodes. During automatic layouts the Group.layouts are always performed first, in a depth-first fashion, so it’s safe to treat groups as “atomic” nodes.

I don’t mind giving you the code for Layout.doLayout, but it depends on internal stuff that make it complicated, so I don’t know how instructive it would be. It does call Part.move, but it is also aware of whether or not groups ought to need a new location.

Yes, if you don’t mind, it would be helpful to base my doLayout on what’s actually in the base class, even if there’s some opaque internal stuff in there.

This is a simplified version of the implementation of Layout.doLayout:

Layout.prototype.doLayout = function(coll) {
  if (coll === null) Util.throwError('Layout.doLayout(collection) argument must not be null but a Diagram, a Group, or an Iterable of Parts');
  var allparts = new Set(Part);
  . . . initialize allparts with all of the Parts in coll that meet the requirements for this layout,
  . . . including ignoring Links and all Parts that have real locations
  var num = allparts.count;  // all non-links
  if (num > 0) {
    var diagram = this.diagram;
    if (diagram !== null) diagram.startTransaction('Layout');
    var sqrt = Math.ceil(Math.sqrt(num));
    var originx = this.arrangementOrigin.x;
    var originy = this.arrangementOrigin.y;
    var x = originx;
    var y = originy;
    var i = 0;
    var maxh = 0.0;
    var idx = allparts.iterator;
    while (idx.next()) {
      var part = idx.value;
      part.ensureBounds();
      var b = part.measuredBounds;
      var w = b.width;
      var h = b.height;
      part.moveTo(x, y);
      x += Math.max(w, 50) + 20;
      maxh = Math.max(maxh, Math.max(h, 50));
      if (i >= sqrt - 1) {
        i = 0;
        x = originx;
        y += maxh + 20;
        maxh = 0;
      } else {
        i++;
      }
    }
    if (diagram !== null) diagram.commitTransaction('Layout');
  }
  this.isValidLayout = true;
};

Thanks, that gets me down a workable path.