d3.zoom(): основы

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

Система зума опирается на три ключевых компонента:

  • масштаб (scale)
  • смещение (translate)
  • события взаимодействия (zoom events)

Каждое взаимодействие пользователя преобразуется в объект трансформации:

transform = { k, x, y }

где:

  • k — коэффициент масштабирования
  • x, y — смещение по осям

Начальное состояние задаётся через:

const zoom = d3.zoom();

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

d3.select("svg")
  .call(zoom);

Обработчик событий zoom

Основной механизм работы строится вокруг события zoom, которое вызывается при любом изменении трансформации:

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

Объект event.transform содержит актуальное состояние сцены. Применение трансформации к группе <g> позволяет перемещать и масштабировать всю визуализацию как единое целое.

d3.zoomIdentity и начальное состояние

Начальная трансформация представляется как:

d3.zoomIdentity

Она эквивалентна:

k = 1, x = 0, y = 0

Принудительная установка начального масштаба выполняется через:

svg.call(zoom.transform, d3.zoomIdentity);

Или с изменёнными параметрами:

svg.call(zoom.transform, d3.zoomIdentity.translate(100, 50).scale(2));

Масштабирование колесиком мыши

По умолчанию d3.zoom() обрабатывает событие wheel. Внутренний алгоритм:

  1. фиксирует позицию курсора
  2. вычисляет новый коэффициент масштаба
  3. корректирует смещение так, чтобы точка под курсором оставалась фиксированной

Управление диапазоном масштабирования задаётся через:

zoom.scaleExtent([1, 8]);

Минимальное значение предотвращает “отдаление” сцены до бесконечности, максимальное ограничивает детализацию.

Ограничение области перемещения

Перемещение сцены регулируется через:

zoom.translateExtent([
  [0, 0],
  [width, height]
]);

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

Отдельно существует extent, задающий зону реагирования:

zoom.extent([
  [0, 0],
  [width, height]
]);

Программное управление масштабом

Изменение состояния без пользовательского ввода выполняется через вызов transform:

svg.transition()
  .duration(750)
  .call(zoom.transform, d3.zoomIdentity.scale(3).translate(50, 20));

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

Привязка zoom к контейнеру <g>

Типичная структура SVG:

<svg>
  <g class="viewport">
    <!-- графические элементы -->
  </g>
</svg>

Применение transform:

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

Такой подход отделяет координатную систему сцены от внешнего SVG-контейнера.

Работа с масштабом и координатами

Любое изменение масштаба влияет на интерпретацию координат. Для обратного преобразования используется:

const inverted = event.transform.invert([x, y]);

Это позволяет сопоставлять экранные координаты с координатами сцены.

Математически преобразование описывается:

x' = kx + tx
y' = ky + ty

где:

  • k — масштаб
  • tx, ty — сдвиг

События и структура zoom event

Каждое событие zoom содержит:

  • event.transform
  • event.sourceEvent
  • event.type

Типы источников:

  • wheel — колесо мыши
  • mousemove — перетаскивание
  • touchstart / touchmove — сенсорные жесты
  • programmatic — вызовы через API

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

Ограничение чувствительности и фильтрация событий

Фильтрация событий выполняется через:

zoom.filter((event) => {
  return !event.ctrlKey;
});

Это позволяет отключить масштабирование при определённых модификаторах клавиатуры.

Дополнительно можно ограничивать поведение:

zoom.scaleExtent([1, 5]).filter(event => event.type !== "dblclick");

Управление скоростью масштабирования

Параметр scaleExtent влияет на диапазон, но не на скорость. Для изменения чувствительности используется:

zoom.scaleBy(selection, factor);

или:

zoom.scaleTo(selection, k);

Эти методы изменяют масштаб относительно текущего состояния.

Синхронизация нескольких элементов

Один экземпляр zoom может применяться к нескольким контейнерам:

svg1.call(zoom);
svg2.call(zoom);

Общее состояние поддерживается через ручное распространение transform:

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

Взаимодействие с осями и шкалами

При использовании шкал d3.scaleLinear и других масштабируемых функций необходимо учитывать трансформацию:

const newX = event.transform.rescaleX(xScale);

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

Применение:

xAxis.call(d3.axisBottom(event.transform.rescaleX(xScale)));

Координатные особенности и матричная модель

Внутренне transform представляется как матрица:

[k, 0, 0,
 0, k, 0,
 x, y, 1]

Это обеспечивает совместимость с SVG transform-цепочками и позволяет комбинировать масштабирование с поворотами и смещениями.

Поведение при мультитач-жестах

На сенсорных устройствах zoom автоматически интерпретирует:

  • pinch-to-zoom → изменение k
  • drag → изменение x, y

При этом сохраняется центр между двумя пальцами как точка фокуса масштабирования.

Прерывание и восстановление состояния

Сброс состояния выполняется через:

svg.call(zoom.transform, d3.zoomIdentity);

Частичное восстановление:

svg.call(zoom.translateBy, 50, 50);

или

svg.call(zoom.scaleBy, 1.2);

Поведение при высокой плотности данных

При большом количестве элементов zoom часто комбинируется с:

  • canvas-рендерингом
  • виртуализацией объектов
  • агрегацией данных

В таких сценариях трансформация применяется не к DOM-элементам, а к контексту отрисовки.

Типичные ошибки интеграции

Наиболее частые проблемы:

  • одновременное применение transform в нескольких местах
  • конфликт CSS transform и SVG transform
  • отсутствие группировки <g>
  • несинхронизированные шкалы

Корректная архитектура всегда предполагает единый источник трансформации — event.transform.

Итоговая структура обработки

Типовой цикл работы zoom:

  1. пользователь инициирует событие
  2. d3.zoom рассчитывает transform
  3. генерируется zoom event
  4. обновляется визуальный контейнер
  5. при необходимости пересчитываются шкалы и оси
  6. состояние сохраняется внутри zoom behavior