Initial layout issue


We have a fairly simple diagram using a ForceDirectedLayout to show relationships between elements in an underlying business model.

A node’s location is in a two-way bind with the matching element in the underlying model. Model elements may be added by the user through other means than the diagram. When that happens, those elements don’t have a location.

When opening a diagram, we check whether at least 1 element is missing a location. In that case, we trigger an initial layout of the diagram, which assigns a location to all nodes.

This approach works as expected for new diagrams, where all elements/nodes are without a location. A similar approach that is used when the user drops new nodes into the diagram from the palette also works well.

However, there is a use case where we reliably hit a display issue. When a model element is added from outside of a diagram, and that diagram is then opened, we get misplaced links that look like the following:

Here the added element was “[Vision] Our Vision”, which incidentally is the only node whose link is correctly positioned.

This happens whether we use the isInitial layout property or call layoutDiagram() on the diagram after fully populating its model.

The issue can be corrected in 2 ways: either setting a timeout of a few seconds before calling layoutDiagram(), or having the user manually trigger a layout of the diagram through a toolbar button we have implemented.

So the issue seems to have to do with the interplay of the initial layout with the initialization of the diagram. Is this something that others have observed as well and do you have any recommendations to address the issue?

We tried to find an event that would fire once the diagram is fully initialized and drawn, so that we could listen for it and only trigger a layout at that time. Unfortunately, the only event that comes close is “InitialLayoutCompleted”, which obviously won’t do.

“InitialLayoutCompleted” is the DiagramEvent that I think you are looking for.

Try setting ForceDirectedLayout.isInitial to false. Then in that event listener you could do:

  . . .,
  "InitialLayoutCompleted": function(e) {
    if (e.diagram.nodes.any(function(n) { return !n.location.isReal(); })) {
  . . .

Basically, if upon loading all of the nodes have real locations, there’s no layout, because Layout.isInitial is false.

But if there are any nodes with non-real locations, then the “InitialLayoutCompleted” listener will force a layout.

I can’t explain why the links aren’t routed properly in your situation. Perhaps you have a binding on Link.points, so the load will restore all of the link routes to be where they had been, before a layout.

I’ve tried hooking up a call (conditional and not) to layoutDiagram() to the InitialLayoutCompleted event, to no avail. The only way it works is if I set a timeout of a few seconds before the call, which isn’t really a solution obviously. It’s like the diagram isn’t actually fully initialized/drawn when the event is fired, which compromises the layout somehow.

We indeed have a two-way binding on the Link.points property. Do you see any reason why that would be problematic for an initial layout, but not for a deferred or manual layout?

Yes, it’s the process of handling the “initial” layout that the link routes are restored. It has to wait until then because nodes might move or appear to change size during the evaluation of bindings.

Before I posted that “InitialLayoutCompleted” listener I tried it in a sample which sets Layout.isInitial to false and had TwoWay Bindings on both Node.location and Link.points. I made sure all of the node data in the model had proper locations matching all of the saved link routes, except for one that had none. When I loaded the model the call to Diagram.layoutDiagram happened in the new listener and everything looked good. I believe this is the behavior that you are looking for.

Without that “InitialLayoutCompleted” DiagramEvent listener, loading that model resulted in everything looking good except for the missing node (because it had no real location and there was no layout to assign a real location) and the missing links that connected with that un-located node.

I agree that a layout is necessary to handle nodes without location. The issue is that even if I force an unconditional layout from an InitialLayoutCompleted listener, the links are still misplaced. It’s like the diagram hasn’t had time to settle down and the layout fails to place everything correctly.

Try it in a simpler app – I think I used the PageFlow sample.

I have no doubt it may work in a simpler app, but what I’m interested in is to make it work for our own app, as you may imagine.

What I’ve been trying, and appears to work, is to wrap the call to layoutDiagram() in window.requestAnimationFrame(). Then everything gets positioned correctly, whether the call happens in an InitialLayoutCompleted listener or right after setting the diagram model.

Does it make sense to you that such an approach may work, and do you see anything that may be problematic with it?

If you find that it works reliably, that’s great.