Expanding nodes with Cytoscape - javascript

I am trying to make a graph that, when the page loads, expands all the nodes in the graph out from the root node like a tree, like the opposite of this example. The graph is directed with no cycles and a pre-defined root node.
Right now, I hide all the nodes on the graph. I then run a DFS from the root node. At each child node, I move it to the location of the parent node, and then want to run .animate() with its original position as the destination so that there is a smooth animation. As seen in the code, the .animate() function doesn't pause the rest of the program. Any advice?
var cy = cytoscape({
container: document.getElementById('cy'),
boxSelectionEnabled: false,
autounselectify: true,
style: cytoscape.stylesheet()
.selector('node')
.css({
'content': 'data(id)'
})
.selector('edge')
.css({
'curve-style': 'bezier',
'target-arrow-shape': 'triangle',
'width': 4,
'line-color': '#ddd',
'target-arrow-color': '#ddd',
}),
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: 'bc', weight: 5, source: 'b', target: 'c' } },
{ data: { id: 'cd', weight: 2, source: 'c', target: 'd' } },
]
},
layout: {
name: 'breadthfirst',
directed: true,
padding: 10,
roots: '#a',
}
}); // cy init
var root_id = 'a'
//get root
var root = cy.$('#'+root_id)[0]
//empty collection
var collection = cy.collection()
//hide all nodes except root
cy.ready(function(event){
collection = collection.add(cy.nodes('node[id!="'+root_id+'"]'))
collection = collection.add(cy.nodes().connectedEdges());
collection.style('visibility', 'hidden')
});
var visited_nodes = [root];
function dfs(node) {
//visit node
visited_nodes.push(node)
//for each neighbor w ov f
neighbors = cy.$('#'+node.id()).outgoers('edge')
neighbors.forEach(function (next) {
var next_node = cy.$('#'+next.data().target)
if (visited_nodes.indexOf(next_node) < 0){
//visit each unvisited node
//we will move the target node to the source node, then use .animate() to move the target node back to it's original location
source_id = next.data('source')
target_id = next.data('target')
var node_to_move = cy.$('#' + next.data('target'))[0]
//record the x and y coordinates to avoid messing around for objects. temporary.
var start_position_x = cy.$('#' + next.data('source')).position().x
var start_position_y = cy.$('#' + next.data('source')).position().y
var end_position_x = cy.$('#' + next.data('target')).position().x
var end_position_y = cy.$('#' + next.data('target')).position().y
//move the target node to its start position
node_to_move.position('x',start_position_x)
node_to_move.position('y',start_position_y)
node_to_move.style('visibility', 'visible')
//then animate the target node moving to it's original position
node_to_move.animate(
{
position: {end_position_x, end_position_y}
},
{
duration: 1000,
complete: function(){
node_to_move.style('visibility', 'visible')
// if (e !== undefined){
// e.style('visibility', 'visible')
// }
}
})
//DOESNT WORK WITH THESE COMMENTED OUT, DOES WITH THEM COMMENTED IN/
//I think this means it is a timing problem, with the dsf moving forward without the nodes getting moved
// node_to_move.position('x',end_position_x)
// node_to_move.position('y',end_position_y)
dfs(next_node)
}
})
}
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
}
#eat {
position: absolute;
left: 1em;
top: 1em;
font-size: 1em;
z-index: -1;
color: #c88;
}
<!DOCTYPE html>
<!-- This code is for demonstration purposes only. You should not hotlink to Github, Rawgit, or files from the Cytoscape.js documentation in your production apps. -->
<html>
<head>
<link href="style.css" rel="stylesheet" />
<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">
<title>Images</title>
<script src="cube/node_modules/cytoscape/dist/cytoscape.js"></script>
</head>
<body>
<div id="cy"></div>
<!-- Load appplication code at the end to ensure DOM is loaded -->
<script src="expand.js"></script>
</body>
</html>

You ran the animations at the same time. Chain animations using promises, e.g. node1.animation().play().promise().then( node2.animation().play.promise() ).then( ... )

Related

Cytoscape.js Show/Hide labels on elements in the graph on button click

