├── .gitignore ├── Procfile ├── logotype.png ├── package.json ├── README.md ├── web.js ├── www ├── index.html └── d3.v2.min.js ├── crossfilter.v1.js └── crossfilter.v1.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node web.js -------------------------------------------------------------------------------- /logotype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZJONSSON/crossfilter/HEAD/logotype.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crossfilter-example", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "express": "3.0.x" 6 | }, 7 | "engines": { 8 | "node": "0.8.x", 9 | "npm": "1.1.x" 10 | } 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Forked example demonstrating Ajax updating 2 | 3 | Simple example using Crossfilter on node.js and serving the results through AJAX eliminating the need to run Crossfilter in the browser. -------------------------------------------------------------------------------- /web.js: -------------------------------------------------------------------------------- 1 | // Requires Express.js 2 | 3 | var fs = require("fs"), 4 | express=require("express"), 5 | crossfilter = require("./crossfilter.v1.min.js").crossfilter, 6 | app=express(); 7 | 8 | // Like d3.time.format, but faster. 9 | function parseDate(d) { 10 | return new Date(2001, 11 | d.substring(0, 2) - 1, 12 | d.substring(2, 4), 13 | d.substring(4, 6), 14 | d.substring(6, 8)); 15 | } 16 | 17 | // Read the CSV file into flights 18 | var flights = fs.readFileSync("flights-3m.json").toString().replace(/\r/g,"").split("\n"), 19 | header = flights[0].split(","); 20 | 21 | flights = flights.slice(1).map(function(d) { 22 | var line = {}; 23 | d.split(",").forEach(function(d,i) { 24 | line[header[i]] = d; 25 | }) 26 | return line; 27 | }) 28 | 29 | // A little coercion, since the CSV is untyped. 30 | flights.forEach(function(d, i) { 31 | d.index = i; 32 | d.date = parseDate(d.date); 33 | d.delay = +d.delay; 34 | d.distance = +d.distance; 35 | }); 36 | 37 | // Create the crossfilter for the relevant dimensions and groups. 38 | var flight = crossfilter(flights), 39 | dimensions = { 40 | date: flight.dimension(function(d) { var d = new Date(d.date); d.setHours(0,0,0,0); return d; }), 41 | hour: flight.dimension(function(d) { return d.date.getHours() + d.date.getMinutes() / 60; }), 42 | delay: flight.dimension(function(d) { return Math.max(-60, Math.min(149, d.delay)); }), 43 | distance: flight.dimension(function(d) { return Math.min(1999, d.distance); }) 44 | }, 45 | 46 | groups = { 47 | date : dimensions.date.group(), 48 | hour : dimensions.hour.group(Math.floor), 49 | delay : dimensions.delay.group(function(d) { return Math.floor(d / 10) * 10; }), 50 | distance : dimensions.distance.group(function(d) { return Math.floor(d / 50) * 50; }) 51 | }, 52 | 53 | size = flight.size(), 54 | all = flight.groupAll(); 55 | 56 | 57 | // Handle the AJAX requests 58 | app.use("/flights-3m",function(req,res,next) { 59 | filter = req.param("filter") ? JSON.parse(req.param("filter")) : {} 60 | // Loop through each dimension and check if user requested a filter 61 | Object.keys(dimensions).forEach(function(dim) { 62 | if (filter[dim]) { 63 | // In this example the only string variables in the filter are dates 64 | if (typeof filter[dim][0] === 'string') filter[dim]=[new Date(filter[dim][0]),new Date(filter[dim][1])] 65 | dimensions[dim].filterRange(filter[dim]) 66 | } else { 67 | dimensions[dim].filterAll() 68 | } 69 | }) 70 | 71 | // Assemble group results and and the maximum value for each group 72 | var results = {} 73 | Object.keys(groups).forEach(function(key) { 74 | results[key] = {values:groups[key].all(),top:groups[key].top(1)[0].value} 75 | }) 76 | 77 | // Send back as json 78 | res.writeHead(200, { 'content-type': 'application/json' }); 79 | res.end((JSON.stringify(results))) 80 | }) 81 | 82 | // Change this to the static directory of the index.html file 83 | app.use("/",express.static("./www/", {maxAge: 0})) 84 | 85 | var port = process.env.PORT || 5000; 86 | app.listen(port,function() { 87 | console.log("listening to port "+port) 88 | }) -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
191 |
192 |
193 |
194 |
486 |
--------------------------------------------------------------------------------
/crossfilter.v1.js:
--------------------------------------------------------------------------------
1 | (function(exports){
2 | crossfilter.version = "1.1.2";
3 | function crossfilter_identity(d) {
4 | return d;
5 | }
6 | crossfilter.permute = permute;
7 |
8 | function permute(array, index) {
9 | for (var i = 0, n = index.length, copy = new Array(n); i < n; ++i) {
10 | copy[i] = array[index[i]];
11 | }
12 | return copy;
13 | }
14 | var bisect = crossfilter.bisect = bisect_by(crossfilter_identity);
15 |
16 | bisect.by = bisect_by;
17 |
18 | function bisect_by(f) {
19 |
20 | // Locate the insertion point for x in a to maintain sorted order. The
21 | // arguments lo and hi may be used to specify a subset of the array which
22 | // should be considered; by default the entire array is used. If x is already
23 | // present in a, the insertion point will be before (to the left of) any
24 | // existing entries. The return value is suitable for use as the first
25 | // argument to `array.splice` assuming that a is already sorted.
26 | // Incomparable values such as NaN and undefined are assumed to be at the end
27 | // of the array.
28 | //
29 | // The returned insertion point i partitions the array a into two halves so
30 | // that all v < x for v in a[lo:i] for the left side and all v >= x for v in
31 | // a[i:hi] for the right side.
32 | function bisectLeft(a, x, lo, hi) {
33 | while (lo < hi) {
34 | var mid = lo + hi >>> 1,
35 | y = f(a[mid]);
36 | if (x <= y || !(y <= y)) hi = mid;
37 | else lo = mid + 1;
38 | }
39 | return lo;
40 | }
41 |
42 | // Similar to bisectLeft, but returns an insertion point which comes after (to
43 | // the right of) any existing entries of x in a.
44 | //
45 | // The returned insertion point i partitions the array into two halves so that
46 | // all v <= x for v in a[lo:i] for the left side and all v > x for v in
47 | // a[i:hi] for the right side.
48 | function bisectRight(a, x, lo, hi) {
49 | while (lo < hi) {
50 | var mid = lo + hi >>> 1,
51 | y = f(a[mid]);
52 | if (x < y || !(y <= y)) hi = mid;
53 | else lo = mid + 1;
54 | }
55 | return lo;
56 | }
57 |
58 | bisectRight.right = bisectRight;
59 | bisectRight.left = bisectLeft;
60 | return bisectRight;
61 | }
62 | var heap = crossfilter.heap = heap_by(crossfilter_identity);
63 |
64 | heap.by = heap_by;
65 |
66 | function heap_by(f) {
67 |
68 | // Builds a binary heap within the specified array a[lo:hi]. The heap has the
69 | // property such that the parent a[lo+i] is always less than or equal to its
70 | // two children: a[lo+2*i+1] and a[lo+2*i+2].
71 | function heap(a, lo, hi) {
72 | var n = hi - lo,
73 | i = (n >>> 1) + 1;
74 | while (--i > 0) sift(a, i, n, lo);
75 | return a;
76 | }
77 |
78 | // Sorts the specified array a[lo:hi] in descending order, assuming it is
79 | // already a heap.
80 | function sort(a, lo, hi) {
81 | var n = hi - lo,
82 | t;
83 | while (--n > 0) t = a[lo], a[lo] = a[lo + n], a[lo + n] = t, sift(a, 1, n, lo);
84 | return a;
85 | }
86 |
87 | // Sifts the element a[lo+i-1] down the heap, where the heap is the contiguous
88 | // slice of array a[lo:lo+n]. This method can also be used to update the heap
89 | // incrementally, without incurring the full cost of reconstructing the heap.
90 | function sift(a, i, n, lo) {
91 | var d = a[--lo + i],
92 | x = f(d),
93 | child;
94 | while ((child = i << 1) <= n) {
95 | if (child < n && f(a[lo + child]) > f(a[lo + child + 1])) child++;
96 | if (x <= f(a[lo + child])) break;
97 | a[lo + i] = a[lo + child];
98 | i = child;
99 | }
100 | a[lo + i] = d;
101 | }
102 |
103 | heap.sort = sort;
104 | return heap;
105 | }
106 | var heapselect = crossfilter.heapselect = heapselect_by(crossfilter_identity);
107 |
108 | heapselect.by = heapselect_by;
109 |
110 | function heapselect_by(f) {
111 | var heap = heap_by(f);
112 |
113 | // Returns a new array containing the top k elements in the array a[lo:hi].
114 | // The returned array is not sorted, but maintains the heap property. If k is
115 | // greater than hi - lo, then fewer than k elements will be returned. The
116 | // order of elements in a is unchanged by this operation.
117 | function heapselect(a, lo, hi, k) {
118 | var queue = new Array(k = Math.min(hi - lo, k)),
119 | min,
120 | i,
121 | x,
122 | d;
123 |
124 | for (i = 0; i < k; ++i) queue[i] = a[lo++];
125 | heap(queue, 0, k);
126 |
127 | if (lo < hi) {
128 | min = f(queue[0]);
129 | do {
130 | if (x = f(d = a[lo]) > min) {
131 | queue[0] = d;
132 | min = f(heap(queue, 0, k)[0]);
133 | }
134 | } while (++lo < hi);
135 | }
136 |
137 | return queue;
138 | }
139 |
140 | return heapselect;
141 | }
142 | var insertionsort = crossfilter.insertionsort = insertionsort_by(crossfilter_identity);
143 |
144 | insertionsort.by = insertionsort_by;
145 |
146 | function insertionsort_by(f) {
147 |
148 | function insertionsort(a, lo, hi) {
149 | for (var i = lo + 1; i < hi; ++i) {
150 | for (var j = i, t = a[i], x = f(t), y; j > lo && ((y = f(a[j - 1])) > x || !(y <= y)); --j) {
151 | a[j] = a[j - 1];
152 | }
153 | a[j] = t;
154 | }
155 | return a;
156 | }
157 |
158 | return insertionsort;
159 | }
160 | // Algorithm designed by Vladimir Yaroslavskiy.
161 | // Implementation based on the Dart project; see lib/dart/LICENSE for details.
162 |
163 | var quicksort = crossfilter.quicksort = quicksort_by(crossfilter_identity);
164 |
165 | quicksort.by = quicksort_by;
166 |
167 | function quicksort_by(f) {
168 | var insertionsort = insertionsort_by(f);
169 |
170 | function sort(a, lo, hi) {
171 | return (hi - lo < quicksort_sizeThreshold
172 | ? insertionsort
173 | : quicksort)(a, lo, hi);
174 | }
175 |
176 | function quicksort(a, lo, hi) {
177 | // First move NaN and undefined to the end.
178 | var x, y;
179 | while (lo < hi && !(x = f(a[hi - 1]), x <= x)) hi--;
180 | for (var i = hi; --i >= lo; ) {
181 | x = f(y = a[i]);
182 | if (!(x <= x)) {
183 | a[i] = a[--hi];
184 | a[hi] = y;
185 | }
186 | }
187 |
188 | // Compute the two pivots by looking at 5 elements.
189 | var sixth = (hi - lo) / 6 | 0,
190 | i1 = lo + sixth,
191 | i5 = hi - 1 - sixth,
192 | i3 = lo + hi - 1 >> 1, // The midpoint.
193 | i2 = i3 - sixth,
194 | i4 = i3 + sixth;
195 |
196 | var e1 = a[i1], x1 = f(e1),
197 | e2 = a[i2], x2 = f(e2),
198 | e3 = a[i3], x3 = f(e3),
199 | e4 = a[i4], x4 = f(e4),
200 | e5 = a[i5], x5 = f(e5);
201 |
202 | var t;
203 |
204 | // Sort the selected 5 elements using a sorting network.
205 | if (x1 > x2) t = e1, e1 = e2, e2 = t, t = x1, x1 = x2, x2 = t;
206 | if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
207 | if (x1 > x3) t = e1, e1 = e3, e3 = t, t = x1, x1 = x3, x3 = t;
208 | if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
209 | if (x1 > x4) t = e1, e1 = e4, e4 = t, t = x1, x1 = x4, x4 = t;
210 | if (x3 > x4) t = e3, e3 = e4, e4 = t, t = x3, x3 = x4, x4 = t;
211 | if (x2 > x5) t = e2, e2 = e5, e5 = t, t = x2, x2 = x5, x5 = t;
212 | if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
213 | if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
214 |
215 | var pivot1 = e2, pivotValue1 = x2,
216 | pivot2 = e4, pivotValue2 = x4;
217 |
218 | // e2 and e4 have been saved in the pivot variables. They will be written
219 | // back, once the partitioning is finished.
220 | a[i1] = e1;
221 | a[i2] = a[lo];
222 | a[i3] = e3;
223 | a[i4] = a[hi - 1];
224 | a[i5] = e5;
225 |
226 | var less = lo + 1, // First element in the middle partition.
227 | great = hi - 2; // Last element in the middle partition.
228 |
229 | // Note that for value comparison, <, <=, >= and > coerce to a primitive via
230 | // Object.prototype.valueOf; == and === do not, so in order to be consistent
231 | // with natural order (such as for Date objects), we must do two compares.
232 | var pivotsEqual = pivotValue1 <= pivotValue2 && pivotValue1 >= pivotValue2;
233 | if (pivotsEqual) {
234 |
235 | // Degenerated case where the partitioning becomes a dutch national flag
236 | // problem.
237 | //
238 | // [ | < pivot | == pivot | unpartitioned | > pivot | ]
239 | // ^ ^ ^ ^ ^
240 | // left less k great right
241 | //
242 | // a[left] and a[right] are undefined and are filled after the
243 | // partitioning.
244 | //
245 | // Invariants:
246 | // 1) for x in ]left, less[ : x < pivot.
247 | // 2) for x in [less, k[ : x == pivot.
248 | // 3) for x in ]great, right[ : x > pivot.
249 | for (var k = less; k <= great; ++k) {
250 | var ek = a[k], xk = f(ek);
251 | if (xk < pivotValue1) {
252 | if (k !== less) {
253 | a[k] = a[less];
254 | a[less] = ek;
255 | }
256 | ++less;
257 | } else if (xk > pivotValue1) {
258 |
259 | // Find the first element <= pivot in the range [k - 1, great] and
260 | // put [:ek:] there. We know that such an element must exist:
261 | // When k == less, then el3 (which is equal to pivot) lies in the
262 | // interval. Otherwise a[k - 1] == pivot and the search stops at k-1.
263 | // Note that in the latter case invariant 2 will be violated for a
264 | // short amount of time. The invariant will be restored when the
265 | // pivots are put into their final positions.
266 | while (true) {
267 | var greatValue = f(a[great]);
268 | if (greatValue > pivotValue1) {
269 | great--;
270 | // This is the only location in the while-loop where a new
271 | // iteration is started.
272 | continue;
273 | } else if (greatValue < pivotValue1) {
274 | // Triple exchange.
275 | a[k] = a[less];
276 | a[less++] = a[great];
277 | a[great--] = ek;
278 | break;
279 | } else {
280 | a[k] = a[great];
281 | a[great--] = ek;
282 | // Note: if great < k then we will exit the outer loop and fix
283 | // invariant 2 (which we just violated).
284 | break;
285 | }
286 | }
287 | }
288 | }
289 | } else {
290 |
291 | // We partition the list into three parts:
292 | // 1. < pivot1
293 | // 2. >= pivot1 && <= pivot2
294 | // 3. > pivot2
295 | //
296 | // During the loop we have:
297 | // [ | < pivot1 | >= pivot1 && <= pivot2 | unpartitioned | > pivot2 | ]
298 | // ^ ^ ^ ^ ^
299 | // left less k great right
300 | //
301 | // a[left] and a[right] are undefined and are filled after the
302 | // partitioning.
303 | //
304 | // Invariants:
305 | // 1. for x in ]left, less[ : x < pivot1
306 | // 2. for x in [less, k[ : pivot1 <= x && x <= pivot2
307 | // 3. for x in ]great, right[ : x > pivot2
308 | for (var k = less; k <= great; k++) {
309 | var ek = a[k], xk = f(ek);
310 | if (xk < pivotValue1) {
311 | if (k !== less) {
312 | a[k] = a[less];
313 | a[less] = ek;
314 | }
315 | ++less;
316 | } else {
317 | if (xk > pivotValue2) {
318 | while (true) {
319 | var greatValue = f(a[great]);
320 | if (greatValue > pivotValue2) {
321 | great--;
322 | if (great < k) break;
323 | // This is the only location inside the loop where a new
324 | // iteration is started.
325 | continue;
326 | } else {
327 | // a[great] <= pivot2.
328 | if (greatValue < pivotValue1) {
329 | // Triple exchange.
330 | a[k] = a[less];
331 | a[less++] = a[great];
332 | a[great--] = ek;
333 | } else {
334 | // a[great] >= pivot1.
335 | a[k] = a[great];
336 | a[great--] = ek;
337 | }
338 | break;
339 | }
340 | }
341 | }
342 | }
343 | }
344 | }
345 |
346 | // Move pivots into their final positions.
347 | // We shrunk the list from both sides (a[left] and a[right] have
348 | // meaningless values in them) and now we move elements from the first
349 | // and third partition into these locations so that we can store the
350 | // pivots.
351 | a[lo] = a[less - 1];
352 | a[less - 1] = pivot1;
353 | a[hi - 1] = a[great + 1];
354 | a[great + 1] = pivot2;
355 |
356 | // The list is now partitioned into three partitions:
357 | // [ < pivot1 | >= pivot1 && <= pivot2 | > pivot2 ]
358 | // ^ ^ ^ ^
359 | // left less great right
360 |
361 | // Recursive descent. (Don't include the pivot values.)
362 | sort(a, lo, less - 1);
363 | sort(a, great + 2, hi);
364 |
365 | if (pivotsEqual) {
366 | // All elements in the second partition are equal to the pivot. No
367 | // need to sort them.
368 | return a;
369 | }
370 |
371 | // In theory it should be enough to call _doSort recursively on the second
372 | // partition.
373 | // The Android source however removes the pivot elements from the recursive
374 | // call if the second partition is too large (more than 2/3 of the list).
375 | if (less < i1 && great > i5) {
376 | var lessValue, greatValue;
377 | while ((lessValue = f(a[less])) <= pivotValue1 && lessValue >= pivotValue1) ++less;
378 | while ((greatValue = f(a[great])) <= pivotValue2 && greatValue >= pivotValue2) --great;
379 |
380 | // Copy paste of the previous 3-way partitioning with adaptions.
381 | //
382 | // We partition the list into three parts:
383 | // 1. == pivot1
384 | // 2. > pivot1 && < pivot2
385 | // 3. == pivot2
386 | //
387 | // During the loop we have:
388 | // [ == pivot1 | > pivot1 && < pivot2 | unpartitioned | == pivot2 ]
389 | // ^ ^ ^
390 | // less k great
391 | //
392 | // Invariants:
393 | // 1. for x in [ *, less[ : x == pivot1
394 | // 2. for x in [less, k[ : pivot1 < x && x < pivot2
395 | // 3. for x in ]great, * ] : x == pivot2
396 | for (var k = less; k <= great; k++) {
397 | var ek = a[k], xk = f(ek);
398 | if (xk <= pivotValue1 && xk >= pivotValue1) {
399 | if (k !== less) {
400 | a[k] = a[less];
401 | a[less] = ek;
402 | }
403 | less++;
404 | } else {
405 | if (xk <= pivotValue2 && xk >= pivotValue2) {
406 | while (true) {
407 | var greatValue = f(a[great]);
408 | if (greatValue <= pivotValue2 && greatValue >= pivotValue2) {
409 | great--;
410 | if (great < k) break;
411 | // This is the only location inside the loop where a new
412 | // iteration is started.
413 | continue;
414 | } else {
415 | // a[great] < pivot2.
416 | if (greatValue < pivotValue1) {
417 | // Triple exchange.
418 | a[k] = a[less];
419 | a[less++] = a[great];
420 | a[great--] = ek;
421 | } else {
422 | // a[great] == pivot1.
423 | a[k] = a[great];
424 | a[great--] = ek;
425 | }
426 | break;
427 | }
428 | }
429 | }
430 | }
431 | }
432 | }
433 |
434 | // The second partition has now been cleared of pivot elements and looks
435 | // as follows:
436 | // [ * | > pivot1 && < pivot2 | * ]
437 | // ^ ^
438 | // less great
439 | // Sort the second partition using recursive descent.
440 |
441 | // The second partition looks as follows:
442 | // [ * | >= pivot1 && <= pivot2 | * ]
443 | // ^ ^
444 | // less great
445 | // Simply sort it by recursive descent.
446 |
447 | return sort(a, less, great + 1);
448 | }
449 |
450 | return sort;
451 | }
452 |
453 | var quicksort_sizeThreshold = 32;
454 | var crossfilter_array8 = crossfilter_arrayUntyped,
455 | crossfilter_array16 = crossfilter_arrayUntyped,
456 | crossfilter_array32 = crossfilter_arrayUntyped,
457 | crossfilter_arrayLengthen = crossfilter_identity,
458 | crossfilter_arrayWiden = crossfilter_identity;
459 |
460 | if (typeof Uint8Array !== "undefined") {
461 | crossfilter_array8 = function(n) { return new Uint8Array(n); };
462 | crossfilter_array16 = function(n) { return new Uint16Array(n); };
463 | crossfilter_array32 = function(n) { return new Uint32Array(n); };
464 |
465 | crossfilter_arrayLengthen = function(array, length) {
466 | var copy = new array.constructor(length);
467 | copy.set(array);
468 | return copy;
469 | };
470 |
471 | crossfilter_arrayWiden = function(array, width) {
472 | var copy;
473 | switch (width) {
474 | case 16: copy = crossfilter_array16(array.length); break;
475 | case 32: copy = crossfilter_array32(array.length); break;
476 | default: throw new Error("invalid array width!");
477 | }
478 | copy.set(array);
479 | return copy;
480 | };
481 | }
482 |
483 | function crossfilter_arrayUntyped(n) {
484 | return new Array(n);
485 | }
486 | function crossfilter_filterExact(bisect, value) {
487 | return function(values) {
488 | var n = values.length;
489 | return [bisect.left(values, value, 0, n), bisect.right(values, value, 0, n)];
490 | };
491 | }
492 |
493 | function crossfilter_filterRange(bisect, range) {
494 | var min = range[0],
495 | max = range[1];
496 | return function(values) {
497 | var n = values.length;
498 | return [bisect.left(values, min, 0, n), bisect.left(values, max, 0, n)];
499 | };
500 | }
501 |
502 | function crossfilter_filterAll(values) {
503 | return [0, values.length];
504 | }
505 | function crossfilter_null() {
506 | return null;
507 | }
508 | function crossfilter_zero() {
509 | return 0;
510 | }
511 | function crossfilter_reduceIncrement(p) {
512 | return p + 1;
513 | }
514 |
515 | function crossfilter_reduceDecrement(p) {
516 | return p - 1;
517 | }
518 |
519 | function crossfilter_reduceAdd(f) {
520 | return function(p, v) {
521 | return p + +f(v);
522 | };
523 | }
524 |
525 | function crossfilter_reduceSubtract(f) {
526 | return function(p, v) {
527 | return p - f(v);
528 | };
529 | }
530 | exports.crossfilter = crossfilter;
531 |
532 | function crossfilter() {
533 | var crossfilter = {
534 | add: add,
535 | dimension: dimension,
536 | groupAll: groupAll,
537 | size: size
538 | };
539 |
540 | var data = [], // the records
541 | n = 0, // the number of records; data.length
542 | m = 0, // number of dimensions in use
543 | M = 8, // number of dimensions that can fit in `filters`
544 | filters = crossfilter_array8(0), // M bits per record; 1 is filtered out
545 | filterListeners = [], // when the filters change
546 | dataListeners = []; // when data is added
547 |
548 | // Adds the specified new records to this crossfilter.
549 | function add(newData) {
550 | var n0 = n,
551 | n1 = newData.length;
552 |
553 | // If there's actually new data to add…
554 | // Merge the new data into the existing data.
555 | // Lengthen the filter bitset to handle the new records.
556 | // Notify listeners (dimensions and groups) that new data is available.
557 | if (n1) {
558 | data = data.concat(newData);
559 | filters = crossfilter_arrayLengthen(filters, n += n1);
560 | dataListeners.forEach(function(l) { l(newData, n0, n1); });
561 | }
562 |
563 | return crossfilter;
564 | }
565 |
566 | // Adds a new dimension with the specified value accessor function.
567 | function dimension(value) {
568 | var dimension = {
569 | filter: filter,
570 | filterExact: filterExact,
571 | filterRange: filterRange,
572 | filterAll: filterAll,
573 | top: top,
574 | bottom: bottom,
575 | group: group,
576 | groupAll: groupAll
577 | };
578 |
579 | var one = 1 << m++, // bit mask, e.g., 00001000
580 | zero = ~one, // inverted one, e.g., 11110111
581 | values, // sorted, cached array
582 | index, // value rank ↦ object id
583 | newValues, // temporary array storing newly-added values
584 | newIndex, // temporary array storing newly-added index
585 | sort = quicksort_by(function(i) { return newValues[i]; }),
586 | refilter = crossfilter_filterAll, // for recomputing filter
587 | indexListeners = [], // when data is added
588 | lo0 = 0,
589 | hi0 = 0;
590 |
591 | // Updating a dimension is a two-stage process. First, we must update the
592 | // associated filters for the newly-added records. Once all dimensions have
593 | // updated their filters, the groups are notified to update.
594 | dataListeners.unshift(preAdd);
595 | dataListeners.push(postAdd);
596 |
597 | // Incorporate any existing data into this dimension, and make sure that the
598 | // filter bitset is wide enough to handle the new dimension.
599 | if (m > M) filters = crossfilter_arrayWiden(filters, M <<= 1);
600 | preAdd(data, 0, n);
601 | postAdd(data, 0, n);
602 |
603 | // Incorporates the specified new records into this dimension.
604 | // This function is responsible for updating filters, values, and index.
605 | function preAdd(newData, n0, n1) {
606 |
607 | // Permute new values into natural order using a sorted index.
608 | newValues = newData.map(value);
609 | newIndex = sort(crossfilter_range(n1), 0, n1);
610 | newValues = permute(newValues, newIndex);
611 |
612 | // Bisect newValues to determine which new records are selected.
613 | var bounds = refilter(newValues), lo1 = bounds[0], hi1 = bounds[1], i;
614 | for (i = 0; i < lo1; ++i) filters[newIndex[i] + n0] |= one;
615 | for (i = hi1; i < n1; ++i) filters[newIndex[i] + n0] |= one;
616 |
617 | // If this dimension previously had no data, then we don't need to do the
618 | // more expensive merge operation; use the new values and index as-is.
619 | if (!n0) {
620 | values = newValues;
621 | index = newIndex;
622 | lo0 = lo1;
623 | hi0 = hi1;
624 | return;
625 | }
626 |
627 | var oldValues = values,
628 | oldIndex = index,
629 | i0 = 0,
630 | i1 = 0;
631 |
632 | // Otherwise, create new arrays into which to merge new and old.
633 | values = new Array(n);
634 | index = crossfilter_index(n, n);
635 |
636 | // Merge the old and new sorted values, and old and new index.
637 | for (i = 0; i0 < n0 && i1 < n1; ++i) {
638 | if (oldValues[i0] < newValues[i1]) {
639 | values[i] = oldValues[i0];
640 | index[i] = oldIndex[i0++];
641 | } else {
642 | values[i] = newValues[i1];
643 | index[i] = newIndex[i1++] + n0;
644 | }
645 | }
646 |
647 | // Add any remaining old values.
648 | for (; i0 < n0; ++i0, ++i) {
649 | values[i] = oldValues[i0];
650 | index[i] = oldIndex[i0];
651 | }
652 |
653 | // Add any remaining new values.
654 | for (; i1 < n1; ++i1, ++i) {
655 | values[i] = newValues[i1];
656 | index[i] = newIndex[i1] + n0;
657 | }
658 |
659 | // Bisect again to recompute lo0 and hi0.
660 | bounds = refilter(values), lo0 = bounds[0], hi0 = bounds[1];
661 | }
662 |
663 | // When all filters have updated, notify index listeners of the new values.
664 | function postAdd(newData, n0, n1) {
665 | indexListeners.forEach(function(l) { l(newValues, newIndex, n0, n1); });
666 | newValues = newIndex = null;
667 | }
668 |
669 | // Updates the selected values based on the specified bounds [lo, hi].
670 | // This implementation is used by all the public filter methods.
671 | function filterIndex(bounds) {
672 | var i,
673 | j,
674 | k,
675 | lo1 = bounds[0],
676 | hi1 = bounds[1],
677 | added = [],
678 | removed = [];
679 |
680 | // Fast incremental update based on previous lo index.
681 | if (lo1 < lo0) {
682 | for (i = lo1, j = Math.min(lo0, hi1); i < j; ++i) {
683 | filters[k = index[i]] ^= one;
684 | added.push(k);
685 | }
686 | } else if (lo1 > lo0) {
687 | for (i = lo0, j = Math.min(lo1, hi0); i < j; ++i) {
688 | filters[k = index[i]] ^= one;
689 | removed.push(k);
690 | }
691 | }
692 |
693 | // Fast incremental update based on previous hi index.
694 | if (hi1 > hi0) {
695 | for (i = Math.max(lo1, hi0), j = hi1; i < j; ++i) {
696 | filters[k = index[i]] ^= one;
697 | added.push(k);
698 | }
699 | } else if (hi1 < hi0) {
700 | for (i = Math.max(lo0, hi1), j = hi0; i < j; ++i) {
701 | filters[k = index[i]] ^= one;
702 | removed.push(k);
703 | }
704 | }
705 |
706 | lo0 = lo1;
707 | hi0 = hi1;
708 | filterListeners.forEach(function(l) { l(one, added, removed); });
709 | return dimension;
710 | }
711 |
712 | // Filters this dimension using the specified range, value, or null.
713 | // If the range is null, this is equivalent to filterAll.
714 | // If the range is an array, this is equivalent to filterRange.
715 | // Otherwise, this is equivalent to filterExact.
716 | function filter(range) {
717 | return range == null
718 | ? filterAll() : Array.isArray(range)
719 | ? filterRange(range)
720 | : filterExact(range);
721 | }
722 |
723 | // Filters this dimension to select the exact value.
724 | function filterExact(value) {
725 | return filterIndex((refilter = crossfilter_filterExact(bisect, value))(values));
726 | }
727 |
728 | // Filters this dimension to select the specified range [lo, hi].
729 | // The lower bound is inclusive, and the upper bound is exclusive.
730 | function filterRange(range) {
731 | return filterIndex((refilter = crossfilter_filterRange(bisect, range))(values));
732 | }
733 |
734 | // Clears any filters on this dimension.
735 | function filterAll() {
736 | return filterIndex((refilter = crossfilter_filterAll)(values));
737 | }
738 |
739 | // Returns the top K selected records based on this dimension's order.
740 | // Note: observes this dimension's filter, unlike group and groupAll.
741 | function top(k) {
742 | var array = [],
743 | i = hi0,
744 | j;
745 |
746 | while (--i >= lo0 && k > 0) {
747 | if (!filters[j = index[i]]) {
748 | array.push(data[j]);
749 | --k;
750 | }
751 | }
752 |
753 | return array;
754 | }
755 |
756 | // Returns the bottom K selected records based on this dimension's order.
757 | // Note: observes this dimension's filter, unlike group and groupAll.
758 | function bottom(k) {
759 | var array = [],
760 | i = lo0,
761 | j;
762 |
763 | while (i < hi0 && k > 0) {
764 | if (!filters[j = index[i]]) {
765 | array.push(data[j]);
766 | --k;
767 | }
768 | i++;
769 | }
770 |
771 | return array;
772 | }
773 |
774 | // Adds a new group to this dimension, using the specified key function.
775 | function group(key) {
776 | var group = {
777 | top: top,
778 | all: all,
779 | reduce: reduce,
780 | reduceCount: reduceCount,
781 | reduceSum: reduceSum,
782 | order: order,
783 | orderNatural: orderNatural,
784 | size: size
785 | };
786 |
787 | var groups, // array of {key, value}
788 | groupIndex, // object id ↦ group id
789 | groupWidth = 8,
790 | groupCapacity = crossfilter_capacity(groupWidth),
791 | k = 0, // cardinality
792 | select,
793 | heap,
794 | reduceAdd,
795 | reduceRemove,
796 | reduceInitial,
797 | update = crossfilter_null,
798 | reset = crossfilter_null,
799 | resetNeeded = true;
800 |
801 | if (arguments.length < 1) key = crossfilter_identity;
802 |
803 | // The group listens to the crossfilter for when any dimension changes, so
804 | // that it can update the associated reduce values. It must also listen to
805 | // the parent dimension for when data is added, and compute new keys.
806 | filterListeners.push(update);
807 | indexListeners.push(add);
808 |
809 | // Incorporate any existing data into the grouping.
810 | add(values, index, 0, n);
811 |
812 | // Incorporates the specified new values into this group.
813 | // This function is responsible for updating groups and groupIndex.
814 | function add(newValues, newIndex, n0, n1) {
815 | var oldGroups = groups,
816 | reIndex = crossfilter_index(k, groupCapacity),
817 | add = reduceAdd,
818 | initial = reduceInitial,
819 | k0 = k, // old cardinality
820 | i0 = 0, // index of old group
821 | i1 = 0, // index of new record
822 | j, // object id
823 | g0, // old group
824 | x0, // old key
825 | x1, // new key
826 | g, // group to add
827 | x; // key of group to add
828 |
829 | // If a reset is needed, we don't need to update the reduce values.
830 | if (resetNeeded) add = initial = crossfilter_null;
831 |
832 | // Reset the new groups (k is a lower bound).
833 | // Also, make sure that groupIndex exists and is long enough.
834 | groups = new Array(k), k = 0;
835 | groupIndex = k0 > 1 ? crossfilter_arrayLengthen(groupIndex, n) : crossfilter_index(n, groupCapacity);
836 |
837 | // Get the first old key (x0 of g0), if it exists.
838 | if (k0) x0 = (g0 = oldGroups[0]).key;
839 |
840 | // Find the first new key (x1).
841 | x1 = key(newValues[i1]);
842 |
843 | // While new keys remain…
844 | while (i1 < n1) {
845 |
846 | // Determine the lesser of the two current keys; new and old.
847 | // If there are no old keys remaining, then always add the new key.
848 | if (g0 && x0 <= x1) {
849 | g = g0, x = x0;
850 |
851 | // Record the new index of the old group.
852 | reIndex[i0] = k;
853 |
854 | // Retrieve the next old key.
855 | if (g0 = oldGroups[++i0]) x0 = g0.key;
856 | } else {
857 | g = {key: x1, value: initial()}, x = x1;
858 | }
859 |
860 | // Add the lesser group.
861 | groups[k] = g;
862 |
863 | // Add any selected records belonging to the added group, while
864 | // advancing the new key and populating the associated group index.
865 | while (x1 <= x || !(x1 <= x1) && !(x <= x)) {
866 | groupIndex[j = newIndex[i1] + n0] = k;
867 | if (!(filters[j] & zero)) g.value = add(g.value, data[j]);
868 | if (++i1 >= n1) break;
869 | x1 = key(newValues[i1]);
870 | }
871 |
872 | groupIncrement();
873 | }
874 |
875 | // Add any remaining old groups that were greater than all new keys.
876 | // No incremental reduce is needed; these groups have no new records.
877 | // Also record the new index of the old group.
878 | while (i0 < k0) {
879 | groups[reIndex[i0] = k] = oldGroups[i0++];
880 | groupIncrement();
881 | }
882 |
883 | // If we added any new groups before any old groups,
884 | // update the group index of all the old records.
885 | if (k > i0) for (i0 = 0; i0 < n0; ++i0) {
886 | groupIndex[i0] = reIndex[groupIndex[i0]];
887 | }
888 |
889 | // Modify the update and reset behavior based on the cardinality.
890 | // If the cardinality is less than or equal to one, then the groupIndex
891 | // is not needed. If the cardinality is zero, then there are no records
892 | // and therefore no groups to update or reset. Note that we also must
893 | // change the registered listener to point to the new method.
894 | j = filterListeners.indexOf(update);
895 | if (k > 1) {
896 | update = updateMany;
897 | reset = resetMany;
898 | } else {
899 | if (k === 1) {
900 | update = updateOne;
901 | reset = resetOne;
902 | } else {
903 | update = crossfilter_null;
904 | reset = crossfilter_null;
905 | }
906 | groupIndex = null;
907 | }
908 | filterListeners[j] = update;
909 |
910 | // Count the number of added groups,
911 | // and widen the group index as needed.
912 | function groupIncrement() {
913 | if (++k === groupCapacity) {
914 | reIndex = crossfilter_arrayWiden(reIndex, groupWidth <<= 1);
915 | groupIndex = crossfilter_arrayWiden(groupIndex, groupWidth);
916 | groupCapacity = crossfilter_capacity(groupWidth);
917 | }
918 | }
919 | }
920 |
921 | // Reduces the specified selected or deselected records.
922 | // This function is only used when the cardinality is greater than 1.
923 | function updateMany(filterOne, added, removed) {
924 | if (filterOne === one || resetNeeded) return;
925 |
926 | var i,
927 | k,
928 | n,
929 | g;
930 |
931 | // Add the added values.
932 | for (i = 0, n = added.length; i < n; ++i) {
933 | if (!(filters[k = added[i]] & zero)) {
934 | g = groups[groupIndex[k]];
935 | g.value = reduceAdd(g.value, data[k]);
936 | }
937 | }
938 |
939 | // Remove the removed values.
940 | for (i = 0, n = removed.length; i < n; ++i) {
941 | if ((filters[k = removed[i]] & zero) === filterOne) {
942 | g = groups[groupIndex[k]];
943 | g.value = reduceRemove(g.value, data[k]);
944 | }
945 | }
946 | }
947 |
948 | // Reduces the specified selected or deselected records.
949 | // This function is only used when the cardinality is 1.
950 | function updateOne(filterOne, added, removed) {
951 | if (filterOne === one || resetNeeded) return;
952 |
953 | var i,
954 | k,
955 | n,
956 | g = groups[0];
957 |
958 | // Add the added values.
959 | for (i = 0, n = added.length; i < n; ++i) {
960 | if (!(filters[k = added[i]] & zero)) {
961 | g.value = reduceAdd(g.value, data[k]);
962 | }
963 | }
964 |
965 | // Remove the removed values.
966 | for (i = 0, n = removed.length; i < n; ++i) {
967 | if ((filters[k = removed[i]] & zero) === filterOne) {
968 | g.value = reduceRemove(g.value, data[k]);
969 | }
970 | }
971 | }
972 |
973 | // Recomputes the group reduce values from scratch.
974 | // This function is only used when the cardinality is greater than 1.
975 | function resetMany() {
976 | var i,
977 | g;
978 |
979 | // Reset all group values.
980 | for (i = 0; i < k; ++i) {
981 | groups[i].value = reduceInitial();
982 | }
983 |
984 | // Add any selected records.
985 | for (i = 0; i < n; ++i) {
986 | if (!(filters[i] & zero)) {
987 | g = groups[groupIndex[i]];
988 | g.value = reduceAdd(g.value, data[i]);
989 | }
990 | }
991 | }
992 |
993 | // Recomputes the group reduce values from scratch.
994 | // This function is only used when the cardinality is 1.
995 | function resetOne() {
996 | var i,
997 | g = groups[0];
998 |
999 | // Reset the singleton group values.
1000 | g.value = reduceInitial();
1001 |
1002 | // Add any selected records.
1003 | for (i = 0; i < n; ++i) {
1004 | if (!(filters[i] & zero)) {
1005 | g.value = reduceAdd(g.value, data[i]);
1006 | }
1007 | }
1008 | }
1009 |
1010 | // Returns the array of group values, in the dimension's natural order.
1011 | function all() {
1012 | if (resetNeeded) reset(), resetNeeded = false;
1013 | return groups;
1014 | }
1015 |
1016 | // Returns a new array containing the top K group values, in reduce order.
1017 | function top(k) {
1018 | var top = select(all(), 0, groups.length, k);
1019 | return heap.sort(top, 0, top.length);
1020 | }
1021 |
1022 | // Sets the reduce behavior for this group to use the specified functions.
1023 | // This method lazily recomputes the reduce values, waiting until needed.
1024 | function reduce(add, remove, initial) {
1025 | reduceAdd = add;
1026 | reduceRemove = remove;
1027 | reduceInitial = initial;
1028 | resetNeeded = true;
1029 | return group;
1030 | }
1031 |
1032 | // A convenience method for reducing by count.
1033 | function reduceCount() {
1034 | return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero);
1035 | }
1036 |
1037 | // A convenience method for reducing by sum(value).
1038 | function reduceSum(value) {
1039 | return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero);
1040 | }
1041 |
1042 | // Sets the reduce order, using the specified accessor.
1043 | function order(value) {
1044 | select = heapselect_by(valueOf);
1045 | heap = heap_by(valueOf);
1046 | function valueOf(d) { return value(d.value); }
1047 | return group;
1048 | }
1049 |
1050 | // A convenience method for natural ordering by reduce value.
1051 | function orderNatural() {
1052 | return order(crossfilter_identity);
1053 | }
1054 |
1055 | // Returns the cardinality of this group, irrespective of any filters.
1056 | function size() {
1057 | return k;
1058 | }
1059 |
1060 | return reduceCount().orderNatural();
1061 | }
1062 |
1063 | // A convenience function for generating a singleton group.
1064 | function groupAll() {
1065 | var g = group(crossfilter_null), all = g.all;
1066 | delete g.all;
1067 | delete g.top;
1068 | delete g.order;
1069 | delete g.orderNatural;
1070 | delete g.size;
1071 | g.value = function() { return all()[0].value; };
1072 | return g;
1073 | }
1074 |
1075 | return dimension;
1076 | }
1077 |
1078 | // A convenience method for groupAll on a dummy dimension.
1079 | // This implementation can be optimized since it is always cardinality 1.
1080 | function groupAll() {
1081 | var group = {
1082 | reduce: reduce,
1083 | reduceCount: reduceCount,
1084 | reduceSum: reduceSum,
1085 | value: value
1086 | };
1087 |
1088 | var reduceValue,
1089 | reduceAdd,
1090 | reduceRemove,
1091 | reduceInitial,
1092 | resetNeeded = true;
1093 |
1094 | // The group listens to the crossfilter for when any dimension changes, so
1095 | // that it can update the reduce value. It must also listen to the parent
1096 | // dimension for when data is added.
1097 | filterListeners.push(update);
1098 | dataListeners.push(add);
1099 |
1100 | // For consistency; actually a no-op since resetNeeded is true.
1101 | add(data, 0, n);
1102 |
1103 | // Incorporates the specified new values into this group.
1104 | function add(newData, n0) {
1105 | var i;
1106 |
1107 | if (resetNeeded) return;
1108 |
1109 | // Add the added values.
1110 | for (i = n0; i < n; ++i) {
1111 | if (!filters[i]) {
1112 | reduceValue = reduceAdd(reduceValue, data[i]);
1113 | }
1114 | }
1115 | }
1116 |
1117 | // Reduces the specified selected or deselected records.
1118 | function update(filterOne, added, removed) {
1119 | var i,
1120 | k,
1121 | n;
1122 |
1123 | if (resetNeeded) return;
1124 |
1125 | // Add the added values.
1126 | for (i = 0, n = added.length; i < n; ++i) {
1127 | if (!filters[k = added[i]]) {
1128 | reduceValue = reduceAdd(reduceValue, data[k]);
1129 | }
1130 | }
1131 |
1132 | // Remove the removed values.
1133 | for (i = 0, n = removed.length; i < n; ++i) {
1134 | if (filters[k = removed[i]] === filterOne) {
1135 | reduceValue = reduceRemove(reduceValue, data[k]);
1136 | }
1137 | }
1138 | }
1139 |
1140 | // Recomputes the group reduce value from scratch.
1141 | function reset() {
1142 | var i;
1143 |
1144 | reduceValue = reduceInitial();
1145 |
1146 | for (i = 0; i < n; ++i) {
1147 | if (!filters[i]) {
1148 | reduceValue = reduceAdd(reduceValue, data[i]);
1149 | }
1150 | }
1151 | }
1152 |
1153 | // Sets the reduce behavior for this group to use the specified functions.
1154 | // This method lazily recomputes the reduce value, waiting until needed.
1155 | function reduce(add, remove, initial) {
1156 | reduceAdd = add;
1157 | reduceRemove = remove;
1158 | reduceInitial = initial;
1159 | resetNeeded = true;
1160 | return group;
1161 | }
1162 |
1163 | // A convenience method for reducing by count.
1164 | function reduceCount() {
1165 | return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero);
1166 | }
1167 |
1168 | // A convenience method for reducing by sum(value).
1169 | function reduceSum(value) {
1170 | return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero);
1171 | }
1172 |
1173 | // Returns the computed reduce value.
1174 | function value() {
1175 | if (resetNeeded) reset(), resetNeeded = false;
1176 | return reduceValue;
1177 | }
1178 |
1179 | return reduceCount();
1180 | }
1181 |
1182 | // Returns the number of records in this crossfilter, irrespective of any filters.
1183 | function size() {
1184 | return n;
1185 | }
1186 |
1187 | return arguments.length
1188 | ? add(arguments[0])
1189 | : crossfilter;
1190 | }
1191 |
1192 | // Returns an array of size n, big enough to store ids up to m.
1193 | function crossfilter_index(n, m) {
1194 | return (m < 0x101
1195 | ? crossfilter_array8 : m < 0x10001
1196 | ? crossfilter_array16
1197 | : crossfilter_array32)(n);
1198 | }
1199 |
1200 | // Constructs a new array of size n, with sequential values from 0 to n - 1.
1201 | function crossfilter_range(n) {
1202 | var range = crossfilter_index(n, n);
1203 | for (var i = -1; ++i < n;) range[i] = i;
1204 | return range;
1205 | }
1206 |
1207 | function crossfilter_capacity(w) {
1208 | return w === 8
1209 | ? 0x100 : w === 16
1210 | ? 0x10000
1211 | : 0x100000000;
1212 | }
1213 | })(this);
1214 |
--------------------------------------------------------------------------------
/crossfilter.v1.min.js:
--------------------------------------------------------------------------------
1 | (function(exports){
2 | crossfilter.version = "1.1.2";
3 | function crossfilter_identity(d) {
4 | return d;
5 | }
6 | crossfilter.permute = permute;
7 |
8 | function permute(array, index) {
9 | for (var i = 0, n = index.length, copy = new Array(n); i < n; ++i) {
10 | copy[i] = array[index[i]];
11 | }
12 | return copy;
13 | }
14 | var bisect = crossfilter.bisect = bisect_by(crossfilter_identity);
15 |
16 | bisect.by = bisect_by;
17 |
18 | function bisect_by(f) {
19 |
20 | // Locate the insertion point for x in a to maintain sorted order. The
21 | // arguments lo and hi may be used to specify a subset of the array which
22 | // should be considered; by default the entire array is used. If x is already
23 | // present in a, the insertion point will be before (to the left of) any
24 | // existing entries. The return value is suitable for use as the first
25 | // argument to `array.splice` assuming that a is already sorted.
26 | // Incomparable values such as NaN and undefined are assumed to be at the end
27 | // of the array.
28 | //
29 | // The returned insertion point i partitions the array a into two halves so
30 | // that all v < x for v in a[lo:i] for the left side and all v >= x for v in
31 | // a[i:hi] for the right side.
32 | function bisectLeft(a, x, lo, hi) {
33 | while (lo < hi) {
34 | var mid = lo + hi >>> 1,
35 | y = f(a[mid]);
36 | if (x <= y || !(y <= y)) hi = mid;
37 | else lo = mid + 1;
38 | }
39 | return lo;
40 | }
41 |
42 | // Similar to bisectLeft, but returns an insertion point which comes after (to
43 | // the right of) any existing entries of x in a.
44 | //
45 | // The returned insertion point i partitions the array into two halves so that
46 | // all v <= x for v in a[lo:i] for the left side and all v > x for v in
47 | // a[i:hi] for the right side.
48 | function bisectRight(a, x, lo, hi) {
49 | while (lo < hi) {
50 | var mid = lo + hi >>> 1,
51 | y = f(a[mid]);
52 | if (x < y || !(y <= y)) hi = mid;
53 | else lo = mid + 1;
54 | }
55 | return lo;
56 | }
57 |
58 | bisectRight.right = bisectRight;
59 | bisectRight.left = bisectLeft;
60 | return bisectRight;
61 | }
62 | var heap = crossfilter.heap = heap_by(crossfilter_identity);
63 |
64 | heap.by = heap_by;
65 |
66 | function heap_by(f) {
67 |
68 | // Builds a binary heap within the specified array a[lo:hi]. The heap has the
69 | // property such that the parent a[lo+i] is always less than or equal to its
70 | // two children: a[lo+2*i+1] and a[lo+2*i+2].
71 | function heap(a, lo, hi) {
72 | var n = hi - lo,
73 | i = (n >>> 1) + 1;
74 | while (--i > 0) sift(a, i, n, lo);
75 | return a;
76 | }
77 |
78 | // Sorts the specified array a[lo:hi] in descending order, assuming it is
79 | // already a heap.
80 | function sort(a, lo, hi) {
81 | var n = hi - lo,
82 | t;
83 | while (--n > 0) t = a[lo], a[lo] = a[lo + n], a[lo + n] = t, sift(a, 1, n, lo);
84 | return a;
85 | }
86 |
87 | // Sifts the element a[lo+i-1] down the heap, where the heap is the contiguous
88 | // slice of array a[lo:lo+n]. This method can also be used to update the heap
89 | // incrementally, without incurring the full cost of reconstructing the heap.
90 | function sift(a, i, n, lo) {
91 | var d = a[--lo + i],
92 | x = f(d),
93 | child;
94 | while ((child = i << 1) <= n) {
95 | if (child < n && f(a[lo + child]) > f(a[lo + child + 1])) child++;
96 | if (x <= f(a[lo + child])) break;
97 | a[lo + i] = a[lo + child];
98 | i = child;
99 | }
100 | a[lo + i] = d;
101 | }
102 |
103 | heap.sort = sort;
104 | return heap;
105 | }
106 | var heapselect = crossfilter.heapselect = heapselect_by(crossfilter_identity);
107 |
108 | heapselect.by = heapselect_by;
109 |
110 | function heapselect_by(f) {
111 | var heap = heap_by(f);
112 |
113 | // Returns a new array containing the top k elements in the array a[lo:hi].
114 | // The returned array is not sorted, but maintains the heap property. If k is
115 | // greater than hi - lo, then fewer than k elements will be returned. The
116 | // order of elements in a is unchanged by this operation.
117 | function heapselect(a, lo, hi, k) {
118 | var queue = new Array(k = Math.min(hi - lo, k)),
119 | min,
120 | i,
121 | x,
122 | d;
123 |
124 | for (i = 0; i < k; ++i) queue[i] = a[lo++];
125 | heap(queue, 0, k);
126 |
127 | if (lo < hi) {
128 | min = f(queue[0]);
129 | do {
130 | if (x = f(d = a[lo]) > min) {
131 | queue[0] = d;
132 | min = f(heap(queue, 0, k)[0]);
133 | }
134 | } while (++lo < hi);
135 | }
136 |
137 | return queue;
138 | }
139 |
140 | return heapselect;
141 | }
142 | var insertionsort = crossfilter.insertionsort = insertionsort_by(crossfilter_identity);
143 |
144 | insertionsort.by = insertionsort_by;
145 |
146 | function insertionsort_by(f) {
147 |
148 | function insertionsort(a, lo, hi) {
149 | for (var i = lo + 1; i < hi; ++i) {
150 | for (var j = i, t = a[i], x = f(t), y; j > lo && ((y = f(a[j - 1])) > x || !(y <= y)); --j) {
151 | a[j] = a[j - 1];
152 | }
153 | a[j] = t;
154 | }
155 | return a;
156 | }
157 |
158 | return insertionsort;
159 | }
160 | // Algorithm designed by Vladimir Yaroslavskiy.
161 | // Implementation based on the Dart project; see lib/dart/LICENSE for details.
162 |
163 | var quicksort = crossfilter.quicksort = quicksort_by(crossfilter_identity);
164 |
165 | quicksort.by = quicksort_by;
166 |
167 | function quicksort_by(f) {
168 | var insertionsort = insertionsort_by(f);
169 |
170 | function sort(a, lo, hi) {
171 | return (hi - lo < quicksort_sizeThreshold
172 | ? insertionsort
173 | : quicksort)(a, lo, hi);
174 | }
175 |
176 | function quicksort(a, lo, hi) {
177 | // First move NaN and undefined to the end.
178 | var x, y;
179 | while (lo < hi && !(x = f(a[hi - 1]), x <= x)) hi--;
180 | for (var i = hi; --i >= lo; ) {
181 | x = f(y = a[i]);
182 | if (!(x <= x)) {
183 | a[i] = a[--hi];
184 | a[hi] = y;
185 | }
186 | }
187 |
188 | // Compute the two pivots by looking at 5 elements.
189 | var sixth = (hi - lo) / 6 | 0,
190 | i1 = lo + sixth,
191 | i5 = hi - 1 - sixth,
192 | i3 = lo + hi - 1 >> 1, // The midpoint.
193 | i2 = i3 - sixth,
194 | i4 = i3 + sixth;
195 |
196 | var e1 = a[i1], x1 = f(e1),
197 | e2 = a[i2], x2 = f(e2),
198 | e3 = a[i3], x3 = f(e3),
199 | e4 = a[i4], x4 = f(e4),
200 | e5 = a[i5], x5 = f(e5);
201 |
202 | var t;
203 |
204 | // Sort the selected 5 elements using a sorting network.
205 | if (x1 > x2) t = e1, e1 = e2, e2 = t, t = x1, x1 = x2, x2 = t;
206 | if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
207 | if (x1 > x3) t = e1, e1 = e3, e3 = t, t = x1, x1 = x3, x3 = t;
208 | if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
209 | if (x1 > x4) t = e1, e1 = e4, e4 = t, t = x1, x1 = x4, x4 = t;
210 | if (x3 > x4) t = e3, e3 = e4, e4 = t, t = x3, x3 = x4, x4 = t;
211 | if (x2 > x5) t = e2, e2 = e5, e5 = t, t = x2, x2 = x5, x5 = t;
212 | if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
213 | if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
214 |
215 | var pivot1 = e2, pivotValue1 = x2,
216 | pivot2 = e4, pivotValue2 = x4;
217 |
218 | // e2 and e4 have been saved in the pivot variables. They will be written
219 | // back, once the partitioning is finished.
220 | a[i1] = e1;
221 | a[i2] = a[lo];
222 | a[i3] = e3;
223 | a[i4] = a[hi - 1];
224 | a[i5] = e5;
225 |
226 | var less = lo + 1, // First element in the middle partition.
227 | great = hi - 2; // Last element in the middle partition.
228 |
229 | // Note that for value comparison, <, <=, >= and > coerce to a primitive via
230 | // Object.prototype.valueOf; == and === do not, so in order to be consistent
231 | // with natural order (such as for Date objects), we must do two compares.
232 | var pivotsEqual = pivotValue1 <= pivotValue2 && pivotValue1 >= pivotValue2;
233 | if (pivotsEqual) {
234 |
235 | // Degenerated case where the partitioning becomes a dutch national flag
236 | // problem.
237 | //
238 | // [ | < pivot | == pivot | unpartitioned | > pivot | ]
239 | // ^ ^ ^ ^ ^
240 | // left less k great right
241 | //
242 | // a[left] and a[right] are undefined and are filled after the
243 | // partitioning.
244 | //
245 | // Invariants:
246 | // 1) for x in ]left, less[ : x < pivot.
247 | // 2) for x in [less, k[ : x == pivot.
248 | // 3) for x in ]great, right[ : x > pivot.
249 | for (var k = less; k <= great; ++k) {
250 | var ek = a[k], xk = f(ek);
251 | if (xk < pivotValue1) {
252 | if (k !== less) {
253 | a[k] = a[less];
254 | a[less] = ek;
255 | }
256 | ++less;
257 | } else if (xk > pivotValue1) {
258 |
259 | // Find the first element <= pivot in the range [k - 1, great] and
260 | // put [:ek:] there. We know that such an element must exist:
261 | // When k == less, then el3 (which is equal to pivot) lies in the
262 | // interval. Otherwise a[k - 1] == pivot and the search stops at k-1.
263 | // Note that in the latter case invariant 2 will be violated for a
264 | // short amount of time. The invariant will be restored when the
265 | // pivots are put into their final positions.
266 | while (true) {
267 | var greatValue = f(a[great]);
268 | if (greatValue > pivotValue1) {
269 | great--;
270 | // This is the only location in the while-loop where a new
271 | // iteration is started.
272 | continue;
273 | } else if (greatValue < pivotValue1) {
274 | // Triple exchange.
275 | a[k] = a[less];
276 | a[less++] = a[great];
277 | a[great--] = ek;
278 | break;
279 | } else {
280 | a[k] = a[great];
281 | a[great--] = ek;
282 | // Note: if great < k then we will exit the outer loop and fix
283 | // invariant 2 (which we just violated).
284 | break;
285 | }
286 | }
287 | }
288 | }
289 | } else {
290 |
291 | // We partition the list into three parts:
292 | // 1. < pivot1
293 | // 2. >= pivot1 && <= pivot2
294 | // 3. > pivot2
295 | //
296 | // During the loop we have:
297 | // [ | < pivot1 | >= pivot1 && <= pivot2 | unpartitioned | > pivot2 | ]
298 | // ^ ^ ^ ^ ^
299 | // left less k great right
300 | //
301 | // a[left] and a[right] are undefined and are filled after the
302 | // partitioning.
303 | //
304 | // Invariants:
305 | // 1. for x in ]left, less[ : x < pivot1
306 | // 2. for x in [less, k[ : pivot1 <= x && x <= pivot2
307 | // 3. for x in ]great, right[ : x > pivot2
308 | for (var k = less; k <= great; k++) {
309 | var ek = a[k], xk = f(ek);
310 | if (xk < pivotValue1) {
311 | if (k !== less) {
312 | a[k] = a[less];
313 | a[less] = ek;
314 | }
315 | ++less;
316 | } else {
317 | if (xk > pivotValue2) {
318 | while (true) {
319 | var greatValue = f(a[great]);
320 | if (greatValue > pivotValue2) {
321 | great--;
322 | if (great < k) break;
323 | // This is the only location inside the loop where a new
324 | // iteration is started.
325 | continue;
326 | } else {
327 | // a[great] <= pivot2.
328 | if (greatValue < pivotValue1) {
329 | // Triple exchange.
330 | a[k] = a[less];
331 | a[less++] = a[great];
332 | a[great--] = ek;
333 | } else {
334 | // a[great] >= pivot1.
335 | a[k] = a[great];
336 | a[great--] = ek;
337 | }
338 | break;
339 | }
340 | }
341 | }
342 | }
343 | }
344 | }
345 |
346 | // Move pivots into their final positions.
347 | // We shrunk the list from both sides (a[left] and a[right] have
348 | // meaningless values in them) and now we move elements from the first
349 | // and third partition into these locations so that we can store the
350 | // pivots.
351 | a[lo] = a[less - 1];
352 | a[less - 1] = pivot1;
353 | a[hi - 1] = a[great + 1];
354 | a[great + 1] = pivot2;
355 |
356 | // The list is now partitioned into three partitions:
357 | // [ < pivot1 | >= pivot1 && <= pivot2 | > pivot2 ]
358 | // ^ ^ ^ ^
359 | // left less great right
360 |
361 | // Recursive descent. (Don't include the pivot values.)
362 | sort(a, lo, less - 1);
363 | sort(a, great + 2, hi);
364 |
365 | if (pivotsEqual) {
366 | // All elements in the second partition are equal to the pivot. No
367 | // need to sort them.
368 | return a;
369 | }
370 |
371 | // In theory it should be enough to call _doSort recursively on the second
372 | // partition.
373 | // The Android source however removes the pivot elements from the recursive
374 | // call if the second partition is too large (more than 2/3 of the list).
375 | if (less < i1 && great > i5) {
376 | var lessValue, greatValue;
377 | while ((lessValue = f(a[less])) <= pivotValue1 && lessValue >= pivotValue1) ++less;
378 | while ((greatValue = f(a[great])) <= pivotValue2 && greatValue >= pivotValue2) --great;
379 |
380 | // Copy paste of the previous 3-way partitioning with adaptions.
381 | //
382 | // We partition the list into three parts:
383 | // 1. == pivot1
384 | // 2. > pivot1 && < pivot2
385 | // 3. == pivot2
386 | //
387 | // During the loop we have:
388 | // [ == pivot1 | > pivot1 && < pivot2 | unpartitioned | == pivot2 ]
389 | // ^ ^ ^
390 | // less k great
391 | //
392 | // Invariants:
393 | // 1. for x in [ *, less[ : x == pivot1
394 | // 2. for x in [less, k[ : pivot1 < x && x < pivot2
395 | // 3. for x in ]great, * ] : x == pivot2
396 | for (var k = less; k <= great; k++) {
397 | var ek = a[k], xk = f(ek);
398 | if (xk <= pivotValue1 && xk >= pivotValue1) {
399 | if (k !== less) {
400 | a[k] = a[less];
401 | a[less] = ek;
402 | }
403 | less++;
404 | } else {
405 | if (xk <= pivotValue2 && xk >= pivotValue2) {
406 | while (true) {
407 | var greatValue = f(a[great]);
408 | if (greatValue <= pivotValue2 && greatValue >= pivotValue2) {
409 | great--;
410 | if (great < k) break;
411 | // This is the only location inside the loop where a new
412 | // iteration is started.
413 | continue;
414 | } else {
415 | // a[great] < pivot2.
416 | if (greatValue < pivotValue1) {
417 | // Triple exchange.
418 | a[k] = a[less];
419 | a[less++] = a[great];
420 | a[great--] = ek;
421 | } else {
422 | // a[great] == pivot1.
423 | a[k] = a[great];
424 | a[great--] = ek;
425 | }
426 | break;
427 | }
428 | }
429 | }
430 | }
431 | }
432 | }
433 |
434 | // The second partition has now been cleared of pivot elements and looks
435 | // as follows:
436 | // [ * | > pivot1 && < pivot2 | * ]
437 | // ^ ^
438 | // less great
439 | // Sort the second partition using recursive descent.
440 |
441 | // The second partition looks as follows:
442 | // [ * | >= pivot1 && <= pivot2 | * ]
443 | // ^ ^
444 | // less great
445 | // Simply sort it by recursive descent.
446 |
447 | return sort(a, less, great + 1);
448 | }
449 |
450 | return sort;
451 | }
452 |
453 | var quicksort_sizeThreshold = 32;
454 | var crossfilter_array8 = crossfilter_arrayUntyped,
455 | crossfilter_array16 = crossfilter_arrayUntyped,
456 | crossfilter_array32 = crossfilter_arrayUntyped,
457 | crossfilter_arrayLengthen = crossfilter_identity,
458 | crossfilter_arrayWiden = crossfilter_identity;
459 |
460 | if (typeof Uint8Array !== "undefined") {
461 | crossfilter_array8 = function(n) { return new Uint8Array(n); };
462 | crossfilter_array16 = function(n) { return new Uint16Array(n); };
463 | crossfilter_array32 = function(n) { return new Uint32Array(n); };
464 |
465 | crossfilter_arrayLengthen = function(array, length) {
466 | var copy = new array.constructor(length);
467 | copy.set(array);
468 | return copy;
469 | };
470 |
471 | crossfilter_arrayWiden = function(array, width) {
472 | var copy;
473 | switch (width) {
474 | case 16: copy = crossfilter_array16(array.length); break;
475 | case 32: copy = crossfilter_array32(array.length); break;
476 | default: throw new Error("invalid array width!");
477 | }
478 | copy.set(array);
479 | return copy;
480 | };
481 | }
482 |
483 | function crossfilter_arrayUntyped(n) {
484 | return new Array(n);
485 | }
486 | function crossfilter_filterExact(bisect, value) {
487 | return function(values) {
488 | var n = values.length;
489 | return [bisect.left(values, value, 0, n), bisect.right(values, value, 0, n)];
490 | };
491 | }
492 |
493 | function crossfilter_filterRange(bisect, range) {
494 | var min = range[0],
495 | max = range[1];
496 | return function(values) {
497 | var n = values.length;
498 | return [bisect.left(values, min, 0, n), bisect.left(values, max, 0, n)];
499 | };
500 | }
501 |
502 | function crossfilter_filterAll(values) {
503 | return [0, values.length];
504 | }
505 | function crossfilter_null() {
506 | return null;
507 | }
508 | function crossfilter_zero() {
509 | return 0;
510 | }
511 | function crossfilter_reduceIncrement(p) {
512 | return p + 1;
513 | }
514 |
515 | function crossfilter_reduceDecrement(p) {
516 | return p - 1;
517 | }
518 |
519 | function crossfilter_reduceAdd(f) {
520 | return function(p, v) {
521 | return p + +f(v);
522 | };
523 | }
524 |
525 | function crossfilter_reduceSubtract(f) {
526 | return function(p, v) {
527 | return p - f(v);
528 | };
529 | }
530 | exports.crossfilter = crossfilter;
531 |
532 | function crossfilter() {
533 | var crossfilter = {
534 | add: add,
535 | dimension: dimension,
536 | groupAll: groupAll,
537 | size: size
538 | };
539 |
540 | var data = [], // the records
541 | n = 0, // the number of records; data.length
542 | m = 0, // number of dimensions in use
543 | M = 8, // number of dimensions that can fit in `filters`
544 | filters = crossfilter_array8(0), // M bits per record; 1 is filtered out
545 | filterListeners = [], // when the filters change
546 | dataListeners = []; // when data is added
547 |
548 | // Adds the specified new records to this crossfilter.
549 | function add(newData) {
550 | var n0 = n,
551 | n1 = newData.length;
552 |
553 | // If there's actually new data to add…
554 | // Merge the new data into the existing data.
555 | // Lengthen the filter bitset to handle the new records.
556 | // Notify listeners (dimensions and groups) that new data is available.
557 | if (n1) {
558 | data = data.concat(newData);
559 | filters = crossfilter_arrayLengthen(filters, n += n1);
560 | dataListeners.forEach(function(l) { l(newData, n0, n1); });
561 | }
562 |
563 | return crossfilter;
564 | }
565 |
566 | // Adds a new dimension with the specified value accessor function.
567 | function dimension(value) {
568 | var dimension = {
569 | filter: filter,
570 | filterExact: filterExact,
571 | filterRange: filterRange,
572 | filterAll: filterAll,
573 | top: top,
574 | bottom: bottom,
575 | group: group,
576 | groupAll: groupAll
577 | };
578 |
579 | var one = 1 << m++, // bit mask, e.g., 00001000
580 | zero = ~one, // inverted one, e.g., 11110111
581 | values, // sorted, cached array
582 | index, // value rank ↦ object id
583 | newValues, // temporary array storing newly-added values
584 | newIndex, // temporary array storing newly-added index
585 | sort = quicksort_by(function(i) { return newValues[i]; }),
586 | refilter = crossfilter_filterAll, // for recomputing filter
587 | indexListeners = [], // when data is added
588 | lo0 = 0,
589 | hi0 = 0;
590 |
591 | // Updating a dimension is a two-stage process. First, we must update the
592 | // associated filters for the newly-added records. Once all dimensions have
593 | // updated their filters, the groups are notified to update.
594 | dataListeners.unshift(preAdd);
595 | dataListeners.push(postAdd);
596 |
597 | // Incorporate any existing data into this dimension, and make sure that the
598 | // filter bitset is wide enough to handle the new dimension.
599 | if (m > M) filters = crossfilter_arrayWiden(filters, M <<= 1);
600 | preAdd(data, 0, n);
601 | postAdd(data, 0, n);
602 |
603 | // Incorporates the specified new records into this dimension.
604 | // This function is responsible for updating filters, values, and index.
605 | function preAdd(newData, n0, n1) {
606 |
607 | // Permute new values into natural order using a sorted index.
608 | newValues = newData.map(value);
609 | newIndex = sort(crossfilter_range(n1), 0, n1);
610 | newValues = permute(newValues, newIndex);
611 |
612 | // Bisect newValues to determine which new records are selected.
613 | var bounds = refilter(newValues), lo1 = bounds[0], hi1 = bounds[1], i;
614 | for (i = 0; i < lo1; ++i) filters[newIndex[i] + n0] |= one;
615 | for (i = hi1; i < n1; ++i) filters[newIndex[i] + n0] |= one;
616 |
617 | // If this dimension previously had no data, then we don't need to do the
618 | // more expensive merge operation; use the new values and index as-is.
619 | if (!n0) {
620 | values = newValues;
621 | index = newIndex;
622 | lo0 = lo1;
623 | hi0 = hi1;
624 | return;
625 | }
626 |
627 | var oldValues = values,
628 | oldIndex = index,
629 | i0 = 0,
630 | i1 = 0;
631 |
632 | // Otherwise, create new arrays into which to merge new and old.
633 | values = new Array(n);
634 | index = crossfilter_index(n, n);
635 |
636 | // Merge the old and new sorted values, and old and new index.
637 | for (i = 0; i0 < n0 && i1 < n1; ++i) {
638 | if (oldValues[i0] < newValues[i1]) {
639 | values[i] = oldValues[i0];
640 | index[i] = oldIndex[i0++];
641 | } else {
642 | values[i] = newValues[i1];
643 | index[i] = newIndex[i1++] + n0;
644 | }
645 | }
646 |
647 | // Add any remaining old values.
648 | for (; i0 < n0; ++i0, ++i) {
649 | values[i] = oldValues[i0];
650 | index[i] = oldIndex[i0];
651 | }
652 |
653 | // Add any remaining new values.
654 | for (; i1 < n1; ++i1, ++i) {
655 | values[i] = newValues[i1];
656 | index[i] = newIndex[i1] + n0;
657 | }
658 |
659 | // Bisect again to recompute lo0 and hi0.
660 | bounds = refilter(values), lo0 = bounds[0], hi0 = bounds[1];
661 | }
662 |
663 | // When all filters have updated, notify index listeners of the new values.
664 | function postAdd(newData, n0, n1) {
665 | indexListeners.forEach(function(l) { l(newValues, newIndex, n0, n1); });
666 | newValues = newIndex = null;
667 | }
668 |
669 | // Updates the selected values based on the specified bounds [lo, hi].
670 | // This implementation is used by all the public filter methods.
671 | function filterIndex(bounds) {
672 | var i,
673 | j,
674 | k,
675 | lo1 = bounds[0],
676 | hi1 = bounds[1],
677 | added = [],
678 | removed = [];
679 |
680 | // Fast incremental update based on previous lo index.
681 | if (lo1 < lo0) {
682 | for (i = lo1, j = Math.min(lo0, hi1); i < j; ++i) {
683 | filters[k = index[i]] ^= one;
684 | added.push(k);
685 | }
686 | } else if (lo1 > lo0) {
687 | for (i = lo0, j = Math.min(lo1, hi0); i < j; ++i) {
688 | filters[k = index[i]] ^= one;
689 | removed.push(k);
690 | }
691 | }
692 |
693 | // Fast incremental update based on previous hi index.
694 | if (hi1 > hi0) {
695 | for (i = Math.max(lo1, hi0), j = hi1; i < j; ++i) {
696 | filters[k = index[i]] ^= one;
697 | added.push(k);
698 | }
699 | } else if (hi1 < hi0) {
700 | for (i = Math.max(lo0, hi1), j = hi0; i < j; ++i) {
701 | filters[k = index[i]] ^= one;
702 | removed.push(k);
703 | }
704 | }
705 |
706 | lo0 = lo1;
707 | hi0 = hi1;
708 | filterListeners.forEach(function(l) { l(one, added, removed); });
709 | return dimension;
710 | }
711 |
712 | // Filters this dimension using the specified range, value, or null.
713 | // If the range is null, this is equivalent to filterAll.
714 | // If the range is an array, this is equivalent to filterRange.
715 | // Otherwise, this is equivalent to filterExact.
716 | function filter(range) {
717 | return range == null
718 | ? filterAll() : Array.isArray(range)
719 | ? filterRange(range)
720 | : filterExact(range);
721 | }
722 |
723 | // Filters this dimension to select the exact value.
724 | function filterExact(value) {
725 | return filterIndex((refilter = crossfilter_filterExact(bisect, value))(values));
726 | }
727 |
728 | // Filters this dimension to select the specified range [lo, hi].
729 | // The lower bound is inclusive, and the upper bound is exclusive.
730 | function filterRange(range) {
731 | return filterIndex((refilter = crossfilter_filterRange(bisect, range))(values));
732 | }
733 |
734 | // Clears any filters on this dimension.
735 | function filterAll() {
736 | return filterIndex((refilter = crossfilter_filterAll)(values));
737 | }
738 |
739 | // Returns the top K selected records based on this dimension's order.
740 | // Note: observes this dimension's filter, unlike group and groupAll.
741 | function top(k) {
742 | var array = [],
743 | i = hi0,
744 | j;
745 |
746 | while (--i >= lo0 && k > 0) {
747 | if (!filters[j = index[i]]) {
748 | array.push(data[j]);
749 | --k;
750 | }
751 | }
752 |
753 | return array;
754 | }
755 |
756 | // Returns the bottom K selected records based on this dimension's order.
757 | // Note: observes this dimension's filter, unlike group and groupAll.
758 | function bottom(k) {
759 | var array = [],
760 | i = lo0,
761 | j;
762 |
763 | while (i < hi0 && k > 0) {
764 | if (!filters[j = index[i]]) {
765 | array.push(data[j]);
766 | --k;
767 | }
768 | i++;
769 | }
770 |
771 | return array;
772 | }
773 |
774 | // Adds a new group to this dimension, using the specified key function.
775 | function group(key) {
776 | var group = {
777 | top: top,
778 | all: all,
779 | reduce: reduce,
780 | reduceCount: reduceCount,
781 | reduceSum: reduceSum,
782 | order: order,
783 | orderNatural: orderNatural,
784 | size: size
785 | };
786 |
787 | var groups, // array of {key, value}
788 | groupIndex, // object id ↦ group id
789 | groupWidth = 8,
790 | groupCapacity = crossfilter_capacity(groupWidth),
791 | k = 0, // cardinality
792 | select,
793 | heap,
794 | reduceAdd,
795 | reduceRemove,
796 | reduceInitial,
797 | update = crossfilter_null,
798 | reset = crossfilter_null,
799 | resetNeeded = true;
800 |
801 | if (arguments.length < 1) key = crossfilter_identity;
802 |
803 | // The group listens to the crossfilter for when any dimension changes, so
804 | // that it can update the associated reduce values. It must also listen to
805 | // the parent dimension for when data is added, and compute new keys.
806 | filterListeners.push(update);
807 | indexListeners.push(add);
808 |
809 | // Incorporate any existing data into the grouping.
810 | add(values, index, 0, n);
811 |
812 | // Incorporates the specified new values into this group.
813 | // This function is responsible for updating groups and groupIndex.
814 | function add(newValues, newIndex, n0, n1) {
815 | var oldGroups = groups,
816 | reIndex = crossfilter_index(k, groupCapacity),
817 | add = reduceAdd,
818 | initial = reduceInitial,
819 | k0 = k, // old cardinality
820 | i0 = 0, // index of old group
821 | i1 = 0, // index of new record
822 | j, // object id
823 | g0, // old group
824 | x0, // old key
825 | x1, // new key
826 | g, // group to add
827 | x; // key of group to add
828 |
829 | // If a reset is needed, we don't need to update the reduce values.
830 | if (resetNeeded) add = initial = crossfilter_null;
831 |
832 | // Reset the new groups (k is a lower bound).
833 | // Also, make sure that groupIndex exists and is long enough.
834 | groups = new Array(k), k = 0;
835 | groupIndex = k0 > 1 ? crossfilter_arrayLengthen(groupIndex, n) : crossfilter_index(n, groupCapacity);
836 |
837 | // Get the first old key (x0 of g0), if it exists.
838 | if (k0) x0 = (g0 = oldGroups[0]).key;
839 |
840 | // Find the first new key (x1).
841 | x1 = key(newValues[i1]);
842 |
843 | // While new keys remain…
844 | while (i1 < n1) {
845 |
846 | // Determine the lesser of the two current keys; new and old.
847 | // If there are no old keys remaining, then always add the new key.
848 | if (g0 && x0 <= x1) {
849 | g = g0, x = x0;
850 |
851 | // Record the new index of the old group.
852 | reIndex[i0] = k;
853 |
854 | // Retrieve the next old key.
855 | if (g0 = oldGroups[++i0]) x0 = g0.key;
856 | } else {
857 | g = {key: x1, value: initial()}, x = x1;
858 | }
859 |
860 | // Add the lesser group.
861 | groups[k] = g;
862 |
863 | // Add any selected records belonging to the added group, while
864 | // advancing the new key and populating the associated group index.
865 | while (x1 <= x || !(x1 <= x1) && !(x <= x)) {
866 | groupIndex[j = newIndex[i1] + n0] = k;
867 | if (!(filters[j] & zero)) g.value = add(g.value, data[j]);
868 | if (++i1 >= n1) break;
869 | x1 = key(newValues[i1]);
870 | }
871 |
872 | groupIncrement();
873 | }
874 |
875 | // Add any remaining old groups that were greater than all new keys.
876 | // No incremental reduce is needed; these groups have no new records.
877 | // Also record the new index of the old group.
878 | while (i0 < k0) {
879 | groups[reIndex[i0] = k] = oldGroups[i0++];
880 | groupIncrement();
881 | }
882 |
883 | // If we added any new groups before any old groups,
884 | // update the group index of all the old records.
885 | if (k > i0) for (i0 = 0; i0 < n0; ++i0) {
886 | groupIndex[i0] = reIndex[groupIndex[i0]];
887 | }
888 |
889 | // Modify the update and reset behavior based on the cardinality.
890 | // If the cardinality is less than or equal to one, then the groupIndex
891 | // is not needed. If the cardinality is zero, then there are no records
892 | // and therefore no groups to update or reset. Note that we also must
893 | // change the registered listener to point to the new method.
894 | j = filterListeners.indexOf(update);
895 | if (k > 1) {
896 | update = updateMany;
897 | reset = resetMany;
898 | } else {
899 | if (k === 1) {
900 | update = updateOne;
901 | reset = resetOne;
902 | } else {
903 | update = crossfilter_null;
904 | reset = crossfilter_null;
905 | }
906 | groupIndex = null;
907 | }
908 | filterListeners[j] = update;
909 |
910 | // Count the number of added groups,
911 | // and widen the group index as needed.
912 | function groupIncrement() {
913 | if (++k === groupCapacity) {
914 | reIndex = crossfilter_arrayWiden(reIndex, groupWidth <<= 1);
915 | groupIndex = crossfilter_arrayWiden(groupIndex, groupWidth);
916 | groupCapacity = crossfilter_capacity(groupWidth);
917 | }
918 | }
919 | }
920 |
921 | // Reduces the specified selected or deselected records.
922 | // This function is only used when the cardinality is greater than 1.
923 | function updateMany(filterOne, added, removed) {
924 | if (filterOne === one || resetNeeded) return;
925 |
926 | var i,
927 | k,
928 | n,
929 | g;
930 |
931 | // Add the added values.
932 | for (i = 0, n = added.length; i < n; ++i) {
933 | if (!(filters[k = added[i]] & zero)) {
934 | g = groups[groupIndex[k]];
935 | g.value = reduceAdd(g.value, data[k]);
936 | }
937 | }
938 |
939 | // Remove the removed values.
940 | for (i = 0, n = removed.length; i < n; ++i) {
941 | if ((filters[k = removed[i]] & zero) === filterOne) {
942 | g = groups[groupIndex[k]];
943 | g.value = reduceRemove(g.value, data[k]);
944 | }
945 | }
946 | }
947 |
948 | // Reduces the specified selected or deselected records.
949 | // This function is only used when the cardinality is 1.
950 | function updateOne(filterOne, added, removed) {
951 | if (filterOne === one || resetNeeded) return;
952 |
953 | var i,
954 | k,
955 | n,
956 | g = groups[0];
957 |
958 | // Add the added values.
959 | for (i = 0, n = added.length; i < n; ++i) {
960 | if (!(filters[k = added[i]] & zero)) {
961 | g.value = reduceAdd(g.value, data[k]);
962 | }
963 | }
964 |
965 | // Remove the removed values.
966 | for (i = 0, n = removed.length; i < n; ++i) {
967 | if ((filters[k = removed[i]] & zero) === filterOne) {
968 | g.value = reduceRemove(g.value, data[k]);
969 | }
970 | }
971 | }
972 |
973 | // Recomputes the group reduce values from scratch.
974 | // This function is only used when the cardinality is greater than 1.
975 | function resetMany() {
976 | var i,
977 | g;
978 |
979 | // Reset all group values.
980 | for (i = 0; i < k; ++i) {
981 | groups[i].value = reduceInitial();
982 | }
983 |
984 | // Add any selected records.
985 | for (i = 0; i < n; ++i) {
986 | if (!(filters[i] & zero)) {
987 | g = groups[groupIndex[i]];
988 | g.value = reduceAdd(g.value, data[i]);
989 | }
990 | }
991 | }
992 |
993 | // Recomputes the group reduce values from scratch.
994 | // This function is only used when the cardinality is 1.
995 | function resetOne() {
996 | var i,
997 | g = groups[0];
998 |
999 | // Reset the singleton group values.
1000 | g.value = reduceInitial();
1001 |
1002 | // Add any selected records.
1003 | for (i = 0; i < n; ++i) {
1004 | if (!(filters[i] & zero)) {
1005 | g.value = reduceAdd(g.value, data[i]);
1006 | }
1007 | }
1008 | }
1009 |
1010 | // Returns the array of group values, in the dimension's natural order.
1011 | function all() {
1012 | if (resetNeeded) reset(), resetNeeded = false;
1013 | return groups;
1014 | }
1015 |
1016 | // Returns a new array containing the top K group values, in reduce order.
1017 | function top(k) {
1018 | var top = select(all(), 0, groups.length, k);
1019 | return heap.sort(top, 0, top.length);
1020 | }
1021 |
1022 | // Sets the reduce behavior for this group to use the specified functions.
1023 | // This method lazily recomputes the reduce values, waiting until needed.
1024 | function reduce(add, remove, initial) {
1025 | reduceAdd = add;
1026 | reduceRemove = remove;
1027 | reduceInitial = initial;
1028 | resetNeeded = true;
1029 | return group;
1030 | }
1031 |
1032 | // A convenience method for reducing by count.
1033 | function reduceCount() {
1034 | return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero);
1035 | }
1036 |
1037 | // A convenience method for reducing by sum(value).
1038 | function reduceSum(value) {
1039 | return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero);
1040 | }
1041 |
1042 | // Sets the reduce order, using the specified accessor.
1043 | function order(value) {
1044 | select = heapselect_by(valueOf);
1045 | heap = heap_by(valueOf);
1046 | function valueOf(d) { return value(d.value); }
1047 | return group;
1048 | }
1049 |
1050 | // A convenience method for natural ordering by reduce value.
1051 | function orderNatural() {
1052 | return order(crossfilter_identity);
1053 | }
1054 |
1055 | // Returns the cardinality of this group, irrespective of any filters.
1056 | function size() {
1057 | return k;
1058 | }
1059 |
1060 | return reduceCount().orderNatural();
1061 | }
1062 |
1063 | // A convenience function for generating a singleton group.
1064 | function groupAll() {
1065 | var g = group(crossfilter_null), all = g.all;
1066 | delete g.all;
1067 | delete g.top;
1068 | delete g.order;
1069 | delete g.orderNatural;
1070 | delete g.size;
1071 | g.value = function() { return all()[0].value; };
1072 | return g;
1073 | }
1074 |
1075 | return dimension;
1076 | }
1077 |
1078 | // A convenience method for groupAll on a dummy dimension.
1079 | // This implementation can be optimized since it is always cardinality 1.
1080 | function groupAll() {
1081 | var group = {
1082 | reduce: reduce,
1083 | reduceCount: reduceCount,
1084 | reduceSum: reduceSum,
1085 | value: value
1086 | };
1087 |
1088 | var reduceValue,
1089 | reduceAdd,
1090 | reduceRemove,
1091 | reduceInitial,
1092 | resetNeeded = true;
1093 |
1094 | // The group listens to the crossfilter for when any dimension changes, so
1095 | // that it can update the reduce value. It must also listen to the parent
1096 | // dimension for when data is added.
1097 | filterListeners.push(update);
1098 | dataListeners.push(add);
1099 |
1100 | // For consistency; actually a no-op since resetNeeded is true.
1101 | add(data, 0, n);
1102 |
1103 | // Incorporates the specified new values into this group.
1104 | function add(newData, n0) {
1105 | var i;
1106 |
1107 | if (resetNeeded) return;
1108 |
1109 | // Add the added values.
1110 | for (i = n0; i < n; ++i) {
1111 | if (!filters[i]) {
1112 | reduceValue = reduceAdd(reduceValue, data[i]);
1113 | }
1114 | }
1115 | }
1116 |
1117 | // Reduces the specified selected or deselected records.
1118 | function update(filterOne, added, removed) {
1119 | var i,
1120 | k,
1121 | n;
1122 |
1123 | if (resetNeeded) return;
1124 |
1125 | // Add the added values.
1126 | for (i = 0, n = added.length; i < n; ++i) {
1127 | if (!filters[k = added[i]]) {
1128 | reduceValue = reduceAdd(reduceValue, data[k]);
1129 | }
1130 | }
1131 |
1132 | // Remove the removed values.
1133 | for (i = 0, n = removed.length; i < n; ++i) {
1134 | if (filters[k = removed[i]] === filterOne) {
1135 | reduceValue = reduceRemove(reduceValue, data[k]);
1136 | }
1137 | }
1138 | }
1139 |
1140 | // Recomputes the group reduce value from scratch.
1141 | function reset() {
1142 | var i;
1143 |
1144 | reduceValue = reduceInitial();
1145 |
1146 | for (i = 0; i < n; ++i) {
1147 | if (!filters[i]) {
1148 | reduceValue = reduceAdd(reduceValue, data[i]);
1149 | }
1150 | }
1151 | }
1152 |
1153 | // Sets the reduce behavior for this group to use the specified functions.
1154 | // This method lazily recomputes the reduce value, waiting until needed.
1155 | function reduce(add, remove, initial) {
1156 | reduceAdd = add;
1157 | reduceRemove = remove;
1158 | reduceInitial = initial;
1159 | resetNeeded = true;
1160 | return group;
1161 | }
1162 |
1163 | // A convenience method for reducing by count.
1164 | function reduceCount() {
1165 | return reduce(crossfilter_reduceIncrement, crossfilter_reduceDecrement, crossfilter_zero);
1166 | }
1167 |
1168 | // A convenience method for reducing by sum(value).
1169 | function reduceSum(value) {
1170 | return reduce(crossfilter_reduceAdd(value), crossfilter_reduceSubtract(value), crossfilter_zero);
1171 | }
1172 |
1173 | // Returns the computed reduce value.
1174 | function value() {
1175 | if (resetNeeded) reset(), resetNeeded = false;
1176 | return reduceValue;
1177 | }
1178 |
1179 | return reduceCount();
1180 | }
1181 |
1182 | // Returns the number of records in this crossfilter, irrespective of any filters.
1183 | function size() {
1184 | return n;
1185 | }
1186 |
1187 | return arguments.length
1188 | ? add(arguments[0])
1189 | : crossfilter;
1190 | }
1191 |
1192 | // Returns an array of size n, big enough to store ids up to m.
1193 | function crossfilter_index(n, m) {
1194 | return (m < 0x101
1195 | ? crossfilter_array8 : m < 0x10001
1196 | ? crossfilter_array16
1197 | : crossfilter_array32)(n);
1198 | }
1199 |
1200 | // Constructs a new array of size n, with sequential values from 0 to n - 1.
1201 | function crossfilter_range(n) {
1202 | var range = crossfilter_index(n, n);
1203 | for (var i = -1; ++i < n;) range[i] = i;
1204 | return range;
1205 | }
1206 |
1207 | function crossfilter_capacity(w) {
1208 | return w === 8
1209 | ? 0x100 : w === 16
1210 | ? 0x10000
1211 | : 0x100000000;
1212 | }
1213 | })(this);
1214 |
--------------------------------------------------------------------------------
/www/d3.v2.min.js:
--------------------------------------------------------------------------------
1 | (function(){function e(a,b){try{for(var c in b)Object.defineProperty(a.prototype,c,{value:b[c],enumerable:!1})}catch(d){a.prototype=b}}function g(a){var b=-1,c=a.length,d=[];while(++b