Skip to content

Instantly share code, notes, and snippets.

@mikaelhm
Created July 24, 2014 01:19
Show Gist options
  • Save mikaelhm/e921cced496eb6586092 to your computer and use it in GitHub Desktop.
Save mikaelhm/e921cced496eb6586092 to your computer and use it in GitHub Desktop.
Pivot operations from node.js
/**
* This file is a simplified version of https://github.com/nicolaskruchten/pivottable
* that does not depend on UI web components or jQuery/
*
* This makes it possible to run from node.js
*
* / Mikael
*/
var _ = require('lodash-node');
/*
Short handles
*/
var __slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
_this = this,
__hasProp = {}.hasOwnProperty,
__bind = function (fn, me) {
return function () {
return fn.apply(me, arguments);
};
};
function deriveAttributes(record, derivedAttributes, f) {
var k, v, _ref;
for (k in derivedAttributes) {
v = derivedAttributes[k];
record[k] = (_ref = v(record)) !== null ? _ref : record[k];
}
for (k in record) {
if (!__hasProp.call(record, k)) {
continue;
}
if (record[k] === null) {
record[k] = "null";
}
}
return f(record);
}
function forEachRecord(input, derivedAttributes, f) {
var addRecord, compactRecord, i, j, k, record, tblCols, _i, _len, _ref, _results, _results1;
addRecord = function (record) {
return deriveAttributes(record, derivedAttributes, f);
};
if (_.isFunction(input)) {
return input(addRecord);
}
if (_.isArray(input)) {
if (_.isArray(input[0])) {
_results = [];
for (i in input) {
if (!__hasProp.call(input, i)) {
continue;
}
compactRecord = input[i];
if (!(i > 0)) {
continue;
}
record = {};
_ref = input[0];
for (j in _ref) {
if (!__hasProp.call(_ref, j)) continue;
k = _ref[j];
record[k] = compactRecord[j];
}
_results.push(addRecord(record));
}
return _results;
} else {
_results1 = [];
for (_i = 0, _len = input.length; _i < _len; _i++) {
record = input[_i];
_results1.push(addRecord(record));
}
return _results1;
}
// } else if (input instanceof jQuery) {
// tblCols = [];
// $("thead > tr > th", input).each(function (i) {
// return tblCols.push($(this).text());
// });
// return $("tbody > tr", input).each(function (i) {
// record = {};
// $("td", this).each(function (j) {
// return record[tblCols[j]] = $(this).text();
// });
// return addRecord(record);
// });
} else {
throw new Error("unknown input format");
}
};
PivotData = (function () {
function PivotData(aggregator, colAttrs, rowAttrs) {
this.aggregator = aggregator;
this.colAttrs = colAttrs;
this.rowAttrs = rowAttrs;
this.getAggregator = __bind(this.getAggregator, this);
this.flattenKey = __bind(this.flattenKey, this);
this.getRowKeys = __bind(this.getRowKeys, this);
this.getColKeys = __bind(this.getColKeys, this);
this.sortKeys = __bind(this.sortKeys, this);
this.arrSort = __bind(this.arrSort, this);
this.natSort = __bind(this.natSort, this);
this.tree = {};
this.rowKeys = [];
this.colKeys = [];
this.flatRowKeys = [];
this.flatColKeys = [];
this.rowTotals = {};
this.colTotals = {};
this.allTotal = this.aggregator(this, [], []);
this.sorted = false;
}
PivotData.prototype.natSort = function (as, bs) {
return naturalSort(as, bs);
};
PivotData.prototype.arrSort = function (a, b) {
return this.natSort(a.join(), b.join());
};
PivotData.prototype.sortKeys = function () {
if (!this.sorted) {
this.rowKeys.sort(this.arrSort);
this.colKeys.sort(this.arrSort);
}
return this.sorted = true;
};
PivotData.prototype.getColKeys = function () {
this.sortKeys();
return this.colKeys;
};
PivotData.prototype.getRowKeys = function () {
this.sortKeys();
return this.rowKeys;
};
PivotData.prototype.flattenKey = function (x) {
return x.join(String.fromCharCode(0));
};
PivotData.prototype.processRecord = function (record) {
var colKey, flatColKey, flatRowKey, rowKey, x;
colKey = (function () {
var _i, _len, _ref, _results;
_ref = this.colAttrs;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
x = _ref[_i];
_results.push(record[x]);
}
return _results;
}).call(this);
rowKey = (function () {
var _i, _len, _ref, _results;
_ref = this.rowAttrs;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
x = _ref[_i];
_results.push(record[x]);
}
return _results;
}).call(this);
flatRowKey = this.flattenKey(rowKey);
flatColKey = this.flattenKey(colKey);
this.allTotal.push(record);
if (rowKey.length !== 0) {
if (__indexOf.call(this.flatRowKeys, flatRowKey) < 0) {
this.rowKeys.push(rowKey);
this.flatRowKeys.push(flatRowKey);
}
if (!this.rowTotals[flatRowKey]) {
this.rowTotals[flatRowKey] = this.aggregator(this, rowKey, []);
}
this.rowTotals[flatRowKey].push(record);
}
if (colKey.length !== 0) {
if (__indexOf.call(this.flatColKeys, flatColKey) < 0) {
this.colKeys.push(colKey);
this.flatColKeys.push(flatColKey);
}
if (!this.colTotals[flatColKey]) {
this.colTotals[flatColKey] = this.aggregator(this, [], colKey);
}
this.colTotals[flatColKey].push(record);
}
if (colKey.length !== 0 && rowKey.length !== 0) {
if (!(flatRowKey in this.tree)) {
this.tree[flatRowKey] = {};
}
if (!(flatColKey in this.tree[flatRowKey])) {
this.tree[flatRowKey][flatColKey] = this.aggregator(this, rowKey, colKey);
}
return this.tree[flatRowKey][flatColKey].push(record);
}
};
PivotData.prototype.getAggregator = function (rowKey, colKey) {
var agg, flatColKey, flatRowKey;
flatRowKey = this.flattenKey(rowKey);
flatColKey = this.flattenKey(colKey);
if (rowKey.length === 0 && colKey.length === 0) {
agg = this.allTotal;
} else if (rowKey.length === 0) {
agg = this.colTotals[flatColKey];
} else if (colKey.length === 0) {
agg = this.rowTotals[flatRowKey];
} else {
agg = this.tree[flatRowKey][flatColKey];
}
return agg != null ? agg : {
value: (function () {
return null;
}),
format: function () {
return "";
}
};
};
return PivotData;
})();
module.exports.getPivotData = function (input, cols, rows, aggregator, filter, derivedAttributes) {
var pivotData;
pivotData = new PivotData(aggregator, cols, rows);
forEachRecord(input, derivedAttributes, function (record) {
if (filter(record)) {
return pivotData.processRecord(record);
}
});
return pivotData;
};
// aggregators
function addSeparators(nStr, thousandsSep, decimalSep) {
var rgx, x, x1, x2;
nStr += '';
x = nStr.split('.');
x1 = x[0];
x2 = x.length > 1 ? decimalSep + x[1] : '';
rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + thousandsSep + '$2');
}
return x1 + x2;
}
function numberFormat(sigfig, scaler, thousandsSep, decimalSep) {
if (sigfig === null) {
sigfig = 3;
}
if (scaler === null) {
scaler = 1;
}
if (thousandsSep === null) {
thousandsSep = ",";
}
if (decimalSep === null) {
decimalSep = ".";
}
return function (x) {
if (x === 0 || isNaN(x) || !isFinite(x)) {
return "";
} else {
return addSeparators((scaler * x).toFixed(sigfig), thousandsSep, decimalSep);
}
};
}
var aggregatorTemplates = {
sum: function (sigfig, scaler) {
if (sigfig === null) {
sigfig = 3;
}
if (scaler === null) {
scaler = 1;
}
return function (_arg) {
var attr;
attr = _arg[0];
return function () {
return {
sum: 0,
push: function (record) {
if (!isNaN(parseFloat(record[attr]))) {
return this.sum = parseFloat(record[attr]);
}
},
value: function () {
return this.sum;
},
format: numberFormat(sigfig, scaler),
label: "Sum of " + attr
};
};
};
},
average: function (sigfig, scaler) {
if (sigfig === null) {
sigfig = 3;
}
if (scaler === null) {
scaler = 1;
}
return function (_arg) {
var attr;
attr = _arg[0];
return function () {
return {
sum: 0,
len: 0,
push: function (record) {
if (!isNaN(parseFloat(record[attr]))) {
this.sum += parseFloat(record[attr]);
return this.len++;
}
},
value: function () {
return this.sum / this.len;
},
format: numberFormat(sigfig, scaler),
label: "Average of " + attr
};
};
};
},
sumOverSum: function (sigfig, scaler) {
if (sigfig === null) {
sigfig = 3;
}
if (scaler === null) {
scaler = 1;
}
return function (_arg) {
var denom, num;
num = _arg[0];
denom = _arg[1];
return function () {
return {
sumNum: 0,
sumDenom: 0,
push: function (record) {
if (!isNaN(parseFloat(record[num]))) {
this.sumNum += parseFloat(record[num]);
}
if (!isNaN(parseFloat(record[denom]))) {
return this.sumDenom += parseFloat(record[denom]);
}
},
value: function () {
return this.sumNum / this.sumDenom;
},
format: numberFormat(sigfig, scaler),
label: "" + num + "/" + denom
};
};
};
},
sumOverSumBound80: function (sigfig, scaler, upper) {
if (sigfig === null) {
sigfig = 3;
}
if (scaler === null) {
scaler = 1;
}
if (upper === null) {
upper = true;
}
return function (_arg) {
var denom, num;
num = _arg[0];
denom = _arg[1];
return function () {
return {
sumNum: 0,
sumDenom: 0,
push: function (record) {
if (!isNaN(parseFloat(record[num]))) {
this.sumNum += parseFloat(record[num]);
}
if (!isNaN(parseFloat(record[denom]))) {
return this.sumDenom += parseFloat(record[denom]);
}
},
value: function () {
var sign;
sign = upper ? 1 : -1;
return (0.821187207574908 / this.sumDenom + this.sumNum / this.sumDenom + 1.2815515655446004 * sign * Math.sqrt(0.410593603787454 / (this.sumDenom * this.sumDenom) + (this.sumNum * (1 - this.sumNum / this.sumDenom)) / (this.sumDenom * this.sumDenom))) / (1 + 1.642374415149816 / this.sumDenom);
},
format: numberFormat(sigfig, scaler),
label: "" + (upper ? "Upper" : "Lower") + " Bound of " + num + "/" + denom
};
};
};
},
fractionOf: function (wrapped, type) {
if (type === null) {
type = "total";
}
return function () {
var x;
x = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return function (data, rowKey, colKey) {
return {
selector: {
total: [
[],
[]
],
row: [rowKey, []],
col: [
[], colKey
]
}[type],
inner: wrapped.apply(null, x)(data, rowKey, colKey),
push: function (record) {
return this.inner.push(record);
},
format: function (v) {
return numberFormat(2)(100 * v) + "%";
},
label: wrapped.apply(null, x)(data, rowKey, colKey).label + " % of " + type,
value: function () {
return this.inner.value() / data.getAggregator.apply(data, this.selector).inner.value();
}
};
};
};
},
l10nWrapper: function (wrapped, formatter, labelFn) {
return function () {
var x;
x = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
return function (data, rowKey, colKey) {
return {
inner: wrapped.apply(null, x)(data, rowKey, colKey),
push: function (record) {
return this.inner.push(record);
},
format: formatter,
label: labelFn(data),
value: function () {
return this.inner.value();
}
};
};
};
}
};
var aggregators = {
count: function () {
return function () {
return {
count: 0,
push: function () {
return this.count++;
},
value: function () {
return this.count;
},
format: numberFormat(0),
label: "Count"
};
};
},
countUnique: function (_arg) {
var attr;
attr = _arg[0];
return function () {
return {
uniq: [],
push: function (record) {
var _ref;
if (_ref = record[attr], __indexOf.call(this.uniq, _ref) < 0) {
return this.uniq.push(record[attr]);
}
},
value: function () {
return this.uniq.length;
},
format: numberFormat(0),
label: "Count Unique " + attr
};
};
},
listUnique: function (_arg) {
var attr;
attr = _arg[0];
return function () {
return {
uniq: [],
push: function (record) {
var _ref;
if (_ref = record[attr], __indexOf.call(this.uniq, _ref) < 0) {
return this.uniq.push(record[attr]);
}
},
value: function () {
return this.uniq.join(", ");
},
format: function (x) {
return x;
},
label: "List Unique " + attr
};
};
},
intSum: aggregatorTemplates.sum(0),
sum: aggregatorTemplates.sum(3),
average: aggregatorTemplates.average(3),
sumOverSum: aggregatorTemplates.sumOverSum(3),
ub80: aggregatorTemplates.sumOverSumBound80(3, 1, true),
lb80: aggregatorTemplates.sumOverSumBound80(3, 1, false)
};
aggregators.sumAsFractionOfTotal = aggregatorTemplates.fractionOf(aggregators.sum);
aggregators.sumAsFractionOfRow = aggregatorTemplates.fractionOf(aggregators.sum, "row");
aggregators.sumAsFractionOfCol = aggregatorTemplates.fractionOf(aggregators.sum, "col");
aggregators.countAsFractionOfTotal = aggregatorTemplates.fractionOf(aggregators.count);
aggregators.countAsFractionOfRow = aggregatorTemplates.fractionOf(aggregators.count, "row");
aggregators.countAsFractionOfCol = aggregatorTemplates.fractionOf(aggregators.count, "col");
module.exports.aggregators = aggregators;
@nishanht
Copy link

Could you please brief on getDataAsTableRows in your nodejs implementation.
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment