Edge Data Format - cytoscape.js

I am trying to find the most efficient way to return data for a network graph which will be constructed with cytoscape js on the front end. I know that the standard way to add edges and nodes is :
// can use reference to eles later
var eles = cy.add([
{ group: 'nodes', data: { id: 'n0' }, position: { x: 100, y: 100 } },
{ group: 'nodes', data: { id: 'n1' }, position: { x: 200, y: 200 } },
{ group: 'nodes', data: { id: 'n2' }, position: { x: 300, y: 300 } },
{ group: 'edges', data: { id: 'e0', source: 'n0', target: 'n1' } },
{ group: 'edges', data: { id: 'e0', source: 'n0', target: 'n2' } }
]);
Taken from their documentation
I was wondering if there is a way to effectively do the following:
// can use reference to eles later
var eles = cy.add([
{ group: 'nodes', data: { id: 'n0' }, position: { x: 100, y: 100 } },
{ group: 'nodes', data: { id: 'n1' }, position: { x: 200, y: 200 } },
{ group: 'nodes', data: { id: 'n2' }, position: { x: 300, y: 300 } },
{ group: 'edges', data: { id: 'e0', source: 'n0', target: ['n1', 'n2'] } }
]);
This way I can return an adjacency list from the server instead of an edge/node list.

Intresting format, but unfortunately, this won't work without maybe a new extension.
The thing is, that every element of the graph has to have an id! So when you add an edge to the graph and assign multiple targets, there ought to be 2 id's for every edge.
If you are trying to optimise your cy instance, you can go over this section of the documentation instead.

Related

Define (and preserve) compound node shape in Cytoscape, allowing rotation

Is it possible to specify a shape (i.e. square, or triangle) of a compound node in Cytoscape, which will be preserved when the layout of the graph is computed/rendered? The compound nodes should be rotated freely, as needed to give a good/simple layout.
As an example, consider the graph below with three compound nodes, I - III. Compound nodes II and III could be rotated to give a simple layout. Importantly, the internal layout of compound nodes is not affected (I remains a triangle, and the other two remain linear).
Is is possible to define such a graph, and get a similar output, using Cytoscape.js?
Or maybe a slightly more complex example where all three nodes are rotated
Here's what I've tried so far:
var cy = cytoscape({
container: document.getElementById('cy'),
elements: [
// nodes
{ data: { id: 'IA', parent: 'I' } },
{ data: { id: 'IB', parent: 'I' } },
{ data: { id: 'IC', parent: 'I' } },
{ data: { id: 'I' } },
{ data: { id: 'IIA', parent: 'II' } },
{ data: { id: 'IIB', parent: 'II' } },
{ data: { id: 'IIC', parent: 'II' } },
{ data: { id: 'II' } },
{ data: { id: 'IIIA', parent: 'III' } },
{ data: { id: 'IIIB', parent: 'III' } },
{ data: { id: 'III' } },
// edges
{
data: {
id: '1',
source: 'IA',
target: 'IIA'
}
},
{
data: {
id: '2',
source: 'IC',
target: 'IIB'
}
},
{
data: {
id: '3',
source: 'IC',
target: 'IIIA'
}
},
{
data: {
id: '4',
source: 'IB',
target: 'IIIB'
}
},
],
layout: {
name: 'grid'
},
style: [
{
selector: 'node',
style: {
label: 'data(id)'
}
}]
});
cy.layout({
name: 'fcose'
}).run();
which gives this

Cytoscape - headless mode example

