UNPKG

reductio

Version:

Reductio: Crossfilter groupings

1,292 lines (1,142 loc) 33.7 kB
// https://github.com/esjewett/reductio v1.0.0 Copyright 2020 Ethan Jewett (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('crossfilter2')) : typeof define === 'function' && define.amd ? define(['crossfilter2'], factory) : (global = global || self, global.reductio = factory(global.crossfilter)); }(this, (function (crossfilter) { 'use strict'; crossfilter = crossfilter && crossfilter.hasOwnProperty('default') ? crossfilter['default'] : crossfilter; var filter = { // The big idea here is that you give us a filter function to run on values, // a 'prior' reducer to run (just like the rest of the standard reducers), // and a reference to the last reducer (called 'skip' below) defined before // the most recent chain of reducers. This supports individual filters for // each .value('...') chain that you add to your reducer. add: function (filter, prior, skip) { return function (p, v, nf) { if (filter(v, nf)) { if (prior) prior(p, v, nf); } else { if (skip) skip(p, v, nf); } return p; }; }, remove: function (filter, prior, skip) { return function (p, v, nf) { if (filter(v, nf)) { if (prior) prior(p, v, nf); } else { if (skip) skip(p, v, nf); } return p; }; } }; var count = { add: function(prior, path, propName) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p)[propName]++; return p; }; }, remove: function(prior, path, propName) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p)[propName]--; return p; }; }, initial: function(prior, path, propName) { return function (p) { if(prior) p = prior(p); // if(p === undefined) p = {}; path(p)[propName] = 0; return p; }; } }; var sum = { add: function (a, prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p).sum = path(p).sum + a(v); return p; }; }, remove: function (a, prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p).sum = path(p).sum - a(v); return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).sum = 0; return p; }; } }; var avg = { add: function (a, prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); if(path(p).count > 0) { path(p).avg = path(p).sum / path(p).count; } else { path(p).avg = 0; } return p; }; }, remove: function (a, prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); if(path(p).count > 0) { path(p).avg = path(p).sum / path(p).count; } else { path(p).avg = 0; } return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).avg = 0; return p; }; } }; var median = { add: function (prior, path) { var half; return function (p, v, nf) { if(prior) prior(p, v, nf); half = Math.floor(path(p).valueList.length/2); if(path(p).valueList.length % 2) { path(p).median = path(p).valueList[half]; } else { path(p).median = (path(p).valueList[half-1] + path(p).valueList[half]) / 2.0; } return p; }; }, remove: function (prior, path) { var half; return function (p, v, nf) { if(prior) prior(p, v, nf); half = Math.floor(path(p).valueList.length/2); // Check for undefined. if(path(p).valueList.length === 0) { path(p).median = undefined; return p; } if(path(p).valueList.length === 1 || path(p).valueList.length % 2) { path(p).median = path(p).valueList[half]; } else { path(p).median = (path(p).valueList[half-1] + path(p).valueList[half]) / 2.0; } return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).median = undefined; return p; }; } }; var min = { add: function (prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p).min = path(p).valueList[0]; return p; }; }, remove: function (prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); // Check for undefined. if(path(p).valueList.length === 0) { path(p).min = undefined; return p; } path(p).min = path(p).valueList[0]; return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).min = undefined; return p; }; } }; var max = { add: function (prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p).max = path(p).valueList[path(p).valueList.length - 1]; return p; }; }, remove: function (prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); // Check for undefined. if(path(p).valueList.length === 0) { path(p).max = undefined; return p; } path(p).max = path(p).valueList[path(p).valueList.length - 1]; return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).max = undefined; return p; }; } }; var value_count = { add: function (a, prior, path) { var i, curr; return function (p, v, nf) { if(prior) prior(p, v, nf); // Not sure if this is more efficient than sorting. i = path(p).bisect(path(p).values, a(v), 0, path(p).values.length); curr = path(p).values[i]; if(curr && curr[0] === a(v)) { // Value already exists in the array - increment it curr[1]++; } else { // Value doesn't exist - add it in form [value, 1] path(p).values.splice(i, 0, [a(v), 1]); } return p; }; }, remove: function (a, prior, path) { var i; return function (p, v, nf) { if(prior) prior(p, v, nf); i = path(p).bisect(path(p).values, a(v), 0, path(p).values.length); // Value already exists or something has gone terribly wrong. path(p).values[i][1]--; return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); // Array[Array[value, count]] path(p).values = []; path(p).bisect = crossfilter.bisect.by(function(d) { return d[0]; }).left; return p; }; } }; var value_list = { add: function (a, prior, path) { var i; var bisect = crossfilter.bisect.by(function(d) { return d; }).left; return function (p, v, nf) { if(prior) prior(p, v, nf); // Not sure if this is more efficient than sorting. i = bisect(path(p).valueList, a(v), 0, path(p).valueList.length); path(p).valueList.splice(i, 0, a(v)); return p; }; }, remove: function (a, prior, path) { var i; var bisect = crossfilter.bisect.by(function(d) { return d; }).left; return function (p, v, nf) { if(prior) prior(p, v, nf); i = bisect(path(p).valueList, a(v), 0, path(p).valueList.length); // Value already exists or something has gone terribly wrong. path(p).valueList.splice(i, 1); return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).valueList = []; return p; }; } }; var exception_count = { add: function (a, prior, path) { var i, curr; return function (p, v, nf) { if(prior) prior(p, v, nf); // Only count++ if the p.values array doesn't contain a(v) or if it's 0. i = path(p).bisect(path(p).values, a(v), 0, path(p).values.length); curr = path(p).values[i]; if((!curr || curr[0] !== a(v)) || curr[1] === 0) { path(p).exceptionCount++; } return p; }; }, remove: function (a, prior, path) { var i, curr; return function (p, v, nf) { if(prior) prior(p, v, nf); // Only count-- if the p.values array contains a(v) value of 1. i = path(p).bisect(path(p).values, a(v), 0, path(p).values.length); curr = path(p).values[i]; if(curr && curr[0] === a(v) && curr[1] === 1) { path(p).exceptionCount--; } return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).exceptionCount = 0; return p; }; } }; var exception_sum = { add: function (a, sum, prior, path) { var i, curr; return function (p, v, nf) { if(prior) prior(p, v, nf); // Only sum if the p.values array doesn't contain a(v) or if it's 0. i = path(p).bisect(path(p).values, a(v), 0, path(p).values.length); curr = path(p).values[i]; if((!curr || curr[0] !== a(v)) || curr[1] === 0) { path(p).exceptionSum = path(p).exceptionSum + sum(v); } return p; }; }, remove: function (a, sum, prior, path) { var i, curr; return function (p, v, nf) { if(prior) prior(p, v, nf); // Only sum if the p.values array contains a(v) value of 1. i = path(p).bisect(path(p).values, a(v), 0, path(p).values.length); curr = path(p).values[i]; if(curr && curr[0] === a(v) && curr[1] === 1) { path(p).exceptionSum = path(p).exceptionSum - sum(v); } return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).exceptionSum = 0; return p; }; } }; var histogram = { add: function (a, prior, path) { var bisect = crossfilter.bisect.by(function(d) { return d; }).left; var bisectHisto = crossfilter.bisect.by(function(d) { return d.x; }).right; var curr; return function (p, v, nf) { if(prior) prior(p, v, nf); curr = path(p).histogram[bisectHisto(path(p).histogram, a(v), 0, path(p).histogram.length) - 1]; curr.y++; curr.splice(bisect(curr, a(v), 0, curr.length), 0, a(v)); return p; }; }, remove: function (a, prior, path) { var bisect = crossfilter.bisect.by(function(d) { return d; }).left; var bisectHisto = crossfilter.bisect.by(function(d) { return d.x; }).right; var curr; return function (p, v, nf) { if(prior) prior(p, v, nf); curr = path(p).histogram[bisectHisto(path(p).histogram, a(v), 0, path(p).histogram.length) - 1]; curr.y--; curr.splice(bisect(curr, a(v), 0, curr.length), 1); return p; }; }, initial: function (thresholds, prior, path) { return function (p) { p = prior(p); path(p).histogram = []; var arr = []; for(var i = 1; i < thresholds.length; i++) { arr = []; arr.x = thresholds[i - 1]; arr.dx = (thresholds[i] - thresholds[i - 1]); arr.y = 0; path(p).histogram.push(arr); } return p; }; } }; var sum_of_sq = { add: function (a, prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p).sumOfSq = path(p).sumOfSq + a(v)*a(v); return p; }; }, remove: function (a, prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p).sumOfSq = path(p).sumOfSq - a(v)*a(v); return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).sumOfSq = 0; return p; }; } }; var std = { add: function (prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); if(path(p).count > 0) { path(p).std = 0.0; var n = path(p).sumOfSq - path(p).sum*path(p).sum/path(p).count; if (n>0.0) path(p).std = Math.sqrt(n/(path(p).count-1)); } else { path(p).std = 0.0; } return p; }; }, remove: function (prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); if(path(p).count > 0) { path(p).std = 0.0; var n = path(p).sumOfSq - path(p).sum*path(p).sum/path(p).count; if (n>0.0) path(p).std = Math.sqrt(n/(path(p).count-1)); } else { path(p).std = 0; } return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).std = 0; return p; }; } }; var nest = { add: function (keyAccessors, prior, path) { var arrRef; var newRef; return function (p, v, nf) { if(prior) prior(p, v, nf); arrRef = path(p).nest; keyAccessors.forEach(function(a) { newRef = arrRef.filter(function(d) { return d.key === a(v); })[0]; if(newRef) { // There is another level. arrRef = newRef.values; } else { // Next level doesn't yet exist so we create it. newRef = []; arrRef.push({ key: a(v), values: newRef }); arrRef = newRef; } }); arrRef.push(v); return p; }; }, remove: function (keyAccessors, prior, path) { var arrRef; return function (p, v, nf) { if(prior) prior(p, v, nf); arrRef = path(p).nest; keyAccessors.forEach(function(a) { arrRef = arrRef.filter(function(d) { return d.key === a(v); })[0].values; }); // Array contains an actual reference to the row, so just splice it out. arrRef.splice(arrRef.indexOf(v), 1); // If the leaf now has length 0 and it's not the base array remove it. // TODO return p; }; }, initial: function (prior, path) { return function (p) { p = prior(p); path(p).nest = []; return p; }; } }; var alias = { initial: function(prior, path, obj) { return function (p) { if(prior) p = prior(p); function buildAliasFunction(key){ return function(){ return obj[key](path(p)); }; } for(var prop in obj) { path(p)[prop] = buildAliasFunction(prop); } return p; }; } }; var alias_prop = { add: function (obj, prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); for(var prop in obj) { path(p)[prop] = obj[prop](path(p),v); } return p; }; } }; var data_list = { add: function(a, prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p).dataList.push(v); return p; }; }, remove: function(a, prior, path) { return function (p, v, nf) { if(prior) prior(p, v, nf); path(p).dataList.splice(path(p).dataList.indexOf(v), 1); return p; }; }, initial: function(prior, path) { return function (p) { if(prior) p = prior(p); path(p).dataList = []; return p; }; } }; var custom = { add: function(prior, path, addFn) { return function (p, v, nf) { if(prior) prior(p, v, nf); return addFn(p, v); }; }, remove: function(prior, path, removeFn) { return function (p, v, nf) { if(prior) prior(p, v, nf); return removeFn(p, v); }; }, initial: function(prior, path, initialFn) { return function (p) { if(prior) p = prior(p); return initialFn(p); }; } }; function build_function(p, f, path) { // We have to build these functions in order. Eventually we can include dependency // information and create a dependency graph if the process becomes complex enough. if(!path) path = function (d) { return d; }; // Keep track of the original reducers so that filtering can skip back to // them if this particular value is filtered out. var origF = { reduceAdd: f.reduceAdd, reduceRemove: f.reduceRemove, reduceInitial: f.reduceInitial }; if(p.count || p.std) { f.reduceAdd = count.add(f.reduceAdd, path, p.count); f.reduceRemove = count.remove(f.reduceRemove, path, p.count); f.reduceInitial = count.initial(f.reduceInitial, path, p.count); } if(p.sum) { f.reduceAdd = sum.add(p.sum, f.reduceAdd, path); f.reduceRemove = sum.remove(p.sum, f.reduceRemove, path); f.reduceInitial = sum.initial(f.reduceInitial, path); } if(p.avg) { if(!p.count || !p.sum) { console.error("You must set .count(true) and define a .sum(accessor) to use .avg(true)."); } else { f.reduceAdd = avg.add(p.sum, f.reduceAdd, path); f.reduceRemove = avg.remove(p.sum, f.reduceRemove, path); f.reduceInitial = avg.initial(f.reduceInitial, path); } } // The unique-only reducers come before the value_count reducers. They need to check if // the value is already in the values array on the group. They should only increment/decrement // counts if the value not in the array or the count on the value is 0. if(p.exceptionCount) { if(!p.exceptionAccessor) { console.error("You must define an .exception(accessor) to use .exceptionCount(true)."); } else { f.reduceAdd = exception_count.add(p.exceptionAccessor, f.reduceAdd, path); f.reduceRemove = exception_count.remove(p.exceptionAccessor, f.reduceRemove, path); f.reduceInitial = exception_count.initial(f.reduceInitial, path); } } if(p.exceptionSum) { if(!p.exceptionAccessor) { console.error("You must define an .exception(accessor) to use .exceptionSum(accessor)."); } else { f.reduceAdd = exception_sum.add(p.exceptionAccessor, p.exceptionSum, f.reduceAdd, path); f.reduceRemove = exception_sum.remove(p.exceptionAccessor, p.exceptionSum, f.reduceRemove, path); f.reduceInitial = exception_sum.initial(f.reduceInitial, path); } } // Maintain the values array. if(p.valueList || p.median || p.min || p.max) { f.reduceAdd = value_list.add(p.valueList, f.reduceAdd, path); f.reduceRemove = value_list.remove(p.valueList, f.reduceRemove, path); f.reduceInitial = value_list.initial(f.reduceInitial, path); } // Maintain the data array. if(p.dataList) { f.reduceAdd = data_list.add(p.dataList, f.reduceAdd, path); f.reduceRemove = data_list.remove(p.dataList, f.reduceRemove, path); f.reduceInitial = data_list.initial(f.reduceInitial, path); } if(p.median) { f.reduceAdd = median.add(f.reduceAdd, path); f.reduceRemove = median.remove(f.reduceRemove, path); f.reduceInitial = median.initial(f.reduceInitial, path); } if(p.min) { f.reduceAdd = min.add(f.reduceAdd, path); f.reduceRemove = min.remove(f.reduceRemove, path); f.reduceInitial = min.initial(f.reduceInitial, path); } if(p.max) { f.reduceAdd = max.add(f.reduceAdd, path); f.reduceRemove = max.remove(f.reduceRemove, path); f.reduceInitial = max.initial(f.reduceInitial, path); } // Maintain the values count array. if(p.exceptionAccessor) { f.reduceAdd = value_count.add(p.exceptionAccessor, f.reduceAdd, path); f.reduceRemove = value_count.remove(p.exceptionAccessor, f.reduceRemove, path); f.reduceInitial = value_count.initial(f.reduceInitial, path); } // Histogram if(p.histogramValue && p.histogramThresholds) { f.reduceAdd = histogram.add(p.histogramValue, f.reduceAdd, path); f.reduceRemove = histogram.remove(p.histogramValue, f.reduceRemove, path); f.reduceInitial = histogram.initial(p.histogramThresholds ,f.reduceInitial, path); } // Sum of Squares if(p.sumOfSquares) { f.reduceAdd = sum_of_sq.add(p.sumOfSquares, f.reduceAdd, path); f.reduceRemove = sum_of_sq.remove(p.sumOfSquares, f.reduceRemove, path); f.reduceInitial = sum_of_sq.initial(f.reduceInitial, path); } // Standard deviation if(p.std) { if(!p.sumOfSquares || !p.sum) { console.error("You must set .sumOfSq(accessor) and define a .sum(accessor) to use .std(true). Or use .std(accessor)."); } else { f.reduceAdd = std.add(f.reduceAdd, path); f.reduceRemove = std.remove(f.reduceRemove, path); f.reduceInitial = std.initial(f.reduceInitial, path); } } // Custom reducer defined by 3 functions : add, remove, initial if (p.custom) { f.reduceAdd = custom.add(f.reduceAdd, path, p.custom.add); f.reduceRemove = custom.remove(f.reduceRemove, path, p.custom.remove); f.reduceInitial = custom.initial(f.reduceInitial, path, p.custom.initial); } // Nesting if(p.nestKeys) { f.reduceAdd = nest.add(p.nestKeys, f.reduceAdd, path); f.reduceRemove = nest.remove(p.nestKeys, f.reduceRemove, path); f.reduceInitial = nest.initial(f.reduceInitial, path); } // Alias functions if(p.aliasKeys) { f.reduceInitial = alias.initial(f.reduceInitial, path, p.aliasKeys); } // Alias properties - this is less efficient than alias functions if(p.aliasPropKeys) { f.reduceAdd = alias_prop.add(p.aliasPropKeys, f.reduceAdd, path); // This isn't a typo. The function is the same for add/remove. f.reduceRemove = alias_prop.add(p.aliasPropKeys, f.reduceRemove, path); } // Filters determine if our built-up priors should run, or if it should skip // back to the filters given at the beginning of this build function. if (p.filter) { f.reduceAdd = filter.add(p.filter, f.reduceAdd, origF.reduceAdd, path); f.reduceRemove = filter.remove(p.filter, f.reduceRemove, origF.reduceRemove, path); } // Values go last. if(p.values) { Object.getOwnPropertyNames(p.values).forEach(function(n) { // Set up the path on each group. var setupPath = function(prior) { return function (p) { p = prior(p); path(p)[n] = {}; return p; }; }; f.reduceInitial = setupPath(f.reduceInitial); build_function(p.values[n].parameters, f, function (p) { return p[n]; }); }); } } var build = { build: build_function }; var parameters = function() { return { order: false, avg: false, count: false, sum: false, exceptionAccessor: false, exceptionCount: false, exceptionSum: false, filter: false, valueList: false, median: false, histogramValue: false, min: false, max: false, histogramThresholds: false, std: false, sumOfSquares: false, values: false, nestKeys: false, aliasKeys: false, aliasPropKeys: false, groupAll: false, dataList: false, custom: false }; }; function assign(target) { if (target == null) { throw new TypeError('Cannot convert undefined or null to object'); } var output = Object(target); for (var index = 1; index < arguments.length; ++index) { var source = arguments[index]; if (source != null) { for (var nextKey in source) { if(source.hasOwnProperty(nextKey)) { output[nextKey] = source[nextKey]; } } } } return output; } function accessor_build(obj, p) { // obj.order = function(value) { // if (!arguments.length) return p.order; // p.order = value; // return obj; // }; // Converts a string to an accessor function function accessorify(v) { if( typeof v === 'string' ) { // Rewrite to a function var tempValue = v; var func = function (d) { return d[tempValue]; }; return func; } else { return v; } } // Converts a string to an accessor function function accessorifyNumeric(v) { if( typeof v === 'string' ) { // Rewrite to a function var tempValue = v; var func = function (d) { return +d[tempValue]; }; return func; } else { return v; } } obj.fromObject = function(value) { if(!arguments.length) return p; assign(p, value); return obj; }; obj.toObject = function() { return p; }; obj.count = function(value, propName) { if (!arguments.length) return p.count; if (!propName) { propName = 'count'; } p.count = propName; return obj; }; obj.sum = function(value) { if (!arguments.length) return p.sum; value = accessorifyNumeric(value); p.sum = value; return obj; }; obj.avg = function(value) { if (!arguments.length) return p.avg; value = accessorifyNumeric(value); // We can take an accessor function, a boolean, or a string if( typeof value === 'function' ) { if(p.sum && p.sum !== value) console.warn('SUM aggregation is being overwritten by AVG aggregation'); p.sum = value; p.avg = true; p.count = 'count'; } else { p.avg = value; } return obj; }; obj.exception = function(value) { if (!arguments.length) return p.exceptionAccessor; value = accessorify(value); p.exceptionAccessor = value; return obj; }; obj.filter = function(value) { if (!arguments.length) return p.filter; p.filter = value; return obj; }; obj.valueList = function(value) { if (!arguments.length) return p.valueList; value = accessorify(value); p.valueList = value; return obj; }; obj.median = function(value) { if (!arguments.length) return p.median; value = accessorifyNumeric(value); if(typeof value === 'function') { if(p.valueList && p.valueList !== value) console.warn('VALUELIST accessor is being overwritten by median aggregation'); p.valueList = value; } p.median = value; return obj; }; obj.min = function(value) { if (!arguments.length) return p.min; value = accessorifyNumeric(value); if(typeof value === 'function') { if(p.valueList && p.valueList !== value) console.warn('VALUELIST accessor is being overwritten by min aggregation'); p.valueList = value; } p.min = value; return obj; }; obj.max = function(value) { if (!arguments.length) return p.max; value = accessorifyNumeric(value); if(typeof value === 'function') { if(p.valueList && p.valueList !== value) console.warn('VALUELIST accessor is being overwritten by max aggregation'); p.valueList = value; } p.max = value; return obj; }; obj.exceptionCount = function(value) { if (!arguments.length) return p.exceptionCount; value = accessorify(value); if( typeof value === 'function' ) { if(p.exceptionAccessor && p.exceptionAccessor !== value) console.warn('EXCEPTION accessor is being overwritten by exception count aggregation'); p.exceptionAccessor = value; p.exceptionCount = true; } else { p.exceptionCount = value; } return obj; }; obj.exceptionSum = function(value) { if (!arguments.length) return p.exceptionSum; value = accessorifyNumeric(value); p.exceptionSum = value; return obj; }; obj.histogramValue = function(value) { if (!arguments.length) return p.histogramValue; value = accessorifyNumeric(value); p.histogramValue = value; return obj; }; obj.histogramBins = function(value) { if (!arguments.length) return p.histogramThresholds; p.histogramThresholds = value; return obj; }; obj.std = function(value) { if (!arguments.length) return p.std; value = accessorifyNumeric(value); if(typeof(value) === 'function') { p.sumOfSquares = value; p.sum = value; p.count = 'count'; p.std = true; } else { p.std = value; } return obj; }; obj.sumOfSq = function(value) { if (!arguments.length) return p.sumOfSquares; value = accessorifyNumeric(value); p.sumOfSquares = value; return obj; }; obj.value = function(value, accessor) { if (!arguments.length || typeof value !== 'string' ) { console.error("'value' requires a string argument."); } else { if(!p.values) p.values = {}; p.values[value] = {}; p.values[value].parameters = parameters(); accessor_build(p.values[value], p.values[value].parameters); if(accessor) p.values[value].accessor = accessor; return p.values[value]; } }; obj.nest = function(keyAccessorArray) { if(!arguments.length) return p.nestKeys; keyAccessorArray.map(accessorify); p.nestKeys = keyAccessorArray; return obj; }; obj.alias = function(propAccessorObj) { if(!arguments.length) return p.aliasKeys; p.aliasKeys = propAccessorObj; return obj; }; obj.aliasProp = function(propAccessorObj) { if(!arguments.length) return p.aliasPropKeys; p.aliasPropKeys = propAccessorObj; return obj; }; obj.groupAll = function(groupTest) { if(!arguments.length) return p.groupAll; p.groupAll = groupTest; return obj; }; obj.dataList = function(value) { if (!arguments.length) return p.dataList; p.dataList = value; return obj; }; obj.custom = function(addRemoveInitialObj) { if (!arguments.length) return p.custom; p.custom = addRemoveInitialObj; return obj; }; } var accessors = { build: accessor_build }; function postProcess(reductio) { return function (group, p, f) { group.post = function(){ var postprocess = function () { return postprocess.all(); }; postprocess.all = function () { return group.all(); }; var postprocessors = reductio.postprocessors; Object.keys(postprocessors).forEach(function (name) { postprocess[name] = function () { var _all = postprocess.all; var args = [].slice.call(arguments); postprocess.all = function () { return postprocessors[name](_all, f, p).apply(null, args); }; return postprocess; }; }); return postprocess; }; }; } var pluck = function(n){ return function(d){ return d[n]; }; }; // supported operators are sum, avg, and count const _grouper = function(path, prior){ if(!path) path = function(d){return d;}; return function(p, v){ if(prior) prior(p, v); var x = path(p), y = path(v); if(typeof y.count !== 'undefined') x.count += y.count; if(typeof y.sum !== 'undefined') x.sum += y.sum; if(typeof y.avg !== 'undefined') x.avg = x.sum/x.count; return p; }; }; const cap = function (prior, f, p) { var obj = f.reduceInitial(); // we want to support values so we'll need to know what those are var values = p.values ? Object.keys(p.values) : []; var _othersGrouper = _grouper(); if (values.length) { for (var i = 0; i < values.length; ++i) { _othersGrouper = _grouper(pluck(values[i]), _othersGrouper); } } return function (cap, othersName) { if (!arguments.length) return prior(); if( cap === Infinity || !cap ) return prior(); var all = prior(); var slice_idx = cap-1; if(all.length <= cap) return all; var data = all.slice(0, slice_idx); var others = {key: othersName || 'Others'}; others.value = f.reduceInitial(); for (var i = slice_idx; i < all.length; ++i) { _othersGrouper(others.value, all[i].value); } data.push(others); return data; }; }; var pluck_n = function (n) { if (typeof n === 'function') { return n; } if (~n.indexOf('.')) { var split = n.split('.'); return function (d) { return split.reduce(function (p, v) { return p[v]; }, d); }; } return function (d) { return d[n]; }; }; function ascending(a, b) { return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; } var comparer = function (accessor, ordering) { return function (a, b) { return ordering(accessor(a), accessor(b)); }; }; function sortBy (prior) { return function (value, order) { if (arguments.length === 1) { order = ascending; } return prior().sort(comparer(pluck_n(value), order)); }; } function postprocessors(reductio){ reductio.postprocessors = {}; reductio.registerPostProcessor = function(name, func){ reductio.postprocessors[name] = func; }; reductio.registerPostProcessor('cap', cap); reductio.registerPostProcessor('sortBy', sortBy); } function reductio() { var parameters$1 = parameters(); var funcs = {}; function my(group) { // Start fresh each time. funcs = { reduceAdd: function(p) { return p; }, reduceRemove: function(p) { return p; }, reduceInitial: function () { return {}; }, }; build.build(parameters$1, funcs); // If we're doing groupAll if(parameters$1.groupAll) { if(group.top) { console.warn("'groupAll' is defined but attempting to run on a standard dimension.group(). Must run on dimension.groupAll()."); } else { var bisect = crossfilter.bisect.by(function(d) { return d.key; }).left; var i, j; var keys; var keysLength; var k; // Key group.reduce( function(p, v, nf) { keys = parameters$1.groupAll(v); keysLength = keys.length; for(j=0;j<keysLength;j++) { k = keys[j]; i = bisect(p, k, 0, p.length); if(!p[i] || p[i].key !== k) { // If the group doesn't yet exist, create it first. p.splice(i, 0, { key: k, value: funcs.reduceInitial() }); } // Then pass the record and the group value to the reducers funcs.reduceAdd(p[i].value, v, nf); } return p; }, function(p, v, nf) { keys = parameters$1.groupAll(v); keysLength = keys.length; for(j=0;j<keysLength;j++) { i = bisect(p, keys[j], 0, p.length); // The group should exist or we're in trouble! // Then pass the record and the group value to the reducers funcs.reduceRemove(p[i].value, v, nf); } return p; }, function() { return []; } ); if(!group.all) { // Add an 'all' method for compatibility with standard Crossfilter groups. group.all = function() { return this.value(); }; } } } else { group.reduce(funcs.reduceAdd, funcs.reduceRemove, funcs.reduceInitial); } postprocessed(group, parameters$1, funcs); return group; } accessors.build(my, parameters$1); return my; } postprocessors(reductio); const postprocessed = postProcess(reductio); return reductio; })));