Cytoscape.js how to layout connected nodes - cytoscape.js

I first create a graph with 100's of connected nodes. After all the nodes have been added I call
cy.layout({name: "dagre"});
Next, I'm creating 5 or so additional connected nodes I call layout on the nodes added but it doesn't lay them out as expected. Instead of being more like a tree all the nodes are in a straight line.
It looks like this:
var collection = cy.collection();
collection.merge(eles);
...
// I merge in another 5 newly created nodes.
// Next I call layout
collection.layout({
name: "dagre", fit: false,
boundingBox: {
x1: mousex - width / 2, y1: mousey - height / 2, x2: mousex + width, y2: mousey + height
},
nodeSep: 30
}).run();
But I expect it to look like the image below.
In order to get it to look like the above, I call layout shown below.
cy.layout({name: "dagre"});
I've looked through all the options for a dagre layout and can't find anything to make it create the tree.

Edit: The dagre layout needs nodes and edges to calculate the right positions for the nodes, the way you use it, dagre thinks you give it 5 seperate nodes, which explains your wrong layout. The mistake lies here:
collection.merge(eles); // here you should add all relevant nodes and edges
End
I have an example for you --->here<---, just copy that and add your real data:
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
boxSelectionEnabled: false,
autounselectify: true,
style: [
{
selector: "node",
css: {
content: "data(id)",
"text-valign": "center",
"text-halign": "center",
height: "60px",
width: "100px",
shape: "rectangle",
"background-color": "data(faveColor)"
}
},
{
selector: "edge",
css: {
"curve-style": "bezier",
"control-point-step-size": 40,
"target-arrow-shape": "triangle"
}
}
],
elements: {
nodes: [
{ data: { id: "Top", faveColor: "#2763c4" } },
{ data: { id: "yes", faveColor: "#37a32d" } },
{ data: { id: "no", faveColor: "#2763c4" } },
{ data: { id: "Third", faveColor: "#2763c4" } },
{ data: { id: "Fourth", faveColor: "#56a9f7" } }
],
edges: [
{ data: { source: "Top", target: "yes" } },
{ data: { source: "Top", target: "no" } },
{ data: { source: "no", target: "Third" } },
{ data: { source: "Third", target: "Fourth" } },
{ data: { source: "Fourth", target: "Third" } }
]
},
layout: {
name: "random"
}
}));
cy.ready(function () {
setTimeout(function () {
cy.nodes().layout({ name: 'dagre' }).run(); // this is what you do!!
setTimeout(function () {
cy.elements().layout({ name: 'dagre' }).run(); // this is what you should do!!
},5000);
}, 5000);
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
left: 0;
top: 0;
float: left;
position: absolute;
}
<html>
<head>
<meta charset=utf-8 />
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.2.17/cytoscape.min.js">
</script>
<!-- cyposcape dagre -->
<script src="https://unpkg.com/dagre#0.7.4/dist/dagre.js"></script>
<script src="https://cdn.rawgit.com/cytoscape/cytoscape.js-dagre/1.5.0/cytoscape-dagre.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>

Related

How to show only first depth in cytoscape js?

Is it possible to show a root element and its neighbors only without going deeper? I need this because I want that user expands the tree by clicking on nodes, while initially, the user can see only the first level.
I'm going to use cytoscape.js-view-utilities plugin for showing/hiding elements.
I know that there is neighborhood() method, but cannot figure out how to do this
As you said, you can do it by using view-utilities extension. First you should hide all nodes except the root and its neighbors. Then with a tap event on nodes, you can show neighbors of clicked node. In the below example, if you click on node n1 and n2, their neighbors are shown. If you want to differentiate nodes with hidden neighbors, you can assign them a new class and style, and define your tap event on that class.
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
layout: {name: 'breadthfirst', directed: true},
style: [{
selector: 'node',
css: {
'label': 'data(id)'
}
}
],
elements: {
nodes: [{
data: {
id: 'n0'
}
},
{
data: {
id: 'n1'
}
},
{
data: {
id: 'n2'
}
},
{
data: {
id: 'n3'
}
},
{
data: {
id: 'n4'
}
},
{
data: {
id: 'n5'
}
},
{
data: {
id: 'n6'
}
}
],
edges: [{
data: {
id: 'n0n1',
source: 'n0',
target: 'n1',
}
},
{
data: {
id: 'n0n2',
source: 'n0',
target: 'n2'
}
},
{
data: {
id: 'n1n3',
source: 'n1',
target: 'n3'
}
},
{
data: {
id: 'n1n4',
source: 'n1',
target: 'n4'
}
},
{
data: {
id: 'n2n5',
source: 'n2',
target: 'n5'
}
},
{
data: {
id: 'n2n6',
source: 'n2',
target: 'n6'
}
}
]
}
});
var instance = cy.viewUtilities();
instance.hide(cy.elements().difference(cy.getElementById('n0').closedNeighborhood()));
cy.on('tap', 'node', function(event){
instance.show(event.target.neighborhood());
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#button {
z-index = 1000;
}
#cy {
height: 95%;
width: 95%;
left: 0;
top: 50;
z-index = 900;
position: absolute;
}
<html>
<head>
<meta charset=utf-8 />
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js">
</script>
<script src="https://unpkg.com/cytoscape-view-utilities/cytoscape-view-utilities.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>

