Cytoscape JS node thickness - cytoscape.js

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'
});

Related

In Cytoscape.js, how do I select the set of visible nodes that have no visible edges?

After hiding a set of nodes by shape
cy.elements('[shape="ellipse"]').addClass('hidden');
I am left with a few nodes that have no remaining visible edges (incoming or outgoing), which I would also like to hide. But after a considerable time trying to find invisible roots and leaves and union'ing and differenc'ing them, I simply can't see what must be a simple solution.
Any help greatly appreciated!
Your approach is good, it just needs some adjustments for your desired result. I'd add a filter for every non ellipse node and remove the nodes with no visible connected edges. I use the function .hidden() for that, it returns true if every edge is hidden and false if there is at least one visible edge:
cy.nodes('[shape="ellipse"]').addClass("hidden");
cy.nodes('[shape!="ellipse"]').each(function(node) {
if (node.connectedEdges().hidden()) {
node.addClass("hidden");
}
});
This works because of the style I added. Nodes with a display value of none hide their edges automatically, so there is no need to hide edges manually:
{
selector: '.hidden',
css: {
'display': 'none'
}
}
I tried this out in this dagre graph within a click event listener. I also changed the addClass to toggleClass, you can remove and add the classes with repeated clicks that way:
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',
'shape': 'data(shape)'
}
},
{
selector: '$node > node',
css: {
'padding-top': '10px',
'padding-left': '10px',
'padding-bottom': '10px',
'padding-right': '10px',
'text-valign': 'top',
'text-halign': 'center',
'background-color': '#bbb'
}
},
{
selector: 'edge',
css: {
'target-arrow-shape': 'triangle'
}
},
{
selector: ':selected',
css: {
'background-color': 'black',
'line-color': 'black',
'target-arrow-color': 'black',
'source-arrow-color': 'black'
}
},
{
selector: '.hidden',
css: {
'display': 'none'
}
}
],
elements: {
nodes: [{
data: {
id: 'n0',
shape: 'ellipse'
}
},
{
data: {
id: 'n1',
shape: 'ellipse'
}
},
{
data: {
id: 'n2',
shape: 'ellipse'
}
},
{
data: {
id: 'n3',
shape: 'ellipse'
}
},
{
data: {
id: 'n4',
shape: 'ellipse'
}
},
{
data: {
id: 'n5',
shape: 'ellipse'
}
},
{
data: {
id: 'n6',
shape: 'rectangle'
}
},
{
data: {
id: 'n7',
shape: 'ellipse'
}
},
{
data: {
id: 'n8',
shape: 'rectangle'
}
},
{
data: {
id: 'n9',
shape: 'ellipse'
}
},
{
data: {
id: 'n10',
shape: 'ellipse'
}
},
{
data: {
id: 'n11',
shape: 'ellipse'
}
},
{
data: {
id: 'n12',
shape: 'ellipse'
}
},
{
data: {
id: 'n13',
shape: 'rectangle'
}
},
{
data: {
id: 'n14',
shape: 'ellipse'
}
},
{
data: {
id: 'n15',
shape: 'rectangle'
}
},
{
data: {
id: 'n16',
shape: 'ellipse'
}
}
],
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", function(event) {
cy.nodes('[shape!="ellipse"]').toggleClass("hidden");
cy.nodes('[shape="ellipse"]').each(function(node) {
if (node.connectedEdges().hidden()) {
node.toggleClass("hidden");
}
});
});
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>
<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>

Positioning a cytoscape.js "loop" edge in the bottomright of a node, going counter clockwise

