d3.parsets.js 24 KB

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