rails-data-explorer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG.md +3 -0
  3. data/Gemfile +7 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +52 -0
  6. data/Rakefile +18 -0
  7. data/lib/rails-data-explorer.rb +44 -0
  8. data/lib/rails-data-explorer/action_view_extension.rb +12 -0
  9. data/lib/rails-data-explorer/active_record_extension.rb +14 -0
  10. data/lib/rails-data-explorer/chart.rb +52 -0
  11. data/lib/rails-data-explorer/chart/box_plot.rb +79 -0
  12. data/lib/rails-data-explorer/chart/box_plot_group.rb +109 -0
  13. data/lib/rails-data-explorer/chart/contingency_table.rb +189 -0
  14. data/lib/rails-data-explorer/chart/descriptive_statistics_table.rb +22 -0
  15. data/lib/rails-data-explorer/chart/descriptive_statistics_table_group.rb +0 -0
  16. data/lib/rails-data-explorer/chart/histogram_categorical.rb +73 -0
  17. data/lib/rails-data-explorer/chart/histogram_quantitative.rb +73 -0
  18. data/lib/rails-data-explorer/chart/histogram_temporal.rb +78 -0
  19. data/lib/rails-data-explorer/chart/multi_dimensional_charts.rb +1 -0
  20. data/lib/rails-data-explorer/chart/parallel_coordinates.rb +89 -0
  21. data/lib/rails-data-explorer/chart/parallel_set.rb +65 -0
  22. data/lib/rails-data-explorer/chart/pie_chart.rb +67 -0
  23. data/lib/rails-data-explorer/chart/scatterplot.rb +120 -0
  24. data/lib/rails-data-explorer/chart/scatterplot_matrix.rb +1 -0
  25. data/lib/rails-data-explorer/chart/stacked_bar_chart_categorical_percent.rb +120 -0
  26. data/lib/rails-data-explorer/data_series.rb +115 -0
  27. data/lib/rails-data-explorer/data_set.rb +127 -0
  28. data/lib/rails-data-explorer/data_type.rb +34 -0
  29. data/lib/rails-data-explorer/data_type/categorical.rb +117 -0
  30. data/lib/rails-data-explorer/data_type/geo.rb +1 -0
  31. data/lib/rails-data-explorer/data_type/quantitative.rb +109 -0
  32. data/lib/rails-data-explorer/data_type/quantitative/decimal.rb +13 -0
  33. data/lib/rails-data-explorer/data_type/quantitative/integer.rb +13 -0
  34. data/lib/rails-data-explorer/data_type/quantitative/temporal.rb +62 -0
  35. data/lib/rails-data-explorer/engine.rb +24 -0
  36. data/lib/rails-data-explorer/exploration.rb +89 -0
  37. data/lib/rails-data-explorer/statistics/pearsons_chi_squared_independence_test.rb +75 -0
  38. data/lib/rails-data-explorer/statistics/rng_category.rb +37 -0
  39. data/lib/rails-data-explorer/statistics/rng_gaussian.rb +24 -0
  40. data/lib/rails-data-explorer/statistics/rng_power_law.rb +21 -0
  41. data/lib/rails-data-explorer/utils/color_scale.rb +33 -0
  42. data/lib/rails-data-explorer/utils/data_binner.rb +8 -0
  43. data/lib/rails-data-explorer/utils/data_encoder.rb +2 -0
  44. data/lib/rails-data-explorer/utils/data_quantizer.rb +2 -0
  45. data/lib/rails-data-explorer/utils/value_formatter.rb +41 -0
  46. data/rails-data-explorer.gemspec +30 -0
  47. data/vendor/assets/javascripts/d3.boxplot.js +302 -0
  48. data/vendor/assets/javascripts/d3.parcoords.js +585 -0
  49. data/vendor/assets/javascripts/d3.parsets.js +663 -0
  50. data/vendor/assets/javascripts/d3.v3.js +9294 -0
  51. data/vendor/assets/javascripts/nv.d3.js +14369 -0
  52. data/vendor/assets/javascripts/rails-data-explorer.js +19 -0
  53. data/vendor/assets/stylesheets/bootstrap-theme.css +346 -0
  54. data/vendor/assets/stylesheets/bootstrap.css +1727 -0
  55. data/vendor/assets/stylesheets/d3.boxplot.css +20 -0
  56. data/vendor/assets/stylesheets/d3.parcoords.css +34 -0
  57. data/vendor/assets/stylesheets/d3.parsets.css +34 -0
  58. data/vendor/assets/stylesheets/nv.d3.css +769 -0
  59. data/vendor/assets/stylesheets/rails-data-explorer.css +21 -0
  60. data/vendor/assets/stylesheets/rde-default-style.css +42 -0
  61. metadata +250 -0
