Direction aware neighborhood - cytoscape.js

Are there ways to get neighborhoods of node but direction aware?
For example, I have nodes:
[
{ 'id': 'a' },
{ 'id': 'b' },
{ 'id': 'c' }
]
and edges:
[
{ 'id': 'ab', 'source': 'a', 'destination': 'b' },
{ 'id': 'ac', 'source': 'a', 'destination': 'c' },
]
And now, when I call nodeB.neighborhoods() I have a nodeA as result.
But nodeB is not connected to nodeA (only nodeA connected to nodeB).
Maybe cytoscape.js already has function, that I can use for get all connected nodes to specific node?

You can do a selector query on the edges, which have a source and target property
Please note that this snippet doesn't have console.log() for a reason:
The collection cytoscape returns is a pretty large array of information, so the stackoverflow console is not capable to show that, it tries to show it as a formatted string or something and then freezes on the sheer amount of lines it has to process...
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': '60px',
'border-color': 'black',
'border-opacity': '1',
'border-width': '10px'
}
},
{
selector: 'edge',
css: {
'target-arrow-shape': 'triangle'
}
}
],
elements: {
nodes: [{
data: {
id: 'a'
}
},
{
data: {
id: 'b'
}
},
{
data: {
id: 'c'
}
}
],
edges: [{
data: {
source: 'a',
target: 'b'
}
},
{
data: {
source: 'a',
target: 'c'
}
}
]
},
layout: {
name: 'concentric'
}
});
// First option: getting the edges with the sourceId "a" and then all targtes of these edges
var targets = cy.edges('[source = "a"]').targets();
// Second option: getting the node with the id "#a" and then this nodes outgoers (all outgoing edges and their target node). After that, you can either get all edges with .edges() or all nodes with .nodes()
var alternative = cy.$('#a').outgoers().nodes();
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 75%;
position: absolute;
left: 0;
top: 0;
float: left;
}
<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>
<script src="https://unpkg.com/jquery#3.3.1/dist/jquery.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>

Related

Cytoscape edgehandles ghost preview shows "undefined" when dragging from one node to another

When using edgehandles, the cytoscape extension, to create an edge between two nodes like this, I see "undefined" at the edge of the arrow. Is there some attribute I can set to get rid of this? Either in the inputs or in the style page with a selector?
For reference, I'm using
var eh = cy.edgehandles({defaults}) and eh.enableDrawMode() with the defaults defined in the edgehandles github.
Thanks!
The code from the edge-handles demo works fine in my snippet, you can use that as a starting point:
document.addEventListener('DOMContentLoaded', function() {
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
layout: {
name: 'grid',
rows: 2,
cols: 2
},
style: [{
selector: 'node[name]',
style: {
'content': 'data(name)'
}
},
{
selector: 'edge',
style: {
'curve-style': 'bezier',
'target-arrow-shape': 'triangle'
}
},
{
selector: '.eh-handle',
style: {
'background-color': 'red',
'width': 12,
'height': 12,
'shape': 'ellipse',
'overlay-opacity': 0,
'border-width': 12, // makes the handle easier to hit
'border-opacity': 0
}
},
{
selector: '.eh-hover',
style: {
'background-color': 'red'
}
},
{
selector: '.eh-source',
style: {
'border-width': 2,
'border-color': 'red'
}
},
{
selector: '.eh-target',
style: {
'border-width': 2,
'border-color': 'red'
}
},
{
selector: '.eh-preview, .eh-ghost-edge',
style: {
'background-color': 'red',
'line-color': 'red',
'target-arrow-color': 'red',
'source-arrow-color': 'red'
}
},
{
selector: '.eh-ghost-edge.eh-preview-active',
style: {
'opacity': 0
}
}
],
elements: {
nodes: [{
data: {
id: 'j',
name: 'Jerry'
}
},
{
data: {
id: 'e',
name: 'Elaine'
}
},
{
data: {
id: 'k',
name: 'Kramer'
}
},
{
data: {
id: 'g',
name: 'George'
}
}
],
edges: [{
data: {
source: 'j',
target: 'e'
}
},
{
data: {
source: 'j',
target: 'k'
}
},
{
data: {
source: 'j',
target: 'g'
}
},
{
data: {
source: 'e',
target: 'j'
}
},
{
data: {
source: 'e',
target: 'k'
}
},
{
data: {
source: 'k',
target: 'j'
}
},
{
data: {
source: 'k',
target: 'e'
}
},
{
data: {
source: 'k',
target: 'g'
}
},
{
data: {
source: 'g',
target: 'j'
}
}
]
}
});
var eh = cy.edgehandles();
document.querySelector('#draw-on').addEventListener('click', function() {
eh.enableDrawMode();
});
document.querySelector('#draw-off').addEventListener('click', function() {
eh.disableDrawMode();
});
document.querySelector('#start').addEventListener('click', function() {
eh.start(cy.$('node:selected'));
});
});
body {
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
font-size: 14px;
}
#cy {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 999;
}
h1 {
opacity: 0.5;
font-size: 1em;
font-weight: bold;
}
#buttons {
position: absolute;
right: 0;
bottom: 0;
z-index: 99999;
}
<html>
<head>
<title>cytoscape-edgehandles.js demo</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-edgehandles#3.6.0/cytoscape-edgehandles.min.js"></script>
</head>
<body>
<h1>cytoscape-edgehandles demo</h1>
<div id="cy"></div>
<div id="buttons">
<button id="start">Start on selected</button>
<button id="draw-on">Draw mode on</button>
<button id="draw-off">Draw mode off</button>
</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, 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.

