Merge remote-tracking branch 'upstream/master'
diff --git a/render.js b/render.js
index 27c37bd..28dc211 100644
--- a/render.js
+++ b/render.js
@@ -283,6 +283,28 @@
}
+ function zpad(n) {
+ var s = '' + n;
+ if (s.length < 2) s = '0' + s;
+ return s;
+ }
+
+
+ function dateToStr(d) {
+ return (d.getFullYear() + '-' +
+ zpad(d.getMonth() + 1) + '-' +
+ zpad(d.getDate()));
+ }
+
+
+ function dateTimeToStr(d) {
+ return (dateToStr(d) + ' ' +
+ zpad(d.getHours()) + ':' +
+ zpad(d.getMinutes()) + ':' +
+ zpad(d.getSeconds()));
+ }
+
+
function parseDates(data, types) {
for (var coli in types) {
var type = types[coli];
@@ -440,6 +462,37 @@
return agg_funcs.sum(l) / agg_funcs.count_nz(l);
},
+ // also works for non-numeric values, as long as they're sortable
+ median: function(l) {
+ var comparator = function(a, b) {
+ a = a || '0'; // ensure consistent ordering given NaN and undefined
+ b = b || '0';
+ if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ if (l.length > 0) {
+ l.sort(comparator);
+ return l[parseInt(l.length/2)];
+ } else {
+ return null;
+ }
+ },
+
+ stddev: function(l) {
+ var avg = agg_funcs.avg(l);
+ var sumsq = 0.0;
+ for (var i in l) {
+ var d = parseFloat(l[i]) - avg;
+ if (d) sumsq += d * d;
+ }
+ return Math.sqrt(sumsq);
+ },
+
color: function(l) {
for (var i in l) {
var v = l[i];
@@ -455,6 +508,7 @@
agg_funcs.count_distinct.return_type = T_NUM;
agg_funcs.sum.return_type = T_NUM;
agg_funcs.avg.return_type = T_NUM;
+ agg_funcs.stddev.return_type = T_NUM;
agg_funcs.cat.return_type = T_STRING;
agg_funcs.color.return_type = T_NUM;
@@ -538,7 +592,7 @@
var colkey = [];
for (var coli in colkey_incols) {
var colnum = colkey_incols[coli];
- colkey.push(row[colnum]);
+ colkey.push(stringifiedCol(row[colnum], ingrid.types[colnum]));
}
for (var coli in valkeys) {
var xcolkey = colkey.concat([valkeys[coli]]);
@@ -548,7 +602,7 @@
// just clutter.
var name = valkeys.length > 1 ?
xcolkey.join(' ') : colkey.join(' ');
- var colnum = keyToColNum(ingrid, valkeys[coli]);
+ var colnum = rowkeys.length + colkeys.length + parseInt(coli);
colkey_outcols[xcolkey] = outgrid.headers.length;
valuecols[xcolkey] = colnum;
outgrid.headers.push(name);
@@ -568,7 +622,7 @@
var colkey = [];
for (var coli in colkey_incols) {
var colnum = colkey_incols[coli];
- colkey.push(row[colnum]);
+ colkey.push(stringifiedCol(row[colnum], ingrid.types[colnum]));
}
for (var coli in valkeys) {
var xcolkey = colkey.concat([valkeys[coli]]);
@@ -583,16 +637,21 @@
}
+ function stringifiedCol(value, typ) {
+ if (typ === T_DATE) {
+ return dateToStr(value) || '';
+ } else if (typ === T_DATETIME) {
+ return dateTimeToStr(value) || '';
+ } else {
+ return (value + '') || '(none)';
+ }
+ }
+
+
function stringifiedCols(row, types) {
var out = [];
for (var coli in types) {
- if (types[coli] === T_DATE) {
- out.push(row[coli].strftime('%Y-%m-%d') || '');
- } else if (types[coli] === T_DATETIME) {
- out.push(row[coli].strftime('%Y-%m-%d %H:%M:%S') || '');
- } else {
- out.push((row[coli] + '') || '(none)');
- }
+ out.push(stringifiedCol(row[coli], types[coli]));
}
return out;
}
@@ -1412,7 +1471,7 @@
$(el).height(window.innerHeight);
options.height = window.innerHeight;
t.draw(datatable, options);
- }
+ };
doRender();
$(window).resize(function() {
clearTimeout(resizeTimer);
@@ -1675,6 +1734,7 @@
trySplitOne: trySplitOne,
dataToGvizTable: dataToGvizTable,
guessTypes: guessTypes,
+ myParseDate: myParseDate,
groupBy: groupBy,
pivotBy: pivotBy,
stringifiedCols: stringifiedCols,
@@ -1691,6 +1751,11 @@
runqueue: runqueue,
gridFromData: gridFromData
},
+ T_NUM: T_NUM,
+ T_DATE: T_DATE,
+ T_DATETIME: T_DATETIME,
+ T_BOOL: T_BOOL,
+ T_STRING: T_STRING,
parseArgs: parseArgs,
exec: exec,
render: wrap(render)
diff --git a/t/trender.js b/t/trender.js
index ba09458..e65ff81 100644
--- a/t/trender.js
+++ b/t/trender.js
@@ -239,3 +239,78 @@
WVPASSEQ(grid.data, [[1, 1, 2]]);
});
});
+
+wvtest('pivot', function() {
+ var rawdata = [
+ ['a', 'b', 'c'],
+ ['fred', 9, '2013/01/02'],
+ ['bob', 7, '2013/01/01'],
+ ['fred', 11, '2013/02/03']
+ ];
+ var mpd = afterquery.internal.myParseDate;
+ var dlist = [mpd('2013/01/02'), mpd('2013/01/01'), mpd('2013/02/03')];
+ afterquery.exec('group=a,b;only(c),count(c),sum(c),min(c),max(c),' +
+ 'avg(c),median(c),stddev(c)', rawdata, function(grid) {
+ WVPASSEQ(grid.headers, ['a', 'b', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c']);
+ WVPASSEQ(grid.data, [
+ ['fred', 9, dlist[0], 1, 0, dlist[0], dlist[0], 0, dlist[0], 0],
+ ['bob', 7, dlist[1], 1, 0, dlist[1], dlist[1], 0, dlist[1], 0],
+ ['fred', 11, dlist[2], 1, 0, dlist[2], dlist[2], 0, dlist[2], 0]
+ ]);
+ });
+ afterquery.exec('group=;count(b),sum(b),min(b),max(b),' +
+ 'avg(b),median(b),stddev(b)', rawdata, function(grid) {
+ WVPASSEQ(grid.headers, ['b', 'b', 'b', 'b', 'b', 'b', 'b']);
+ WVPASSEQ(grid.data, [[3, 27, 7, 11, 27.0/3.0, 9, Math.sqrt(8)]]);
+ });
+ afterquery.exec('pivot=a;b;only(c)', rawdata, function(grid) {
+ WVPASSEQ(grid.headers, ['a', 9, 7, 11]);
+ WVPASSEQ(grid.types, [
+ afterquery.T_STRING,
+ afterquery.T_DATE,
+ afterquery.T_DATE,
+ afterquery.T_DATE
+ ]);
+ WVPASSEQ(grid.data, [
+ ['fred', dlist[0], null, dlist[2]],
+ ['bob', null, dlist[1], null]
+ ]);
+ });
+ afterquery.exec('pivot=a;b;c', rawdata, function(grid) {
+ WVPASSEQ(grid.headers, ['a', 9, 7, 11]);
+ WVPASSEQ(grid.types, [
+ afterquery.T_STRING,
+ afterquery.T_NUM,
+ afterquery.T_NUM,
+ afterquery.T_NUM
+ ]);
+ WVPASSEQ(grid.data, [
+ ['fred', 1, null, 1],
+ ['bob', null, 1, null]
+ ]);
+ });
+ afterquery.exec('pivot=a;b;only(c),count(c)', rawdata, function(grid) {
+ WVPASSEQ(grid.headers, [
+ 'a',
+ '9 only(c)', '9 count(c)',
+ '7 only(c)', '7 count(c)',
+ '11 only(c)', '11 count(c)'
+ ]);
+ WVPASSEQ(grid.data, [
+ ['fred', dlist[0], 1, null, null, dlist[2], 1],
+ ['bob', null, null, dlist[1], 1, null, null]
+ ]);
+ });
+ afterquery.exec('pivot=a;b,c;count(*)', rawdata, function(grid) {
+ WVPASSEQ(grid.headers, [
+ 'a',
+ '9 2013-01-02',
+ '7 2013-01-01',
+ '11 2013-02-03'
+ ]);
+ WVPASSEQ(grid.data, [
+ ['fred', 1, null, 1],
+ ['bob', null, 1, null]
+ ]);
+ });
+});