addTreeToSvgGroup = function (tree, g) {
const totalDepth = getDepth(tree);
function drawNode(node, shaper, depth = 0) {
if (shaper) {
const sc = 100;
let firstSegment = 0;
for (let i = 0; i <= sc; i++) {
const x = i / sc;
if (shaper(x, 0) - shaper(x, 1) != 0) {
firstSegment = Math.max(0, i - 1);
break;
}
}
const p = d3.path();
p.moveTo(firstSegment / sc, shaper(firstSegment / sc, 1));
for (let i = firstSegment + 1; i <= sc; i++) {
const x = i / sc;
p.lineTo(x, shaper(x, 1));
}
for (let i = sc; i >= firstSegment; i--) {
const x = i / sc;
p.lineTo(x, shaper(x, 0));
}
const clonePath = g.path(p.toString());
clonePath.fill(node.color);
clonePath.stroke("black");
clonePath.attr("stroke-opacity", 0.3);
clonePath.attr("vector-effect", "non-scaling-stroke");
clonePath.addClass("clone");
clonePath.data("clone", node.data);
} else {
shaper = (x, y) => y; // Make an initial shaper. Just a rectangle, no bell shape
}
// Children emerge as spread to better emphasize what their parent is
const spreadPositions = stackChildren(node, true);
// They end up as stacked to make the perception of the proportions easier
const stackedPositions = stackChildren(node, false);
const childDepth = depth + 1;
const fractionalChildDepth = childDepth / totalDepth;
// Make an interpolator that smoothly interpolates between the spread and stacked positions
const interpolatePositions = (childIdx, x) => {
let a = smoothstep(fractionalChildDepth, 1, x);
const s = 1 - spreadStrength;
a = a * (1 - s) + s;
return lerp(spreadPositions[childIdx], stackedPositions[childIdx], a);
};
for (let i = 0; i < node.children.length; i++) {
const childNode = node.children[i];
// Fractions indicate the proportion of the subclone in the whole sample.
// However, we need the fraction within its parent.
const childFraction = childNode.fraction / node.fraction;
// Create a new shaper for each children. Also apply parent's shaper.
const childShaper = (x, y) => {
// The fractionalChildDepth defines when the bell starts to appear
const v = fancystep(fractionalChildDepth, 1, x) * childFraction;
y = v * (y - 0.5) + 0.5 + interpolatePositions(i, x);
return shaper(x, y);
};
drawNode(childNode, childShaper, childDepth);
}
}
// Make a pseudo root that contains the actual root.
// Rationale: the drawNode function provides shapers for node's children.
// We need an imaginary node so that we get a shaper for the root node.
const pseudoRoot = {
fraction: 1,
children: [tree]
};
drawNode(
pseudoRoot, // root node
null, // no initial shaper. One is created for the true root node.
-1 // initial depth
);
return g;
}