Related
I have a collection of elements (created by using pathTo from the dijkstra function).
I need to reverse elements of this collection, for instance I want to transform
[14, 2, 37, 4, 5] into [5, 4, 37, 2, 14].
I tried using the filter function as per below but to no success. Does anyone know how to reverse a collection (not an array)?
path = dijkstraDiverse.pathTo(cy.$id('5'));
newpath = path.filter(function(ele, i, eles) {
return eles[path.length - 1 - i];
});
I used the sort function and just reversed the collection that way. The important part is using -1 as the sorting metric, that way everything is simply reversed. I added some visual clarification derived from this BFS example:
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"
}
},
{
selector: "edge",
css: {
"label": "data(weight)",
"target-arrow-shape": "triangle"
}
},
{
selector: ".highlight",
css: {
'background-color': '#61bffc',
'line-color': '#61bffc',
'target-arrow-color': '#61bffc',
'transition-property': 'background-color, line-color, target-arrow-color',
'transition-duration': '0.5s'
}
},
{
selector: ".old",
css: {
'background-color': '#ff6e63',
'line-color': '#ff6e63',
'target-arrow-color': '#ff6e63',
'transition-property': 'background-color, line-color, target-arrow-color',
'transition-duration': '0.5s'
}
}
],
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",
weight: 1
}
},
{
data: {
source: "n1",
target: "n2",
weight: 11
}
},
{
data: {
source: "n1",
target: "n3",
weight: 12
}
},
{
data: {
source: "n2",
target: "n7",
weight: 2
}
},
{
data: {
source: "n2",
target: "n11",
weight: 3
}
},
{
data: {
source: "n2",
target: "n16",
weight: 1
}
},
{
data: {
source: "n3",
target: "n4",
weight: 32
}
},
{
data: {
source: "n3",
target: "n16",
weight: 7
}
},
{
data: {
source: "n4",
target: "n5",
weight: 6
}
},
{
data: {
source: "n4",
target: "n6",
weight: 4
}
},
{
data: {
source: "n6",
target: "n8",
weight: 11
}
},
{
data: {
source: "n8",
target: "n9",
weight: 12
}
},
{
data: {
source: "n8",
target: "n10",
weight: 1
}
},
{
data: {
source: "n11",
target: "n12",
weight: 1
}
},
{
data: {
source: "n12",
target: "n13",
weight: 2
}
},
{
data: {
source: "n13",
target: "n14",
weight: 3
}
},
{
data: {
source: "n13",
target: "n15",
weight: 5
}
}
]
},
layout: {
name: "dagre",
padding: 5
}
}));
cy.ready(function() {
const dijkstra = cy.elements().dijkstra(
"#n0",
function(edge) {
const weight = edge.data("weight");
return weight;
}
);
const oldPath = dijkstra.pathTo(cy.$("#n10"));
console.log(oldPath);
const newPath = oldPath.sort(function(a, b) {
return -1;
});
console.log(newPath);
let i = 0;
let j = 0;
let highlightPath = newPath;
let highlightNextEle = function() {
if (i < highlightPath.length) {
if (j == 0) {
highlightPath[i].addClass("highlight");
} else {
//highlightPath[i].removeClass("highlight");
highlightPath[i].addClass("old");
}
i++;
setTimeout(highlightNextEle, 1000);
} else if (i == highlightPath.length && j !== 1) {
i = 0;
j = 1;
highlightPath = oldPath;
setTimeout(highlightNextEle, 1000);
}
};
// kick off first highlight
highlightNextEle();
});
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>
<script src="https://unpkg.com/jquery#3.3.1/dist/jquery.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 am using cytoscape.js for my visualization project in which I have to show a Hierarchical structure with compound nodes.
So I initially used Cose-Bilkent layout which worked like a charm but the requirement is that all the child nodes of a parent must be in a single row. So I tried to tweak around a bit but couldn't get the exact result.
Then I tried to use grid layout by giving hardcoded row and column numbers and I got the exact result but as my data is dynamic I realized its difficult to assign row numbers and column numbers on my own.
Here is the data I used,
elements: [ // list of graph elements to start with
{ // node a
data: { id: 'X1', label: 'X1'}
},
{
data: { id: 'X2', label: 'X2'}
},
{
data: { id: 'X3', label: 'X3'}
},
{
data: { id: 'X4', label: 'X4'}
},
{
data: { id: 'X5', label: 'X5'}
},
{
data: { id: 'X6', label: 'X6'}
},
{
data: { id: 'X7', label: 'X7'}
},
{
data: { id: 'X8', label: 'X8'}
},
{
data: { id: 'X9', label: 'X9'}
},
{
data: { id: 'X10', label: 'X10'}
},
{
data: { id: 'X1e1',label: 'e1', parent: 'X1', row: '1' ,col: '1'}
},
{
data: { id: 'X1e5',label: 'e5', parent: 'X1', row: '1',col: '2'}
},
{
data: { id: 'X1e6',label: 'e6', parent: 'X1', row: '1',col: '3'}
},
{
data: { id: 'X2e2',label: 'e2', parent: 'X2', row: '3',col: '1'}
},
{
data: { id: 'X2e3',label: 'e3', parent: 'X2', row: '3',col: '2'}
},
{
data: { id: 'X3e4',label: 'e4', parent: 'X3', row: '4',col: '1'}
},
{
data: { id: 'X4e5',label: 'e5', parent: 'X4', row: '2',col: '1'}
},
{
data: { id: 'X4e6',label: 'e6', parent: 'X4', row: '2',col: '2'}
},
{
data: { id: 'X5e7',label: 'e7', parent: 'X5', row: '7',col: '1'}
},
{
data: { id: 'X6e8',label: 'e8', parent: 'X6', row: '5',col: '1'}
},
{
data: { id: 'X6e9',label: 'e9', parent: 'X6', row: '5',col: '2'}
},
{
data: { id: 'X7e10',label: 'e10', parent: 'X7', row: '7',col: '2'}
},
{
data: { id: 'X7e11',label: 'e11', parent: 'X7', row: '7',col: '3'}
},
{
data: { id: 'X7e12',label: 'e12', parent: 'X7', row: '7',col: '4'}
},
{
data: { id: 'X8e13',label: 'e13', parent: 'X8', row: '6',col: '1'}
},
{
data: { id: 'X8e14',label: 'e14', parent: 'X8', row: '6',col: '2'}
},
{
data: { id: 'X8e15',label: 'e15', parent: 'X8', row: '6',col: '3'}
},
{
data: { id: 'X8e16',label: 'e16', parent: 'X8', row: '6',col: '4'}
},
{
data: { id: 'X9e17',label: 'e17', parent: 'X9', row: '8',col: '1'}
},
{
data: { id: 'X10e18',label: 'e18', parent: 'X10', row: '8',col: '2'}
},
{
data: { id: 'X1e5X4e5', source:'X1e5', target:'X4e5'}
},
{
data: { id: 'X1e6X4e6', source:'X1e6', target:'X4e6'}
},
{
data: { id: 'X1e1X2', source:'X1e1', target:'X2'}
},
{
data: { id: 'X2e3X3', source:'X2e3', target:'X3'}
},
{
data: { id: 'X4e5X5', source:'X4e5', target:'X5'}
},
{
data: { id: 'X4e6X6', source:'X4e6', target:'X6'}
},
{
data: { id: 'X6X8e16', source:'X6', target:'X8e16'}
},
{
data: { id: 'X6e9X8', source:'X6e9', target:'X8'}
},
{
data: { id: 'X6e8X7', source:'X6e8', target:'X7'}
},
{
data: { id: 'X6X7e12', source:'X6', target:'X7e12'}
}
]
and layout
layout:{
name: 'grid',
fit: true,
position: function( node ){ return {row:node.data('row'), col:node.data('col') }}
}
And here is the result I got(and also expected) by setting manual rows and columns
Any help would be appreciated. Thanks
Well there are two extensions, which would achieve just what you need:
even-parents
no-overlap
Coincidentally, both come from the same person, so this should not be a problem at all, all you have to do from there is to apply the right styles for the application to look like your example:
document.addEventListener("DOMContentLoaded", function() {
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
layout: {
name: "evenParent"
},
style: [{
selector: "node",
style: {
"content": "data(id)",
"background-color": "#ad1a66"
}
},
{
selector: ":parent",
style: {
"background-opacity": 0.333
}
},
{
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: {
nodes: [{
data: {
id: "Jerry",
name: "Jerry"
}
},
{
data: {
id: "Elaine",
name: "Elaine"
}
},
{
data: {
id: "Kramer",
name: "Kramer"
}
},
{
data: {
id: "George",
name: "George"
}
},
{
data: {
id: "Martin",
name: "Martin"
}
},
{
data: {
id: "Philippe",
name: "Philippe"
}
},
{
data: {
id: "Louis",
name: "Louis"
}
},
{
data: {
id: "Genevieve",
name: "Genevieve"
}
},
{
data: {
id: "Leo",
name: "Leo"
}
},
{
data: {
id: "Larry",
name: "Larry"
}
},
{
data: {
id: "Logaina",
name: "Logaina"
}
}
],
edges: [{
data: {
source: "Jerry",
target: "Elaine"
}
},
{
data: {
source: "Jerry",
target: "Kramer"
}
},
{
data: {
source: "Jerry",
target: "George"
}
},
{
data: {
source: "Elaine",
target: "Martin"
}
},
{
data: {
source: "Elaine",
target: "Philippe"
}
},
{
data: {
source: "Elaine",
target: "Louis"
}
},
{
data: {
source: "Elaine",
target: "Genevieve"
}
},
{
data: {
source: "Elaine",
target: "Leo"
}
},
{
data: {
source: "Kramer",
target: "Larry"
}
},
{
data: {
source: "Kramer",
target: "Logaina"
}
}
]
}
}));
// demo your collection ext
cy.nodes().noOverlap({
padding: 5
});
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
}
<html>
<head>
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-even-parent#1.1.1/cytoscape-even-parent.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-no-overlap#1.0.1/cytoscape-no-overlap.min.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>
The following code worked for me,
Please consider #Stephen's code if this doesn't work.
var cy = cytoscape({
container: /* your div within which you want to render */ ,
elements: [ /*list of graph elements to start with */ ] ,
style: [ /* the stylesheet for the graph */ ] ,
layout:{
name: 'cola',
fit: false,
infinite: false,
avoidOverlap: true
}
});
//Used to make child nodes stay on the same row
cy.ready(function(){
setTimeout(function(){
cy.zoom(0.5);
cy.nodes(':compound').forEach(function(comp,j,comps){
var nodePosition={};
if(comp.descendants().length>1)
{
var minX;
var maxY;
comp.descendants().forEach(function(ele,i,eles){
if(i==0)
{
minX=ele.renderedPosition('x');
maxY=ele.renderedPosition('y');
}
else
{
var tempX=ele.renderedPosition('x');
var tempY=ele.renderedPosition('y');
if(tempX<minX)
{
minX=tempX;
}
if(tempY>maxY)
{
maxY=tempY;
}
}
});
comp.descendants().forEach(function(ele,i,eles){
ele.renderedPosition({x:minX,y:maxY});
minX=minX+60;
});
}
cy.resize();
cy.fit();
cy.minZoom(cy.zoom());
});
},1000);
});
P.S: Don't forget to include the cytoscape scripts;)
I have an undirected graph like this :
var cy = cytoscape({
container: document.getElementById('cy'),
elements: [
// nodes
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{ data: { id: 'c' } },
{ data: { id: 'd' } },
// edges
{
data: {
id: 'ab',
source: 'a',
target: 'b'
}
},
{
data: {
id: 'bc',
source: 'b',
target: 'c'
}
},
{
data: {
id: 'cd',
source: 'c',
target: 'd'
}
}
],
style: [
{
selector: 'node',
style: {
shape: 'hexagon',
'background-color': 'red',
label: 'data(id)'
}
}],
layout: {
name: 'grid'
}
});
As you can see you can go to d from a by using c and d. What I'm trying to achieve is if the edge ab is highlighted, the edges bc and cd will also be highlighted. Is there a way to do this? I couldn't find any source on this on the Internet... Thanks.
Have a look at the included algorithms like Floyd-Warshall or Dijkstra. From their output you should be able to generate highlighting for relevant edges.
Ex.
var dijkstra = cy.elements().dijkstra('#an_id')
var path = dijkstra.pathTo( cy.elements('node#another_id'))
path.forEach(function(ele){
if (ele.isEdge()){
ele.addClass("class_that_has_highlight_styling")
}
})
I just modified a sample from the cytoscape.js ("Animated BFS") and added some nodes:
$(function(){ // on dom ready
$('#cy').cytoscape({
style: cytoscape.stylesheet()
.selector('node')
.css({
'content': 'data(id)',
'background-color': 'data(myColor)'
})
.selector('edge')
.css({
'target-arrow-shape': 'triangle',
'width': 2,
'line-color': '#ddd',
'target-arrow-color': '#ddd'
}),
elements: {
nodes: [
{ data: { id: 'a', myColor: '#035530', addinf: 'sometext' } },
{ data: { id: 'b' } },
{ data: { id: 'c' } },
{ data: { id: 'd' } },
{ data: { id: 'e' } },
{ data: { id: 'ABC', myColor: '#CBB089' } },
{ data: { id: 'DEF', myColor: '#C0E234' } },
{ data: { id: 'GHI', myColor: '#C0E234' } },
{ data: { id: 'JKL', myColor: '#C0E234' } },
{ data: { id: 'MNO', myColor: '#C0E234' } },
{ data: { id: 'PQR', myColor: '#C0E234' } },
{ data: { id: 'STR', myColor: '#C0E234' } },
{ data: { id: 'ZXY', myColor: '#C0E234' } }
],
edges: [
{ data: { id: 'a"e', weight: 2, 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' } },
{ data: { id: 'S', source:'a', target: 'ABC'}},
{ data: { id: '1', source:'a', target: 'DEF'}},
{ data: { id: '2', source:'b', target: 'GHI'}},
{ data: { id: '3', source:'e', target: 'JKL'}},
{ data: { id: '4', source:'c', target: 'MNO'}},
{ data: { id: '5', source:'d', target: 'PQR'}},
{ data: { id: '6', source:'a', target: 'STR'}},
{ data: { id: '7', source:'ABC', target: 'ZXY'}}
]
},
layout: {
name: 'breadthfirst',
directed: true,
fit: true,
maximalAdjustments: 10,
nodeRepulsion : 1000,
nodeOverlap : 10,
roots: '#a',
padding: 10
},
hideEdgesOnViewport: true,
ready: function(){
window.cy = this;
}
});
}); // on dom ready
And this is the resulting graph that I obtained (manually curated):
)
So I just want to select for example the node 'a' ( by clicking it or by user input and save it in a variable) and all of the childs should remain displayed, but all the others should temporarily dissapear. Remaining with a photo like this:
In Cytoscape.js how could I do that:
Select the node 'a' and get all the childs to remain displayed, while the others dissapear.
And if I would want the child + the grandchild of 'a' the commands will be different?
Also, my last question: in node 'a' I have some additional information about the node "addinf: 'sometext'", how could it be also be displayed when the node is clicked?
Thanks in advance!
1 and 2. I had never needed to manipulate children, but I think it's possible to do that by using the eles.depthFirstSearch() function; you can set depth as 1 to get direct children, or any amount as you want. Check this documentation.
3. Personally, I prefered to create a function named clickInNode(), where I could do some stuff when a node is selected. Then, to call it I use:
var nodeClicked = cy.on('tap', 'node', function(e) {
clickInNode(e.cyTarget);
});
If you have a textbox with ID nodedata and wants to fill it with the addinf data from node, then you can use:
$('#nodedata').val(node.data('addinf'));
inside your clickInNode() function.
Hope I've been helpful/clear enough.
If you use directed eles.bfs(), you can build an array of visited elements -- perhaps limiting list based on depth.
D3 has a variety of layouts for directed graphs that are strict trees, such as the following:
A
|\
B C
/ \
D E
I need to draw a hierarchy of nodes that is not a tree, but is a directed acyclic graph. This is a problem for a tree layout, because several of the branches converge:
A
|\
B C
\|
D
Does anyone know of a D3 layout for general hierarchies? Or alternatively, some clever hack to the existing treelayout? I've noticed GraphVis handles this situation well, but D3 produces a graph that better suits the requirements here.
You could create your own code without having to rely on a D3 layout in order to get it done.
I've provided an example in a jsFiddle. The example is pretty simplistic and would need to be worked a little bit to accommodate more complex examples.
The example could be re-worked to process hierarchical data as well with relatively little effort.
Here is the code I have used in the jsFiddle:
// Sample data set
var json = {
nodes: [{
name: 'A'},
{
name: 'B'},
{
name: 'C'},
{
name: 'D'}],
links: [{
source: 'A',
target: 'B'},
{
source: 'A',
target: 'C'},
{
source: 'B',
target: 'D'},
{
source: 'C',
target: 'D'}
]
};
var vis = d3.select('#vis').attr('transform', 'translate(20, 20)');
// Build initial link elements - Build first so they are under the nodes
var links = vis.selectAll('line.link').data(json.links);
links.enter().append('line').attr('class', 'link').attr('stroke', '#000');
// Build initial node elements
var nodes = vis.selectAll('g.node').data(json.nodes);
nodes.enter().append('g').attr('class', 'node').append('circle').attr('r', 10).append('title').text(function(d) {
return d.name;
});
// Store nodes in a hash by name
var nodesByName = {};
nodes.each(function(d) {
nodesByName[d.name] = d;
});
// Convert link references to objects
links.each(function(link) {
link.source = nodesByName[link.source];
link.target = nodesByName[link.target];
if (!link.source.links) {
link.source.links = [];
}
link.source.links.push(link.target);
if (!link.target.links) {
link.target.links = [];
}
link.target.links.push(link.source);
});
// Compute positions based on distance from root
var setPosition = function(node, i, depth) {
if (!depth) {
depth = 0;
}
if (!node.x) {
node.x = (i + 1) * 40;
node.y = (depth + 1) * 40;
if (depth <= 1) {
node.links.each(function(d, i2) {
setPosition(d, i2, depth + 1);
});
}
}
};
nodes.each(setPosition);
// Update inserted elements with computed positions
nodes.attr('transform', function(d) {
return 'translate(' + d.x + ', ' + d.y + ')';
});
links.attr('x1', function(d) {
return d.source.x;
}).attr('y1', function(d) {
return d.source.y;
}).attr('x2', function(d) {
return d.target.x;
}).attr('y2', function(d) {
return d.target.y;
});
As this example: "Force Directed Trees" illustrates there is a trick that often works. In the example the force direction's behavior is adjusted on each tick so that nodes drift slightly up or down depending on the direction of the links. As shown this will do a fine job for trees, but I've found it also works tolerable well for acyclic graphs. No promises, but it may help.
Speaking generally of trees and the data hierarchy, you just need to have "D" in the children list for both B and C.
Creating your node list, make sure you have a unique id returned so that "D" doesn't appear twice.
vis.selectAll("g.node").data(nodes, function(d) { return d.id; });
Then when you call
var links = tree.links(nodes)
you should get D appearing as the "target" twice (with B and C as the "source" respectively) which results in two lines to the single node "D".
I was able to do this using a combination of Dagre(https://github.com/dagrejs/dagre) and cytoscape
There's a library called Dagre-D3 that you can use as a renderer for this library so it looks like the D3 solution you want.
Check out this fiddle to see the basic implementation with Dagre and Cytoscape: https://jsfiddle.net/KateJean/xweudjvm/
var cy = cytoscape({
container: document.getElementById('cy'),
elements: {
nodes: [
{ data: { id: '1' } },
{ data: { id: '2' } },
{ data: { id: '3' } },
{ data: { id: '4' } },
{ data: { id: '5' } },
{ data: { id: '6' } },
{ data: { id: '7' } },
{ data: { id: '8' } },
{ data: { id: '9' } },
{ data: { id: '10' } },
{ data: { id: '11' } },
{ data: { id: '12' } },
{ data: { id: '13' } },
{ data: { id: '14' } },
{ data: { id: '15' } },
{ data: { id: '16' } },
{ data: { id: '17' } },
{ data: { id: '18' } }
],
edges: [
{ data: { source: '1', target: '2' } },
{ data: { source: '1', target: '3' } },
{ data: { source: '2', target: '4' } },
{ data: { source: '4', target: '5' } },
{ data: { source: '4', target: '6' } },
{ data: { source: '5', target: '6' } },
{ data: { source: '5', target: '7' } },
{ data: { source: '7', target: '8' } },
{ data: { source: '3', target: '9' } },
{ data: { source: '3', target: '10' } },
{ data: { source: '10', target: '11' } },
{ data: { source: '11', target: '12' } },
{ data: { source: '12', target: '13' } },
{ data: { source: '12', target: '14' } },
{ data: { source: '14', target: '15' } },
{ data: { source: '15', target: '16' } },
{ data: { source: '16', target: '17' } },
{ data: { source: '16', target: '18' } }
]
},
layout: {
name: "dagre",
rankDir: 'TB' //love this. you can quickly change the orientation here from LR(left to right) TB (top to bottom), RL, BT. Great dropdown option for users here.
},
style: [{
selector: 'node',
style: {
'label': 'data(id)',
'width': '30%',
'font-size': '20px',
'text-valign': 'center',
'shape': 'circle',
'background-color': 'rgba(113,158,252,1)',
'border': '2px grey #ccc'
}
}, {
selector: 'edge',
style: {
'width': 2,
'line-color': '#ccc',
'target-arrow-color': '#ccc',
'target-arrow-shape': 'triangle'
}
}]
});