Auto BPMN Layout Advice

Hi,

We’re currently trying to produce a BPMN layout very similar to the BPMN example on the GoJS samples page, but we want the nodes to be positioned automatically. I have a screenshot below of a simple example:

I found this example which uses yFiles for HTML, to give you an idea of what we’re trying to achieve:

I wondered if this is something someone has already looked into producing? Or if anyone has any advice on how we could go about achieving this using the GoJS library?

I ideally we wouldn’t want to write a full BPMN algorithm from scratch.

Thanks!
Gary

Yes, GoJS does not have an existing sample like that. We should work on that.

So do you know ahead of time exactly which lane each node should be in? Is the vertical order of the lanes fixed beforehand?

I think the basic idea is to set each lane’s Group.layout to null, and to set the layout of the container of the lanes to a LayeredDigraphLayout (or perhaps a TreeLayout if the graphs are suitable). This will layout all of the nodes and links in all of those lane groups as if the groups did not exist.

The layout would need to be extended to shift nodes up or down to make sure they were in the correct lane, and to expand the breadth of lanes that had multiple nodes overlapping in any area.

Hi Walter,

Thanks for the tips.

We do know ahead of time which nodes appear in which groups. I don’t really mind at the moment which vertical order the lanes are in. The diagram data has no position information in it, so I’m trying to create a fully automatic layout. We do not allow the user to position or resize objects, we rely solely on the layout (currently the tree layout).

In our case, the lanes do not have a container, they’re individual groups on the diagram (we don’t have a concept of pools).

I currently extend the the layeredDigraph layout and manually size and position the lanes after doLayout has been run of LayeredDigraph layout - maybe this isn’t the best way to do it.

    swimlanes.each(function (p) {

        var shape = p.resizeObject;

        shape.width = longestSwimlane;

        shape.move(new go.Point(0, maxSwimlaneY));

        maxSwimlaneY += shape.actualBounds.height;

    });

I’ve also tried to manually call the GridLayout on only the lane parts. Neither of these approached seem to work if I set the group layout to null.

How can update the size and position of the lanes with the group layout set to null?

Would you recommend I re position the lanes before or after the LayeredDigraph layout has been performed.

I’m very new to GoJS so my approach might be totally wrong!

Thanks

Hmm, upon further thought, I wonder if you might not benefit from using Table Layout: Table Layout and http://gojs.net/latest/extensions/TableLayout.js. Unlike that sample, you do not have to have a Group in each cell! In fact, you should not use any Groups at all.

Assuming each lane is one row, you can assign the row for each node. To determine the column for each node, you would run a TreeLayout or a LayeredDigraphLayout with overrides that made commitNodes and commitLinks no-ops, and with an override of commitLayers, which provides column information.

So Diagram.layout would be a TableLayout, and you would not need to use Groups at all – all Nodes would be top-level Nodes. Note that that TreeLayout or LayeredDigraphLayout would not be the Diagram.layout – it would just be an instance upon which you explicitly called doLayout, passing all of the Nodes and Links that you want to lay out, just to capture the “column” information for each node.

I think you can implement the visual appearance of lanes by using Parts in layerName: "Background" at column: 1 with columnSpan: 9999 consisting of simple rectangular Shapes. Lane headers would go in column: 0, just as that Table Layout sample demonstrates.

Thanks for the suggestion, it sounds like an interesting approach that could work for what we need.

In our case, we can have more than one row per swimlane, but I think the grid idea could still work with rowspan on the header and some addition logic to avoid clashing nodes.

I’m going to give this a go and see how I get on, thanks for the advice!

Hi Walter,

I’m trying to get column information using the TreeLayout as you suggested above, but from what I can tell the part.column property doesn’t get set, maybe I’m looking in the wrong place?

 var cwTreeLayoutNoOps = function () {

        go.TreeLayout.call(this);

        this.layerStyle = go.TreeLayout.LayerUniform;

    };

    go.Diagram.inherit(cwTreeLayoutNoOps, go.TreeLayout);

    cwTreeLayoutNoOps.prototype.commitNodes = function () { /* noops */ }

    cwTreeLayoutNoOps.prototype.commitLinks = function () { /* noops */ }

    cwTreeLayoutNoOps.prototype.commitLayers = function (layerRects, offset) {

        this.network.findAllParts().each(function (p) {

            if (p instanceof go.Node) {

                console.log(p.column);
            }

        });

    };

As far as I can tell, the layerRects don’t offer me any useful information either.

Am I missing a layout property for the TreeLayout maybe which would cause the column property to be set?

Thanks

Sorry about that advice. Try this initialization of your Diagram:

    $(go.Diagram, . . .,  // Diagram.div reference or name
        { . . .,  // other Diagram properties
            layout: $(TableLayout, {
              . . ., // other TableLayout properties
              doLayout: function(coll) {
                var tlayout = new go.TreeLayout();
                tlayout.commitNodes = function() {
                  this.network.vertexes.each(function(v) {
                    v.node.column = v.level;
                  });
                };
                tlayout.commitLinks = function() { };
                tlayout.doLayout(coll);
                TableLayout.prototype.doLayout.call(this, coll);
              }
            }),
        })

Hi Walter,

Thanks a lot for your help. The code above worked great, I built upon that to allow for multiple rows within a swimlane. This is a screenshot of the current working version:

There’s a lot of cleaning up to be done, but it’s getting there.

Just wanted to say thanks :)

You are welcome. Thanks for posting a screenshot.

hello,garyscarter
I found this topic,which is a bit old.But that’s exactly what I’ve come across,I am a novice for gojs,and I see you have solved this problem, so I want to learn from your code, can you share your gojs code? Thank you.