Leaflet & UndoManager - Undo & Redo Event?

Hello,
I have a question about the UndoManager. I am working with a leaflet map, which can make undo/redo pretty messy because of the conversion between Lat/Lng Coordinates and X/Y coordinates. Mostly, links are not displayed properly and start or end in nothingness, when mixing Leaflet Moving,Leaflet Zooming, Node Dragging and so on. Below is an example, where it got messy, although there are only 2 move transactions like you can see in the undo manager history.

If I manually hit
this.myDiagram.updateAllTargetBindings(‘location’);
this.myDiagram.redraw();
everything is set accordingly again. So, my question is: Is there an event that I can catch, which fires after a redo or undo and that I can connect my diagram update with? Or - since this is probably not a very good / performant solution - is there a different best practise for the combination of UndoManager and GoJS Leaflet Maps?

I could not find any solution in the UndoManager section nor in the Diagram Events

Thanks in regard
Jonas Czeslik

To have that sample support undo and redo, I added these initializations of the Diagram:

    myDiagram = $(go.Diagram, "myDiagramDiv",
      {
        "undoManager.isEnabled": true,
        "ModelChanged": e => {
          if (e.isTransactionFinished) { myDiagram.updateAllTargetBindings("latlong"); myDiagram.redraw(); }
        },
        . . .

I hope that is sufficient and correct for your app.

Hi Walter!
It does indeed offers the connection that I thought I would need, but unfortunately that does not seem to solve the problem.

I’ll try to explain the problem and a way to produce the bug very detailed, so perhaps you can recognize, where the problem could be.

This is the initial situation:

I placed a node in the center of UK and another one in the center of Denmark. You can see the initial coordinates and that there are no transactions in the console.

In the next step, I moved the left node to the southern part of UK:

So, of course, coordinates change accordingly and there is a first transaction (move).

After that I zoomed out, still having the same undo history and coordinates:

Then, I undoed the Move transaction leading to this:

The coordinates are correct (the second node has the coordinates again from the beginning), but it is not displayed correctly and the link is a complete mess.

After moving the left node just a tiny bit, its that way:


So, link is corrected, but the coordinates from the nodedataarray somehow dont fit with the map anymore.

Zooming in and out leaves again to the correct locations:


That’s how I handle locations:

The Bindings:

new go.Binding("location", "location", (data) => this.setMapLocation(data)).makeTwoWay((pt, data) => this.setMapLocationTwoWay(pt, data))
    setMapLocation(data){
        var point;
        if(this.myLeafletMap){
            point = this.myLeafletMap.latLngToContainerPoint(data);
        }
        else{
            point = new L.Point(data[0], data[1]);
        }
        return new go.Point(point.x, point.y);
    }

    setMapLocationTwoWay(pt, data){
        if (this.myUpdatingGoJS) {
            return data.location; // no-op
        } else {
            if(this.myLeafletMap){
                var ll = (this.myLeafletMap.containerPointToLatLng([pt.x, pt.y]));
                return [ll.lat, ll.lng];
            }
            else{
                return [pt.x, pt.y];
            }
        }
    }

After Map Moving:

this.myLeafletMap.on("moveend", e => this.onMoveEnd(e));

onMoveEnd(e){
      this.myUpdatingGoJS = true;
      this.myDiagram.updateAllTargetBindings("location"); // Without virtualization this can be slow if there are many nodes
      this.myDiagram.redraw(); // At the expense of performance, this will make sure GoJS positions are updated immediately
      this.myUpdatingGoJS = false;

      this.myDiagram.skipsUndoManager = false;
  }


After Map Zooming:

this.myLeafletMap.on('zoomend', e => this.onZoomEnd(e));

onZoomEnd(e: L.LeafletEvent){

    this.adaptNodeScales();

    this.myUpdatingGoJS = true;
    this.myDiagram.updateAllTargetBindings("location");
    this.myDiagram.redraw();
    this.myUpdatingGoJS = false;

    this.adaptLabelPositions();
}

And finally the change from first question:

[...]
"undoManager.isEnabled": true,
"ModelChanged": e => {
    if (e.isTransactionFinished && this.myLeafletMap) {        
        this.myUpdatingGoJS = true;
        this.myDiagram.updateAllTargetBindings("location");
        this.myDiagram.redraw();
        this.myUpdatingGoJS = false;

        this.adaptLabelPositions();
    }
}, 
[...]

So, the nodes should always be feeded with the correct lat/lng coordinates, which is also underlined by the example above, but still I cannot seem to have it displayed correctly.

Perhaps you have an idea how to solve this issue cause I dont.

Thanks again!
Jonas

Hmmm, I guess what I tried doesn’t help with implementing undo/redo. I’ll look at this tomorrow.

This seems to work better for undo and redo. First, change the “moveend” event handler:

    myLeafletMap.on("moveend", updateNodes);

    var myUpdatingGoJS = false;  // prevent modifying data.latlong properties upon Leaflet "move" events
    function updateNodes() {
      myUpdatingGoJS = true;
      myDiagram.commit(diag => {
        diag.nodes.each(n => n.updateTargetBindings("latlong")); // Without virtualization this can be slow if there are many nodes
      }, null);
      myUpdatingGoJS = false;
    }

Second, enable the UndoManager and implement a Model Changed listener that updates node positions after undo or redo:

    myDiagram = $(go.Diagram, "myDiagramDiv",
      {
        "undoManager.isEnabled": true,
        "ModelChanged": e => {
          if (e.change === go.ChangedEvent.Transaction &&
              (e.propertyName === "FinishedUndo" || e.propertyName === "FinishedRedo")) {
            setTimeout(() => updateNodes());
          }
        },
        . . .

Thanks a lot! That does indeed update all the nodes accordingly, although it is a bit jumpy, since the nodes are first placed wrong and then a few milliseconds later they are corrected, but I guess that’s okay for now.

Is that the reason for chosing the setTimeout without delay? So, the nodes are updated as quick as possible or does it have a different reason?

One isn’t supposed to make changes to the model or to the diagram in a Transaction type ChangedEvent.

Okay, thanks!