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,663 @@
1
+ // Parallel Sets by Jason Davies, http://www.jasondavies.com/
2
+ // Functionality based on http://eagereyes.org/parallel-sets
3
+ (function() {
4
+ d3.parsets = function() {
5
+ var event = d3.dispatch("sortDimensions", "sortCategories"),
6
+ dimensions_ = autoDimensions,
7
+ dimensionFormat = String,
8
+ tooltip_ = defaultTooltip,
9
+ categoryTooltip = defaultCategoryTooltip,
10
+ value_,
11
+ spacing = 20,
12
+ width,
13
+ height,
14
+ tension = 1,
15
+ tension0,
16
+ duration = 500;
17
+
18
+ function parsets(selection) {
19
+ selection.each(function(data, i) {
20
+ var g = d3.select(this),
21
+ ordinal = d3.scale.ordinal(),
22
+ dragging = false,
23
+ dimensionNames = dimensions_.call(this, data, i),
24
+ dimensions = [],
25
+ tree = {children: {}, count: 0},
26
+ nodes,
27
+ total,
28
+ ribbon;
29
+
30
+ d3.select(window).on("mousemove.parsets." + ++parsetsId, unhighlight);
31
+
32
+ if (tension0 == null) tension0 = tension;
33
+ g.selectAll(".ribbon, .ribbon-mouse")
34
+ .data(["ribbon", "ribbon-mouse"], String)
35
+ .enter().append("g")
36
+ .attr("class", String);
37
+ updateDimensions();
38
+ if (tension != tension0) {
39
+ var t = d3.transition(g);
40
+ if (t.tween) t.tween("ribbon", tensionTween);
41
+ else tensionTween()(1);
42
+ }
43
+
44
+ function tensionTween() {
45
+ var i = d3.interpolateNumber(tension0, tension);
46
+ return function(t) {
47
+ tension0 = i(t);
48
+ ribbon.attr("d", ribbonPath);
49
+ };
50
+ }
51
+
52
+ function updateDimensions() {
53
+ // Cache existing bound dimensions to preserve sort order.
54
+ var dimension = g.selectAll("g.dimension"),
55
+ cache = {};
56
+ dimension.each(function(d) { cache[d.name] = d; });
57
+ dimensionNames.forEach(function(d) {
58
+ if (!cache.hasOwnProperty(d)) {
59
+ cache[d] = {name: d, categories: []};
60
+ }
61
+ dimensions.push(cache[d]);
62
+ });
63
+ dimensions.sort(compareY);
64
+ // Populate tree with existing nodes.
65
+ g.select(".ribbon").selectAll("path")
66
+ .each(function(d) {
67
+ var path = d.path.split("\0"),
68
+ node = tree,
69
+ n = path.length - 1;
70
+ for (var i = 0; i < n; i++) {
71
+ var p = path[i];
72
+ node = node.children.hasOwnProperty(p) ? node.children[p]
73
+ : node.children[p] = {children: {}, count: 0};
74
+ }
75
+ node.children[d.name] = d;
76
+ });
77
+ tree = buildTree(tree, data, dimensions.map(dimensionName), value_);
78
+ cache = dimensions.map(function(d) {
79
+ var t = {};
80
+ d.categories.forEach(function(c) {
81
+ t[c.name] = c;
82
+ });
83
+ return t;
84
+ });
85
+ (function categories(d, i) {
86
+ if (!d.children) return;
87
+ var dim = dimensions[i],
88
+ t = cache[i];
89
+ for (var k in d.children) {
90
+ if (!t.hasOwnProperty(k)) {
91
+ dim.categories.push(t[k] = {name: k});
92
+ }
93
+ categories(d.children[k], i + 1);
94
+ }
95
+ })(tree, 0);
96
+ ordinal.domain([]).range(d3.range(dimensions[0].categories.length));
97
+ nodes = layout(tree, dimensions, ordinal);
98
+ total = getTotal(dimensions);
99
+ dimensions.forEach(function(d) {
100
+ d.count = total;
101
+ });
102
+ dimension = dimension.data(dimensions, dimensionName);
103
+
104
+ var dEnter = dimension.enter().append("g")
105
+ .attr("class", "dimension")
106
+ .attr("transform", function(d) { return "translate(0," + d.y + ")"; })
107
+ .on("mousedown.parsets", cancelEvent);
108
+ dimension.each(function(d) {
109
+ d.y0 = d.y;
110
+ d.categories.forEach(function(d) { d.x0 = d.x; });
111
+ });
112
+ dEnter.append("rect")
113
+ .attr("width", width)
114
+ .attr("y", -45)
115
+ .attr("height", 45);
116
+ var textEnter = dEnter.append("text")
117
+ .attr("class", "dimension")
118
+ .attr("transform", "translate(0,-25)");
119
+ textEnter.append("tspan")
120
+ .attr("class", "name")
121
+ .text(dimensionFormatName);
122
+ textEnter.append("tspan")
123
+ .attr("class", "sort alpha")
124
+ .attr("dx", "2em")
125
+ .text("alpha »")
126
+ .on("mousedown.parsets", cancelEvent);
127
+ textEnter.append("tspan")
128
+ .attr("class", "sort size")
129
+ .attr("dx", "2em")
130
+ .text("size »")
131
+ .on("mousedown.parsets", cancelEvent);
132
+ dimension
133
+ .call(d3.behavior.drag()
134
+ .origin(identity)
135
+ .on("dragstart", function(d) {
136
+ dragging = true;
137
+ d.y0 = d.y;
138
+ })
139
+ .on("drag", function(d) {
140
+ d.y0 = d.y = d3.event.y;
141
+ for (var i = 1; i < dimensions.length; i++) {
142
+ if (height * dimensions[i].y < height * dimensions[i - 1].y) {
143
+ dimensions.sort(compareY);
144
+ dimensionNames = dimensions.map(dimensionName);
145
+ ordinal.domain([]).range(d3.range(dimensions[0].categories.length));
146
+ nodes = layout(tree = buildTree({children: {}, count: 0}, data, dimensionNames, value_), dimensions, ordinal);
147
+ total = getTotal(dimensions);
148
+ g.selectAll(".ribbon, .ribbon-mouse").selectAll("path").remove();
149
+ updateRibbons();
150
+ updateCategories(dimension);
151
+ dimension.transition().duration(duration)
152
+ .attr("transform", translateY)
153
+ .tween("ribbon", ribbonTweenY);
154
+ event.sortDimensions();
155
+ break;
156
+ }
157
+ }
158
+ d3.select(this)
159
+ .attr("transform", "translate(0," + d.y + ")")
160
+ .transition();
161
+ ribbon.filter(function(r) { return r.source.dimension === d || r.target.dimension === d; })
162
+ .attr("d", ribbonPath);
163
+ })
164
+ .on("dragend", function(d) {
165
+ dragging = false;
166
+ unhighlight();
167
+ var y0 = 45,
168
+ dy = (height - y0 - 2) / (dimensions.length - 1);
169
+ dimensions.forEach(function(d, i) {
170
+ d.y = y0 + i * dy;
171
+ });
172
+ transition(d3.select(this))
173
+ .attr("transform", "translate(0," + d.y + ")")
174
+ .tween("ribbon", ribbonTweenY);
175
+ }));
176
+ dimension.select("text").select("tspan.sort.alpha")
177
+ .on("click.parsets", sortBy("alpha", function(a, b) { return a.name < b.name ? 1 : -1; }, dimension));
178
+ dimension.select("text").select("tspan.sort.size")
179
+ .on("click.parsets", sortBy("size", function(a, b) { return a.count - b.count; }, dimension));
180
+ dimension.transition().duration(duration)
181
+ .attr("transform", function(d) { return "translate(0," + d.y + ")"; })
182
+ .tween("ribbon", ribbonTweenY);
183
+ dimension.exit().remove();
184
+
185
+ updateCategories(dimension);
186
+ updateRibbons();
187
+ }
188
+
189
+ function sortBy(type, f, dimension) {
190
+ return function(d) {
191
+ var direction = this.__direction = -(this.__direction || 1);
192
+ d3.select(this).text(direction > 0 ? type + " »" : "« " + type);
193
+ d.categories.sort(function() { return direction * f.apply(this, arguments); });
194
+ nodes = layout(tree, dimensions, ordinal);
195
+ updateCategories(dimension);
196
+ updateRibbons();
197
+ event.sortCategories();
198
+ };
199
+ }
200
+
201
+ function updateRibbons() {
202
+ ribbon = g.select(".ribbon").selectAll("path")
203
+ .data(nodes, function(d) { return d.path; });
204
+ ribbon.enter().append("path")
205
+ .each(function(d) {
206
+ d.source.x0 = d.source.x;
207
+ d.target.x0 = d.target.x;
208
+ })
209
+ .attr("class", function(d) { return "category-" + d.major; })
210
+ .attr("d", ribbonPath);
211
+ ribbon.sort(function(a, b) { return b.count - a.count; });
212
+ ribbon.exit().remove();
213
+ var mouse = g.select(".ribbon-mouse").selectAll("path")
214
+ .data(nodes, function(d) { return d.path; });
215
+ mouse.enter().append("path")
216
+ .on("mousemove.parsets", function(d) {
217
+ ribbon.classed("active", false);
218
+ if (dragging) return;
219
+ highlight(d = d.node, true);
220
+ showTooltip(tooltip_.call(this, d));
221
+ d3.event.stopPropagation();
222
+ });
223
+ mouse
224
+ .sort(function(a, b) { return b.count - a.count; })
225
+ .attr("d", ribbonPathStatic);
226
+ mouse.exit().remove();
227
+ }
228
+
229
+ // Animates the x-coordinates only of the relevant ribbon paths.
230
+ function ribbonTweenX(d) {
231
+ var nodes = [d],
232
+ r = ribbon.filter(function(r) {
233
+ var s, t;
234
+ if (r.source.node === d) nodes.push(s = r.source);
235
+ if (r.target.node === d) nodes.push(t = r.target);
236
+ return s || t;
237
+ }),
238
+ i = nodes.map(function(d) { return d3.interpolateNumber(d.x0, d.x); }),
239
+ n = nodes.length;
240
+ return function(t) {
241
+ for (var j = 0; j < n; j++) nodes[j].x0 = i[j](t);
242
+ r.attr("d", ribbonPath);
243
+ };
244
+ }
245
+
246
+ // Animates the y-coordinates only of the relevant ribbon paths.
247
+ function ribbonTweenY(d) {
248
+ var r = ribbon.filter(function(r) { return r.source.dimension.name == d.name || r.target.dimension.name == d.name; }),
249
+ i = d3.interpolateNumber(d.y0, d.y);
250
+ return function(t) {
251
+ d.y0 = i(t);
252
+ r.attr("d", ribbonPath);
253
+ };
254
+ }
255
+
256
+ // Highlight a node and its descendants, and optionally its ancestors.
257
+ function highlight(d, ancestors) {
258
+ if (dragging) return;
259
+ var highlight = [];
260
+ (function recurse(d) {
261
+ highlight.push(d);
262
+ for (var k in d.children) recurse(d.children[k]);
263
+ })(d);
264
+ highlight.shift();
265
+ if (ancestors) while (d) highlight.push(d), d = d.parent;
266
+ ribbon.filter(function(d) {
267
+ var active = highlight.indexOf(d.node) >= 0;
268
+ if (active) this.parentNode.appendChild(this);
269
+ return active;
270
+ }).classed("active", true);
271
+ }
272
+
273
+ // Unhighlight all nodes.
274
+ function unhighlight() {
275
+ if (dragging) return;
276
+ ribbon.classed("active", false);
277
+ hideTooltip();
278
+ }
279
+
280
+ function updateCategories(g) {
281
+ var category = g.selectAll("g.category")
282
+ .data(function(d) { return d.categories; }, function(d) { return d.name; });
283
+ var categoryEnter = category.enter().append("g")
284
+ .attr("class", "category")
285
+ .attr("transform", function(d) { return "translate(" + d.x + ")"; });
286
+ category.exit().remove();
287
+ category
288
+ .on("mousemove.parsets", function(d) {
289
+ ribbon.classed("active", false);
290
+ if (dragging) return;
291
+ d.nodes.forEach(function(d) { highlight(d); });
292
+ showTooltip(categoryTooltip.call(this, d));
293
+ d3.event.stopPropagation();
294
+ })
295
+ .on("mouseout.parsets", unhighlight)
296
+ .on("mousedown.parsets", cancelEvent)
297
+ .call(d3.behavior.drag()
298
+ .origin(identity)
299
+ .on("dragstart", function(d) {
300
+ dragging = true;
301
+ d.x0 = d.x;
302
+ })
303
+ .on("drag", function(d) {
304
+ d.x = d3.event.x;
305
+ var categories = d.dimension.categories;
306
+ for (var i = 0, c = categories[0]; ++i < categories.length;) {
307
+ if (c.x + c.dx / 2 > (c = categories[i]).x + c.dx / 2) {
308
+ categories.sort(function(a, b) { return a.x + a.dx / 2 - b.x - b.dx / 2; });
309
+ nodes = layout(tree, dimensions, ordinal);
310
+ updateRibbons();
311
+ updateCategories(g);
312
+ highlight(d.node);
313
+ event.sortCategories();
314
+ break;
315
+ }
316
+ }
317
+ var x = 0,
318
+ p = spacing / (categories.length - 1);
319
+ categories.forEach(function(e) {
320
+ if (d === e) e.x0 = d3.event.x;
321
+ e.x = x;
322
+ x += e.count / total * (width - spacing) + p;
323
+ });
324
+ d3.select(this)
325
+ .attr("transform", function(d) { return "translate(" + d.x0 + ")"; })
326
+ .transition();
327
+ ribbon.filter(function(r) { return r.source.node === d || r.target.node === d; })
328
+ .attr("d", ribbonPath);
329
+ })
330
+ .on("dragend", function(d) {
331
+ dragging = false;
332
+ unhighlight();
333
+ updateRibbons();
334
+ transition(d3.select(this))
335
+ .attr("transform", "translate(" + d.x + ")")
336
+ .tween("ribbon", ribbonTweenX);
337
+ }));
338
+ category.transition().duration(duration)
339
+ .attr("transform", function(d) { return "translate(" + d.x + ")"; })
340
+ .tween("ribbon", ribbonTweenX);
341
+
342
+ categoryEnter.append("rect")
343
+ .attr("width", function(d) { return d.dx; })
344
+ .attr("y", -20)
345
+ .attr("height", 20);
346
+ categoryEnter.append("line")
347
+ .style("stroke-width", 2);
348
+ categoryEnter.append("text")
349
+ .attr("dy", "-.3em");
350
+ category.select("rect")
351
+ .attr("width", function(d) { return d.dx; })
352
+ .attr("class", function(d) {
353
+ return "category-" + (d.dimension === dimensions[0] ? ordinal(d.name) : "background");
354
+ });
355
+ category.select("line")
356
+ .attr("x2", function(d) { return d.dx; });
357
+ category.select("text")
358
+ .text(truncateText(function(d) { return d.name; }, function(d) { return d.dx; }));
359
+ }
360
+ });
361
+ }
362
+
363
+ parsets.dimensionFormat = function(_) {
364
+ if (!arguments.length) return dimensionFormat;
365
+ dimensionFormat = _;
366
+ return parsets;
367
+ };
368
+
369
+ parsets.dimensions = function(_) {
370
+ if (!arguments.length) return dimensions_;
371
+ dimensions_ = d3.functor(_);
372
+ return parsets;
373
+ };
374
+
375
+ parsets.value = function(_) {
376
+ if (!arguments.length) return value_;
377
+ value_ = d3.functor(_);
378
+ return parsets;
379
+ };
380
+
381
+ parsets.width = function(_) {
382
+ if (!arguments.length) return width;
383
+ width = +_;
384
+ return parsets;
385
+ };
386
+
387
+ parsets.height = function(_) {
388
+ if (!arguments.length) return height;
389
+ height = +_;
390
+ return parsets;
391
+ };
392
+
393
+ parsets.spacing = function(_) {
394
+ if (!arguments.length) return spacing;
395
+ spacing = +_;
396
+ return parsets;
397
+ };
398
+
399
+ parsets.tension = function(_) {
400
+ if (!arguments.length) return tension;
401
+ tension = +_;
402
+ return parsets;
403
+ };
404
+
405
+ parsets.duration = function(_) {
406
+ if (!arguments.length) return duration;
407
+ duration = +_;
408
+ return parsets;
409
+ };
410
+
411
+ parsets.tooltip = function(_) {
412
+ if (!arguments.length) return tooltip;
413
+ tooltip = _ == null ? defaultTooltip : _;
414
+ return parsets;
415
+ };
416
+
417
+ parsets.categoryTooltip = function(_) {
418
+ if (!arguments.length) return categoryTooltip;
419
+ categoryTooltip = _ == null ? defaultCategoryTooltip : _;
420
+ return parsets;
421
+ };
422
+
423
+ var body = d3.select("body");
424
+ var tooltip = body.append("div")
425
+ .style("display", "none")
426
+ .attr("class", "parsets tooltip");
427
+
428
+ return d3.rebind(parsets, event, "on").value(1).width(960).height(600);
429
+
430
+ function dimensionFormatName(d, i) {
431
+ return dimensionFormat.call(this, d.name, i);
432
+ }
433
+
434
+ function showTooltip(html) {
435
+ var m = d3.mouse(body.node());
436
+ tooltip
437
+ .style("display", null)
438
+ .style("left", m[0] + 30 + "px")
439
+ .style("top", m[1] - 20 + "px")
440
+ .html(html);
441
+ }
442
+
443
+ function hideTooltip() {
444
+ tooltip.style("display", "none");
445
+ }
446
+
447
+ function transition(g) {
448
+ return duration ? g.transition().duration(duration).ease(parsetsEase) : g;
449
+ }
450
+
451
+ function layout(tree, dimensions, ordinal) {
452
+ var nodes = [],
453
+ nd = dimensions.length,
454
+ y0 = 45,
455
+ dy = (height - y0 - 2) / (nd - 1);
456
+ dimensions.forEach(function(d, i) {
457
+ d.categories.forEach(function(c) {
458
+ c.dimension = d;
459
+ c.count = 0;
460
+ c.nodes = [];
461
+ });
462
+ d.y = y0 + i * dy;
463
+ });
464
+
465
+ // Compute per-category counts.
466
+ var total = (function rollup(d, i) {
467
+ if (!d.children) return d.count;
468
+ var dim = dimensions[i],
469
+ total = 0;
470
+ dim.categories.forEach(function(c) {
471
+ var child = d.children[c.name];
472
+ if (!child) return;
473
+ c.nodes.push(child);
474
+ var count = rollup(child, i + 1);
475
+ c.count += count;
476
+ total += count;
477
+ });
478
+ return total;
479
+ })(tree, 0);
480
+
481
+ // Stack the counts.
482
+ dimensions.forEach(function(d) {
483
+ d.categories = d.categories.filter(function(d) { return d.count; });
484
+ var x = 0,
485
+ p = spacing / (d.categories.length - 1);
486
+ d.categories.forEach(function(c) {
487
+ c.x = x;
488
+ c.dx = c.count / total * (width - spacing);
489
+ c.in = {dx: 0};
490
+ c.out = {dx: 0};
491
+ x += c.dx + p;
492
+ });
493
+ });
494
+
495
+ var dim = dimensions[0];
496
+ dim.categories.forEach(function(c) {
497
+ var k = c.name;
498
+ if (tree.children.hasOwnProperty(k)) {
499
+ recurse(c, {node: tree.children[k], path: k}, 1, ordinal(k));
500
+ }
501
+ });
502
+
503
+ function recurse(p, d, depth, major) {
504
+ var node = d.node,
505
+ dimension = dimensions[depth];
506
+ dimension.categories.forEach(function(c) {
507
+ var k = c.name;
508
+ if (!node.children.hasOwnProperty(k)) return;
509
+ var child = node.children[k];
510
+ child.path = d.path + "\0" + k;
511
+ var target = child.target || {node: c, dimension: dimension};
512
+ target.x = c.in.dx;
513
+ target.dx = child.count / total * (width - spacing);
514
+ c.in.dx += target.dx;
515
+ var source = child.source || {node: p, dimension: dimensions[depth - 1]};
516
+ source.x = p.out.dx;
517
+ source.dx = target.dx;
518
+ p.out.dx += source.dx;
519
+
520
+ child.node = child;
521
+ child.source = source;
522
+ child.target = target;
523
+ child.major = major;
524
+ nodes.push(child);
525
+ if (depth + 1 < dimensions.length) recurse(c, child, depth + 1, major);
526
+ });
527
+ }
528
+ return nodes;
529
+ }
530
+
531
+ // Dynamic path string for transitions.
532
+ function ribbonPath(d) {
533
+ var s = d.source,
534
+ t = d.target;
535
+ return ribbonPathString(s.node.x0 + s.x0, s.dimension.y0, s.dx, t.node.x0 + t.x0, t.dimension.y0, t.dx, tension0);
536
+ }
537
+
538
+ // Static path string for mouse handlers.
539
+ function ribbonPathStatic(d) {
540
+ var s = d.source,
541
+ t = d.target;
542
+ return ribbonPathString(s.node.x + s.x, s.dimension.y, s.dx, t.node.x + t.x, t.dimension.y, t.dx, tension);
543
+ }
544
+
545
+ function ribbonPathString(sx, sy, sdx, tx, ty, tdx, tension) {
546
+ var m0, m1;
547
+ return (tension === 1 ? [
548
+ "M", [sx, sy],
549
+ "L", [tx, ty],
550
+ "h", tdx,
551
+ "L", [sx + sdx, sy],
552
+ "Z"]
553
+ : ["M", [sx, sy],
554
+ "C", [sx, m0 = tension * sy + (1 - tension) * ty], " ",
555
+ [tx, m1 = tension * ty + (1 - tension) * sy], " ", [tx, ty],
556
+ "h", tdx,
557
+ "C", [tx + tdx, m1], " ", [sx + sdx, m0], " ", [sx + sdx, sy],
558
+ "Z"]).join("");
559
+ }
560
+
561
+ function compareY(a, b) {
562
+ a = height * a.y, b = height * b.y;
563
+ return a < b ? -1 : a > b ? 1 : a >= b ? 0 : a <= a ? -1 : b <= b ? 1 : NaN;
564
+ }
565
+ };
566
+ d3.parsets.tree = buildTree;
567
+
568
+ function autoDimensions(d) {
569
+ return d.length ? d3.keys(d[0]).sort() : [];
570
+ }
571
+
572
+ function cancelEvent() {
573
+ d3.event.stopPropagation();
574
+ d3.event.preventDefault();
575
+ }
576
+
577
+ function dimensionName(d) { return d.name; }
578
+
579
+ function getTotal(dimensions) {
580
+ return dimensions[0].categories.reduce(function(a, d) {
581
+ return a + d.count;
582
+ }, 0);
583
+ }
584
+
585
+ // Given a text function and width function, truncates the text if necessary to
586
+ // fit within the given width.
587
+ function truncateText(text, width) {
588
+ return function(d, i) {
589
+ var t = this.textContent = text(d, i),
590
+ w = width(d, i);
591
+ if (this.getComputedTextLength() < w) return t;
592
+ this.textContent = "…" + t;
593
+ var lo = 0,
594
+ hi = t.length + 1,
595
+ x;
596
+ while (lo < hi) {
597
+ var mid = lo + hi >> 1;
598
+ if ((x = this.getSubStringLength(0, mid)) < w) lo = mid + 1;
599
+ else hi = mid;
600
+ }
601
+ return lo > 1 ? t.substr(0, lo - 2) + "…" : "";
602
+ };
603
+ }
604
+
605
+ var percent = d3.format("%"),
606
+ comma = d3.format(",f"),
607
+ parsetsEase = "elastic",
608
+ parsetsId = 0;
609
+
610
+ // Construct tree of all category counts for a given ordered list of
611
+ // dimensions. Similar to d3.nest, except we also set the parent.
612
+ function buildTree(root, data, dimensions, value) {
613
+ zeroCounts(root);
614
+ var n = data.length,
615
+ nd = dimensions.length;
616
+ for (var i = 0; i < n; i++) {
617
+ var d = data[i],
618
+ v = +value(d, i),
619
+ node = root;
620
+ for (var j = 0; j < nd; j++) {
621
+ var dimension = dimensions[j],
622
+ category = d[dimension],
623
+ children = node.children;
624
+ node.count += v;
625
+ node = children.hasOwnProperty(category) ? children[category]
626
+ : children[category] = {
627
+ children: j === nd - 1 ? null : {},
628
+ count: 0,
629
+ parent: node,
630
+ dimension: dimension,
631
+ name: category
632
+ };
633
+ }
634
+ node.count += v;
635
+ }
636
+ return root;
637
+ }
638
+
639
+ function zeroCounts(d) {
640
+ d.count = 0;
641
+ if (d.children) {
642
+ for (var k in d.children) zeroCounts(d.children[k]);
643
+ }
644
+ }
645
+
646
+ function identity(d) { return d; }
647
+
648
+ function translateY(d) { return "translate(0," + d.y + ")"; }
649
+
650
+ function defaultTooltip(d) {
651
+ var count = d.count,
652
+ path = [];
653
+ while (d.parent) {
654
+ if (d.name) path.unshift(d.name);
655
+ d = d.parent;
656
+ }
657
+ return path.join(" → ") + "<br>" + comma(count) + " (" + percent(count / d.count) + ")";
658
+ }
659
+
660
+ function defaultCategoryTooltip(d) {
661
+ return d.name + "<br>" + comma(d.count) + " (" + percent(d.count / d.dimension.count) + ")";
662
+ }
663
+ })();