Copying links between nodes key problem

Hi,

I am using the makeUniqueLinkKeyFunction on the go.GraphLinksModel to create a link key based on the the link data. This works fine when I initialize the model and when I create new links. When creating a new link, the link data is filled based on the properties that I have set on the model:

linkFromKeyProperty
linkToKeyProperty

I am using these properties in the link data to create the unique key in the makeUniqueLinkKeyFunction function.

However, when I am copying nodes that have links, the makeUniqueLinkKeyFunction is called with the old properties. So the properties from the source nodes that I am copying (instead of the newly created copied nodes). This results in a duplicated link key and therefore gojs appends a numeric value.

Would it be possible to received the copied node properties in the makeUniqueLinkKeyFunction or how should I address this issue?

For clarification: my link key is simply: ${fromNode}-${toNode} based on the linkFromKeyProperty and linkToKeyProperty properties in the linkdata.

If I create a model such as:

$(go.GraphLinksModel,
  {
    linkKeyProperty: "key",
    makeUniqueLinkKeyFunction: (m, d) => `${d.from}-${d.to}`,
    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 }
    ]
  });

And then I copy some nodes and links, I get link keys such as:

{"from":-5,"to":-5,"key":"undefined-undefined"},
{"from":-6,"to":-5,"key":"undefined-undefined2"},

That’s because when links are copied they do not have any key references copied. Such references are determined by the policies of the copying code, since that is what needs to decide whether or not to refer to an original or to a copy or to some other node altogether.

So if you want the link key to be dependent on the connected node keys, you’ll need to reassign the key later by calling GraphLinksModel.setKeyForLinkData.

      $(go.Diagram, . . .,
        {
          "SelectionCopied": function(e) {
            e.subject.each(p => {
              if (p instanceof go.Link) {
                const d = p.data;
                p.diagram.model.setKeyForLinkData(d, `${d.from}-${d.to}`);
              }
            });
          },

As another situation where you would have to do this extra work, say you called Model.setKeyForNodeData on some node data. Then all of the links connected with that node would have updated “from” and “to” key values, but the out-of-date “key” values, and you would need to call setKeyForLinkData on each one.

I tried your approach but this will not work for my case because I sync the gojs state via the addModelChangedListener that is called before the SelectionCopied callback. This way, I am propagating wrong keys to the rest of my app.

However, I do not totally understand why the copy code is not aware of any node references since apparently the diagram renders the link properly. If it would first copy the nodes and then create the links it could pass the right info the the makeUniqueLinkKeyFunction right. Anyhow, would you advice to take another approach here? I could validate the keys according to the makeUniqueLinkKeyFunction function in the modelChangeCallback and fix the case there with the method you mentioned but this also seems a bit like a workaround.

What is your Model Changed listener looking for? Maybe you need to wait until the transaction has finished – check for ChangedEvent.isTransactionFinished, which will be true only for “Transaction” ChangedEvents.

That is exactly what my mode changed listener does. I use the listener to sync the diagram state with the app state using incremental data. When I tried a little bit further with your approach, it seems that the SelectionCopied callback is not fired at all:

diagram.addDiagramListener('SelectionCopied', (e) => {
  debugger;
});

How is the user doing the copy? Maybe “ClipboardPasted” as well?

That worked; didn’t know there was a difference between the two. Implemented the approach you mentioned and it seems to work nicely