diff --git a/README.md b/README.md
index f07a2c40..ea5474ec 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ var treemap = d3.treemap();
* [Cluster](#cluster)
* [Tree](#tree)
* [Treemap](#treemap) ([Treemap Tiling](#treemap-tiling))
+* [Stacked Tree](#stackedtree)
* [Partition](#partition)
* [Pack](#pack)
@@ -493,6 +494,54 @@ Like [d3.treemapSquarify](#treemapSquarify), except preserves the topology (node
Specifies the desired aspect ratio of the generated rectangles. The *ratio* must be specified as a number greater than or equal to one. Note that the orientation of the generated rectangles (tall or wide) is not implied by the ratio; for example, a ratio of two will attempt to produce a mixture of rectangles whose *width*:*height* ratio is either 2:1 or 1:2. (However, you can approximately achieve this result by generating a square treemap at different dimensions, and then [stretching the treemap](https://observablehq.com/@d3/stretched-treemap) to the desired aspect ratio.) Furthermore, the specified *ratio* is merely a hint to the tiling algorithm; the rectangles are not guaranteed to have the specified aspect ratio. If not specified, the aspect ratio defaults to the golden ratio, φ = (1 + sqrt(5)) / 2, per [Kong *et al.*](http://vis.stanford.edu/papers/perception-treemaps)
+### Stacked Tree
+
+The **stacked tree layout** produces a dendrogram-like diagram based on Bisson and Blanch (2012). Stacked trees are a more compact version of the [cluster](#cluster) layout, useful for very large hierarchical clusters.
+
+# d3.stackedtree() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/stackedtree.js), [Examples](https://observablehq.com/@martialblog/d3-stacked-tree)
+
+Creates a new stacked tree layout with default settings.
+
+# stackedtree(root)
+
+Lays out the specified *root* [hierarchy](#hierarchy), assigning the following properties on *root* and its descendants:
+
+* *node*.x - the *x*-coordinate of the node
+* *node*.y - the *y*-coordinate of the node
+
+# stackedtree.size([size])
+
+If *size* is specified, sets this stacked tree layout’s size to the specified two-element array of numbers [*width*, *height*] and returns this stacked tree layout. If *size* is not specified, returns the current layout size, which defaults to [1, 1]. A layout size of null indicates that a [node size](#stackedtree_nodeSize) will be used instead.
+
+# stackedtree.nodeSize([size])
+
+If *size* is specified, sets this stackedtree layout’s node size to the specified two-element array of numbers [*width*, *height*] and returns this stackedtree layout. If *size* is not specified, returns the current node size, which defaults to null. A node size of null indicates that a [layout size](#stackedtree_size) will be used instead. When a node size is specified, the root node is always positioned at ⟨0, 0⟩.
+
+# stackedtree.separation([separation])
+
+If *separation* is specified, sets the separation accessor to the specified function and returns this stackedtree layout. If *separation* is not specified, returns the current separation accessor, which defaults to:
+
+```js
+function separation(a, b) {
+ return a.parent == b.parent ? 0 : 1;
+}
+```
+
+# stackedtree.stacking([stacking])
+
+If *stacking* is specified, sets the stacking accessor to the specified function and returns this stackedtree layout. If *stacking* is not specified, returns the current stacking accessor, which defaults to:
+
+```js
+function stacking(a, b, n) {
+ // With n being the length of the longest leaf array
+ return a.parent === b.parent ? 1 / n : 0;
+}
+```
+
+# stackedtree.ratio([ratio])
+
+If *ratio* is specified, sets the tree-to-stack ratio. Meaning, the lower the ratio the lower the focus on the tree. The ratio must be specified as a number between 0 and 1. If *ratio* is not specified, returns the current ratio, which defaults to: 1.
+
### Partition
[](https://observablehq.com/@d3/icicle)
diff --git a/src/index.js b/src/index.js
index cd4cca39..e52f0b18 100644
--- a/src/index.js
+++ b/src/index.js
@@ -4,6 +4,7 @@ export {default as pack} from "./pack/index.js";
export {default as packSiblings} from "./pack/siblings.js";
export {default as packEnclose} from "./pack/enclose.js";
export {default as partition} from "./partition.js";
+export {default as stackedtree} from "./stackedtree.js";
export {default as stratify} from "./stratify.js";
export {default as tree} from "./tree.js";
export {default as treemap} from "./treemap/index.js";
diff --git a/src/stackedtree.js b/src/stackedtree.js
new file mode 100644
index 00000000..8c028984
--- /dev/null
+++ b/src/stackedtree.js
@@ -0,0 +1,114 @@
+function defaultSeparation(a, b) {
+ return a.parent === b.parent ? 0 : 1;
+}
+
+function defaultStacking(a, b, n) {
+ return a.parent === b.parent ? 1 / n : 0;
+}
+
+function meanX(children) {
+ return children.reduce(meanXReduce, 0) / children.length;
+}
+
+function meanXReduce(x, c) {
+ return x + c.x;
+}
+
+function maxY(children) {
+ return children.reduce(maxYReduce, 1);
+}
+
+function maxYReduce(y, c) {
+ return Math.max(y, c.y);
+}
+
+function leafLeft(node) {
+ var children;
+ while (children = node.children) node = children[0];
+ return node;
+}
+
+function leafRight(node) {
+ var children;
+ while (children = node.children) node = children[children.length - 1];
+ return node;
+}
+
+export default function() {
+ var separation = defaultSeparation,
+ stacking = defaultStacking,
+ ratio = 1,
+ dx = 1,
+ dy = 1,
+ nodeSize = false;
+
+ function stackedtree(root) {
+ var previousNode,
+ stackHeight = 1,
+ y = 0,
+ x = 0;
+
+ // Find longest children array to calculate stacking distance
+ root.each(function(node){
+ var leaves = node.children;
+ stackHeight = leaves ? Math.max(node.children.length, stackHeight) : stackHeight;
+ })
+
+ // First walk, computing the initial x & y values.
+ root.eachAfter(function(node) {
+
+ // TODO: Is this flexible enough?
+ // Resetting y for new stack
+ y = previousNode && previousNode.parent !== node.parent ? 0 : y;
+
+ var children = node.children;
+ if (children) {
+ node.x = meanX(children);
+ node.y = ratio + maxY(children);
+ } else {
+ node.x = previousNode ? x += separation(node, previousNode) : 0;
+ node.y = previousNode ? y += stacking(node, previousNode, stackHeight) : 0;
+ previousNode = node;
+ }
+ });
+
+ var left = leafLeft(root),
+ right = leafRight(root),
+ x0 = left.x - separation(left, right) / 2,
+ x1 = right.x + separation(right, left) / 2;
+
+ // Second walk, normalizing x & y to the desired size.
+ return root.eachAfter(nodeSize ? function(node) {
+ node.x = (node.x - root.x) * dx;
+ node.y = (root.y - node.y) * dy;
+ } : function(node) {
+ node.x = (node.x - x0) / (x1 - x0) * dx;
+ node.y = (1 - (root.y ? node.y / root.y : 1)) * dy;
+ });
+ }
+
+ stackedtree.separation = function(x) {
+ return arguments.length ? (separation = x, stackedtree) : separation;
+ };
+
+ stackedtree.stacking = function(y) {
+ return arguments.length ? (stacking = y, stackedtree) : stacking;
+ };
+
+ stackedtree.ratio = function(x) {
+ // TODO: This a good solution?
+ // Tree-to-Stack Ratio from 0 to 1 (default: 1)
+ // Lower value means less emphasis on the tree, more on the stacks.
+ return arguments.length ? (ratio = x, stackedtree) : ratio;
+ };
+
+ stackedtree.size = function(x) {
+ return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], stackedtree) : (nodeSize ? null : [dx, dy]);
+ };
+
+ stackedtree.nodeSize = function(x) {
+ return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], stackedtree) : (nodeSize ? [dx, dy] : null);
+ };
+
+ return stackedtree;
+}