Cytoscape.js get node position from dagre layout - cytoscape.js

In order to make zigzag like edges I need to make some calculations and for that I need every node's position on the layout and I am having some trouble achieving that.
I am trying to get the node's position this way:
cy.$('#1891').position()
but no matter which id I use, I always get {x: 0, y: 0} values.
When I add an event like this:
cy.on('mouseover', 'node', function(evt){
var node = evt.target;
console.log(node.position());
});
I get every node's postion.
What would be the right way of getting every node's position from dagre layout?

I found the solution.
It was a silly mistake by me. I was trying to get the node's position before the layout was applied. Putting the cy.$('#1891').position(); after the layout.run(); did the trick.

Related

Create a square with additional data node data

I am pretty much a javascript noob, and very new to cytoscape (but i code in other languages)...
I am trying to use Cytoscape to graph out the servers available in my network. Creating a node per server and their relationship was straightforward actually.
I would like to add the possibility than when I click a node (let's say server12345) that it would open kind of a table with data associated with that server (servertype etc...).
Do you have any idea / examples / guidance on how I could:
add functionality on that click event of a specific node ?
how to create a 'details' table using cytoscope?
Thank you guys so much in advance!
(Ps: My first post on StackOverflow...)
Nice question.
Firstly, you can capture the click event of cytoscape.js like below
cy.nodes().on('click', function(e){
var clickedNode = e.target;
console.log(clickedNode.id());
});
or you can also use the tap event of cytoscape.js
cy.on('tap', 'node', function(evt){
var node = evt.target;
console.log( 'tapped ' + node.id() );
});
You can read events section of the documentation for more details
After you capture this event, I see to ways.
1-) Just modify an existing HTML element on the UI. For example in here you can see on the left side there are some other elements.
So you can write your code like
cy.on('tap', 'node', function(evt){
var node = evt.target;
document.getElementById('someOtherElementOnUI').innerHTML = JSON.stringfy(node.data());
});
This will simply modify some other UI element (HTML element) and changes its content to the data of the selected node.
2-) You can use some extensions like Cytoscape.js-popper and show data dynamically like a tooltip. You can check my answer https://stackoverflow.com/a/66813720/3209523

Cytoscape layouts - Handle locked nodes

I am using vue-cytoscape to render a graph and navigate through a tree-like data structure.
My goal is to expand parent nodes and keep their position in the graph. I would like to simply add the new children nodes.
My approach is to lock current nodes, add the children and unlock the nodes.
this.cy.nodes().lock()
for(let d of data){
this.cy.add(d)
}
this.cy.elements().layout(this.config.layout).run()
setTimeout(() => {this.cy.nodes().unlock()}, 2000) // Give some time for the layout to render before unlocking nodes.
The problem is that the layouts do not consider the locked state of the nodes. Only the new nodes are moved around, which is fine. But the layout is not respected. I am under the impression that the layout calculates a new position for all nodes, but then moves only nodes that are unlocked.
According to this GitHub issue, some layout algorithm should handle locked nodes. I am using the following layouts and none seem to consider locked nodes.
Cola
Fcose
Dagre
avsdf
grid
concentric
Please try calling the layout function only on the added nodes:
var eles = cy.add(data); // refer to http://js.cytoscape.org/#cy.add for adding nodes
eles.layout(this.config.layout).run();
If you don't want nodes to move when calling the layout function, you can exclude them from the rendering. While calling cy.add(), the function returns an object with every added element inside (see var eles = ... in the code).

Get parallel edges and give them color

I want to get list of all parallel edges within a collection and give them seperate color
My code is
cy.elements().parallelEdges().animate({ 'style':{'line-color':'coral', 'width': '10px'} }, {duration: 1000 })
But it end up in coloring all edges....
You call the function on all elements in cytoscape, the documentation implys, that you have to callthe function on edges.
cy.edges().parallelEdges().animate();
Edit:
cy.edges() wont work, the funciton is designed to give you the parallel edges to a set collection of edges, so if you want to achieve this, I think you have to:
Iterate through all nodes via cy.nodes() and a for loop
Get the edges of the current node with cy.edges("[source = theRightId]")
Check for every edge (Just one edge for the following function!!) if it has a parallel edge via cy.$('#idOfEdge').parallelEdges()
Put those with a parallel edge in a cy.collection() [add elements in a collection with: yourCollection = yourCollection.add(theParallelEdges)]

How to create a hierarchy

just started playing with cytoscape.js and the breathfirst layout. Looks like it will be a great library. I want to be able to create a layered or tree like layout, ideally being able to pin nodes to a level. Is there any suggestions on the correct layout to use or how to achieve it.
Does anyone know how to use the roots: ? I tried like this "roots: 'xyz'" where xyz is the id of the root node in my diagram.
Also I was trying to lock my root node like below using the position and locked attribute which did not seem to work.
{ data: { id: "XYZ", name: "XYZ", weight: 100, faveColor: "#F5A45D", faveShape: "triangle" , position: {x: 150, y: 10}, locked: true}},
If I can't get it to work my fall back will be to use graphviz to calculate the layout and position the nodes manually in code.
You can only specify elements as roots. I'll add the ability to use selector strings for 2.2.4:
https://github.com/cytoscape/cytoscape.js/issues/498
As for the layout itself, nodes are positioned based on edge direction hierarchy, so you can only guarantee the position of the specified root nodes. The rest fall underneath in the hierarchy.

Can we have nested targets in Dojo?

I have two divs nested under a parent div and I want all these to be source as well as targets for dojo.dnd.
I want to be able to add nodes to the div over which the content was dropped and also allow the user to move this in between the 3 divs.
Something like this -
http://www.upscale.utoronto.ca/test/dojo/tests/dnd/test_nested_drop_targets.html
This is I gues implemented in older version of Dojo and doesn' seem to work with 1.4
Is the support for nested targets removed? Is there any way to achieve this?
Nested sources/targets are not supported currently. In most cases you can work around this restriction by using independent sources/targets, yet positioning them as you wish with CSS.
I used a workaround for this case. I create another DIV element which positioned at the same place of the nested target with same width and height but with higher z-Index value. Then the new DIV element covers the nested target. When user is trying to drop on the nested target, he actually drops to the above new DIV element. As long as the new DIV element is not nested in the parent drop target, Dojo's dnd operation works well. I usually put the new DIV element as a child of the body element.
What you need to do is to create the new DIV in onDndStart and destroy it in onDndCancel, then everything should work well.
Dojo version 1.10 still does not support nested Dnd.
CSS positioning and overlay div's didn't work for me. But I noticed that dragging an element out of a dndContainer into a parent dndContainer doesn't trigger onMouseOverEvent for the parent.
In case someone is still using dojo and has the same problem, here is my approach to solve this:
Declare your own dndSource e.g. nestedDndSource.js
define([
"dojo/_base/declare",
"dojo/dnd/Source",
"dojo/dnd/Manager"
], function(declare,dndSource, Manager){
var Source = declare("dojo.dnd.Source", dndSource, {
parentSource: null,
onOutEvent: function(){
if(this.parentSource != undefined)
Manager.manager().overSource(this.parentSource)
Source.superclass.onOutEvent.call(this);
}
});
return Source;
})
Use that nestedDndSource for the children instead of dojos and make sure to provide the dndSource of the parent as parentSource-Parameter:
var parentDndSource = new dojoDndSource(parentNode, {..});
var childDnDSource = new nestedDndSource(childNode,{
parentSource: parentDndSource,
onDropExternal: ...
});
Working example: https://jsfiddle.net/teano87/s4pe2jjz/1/