Controlling position of self-transition link

Hi,

We have a sequence diagram which is based on your example of the sequence diagram. We want to add the possibility of a self-transition however, I can not get it to work: somehow the link always ends up in the middle of the lifeline (which is the middle of the node). It looks like the result of the function “getLinkPoint” is completely ignored.

This is easy to reproduce: in the Sequence Diagram on your website, change the “to” field from the first link to “Fred” and press load: the “order” link is now in the middle of the lifeline of “Fred”.

Any suggestion how we can control the position and layouting of a self-transition link?

Ah, yes, the sample does not support drawing reflexive links in a special manner. I can look into that.

Something like this will be in version 1.5, but should work in 1.4:

[code] // a custom routed Link
function MessageLink() {
go.Link.call(this);
this.time = 0; // use this “time” value when this is the temporaryLink
}
go.Diagram.inherit(MessageLink, go.Link);

/** @override */
MessageLink.prototype.getLinkPoint = function(node, port, spot, from, ortho, othernode, otherport) {
var p = port.getDocumentPoint(go.Spot.Center);
var r = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft),
port.getDocumentPoint(go.Spot.BottomRight));
var op = otherport.getDocumentPoint(go.Spot.Center);

var data = this.data;
var time = data !== null ? data.time : this.time;  // if not bound, assume this has its own "time" property

var aw = this.findActivityWidth(node, time);
var x = (op.x > p.x ? p.x + aw / 2 : p.x - aw / 2);
var y = convertTimeToY(time);
return new go.Point(x, y);

};

MessageLink.prototype.findActivityWidth = function(node, time) {
var aw = ActivityWidth;
if (node instanceof go.Group) {
// see if there is an Activity Node at this point – if not, connect the link directly with the Group’s lifeline
if (!node.memberParts.any(function(mem) {
var act = mem.data;
return (act !== null && act.start <= time && time <= act.start + act.duration);
})) {
aw = 0;
}
}
return aw;
};

/** @override */
MessageLink.prototype.getLinkDirection = function(node, port, linkpoint, spot, from, ortho, othernode, otherport) {
var p = port.getDocumentPoint(go.Spot.Center);
var op = otherport.getDocumentPoint(go.Spot.Center);
var right = op.x > p.x;
return right ? 0 : 180;
};

/** @override */
MessageLink.prototype.computePoints = function() {
if (this.fromNode === this.toNode) { // also handle a reflexive link as a simple orthogonal loop
var data = this.data;
var time = data !== null ? data.time : this.time; // if not bound, assume this has its own “time” property
var p = this.fromNode.port.getDocumentPoint(go.Spot.Center);
var aw = this.findActivityWidth(this.fromNode, time);

  var x = p.x + aw / 2;
  var y = convertTimeToY(time);
  this.clearPoints();
  this.addPoint(new go.Point(x, y));
  this.addPoint(new go.Point(x + 50, y));
  this.addPoint(new go.Point(x + 50, y + 5));
  this.addPoint(new go.Point(x, y + 5));
  return true;
} else {
  return go.Link.prototype.computePoints.call(this);
}

}
// end MessageLink[/code]

Thanks! Self-transition/reflexive link is working now. And as usual your help was very quick and spot on; thanks again.

-Paul.

Hi,

I have an additional question: I am using above code also for our state diagrams. For these state diagrams we use “dot” as external tool for doing the layouting. For normal edges, go-js does take over the initial filled points array but changes the start & end point by snapping them to the node. However, for above code I am missing this functionality for the self-transitions and as an end result the start and/or end of the link are not really attached to the node. Example:

If I drag a little bit the control points of the edge, gojs will snap the start and end point to the node:

How can I force gojs to do this automatically? Basically, for an self transition I want the same behaviour as for regular links: all the point of the path should be definable and the start and end ot the link should snap to the node.

Thank you,

Cheers,

-Paul.

It seems to me that your requirements a bit self-contradictory. You say that you want to specify all of the points of the route, and yet you want GoJS to automatically compute the first and last points of the route.

But I think I see what you want, and that sounds reasonable. How are you defining your link template? Well, I’ll just make some assumptions, such as that the ports have no fromSpot or toSpot, and that the curve is Bezier and that the routing is not Orthogonal or AvoidsNodes.

