iVis-at-Bilkent / cytoscape.js-fcose

fCoSE: a fast Compound Spring Embedder
MIT License
145 stars 26 forks source link

Trying out fixedNodeConstraint, can't get it to work #66

Closed Madd0g closed 5 months ago

Madd0g commented 1 year ago

I'm having trouble with fixing a node in a specific position.

I tried passing:

fixedNodeConstraint: [
  { nodeId: "b", position: {x: 618, y: 100} }
}

I want my node on the right top area of the viewport. the x: 618 is half of the viewport size, I also tried the full number. it seems to be ignoring it, the node is randomly positioned on the screen.

I tried passing a wrong nodeId and it blew up with an error, so I guess the value is getting most of the way through. When I add this node into vertical alignment with another node, that constraint works perfectly.

Any idea what I might be doing wrong? I stared at nodeId/x/y for a while and compared it with examples, it seems like I'm using it correctly.

Thanks

(p.s. I'll try to reproduce in some online editor)

hasanbalci commented 1 year ago

My first thought is that it might be working correctly but not noticeable because layout also fits the graph to the viewport by panning and zooming.

The point is that a node has a model position and a rendered position in Cytoscape.js as stated here. The position given in the fixed node constraint is the model position and layout changes only the model positions of the nodes. The model position and rendered position of a node may be different if the graph does not have default zoom (1) and pan (x: 0, y:0) values. Enabling layout's fit option also changes these zoom and pan values to fit the graph to the viewport.

You can see the difference by printing the model and rendered positions of the fixed node after the layout. You must see the model position is set to the values you provided in the option.

Madd0g commented 1 year ago

since I'm trying to position my node on the top right "end" of the canvas viewport, I stopped dividing the viewport by 2, just to see if it would change anything.

fixedNodeConstraint.push({
    nodeId: 'b',
    position: { x: viewportWidth - 100, y: 100 },
});

then, I ran this from the console:

cy.viewport().pan()
// Proxy(Object) {x: -762.4522401888385, y: 243.01350028884474}
cy.viewport().size()
// Proxy(Object) {width: 1236, height: 513}
cy.$('#b')[0].position()
// Proxy(Object) {x: 1121, y: 90}
cy.$('#b')[0].renderedPosition()
// {x: 491.656275306156, y: 343.70017860690854}

I have not panned manually, this is after the initial load of the component and calling layout().

I have tried to detect the pan before calling layout() (I thought I'd subtract it in the position.x calculation, but pan.x / y always zero before calling layout().

Funny thing is, if I focus just on the y (supposed to be 100 always) - it seems sort of correct if you subtract the pan. However at the time of setting position.x the pan of the canvas is still zero.

This results in something that looks randomly positioned, but if you subtract the pan - the value is the provided value.

EDIT: when I set randomize: false (didn't notice that true was the default and was commenting it out to test, ha) it does not look so random anymore, it's still incorrect but always placed in the same position.

hasanbalci commented 1 year ago

Please see this JSBin. First click on "fCoSE" button to apply layout which fits the node with id "glyph1" to (viewportWidth - 100, 100) and then click on "Print Positions" button to print both viewport size and node's coordinates to the console. You will see that node's position is (viewportWidth - 100, 100).

Please note that if you don't want to set zoom and pan values to default before layout, you need to make correct conversion between model and rendered coordinates to give the correct values to the constraint option.

Madd0g commented 1 year ago

I see in your jsbin it works even if I put the fixedNodeConstraint in the first layout() call. I also tried doing multiple calls to layout, but it doesn't fix it.

        cy.zoom(1);
        cy.pan({x:0, y: 0})

I tried taking this idea of resetting the pan/zoom, so I added this in my code in the stop function of the layout just to see what would happen and bam, after the layout and the pan reset the node is exactly where I want it to be, but the rest are offscreen and badly positioned.

Please note that if you don't want to set zoom and pan values to default before layout, you need to make correct conversion between model and rendered coordinates to give the correct values to the constraint option.

It's a fresh canvas, I'm adding elements to it and immediately after that I'm calling layout, the pan shows x:0, y:0 before the layout runs.

I'm trying to play with all the different options to try to understand how my code could be causing this weird miscalculation.

I added on('viewport') events, I see that while it's animating, it's also panning...

{"size":[1221,513],"pan":{"x":0,"y":0},"zoom":1}
{"size":[1221,513],"pan":{"x":0,"y":0},"zoom":1}
{"size":[1221,513],"pan":{"x":0,"y":0},"zoom":1}
{"size":[1221,513],"pan":{"x":-47.28413458402551,"y":10.913080230874382},"zoom":1.0177211000526032}
{"size":[1221,513],"pan":{"x":-63.045512778700655,"y":14.550773641165836},"zoom":1.023628133403471}
{"size":[1221,513],"pan":{"x":-78.8068909733758,"y":18.188467051457295},"zoom":1.0295351667543386}
{"size":[1221,513],"pan":{"x":-110.32964736272605,"y":25.463853872040193},"zoom":1.0413492334560739}
{"size":[1221,513],"pan":{"x":-126.09102555740125,"y":29.101547282331662},"zoom":1.0472562668069416}
Madd0g commented 1 year ago

Oof sorry, I figured it out, it's the defaults messing with me again, I've been commenting out fit: true instead of trying to set it to false. That was the problematic setting, without it the node is exactly at the coordinates I wanted.

But while the node is exactly where I want it, others are offscreen (it happens in the jsbin too). I kinda was hoping to be able to set one node's position visually and have others just rearrange within the viewport while considering the available space.

hasanbalci commented 1 year ago

Then I'm not sure but adding relative placement constraints for other nodes may help. Assume you fixed one node to the top-right of viewport, then you may try to add relative placement constraints for every other node so that they will be on the left and below of the fixed node.

Madd0g commented 1 year ago

I have played with alternatives - for example, I tried to ensure that certain nodes are oriented to the top right instead of statically positioned there.

so I was able to achieve the above by just saying - every single other node is lower and to the left from the ones I want at top right. It sort of achieves what I want (regardless of fit), but slighltly more fragile and noisy (I have to find all the "other" nodes and create rules for them all).

oh, after re-reading what you said, you suggested to mix both approaches, I have not tried that yet, but it does make sense. I'll try.