I have a node application using cytoscape v3.15.2. I have the styling set as follows
let cy = cytoscape({
container: document.getElementById('graph-div'),
style: [
{
selector: 'node',
style: {
'label': 'data(id)',
}
},
{
selector: 'edge',
style: {
'label': (ele) => {
if(ele.data('type') === '1') return 'data(category1)';
if(ele.data('type') === '2') return 'data(category2)';
return value;
}
}]
});
Now, using a checkbox, I am trying to show/hide the labels on the elements. I have tried doing the following:
cy.elements().style("label","");
But this doesnt work. The same thing works when I pass a random string instead an empty string, something like this : cy.elements().style("label","random");. Doing this sets the labels of all elements in the graph to hidden. Could you please help me how to do this. Thanks
You can manage showing/hiding labels by specifying a class in the stylesheet and then toggling it on button click as in the below example.
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
layout: {name: 'grid', rows: 2},
style: [{
selector: '.hasLabel',
css: {
'label': (ele) => {
if(ele.isNode()) return ele.data('id');
if(ele.isEdge()) return ele.data('weight');
}
}
}
],
elements: {
nodes: [{
data: {
id: 'n0'
}
},
{
data: {
id: 'n1'
}
},
{
data: {
id: 'n2'
}
},
{
data: {
id: 'n3'
}
}
],
edges: [{
data: {
id: 'n0n1',
source: 'n0',
target: 'n1',
weight: 3
}
},
{
data: {
id: 'n1n2',
source: 'n1',
target: 'n2',
weight: 5
}
},
{
data: {
id: 'n2n3',
source: 'n2',
target: 'n3',
weight: 7
}
}
]
}
});
document.getElementById("labelButton").addEventListener("click", function() {
cy.elements().toggleClass('hasLabel');
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#button {
z-index = 1000;
}
#cy {
height: 95%;
width: 95%;
left: 0;
top: 50;
z-index = 900;
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.10.0/dist/cytoscape.min.js">
</script>
</head>
<body>
<button id="labelButton" type="button">Show/Hide Labels</button>
<div id="cy"></div>
</body>
</html>

Y-axis force with cola.js and Cytoscape

I have noticed that using Cola.js (with Cytoscape.js) most of my layouts tend to form in a square layout and not using up my bounding box which is more wide than tall.
I've been looking around and found d3-force which has this option of forceY that transform a square layout (https://bl.ocks.org/steveharoz/8c3e2524079a8c440df60c1ab72b5d03):
To this more wide layout:
I would really like to do the same for Cola.js, however I have been struggling to do it and tried all possible options like setting the bounding box, disabling zoom and etc. Is this possible at all?
I've found a demo for Cola.js that provides somewhat what I need, but unable to make it work in Cytoscape.js: https://ialab.it.monash.edu/webcola/examples/pageBoundsConstraints.html
Based on the link you provide, you can apply a similar functionality with cola.js. You need to have two dummy nodes locked (one for top-left and one for bottom-right) and then add constraints for all other nodes appropriately. You can disable the visibility of dummy nodes (I left them as visible so we can see the top-left and bottom-right of the bounding box.). You can play with the position of dummy nodes to adjust your bounding box.
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
style: [{
selector: 'node',
css: {
'content': 'data(id)',
'text-valign': 'center',
'text-halign': 'center'
}
},
{
selector: 'edge',
css: {
'curve-style': 'straight',
}
}
],
layout: {
name: 'preset'
},
elements: {
nodes: [{
data: {
id: 'n0'
}
},
{
data: {
id: 'n1'
}
},
{
data: {
id: 'n2'
}
},
{
data: {
id: 'n3'
}
},
{
data: {
id: 'n4'
}
},
{
data: {
id: 'd0'
},
position: {x: 0, y:0}
},
{
data: {
id: 'd1'
},
position: {x: 400, y:150}
}
],
edges: [{
data: {
id: 'n0n1',
source: 'n0',
target: 'n1'
}
},
{
data: {
id: 'n1n2',
source: 'n1',
target: 'n2'
}
},
{
data: {
id: 'n2n3',
source: 'n2',
target: 'n3'
}
},
{
data: {
id: 'n4n1',
source: 'n4',
target: 'n1'
}
}
]
}
});
var tl = cy.getElementById('d0');
var br = cy.getElementById('d1');
tl.lock();
br.lock();
var realGraphNodes = cy.nodes().difference(tl.union(br));
var constraints = [];
for (var i = 0; i < realGraphNodes.length; i++) {
constraints.push({ axis: 'x', left: tl, right: realGraphNodes[i], gap: 100 });
constraints.push({ axis: 'y', left: tl, right: realGraphNodes[i], gap: 100 });
constraints.push({ axis: 'x', left: realGraphNodes[i], right: br, gap: 100 });
constraints.push({ axis: 'y', left: realGraphNodes[i], right: br, gap: 100 });
}
cy.layout({name: 'cola', randomize: true, animate: false, gapInequalities: constraints}).run();
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 95%;
width: 95%;
left: 0;
top: 0;
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.10.0/dist/cytoscape.min.js"></script>
<script src="https://unpkg.com/webcola/WebCola/cola.min.js"></script>
<script src="https://unpkg.com/cytoscape-cola/cytoscape-cola.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>

