How can minimize edge crossings in using cytoscape dagre? - cytoscape.js

In the following graph:
it's obvious that by swapping l3_b and l3_c the graph all edge crossing will be removed.
The graph is fairly simple. But the dagre layout seems to "miss" the possibility of reordering those two nodes. It was my understanding that dagre will try some heuristics for minimizing the crossings between layers, at least they mention Jünger and Mutzel, "2-Layer Straightline Crossing Minimization", in dagre's recommended readings.
I understand that there is no guarantee that it will find the optimal ordering in the general case, but is there any way to "force" cytoscape's dagre to "try harder" for simple graphs like this? Or is there any other similar layout algorithm that will provide more optimal solutions (even at the expense of slower computation)?
Here is the code I've been using:
import cytoscape from 'cytoscape';
import dagre from 'cytoscape-dagre';
cytoscape.use( dagre );
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
elements: [
{data: { id: 'l1'}},
{data: { id: 'l1_a', parent: 'l1'}},
{data: { id: 'l2'}},
{data: { id: 'l2_a', parent: 'l2'}},
{data: { id: 'l2_b', parent: 'l2'}},
{data: { id: 'l3'}},
{data: { id: 'l3_a', parent: 'l3'}},
{data: { id: 'l3_b', parent: 'l3'}},
{data: { id: 'l3_c', parent: 'l3'}},
{data: { id: 'l3_d', parent: 'l3'}},
{data: { id: 'link_1', source: 'l1_a', target: 'l2_a'}},
{data: { id: 'link_2', source: 'l1_a', target: 'l2_b'}},
{data: { source: 'l1_a', target: 'l3_a'}},
{data: { source: 'l2_a', target: 'l3_b'}},
{data: { source: 'l2_a', target: 'l3_d'}},
{data: { source: 'l2_b', target: 'l3_c'}},
{data: { source: 'l2_b', target: 'l3_b'}},
],
style: [
{
selector: ':childless',
style: {
'label': 'data(id)'
}
},
{
selector: 'edge',
style: {
'width': 1,
'line-color': '#ccc',
'target-arrow-color': '#ccc',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier'
}
}
],
layout: {
name: 'dagre',
rankDir: 'LR'
}
});

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

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

I am using cytoscape set "text-opacity:0" in style but it does not work

