Links rerouting when shape hasn't changed

Hi

We have a shape with a textbox external to it. We have set the …ObjectName properties to the shape so all links connect to the shape rather than the Node. If the user edits the text in the textbox, the links are rerouting even though the shape hasn’t moved (we also get a size model change event triggered even though e.newValue and e.oldValue are the same). It seems like GoJS is re-routing because the Node has changed size. Is this expected behaviour?

Actually on further investigation this also happens when the text is inside the shape

Yes for version 1.6 and earlier, maybe for version 1.7. Try upgrading to 1.7, and tell us what changes, if anything.

It looks like 1.7 behaves in the same manner. Is this something you’re planning on looking at at some point?

Can we have a look at your template (with maybe some node and link data) to investigate this? It may be one of the cases that we expected 1.7 to fix.

To be clear: Unnecessary link re-routing is something we’ve improved in 1.7, but we do not handle all possible cases.

The template is pretty complex. I can try to reproduce a small test case or I can give you access to our app, whichever you prefer

We’d prefer the small test case, if you don’t mind the trouble.

OK, will have a go, it may take a while! It does seem that 1.7 is behaving better when renaming shapes that have the text inside the shape but still seems to suffer when the text is outside

Actually setting Link.adjusting to End in GoJS 1.7 seems to fix this. We didn’t set it before since it didn’t seem to have any effect. Thanks!

Or maybe not… Setting Link.adjusting to End makes links behave in ways we don’t really want. Below is a modified version of the script in the basic example. If you adjust the link between Alpha and Gamma and then rename Alpha, you should see that

  • if the name change is narrower than the width of the Alpha rectangle, the link remains where it is
  • if the name change is wider than the width of the Alpha rectangle, the link is re-routed
    This suggests to me the linking is taking note of the changed dimensions of the node, rather than the rectangle shape