How to hide compound nodes while still displaying children in cytoscape.js

I want to create a graph where a few nodes are "compound" nodes, embedding several children nodes, but without displaying them: the children nodes should be displayed but the compounds should be hidden.
As a layout I am using an extension for cytoscape.js called dagre-cytoscape.
So far I created a 'compound' class that only the compound nodes have, and I tried to set opacity: 0 or visibility: hidden for this class only, but which each of these options the children nodes became invisible as well.
Here is a bin of a non-working example:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<title>dagre demo</title>
<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://cytoscape.org/cytoscape.js-dagre/cytoscape-dagre.js"></script>
<style>
body {
font-family: helvetica;
font-size: 14px;
}
#cy {
width: 75%;
height: 100%;
position: absolute;
left: 200;
top: 200;
z-index: 999;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', function () {
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
layout: {
name: 'dagre'
},
style: [{
selector: 'node',
style: {
'background-color': 'teal',
'label': 'data(id)',
'visibility': 'visible'
}
},
{
selector: '.compound',
style: {
'visibility': 'hidden'
}
}
],
elements: {
nodes: [{
data: {
id: 'n0'
}
},
{
data: {
id: 'n1',
parent: 'parent'
}
},
{
data: {
id: 'n2',
parent: 'parent'
}
},
{
data: {
id: 'parent'
},
classes: 'compound'
}
],
edges: [{
data: {
source: 'n0',
target: 'n1'
}
},
{
data: {
source: 'n0',
target: 'n2'
}
}
]
}
});
});
</script>
</head>
<body>
<h1>cytoscape-dagre test</h1>
<div id="cy"></div>
</body>
</html>
jsbin version
In this example the nodes n1 and n2 are hidden, as well as the parent node. I would like to keep parent hidden but have n1 and n2 visible.
Try setting background-opacity and border-width of compound nodes to 0.

Modify the style for a specific node selected in the network for vis.js

Is there a way to change the node size for the selected node without changing the size for all nodes in the options ?
These are my node options:
nodes: {
borderWidth: 1,
borderWidthSelected: 2,
physics: true,
color: {
border: '#000000',
background: '#ffffff',
highlight: {
border: '#000000',
background: '#B9B9BF'
}
},
shadow: {
enabled: false,
color: '#C11818',
size: 10,
x: 5,
y: 5
},
shape: 'circularImage',
mass: 2,
size: 25
}
I want to enlarge the selected node so it is more visible than the others.
network.on("selectNode", function (params) {
var nodeId = params.nodes[0];
var node = nodes.get(nodeId);
nodeClick(nodeId, nodes, edges, network);
// var options= {
// nodes: {
// size: 40
// }
// };
// network.setOptions(options);
});
The commented part sets the size for all nodes rather than the one selected and the node object doesn't have any handle on the options either.
You can change the font-size of the selected node to increase its size:
var nodes = new vis.DataSet([
{id: 1, label: 'Node 1'},
{id: 2, label: 'Node 2'},
{id: 4, label: 'Node 4'},
{id: 5, label: 'Node 5'}
]);
var edges = new vis.DataSet([
{from: 1, to: 2},
{from: 2, to: 4},
{from: 2, to: 5}
]);
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = {
interaction: { hover:true },
nodes: { font: { size: 14 }}
};
var network = new vis.Network(container, data, options);
network.on("selectNode", function (params) {
var selectedNodeId = params.nodes[0];
var node = network.body.nodes[selectedNodeId];
node.setOptions({
font: {
size: 20
}
});
});
network.on("deselectNode", function (params) {
var deselectedNodeId = params.previousSelection.nodes[0];
var node = network.body.nodes[deselectedNodeId];
node.setOptions({
font: {
size: options.nodes.font.size
}
});
});
#mynetwork {
width: 100%;
height: 100%;
border: 1px solid lightgray;
}
<!DOCTYPE html>
<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div id="mynetwork"></div>
</body>
</html>
if you have multiselect enabled, you can loop over params.nodes
for (id in params.nodes){
var node = network.body.nodes[params.nodes[id]];
...
}
(deselect respectivly)

