Public
Edited
Mar 17, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const root = pack(data);
let focus = root;
let view;
let transitioning = false;

const svg = d3
.create("svg")
.attr("viewBox", `-${width / 2} -${height / 2} ${width} ${height}`)
.style("display", "block")
.style("margin", "0 -14px")
.style("background", color(0))
.style("cursor", "pointer")
.on("mousemove", function (event, d) {
if (transitioning || event.target.tagName != "svg") return;
zoom(event, root);
});

const node = svg
.append("g")
.selectAll("circle")
.data(root.descendants().slice(1))
.join("circle")
.attr("fill", (d) => (d.children ? color(d.depth) : "white"))
.attr("pointer-events", (d) => (!d.children ? "none" : null))
.on("mouseover", function (event, d) {
d3.select(this).attr("stroke", "#000");
})
.on("mouseout", function (event, d) {
d3.select(this).attr("stroke", null);
})
.on("mousemove", function (event, d) {
if (transitioning || focus === d) return;
event.stopPropagation();
zoom(event, d);
});

const textOutlineColor = "#FFF"; /* white outline */
const textOutlineRadius = 0.8; /* width of outline in pixels */
const label = svg
.append("g")
.style("font", "20px Source Serif Pro")
.style("fill", "#000")
.style("text-shadow", () => {
const n = Math.ceil(
2 * Math.PI * textOutlineRadius
); /* number of shadows */
let str = "";
for (
let i = 0;
i < n;
i++ /* append shadows in n evenly distributed directions */
) {
const theta = (2 * Math.PI * i) / n;
str +=
textOutlineRadius * Math.cos(theta) +
"px " +
textOutlineRadius * Math.sin(theta) +
"px 0 " +
textOutlineColor +
(i == n - 1 ? "" : ",");
}
return str;
})
// .attr("stroke", "white")
// .attr("stroke-width", 0.3)
// .attr("stroke-linejoin", "round")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.selectAll("text")
.data(root.descendants())
.join("text")
.attr("font-weight", 900)
.style("fill-opacity", (d) => (d.parent === root ? 1 : 0))
.style("display", (d) => (d.parent === root ? "inline" : "none"))
.text((d) => d.data.name);

// const label = svg
// .append("g")
// .style("font", "20px sans-serif")
// .attr("pointer-events", "none")
// .attr("text-anchor", "middle")
// .selectAll("text")
// .data(root.descendants())
// .join("text");
// // .attr(
// // "transform",
// // (d, i, nodes) =>
// // `translate(${+d3
// // .select(nodes[i].parentNode)
// // .select("circle")
// // .attr("cx")},
// // ${+d3
// // .select(nodes[i].parentNode)
// // .select("circle")
// // .attr("cx")})
// // scale(${
// // (+d3
// // .select(nodes[i].parentNode)
// // .select("circle")
// // .attr("r") *
// // 0.6) /
// // textRadius(lines(words(d.data.name)))
// // })`
// // )
// label
// .selectAll("tspan")
// .data((d) => lines(words(d.data.name)))
// .join("tspan")
// //.attr("clip-path", d => d.clipUid)
// .attr("x", 0)
// .attr("y", (d, i, nodes) => `${i - nodes.length / 2 + 0.8}em`)
// .text((d) => d.text);

zoomTo([root.x, root.y, root.r * 2]);

function zoomTo(v) {
const k = height / v[2];

view = v;

label.attr(
"transform",
(d) => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`
);
node.attr(
"transform",
(d) => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`
);
node.attr("r", (d) => d.r * k);
}

function zoom(event, d) {
event.stopPropagation();
const focus0 = focus;

focus = d;

const transition = svg
.transition()
.duration(750)
.tween("zoom", (d) => {
const i = d3.interpolateZoom(view, [
focus.x,
focus.y,
focus.r * 2 + 15
]);
return (t) => zoomTo(i(t));
});

label
.filter(function (d) {
return d.parent === focus || this.style.display === "inline";
})
.transition(transition)
.style("fill-opacity", (d) => (d.parent === focus ? 1 : 0))
.on("start", function (d) {
transitioning = true;
if (d.parent === focus) {
this.style.display = "inline";
}
})
.on("end", function (d) {
// transitioning = false;
setTimeout(() => {
transitioning = false;
}, delay);
if (d.parent !== focus) {
this.style.display = "none";
}
});
}

return svg.node();
}
Insert cell
Insert cell
Insert cell
height = 1080
Insert cell
width = 1920
Insert cell
lineHeight = 40
Insert cell
Insert cell
pack = data => d3.pack()
.size([width, height])
.padding(5)
(d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value))
Insert cell
color = d3.scaleLinear()
.domain([0, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl)
Insert cell
function buildTree(nodes, links) {
// https://stackoverflow.com/a/68209716
const map = new Map(nodes.map((o) => [o.name, { ...o, children: [] }]));
for (let { source, target } of links)
map.get(source).children.push(map.get(target));
return map;
}
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
d3 = require("d3@6")
Insert cell
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