I am using cytoscape.js version 3.7.0 to show a relation graph.The desired effect is when click one node, the other nodes and edges turn to unusable state.I do not find one attribute to achieve the goal.I am trying to another way: add click event to node, when the node is clicked, set all element "opacity":"0.2", "text-opacity":"0", then set the current node "opacity":"1", "text-opacity":"1", the opacity attribute is work, but the text-opacity is not, only scroll the mouse to zoom-in the graph, the label text on nodes and edges are invisible, scroll the mouse to zoom-out the graph the label text visible again.In my submission, when "text-opacity":"0" been setted, whether zoom or not, the label text should be invisible.
cytoscape.js version is 3.7.0.
I tried to added click event to node, when the node is clicked, set all element "opacity":"0.2", "text-opacity":"0", then set the current node "opacity":"1", "text-opacity":"1", only scroll the mouse to zoom-in the graph the "text-opacity":"0", it worked.
<!DOCTYPE html>
<html>
<head>
<title>Learning Cytoscape.js</title>
<style type="text/css">
/* cytoscape graph */
#cy {
height: 600px;
width: 1200px;
background-color: #00f9f9;
}
</style>
<script src="jquery-2.1.4.min.js"></script>
<script src="cytoscape.min.js"></script>
<script>
let highlightLineWidth = "3px";
let normalLineWidth = "1px";
$(function () {
let edgeMouseOverHandler = function (evt) {
evt.target.style('width', highlightLineWidth);
};
let edgeMouseOutHandler = function (evt) {
evt.target.style('width', normalLineWidth);
};
let nodeMouseOverHandler = function (evt) {
evt.target.connectedEdges().style('width', highlightLineWidth);
};
let nodeMouseOutHandler = function (evt) {
evt.target.connectedEdges().style('width', normalLineWidth);
};
let cy = cytoscape({
container: document.getElementById('cy'),
style: [
{
selector: 'node[label = "Person"]',
css: {
'background-color': '#6FB1FC', 'content': 'data(name)', "font-size": "5px",
"overlay-opacity": "0",
"text-valign": "center",
"text-halign": "center",
"width": "50px",
"height": "50px",
"shape": "circle"
}
},
{
selector: 'node[label = "Movie"]',
css: {
'background-color': '#F5A45D', 'content': 'data(title)', "font-size": "5px",
"text-valign": "center",
"text-halign": "center",
"width": "50px",
"height": "50px",
"shape": "circle",
"overlay-opacity": "0"
}
},
{
selector: 'edge',
css: {
'content': 'data(relationship)', 'target-arrow-shape': 'vee', "font-size": "5px",
'curve-style': 'bezier',
"line-style": "solid",
"width": normalLineWidth,
"overlay-opacity": "0",
}
}
],
elements: {
nodes: [
{data: {id: '172', name: 'Tom', label: 'Person'}, position: { x: 300, y: 300 }},
{data: {id: '173', name: 'Jack', label: 'Person'}, position: { x: 400, y: 230 }},
{data: {id: '174', name: 'Mike', label: 'Person'}, position: { x: 210, y: 350 }},
{data: {id: '175', name: 'Lucy', label: 'Person'}, position: { x: 80, y: 60 }},
{data: {id: '183', title: 'ABC', label: 'Movie'}, position: { x: 370, y: 390 }},
{data: {id: '184', title: 'EFG', label: 'Movie'}, position: { x: 200, y: 100 }},
{data: {id: '185', title: 'movie3', label: 'Movie'}, position: { x: 130, y: 165 }},
{data: {id: '186', title: 'movie4', label: 'Movie'}, position: { x: 278, y: 55 }},
{data: {id: '187', title: 'movie5', label: 'Movie'}, position: { x: 180, y: 30 }},
{data: {id: '188', title: 'movie6', label: 'Movie'}, position: { x: 90, y: 290 }},
{data: {id: '189', title: 'movie7', label: 'Movie'}, position: { x: 45, y: 150 }},
{data: {id: '190', title: 'movie8', label: 'Movie'}, position: { x: 250, y: 200 }},
{data: {id: '191', title: 'movie9', label: 'Movie'}, position: { x: 300, y: 400 }},
{data: {id: '192', title: 'movie10', label: 'Movie'}, position: { x: 500, y: 300 }},
{data: {id: '193', title: 'movie11', label: 'Movie'}, position: { x: 280, y: 120 }},
{data: {id: '194', title: 'movie112', label: 'Movie'}, position: { x: 80, y: 196 }},
],
edges: [{data: {source: '172', target: '183', relationship: 'like'}},
{data: {source: '172', target: '185', relationship: 'like'}},
{data: {source: '173', target: '187', relationship: 'like'}},
{data: {source: '174', target: '188', relationship: 'like'}},
{data: {source: '175', target: '184', relationship: 'like'}},
{data: {source: '172', target: '183', relationship: 'like'}},
{data: {source: '172', target: '192', relationship: 'like'}},
{data: {source: '172', target: '194', relationship: 'like'}},
{data: {source: '172', target: '190', relationship: 'like'}},
{data: {source: '172', target: '183', relationship: 'like'}},
]
},
layout: {
name: 'preset',
}
});
bindEventHandler();
console.log("--------------------------");
cy.on('click', function (event) {
if (event.target === cy) {
cy.elements()
.style("opacity", 1)
.style("events", "yes");
console.log('tap on background');
} else {
console.log('tap on some element');
}
});
cy.on('click', 'node', function (evt) {
console.log("======================");
console.log(evt.target.style());
let toHighlightElements = getToHighlightElements(evt);
// cy.startBatch();
cy.elements().style({"events":"no", "opacity":"0.2", "text-opacity":"0"});
console.log("+++++++++++++++++++++++");
console.log(evt.target.style());
toHighlightElements
.style("opacity", 1);
evt.target.connectedEdges().style("width", highlightLineWidth);
// cy.endBatch();
});
function getToHighlightElements(evt) {
let result = cy.collection();
let tapNode = evt.target;
result.merge(tapNode).merge(tapNode.neighborhood());
return result;
}
function getToIgnoreElements(eles) {
if (eles) {
return cy.elements().unmerge(eles);
} else {
return cy.elements();
}
}
function bindEventHandler() {
cy.on('mouseover', 'edge', edgeMouseOverHandler);
cy.on('mouseout', 'edge', edgeMouseOutHandler);
cy.on('mouseover', 'node', nodeMouseOverHandler);
cy.on('mouseout', 'node', nodeMouseOutHandler);
}
});
</script>
</head>
<body>
<div id="cy"></div>
</body>
</html>
Describe expected : when set "text-opacity":"0" the label text on element is invisible.
Actual results: after set the style, scroll the mouse to zoom-in the graph, text invisible, scroll the mouse to zoom-out the graph, the text visible again.