How can I set the styling of a cytoscape.js 'loop edge' so that it rotates counterclockwise about the bottom right corner of a long (150px wide) rectangular node?
I have been fiddling with the style settings and just can't figure it out. The best I can tell I should be able to tune these for styles to get what I want:
selector: '.loop',
style: {
'loop-direction': '?deg',
'loop-sweep': '?deg',
'target-endpoint': '90deg',
'source-endpoint': '105deg',
}
},
Which is something like this arrow, in red:
But I can't really get anything better than this snippet. I just can't get the curve to "flip" to the other side.
window.addEventListener('DOMContentLoaded', function() {
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
style: [{
selector: 'node',
style: {
'width': 150,
'shape': 'rectangle',
'background-opacity': 0.5,
}
},
{
selector: 'edge',
style: {
'line-color': 'black',
'target-arrow-color': 'black',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier'
}
},
{
selector: '.loop',
style: {
'loop-direction': '90deg',
'loop-sweep': '-90deg',
'target-endpoint': '90deg',
'source-endpoint': '105deg',
}
},
],
elements: {
nodes: [{
data: {
id: 'n16'
}
}],
edges: [{
data: {
source: 'n16',
target: 'n16'
},
classes: 'loop'
}, ]
}
});
});
#cy {
width: 300px;
height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.15.1/cytoscape.min.js"></script>
<body>
<h1>cytoscape loop edge demo</h1>
<div id="cy"></div>
</body>
Your thinking is correct yet it seems Cytoscape will always default to clockwise loops when the endpoints are specified with angles (they are relative to the shape border!).
If you don't resort to "unbundled bezier-edges" where you can manually specify control points (and thus the curve of the edge) then using predefined values for endpoints might be good enough:
window.addEventListener('DOMContentLoaded', function() {
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
style: [{
selector: 'node',
style: {
'width': 150,
'shape': 'rectangle',
'background-opacity': 0.5,
}
},
{
selector: 'edge',
style: {
'line-color': 'black',
'target-arrow-color': 'black',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier'
}
},
{
selector: '.loop',
style: {
'loop-direction': '90deg',
'loop-sweep': '-90deg',
'target-endpoint': 'outside-to-line',
'source-endpoint': 'outside-to-line',
}
},
],
elements: {
nodes: [{
data: {
id: 'n16'
}
}],
edges: [{
data: {
source: 'n16',
target: 'n16'
},
classes: 'loop'
}, ]
}
});
});
#cy {
width: 300px;
height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.15.1/cytoscape.min.js"></script>
<body>
<h1>cytoscape loop edge demo</h1>
<div id="cy"></div>
</body>
You can find a reference for these values here:
https://js.cytoscape.org/#style/edge-endpoints
Was able to get it to work by adding the control-point-step-size key and then fiddling with all the dials. I wish there was a "debug" mode where I could see the control points and bezier stuff that these dials manipulate:
'loop-direction': '100deg',
'loop-sweep': '-20deg',
'target-endpoint': '90deg', // Up is 0 deg, going clockwise. From center of node, where the edge ends (pointing back into the node.
'source-endpoint': '105deg', // Up is 0 deg, going clockwise. From center of node, where the edge comes out of the node.
'control-point-step-size': 72,
window.addEventListener('DOMContentLoaded', function() {
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
style: [{
selector: 'node',
style: {
'width': 150,
'shape': 'rectangle',
'background-opacity': 0.5,
}
},
{
selector: 'edge',
style: {
'line-color': 'black',
'target-arrow-color': 'black',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier'
}
},
{
selector: '.loop',
style: {
'loop-direction': '100deg',
'loop-sweep': '-20deg',
'target-endpoint': '90deg',
'source-endpoint': '105deg',
'control-point-step-size': 72,
}
},
],
elements: {
nodes: [{
data: {
id: 'n16'
}
}],
edges: [{
data: {
source: 'n16',
target: 'n16'
},
classes: 'loop'
}, ]
}
});
});
#cy {
width: 300px;
height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.15.1/cytoscape.min.js"></script>
<body>
<h1>cytoscape loop edge demo</h1>
<div id="cy"></div>
</body>

Cytoscape js how to get all edges (text label) when clicked on a node

