Open TJBrunson opened 4 years ago
Ok, so, it seems like the issue is that this sets the layout before all of the elements are added. If I store the instance of cytoscape and reset the layout from a button press, it corrects the issue.
How can I get the layout to auto update after elements are added?
Hi @TJBrunson,
How can I get the layout to auto update after elements are added?
After you add an element you can call layout()
again. Check the addNode
method in the example. Something like:
this.$refs.cy.instance.layout({ name: "cose" }).run()
Let me know if that works for you.
I have solved it by adding a button and clicking it after I ensure the cy-element for loop has ended. Is there a way I can tell programmatically when the last node I add is added so I can run it then?
I tried running it from the :afterCreated directive on the cytoscape instance, but it causes an error. I think it is because no nodes are created at that point.
I have ran a pretty thorough debug and the issue seems to be either:
I think probably the issue has to do with point 1 because I can use a button to set the layout after render.
ok. This is how I fixed it.
I removed the cy-element v-for loop to add all of the elements and added this code:
afterCreated(cy) {
this.cy = cy;
this.addInitialNodes();
},
addInitialNodes() {
this.cy.add(this.fiData);
this.cy.layout({ name: "cose" }).run();
},
Basically, if you try to set the layout from the afterCreated method, no nodes have been added yet. So instead of adding nodes through the cy-elements tag with a loop, I add them through the cy instance and then set the layout after that method call returns.
There has to be a more elegant way to do this? I tried your solution @TJBrunson and it only works for "cose". "random" or "grid" layouts for example don't work. I wonder if this is to do some async-stuff going on there?
Anyone find a solution for this yet? It seems a pity to waste the potential of using <cy-element>
components when we can't apply a layout after they have initialized.
I've successfully set the layout both using a secondary "layout" button and by explicitly calling layout().run()
in the afterCreated()
when I manually add data, but it seems like a serious issue that layouts don't gel with the <cy-element>
component.
I have a solution, I think. I'm new to frontend stuff so please correct me if my understanding or terminology is wrong. I've been having this same issue of nodes getting stuck in the upper left corner but rendering correctly after I click a "layout" button.
I think the problem with using the cy-elements API is that calling layout.run() gets called before the DOM is aware that something has updated. This causes either some nodes or no nodes at all to be positioned correctly for the layout and they're all stacked up on each other in the upper left corner.
What needs to happen is that the layout needs to be called after all of the cyelements have been added. Here is what cyelements typically looks like:
<cy-element
v-for="def in cyElements"
:key="`${def.data.id}`"
:definition="def"
v-on:click="showData($event, def)"
/>
The proper way to run layout().run() is to use Vue's nextTick() which tells the code to run after the next DOM cycle updates. Not sure if there might be race conditions when there's a lot going on the screen but here's what I have for my code to update layout:
watch: {
cyElements: async function (val) {
const cy = await cyPromise;
this.$nextTick(() => {
cy.layout(this.layoutMode).run();
cy.fit(null, 200);
});
},
In my case, cyElements is something I'm pulling from Vuex after a call to a neo4j backend and layoutMode is a set of layouts I have defined elsewhere but the important part is that the elements don't get the layout until all of the cyElements have been updated to cytoscape! I no longer get nodes stacking up in the corner anymore even after dynamically changing the list of elements.
I would make a PR but not only am I not sure if one is needed, but I wouldn't know what to change in the first place. Definitely could use the change in documentation though if this ends up working for others.
I have a solution, I think. I'm new to frontend stuff so please correct me if my understanding or terminology is wrong. I've been having this same issue of nodes getting stuck in the upper left corner but rendering correctly after I click a "layout" button.
I think the problem with using the cy-elements API is that calling layout.run() gets called before the DOM is aware that something has updated. This causes either some nodes or no nodes at all to be positioned correctly for the layout and they're all stacked up on each other in the upper left corner.
What needs to happen is that the layout needs to be called after all of the cyelements have been added. Here is what cyelements typically looks like:
<cy-element v-for="def in cyElements" :key="`${def.data.id}`" :definition="def" v-on:click="showData($event, def)" />
The proper way to run layout().run() is to use Vue's nextTick() which tells the code to run after the next DOM cycle updates. Not sure if there might be race conditions when there's a lot going on the screen but here's what I have for my code to update layout:
watch: { cyElements: async function (val) { const cy = await cyPromise; this.$nextTick(() => { cy.layout(this.layoutMode).run(); cy.fit(null, 200); }); },
In my case, cyElements is something I'm pulling from Vuex after a call to a neo4j backend and layoutMode is a set of layouts I have defined elsewhere but the important part is that the elements don't get the layout until all of the cyElements have been updated to cytoscape! I no longer get nodes stacking up in the corner anymore even after dynamically changing the list of elements.
I would make a PR but not only am I not sure if one is needed, but I wouldn't know what to change in the first place. Definitely could use the change in documentation though if this ends up working for others.
This workaround worked as well for me
I can't get this to work either, and @daddycocoaman's solution doesn't seem to do the trick either (maybe I did it wrong).
<cytoscape ref="cy"
:config="config"
v-on:mousedown="mouseDown"
v-on:cxttapstart="updateNode"
:afterCreated="afterCreated" :preConfig="preConfig">
<cy-element
v-for="def in elements"
:key="`${def.data.id}`"
:definition="def"
:sync="true"
/>
</cytoscape>
And the elements and config defined as reactive data:
data () {
return {
elements: [],
config: {
layout: {
name: 'dagre' // tried others too
},
style: [
....
This by itself does not make a layout change, even when forced like this:
preConfig: function (cytoscape) {
cytoscape.use(dagre)
},
afterCreated: function(cy) {
console.log(cy)
this.$refs.cy.instance.layout(this.config.layout).run() //tried with cy too
this.$refs.cy.instance.fit(100)
}
@manishpatelUK Hi, the most important thing was the wrap any changes or methods called from the cy instance in a nextTick() block.
Thanks for the quick response @daddycocoaman . I did as below, but still no joy unfortunately.
afterCreated: function(cy) {
console.log(cy)
this.$nextTick(() => {
cy.layout(this.config.layout).run();
cy.fit(null, 200);
});
}
For the afterCreated
workaround, the logic surrounding the cy
Promise is a little rough. There is no use of async/await or then syntax, which is quite unfortunate, because it is a little hard to follow. I am pretty sure the cy
Promise is just not being resolved correctly on line 46 before running afterCreated
on 47. The duality between instance
and cy
is confusing as well. This seems to work,
async function afterCreated(cy) {
await cy;
cy.layout({ name: "grid" }).run();
}
@rcarcasses if my company gets behind this then maybe I can help maintain it, but I am unsure at the moment.
Hey guys, sorry for being off but currently I have little time to maintain this. @aentwist I'm happy to hear you/your company are considering to opt in as a maintainer, you are very welcome!
I am attempting to load data through axios and pass it into a child component that sets up Cytoscape. I have configured the style and display as directed in the documentation, but I get all of my nodes stacked on top of each other.
How can I either, reset the layout after the nodes are added, or have it set automatically when nodes are added?
For reference, here is the relevant part of my template:
my afterCreated method is:
and hee is my cyConfig located in my data property: