Zoom animation calculations for zoomToRect

So after some looking around, it seems that there is no current zoomToRect functionality that handles animation.

The diagram has a zoomToRect that does not animate, and commandHandler has a zoom in and a scrollToRect which both animate, but no zoomToRect, so I’m just rolling my own using the animation system:

var animation = new go.Animation();
animation.add(myDiagram, "scale", myDiagram.scale, scale);
animation.add(myDiagram, "position", myDiagram.position, new go.Point(rect.x, rect.y));
animation.start();

However, I’m having some trouble getting the correct values for scale and position.

Right now I get the position and scale in a doubleClick handler that does the subGraph expansion:

cmd.expandSubGraph(group);
var rect = group.actualBounds;
var scale = Math.min(myDiagram.viewportBounds.width/rect.width, 
myDiagram.viewportBounds.height/rect.height);

However, these values don’t see to be quite right, and my spatial geometry brain apparently isn’t quite up to the task of figuring out what the right calculation is. I tried looking at the zoomToRect function in diagram, but the minified code makes parsing the logic a bit difficult.

It looks like it’s checking for a NaN height/width and if it finds one, the logic is just scale = <viewport.height/width>*/<new rect height/width>, however I can’t figure out what the calculation is when the rect has both a height and a width.

What would be the correct way to calculate the position and scale to create a “zoomToRect” animation for an arbitrary rectangle +/- a margin?

basically this but an animation:

myDiagram.zoomToRect(group.actualBounds.addMargin(new go.Margin(ZOOM_MARGIN)));
myDiagram.centerRect(group.actualBounds);

So I’ve gotten most of your code figured out (outside of a parameter it doesn’t look like I’m ever using that I can’t decipher) and it looks like this now:

cmd.expandSubGraph(group); // Expand the subgroup
var rect = group.actualBounds.copy();
rect.grow(rect.height/6, rect.width/6, rect.height/6, rect.width/6); // Copy and modify the new viewport rect so the new group takes up ~2/3rds of the screen
var width = rect.width, height = rect.height, viewWidth = myDiagram.viewportBounds.width, viewHeight = myDiagram.viewportBounds.height;

var scale = Math.min(viewWidth * myDiagram.scale / rect.width, viewHeight * myDiagram.scale / rect.height); // Sanity check scale value

if (viewHeight / height > viewWidth / width) { 
	scale = (viewHeight / height);  // Copied from zoomToRect; check if the height or the width is the 'limiting factor' and scale from there.
} else {
	scale = (viewWidth / width);
}

// Build an animation from the current to the newly calc'd values and animate!
var animation = new go.Animation();
animation.add(myDiagram, "scale", myDiagram.scale, scale);
animation.add(myDiagram, "position", myDiagram.position, new go.Point(rect.x, rect.y));
animation.start();

However, it looks like my issue now is that the point I’m getting from the group’s bounds is from before the expansion, so my center point is off. I can mess around with a setTimeout or something similar, but I assume there’s a better way to get the new center point after the expansion has laid itself out?

I suggest that you try starting the animation in a “SubGraphExpanded” DiagramEvent listener, https://gojs.net/latest/intro/events.html#SubGraphExpanded. If that doesn’t work, try it in a “LayoutCompleted” DiagramEvent listener.

My only problem with that approach is that I wanted two different ‘expansions’. One using a doubleClick handler that does zoom/fit and one using a button (the SubGraph Expander) that does not.

I’m not sure how to differentiate those two in a diagram event handler.

That was what started my experimentation with using the templates, since then I could have them change to two different template names which were both associated with the same template, e.g:

var template = GO(...) 
map.add("withZoom", template) 
map.add("withoutZoom", template)

then check in the handler which template it was and either zoom or not there, but this caused a bunch of issues with layouts and code duplication and was generally pretty ugly. I considered trying it in the event handler and using a global var or something like that to let the handler know whether it should zoom or not, but that felt very hacky to me.

I looked and it doesn’t seem like those events carry any associated “triggering element” information or anything like that I can go off of, unless I missed something?

Also, the subGraphExpanded event would be able to tell me which group was expanded to zoom to, but would the LayoutCompleted?

That is correct – DiagramEvents happen due to some command or tool having operated (or some other circumstance, depending on the event name) so there is no innate way to tell the reason for the event.

So, for example, a “LayoutCompleted” DiagramEvent just tells you that a layout has been performed, but not the reason for the layout. There might be many reasons that several Layouts were invalidated.

And I agree that you should not have multiple templates for what really are the same thing.

But you could add your specific DiagramEvent listener in your click handler(s), whether GraphObject.click or HTML DOM event onclick, and then remove that listener when you have done what you want.

That means one handler for the “SubGraphExpanderButton” click, and another one for the Diagram.doubleClick, and perhaps others too – whatever you need.

Oh, I see what you mean. Have the listener functions predefined as a Zoom/No Zoom, add/register them in the handler then have them remove themselves.

Though that still won’t save me from needing a global var to save off the group that I should be expanding/zooming to, unless I’m missing something.

https://gojs.net/latest/api/symbols/Group.html#subGraphExpandedChanged passes the Group as the only argument

https://gojs.net/latest/intro/events.html#SubGraphCollapsed and https://gojs.net/latest/intro/events.html#SubGraphExpanded have the DiagramEvent.subject be the collection of Groups that were collapsed or expanded.