Cytoscape JS node thickness

I want to draw a simple graph with Nodes and Links between Nodes. The thing is that I want the thickness of the Links to be set depending on a variable, so I cannot pre-determine a CSS class for that.
The code is from the example http://js.cytoscape.org/demos/dagre-layout/
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
boxSelectionEnabled: false,
autounselectify: true,
layout: {
name: 'dagre'
},
style: [
{
selector: 'node',
style: {
'content': 'data(id)',
'text-opacity': 0.5,
'text-valign': 'center',
'text-halign': 'right',
'background-color': '#11479e'
}
},
{
selector: 'edge',
style: {
'curve-style': 'bezier',
'width': 4,
'target-arrow-shape': 'triangle',
'line-color': '#9dbaea',
'target-arrow-color': '#9dbaea'
}
}
],
elements: {
nodes: [
{ data: { id: 'n0' } },
{ data: { id: 'n1' } },
{ data: { id: 'n2' } }
],
edges: [
{ data: { source: 'n0', target: 'n1' } },
{ data: { source: 'n1', target: 'n2' } },
]
},
});
How to add this kind of feature to the { data } ?
You can always reference the data of the edges like this:
// Initialize cytoscape
cy = window.cy = cytoscape({
container: $('.cy'),
boxSelectionEnabled: false,
autounselectify: true,
layout: {
name: 'yourLayout'
},
style: [
{
selector: 'node',
style: {
'shape': 'data(faveShape)',
'content': 'data(DisplayName)',
'height': 'data(faveHeight)',
'width': 'data(faveWidth)',
'background-color': 'data(faveColor)',
'line-color': '#a8eae5',
'font-family': 'Segoe UI,Helvetica Neue,Helvetica,Arial,Verdana',
'font-size': '15px',
}
},
{
selector: 'edge',
style: {
'curve-style': 'bezier',
'width': data(myWidth),
'target-arrow-shape': 'triangle',
'line-color': '#9dbaea',
'target-arrow-color': '#9dbaea'
}
}
],
});
When you defined the style of your nodes, you used data(id) as the nodes name, so when you want to define the style of your edges, you can always get the data of the edges for their style by using the same method data(whateverYouWantToGet).
When you define the edge, you can do it like this maybe:
var x = 0; // This is your variable, alter it like you want
var i = 0;
cy.add({
data: {
id: ('edge' + (i)),
source: n0, // first node for example
target: n1,
myWidth: x
},
position: {},
group: 'edges',
removed: false,
selected: false,
selectable: true,
locked: false,
grabbed: false,
grabbable: true,
classes: 'autorotate'
});

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