UNPKG

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
'use strict'; 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;