Pivot data dynamically for google line chart - dynamic

I want to display "population" of various countries through the years in the same line chart. The data displayed is based on selections from a multi-select dropdown "Countries". Underlying Data Table has 3 columns:
Year, Country, Population
2012,countryA,33
2013,countryA,35
2014,countryA,40
2012,countryB,65
2013,countryB,70
2014,countryB,75
2012,countryC,15
2013,countryC,20
2014,countryC,25
I am trying to create a pivoted Data View from the underlying Data Table
The code I am using is:
function drawLineChart() {
var arr = $('#country').val();
var lineChartJson = $.ajax({
url: "../json/lineChart.json",
dataType: "json",
async: false
}).responseText;
var lineChartData = new google.visualization.DataTable(lineChartJson);
var view = new google.visualization.DataView(lineChartData);
var viewCols = [0];
for(var i = 0; i < arr.length; i++) {
var viewCols1 = [{
type: 'number',
label: arr[i],
calc: function (dt, row) {
return (dt.getValue(row, 1) == arr[i]) ? dt.getValue(row, 2) : null;
}
}];
viewCols = viewCols.concat(viewCols1);
}
view.setColumns(viewCols);
var aggCols = [{
column: 1,
type: 'number',
label: view.getColumnLabel(1),
aggregation: google.visualization.data.sum
}];
for(var i = 2; i < 4; i++) {
var aggCols1 = [{
column: i,
type: 'number',
label: view.getColumnLabel(i),
aggregation: google.visualization.data.sum
}];
aggCols = aggCols.concat(aggCols1);
}
var pivotedData = google.visualization.data.group(view, [0], aggCols);
But this does not seem to work as expected and I just get 1 Line in the chart with values for all countries added up (although I can see the legend for 3 countries)
On the other hand if I set my View columns as below, it works as expected.
view.setColumns([0, {
type: 'number',
label: arr[0],
calc: function (dt, row) {
return (dt.getValue(row, 1) == arr[0]) ? dt.getValue(row, 2) : null;
}
}, {
type: 'number',
label: arr[1],
calc: function (dt, row) {
// return values of C only for the rows where B = "bar"
return (dt.getValue(row, 1) == arr[1]) ? dt.getValue(row, 2) : null;
}
}, {
type: 'number',
label: arr[2],
calc: function (dt, row) {
return (dt.getValue(row, 1) == arr[2]) ? dt.getValue(row, 2) : null;
}
}]);
What is going wrong in the loop? Is something wrong with "concat" in the loop where I am creating View Columns? I also saw the viewCols array by using console.log and it seems to have the right elements
I was trying to follow the below post:
Creating pivoted DataView from existing google charts DataTable object

the problem has to do with scope
arr[i] is undefined within calc: function (dt, row)
here is another way to pivot the data...
google.charts.load('current', {
callback: function () {
var arr = [
'countryA',
'countryB',
'countryC'
];
var lineChartData = google.visualization.arrayToDataTable([
['Year', 'Country', 'Population'],
[2012,'countryA',33],
[2013,'countryA',35],
[2014,'countryA',40],
[2012,'countryB',65],
[2013,'countryB',70],
[2014,'countryB',75],
[2012,'countryC',15],
[2013,'countryC',20],
[2014,'countryC',25]
]);
// sort by year
lineChartData.sort([{column: 0}]);
// get unique countries
var countryGroup = google.visualization.data.group(
lineChartData,
[1]
);
// build country data table
var countryData = new google.visualization.DataTable({
cols: [
{label: 'Year', type: 'number'},
]
});
// add column for each country
for (var i = 0; i < countryGroup.getNumberOfRows(); i++) {
countryData.addColumn(
{label: countryGroup.getValue(i, 0), type: 'number'}
);
}
// add row for each year / country
var rowYear;
var rowIndex;
for (var i = 0; i < lineChartData.getNumberOfRows(); i++) {
if (rowYear !== lineChartData.getValue(i, 0)) {
rowYear = lineChartData.getValue(i, 0);
rowIndex = countryData.addRow();
countryData.setValue(rowIndex, 0, rowYear);
}
for (var x = 1; x < countryData.getNumberOfColumns(); x++) {
if (countryData.getColumnLabel(x) === lineChartData.getValue(i, 1)) {
countryData.setValue(rowIndex, x, lineChartData.getValue(i, 2));
}
}
}
// draw agg table
new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'table-div',
dataTable: countryData
}).draw();
// draw line chart
new google.visualization.ChartWrapper({
chartType: 'LineChart',
containerId: 'chart-div',
dataTable: countryData
}).draw();
},
packages: ['corechart', 'table']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="table-div"></div>
<div id="chart-div"></div>

I could figure out the problem with my code above.
"calc" is the callback function in loop. So only last value of loop variable "i" is visible within the loop.
Putting a wrapper function fixes it:
for(var i = 0; i <= arr.length; i++)(function(i) {
var viewCols1 = [{
type: 'number',
label: arr[i],
calc: function (dt, row) {
return (dt.getValue(row, 1) == arr[i]) ? dt.getValue(row, 2) : null;
}
}];
viewCols = viewCols.concat(viewCols1);
})(i);

Related

How to add a subtotal column in Datatables

My last question was related to the sum of columns (vertically) and now I need to sum the values of the columns (horizontally)
What I've tried:
I've tried iterating all columns table.columns().data() and sum the values but I do not know how to "inject" the result of the sum in the last column.
Desired result:
Sub Total is the result of: 3+4+5+6+7+8+9+10+11+12
LIVE JS BIN
Here is your JS code with your old code. You can also see it from here
<script>
$(document).ready(function() {
var DT1 = $('#example').DataTable({
columnDefs: [{
orderable: false,
className: 'select-checkbox',
targets: 0,
}],
select: {
style: 'os',
selector: 'td:first-child'
},
order: [
[1, 'asc']
],
"language": {
"info": "Showing _START_ to _END_ of _TOTAL_ Projects",
"infoEmpty": "Showing 0 to 0 of 0 Projects",
"infoFiltered": "(filtered from _MAX_ total Projects)"
},
"pagingType": "numbers",
dom: 'rtip',
initComplete: function(settings, json) {
// calculate the sum when table is first created:
doSum();
}
});
$('#example').on('draw.dt', function() {
// re-calculate the sum whenever the table is re-displayed:
doSum();
});
$(".selectAll").on("click", function(e) {
if ($(this).is(":checked")) {
DT1.rows().select();
} else {
DT1.rows().deselect();
}
});
// This provides the sum of all records:
function doSum() {
// get the DataTables API object:
var table = $('#example').DataTable();
// set up the initial (unsummed) data array for the footer row:
var totals = ['', 'Total:', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
// iterate all rows - use table.rows( {search: 'applied'} ).data()
// if you want to sum only filtered (visible) rows:
totals = table.rows().data()
// sum the amounts:
.reduce(function(sum, record) {
for (let i = 2; i <= 12; i++) {
sum[i] = sum[i] + numberFromString(record[i]);
}
return sum;
}, totals);
// place the sum in the relevant footer cell:
for (let i = 1; i <= 12; i++) {
var column = table.column(i);
$(column.footer()).html(formatNumber(totals[i]));
}
$('#example > tbody > tr').each(function(index, tr) {
var rowSum = 0;
for (let j = 0; j < 12; j++) {
if (j >= 2) {
var tdCellValue = numberFromString(tr.cells[j].innerText);
rowSum = rowSum + tdCellValue;
}
if (j >= 11) {
tr.cells[12].innerText = rowSum;
}
}
});
$('#example > tfoot > tr').each(function(index, tr) {
var rowSum = 0;
for (let j = 0; j < 12; j++) {
if (j >= 2) {
var tdCellValue = numberFromString(tr.cells[j].innerText);
rowSum = rowSum + tdCellValue;
}
if (j >= 11) {
tr.cells[12].innerText = rowSum;
}
}
});
}
function numberFromString(s) {
return typeof s === 'string' ?
s.replace(/[\$,]/g, '') * 1 :
typeof s === 'number' ?
s : 0;
}
function formatNumber(n) {
return n.toLocaleString(); // or whatever you prefer here
}
});
</script>

How group and count elements by lodash

has data
items = {
0: {id:1,name:'foo'},
1: {id:2,name:'bar'},
2: {id:1,name:'foo'}
};
I wont get counted elements like this
result = {
0: {id:1,name:'foo', count:2},
1: {id:2,name:'bar', count:1}
};
lodash has function _.countBy(items, 'name') it's got {'foo': 2, 'bar':1}, i need id too.
If pure JS approach is acceptable, you can try something like this:
Logiic:
Loop over array and copy the object and add a property count and set it to 0.
Now on every iteration update this count variable.
Using above 2 steps, create a hashMap.
Now loop over hashMap again and convert it back to array.
var items = [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 1,
name: 'foo'
}
];
var temp = items.reduce(function(p,c){
var defaultValue = {
name: c.name,
id: c.id,
count: 0
};
p[c.name] = p[c.name] || defaultValue
p[c.name].count++;
return p;
}, {});
var result = [];
for( var k in temp ){
result.push(temp[k]);
}
console.log(result)

c3.js total count in title of pie chart

I have a question about the pie chart in c3.js.
How can I add the total count of a pie chart in the title??
var title = new Array('data1.sql','data2.sql')
var dtitle = new Array('title1','title2')
var chart = new Array('chart0', 'chart1')
for(var i = 0; i < title.length; i++){
chart[i] = c3.generate({
bindto : '#chart' + i,
size: {
height: 550,
width: 800
},
data : {
url : '/json/sql/data/test/' + title[i],
mimeType : 'json',
type : 'donut'
},
donut: {
title: dtitle[i] + ' - Total:' ,
label: {
format: function(value, ratio, id) {
return value;
}
}
}
});
}
The annoying thing here is that the title option can take a function, but the chart variable is not initialised within it so using the c3 api methods can't be done at this point.
So the best (maybe only) way is to add an onrendered callback that adds up the data as you'd need to anyways and then replace the text in the chart's title text using a spot of d3:
onrendered: function () {
var data = this.api.data();
var total = data.reduce (function (subtotal, t) {
return subtotal + t.values.reduce (function (subsubtotal,b) { return subsubtotal + b.value; }, 0);
}, 0);
d3.select(this.config.bindto + " .c3-chart-arcs-title").text("Total: "+total);
}
Edit: If you want it to keep track of a total as you hide/show series use this
var data = this.api.data.shown.call (this.api);
instead of
var data = this.api.data();

Dynamically update lines in Highcharts time series chart

Here I'm working on Highcharts time series chart with live streaming data, based on the sample jsfiddle. In the fiddle there shows 4 lines named as input1, input2, input3, & input 4 and it is updated with live random data but in my actual project the input values are updated via MQTT. In actual project, sometimes, when we push streaming data, there will be increase or decrease in no: of inputs (such as input5, input6 like wise). So how can we add new line or remove line dynamically in time series chart with streaming data.
javascript code :
$(function() {
$(document).ready(function() {
Highcharts.setOptions({
global: {
useUTC: false
}
});
$('#container').highcharts({
chart: {
type: 'spline',
animation: Highcharts.svg, // don't animate in old IE
marginRight: 10,
events: {
load: function() {
// set up the updating of the chart each second
var series = this.series;
var length = series.length;
setInterval(function() {
var x = (new Date()).getTime(), // current time
a0 = Math.random();
a1 = Math.random();
a2 = Math.random();
series[0].addPoint([x, Math.random()], true, true);
for (var i = 1; i < length; i++) {
series[i].addPoint([x, Math.random()], false, true);
}
}, 1000);
}
}
},
title: {
text: 'Live random data'
},
legend: {
enabled: true
},
xAxis: {
type: 'datetime',
tickPixelInterval: 150
},
yAxis: {
title: {
text: 'Value'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
tooltip: {
formatter: function() {
return '<b>' + this.series.name + '</b><br/>' +
Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +
Highcharts.numberFormat(this.y, 2);
}
},
exporting: {
enabled: false
},
series: [{
name: 'input1',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'input2',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'input3',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'input4',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}]
});
});
});

How to get textbox widget inside grid to work property?

I have placed a textbox widget inside grid cell by using formatter. However, I cannot move my cursor around nor select text inside the textbox.
E.g.
http://jsfiddle.net/g33m9/69/
Does anyone know how to fix this?
Thanks
You need to set the column as 'editable' so that the Grid component will know how to handle keypressed events. So a modification to the layout is in order
from
var layout = [[
{name: 'Column 1', field: 'col1'},
{name: 'Column 2', field: 'col2', width:'200px', formatter: func}
]];
to
var layout = [[
{name: 'Column 1', field: 'col1'},
{name: 'Column 2', field: 'col2', width:'200px', formatter: func, editable: true}
]];
Edit state activates by doubleclick.
Now, OP wants it to be a fully bloated widget, popping up in the editable state. For this to be scaleable up with any number of rows/columns i will restrict this to the edit state, so that the value simply shows text but once double-clicked it will pop a FilteringSelect. Same principle goes with the dijit widget ValidationTextBox.
Currently (1.7.2) the possible celltypes are:
dojox.grid.cells.Bool
dojox.grid.cells.ComboBox
dojox.grid.cells.DateTextBox
dojox.grid.cells.Select
Catch me SEO:
example of custom dojox.grid cellType widget - semi-programmatic
First step - create some data
var i = 0,
data = {
identifier: 'id',
items: [
{ id: i, value: 'val'+i++},
{ id: i, value: 'val'+i++},
{ id: i, value: 'val'+i++},
{ id: i, value: 'val'+i++}
]
},
// The item label which holds visible value and which holds the value to represent
searchAttr = 'value',
valueAttr = data.identifier,
// The store to use for select widget
store = new dojo.data.ItemFileReadStore({ data: data }),
// And the options, reassembling the valid options we will present in dropdown
// Used when cellType is dojox.grid.cells.Select to name the allowable options
options = [];
dojo.forEach(data.items, function(it) { options.push(it[searchAttr])});
Tricky part - Define a cellType
Lets extend the existing dojox.grid.cells.Cell, it has two key features - an edit-state-formatter and the default-formatter. The default would work just fine. Last but not least, we'll override the '_finish' function allthough allow the Cell to process its own definition too.
var whenIdle = function( /*inContext, inMethod, args ...*/ ) {
setTimeout(dojo.hitch.apply(dojo, arguments), 0);
};
var FilteringSelectCell = declare("dojox.grid.cells.FilteringSelect", [dojox.grid.cells.Cell], {
options: null,
values: null,
_destroyOnRemove: true,
constructor: function(inCell){
this.values = this.values || this.options;
},
selectMarkupFactory: function(cellData, rowIndex) {
var h = ['<select data-dojo-type="dijit.form.FilteringSelect" id="deleteme' + rowIndex + '" name="foo">'];
for (var i = 0, o, v;
((o = this.options[i]) !== undefined) && ((v = this.values[i]) !== undefined); i++) {
v = v.replace ? v.replace(/&/g, '&').replace(/</g, '<') : v;
o = o.replace ? o.replace(/&/g, '&').replace(/</g, '<') : o;
h.push("<option", (cellData == v ? ' selected' : ''), ' value="' + v + '"', ">", o, "</option>");
}
h.push('</select>');
return h;
},
textMarkupFactory: function(cellData, rowIndex) {
return ['<input class="dojoxGridInput" id="deleteme' + rowIndex + '" data-dojo-type="dijit.form.ValidationTextBox" type="text" value="' + cellData + '">']
},
// #override
formatEditing: function(cellData, rowIndex) {
this.needFormatNode(cellData, rowIndex);
var h = (cellData == "W1")
? this.textMarkupFactory(cellData, rowIndex)
: this.selectMarkupFactory(cellData, rowIndex);
// a slight hack here, i had no time to figure out when the html would actually be inserted to the '<td>' so.. Use 'debugger' statement and track function to hook into
whenIdle(function() {
dojo.parser.parse(dojo.byId('deleteme' + rowIndex).parentNode);
var w = dijit.byId('deleteme' + rowIndex);
w.focus()
});
return h.join('');
},
// clean up avoiding multiple widget definitions 'hanging'
_finish: function(inRowIndex) {
this.inherited(arguments)
dijit.byId('deleteme' + inRowIndex).destroy();
},
// needed to read the value properly, will work with either variant
getValue: function(rowIndex) {
var n = this.getEditNode(rowIndex);
n = dijit.getEnclosingWidget(n);
return n.get("value");
}
});
Last bit, a new layout
var layout = [[
{ name: 'Column 1', field: 'col1' },
{ name: 'Column 2', field: 'col2',
cellType: FilteringSelectCell, options: options, editable: true
}
]];
Running sample here http://jsfiddle.net/dgbxw/1/