How to stop link rerouting

Hi,

When I drag a node and drop it onto a link, the link automatically reroutes to make space for the dropped node. I need to keep routing: go.Link.AvoidsNodes on my link template.

I tested this with Flowgrammer, where routing is also set to go.Link.AvoidsNodes. However, in Flowgrammer, the link remains static and does not reroute when a node is dropped onto it.

Could there be any other property that differs between my diagram and Flowgrammer, causing the link to reroute in my case?

Which GoJS version are you using? 3.0.13 has an AvoidsNodesRouter bug, as can be seen in that sample.

That Flowgrammer sample has always performed a layout whenever dropping a new node anywhere, so that would not be the same situation as the FlowChart sample, Interactive Diagram for Building Flowcharts | GoJS Diagramming Library, where a layout is not performed.

We’re currently using version 2.3.5, and our link behavior matches that of the FlowChart sample. However, we’d like to achieve the behavior seen in the Flowgrammer sample. Our link template currently uses routing: go.Link.AvoidsNodes. What changes can we make to accomplish this?

What is the value of Diagram.layout?
Do you use Groups, and if so, what is Group.layout?
What DiagramEvents listeners are there and what do they do?
What Diagram initializations do you do besides templates?
What is your link template?

Diagram.layout : TreeLayout
Group.layout : TreeLayout
DiagramEvents:
‘relinkingTool.linkValidation’: function(fromNode, fromPort, toNode) {
if (fromNode.containingGroup || toNode.containingGroup) {
return (fromNode.containingGroup && toNode.containingGroup && toNode.containingGroup.data.key === fromNode.containingGroup.data.key);
}
return true;
},
‘contextMenuTool.canStart’: function() {
if (this.diagram.lastInput.clickCount > 1) return false;
return go.ContextMenuTool.prototype.canStart.call(this);
},
ExternalObjectsDropped: function(event) {
//Handles the drops from Palette
},

Diagram initializations:
allowDelete: false,
initialDocumentSpot: go.Spot.Top,
initialViewportSpot: go.Spot.Top,
maxSelectionCount: 1,
‘toolManager.gestureBehavior’: go.ToolManager.GestureZoom,
‘toolManager.hoverDelay’: 300,
‘draggingTool.dragsLink’: true,
‘undoManager.isEnabled’: true,
‘undoManager.maxHistoryLength’: 0,
‘draggingTool.isEnabled’: true,
‘animationManager.isEnabled’: false,
contentAlignment: go.Spot.Center,

Link Template:
myDiagram.linkTemplate =
$(go.Link,
{
cursor: “pointer”,
relinkableFrom: true,
relinkableTo: true,
selectionAdorned: false,
routing: go.Link.AvoidsNodes,
reshapable: true,
mouseEnter: function(e, link) { /* custom mouseEnter function / },
mouseLeave: function(e, link) { /
custom mouseLeave function / },
mouseDragEnter: function(e, link) { /
custom mouseDragEnter function / },
mouseDragLeave: function(e, link) { /
custom mouseDragLeave function / },
mouseDrop: function(e, link) { /
custom mouseDrop function */ }
},
$(go.Shape), // the link shape
$(go.Shape, { toArrow: “Standard” }) // the arrowhead
);

How are both layouts defined?
What do those mouse event handlers do? I’m wondering if they have any side effects that would invalidate a layout, or would just cause the link route to be invalidated.

Separate issue: are you missing a linkValidation predicate for the LinkingTool? You just seem to have one for the RelinkingTool.

Diagram:

$(go.TreeLayout, {
	angle: 90,
	nodeSpacing: 100,
	layerSpacing: 120,
	alternateAngle: 90,
	setsChildPortSpot: false,
	setsPortSpot: false,
})

Group:

$(go.TreeLayout, {
        isRealtime: false,
        angle: 90,
        nodeSpacing: 100,
        layerSpacing: 80,
        alternateAngle: 90
    })

mouseEnter, mouseLeave, mouseDragEnter, mouseDragLeave : All these four events are just used to apply and remove the stroke when cursor is on the link.

mouseDrop is used to add the dropping node in between the link.

Yes we only have for RelinkingTool. There is no LinkingTool defined. Is that something that is causing the issue Walter?