`function init() {
if (window.goSamples) goSamples(); // init for these samples – you don’t need to call this
var $ = go.GraphObject.make; // for conciseness in defining templates

myDiagram =
  $(go.Diagram, "myDiagramDiv",  // create a Diagram for the DIV HTML element
    {
      // position the graph in the middle of the diagram
      initialContentAlignment: go.Spot.Center,

      // allow double-click in background to create a new node
      "clickCreatingTool.archetypeNodeData": { text: "Node", color: "white" },

      // allow Ctrl-G to call groupSelection()
      "commandHandler.archetypeGroupData": { text: "Group", isGroup: true, color: "blue" },

      // enable undo & redo
      "undoManager.isEnabled": true
    });

// Define the appearance and behavior for Nodes:
// These nodes have text surrounded by a rounded rectangle
// whose fill color is bound to the node data.
// The user can drag a node by dragging its TextBlock label.
// Dragging from the Shape will start drawing a new link.
myDiagram.nodeTemplate =
  $(go.Node, "Vertical",
    { 
	  locationSpot: go.Spot.Center,
	  locationObjectName: "SHAPE",
      selectionObjectName: "SHAPE",
      resizeObjectName: "SHAPE",
      movable: true,
      resizable: true		  
	},
    $(go.Shape, "RoundedRectangle",
      {
	    name: "SHAPE",
        fill: "white", // the default fill, if there is no data bound value
        portId: "", cursor: "pointer",  // the Shape is the port, not the whole Node
        // allow all kinds of links from and to this port
        fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
        toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true
      },
      new go.Binding("fill", "color")
	),
    $(go.TextBlock,
      {
        font: "bold 14px sans-serif",
        stroke: '#333',
        margin: 6,  // make some extra space for the shape around the text
        isMultiline: false,  // don't allow newlines in text
        editable: true  // allow in-place editing by user
      },
      new go.Binding("text", "text").makeTwoWay()
	)
  );

// Define the appearance and behavior for Links:

function linkInfo(d) {  // Tooltip info for a link data object
  return "Link:\nfrom " + d.from + " to " + d.to;
}

// The link shape and arrowhead have their stroke brush data bound to the "color" property
myDiagram.linkTemplate =
  $(go.Link,
    { toShortLength: 3, 
	  relinkableFrom: true, 
	  relinkableTo: true,
	  reshapable: true,
	  routing: go.Link.AvoidsNodes		  
	},  // allow the user to relink existing links
    $(go.Shape,
      { strokeWidth: 2 },
      new go.Binding("stroke", "color")),
    $(go.Shape,
      { toArrow: "Standard", stroke: null },
      new go.Binding("fill", "color"))
  );

// Define the appearance and behavior for Groups:

function groupInfo(adornment) {  // takes the tooltip or context menu, not a group node data object
  var g = adornment.adornedPart;  // get the Group that the tooltip adorns
  var mems = g.memberParts.count;
  var links = 0;
  g.memberParts.each(function(part) {
    if (part instanceof go.Link) links++;
  });
  return "Group " + g.data.key + ": " + g.data.text + "\n" + mems + " members including " + links + " links";
}

// Groups consist of a title in the color given by the group node data
// above a translucent gray rectangle surrounding the member parts
myDiagram.groupTemplate =
  $(go.Group, "Vertical",
    { selectionObjectName: "PANEL",  // selection handle goes around shape, not label
      ungroupable: true },  // enable Ctrl-Shift-G to ungroup a selected Group
    $(go.TextBlock,
      {
        font: "bold 19px sans-serif",
        isMultiline: false,  // don't allow newlines in text
        editable: true  // allow in-place editing by user
      },
      new go.Binding("text", "text").makeTwoWay(),
      new go.Binding("stroke", "color")),
    $(go.Panel, "Auto",
      { name: "PANEL" },
      $(go.Shape, "Rectangle",  // the rectangular shape around the members
        {
          fill: "rgba(128,128,128,0.2)", stroke: "gray", strokeWidth: 3,
          portId: "", cursor: "pointer",  // the Shape is the port, not the whole Node
          // allow all kinds of links from and to this port
          fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
          toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true
        }),
      $(go.Placeholder, { margin: 10, background: "transparent" })  // represents where the members are
    ),
    { // this tooltip Adornment is shared by all groups
      toolTip:
        $(go.Adornment, "Auto",
          $(go.Shape, { fill: "#FFFFCC" }),
          $(go.TextBlock, { margin: 4 },
            // bind to tooltip, not to Group.data, to allow access to Group properties
            new go.Binding("text", "", groupInfo).ofObject())
        )
    }
  );

// Define the behavior for the Diagram background:

// Create the Diagram's Model:
var nodeDataArray = [
  { key: 1, text: "Alpha", color: "lightblue" },
  { key: 2, text: "Beta", color: "orange" },
  { key: 3, text: "Gamma", color: "lightgreen", group: 5 },
  { key: 4, text: "Delta", color: "pink", group: 5 },
  { key: 5, text: "Epsilon", color: "green", isGroup: true }
];
var linkDataArray = [
  { from: 1, to: 2, color: "blue" },
  { from: 2, to: 2 },
  { from: 3, to: 4, color: "green" },
  { from: 3, to: 1, color: "purple" }
];
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);

}`

Yes, that is true. When the node changes its bounds, it invalidates connected link routes. Although it is smarter than it used to be, it still is not optimal.

Is it something you’re looking at improving? Gateways in BPMN diagrams generally have a name that is wider than the gateway so a few of our customers have noticed this behaviour

That would be desirable, and it’s on our list of future improvements, but I’m not sure we’ll do such a risky thing with a 1.7.* bug fix release.

Have you tried what I suggested? Either clear out the routes upon “InitialLayoutCompleted”, or change the Link.adjusting in “InitialLayoutCompleted”.

If I bind the adjusting property so it it only gets set to End if there are points in the link and also clear out the points whenever it relinks, I get pretty much what I’m after. It does seem to occasionally relink when I wouldn’t expect it to, but that may be due to some faulty logic somewhere. Thanks for the help