cytoscape / cytoscape.js

Graph theory (network) library for visualisation and analysis
https://js.cytoscape.org
MIT License
10.09k stars 1.64k forks source link

Quick way to position node on renderedPosition by pan? #1691

Closed buffpojken closed 5 years ago

buffpojken commented 7 years ago

Possible feature request: a convenience method to animate/pan the viewport in order to position a given node on a specific renderedPosition?

Or perhaps too trivial to implement as a convenience?

buffpojken commented 7 years ago

Hmm, further thought - maybe not a bad fit after all, due to the zoom-pan interdependency?

maxkfranz commented 7 years ago

You can set rendered position in the json on add or you can change to it with the api function http://js.cytoscape.org/#node.renderedPosition

buffpojken commented 7 years ago

Hmm, sure thing - but that doesn't make it possible to animate/pan the viewport to a corresponding position?

As in:

When tapping a node, animate viewport so as to place said node 300px right of canvas edge?

maxkfranz commented 7 years ago

The pan is always rendered co-ordinates: http://js.cytoscape.org/#cy.pan

I think you can just animate the pan to the rendered position of the node, plus or minus whatever offset you'd like.

It might be easier to use the panBy prop for this, since that's relative (and panning and rendered co-ordinates are relative).

buffpojken commented 7 years ago

That was my initial thought as well - but the moment you also throw a zoom into that animation, that pan goes all over the place?

maxkfranz commented 7 years ago

If you promise chain the animations and do the zooming one first, then it's exactly the same.

It's only a bit more complicated if you do both at once. You'd just need to scale the panBy by z2/z1, I think

buffpojken commented 7 years ago

That was my thought as well - but no go (or I've gone completely blind in which case I apologize):

    let pos     = node.renderedPosition();
    let currentZoom     = this.graph.cy.zoom();  // 0.6
    let desiredZoom     = 1; 
// Pan the viewport so as to place node 300px from right edge and vertically centered on canvas
    let newPan          = {x: this.graph.cy.width()-300, y: this.graph.cy.height()/2}
    let desiredPanBy    = {x: newPan.x-pos.x, y: newPan.y-pos.y}
    let scaledPanBy     = {x: desiredPanBy.x*(desiredZoom/currentZoom), y: desiredPanBy.y*(desiredZoom/currentZoom)}
    this.graph.cy.animate({
      zoom: desiredZoom,
      panBy: scaledPanBy
    })

because this results in all sorts of weird panning animations?

buffpojken commented 7 years ago

So, some further investigation. I'm pretty sure there's some confusion (probably) on my part regarding model positions vs. rendered positions.

Since cy.animate({zoom: N, center: {eles: aNode}}) works exactly like my desired behavior, I've read through the implementation. By re-implementing my own implementation based on that very same implementation, it now works - but according to my understanding of model positions vs. rendered positions it shouldn't.

    let zoom    = 1.1;
    let xDiff   = (this.graph.cy.width()/2)-300;
    let bb = node.boundingBox(); 
    let w   = this.graph.cy.width()
    let h = this.graph.cy.height();
    var pan = {
      x: ((w - zoom * ( bb.x1 + bb.x2 )) / 2)+xDiff,
      y: (h - zoom * ( bb.y1 + bb.y2 )) / 2
    };

    this.graph.cy.animate({
      zoom: 1.1, 
      pan: pan
    })

This works exactly as intended. But boundingBox returns model positions, not rendered positions? How can I just mix-n-match the positions provided with boundingBox (which ought to be model positions) with my renderedPosition-based offset (300px), scale them - and then throw them both to pan - which ought only handle rendered positions?

maxkfranz commented 7 years ago

The ordering for zoom and pan were chosen to make panning always rendered co-ords and to make it so things tend to cancel out to simplify the final version of a formula, like the one you posted.

In this case, the multiply by zoom is enough to effectively make the bb in rendered units. It's also a bit simpler, because it's centring.

To have an animation preset for this (or an immediate function), you'd need to specify both a model position and a rendered position --- i.e. place this model position at this rendered position. Something like cy.panTo({ position: p1, renderedPosition: p2 }).