OK, since you haven’t set Layout.isOngoing to false, it means that every time that a node or a link is added or removed or changes visibility, or whenever a node changes size (including groups), the responsible Layout is invalidated and performed before the end of the transaction. That includes the case where a (new) node is “spliced” into a link, which actually involves reconnecting the existing link and adding a new link.

In the Flowgrammer sample, when a node is spliced into a link it causes a layout to be performed again, which may cause containing groups’ layouts to be performed again too. So it may seem as if the existing link is not being rerouted, but it actually is.

Every Diagram has a LinkingTool to allow users to draw new links. The RelinkingTool is only used to reconnect an existing link. Both classes inherit from the same LinkingBaseTool class. I was just guessing that if you allow users to draw new links, that they have the same constraints imposed by your RelinkingTool.linkingValidation predicate. But if you don’t allow users to draw new links, then the issue is moot.

Hi Walter,

Not sure if we are going in the expected solution path. I’m facing an issue with ghost nodes: when a node is dragged from the Palette, it disappears upon entering the main canvas, making it hard to determine the exact drop location. This may stem from our GoJS configuration or framework gaps.

As we near release and haven’t pinpointed the root cause, we’re exploring workarounds. Our current focus is to identify the drop target—whether it’s a node, link, or the canvas. Currently, we use event.diagram.selection.first().position to get the drop location, but this isn’t always accurate, especially when a node is dropped on a link (since the link reroutes automatically).

Our use cases for dropping nodes are:

  1. On Canvas: Add the node as an orphan.
  2. On a Node: Replace the existing node.
  3. On a Link: Insert the new node between linked nodes.

We’ve implemented the logic for these cases but struggle to reliably detect the exact link for case 3. Could you suggest any alternative workarounds or ways to prevent link rerouting in this scenario?

Thanks!

Oh, so you are still having a problem with Drag from palette to diagram is not showing the shape ? Have you tried putting both the Palette and the target Diagram in the same component?

Back to this topic. If when the user drops a node on a link, you say that you insert the new node between the linked nodes. I would think that this should normally result in a layout being performed, to make sure that there’s enough room for the new node and that it is positioned reasonably.

Even if a layout does not happen, the modification of the existing link (or, alternatively, the removal of that link and the addition of two new links connecting with the new node) would cause the link to be re-routed. So I do not see how it could be possible to avoid re-routing the existing link.

Yes, we are still facing the issue, even with the Palette and target Diagram in the same component.

Back to this topic: Okay then setting that aside, can we explore another workaround. Currently, when a node is dropped from the Palette onto a Node or Link (both having mouseDrop events) on the canvas, two events are triggered:

  1. ExternalEventsDropped
  2. mouseDrop

This is causing a race condition. Is there any way to address this? Alternatively, is there a hack to determine whether the drop target is a Node or Link, or to know if the mouseDrop event is being triggered within the ExternalObjectsDropped handler?

What do those two things do in your app?

The mouseDrop handler is defined on the GraphObject class, so the handler’s second argument will be an instance of whatever it was declared on.

You can tell whether the mouseDrop is internal (wtihin the Diagram) or external (started in a different Diagram) by looking at Diagram.currentTool. If it’s an instance of DraggingTool, then you know that the DraggingTool is running, so it must have started in this same Diagram. If it’s an instance of ToolManager, then you know that the drag had not started in this Diagram.

Hmmm, it isn’t obvious to me whether you can tell which Diagram it started in, whereas in an “ExternalObjectsDropped” listener the DiagramEvent.parameter is the source Diagram.

The ExternalObjectsDropped event creates custom properties and loads the shape templates for nodes being dropped from the Palette.

The mouseDrop event performs the following actions (custom logic is implemented for these):

  1. On a Node: Replaces the existing node.
  2. On a Link: Inserts the new node between the linked nodes.

We can already differentiate the starting point in mouseDrop using a custom property on the node.

The only challenge we are facing is determining if mouseDrop was triggered (indicating the drop occurred on a Node or Link, rather than on the canvas) while inside ExternalObjectsDropped.

GraphObject.mouseDrop or Diagram.mouseDrop event handlers are always called before any “ExternalObjectsDropped”, “SelectionMoved” or “SelectionCopied” DiagramEvent listener is called.

But now you know about how to detect whether the mouseDrop event handler was called due to an external or an internal drag-and-drop.