I know that I can use node.connectedEdges() to return an object of the edges connected to that node. How do I access the data fields of all the edges. I want to be able to click on a node and have the connected edges labels (text) in a list.
Also when you click on a node how can you access that nodes meta data (other attributes that are in the nodes JSON object).
Thanks
Well, everything in the node/edge metadata is accessible with the .data() method. This is a core functionality and i would suggest you to look at the docs, because this is a fairly simple task. All you have to do is to call the .connectedEdges() method and map the resulting object accordingly (using .data()):
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'
}
},
{
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.ready(function() {
cy.unbind('click')
cy.bind('click', 'node', function(event) {
let edges = event.target.connectedEdges().map(edge => edge.data().id)
console.log(edges)
})
})
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>
You just need some extra data in the edges and write that key instead of id:
edge.data().someKey

Content and Label on a Node - Cytoscape

I'm trying to display nodes that have a font icon in the center of the node using 'content' and a text label underneath.
My styling is currently:
{
'selector': 'node[icon]',
'style': {
'content': 'data(icon)',
'font-family': 'Material Icons',
'text-valign': 'center',
'text-halign': 'center'
}
},
{
'selector': 'node[label]',
'style': {
'label': 'data(label)',
'text-valign': 'bottom',
'text-halign': 'center'
}
}
However, this doesn't work as i assume both of the styles is used on one element (the node).
There are a few solutions I've considered, such as:
Putting the label on a parent node
Use Popper.js or similar to show the label
Use a multi-lined label
The first 2 seem 'hacky', and the third could cause a lot of alignment problems. Is there a better solution to this?
I've found a solution using the extension: https://github.com/kaluginserg/cytoscape-node-html-label.
You can create custom HTML labels for nodes which do not interfere with the base Cytoscape labels. An example of using the Material Icons:
// Initialise the HTML Label
this.cy.nodeHtmlLabel([{
query: '.nodeIcon',
halign: 'center',
valign: 'center',
halignBox: 'center',
valignBox: 'center',
tpl: (data) => {
return '<i class="material-icons">' + data.icon + '</i>';
}
}]);
// Add the HTML Label to the node:
const node = {
group: 'nodes',
data: {
id: data.id,
label: data.label,
icon: data.icon
},
classes: 'nodeIcon' // <---- Add the HTML Label class here
};
With the method you can dynamically create nodes with font icons without the need to download a load of images.
You can even add CSS styling to change the colour of the icon:
If you want an icon as the nodes body, you can use it as the background image and define the label like you do:
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
boxSelectionEnabled: false,
autounselectify: true,
style: [{
selector: 'node',
css: {
'label': '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',
"text-background-opacity": 1,
"text-background-color": "lightgray"
}
},
{
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
}
});
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>
<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>

Setting Colors for Rally Chart with 2.0rc1

I have an app that worked great for 2.0p5. I was able to set the various columns in the chart specific colors. In the chart config, I added
colors: [
'#89A54E',
'#4572A7',
'#AA4643'
],
When I upgraded to 2.0rc1, the colors no longer seem to get set. My chart config is:
Ext.create('Rally.ui.chart.Chart', {
animate: true,
chartData: {
series: [{
type: 'column',
name: 'Data1',
data: data1
},
{
type: 'column',
name: 'Data2',
data: data2
},
{
type: 'column',
name: 'Data3',
data: data3
}],
categories: xAxisData
},
chartConfig: {
chart: {
type: 'column'
},
title: {
text: 'Show Data'
},
yAxis: {
min: 0,
title: {
text: 'yAxis Info'
},
stackLabels: {
enabled: true,
style: {
fontWeight: 'bold',
color: 'gray'
}
}
},
legend: {
align: 'right',
x: -100,
verticalAlign: 'top',
y: 20,
floating: true,
backgroundColor: 'white',
borderColor: '#CCC',
borderWidth: 1,
shadow: false
},
tooltip: {
formatter: function() {
return '<b>'+ this.x +'</b><br/>'+
this.series.name +': '+ this.y +'<br/>'+
'Total: '+ this.point.stackTotal;
}
},
colors: [
'#89A54E',
'#4572A7',
'#AA4643'
],
plotOptions: {
column: {
stacking: 'normal',
dataLabels: {
enabled: true,
color: 'white'
}
}
}
}
});
Any ideas why I lost my color setting capabilities in 2.0rc1?
An example from one of my apps:
var series = [{
name : 'Col 1',
data : [],
showInLegend : false
},{
name : 'Col 2',
data : [],
showInLegend : false
},{
name : 'Col 3',
data : [],
showInLegend : false
}];
Ext.Array.each(records, function(record) {
//HighCharts Data
record.set('name', ...);
record.set('y', ...);
record.set('color', ...);
//Add record to series
series[index].data.push(record.data);
// Add to chartData
});
Hope this helps/works for you!