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,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
+ })();