How to programmatically create element and set its coordinates?

Desired result: everytime an element on diagram changes, collect each element’s coordinates (position, size, waypoints (if sequenceflow)) and save them. The next time the diagram is loaded, I need to recreate the exact positions of the elements using the saved coordinates.

Actual result: Saving is working fine, but when I reacreate the elements using modeling.createShape their positioning is way off.
Before reload:
image
After reload:
image

Simplified example on how I collect coordinates and create shapes:

You have to notice that we have a autoplace feature in place that makes such automatic positions when creating new elements. If you don’t want that you will have to disable these modules, so something like

const modeler = new Modeler({
  container,
  keyboard: {
    bindTo: document
  },
  additionalModules: [
    {
      autoPlace: ["value", {}],
      bpmnAutoPlace: ["value", {}]
    }
  ]
});

This unfortunately didn’t help. The participant keeps expanding after each reload and elements inside it also move a little.
image

Why autoplace behavior is triggered if I explicitly send x, y, width, height to createShape function?

Is there any robust way to make it not ignore the parameters I send?

The main problem was with autoResize functionality. I’ve added a custom rule to disable all autoresize operations. You could also pass autoResize: [ 'value', null ], in Modeler’s additional modules instead of creating custom rules.

But this was not enough since createShape function ignored coordinates I was passing in attrs for some reason.

So I’ve created a wrapper createShape function, created a shape there, manually updated the coordinates, and then called Modeling’s updateElementProperties passing an empty object like so:

public createShape(attrs: Partial<ShapeAttributes>, parent: Shape) {
    const shape = this.modeling.createShape(attrs, {x: attrs.x, y: attrs.y}, parent);
    shape.x = attrs.x;
    shape.y = attrs.y;
    if (attrs.width && attrs.height) {
      shape.width = attrs.width;
      shape.height = attrs.height;
    }
    this.updateElementProperties(shape, {});
    return shape;
  }

And this worked for shapes. Calling updateElementProperties is crucial by the way.

For connections manually updating waypoints didn’t work. They looked broken on the diagram: sometimes start and tip of connections were not connected to sources and targets. I’ve devised this logic to fix it:

const connectionStart = getMid(flow.source);
const connectionEnd = getMid(flow.target);
const middle: Waypoint[] = waypoints.slice(1, waypoints.length - 1);
this.modeling.updateWaypoints(flow, [connectionStart, ...middle, connectionEnd]);

Basically what’s happening here is I take the first and last waypoint and smoothly connect to source and target respectively (due to that sometimes the original waypoints from the api ignored to make the connection smooth), the rest of the waypoints in between are left untouched.

This piece of code checks if there are only two waypoints, then I’m passing an empty object to layoutConnection. Otherwise it automatically lays out connections the default way even if they were diagonal.

const layout = middle.length === 0 ? {} : {connectionStart, connectionEnd};
this.modeling.layoutConnection(flow, layout)
1 Like