Мышиные события: click, mouseover, mouseout, mousemove

Обработка взаимодействия пользователя с визуализацией в D3.js строится вокруг системы событий браузера, расширенной возможностями привязки обработчиков к DOM-элементам через selection.on. Мышиные события формируют основу интерактивных графиков: они позволяют реагировать на наведение, клики, перемещение курсора и уход указателя с элемента.

Привязка обработчиков событий

В D3.js события подключаются непосредственно к выбранным элементам через метод on:

d3.selectAll("circle")
  .on("click", function(event, d) {
    console.log("Клик по элементу:", d);
  });

Первый аргумент функции — объект события event, второй — связанные данные d. В современных версиях D3 (v6+) доступ к событию осуществляется исключительно через параметр функции, без использования глобального d3.event.

Удаление обработчика выполняется передачей null:

d3.selectAll("circle")
  .on("click", null);

Событие click

Событие click возникает при полном цикле нажатия и отпускания кнопки мыши над одним и тем же элементом. В контексте визуализаций D3 оно используется для выбора элементов, раскрытия деталей и переключения состояний.

Типичный сценарий — изменение состояния элемента:

d3.selectAll("rect")
  .on("click", function(event, d) {
    const el = d3.select(this);
    const active = el.classed("active");
    el.classed("active", !active);
  });

Здесь this указывает на DOM-элемент, на который навешан обработчик. Через classed изменяется его визуальное состояние.

Событие mouseover

mouseover срабатывает при входе курсора в область элемента. Оно полезно для подсветки, отображения подсказок и динамического выделения данных.

d3.selectAll("circle")
  .on("mouseover", function(event, d) {
    d3.select(this)
      .attr("r", 8);
  });

Важно учитывать, что mouseover всплывает по DOM-дереву, поэтому при вложенных элементах может вызываться несколько раз при переходе между дочерними узлами.

Для более предсказуемого поведения часто используется логика фильтрации или пара mouseenter, однако в D3 чаще остаются именно mouseover и ручной контроль поведения.

Событие mouseout

mouseout фиксирует момент выхода курсора из элемента. Оно логически дополняет mouseover и используется для возврата визуального состояния.

d3.selectAll("circle")
  .on("mouseout", function(event, d) {
    d3.select(this)
      .attr("r", 4);
  });

При работе с вложенными структурами важно учитывать всплытие: mouseout может срабатывать при переходе на дочерние элементы. Это приводит к визуальным «дёрганиям», если не контролировать структуру SVG.

Событие mousemove

mousemove срабатывает при каждом перемещении курсора внутри элемента. Это одно из самых часто используемых событий в интерактивных визуализациях, но и одно из самых «тяжёлых» с точки зрения производительности.

d3.selectAll("rect")
  .on("mousemove", function(event, d) {
    console.log(event.clientX, event.clientY);
  });

Для получения координат относительно SVG-элемента используется d3.pointer:

d3.select("svg")
  .on("mousemove", function(event) {
    const [x, y] = d3.pointer(event);
    console.log(x, y);
  });

d3.pointer возвращает координаты в системе координат выбранного контейнера, что критично для построения интерактивных графиков, где требуется точное позиционирование элементов.

Координаты курсора и системы отсчёта

В D3.js различают несколько систем координат:

  • event.clientX / clientY — координаты относительно окна браузера
  • event.pageX / pageY — координаты относительно документа
  • d3.pointer(event) — координаты относительно SVG или контейнера

Использование d3.pointer является стандартом при работе с визуализациями, так как SVG-координаты не совпадают с экранными.

svg.on("mousemove", function(event) {
  const [x, y] = d3.pointer(event);
  d3.select("#marker")
    .attr("cx", x)
    .attr("cy", y);
});

Контекст this и работа с selection

В обработчиках событий D3 this всегда ссылается на DOM-элемент, к которому привязан обработчик. Это позволяет быстро модифицировать текущий узел без дополнительного поиска.

d3.selectAll("circle")
  .on("mouseover", function(event, d) {
    d3.select(this)
      .style("fill", "orange");
  });

При использовании стрелочных функций this теряет привязку, что часто приводит к ошибкам:

d3.selectAll("circle")
  .on("mouseover", (event, d) => {
    // this не указывает на элемент
  });

Поэтому в D3 традиционно используются обычные функции.

Передача данных в событиях

D3 связывает данные с DOM-элементами через data(), и эти данные автоматически доступны в обработчиках:

d3.selectAll("circle")
  .data(dataset)
  .join("circle")
  .on("click", function(event, d) {
    console.log(d.value);
  });

Здесь d представляет объект данных, ассоциированный с конкретным элементом визуализации.

Всплытие событий и управление поведением

Мышиные события в DOM всплывают от дочерних элементов к родителям. В SVG-визуализациях это часто приводит к нежелательным эффектам при сложной структуре слоёв.

Для остановки всплытия используется стандартный механизм браузера:

d3.selectAll("circle")
  .on("click", function(event, d) {
    event.stopPropagation();
  });

Это позволяет изолировать обработку событий внутри конкретного элемента.

Сочетание mouseover и mouseout для интерактивных эффектов

Комбинация mouseover и mouseout применяется для создания динамических эффектов подсветки:

d3.selectAll("rect")
  .on("mouseover", function(event, d) {
    d3.select(this).style("opacity", 0.7);
  })
  .on("mouseout", function(event, d) {
    d3.select(this).style("opacity", 1);
  });

Такая модель часто используется в столбчатых диаграммах, тепловых картах и сетевых графах.

Mousemove как основа интерактивных инструментов

Событие mousemove лежит в основе сложных взаимодействий: tooltip-индикаторов, перетаскивания, динамического масштабирования.

Пример позиционирования всплывающей подсказки:

svg.on("mousemove", function(event) {
  const [x, y] = d3.pointer(event);

  d3.select("#tooltip")
    .style("left", `${x + 10}px`)
    .style("top", `${y + 10}px`);
});

При этом важно учитывать производительность: обработчик вызывается десятки раз в секунду, поэтому внутри него избегают тяжёлых вычислений и DOM-операций.

Различия поведения событий в SVG и HTML

В SVG-контексте события работают на графических примитивах (circle, rect, path), тогда как в HTML — на блочных и строчных элементах. В D3 оба контекста обрабатываются одинаково через selection.on, но координатные системы различаются.

SVG требует явного преобразования координат, тогда как HTML чаще использует offsetX / offsetY.

Типичные ошибки при работе с мышиными событиями

Одной из распространённых проблем является потеря контекста элемента при использовании стрелочных функций, что приводит к невозможности обратиться к текущему узлу через this.

Ещё одна ошибка — попытка использовать глобальное событие вместо передачи event в аргументах, что приводит к неработающему коду в современных версиях D3.

Также часто игнорируется влияние всплытия событий, из-за чего обработчики срабатывают несколько раз при одном физическом действии пользователя.

Координация нескольких событий на одном элементе

Элементы визуализации часто одновременно обрабатывают несколько типов событий:

d3.selectAll("circle")
  .on("mouseover", highlight)
  .on("mouseout", reset)
  .on("click", select);

Такой подход позволяет разделить логику взаимодействия на независимые функции, каждая из которых отвечает за своё состояние элемента.

Динамическое изменение визуализации через события

Мышиные события часто используются для перестройки визуализации в реальном времени:

d3.selectAll("rect")
  .on("click", function(event, d) {
    d3.selectAll("rect")
      .attr("height", h => h * 1.1);
  });

Хотя такой подход прост, он требует осторожности при масштабировании данных и сложных графиках, так как каждое событие может инициировать перерасчёт всей сцены.