Public
Edited
Nov 8, 2024
2 forks
Importers
21 stars
Insert cell
Insert cell
chart = {
const container = html`<div class="tooltip-demo">
<style>
/*
* Be sure to set the tooltip's DOM element's styles!
*/
div.tooltip-demo > div.tooltip {
position: fixed;
display: none;
padding: 12px 6px;
background: #fff;
border: 1px solid #333;
pointer-events: none;
}
</style>
</div>`;

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

const div = d3
.select(container)
.append("div")
.classed("tooltip", true);

// create a new tooltip,
// passing it a selection for the tooltip's DOM element.
const tooltip = new Tooltip(div);

// function that sets the tooltip's HTML when revealed
// can be passed the hovered selection's datum
function tooltipContents(datum) {
const { x, y } = datum;
return `x: ${x}, y: ${y}`;
}

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")
.on("mouseover", (event, d) => {
// reveal the tooltip.
// pass the current datum (if desired),
// and a render function that returns an HTML string (required)
tooltip.display(d, tooltipContents);
})
.on("mouseout", () => {
// hide the tooltip
tooltip.hide();
})
.on("mousemove", event => {
// move the tooltip,
// passing the native browser event
tooltip.move(event);
});

return container;
}
Insert cell
height = 400
Insert cell
margin = ({ top: 10, right: 10, bottom: 10, left: 10 })
Insert cell
data = new Array(20).fill(undefined).map(() => ({
x: Math.floor(Math.random() * (width - margin.left - margin.right)),
y: Math.floor(Math.random() * (height - margin.top - margin.bottom))
}))
Insert cell
class Tooltip {
constructor(selection) {
if (!selection || !selection.size()) {
throw "Requires a tooltip div element selection";
}
this._selection = selection;
}

move(event) {
if (!event) return;
const margin = 20;
const { clientX: x, clientY: y } = event;
const { width, height } = this.selection.node().getBoundingClientRect();
const left = this.clamp(
margin,
x - width / 2,
window.innerWidth - width - margin
);
const top =
window.innerHeight > y + margin + height
? y + margin
: y - height - margin;
this.selection.style("top", `${top}px`).style("left", `${left}px`);
}

display(datum, callback) {
if (!callback || typeof callback !== "function") {
throw new Error(
"ToolTip.display requires a callback function that returns an HTML string"
);
}
this.selection.style("display", "block").html(callback(datum));
}

hide() {
this.selection.style("display", "none").html("");
}

clamp(min, d, max) {
return Math.max(min, Math.min(max, d));
}

get selection() {
return this._selection;
}

set selection(sel) {
if (sel && sel.size()) {
this._selection = sel;
} else {
throw new Error("selection must be a non-empty selected element");
}
}
}
Insert cell
d3 = require("d3-selection")
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