rails-data-explorer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ });