Добавление обработчиков событий: .on()

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

Общий синтаксис метода:

selection.on(type, listener[, options])
  • type — строка события (например, "click", "mouseover", "input")
  • listener — функция обработчика события
  • options — необязательные параметры (например, управление фазой capture)

Привязка события происходит к каждому элементу выборки:

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

Каждый элемент, входящий в выборку, получает собственный обработчик, который получает доступ к:

  • DOM-событию (event)
  • связанным данным (d)

Контекст данных и доступ к элементам

D3.js связывает данные с DOM-элементами через механизм data join. В обработчиках .on() это становится основным способом взаимодействия с визуализацией.

d3.selectAll("rect")
  .data([10, 20, 30])
  .on("mouseover", function(event, d) {
    d3.select(this)
      .attr("fill", "orange");

    console.log("Значение:", d);
  });

Здесь:

  • this указывает на текущий DOM-элемент
  • d — значение, привязанное к элементу
  • event содержит информацию о событии (координаты, тип и др.)

Поддерживаемые события и их типы

Метод .on() поддерживает любые DOM-события:

Мышь

.on("click", handler)
.on("dblclick", handler)
.on("mouseover", handler)
.on("mouseout", handler)
.on("mousemove", handler)

Клавиатура

.on("keydown", handler)
.on("keyup", handler)

Формы и ввод

.on("input", handler)
.on("change", handler)

Сенсорные события

.on("touchstart", handler)
.on("touchmove", handler)
.on("touchend", handler)

D3 не ограничивает список событий и работает с любыми стандартными DOM event types.

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

В D3 v6+ обработчик получает event как первый аргумент, что отличается от старых версий, где использовался глобальный d3.event.

d3.select("button")
  .on("click", function(event) {
    console.log(event.type);
  });

Данные передаются вторым параметром:

d3.selectAll("li")
  .data(["A", "B", "C"])
  .on("click", function(event, d) {
    console.log(d);
  });

Использование this внутри обработчиков

Контекст this сохраняется и указывает на текущий элемент DOM:

d3.selectAll("path")
  .on("mouseover", function(event, d) {
    d3.select(this)
      .attr("stroke-width", 3);
  });

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

Добавление и замена обработчиков

При повторном вызове .on() с тем же типом события предыдущий обработчик заменяется:

d3.select("div")
  .on("click", function() {
    console.log("Первый обработчик");
  });

d3.select("div")
  .on("click", function() {
    console.log("Второй обработчик");
  });

После выполнения активен только второй обработчик.

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

Удаление осуществляется передачей null:

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

Это полностью снимает обработчик с выбранных элементов.

Именованные события и пространства имён

D3 поддерживает namespacing событий через точку:

d3.select("svg")
  .on("click.zoom", zoomHandler)
  .on("click.tooltip", tooltipHandler);

Удаление по пространству имён:

d3.select("svg")
  .on("click.zoom", null);

Это позволяет управлять несколькими логическими слоями поведения независимо.

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

События в D3 следуют стандартной модели DOM: capture → target → bubble.

Остановка всплытия:

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

Отмена стандартного поведения:

d3.select("a")
  .on("click", function(event) {
    event.preventDefault();
  });

Работа с динамически создаваемыми элементами

При использовании enter/update/exit-паттерна обработчики часто назначаются после создания элементов:

const circles = d3.select("svg")
  .selectAll("circle")
  .data([5, 10, 15])
  .enter()
  .append("circle");

circles
  .on("click", function(event, d) {
    d3.select(this).attr("r", d);
  });

Важно, что обработчик привязывается к результату enter().

Делегирование событий в D3

Хотя D3 не использует делегирование в классическом jQuery-стиле автоматически, его можно реализовать вручную через общий контейнер:

d3.select("svg")
  .on("click", function(event) {
    const target = event.target;
    if (target.tagName === "circle") {
      console.log("Клик по circle");
    }
  });

Такой подход полезен при большом количестве элементов.

Обновление поведения при изменении данных

При повторной привязке данных обработчики остаются, если не происходит пересоздания элементов:

const selection = d3.selectAll("rect")
  .data([1, 2, 3])
  .on("click", handler);

selection.data([4, 5, 6]); // обработчики сохраняются

Если элементы пересоздаются через enter().append(), обработчики нужно назначать заново.

События и масштабируемость визуализаций

При большом количестве элементов использование .on() на каждом узле может создавать нагрузку. В таких случаях применяются стратегии:

  • делегирование событий через контейнер
  • минимизация количества обработчиков
  • объединение логики в один обработчик

Пример централизованного управления:

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

Интеграция с анимациями и переходами

Обработчики событий часто комбинируются с transition():

d3.selectAll("circle")
  .on("mouseover", function() {
    d3.select(this)
      .transition()
      .duration(300)
      .attr("r", 20);
  })
  .on("mouseout", function() {
    d3.select(this)
      .transition()
      .duration(300)
      .attr("r", 10);
  });

События становятся триггерами для анимаций, создавая интерактивные визуальные реакции.

Особенности обработки нескольких событий

Один элемент может иметь несколько независимых обработчиков:

d3.select("rect")
  .on("mouseenter", handlerA)
  .on("mouseleave", handlerB)
  .on("click", handlerC);

Каждое событие изолировано и не конфликтует с другими.

Условия внутри обработчиков

Часто обработчики содержат условную логику, зависящую от данных:

d3.selectAll("circle")
  .on("click", function(event, d) {
    if (d > 10) {
      d3.select(this).attr("fill", "red");
    } else {
      d3.select(this).attr("fill", "blue");
    }
  });

Такой подход позволяет реализовывать интерактивную фильтрацию и визуальные состояния.

Передача дополнительных параметров через замыкания

Обработчики .on() часто используют замыкания для доступа к внешним переменным:

const scale = 2;

d3.selectAll("circle")
  .on("click", function(event, d) {
    d3.select(this)
      .attr("r", d * scale);
  });

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

Синхронизация событий между элементами

Событие на одном элементе может влиять на другие элементы выборки:

d3.selectAll("circle")
  .on("mouseover", function(event, d) {
    d3.selectAll("circle")
      .attr("opacity", 0.3);

    d3.select(this)
      .attr("opacity", 1);
  });

Такой паттерн часто используется для подсветки связей и сравнений в данных.