supergroup
Version:
Nested groups on arrays of objects where groups are Strings that know what you want them to know about themselves and their relatives.
1,457 lines (1,329 loc) • 49.2 kB
JavaScript
;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.flatten = exports.FilterSet = exports.Supergroup = exports.SGNodeList = exports.ArrayMap = exports.ArraySet = exports.SGNode = exports.SupergroupHOLD = undefined;
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _assert = require('assert');
var _assert2 = _interopRequireDefault(_assert);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extendableBuiltin5(cls) {
function ExtendableBuiltin() {
var instance = Reflect.construct(cls, Array.from(arguments));
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
return instance;
}
ExtendableBuiltin.prototype = Object.create(cls.prototype, {
constructor: {
value: cls,
enumerable: false,
writable: true,
configurable: true
}
});
if (Object.setPrototypeOf) {
Object.setPrototypeOf(ExtendableBuiltin, cls);
} else {
ExtendableBuiltin.__proto__ = cls;
}
return ExtendableBuiltin;
}
function _extendableBuiltin3(cls) {
function ExtendableBuiltin() {
var instance = Reflect.construct(cls, Array.from(arguments));
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
return instance;
}
ExtendableBuiltin.prototype = Object.create(cls.prototype, {
constructor: {
value: cls,
enumerable: false,
writable: true,
configurable: true
}
});
if (Object.setPrototypeOf) {
Object.setPrototypeOf(ExtendableBuiltin, cls);
} else {
ExtendableBuiltin.__proto__ = cls;
}
return ExtendableBuiltin;
}
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _extendableBuiltin(cls) {
function ExtendableBuiltin() {
var instance = Reflect.construct(cls, Array.from(arguments));
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
return instance;
}
ExtendableBuiltin.prototype = Object.create(cls.prototype, {
constructor: {
value: cls,
enumerable: false,
writable: true,
configurable: true
}
});
if (Object.setPrototypeOf) {
Object.setPrototypeOf(ExtendableBuiltin, cls);
} else {
ExtendableBuiltin.__proto__ = cls;
}
return ExtendableBuiltin;
}
var WARN = false;
/**
* @Author: [Sigfried Gold](http://sigfried.org)
* @License: [MIT](http://sigfried.mit-license.org/)
* @Version: 2.0.0
*/
; // jshint -W053
var SupergroupHOLD = exports.SupergroupHOLD = function (_extendableBuiltin2) {
_inherits(SupergroupHOLD, _extendableBuiltin2);
function SupergroupHOLD() {
var _Object$getPrototypeO;
_classCallCheck(this, SupergroupHOLD);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var _this = _possibleConstructorReturn(this, (_Object$getPrototypeO = Object.getPrototypeOf(SupergroupHOLD)).call.apply(_Object$getPrototypeO, [this].concat(args)));
console.log('MAKING A SUPERGROUP!!!');
return _this;
}
return SupergroupHOLD;
}(_extendableBuiltin(Array));
// @class SGNode
// @description Supergroups and SGNodeLists are composed of SGNodes which are
// objects of any sort representing group values.
var SGNode = exports.SGNode = function () {
function SGNode(val) {
_classCallCheck(this, SGNode);
// changed class name from Value to SGNode, havent fixed all the code yet
this.val = val;
this._hasChildren = false;
}
_createClass(SGNode, [{
key: 'toString',
value: function toString() {
return this.val && this.val.toString && this.val.toString() || this.val;
}
}, {
key: 'valueOf',
//return Array.from(this.recsMap.values());
value: function valueOf() {
return this.val.valueOf();
}
}, {
key: 'dimPath',
value: function dimPath(opts) {
opts = delimOpts(opts);
opts.dimName = true;
return this.namePath(opts);
}
}, {
key: 'namePath',
value: function namePath(opts) {
opts = delimOpts(opts);
var path = this.pedigree(opts);
if (opts.dimName) path = path.map(function (d) {
return d.dim;
});
//_.pluck(path, 'dim');
if (opts.asArray) return path;
return path.join(opts.delim);
/*
var delim = opts.delim || '/';
return (this.parent ?
this.parent.namePath(_.extend({},opts,{notLeaf:true})) : '') +
((opts.noRoot && this.depth===0) ? '' :
(this + (opts.notLeaf ? delim : ''))
)
*/
}
}, {
key: 'pedigree',
value: function pedigree(opts) {
opts = opts || {};
var path = [];
if (!opts.notThis) path.push(this);
var ptr = this;
while (ptr.depth > 0 && (ptr = ptr.parentNode)) {
path.unshift(ptr);
}
if (opts.noRoot) path.shift();
if (opts.backwards || this.backwards) path.reverse(); //kludgy?
return path;
// CHANGING -- HOPE THIS DOESN'T BREAK STUFF (pedigree isn't
// documented yet)
if (!opts.asNodes) return path.map(function (d) {
return d.val;
});
return path;
}
}, {
key: 'path',
value: function path(opts) {
return this.pedigree(opts);
}
//SGNode.prototype.extendGroupBy = // backward compatibility
/*
addLevel(dim, opts) {
opts = opts || {};
debugger;
_.each(this.leafNodes() || [this], function(d) {
opts.parentNode = d;
if (!('in' in d)) { // d.in means it's part of a diffList
d.children = new Supergroup(d.records, dim, opts);
} else { // allows adding levels to diffLists. haven't used for a long time
if (d['in'] === "both") {
d.children = diffList(d.from, d.to, dim, opts);
} else {
d.children = new Supergroup(d.records, dim, opts);
_.each(d.children, function(c) {
c['in'] = d['in'];
c[d['in']] = d[d['in']];
});
}
}
d.children.parentNode = d; // NOT TESTED, NOT USED, PROBABLY WRONG!!!
});
} */
}, {
key: '_descendants',
value: function _descendants(opts) {
// should descendants include self? yes for now
// (old) these two lines fix a treelike bug, hope they don't do harm
//this.children = this.children || [];
var nodeList = this._hasChildren && _lodash2.default.flatten([d].concat(this.children.map(function (d) {
return d._descendants();
})), true) || [this];
return nodeList;
}
}, {
key: 'descendants',
value: function descendants(opts) {
return new SGNodeList(this._descendants(opts));
}
}, {
key: '_leafNodes',
value: function _leafNodes(level) {
//console.log(`this is SGNode: ${this instanceof SGNode}, this: ${this}`);
// until commit 31278a35b91a8f4bd4ddc4376c840fb14d2723f9
// supported level param, to only go down so many levels
// not supporting that any more. wasn't using it
var nodeList = this._hasChildren && _lodash2.default.flatten(this.children.map(function (d) {
return d._leafNodes();
}), true) || [this];
return nodeList;
}
}, {
key: 'leafNodes',
value: function leafNodes(opts) {
return new SGNodeList(this._leafNodes(opts), function (d) {
return d.val;
});
}
/*
addRecordsAsChildrenToLeafNodes(truncateEmpty) {
function fixLeaf(node) {
node.children = node.records;
_.each(node.children, function(rec) {
rec.parentNode = node;
rec.depth = node.depth + 1;
for(var method in SGNode.prototype) {
Object.defineProperty(rec, method, {
value: SGNode.prototype[method]
});
}
});
}
if (typeof truncateEmpty === "undefined")
truncateEmpty = true;
if (truncateEmpty) {
var self = this;
self.descendants().forEach(function(node) {
if (self.parentNode && self.parentNode.children.length === 1) {
fixLeaf(node);
}
});
} else {
_.each(this.leafNodes(), function(node) {
fixLeaf(node);
});
}
return this;
}
*/
}, {
key: 'lookup',
value: function lookup(key) {
return this.children && this.children.lookup(key) || key === this.valueOf() && this;
}
}, {
key: 'pct',
value: function pct() {
return this.records.length / this.parentList.records.length;
}
}, {
key: 'previous',
value: function previous() {
if (this.parentList) {
// could store pos on each value, but not doing that now
var pos = this.parentList.indexOf(this);
if (pos > 0) {
return this.parentList[pos - 1];
}
}
}
}, {
key: 'aggregate',
value: function aggregate(func, field) {
if (typeof field === "function") return func(this.records.map(field));
return func(this.records.map(function (d) {
return d[field];
}));
}
/* didn't make this yet, just copied from above
SGNode.prototype.descendants(level) {
var ret = [this];
if (level !== 0 && this[childProp] && (!level || this.depth < level))
ret = _.flatten(_.map(this[childProp], function(c) {
return c.leafNodes(level);
}), true);
return makeList(ret);
};
*/
}, {
key: 'children',
get: function get() {
return this._hasChildren && this._children;
},
set: function set(sg) {
//console.log(`in set children with a ${sg.constructor}`);
if (!(sg instanceof SGNodeList)) throw new Error("SGNode children can only be Supergroups");
//console.log("set children worked this time");
this._children = sg;
this._hasChildren = true;
}
}, {
key: 'records',
set: function set(recs) {
//this._records = recs;
this._records = new ArraySet(recs);
//WARN && console.warn("TEMP SETTER");
//this._records = recs;
},
get: function get() {
WARN && console.warn("TEMP GETTER");
return this._records;
}
}]);
return SGNode;
}();
/**
* @param {Object[]} rawArray Array or another ArraySet
*/
var ArraySet = exports.ArraySet = function (_extendableBuiltin4) {
_inherits(ArraySet, _extendableBuiltin4);
function ArraySet(arr) {
_classCallCheck(this, ArraySet);
var _this2 = _possibleConstructorReturn(this, Object.getPrototypeOf(ArraySet).call(this));
for (var i = 0; i < arr.length; i++) {
_this2[i] = arr[i];
} //this.push(...arr);
return _this2;
}
_createClass(ArraySet, [{
key: 'filter',
value: function filter(filt) {
var arr = Array.filter(this, filt);
return new ArraySet(arr);
}
}, {
key: 'union',
value: function union(arrSet) {
//assert(this.sameRootArray(arrSet));
return new ArraySet(_lodash2.default.union(this, arrSet));
}
}, {
key: 'intersection',
value: function intersection(arrSet) {
//assert(this.sameRootArray(arrSet));
return new ArraySet(_lodash2.default.intersection(this, arrSet));
}
}, {
key: 'minus',
value: function minus(arrSet) {
//assert(this.sameRootArray(arrSet));
return new ArraySet(_lodash2.default.difference(this, arrSet));
}
}, {
key: 'plainArray',
value: function plainArray() {
return this.slice(0);
}
}], [{
key: 'union',
value: function union(sets) {
var u = sets.shift();
sets.forEach(function (set) {
return u = u.union(set);
});
return u;
}
}, {
key: 'intersection',
value: function intersection(sets) {
var i = sets.shift();
sets.forEach(function (set) {
return i = i.intersection(set);
});
return i;
}
}]);
return ArraySet;
}(_extendableBuiltin3(Array));
/**
* @param {int[]} [indices] List of indexes into rawArray. Defaults to 0..rawArray.length
* @param {Object[]} rawArray Array or another ArraySet
*/
/*
export class ArraySetWITH_INDICES extends Array {
constructor(rawArray, indices) {
super( ...(indices && indices.map(i=>rawArray[i]) || rawArray));
this.indices = indices || _.range(rawArray.length);
this.rawArray = rawArray;
if (rawArray instanceof ArraySet) {
// this.arrayLookupFunc...
} else if (Array.isArray(rawArray)) {
}
}
indices() {
return this.indices;
}
/*
subset(indices) {
return this.intersection(indices);
}
* /
subset(indices) { // actually a subset of the original array
if (indices instanceof ArraySet)
throw new Error("didn't expect that");
let intersect = this.intersection(indices).indices;
//console.log(indices, intersect);
if (indices.join(',') !== intersect.join(','))
throw new Error('eek');
return this.intersection(indices);
return new ArraySet(this.rawArray, indices);
}
newSet(indices) {
if (indices instanceof ArraySet)
throw new Error("didn't expect that");
//indices = indices.indices;
return new ArraySet(this.rawArray, indices);
}
filter(filterFunc) {
let indices = [];
let recs = this.filter((d,i) => {
let include = filterFunc(d);
if (include) {
indices.push(this.indices[i]);
}
});
return this.subset(indices);
}
sameUniverse(set) {
return set instanceof ArraySet && set.rawArray === this.rawArray ||
!(set instanceof ArraySet) &&
_.union(this.indices, set).length === this.length; // set should be a subset of indices
// if not an ArraySet from the same universe
}
union(set) {
//assert(this.sameUniverse(set));
return this.newSet(_.union(this.indices, set instanceof ArraySet && set.indices || set));
}
intersection(set) {
//assert(this.sameUniverse(set));
return this.newSet(_.intersection(this.indices, set instanceof ArraySet && set.indices || set));
}
minus(set) {
//assert(this.sameUniverse(set));
return this.newSet(_.difference(this.indices, set instanceof ArraySet && set.indices || set));
}
static union(sets) {
let u = sets.shift();
sets.forEach(set => u = u.union(set));
return u;
}
static intersection(sets) {
let i = sets.shift();
sets.forEach(set => i = i.intersection(set));
return i;
}
}
*/
/**
* An ArrayMap is a redundant structure: elements are stored in a
* public-facing array and also in a private Map. The Map allows
* elements to be retrieved by key. By default the object appears
* as an array of objects.
* REVISION: an optional key function associates a key with an element.
* You can also associate each element with an index number which is
* not necessarily its position in the current array (usually it will
* be its position in some other (parent) array).
* With a key you can retrieve its element and vice versa. Also you
* can retrieve an index for an element or key.
* @param {Object[]} arr Array of anything
* @param {function} [keyfunc] generates an object to key on by being called
* with parameters (obj, i)
*/
var ArrayMap = exports.ArrayMap = function (_extendableBuiltin6) {
_inherits(ArrayMap, _extendableBuiltin6);
function ArrayMap() {
var arr = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
var keyFunc = arguments[1];
_classCallCheck(this, ArrayMap);
//super(...arr);
if (typeof keyFunc !== "function") throw new Error("making ArrayMap without keyFunc");
var _this3 = _possibleConstructorReturn(this, Object.getPrototypeOf(ArrayMap).call(this));
console.log('called ArrayMap with ' + arr.length + ' length array');
for (var i = 0; i < arr.length; i++) {
_this3[i] = arr[i];
}_this3._keyMap = new Map(); // map from key to index
_this3._keyFunc = keyFunc;
_this3.forEach(function (element, i) {
var key = keyFunc(element);
/*
let key = keyFunc && keyFunc(element) ||
(typeof element.val !== "undefined") && element.val ||
i;
*/
_this3._keyMap.set(key, element);
});
return _this3;
}
_createClass(ArrayMap, [{
key: 'lookup',
value: function lookup(key) {
var ret = this._keyMap.get(key);
//console.log(this._keyMap);
//console.log(`${this}.lookup(${key}) = ${ret}`);
return ret;
}
}, {
key: 'has',
value: function has(key) {
return this._keyMap.has(key);
}
}, {
key: 'keys',
value: function keys() {
return Array.from(this.keyIterator());
}
}, {
key: 'keyIterator',
value: function keyIterator() {
return this._keyMap.keys();
}
}]);
return ArrayMap;
}(_extendableBuiltin5(Array));
var SGNodeList = exports.SGNodeList = function (_ArrayMap) {
_inherits(SGNodeList, _ArrayMap);
function SGNodeList() {
_classCallCheck(this, SGNodeList);
return _possibleConstructorReturn(this, Object.getPrototypeOf(SGNodeList).apply(this, arguments));
}
_createClass(SGNodeList, [{
key: 'rawNodes',
value: function rawNodes() {
//console.log(`this.length: ${this.length}, this.keyMap: ${!!this.keyMap}, this.keyMap.keys().length: ${this.keyMap.keys().length}`);
if (!('keys' in this)) return this.map(String);
return this.keys();
//return this.keyMap(String);
}
}, {
key: 'rawValues',
value: function rawValues() {
return this.rawNodes();
}
// lookup more than one thing at a time
}, {
key: 'lookupMany',
value: function lookupMany(query) {
var many = query.map(function (d) {
return list.singleLookup(d);
}).filter(function (d) {
return typeof d === "undefined";
});
//let many = _.chain(query).map(d => list.singleLookup(d)).compact().value();
return new SGNodeList(many);
//return many;
//return addSupergroupMethods(many);
}
}, {
key: 'namePaths',
value: function namePaths(opts) {
//console.log(`this: ${this}, this[0]: ${this[0]}, this[0] is SGNode: ${this[0] instanceof SGNode}`);
return this.map(function (d) {
return d.namePath(opts);
});
}
}, {
key: 'aggregates',
// apply a function to the records of each group
//
value: function aggregates(func, field, ret) {
var results = _lodash2.default.map(this, function (val) {
return val.aggregate(func, field);
});
if (ret === 'dict') return _lodash2.default.object(this, results);
return results;
}
}, {
key: 'd3NestEntries',
value: function d3NestEntries() {
return _lodash2.default.map(this, function (val) {
if ('children' in val) return { key: val.toString(), values: val.children.d3NestEntries() };
return { key: val.toString(), values: val.records };
});
}
}, {
key: 'd3NestMap',
value: function d3NestMap() {
return _lodash2.default.chain(this).map(function (val) {
if (val.children) return [val + '', val.children.d3NestMap()];
return [val + '', val.records];
}).object().value();
}
}, {
key: 'filterSet',
value: function filterSet() {
return new FilterSet(this);
}
}]);
return SGNodeList;
}(ArrayMap);
/**
* ### [http://sigfried.github.io/supergroup/ -- Tutorial and demo]
* ### [http://www.toptal.com/javascript/ultimate-in-memory-data-collection-manipulation-with-supergroup-js](Article)
*
* usage examples at [http://sigfried.github.io/blog/supergroup](http://sigfried.github.io/blog/supergroup)
*
* Avaailable as _.supergroup, Underscore mixin
* ### Class of grouped records masquerading as an array
* A `Supergroup` object is an array of `SGNode` objects made by grouping
* an array of json objects by some set of properties or functions performed
* on those objects. Each `SGNode` represents a single group. Think of it as
* a SQL group by:
*
* SELECT state, zipcode, count(*)
* FROM addresses
* GROUP BY state, zipcode
*
* In Supergroup parlance: 'state' and 'zipcode' are _dimensions_; states
* ('Alabama', 'Alaska') and zipcodes (50032, 20002) are _values_, or,
* rather, value _keys_; and `count(*)` is an aggregation performed on the
* group. In regular SQL the underlying records represented in a group are
* not available, with Supergroup they are. So a `SGNode` has a `key` which
* is the text or number or any javascript object used to form the group.
* In a group of states, the _key_ of each value would be a `string`, for
* zipdcodes it could be a `number`. (In previous versions of Supergroup,
* these were `String` and `Number` objects, but now they are `string`
* literals or anything else returnable by a grouping function.)
*
* `SGNode` objects have a `key`, and `valueobj.valueOf()` will return that
* key, and `valueobj.toString()` will return the results of the default
* toString method on that key. `valueobj.records` is an array of the original
* javascript objects included in the group represented by the key. And
* `valueobj.indexes` is an array of the positions of those records in the
* original array.
*
* - #### Supergroup extends `Array`
* - `Array` values are `SGNodes`
* - properties:
* - map: keys are the keys used to group SGNodes, values are SGNodes
* - recsMap: keys are index into original records array, values are orig records
* - methods:
* - rawNodes: returns keys from map
*
* - SGNodes
* - depth: same as the depth of its parentList (supergroup)
* - children: array of child SGNodes collected in a supergroup (whose
* depth is one greater than the depth of this SGNode)
*
* Inherits
* - from `ArrayMap`
* - Acting like an array of Nodes
* - `keys()`: an array of Node keys
* - `keyIterator()`: iterator over Node keys
* - from `SGNodeList`
* - `rawNodes()`
* - `rawValues()`
* - lookup(key)`
* - `getLookupMap()`
* - `singleLookup(key)`
* - `lookupMany(key)`
* - `namePaths(opts)`
* - `aggregates(func, field, ret)`
* - `d3NestEntries()`
* - `d3NestMap()`
* - `filterSet()`
*/
var Supergroup = exports.Supergroup = function (_SGNodeList) {
_inherits(Supergroup, _SGNodeList);
/**
* Constructor groups records and builds tree structure
* @exported class supergroup.group(recs, dim, opts)
* @param {Object[]} recs array of objects, raw data
* @param {string[]} dims property names to be used for grouping the raw objects
* @param {function[]} dims functions on raw objects that return any kind of
* object to be used for grouping. property names and
* functions can be mixed in dims array. For single-level
* grouping, a single property name or function can be
* used instead of an array.
* @param {string[]} [opts.dimNames] array (or single value) of dim names of
* same length as dims. Property name dims
* are used as dimName by default.
* @param {Object} [opts] options for configuring supergroup behavior. opts are
* forwarded to SGNode constructors and subgroup constructors.
* @param {Object[]} [opts.excludeNodes] to exlude specific group values
* @param {function} [opts.preListRecsHook] run recs through this function before continuing processing __currently unused__
* @param {function} [opts.truncateBranchOnEmptyVal]
* @return {Array of SGNodes} enhanced with all the List methods
*/
function Supergroup(recs, dims) // indices,
{
var opts = arguments.length <= 2 || arguments[2] === undefined ? { parentNode: null,
//recs : [],
//groups:[], // not for real use
//dims:[],
dimNames: [] } : arguments[2];
_classCallCheck(this, Supergroup);
// missing args constructor probably not permanent:
if ('groups' in opts) {
var _this5 = _possibleConstructorReturn(this, Object.getPrototypeOf(Supergroup).call(this, opts.groups)); // why are groups already calculated?
return _possibleConstructorReturn(_this5);
}
//throw new Error("not sure what should be happening here yet.");
if (recs.length === 0) {
var _this5, _ret;
return _ret = (_this5 = _possibleConstructorReturn(this, Object.getPrototypeOf(Supergroup).call(this)), _this5), _possibleConstructorReturn(_this5, _ret);
}
if (dims.length === 0) {
var _this5, _ret2;
return _ret2 = (_this5 = _possibleConstructorReturn(this, Object.getPrototypeOf(Supergroup).call(this, recs)), _this5), _possibleConstructorReturn(_this5, _ret2);
}
Supergroup.processOpts(opts); // just throws errors for unsupported opts
var _Supergroup$handleDim = Supergroup.handleDimStuff(dims, opts.dimNames);
var _Supergroup$handleDim2 = _slicedToArray(_Supergroup$handleDim, 5);
var dims_local = _Supergroup$handleDim2[0];
var dim = _Supergroup$handleDim2[1];
var dimNames_local = _Supergroup$handleDim2[2];
var dimName = _Supergroup$handleDim2[3];
var dimFunc = _Supergroup$handleDim2[4];
//indices = indices || _.range(recs.length);
var parentNode = opts.parentNode || Supergroup.makeRoot('root', -1, recs, dim, dimName, dims, opts.dimNames);
if (!parentNode) console.error("what's up?");
//console.log(`${dims}: ${dim}, ${dimNames}: ${dimName}, ${indices}`);
var depth = parentNode.depth + 1;
//console.log(`depth: ${depth}, dims: ${dims}, dim: ${dim}`);
var groupsMap = new Map();
var records = parentNode.records;
records.forEach(function (rec, i) {
var key = dimFunc(rec); // this is the key for grouping!
var val = undefined;
if (!groupsMap.has(key)) {
if (opts.excludeNodes) {
if (_lodash2.default.isArray(opts.excludeNodes) && !_lodash2.default.find(opts.exludeNodes(key))) {} else if (opts.excludeNodes instanceof Map && !opt.excludeNodes.has(key)) {}
} else {
val = new SGNode(key); // val.val = key
val.dim = dimName;
val.depth = depth;
val.parentNode = parentNode;
val._recs = [];
//val.indices = [];
groupsMap.set(key, val); // save the val in the keyed map
}
} else {
val = groupsMap.get(key);
}
val._recs.push(rec);
//val.indices.push(indices[i]);
//val.recsMap.set(i, rec); // each val gets records and index where
// record is in the original array
});
//console.log(groupsMap);
//throw new Error("stop here");
// ArrayMap.constructor(arr = [], keyFunc, indices)
var _this5 = _possibleConstructorReturn(this, Object.getPrototypeOf(Supergroup).call(this, Array.from(groupsMap.values()), function (d) {
return d.val;
}));
_this5.parentNode = parentNode;
_this5.records = records;
_this5.parentNode.children = _this5;
_this5.root = _this5.parentNode.root;
_this5.dims = dims_local;
_this5.dimNames = dimNames_local;
_this5.dim = dim;
_this5.dimName = dimName;
_this5.depth = depth;
_this5.dimFunc = dimFunc;
_this5.forEach(function (val) {
val.parentList = _this5;
val.root = _this5.root;
//val.recsMap = this.recsMap.subset(val.indices);
val.records = new ArraySet(val._recs);
if (dims_local.length) {
//console.log(`ADDING CHILDREN to ${val}`);
/* Supergroup.constructor({ parentNode=null, recs = [], dims=[],
* dimNames=[], opts={} } = {}) */
val.children = new Supergroup(val.records, _lodash2.default.clone(dims_local), _extends({ parentNode: val, //recs:val.records,
//dims:_.clone(dims_local),
dimNames: _lodash2.default.clone(dimNames_local)
}, opts));
}
});
return _possibleConstructorReturn(_this5);
}
/** There are time when you want to give your supergroup tree an explicit
* root, like when creating hierarchies in D3. In that case call supergroup
* like:
*
* let root = makeRoot('Tree Top', 0, recs),
* let sg = new Supergroup({parent=root, dims=['state','zipcode']});
*
* Otherwise Supergroup will make its own fake root with depth -1 instead
* of depth 0;
*/
_createClass(Supergroup, [{
key: 'lookup',
/** lookup a value in a list, or, if key is an array
* it is interpreted as a path down the group hierarchy */
value: function lookup(key) {
if (Array.isArray(key)) {
return this.lookupPath(key);
}
return _get(Object.getPrototypeOf(Supergroup.prototype), 'lookup', this).call(this, key);
}
}, {
key: 'lookupPath',
value: function lookupPath(keys) {
keys = keys.slice(0);
var sg = this;
var ret;
while (keys.length) {
var key = keys.shift();
ret = sg.lookup(key);
if (!ret) throw new Error('can\'t find ' + key + ' in supergroup ' + sg);
sg = ret.children;
}
return ret;
}
// sometimes a root value is needed as the top of a hierarchy
}, {
key: 'asRootNode',
value: function asRootNode(name, dimName) {
return this.parentNode;
/*
var val = new SGNode(name || 'Root');
val.dim = dimName || 'root';
val.depth = 0;
val.records = this.records;
val.children= this;
_.each(val.children, function(d) { d.parentNode = val; });
_.each(val.descendants(), function(d) { d.depth = d.depth + 1; });
return val;
*/
}
}, {
key: 'leafNodes',
value: function leafNodes() {
return this.parentNode.leafNodes();
}
}, {
key: 'flattenTree',
value: function flattenTree() {
return this.parentNode.descendants();
//return flatten(this.map(d => [d].concat(d.descendants()))).filter(d=>d);
}
}, {
key: 'rawNodes',
value: function rawNodes() {
//console.log(`this.length: ${this.length}, this.map: ${!!this.map}, this.map.keys().length: ${this.map.keys().length}`);
return Array.from(this.keys());
}
}, {
key: 'rawValues',
value: function rawValues() {
return this.rawNodes();
}
}, {
key: 'filterSet',
value: function filterSet() {
return new FilterSet(this);
}
/*
addLevel(dim, opts) {
_.each(this, function(val) {
val.addLevel(dim, opts);
});
return this;
};
*/
}, {
key: 'map',
value: function map() {
var _Array;
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return Object.setPrototypeOf((_Array = Array).map.apply(_Array, [this].concat(args)), Supergroup.prototype);
}
}, {
key: 'filter',
value: function filter() {
var _Array2;
for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return Object.setPrototypeOf((_Array2 = Array).filter.apply(_Array2, [this].concat(args)), Supergroup.prototype);
}
}, {
key: 'slice',
value: function slice() {
var _Array3;
for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
return Object.setPrototypeOf((_Array3 = Array).slice.apply(_Array3, [this].concat(args)), Supergroup.prototype);
}
}, {
key: 'splice',
value: function splice() {
var _Array4;
for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
args[_key5] = arguments[_key5];
}
return Object.setPrototypeOf((_Array4 = Array).splice.apply(_Array4, [this].concat(args)), Supergroup.prototype);
}
}, {
key: 'concat',
value: function concat() {
var _Array5;
for (var _len6 = arguments.length, args = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
args[_key6] = arguments[_key6];
}
return Object.setPrototypeOf((_Array5 = Array).concat.apply(_Array5, [this].concat(args)), Supergroup.prototype);
}
}, {
key: 'records',
get: function get() {
//WARN && console.warn("TEMP GETTER");
//return this._records;
return this.root._records;
},
set: function set(recs) {
this.parentNode._records = recs;
//this.root._records = recs;
//recs = new ArraySet(recs);
//WARN && console.warn("TEMP SETTER");
//this._records = recs;
}
}], [{
key: 'makeRoot',
value: function makeRoot(name, depth, recs, dim, dimName, dims, dimNames) {
name = name || "root";
dimName = dimName || name;
var root = new SGNode(name);
root.dim = dim;
root.dimName = dimName;
root.dims = dims;
root.dimNames = dimNames;
root.depth = depth;
root.root = root;
root.records = new ArraySet(recs);
//root.indices = root.recsMap.indices;
return root;
}
}, {
key: 'processOpts',
value: function processOpts(opts) {
if (opts.multiValuedGroup || opts.multiValuedGroups) {
throw new Error("multiValuedGroup not implemented in es6 version yet");
}
if (opts.preListRecsHook) {
throw new Error("preListRecsHook not re-implemented yet");
recs = opts.preListRecsHook ? opts.preListRecsHook(recs) : recs;
}
if (opts.truncateBranchOnEmptyVal) {
// can't remember when this is used
throw new Error("truncateBranchOnEmptyVal not re-implemented yet");
recs = recs.filter(function (r) {
return !_lodash2.default.isEmpty(r[dim]) || _lodash2.default.isNumber(r[dim]) && isFinite(r[dim]);
});
}
}
}, {
key: 'handleDimStuff',
value: function handleDimStuff(dims, dimNames) {
if (!Array.isArray(dims)) dims = [dims];
var dims_local = _lodash2.default.clone(dims); // don't want to mess with original
var dim = dims_local.shift();
dimNames = dimNames || _lodash2.default.clone(dims);
var dimNames_local = _lodash2.default.clone(dimNames);
// forget opts for now
//dimNames = opts.dimName && opts.dimName.length && [opts.dimName] ||
//opts.dimNames || dimNames || dims;
var dimName = dimNames_local.shift();
var dimFunc = undefined;
if (_lodash2.default.isFunction(dim)) {
dimFunc = dim;
dimName = dimName || dim.toString();
} else {
dimFunc = function dimFunc(d) {
return d[dim];
};
dimName = dimName || dim.toString();
}
return [dims_local, dim, dimNames_local, dimName, dimFunc];
}
}, {
key: 'wholeListNumeric',
value: function wholeListNumeric(groups) {
var isNumeric = _lodash2.default.every(_lodash2.default.keys(groups), function (k) {
return k === null || k === undefined || !isNaN(Number(k)) || ["null", ".", "undefined"].indexOf(k.toLowerCase()) > -1;
});
if (isNumeric) {
_lodash2.default.each(_lodash2.default.keys(groups), function (k) {
if (isNaN(k)) {
delete groups[k]; // getting rid of NULL values in dim list!!
}
});
}
return isNumeric;
}
}]);
return Supergroup;
}(SGNodeList);
/**
* Class for managing filter state while leaving Supgergroups immutable
* as much as possible.
*/
var FilterSet = exports.FilterSet = function () {
function FilterSet(listOrNode) {
_classCallCheck(this, FilterSet);
this.rootNode = listOrNode.root; // every node and list should point up to
// the same root, including the root itself
this.filters = [];
this.selectedNodes = [];
}
_createClass(FilterSet, [{
key: 'excludeNodes',
value: function excludeNodes(nodes) {
var _this6 = this;
nodes = Array.isArray(ndoes) && nodes || [nodes];
nodes.forEach(function (node) {
return _this6.filters.push(new ExcludeNodeFilter(node));
});
}
}, {
key: 'addFilter',
value: function addFilter(type, key, filt, ids) {}
}, {
key: 'selectByNode',
value: function selectByNode(node) {
_assert2.default.equal(node.root, node.root); // assume state only on root lists
this.selectedNodes.push(node);
}
}, {
key: 'selectByFilter',
value: function selectByFilter(filt) {
this.selectedNodes.push(node);
}
}, {
key: 'selectedRecs',
value: function selectedRecs() {
return _lodash2.default.chain(this.selectedNodes).pluck('records').flatten().value();
}
}]);
return FilterSet;
}();
var Filter =
/**
* abstract Filter class
*/
function Filter(key, recsMap) {
_classCallCheck(this, Filter);
if (new.target === Filter) {
throw new TypeError("Cannot construct Filter instances directly");
}
this.key = key;
this.filt = filt;
this.recsMap = recsMap;
};
var NodeFilter = function (_Filter) {
_inherits(NodeFilter, _Filter);
/**
* @param {SGNodeList} filt SGNodeList whose records should be filtered
* @param {String} key SGNode value (not necessarily a string),
* @param {int[]} ids record ids matched by this filter
*/
function NodeFilter(node, filt, ids) {
_classCallCheck(this, NodeFilter);
if (new.target === Filter) {
throw new TypeError("Cannot construct Filter instances directly");
}
var _this7 = _possibleConstructorReturn(this, Object.getPrototypeOf(NodeFilter).call(this, node.key, node.recsMap));
_this7.node = node;
_this7.filt = filt;
_this7.ids = ids; // records ids matched by this filter
return _this7;
}
return NodeFilter;
}(Filter);
/** Summarize records by a dimension
*
* @param {list} Records to be summarized
* @param {numericDim} Dimension to summarize by
*
* @memberof supergroup
*/
var aggregate = function aggregate(list, numericDim) {
if (numericDim) {
list = _lodash2.default.pluck(list, numericDim);
}
return _lodash2.default.reduce(list, function (memo, num) {
memo.sum += num;
memo.cnt++;
memo.avg = memo.sum / memo.cnt;
memo.max = Math.max(memo.max, num);
return memo;
}, { sum: 0, cnt: 0, max: -Infinity });
};
/** Compare groups across two similar root nodes
*
* @param {from} ...
* @param {to} ...
* @param {dim} ...
* @param {opts} ...
*
* used by treelike and some earlier code
*
* @memberof supergroup
*/
var diffList = function diffList(from, to, dim, opts) {
var fromList = new Supergroup(from.records, dim, opts);
var toList = new Supergroup(to.records, dim, opts);
//var list = makeList(sg.compare(fromList, toList, dim));
var list = compare(fromList, toList, dim);
list.dim = opts && opts.dimName ? opts.dimName : dim;
return list;
};
/** Compare two groups by a dimension
*
* @param {A} ...
* @param {B} ...
* @param {dim} ...
*
* @memberof supergroup
*/
var compare = function compare(A, B, dim) {
var a = _lodash2.default.chain(A).map(function (d) {
return d + '';
}).value();
var b = _lodash2.default.chain(B).map(function (d) {
return d + '';
}).value();
var comp = {};
_lodash2.default.each(A, function (d, i) {
comp[d + ''] = {
name: d + '',
'in': 'from',
from: d,
fromIdx: i,
dim: dim
};
});
_lodash2.default.each(B, function (d, i) {
if (d + '' in comp) {
var c = comp[d + ''];
c['in'] = "both";
c.to = d;
c.toIdx = i;
} else {
comp[d + ''] = {
name: d + '',
'in': 'to',
to: d,
toIdx: i,
dim: dim
};
}
});
var list = _lodash2.default.chain(comp).values().sort(function (a, b) {
return a.fromIdx - b.fromIdx || a.toIdx - b.toIdx;
}).map(function (d) {
var val = new SGNode(d.name);
_lodash2.default.extend(val, d);
val.records = [];
if ('from' in d) val.records = val.records.concat(d.from.records);
if ('to' in d) val.records = val.records.concat(d.to.records);
return val;
}).value();
_lodash2.default.chain(list).map(function (d) {
d.parentList = list; // NOT TESTED, NOT USED, PROBABLY WRONG
d.records.parentNode = d; // NOT TESTED, NOT USED, PROBABLY WRONG
}).value();
return list;
};
/** Concatenate two SGNodes into a new one (??)
*
* @param {from} ...
* @param {to} ...
*
* @memberof supergroup
*/
var compareNode = function compareNode(from, to) {
// any reason to keep this?
if (from.dim !== to.dim) {
throw new Error("not sure what you're trying to do");
}
var name = from + ' to ' + to;
var val = new SGNode(name);
val.from = from;
val.to = to;
val.depth = 0;
val['in'] = "both";
val.records = [].concat(from.records, to.records);
val.records.parentNode = val; // NOT TESTED, NOT USED, PROBABLY WRONG
val.dim = from.dim;
return val;
};
function delimOpts(opts) {
if (typeof opts === "string") opts = { delim: opts };
opts = opts || {};
if (!(0, _lodash2.default)(opts).has('delim')) opts.delim = '/';
return opts;
}
var hierarchicalTableToTree = function hierarchicalTableToTree(data, parentPropchildProp) {
throw new Error("fix this after getting rid of childProp");
// does not do the right thing if a value has two parents
// also, does not yet fix depth numbers
var parents = new Supergroup(data, [parentProp, childProp]); // 2-level grouping with all parent/child pairs
var children = parents.leafNodes();
var topParents = _lodash2.default.filter(parents, function (parent) {
var adoptiveParent = children.lookup(parent); // is this parent also a child?
if (adoptiveParent) {
// if so, make it the parent
//adoptiveParent.children = addSupergroupMethods([]);
adoptiveParent.children = new SGNodeList([]);
_lodash2.default.each(parent.children, function (c) {
c.parent = adoptiveParent;
adoptiveParent.children.push(c);
});
} else {
// if not, this is a top parent
return parent;
}
// if so, make use that child node, move this parent node's children over to it
});
//return addSupergroupMethods(topParents);
return new SGNodeList(topParents);
};
// allows grouping by a field that contains an array of values rather than just a single value
if (_lodash2.default.createAggregator) {
var multiValuedGroupBy = _lodash2.default.createAggregator(function (result, value, keys) {
_lodash2.default.each(keys, function (key) {
if (hasOwnProperty.call(result, key)) {
result[key].push(value);
} else {
result[key] = [value];
}
});
});
} else {
var multiValuedGroupBy = function multiValuedGroupBy() {
throw new Error("couldn't install multiValuedGroupBy");
};
}
_lodash2.default.mixin({
sgroup: _lodash2.default.supergroup
});
_lodash2.default.mixin({
//supergroup: (...args)=>{let sg=_.sgroup(...args);Object.setPrototypeOf(sg,Supergroup.prototype); return sg;},
//supergroup: supergroup.supergroup,
supergroup: function supergroup(recs, dims) {
for (var _len7 = arguments.length, args = Array(_len7 > 2 ? _len7 - 2 : 0), _key7 = 2; _key7 < _len7; _key7++) {
args[_key7 - 2] = arguments[_key7];
}
return new Supergroup(recs, dims, _extends({}, args));
},
//supergroup: (recs, dims, ...args) => new Supergroup({recs, dims, ...args}),
//supergroup: function(d) { console.log('EEK'); debugger; throw new Error("blah");},
//addSupergroupMethods: supergroup.addSupergroupMethods,
multiValuedGroupBy: multiValuedGroupBy,
sgDiffList: diffList,
sgCompare: compare,
sgCompareNode: compareNode,
sgAggregate: aggregate,
hierarchicalTableToTree: hierarchicalTableToTree,
// FROM https://gist.github.com/AndreasBriese/1670507
// Return aritmethic mean of the elements
// if an iterator function is given, it is applied before
sum: function sum(obj, iterator, context) {
if (!iterator && _lodash2.default.isEmpty(obj)) return 0;
var result = 0;
if (!iterator && _lodash2.default.isArray(obj)) {
for (var i = obj.length - 1; i > -1; i -= 1) {
result += obj[i];
};
return result;
};
each(obj, function (value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
result += computed;
});
return result;
},
mean: function mean(obj, iterator, context) {
if (!iterator && _lodash2.default.isEmpty(obj)) return Infinity;
if (!iterator && _lodash2.default.isArray(obj)) return _lodash2.default.sum(obj) / obj.length;
if (_lodash2.default.isArray(obj) && !_lodash2.default.isEmpty(obj)) return _lodash2.default.sum(obj, iterator, context) / obj.length;
},
// Return median of the elements
// if the object element number is odd the median is the
// object in the "middle" of a sorted array
// in case of an even number, the arithmetic mean of the two elements
// in the middle (in case of characters or strings: obj[n/2-1] ) is returned.
// if an iterator function is provided, it is applied before
median: function median(obj, iterator, context) {
if (_lodash2.default.isEmpty(obj)) return Infinity;
var tmpObj = [];
if (!iterator && _lodash2.default.isArray(obj)) {
tmpObj = _lodash2.default.clone(obj);
tmpObj.sort(function (f, s) {
return f - s;
});
} else {
_lodash2.default.isArray(obj) && each(obj, function (value, index, list) {
tmpObj.push(iterator ? iterator.call(context, value, index, list) : value);
tmpObj.sort();
});
};
return tmpObj.length % 2 ? tmpObj[Math.floor(tmpObj.length / 2)] : _lodash2.default.isNumber(tmpObj[tmpObj.length / 2 - 1]) && _lodash2.default.isNumber(tmpObj[tmpObj.length / 2]) ? (tmpObj[tmpObj.length / 2 - 1] + tmpObj[tmpObj.length / 2]) / 2 : tmpObj[tmpObj.length / 2 - 1];
}
});
var flatten = exports.flatten = function flatten(list) {
return list.reduce(function (a, b) {
return a.concat(Array.isArray(b) ? flatten(b) : b);
}, []);
};
exports.default = _lodash2.default;