cytoscape.js & meteor simple example doesnt work

i added to meteor cytoscape : infinitedg:cytoscape
i have very basic meteor app:
hello.js http://pastebin.com/2frsHc9g
hello.html http://pastebin.com/10EYyJ74
but i am not able to make it work
here is error i can see in console of web browser:
on rendered zavolana hello.js:9 ss [object Object] debug.js:41
Exception from Tracker afterFlush function: debug.js:41 TypeError:
Cannot read property 'addEventListener' of undefined
at CanvasRenderer.registerBinding (infinitedg_cytoscape.js:17127)
at CanvasRenderer.load (infinitedg_cytoscape.js:17283)
at new CanvasRenderer (infinitedg_cytoscape.js:13419)
at $$.fn.core.initRenderer (infinitedg_cytoscape.js:7527)
at new $$.Core (infinitedg_cytoscape.js:6592)
at Function.$$.init (infinitedg_cytoscape.js:75)
at cytoscape (infinitedg_cytoscape.js:58)
at HTMLDivElement. (infinitedg_cytoscape.js:2808)
at Function.jQuery.extend.each (jquery.js:384)
at jQuery.fn.jQuery.each (jquery.js:136)
do you have please some "hello world" of combination of cytoscape and meteor ?
problem was with wrong library installed via meteor
after i installed correct cytoscape library, it is working
correct is cytoscape:cytoscape
here is minimal and working example:
JS
sit = "" //hlavni objekt
if (Meteor.isClient) {
Template.graf.rendered = function() {
// Meteor.defer(function() {
//setTimeout(function(){
console.log("on rendered called");
//var divcy = $('#cy');
// console.log("ss " + divcy);
sit = cytoscape({
container: document.getElementById('cy'),
ready: function() {
console.log("network ready");
updateNetworkData(sit); // load data when cy is ready
},
style: cytoscape.stylesheet()
.selector('node')
.style({
'content': function(e) {
return e.data("name")
},
'font-size': 12,
'text-valign': 'center',
'color': 'white',
'text-outline-width': 2,
'text-outline-color': function(e) {
return e.locked() ? "red" : "#888"
},
'min-zoomed-font-size': 8
// 'width': 'mapData(score, 0, 1, 20, 50)',
// 'height': 'mapData(score, 0, 1, 20, 50)'
})
.selector('edge')
.style({
'content': function(e) {
return e.data("name") ? e.data("name") : "";
},
'target-arrow-shape': 'triangle',
})
});
//})
}
}
if (Meteor.isServer) {
Meteor.startup(function() {
// code to run on server at startup
});
}
function updateNetworkData(net) {
// init Data
var nodes = [{ // node a
group: 'nodes',
data: {
id: 'a',
name:'a'
}
}, { // node b
group: 'nodes',
data: {
id: 'b',
name:'b'
}
}
]
var edges = [{ // edge ab
group: 'edges',
data: {
id: 'ab',
name:'ab',
source: 'a',
target: 'b'
}
}
]
net.elements().remove(); // make sure evything is clean
net.add(nodes);
net.add(edges);
net.reset() // render layout
}
CSS
#cy {
width : 70vw;
height: 50vw;
position: absolute;
}
HTML
<head>
<title>hello</title>
</head>
<body>
<h1>Welcome to Meteor!b</h1>
{{>graf}}
</body>
<template name="graf">
<div id="cy"></div>
</template>

Categories