"Setting a `style` bypass at element creation is deprecated" when specifying style along with data for a node

Using cytoscape.js, I am setting the style of a node while defining it.
Shortened example below:
window.addEventListener('DOMContentLoaded', function(){
var cy = window.cy = cytoscape({
elements: {
nodes: [
{ data: { id: 'a', name: 'apple' }, style: { 'background-color': 'darkgreen' } },
]
}
});
});
(I have a default style for a node that is a different color)
This works fine, but when I run my code, the browser console shows the following warning:
Setting a `style` bypass at element creation is deprecated
What does it mean and what is the correct (non-deprecated) way to set it?
Thank you in advance!
The correct way of doing this would be to use the cytoscape stylesheet, as can be seen in every example in the docs:
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
boxSelectionEnabled: false,
autounselectify: true,
style: [{
selector: "node",
css: {
"label": "data(id)",
"text-valign": "center",
"text-halign": "center",
"background-color": "data(color)"
}
},
{
selector: "edge",
css: {
"line-fill": "radial-gradient",
"line-gradient-stop-colors": "red green blue",
"line-gradient-stop-positions": "25 50 75"
}
}
],
elements: {
nodes: [{
data: {
id: "a",
color: "#2763c4"
}
},
{
data: {
id: "b",
color: "#37a32d"
}
},
{
data: {
id: "c",
color: "#37a32d"
}
}
],
edges: [{
data: {
source: "a",
target: "b"
}
},
{
data: {
source: "a",
target: "c"
}
}
]
},
layout: {
name: "dagre"
}
}));
cy.ready(function() {
cy.dblclick();
});
var nid = 0;
cy.bind('dblclick', function(evt) {
console.log('dblclick');
cy.add({
group: 'nodes',
data: {
id: nid,
faveColor: 'red'
},
position: {
x: evt.x,
y: evt.y
}
});
nid++;
});
cy.bind('click', 'node', function(evt) {
console.log('node clicked: ', evt.target.id());
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
float: right;
position: absolute;
}
<html>
<head>
<meta charset=utf-8 />
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<script src="https://unpkg.com/cytoscape#3.3.0/dist/cytoscape.min.js">
</script>
<!-- cyposcape dagre -->
<script src="https://unpkg.com/dagre#0.7.4/dist/dagre.js"></script>
<script src="https://cdn.rawgit.com/cytoscape/cytoscape.js-dagre/1.5.0/cytoscape-dagre.js"></script>
<script src="https://unpkg.com/cytoscape-dblclick/dist/index.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>

Cytoscape js - Call a function whenever a node is clicked

I initialized the cytoscape like this:
var cy = cytoscape({
container: document.getElementById('cy'),
elements: [
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{ data: { id: 'c' } },
{
data: {
id: 'ab',
source: 'a',
target: 'b'
}
},
{
data: {
id: 'ac',
source: 'a',
target: 'c'
}
}
]
});
Then I added a function which adds a new node whenever the user double clicks on the viewport.
var nid = 1;
document.getElementById("cy").ondblclick = function(e) {
cy.add({ data: { id: nid }, renderedPosition: { x: e.x, y: e.y } });
nid++;
};
Then I wrote this function which should be called whenever user clicks a node. It works whenever user clicks on a node which I added manually when initializing the cytoscape but the problem is its not working for the nodes which user added by double clicking.
cy.$('node').on('click', function (e) {
console.log('node clicked: ', e.target.id());
});
Any idea what am I doing wrong?
I have a working version of your code here:
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
boxSelectionEnabled: false,
autounselectify: true,
style: [{
selector: "node",
css: {
"label": "data(id)",
"text-valign": "center",
"text-halign": "center",
"background-color": "data(faveColor)"
}
},
{
selector: "edge",
css: {
"curve-style": "bezier",
"control-point-step-size": 40,
"target-arrow-shape": "triangle"
}
}
],
elements: {
nodes: [{
data: {
id: "a",
faveColor: "#2763c4"
}
},
{
data: {
id: "b",
faveColor: "#37a32d"
}
},
{
data: {
id: "c",
faveColor: "#37a32d"
}
}
],
edges: [{
data: {
source: "a",
target: "b"
}
},
{
data: {
source: "a",
target: "c"
}
}
]
},
layout: {
name: "dagre"
}
}));
cy.ready(function() {
cy.dblclick();
});
var nid = 0;
cy.bind('dblclick', function(evt) {
cy.add({
group: 'nodes',
data: {
id: nid,
faveColor: 'red'
},
position: {
x: evt.x,
y: evt.y
}
});
nid++;
});
cy.bind('click', 'node', function(evt) {
console.log('node clicked: ', evt.target.id());
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
float: right;
position: absolute;
}
<html>
<head>
<meta charset=utf-8 />
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<script src="https://unpkg.com/cytoscape#3.3.0/dist/cytoscape.min.js">
</script>
<!-- cyposcape dagre -->
<script src="https://unpkg.com/dagre#0.7.4/dist/dagre.js"></script>
<script src="https://cdn.rawgit.com/cytoscape/cytoscape.js-dagre/1.5.0/cytoscape-dagre.js"></script>
<script src="https://unpkg.com/cytoscape-dblclick/dist/index.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>
I used the cy.on()/cy.bind() method, that seems to work with newly added nodes :)

