Published
Edited
May 8, 2021
3 forks
11 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
cloneDeep = (literals, maxDepth) => {
const cloneDeep = (literal, depth, maxDepth) => {
const clone = {name: literal.name};
if (literal.children && (depth < maxDepth)) {
clone.children = [];
for (const child of literal.children) {
const childClone = cloneDeep(child, depth + 1, maxDepth);
clone.children.push(childClone);
}
}
return clone;
}
return cloneDeep(literals, 0, maxDepth);
}
Insert cell
literals = ({
name: "alfa",
children: [
{
name: "bravo",
children: [
{
name: "delta",
children: [
{name: "hotel"},
{name: "india"}
]
},
{
name: "echo",
children: [
{name: "juliett"},
{name: "kilo"}
]
}
]
},
{
name: "charlie",
children: [
{
name: "foxtrot",
children: [
{name: "lima"},
{name: "mike"}
]
},
{
name: "golf",
children: [
{name: "november"},
{name: "oscar"}
]
}
]
}
]
})
Insert cell
Insert cell
Insert cell
verticalTree = (root) => {
const layOut = d3.tree()
.nodeSize([nodeWidth, nodeHeight])
.separation((a, b) => a.parent == b.parent ? 1 : nodeSeparation);
layOut(root);
const margin = {top: 20, right: 40, bottom: 20, left: 40};
const minNodeX = d3.min(root.descendants(), d => d.x);
const maxNodeX = d3.max(root.descendants(), d => d.x);
const planeWidth = maxNodeX - minNodeX;
const width = margin.left + planeWidth + margin.right;
const planeHeight = nodeHeight * root.height;
const height = margin.top + planeHeight + margin.bottom;
const svg = createCanvas("vertical-tree", width, height);
const plane = svg.append("g")
.attr("class", "plane")
.attr("transform", `translate(${width / 2},${margin.top})`);
const guidelineContainer = plane.append("g")
.attr("class", "guidelines")

guidelineContainer.append("rect")
.attr("x", minNodeX)
.attr("y", planeHeight - nodeHeight)
.attr("width", nodeWidth)
.attr("height", nodeHeight);
const linkContainter = plane.append("g")
.attr("class", "links");
const createPath = d3.linkVertical()
.x(d => d.x)
.y(d => d.y);
root.links().forEach(link => appendLink(linkContainter, createPath(link)));
const appendLabel = (container, node) => {
const isInnerNode = node.children != null;
const text = getText(node);
const dy = isInnerNode ? -7 : 13;
const labelContainer = container.append("g")
.attr("class", "label")
.attr("transform", `translate(0,${dy})`);
labelContainer.append("text")
.attr("class", "background")
.text(text);
labelContainer.append("text")
.text(text);
};
const nodeContainer = plane.append("g")
.attr("class", "nodes")
.attr("text-anchor", "middle");
root.each(node => {
const container = nodeContainer.append("g")
.attr("transform", `translate(${node.x},${node.y})`);

container.append("circle");
appendLabel(container, node);
});
return svg.node();
}
Insert cell
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();
}
Insert cell
radialTree = (root) => {
const outerRadius = nodeHeight * root.height;
const layOut = d3.tree()
.size([360, outerRadius])
.separation((a, b) => a.parent == b.parent ? 1 : nodeSeparation);
layOut(root);
const margin = {top: 20, right: 20, bottom: 20, left: 20};
const outerDiameter = outerRadius * 2;
const width = margin.left + outerDiameter + margin.right;
const height = margin.top + outerDiameter + margin.bottom;
const svg = createCanvas("radial-tree", width, height);
const center = {
x: width / 2,
y: height / 2
};
const plane = svg.append("g")
.attr("class", "plane")
.attr("transform", `translate(${center.x},${center.y})`);

const guidelineContainer = plane.append("g")
.attr("class", "guidelines");
for (let generation = 1; generation <= root.height; generation++) {
const guidelineRadius = nodeHeight * generation;
guidelineContainer.append("circle")
.attr("r", guidelineRadius);
}
const linkContainer = plane.append("g")
.attr("class", "links");
const generatePath = d3.linkRadial()
.angle(d => toRadian(d.x))
.radius(d => d.y);
root.links().forEach(link => appendLink(linkContainer, generatePath(link)));
const nodeContainer = plane.append("g")
.attr("class", "nodes");
const appendLabel = (container, node) => {
const isLeafNode = node.children === undefined;
const angleInDegree = node.x;
const textAnchor = (angleInDegree < 180) === isLeafNode ? "start" : "end";
const dx = (angleInDegree < 180) === isLeafNode ? 5 : -5;
const dy = 2.5;
const translate = `translate(${dx},${dy})`;
const transform = angleInDegree >= 180 ? `rotate(180) ${translate}` : translate;
const labelContainer = container.append("g")
.attr("class", "label")
.attr("text-anchor", textAnchor)
.attr("transform", transform);

const text = getText(node);
labelContainer.append("text")
.attr("class", "background")
.text(text);
labelContainer.append("text")
.text(text);
};
root.each(node => {
const angleInDegree = node.x;
const radius = node.y;
const container = nodeContainer.append("g")
.attr("transform", `rotate(${angleInDegree - 90.1}) translate(${radius},0)`);

container.append("circle");
appendLabel(container, node);
});
return svg.node();
}
Insert cell
createCanvas = (className, width, height) =>
d3.create("svg")
.attr("class", className)
.attr("width", width)
.attr("height", height)
Insert cell
Insert cell
getText = (node) => {
let text = node.data.name;
if (showNodeCoordinates) {
text += ` (${Math.round(node.x)},${Math.round(node.y)})`;
}
return text;
}
Insert cell
toRadian = (degree) => degree / 180 * Math.PI
Insert cell
d3 = require("d3@6")
Insert cell
html`<style>
.links path,
.guidelines rect,
.guidelines circle {
fill: none;
stroke: #555;
stroke-width: 1.5;
}

.links path {
stroke-opacity: 0.4;
}

.guidelines rect,
.guidelines circle {
stroke-opacity: 0.3;
stroke-dasharray: 2px, 2px;
}

.nodes circle {
fill: #555;
r: 2.5;
}

.nodes text {
font-family: sans-serif;
font-size: 10px;
}

.nodes text.background {
stroke: white;
stroke-width: 3;
}
</style>`
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more