Published
Edited
Sep 21, 2022
4 forks
Importers
63 stars
Insert cell
Insert cell
demo = {
const container = d3.select(
html`<div style="position:relative;">${tooltipTemplate}</div>`
);

const tooltipDiv = container.select(".tooltip");

const svg = container.append("svg").attr("viewBox", `0 0 ${width} ${height}`);

svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`)
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 5)
.attr("fill", "#333")
.call(tooltip, tooltipDiv);

return container.node();
}
Insert cell
Insert cell
tooltipId = DOM.uid().id
Insert cell
tooltipTemplate = html`<div class="tooltip tooltip-${tooltipId}">
${tooltipStyles}
<div class="tooltip-contents"></div>
</div>`
Insert cell
tooltip = (selectionGroup, tooltipDiv) => {
selectionGroup.each(function () {
d3.select(this)
.on("mouseover.tooltip", handleMouseover)
.on("mousemove.tooltip", handleMousemove)
.on("mouseleave.tooltip", handleMouseleave);
});

function handleMouseover() {
// show/reveal the tooltip, set its contents,
// style the element being hovered on
showTooltip();
setContents(d3.select(this).datum(), tooltipDiv);
setStyle(d3.select(this));
}

function handleMousemove(event) {
// update the tooltip's position
const [mouseX, mouseY] = d3.pointer(event, this);
// add the left & top margin values to account for the SVG g element transform
setPosition(mouseX + margin.left, mouseY + margin.top);
}

function handleMouseleave() {
// do things like hide the tooltip
// reset the style of the element being hovered on
hideTooltip();
resetStyle(d3.select(this));
}

function showTooltip() {
tooltipDiv.style("display", "block");
}

function hideTooltip() {
tooltipDiv.style("display", "none");
}

function setPosition(mouseX, mouseY) {
tooltipDiv
.style(
"top",
mouseY < height / 2 ? `${mouseY + MOUSE_POS_OFFSET}px` : "initial"
)
.style(
"right",
mouseX > width / 2
? `${width - mouseX + MOUSE_POS_OFFSET}px`
: "initial"
)
.style(
"bottom",
mouseY > height / 2
? `${height - mouseY + MOUSE_POS_OFFSET}px`
: "initial"
)
.style(
"left",
mouseX < width / 2 ? `${mouseX + MOUSE_POS_OFFSET}px` : "initial"
);
}
}
Insert cell
Insert cell
function setContents(datum, tooltipDiv) {
// customize this function to set the tooltip's contents however you see fit
tooltipDiv
.selectAll("p")
.data(Object.entries(datum))
.join("p")
.filter(([key, value]) => value !== null && value !== undefined)
.html(
([key, value]) =>
`<strong>${key}</strong>: ${
typeof value === "object" ? value.toLocaleString("en-US") : value
}`
);
}
Insert cell
function resetStyle(selection) {
selection.attr("fill", "#333");
}
Insert cell
function setStyle(selection) {
selection.attr("fill", "magenta");
}
Insert cell
tooltipStyles = htl.html`<style>
/* modify these styles to however you see fit */
div.tooltip-${tooltipId} {
box-sizing: border-box;
position: absolute;
display: none;
top: 0;
left: -100000000px;
padding: 8px 12px;
font-family: sans-serif;
font-size: 12px;
color: #333;
background-color: #fff;
border: 1px solid #333;
border-radius: 4px;
pointer-events: none;
z-index: 1;
}
div.tooltip-${tooltipId} p {
margin: 0;
}
</style>`
Insert cell
// padding between the tooltip and mouse cursor
MOUSE_POS_OFFSET = 8
Insert cell
Insert cell
height = 400
Insert cell
margin = ({ top: 10, right: 10, bottom: 10, left: 10 })
Insert cell
data = new Array(50).fill(undefined).map((d, i) => ({
x: Math.floor(Math.random() * (width - margin.left - margin.right)),
y: Math.floor(Math.random() * (height - margin.top - margin.bottom)),
id: i
}))
Insert cell
Insert cell
d3 = require("d3-selection@2")
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