Cytoscape.js, display round shape on top of the edge

I have a cytoscape.js model, where edges between nodes represent different relations.
I want to have possibility to display something like round shape or button in the middle of the edge, where user could click and get popup to change the relationship type.
So far I see in base package there is no option to display round shape.
I am using dagre layout, and this is my current edge configuration:
{
selector: "edge",
style: {
width: 1,
"font-size": "20px",
//opacity: "0.5",
label: "data(type)",
color: function(ele) {
return getEdgeColor(ele.data("type"));
},
"line-color": function(ele) {
return getEdgeColor(ele.data("type"));
},
"target-arrow-color": function(ele) {
return getEdgeColor(ele.data("type"));
},
"curve-style": "straight",
"target-arrow-shape": "triangle"
}
},
Could you recommend me any easy solution?
I have a workaround for this (the editation works on the whole edge, the dot is just for the looks:
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
style: [{
selector: 'node',
css: {
'content': 'data(id)',
'text-valign': 'center',
'text-halign': 'center',
'height': '60px',
'width': '60px'
}
},
{
selector: 'edge',
css: {
'font-size': "40px",
'label': "\u2022",
'curve-style': 'bezier',
'target-arrow-shape': 'data(arrow)'
}
}
],
elements: {
nodes: [{
data: {
id: 'n0'
}
},
{
data: {
id: 'n1'
}
},
{
data: {
id: 'n2'
}
},
{
data: {
id: 'n3'
}
}
],
edges: [{
data: {
source: 'n0',
target: 'n1',
arrow: 'triangle'
}
},
{
data: {
source: 'n1',
target: 'n2',
arrow: 'triangle'
}
},
{
data: {
source: 'n1',
target: 'n3',
arrow: 'triangle'
}
},
]
},
layout: {
name: 'concentric',
minNodeSpacing: 140,
}
});
cy.cxtmenu({
selector: 'edge',
menuRadius: 90,
commands: [{
content: 'Direction',
select: function(edge) {
edge.move({
source: edge.target().id(),
target: edge.source().id()
});
}
}]
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
left: 0;
top: 0;
float: left;
position: absolute;
}
.cxtmenu-disabled {
opacity: 0.333;
}
<html>
<head>
<meta charset=utf-8 />
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<script src="https://cdn.jsdelivr.net/npm/cytoscape#3.10.1/dist/cytoscape.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-cxtmenu#3.1.1/cytoscape-cxtmenu.min.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>
I use the ctx-menu extension here (right click on edge to open menu) and the dot is just an unicode character.

How can I find the positions of nodes in Dash/Cytoscape after the user moved them?

I'm using Cytoscape in Plotly/Dash and I'm creating a simple graph. I then move a few nodes around to various positions. I am then trying to read the these positions in a Dash callback - I've tried:
State("cytoscape-graph", "stylesheets")
State("cytoscape-graph", "layout")
State("cytoscape-graph", "elements")
More or less anything I could find in the docs here
Example nodes:
{'data': {'id': 'id0', 'label': 'Node 0'}, 'position': {'x': 50, 'y': 150}},
{'data': {'id': 'node1', 'label': 'Node 1'}, 'position': {'x': 200, 'y': 100}},
{'data': {'id': 'node2', 'label': 'Node 2'}, 'position': {'x': 200, 'y': 200}},
{'data': {'source': 'node0', 'target': 'node1', 'id': '4300352b-9533-4fef-90ec-7a5cbff1f8c4'}},
{'data': {'source': 'node1', 'target': 'node2', 'id': '33d4c841-bcfc-4a07-ac56-90d36982601a'}},
}
The above either return None, anything else that doesn't contain relevant information, or the initial node positions. I tried looking at the rendered source code but I can't seem to find information there; only a canvas element. I'd appreciate pointers even for the Javascript backend.
EDIT:
It seems that Dash does not allow direct access to ...elements().jsons() so the answer to the question is that it's probably not possible to do purely with Dash. You will probably have to write a callback for every time a node is clicked to run this in javascript and output it to some hidden div, and then read it in Dash.
~ Credit goes to OP for answering this question
This is, as pointed out in this section of the documentation, easily achievable. The method cy.elements()/nodes()/edges().jsons() gets an array of the plain JavaScript object representation of all specified elements in the collection.
var json;
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
boxSelectionEnabled: false,
autounselectify: true,
style: [{
selector: "node",
css: {
content: "data(id)",
"text-valign": "center",
"text-halign": "center",
height: "60px",
width: "100px",
shape: "rectangle",
"background-color": "data(faveColor)"
}
},
{
selector: "edge",
css: {
"curve-style": "bezier",
"control-point-step-size": 40,
"target-arrow-shape": "triangle"
}
}
],
elements: {
nodes: [{
data: {
id: "Top",
faveColor: "#2763c4"
}
},
{
data: {
id: "yes",
faveColor: "#37a32d"
}
},
{
data: {
id: "no",
faveColor: "#2763c4"
}
},
{
data: {
id: "Third",
faveColor: "#2763c4"
}
},
{
data: {
id: "Fourth",
faveColor: "#56a9f7"
}
}
],
edges: [{
data: {
source: "Top",
target: "yes"
}
},
{
data: {
source: "Top",
target: "no"
}
},
{
data: {
source: "no",
target: "Third"
}
},
{
data: {
source: "Third",
target: "Fourth"
}
},
{
data: {
source: "Fourth",
target: "Third"
}
}
]
},
layout: {
name: "dagre"
}
}));
cy.ready(function() {
console.log('Nodes:', cy.nodes().jsons());
console.log('Edges:', cy.edges().jsons());
console.log('Elements:', cy.elements().jsons());
});
document.getElementById('save').addEventListener('click', function() {
json = cy.json();
});
document.getElementById('load').addEventListener('click', function() {
cy.json(json);
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 85%;
width: 100%;
float: right;
position: absolute;
}
<html>
<head>
<meta charset=utf-8 />
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<script src="https://unpkg.com/cytoscape#3.3.0/dist/cytoscape.min.js">
</script>
<!-- cyposcape dagre -->
<script src="https://unpkg.com/dagre#0.7.4/dist/dagre.js"></script>
<script src="https://cdn.rawgit.com/cytoscape/cytoscape.js-dagre/1.5.0/cytoscape-dagre.js"></script>
</head>
<body>
<b id="save" style="cursor: pointer; color: darksalmon">Save graph</b> / <b id="load" style="cursor: pointer; color: darkmagenta">Load graph</b>
<div id="cy"></div>
</body>
</html>