Public
Edited
May 14, 2024
3 forks
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mutable debug = {
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
nestedTreeNoTransitions = (origRoot, showOwnerLinks = false) => {
const root = prepareRoot(origRoot);
const nodes = root.descendants();
const links = root.links();
const height = (nodes.length + 1) * nodeSize;

const svg = d3
.create("svg")
// .attr("width", width / 2)
.attr("height", height)
.attr("viewBox", [-nodeSize / 2, (-nodeSize * 3) / 2, width / 2, height])
.attr(
"style",
"max-width: 100%; height: auto; font: 10px sans-serif; overflow: visible;"
);

const g = svg
.append("g")
.attr("transform", `translate(${showOwnerLinks ? 220 : 0},0)`)
.attr("name", "move-to-middle");

const linksGroup = g
.append("g")
.attr("fill", "none")
.attr("stroke", "#333")
.attr("name", "links");

const ownerGroup = g.append("g").attr("name", "owner-arcs");

const nodesGroup = g.append("g").attr("name", "nodes");

// initial draw
draw(nodes, links);

function draw(nodes, links) {
const link = linksGroup
.selectAll("path")

.data(links, (l) => `${l.source.data.id}-${l.target.data.id}`)
.join((enter) => enter.append("path").attr("d", linkPath));

const node = nodesGroup
.selectAll("g")
.data(nodes, (d) => d.data.id)
.join((enter) => drawNode(enter));

if (showOwnerLinks && !showOnlyDOM) {
const callees = nodes.filter((d) => {
// only show owner links between components, not host elements or fragments
return d.data.owner && d.data.name !== "[ ]"; // !d.data.isDOM &&
});

const ownerArcs = ownerGroup
.selectAll("path")
.data(callees)
.join("path")
.attr("stroke", "black")
.attr("stroke-dasharray", 2)
.attr("fill", "none")
.attr("d", (d, i) => {
// Arc from callerNode to current node (d)
const { x1, y1, x2, y2, r } = d.data;

return arc({ x1, y1, x2, y2, r }); // ideally wouldn't have arc but arc-y curve
});
}
}

return Object.assign(svg.node(), { draw });
}
Insert cell
nestedTree = (origRoot, showOwnerLinks = false) => {
const root = prepareRoot(origRoot);
const nodes = root.descendants();
const links = root.links();
const height = (nodes.length + 1) * nodeSize;

const svg = d3
.create("svg")
.attr("width", width / 2)
.attr("height", height)
.attr("viewBox", [-nodeSize / 2, (-nodeSize * 3) / 2, width / 2, height])
.attr(
"style",
"max-width: 100%; height: auto; font: 10px sans-serif; overflow: visible;"
);

const g = svg
.append("g")
.attr("transform", "translate(220,0)")
.attr("name", "move-to-middle");

const linksGroup = g
.append("g")
.attr("fill", "none")
.attr("stroke", "#333")
.attr("name", "links");

const nodesGroup = g.append("g").attr("name", "nodes");

const ownerGroup = g.append("g").attr("name", "owner-arcs");

// initial draw
draw(nodes, links);

function draw(nodes, links) {
const entry = g.transition().duration(700);
const update = g.transition().duration(700);
const exit = g.transition().duration(400);

const link = linksGroup
.selectAll("path")
.data(links, (l) => `${l.source.data.id}-${l.target.data.id}`)
.join(
(enter) =>
enter
.append("path")
.attr("d", linkPath)
.attr("stroke-dashoffset", strokeDashOffset)
.attr("stroke-dasharray", strokeDashArray)
.transition(entry)
.attr("stroke-dashoffset", 0),
(update) => {
return update.transition(update).attr("d", linkPath);
},
(exit) =>
exit
.attr("stroke-dashoffset", 0)
.transition(exit)
.attr("opacity", 0)
// animate pathlength to 0
.attr("stroke-dashoffset", strokeDashOffset)
.attr("stroke-dasharray", strokeDashArray)
.remove()
);

const node = nodesGroup
.selectAll("g")
.data(nodes, (d) => d.data.id)
.join(
(enter) => drawNode(enter),
(update) => {
update
.selectAll("circle")
.data((d) => d)
.transition(update)
.attr("cx", (d) => d.depth * nodeSize);

update
.selectAll("text")
.data((d) => d)
.transition(update)
.attr("x", (d) => d.depth * nodeSize + 6);

update.selectAll("circle").attr("cx", (d) => d.depth * nodeSize);

return update
.transition(update)
.attr("transform", (d) => `translate(0,${d.index * nodeSize})`);
},
(exit) => exit.transition().attr("opacity", 0).remove()
);

if (showOwnerLinks && !showOnlyDOM) {
const callees = nodes.filter((d) => {
// only show owner links between components, not host elements or fragments
return !d.data.isDOM && d.data.owner && d.data.name !== "[ ]";
});

const ownerArcs = ownerGroup
.selectAll("g")
.data(callees)
.join("g")
.attr("text-align", "middle");

ownerArcs
.append("path")
.attr("stroke", "black")
.attr("stroke-dasharray", 2)
.attr("fill", "none")
.attr("d", (d, i) => {
// Arc from callerNode to current node (d)
const { x1, y1, x2, y2, r } = d.data;

return arc({ x1, y1, x2, y2, r }); // ideally wouldn't have arc but arc-y curve, flatter curve with larger height
});

// TODO show only on hover or click
// callerArcs
// .append("text")
// .attr("transform", (d) => {
// const { x1, x2, y1, y2, r, id, owner } = d.data;

// // mutable debug = { x1, x2, y1, y2, id, owner };

// // NOT GOOD, WE NEED FIND POSITION ALONG ARC, EG 0.5 OF ARC
// return `translate(${x1 - r}, ${y1 + r})`;
// })
// .attr("stroke", "white")
// .attr("paint-order", "stroke")
// .text((d) => `${d.data.owner} calls ${d.data.id || d.data.name}`);
}

// function reset() {
// link.attr("stroke", "black");
// nodeCircles.attr("fill", nodeCircleFill);
// }

// function onClick(e, d) {
// reset();

// const highlight = new Set(d.ancestors());

// nodeCircles
// .filter((circle) => highlight.has(circle))
// .attr("fill", "hotpink");

// link
// .filter((link) => highlight.has(link.target))
// .attr("stroke", "hotpink")
// .raise();
// }
}

return Object.assign(svg.node(), { draw });
}
Insert cell
nodeCircleFill = (d) => d.data.name && isLowercase(d.data.name) && "white"
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
nestedTreeNoTransitions(makeOwnersRoot(memoInitRoot))
Insert cell
Insert cell
Insert cell
Insert cell
nestedTreeNoTransitions(makeOwnersRoot(memoSmartRoot))
Insert cell
Insert cell
postTestRoot = d3.hierarchy(postTest)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
nodeSize = 25
Insert cell
Insert cell
Insert cell
function drawNode(enter) {
const node = enter
.append("g")
.attr("name", (d) => d.data.name)
.attr("transform", (d) => `translate(0,${d.index * nodeSize})`)
.attr("opacity", (d) => (!showOnlyDOM || d.data.isDOM ? 1 : 0)); // or filter?

const nodeCircles = node
.append("circle")
.attr("cx", (d) => d.depth * nodeSize) // HOW TO UPDATE THIS?
.attr("r", 3)
.attr("fill", nodeCircleFill)
.attr("stroke", "black");

node
.append("text")
.attr("dy", "0.32em")
.attr("x", (d) => d.depth * nodeSize + 6) // HOW TO UPDATE THIS?
.attr("stroke", "white")
.attr("stroke-width", 0.2)
.attr("paint-order", "stroke")
.text((d) => d.data.name)
.append("tspan")
.text((d) => ` (${d.data.id})`);

node.append("title").text((d) =>
d
.ancestors()
.reverse()
.map((d) => d.data.name)
.join("/")
);
}
Insert cell
function linkPath(d) {
return `M${d.source.depth * nodeSize},${d.source.index * nodeSize}
V${d.target.index * nodeSize}
h${nodeSize}
`;
}
Insert cell
function strokeDashArray(d) {
const l = this.getTotalLength();
return `${l} ${l}`;
}
Insert cell
function strokeDashOffset(d) {
return this.getTotalLength();
}
Insert cell
function arc({ x1, y1, x2, y2, r }) {

return `
M${x1},${y1}
A${r},${r} 0,0,${y1 < y2 ? 0 : 1} ${x2},${y2}`;
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
postTest = ({
id: "1",
name: "Post",
children: [
{
id: "2",
name: "Article",
owner: "1",
children: [
{
id: "3",
name: "article",
owner: "2",
children: [
{
id: "4",
name: "h1",
owner: "2",
children: []
},
{
id: "5",
name: "Section",
owner: "1",
children: [
{
id: "6",
name: "section",
owner: "5",
children: [
{
id: "7",
name: "h2",
owner: "5",
children: []
},
{
id: "7",
name: "p",
owner: "1",
children: []
},
{
id: "8",
name: "Callout",
owner: "5",
children: [
{
id: "9",
name: "aside",
owner: "8",
children: [
{
id: "10",
name: "strong",
owner: "8",
children: []
}
]
}
]
}
]
}
]
}
]
}
]
}
]
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
isLowercase = (string) => {
const firstLetter = string[0];

return firstLetter === firstLetter.toLowerCase();
}
Insert cell
d3 = require("d3@7", "d3-interpolate-path@v2")
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