sequential edge animation in cystoscape

I want to make an animation for each edge that I'm visiting sequentially, but when I try to do that the animation is done for all the edges at the same time, how can I fix this?
cy.edges().forEach(e => {
let src = parseInt(e.source().data('id'))
let trg = parseInt(e.target().data('id'))
let w = parseInt(e.style('label'))
e.animation({
'style': {'line-color': '#FF0000'}},
{'duration': '2000'}).play()
}
As you can see here, cytoscape provided this demo for your problem:
var cy = cytoscape({
container: document.getElementById('cy'),
boxSelectionEnabled: false,
autounselectify: true,
style: cytoscape.stylesheet()
.selector('node')
.style({
'content': 'data(id)'
})
.selector('edge')
.style({
'curve-style': 'bezier',
'target-arrow-shape': 'triangle',
'width': 4,
'line-color': '#ddd',
'target-arrow-color': '#ddd'
})
.selector('.highlighted')
.style({
'background-color': '#61bffc',
'line-color': '#61bffc',
'target-arrow-color': '#61bffc',
'transition-property': 'background-color, line-color, target-arrow-color',
'transition-duration': '0.5s'
}),
elements: {
nodes: [{
data: {
id: 'a'
}
},
{
data: {
id: 'b'
}
},
{
data: {
id: 'c'
}
},
{
data: {
id: 'd'
}
},
{
data: {
id: 'e'
}
}
],
edges: [{
data: {
id: 'a"e',
weight: 1,
source: 'a',
target: 'e'
}
},
{
data: {
id: 'ab',
weight: 3,
source: 'a',
target: 'b'
}
},
{
data: {
id: 'be',
weight: 4,
source: 'b',
target: 'e'
}
},
{
data: {
id: 'bc',
weight: 5,
source: 'b',
target: 'c'
}
},
{
data: {
id: 'ce',
weight: 6,
source: 'c',
target: 'e'
}
},
{
data: {
id: 'cd',
weight: 2,
source: 'c',
target: 'd'
}
},
{
data: {
id: 'de',
weight: 7,
source: 'd',
target: 'e'
}
}
]
},
layout: {
name: 'breadthfirst',
directed: true,
roots: '#a',
padding: 10
}
});
var bfs = cy.elements().bfs('#a', function() {}, true);
var i = 0;
var highlightNextEle = function() {
if (i < bfs.path.length) {
bfs.path[i].addClass('highlighted');
i++;
setTimeout(highlightNextEle, 1000);
}
};
// kick off first highlight
highlightNextEle();
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>
</head>
<body>
<div id="cy"></div>
</body>
</html>
So instead of going for the bfs path, you can define your own path and use the higlighting function of the demo.
Good luck

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>