Cytoscape style definitions not taken into account for selectors - cytoscape.js

Building a little prototype for experimenting compound graph editing, I've been facing some issues for application of styles for various selectors: they are not considered for the rendering, and I've no idea why.
Here is a capture of the outcome
Here is the HTML page and the code I used.
...
<!DOCTYPE>
<html>
<head>
<title>Test Compound Graph Editor</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<script src="https://unpkg.com/cytoscape#3.21.0/dist/cytoscape.min.js"></script>
<script src="https://unpkg.com/layout-base#2.0.1/layout-base.js"></script>
<script src="https://unpkg.com/cose-base#2.1.0/cose-base.js"></script>
<script src="https://unpkg.com/cytoscape-fcose#2.1.0/cytoscape-fcose.js"></script>
<script src="https://unpkg.com/cytoscape-expand-collapse#4.1.0/cytoscape-expand-collapse.js"></script>
<script src="https://unpkg.com/pure-uuid#1.6.2/uuid.js"></script>
</head>
<body>
<div id="cy" style="width:100%;height:90%;background-color:white"></div>
<div class="menu-container">
<div style="text-align:center" class="panel">
<button id="compound">Add Compound Node</button>
<button id="collapseAll">Collapse all</button>
<button id="collapseRecursively">Collapse Recursively</button>
<button id="expandAll">Expand all</button>
<button id="expandRecursively">Expand Recursively</button>
</div>
</div>
</body>
<script>
var myData = [
{ data: { id: "o", "label": "Top node" } },
{ data: { id: "a", parent: "o", "label": "a" } },
{ data: { id: "b", parent: "o" } },
{ data: { id: "c", parent: "o" } },
{ data: { id: "d", parent: "o" } },
{ data: { id: "e", parent: "o" } },
{ data: { id: "f", parent: "o" } },
{ data: { id: "a1", parent: "a" } },
{ data: { id: "b1", parent: "b" } },
{ data: { id: "c1", parent: "c" } },
{ data: { id: "d1", parent: "d" } },
{ data: { id: "e1", parent: "e" } },
{ data: { id: "f1", parent: "f" } },
{ data: { id: "a2", parent: "a" } },
{ data: { id: "b2", parent: "b" } },
{ data: { id: "c2", parent: "c" } },
{ data: { id: "d2", parent: "d" } },
{ data: { id: "e2", parent: "e" } },
{ data: { id: "f2", parent: "f" } },
{ data: { id: "a3", parent: "a" } },
{ data: { id: "b3", parent: "b" } },
{ data: { id: "c3", parent: "c" } },
{ data: { id: "d3", parent: "d" } },
{ data: { id: "e3", parent: "e" } },
{ data: { id: "f3", parent: "f" } },
{ data: { id: "a4", parent: "a" } },
{ data: { id: "b4", parent: "b" } },
{ data: { id: "c4", parent: "c" } },
{ data: { id: "d4", parent: "d" } },
{ data: { id: "e4", parent: "e" } },
{ data: { id: "f4", parent: "f" } },
{ data: { id: "a5", parent: "a" } },
{ data: { id: "b5", parent: "b" } },
{ data: { id: "c5", parent: "c" } },
{ data: { id: "d5", parent: "d" } },
{ data: { id: "e5", parent: "e" } },
{ data: { id: "f5", parent: "f" } },
{ data: { id: "a6", parent: "a" } },
{ data: { id: "b6", parent: "b" } },
{ data: { id: "c6", parent: "c" } },
{ data: { id: "d6", parent: "d" } },
{ data: { id: "e6", parent: "e" } },
{ data: { id: "f6", parent: "f" } }
];
var cy = window.cy = cytoscape({
container: document.getElementById("cy"),
ready: function () {
this.layout({
name: "fcose",
randomize: true,
fit: true,
animate: true,
nodeDimensionsIncludeLabels: true,
condense: false
}).run(20, 0, 10);
var api = this.expandCollapse({
layoutBy: {
name: "fcose",
animate: true,
randomize: false,
fit: true,
nodeDimensionsIncludeLabels: true,
condense: false
},
fisheye: true,
animate: true,
undoable: false
});
api.expandAll({
layoutBy: {
name: "fcose",
animate: true,
randomize: false,
fit: true,
nodeDimensionsIncludeLabels: true,
condense: false
},
fisheye: true,
animate: true,
undoable: false,
nodeDimensionsIncludeLabels: true,
condense: false
});
},
elements: myData,
style: [
{
selector: "node",
style: {
"label": (ele) => ele.data("label"),
"color": "blue",
"shape": "diamond"
}
},
{
selector: "node.cy-expand-collapse-collapsed-node",
style: {
"shape": "rectangle",
"border-color":"black",
"border-width":3,
"color":"blue"
}
},
{
selector: ":selected",
style: {
"overlay-color": "green",
"overlay-opacity": 0.3,
'background-color': 'green',
'shape': 'rectangle',
}
},
{
selector: ":parent",
style: {
"background-color": "yellow",
"z-compound-depth": "auto",
shape: "rectangle"
}
},
{
selector: "edge",
style: {
"target-arrow-shape": "triangle",
"target-endpoint": "outside-to-node",
"target-arrow-color": "lightgreen",
"line-color": "green",
"target-distance-from-node": 0
}
}
],
layout: { name: "fcose" },
style: [
{
selector: "edge",
style: {
"curve-style": "bezier",
"target-arrow-shape": "triangle"
}
}
],
wheelSensitivity: 0.5
});
var api = cy.expandCollapse("get");
document.getElementById("collapseAll").addEventListener("click", function () {
api.collapseAll();
});
document.getElementById("expandAll").addEventListener("click", function () {
api.expandAll();
});
document
.getElementById("collapseRecursively")
.addEventListener("click", function () {
api.collapseRecursively(cy.$(":selected"));
});
document
.getElementById("expandRecursively")
.addEventListener("click", function () {
api.expandRecursively(cy.$(":selected"));
});
document.getElementById("compound").addEventListener("click", function () {
var selection = cy.nodes(":selected");
if (selection.length < 1) {
return;
}
var parent = selection[0].parent().id();
for (let i = 1; i < selection.length; i++) {
if (parent !== selection[i].parent().id()) {
return;
}
}
var myUUID = new UUID(4);
const parentNode = {
data: { id: `${myUUID}`, parent: parent, label: "label" }
};
cy.add(parentNode);
for (let i = 0; i < selection.length; i++) {
selection[i].move({ parent: `${myUUID}` });
}
api.expandAll();
});
</script>
</html>
...
In this code, you can see the created data :mainly a set of compound nodes (no edges). When opening the page, the graph is displayed, by the labels don't appear. Idem for colors or shapes indicated, they are not taken into account.
I just can't identify why such a behavior.
Any idea?

