Modest sized graph renders with a strange jump - cytoscape.js

I find cytoscape.js renders my graph strangely - first it runs through several seconds of force-directed adjustment, and then the nodes all suddenly snap to what looks like maybe a separate layout. It is a big jump and not a smooth process. This illustrates:
index.html
<!doctype html>
<html>
<head>
<title>Cytoscape issue</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<link type="text/css" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" />
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.9.4/cytoscape.umd.js'></script>
</head>
<style>
#cy {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
}
#advance {
width: 20%;
height: 10%;
position: absolute;
top: 5%;
right: 5%;
}
#loading {
position: absolute;
display: block;
left: 0;
top: 50%;
width: 100%;
text-align: center;
margin-top: -0.5em;
font-size: 2em;
color: #000;
}
#loading.loaded {
display: none;
}
</style>
<body>
<div id="cy"></div>
<div id="loading"><span class="fa fa-refresh fa-spin"></span></div>
<script>
var d;
document.addEventListener("DOMContentLoaded", function() {
$.getJSON("https://gist.githubusercontent.com/geotheory/3b8cf288c5f7b84fa8635e3dc9171ab8/raw/ff4e93e8cd727930624da3a1e910ebd6844caeff/graph-data.json", function(json){
d = json;
var cy = cytoscape({
container: document.getElementById('cy'),
elements: json,
style: [{
selector: 'node',
style: {
// 'label': 'none',
'width': '10px',
'height': '10px',
'color': 'blue',
'font-size': '8px',
'text-halign': 'right',
'text-valign': 'center',
'background-opacity': 1,
'background-image': 'none',
'background-fit': 'contain',
'background-clip': 'none'
}
}, {
selector: 'edge',
style: {
'curve-style': 'bezier',
'opacity': 1,
'width': '1px',
'target-arrow-shape': 'none',
'arrow-scale': 0,
'control-point-step-size': '1px'
}
}],
layout: {
name: 'cose',
fit: true,
avoidOverlap: true,
avoidOverlapPadding: 10
},
hideEdgesOnViewport: true
});
cy.center();
var loading = document.getElementById('loading');
loading.classList.add('loaded');
});
console.log('done');
});
</script>
</body>
</html>
The force-directed adjustment stage:
Final layout:
Any suggestions what I'm doing to cause this?

I think it's not your fault. It is more related with the cose layout while animate option is true. If in-between iterations are not so important, you can use animate: 'end' or animate: false options.

Related

Is there a way to display a node label at the top inside the node?

I am creating a graph using cytoscape.js and I have compounded nodes which are inside the parent node. I would like to have the title of the main/parent node at the top of the node but inside the node. Is that possible in cytoscape?
I have tried using using halign and valign. Whenever I use top value, it shows outside the box.
Is there a extension or a plugin that lets us do it?
Example with child node: https://stackblitz.com/edit/cytoscape-call-method-child-efmbaj?file=src%2Fapp%2FstylesheetObject.ts
As you can read here, you can only place labels inside a node with the center option, a good configuration (label inside at the top) requires a margin to be added to your label:
.selector(':parent')
.css({
'text-valign': 'center',
// the next line moves the parents label up to the top of the node and 5px down to create a padding
'text-margin-y': function (node) { return -node.height() + 5 }
})
Here is a working example:
var cy = cytoscape({
container: document.getElementById('cy'),
style: cytoscape.stylesheet()
.selector(':parent')
.css({
'text-valign': 'center',
'text-margin-y': function(node) {
return -node.height() + 5
}
})
.selector('node')
.css({
'height': 'data(size)',
'width': 'data(size)',
'border-color': '#000',
'border-width': '1',
'content': 'data(name)'
})
.selector('edge')
.css({
'width': 'data(strength)'
})
.selector('#1')
.css({
'background-color': 'red'
})
.selector('#4')
.css({
'background-color': 'green'
}),
elements: {
nodes: [{
data: {
id: '1',
size: 50,
name: 'a'
}
},
{
data: {
id: '2',
size: 20,
name: 'b',
parent: '1'
}
},
{
data: {
id: '3',
size: 40,
name: 'c',
parent: '1'
}
},
{
data: {
id: '4',
size: 50,
name: 'd'
}
},
{
data: {
id: '5',
size: 20,
name: 'e',
parent: '4'
}
},
{
data: {
id: '6',
size: 40,
name: 'f',
parent: '4'
}
}
],
},
});
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>
</head>
<body>
<div id="cy"></div>
</body>
</html>
What I understand is that you want to put the parent label inside the box above child? apologies if I'm wrong
If so, My solution will be to add a padding and then apply margin for alignment.
Modified your example
Styles updated:
'padding-top':60
selector: 'node',
css: {
content: 'data(label)',
'text-valign': 'center',
'text-halign': 'center',
'font-size': 28,
'padding-top':60
}
'text-valign': 'top', and 'text-margin-y': function(node) {
return node.height() - 10;
}
selector: 'node[type="parent"]',
style: {
shape: 'rectangle',
'background-color': 'grey',
width: 300,
height: 100,
'font-size': 25.5,
'font-family': 'Lato, Helvetica Neue, Helvetica, Arial, sans-serif',
color: 'black',
'text-valign': 'top',
'text-halign': 'center',
'text-margin-y': function(node) {
return node.height() - 10;
}
}

