Тултип (tooltip) в визуализациях — это динамический элемент интерфейса, отображающий контекстную информацию при наведении курсора на графический объект. В экосистеме D3.js тултипы реализуются вручную, что даёт полный контроль над их поведением, стилизацией и производительностью.
Основная задача тултипа — связывать абстрактную визуализацию с
конкретными данными, не перегружая график постоянными подписями. В D3.js
существует два основных подхода: использование HTML-элемента
div поверх SVG-контейнера и создание тултипа
непосредственно внутри SVG.
Вся логика тултипов строится на событийной модели:
mouseover — вход курсора в область элементаmousemove — перемещение курсораmouseout — выход курсораmouseenter / mouseleave — альтернативные
события без всплытияКаждое событие предоставляет доступ к данным элемента
(datum) и координатам указателя, что позволяет динамически
формировать содержимое тултипа.
Ключевой механизм:
.data()function(event, d) в обработчикахevent или
d3.pointer(event)Подход с div считается наиболее гибким и
распространённым. Он отделяет визуализацию (SVG) от интерфейсных
элементов (HTML), что упрощает стилизацию через CSS.
const tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "rgba(0,0,0,0.8)")
.style("color", "#fff")
.style("padding", "6px 10px")
.style("border-radius", "4px")
.style("font-size", "12px")
.style("pointer-events", "none")
.style("opacity", 0);
Ключевой момент — pointer-events: none, предотвращающий
«залипание» курсора на тултипе.
circles
.on("mouseover", function(event, d) {
tooltip
.style("opacity", 1)
.html(`Значение: ${d.value}`);
})
.on("mousemove", function(event) {
tooltip
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY + 10) + "px");
})
.on("mouseout", function() {
tooltip.style("opacity", 0);
});
Такой подход обеспечивает:
Вместо pageX/pageY часто применяется:
.on("mousemove", function(event) {
const [x, y] = d3.pointer(event);
});
Это особенно важно при масштабировании (zoom) и
трансформациях SVG, поскольку координаты привязаны к локальной системе
визуализации.
Несмотря на гибкость, существуют ограничения:
transform: scaleПри высоконагруженных визуализациях (тысячи элементов) частые
обновления div могут создавать лишние reflow/repaint.
SVG-ориентированный подход предполагает размещение текста прямо внутри графического контекста.
const tooltip = svg.append("text")
.attr("class", "tooltip")
.attr("fill", "#000")
.attr("visibility", "hidden");
circles
.on("mouseover", function(event, d) {
tooltip
.attr("visibility", "visible")
.text(d.value);
})
.on("mousemove", function(event) {
const [x, y] = d3.pointer(event);
tooltip
.attr("x", x + 10)
.attr("y", y - 10);
})
.on("mouseout", function() {
tooltip.attr("visibility", "hidden");
});
Этот способ сохраняет тултип в пределах SVG-сцены и упрощает синхронизацию координат.
SVG-реализация имеет структурные ограничения:
Для обхода используется <tspan>:
tooltip.selectAll("tspan")
.data(["Строка 1", "Строка 2"])
.join("tspan")
.attr("x", 0)
.attr("dy", (d, i) => i * 14)
.text(d => d);
Более гибкий вариант — использование foreignObject,
позволяющего вставлять HTML в SVG-контекст.
const tooltip = svg.append("foreignObject")
.attr("width", 200)
.attr("height", 100)
.style("visibility", "hidden")
.append("xhtml:div")
.style("background", "rgba(0,0,0,0.8)")
.style("color", "#fff")
.style("padding", "8px")
.style("border-radius", "4px");
Обновление позиции:
.on("mousemove", function(event) {
const [x, y] = d3.pointer(event);
tooltip
.attr("x", x + 10)
.attr("y", y + 10);
});
Этот подход объединяет:
Однако поддержка foreignObject в разных окружениях может
отличаться, особенно в старых браузерах и некоторых рендерерах.
div-тултип:
SVG-тултип:
При работе с тултипами важно учитывать:
event.pageX / pageY — координаты окнаevent.clientX / clientY — координаты viewportd3.pointer(event) — координаты SVG-сценыПри использовании масштабирования:
const transform = d3.zoomTransform(svg.node());
const [x, y] = transform.invert(d3.pointer(event));
Это позволяет корректно позиционировать тултип в трансформированных системах координат.
При частых mousemove событиях критично:
.style() вызововrequestAnimationFrame для сглаживанияОптимизированный шаблон:
let tooltipX = 0;
let tooltipY = 0;
function renderTooltip() {
tooltip
.style("left", tooltipX + "px")
.style("top", tooltipY + "px");
}
.on("mousemove", function(event) {
tooltipX = event.pageX + 10;
tooltipY = event.pageY + 10;
requestAnimationFrame(renderTooltip);
});
В сложных визуализациях на D3.js часто применяется централизованный тултип:
Это снижает нагрузку и упрощает управление слоями отображения.
При использовании d3.zoom важно:
Типичная проблема — рассинхронизация при быстром движении курсора на масштабированной сцене.
Для HTML-тултипов ключевые параметры:
Пример:
.tooltip {
transition: opacity 0.15s ease;
max-width: 220px;
line-height: 1.4;
}
SVG-стилизация ограничена атрибутами fill,
font-size, font-family, что делает HTML-подход
предпочтительным для сложных интерфейсов.
При наведении на элементы, расположенные у границ контейнера:
Логика:
window.innerWidthЭто особенно важно при плотных scatterplot-визуализациях.