@@ -0,0 +1,585 @@
1
+ d3.parcoords = function(config) {
2
+ var __ = {
3
+ data: [],
4
+ dimensions: [],
5
+ dimensionTitles: {},
6
+ types: {},
7
+ brushed: false,
8
+ mode: "default",
9
+ rate: 20,
10
+ width: 600,
11
+ height: 300,
12
+ margin: { top: 24, right: 0, bottom: 12, left: 0 },
13
+ color: "#069",
14
+ composite: "source-over",
15
+ alpha: 0.7
16
+ };
17
+
18
+ extend(__, config);
19
+ var pc = function(selection) {
20
+ selection = pc.selection = d3.select(selection);
21
+
22
+ __.width = selection[0][0].clientWidth;
23
+ __.height = selection[0][0].clientHeight;
24
+
25
+ // canvas data layers
26
+ ["shadows", "marks", "foreground", "highlight"].forEach(function(layer) {
27
+ canvas[layer] = selection
28
+ .append("canvas")
29
+ .attr("class", layer)[0][0];
30
+ ctx[layer] = canvas[layer].getContext("2d");
31
+ });
32
+
33
+ // svg tick and brush layers
34
+ pc.svg = selection
35
+ .append("svg")
36
+ .attr("style", "width: " + __.width + "px; height: " + __.height + "px")
37
+ .append("svg:g")
38
+ .attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
39
+
40
+ return pc;
41
+ };
42
+ var events = d3.dispatch.apply(this,["render", "resize", "highlight", "brush"].concat(d3.keys(__))),
43
+ w = function() { return __.width - __.margin.right - __.margin.left; },
44
+ h = function() { return __.height - __.margin.top - __.margin.bottom },
45
+ flags = {
46
+ brushable: false,
47
+ reorderable: false,
48
+ axes: false,
49
+ interactive: false,
50
+ shadows: false,
51
+ debug: false
52
+ },
53
+ xscale = d3.scale.ordinal(),
54
+ yscale = {},
55
+ dragging = {},
56
+ line = d3.svg.line(),
57
+ axis = d3.svg.axis().orient("left").ticks(5),
58
+ g, // groups for axes, brushes
59
+ ctx = {},
60
+ canvas = {};
61
+
62
+ // side effects for setters
63
+ var side_effects = d3.dispatch.apply(this,d3.keys(__))
64
+ .on("composite", function(d) { ctx.foreground.globalCompositeOperation = d.value; })
65
+ .on("alpha", function(d) { ctx.foreground.globalAlpha = d.value; })
66
+ .on("width", function(d) { pc.resize(); })
67
+ .on("height", function(d) { pc.resize(); })
68
+ .on("margin", function(d) { pc.resize(); })
69
+ .on("rate", function(d) { rqueue.rate(d.value); })
70
+ .on("data", function(d) {
71
+ if (flags.shadows) paths(__.data, ctx.shadows);
72
+ })
73
+ .on("dimensions", function(d) {
74
+ xscale.domain(__.dimensions);
75
+ if (flags.interactive) pc.render().updateAxes();
76
+ });
77
+
78
+ // expose the state of the chart
79
+ pc.state = __;
80
+ pc.flags = flags;
81
+
82
+ // create getter/setters
83
+ getset(pc, __, events);
84
+
85
+ // expose events
86
+ d3.rebind(pc, events, "on");
87
+
88
+ // tick formatting
89
+ d3.rebind(pc, axis, "ticks", "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat");
90
+
91
+ // getter/setter with event firing
92
+ function getset(obj,state,events) {
93
+ d3.keys(state).forEach(function(key) {
94
+ obj[key] = function(x) {
95
+ if (!arguments.length) return state[key];
96
+ var old = state[key];
97
+ state[key] = x;
98
+ side_effects[key].call(pc,{"value": x, "previous": old});
99
+ events[key].call(pc,{"value": x, "previous": old});
100
+ return obj;
101
+ };
102
+ });
103
+ };
104
+
105
+ function extend(target, source) {
106
+ for (key in source) {
107
+ target[key] = source[key];
108
+ }
109
+ return target;
110
+ };
111
+ pc.autoscale = function() {
112
+ // yscale
113
+ var defaultScales = {
114
+ "date": function(k) {
115
+ return d3.time.scale()
116
+ .domain(d3.extent(__.data, function(d) {
117
+ return d[k] ? d[k] : null;
118
+ }))
119
+ .nice()
120
+ .range([h()+1, 1])
121
+ },
122
+ "number": function(k) {
123
+ return d3.scale.linear()
124
+ .domain(d3.extent(__.data, function(d) { return +d[k]; }))
125
+ .range([h()+1, 1])
126
+ },
127
+ "string": function(k) {
128
+ return d3.scale.ordinal()
129
+ .domain(__.data.map(function(p) { return p[k]; }))
130
+ .rangePoints([h()+1, 1])
131
+ }
132
+ };
133
+
134
+ __.dimensions.forEach(function(k) {
135
+ yscale[k] = defaultScales[__.types[k]](k);
136
+ });
137
+
138
+ // hack to remove ordinal dimensions with many values
139
+ pc.dimensions(pc.dimensions().filter(function(p,i) {
140
+ var uniques = yscale[p].domain().length;
141
+ if (__.types[p] == "string" && (uniques > 60 || uniques < 2)) {
142
+ return false;
143
+ }
144
+ return true;
145
+ }));
146
+
147
+ // xscale
148
+ xscale.rangePoints([0, w()], 1);
149
+
150
+ // canvas sizes
151
+ pc.selection.selectAll("canvas")
152
+ .style("margin-top", __.margin.top + "px")
153
+ .style("margin-left", __.margin.left + "px")
154
+ .attr("width", w()+2)
155
+ .attr("height", h()+2)
156
+
157
+ // default styles, needs to be set when canvas width changes
158
+ ctx.foreground.strokeStyle = __.color;
159
+ ctx.foreground.lineWidth = 1.4;
160
+ ctx.foreground.globalCompositeOperation = __.composite;
161
+ ctx.foreground.globalAlpha = __.alpha;
162
+ ctx.highlight.lineWidth = 3;
163
+ ctx.shadows.strokeStyle = "#dadada";
164
+
165
+ return this;
166
+ };
167
+ pc.detectDimensions = function() {
168
+ pc.types(pc.detectDimensionTypes(__.data));
169
+ pc.dimensions(d3.keys(pc.types()));
170
+ return this;
171
+ };
172
+
173
+ // a better "typeof" from this post: http://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable
174
+ pc.toType = function(v) {
175
+ return ({}).toString.call(v).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
176
+ };
177
+
178
+ // try to coerce to number before returning type
179
+ pc.toTypeCoerceNumbers = function(v) {
180
+ if ((parseFloat(v) == v) && (v != null)) return "number";
181
+ return pc.toType(v);
182
+ };
183
+
184
+ // attempt to determine types of each dimension based on first row of data
185
+ pc.detectDimensionTypes = function(data) {
186
+ var types = {}
187
+ d3.keys(data[0])
188
+ .forEach(function(col) {
189
+ types[col] = pc.toTypeCoerceNumbers(data[0][col]);
190
+ });
191
+ return types;
192
+ };
193
+ pc.render = function() {
194
+ // try to autodetect dimensions and create scales
195
+ if (!__.dimensions.length) pc.detectDimensions();
196
+ if (!(__.dimensions[0] in yscale)) pc.autoscale();
197
+
198
+ pc.render[__.mode]();
199
+
200
+ events.render.call(this);
201
+ return this;
202
+ };
203
+
204
+ pc.render.default = function() {
205
+ pc.clear('foreground');
206
+ if (__.brushed) {
207
+ __.brushed.forEach(path_foreground);
208
+ } else {
209
+ __.data.forEach(path_foreground);
210
+ }
211
+ };
212
+
213
+ var rqueue = d3.renderQueue(path_foreground)
214
+ .rate(50)
215
+ .clear(function() { pc.clear('foreground'); });
216
+
217
+ pc.render.queue = function() {
218
+ if (__.brushed) {
219
+ rqueue(__.brushed);
220
+ } else {
221
+ rqueue(__.data);
222
+ }
223
+ };
224
+ pc.shadows = function() {
225
+ flags.shadows = true;
226
+ if (__.data.length > 0) paths(__.data, ctx.shadows);
227
+ return this;
228
+ };
229
+
230
+ // draw little dots on the axis line where data intersects
231
+ pc.axisDots = function() {
232
+ var ctx = pc.ctx.marks;
233
+ ctx.globalAlpha = d3.min([1/Math.pow(data.length, 1/2), 1]);
234
+ __.data.forEach(function(d) {
235
+ __.dimensions.map(function(p,i) {
236
+ ctx.fillRect(position(p)-0.75,yscale[p](d[p])-0.75,1.5,1.5);
237
+ });
238
+ });
239
+ return this;
240
+ };
241
+
242
+ // draw single polyline
243
+ function color_path(d, ctx) {
244
+ ctx.strokeStyle = d3.functor(__.color)(d);
245
+ ctx.beginPath();
246
+ __.dimensions.map(function(p,i) {
247
+ if (i == 0) {
248
+ ctx.moveTo(position(p),yscale[p](d[p]));
249
+ } else {
250
+ ctx.lineTo(position(p),yscale[p](d[p]));
251
+ }
252
+ });
253
+ ctx.stroke();
254
+ };
255
+
256
+ // draw many polylines of the same color
257
+ function paths(data, ctx) {
258
+ ctx.clearRect(-1,-1,w()+2,h()+2);
259
+ ctx.beginPath();
260
+ data.forEach(function(d) {
261
+ __.dimensions.map(function(p,i) {
262
+ if (i == 0) {
263
+ ctx.moveTo(position(p),yscale[p](d[p]));
264
+ } else {
265
+ ctx.lineTo(position(p),yscale[p](d[p]));
266
+ }
267
+ });
268
+ });
269
+ ctx.stroke();
270
+ };
271
+
272
+ function path_foreground(d) {
273
+ return color_path(d, ctx.foreground);
274
+ };
275
+
276
+ function path_highlight(d) {
277
+ return color_path(d, ctx.highlight);
278
+ };
279
+ pc.clear = function(layer) {
280
+ ctx[layer].clearRect(0,0,w()+2,h()+2);
281
+ return this;
282
+ };
283
+ pc.createAxes = function() {
284
+ if (g) pc.removeAxes();
285
+
286
+ // Add a group element for each dimension.
287
+ g = pc.svg.selectAll(".dimension")
288
+ .data(__.dimensions, function(d) { return d; })
289
+ .enter().append("svg:g")
290
+ .attr("class", "dimension")
291
+ .attr("transform", function(d) { return "translate(" + xscale(d) + ")"; })
292
+
293
+ // Add an axis and title.
294
+ g.append("svg:g")
295
+ .attr("class", "axis")
296
+ .attr("transform", "translate(0,0)")
297
+ .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
298
+ .append("svg:text")
299
+ .attr({
300
+ "text-anchor": "middle",
301
+ "y": 0,
302
+ "transform": "translate(0,-12)",
303
+ "x": 0,
304
+ "class": "label"
305
+ })
306
+ .text(function(d) {
307
+ return d in __.dimensionTitles ? __.dimensionTitles[d] : d; // dimension display names
308
+ })
309
+
310
+ flags.axes= true;
311
+ return this;
312
+ };
313
+
314
+ pc.removeAxes = function() {
315
+ g.remove();
316
+ return this;
317
+ };
318
+
319
+ pc.updateAxes = function() {
320
+ var g_data = pc.svg.selectAll(".dimension")
321
+ .data(__.dimensions, function(d) { return d; })
322
+
323
+ g_data.enter().append("svg:g")
324
+ .attr("class", "dimension")
325
+ .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
326
+ .style("opacity", 0)
327
+ .append("svg:g")
328
+ .attr("class", "axis")
329
+ .attr("transform", "translate(0,0)")
330
+ .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
331
+ .append("svg:text")
332
+ .attr({
333
+ "text-anchor": "middle",
334
+ "y": 0,
335
+ "transform": "translate(0,-12)",
336
+ "x": 0,
337
+ "class": "label"
338
+ })
339
+ .text(String);
340
+
341
+ g_data.exit().remove();
342
+
343
+ g = pc.svg.selectAll(".dimension");
344
+
345
+ g.transition().duration(1100)
346
+ .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
347
+ .style("opacity", 1)
348
+ if (flags.shadows) paths(__.data, ctx.shadows);
349
+ return this;
350
+ };
351
+
352
+ pc.brushable = function() {
353
+ if (!g) pc.createAxes();
354
+
355
+ // Add and store a brush for each axis.
356
+ g.append("svg:g")
357
+ .attr("class", "brush")
358
+ .each(function(d) {
359
+ d3.select(this).call(
360
+ yscale[d].brush = d3.svg.brush()
361
+ .y(yscale[d])
362
+ .on("brushstart", function() {
363
+ d3.event.sourceEvent.stopPropagation();
364
+ })
365
+ .on("brush", pc.brush)
366
+ );
367
+ })
368
+ .selectAll("rect")
369
+ .style("visibility", null)
370
+ .attr("x", -15)
371
+ .attr("width", 30)
372
+ flags.brushable = true;
373
+ return this;
374
+ };
375
+
376
+ // Jason Davies, http://bl.ocks.org/1341281
377
+ pc.reorderable = function() {
378
+ if (!g) pc.createAxes();
379
+
380
+ g.style("cursor", "move")
381
+ .call(d3.behavior.drag()
382
+ .on("dragstart", function(d) {
383
+ dragging[d] = this.__origin__ = xscale(d);
384
+ })
385
+ .on("drag", function(d) {
386
+ dragging[d] = Math.min(w(), Math.max(0, this.__origin__ += d3.event.dx));
387
+ __.dimensions.sort(function(a, b) { return position(a) - position(b); });
388
+ xscale.domain(__.dimensions);
389
+ pc.render();
390
+ g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
391
+ })
392
+ .on("dragend", function(d) {
393
+ delete this.__origin__;
394
+ delete dragging[d];
395
+ d3.select(this).transition().attr("transform", "translate(" + xscale(d) + ")");
396
+ pc.render();
397
+ }));
398
+ flags.reorderable = true;
399
+ return this;
400
+ };
401
+
402
+ // pairs of adjacent dimensions
403
+ pc.adjacent_pairs = function(arr) {
404
+ var ret = [];
405
+ for (var i = 0; i < arr.length-1; i++) {
406
+ ret.push([arr[i],arr[i+1]]);
407
+ };
408
+ return ret;
409
+ };
410
+ pc.interactive = function() {
411
+ flags.interactive = true;
412
+ return this;
413
+ };
414
+
415
+ // Get data within brushes
416
+ pc.brush = function() {
417
+ __.brushed = selected();
418
+ events.brush.call(pc,__.brushed);
419
+ pc.render();
420
+ };
421
+
422
+ // expose a few objects
423
+ pc.xscale = xscale;
424
+ pc.yscale = yscale;
425
+ pc.ctx = ctx;
426
+ pc.canvas = canvas;
427
+ pc.g = function() { return g; };
428
+
429
+ pc.brushReset = function(dimension) {
430
+ if (g) {
431
+ g.selectAll('.brush')
432
+ .each(function(d) {
433
+ d3.select(this).call(
434
+ yscale[d].brush.clear()
435
+ );
436
+ })
437
+ pc.brush();
438
+ }
439
+ return this;
440
+ };
441
+
442
+ // rescale for height, width and margins
443
+ // TODO currently assumes chart is brushable, and destroys old brushes
444
+ pc.resize = function() {
445
+ // selection size
446
+ pc.selection.select("svg")
447
+ .attr("width", __.width)
448
+ .attr("height", __.height)
449
+ pc.svg.attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
450
+
451
+ // scales
452
+ pc.autoscale();
453
+
454
+ // axes, destroys old brushes. the current brush state should pass through in the future
455
+ if (g) pc.createAxes().brushable();
456
+
457
+ events.resize.call(this, {width: __.width, height: __.height, margin: __.margin});
458
+ return this;
459
+ };
460
+
461
+ // highlight an array of data
462
+ pc.highlight = function(data) {
463
+ pc.clear("highlight");
464
+ d3.select(canvas.foreground).classed("faded", true);
465
+ data.forEach(path_highlight);
466
+ events.highlight.call(this,data);
467
+ return this;
468
+ };
469
+
470
+ // clear highlighting
471
+ pc.unhighlight = function(data) {
472
+ pc.clear("highlight");
473
+ d3.select(canvas.foreground).classed("faded", false);
474
+ return this;
475
+ };
476
+
477
+ // calculate 2d intersection of line a->b with line c->d
478
+ // points are objects with x and y properties
479
+ pc.intersection = function(a, b, c, d) {
480
+ return {
481
+ x: ((a.x * b.y - a.y * b.x) * (c.x - d.x) - (a.x - b.x) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x)),
482
+ y: ((a.x * b.y - a.y * b.x) * (c.y - d.y) - (a.y - b.y) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x))
483
+ };
484
+ };
485
+
486
+ function is_brushed(p) {
487
+ return !yscale[p].brush.empty();
488
+ };
489
+
490
+ // data within extents
491
+ function selected() {
492
+ var actives = __.dimensions.filter(is_brushed),
493
+ extents = actives.map(function(p) { return yscale[p].brush.extent(); });
494
+
495
+ // test if within range
496
+ var within = {
497
+ "date": function(d,p,dimension) {
498
+ return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
499
+ },
500
+ "number": function(d,p,dimension) {
501
+ return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
502
+ },
503
+ "string": function(d,p,dimension) {
504
+ return extents[dimension][0] <= yscale[p](d[p]) && yscale[p](d[p]) <= extents[dimension][1]
505
+ }
506
+ };
507
+
508
+ return __.data
509
+ .filter(function(d) {
510
+ return actives.every(function(p, dimension) {
511
+ return within[__.types[p]](d,p,dimension);
512
+ });
513
+ });
514
+ };
515
+
516
+ function position(d) {
517
+ var v = dragging[d];
518
+ return v == null ? xscale(d) : v;
519
+ }
520
+ pc.toString = function() { return "Parallel Coordinates: " + __.dimensions.length + " dimensions (" + d3.keys(__.data[0]).length + " total) , " + __.data.length + " rows"; };
521
+
522
+ pc.version = "0.2.2";
523
+
524
+ return pc;
525
+ };
526
+
527
+ d3.renderQueue = (function(func) {
528
+ var _queue = [], // data to be rendered
529
+ _rate = 10, // number of calls per frame
530
+ _clear = function() {}, // clearing function
531
+ _i = 0; // current iteration
532
+
533
+ var rq = function(data) {
534
+ if (data) rq.data(data);
535
+ rq.invalidate();
536
+ _clear();
537
+ rq.render();
538
+ };
539
+
540
+ rq.render = function() {
541
+ _i = 0;
542
+ var valid = true;
543
+ rq.invalidate = function() { valid = false; };
544
+
545
+ function doFrame() {
546
+ if (!valid) return true;
547
+ if (_i > _queue.length) return true;
548
+ var chunk = _queue.slice(_i,_i+_rate);
549
+ _i += _rate;
550
+ chunk.map(func);
551
+ }
552
+
553
+ d3.timer(doFrame);
554
+ };
555
+
556
+ rq.data = function(data) {
557
+ rq.invalidate();
558
+ _queue = data.slice(0);
559
+ return rq;
560
+ };
561
+
562
+ rq.rate = function(value) {
563
+ if (!arguments.length) return _rate;
564
+ _rate = value;
565
+ return rq;
566
+ };
567
+
568
+ rq.remaining = function() {
569
+ return _queue.length - _i;
570
+ };
571
+
572
+ // clear the canvas
573
+ rq.clear = function(func) {
574
+ if (!arguments.length) {
575
+ _clear();
576
+ return rq;
577
+ }
578
+ _clear = func;
579
+ return rq;
580
+ };
581
+
582
+ rq.invalidate = function() {};
583
+
584
+ return rq;
585
+ });