FluentAPI Gateway Waypoints Issue

Hi all,
First of all thank you for your great work and providing your product also as open source.
Currently I am working on an atomated translation of a DSL I provided during a running PhD project to BPMN. The DSL deals with realworld services as well as technical services, which are grounded on an ontological sound basis. Basically the DSL supports a sub-set of BPM concepts, such as roles, subprocesses, user task, service task, parallelity etc.

I am using your Fluent API to generate BPMN elements based on services models described in my DSL. Works great so far. The only issue is that I encounter relates to the waypoints generated:

I would expect the waypoints to follow the red examples I gave in the image.
How can I instruct the API to find a better way to connect to the pgw?

Any help on this topic would greatly be appreciated!
Meikel

Hi @edobm,

I love hearing stories from academia. Thanks for sharing this!

As to your question, I assume that you are using the Java API (and not BPMN.io).
The in-build layouting is not very sophisticated, as you have observed yourself. In this particular example, the block-structured model makes it easy to improve the layout:

  1. find all gateways with more than one incoming sequence flow
  2. find all respective sequence flows
  3. add waypoints:
    1. Waypoint: right mid-point of source. If source and target are not horizontally aligned, add
    2. Waypoint: horizontally aligned with first source, vertically aligned with target.
    3. Waypoint: bottom mid-point of target

Note, calculating and adding these waypoints is not possible in the fluent API. I’d add a post-processing step instead.
The following snippet may aid as a starting point:


      List<SequenceFlow> sequenceFlows = (List<SequenceFlow>) model.getModelElementsByType(SequenceFlow.class);
      for (SequenceFlow sf : sequenceFlows) {
        if (!(sf.getTarget() instanceof Gateway)) {
          continue;
        }
        ...
        Waypoint wp = model.newInstance(Waypoint.class);
        wp.setX(...);
        wp.setY(...);
        sf.getDiagramElement().getWaypoints().add(wp);
      }

Hi Stephan,
Many thanks your quick response!
You are right. I use the Fluent API to construct the model. BPMN.io is only used for visualizing the result.
Blond question: Which class holds the DI specific coordinates? Looked at DiagramElement but there are no position nor location related attributes…
Best,
Meikel

The diagram element has child-element(s). For nodes, it is called bounds. For edges, it is called waypoints. For example:

      <bpmndi:BPMNEdge id="Flow_0d2vp58_di" bpmnElement="Flow_0d2vp58">
        <di:waypoint x="400" y="120" />
        <di:waypoint x="452" y="120" />
      </bpmndi:BPMNEdge>
     
      <bpmndi:BPMNShape id="Event_1fwzzt5_di" bpmnElement="Event_1fwzzt5">
        <dc:Bounds x="452" y="102" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="434" y="145" width="73" height="27" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>

To access them via the BPMN Model library, you can do the following:

  List<SequenceFlow> sfs = (List<SequenceFlow>) model.getModelElementsByType(SequenceFlow.class);
  for (SequenceFlow sf : sfs) {
    List<Waypoints> wps = sf.getDiagramElement().getWaypoints();
    Bounds bounds = (Bounds) sf.getTarget().getDiagramElement().getUniqueChildElementByType(Bounds.class);
  }

Dear Stephan,

Great. That worked out as expected. Thanks a lot!

I add the corresponding coding if someone else has similar issues (missing some cases):

    private void correctWayPoints(BpmnModelInstance model) {
        List<SequenceFlow> sequenceFlows = (List<SequenceFlow>) model.getModelElementsByType(SequenceFlow.class);

        for (SequenceFlow sf : sequenceFlows) {
            if (!(sf.getTarget() instanceof Gateway)) {
                continue;
            }

            // Clear the old waypoints
            sf.getDiagramElement().getWaypoints().clear();

            // Get source and target's bounds.
            Bounds s_b = (Bounds) sf.getSource().getDiagramElement().getUniqueChildElementByType(Bounds.class);
            Bounds t_b = (Bounds) sf.getTarget().getDiagramElement().getUniqueChildElementByType(Bounds.class);

            // Calculate the new waypoints
            Waypoint wp;

            // Source and target are at the same y-coordinate
            if (s_b.getY() + s_b.getHeight() / 2 == t_b.getY() + t_b.getHeight() / 2) {
                wp = model.newInstance(Waypoint.class);
                wp.setX(s_b.getX() + s_b.getWidth());
                wp.setY(s_b.getY() + s_b.getHeight() / 2);
                sf.getDiagramElement().getWaypoints().add(wp);

                wp = model.newInstance(Waypoint.class);
                wp.setX(t_b.getX());
                wp.setY(t_b.getY() + t_b.getHeight()/2);
                sf.getDiagramElement().getWaypoints().add(wp);
            }

            // Source and target are not at the same y-coordinate
            if (s_b.getY() + s_b.getHeight() / 2 > t_b.getY() + t_b.getHeight() / 2) {
                wp = model.newInstance(Waypoint.class);
                wp.setX(s_b.getX() + s_b.getWidth());
                wp.setY(s_b.getY() + s_b.getHeight() / 2);
                sf.getDiagramElement().getWaypoints().add(wp);

                wp = model.newInstance(Waypoint.class);
                wp.setX(t_b.getX() + t_b.getWidth() / 2);
                wp.setY(s_b.getY() + s_b.getHeight() / 2);
                sf.getDiagramElement().getWaypoints().add(wp);

                wp = model.newInstance(Waypoint.class);
                wp.setX(t_b.getX() + t_b.getWidth() / 2);
                wp.setY(t_b.getY() + t_b.getHeight());
                sf.getDiagramElement().getWaypoints().add(wp);
            }
        }

    }

Best,
Meikel

5 Likes