I am unclear on how to get the headless mode (on node.js) to layout a graph and extract
the computed (per node) positioning information.
Below is the simplest example I could think of that should have worked, but doesn't.
What am I missing?
const cytoscape = require('cytoscape');
const fcose = require('cytoscape-fcose');
const elements = [
{ data: { id: 'n0' } },
{ data: { id: 'n1' } },
{ data: { id: 'n2' } },
{ data: { id: 'n3' } },
{ data: { id: 'e1', source: 'n0', target: 'n1' } },
{ data: { id: 'e2', source: 'n2', target: 'n3' } },
]
cytoscape.use(fcose); // register extension
const cy = cytoscape()
const layout = cy.layout({
name: 'fcose',
container: null,
layout: {
boundingBox: {
x1: 0,
y1: 0,
w: 600,
h: 600
},
},
elements,
headless: true,
styleEnabled: false,
animate: false,
ready: function () {
console.log(this)
}
}).run()
console.log(cy.nodes())
A working example:
const cytoscape = require('cytoscape');
const fcose = require('cytoscape-fcose');
const elements = [
{ data: { id: 'n0' } },
{ data: { id: 'n1' } },
{ data: { id: 'n2' } },
{ data: { id: 'n3' } },
{ data: { id: 'e1', source: 'n0', target: 'n1' } },
{ data: { id: 'e2', source: 'n2', target: 'n3' } },
]
cytoscape.use(fcose); // register extension
const cy = cytoscape({
container: null,
elements,
headless: true,
styleEnabled: false,
animate: null,
})
const layout = cy.layout({
name: 'fcose',
animate: null,
}).run()
cy.nodes().map((node, id) => {
console.log({
id,
position: node.position(),
boundingbox: node.boundingbox(),
})
})
Output:
{
id: 0,
position: { x: -10.739928259168892, y: 23.05192029205518 },
boundingbox: {
x1: -12.239928259168892,
y1: 21.55192029205518,
x2: -9.239928259168892,
y2: 24.55192029205518,
w: 3,
h: 3
}
}
{
id: 1,
position: { x: -34.115851865686764, y: -17.994146351728972 },
boundingbox: {
x1: -35.615851865686764,
y1: -19.494146351728972,
x2: -32.615851865686764,
y2: -16.494146351728972,
w: 3,
h: 3
}
}
{
id: 2,
position: { x: 34.115851865686764, y: -6.375158623550128 },
boundingbox: {
x1: 32.615851865686764,
y1: -7.875158623550128,
x2: 35.615851865686764,
y2: -4.875158623550128,
w: 3,
h: 3
}
}
{
id: 3,
position: { x: -4.785486637914051, y: -23.05192029205518 },
boundingbox: {
x1: -6.285486637914051,
y1: -24.55192029205518,
x2: -3.285486637914051,
y2: -21.55192029205518,
w: 3,
h: 3
}
}

Keep edges seperated

