Published
Edited
Dec 23, 2021
3 forks
3 stars
Insert cell
Insert cell
chart = {
const root = pack(data);
let focus = root;
let view;

const svg = d3.create("svg")
.attr("viewBox", `-${width / 2} -${height / 2} ${width} ${height}`)
.style("display", "block")
.style("margin", "0 -14px")
.style("background", 'white')
.style("cursor", "pointer")
.on("click", (event) => zoom(event, root));

const node = svg.append("g")
.selectAll("circle")
.data(root.descendants().slice(1))
.join("circle")
.attr("fill", d => d.children ? "#FFFCFF" : color(2))
.attr("stroke", d => d.children ? "lightgrey" : 'none')
.attr("stroke-width", function(d){
// console.log(d);
return !d.children? 0:(0.3)})
.attr("pointer-events", d => !d.children ? "none" : null)
.on("mouseover", function() { d3.select(this).attr("stroke", "purple"); })
.on("mouseout", function(event, d) { d3.select(this).attr("stroke", color(d.depth)); })
.on("click", (event, d) => focus !== d && (zoom(event, d), event.stopPropagation()))
;

const icons = svg.append("g")
.selectAll("g")
.data(root.descendants().slice(1))
.join("g")
.style('display', d=> d.depth>2&&d.data.value>13000?'inline':'none')
;
icons.append('image')
.attr("xlink:href", d=>icon1)
.attr('width', 10)
.attr('height', 10)
.classed('bounce-7', true)

const label = svg
.append("g")
.style("font", "10px sans-serif")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.selectAll("text")
.data(root.descendants())
.join("text")
.style("fill", "darkblue")
// .attr("stroke", 'white')
// .attr("stroke-width", 1)
.attr("stroke-align", "middle")
.append("textPath")
.attr("alignment-baseline", "middle")
.attr("font-size", "10px")
.attr("startOffset", "50%")
.attr("xlink:href", (d) => "#" + d.data.name)
.style("fill-opacity", (d) => (d.parent === root ? 1 : 0))
.style("display", (d) => (d.parent === root ? "inline" : "none"))
.text((d) => d.data.name);
const pathes = svg
.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.join("path")
.attr("id", (d) => d.data.name)
.attr("fill", "none")
zoomTo([root.x, root.y, root.r * 2]);

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

view = v;
console.log('k',k, v);
const x = (d) => (d.x - v[0]) * k
const y = (d) => (d.y - v[1]) * k

label.attr(
"transform",
(d) => `translate(${x(d)},${y(d)})`
);

icons.attr(
"transform",
(d) => `translate(${x(d)-5},${y(d)-5})`
)
node.attr(
"transform",
function(d) {console.log('node.attr', d);return `translate(${x(d)},${y(d)})`}
);
node.attr("r", (d) => d.r * k);
pathes.attr("d", (d) => {
const dx = x(d)
const dy = y(d)
const dr = d.r * k
return `M ${dx - dr} ${dy} a ${dr} ${dr} 0 0 1 ${dr * 2} 0`});
}

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

focus = d;

const transition = svg
.transition()
.duration(event.altKey ? 7500 : 750)
.tween("zoom", (d) => {
const i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2]);
console.log('i',view);
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) {
if (d.parent === focus) this.style.display = "inline";
})
.on("end", function (d) {
if (d.parent !== focus) this.style.display = "none";
});
}

return svg.node();
}
Insert cell
<style>
.bounce-7 {
animation-duration: 1s;
animation-iteration-count: infinite;
height: 10px;
width: 10px;
animation-name: bounce-7;
animation-timing-function: cubic-bezier(0.280, 0.840, 0.420, 1);
}
@keyframes bounce-7 {
0% { transform: scale(1,1) translateY(0); }
10% { transform: scale(1.1,.9) translateY(0); }
30% { transform: scale(.9,1.1) translateY(-4px); }
50% { transform: scale(1.05,.95) translateY(0); }
57% { transform: scale(1,1) translateY(-2px); }
64% { transform: scale(1,1) translateY(0); }
100% { transform: scale(1,1) translateY(0); }
}
</style>
Insert cell
icon1 = FileAttachment("Places.png").url()
Insert cell
data = FileAttachment("flare-2.json").json()
Insert cell
pack = data => d3.pack()
.size([width, height])
.padding(2)
(d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value))
Insert cell
width = 500
Insert cell
height = width
Insert cell
format = d3.format(",d")
Insert cell
d3 = require("d3@6")
Insert cell
color = d3.scaleLinear()
.domain([0, 5])
.range(["hsl(252,80%,100%)", "hsl(248,30%,80%)"])
.interpolate(d3.interpolateHcl)
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