Dynamically adding nodes to Cytoscape
Asked Answered
B

1

8

My company is building a graph-view editor for chatbots. We are using Cytoscape along with the cytoscape-cola extension to accomplish this. One of the issues we are facing is dynamically adding new nodes to the graph without them overlapping with existing nodes on the graph.

I have looked through previous similar questions(listed below) but to no avail:

Cytoscape - handle locked nodes

I tried the solution in there i.e. applying the layout only on the newly added nodes but they keep getting stacked in the centre of the screen.

Cytoscape - ignore locked nodes

I tried the solution mentioned in here but regardless of locking the nodes in one go i.e. cy.nodes().lock() or individually i.e. cy.nodes().forEach(node => node.lock()), the locked nodes still keep moving. Also interesting thing to note here is that when locking nodes individually, the newly added node(s) are also locked regardless of whether or not I am locking in the call above or not.

Cytoscape - dynamically add nodes without moving others

I tried this solution as well but the locked nodes still move and the complete layout is changed - sometimes altogether, sometimes just a little.

Code

This is currently what I am using to construct the graph:

const layoutConfig = {
    name: "cola",
    handleDisconnected: true,
    animate: true,
    avoidOverlap: true,
    infinite: false,
    unconstrIter: 1,
    userConstIter: 0,
    allConstIter: 1,
    ready: e => {
        e.cy.fit()
        e.cy.center()
    }
}
this.graph = Cytoscape({ ... })

this.layout = this.grapg.makeLayout(layoutConfig)

this.layout.run();

This is what I am using to add new node(s) to the graph:

const addElements = (elements: ElementSingular | ElementMultiple) => {
    this.graph.nodes().forEach(node => {
        node.lock();
    })

    this.graph.add(elements)

    this.layout = this.graph.makeLayout(layoutConfig)

    this.layout.on("layoutready", () => {
        this.nodes().forEach(node => {
            node.unlock();
        })
    })

    this.layout.run()

    this.graph.nodes().forEach(node => {
        node.unlock();
    })
}

I'd like to do one of the following:

  1. Understand what I am doing wrong if what I am trying to accomplish is possible but my code doesn't achieve it
  2. Understand why I would not be able to accomplish it i.e. the limitations that govern it
Blackdamp answered 1/10, 2019 at 7:19 Comment(1)
I did some updates to my answer, i didn't notice that you were calling the unlocks after the run.Dermoid
D
3

Edit: Is this what you were looking for? https://output.jsbin.com/hokineluwo

Edit: I didn't saw before, but you are also unlocking the nodes right after the layout call, Cola is async, the run only kicks-off the process. Remove that code and only use the layoutstop method.

I don't remember correctly, but i think that cola keeps moving the elements after the layoutready. From their code:

// trigger layoutready when each node has had its position set at least once

There is a layoutstop event that you could use (and cytoscape-cola uses too).

From the docs (emphasis is mine):

layoutready : when a layout has set initial positions for all the nodes (but perhaps not final positions)

layoutstop : when a layout has finished running completely or otherwise stopped running

I would try to remove that callback and inspect if it gives good results. Also, you should try to reproduce it here: http://jsbin.com/fiqugiq

It makes it easier for others (even the authors) to play with it and see if you have found a bug or not.

Dermoid answered 2/10, 2019 at 14:18 Comment(7)
Thanks for the response! I'll try it and get backBlackdamp
I tried your solution. I modified it slightly and my issues arises again. Here you go : jsbin.com/kokokal/edit?html,css,js,console,outputBlackdamp
^^ I just removed the edges from the new node that you were adding and the overlap behaviour starts againBlackdamp
I think you need to play a bit with the layout params. I'm blindly moving them and got better results. Read what they are for and adapt as you need. You should have better results with these values i tried (again, blindly modified them, please read what they are for) unconstrIter: 100, userConstIter: 100, allConstIter: 100,Dermoid
Note that potentially this won't scale well. When adding multiple nodes it might be better to just run the layout again, so every node will find its perfect place according to the cola layout.Dermoid
@parthMehta Did this work for you? do you have additional questions?Dermoid
No additional questions for now. Marked as correct answer. @josejulio you are are correct that every use would need different set of params so I have been playing around and found something suitable!Blackdamp

© 2022 - 2024 — McMap. All rights reserved.