I have created the following diagram using ELK.
Now I'm trying to use cytoscape.js with cytoscape.js-elk to generate the same routing. However I cannot get the edges the same was with ELK.
I keep getting the following:
What do I need to do to get the same edge route as ELK generates? I have tried changing the ELK options, but I don't think this is caused by elk, I think this is caused by cytoscape.
My style
const style = [ // the stylesheet for the graph
{
selector: 'node',
style: {
shape: 'rectangle',
label: 'data(id)',
'font-size': '0.5em',
}
},
{
selector: 'edge',
style: {
'width': 1,
'curve-style': 'taxi',
'line-color': '#ccc',
label: 'data(id)',
'font-size': '0.5em',
'color': 'blue',
'target-arrow-color': '#ccc',
'target-arrow-shape': 'triangle'
}
}
];
Settings
cytoscape.use(elk);
var cy = cytoscape({
container: document.getElementById('cy'),
style: style,
layout: { name: 'elk' }
});
Nodes & Edges
var nodes = cy.add([
{ group: 'nodes', data: { id: 'n1'} },
{ group: 'nodes', data: { id: 'n2'} },
{ group: 'nodes', data: { id: 'n4'} },
{ group: 'nodes', data: { id: 'n9'} },
{ group: 'nodes', data: { id: 'n10' } },
{ group: 'nodes', data: { id: 'n555'} },
{ group: 'nodes', data: { id: 'n556'} },
{ group: 'nodes', data: { id: 'n557'} },
])
var edges = cy.add([
{ group: 'edges', data: { id: 'e3', source: 'n1', target: 'n4' } },
{ group: 'edges', data: { id: 'e19', source: 'n1', target: 'n9' } },
{ group: 'edges', data: { id: 'e12', source: 'n2', target: 'n1' } },
{ group: 'edges', data: { id: 'e842', source: 'n2', target: 'n9' } },
{ group: 'edges', data: { id: 'e15', source: 'n2', target: 'n10' } },
{ group: 'edges', data: { id: 'e10', source: 'n4', target: 'n2' } },
{ group: 'edges', data: { id: 'e908', source: 'n4', target: 'n4' } },
{ group: 'edges', data: { id: 'e843', source: 'n4', target: 'n9' } },
{ group: 'edges', data: { id: 'e11', source: 'n4', target: 'n10' } },
{ group: 'edges', data: { id: 'e905', source: 'n4', target: 'n555' } },
{ group: 'edges', data: { id: 'e862', source: 'n4', target: 'n557' } },
{ group: 'edges', data: { id: 'e802', source: 'n9', target: 'n1' } },
{ group: 'edges', data: { id: 'e7', source: 'n10', target: 'n2' } },
{ group: 'edges', data: { id: 'e8', source: 'n10', target: 'n4' } },
]);
Options
var options = {
name: "elk",
nodeDimensionsIncludeLabels: true, // Boolean which changes whether label dimensions are included when calculating node dimensions
fit: true, // Whether to fit
padding: 20, // Padding on fit
animate: false, // Whether to transition the node positions
animateFilter: function (node, i) { return true; }, // Whether to animate specific nodes when animation is on; non-animated nodes immediately go to their final positions
animationDuration: 500, // Duration of animation in ms if enabled
animationEasing: undefined, // Easing of animation if enabled
transform: function (node, pos) { return pos; }, // A function that applies a transform to the final node position
ready: undefined, // Callback on layoutready
stop: undefined, // Callback on layoutstop
elk: {
'algorithm': 'layered',
'layered.mergeEdges': 'false',
'layered.mergeHierarchyEdges': 'false',
'crossingMinimization.semiInteractive': true,
'nodePlacement.strategy': 'NETWORK_SIMPLEX',
'layered.wrapping.additionalEdgeSpacing': 50,
'spacing.nodeNode': 50,
'spacing.nodeNodeBetweenLayers': 25,
'spacing.edgeNode': 25,
'spacing.edgeNodeBetweenLayers': 20,
'spacing.edgeEdge': 20,
'spacing.edgeEdgeBetweenLayers': 15,
// All options are available at http://www.eclipse.org/elk/reference.html
// 'org.eclipse.elk.' can be dropped from the Identifier
// Or look at demo-demo.js for an example.
// Enums use the name of the enum e.g.
// 'searchOrder': 'DFS'
//
// The main field to set is `algorithm`, which controls which particular
// layout algorithm is used.
},
priority: function (edge) { return null; }, // Edges with a non-nil value are skipped when geedy edge cycle breaking is enabled
};
cy.layout(options).run();
I hit the same problem. Another you will encounter is that the lines will go through the nodes, there is no effort to route around them. For me this has rendered taxi useless, even though I much prefer it's appearance. I am not aware of any line drawing algorithm in cytoscape that fixes either of these issues.
My reluctant conclusion is that I need to switch platforms, and transfer entirely to Sprotty and ELK (which was probably used to draw your reference graph).
https://rtsys.informatik.uni-kiel.de/elklive/examples.html?e=general%2FmixingDirection
https://www.typefox.io/blog/sprotty-a-web-based-diagramming-framework
Examples with Sprotty show lines actually routing around nodes, and junction points on nodes to show where overlapping lines actually join. Additionally it uses SVGs rather than a raster-render system. This may result in a crisper font, but also has the advantage of searchable text in your diagram (if you care about that).
I have yet to take the plunge on yet another framework, but it looks good.
I am also facing similar kind of problem where my edges are overlapping to each other. To overcome this problem you can use curve-style: straight;.
Edges type demo

With cytoscape.js how to automatically expand the height of the container of a graph?

