2
votes

This question is similar to Unlock nodes on grab/mousedown event, can not move them immediately in Cytoscape.js , but bear with me. My situation has an added wrinkle so that the accepted answer does not fit for me; I want to use a continuous layout.

I am creating an interactive graph editor with Cytoscape. I want the graph to be continuously laid out with a spring based layout, similar to d3's force layout. The user should be able to click and drag any of the nodes at any time to manually tweak the layout, and after they let go of a node, the node's position should become fixed; it should no longer be affected by the force layout, it should just stay where it is until the user moves it again or unlocks it. .

See https://apo.adrian-jagusch.de/#/ for an example of the desired behavior. Click the circle or square button on the toolbar, then click a bunch of times on the white empty area to place some nodes. Then click the crosshairs button and try dragging the nodes around. The ones you drag will stop moving until you unlock them by double-clicking on them.

I have managed to set things up so that my nodes get locked when I let go of them. The problem that I have in common with the asker of the question I linked above is that, once the nodes are locked, it is not possible to click and drag them any more until they are unlocked. If I register the event handler cy.nodes().on('mousedown', (event) => event.target.unlock()), then the node gets unlocked when I click on it, but I can't move it immediately; I have to let go of the mouse button and click a second time before the node will be dragged. I want to be able to drag it on the first click.

I have considered a couple of solutions to the problem:

  1. Following maxkfranz's suggestion to lock nodes only during layout and unlock them afterwards, I have tried using a non-continuous layout (Euler or Cose) and simply re-running it in response to the "free" event that is fired when a user finishes dragging a node. I used animate: "end" to provide a visual transition. This works pretty much like I expected, but it doesn't look good and doesn't feel good to interact with. I really want the layout to be continuous.

  2. I have thought about applying the continuous layout to only the subset of nodes that have not been fixed by the user, i.e. cy.nodes('[!fixedByUser]').layout({ name: "cola" ...}), stopping the existing layout and instantiating a new layout every time a node gets fixed or un-fixed by the user. The problem with this idea is, the nodes whose positions have been fixed by the user should not be ignored completely by the layout. They should still exert forces upon non-fixed nodes.

  3. Patch the cola layout so that it does not update the positions of nodes whose data includes the property fixedByUser: true. This just seems like way too much work to have to go through. I would rather just use a library that supports this feature to begin with.

  4. Switch to a different graph library that supports my use case better.

I'd appreciate your thoughts about this!

1
Might be a dirty fix, but what about using mouseover/out to (un)lock the nodes? - Tom Aerts
Thanks for the suggestion, I tried it out just now: cy.nodes().on('mouseover', function (event) { if (event.target.data('fixedByUser')) { event.target.unlock() } }) cy.nodes().on('mouseout', function (event) { if (event.target.data('fixedByUser')) { event.target.lock() } }) The problem with this is that as soon as I unlock the node, it moves out from underneath the mouse cursor, making it hard to click on it. It's like it's running away from the cursor. - Ann
@Ann I'm facing the same issue. Have you found any solution for this? - DongBin Kim
@DongBinKim In the end, I chose to develop my app with D3 rather than Cytoscape. D3 proved to be quite flexible. It was easy to get started and implement the behavior I wanted here using d3-force and 'fx' & 'fy' attributes. But beware: As the project grew larger and more complex over 2 years, I felt that I often had to reinvent the wheel, reimplementing things which Cytoscape may have handled for me. I have quite often run into bugs because I made mistakes in my low-level code working directly with SVG elements via D3. If were to start this project over again, I would consider option #3. - Ann

1 Answers

0
votes

If you don't want a node to be affected by a layout, then don't include it in the layout using eles.layout(). If you want to change that condition while the layout is running, then stop the old layout and start a new one with the new set of elements. Any layout that can be run infinitely can be replaced without issue.