Public
Edited
Nov 18, 2023
1 fork
Importers
20 stars
Insert cell
Insert cell
chart = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("overflow", "visible");

svg.append("defs").append("style").text(`
circle.highlight { fill: red; }
`);

const g = svg.append("g").attr("id", "conventions");

const dots = g
.append("g")
.attr("id", "dots")
.selectAll("circle")
.data(data)
.join("circle")
.attr("r", (d) => d[2])
.attr("cx", (d) => d[0])
.attr("cy", (d) => d[1]);

hover(dots, invalidation, { tipcontent });
return svg.node();
}
Insert cell
function tipcontent(datum) {
return `
<div>
<strong>${datum[3]}</strong>
<div style="max-height: 11em; overflow: auto">
<div><img src="https://picsum.photos/316/120?${datum[3]}" height=120 /></div>
${datum[4].join(" ").concat(".")}
</div>
</div>`;
}
Insert cell
Insert cell
Insert cell
mutable debug = ({})
Insert cell
function hover(
dots,
invalidation,
{
raise = true, // If true, will raise the selected node
r = 5, // Default dot radius
tipcontent = (d) =>
"<div>" +
Object.entries(d).map(([k, v]) => `<div>${k}: ${v}</div>`).join("\n") +
"</div>"
} = {}
) {
const hitRadius = 20;

const svg = d3.select(dots.nodes()[0].ownerSVGElement);

const data = dots.data();

const mouse = svg
.append("circle")
.attr("r", r)
.attr("fill", "none")
.style("pointer-events", "none");
const tipg = tippy(mouse.node(), {
theme: tippytheme,
allowHTML: true,
hideOnClick: false,
interactive: true,
appendTo: () => document.body
});

let positions, radiuses, quadtree, hovered;
invalidation && invalidation.then(tipg.destroy);

svg
.on("touchstart", function (event) {
// see https://observablehq.com/@d3/multitouch for details
event.preventDefault();
})
.on("pointerdown.action pointermove.action", (event) => {
// lazily compute quadtree on first interaction
if (!quadtree) {
const base = svg.node().getBoundingClientRect();
radiuses = dots.nodes().map((d) => +d3.select(d).attr("r"));
positions = Array.from(dots, (d, i) => {
const bbox = d.getBoundingClientRect();
// Positions should consider the radius of the node
return [bbox.x - base.x + radiuses[i], bbox.y - base.y + radiuses[i]];
});

quadtree = d3
.quadtree()
.x((i) => positions[i][0])
.y((i) => positions[i][1])
.addAll(d3.range(positions.length));
}

// find the closest point
const m = d3.pointer(event, svg.node()),
i = quadtree.find(...m),
position = positions[i];
// if we're close, highlight the tooltip / annotation
if (Math.hypot(position[0] - m[0], position[1] - m[1]) < hitRadius + 10) {
hovered = { position, datum: data[i] };

dots.classed("highlight", function (e) {
if (hovered.datum === e) {
mouse.attr("transform", `translate(${position})`);
d3.select(this)?.attr("r") &&
mouse.attr("r", +d3.select(this)?.attr("r") + 1);

return raise ? d3.select(this).raise() : d3.select(this);
}
});
} else {
dots.classed("highlight", false);
hovered = null;
}
})
.on("pointermove.tipmove pointerup.tipmove", (event) => {
if (!hovered) {
tipg.hide();
} else {
if (event.type === "pointerup") {
tipg.show();
tipg.setProps({
placement: hovered.position[1] < 150 ? "bottom" : "top"
});
tipg.setContent(tipcontent(hovered.datum));
}
}
})
//.style("background", "white") // doesn't seem necessary anymore?
.style("cursor", "pointer");

// use if you zoom
return {
reset: () => (quadtree = undefined)
};
}
Insert cell
Insert cell
data = Array.from({ length: 200 }, () => [
randomX(),
randomY(),
randomR(),
randomTitle(),
randomText()
])
Insert cell
randomX = d3.randomNormal(width / 2, 100)
Insert cell
randomY = d3.randomNormal(height / 2, 100)
Insert cell
randomR = d3.randomNormal(7, 2)
Insert cell
randomText = () =>
Array.from({ length: 100 }, rwg)
Insert cell
randomTitle = () => rwg()
Insert cell
height = 500
Insert cell
// https://www.npmjs.com/package/random-words
rwg = require("random-words@1.1.0").catch(() => window.words)
Insert cell
tippy = require("tippy.js@6.3.7")
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