Related

Generate highcharts from v-for not populating all graphs with data

I'm working with highcharts to generate some polar graphs. https://codesandbox.io/s/vue-template-nibjp?file=/src/App.vue
The idea is that once the user presses update, a new array is pulled and it generates an n amount of charts based on that.
This is the code for the chart:
<template>
<div>
<highcharts :options="chartOptions" ref="lineCharts" :constructor-type="'chart'"></highcharts>
</div>
</template>
<script>
import {Chart} from 'highcharts-vue'
import Highcharts from "highcharts";
import HighchartsMore from "highcharts/highcharts-more";
HighchartsMore(Highcharts);
export default {
props:["options"],
components: {
highcharts: Chart
},
watch: {
options: {
handler(newOpt) {
this.chartOptions.series = newOpt.series
},
deep: true
}
},
data() {
return {
chartOptions: Highcharts.merge(this.options, {
chart: {
polar: true,
},
tooltip: {
},
title: {
text: null
},
subtitle: {
text:null
},
legend: {
enabled: false
},
credits: {
enabled: false
},
lang: {
noData: null
},
exporting: {
buttons: {
contextButton: {
theme: {
fill: "#F5F5F5"
}
}
}
},
pane: {
startAngle: -60,
size: '100%'
},
xAxis: {
tickPositions:[0,120,240],
min: 0,
max: 360,
labels: false
},
yAxis: {
max: 10,
tickInterval:2,
labels: false
},
plotOptions: {
series: {
grouping: true,
groupPadding:0,
pointPadding:0,
borderColor: '#000',
borderWidth: '0',
stacking: 'normal',
pointPlacement: 'between',
showInLegend: true
},
column: {
pointPadding: 0,
groupPadding: 0
}
},
series: [
{
data: [],
type: 'column',
name: 'On-time',
color: "#1DACE8",
pointStart: 0,
pointRange: 120
},
{
data: [],
type: 'column',
name: 'Fare',
color: "#EDCB64",
pointStart: 120,
pointRange: 120
},
{
data: [],
type: 'column',
name: 'Route Share',
color: "#F24D29",
pointStart: 240,
pointRange: 120
}
]
})
}}
}
</script>
And the code for the page:
<template>
<div id="app">
<button #click="updateChart">Update</button>
<v-item-group>
<v-container>
<v-row>
<v-col v-for="(chart, index) in chartSet" :key="index" cols="2">
<highcharts v-bind:options="chart"/>
</v-col>
</v-row>
</v-container>
</v-item-group>
</div>
</template>
<script>
import Chart from "./components/Chart";
var newSet = [
{ series: [{ data: [8] }, { data: [2] }, { data: [10] }] },
{ series: [{ data: [4] }, { data: [6] }, { data: [3] }] },
{ series: [{ data: [2] }, { data: [3] }, { data: [1] }] }
];
export default {
name: "App",
components: {
highcharts: Chart
},
data() {
return {
chartSet: [
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] }
]
};
},
methods: {
updateChart() {
this.chartSet = newSet;
}
}
};
</script>
<style>
</style>
The problem I'm having is the following. If the chartSet is bigger than the newSet, and the newSet (has data for 3 charts), all works well and the charts are populated as they should.
chartSet: [
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] }
]
newSet = [
{ series: [{ data: [8] }, { data: [2] }, { data: [10] }] },
{ series: [{ data: [4] }, { data: [6] }, { data: [3] }] },
{ series: [{ data: [2] }, { data: [3] }, { data: [1] }] }
];
If the chartSet is smaller than the newSet, then only the first charts are populated.
chartSet: [
{ series: [{ data: [] }, { data: [] }, { data: [] }] }
]
newSet = [
{ series: [{ data: [8] }, { data: [2] }, { data: [10] }] },
{ series: [{ data: [4] }, { data: [6] }, { data: [3] }] },
{ series: [{ data: [2] }, { data: [3] }, { data: [1] }] }
];
The problem is that I don't know in advance how large the newSet will be as this depends on user input. So I could create a chartSet with 20 entries but then my page would be populated with empty graphs until the user presses update and that doesn't look ok. How can I start with only one graph and populate all the rest on demand?
To achieve it you have to update only series data and not the whole series object:
watch: {
options: {
handler(newOpt) {
const newChartSeries = [];
this.chartOptions.series.forEach(function(series, i) {
newChartSeries.push(Highcharts.merge(
series,
newOpt.series[i]
));
});
this.chartOptions.series = newChartSeries;
},
deep: true
}
},
created: function() {
const newChartSeries = [];
const options = this.options;
this.plotOptions.series.forEach(function(series, i) {
newChartSeries.push(Highcharts.merge(
series,
options.series[i]
));
});
this.chartOptions = Highcharts.merge(this.plotOptions, {
series: newChartSeries
});
},
data() {
return {
plotOptions: {
...
plotOptions: {
series: {
grouping: true,
groupPadding: 0,
pointPadding: 0,
borderColor: '#000',
borderWidth: '0',
stacking: 'normal',
pointPlacement: 'between',
showInLegend: true
},
column: {
pointPadding: 0,
groupPadding: 0
}
},
series: [{
data: [],
type: 'column',
name: 'On-time',
color: "#1DACE8",
pointStart: 0,
pointRange: 120
}, {
data: [],
type: 'column',
name: 'Fare',
color: "#EDCB64",
pointStart: 120,
pointRange: 120
}, {
data: [],
type: 'column',
name: 'Route Share',
color: "#F24D29",
pointStart: 240,
pointRange: 120
}]
},
chartOptions: null
}
}
Demo:
https://codesandbox.io/s/vue-template-1gyhw

