ChartJS in Datatable cell performance - performance

So for some reason I have to put a tiny chart into a column cell.
I did it with a simple div and initizialized every single chart:
<div>
<canvas id="6" height="50px" width="150px"></canvas>
</div>
jsFiddle
Which works great for 10 dataset or 20. But i got a huge datatable with 380 rows and after "2 minutes" of waiting it even loads the charts for every single table row.
Is there a better way to do it, or boost performance?

I found the solution.
Like mentioned in this post: Pagination triggers
I call a function which inits the visible charts on datatable draw. Means i only draw them when they get viewed. Nice performance and super slim solution.
$('#Table')
.on( 'draw.dt', function () { initSparkline(); } )
.dataTable();
});

Another option might be to use the drawCallback option to use data in the table to render charts for the visible rows each time the DataTable is re-drawn for a page change.
Below is an simplified example of how this might be done for a doughnut chart.
$('#dataTableId').DataTable({
// I use the "columns" option to tell each column what data to show.
// One column should have a uniquely id'ed canvas.
"columns": [
{ "data": "id" },
{ "data": "A" },
{ "data": "B" },
{ "data": function(row){
return "<canvas height=\"40px\" width=\"40px\" id=\"chart"+row.id+"\"></canvas>";
},
"orderable": false}
],
"drawCallback": function() {
// Get data, only from the rows displayed on the current page.
var data = this.api().rows({page:'current'}).data();
// The first draw appears to have a length of 0,
// but subsequent ones have length equal to number of rows drawn.
if (data.length !== 0){
// Loop through each row to render each chart
for (var i = 0; i < data.length; i++) {
// Find the chart intended for this data
var ctx = $("#chart"+data[i].id);
// Make the chart
var newChart = new Chart(ctx, {
"type": "doughnut",
"data": {
"labels": [
"A",
"B"
],
"datasets": [{
"data": [data[i].A,data[i].B]
}],
},
}) // /Chart
}
}
}
// In a real table, the object passed into the DataTable() function will
// probably also use other options, such as "ajax", "serverSide" or
// "pageLength". For simplicity, only "columns" and "drawCallback" are
// shown in this example.
}); // /DataTable

Related

Cant get a grouped kendo ui column chart to hide when some items in group don't have a value

I've been trying to get a telerik kendo ui column chart to display grouped data but where the groups might not have entries for all possible values and I don't want to show space/empty columns in these empty cases.
Telerik dojo of problem
Is anyone aware of anyway to get this to work more like the screenshot below
Excel has grouped the data but doesn't display a column at all if the data is null/zero
I couldn't find a built-in way to do this, so I ended up just placing the bars manually by overriding the visual function. In my case, I only needed to move one bar and that bar will always be the same category, which made it a whole lot easier in that I only had to identify it by matching the category. I could then move it with a transform. You cannot move it by setting the coordinates because the visual has already been created.
It would be more complex to do this dynamically, but it's certainly possible. This may give someone a start in the right direction.
One downside of this method is that you must also place the labels manually, which I have also done below. You can override the visual function of the labels, as well, but no references to any other elements are passed with the event data. Note how the documentation says the sender field may be undefined; in my experience, this is always the case.
It also does not move the tooltip or the highlight. You could use the same method to move the highlight (override the visual function, though on the series instead of the seriesDefaults) and draw the tooltip manually while moving the highlight -- similar to how the method below draws the label while moving the column.
Telerik Dojo Example
$(document).ready(function () {
$("#chart").kendoChart({
legend: { visible: false },
tooltip: { visible: false },
categoryAxis: {
name: "categoryAxis",
categories: ["1", "2", "3"],
},
series: [
{
data: [1, 2, 3],
highlight: { visible: false },
},
{
data: [1.5, null, 3.5],
highlight: { visible: false },
}
],
seriesDefaults: {
type: "column",
labels: { visible: false },
visual: function (e) {
if (e.value === null) return;
var visual = e.createVisual();
var axisRect = e.sender.getAxis("categoryAxis").slot("2");
var group = new kendo.drawing.Group();
var label = new kendo.drawing.Text(e.value, [0, 0], {
font: "20px sans-serif",
fill: { color: "black" }
});
var lbox = label.clippedBBox();
label.position([
e.rect.origin.x + e.rect.size.width / 2 - lbox.size.width / 2,
e.rect.origin.y - label.bbox().size.height * 1.5
]);
group.append(visual, label);
if (e.category === "2") {
var x = (axisRect.origin.x + axisRect.size.width / 2) - e.rect.size.width / 2;
group.transform(kendo.geometry.transform().translate(x - e.rect.origin.x, 0));
}
return group;
},
}
});
});

