Define (and preserve) compound node shape in Cytoscape, allowing rotation - cytoscape.js

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

Related

How to keep a node at the centre of circle layout in cytoscape.js?

I am trying to show metabolite-protein interaction using cytoscape.min.js. I am using the circle layout to view the network.
I have two types of node in the network,"prot" and "met". For the better visualization, I want to create a circular layout with the node "met" at the center. In this case, I want to keep the metabolite (here node 'a') at the center of the circle. How to do the same ?
Thanks,
Santosh
My script for cytoscape.min.js is as follows:
<script>
var cy = cytoscape({
container: document.getElementById('cy'),
elements: [
// nodes
{ data: { id: 'a', type: "met" } },
{ data: { id: 'b', type: "prot" } },
{ data: { id: 'c', type: "prot" } },
{ data: { id: 'd', type: "prot" } },
{ data: { id: 'e', type: "prot" } },
{ data: { id: 'f', type: "prot" } },
// edges
{
data: {
id: 'ab',
source: 'a',
target: 'b'
}
},
{
data: {
id: 'ae',
source: 'a',
target: 'e'
}
},
{
data: {
id: 'cd',
source: 'c',
target: 'd'
}
},
{
data: {
id: 'ef',
source: 'e',
target: 'f'
}
},
{
data: {
id: 'ac',
source: 'a',
target: 'c'
}
},
{
data: {
id: 'be',
source: 'b',
target: 'e'
}
}
],
style: [
{
selector: 'node[type="prot"]',
style: {
'shape': 'circle',
'background-color': 'red',
label: 'data(id)'
}
},
{
selector: 'node[type="met"]',
style: {
'shape': 'square',
'background-color': 'blue',
label: 'data(id)'
}
}]
});
cy.layout({
name: 'circle',
animate: true
}).run();
</script>

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

Filtering graph by connectivity to a specific node in cytoscape.js

New to Cytoscape. I have a graph with dominant main network and some smaller networks unconnected to the main one that I want to remove. Looking through the documentation I can't see an obvious solution to this. I'm guessing maybe a custom approach is required that loops through all nodes, checks their graph distance from the most central node in the main cluster, and if this distance is undefined remove that node and all others it does connect to. But keen to get a steer from others with more experience with the library. Any advice is much appreciated. I note this unanswered but related question.
Here is an example graph. Though I can't get working on jsfiddle here is working version:
<!DOCTYPE>
<html>
<head>
<title>cytoscape-dagre.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://unpkg.com/dagre#0.7.4/dist/dagre.js"></script>
<script src="cytoscape-dagre.js"></script>
<style>
#cy {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 999;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', function(){
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
boxSelectionEnabled: false,
autounselectify: true,
layout: {
name: 'dagre'
},
style: [
{
selector: 'node',
style: {
'background-color': '#11479e'
}
},
{
selector: 'edge',
style: {
'width': 4,
'target-arrow-shape': 'triangle',
'line-color': '#9dbaea',
'target-arrow-color': '#9dbaea',
'curve-style': 'bezier'
}
}
],
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: 'n4', target: 'n5' } },
{ data: { source: 'n4', target: 'n6' } },
{ data: { source: 'n6', target: 'n7' } },
{ 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' } },
]
}
});
});
</script>
</head>
<body>
<h1>cytoscape-dagre demo</h1>
<div id="cy"></div>
</body>
</html>
You can do this with the filtering methods provided in the docs, if you find some method better suited for this problem, just fiddle around with them until you get the right result. The important part here is the .union() and the .not() method. You can use these to:
traverse the graph in a controlled way, saving the important nodes on the way
and then filter the collection to fit your needs
You mentioned not being able to get the jsfiddle to work, you can test the code below in here
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",
"border-color": "black",
"border-opacity": "1",
"border-width": "10px"
}
},
{
selector: "edge",
css: {
"target-arrow-shape": "triangle"
}
}
],
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: "n4",
target: "n5"
}
},
{
data: {
source: "n4",
target: "n6"
}
},
{
data: {
source: "n6",
target: "n7"
}
},
{
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) {
// .union() takes two collections and adds both together without duplicates
var connected = event.target
connected = connected.union(event.target.predecessors())
connected = connected.union(connected.successors())
// in one line:
// event.target.union(event.target.predecessors().union(event.target.successors()))
// .not() filters out whatever is not specified in connected, e.g. every other node/edge not present in connected
var notConnected = cy.elements().not(connected)
// if you want, you can later add the saved elements again
var saved = cy.remove(notConnected)
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
float: left;
}
<html>
<head>
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
<script src="https://unpkg.com/dagre#0.7.4/dist/dagre.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-dagre#2.1.0/cytoscape-dagre.min.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>

Edge Data Format

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.

Circular Hierarchy in Cytoscape js?

Is it possible to obtain a circular hierarchy in Cytoscape js?
The breadthfirst layout gives a generic hierarchy layout, if somehow nodes could be arranged in a circrular hierarchy (with roots being in the center...), please do let me know the way out.
Thanks
Have you tried the concentric options for the breadth-first layout? By default breadth-first gives a pyramid-like layout but you can specify a circular layout.
For example:
var cy = cytoscape({
container: document.getElementById('cy'),
elements: {
nodes: [
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{ data: { id: 'c' } },
{ data: { id: 'd' } },
{ data: { id: 'e' } }
],
edges: [
{ data: { id: 'ae', 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',
circle: true,
root: 'a',
},
});
See https://jsfiddle.net/josephst18/kznos1x9/2/ for the rest of my code.