I'm trying to use cytoscape.js to display a graph on my page and I'm having a hard time just getting a basic instance to display properly.
Code Breakdown:
I get the graph elements via an AJAX call, pass the elements into the cytoscape constructor, and display the instance in a Bootstrap modal.
Here's my JavaScript:
var cy;
$.ajax({
url : "getGraphElements",
data : {
str : variableToGetCorrectGraphData
},
success : function(data) {
var elementsJson = JSON.parse(data.elements);
console.log(elementsJson);
cy = cytoscape({
container : document.getElementById('cy'),
wheelSensitivity : 0.25,
elements : elementsJson,
style : [
{
selector: 'node',
style:
{
'background-color' : '#666',
label : 'data(id)'
}
},
{
selector: 'edge',
style:
{
'width' : 3,
'line-color' : '#737373',
'target-arrow-color' : '#737373',
'target-arrow-shape' : 'triangle',
'curve-style' : 'bezier'
}
}
],
layout : {
name: 'grid',
fit: true, // whether to fit the viewport to the graph
padding: 0, // padding used on fit
avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space
avoidOverlapPadding: 20, // extra spacing around nodes when avoidOverlap: true
nodeDimensionsIncludeLabels: false, // Excludes the label when calculating node bounding boxes for the layout algorithm
condense: false, // uses all available space on false, uses minimal space on true
sort: function(a,b) { // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') }
return a.degree() - b.degree();
},
animate: false, // whether to transition the node positions
transform: function (node, position ){ return position; } // transform a given node position. Useful for changing flow direction in discrete layouts
}
});
$('#cyModal').modal('show');
}
});
});
Here's my Bootstrap Modal :
<div class="modal fade bd-example-modal-lg" id="cyModal" tabindex="-1" role="dialog" aria-labelledby="cyModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span> </button>
<h4 class="modal-title" id="cyModalLabel">Graph View</h4>
</div>
<div class="modal-body">
<div id="cy" style="height : 750px"></div>
</div>
</div>
</div>
</div>
And here's a sample of the JSON returned by the AJAX (although I'm almost certain this is not incorrect because all the elements appear to be present in the constructed graph) :
{ "nodes" : [{ "data" : { "id" : "12293"} }...], "edges" : [{ "data" : { "id" : "24607-26336", "source" : "24607", "target" : "26336" } }...] }
My Problem is that all the nodes appear stacked in the top left corner when the graph finishes initializing. I believe this is because the instance is using the null layout. A quick peek in the inspector console shows that cy.layout.name = 'layout'.
I just can't get it to initialize with the grid layout like I want it to. I've tried taking out the layout in the constructor, and using cy.layout({name : 'grid',}).run();. I've tried using both, one after the other. I've even tried putting cy.layout({name : 'grid',}).run(); in a while loop until cy.layout.name == 'grid' - that just caused the page to freeze. I've tried changing basically every option in both the Cytoscape initializer and the Layout initializer - no dice. What I find weird is that when I execute cy.layout({name : 'grid',}).run(); in the inspector console, the layout sets up properly...
If anyone has any ideas I'd greatly appreciate it!
Ok, OP here. I think my problem is with Bootstrap.
I started by noticing that I call $('#cyModal').modal('show'); after I've finished initializing the Cytoscape instance. I realized that before I call $('#cyModal').modal('show'); the div container for my Cytoscape instance has no size and is invisible. So I tried calling $('#cyModal').modal('show'); before initializing Cytoscape, but that still didn't work. Furthermore, I noticed that the modal wasn't actually showing until after Cytoscape initialized.
Apparently, for this sort of thing, I need to wait for the Bootstrap modal to fire the 'shown' event before setting my Cytoscape layout to be sure that the div is visible and has a size. So I put my layout setter in a listener for shown.bs.modal:
$('#cyModal').on('shown.bs.modal', function (e) {
cy.layout({
name: 'grid',
fit: true, // whether to fit the viewport to the graph
padding: 0, // padding used on fit
avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space
avoidOverlapPadding: 20, // extra spacing around nodes when avoidOverlap: true
nodeDimensionsIncludeLabels: false, // Excludes the label when calculating node bounding boxes for the layout algorithm
condense: false, // uses all available space on false, uses minimal space on true
sort: function(a,b) { // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') }
return a.degree() - b.degree();
},
animate: false, // whether to transition the node positions
transform: function (node, position ){ return position; } // transform a given node position. Useful for changing flow direction in discrete layouts
}).run();
});
This works for me, but it feels a bit like a hack - why can't I just do it like I originally wanted to? If that's just the way the cookie crumbles with Cytoscape.js and Bootstrap - c'est la vie; but I'll take any other suggestions if anyone has a more 'natural feeling' solution.
Related
My team and I are attempting to develop an application on Cesium that requires circle and rectangle points. We are using the native PointGraphics to make the circles, but are creating entities for the rectangles. The code is shown below:
var entity = {
id: point.id,
box: {
dimensions: new Cesium.Cartesian3(20000,
20000,
0),
outline: true,
material: Cesium.Color.fromHsl(hue, 1, 0.5)
},
position: Cesium.Cartesian3.fromDegrees(point.lon, point.lat)
};
We are getting the boxes to draw but with some issues. First, when two boxes overlap, the graphic distorts as shown below:
I'm not sure if it's a code or browser issue. This screenshot is from Chrome, but we have tried it on Chrome and Firefox on two different machines.
Second, there is no automated zoom scaling. When we zoom out, the boxes stay the absolute size instead of relative to the zoom number like the PointGraphics. Compare the example below to the image above:
We may try multiplying the dimensions (not sure what unit they are in) by the zoom level as soon as we figure out how to get the zoom from Cesium, but I'm not sure if that will work since the entity creation may be static?
As a side note, we are using an angular version of Cesium, but I don't think that will prevent us from implementing a solution even if it's solved in regular JS.
The artifacts you're seeing are called "z-fighting", which is a common problem in 3D rendering when multiple polygons are rendered at the same depth and the depth buffer can't distinguish them.
But let's try a different approach: If you want these boxes to stay the same size regardless of zoom level, then you should probably switch to using Billboards to render the boxes. This will fix the z-fighting artifacts in the process. Run this code snippet and see if it's closer to your desired result.
var viewer = new Cesium.Viewer('cesiumContainer', {
navigationHelpButton: false, animation: false, timeline: false
});
var boxImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wwDFRU4/aN5pAAAACNJREFUKM9jYCARMDIwMPz//59Y1YyMTKTaMKphVAO1NJAMALu5AxxK3JB5AAAAAElFTkSuQmCC';
viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard : {
image : boxImage,
color : Cesium.Color.LIME
}
});
viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(-75.2, 39.8),
billboard : {
image : boxImage,
color : new Cesium.Color(0, 0.5, 1.0, 1.0)
}
});
viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(-70.0, 41.0),
billboard : {
image : boxImage,
color : new Cesium.Color(0.5, 0.9, 1.0, 1.0)
}
});
viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(-73.0, 37.0),
billboard : {
image : boxImage,
color : Cesium.Color.RED
}
});
viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(-89.0, 35.0),
billboard : {
image : boxImage,
color : Cesium.Color.YELLOW,
scale : 2.0
}
});
html, body, #cesiumContainer {
width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden;
font-family: sans-serif;
}
<link href="http://cesiumjs.org/releases/1.15/Build/Cesium/Widgets/widgets.css"
rel="stylesheet"/>
<script src="http://cesiumjs.org/releases/1.15/Build/Cesium/Cesium.js">
</script>
<div id="cesiumContainer"></div>
I am creating a custom angular directive that will use the Image Tilt Effect plugin.
<tilt></tilt>
the template looks like this.
<li class="grid__item">
<div class="grid__img grid__img--example-1" >
<img src="img/3.jpg" class="tilt-effect" alt="grid01" data-tilt-options='{ "opacity" : 0.3, "extraImgs" : 3, "movement": { "perspective" : 1200, "translateX" : -5, "translateY" : -5, "rotateX" : -5, "rotateY" : -5 } }' />
</div>
The problem I have is this script that I am loading at the bottom of the page doesnt seem to be on when the custom directive is injected into the page.
<script src="js/tiltfx.js"></script>
If I try and move the script into the html fragment for the custom directive I get an Error: $compile:tplrt
I've created a wrapper directive for this the Image Tilt Effect plugin (fiddle).
When you have a DOM plugin you need to use in angular, don't use auto initialization, such as the data-tilt-options of this plugin, because they are hard to predict, and may cause weird behavior, memory leaks, etc... This plugin has a manual init method - new TiltFx(element, options), so we can use that.
Another problem is that this plugin must wait for angular to finish rendering, before it should be initialized. The simple fix is just using setTimeout or $timeout (if we need to update the digest cycle), to put the init at the end of the JS queue. We can also use mutation observers to do that, but that's out of this answer's scope.
One troubling aspect of using a DOM plugin in angular, is memory leaks. Plugins should have some sort of a cleaning mechanism. This plugin doesn't. So, you'll have to check for memory leaks, and if there are stray event handlers remove them, when the wrapped element is removed.
Directive code:
appModule.directive('tilt', function tilt() {
var ddo = {
template: '<div class="grid__img"><img class="tilt-effect" ng-src="{{imgSrc}}" alt="The image" />',
restrict: 'E',
replace: true,
scope: {
imgSrc: '#', // the image src
tiltOptions: '=?' // the tilt options object - optional
},
link: function (scope, $el) {
var img = $el[0].querySelector('img.tilt-effect'); // find the img element
var tilt;
setTimeout(function () { // wait 'till directive is rendered
tilt = new TiltFx(img, scope.tiltOptions); // apply tilt on image with options (if any)
});
$el.on('$destroy', function() {
// use tilt variable to perform cleanup on wrapper and img if needed
tilt = null;
});
}
};
return ddo;
});
Usage:
<div ng-app="tilt">
<tilt img-src="http://cdn.cutestpaw.com/wp-content/uploads/2013/12/Most-Famous-Felines-034.jpg" tilt-options='{ "opacity" : 0.3, "extraImgs" : 3, "movement": { "perspective" : 1500, "translateX" : -5, "translateY" : -5, "rotateX" : -5, "rotateY" : -5 } }'></tilt>
<tilt img-src="http://www.cats.org.uk/uploads/images/pages/photo_latest14.jpg"></tilt>
</div>
Don't forget that this plugin requires the container to have fixed width and height, for example:
.grid__img {
width: 400px;
height: 400px;
}
Change
<script src="js/tiltfx.js"></script>
to
<script ng-src="js/tiltfx.js"></script>
I am using angularjs-gridster (https://github.com/ManifestWebDesign/angular-gridster) with higharts-ng directive (https://github.com/pablojim/highcharts-ng/blob/master/README.md)
I am trying to generate these highcharts inside the grid cells. My problem is that the highcharts are occupying their default width and height (600px * 400px) even when i place my graph drawer function in a $timeout service. Here's the code:
HTML:
<div class="graph-list" gridster="gridsterOpts">
<ul>
<li gridster-item="graph.grid" class="graph-set" ng-repeat="graph in graphs | orderBy: 'number'">
<highchart id="{{'graph' + graph.number}}" class="graph" config="graph.config"></highchart>
</li>
</ul>
</div>
JS:
// inside the graph-list div controller
$scope.gridsterOpts = {
colums: 4,
rowHeight: 240,
margins: [10,10],
outerMargin: false,
draggable: {
enabled: false // whether dragging items is supported
}
};
$scope.graphs = {}; //
$scope.somefunction(){ /* this function populates the graphs object */ };
function drawGraphs(){ /* this function populates the graph.config object by looping through all the graph objects */ }
$timeout(function(){
drawGraphs();
});
I have tried creating watch on the grid-cell width and height but it shows no change. I have not given the highchart width and height explicitly in the graph.config options because I read in the highcharts-ng documentation that it takes the parent width and height by default but its not happening. Can anyone guide me what could be the possible problem.
Seems to me that the angularjs-gridster plugin is not able to set the grid width and height before the highcharts directive is able to render itself. Please help.
I eventually did it. I needed to add the chart.reflow() method (which just resizes the chart instead of redrawing it so better performance wise also, I guess) in the func() options as provided in the highcharts-ng documentation.
graph.config = {
options: { },
series: [],
func: function (chart) {
$timeout(function(){
chart.reflow();
})
}
}
Hope it helps someone else.
I'm trying to create a pie chart through the use of flot charts. I have successfully managed to create one with the following code:
HTML:
<div class="row">
<div class="col-md-6">
<div id="pie-chart"></div>
</div>
</div>
Javascript:
var data = [];
data[0] = { label: "Vertification successful", data: 9 };
data[1] = { label: "Vertification failed", data: 2 };
var series = Math.floor(Math.random() * 10) + 1;
$.plot($("#pie-chart"), data,
{
series: {
pie: {
show: true,
}
},
grid: { hoverable: true },
});
And it displays just fine.
The thing is, if I change the ID of the div element to "pie-chart1" (rather than "pie-chart")
and update the javascript accordingly:
$.plot($("#pie-chart1"), data,
I get the following error:
Uncaught Invalid dimensions for plot, width = 501, height = 0
What on earth could be causing this? I simply wanna rename the ID which apparently for some reason is impossible.
It's very likely that there is some CSS or possibly JS elsewhere on your site that expects the div to be called pie-chart. You need to ensure that it still applies to the new div. For example, if you had:
#pie-chart {
width: 400px;
height: 300px;
}
When you change the ID of the div, you need to update that reference too, or else the placeholder's height and width become undefined, which Flot cannot handle.
If your goal in adding that number is to create several charts, then you should use a class to apply the styles rather than an ID.
I am trying to create a simple effect for my application which is to Fade it in from white over a period of 1-2 seconds so that the user doesn't have to see it being assembled.
I almost have it working, but there is some flickering that I can't seem to get rid of. Basically ExtJS is rendering my UI and then immediately hiding it so it can be faded in.
Here's my app:
Ext.application({
name : 'MyApp', // Application level namespace
appFolder : 'js/myapp', // Directory path to app
autoCreateViewport : true,
launch : function() {
// fade in the viewport
var form = Ext.ComponentQuery.query("viewport")[0];
form.getEl().fadeIn({
from : {
opacity : 0
},
duration : 1000
});
}
});
What can I do to get rid of the initial draw before the FadeIn?
Took a wild guess that I could set the opacity of the viewport to 0 by default and it worked:
Ext.define('MyApp.view.Viewport', {
extend : 'Ext.container.Viewport',
style: 'opacity: 0;',
items : [ {
xtype : 'someview'
} ]
});