Flatten nested array created with d3.nest() to create stacked bar

I have data returned from a REST API in the following form.
[{
"created": "2014-06-01T11:21:47Z",
"is_good": false,
"amount": 10
},{
"created": "2014-06-01T12:01:00Z",
"is_good": false,
"amount": 12
},{
"created": "2014-06-02T10:00:00Z",
"is_good": true,
"amount": 8
},{
"created": "2014-06-02T08:00:00Z",
"is_good": false,
"amount": 3
},
...
]
In order to make a stacked bar chart, I thought the solution would be to use d3.nest() to rollup the amounts, first by date, then by is_good (the stacking category).
nestedData = d3.nest()
.key(function(d) { return d3.time.day(new Date(d.created)); })
.key(function(d) { return d.is_good; })
.rollup(function(leaves) { return {amount: d3.sum(leaves, function(d) { return d.amount; })}; })
.entries(jsonData);
That would probably be fine when drawing the chart following Mike Bostock's example here, but wouldn't work in a d3.layout.stack() call, because it requires the .values() to be the group iterable from which x and y is then calculated. That lead me to try the keys the other way around, but then drawing the chart itself becomes tricky.
So after all of that, I'm now wondering if there's a neat d3 way of flattening the nested values into something that resembles the datasets in almost all stacked bar chart examples.
Alternatively, perhaps I'm just not seeing how best to use the double nested data to create a stacked bar chart based on the examples.
Any help would be much appreciated.
I eventually decided to tackle this without using d3.layout.stack at all, and attempted to convert the double nested array into something resembling the example given. This is the code that will take that complex array and squash it down into something more manageable when drawing the chart.
data.forEach(function(d) {
var y0 = 0;
d.amounts = color.domain().map(function(is_good) {
return {is_good: is_good, y0: y0, y1: y0 += +d.values.filter(function(d) {
return d.key == is_good;
})[0].values.amount};
});
d.total = d.amounts[d.amounts.length - 1].y1;
});
Here's a working example.
I'm sure this isn't perfect, so if anyone has a better way of achieving the same result, I'd be interested to see it!

Extjs 4 disable sort if grid is empty