cytoscape js multiline label - different css rule for second line

I am using a multiline option on a label (using \n to delineate lines) and would like to know if it is possible to use a different font size for the second line (in the example having 'test' be a smaller font size
if you really need this, I would use the cytoscape-node-html-label extension (you can find it here). According to the documentation, you can use this code to get a multiline multistyle label:
document.addEventListener("DOMContentLoaded", function() {
var sampleDataset = [{
group: "nodes",
data: {
id: "16150999",
name: "xps plrmr",
type: 0,
code: "7704322293"
},
classes: "class1"
}];
var mainNodeDiameter = 20;
var otherNodesDiameter = 17;
var cy = (window.cy = cytoscape({
container: document.getElementById("cy"),
minZoom: 0.1,
maxZoom: 3,
zoom: 0.5,
style: [{
selector: "node", // default node style
style: {
width: mainNodeDiameter + "px",
height: mainNodeDiameter + "px",
"overlay-padding": "5px",
"overlay-opacity": 0,
"z-index": 10,
"border-width": 2,
"border-opacity": 0
}
},
{
selector: "node[type=0]",
style: {
"background-color": "#7CACC2"
}
},
{
selector: "edge", // default edge style
style: {
"curve-style": "unbundled-bezier",
"control-point-distance": 30,
"control-point-weight": 0.5,
opacity: 0.9,
"overlay-padding": "3px",
"overlay-opacity": 0,
label: "data(title)",
"font-family": "FreeSet,Arial,sans-serif",
"font-size": 9,
"font-weight": "bold",
"text-background-opacity": 1,
"text-background-color": "#ffffff",
"text-background-padding": 3,
"text-background-shape": "roundrectangle",
width: 1
}
},
{
selector: "node:selected",
style: {
"border-width": 2,
"border-style": "solid",
"border-color": "#3f3f3f",
"border-opacity": 1
}
}
],
layout: {
name: "random"
},
elements: sampleDataset
}));
// add html labels to cytoscape
cy.nodeHtmlLabel([{
query: "node[type=0]",
cssClass: "cy-title",
valign: "top",
valignBox: "top",
tpl: function(data) {
return '<div><p class="cy-title__name">' + data.name + '</p><p class="cy-title__info">' + data.code + "</p></div>";
}
}]);
// fit cy to viewport
cy.ready(function () {
cy.fit();
cy.center();
});
});
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
float: left;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 999;
}
h1 {
opacity: 0.5;
font-size: 1em;
font-weight: bold;
}
p {
margin: 0;
padding: 0;
}
.cy-title {
text-align: center;
font-size: 13px;
width: 130px;
color: #2b2b2b;
background: radial-gradient(#87CeFa, #7B68EE);
text-transform: capitalize;
}
.cy-title__name {
font-size: 1.5em;
}
.cy-title__info {
font-size: 0.9em;
}
<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>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-node-html-label#1.1.3/dist/cytoscape-node-html-label.min.js"></script>
</head>
<body>
<div id="cy"></div>
</body>
</html>
This way, you can use html styling for each label.
The node-html-label is a good one, as Stephen mentioned. You also may be interested in the popper extension:
https://www.npmjs.com/package/cytoscape-popper
The popper extension is a bit more flexible, but the html-label one is a bit more automatic. Take a look at both and see which suits your project best.
Always check the extension list for an up-to-date list: http://js.cytoscape.org/#extensions

Swiper, swiper-slide background color is not correct

I want these three slide background color take turns.
like this: green(1) -> orange(2) -> green(3) - orange(1)...
But current behaviour is not as expected.
How can I fix this?
window.onload = function() {
const defaultOptions = {
speed: 2000,
autoplay: true,
spaceBetween: 4,
direction: 'vertical',
loop: true,
slidesPerView: 'auto',
watchSlidesVisibility: true
};
const swiper = new Swiper('.swiper-container', defaultOptions)
}
.swiper-container{
height: 52px;
}
.swiper-slide{
display: inline-block;
width: auto;
height: 26px;
max-width: 100%;
padding: 0 10px;
}
<script src="https://cdn.staticfile.org/Swiper/3.4.2/js/swiper.js"></script>
<link href="https://cdn.staticfile.org/Swiper/3.4.2/css/swiper.css" rel="stylesheet"/>
<div class='swiper-container'>
<div class='swiper-wrapper'>
<div class='swiper-slide' style='background: green'>message 1</div>
<div class='swiper-slide' style='background: orange'>message 2</div>
<div class='swiper-slide' style='background: green'>message 3</div>
</div>
</div>
You can set the background color on alternate slides but the real trick is toggling them when the duplicate slides are regenerated for the looping functionality.
I've modified your supplied code with a variable to track progress and a test condition when the slides begin to change.
window.onload = function() {
var lastIndex = 0;
const defaultOptions = {
speed: 2000,
autoplay: true,
spaceBetween: 4,
direction: 'vertical',
loop: true,
slidesPerView: 'auto',
watchSlidesVisibility: true,
onSlideNextStart: function(swiperObj) {
if ( swiperObj.activeIndex < lastIndex ) {
swiperObj.container[0].classList.toggle('alt-bg');
}
lastIndex = swiperObj.activeIndex;
}
};
const swiper = new Swiper('.swiper-container', defaultOptions)
}
.swiper-container{
height: 52px;
}
.swiper-slide{
display: inline-block;
width: auto;
height: 26px;
max-width: 100%;
padding: 0 10px;
background: green;
}
.swiper-slide:nth-child(2n+1){
background: orange;
}
.alt-bg .swiper-slide{
background: orange;
}
.alt-bg .swiper-slide:nth-child(2n+1){
background: green;
}
<script src="https://cdn.staticfile.org/Swiper/3.4.2/js/swiper.js"></script>
<link href="https://cdn.staticfile.org/Swiper/3.4.2/css/swiper.css" rel="stylesheet"/>
<div class='swiper-container'>
<div class='swiper-wrapper'>
<div class='swiper-slide'>message 1</div>
<div class='swiper-slide'>message 2</div>
<div class='swiper-slide'>message 3</div>
</div>
</div>

Slick slider change dots to words

In my project I'm using slick slider plugin ( http://kenwheeler.github.io/slick/)
I need change default dots nav for words. Slides should be changed after clicking on the words.
Here is the Updated Code and check my example in CODEPEN
$(".slider").slick({
autoplay: true,
dots: true,
customPaging : function(slider, i) {
var thumb = $(slider.$slides[i]).data();
return '<a class="dot">'+i+'</a>';
},
responsive: [{
breakpoint: 500,
settings: {
dots: false,
arrows: false,
infinite: false,
slidesToShow: 2,
slidesToScroll: 2
}
}]
});
.frst{
background: #3a8999;
}
.scnd{
background: #e84a69;
}
.thrd{
background: #980505;
}
.frth{
background: #094602;
}
.slider {
width: auto;
margin: 30px 50px 50px;
}
.slick-slide {
color: white;
padding: 40px 0;
font-size: 30px;
font-family: "Arial", "Helvetica";
text-align: center;
}
.slick-prev:before,
.slick-next:before {
color: black !important;
}
.slick-dots {
bottom: -30px;
}
a.dot{
padding:10px 10px;
}
a.dot:hover{
background:#ddd;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/kenwheeler/slick/master/slick/slick.js"></script>
<link href="https://rawgit.com/kenwheeler/slick/master/slick/slick.css" rel="stylesheet"/>
<link href="https://rawgit.com/kenwheeler/slick/master/slick/slick-theme.css" rel="stylesheet"/>
<section class="slider">
<div class="frst">slide1</div>
<div class="scnd">slide2</div>
<div class="thrd">slide3</div>
<div class="frth">slide4</div>
<div class="frst">slide5</div>
<div class="thrd">slide6</div>
</section>
<span class="pagingInfo"></span>

Uncaught TypeError: Cannot read property 'on' of undefined in arcgis

i am trying to display navigation tool and switch base map.Individually both are working good when i combine it its showing Uncaught Type Error: Cannot read property 'on' of undefined.can any tell me what is the mistake
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
<title></title>
<link rel="stylesheet" href="https://js.arcgis.com/3.15/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="https://js.arcgis.com/3.15/esri/css/esri.css">
<style>
html, body, #map { height: 100%; width: 100%; margin: 0; padding: 0; }
#switch{
position:absolute;
right:20px;
top:10px;
z-Index:999;
}
#basemapGallery{
width:380px;
height:280px;
}
#HomeButton {
position: absolute;
top: 95px;
left: 20px;
z-index: 50;
}
#navToolbar{
display: block;
position: absolute;
z-index: 2;
top: 10px;
left:2px
}
.zoominIcon {
display: block;
position: absolute;
width: 16px;
height: 16px;
}
.zoomoutIcon {
position: absolute;
width: 16px;
height: 16px;
}
.zoomfullextIcon {
position: absolute;
width: 16px;
height: 16px;
}
.zoomprevIcon {
position: absolute;
width: 16px;
height: 16px;
}
.zoomnextIcon {
position: absolute;
width: 16px;
height: 16px;
}
.panIcon {
position: absolute;
width: 16px;
height: 16px;
}
.deactivateIcon {
position: absolute;
width: 16px;
height: 16px;
}
</style>
<script src="https://js.arcgis.com/3.15/"></script>
<script>
var map;
require([
"esri/map",
"esri/dijit/BasemapGallery",
"esri/dijit/HomeButton",
"esri/toolbars/navigation",
"dojo/on",
"dojo/parser",
"dijit/registry",
"dijit/Toolbar",
"dijit/form/Button",
"dojo/domReady!"
], function(
Map,
BasemapGallery,
HomeButton,
Navigation,
on,
parser,
registry
) {
parser.parse();
var navToolbar;
map = new Map("map", {
basemap: "topo",
center: [-105.255, 40.022],
zoom: 13,
slider:false
});
//add the basemap gallery, in this case we'll display maps from ArcGIS.com including bing maps
var basemapGallery = new BasemapGallery({
showArcGISBasemaps: true,
map: map
}, "basemapGallery");
basemapGallery.on('load',function(){
basemapGallery.remove('basemap_1');
basemapGallery.remove('basemap_2');
basemapGallery.remove('basemap_3');
basemapGallery.remove('basemap_4');
basemapGallery.remove('basemap_5');
basemapGallery.remove('basemap_8');
});
basemapGallery.startup();
basemapGallery.on("error", function(msg) {
console.log("basemap gallery error: ", msg);
});
var home = new HomeButton({
map: map
}, "HomeButton");
home.startup();
navToolbar = new Navigation(map);
on(navToolbar, "onExtentHistoryChange", extentHistoryChangeHandler);
registry.byId("zoomin").on("click", function () {
navToolbar.activate(Navigation.ZOOM_IN);
});
registry.byId("zoomout").on("click", function () {
navToolbar.activate(Navigation.ZOOM_OUT);
});
registry.byId("zoomfullext").on("click", function () {
navToolbar.zoomToFullExtent();
});
registry.byId("zoomprev").on("click", function () {
navToolbar.zoomToPrevExtent();
});
registry.byId("zoomnext").on("click", function () {
navToolbar.zoomToNextExtent();
});
registry.byId("pan").on("click", function () {
navToolbar.activate(Navigation.PAN);
});
registry.byId("deactivate").on("click", function () {
navToolbar.deactivate();
});
function extentHistoryChangeHandler () {
registry.byId("zoomprev").disabled = navToolbar.isFirstExtent();
registry.byId("zoomnext").disabled = navToolbar.isLastExtent();
}
});
</script>
</head>
<body class="claro">
<div id="map">
<div id="navToolbar" data-dojo-type="dijit/Toolbar">
<div data-dojo-type="dijit/form/Button" id="zoomin" data-dojo-props="iconClass:'zoominIcon'">Zoom In</div>
<div data-dojo-type="dijit/form/Button" id="zoomout" data-dojo-props="iconClass:'zoomoutIcon'">Zoom Out</div>
<div data-dojo-type="dijit/form/Button" id="zoomfullext" data-dojo-props="iconClass:'zoomfullextIcon'">Full Extent</div>
<div data-dojo-type="dijit/form/Button" id="zoomprev" data-dojo-props="iconClass:'zoomprevIcon'">Prev Extent</div>
<div data-dojo-type="dijit/form/Button" id="zoomnext" data-dojo-props="iconClass:'zoomnextIcon'">Next Extent</div>
<div data-dojo-type="dijit/form/Button" id="pan" data-dojo-props="iconClass:'panIcon'">Pan</div>
<div data-dojo-type="dijit/form/Button" id="deactivate" data-dojo-props="iconClass:'deactivateIcon'">Deactivate</div>
</div>
<div id="HomeButton"></div>
<div id="switch" data-dojo-type="dijit/TitlePane" data-dojo-props="title:'Switch Basemap', closable:false, open:false">
<div id="basemapGallery"></div>
</div>
</div>
</body>
</html>
parser.parse returns a deferred in dojo 1.8+
what this means is that after
parser.parse()
your widgets are not necessarily instantiated and ready to be referenced as widgets via dijit/registry.
Also there is this is directly from the Dojo reference guide:
Note that waiting for dojo/domReady! to fire is often not sufficient when working with widgets. Many widgets shouldn’t be initialized or accessed until the following modules load and execute:
dojo/uacss
dijit/hccss
dojo/parser
Thus when working with widgets you should generally put your code inside of a dojo/ready() callback:
you do this by including "dojo/ready" in your require array and then wrapping any widget code in
ready(function(){
...your widget code....
})
in your case you could probably just wrap your entire javascript code in a ready function
require([
"esri/map",
"esri/dijit/BasemapGallery",
"esri/dijit/HomeButton",
"esri/toolbars/navigation",
"dojo/on",
"dojo/parser",
"dijit/registry",
"dojo/ready",
"dijit/Toolbar",
"dijit/form/Button",
"dojo/domReady!"
], function(
Map,
BasemapGallery,
HomeButton,
Navigation,
on,
parser,
registry,
ready
) {
ready(function() {
var navToolbar;
map = new Map("map", {
basemap: "topo",
center: [-105.255, 40.022],
zoom: 13,
slider: false
});
...etc
I also like to use parseOnLoad = true which I find to be less prone to errors (both human and otherwise)
Just put this script element above the arcgis js script tag like so
<script type="text/javascript">
var dojoConfig = {
parseOnLoad: true
};
</script>
<script src="https://js.arcgis.com/3.15/"></script>
and get rid of the call to parser.parse() at the top of your script.