How to add a subtotal column in Datatables - 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>

Related

Row Span AutoGrouping and Bug

I've created a plnkr to auto-group row-spans the way you would really expect it to work out of the box IMHO.
Anyhow... doing this surfaces an apparent bug... the rowSpan is not consistently applied to the grid.. if you scroll up and down, it sometimes applies, and sometimes does not.
In the screenshot below... you can see 'Aaron Peirsol' is spanning... but if I scroll up and down it might not span on him... not consistent.
Here 'Aaron Peirsol' is no longer spanning all 3 rows -- all I did was scroll up and back down
See this Sample
https://plnkr.co/edit/UxOcCL1SEY4tScn2?open=app%2Fapp.component.ts
Here I've added columndefs for the grouping
{
field: 'athlete',
rowSpan: params => params.data.groupCount,
cellClassRules: {
'cell-span': "data.isFirst && data.groupCount>1",
},
width: 200,
},
{field:'groupCount', width: 20}, /* included for debugging */
{field:'isFirst', width: 20}, /* included for debugging */
And here I'm doing the auto-grouping code:
onGridReady(params: GridReadyEvent) {
this.http
.get<any[]>('https://www.ag-grid.com/example-assets/olympic-winners.json')
.subscribe((data) => {
let groupKey = 'athlete';
let sorted = data.sort((a,b) => (a[groupKey] > b[groupKey]) ? 1 :
((b[groupKey] > a[groupKey]) ? -1 : 0));
let filtered = sorted.filter(x => {
return x[groupKey] < 'Albert' && x[groupKey];
});
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
let keyValue = x[key];
if (rv[keyValue] === undefined)
{
rv[keyValue] = 0;
}
if (keyValue) {
rv[keyValue] ++;
}
return rv;
}, {});
};
let grouped = groupBy(filtered, groupKey);
let prev = '';
for (let i=0; i<filtered.length; i++)
{
let keyValue = filtered[i][groupKey];
filtered[i]['groupCount'] = grouped[keyValue];
if (keyValue == prev)
{
filtered[i]['isFirst'] = false;
}
else
{
filtered[i]['isFirst'] = true;
}
prev = keyValue;
}
this.rowData = filtered});
}
OK, found the issue...
rowSpan function must only return a span count for the first row of the span...
every other row it must return 1
I've updated the plunker
public columnDefs: ColDef[] = [
{
field: 'athlete',
rowSpan: params => params.data.groupRowCount == 1 ? params.data.groupCount: 1, //THIS IS CRUCIAL.. only return count for first row
cellClassRules: {
'cell-span': "data.groupRowCount==1 && data.groupCount>1",
},
width: 200,
},

use Swiper.js to do a circle carousel

It's my code
https://codepen.io/daylilystudio/pen/XWzVvVb
I make a circle carousel like Ferris wheel,
but after every 5 carousel,a group will fade in from right.
I don't want the group fade in suddenly.
How can I just fade in one slide every time?
var certifySwiper = new Swiper('#certify .swiper-container', {
watchSlidesProgress: true,
slidesPerView: 'auto',
centeredSlides: true,
loop: true,
autoplay: true,
loopedSlides: 5,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
//clickable :true,
},
on: {
progress: function(progress) {
for (i = 0; i < this.slides.length; i++) {
var slide = this.slides.eq(i);
var slideProgress = this.slides[i].progress;
modify = 1;
if (Math.abs(slideProgress) > 1) {
modify = (Math.abs(slideProgress) - 1) * 0.3 + 1;
}
translate = slideProgress * modify * 260 + "px";
scale = 1 - Math.abs(slideProgress) / 5;
zIndex = 999 - Math.abs(Math.round(10 * slideProgress));
slide.transform("translateX(" + translate + ") scale(" + scale + ")");
slide.css("zIndex", zIndex);
slide.css("opacity", 1);
if (Math.abs(slideProgress) > 3) {
slide.css("opacity", 0);
}
}
},
setTransition: function(transition) {
for (var i = 0; i < this.slides.length; i++) {
var slide = this.slides.eq(i);
slide.transition(transition);
}
}
}
})

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;
}())
}]
});
});
});

Pivot data dynamically for google line chart

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);

Highcharts - Lazy loading combined with SQL queries

