Rendering speed of cola-layout in cytoscape.js - cytoscape.js

I applied cola-layout from cytoscape.js on a data set of over 500 nodes and edges.
The layout renders the graph and takes more than 10 minutes to settle down to an open-graph.
My question is whether or not this is the amount of time, cola would take to display a graph with such complexity?
In order to get a visually pleasing (balanced) graph like this infinite:true
within cytoscape.js file that I had downloaded from here.
I had set,
infinite: true; // for cola layout
Without which it would settled down to an entangled graph like this :
infinite:false
Here is the javascript code:
$(function()
{
$('#cy').cytoscape
({
style: cytoscape.stylesheet()
.selector('node').css({'content': 'data(name)'})
.selector('edge').css({'target-arrow-shape': 'triangle'})
.selector(':selected').css({'line-color': 'black'})
elements: {
nodes: [
{ data: { id: '2335', name: '2335' } },
//.
//. data from www.briandunning.com/sample-data/ca-500.zip
},
],
edges: [
{ data: { source:'2335', target:'Canton' }
//.
//. data from www.briandunning.com/sample-data/ca-500.zip
}]
},
layout: { name: 'cola'},
ready: function()
{
window.cy = this;
}
});
});

I think you may have answered your own question! If you set infinite: true, then the layout will run infinitely. So, of course in that case it will run a long time, and in fact it will never stop unless you make API calls to do so explicitly.

Related

Datatable export generic data outside table

