Объект события и d3.pointer()

В модели обработки взаимодействий в браузере каждое пользовательское действие — движение мыши, клик, касание экрана, прокрутка — формируется как объект события. В D3.js этот объект выступает центральным источником информации о контексте взаимодействия: тип действия, координаты указателя, целевой элемент, состояние модификаторов клавиатуры.

Современные версии D3 (v6+) опираются на стандартный механизм событий браузера, где объект события передаётся напрямую в обработчик. Это устранило необходимость в глобальной переменной d3.event, использовавшейся в более ранних версиях.

Основные свойства объекта события:

  • type — тип события (click, mousemove, pointermove и т.д.)
  • target — DOM-элемент, на котором произошло событие
  • currentTarget — элемент, к которому привязан обработчик
  • clientX, clientY — координаты относительно окна браузера
  • pageX, pageY — координаты относительно документа
  • screenX, screenY — координаты относительно экрана
  • altKey, ctrlKey, shiftKey, metaKey — состояние клавиш-модификаторов
  • button — нажатая кнопка мыши

В контексте визуализаций D3 особое значение имеют координаты указателя, поскольку они используются для сопоставления действий пользователя с геометрией SVG или Canvas-сцены.


Передача события в обработчики D3

Обработчики событий в D3 задаются через .on():

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

Первый аргумент функции обработчика — объект события. Второй важный контекст — this, указывающий на DOM-элемент, к которому привязан обработчик. Внутри D3 часто используется event.currentTarget, так как он более явно отражает элемент привязки.


Координатные системы и проблема интерпретации позиции

Одной из ключевых сложностей интерактивной визуализации является преобразование координат события в систему координат графика.

Событие мыши предоставляет координаты в экранной системе (viewport), тогда как элементы SVG и Canvas могут иметь собственную локальную систему координат, зависящую от:

  • viewBox SVG
  • трансформаций (translate, scale, rotate)
  • вложенности групп <g>
  • масштабирования через zoom-поведение D3

Прямое использование event.clientX и event.clientY приводит к некорректному позиционированию в большинстве случаев.


d3.pointer(): базовый механизм привязки координат

Функция d3.pointer() предназначена для преобразования координат события в локальную систему координат заданного контейнера.

Сигнатура:

d3.pointer(event, target)
  • event — объект события браузера
  • target — DOM-элемент, относительно которого вычисляются координаты

Возвращаемое значение — массив из двух чисел:

[x, y]

где x и y — координаты указателя в системе координат целевого элемента.


Принцип работы d3.pointer()

Внутренне d3.pointer() выполняет цепочку преобразований:

  1. Берёт координаты события (clientX, clientY)
  2. Вычитает положение целевого элемента на странице
  3. Учитывает трансформации SVG (через CTM — current transformation matrix)
  4. Возвращает результат в локальной системе координат

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


Использование d3.pointer() в SVG

Типичный сценарий — обработка клика внутри SVG-области:

const svg = d3.select("svg");

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

  console.log("SVG координаты:", x, y);
});

Здесь this указывает на SVG-элемент, что делает координаты согласованными с его системой координат.


Работа с группами и трансформациями

SVG часто содержит вложенные группы <g> с трансформациями:

<svg>
  <g transform="translate(50,50) scale(2)">
    <circle cx="0" cy="0" r="10"></circle>
  </g>
</svg>

При клике внутри такого элемента прямое использование экранных координат не даёт корректного результата. d3.pointer() учитывает матрицу трансформации и возвращает координаты уже в пространстве группы.

d3.select("g")
  .on("click", function(event) {
    const coords = d3.pointer(event, this);
    console.log(coords);
  });

d3.pointer() и масштабирование (zoom behavior)

При использовании d3.zoom() происходит динамическое изменение трансформации контейнера. Это добавляет дополнительный слой сложности, так как координаты события должны учитываться относительно текущего масштаба.

const zoom = d3.zoom()
  .on("zoom", (event) => {
    g.attr("transform", event.transform);
  });

svg.call(zoom);

При обработке кликов внутри масштабируемой группы d3.pointer() автоматически учитывает текущую матрицу трансформации, возвращая координаты в уже преобразованном пространстве.


d3.pointer() и Canvas

В Canvas нет встроенной системы координат элементов, поэтому преобразование выполняется относительно bounding box элемента:

const canvas = d3.select("canvas");

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

Здесь координаты соответствуют пикселям внутри canvas-элемента, без дополнительной иерархии DOM.


Сравнение d3.pointer() и ручных вычислений

Без d3.pointer() преобразование координат требует ручной обработки:

const rect = svg.node().getBoundingClientRect();

const x = event.clientX - rect.left;
const y = event.clientY - rect.top;

Однако этот подход не учитывает SVG-трансформации, что делает его ограниченным.

d3.pointer() расширяет эту модель, добавляя поддержку:

  • SVG transform matrix
  • вложенных групп
  • zoom/pan поведения
  • корректной работы с разными контейнерами

Поведение с touch-событиями

В современных браузерах pointer events унифицируют мышь и касания. Однако при работе с touch или legacy mouse events d3.pointer() одинаково корректно обрабатывает координаты.

selection.on("touchmove", function(event) {
  const [x, y] = d3.pointer(event, this);
});

При множественных касаниях используется первый touch-point, если не задан иной режим обработки.


Использование d3.pointer() с выборками

Часто координаты требуются внутри цепочки D3:

d3.selectAll("circle")
  .on("mousemove", function(event, d) {
    const [x, y] = d3.pointer(event, this);
  });

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


Особенности контекста target

Второй аргумент d3.pointer(event, target) критически влияет на результат. Разные значения target дают разные системы координат:

  • this (DOM-элемент) — локальные координаты элемента
  • svg.node() — координаты всей SVG-сцены
  • конкретная группа <g> — координаты внутри группы

Пример:

const [x1, y1] = d3.pointer(event, svg.node());
const [x2, y2] = d3.pointer(event, g.node());

Эти координаты будут различаться, так как относятся к разным системам.


Взаимодействие с d3.event (устаревший подход)

В старых версиях D3 использовалась глобальная переменная:

d3.event

Она содержала текущий объект события и использовалась вместе с:

d3.mouse(node)

Современная модель:

  • d3.event удалена
  • d3.mouse() заменена на d3.pointer()
  • событие передаётся явно в обработчик

Это делает систему более предсказуемой и совместимой с нативным DOM API.


Типичные ошибки при работе с d3.pointer()

  • использование неправильного target (например, window вместо SVG-элемента)
  • игнорирование трансформаций групп
  • попытка использовать координаты события без преобразования
  • смешивание координат разных систем (screen vs SVG)

Корректная работа требует строгого разделения:

  • координаты события (browser space)
  • координаты элемента (local SVG/Canvas space)

Практическая модель интерпретации координат

В интерактивных визуализациях координаты, полученные через d3.pointer(), становятся базой для:

  • построения tooltip
  • выделения элементов
  • динамического рисования
  • обработки drag-событий
  • вычисления расстояний до узлов графа

Каждый из этих сценариев опирается на единый принцип: преобразование глобального события в локальное пространство визуализации.