Hello stackoverflow community,
is it somehow possible to disable the sorting mechanism in a grid for every column on a condition? Like...
if the grid hasn't any data loaded,
the sorting should be disabled,
else enabled.
I have the problem that if there is no data and you click on a column to sort, the remote sorting mechanism, will start loading the whole data and sorts it, too ...
This behaviour isn't needed or wished, so I want to disable the sorting possibility.
Your help is appreciated.
Thanks in advance and have a nice day.
This is a bit of a hack, but seems to work OK:
http://jsfiddle.net/q43HC/6/
var data = [{
data1: 'test',
data2: 'test'
}, {
data1: 'test2',
data2: 'test2'
}];
var store = Ext.create('Ext.data.Store', {
fields: ['data1', 'data2'],
data: data
});
Ext.define('SampleGrid', {
extend: 'Ext.grid.Panel',
store: store,
height: 250,
width: 400,
title: 'My Window',
columns: [{
text: 'test',
dataIndex: 'data1'
}, {
text: 'test2',
dataIndex: 'data2'
}],
bbar: [{
text: 'Clear Store',
handler: function() {
store.removeAll();
var grid = this.up('grid'),
cols = grid.query('gridcolumn'),
i = 0,
len = cols.length;
for (; i < len; i++) {
cols[i].doSort(null);
cols[i].__toggleSortState = cols[i].toggleSortState;
cols[i].toggleSortState = function() {};
}
}
}, {
text: 'Reload Store',
handler: function() {
store.loadData(data);
var grid = this.up('grid'),
cols = grid.query('gridcolumn'),
i = 0,
len = cols.length;
for (; i < len; i++) {
if (cols[i].__toggleSortState) {
cols[i].toggleSortState = cols[i].__toggleSortState;
}
}
}
}],
renderTo: Ext.getBody()
});
Ext.onReady(function() {
var grd = new SampleGrid();
});
I am just changing the sort state when the data is removed in order to remove any current sorting then replacing the toggleSortState function with an empty one so clicking the header will not sort the column. When reloading the store I put the function back.. You will have to adapt this to your project, but could create a store aware grid with similar logic.
You can do this globally by overriding the Ext.data.Store.sort method. The system I was working on uses remoteSort and hence hits the server everytime the user clicks the column header to sort, even if the grid itself is empty, pretty much same with your problem.
This is the code that you only need to declare in a single location (ie. your ext-overrides file or commons file), instead of in every single grid:
Ext.define('Ext.overrides.data.Store', {
override: 'Ext.data.Store',
sort: function() {
//prevents the grid from submitting a request if store is empty, for remote sort
if (this.count() > 0) {
this.callParent(arguments);
}
}
});
I have written the following function to achieve the same solution:
function disableColumnSorting(disable){
var grid = Ext.getCmp('billRecordGrid'),
cols = grid.query('gridcolumn'),
colLength = cols.length,
i = 0;
for(i; i < colLength; i++) {
if (disable) {
cols[i].sortable= false;
} else {
cols[i].sortable= true;
}
}
}
Thanks to drew630, you gave me a helpful hint, so I could solve my problem on my own.
If you do not want anything to happen when the grid is empty, you could put this in the store of the grid:
listeners: {
beforeload: function() {
return false when my conditions is met.;
}
}
This way your store will not load and nothing will happen. This is also handy when you have a pager component on the grid and where your server expects e.g. extraParams that are not yet set. If you expect extraParams, and when not yet set, the user clicks the refresh on the pager, you end up with exceptions. But with this check, you can prevent a call to the back-end if conditions are not met.
I sometimes set my api.read URL to undefined, (in cases where the store needs extraParams that are not yet set) and only when those params are available, I also set the URL (with a call in the store itself where the url is already set e.g. activateRead(). In the beforeload, I then test if the api.read is undefined or not.
Hope it helps.
Regards
Lawrende

Kendo grid resizable column width

The grid columns may be resizable. I want to store user-adjusted columns width and restore them when the next session starts.
The best way to store columns width I've found is the following:
var element = $('#grid').kendoGrid({
...
resizable: true,
columnResize: function(e) {
var state = {};
this.columns.every(function(c,i) {
state[c.field] = c.width;
return true;
});
var state_txt = JSON.stringify(state);
localStorage['profile_userprofile_grid_column_width'] = state_txt;
}
}
Now I want to restore column width saved in the previous user session. I can read columns width from the storage:
var state = JSON.parse(localStorage['profile_userprofile_grid_column_width']);
Does somebody know some elegant way to apply these values back to the grid if it is already created at this time? The resize handle does it internally, so it is possible, but the code doing it in the grid source is ugly.
You can trigger the columnResize event post initilisation as shown below
function grid_columnResize(e) {
// Put your code in here
console.log(e.column.field, e.newWidth, e.oldWidth);
}
$("#grid").kendoGrid({
columns: [
{ field: "name" },
{ field: "age" }
],
dataSource: [
{ name: "Jane Doe", age: 30 },
{ name: "John Doe", age: 33 }
],
resizable: true
});
var grid = $("#grid").data("kendoGrid");
grid.bind("columnResize", grid_columnResize);
Documentation
This is an old question, but here is what we have. Function handles column widths and groups.
var _updateResultsGridColumns = function(columns, groups) {
var kendoGrid = $resultsGrid.data("kendoGrid");
if (kendoGrid) {
kendoGrid.setOptions({
columns: columns,
});
var dataSource = kendoGrid.dataSource;
dataSource.group(groups);
kendoGrid.setDataSource(dataSource);
}
}

jqgrid: several questions - matrix display

I have data of matrix stored in table as below tables:
MatrixDimensions - MatrixId, NoOfRows, NoOfCol
MatrixValues - MatrixId, RowNo, ColNo, Value
How can I make jqGrid to take no. of rows & columns dynamically
and also display the serialized data in matrix? Is there a direct way or will I have to implement for loops to upload the data in matrix?
Can I display rows as columns and columns as rows (so having column headers vertically aligned)?
Can I enable only inline editing and disable form based editing?
I just wrote the answer to another question where I described how to create the grid with dynamic number of columns (number of rows is always dynamic in jqGrid). It seems to me this way you can display any matrix. In you case you can probably make the code simpler because you can use generic column names like "1", "2", etc. (or "Col. 1", "Col. 2", etc.) and so on.
I modified the code so that it uses array of arrays (matrix) instead of the array on objects with named properties. So jqGrid will looks like this:
or this:
depending on the input JSON data.
The full demo can be found here. The full JavaScript code of the demo you can find below:
var mygrid=jQuery("#list"),
cmTxtTemplate = {
width:40,
align:"center",
sortable:false,
hidden:true
}, currentTemplate = cmTxtTemplate, i,
cm = [], maxCol = 30, dummyColumnNamePrefix = "", //"Col. ",
clearShrinkToFit = function() {
// save the original value of shrinkToFit
var orgShrinkToFit = mygrid.jqGrid('getGridParam','shrinkToFit');
// set shrinkToFit:false to prevent shrinking
// the grid columns after its showing or hiding
mygrid.jqGrid('setGridParam',{shrinkToFit:false});
return orgShrinkToFit;
},
setGridWidthAndRestoreShrinkToFit = function(orgShrinkToFit,width) {
// restore the original value of shrinkToFit
mygrid.jqGrid('setGridParam',{shrinkToFit:orgShrinkToFit});
mygrid.jqGrid('setGridWidth',width);
},
dummyTestRegex = new RegExp(dummyColumnNamePrefix+"(\\d)+"),
counter = 1;
// Add dummy hidden columns. All the columns has the same template
for (i=0;i<maxCol;i++) {
cm.push({name:dummyColumnNamePrefix+(i+1),template:currentTemplate});
}
mygrid.jqGrid({
url:'Matrix1.json',
datatype: "json",
// colNames will be set based on the properties for JSON input
colModel:cm,
height:"auto",
rownumbers:true,
loadonce:true,
gridview: true,
rowNum: 1000,
sortname:"",
jsonReader: {
cell: "",
id: function (obj) {
return "id"+counter++;
},
page: function (obj) {
var rows = obj.rows, colModel = mygrid[0].p.colModel,
cmi, width = 0, iFirstDummy, cols, orgShrinkToFit,
showColNames = [], hideColNames = [];
if (typeof(rows) === "undefined" || !(rows.length>0)) {
// something wrong need return
return obj.page;
}
// find the index of the first dummy column
// in the colModel. If we use rownumbers:true,
// multiselect:true or subGrid:true additional
// columns will be inserted at the begining
// of the colModel
iFirstDummy = -1;
for(i=0;i<colModel.length;i++) {
cmi = colModel[i];
if (dummyTestRegex.test(cmi.name)) {
iFirstDummy = i;
break;
}
}
if (iFirstDummy === -1) {
// something wrong need return
return obj.page;
}
orgShrinkToFit = clearShrinkToFit();
// we get the first row of the JSON data
cols = rows[0].length;
// fill the list of unused columns
for(i=0;i<colModel.length;i++) {
cmi = colModel[i];
if (i<iFirstDummy+cols) {
cmi.width = currentTemplate.width;
showColNames.push(cmi.name);
} else {
hideColNames.push(cmi.name);
}
}
mygrid.jqGrid('showCol',showColNames);
mygrid.jqGrid('hideCol',hideColNames);
setGridWidthAndRestoreShrinkToFit(orgShrinkToFit,
cols*currentTemplate.width);
return obj.page;
}
}
});
$("#readJson1").click(function() {
mygrid.jqGrid('setGridParam',
{datatype:'json',page:1,url:'Matrix1.json'})
.trigger('reloadGrid');
});
$("#readJson2").click(function() {
mygrid.jqGrid('setGridParam',
{datatype:'json',page:1,url:'Matrix2.json'})
.trigger('reloadGrid');
});
The simplest way to transpose the matrix (reflect it over its main diagonal) is on the server. If you can't do this, you can create and fill the transposed matrix inside of page function (see my code above) and just replace the row part of the obj with the transposed matrix.

Resources