Binding based on modelData and LinkData with undoManager

    function init() {
      var $ = go.GraphObject.make;  // for conciseness in defining templates

      myDiagram = $(go.Diagram, "diagram", {
        "undoManager.isEnabled": true
      });

      // define a simple Node template
      myDiagram.nodeTemplate = $(go.Node, "Auto",
        $(go.Shape, "RoundedRectangle", { strokeWidth: 0 },
          new go.Binding("fill", "", (value, target) =>
              value[target.diagram.model.modelData.colorVisualizationSetting || "color1"]),
          // new go.Binding("fill", "colorVisualizationSetting", (value, target) => {
          //   return target.part.data[value]
          // }).ofModel(),
        ),
        $(go.TextBlock,
          { margin: 8, font: "bold 14px sans-serif", stroke: '#333' },
          new go.Binding("text", "key")
        )
      );

      myDiagram.model = new go.GraphLinksModel(
        [
          { key: "alpha", color1: 'red', color2: 'blue', color3: 'green' },
          { key: "bravo", color1: 'green', color2: 'red', color3: 'blue' },
          { key: "charlie", color1: 'blue', color2: 'green', color3: 'red' },
        ]
      );
      myDiagram.model.modelData = {
        colorVisualizationSetting: 'color1'
      }
      myDiagram.model.linkKeyProperty = 'key'

      myDiagram.addModelChangedListener((e) => {
        if (e.isTransactionFinished) {
          document.getElementById('modelData').innerHTML = myDiagram.model.toJson();

          document.getElementById('colorVisualizationSetting').value = myDiagram.model.modelData.colorVisualizationSetting;

          document.getElementById('alphaColor1').value = myDiagram.model.findNodeDataForKey('alpha').color1
          document.getElementById('alphaColor2').value = myDiagram.model.findNodeDataForKey('alpha').color2
          document.getElementById('alphaColor3').value = myDiagram.model.findNodeDataForKey('alpha').color3

          document.getElementById('bravoColor1').value = myDiagram.model.findNodeDataForKey('bravo').color1
          document.getElementById('bravoColor2').value = myDiagram.model.findNodeDataForKey('bravo').color2
          document.getElementById('bravoColor3').value = myDiagram.model.findNodeDataForKey('bravo').color3

          document.getElementById('charlieColor1').value = myDiagram.model.findNodeDataForKey('charlie').color1
          document.getElementById('charlieColor2').value = myDiagram.model.findNodeDataForKey('charlie').color2
          document.getElementById('charlieColor3').value = myDiagram.model.findNodeDataForKey('charlie').color3

          myDiagram.skipsUndoManager = true;
          myDiagram.updateAllTargetBindings();
          myDiagram.skipsUndoManager = false;
        }
      })
    }

    function updateColor(key, prop, value) {
      myDiagram.model.commit(m => {
        m.set(m.findNodeDataForKey(key), prop, value);
      });
    }

    function updateColorVisualizationSettings() {
      const value = document.getElementById("colorVisualizationSetting").value;
      myDiagram.commit(diag => {
        diag.model.modelData.colorVisualizationSetting = value;
        diag.updateAllTargetBindings();
      }, null);  // skipsUndoManager

    }

I really don’t like having the Bindings inconsistent between the data and the Shapes. I hope it doesn’t cause problems for you later.

Thanks for your response.

So what fixes the issue that we are seeing is the call to myDiagram.updateAllTargetBindings(); on every finished transaction which causes the undo/redo to re-evaluate the bindings, am I right?

I suspect that maybe you need to temporarily set skipsUndoManager to true right around that call to updateAllTargetBindings.

For now we have implemented it as follows in the modelChanged callback:

if (e.propertyName === 'FinishedUndo' || e.propertyName === 'FinishedRedo') {
  if (data.modifiedLinkData) {
    data.modifiedLinkData.forEach((ld) => {
      const link = this.findLinkForKey(ld._key);
      if (link !== null) {
        link.updateTargetBindings();
      }
    });
  }
  if (data.modifiedNodeData) {
    data.modifiedNodeData.forEach((nd) => {
      const node = this.findNodeForKey(nd.id);
      if (node !== null) {
        node.updateTargetBindings();
      }
    });
  }
}

This enforces binding evaluation on undo/redo for the node and link subjects.