datalib
Version:
JavaScript utilites for loading, summarizing and working with data.
299 lines (240 loc) • 7.36 kB
JavaScript
var u = module.exports;
// utility functions
var FNAME = '__name__';
u.namedfunc = function(name, f) { return (f[FNAME] = name, f); };
u.name = function(f) { return f==null ? null : f[FNAME]; };
u.identity = function(x) { return x; };
u.true = u.namedfunc('true', function() { return true; });
u.false = u.namedfunc('false', function() { return false; });
u.duplicate = function(obj) {
return JSON.parse(JSON.stringify(obj));
};
u.equal = function(a, b) {
return JSON.stringify(a) === JSON.stringify(b);
};
u.extend = function(obj) {
for (var x, name, i=1, len=arguments.length; i<len; ++i) {
x = arguments[i];
for (name in x) { obj[name] = x[name]; }
}
return obj;
};
u.length = function(x) {
return x != null && x.length != null ? x.length : null;
};
u.keys = function(x) {
var keys = [], k;
for (k in x) keys.push(k);
return keys;
};
u.vals = function(x) {
var vals = [], k;
for (k in x) vals.push(x[k]);
return vals;
};
u.toMap = function(list, f) {
return (f = u.$(f)) ?
list.reduce(function(obj, x) { return (obj[f(x)] = 1, obj); }, {}) :
list.reduce(function(obj, x) { return (obj[x] = 1, obj); }, {});
};
u.keystr = function(values) {
// use to ensure consistent key generation across modules
var n = values.length;
if (!n) return '';
for (var s=String(values[0]), i=1; i<n; ++i) {
s += '|' + String(values[i]);
}
return s;
};
// type checking functions
var toString = Object.prototype.toString;
u.isObject = function(obj) {
return obj === Object(obj);
};
u.isFunction = function(obj) {
return toString.call(obj) === '[object Function]';
};
u.isString = function(obj) {
return typeof value === 'string' || toString.call(obj) === '[object String]';
};
u.isArray = Array.isArray || function(obj) {
return toString.call(obj) === '[object Array]';
};
u.isNumber = function(obj) {
return typeof obj === 'number' || toString.call(obj) === '[object Number]';
};
u.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
u.isDate = function(obj) {
return toString.call(obj) === '[object Date]';
};
u.isValid = function(obj) {
return obj != null && obj === obj;
};
u.isBuffer = (typeof Buffer === 'function' && Buffer.isBuffer) || u.false;
// type coercion functions
u.number = function(s) {
return s == null || s === '' ? null : +s;
};
u.boolean = function(s) {
return s == null || s === '' ? null : s==='false' ? false : !!s;
};
// parse a date with optional d3.time-format format
u.date = function(s, format) {
var d = format ? format : Date;
return s == null || s === '' ? null : d.parse(s);
};
u.array = function(x) {
return x != null ? (u.isArray(x) ? x : [x]) : [];
};
u.str = function(x) {
return u.isArray(x) ? '[' + x.map(u.str) + ']'
: u.isObject(x) || u.isString(x) ?
// Output valid JSON and JS source strings.
// See http://timelessrepo.com/json-isnt-a-javascript-subset
JSON.stringify(x).replace('\u2028','\\u2028').replace('\u2029', '\\u2029')
: x;
};
// data access functions
var field_re = /\[(.*?)\]|[^.\[]+/g;
u.field = function(f) {
return String(f).match(field_re).map(function(d) {
return d[0] !== '[' ? d :
d[1] !== "'" && d[1] !== '"' ? d.slice(1, -1) :
d.slice(2, -2).replace(/\\(["'])/g, '$1');
});
};
u.accessor = function(f) {
/* jshint evil: true */
return f==null || u.isFunction(f) ? f :
u.namedfunc(f, Function('x', 'return x[' + u.field(f).map(u.str).join('][') + '];'));
};
// short-cut for accessor
u.$ = u.accessor;
u.mutator = function(f) {
var s;
return u.isString(f) && (s=u.field(f)).length > 1 ?
function(x, v) {
for (var i=0; i<s.length-1; ++i) x = x[s[i]];
x[s[i]] = v;
} :
function(x, v) { x[f] = v; };
};
u.$func = function(name, op) {
return function(f) {
f = u.$(f) || u.identity;
var n = name + (u.name(f) ? '_'+u.name(f) : '');
return u.namedfunc(n, function(d) { return op(f(d)); });
};
};
u.$valid = u.$func('valid', u.isValid);
u.$length = u.$func('length', u.length);
u.$in = function(f, values) {
f = u.$(f);
var map = u.isArray(values) ? u.toMap(values) : values;
return function(d) { return !!map[f(d)]; };
};
// comparison / sorting functions
u.comparator = function(sort) {
var sign = [];
if (sort === undefined) sort = [];
sort = u.array(sort).map(function(f) {
var s = 1;
if (f[0] === '-') { s = -1; f = f.slice(1); }
else if (f[0] === '+') { s = +1; f = f.slice(1); }
sign.push(s);
return u.accessor(f);
});
return function(a, b) {
var i, n, f, c;
for (i=0, n=sort.length; i<n; ++i) {
f = sort[i];
c = u.cmp(f(a), f(b));
if (c) return c * sign[i];
}
return 0;
};
};
u.cmp = function(a, b) {
return (a < b || a == null) && b != null ? -1 :
(a > b || b == null) && a != null ? 1 :
((b = b instanceof Date ? +b : b),
(a = a instanceof Date ? +a : a)) !== a && b === b ? -1 :
b !== b && a === a ? 1 : 0;
};
u.numcmp = function(a, b) { return a - b; };
u.stablesort = function(array, sortBy, keyFn) {
var indices = array.reduce(function(idx, v, i) {
return (idx[keyFn(v)] = i, idx);
}, {});
array.sort(function(a, b) {
var sa = sortBy(a),
sb = sortBy(b);
return sa < sb ? -1 : sa > sb ? 1
: (indices[keyFn(a)] - indices[keyFn(b)]);
});
return array;
};
// permutes an array using a Knuth shuffle
u.permute = function(a) {
var m = a.length,
swap,
i;
while (m) {
i = Math.floor(Math.random() * m--);
swap = a[m];
a[m] = a[i];
a[i] = swap;
}
};
// string functions
u.pad = function(s, length, pos, padchar) {
padchar = padchar || " ";
var d = length - s.length;
if (d <= 0) return s;
switch (pos) {
case 'left':
return strrep(d, padchar) + s;
case 'middle':
case 'center':
return strrep(Math.floor(d/2), padchar) +
s + strrep(Math.ceil(d/2), padchar);
default:
return s + strrep(d, padchar);
}
};
function strrep(n, str) {
var s = "", i;
for (i=0; i<n; ++i) s += str;
return s;
}
u.truncate = function(s, length, pos, word, ellipsis) {
var len = s.length;
if (len <= length) return s;
ellipsis = ellipsis !== undefined ? String(ellipsis) : '\u2026';
var l = Math.max(0, length - ellipsis.length);
switch (pos) {
case 'left':
return ellipsis + (word ? truncateOnWord(s,l,1) : s.slice(len-l));
case 'middle':
case 'center':
var l1 = Math.ceil(l/2), l2 = Math.floor(l/2);
return (word ? truncateOnWord(s,l1) : s.slice(0,l1)) +
ellipsis + (word ? truncateOnWord(s,l2,1) : s.slice(len-l2));
default:
return (word ? truncateOnWord(s,l) : s.slice(0,l)) + ellipsis;
}
};
function truncateOnWord(s, len, rev) {
var cnt = 0, tok = s.split(truncate_word_re);
if (rev) {
s = (tok = tok.reverse())
.filter(function(w) { cnt += w.length; return cnt <= len; })
.reverse();
} else {
s = tok.filter(function(w) { cnt += w.length; return cnt <= len; });
}
return s.length ? s.join('').trim() : tok[0].slice(0, len);
}
var truncate_word_re = /([\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF])/;