Filtering graph by connectivity to a specific node in cytoscape.js

New to Cytoscape. I have a graph with dominant main network and some smaller networks unconnected to the main one that I want to remove. Looking through the documentation I can't see an obvious solution to this. I'm guessing maybe a custom approach is required that loops through all nodes, checks their graph distance from the most central node in the main cluster, and if this distance is undefined remove that node and all others it does connect to. But keen to get a steer from others with more experience with the library. Any advice is much appreciated. I note this unanswered but related question.
Here is an example graph. Though I can't get working on jsfiddle here is working version:
<!DOCTYPE>
<html>
<head>
<title>cytoscape-dagre.js demo</title>
<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/dagre#0.7.4/dist/dagre.js"></script>
<script src="cytoscape-dagre.js"></script>
<style>
#cy {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 999;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', function(){
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
boxSelectionEnabled: false,
autounselectify: true,
layout: {
name: 'dagre'
},
style: [
{
selector: 'node',
style: {
'background-color': '#11479e'
}
},
{
selector: 'edge',
style: {
'width': 4,
'target-arrow-shape': 'triangle',
'line-color': '#9dbaea',
'target-arrow-color': '#9dbaea',
'curve-style': 'bezier'
}
}
],
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: 'n4', target: 'n5' } },
{ data: { source: 'n4', target: 'n6' } },
{ data: { source: 'n6', target: 'n7' } },
{ 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' } },
]
}
});
});
</script>
</head>
<body>
<h1>cytoscape-dagre demo</h1>
<div id="cy"></div>
</body>
</html>
You can do this with the filtering methods provided in the docs, if you find some method better suited for this problem, just fiddle around with them until you get the right result. The important part here is the .union() and the .not() method. You can use these to:
traverse the graph in a controlled way, saving the important nodes on the way
and then filter the collection to fit your needs
You mentioned not being able to get the jsfiddle to work, you can test the code below in here
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"
}
}
],
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: "n4",
target: "n5"
}
},
{
data: {
source: "n4",
target: "n6"
}
},
{
data: {
source: "n6",
target: "n7"
}
},
{
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', 'node', function(event) {
// .union() takes two collections and adds both together without duplicates
var connected = event.target
connected = connected.union(event.target.predecessors())
connected = connected.union(connected.successors())
// in one line:
// event.target.union(event.target.predecessors().union(event.target.successors()))
// .not() filters out whatever is not specified in connected, e.g. every other node/edge not present in connected
var notConnected = cy.elements().not(connected)
// if you want, you can later add the saved elements again
var saved = cy.remove(notConnected)
});
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>

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