Is it possible to automatically expand the height of the container of a graph, according to the size of the graph?
I want to display lots of graphs of different sizes on a web page, without having to specify the height of each DOM container.
Does it make sense?
Answer:
It is not the best solution to change anything regarding the container of cytoscape.
Alternative for you:
It is far better to work with the padding of the graph in order to display the graph in a dynamic/suitable way. The easiest way to do that, is to add your nodes and run your layout and then, when the layout is loaded, you can call cy.fit(cy.elements(), yourPadding).
In cytoscape, padding is handled exactly the same way as in css, so you just calculate (based on the number of nodes maybe?) the appropriate padding for your graph and fit it to the viewport.
Additionally, you can always wrap things up with a slick ease-in animation to top things off.
Code:
Snippet works when you click on edit the above snippet and run it in the snippet editor, here the function does not work due to the size limitation and I can't be bothered to adjust it without seeing the outcome in the editor.
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
boxSelectionEnabled: false,
autounselectify: true,
style: [
{
selector: "node",
css: {
content: "data(id)",
"text-valign": "bottom",
"text-halign": "center",
height: "60px",
width: "60px",
"border-color": "black",
"border-opacity": "1",
"background-image":
"https://farm8.staticflickr.com/7272/7633179468_3e19e45a0c_b.jpg"
}
},
{
selector: "edge",
css: {
"target-arrow-shape": "triangle"
}
},
{
selector: ":selected",
css: {
"background-color": "black",
"line-color": "black",
"target-arrow-color": "black",
"source-arrow-color": "black"
}
}
],
elements: {
nodes: [
{ data: { id: "n0" } },
{ data: { id: "n1" } },
{ data: { id: "n2" } },
{ data: { id: "n3" } },
{ data: { id: "n4" } },
{ data: { id: "n5" } },
{ data: { id: "n6" } },
{ data: { id: "n7" } },
{ data: { id: "n8" } },
{ data: { id: "n9" } },
{ data: { id: "n10" } },
{ data: { id: "n11" } },
{ data: { id: "n12" } },
{ data: { id: "n13" } },
{ data: { id: "n14" } },
{ data: { id: "n15" } },
{ data: { id: "n16" } }
],
edges: [
{ data: { source: "n0", target: "n1" } },
{ data: { source: "n1", target: "n2" } },
{ data: { source: "n1", target: "n3" } },
{ data: { source: "n2", target: "n7" } },
{ data: { source: "n2", target: "n11" } },
{ data: { source: "n2", target: "n16" } },
{ data: { source: "n3", target: "n4" } },
{ data: { source: "n3", target: "n16" } },
{ data: { source: "n4", target: "n5" } },
{ data: { source: "n4", target: "n6" } },
{ data: { source: "n6", target: "n8" } },
{ data: { source: "n8", target: "n9" } },
{ data: { source: "n8", target: "n10" } },
{ data: { source: "n11", target: "n12" } },
{ data: { source: "n12", target: "n13" } },
{ data: { source: "n13", target: "n14" } },
{ data: { source: "n13", target: "n15" } }
]
},
layout: {
name: "dagre",
padding: 5
}
}));
cy.unbind("click");
cy.bind("click", "node", function(event) { // just for demonstration purposes here
var coll = cy.$(event.target).successors(); // get all outgoing nodes
coll = coll.add(event.target); // add their source
var removed = cy.remove(cy.elements().not(coll)); // remove all other elements
var len = cy.nodes().length;
var pad = (len < 10 ? (len < 5 ? (len < 3 ? (len < 2 ? 150 : 100 ) : 75 ) : 50 ) : 25); // custom padding function here
cy.animate({
fit: {
eles: cy.elements(),
padding: pad
}
}, {
duration: 500,
easing: 'ease-in'
});
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
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>
<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>

Cytoscape.js setting node colour and other details

I am using Cytoscape.js to create a basic diagram and am curious as to how to set the colour and particular shape of each node. Here is my existing code:
layout: {
name: 'preset'
},
ready: function(){
window.cy = this;
cy.add([
{ group: "nodes", data: { id: "n0" }, position: { x: 100, y: 100 } },
{ group: "nodes", data: { id: "n1", shape: 'rectangle' }, position: { x: 200, y: 200 } },
{ group: "edges", data: { id: "e0", source: "n0", target: "n1" } }
]);
}
});
});
I was also curious as to how to animate the diagram and if there were any similar examples out there on that.
cheers,
...
style: [ // the stylesheet for the graph
{
selector: 'node',
style: {
'background-color': '#666',
'shape': 'rectangle',
}
},
],
...
background-color is probably the attribute you are looking for, this is set in the style option. More info can be in http://js.cytoscape.org/#getting-started/specifying-basic-options.
As for the shape, cytoscape has a few predefined shapes such as rectangle, circle, etc. You can even make your own, or have an svg as the node's image. Options for nodes are listed in http://js.cytoscape.org/#style/node-body.
For animation I would look at some of the demos uploaded and go from there. This one is particularly interesting js.cytoscape.org/demos/aedff159b0df05ccfaa5