horizontalTree = (root) => {
const layOut = d3.tree()
.nodeSize([nodeWidth, nodeHeight])
.separation((a, b) => a.parent == b.parent ? 1 : nodeSeparation);
layOut(root);
const margin = {top: 20, right: 100, bottom: 20, left: 60};
const planeWidth = nodeHeight * root.height;
const width = margin.left + planeWidth + margin.right;
const minNodeY = d3.min(root.descendants(), d => d.x);
const maxNodeY = d3.max(root.descendants(), d => d.x);
const planeHeight = maxNodeY - minNodeY;
const height = margin.top + planeHeight + margin.bottom;
const svg = createCanvas("horizontal-tree", width, height);
const plane = svg.append("g")
.attr("class", "plane")
.attr("transform", `translate(${margin.left},${height / 2})`);
const guidelineContainer = plane.append("g")
.attr("class", "guidelines");
guidelineContainer.append("rect")
.attr("x", planeWidth - nodeHeight)
.attr("y", minNodeY)
.attr("width", nodeHeight)
.attr("height", nodeWidth);
const linkContainer = plane.append("g")
.attr("class", "links");
const createPath = d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x);
root.links().forEach(link => appendLink(linkContainer, createPath(link)));
const nodeContainer = plane.append("g")
.attr("class", "nodes");
const getText = (node) => {
let text = node.data.name;
if (showNodeCoordinates) {
text += ` (${Math.round(node.y)},${Math.round(node.x)})`;
}
return text;
};
const appendLabel = (container, node) => {
const isInnerNode = node.children != null;
const textAnchor = isInnerNode ? "end" : "start";
const dx = isInnerNode ? -5 : 5;
const dy = 3;
const labelContainer = container.append("g")
.attr("class", "label")
.attr("text-anchor", textAnchor)
.attr("transform", `translate(${dx},${dy})`);
const text = getText(node);
labelContainer.append("text")
.attr("class", "background")
.text(text);
labelContainer.append("text")
.text(text);
};
root.each(node => {
const container = nodeContainer.append("g")
.attr("transform", `translate(${node.y},${node.x})`);
container.append("circle");
appendLabel(container, node);
});
return svg.node();
}