Cytoscape remove element layout animation

Is there a way to rerender the layout while ignoring some nodes which will be removed with custom animation?
cy.layout(
{
name: "dagre",
rankDir: "LR",
spacingFactor: 1,
animate: true,
animationDuration: 5000,
rankSep: 200,
}).run();
setTimeout(() => {
cy.elements()[2].animate(
{
style: {
opacity: 0
}
},
{
duration: 5000
);
}, 5000);
cy.layout(
{
name: "dagre",
rankDir: "LR",
spacingFactor: 1,
animate: true,
animationDuration: 5000,
rankSep: 200,
}).run();
I want to filter the node that is animating while I rerender the layout excluding this node
First things first, you can always run layouts on subsets of the elements by calling the layout function on the desired collection:
cy.elements().layout({...})
Also, you can filter out specific elements by asserting a difference between two collections:
cy.elements().not(filterOutElements)
If you put that together, you should get something like this:
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: {
"target-arrow-shape": "triangle"
}
}
],
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.elements().layout({
name: "dagre",
rankDir: "LR",
spacingFactor: 1,
animate: true,
animationDuration: 5000,
rankSep: 200
}).run();
setTimeout(() => {
cy.elements()[0].animate({
style: {
opacity: 0
}
}, {
duration: 5000
});
cy.elements().not(cy.elements()[0]).layout({
name: "dagre",
spacingFactor: 1,
animate: true,
animationDuration: 5000,
rankSep: 200
}).run();
});
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>

With cytoscape.js how to automatically expand the height of the container of a graph?

Is it possible to automatically expand the height of the container of a graph, according to the size of the graph?
I want to display lots of graphs of different sizes on a web page, without having to specify the height of each DOM container.
Does it make sense?
Answer:
It is not the best solution to change anything regarding the container of cytoscape.
Alternative for you:
It is far better to work with the padding of the graph in order to display the graph in a dynamic/suitable way. The easiest way to do that, is to add your nodes and run your layout and then, when the layout is loaded, you can call cy.fit(cy.elements(), yourPadding).
In cytoscape, padding is handled exactly the same way as in css, so you just calculate (based on the number of nodes maybe?) the appropriate padding for your graph and fit it to the viewport.
Additionally, you can always wrap things up with a slick ease-in animation to top things off.
Code:
Snippet works when you click on edit the above snippet and run it in the snippet editor, here the function does not work due to the size limitation and I can't be bothered to adjust it without seeing the outcome in the editor.
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
boxSelectionEnabled: false,
autounselectify: true,
style: [
{
selector: "node",
css: {
content: "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"
}
},
{
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.unbind("click");
cy.bind("click", "node", function(event) { // just for demonstration purposes here
var coll = cy.$(event.target).successors(); // get all outgoing nodes
coll = coll.add(event.target); // add their source
var removed = cy.remove(cy.elements().not(coll)); // remove all other elements
var len = cy.nodes().length;
var pad = (len < 10 ? (len < 5 ? (len < 3 ? (len < 2 ? 150 : 100 ) : 75 ) : 50 ) : 25); // custom padding function here
cy.animate({
fit: {
eles: cy.elements(),
padding: pad
}
}, {
duration: 500,
easing: 'ease-in'
});
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
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>