I'm trying to add aditional rows during exportation(plz, see attached image), i don't find the right way to do it, please someone tell me whether or not its possible, since many reports carry parent data that not necesarily appears inside the table. I need the Pdf output with the same format. I'm using yajra datatables for laravel.
buttons :[
{
extend: 'pdfHtml5',
pageSize: 'letter',
title: "Informe Asistencia",
exportOptions: {
columns: [0,1,2,3,4,5,6,7,8,9,10 ]
}
},
['excel','csv','colvis']
],
calling the function and drawing table
$.get('{{ url("informes/get_informe_asistencia") }}',
{
'fecha_inicio': fecha_inicio,
'fecha_fin' : fecha_fin,
'numero' : numero
},function(resp){
console.log(resp);
$('.desde').append(fecha_inicio);
$('.hasta').append(fecha_fin);
$('.nombre').append(resp.informe[1].nombre);
$('.ficha').append(resp.informe[1].numero);
dtinforme.clear().draw();
dtinforme.rows.add(resp.informe).draw();
}
missing data
you can export manually.
text: 'PDF',//title
titleAttr: 'Exportar PDF',
exportOptions: {
...
},
action: function (e, dt, node, config) {
var table = this;
dt.clear().draw();
dt.rows.add("YOUR DATA").draw();
$.fn.dataTable.ext.buttons.pdfHtml5.action.call(table, e, dt, node, config);
}
So you need modify your data from with dt.rows().data().toArray(),dt.settings().init().columns.

Selecting edges based on both edge's data and target's data

I would like a selector for edges matching
edge[type="blocker"]
that have a target matching
node[status="complete"]
In other words, is there a valid way of expressing the following:
cytoscape({
...
style: [
...
{
selector: '( node -> node[status="complete"] )[type="blocker"]',
style: {
display: 'none',
},
},
...
],
...
})
I don't see a way of doing this in the documentation.
Obviously, I could copy the target's data into the node's data and use the following:
edge[type="blocker"][target_status="complete"]
But duplicating the data goes against my every instinct as a software developer.
You can provide function to filter method:
cy.edges().filter(function(ele) {
return ele.data('type') == 'blocker' &&
ele.target().data('status') == 'complete';
})
How about this
EDIT:
var selectedEdges = cy.nodes("[status= 'complete']").connectedEdges("[type = 'blocker']);
var selectedEdges.addClass('specialSnowflake')
And in your stylesheet, just define:
{
"selector": "edge.specialSnowflake",
"style": {
"display": "none"
}
}
If a selector does not suffice, then you could (1) suggest a new feature to enhance the edge -> selector and perhaps even make a PR for it or (2) use a function on the style property instead.
E.g. for (2):
{ selector: 'edge', style: { display: edgeIsDisplayed } }
where the function can be anything, like edgeIsDisplayed(edge) => edge.data('foo') === 'bar' && edge.target().hasClass('baz')
See http://js.cytoscape.org/#style/mappers
There's no selector that will help.
However, it is possible to avoid having to manually update two elements when data changes.
Style sheet value functions are called every time a matching element's data changes. In one those function, one could therefore update the data of incoming edges every time the node's data is updated, keeping the data of the two in sync automatically.
var push_status = function(node) {
node.incomers('edge').forEach( edge => edge.data('target_status', node.data('status')) );
node.outgoers('edge').forEach( edge => edge.data('source_status', node.data('status')) );
};
cytoscape({
...
style: [
...
{
selector: 'node',
style: {
label: node => { push_status(node); return node.data('id'); },
},
},
{
selector: 'edge[type="blocker"][target_status="complete"]',
style: {
display: 'none',
},
},
...
],
...
})
This would qualify as a hack, but it works perfectly. Updating the node's data updates the edge's data, which causes the style to be applied or unapplied as appropriate.
Be wary of creating an infinite loop! In particular, modifying the data of a node's parent will trigger a calculation of the node's style. This problem can be avoided by replacing
ele.data('key', val)
with
// Making changes to a element's data triggers a style recalculation.
// This avoids needlessly triggering the style recalculation.
var set_data = function(node, key, new_val) {
let old_val = node.data(key);
if (new_val != old_val)
node.data(key, new_val);
};
set_data(ele, 'key', val)

rally iteration combobox returns empty

I'm new to rally app SDK and trying to do the tutorials (from Youtube and from rally site)
when I'm trying to create an iterationComboBox the object is created but with no values ("There are no Iterations defined").
i tried to run both the video tutorial code from github (session_4_interactive_grid)
// Custom Rally App that displays Defects in a grid and filter by Iteration and/or Severity.
//
// Note: various console debugging messages intentionally kept in the code for learning purposes
Ext.define('CustomApp', {
extend: 'Rally.app.App', // The parent class manages the app 'lifecycle' and calls launch() when ready
componentCls: 'app', // CSS styles found in app.css
defectStore: undefined, // app level references to the store and grid for easy access in various methods
defectGrid: undefined,
// Entry Point to App
launch: function() {
console.log('our second app'); // see console api: https://developers.google.com/chrome-developer-tools/docs/console-api
this.pulldownContainer = Ext.create('Ext.container.Container', { // this container lets us control the layout of the pulldowns; they'll be added below
id: 'pulldown-container-id',
layout: {
type: 'hbox', // 'horizontal' layout
align: 'stretch'
}
});
this.add(this.pulldownContainer); // must add the pulldown container to the app to be part of the rendering lifecycle, even though it's empty at the moment
this._loadIterations();
},
// create iteration pulldown and load iterations
_loadIterations: function() {
this.iterComboBox = Ext.create('Rally.ui.combobox.IterationComboBox', {
fieldLabel: 'Iteration',
labelAlign: 'right',
width: 300,
listeners: {
ready: function(combobox) { // on ready: during initialization of the app, once Iterations are loaded, lets go get Defect Severities
this._loadSeverities();
},
select: function(combobox, records) { // on select: after the app has fully loaded, when the user 'select's an iteration, lets just relaod the data
this._loadData();
},
scope: this
}
});
this.pulldownContainer.add(this.iterComboBox); // add the iteration list to the pulldown container so it lays out horiz, not the app!
},
// create defect severity pulldown then load data
_loadSeverities: function() {
this.severityComboBox = Ext.create('Rally.ui.combobox.FieldValueComboBox', {
model: 'Defect',
field: 'Severity',
fieldLabel: 'Severity',
labelAlign: 'right',
listeners: {
ready: function(combobox) { // this is the last 'data' pulldown we're loading so both events go to just load the actual defect data
this._loadData();
},
select: function(combobox, records) {
this._loadData();
},
scope: this // <--- don't for get to pass the 'app' level scope into the combo box so the async event functions can call app-level func's!
}
});
this.pulldownContainer.add(this.severityComboBox); // add the severity list to the pulldown container so it lays out horiz, not the app!
},
// Get data from Rally
_loadData: function() {
var selectedIterRef = this.iterComboBox.getRecord().get('_ref'); // the _ref is unique, unlike the iteration name that can change; lets query on it instead!
var selectedSeverityValue = this.severityComboBox.getRecord().get('value'); // remember to console log the record to see the raw data and relize what you can pluck out
console.log('selected iter', selectedIterRef);
console.log('selected severity', selectedSeverityValue);
var myFilters = [ // in this format, these are AND'ed together; use Rally.data.wsapi.Filter to create programatic AND/OR constructs
{
property: 'Iteration',
operation: '=',
value: selectedIterRef
},
{
property: 'Severity',
operation: '=',
value: selectedSeverityValue
}
];
// if store exists, just load new data
if (this.defectStore) {
console.log('store exists');
this.defectStore.setFilter(myFilters);
this.defectStore.load();
// create store
} else {
console.log('creating store');
this.defectStore = Ext.create('Rally.data.wsapi.Store', { // create defectStore on the App (via this) so the code above can test for it's existence!
model: 'Defect',
autoLoad: true, // <----- Don't forget to set this to true! heh
filters: myFilters,
listeners: {
load: function(myStore, myData, success) {
console.log('got data!', myStore, myData);
if (!this.defectGrid) { // only create a grid if it does NOT already exist
this._createGrid(myStore); // if we did NOT pass scope:this below, this line would be incorrectly trying to call _createGrid() on the store which does not exist.
}
},
scope: this // This tells the wsapi data store to forward pass along the app-level context into ALL listener functions
},
fetch: ['FormattedID', 'Name', 'Severity', 'Iteration'] // Look in the WSAPI docs online to see all fields available!
});
}
},
// Create and Show a Grid of given defect
_createGrid: function(myDefectStore) {
this.defectGrid = Ext.create('Rally.ui.grid.Grid', {
store: myDefectStore,
columnCfgs: [ // Columns to display; must be the same names specified in the fetch: above in the wsapi data store
'FormattedID', 'Name', 'Severity', 'Iteration'
]
});
this.add(this.defectGrid); // add the grid Component to the app-level Container (by doing this.add, it uses the app container)
}
});
and the code from Rally site (https://help.rallydev.com/apps/2.0rc2/doc/#!/guide/first_app).
// Custom Rally App that displays Defects in a grid and filter by Iteration and/or Severity.
//
// Note: various console debugging messages intentionally kept in the code for learning purposes
Ext.define('CustomApp', {
extend: 'Rally.app.App', // The parent class manages the app 'lifecycle' and calls launch() when ready
componentCls: 'app', // CSS styles found in app.css
launch: function() {
this.iterationCombobox = this.add({
xtype: 'rallyiterationcombobox',
listeners: {
change: this._onIterationComboboxChanged,
ready: this._onIterationComboboxLoad,
scope: this
}
});
},
_onIterationComboboxLoad: function() {
var addNewConfig = {
xtype: 'rallyaddnew',
recordTypes: ['User Story', 'Defect'],
ignoredRequiredFields: ['Name', 'ScheduleState', 'Project'],
showAddWithDetails: false,
listeners: {
beforecreate: this._onBeforeCreate,
scope: this
}
};
this.addNew = this.add(addNewConfig);
var cardBoardConfig = {
xtype: 'rallycardboard',
types: ['Defect', 'User Story'],
attribute: 'ScheduleState',
storeConfig: {
filters: [this.iterationCombobox.getQueryFromSelected()]
}
};
this.cardBoard = this.add(cardBoardConfig);
},
_onBeforeCreate: function(addNewComponent, record) {
record.set('Iteration', this.iterationCombobox.getValue());
},
_onIterationComboboxChanged: function() {
var config = {
storeConfig: {
filters: [this.iterationCombobox.getQueryFromSelected()]
}
};
this.cardBoard.refresh(config);
}
});
both give me an empty iteration box.
i'm getting user stories data when running code from session 3 on the video,by creating a store of user stories. I googled it and searched here for duplicates but with no successso far, so what can be the issue?
Thanks!
I copied the code you posted, both apps, without making any changes, ran the apps and the iteration box was populated in both cases. It's not the code.
Maybe if you are getting "There are no Iterations defined" there are no iterations in your project?
The second code you posted which you copied from the example in the documentation has a bug in it and even though the iteration combobox is populated, the cards do not show on a board. DevTools console has error: "Cannot read property 'refresh' of undefined".
I have a working version of this app in this github repo.

Loading indicator on store loading

My store is taking time to load.so that i want to display a loading indicator while loading store data.Is there any function to know the list store is completely loaded or not?
Please help me..I was searching for this long time..
Here is my code:
Ext.Viewport.setMasked({
xtype: 'loadmask',
message: 'Loading'
});
Ext.define('MyApp.view.Myview1',
{
extend: 'Ext.Panel',
requires: ['Ext.List', 'Ext.util.JSON'],
config: {
layout: 'hbox',
items: [
{
xtype: 'panel',
layout: 'fit',
flex: 1,
items: [
{
xtype: 'list',
itemTpl:
'<div class="myContent">' +
'<div>{name}</div>' +
'</div>',
store: 'MainStore',
disclosure: true,
store.on({
load: {
fn: function( store ) {
Ext.Viewport.setMasked(false);
},
scope: this,
single: true
}
});
store.load();
}
]
}
]
}
});
My requirement is to display indicator while loading data from store and remove it after the list have all data from store.
You will have to bind load event to this store like below code
Ext.Viewport.setMasked({
xtype: 'loadmask',
message: 'Loading'
});
store.on({
load: {
fn: function( store ) {
Ext.Viewport.setMasked(false);
},
scope: this,
single: true
}
});
store.load();
This one is simple, just like #Naresh Said
put load mask inside your function
Ext.Viewport.setMasked({
xtype: 'loadmask',
message: 'Please Wait...'
});
and disable loadmask after store loads
storectn.load({
callback: function (records, operation, success, response) {
if (success==1) {
// Ext.Msg.alert('success');
Ext.Viewport.setMasked(false);
} else {
// Ext.Msg.alert('Failed');
Ext.Viewport.setMasked(false);
}
}
});
Finally I have solved the problem :)
Sencha loads the store and list within a very short time but the UI takes long time to load. So, after finish loading the stores if you set a callback to remove indicator using setTimeout then the problem will be solved. Because when UI loading is finished it will call your final method, because JavaScript is a single thread language.
Ext.Viewport.setMasked({
xtype: 'loadmask',
message: 'Loading'
});
store.on({
load: {
fn: function( store ) {
setTimeout(function(){
Ext.Viewport.setMasked(false);
}, 100);
},
scope: this,
single: true
}
});
store.load();
Here even though the setTimeout is supposed to be called after 100 milliseconds, it will wait for the UI to be loaded first and then be called.
If you are loading the store dynamically by a custom query with store.add(Record), then after loading all data you just call the setTimeout call. It will work as well.
We need to setmask(prevent user interaction and show loading message) while loading data in sencha touch. Sencha touch setMasked(<>) gives option to add mask into containers. But every time we need to manually add the mask and remove the mask. Instant of this manually doing things we can use store listeners.
My requirement here is i have a one store for CRUD process. So every time when i try to sync and loading the data’s i want to setmask of the view. My store code is like the below.
I used beforeload, beforesync, load, metachange, removerecords, updaterecord, write, addrecords events for this task.
Please visit the below url for full source code by clicking the link. [Solved by solution]
http://saravanakumar.org/blog/2014/04/sencha-touch-setmask-display-load-mask-or-waiting-while-loading-data-in-store/

Nested grid in ExtJS 4.1 using Row Expander

On the front-end I have a Calls grid. Each Call may have one or more Notes associated with it, so I want to add the ability to drill down into each Calls grid row and display related Notes.
On the back-end I am using Ruby on Rails, and the Calls controller returns a Calls json recordset, with nested Notes in each row. This is done using to_json(:include => blah), in case you're wondering.
So the question is: how do I add a sub-grid (or just a div) that gets displayed when a user double-clicks or expands a row in the parent grid? How do I bind nested Notes data to it?
I found some answers out there that got me part of the way where I needed to go. Thanks to those who helped me take it from there.
I'll jump straight into posting code, without much explanation. Just keep in mind that my json recordset has nested Notes records. On the client it means that each Calls record has a nested notesStore, which contains the related Notes. Also, I'm only displaying one Notes column - content - for simplicity.
Ext.define('MyApp.view.calls.Grid', {
alias: 'widget.callsgrid',
extend: 'Ext.grid.Panel',
...
initComponent: function(){
var me = this;
...
var config = {
...
listeners: {
afterrender: function (grid) {
me.getView().on('expandbody',
function (rowNode, record, expandbody) {
var targetId = 'CallsGridRow-' + record.get('id');
if (Ext.getCmp(targetId + "_grid") == null) {
var notesGrid = Ext.create('Ext.grid.Panel', {
forceFit: true,
renderTo: targetId,
id: targetId + "_grid",
store: record.notesStore,
columns: [
{ text: 'Note', dataIndex: 'content', flex: 0 }
]
});
rowNode.grid = notesGrid;
notesGrid.getEl().swallowEvent(['mouseover', 'mousedown', 'click', 'dblclick', 'onRowFocus']);
notesGrid.fireEvent("bind", notesGrid, { id: record.get('id') });
}
});
}
},
...
};
Ext.apply(me, Ext.apply(me.initialConfig, config));
me.callParent(arguments);
},
plugins: [{
ptype: 'rowexpander',
pluginId: 'abc',
rowBodyTpl: [
'<div id="CallsGridRow-{id}" ></div>'
]
}]
});