Based on the Lazy Loading Example I wanted to create the similar function for my Highcharts.StockChart. But with the aid of Ajax.
Some words about the database structure:
Tables contains the values of 1 month
Column 'tijd' contains a timestamp
Values are posted every minute to the database, resolution = 1 minute
Please focus on why the zoom function isn't updating the chart. This drives me #!#
For the first time generation of the chart I use this code, which works fine
JAVASCRIPT:
function generateChart(param, options)
{
// EERST DE GESELECTEERDE GEGEVENS GAAN OPVRAGEN
data = param.join();
console.log(data);
t_min = new Date(2015, 0, 1, 0, 0, 0, 0);
t_max = Date.now();
console.log(t_min, t_max);
console.log((t_max - t_min) / 1000);
$.ajax({
url: url,
data: { "data": data, "db": klantnummer, "t_min": t_min, "t_max": t_max },
type: "POST",
async: false,
cache: false,
success:
function (gegevens)
{
// GEGEVENS OPSLAAN IN DE OPTIE.SERIES
console.log('Gegevens:');
console.log(gegevens); //werkt!
options.series = gegevens;
}
});
//EFFECTIEF AANMAKEN VAN DE GRAFIEK
chart = new Highcharts.StockChart(options);
}
PHP:
// READ THE COLUMN NAMES
$data_in = $_POST['data'];
// DATA_IN to array
$name = explode(",", $data_in);
// count the amount of columns
$c_name = count($name);
$name_sql = implode(", ", $name);
//Connection with database
$klantnummer = $_POST['db'];
require 'connectie.php';
//ZOOM: min and max values are converterd to PHP UNIX EPOCH time
$t_min = ($_POST['t_min'])/1000;
$t_max = ($_POST['t_max'])/1000;
$range = $t_max - $t_min;
//Doesn't work, don't need it to load the chart
//$startTime = $t_min->format('m-d-Y H:i:s');
//$stopTime = $t_max->format('m-d-Y H:i:s');
//we only ask a query if there are enough columns
if ($c_name > 0) {
//Ask the names of the tables from the database
$sql = "SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_NAME LIKE 'T%' ORDER BY TABLE_NAME DESC";
$tableName = sqlsrv_query($conn, $sql);
if ( $tableName === false ) {
die(print_r(sqlsrv_errors(), true));
};
$t = array();
while ($row = sqlsrv_fetch_array($tableName)) {
array_push($t, $row['TABLE_NAME']);
};
// So we know the range, which filter should we apply?
if ($range < 14 * 24 *3600) { //2 weeks
$tijdspanne = 2; // load 2 tables
$filter = 1; // show every minute
} elseif ($range < 28 * 24 *3600) { //4 weeks
$tijdspanne = 2; // load 2 tables
$filter = 5; // show every 5 minutes
} elseif ($range < 3 * 28 * 24 *3600) { //3 months
$tijdspanne = 4; // load 4 months
$filter = 15; // show every 15 minutes
} else {
$tijdspanne = count($t); // load every table
$filter = 60; // show every 60 minutes
}
//Namen zijn gekend, nu in query steken om daarna op te vragen
$sql = "select tijd,".$name_sql." FROM ".$t[0]." where datepart(mi,tijd) % ".$filter." = 0";// AND tijd BETWEEN ".$startTime." AND ".$stopTime;
for ($x = 1; $x < count($t) and $x < $tijdspanne ; ++$x ) {
$sql .= " union all select tijd,".$name_sql." FROM ".$t[$x]." where datepart(mi,tijd) % ".$filter." = 0";// AND tijd BETWEEN ".$startTime." AND ".$stopTime;
};
$sql .= " ORDER BY tijd";
//DATA from MS SQL server, save to arrays
$stmt = sqlsrv_query($conn, $sql);
if ( $stmt === false ) {
die(print_r(sqlsrv_errors(), true));
}
while ($row = sqlsrv_fetch_array($stmt)) {
$tijd_U = $row["0"]->format("U");
$tijd = ($tijd_U) * 1000; //date format to javascript
for ($i = 0; $i < $c_name; ++$i) {
$j = $i +1;
$data[$i][]= array($tijd, $row[$j]);
}
}
// prepare JSON format
$data_output = array();
for ($i = 0; $i < $c_name; ++$i) {
$data_output[$i] = array('name' => $name[$i], 'data' => $data[$i]);
}
}
// admit we use JSON
header('Content-Type: application/json');
// send the data to the user
echo json_encode($data_output, JSON_NUMERIC_CHECK);
So this code works, but when the user zooms in. The graph doens't update.
JAVASCRIPT:
function afterSetExtremes(e)
{
console.log(Math.round(e.min));
chart.showLoading('Loading data from server...');
$.ajax({
url: url,
data: { "data": data, "db": klantnummer, "t_min": Math.round(e.min), "t_max": Math.round(e.max) },
type: "POST",
async: false,
cache: false,
success:
function (gegevens)
{
// GEGEVENS OPSLAAN IN DE OPTIE.SERIES
console.log('Gegevens:');
console.log(gegevens); //werkt!
options.series = gegevens;
}
});
chart.hideLoading();
}
With this set of options:
function generateChartOptions(div, ymax, ymin)
{
var options = {
navigator: {
adaptToUpdatedData: false,
series: {
includeInCSVExport: false
}
},
chart: {
renderTo: div,
type: 'line',
zoomType: 'x'
},
rangeSelector: {
buttons: [{
type: 'day',
count: 1,
text: '1d'
}, {
type: 'week',
count: 1,
text: '1w'
}, {
type: 'week',
count: 2,
text: '2w'
}, {
type: 'all',
text: 'All'
}],
selected: 4,
inputBoxWidth: 150,
inputDateFormat: '%d-%m-%Y %H:%M',
inputEditDateFormat: '%d-%m-%Y %H:%M'
},
legend: {
enabled: true
},
xAxis: {
type: 'datetime',
events: {
afterSetExtremes: afterSetExtremes,
setExtremes: function (e)
{
$('#testveld').html('<b>Set extremes:</b> e.min: ' + Highcharts.dateFormat(null, e.min) +
' | e.max: ' + Highcharts.dateFormat(null, e.max) + ' | e.trigger: ' + e.trigger);
}
},
minRange: 60 * 1000 //1 minuut
},
yAxis: {
opposite: false,
max: ymax,
min: ymin
},
plotOptions: {
line: {
tooltip: {
valueDecimals: 1
}
}
},
scrollbar: {
liveRedraw: false
},
series: [{}]
};
return options;
}