I am looking for an example of showing Tooltips when hovering over nodes in Cytoscape graph.
I have seen several examples with popperjs and with qtip , non of them was working (for me at least)
cytoscape-qtip seems to be deprecated
example here (Tippy.js)
https://github.com/cytoscape/cytoscape.js-popper
seems not to work
You can use the code from here just fine, I made some adjustments myself, so that the tooltips appear on hover:
document.addEventListener("DOMContentLoaded", function() {
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
style: [{
selector: "node",
style: {
content: "data(id)"
}
},
{
selector: "edge",
style: {
"curve-style": "bezier",
"target-arrow-shape": "triangle"
}
}
],
elements: {
nodes: [{
data: {
id: "a"
}
}, {
data: {
id: "b"
}
}],
edges: [{
data: {
id: "ab",
source: "a",
target: "b"
}
}]
},
layout: {
name: "grid"
}
}));
function makePopper(ele) {
let ref = ele.popperRef(); // used only for positioning
ele.tippy = tippy(ref, {
// tippy options:
content: () => {
let content = document.createElement("div");
content.innerHTML = ele.id();
return content;
},
trigger: "manual" // probably want manual mode
});
}
cy.ready(function() {
cy.elements().forEach(function(ele) {
makePopper(ele);
});
});
cy.elements().unbind("mouseover");
cy.elements().bind("mouseover", event => event.target.tippy.show());
cy.elements().unbind("mouseout");
cy.elements().bind("mouseout", event => event.target.tippy.hide());
});
body {
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
font-size: 14px
}
#cy {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 1;
}
h1 {
opacity: 0.5;
font-size: 1em;
font-weight: bold;
}
<head>
<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/popper.js#1.14.7/dist/umd/popper.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-popper#1.0.4/cytoscape-popper.min.js"></script>
<script src="https://unpkg.com/tippy.js#4.0.1/umd/index.all.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/tippy.js#4.0.1/index.css" />
</head>
<body>
<div id="cy"></div>
</body>
Related
What I´m trying to achieve here with cytoscape.js library that when I click on an edge then the id of that edge will be shown instead of hovering.
for example the ab id should be displayed only when I click on the edge. I don´t want to display id´s of nodes when I click.
Thanks for help in advance.
is there any documentation with more examples than cytoscape.js ?
document.addEventListener("DOMContentLoaded", function() {
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
style: [{
selector: "node",
style: {
content: "data(id)"
}
},
{
selector: "edge",
style: {
"curve-style": "bezier",
"target-arrow-shape": "triangle"
}
}
],
elements: {
nodes: [{
data: {
id: "a"
}
}, {
data: {
id: "b"
}
}],
edges: [{
data: {
id: "ab",
source: "a",
target: "b"
}
}]
},
layout: {
name: "grid"
}
}));
function makePopper(ele) {
let ref = ele.popperRef(); // used only for positioning
ele.tippy = tippy(ref, { // tippy options:
content: () => {
let content = document.createElement('div');
content.innerHTML = ele.id();
return content;
},
trigger: 'manual' // probably want manual mode
});
}
cy.ready(function() {
cy.elements().forEach(function(ele) {
makePopper(ele);
});
});
cy.elements().unbind('mouseover');
cy.elements().bind('mouseover', (event) => event.target.tippy.show());
cy.elements().unbind('mouseout');
cy.elements().bind('mouseout', (event) => event.target.tippy.hide());
});
body {
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
font-size: 14px
}
#cy {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 1;
}
h1 {
opacity: 0.5;
font-size: 1em;
font-weight: bold;
}
<head>
<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/popper.js#1.14.7/dist/umd/popper.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-popper#1.0.4/cytoscape-popper.min.js"></script>
<script src="https://unpkg.com/tippy.js#4.0.1/umd/index.all.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/tippy.js#4.0.1/index.css" />
</head>
<body>
<div id="cy"></div>
</body>
This is basic cytoscape.js functionality. You are using these two lines as your event trigger:
cy.elements().unbind('mouseover');
cy.elements().bind('mouseover', (event) => event.target.tippy.show());
cy.elements().unbind('mouseout');
cy.elements().bind('mouseout', (event) => event.target.tippy.hide());
If you take a look at the code, you notice two things:
You are calling the .bind() function on cy.elements(), thus calling the bind on every element in the cytoscape.js graph
You are using the mouse-over and mouse-out event to trigger the popper tooltip
The simple fix would be this:
cy.nodes().unbind('mouseover');
cy.nodes().bind('mouseover', (event) => event.target.tippy.show());
cy.nodes().unbind('mouseout');
cy.nodes().bind('mouseout', (event) => event.target.tippy.hide());
cy.edges().unbind('mousedown');
cy.edges().bind('mousedown ', (event) => event.target.tippy.show());
cy.edges().unbind('mouseup');
cy.edges().bind('mouseup', (event) => event.target.tippy.hide());
In your code, this would look like this:
document.addEventListener("DOMContentLoaded", function() {
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
style: [{
selector: "node",
style: {
content: "data(id)"
}
},
{
selector: "edge",
style: {
"curve-style": "bezier",
"target-arrow-shape": "triangle"
}
}
],
elements: {
nodes: [{
data: {
id: "a"
}
}, {
data: {
id: "b"
}
}],
edges: [{
data: {
id: "ab",
source: "a",
target: "b"
}
}]
},
layout: {
name: "grid"
}
}));
function makePopper(ele) {
let ref = ele.popperRef(); // used only for positioning
ele.tippy = tippy(ref, { // tippy options:
content: () => {
let content = document.createElement('div');
content.innerHTML = ele.id();
return content;
},
trigger: 'manual' // probably want manual mode
});
}
cy.ready(function() {
cy.elements().forEach(function(ele) {
makePopper(ele);
});
});
cy.nodes().unbind('mouseover');
cy.nodes().bind('mouseover', (event) => event.target.tippy.show());
cy.nodes().unbind('mouseout');
cy.nodes().bind('mouseout', (event) => event.target.tippy.hide());
cy.edges().unbind('mousedown');
cy.edges().bind('mousedown', (event) => event.target.tippy.show());
cy.edges().unbind('mouseup');
cy.edges().bind('mouseup', (event) => event.target.tippy.hide());
});
body {
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
font-size: 14px
}
#cy {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 1;
}
h1 {
opacity: 0.5;
font-size: 1em;
font-weight: bold;
}
<head>
<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/popper.js#1.14.7/dist/umd/popper.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-popper#1.0.4/cytoscape-popper.min.js"></script>
<script src="https://unpkg.com/tippy.js#4.0.1/umd/index.all.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/tippy.js#4.0.1/index.css" />
</head>
<body>
<div id="cy"></div>
</body>
I have it set up in this codepen:I have two questions related to this solution using Tippy v4. related stackoverflow question
JS:
document.addEventListener("DOMContentLoaded", function() {
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
style: [{
selector: "node",
style: {
content: "data(id)"
}
},
{
selector: "edge",
style: {
"curve-style": "bezier",
"target-arrow-shape": "triangle"
}
}
],
elements: {
nodes: [{
data: {
id: "a"
}
}, {
data: {
id: "b"
}
}],
edges: [{
data: {
id: "ab",
source: "a",
target: "b"
}
}]
},
layout: {
name: "grid"
}
}));
function makePopper(ele) {
let ref = ele.popperRef();
tippy(ref, {
// popperInstance will be available onCreate
lazy: false,
onCreate(instance) {
instance.popperInstance.reference = {
clientWidth: 10,
clientHeight: 10,
getBoundingClientRect() {
return {
w: 20, h:20
};
},
};
},
});
}
cy.ready(function() {
cy.elements().forEach(function(ele) {
makePopper(ele);
});
});
cy.elements().unbind('mouseover');
cy.elements().bind('mouseover', (event) => event.target.tippy.show());
cy.elements().unbind('mouseout');
cy.elements().bind('mouseout', (event) => event.target.tippy.hide());
});
HTML:
<head>
<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/popper.js#1.14.7/dist/umd/popper.js">
</script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-popper#1.0.5/cytoscape-popper.min.js"></script>
<script src="https://unpkg.com/tippy.js#5/dist/tippy-bundle.iife.js">
</script>
<link rel="stylesheet"
href="https://unpkg.com/tippy.js#5/dist/backdrop.css">
</head>
<body>
<div id="cy"></div>
</body>
CSS:
body {
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
font-size: 14px
}
#cy {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 1;
}
h1 {
opacity: 0.5;
font-size: 1em;
font-weight: bold;
}
1) Is it possible to make the Tippy follow the node as it is dragged? I have tried adding the property like this in cy.ready(), tippy.setDefaultProps({followCursor: 'true'}); But I get an error that the tippy.setDefaultProps() is not a function. I have set this Tippy v4 question up in this codepen: setDefaultProps codePen
tippy.setDefaultProps({followCursor: 'true'});
2) I cannot get the tippy example to work with Tippy v5. I get an error, tippy was passed as a plain object which is no longer supported. I have tried passing a ref object, but I get the same error. I have it v5 set up in this codepen:
Tippy v5 error The code shown above is the Tippy v5 code, but the Tippy v4 code is very similar.
I want to make a network graph that looks similar to this below image.
Click on this link to view the image
I have searched across and have not found a solution for this using Cytoscape.
Yes, you can create bidirectional edges in Cytoscape.js. First, the curve-styles of your edges should be different than 'haystack' (below I set it as 'straight'), because 'haystack' doesn't support edge endpoint arrows. Second, you need to specify source and target endpoint arrow shapes. Possible values for arrow shapes are listed here (I used 'triangle-backcurve' below). If you want one directional edge, you can set the arrow shape only for source or target.
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',
}
}
],
elements: {
nodes: [{
data: {
id: 'n0'
}
},
{
data: {
id: 'n1'
}
},
{
data: {
id: 'n2'
}
},
{
data: {
id: 'n3'
}
}
],
edges: [{
data: {
id: 'n0n1',
source: 'n0',
target: 'n1'
}
},
{
data: {
id: 'n1n2',
source: 'n1',
target: 'n2'
}
},
{
data: {
id: 'n2n3',
source: 'n2',
target: 'n3'
}
}
]
}
});
cy.getElementById('n0n1').style({'source-arrow-shape': 'triangle-backcurve', 'target-arrow-shape': 'triangle-backcurve'});
cy.getElementById('n1n2').style({'target-arrow-shape': 'triangle-backcurve'});
cy.getElementById('n2n3').style({'source-arrow-shape': 'triangle-backcurve'});
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>
</head>
<body>
<div id="cy"></div>
</body>
</html>
I want to change the color of the edge which connected a node(nodeA->nodeB and nodeB->nodeC) to red color, when clicking nodeC as in sample.png, by cytoscape.js. Please help me.
cy.on("tap", "node", (evt) => {evt.cyTarget.connectedEdges().animate({
style: {lineColor: "red"}
})
})`
When I use this code, it changed the colors of all edges that connected to the clicked node.
Cytoscape.js provides some neat functions to filter nodes:
incomers() gets edges (and their sources) coming into the nodes in the collection
predecessors() recursively gets edges (and their sources) coming into the nodes in the collection (i.e. the incomers, the incomers' incomers, ...)
edges() gets edges in the graph matching the specified selector
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: "100px",
shape: "rectangle",
"background-color": "data(faveColor)"
}
},
{
selector: "edge",
css: {
"curve-style": "bezier",
"control-point-step-size": 40,
"target-arrow-shape": "triangle"
}
}
],
elements: {
nodes: [{
data: {
id: "Top",
faveColor: "#2763c4"
}
},
{
data: {
id: "yes",
faveColor: "#37a32d"
}
},
{
data: {
id: "no",
faveColor: "#2763c4"
}
},
{
data: {
id: "Third",
faveColor: "#2763c4"
}
},
{
data: {
id: "Fourth",
faveColor: "#56a9f7"
}
}
],
edges: [{
data: {
source: "Top",
target: "yes"
}
},
{
data: {
source: "Top",
target: "no"
}
},
{
data: {
source: "no",
target: "Third"
}
},
{
data: {
source: "Third",
target: "Fourth"
}
}
]
},
layout: {
name: "dagre"
}
}));
cy.unbind('click');
cy.bind('click', 'node', function(node) {
console.log(node.target.predecessors().edges());
node.target.predecessors().edges().animate({
style: {
lineColor: "red"
}
});
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
left: 0;
top: 0;
float: left;
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.3.0/dist/cytoscape.min.js">
</script>
<!-- cyposcape dagre -->
<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>
I learned cytoscape.js and related extension some days and tried its many amazing features.
I find that when the number of children is greater than 5 and expand a node, the whole graph fly out of screen.
I constructed more complex data which own 3 parent nodes and 5 child nodes per parent node. There is a connection between any two child nodes.
So there are 3 parent nodes, 15 children nodes and 14+13+12..1 links.
To sum up, when there are more links, the layout behavior looks abnormal.
See my demo below.
You can modify parameters of my function getInitData() to see the effect.
document.addEventListener('DOMContentLoaded', function(){
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
ready: function(){
var api = this.expandCollapse({
layoutBy: {
name: "cose-bilkent",
animate: 'end',
randomize: false,
fit: false,
idealEdgeLength : 150
},
fisheye: false,
animate: true,
undoable: false
});
api.collapseAll();
},
style: [
{
selector: 'node',
style: {
'background-color': '#ad1a66'
}
},
{
selector: ':parent',
style: {
'background-opacity': 0.333
}
},
{
selector: "node.cy-expand-collapse-collapsed-node",
style: {
"background-color": "darkblue",
"shape": "rectangle"
}
},
{
selector: 'edge',
style: {
'width': 3,
'line-color': '#ad1a66'
}
},
{
selector: 'edge.meta',
style: {
'width': 2,
'line-color': 'red'
}
},
{
selector: ':selected',
style: {
"border-width": 3,
"border-color": '#DAA520'
}
}
],
elements : getInitData(3, 5)
});
var api = cy.expandCollapse('get');
var elements = null;
});
function getInitData(parentNum, childrenNum){
var data = [], children = [], i, j, n;
for(i = 0; i < parentNum; i++){
n = "parent"+i;
data.push({"group":'nodes',data:{"id":n}});
for(j = 0; j < childrenNum; j++){
children.push({"group":'nodes',data:{"id":n+"_child_"+j, parent:n}});
}
}
var s,t;
for(i = 0; i < children.length - 1; i++){
s = children[i].data.id;
for(j = i+1; j < children.length; j++){
t = children[j].data.id;
data.push({"group":'edges',data:{"id":s+"_"+t, source:s, target:t}});
}
}
return data.concat(children);
}
body {
font-family: helvetica neue, helvetica, liberation sans, arial, sans-serif;
font-size: 14px;
}
#cy {
z-index: 999;
width: 100%;
height: 100%;
}
h1 {
opacity: 0.5;
font-size: 1em;
font-weight: bold;
}
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="https://unpkg.com/cytoscape#3.1.0/dist/cytoscape.min.js"></script>
<!-- for testing with local version of cytoscape.js -->
<!--<script src="../cytoscape.js/build/cytoscape.js"></script>-->
<script src="https://unpkg.com/cytoscape-cose-bilkent#4.0.0/cytoscape-cose-bilkent.js"></script>
<script src="https://unpkg.com/cytoscape-expand-collapse#3.1.1/cytoscape-expand-collapse.js"></script>
<div id="cy"></div>
Solution one:
Your graph doesn't have any fitting logic. You can implement that yourself with the two methods cy.center(), which centers the graph to the current viewport and cy.fit(), which zooms the graph to the right position. You would have to call these mehtods everytime you change your graph, e.g. when you add a node, remove a node or, like in your case, expand and collapse. You can do that by binding these events and calling the said methods there.
Binding, as you know from the last question works like this:
cy.unbind('event');
cy.bind('event', 'target', function (event) {...});
Solution two:
You can alternatively, if possible (not all layouts can do this), set the method to fit: true,, which fits the graph with cy.fit(); and cy.center(); internally.
Additional problem and solution for that:
You said, that your graph looks bad when you only have one node in it, so to circumvent that, you can set the padding property of 'cose-bilkent' to a higher number. You can do that at the initialization in the options.
document.addEventListener('DOMContentLoaded', function() {
var padding = 10;
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
layout: {
name: 'cose-bilkent',
animate: false,
randomize: true
},
style: [{
selector: 'node',
style: {
'background-color': '#ad1a66'
}
},
{
selector: 'edge',
style: {
'width': 3,
'line-color': '#ad1a66'
}
}
],
elements: [{
"data": {
"id": "glyph9"
}
}]
});
document.getElementById("add").addEventListener("click", function() {
padding += 10;
var layout = cy.layout({
name: 'cose-bilkent',
animate: false,
padding: padding
});
layout.run();
});
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 90%;
width: 100%;
position: absolute;
float: left;
}
button {
margin-right: 10px;
}
<!DOCTYPE>
<html>
<head>
<title>cytoscape-cose-bilkent.js demo</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="https://unpkg.com/cytoscape#3.1.0/dist/cytoscape.min.js"></script>
<!-- for testing with local version of cytoscape.js -->
<!--<script src="../cytoscape.js/build/cytoscape.js"></script>-->
<script src="https://unpkg.com/cytoscape-cose-bilkent#4.0.0/cytoscape-cose-bilkent.js"></script>
<script src="https://unpkg.com/cytoscape-expand-collapse#3.1.1/cytoscape-expand-collapse.js"></script>
</head>
<body>
<button id="add" type="button">Add padding</button>
<div id="cy"></div>
</body>
</html>