Let’s also assume that for a particular Link you have a desired route that is a List of Points that do not include the desired first point and the desired last point. You want those two end points to be computed based on the actual position and size and shape of the connected Nodes. Then you could call something like:

  function setRouteWithEndPoints(link, pts) {
    var start = link.getLinkPointFromPoint(link.fromNode, link.fromPort, link.fromPort.getDocumentPoint(go.Spot.Center), pts.elt(0), true);
    var end = link.getLinkPointFromPoint(link.toNode, link.toPort, link.toPort.getDocumentPoint(go.Spot.Center), pts.elt(pts.length - 1), false);
    pts.insertAt(0, start);
    pts.add(end);
    link.points = pts;
  }

Hmmm, that’s assuming it’s OK to modify that List, and that you’re using a List instead of an Array, for that matter. You can adapt the code for your own situation regarding how you are getting the route information.

NOTE: you can only call this function after both connected Nodes have been fully measured and arranged. In other words, you cannot call this as soon as the Link is created, because the connected Nodes might not yet have been created or been data-bound or had the opportunity to measure themselves.

Normally you would want to make such calls in an override of the Layout.commitLayout method. For most predefined Layouts, that means something like overriding TreeLayout.commitLinks, which is explicitly called after TreeLayout.commitNodes.

Hi Walter,

Thanks for your answer. This is how my computePoints looks now:

function SDLink() {
  go.Link.call(this);
}
go.Diagram.inherit(SDLink, go.Link);

SDLink.prototype.computePoints = function() {
  if (this.fromNode && this.fromNode === this.toNode) {  
    return true;
  } else {
    return go.Link.prototype.computePoints.call(this);
  }
};

So far a self-transition the “return true” is executed so no additionally layouting is done and the positions of the originally supplied points are used.
However, for other links computePoints is called and as a result also the positions in the original supplied points are used but the link is nicely connected to the start and end nodes. This is the behavior I also want for the self-transitions. (The current implementation of computePoints for self-transitions completely ignores the points array and makes its own loop layout)

So apparently computePoints treat self-transitions separately? Is it not possibly to override this?
I still prefer a solutions which is possible during creation; which is apparently possible for normal links.

Cheers,

-Paul.

PS: Can it be that the notifications of the forum is broken? I did not get any notification of your previous reply of 4 days ago (checked my email and spambox).

The default implementation of Link.computePoints does treat reflexive links specially when routing, but the code is all shared with all of the non-reflexive cases. Now that I try it, I see what you mean about in the default case not restoring a custom route of a reflexive link. We’ll need to investigate this.

Regarding forum and mail notification – maybe you visited the forum before you got a chance to get mail, so it thought that you didn’t need to get any notification by email?

Ah, the problem is in the State Chart sample, not in the library.

Basically, there are two independent data properties that are telling the link how it should be routed. There’s the “curviness” property, which is data bound in the Link template with the Link.curviness property, and there’s the “points” property, which is bound with the Link.points property.

In some of the link data in the State Chart sample there is already a value for the “curviness” property for reflexive links. By itself that works well.

But if the user reshapes such a link, the “points” property gets a new value, an Array of numbers (alternating X and Y values). What happens when the model is saved? Both properties are present.

Then when the model is loaded, which property should take precedence? In this case it just depends on the order in which the Bindings are evaluated.

One way to solve that ambiguity would be to customize the LinkReshapingTool, so that when it operates successfully, not only does it modify the Link.points property, but it also sets Link.curviness to NaN.

There are additional ways to get what you want, such as exchanging the order of the Bindings on the Link.

Cornerning the email notification: no, I had a look at the forum after 4 days wondering why I did not get a responce yet since normally you guys have a excellent response rate; and also of hight quality, a combination you do not see very often ;-)

Hi Walter,

I put the curviness to NaN and indeed I can remove the computePoints now but now also for regular links the start and end points do not get re-layouted so they don’t snap to the nodes. Any ideas?

Cheers,

-Paul.

Are you calling that setRouteWithEndPoints function that I provided above, or better, a variation of it that is appropriate for your app? You should probably do so in an “InitialLayoutCompleted” DiagramEvent listener.

A post was split to a new topic: Dragging reflexive links