// https://crossfilter.github.io/crossfilter/ v1.5.2 Copyright 2020 Mike Bostock (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.crossfilter = factory()); }(this, (function () { 'use strict'; let array8 = arrayUntyped, array16 = arrayUntyped, array32 = arrayUntyped, arrayLengthen = arrayLengthenUntyped, arrayWiden = arrayWidenUntyped; if (typeof Uint8Array !== "undefined") { array8 = function(n) { return new Uint8Array(n); }; array16 = function(n) { return new Uint16Array(n); }; array32 = function(n) { return new Uint32Array(n); }; arrayLengthen = function(array, length) { if (array.length >= length) return array; var copy = new array.constructor(length); copy.set(array); return copy; }; arrayWiden = function(array, width) { var copy; switch (width) { case 16: copy = array16(array.length); break; case 32: copy = array32(array.length); break; default: throw new Error("invalid array width!"); } copy.set(array); return copy; }; } function arrayUntyped(n) { var array = new Array(n), i = -1; while (++i < n) array[i] = 0; return array; } function arrayLengthenUntyped(array, length) { var n = array.length; while (n < length) array[n++] = 0; return array; } function arrayWidenUntyped(array, width) { if (width > 32) throw new Error("invalid array width!"); return array; } // An arbitrarily-wide array of bitmasks function bitarray(n) { this.length = n; this.subarrays = 1; this.width = 8; this.masks = { 0: 0 }; this[0] = array8(n); } bitarray.prototype.lengthen = function(n) { var i, len; for (i = 0, len = this.subarrays; i < len; ++i) { this[i] = arrayLengthen(this[i], n); } this.length = n; }; // Reserve a new bit index in the array, returns {offset, one} bitarray.prototype.add = function() { var m, w, one, i, len; for (i = 0, len = this.subarrays; i < len; ++i) { m = this.masks[i]; w = this.width - (32 * i); // isolate the rightmost zero bit and return it as an unsigned int of 32 bits, if NaN or -1, return a 0 one = (~m & (m + 1)) >>> 0; if (w >= 32 && !one) { continue; } if (w < 32 && (one & (1 << w))) { // widen this subarray this[i] = arrayWiden(this[i], w <<= 1); this.width = 32 * i + w; } this.masks[i] |= one; return { offset: i, one: one }; } // add a new subarray this[this.subarrays] = array8(this.length); this.masks[this.subarrays] = 1; this.width += 8; return { offset: this.subarrays++, one: 1 }; }; // Copy record from index src to index dest bitarray.prototype.copy = function(dest, src) { var i, len; for (i = 0, len = this.subarrays; i < len; ++i) { this[i][dest] = this[i][src]; } }; // Truncate the array to the given length bitarray.prototype.truncate = function(n) { var i, len; for (i = 0, len = this.subarrays; i < len; ++i) { for (var j = this.length - 1; j >= n; j--) { this[i][j] = 0; } } this.length = n; }; // Checks that all bits for the given index are 0 bitarray.prototype.zero = function(n) { var i, len; for (i = 0, len = this.subarrays; i < len; ++i) { if (this[i][n]) { return false; } } return true; }; // Checks that all bits for the given index are 0 except for possibly one bitarray.prototype.zeroExcept = function(n, offset, zero) { var i, len; for (i = 0, len = this.subarrays; i < len; ++i) { if (i === offset ? this[i][n] & zero : this[i][n]) { return false; } } return true; }; // Checks that all bits for the given index are 0 except for the specified mask. // The mask should be an array of the same size as the filter subarrays width. bitarray.prototype.zeroExceptMask = function(n, mask) { var i, len; for (i = 0, len = this.subarrays; i < len; ++i) { if (this[i][n] & mask[i]) { return false; } } return true; }; // Checks that only the specified bit is set for the given index bitarray.prototype.only = function(n, offset, one) { var i, len; for (i = 0, len = this.subarrays; i < len; ++i) { if (this[i][n] != (i === offset ? one : 0)) { return false; } } return true; }; // Checks that only the specified bit is set for the given index except for possibly one other bitarray.prototype.onlyExcept = function(n, offset, zero, onlyOffset, onlyOne) { var mask; var i, len; for (i = 0, len = this.subarrays; i < len; ++i) { mask = this[i][n]; if (i === offset) mask &= zero; if (mask != (i === onlyOffset ? onlyOne : 0)) { return false; } } return true; }; var xfilterArray = { array8: arrayUntyped, array16: arrayUntyped, array32: arrayUntyped, arrayLengthen: arrayLengthenUntyped, arrayWiden: arrayWidenUntyped, bitarray: bitarray }; const filterExact = (bisect, value) => { return function(values) { var n = values.length; return [bisect.left(values, value, 0, n), bisect.right(values, value, 0, n)]; }; }; const filterRange = (bisect, range) => { var min = range[0], max = range[1]; return function(values) { var n = values.length; return [bisect.left(values, min, 0, n), bisect.left(values, max, 0, n)]; }; }; const filterAll = values => { return [0, values.length]; }; var xfilterFilter = { filterExact, filterRange, filterAll }; var cr_identity = d => { return d; }; var cr_null = () => { return null; }; var cr_zero = () => { return 0; }; function heap_by(f) { // Builds a binary heap within the specified array a[lo:hi]. The heap has the // property such that the parent a[lo+i] is always less than or equal to its // two children: a[lo+2*i+1] and a[lo+2*i+2]. function heap(a, lo, hi) { var n = hi - lo, i = (n >>> 1) + 1; while (--i > 0) sift(a, i, n, lo); return a; } // Sorts the specified array a[lo:hi] in descending order, assuming it is // already a heap. function sort(a, lo, hi) { var n = hi - lo, t; while (--n > 0) t = a[lo], a[lo] = a[lo + n], a[lo + n] = t, sift(a, 1, n, lo); return a; } // Sifts the element a[lo+i-1] down the heap, where the heap is the contiguous // slice of array a[lo:lo+n]. This method can also be used to update the heap // incrementally, without incurring the full cost of reconstructing the heap. function sift(a, i, n, lo) { var d = a[--lo + i], x = f(d), child; while ((child = i << 1) <= n) { if (child < n && f(a[lo + child]) > f(a[lo + child + 1])) child++; if (x <= f(a[lo + child])) break; a[lo + i] = a[lo + child]; i = child; } a[lo + i] = d; } heap.sort = sort; return heap; } const h = heap_by(cr_identity); h.by = heap_by; function heapselect_by(f) { var heap = h.by(f); // Returns a new array containing the top k elements in the array a[lo:hi]. // The returned array is not sorted, but maintains the heap property. If k is // greater than hi - lo, then fewer than k elements will be returned. The // order of elements in a is unchanged by this operation. function heapselect(a, lo, hi, k) { var queue = new Array(k = Math.min(hi - lo, k)), min, i, d; for (i = 0; i < k; ++i) queue[i] = a[lo++]; heap(queue, 0, k); if (lo < hi) { min = f(queue[0]); do { if (f(d = a[lo]) > min) { queue[0] = d; min = f(heap(queue, 0, k)[0]); } } while (++lo < hi); } return queue; } return heapselect; } const h$1 = heapselect_by(cr_identity); h$1.by = heapselect_by; // assign the raw function to the export as well function bisect_by(f) { // Locate the insertion point for x in a to maintain sorted order. The // arguments lo and hi may be used to specify a subset of the array which // should be considered; by default the entire array is used. If x is already // present in a, the insertion point will be before (to the left of) any // existing entries. The return value is suitable for use as the first // argument to `array.splice` assuming that a is already sorted. // // The returned insertion point i partitions the array a into two halves so // that all v < x for v in a[lo:i] for the left side and all v >= x for v in // a[i:hi] for the right side. function bisectLeft(a, x, lo, hi) { while (lo < hi) { var mid = lo + hi >>> 1; if (f(a[mid]) < x) lo = mid + 1; else hi = mid; } return lo; } // Similar to bisectLeft, but returns an insertion point which comes after (to // the right of) any existing entries of x in a. // // The returned insertion point i partitions the array into two halves so that // all v <= x for v in a[lo:i] for the left side and all v > x for v in // a[i:hi] for the right side. function bisectRight(a, x, lo, hi) { while (lo < hi) { var mid = lo + hi >>> 1; if (x < f(a[mid])) hi = mid; else lo = mid + 1; } return lo; } bisectRight.right = bisectRight; bisectRight.left = bisectLeft; return bisectRight; } const bisect = bisect_by(cr_identity); bisect.by = bisect_by; // assign the raw function to the export as well var permute = (array, index, deep) => { for (var i = 0, n = index.length, copy = deep ? JSON.parse(JSON.stringify(array)) : new Array(n); i < n; ++i) { copy[i] = array[index[i]]; } return copy; }; const reduceIncrement = p => { return p + 1; }; const reduceDecrement = p => { return p - 1; }; const reduceAdd = f => { return function(p, v) { return p + +f(v); }; }; const reduceSubtract = f => { return function(p, v) { return p - f(v); }; }; var xfilterReduce = { reduceIncrement, reduceDecrement, reduceAdd, reduceSubtract }; function deep(t,e,i,n,r){for(r in n=(i=i.split(".")).splice(-1,1),i)e=e[i[r]]=e[i[r]]||{};return t(e,n)} // Note(cg): result was previsouly using lodash.result, not ESM compatible. const get = (obj, prop) => { const value = obj[prop]; return (typeof value === 'function') ? value.call(obj) : value; }; /** * get value of object at a deep path. * if the resolved value is a function, * it's invoked with the `this` binding of * its parent object and its result is returned. * * @param {Object} obj the object (e.g. { 'a': [{ 'b': { 'c1': 3, 'c2': 4} }], 'd': {e:1} }; ) * @param {String} path deep path (e.g. `d.e`` or `a[0].b.c1`. Dot notation (a.0.b)is also supported) * @return {Any} the resolved value */ const reg = /\[([\w\d]+)\]/g; var result = (obj, path) => { return deep(get, obj, path.replace(reg, '.$1')) }; // constants var REMOVED_INDEX = -1; crossfilter.heap = h; crossfilter.heapselect = h$1; crossfilter.bisect = bisect; crossfilter.permute = permute; function crossfilter() { var crossfilter = { add: add, remove: removeData, dimension: dimension, groupAll: groupAll, size: size, all: all, allFiltered: allFiltered, onChange: onChange, isElementFiltered: isElementFiltered }; var data = [], // the records n = 0, // the number of records; data.length filters, // 1 is filtered out filterListeners = [], // when the filters change dataListeners = [], // when data is added removeDataListeners = [], // when data is removed callbacks = []; filters = new xfilterArray.bitarray(0); // Adds the specified new records to this crossfilter. function add(newData) { var n0 = n, n1 = newData.length; // If there's actually new data to add… // Merge the new data into the existing data. // Lengthen the filter bitset to handle the new records. // Notify listeners (dimensions and groups) that new data is available. if (n1) { data = data.concat(newData); filters.lengthen(n += n1); dataListeners.forEach(function(l) { l(newData, n0, n1); }); triggerOnChange('dataAdded'); } return crossfilter; } // Removes all records that match the current filters, or if a predicate function is passed, // removes all records matching the predicate (ignoring filters). function removeData(predicate) { var // Mapping from old record indexes to new indexes (after records removed) newIndex = new Array(n), removed = [], usePred = typeof predicate === 'function', shouldRemove = function (i) { return usePred ? predicate(data[i], i) : filters.zero(i) }; for (var index1 = 0, index2 = 0; index1 < n; ++index1) { if ( shouldRemove(index1) ) { removed.push(index1); newIndex[index1] = REMOVED_INDEX; } else { newIndex[index1] = index2++; } } // Remove all matching records from groups. filterListeners.forEach(function(l) { l(-1, -1, [], removed, true); }); // Update indexes. removeDataListeners.forEach(function(l) { l(newIndex); }); // Remove old filters and data by overwriting. for (var index3 = 0, index4 = 0; index3 < n; ++index3) { if ( newIndex[index3] !== REMOVED_INDEX ) { if (index3 !== index4) filters.copy(index4, index3), data[index4] = data[index3]; ++index4; } } data.length = n = index4; filters.truncate(index4); triggerOnChange('dataRemoved'); } function maskForDimensions(dimensions) { var n, d, len, id, mask = Array(filters.subarrays); for (n = 0; n < filters.subarrays; n++) { mask[n] = ~0; } for (d = 0, len = dimensions.length; d < len; d++) { // The top bits of the ID are the subarray offset and the lower bits are the bit // offset of the "one" mask. id = dimensions[d].id(); mask[id >> 7] &= ~(0x1 << (id & 0x3f)); } return mask; } // Return true if the data element at index i is filtered IN. // Optionally, ignore the filters of any dimensions in the ignore_dimensions list. function isElementFiltered(i, ignore_dimensions) { var mask = maskForDimensions(ignore_dimensions || []); return filters.zeroExceptMask(i,mask); } // Adds a new dimension with the specified value accessor function. function dimension(value, iterable) { if (typeof value === 'string') { var accessorPath = value; value = function(d) { return result(d, accessorPath); }; } var dimension = { filter: filter, filterExact: filterExact, filterRange: filterRange, filterFunction: filterFunction, filterAll: filterAll, currentFilter: currentFilter, hasCurrentFilter: hasCurrentFilter, top: top, bottom: bottom, group: group, groupAll: groupAll, dispose: dispose, remove: dispose, // for backwards-compatibility accessor: value, id: function() { return id; } }; var one, // lowest unset bit as mask, e.g., 00001000 zero, // inverted one, e.g., 11110111 offset, // offset into the filters arrays id, // unique ID for this dimension (reused when dimensions are disposed) values, // sorted, cached array index, // maps sorted value index -> record index (in data) newValues, // temporary array storing newly-added values newIndex, // temporary array storing newly-added index iterablesIndexCount, iterablesIndexFilterStatus, iterablesEmptyRows = [], sortRange = function(n) { return cr_range(n).sort(function(A, B) { var a = newValues[A], b = newValues[B]; return a < b ? -1 : a > b ? 1 : A - B; }); }, refilter = xfilterFilter.filterAll, // for recomputing filter refilterFunction, // the custom filter function in use filterValue, // the value used for filtering (value, array, function or undefined) filterValuePresent, // true if filterValue contains something indexListeners = [], // when data is added dimensionGroups = [], lo0 = 0, hi0 = 0, t = 0, k; // Updating a dimension is a two-stage process. First, we must update the // associated filters for the newly-added records. Once all dimensions have // updated their filters, the groups are notified to update. dataListeners.unshift(preAdd); dataListeners.push(postAdd); removeDataListeners.push(removeData); // Add a new dimension in the filter bitmap and store the offset and bitmask. var tmp = filters.add(); offset = tmp.offset; one = tmp.one; zero = ~one; // Create a unique ID for the dimension // IDs will be re-used if dimensions are disposed. // For internal use the ID is the subarray offset shifted left 7 bits or'd with the // bit offset of the set bit in the dimension's "one" mask. id = (offset << 7) | (Math.log(one) / Math.log(2)); preAdd(data, 0, n); postAdd(data, 0, n); // Incorporates the specified new records into this dimension. // This function is responsible for updating filters, values, and index. function preAdd(newData, n0, n1) { var newIterablesIndexCount, newIterablesIndexFilterStatus; if (iterable){ // Count all the values t = 0; j = 0; k = []; for (var i0 = 0; i0 < newData.length; i0++) { for(j = 0, k = value(newData[i0]); j < k.length; j++) { t++; } } newValues = []; newIterablesIndexCount = cr_range(newData.length); newIterablesIndexFilterStatus = cr_index(t,1); var unsortedIndex = cr_range(t); for (var l = 0, index1 = 0; index1 < newData.length; index1++) { k = value(newData[index1]); // if(!k.length){ newIterablesIndexCount[index1] = 0; iterablesEmptyRows.push(index1 + n0); continue; } newIterablesIndexCount[index1] = k.length; for (j = 0; j < k.length; j++) { newValues.push(k[j]); unsortedIndex[l] = index1; l++; } } // Create the Sort map used to sort both the values and the valueToData indices var sortMap = sortRange(t); // Use the sortMap to sort the newValues newValues = permute(newValues, sortMap); // Use the sortMap to sort the unsortedIndex map // newIndex should be a map of sortedValue -> crossfilterData newIndex = permute(unsortedIndex, sortMap); } else{ // Permute new values into natural order using a standard sorted index. newValues = newData.map(value); newIndex = sortRange(n1); newValues = permute(newValues, newIndex); } // Bisect newValues to determine which new records are selected. var bounds = refilter(newValues), lo1 = bounds[0], hi1 = bounds[1]; var index2, index3, index4; if(iterable) { n1 = t; if (refilterFunction) { for (index2 = 0; index2 < n1; ++index2) { if (!refilterFunction(newValues[index2], index2)) { if(--newIterablesIndexCount[newIndex[index2]] === 0) { filters[offset][newIndex[index2] + n0] |= one; } newIterablesIndexFilterStatus[index2] = 1; } } } else { for (index3 = 0; index3 < lo1; ++index3) { if(--newIterablesIndexCount[newIndex[index3]] === 0) { filters[offset][newIndex[index3] + n0] |= one; } newIterablesIndexFilterStatus[index3] = 1; } for (index4 = hi1; index4 < n1; ++index4) { if(--newIterablesIndexCount[newIndex[index4]] === 0) { filters[offset][newIndex[index4] + n0] |= one; } newIterablesIndexFilterStatus[index4] = 1; } } } else { if (refilterFunction) { for (index2 = 0; index2 < n1; ++index2) { if (!refilterFunction(newValues[index2], index2)) { filters[offset][newIndex[index2] + n0] |= one; } } } else { for (index3 = 0; index3 < lo1; ++index3) { filters[offset][newIndex[index3] + n0] |= one; } for (index4 = hi1; index4 < n1; ++index4) { filters[offset][newIndex[index4] + n0] |= one; } } } // If this dimension previously had no data, then we don't need to do the // more expensive merge operation; use the new values and index as-is. if (!n0) { values = newValues; index = newIndex; iterablesIndexCount = newIterablesIndexCount; iterablesIndexFilterStatus = newIterablesIndexFilterStatus; lo0 = lo1; hi0 = hi1; return; } var oldValues = values, oldIndex = index, oldIterablesIndexFilterStatus = iterablesIndexFilterStatus, old_n0, i1 = 0; i0 = 0; if(iterable){ old_n0 = n0; n0 = oldValues.length; n1 = t; } // Otherwise, create new arrays into which to merge new and old. values = iterable ? new Array(n0 + n1) : new Array(n); index = iterable ? new Array(n0 + n1) : cr_index(n, n); if(iterable) iterablesIndexFilterStatus = cr_index(n0 + n1, 1); // Concatenate the newIterablesIndexCount onto the old one. if(iterable) { var oldiiclength = iterablesIndexCount.length; iterablesIndexCount = xfilterArray.arrayLengthen(iterablesIndexCount, n); for(var j=0; j+oldiiclength < n; j++) { iterablesIndexCount[j+oldiiclength] = newIterablesIndexCount[j]; } } // Merge the old and new sorted values, and old and new index. var index5 = 0; for (; i0 < n0 && i1 < n1; ++index5) { if (oldValues[i0] < newValues[i1]) { values[index5] = oldValues[i0]; if(iterable) iterablesIndexFilterStatus[index5] = oldIterablesIndexFilterStatus[i0]; index[index5] = oldIndex[i0++]; } else { values[index5] = newValues[i1]; if(iterable) iterablesIndexFilterStatus[index5] = newIterablesIndexFilterStatus[i1]; index[index5] = newIndex[i1++] + (iterable ? old_n0 : n0); } } // Add any remaining old values. for (; i0 < n0; ++i0, ++index5) { values[index5] = oldValues[i0]; if(iterable) iterablesIndexFilterStatus[index5] = oldIterablesIndexFilterStatus[i0]; index[index5] = oldIndex[i0]; } // Add any remaining new values. for (; i1 < n1; ++i1, ++index5) { values[index5] = newValues[i1]; if(iterable) iterablesIndexFilterStatus[index5] = newIterablesIndexFilterStatus[i1]; index[index5] = newIndex[i1] + (iterable ? old_n0 : n0); } // Bisect again to recompute lo0 and hi0. bounds = refilter(values), lo0 = bounds[0], hi0 = bounds[1]; } // When all filters have updated, notify index listeners of the new values. function postAdd(newData, n0, n1) { indexListeners.forEach(function(l) { l(newValues, newIndex, n0, n1); }); newValues = newIndex = null; } function removeData(reIndex) { if (iterable) { for (var i0 = 0, i1 = 0; i0 < iterablesEmptyRows.length; i0++) { if (reIndex[iterablesEmptyRows[i0]] !== REMOVED_INDEX) { iterablesEmptyRows[i1] = reIndex[iterablesEmptyRows[i0]]; i1++; } } iterablesEmptyRows.length = i1; for (i0 = 0, i1 = 0; i0 < n; i0++) { if (reIndex[i0] !== REMOVED_INDEX) { if (i1 !== i0) iterablesIndexCount[i1] = iterablesIndexCount[i0]; i1++; } } iterablesIndexCount = iterablesIndexCount.slice(0, i1); } // Rewrite our index, overwriting removed values var n0 = values.length; for (var i = 0, j = 0, oldDataIndex; i < n0; ++i) { oldDataIndex = index[i]; if (reIndex[oldDataIndex] !== REMOVED_INDEX) { if (i !== j) values[j] = values[i]; index[j] = reIndex[oldDataIndex]; if (iterable) { iterablesIndexFilterStatus[j] = iterablesIndexFilterStatus[i]; } ++j; } } values.length = j; if (iterable) iterablesIndexFilterStatus = iterablesIndexFilterStatus.slice(0, j); while (j < n0) index[j++] = 0; // Bisect again to recompute lo0 and hi0. var bounds = refilter(values); lo0 = bounds[0], hi0 = bounds[1]; } // Updates the selected values based on the specified bounds [lo, hi]. // This implementation is used by all the public filter methods. function filterIndexBounds(bounds) { var lo1 = bounds[0], hi1 = bounds[1]; if (refilterFunction) { refilterFunction = null; filterIndexFunction(function(d, i) { return lo1 <= i && i < hi1; }, bounds[0] === 0 && bounds[1] === values.length); lo0 = lo1; hi0 = hi1; return dimension; } var i, j, k, added = [], removed = [], valueIndexAdded = [], valueIndexRemoved = []; // Fast incremental update based on previous lo index. if (lo1 < lo0) { for (i = lo1, j = Math.min(lo0, hi1); i < j; ++i) { added.push(index[i]); valueIndexAdded.push(i); } } else if (lo1 > lo0) { for (i = lo0, j = Math.min(lo1, hi0); i < j; ++i) { removed.push(index[i]); valueIndexRemoved.push(i); } } // Fast incremental update based on previous hi index. if (hi1 > hi0) { for (i = Math.max(lo1, hi0), j = hi1; i < j; ++i) { added.push(index[i]); valueIndexAdded.push(i); } } else if (hi1 < hi0) { for (i = Math.max(lo0, hi1), j = hi0; i < j; ++i) { removed.push(index[i]); valueIndexRemoved.push(i); } } if(!iterable) { // Flip filters normally. for(i=0; i 0) toSkip = top_offset; while (--i >= lo0 && k > 0) { if (filters.zero(j = index[i])) { if(toSkip > 0) { //skip matching row --toSkip; } else { array.push(data[j]); --k; } } } if(iterable){ for(i = 0; i < iterablesEmptyRows.length && k > 0; i++) { // Add row with empty iterable column at the end if(filters.zero(j = iterablesEmptyRows[i])) { if(toSkip > 0) { //skip matching row --toSkip; } else { array.push(data[j]); --k; } } } } return array; } // Returns the bottom K selected records based on this dimension's order. // Note: observes this dimension's filter, unlike group and groupAll. function bottom(k, bottom_offset) { var array = [], i, j, toSkip = 0; if(bottom_offset && bottom_offset > 0) toSkip = bottom_offset; if(iterable) { // Add row with empty iterable column at the top for(i = 0; i < iterablesEmptyRows.length && k > 0; i++) { if(filters.zero(j = iterablesEmptyRows[i])) { if(toSkip > 0) { //skip matching row --toSkip; } else { array.push(data[j]); --k; } } } } i = lo0; while (i < hi0 && k > 0) { if (filters.zero(j = index[i])) { if(toSkip > 0) { //skip matching row --toSkip; } else { array.push(data[j]); --k; } } i++; } return array; } // Adds a new group to this dimension, using the specified key function. function group(key) { var group = { top: top, all: all, reduce: reduce, reduceCount: reduceCount, reduceSum: reduceSum, order: order, orderNatural: orderNatural, size: size, dispose: dispose, remove: dispose // for backwards-compatibility }; // Ensure that this group will be removed when the dimension is removed. dimensionGroups.push(group); var groups, // array of {key, value} groupIndex, // object id ↦ group id groupWidth = 8, groupCapacity = capacity(groupWidth), k = 0, // cardinality select, heap, reduceAdd, reduceRemove, reduceInitial, update = cr_null, reset = cr_null, resetNeeded = true, groupAll = key === cr_null, n0old; if (arguments.length < 1) key = cr_identity; // The group listens to the crossfilter for when any dimension changes, so // that it can update the associated reduce values. It must also listen to // the parent dimension for when data is added, and compute new keys. filterListeners.push(update); indexListeners.push(add); removeDataListeners.push(removeData); // Incorporate any existing data into the grouping. add(values, index, 0, n); // Incorporates the specified new values into this group. // This function is responsible for updating groups and groupIndex. function add(newValues, newIndex, n0, n1) { if(iterable) { n0old = n0; n0 = values.length - newValues.length; n1 = newValues.length; } var oldGroups = groups, reIndex = iterable ? [] : cr_index(k, groupCapacity), add = reduceAdd, remove = reduceRemove, initial = reduceInitial, k0 = k, // old cardinality i0 = 0, // index of old group i1 = 0, // index of new record j, // object id g0, // old group x0, // old key x1, // new key g, // group to add x; // key of group to add // If a reset is needed, we don't need to update the reduce values. if (resetNeeded) add = initial = cr_null; if (resetNeeded) remove = initial = cr_null; // Reset the new groups (k is a lower bound). // Also, make sure that groupIndex exists and is long enough. groups = new Array(k), k = 0; if(iterable){ groupIndex = k0 ? groupIndex : []; } else{ groupIndex = k0 > 1 ? xfilterArray.arrayLengthen(groupIndex, n) : cr_index(n, groupCapacity); } // Get the first old key (x0 of g0), if it exists. if (k0) x0 = (g0 = oldGroups[0]).key; // Find the first new key (x1), skipping NaN keys. while (i1 < n1 && !((x1 = key(newValues[i1])) >= x1)) ++i1; // While new keys remain… while (i1 < n1) { // Determine the lesser of the two current keys; new and old. // If there are no old keys remaining, then always add the new key. if (g0 && x0 <= x1) { g = g0, x = x0; // Record the new index of the old group. reIndex[i0] = k; // Retrieve the next old key. g0 = oldGroups[++i0]; if (g0) x0 = g0.key; } else { g = {key: x1, value: initial()}, x = x1; } // Add the lesser group. groups[k] = g; // Add any selected records belonging to the added group, while // advancing the new key and populating the associated group index. while (x1 <= x) { j = newIndex[i1] + (iterable ? n0old : n0); if(iterable){ if(groupIndex[j]){ groupIndex[j].push(k); } else{ groupIndex[j] = [k]; } } else{ groupIndex[j] = k; } // Always add new values to groups. Only remove when not in filter. // This gives groups full information on data life-cycle. g.value = add(g.value, data[j], true); if (!filters.zeroExcept(j, offset, zero)) g.value = remove(g.value, data[j], false); if (++i1 >= n1) break; x1 = key(newValues[i1]); } groupIncrement(); } // Add any remaining old groups that were greater th1an all new keys. // No incremental reduce is needed; these groups have no new records. // Also record the new index of the old group. while (i0 < k0) { groups[reIndex[i0] = k] = oldGroups[i0++]; groupIncrement(); } // Fill in gaps with empty arrays where there may have been rows with empty iterables if(iterable){ for (var index1 = 0; index1 < n; index1++) { if(!groupIndex[index1]){ groupIndex[index1] = []; } } } // If we added any new groups before any old groups, // update the group index of all the old records. if(k > i0){ if(iterable){ for (i0 = 0; i0 < n0old; ++i0) { for (index1 = 0; index1 < groupIndex[i0].length; index1++) { groupIndex[i0][index1] = reIndex[groupIndex[i0][index1]]; } } } else{ for (i0 = 0; i0 < n0; ++i0) { groupIndex[i0] = reIndex[groupIndex[i0]]; } } } // Modify the update and reset behavior based on the cardinality. // If the cardinality is less than or equal to one, then the groupIndex // is not needed. If the cardinality is zero, then there are no records // and therefore no groups to update or reset. Note that we also must // change the registered listener to point to the new method. j = filterListeners.indexOf(update); if (k > 1 || iterable) { update = updateMany; reset = resetMany; } else { if (!k && groupAll) { k = 1; groups = [{key: null, value: initial()}]; } if (k === 1) { update = updateOne; reset = resetOne; } else { update = cr_null; reset = cr_null; } groupIndex = null; } filterListeners[j] = update; // Count the number of added groups, // and widen the group index as needed. function groupIncrement() { if(iterable){ k++; return } if (++k === groupCapacity) { reIndex = xfilterArray.arrayWiden(reIndex, groupWidth <<= 1); groupIndex = xfilterArray.arrayWiden(groupIndex, groupWidth); groupCapacity = capacity(groupWidth); } } } function removeData(reIndex) { if (k > 1 || iterable) { var oldK = k, oldGroups = groups, seenGroups = cr_index(oldK, oldK), i, i0, j; // Filter out non-matches by copying matching group index entries to // the beginning of the array. if (!iterable) { for (i = 0, j = 0; i < n; ++i) { if (reIndex[i] !== REMOVED_INDEX) { seenGroups[groupIndex[j] = groupIndex[i]] = 1; ++j; } } } else { for (i = 0, j = 0; i < n; ++i) { if (reIndex[i] !== REMOVED_INDEX) { groupIndex[j] = groupIndex[i]; for (i0 = 0; i0 < groupIndex[j].length; i0++) { seenGroups[groupIndex[j][i0]] = 1; } ++j; } } } // Reassemble groups including only those groups that were referred // to by matching group index entries. Note the new group index in // seenGroups. groups = [], k = 0; for (i = 0; i < oldK; ++i) { if (seenGroups[i]) { seenGroups[i] = k++; groups.push(oldGroups[i]); } } if (k > 1 || iterable) { // Reindex the group index using seenGroups to find the new index. if (!iterable) { for (i = 0; i < j; ++i) groupIndex[i] = seenGroups[groupIndex[i]]; } else { for (i = 0; i < j; ++i) { for (i0 = 0; i0 < groupIndex[i].length; ++i0) { groupIndex[i][i0] = seenGroups[groupIndex[i][i0]]; } } } } else { groupIndex = null; } filterListeners[filterListeners.indexOf(update)] = k > 1 || iterable ? (reset = resetMany, update = updateMany) : k === 1 ? (reset = resetOne, update = updateOne) : reset = update = cr_null; } else if (k === 1) { if (groupAll) return; for (var index3 = 0; index3 < n; ++index3) if (reIndex[index3] !== REMOVED_INDEX) return; groups = [], k = 0; filterListeners[filterListeners.indexOf(update)] = update = reset = cr_null; } } // Reduces the specified selected or deselected records. // This function is only used when the cardinality is greater than 1. // notFilter indicates a crossfilter.add/remove operation. function updateMany(filterOne, filterOffset, added, removed, notFilter) { if ((filterOne === one && filterOffset === offset) || resetNeeded) return; var i, j, k, n, g; if(iterable){ // Add the added values. for (i = 0, n = added.length; i < n; ++i) { if (filters.zeroExcept(k = added[i], offset, zero)) { for (j = 0; j < groupIndex[k].length; j++) { g = groups[groupIndex[k][j]]; g.value = reduceAdd(g.value, data[k], false, j); } } } // Remove the removed values. for (i = 0, n = removed.length; i < n; ++i) { if (filters.onlyExcept(k = removed[i], offset, zero, filterOffset, filterOne)) { for (j = 0; j < groupIndex[k].length; j++) { g = groups[groupIndex[k][j]]; g.value = reduceRemove(g.value, data[k], notFilter, j); } } } return; } // Add the added values. for (i = 0, n = added.length; i < n; ++i) { if (filters.zeroExcept(k = added[i], offset, zero)) { g = groups[groupIndex[k]]; g.value = reduceAdd(g.value, data[k], false); } } // Remove the removed values. for (i = 0, n = removed.length; i < n; ++i) { if (filters.onlyExcept(k = removed[i], offset, zero, filterOffset, filterOne)) { g = groups[groupIndex[k]]; g.value = reduceRemove(g.value, data[k], notFilter); } } } // Reduces the specified selected or deselected records. // This function is only used when the cardinality is 1. // notFilter indicates a crossfilter.add/remove operation. function updateOne(filterOne, filterOffset, added, removed, notFilter) { if ((filterOne === one && filterOffset === offset) || resetNeeded) return; var i, k, n, g = groups[0]; // Add the added values. for (i = 0, n = added.length; i < n; ++i) { if (filters.zeroExcept(k = added[i], offset, zero)) { g.value = reduceAdd(g.value, data[k], false); } } // Remove the removed values. for (i = 0, n = removed.length; i < n; ++i) { if (filters.onlyExcept(k = removed[i], offset, zero, filterOffset, filterOne)) { g.value = reduceRemove(g.value, data[k], notFilter); } } } // Recomputes the group reduce values from scratch. // This function is only used when the cardinality is greater than 1. function resetMany() { var i, j, g; // Reset all group values. for (i = 0; i < k; ++i) { groups[i].value = reduceInitial(); } // We add all records and then remove filtered records so that reducers // can build an 'unfiltered' view even if there are already filters in // place on other dimensions. if(iterable){ for (i = 0; i < n; ++i) { for (j = 0; j < groupIndex[i].length; j++) { g = groups[groupIndex[i][j]]; g.value = reduceAdd(g.value, data[i], true, j); } } for (i = 0; i < n; ++i) { if (!filters.zeroExcept(i, offset, zero)) { for (j = 0; j < groupIndex[i].length; j++) { g = groups[groupIndex[i][j]]; g.value = reduceRemove(g.value, data[i], false, j); } } } return; } for (i = 0; i < n; ++i) { g = groups[groupIndex[i]]; g.value = reduceAdd(g.value, data[i], true); } for (i = 0; i < n; ++i) { if (!filters.zeroExcept(i, offset, zero)) { g = groups[groupIndex[i]]; g.value = reduceRemove(g.value, data[i], false); } } } // Recomputes the group reduce values from scratch. // This function is only used when the cardinality is 1. function resetOne() { var i, g = groups[0]; // Reset the singleton group values. g.value = reduceInitial(); // We add all records and then remove filtered records so that reducers // can build an 'unfiltered' view even if there are already filters in // place on other dimensions. for (i = 0; i < n; ++i) { g.value = reduceAdd(g.value, data[i], true); } for (i = 0; i < n; ++i) { if (!filters.zeroExcept(i, offset, zero)) { g.value = reduceRemove(g.value, data[i], false); } } } // Returns the array of group values, in the dimension's natural order. function all() { if (resetNeeded) reset(), resetNeeded = false; return groups; } // Returns a new array containing the top K group values, in reduce order. function top(k) { var top = select(all(), 0, groups.length, k); return heap.sort(top, 0, top.length); } // Sets the reduce behavior for this group to use the specified functions. // This method lazily recomputes the reduce values, waiting until needed. function reduce(add, remove, initial) { reduceAdd = add; reduceRemove = remove; reduceInitial = initial; resetNeeded = true; return group; } // A convenience method for reducing by count. function reduceCount() { return reduce(xfilterReduce.reduceIncrement, xfilterReduce.reduceDecrement, cr_zero); } // A convenience method for reducing by sum(value). function reduceSum(value) { return reduce(xfilterReduce.reduceAdd(value), xfilterReduce.reduceSubtract(value), cr_zero); } // Sets the reduce order, using the specified accessor. function order(value) { select = h$1.by(valueOf); heap = h.by(valueOf); function valueOf(d) { return value(d.value); } return group; } // A convenience method for natural ordering by reduce value. function orderNatural() { return order(cr_identity); } // Returns the cardinality of this group, irrespective of any filters. function size() { return k; } // Removes this group and associated event listeners. function dispose() { var i = filterListeners.indexOf(update); if (i >= 0) filterListeners.splice(i, 1); i = indexListeners.indexOf(add); if (i >= 0) indexListeners.splice(i, 1); i = removeDataListeners.indexOf(removeData); if (i >= 0) removeDataListeners.splice(i, 1); i = dimensionGroups.indexOf(group); if (i >= 0) dimensionGroups.splice(i, 1); return group; } return reduceCount().orderNatural(); } // A convenience function for generating a singleton group. function groupAll() { var g = group(cr_null), all = g.all; delete g.all; delete g.top; delete g.order; delete g.orderNatural; delete g.size; g.value = function() { return all()[0].value; }; return g; } // Removes this dimension and associated groups and event listeners. function dispose() { dimensionGroups.forEach(function(group) { group.dispose(); }); var i = dataListeners.indexOf(preAdd); if (i >= 0) dataListeners.splice(i, 1); i = dataListeners.indexOf(postAdd); if (i >= 0) dataListeners.splice(i, 1); i = removeDataListeners.indexOf(removeData); if (i >= 0) removeDataListeners.splice(i, 1); filters.masks[offset] &= zero; return filterAll(); } return dimension; } // A convenience method for groupAll on a dummy dimension. // This implementation can be optimized since it always has cardinality 1. function groupAll() { var group = { reduce: reduce, reduceCount: reduceCount, reduceSum: reduceSum, value: value, dispose: dispose, remove: dispose // for backwards-compatibility }; var reduceValue, reduceAdd, reduceRemove, reduceInitial, resetNeeded = true; // The group listens to the crossfilter for when any dimension changes, so // that it can update the reduce value. It must also listen to the parent // dimension for when data is added. filterListeners.push(update); dataListeners.push(add); // For consistency; actually a no-op since resetNeeded is true. add(data, 0); // Incorporates the specified new values into this group. function add(newData, n0) { var i; if (resetNeeded) return; // Cycle through all the values. for (i = n0; i < n; ++i) { // Add all values all the time. reduceValue = reduceAdd(reduceValue, data[i], true); // Remove the value if filtered. if (!filters.zero(i)) { reduceValue = reduceRemove(reduceValue, data[i], false); } } } // Reduces the specified selected or deselected records. function update(filterOne, filterOffset, added, removed, notFilter) { var i, k, n; if (resetNeeded) return; // Add the added values. for (i = 0, n = added.length; i < n; ++i) { if (filters.zero(k = added[i])) { reduceValue = reduceAdd(reduceValue, data[k], notFilter); } } // Remove the removed values. for (i = 0, n = removed.length; i < n; ++i) { if (filters.only(k = removed[i], filterOffset, filterOne)) { reduceValue = reduceRemove(reduceValue, data[k], notFilter); } } } // Recomputes the group reduce value from scratch. function reset() { var i; reduceValue = reduceInitial(); // Cycle through all the values. for (i = 0; i < n; ++i) { // Add all values all the time. reduceValue = reduceAdd(reduceValue, data[i], true); // Remove the value if it is filtered. if (!filters.zero(i)) { reduceValue = reduceRemove(reduceValue, data[i], false); } } } // Sets the reduce behavior for this group to use the specified functions. // This method lazily recomputes the reduce value, waiting until needed. function reduce(add, remove, initial) { reduceAdd = add; reduceRemove = remove; reduceInitial = initial; resetNeeded = true; return group; } // A convenience method for reducing by count. function reduceCount() { return reduce(xfilterReduce.reduceIncrement, xfilterReduce.reduceDecrement, cr_zero); } // A convenience method for reducing by sum(value). function reduceSum(value) { return reduce(xfilterReduce.reduceAdd(value), xfilterReduce.reduceSubtract(value), cr_zero); } // Returns the computed reduce value. function value() { if (resetNeeded) reset(), resetNeeded = false; return reduceValue; } // Removes this group and associated event listeners. function dispose() { var i = filterListeners.indexOf(update); if (i >= 0) filterListeners.splice(i, 1); i = dataListeners.indexOf(add); if (i >= 0) dataListeners.splice(i, 1); return group; } return reduceCount(); } // Returns the number of records in this crossfilter, irrespective of any filters. function size() { return n; } // Returns the raw row data contained in this crossfilter function all(){ return data; } // Returns row data with all dimension filters applied, except for filters in ignore_dimensions function allFiltered(ignore_dimensions) { var array = [], i = 0, mask = maskForDimensions(ignore_dimensions || []); for (i = 0; i < n; i++) { if (filters.zeroExceptMask(i, mask)) { array.push(data[i]); } } return array; } function onChange(cb){ if(typeof cb !== 'function'){ /* eslint no-console: 0 */ console.warn('onChange callback parameter must be a function!'); return; } callbacks.push(cb); return function(){ callbacks.splice(callbacks.indexOf(cb), 1); }; } function triggerOnChange(eventName){ for (var i = 0; i < callbacks.length; i++) { callbacks[i](eventName); } } return arguments.length ? add(arguments[0]) : crossfilter; } // Returns an array of size n, big enough to store ids up to m. function cr_index(n, m) { return (m < 0x101 ? xfilterArray.array8 : m < 0x10001 ? xfilterArray.array16 : xfilterArray.array32)(n); } // Constructs a new array of size n, with sequential values from 0 to n - 1. function cr_range(n) { var range = cr_index(n, n); for (var i = -1; ++i < n;) range[i] = i; return range; } function capacity(w) { return w === 8 ? 0x100 : w === 16 ? 0x10000 : 0x100000000; } var version = "1.5.2"; // Note(cg): exporting current version for umd build. crossfilter.version = version; return crossfilter; })));