Mutiple links drawn from BPMN gateway node are not behaving as expected

I am trying below bpmn diagram and here I want link back from Exclusive Gateway to Task1 and as shown in below diagram link is properly drawn from TopSide of gateway but when I try to draw it from bottom side it is drawing from TopSide only.

We should allow user to draw it from any side. Any idea whats going wrong?

Below is the gateway code taken from BPMN sample

goObj(go.Panel, “Spot”,
goObj(go.Shape, “Diamond”,
{ strokeWidth: 1, fill: _this.GatewayNodeFill, stroke: _this.GatewayNodeStroke,
name: “SHAPE”,
desiredSize: new go.Size(_this.GatewayNodeSize, _this.GatewayNodeSize),
portId: “”, fromLinkable: true, toLinkable: true, cursor: “pointer”,
fromSpot: go.Spot.NotLeftSide, toSpot: go.Spot.MiddleLeft
},
new go.Binding(“desiredSize”, “size”, go.Size.parse).makeTwoWay(go.Size.stringify)), // end main shape

Specifying a Spot of multiple Sides means that it will automatically choose the side that it thinks makes the most sense.

If you really want it to remember a particular side, you need to set the fromSpot or toSpot to a specific side.

If I set fromSpot to a specific side it forces user to start the link from specific side and users may not like it. As shown in above screenshot user should be allowed to draw links from Exclusive Gateway to other nodes from whichever side he likes.

One way to address that requirement is to have four separate ports on each node, one on each side. That’s what the FlowChart sample does, for example.

But you might be able to implement what you want with nodes having only a single large port, if you customize the LinkingTool so that when it starts drawing the new link it sets the Link.fromSpot to the desired Spot, given the point at which the mouse/finger is relative to the node.

I haven’t tried this, but I think you could override LinkingTool.doActivate to look at the this.diagram.lastInput.documentPoint and compare it to the this.originalFromPort.getDocumentPoint(go.Spot.Center) to figure out which Side Spot you want the link to use. Then set the this.temporaryLink.fromSpot to that value.

Then you also need to override LinkingTool.insertLink to call the super method and then set the Link.fromSpot to the desired Spot.

Note that you’ll probably want to have a TwoWay Binding of the Link.fromSpot property so that the Spot is saved on the link data.

If you want to do the same sort of thing for Link.toSpot, I think you would want to add code to the override of LinkingTool.insertLink.

@walter,

Adding separate ports as given in FlowChart sample fixed my issue but now I am facing new problem.

BPMN sample has context menu on Activity node to add events as shown below

Add an event to Activity node by selecting one of the menu and now the link is getting removed between two tasks and after this I won’t be able to draw the link between nodes

After looking at the code I realized that below code that adds the event is adding new ports and this code is called on context menu action

           let _this:any=this;
	this.bpmnDiagram.startTransaction("addBoundaryEvent");
	this.bpmnDiagram.selection.each(function(node:any) {
		// skip any selected Links
		if (!(node instanceof go.Node)){
			return;
		} 
		if (node.data && (node.data.category === "activity" || node.data.category === "subprocess" || node.data.category === "dataobject")) {
			// compute the next available index number for the side
			var i = 0;
			var defaultPort = node.findPort("");
			while (node.findPort("be" + i.toString()) !== defaultPort){
				i++;
			}
			var name = "be" + i.toString();
			if (!node.data.boundaryEventArray) {
				_this.bpmnDiagram.model.setDataProperty(node.data, "boundaryEventArray", []); // initialize the Array of port data
			}
			let stroke:string="black";
			if(node.data.custom){
				stroke="green";
			}
			// create a new port data object
			var newportdata = {
					portId: name,
					eventType: evType,
					eventDimension: evDim,
					color: "white",
					alignmentIndex: i,
					stroke:stroke,
				};
			_this.bpmnDiagram.model.insertArrayItem(node.data.boundaryEventArray, -1, newportdata);
		}
	});
	this.bpmnDiagram.commitTransaction("addBoundaryEvent");

Looks like above code is overriding the ports added earlier. Could you please help me in fixing this issue

That’s interesting – I didn’t know about that use of ports in those BPMN nodes.

OK, I suggest that you do the alternative that I originally suggested – assume the node only has one big default port, but each Link can have its Link.fromSpot and Link.toSpot set and saved via TwoWay Binding.

Thank you. Could you please point me to a sample that has this feature implemented or could you share one or two lines of code

  function sideOf(pt, port) {
    var tl = port.getDocumentPoint(go.Spot.TopLeft);
    var br = port.getDocumentPoint(go.Spot.BottomRight);
    var w = br.x - tl.x;
    var h = br.y - tl.y;
    if (w < 0.5 || h < 0.5) return go.Spot.Center;
    var a = (pt.x - tl.x) / w;
    var b = (pt.y - tl.y) / h;
    if (a + b > 1) {
      return (a > b) ? go.Spot.RightSide : go.Spot.BottomSide;
    } else {
      return (a > b) ? go.Spot.TopSide : go.Spot.LeftSide;
    }
  }
      $(go.Diagram, . . .
          {
            "linkingTool.insertLink": function(fromnode, fromport, tonode, toport) {
              var newlink = go.LinkingTool.prototype.insertLink.call(this, fromnode, fromport, tonode, toport);
              if (newlink !== null) {
                newlink.fromSpot = sideOf(this.diagram.firstInput.documentPoint, this.isForwards ? fromport : toport);
                newlink.toSpot = sideOf(this.diagram.lastInput.documentPoint, this.isForwwards ? toport : fromport);
              }
              return newlink;
            },
            "relinkingTool.reconnectLink": function(existinglink, newnode, newport, toend) {
              var success = go.RelinkingTool.prototype.reconnectLink.call(this, existinglink, newnode, newport, toend);
              if (success) {
                if (toend) {
                  existinglink.toSpot = sideOf(this.diagram.lastInput.documentPoint, newport);
                } else {
                  existinglink.fromSpot = sideOf(this.diagram.lastInput.documentPoint, newport);
                }
              }
              return success;
            },
            . . .