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:
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.
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.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.Switch to a different graph library that supports my use case better.
I'd appreciate your thoughts about this!
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