Animating the auto-centering of the diagram content

I’d like the diagram content to be always aligned in the center of the viewport, after a page resize, node repositioning, etc. I successfully managed to do that by setting the Diagram.contentAlignment property to true.

The only problem is that I’d like to animate the repositioning of the diagram content (which is instead instantaneous), to be consistent with what happens on first load.

I skimmed through the samples and the existing topics but I did’t find anything, I apologise if I missed something obvious. How can I achieve this?

I don’t think you need additional information, but just in case here’s a minimal example:

new go.Diagram('myDiagramDiv', {
    contentAlignment: go.Spot.Center,
    model: new go.GraphLinksModel([
        {key: 'A'},
        {key: 'B'},
        {key: 'C'}
    ], [
        {from: 'A', to: 'B'},
        {from: 'B', to: 'C'},
        {from: 'C', to: 'A'}

Please let me know if you need other details.

Instead of setting Diagram.contentAlignment, I suggest that you implement a “ViewportBoundsChanged” DiagramEvent listener that, when you choose, you implement your own explicit animation of the setting of the Diagram.position property:
GoJS Animation -- Northwoods Software
GoJS Events -- Northwoods Software

Thanks for the suggestion, but the documentation of the ViewportBoundsChanged event states:

Do not modify the Diagram position or scale (i.e. the viewport bounds) in the listener.

Doesn’t this cause an infinite loop? (It does apparently, I’m checking with very brief test.)

My current attempt involves removing the ViewportBoundsChanged listener beforehand, then enabling it back again after the animation is finished. It seems to work, I still need to wrap my head around the coordinate system to compute the right Diagram.position value.

Still, contrary on what happens with setting the Diagram.contentAlignment property, this solution doesn’t take into account the moving of the nodes, so I guess I have to listen for the SelectionMoved property too.

Yes, you have to prevent recursion in a “ViewportBoundsChanged” event listener.
And changes to the Diagram.position would not happen in the listener, but later in animation ticks.

Instead of implementing “ViewportBoundsChanged” and “SelectionMoved” listeners, maybe just implement a “DocumentBoundsChanged” listener.

But DocumentBoundsChanged will not be triggered by, say, a page resize (assuming the div's size depends on it).

Sorry, yes, that’s right; I should have re-read your first post.

OK, in a “ViewportBoundsChanged” DiagramEvent listener, see if !e.subject.canvasSize.equals(e.subject.newCanvasSize), and only in that case do the animation of Diagram.position.

Posting my current solution (relevant parts) for posterity.


myDiagram.animationManager.defaultAnimation.finished = () => {
    myDiagram.addDiagramListener('ViewportBoundsChanged', doLayout);
    myDiagram.addDiagramListener('DocumentBoundsChanged', doLayout);


function doLayout() {
    // disable the listener to avoid an infinite loop
    myDiagram.removeDiagramListener('ViewportBoundsChanged', doLayout);

    // compute the new position
    const {x: dX, y: dY, width: dW, height: dH} = myDiagram.documentBounds;
    const {x: vX, y: vY, width: vW, height: vH} = myDiagram.viewportBounds;
    const position = new go.Point();
    position.x = dX - (vW - dW) / 2;
    position.y = dY - (vH - dH) / 2;

    // create and start the centering animation
    const animation = new go.Animation();
    animation.finished = (animation) => {
        // re-enable the listener
        myDiagram.addDiagramListener('ViewportBoundsChanged', doLayout);
    animation.add(myDiagram, 'position', myDiagram.position, position);

If you keep adding a ‘DocumentBoundsChanged’ listener, you should keep removing it too.

Not sure, setting the position property of the diagram doesn’t seem to trigger the DocumentBoundsChanged event, at least in my case.

That event is raised when the document area changes size or position.
My suggested policy avoids having the same listener get called unnecessarily.

I see, and makes sense in principle, yet I’m unable to reproduce that behaviour.

The “DocumentBoundsChanged” DiagramEvent is raised when the area of the document (i.e. Diagram.documentBounds) changes because the union of the actualBounds of all of the Parts in the Diagram has changed.