How to capture the model changes by doLayout in a commited transaction?

I want to capture the model changes by doLayout in a committed transaction. Is there a way to do it?

The normal way to record changes in node locations is to have a TwoWay Binding of the Node.location property on the node template(s).

There are a lot of examples of this in the docs and in the samples.

I have a binding on locations and the doLayout does trigger transactions. However, I have a condition checking isTransactionFinished in the addModelChangedListener. All the transactions triggered by the doLayout are not CommittedTransaction. How to capture all the the transactions in the doLayout as one CommittedTransaction?

All of the changes that happen in a layout occur within a transaction. That transaction might not be a top-level transaction – i.e. it might have run within an outer transaction.

But why does that matter to you? If all of the changes that you care about are being recorded in the model data via TwoWay Bindings, and you know when a transaction has finished via a “ModelChanged” listener, don’t you have everything that you need?

The problem is I can’t get a finished transaction via ‘addModelChangedListener’. (The purpose is to get the model.toIncrementalData(event)).

Maybe you don’t have any TwoWay Bindings in your templates? Still, adding or removing Nodes or Links should cause something to be generated by Model.toIncrementalData, even if dragging some Nodes doesn’t modify the model.

This seems to work as I would expect:

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2022 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>

  <script src="https://unpkg.com/gojs"></script>
  <script id="code">
const $ = go.GraphObject.make;

const myDiagram =
  $(go.Diagram, "myDiagramDiv",
    {
      "undoManager.isEnabled": true,
      "ModelChanged": e => {
        if (e.isTransactionFinished) {
          document.getElementById("mySavedModel").textContent =
              JSON.stringify(e.model.toIncrementalData(e), null, 2);
        }
      }
    });

myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
    $(go.Shape,
      { fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
      new go.Binding("fill", "color")),
    $(go.TextBlock,
      { margin: 8, editable: true },
      new go.Binding("text").makeTwoWay())
  );

myDiagram.model = new go.GraphLinksModel(
  {
    linkKeyProperty: "key",
    nodeDataArray:
      [
        { key: 1, text: "Alpha", color: "lightblue" },
        { key: 2, text: "Beta", color: "orange" },
        { key: 3, text: "Gamma", color: "lightgreen" },
        { key: 4, text: "Delta", color: "pink" }
      ],
    linkDataArray:
      [
        { from: 1, to: 2 },
        { from: 1, to: 3 },
        { from: 2, to: 2 },
        { from: 3, to: 4 },
        { from: 4, to: 1 }
      ]
  });
  </script>
</body>
</html>

Sorry, I wasn’t very clear. I was trying to change to ForceDirectedLayout from TreeLayout. I want to capture the location after the change. However, isTransactionFinished property is never true, so I can’t get the model.toIncrementalData.
I do have the two way binding for location. Similar to this
new go.Binding(‘location’, ‘’,{ location:{ x, y } }) => {return new go.Point(x, y);}).makeTwoWay(({ x, y }, data, model) => { model.setDataProperty(data, ‘location’, { x: x, y: y });})

The layout doesn’t matter for this issue – just having the TwoWay Binding on the Node “location” property is sufficient to make sure the model data is updated for each node data object whenever the node is moved, whether programmatically or manually (which is actually by the DraggingTool’s code).

So is your Model “Changed” listener is being called at the end of each transaction? If so, are you saying that ChangedEvent.isTransactionFinished is never true? I cannot explain that. Does the sample I just gave you, above, working for you?

That Binding looks rather contorted. Why not use this instead?

new go.Binding("location", "location", o => new go.Point(o.x, o.y)).makeTwoWay(p => ({ x: p.x, y: p.y }))

Just a follow up question. The example does work. However, I need to change the layout type using a react hooks. So, I have the two different hooks - diagram.model = … (in hook one) and diagram.layout, diagram,linkTemplate, digram.nodeTemplate (in hook two for changing layout). Somehow, if I combine these two hooks I do have finished transaction. I wonder if this is the problem.

Are you setting Diagram.layout to an instance of a different layout in a useEffect function? I think that’s OK as long as you don’t do it on each render, but only when you want to change the layout being used. But I’m unfamiliar with React and with hooks, so I may be wrong.